# KinesteX SDK Documentation — Full Text > Complete KinesteX SDK documentation. Source: https://www.kinestex.com/docs This file concatenates every documentation section as Markdown for AI agents and LLMs. Code samples are included for all supported platforms (Swift, Kotlin, React Native, Flutter, HTML/JavaScript, React TypeScript). --- ## Getting Started KinesteX offers a powerful motion tracking SDK that seamlessly integrates into your platform. Choose between ready-made workouts, plans, challenges, AI assessments, or create custom experiences with our advanced motion tracking. ### Overview Our white-label solution supports any camera-enabled device across Android, iOS, web, and gaming platforms, with SDKs for React Native, Flutter, Kotlin, Swift, Java, PWA, and JavaScript for quick integration. KinesteX's real-time AI motion tracking boosts engagement, retention, and revenue while providing valuable data insights to create personalized, growth-driving experiences. #### Plug and Play Integration Pre-built experiences ready to integrate. Customize colors, fonts, and certain UI/UX elements while we handle the core workout experience, motion tracking, and user flow. ##### Complete UX (Main) Full experience with personalized plan selection, user survey, and AI-generated workout schedules. ##### Plan View Display a specific workout plan with schedule and multiple workouts. ##### Workout View Launch directly into a specific workout by name or ID. ##### Challenge View Gamified exercise challenges with leaderboards and rep tracking. ##### Leaderboard View Display real-time leaderboards for challenge exercises as a standalone view. ##### AI Experiences Interactive fitness games like Balloon Pop, Color Chase, and Alien Squat Shooter. ##### Personalized Plan AI-generated workout plans tailored to user biometrics, fitness level, and assessment results. ##### Admin Workout Editor Embedded view for creating and managing workouts and exercises. Receive events for workout/exercise creation, selection, and updates. Flutter only. #### Custom Integration Build everything yourself with full control over UI/UX. Use our Content APIs to fetch workout data and the Camera Component for real-time motion analysis. ##### Custom Workout Create personalized workout sequences with custom exercises, reps, durations, and rest periods. Full control over exercise order and timing. ##### Camera Component Access raw pose analysis data to build your own custom workout UI. Receive real-time rep counts, form feedback, and body tracking data. ##### Content Fetching APIs Fetch workout plans, exercises, and content data via REST APIs. Build your own content browsing and workout selection experience. ### Requirements **Platform Versions:** • iOS 14.0+ • Android API 26+ • Web browser support (Chrome, Safari, Firefox, Edge) **Prerequisites:** • API key from KinesteX • Camera permissions • Internet connection ### Get API Key To get demo access and your API key, fill out the contact form on our website. [Contact Us](/#contact-form) --- ## Installation Follow these steps to install the KinesteX SDK in your project. **Step 4: iOS 15+ Device Orientation Fix (Required)** — React Native react-native-webview doesn't support iOS 15+ motion permissions by default. Apply this patch to enable device orientation tracking: _4.1: Install patch-package_ ```bash npm install patch-package postinstall-postinstall --save-dev ``` _4.2: Add postinstall script to package.json_ ```json { "scripts": { "postinstall": "patch-package" } } ``` _4.3: Patch RNCWebViewImpl.m_ ```objectivec // Edit: node_modules/react-native-webview/apple/RNCWebViewImpl.m // Find requestMediaCapturePermissionForOrigin (line ~1312) // Add this method after it: - (void)webView:(WKWebView *)webView requestDeviceOrientationAndMotionPermissionForOrigin:(WKSecurityOrigin *)origin initiatedByFrame:(WKFrameInfo *)frame decisionHandler:(void (^)(WKPermissionDecision))decisionHandler { (void)webView; (void)origin; (void)frame; decisionHandler(WKPermissionDecisionGrant); } ``` _4.4: Save the patch_ ```bash npx patch-package react-native-webview ``` **Step 5: Expo Android Camera Fix** — React Native For Expo on Android, request app-level camera permission before displaying KinesteX: _5.1: Install expo-camera_ ```bash npx expo install expo-camera ``` _5.2: Configure app.json_ ```json { "expo": { "android": { "permissions": ["android.permission.CAMERA"] }, "ios": { "infoPlist": { "NSCameraUsageDescription": "For AI Motion Tracking" } } } } ``` _5.3: Request permission before showing KinesteX_ ```typescript import { Camera } from 'expo-camera'; import { useState, useEffect } from 'react'; const [hasPermission, setHasPermission] = useState(false); useEffect(() => { (async () => { const { status } = await Camera.requestCameraPermissionsAsync(); setHasPermission(status === 'granted'); })(); }, []); // Only render KinesteX when hasPermission is true ``` ### Step 1: Add Dependencies Add the KinesteX SDK to your project using your platform's package manager. **Install SDK** _Swift (iOS)_ ```swift 1. In Xcode: File > Add Package Dependencies... 2. Enter URL: https://github.com/KinesteX/KinesteX-AI-Kit.git 3. Click "Add Package" ``` _Kotlin (Android)_ ```kotlin // 1. Add JitPack repository in settings.gradle.kts dependencyResolutionManagement { repositories { maven { url = uri("https://jitpack.io") } } } // 2. Add dependency in app/build.gradle.kts dependencies { implementation("com.github.KinesteX:KinesteX-SDK-Kotlin:2.0.0") } // 3. Sync project with Gradle files ``` _React Native_ ```jsx // 1. Install packages npm install kinestex-sdk-react-native react-native-webview // 2. Install iOS dependencies cd ios && pod install && cd .. // For Expo projects: npx expo install react-native-webview ``` _Flutter_ ```dart # 1. Add to pubspec.yaml dependencies: kinestex_sdk_flutter: ^latest permission_handler: ^11.3.1 # 2. Install packages flutter pub get # 3. Install iOS dependencies cd ios && pod install && cd .. ``` _HTML / JavaScript_ ```html ``` _React (TypeScript)_ ```tsx npm i kinestex-sdk-react-ts ``` ### Step 2: Configure Permissions Add the required permissions for camera and motion sensors. **Required Permissions** _Swift (iOS)_ ```swift NSCameraUsageDescription Camera access is required for AI-powered workout tracking NSMotionUsageDescription Motion sensors help position your device correctly for workouts ``` _Kotlin (Android)_ ```kotlin ``` _React Native_ ```jsx NSCameraUsageDescription Camera access is required for AI-powered workout tracking NSMotionUsageDescription Motion sensors help position your device correctly for workouts ``` _Flutter_ ```dart NSCameraUsageDescription Camera access is required for AI-powered workout tracking NSMotionUsageDescription Motion sensors help position your device correctly for workouts ``` _HTML / JavaScript_ ```html ``` _React (TypeScript)_ ```tsx Browser will prompt users for camera access automatically. No additional permissions are needed. ``` ### Step 3: Import the SDK Import the KinesteX SDK in your code to start using it. **Import Statement** _Swift (iOS)_ ```swift import KinesteXAIKit ``` _Kotlin (Android)_ ```kotlin import com.kinestex.kinestexsdkkotlin.KinesteXSDK ``` _React Native_ ```jsx import KinestexSDK from 'kinestex-sdk-react-native'; import { IntegrationOption, IPostData, PlanCategory } from 'kinestex-sdk-react-native'; ``` _Flutter_ ```dart import 'package:kinestex_sdk_flutter/kinestex_sdk_flutter.dart'; ``` _HTML / JavaScript_ ```html // Define the iframe element and create a URL to be later used to load the iframe const webView = document.getElementById('webView'); const srcURL = 'https://ai.kinestex.com'; ``` _React (TypeScript)_ ```tsx import { IntegrationOption, KinesteXSDK, type IPostData, type KinesteXSDKCamera, } from "kinestex-sdk-react-ts"; ``` --- ## Configuration Initialize the SDK with your API credentials. **Step 1: Initialize the SDK** _Swift (iOS)_ ```swift import KinesteXAIKit @State var showKinesteX = false // Controls KinesteX SDK visibility @State var isLoading = false // Optional: Controls custom loading screen // Initialize with your API key, company name, and unique user ID let kinestex = KinesteXAIKit( apiKey: "YOUR_API_KEY", companyName: "YOUR_COMPANY", userId: "unique-user-id" ) // Optional: UserDetails to customize workout intensity let user = UserDetails( age: 20, height: 170, weight: 70, gender: .Male, lifestyle: .Active ) ``` _Kotlin (Android)_ ```kotlin import com.kinestex.kinestexsdkkotlin.KinesteXSDK // Initialize the SDK in your Application class as early as possible class MyApplication : Application() { override fun onCreate() { super.onCreate() KinesteXSDK.initialize( context = this, apiKey = "YOUR_API_KEY", companyName = "YOUR_COMPANY", userId = "unique-user-id" ) } } ``` _React Native_ ```jsx // 1. Create a reference to KinesteXSDK component const kinestexSDKRef = useRef(null); // 2. Create postData object to initialize KinesteX session const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'YOUR_USER_ID', // unique per user company: 'YOUR_COMPANY', style: { style: 'dark', // 'dark' or 'light' loadingBackgroundColor: '000000', // hex without # }, customParameters: { // language: 'es' // customize language }, // Optional: UserDetails for workout intensity age: 25, height: 180, // cm weight: 75, // kg gender: 'Male', lifestyle: Lifestyle.Active, }; // 3. Handle real-time messages from KinesteX const handleMessage = (type: string, data: { [key: string]: any }) => { switch (type) { case 'exit_kinestex': console.log('User exited'); setShowKinesteX(false); break; case 'plan_unlocked': console.log('Plan unlocked:', data); break; default: console.log('Message:', type, data); break; } }; ``` _Flutter_ ```dart import 'package:kinestex_sdk_flutter/kinestex_sdk_flutter.dart'; // Initialize the SDK in your main function as early as possible. await KinesteXAIFramework.initialize( apiKey: "YOUR_API_KEY", companyName: "YOUR_COMPANY", userId: "unique-user-id", ); ``` _HTML / JavaScript_ ```html // 1. Define the config data to be sent to the iframe const postData = { userId: "YOUR_USER_ID", // Unique identifier for the user company: "YOUR_COMPANY", // Your organization's name from kinestex admin dashboard key: "YOUR_API_KEY", // Your API Key from kinestex admin dashboard // Optional parameters for customization style: "dark", // dark or light theme // user related customization age: 30, // User's age (optional) height: 175, // User's height in cm (optional) weight: 70, // User's weight in kg (optional) gender: "Female", // Gender (optional) }; // 2. Sending the messages to the iframe will be done through postMessage API function sendMessage() { if (webView.contentWindow) { webView.contentWindow.postMessage(postData, srcURL); // post initial data to start session } else { setTimeout(() => { try { webView.contentWindow.postMessage(postData, srcURL); // post initial data to start session } catch { webView.contentWindow.postMessage(postData, srcURL); // retry sending message } }, 100); } } // 3. Whenever you want to display the view: webView.src = srcURL; // Update this URL for specific features webView.onload = () => { sendMessage(); }; // 4. Handle the loading confirmation received from the iframe to send the initial data once more (iOS specific verification) window.addEventListener("message", (event) => { if (event.origin !== "https://ai.kinestex.com") return; // prevent listening for messages from other sources for security const message = JSON.parse(event.data); if (message.type === 'kinestex_loaded') { sendMessage(); } }); ``` _React (TypeScript)_ ```tsx // 1. Create a reference to KinesteXSDK component const kinestexSDKRef = useRef(null); // 2. Create postData object to initialize KinesteX session const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'YOUR_USER_ID', // unique per user company: 'YOUR_COMPANY', style: { style: 'dark', // 'dark' or 'light' loadingBackgroundColor: '000000', // hex without # }, customParameters: { // language: 'es' // customize language }, // Optional: UserDetails for workout intensity age: 25, height: 180, // cm weight: 75, // kg gender: 'Male', lifestyle: Lifestyle.Active, }; // 3. Handle real-time messages from KinesteX const handleMessage = (type: string, data: { [key: string]: any }) => { switch (type) { case 'exit_kinestex': console.log('User exited'); setShowKinesteX(false); break; case 'plan_unlocked': console.log('Plan unlocked:', data); break; default: console.log('Message:', type, data); break; } }; ``` **Step 2: Register Application Class** — Kotlin (Android) Register your custom Application class in AndroidManifest.xml: _AndroidManifest.xml_ ```xml ``` **Step 3: Request Camera Permission** — Kotlin (Android) Before a user starts a workout, request camera permission at the app level. Your activity or fragment must implement the PermissionHandler interface: _MainActivity with PermissionHandler_ ```kotlin class MainActivity : AppCompatActivity(), PermissionHandler { // Initialize the webview private var kinesteXWebView: GenericWebView? = null // Optional: Pass user details to adjust exercises and estimate calories // Note: User details are only used on the device for customization during the session private var userDetails = UserDetails( age = 20, height = 170, weight = 180, gender = Gender.MALE, lifestyle = Lifestyle.ACTIVE ) // Register permission launcher private val requestPermissionLauncher = registerForActivityResult( ActivityResultContracts.RequestPermission() ) { isGranted: Boolean -> // Pass permission result to KinesteX webview kinesteXWebView?.handlePermissionResult(isGranted) } // Override to display system dialog for camera access override fun requestCameraPermission() { requestPermissionLauncher.launch(Manifest.permission.CAMERA) } } ``` **Step 4: Handle SDK Messages** — Kotlin (Android) Implement a callback function to handle messages from the SDK: _Message Handler_ ```kotlin private fun handleWebViewMessage(message: WebViewMessage) { when (message) { is WebViewMessage.ExitKinestex -> { // Dismiss KinesteX view when user clicks exit button } // Handle other messages else -> { Log.d("KinesteX", message.toString()) } } } ``` **Step 2: Request Camera Permission** — Flutter Request camera permission before launching KinesteX. Ensure you've added the necessary permissions in AndroidManifest.xml and Info.plist. Add the following to your iOS Podfile: _2.1: ios/Podfile_ ```ruby post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) target.build_configurations.each do |config| config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ '$(inherited)', ## dart: PermissionGroup.camera 'PERMISSION_CAMERA=1', ] end end end ``` _2.2: Request Permission in Dart_ ```dart void _checkCameraPermission() async { if (await Permission.camera.request() != PermissionStatus.granted) { _showCameraAccessDeniedAlert(); } } void _showCameraAccessDeniedAlert() { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text("Camera Permission Denied"), content: const Text("Camera access is required for this app to function properly."), actions: [ TextButton( child: const Text("OK"), onPressed: () { Navigator.of(context).pop(); }, ), ], ); }, ); } ``` **Step 3: Initialize on App Launch** — Flutter Initialize KinesteX in your main function for WebView warmup: _main.dart_ ```dart Future main() async { await KinesteXAIFramework.initialize( apiKey: YOUR_API_KEY, companyName: YOUR_COMPANY_NAME, userId: YOUR_USER_ID, ); runApp( const MaterialApp( home: MyHomePage(), ), ); } ``` **Step 4: Deinitialize on App Closure** — Flutter Call dispose when closing the app: _Dispose Method_ ```dart @override void dispose() { disposeKinesteXAIFramework(); super.dispose(); } ``` **Step 5: Setup Recommendations** — Flutter Use a ValueNotifier to manage KinesteX presentation and handle callback messages: _State Management & Message Handler_ ```dart import 'package:kinestex_sdk_flutter/kinestex_sdk_flutter.dart'; // Add a ValueNotifier to manage presentation ValueNotifier showKinesteX = ValueNotifier(false); // Handle callback messages from KinesteX void handleWebViewMessage(WebViewMessage message) { if (message is ExitKinestex) { setState(() { showKinesteX.value = false; }); } else { print("Message received: ${message.data}"); } } ``` --- ## Integration Options Choose the integration option that best fits your use case. Each option provides a different level of UI customization. ### Plug and Play Integration Pre-built experiences ready to integrate. Customize colors, fonts, and certain UI/UX elements while we handle the core workout experience, motion tracking, and user flow. #### Complete UX (Main) Display the full KinesteX experience with personalized workout plan selection based on category. Includes user survey, assessment, and personalized schedule generation. This is the easiest integration option. **Available Plan Categories:** | Plan Category | Key | |---------------|-----| | Strength | Strength | | Cardio | Cardio | | Weight Management | Weight Management | | Rehabilitation | Rehabilitation | | Custom | Custom | **Define Plan Category** First, define the plan category for personalized fitness goals: _Swift (iOS)_ ```swift // Plan category for personalized fitness goals @State private var planCategory: PlanCategory = .Cardio ``` _Kotlin (Android)_ ```kotlin // Plan category for personalized fitness goals private var planCategory = PlanCategory.Cardio ``` _React Native_ ```jsx // Plan category for personalized fitness goals const planCategory = PlanCategory.Cardio; // Strength, Cardio, etc.; ``` _Flutter_ ```dart // Plan category for personalized fitness goals PlanCategory planCategory = PlanCategory.Cardio; ``` _HTML / JavaScript_ ```html // Plan category for personalized fitness goals const planCategory = "Cardio"; ``` _React (TypeScript)_ ```tsx // Plan category for personalized fitness goals const planCategory = PlanCategory.Cardio; // Strength, Cardio, etc. ``` **Display Category View** Display the category-based view with real-time message handling: _Swift (iOS)_ ```swift kinestex.createCategoryView( planCategory: planCategory, user: user, // optional: can be nil isLoading: $isLoading, customParams: ["style": "dark"], // dark or light theme onMessageReceived: { message in switch message { case .kinestex_launched(let data): print("KinesteX Launched: \(data)") case .finished_workout(let data): print("Workout Finished: \(data)") case .exit_kinestex(let data): showKinesteX = false // Dismiss the view default: print("Received \(message)") break } } ) // OPTIONAL: Display loading screen during view initialization .overlay( Group { if showAnimation { Text("Aifying workouts...") .foregroundColor(.black) .font(.caption) .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color.white) } } ) .onChange(of: isLoading) { newValue in withAnimation(.easeInOut(duration: 2.5)) { showAnimation = !newValue } } ``` _Kotlin (Android)_ ```kotlin KinesteXSDK.createMainView( context = this, planCategory = planCategory, user = userDetails, // optional user details data = mapOf("style" to "dark"), isLoading = viewModel.isLoading, onMessageReceived = { message -> when (message) { is WebViewMessage.KinestexLaunched -> println("KinesteX Launched") is WebViewMessage.FinishedWorkout -> println("Workout Finished: ${message.data}") is WebViewMessage.ExitKinestex -> finish() else -> println("Received: $message") } }, permissionHandler = this ) ``` _React Native_ ```jsx // postData structure const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'YOUR_USER_ID', company: 'YOUR_COMPANY_NAME', planCategory: planCategory, style: { style: 'dark', // or 'light' }, }; { switch (type) { case 'kinestex_launched': console.log('KinesteX Launched:', data); break; case 'finished_workout': console.log('Workout Finished:', data); break; case 'exit_kinestex': setShowKinesteX(false); break; } }} /> ``` _Flutter_ ```dart KinesteXAIFramework.createMainView( isShowKinestex: showKinesteX, planCategory: planCategory, customParams: {"style": "dark"}, isLoading: ValueNotifier(false), onMessageReceived: (message) { switch (message.type) { case 'kinestex_launched': print('KinesteX Launched'); break; case 'finished_workout': print('Workout Finished: ${message.data}'); break; case 'exit_kinestex': setState(() => showKinesteX = false); break; } }, ) ``` _HTML / JavaScript_ ```html // Add planCategory to postData const postData = { // ... all initial fields planCategory: planCategory, // "Strength", "Cardio", etc. }; const srcURL = "https://ai.kinestex.com"; webView.src = srcURL; webView.onload = () => { sendMessage(); }; // Handle messages window.addEventListener('message', (event) => { const { type, data } = event.data; switch (type) { case 'kinestex_launched': console.log('KinesteX Launched:', data); break; case 'exit_kinestex': // Hide the iframe break; } }); ``` _React (TypeScript)_ ```tsx import { IntegrationOption, KinesteXSDK, PlanCategory, type IPostData } from 'kinestex-sdk-react-ts'; // postData structure const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'YOUR_USER_ID', company: 'YOUR_COMPANY_NAME', planCategory: PlanCategory.Cardio, style: { style: 'dark', }, }; { switch (type) { case 'kinestex_launched': console.log('KinesteX Launched:', data); break; case 'finished_workout': console.log('Workout Finished:', data); break; case 'exit_kinestex': setShowKinesteX(false); break; } }} /> ``` **Complete Example** Full implementation example with all required setup: _Swift (iOS)_ ```swift import SwiftUI import KinesteXAIKit struct MainViewIntegration: View { @State private var showKinesteX = false @State private var isLoading = false // Replace with your KinesteX credentials let kinesteXKit = KinesteXAIKit( apiKey: "YOUR API KEY", companyName: "YOUR COMPANY NAME", userId: "YOUR USER ID" ) // Plan category for personalized fitness goals @State private var planCategory: PlanCategory = .Cardio var body: some View { VStack { Text("KinesteX Main View") .font(.title) .padding() Spacer() Button(action: { showKinesteX.toggle() }) { Text("Open Main View") .font(.title3) .foregroundColor(.white) .bold() .padding() .frame(maxWidth: .infinity) .background(Color.green.cornerRadius(10)) .padding(.horizontal) } Spacer() } .fullScreenCover(isPresented: $showKinesteX) { kinestex.createCategoryView( planCategory: planCategory, user: nil, isLoading: $isLoading, customParams: ["style": "light"], onMessageReceived: { message in switch message { case .exit_kinestex(_): showKinesteX = false default: print("Message received: \(message)") } } ) } } } #Preview { MainViewIntegration() } ``` _Kotlin (Android)_ ```kotlin import android.os.Bundle import android.widget.Toast import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.runtime.mutableStateOf import com.kinestex.kinestexsdkkotlin.GenericWebView import com.kinestex.kinestexsdkkotlin.KinesteXSDK import com.kinestex.kinestexsdkkotlin.PlanCategory import com.kinestex.kinestexsdkkotlin.PermissionHandler import com.kinestex.kinestexsdkkotlin.WebViewMessage import kotlinx.coroutines.flow.MutableStateFlow class MainViewActivity : ComponentActivity(), PermissionHandler { private val viewModel = MainViewModel() // OPTIONAL: UserDetails to customize workout intensity and calorie estimation // Note: User details are only used on-device during the session private val userDetails = UserDetails( age = 30, height = 180, weight = 75, gender = Gender.MALE, lifestyle = Lifestyle.ACTIVE ) // Custom data for the WebView private val data = mutableMapOf() // Store reference to the KinesteX WebView private var kinesteXWebView: GenericWebView? = null // Register permission launcher private val requestPermissionLauncher = registerForActivityResult( ActivityResultContracts.RequestPermission() ) { isGranted: Boolean -> // Pass permission result to KinesteX webview kinesteXWebView?.handlePermissionResult(isGranted) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) data["style"] = "light" setContent { val webView = KinesteXSDK.createMainView( context = this, planCategory = PlanCategory.Cardio, user = userDetails, data = data, isLoading = viewModel.isLoading, onMessageReceived = ::handleWebViewMessage, permissionHandler = this ) as GenericWebView kinesteXWebView = webView webView.Render() } } private fun handleWebViewMessage(message: WebViewMessage) { when (message) { is WebViewMessage.ExitKinestex -> finish() is WebViewMessage.KinestexLaunched -> viewModel.isLoading.value = false else -> Toast.makeText(this, "Received: $message", Toast.LENGTH_SHORT).show() } } // When request is sent, display system dialog for camera access override fun requestCameraPermission() { requestPermissionLauncher.launch(Manifest.permission.CAMERA) } } class MainViewModel { val isLoading = MutableStateFlow(true) } ``` _React Native_ ```jsx import React, { useState } from 'react'; import { View, Button, StyleSheet } from 'react-native'; import KinestexSDK, { IntegrationOption, IPostData, PlanCategory } from '@kinestex/sdk-react-native'; const MainViewIntegration = () => { const [showKinesteX, setShowKinesteX] = useState(false); const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'YOUR_USER_ID', company: 'YOUR_COMPANY_NAME', planCategory: PlanCategory.Cardio, style: { style: 'dark', }, }; return ( {showKinesteX ? ( { if (type === 'exit_kinestex') { setShowKinesteX(false); } }} /> ) : ( ``` _React (TypeScript)_ ```tsx import React, { useState } from 'react'; import { IntegrationOption, KinesteXSDK, PlanCategory, type IPostData, } from 'kinestex-sdk-react-ts'; const MainViewIntegration: React.FC = () => { const [showKinesteX, setShowKinesteX] = useState(false); const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'YOUR_USER_ID', company: 'YOUR_COMPANY_NAME', planCategory: PlanCategory.Cardio, style: { style: 'dark', }, }; return (
{showKinesteX ? ( { if (type === 'exit_kinestex') { setShowKinesteX(false); } }} /> ) : ( )}
); }; export default MainViewIntegration; ``` #### Workout View Personalized Workouts: Anytime, Anywhere. - **Tailored for All Levels**: Workouts for strength, flexibility, or relaxation - **Time-Saving**: Quick, efficient sessions with zero hassle - **Engaging**: Keep users motivated with fresh, personalized routines - **Easy Integration**: Add workouts seamlessly with minimal effort You can find workouts in our [workout library](https://workout-view.kinestex.com/?tab=workouts), or create your own workouts in our [admin portal](https://admin.kinestex.com). **Workout Integration** Display a workout by name or ID: _Swift (iOS)_ ```swift kinestex.createWorkoutView( workout: selectedWorkout, // workout name or ID user: nil, isLoading: $isLoading, customParams: ["style": "dark", "language": "en"], // dark or light theme onMessageReceived: { message in switch message { case .exit_kinestex(_): showKinesteX = false // dismiss the view default: print("Received \(message)") break } } ) ``` _Kotlin (Android)_ ```kotlin KinesteXSDK.createWorkoutView( context = this, workout = selectedWorkout, // workout name or ID user = userDetails, // optional user details data = mapOf("style" to "dark", "language" to "en"), isLoading = viewModel.isLoading, onMessageReceived = { message -> when (message) { is WebViewMessage.ExitKinestex -> finish() else -> println("Received: $message") } }, permissionHandler = this ) ``` _React Native_ ```jsx // postData structure const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'YOUR_USER_ID', company: 'YOUR_COMPANY_NAME', style: { style: 'dark', }, }; { if (type === 'exit_kinestex') { setShowKinesteX(false); } }} /> ``` _Flutter_ ```dart KinesteXAIFramework.createWorkoutView( isShowKinestex: showKinesteX, workoutName: selectedWorkout, // workout name or ID customParams: {"style": "dark", "language": "en"}, isLoading: ValueNotifier(false), onMessageReceived: (message) { if (message.type == 'exit_kinestex') { setState(() => showKinesteX = false); } }, ) ``` _HTML / JavaScript_ ```html // Specify workout ID in the URL const srcURL = "https://ai.kinestex.com/workout/YOUR_WOROUT_ID"; webView.src = srcURL; ``` _React (TypeScript)_ ```tsx import { IntegrationOption, KinesteXSDK, type IPostData } from 'kinestex-sdk-react-ts'; // postData structure const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'YOUR_USER_ID', company: 'YOUR_COMPANY_NAME', style: { style: 'dark', }, }; { if (type === 'exit_kinestex') { setShowKinesteX(false); } }} /> ``` **Complete Example** Full implementation example with workout selection: _Swift (iOS)_ ```swift import SwiftUI import KinesteXAIKit struct WorkoutIntegrationView: View { @State private var showKinesteX = false @State private var isLoading = false // Initialize KinesteXAIKit // Replace with your KinesteX credentials let kinesteXKit = KinesteXAIKit( apiKey: "YOUR API KEY", companyName: "YOUR COMPANY NAME", userId: "YOUR USER ID" ) // Replace with the name or ID of the workout let workoutName = "Fitness Lite" var body: some View { VStack { Text("KinesteX Workout Integration") .font(.title) .padding() Spacer() Button(action: { showKinesteX.toggle() }) { Text("Start \(workoutName) Workout") .font(.title3) .foregroundColor(.white) .bold() .padding() .frame(maxWidth: .infinity) .background(Color.green.cornerRadius(10)) .padding(.horizontal) } .padding() Spacer() } .fullScreenCover(isPresented: $showKinesteX) { kinesteXKit.createWorkoutView( workout: workoutName, user: nil, isLoading: $isLoading, customParams: ["style": "dark"], onMessageReceived: { message in switch message { case .exit_kinestex(_): showKinesteX = false default: print("Message received: \(message)") } } ) } } } #Preview { WorkoutIntegrationView() } ``` _Kotlin (Android)_ ```kotlin import android.os.Bundle import android.widget.Toast import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import com.kinestex.kinestexsdkkotlin.GenericWebView import com.kinestex.kinestexsdkkotlin.KinesteXSDK import com.kinestex.kinestexsdkkotlin.PermissionHandler import com.kinestex.kinestexsdkkotlin.WebViewMessage import kotlinx.coroutines.flow.MutableStateFlow class WorkoutViewActivity : ComponentActivity(), PermissionHandler { private val viewModel = WorkoutViewModel() // OPTIONAL: UserDetails to customize workout intensity and calorie estimation // Note: User details are only used on-device during the session private val userDetails = UserDetails( age = 30, height = 180, weight = 75, gender = Gender.MALE, lifestyle = Lifestyle.ACTIVE ) // Custom data for the WebView private val data = mutableMapOf() // Store reference to the KinesteX WebView private var kinesteXWebView: GenericWebView? = null // Register permission launcher private val requestPermissionLauncher = registerForActivityResult( ActivityResultContracts.RequestPermission() ) { isGranted: Boolean -> // Pass permission result to KinesteX webview kinesteXWebView?.handlePermissionResult(isGranted) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) data["style"] = "dark" setContent { val webView = KinesteXSDK.createWorkoutView( context = this, workout = "Fitness Lite", user = userDetails, data = data, isLoading = viewModel.isLoading, onMessageReceived = ::handleWebViewMessage, permissionHandler = this ) as GenericWebView kinesteXWebView = webView webView.Render() } } private fun handleWebViewMessage(message: WebViewMessage) { when (message) { is WebViewMessage.ExitKinestex -> finish() is WebViewMessage.KinestexLaunched -> viewModel.isLoading.value = false else -> Toast.makeText(this, "Received: $message", Toast.LENGTH_SHORT).show() } } // When request is sent, display system dialog for camera access override fun requestCameraPermission() { requestPermissionLauncher.launch(Manifest.permission.CAMERA) } } class WorkoutViewModel { val isLoading = MutableStateFlow(true) } ``` _React Native_ ```jsx import React, { useState } from 'react'; import { View, Button, StyleSheet } from 'react-native'; import KinestexSDK, { IntegrationOption, IPostData } from '@kinestex/sdk-react-native'; const WorkoutIntegration = () => { const [showKinesteX, setShowKinesteX] = useState(false); const selectedWorkout = "Fitness Lite"; const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'YOUR_USER_ID', company: 'YOUR_COMPANY_NAME', style: { style: 'dark', }, }; return ( {showKinesteX ? ( { if (type === 'exit_kinestex') { setShowKinesteX(false); } }} /> ) : ( ``` _React (TypeScript)_ ```tsx import React, { useState } from 'react'; import { IntegrationOption, KinesteXSDK, type IPostData, } from 'kinestex-sdk-react-ts'; const WorkoutIntegration: React.FC = () => { const [showKinesteX, setShowKinesteX] = useState(false); const selectedWorkout = "Fitness Lite"; const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'YOUR_USER_ID', company: 'YOUR_COMPANY_NAME', style: { style: 'dark', }, }; return (
{showKinesteX ? ( { if (type === 'exit_kinestex') { setShowKinesteX(false); } }} /> ) : ( )}
); }; export default WorkoutIntegration; ``` #### Plan View Display a specific workout plan with multiple workouts. Shows plan overview, schedule, and individual workout access. **Key Features of Our Workout Plans:** - **Goal-Oriented**: Supports strength, flexibility, and wellness goals - **Seamless Experience**: From recommendations to real-time feedback - **Customizable**: Brand-aligned app design - **Quick Integration**: Easy setup for advanced fitness solutions You can find plans in our [workout library](https://workout-view.kinestex.com/?tab=plans), or create your own plans in our [admin portal](https://admin.kinestex.com). **Plan Integration** Display a workout plan by name or ID: _Swift (iOS)_ ```swift kinestex.createPlanView( plan: selectedPlan, // name or ID of the plan (string) user: nil, // OPTIONAL: provide user details isLoading: $isLoading, customParams: ["style": "dark"], // dark or light theme onMessageReceived: { message in switch message { case .exit_kinestex(_): showKinesteX = false // dismiss the view default: print("Received \(message)") break } } ) ``` _Kotlin (Android)_ ```kotlin KinesteXSDK.createPlanView( context = this, plan = selectedPlan, // name or ID of the plan user = userDetails, // optional user details data = mapOf("style" to "dark"), isLoading = viewModel.isLoading, onMessageReceived = { message -> when (message) { is WebViewMessage.ExitKinestex -> finish() else -> println("Received: $message") } }, permissionHandler = this ) ``` _React Native_ ```jsx // postData structure const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'YOUR_USER_ID', company: 'YOUR_COMPANY_NAME', style: { style: 'dark', }, }; { if (type === 'exit_kinestex') { setShowKinesteX(false); } }} /> ``` _Flutter_ ```dart KinesteXAIFramework.createPlanView( isShowKinestex: showKinesteX, planName: selectedPlan, // name or ID of the plan customParams: {"style": "dark"}, isLoading: ValueNotifier(false), onMessageReceived: (message) { if (message.type == 'exit_kinestex') { setState(() => showKinesteX = false); } }, ) ``` _HTML / JavaScript_ ```html // Specify plan ID in the URL const srcURL = "https://ai.kinestex.com/plan/YOUR_PLAN_ID"; webView.src = srcURL; ``` _React (TypeScript)_ ```tsx import { IntegrationOption, KinesteXSDK, type IPostData } from 'kinestex-sdk-react-ts'; // postData structure const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'YOUR_USER_ID', company: 'YOUR_COMPANY_NAME', style: { style: 'dark', }, }; { if (type === 'exit_kinestex') { setShowKinesteX(false); } }} /> ``` **Complete Example** Full implementation example with plan selection: _Swift (iOS)_ ```swift import SwiftUI import KinesteXAIKit struct PlanViewIntegration: View { @State private var showKinesteX = false @State private var isLoading = false @State private var selectedPlan = "livggb4P6zoD94VsTBB6" let kinestex = KinesteXAIKit( apiKey: "YOUR API KEY", companyName: "YOUR COMPANY NAME", userId: "YOUR USER ID" ) var body: some View { VStack { Text("Select a Plan") .font(.title) .padding() Spacer() Button(action: { showKinesteX.toggle() }) { Text("Start \(selectedPlan)") .font(.title3) .foregroundColor(.white) .bold() .padding() .frame(maxWidth: .infinity) .background(Color.green.cornerRadius(10)) .padding(.horizontal) } Spacer() } .fullScreenCover(isPresented: $showKinesteX) { kinestex.createPlanView( plan: selectedPlan, user: nil, isLoading: $isLoading, customParams: ["style": "light"], onMessageReceived: { message in switch message { case .exit_kinestex(_): showKinesteX = false default: print("Message: \(message)") } } ) } } } #Preview { PlanViewIntegration() } ``` _Kotlin (Android)_ ```kotlin import android.os.Bundle import android.widget.Toast import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import com.kinestex.kinestexsdkkotlin.GenericWebView import com.kinestex.kinestexsdkkotlin.KinesteXSDK import com.kinestex.kinestexsdkkotlin.PermissionHandler import com.kinestex.kinestexsdkkotlin.WebViewMessage import kotlinx.coroutines.flow.MutableStateFlow class PlanViewActivity : ComponentActivity(), PermissionHandler { private val viewModel = PlanViewModel() // OPTIONAL: UserDetails to customize workout intensity and calorie estimation // Note: User details are only used on-device during the session private val userDetails = UserDetails( age = 30, height = 180, weight = 75, gender = Gender.MALE, lifestyle = Lifestyle.ACTIVE ) // Custom data for the WebView private val data = mutableMapOf() // Store reference to the KinesteX WebView private var kinesteXWebView: GenericWebView? = null // Register permission launcher private val requestPermissionLauncher = registerForActivityResult( ActivityResultContracts.RequestPermission() ) { isGranted: Boolean -> // Pass permission result to KinesteX webview kinesteXWebView?.handlePermissionResult(isGranted) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) data["style"] = "light" setContent { val webView = KinesteXSDK.createPlanView( context = this, plan = "livggb4P6zoD94VsTBB6", // name or ID of the plan user = userDetails, data = data, isLoading = viewModel.isLoading, onMessageReceived = ::handleWebViewMessage, permissionHandler = this ) as GenericWebView kinesteXWebView = webView webView.Render() } } private fun handleWebViewMessage(message: WebViewMessage) { when (message) { is WebViewMessage.ExitKinestex -> finish() is WebViewMessage.KinestexLaunched -> viewModel.isLoading.value = false else -> Toast.makeText(this, "Received: $message", Toast.LENGTH_SHORT).show() } } // When request is sent, display system dialog for camera access override fun requestCameraPermission() { requestPermissionLauncher.launch(Manifest.permission.CAMERA) } } class PlanViewModel { val isLoading = MutableStateFlow(true) } ``` _React Native_ ```jsx import React, { useState } from 'react'; import { View, Button, StyleSheet } from 'react-native'; import KinestexSDK, { IntegrationOption, IPostData } from '@kinestex/sdk-react-native'; const PlanViewIntegration = () => { const [showKinesteX, setShowKinesteX] = useState(false); const selectedPlan = "livggb4P6zoD94VsTBB6"; // plan ID const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'YOUR_USER_ID', company: 'YOUR_COMPANY_NAME', style: { style: 'dark', }, }; return ( {showKinesteX ? ( { if (type === 'exit_kinestex') { setShowKinesteX(false); } }} /> ) : ( ``` _React (TypeScript)_ ```tsx import React, { useState } from 'react'; import { IntegrationOption, KinesteXSDK, type IPostData, } from 'kinestex-sdk-react-ts'; const PlanViewIntegration: React.FC = () => { const [showKinesteX, setShowKinesteX] = useState(false); const selectedPlan = "Full Body Fitness"; const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'YOUR_USER_ID', company: 'YOUR_COMPANY_NAME', style: { style: 'dark', }, }; return (
{showKinesteX ? ( { if (type === 'exit_kinestex') { setShowKinesteX(false); } }} /> ) : ( )}
); }; export default PlanViewIntegration; ``` #### Challenge View Exciting Challenges: Drive Engagement and Motivation. - **Fun and Competitive**: Quick challenges with leaderboards for friendly competition - **Boost Activity**: Keep fitness exciting and rewarding for users - **Easy Integration**: Add dynamic challenges effortlessly to your app You can find exercises in our [exercise library](https://workout-view.kinestex.com/?tab=exercises), or create your own exercises in our [admin portal](https://admin.kinestex.com). **Challenge Integration** Display a challenge by exercise name or ID: _Swift (iOS)_ ```swift kinestex.createChallengeView( exercise: challengeExercise, // exercise name or ID duration: 100, // duration of challenge in seconds user: nil, // Optionally pass user details showLeaderboard: true, // showLeaderboard prompts a user to enter a challenge at the end (true by default) isLoading: $isLoading, customParams: ["style": "dark"], // dark or light theme onMessageReceived: { message in switch message { case .exit_kinestex(let data): showKinesteX = false // dismiss the view default: print("Received \(message)") break } } ) ``` _Kotlin (Android)_ ```kotlin KinesteXSDK.createChallengeView( context = this, exercise = challengeExercise, // exercise name or ID countdown = 100, // duration of challenge in seconds user = userDetails, // optional user details data = mapOf("style" to "dark"), showLeaderboard = true, // show leaderboard at end (default true) isLoading = viewModel.isLoading, onMessageReceived = { message -> when (message) { is WebViewMessage.ExitKinestex -> finish() else -> println("Received: $message") } }, permissionHandler = this ) ``` _React Native_ ```jsx // postData structure with challenge fields const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'YOUR_USER_ID', company: 'YOUR_COMPANY_NAME', exercise: 'Squats', // exercise name or ID countdown: 100, // duration in seconds showLeaderboard: true, // show leaderboard at end style: { style: 'dark', }, }; { if (type === 'exit_kinestex') { setShowKinesteX(false); } }} /> ``` _Flutter_ ```dart KinesteXAIFramework.createChallengeView( isShowKinestex: showKinesteX, exercise: challengeExercise, // exercise name or ID countdown: 100, // seconds showLeaderboard: true, customParams: {"style": "dark"}, isLoading: ValueNotifier(false), onMessageReceived: (message) { if (message.type == 'exit_kinestex') { setState(() => showKinesteX = false); } }, ) ``` _HTML / JavaScript_ ```html // Add challenge params to postData const postData = { // ... all initial fields exercise: challengeExercise, // exercise name or ID showLeaderboard: true, // show leaderboard at end countdown: 100, // duration in seconds }; const srcURL = "https://ai.kinestex.com/challenge"; webView.src = srcURL; webView.onload = () => { sendMessage(); }; ``` _React (TypeScript)_ ```tsx import { IntegrationOption, KinesteXSDK, type IPostData } from 'kinestex-sdk-react-ts'; // postData structure with challenge fields const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'YOUR_USER_ID', company: 'YOUR_COMPANY_NAME', exercise: 'Squats', // exercise name or ID countdown: 100, // duration in seconds showLeaderboard: true, // show leaderboard at end style: { style: 'dark', }, }; { if (type === 'exit_kinestex') { setShowKinesteX(false); } }} /> ``` **Complete Example** Full implementation example with challenge setup: _Swift (iOS)_ ```swift import SwiftUI import KinesteXAIKit struct ChallengeIntegrationView: View { @State private var showKinesteX = false @State private var isLoading = false // Initialize KinesteXAIKit // Replace with your KinesteX credentials let kinesteXKit = KinesteXAIKit( apiKey: "YOUR API KEY", companyName: "YOUR COMPANY NAME", userId: "YOUR USER ID" ) // Challenge parameters let challengeExercise = "Squats" let challengeDuration = 100 // Duration in seconds let showLeaderboardAfterChallenge = true var body: some View { VStack { Text("KinesteX Challenge Integration") .font(.title) .padding() Spacer() Button(action: { showKinesteX.toggle() }) { Text("Start \(challengeExercise) Challenge (\(challengeDuration)s)") .font(.title3) .foregroundColor(.white) .bold() .padding() .frame(maxWidth: .infinity) .background(Color.red.cornerRadius(10)) .padding(.horizontal) } .padding() Spacer() } .fullScreenCover(isPresented: $showKinesteX) { kinesteXKit.createChallengeView( exercise: challengeExercise, duration: challengeDuration, showLeaderboard: showLeaderboardAfterChallenge, user: nil, isLoading: $isLoading, customParams: ["style": "dark"], onMessageReceived: { message in switch message { case .exit_kinestex(_): showKinesteX = false default: print("Message received: \(message)") } } ) } } } #Preview { ChallengeIntegrationView() } ``` _Kotlin (Android)_ ```kotlin import android.os.Bundle import android.widget.Toast import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import com.kinestex.kinestexsdkkotlin.GenericWebView import com.kinestex.kinestexsdkkotlin.KinesteXSDK import com.kinestex.kinestexsdkkotlin.PermissionHandler import com.kinestex.kinestexsdkkotlin.WebViewMessage import kotlinx.coroutines.flow.MutableStateFlow class ChallengeViewActivity : ComponentActivity(), PermissionHandler { private val viewModel = ChallengeViewModel() // OPTIONAL: UserDetails to customize workout intensity and calorie estimation // Note: User details are only used on-device during the session private val userDetails = UserDetails( age = 30, height = 180, weight = 75, gender = Gender.MALE, lifestyle = Lifestyle.ACTIVE ) // Custom data for the WebView private val data = mutableMapOf() // Store reference to the KinesteX WebView private var kinesteXWebView: GenericWebView? = null // Register permission launcher private val requestPermissionLauncher = registerForActivityResult( ActivityResultContracts.RequestPermission() ) { isGranted: Boolean -> // Pass permission result to KinesteX webview kinesteXWebView?.handlePermissionResult(isGranted) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) data["style"] = "dark" setContent { val webView = KinesteXSDK.createChallengeView( context = this, exercise = "Squats", countdown = 60, user = userDetails, data = data, showLeaderboard = true, isLoading = viewModel.isLoading, onMessageReceived = ::handleWebViewMessage, permissionHandler = this ) as GenericWebView kinesteXWebView = webView webView.Render() } } private fun handleWebViewMessage(message: WebViewMessage) { when (message) { is WebViewMessage.ExitKinestex -> finish() is WebViewMessage.KinestexLaunched -> viewModel.isLoading.value = false else -> Toast.makeText(this, "Received: $message", Toast.LENGTH_SHORT).show() } } // When request is sent, display system dialog for camera access override fun requestCameraPermission() { requestPermissionLauncher.launch(Manifest.permission.CAMERA) } } class ChallengeViewModel { val isLoading = MutableStateFlow(true) } ``` _React Native_ ```jsx import React, { useState } from 'react'; import { View, Button, StyleSheet } from 'react-native'; import KinestexSDK, { IntegrationOption, IPostData } from '@kinestex/sdk-react-native'; const ChallengeIntegration = () => { const [showKinesteX, setShowKinesteX] = useState(false); const challengeExercise = "Squats"; const challengeDuration = 100; // Include challenge fields in postData const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'YOUR_USER_ID', company: 'YOUR_COMPANY_NAME', exercise: challengeExercise, // exercise name or ID countdown: challengeDuration, // duration in seconds showLeaderboard: true, // show leaderboard at end style: { style: 'dark', }, }; return ( {showKinesteX ? ( { if (type === 'exit_kinestex') { setShowKinesteX(false); } }} /> ) : ( ``` _React (TypeScript)_ ```tsx import React, { useState } from 'react'; import { IntegrationOption, KinesteXSDK, type IPostData, } from 'kinestex-sdk-react-ts'; const ChallengeIntegration: React.FC = () => { const [showKinesteX, setShowKinesteX] = useState(false); const challengeExercise = "Squats"; const challengeDuration = 100; // Include challenge fields in postData const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'YOUR_USER_ID', company: 'YOUR_COMPANY_NAME', exercise: challengeExercise, // exercise name or ID countdown: challengeDuration, // duration in seconds showLeaderboard: true, // show leaderboard at end style: { style: 'dark', }, }; return (
{showKinesteX ? ( { if (type === 'exit_kinestex') { setShowKinesteX(false); } }} /> ) : ( )}
); }; export default ChallengeIntegration; ``` #### Leaderboard View (Challenge) Ready-made Leaderboard: Boost User Engagement and Motivation. - **Adaptive Design**: The leaderboard automatically adapts to your KinesteX UI and can be fully customized in the admin dashboard - **Real-time Updates**: Whenever a new ranking is available, the leaderboard automatically refreshes to show the latest standings **Leaderboard Integration** Display the leaderboard for a specific exercise: _Swift (iOS)_ ```swift kinestex.createLeaderboardView( exercise: "Squats", // Specify the exercise id or title username: "", // if you know the username: highlight the user by specifying their username isLoading: $isLoading, customParams: [ "style": "dark", // light or dark theme (default is dark) "isHideHeaderMain": true // OPTIONAL: hide the exit button from the leaderboard ], onMessageReceived: { message in switch message { case .exit_kinestex(_): showKinesteX = false default: break } } ) ``` _Kotlin (Android)_ ```kotlin KinesteXSDK.createLeaderboardView( context = this, exercise = "Squats", // exercise name or ID username = "John", // highlight username in leaderboard if known data = mapOf( "style" to "dark", "isHideHeaderMain" to true // hide exit button ), isLoading = viewModel.isLoading, onMessageReceived = { message -> when (message) { is WebViewMessage.ExitKinestex -> finish() else -> println("Received: $message") } }, permissionHandler = this ) ``` _React Native_ ```jsx // postData structure const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'YOUR_USER_ID', company: 'YOUR_COMPANY_NAME', exercise: 'Squats', // exercise name or ID style: { style: 'dark', }, }; { if (type === 'exit_kinestex') { setShowKinesteX(false); } }} /> ``` _Flutter_ ```dart KinesteXAIFramework.createLeaderboardView( isShowKinestex: showKinesteX, exercise: "Squats", username: "", // highlight user if known customParams: { "style": "dark", "isHideHeaderMain": true, // hide exit button }, isLoading: ValueNotifier(false), onMessageReceived: (message) { if (message.type == 'exit_kinestex') { setState(() => showKinesteX = false); } }, ) ``` _HTML / JavaScript_ ```html // Add to postData const postData = { // ... all initial fields exercise: "Squats", // exercise name or ID }; const userId = "unique-user-id"; // OPTIONAL: userId to highlight in leaderboard const srcURL = `https://ai.kinestex.com/leaderboard/?userId=${userId}`; webView.src = srcURL; webView.onload = () => { sendMessage(); }; ``` _React (TypeScript)_ ```tsx import { IntegrationOption, KinesteXSDK, type IPostData } from 'kinestex-sdk-react-ts'; // postData structure const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'YOUR_USER_ID', company: 'YOUR_COMPANY_NAME', exercise: 'Squats', // exercise ID or name style: { style: 'dark', }, }; { if (type === 'exit_kinestex') { setShowKinesteX(false); } }} /> ``` #### AI Experiences AI-powered movement-based games and clinical assessments with real-time motion tracking. **AI Games:** | Game | Exercise ID | |------|-------------| | Balloon Pop | balloonpop | | Color Memory | colorchase | | Alien Squat Shooter | aliensquatshooter | **Clinical Assessments:** | Assessment | Exercise ID | |------------|-------------| | Timed Up and Go (TUG) | tug | | Gait Speed Test | gaitspeedtest | | Sit-to-Stand | sittostand | | Functional Reach Test | functionalreachtest | | Single Leg Stance Test | singlelegstancetest | | Five Times Sit-to-Stand | fivetimessts | | Side-by-Side Stand | sidebysidestand | | Semi-Tandem Stand | semitandemstand | | Full Tandem Stand | fulltandem | | Shoulder Range of Motion | romshoulder | **Experience View** _Swift (iOS)_ ```swift // Launch an AI game or balance assessment // Pass the exercise ID from the tables above kinestex.createExperienceView( experience: "assessment", // experience type exercise: "balloonpop", // exercise ID from table user: nil, isLoading: $isLoading, customParams: ["style": "dark"], onMessageReceived: { message in switch message { case .exit_kinestex(_): showKinesteX = false default: break } } ) ``` _Kotlin (Android)_ ```kotlin // Launch an AI game or balance assessment val data = mutableMapOf() data["style"] = "dark" data["exercise"] = "balloonpop" // exercise ID from table KinesteXSDK.createExperiencesView( context = this, experience = "assessment", user = userDetails, // optional user details data = data, isLoading = viewModel.isLoading, onMessageReceived = { message -> if (message is WebViewMessage.ExitKinestex) finish() }, permissionHandler = this ) ``` _React Native_ ```jsx // postData structure const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'YOUR_USER_ID', company: 'YOUR_COMPANY_NAME', exercise: 'balloonpop', // exercise ID from table style: { style: 'dark', }, }; ``` _Flutter_ ```dart KinesteXAIFramework.createExperienceView( isShowKinestex: showKinesteX, experience: "assessment", exercise: "balloonpop", // exercise ID from table customParams: {"style": "dark"}, isLoading: ValueNotifier(false), onMessageReceived: (message) { handleWebViewMessage(message); }, ) ``` _HTML / JavaScript_ ```html const postData = { // ... all initial fields exercise: "balloonpop", // exercise ID from table }; const srcURL = "https://ai.kinestex.com/experiences/assessment"; webView.src = srcURL; webView.onload = () => { sendMessage(); }; ``` _React (TypeScript)_ ```tsx import { IntegrationOption, KinesteXSDK, type IPostData } from 'kinestex-sdk-react-ts'; // postData structure const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'YOUR_USER_ID', company: 'YOUR_COMPANY_NAME', exercise: 'balloonpop', // exercise ID from table style: { style: 'dark', }, }; ``` **Complete Example** _Swift (iOS)_ ```swift import SwiftUI import KinesteXAIKit struct ExperienceIntegrationView: View { @State private var showKinesteX = false @State private var isLoading = false // Initialize KinesteXAIKit // Replace with your KinesteX credentials let kinesteXKit = KinesteXAIKit( apiKey: "YOUR API KEY", companyName: "YOUR COMPANY NAME", userId: "YOUR USER ID" ) // Parameters for the experience let experienceName = "assessment" // Name of the AI experience let experienceExercise = "balloonpop" // Exercise ID from table var body: some View { VStack { Text("KinesteX Experience Integration") .font(.title) .padding() Spacer() Button(action: { showKinesteX.toggle() }) { Text("Start '\(experienceName.capitalized)' Experience") .font(.title3) .foregroundColor(.white) .bold() .padding() .frame(maxWidth: .infinity) .background(Color.teal.cornerRadius(10)) .padding(.horizontal) } .padding() Spacer() } .fullScreenCover(isPresented: $showKinesteX) { kinesteXKit.createExperienceView( experience: experienceName, exercise: experienceExercise, user: nil, isLoading: $isLoading, customParams: ["style": "dark"], onMessageReceived: { message in switch message { case .exit_kinestex(_): showKinesteX = false default: print("Message received: \(message)") } } ) } } } #Preview { ExperienceIntegrationView() } ``` _Kotlin (Android)_ ```kotlin import android.os.Bundle import android.widget.Toast import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import com.kinestex.kinestexsdkkotlin.GenericWebView import com.kinestex.kinestexsdkkotlin.KinesteXSDK import com.kinestex.kinestexsdkkotlin.PermissionHandler import com.kinestex.kinestexsdkkotlin.WebViewMessage import kotlinx.coroutines.flow.MutableStateFlow class ExperienceActivity : ComponentActivity(), PermissionHandler { private val viewModel = ExperienceViewModel() // OPTIONAL: UserDetails to customize workout intensity and calorie estimation // Note: User details are only used on-device during the session private val userDetails = UserDetails( age = 30, height = 180, weight = 75, gender = Gender.MALE, lifestyle = Lifestyle.ACTIVE ) // Custom data for the WebView private val data = mutableMapOf() // Store reference to the KinesteX WebView private var kinesteXWebView: GenericWebView? = null // Register permission launcher private val requestPermissionLauncher = registerForActivityResult( ActivityResultContracts.RequestPermission() ) { isGranted: Boolean -> // Pass permission result to KinesteX webview kinesteXWebView?.handlePermissionResult(isGranted) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) data["style"] = "dark" data["exercise"] = "balloonpop" // AI game or balance assessment ID setContent { val webView = KinesteXSDK.createExperiencesView( context = this, experience = "assessment", user = userDetails, data = data, isLoading = viewModel.isLoading, onMessageReceived = ::handleWebViewMessage, permissionHandler = this ) as GenericWebView kinesteXWebView = webView webView.Render() } } private fun handleWebViewMessage(message: WebViewMessage) { when (message) { is WebViewMessage.ExitKinestex -> finish() is WebViewMessage.KinestexLaunched -> viewModel.isLoading.value = false else -> Toast.makeText(this, "Received: $message", Toast.LENGTH_SHORT).show() } } // When request is sent, display system dialog for camera access override fun requestCameraPermission() { requestPermissionLauncher.launch(Manifest.permission.CAMERA) } } class ExperienceViewModel { val isLoading = MutableStateFlow(true) } ``` _React Native_ ```jsx import React, { useState } from 'react'; import { View, Button } from 'react-native'; import KinestexSDK, { IntegrationOption, IPostData } from '@kinestex/sdk-react-native'; export default function ExperienceScreen() { const [showKinesteX, setShowKinesteX] = useState(false); const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'YOUR_USER_ID', company: 'YOUR_COMPANY_NAME', exercise: 'balloonpop', // AI game or balance assessment ID style: { style: 'dark', }, }; const handleMessage = (type: string, data: any) => { if (type === 'exit_kinestex') { setShowKinesteX(false); } }; if (showKinesteX) { return ( ); } return ( ``` _React (TypeScript)_ ```tsx import React, { useState } from 'react'; import { IntegrationOption, KinesteXSDK, type IPostData, } from 'kinestex-sdk-react-ts'; export default function ExperienceScreen() { const [showKinesteX, setShowKinesteX] = useState(false); const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'YOUR_USER_ID', company: 'YOUR_COMPANY_NAME', exercise: 'balloonpop', // AI game or balance assessment ID style: { style: 'dark', }, }; const handleMessage = (type: string, data: Record) => { if (type === 'exit_kinestex') { setShowKinesteX(false); } }; if (showKinesteX) { return (
); } return ; } ``` #### Personalized Plan View AI-Generated Personalized Workout Plans. - **Personalized**: Tailored to height, weight, age, gender, activity level, and fitness assessment results - **Goal-Oriented**: Supports strength, flexibility, and wellness goals - **Seamless Experience**: From recommendations to real-time feedback - **Customizable**: Brand-aligned app design - **Quick Integration**: Easy setup for advanced fitness solutions **Personalized Plan Integration** _Swift (iOS)_ ```swift kinestex.createPersonalizedPlanView( user: nil, // OPTIONAL: provide user details isLoading: $isLoading, customParams: ["style": "dark"], // dark or light theme (customizable in admin portal) onMessageReceived: { message in switch message { case .exit_kinestex(_): showKinesteX = false // dismiss the view default: print("Received \(message)") break } } ) ``` _Kotlin (Android)_ ```kotlin KinesteXSDK.createPersonalizedPlanView( context = this, user = userDetails, // optional user details data = mapOf("style" to "dark"), // customizable in admin portal isLoading = viewModel.isLoading, onMessageReceived = { message -> when (message) { is WebViewMessage.ExitKinestex -> finish() else -> println("Received: $message") } }, permissionHandler = this ) ``` _React Native_ ```jsx // postData structure const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'YOUR_USER_ID', company: 'YOUR_COMPANY_NAME', style: { style: 'dark', }, }; ``` _Flutter_ ```dart KinesteXAIFramework.createPersonalizedPlanView( isShowKinestex: showKinesteX, customParams: {"style": "dark"}, isLoading: ValueNotifier(false), onMessageReceived: (message) { handleWebViewMessage(message); }, ) ``` _HTML / JavaScript_ ```html KinesteX: Personalized Plan ``` _React (TypeScript)_ ```tsx import { IntegrationOption, KinesteXSDK, type IPostData } from 'kinestex-sdk-react-ts'; // postData structure const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'YOUR_USER_ID', company: 'YOUR_COMPANY_NAME', style: { style: 'dark', }, }; ``` #### Admin Workout Editor Embedded view for creating and managing workouts and exercises. As users interact with the editor, your application will receive events that you can handle to trigger custom logic. **Available only for Flutter.** **Custom Query Options:** | Option | Description | |--------|-------------| | hidePlansTab | Hide the plans tab in the dashboard | | tab | Default tab: "workouts", "exercises", or "plans" | | isSelectableMenu | Show select button on cards, triggers `_selected` events | **Available Events:** *General Events:* - `kinestex_loaded` - Application fully loaded - `kinestex_launched` - Successful authentication - `error_occurred` - Authentication error *Exercise Events:* - `exercise_opened` - Exercise detail page opened - `exercise_selection_opened` - Exercise list page opened - `exercise_selected` - Exercise selected from menu - `exercise_saved` - Exercise created/updated - `exercise_removed` - Exercise removed from workout *Workout Events:* - `workout_opened` - Workout detail page opened - `workout_selection_opened` - Workout list page opened - `workout_selected` - Workout selected from menu - `workout_saved` - Workout created/updated *Plan Events:* - `plan_opened` - Plan detail page opened - `plan_selection_opened` - Plan list page opened - `plan_selected` - Plan selected from menu - `plan_saved` - Plan created/updated **Admin Workout Editor Integration** Create the admin workout editor view for managing workouts and exercises: _Flutter_ ```dart KinesteXAIFramework.createAdminWorkoutEditor( // Use an organization name to differentiate between different orgs. // If you don't plan to use multiple orgs, you can use your company name. organization: "your_organization_name", isShowKinestex: showKinesteX, // OPTIONAL: show/hide content on the admin dashboard customQueries: { "hidePlansTab": true, // will hide the plans tabs in the dashboard "tab": "workouts", // will default to workouts tab. Options: "exercises", "plans" "isSelectableMenu": true // show select Button, triggers _selected events }, isLoading: ValueNotifier(false), onMessageReceived: (message) { handleWebViewMessage(message); }, ) ``` **Complete Example** Full implementation with event handling: _Flutter_ ```dart import 'package:flutter/material.dart'; import 'package:kinestex_sdk_flutter/kinestex_sdk.dart'; import 'package:permission_handler/permission_handler.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); await KinesteXAIFramework.initialize( apiKey: "your_api_key", companyName: "your_company_name", userId: "your_user_id", ); runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({super.key}); @override State createState() => _MyAppState(); } class _MyAppState extends State { @override void dispose() { disposeKinesteXAIFramework(); super.dispose(); } Future disposeKinesteXAIFramework() async { await KinesteXAIFramework.dispose(); } @override Widget build(BuildContext context) { return MaterialApp( title: 'KinesteX Admin Editor', theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key}); @override State createState() => _MyHomePageState(); } class _MyHomePageState extends State { ValueNotifier showKinesteX = ValueNotifier(false); @override void initState() { super.initState(); _checkCameraPermission(); } void _checkCameraPermission() async { if (await Permission.camera.request() != PermissionStatus.granted) { _showCameraAccessDeniedAlert(); } } void _showCameraAccessDeniedAlert() { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text("Camera Permission Denied"), content: const Text("Camera access is required for this app to function properly."), actions: [ TextButton( child: const Text("OK"), onPressed: () => Navigator.of(context).pop(), ), ], ); }, ); } void handleWebViewMessage(WebViewMessage message) { if (message is ExitKinestex) { setState(() { showKinesteX.value = false; }); } else if (message is WorkoutSaved) { print('Workout saved: ${message.data["workout_id"]}'); } else if (message is ExerciseSaved) { print('Exercise saved: ${message.data["exercise_id"]}'); } else if (message is WorkoutSelected) { print('Workout selected: ${message.data["workout_title"]}'); } else if (message is ExerciseSelected) { print('Exercise selected: ${message.data["exercise_title"]}'); } else if (message is PlanSaved) { print('Plan saved: ${message.data["plan_id"]}'); } else if (message is ErrorOccurred) { print('Error: ${message.data["error_message"]}'); } } Widget createAdminWorkoutEditorView() { return Center( child: KinesteXAIFramework.createAdminWorkoutEditor( organization: "your_organization_name", isShowKinestex: showKinesteX, customQueries: { "hidePlansTab": false, "tab": "workouts", "isSelectableMenu": true, }, isLoading: ValueNotifier(false), onMessageReceived: handleWebViewMessage, ), ); } @override Widget build(BuildContext context) { return ValueListenableBuilder( valueListenable: showKinesteX, builder: (context, isShowKinesteX, child) { return isShowKinesteX ? SafeArea( child: createAdminWorkoutEditorView(), ) : Scaffold( body: Center( child: ElevatedButton( style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric( horizontal: 40, vertical: 20, ), backgroundColor: Colors.green, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), ), onPressed: () { showKinesteX.value = true; }, child: const Text( 'Open Admin Workout Editor', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), ), ); }, ); } } ``` ### Custom Integration Build everything yourself with full control over UI/UX. Use our Camera Component for real-time motion analysis and create custom workout sequences. #### Custom Workout Create and execute personalized workout sequences with custom exercises, repetitions, durations, and rest periods. Define your own workout flow with full control over exercise order and timing. **How it works:** 1. Initialize the SDK with custom workout integration option 2. Pass your custom workout exercises array 3. Wait for `all_resources_loaded` message 4. Send `workout_activity_action: start` to begin **WorkoutSequenceExercise Parameters:** - `exerciseId` - Exercise ID from KinesteX API or admin panel - `reps` - Number of repetitions - `duration` - Duration in seconds (null = unlimited time for reps) - `includeRestPeriod` - Include rest period before exercise - `restDuration` - Rest duration in seconds **Tip:** To create sets, duplicate the same exercise in the array multiple times. **Custom Workout Setup** _Swift (iOS)_ ```swift @State var workoutAction: [String: Any]? = nil let exercises = [ WorkoutSequenceExercise( exerciseId: "jz73VFlUyZ9nyd64OjRb", reps: 15, duration: nil, includeRestPeriod: true, restDuration: 20 ), WorkoutSequenceExercise( exerciseId: "ZVMeLsaXQ9Tzr5JYXg29", reps: 10, duration: 30, includeRestPeriod: true, restDuration: 15 ), WorkoutSequenceExercise( exerciseId: "gJGOiZhCvJrhEP7sTy78", reps: 20, duration: nil, includeRestPeriod: false, restDuration: 0 ) ] kinestex.createCustomWorkoutView( exercises: exercises, user: userDetails, style: nil, isLoading: $isLoading, workoutAction: $workoutAction, onMessageReceived: { message in if case .custom_type(let value) = message, let type = value["type"] as? String, type == "all_resources_loaded" { // Start workout when resources are ready workoutAction = ["workout_activity_action": "start"] } } ) ``` _Kotlin (Android)_ ```kotlin // Define custom workout exercises val customExercises = listOf( WorkoutSequenceExercise( exerciseId = "jz73VFlUyZ9nyd64OjRb", reps = 15, duration = null, includeRestPeriod = true, restDuration = 20 ), WorkoutSequenceExercise( exerciseId = "ZVMeLsaXQ9Tzr5JYXg29", reps = 10, duration = 30, includeRestPeriod = true, restDuration = 15 ) ) KinesteXSDK.createCustomWorkoutView( context = this, customWorkouts = customExercises, user = userDetails, // optional user details isLoading = viewModel.isLoading, customParams = mapOf("style" to "dark"), onMessageReceived = { message -> if (message is WebViewMessage.AllResourcesLoaded) { // Start workout when ready } }, permissionHandler = this ) ``` _React Native_ ```jsx // Step 1: Define custom workout exercises const customWorkoutExercises: WorkoutSequenceExercise[] = [ { exerciseId: "jz73VFlUyZ9nyd64OjRb", // exercise id from kinestex api reps: 15, // number of reps duration: null, // null = unlimited time for reps includeRestPeriod: true, // include rest before exercise restDuration: 20, // rest duration in seconds }, { exerciseId: "ZVMeLsaXQ9Tzr5JYXg29", reps: 10, duration: 30, includeRestPeriod: true, restDuration: 15, }, // Duplicate exercise to create a set { exerciseId: "ZVMeLsaXQ9Tzr5JYXg29", reps: 10, duration: 30, includeRestPeriod: true, restDuration: 15, }, ]; // Step 2: Configure postData with customWorkoutExercises const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'YOUR_USER_ID', company: 'YOUR_COMPANY_NAME', customWorkoutExercises: customWorkoutExercises, // pass exercises in postData style: { style: 'dark', }, }; // Step 3: Handle SDK messages const handleMessage = (type: string, data: any) => { switch (type) { case 'all_resources_loaded': // SDK is ready - show the view and start workout setAllResourcesLoaded(true); kinestexSDKRef.current?.sendAction("workout_activity_action", "start"); break; case 'workout_exit_request': setAllResourcesLoaded(false); setShowKinestex(false); break; case 'exit_kinestex': setAllResourcesLoaded(false); break; } }; // Step 4: Render with conditional visibility {showKinestex && ( )} ``` _Flutter_ ```dart // Define custom workout exercises final customWorkoutExercises = [ WorkoutSequenceExercise( exerciseId: "jz73VFlUyZ9nyd64OjRb", reps: 15, duration: null, includeRestPeriod: true, restDuration: 20, ), WorkoutSequenceExercise( exerciseId: "ZVMeLsaXQ9Tzr5JYXg29", reps: 10, duration: 30, includeRestPeriod: true, restDuration: 15, ), ]; KinesteXAIFramework.createCustomWorkoutView( isShowKinestex: showKinesteX, customWorkouts: customWorkoutExercises, customParams: {"style": "dark"}, isLoading: ValueNotifier(false), onMessageReceived: (message) { if (message.type == "all_resources_loaded") { // Start workout when ready } }, ) ``` _HTML / JavaScript_ ```html // Define custom workout exercises const customWorkoutExercises = [ { exerciseId: "jz73VFlUyZ9nyd64OjRb", reps: 15, duration: null, includeRestPeriod: true, restDuration: 20, }, { exerciseId: "ZVMeLsaXQ9Tzr5JYXg29", reps: 10, duration: 30, includeRestPeriod: true, restDuration: 15, }, ]; const config = { ...postData, customWorkoutExercises: customWorkoutExercises, }; const srcURL = "https://ai.kinestex.com/custom-workout"; webView.src = srcURL; webView.onload = () => { sendMessage(); }; // Listen for ready signal window.addEventListener("message", (e) => { const msg = JSON.parse(e.data); if (msg.type === 'all_resources_loaded') { // Start workout webView.contentWindow.postMessage( { action: "workout_activity_action", value: "start" }, srcURL ); } }); ``` _React (TypeScript)_ ```tsx import { useRef, useState } from 'react'; import { IntegrationOption, KinesteXSDK, type IPostData, type KinesteXSDKCamera, } from 'kinestex-sdk-react-ts'; // Define custom workout exercises interface WorkoutSequenceExercise { exerciseId: string; reps: number | null; duration: number | null; includeRestPeriod: boolean; restDuration: number; } const customWorkoutExercises: WorkoutSequenceExercise[] = [ { exerciseId: "jz73VFlUyZ9nyd64OjRb", reps: 15, duration: null, includeRestPeriod: true, restDuration: 20, }, ]; // postData structure with customWorkoutExercises const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'YOUR_USER_ID', company: 'YOUR_COMPANY_NAME', customWorkoutExercises: customWorkoutExercises, // pass exercises in postData style: { style: 'dark', }, }; const ref = useRef(null); { if (type === 'all_resources_loaded') { ref.current?.sendAction("workout_activity_action", "start"); } }} /> ``` ##### Complete Example Full implementation with state management, loading indicators, and proper message handling. **Complete Implementation** _Swift (iOS)_ ```swift import SwiftUI import KinesteXAIKit struct CustomWorkoutView: View { @State private var showKinesteX = false @State private var isLoading = false @State private var allResourcesLoaded = false @State var workoutAction: [String: Any]? = nil // Initialize KinesteXAIKit with your credentials let kinestex = KinesteXAIKit( apiKey: "YOUR_API_KEY", companyName: "YOUR_COMPANY_NAME", userId: "YOUR_USER_ID" ) // Define custom workout exercises let exercises = [ WorkoutSequenceExercise( exerciseId: "jz73VFlUyZ9nyd64OjRb", reps: 15, duration: nil, includeRestPeriod: true, restDuration: 20 ), WorkoutSequenceExercise( exerciseId: "ZVMeLsaXQ9Tzr5JYXg29", reps: 10, duration: 30, includeRestPeriod: true, restDuration: 15 ), WorkoutSequenceExercise( exerciseId: "gJGOiZhCvJrhEP7sTy78", reps: 20, duration: nil, includeRestPeriod: false, restDuration: 0 ) ] var body: some View { VStack { Text("Custom Workout") .font(.title) .padding() Spacer() Button(action: { showKinesteX.toggle() }) { Text("Start Custom Workout") .font(.title3) .foregroundColor(.white) .bold() .padding() .frame(maxWidth: .infinity) .background(Color.green.cornerRadius(10)) .padding(.horizontal) } Spacer() } .fullScreenCover(isPresented: $showKinesteX) { kinestex.createCustomWorkoutView( exercises: exercises, user: nil, style: nil, isLoading: $isLoading, workoutAction: $workoutAction, onMessageReceived: { message in switch message { case .custom_type(let value): guard let type = value["type"] as? String else { return } if type == "all_resources_loaded" { allResourcesLoaded = true // Start workout when resources are ready workoutAction = ["workout_activity_action": "start"] } case .exit_kinestex(_): showKinesteX = false allResourcesLoaded = false case .workout_overview(let data): print("Workout overview: \(data)") case .error_occurred(let data): print("Error: \(data)") default: print("Message received: \(message)") } } ) } } } #Preview { CustomWorkoutView() } ``` _React Native_ ```jsx import { StyleSheet, View, Button, Text } from "react-native"; import { useEffect, useRef, useState } from "react"; import { Camera } from "expo-camera"; import { SafeAreaView } from "react-native-safe-area-context"; import KinestexSDK from "kinestex-sdk-react-native"; import { KinesteXSDKCamera, WorkoutSequenceExercise, IPostData, IntegrationOption } from "kinestex-sdk-react-native/src/types"; export default function CustomWorkoutScreen() { const kinestexSDKRef = useRef(null); const [permission, setPermission] = useState(false); const [allResourcesLoaded, setAllResourcesLoaded] = useState(false); const [showKinestex, setShowKinestex] = useState(true); // Define workout sequence of exercises const customWorkoutExercises: WorkoutSequenceExercise[] = [ { exerciseId: "jz73VFlUyZ9nyd64OjRb", reps: 15, duration: null, // unlimited time to complete reps includeRestPeriod: true, restDuration: 20, }, { exerciseId: "ZVMeLsaXQ9Tzr5JYXg29", reps: 10, duration: 30, includeRestPeriod: true, restDuration: 15, }, // Duplicate to create a set { exerciseId: "ZVMeLsaXQ9Tzr5JYXg29", reps: 10, duration: 30, includeRestPeriod: true, restDuration: 15, }, { exerciseId: "gJGOiZhCvJrhEP7sTy78", reps: 20, duration: null, includeRestPeriod: false, restDuration: 0, }, ]; // Configuration data with customWorkoutExercises const postData: IPostData = { key: 'YOUR_API_KEY', company: 'YOUR_COMPANY_NAME', userId: "user-123", customWorkoutExercises: customWorkoutExercises, // pass exercises in postData style: { style: "dark", }, }; // Request camera permission useEffect(() => { (async () => { const { status } = await Camera.requestCameraPermissionsAsync(); if (status === "granted") { setPermission(true); } })(); }, []); // Handle messages from SDK const handleMessage = (type: string, data: { [key: string]: any }) => { switch (type) { case "exit_kinestex": console.log("User wishes to exit"); setAllResourcesLoaded(false); break; case "workout_exit_request": console.log("Workout exit request:", data); setAllResourcesLoaded(false); setShowKinestex(false); break; case "all_resources_loaded": console.log("All resources loaded"); setAllResourcesLoaded(true); // Start the workout kinestexSDKRef.current?.sendAction("workout_activity_action", "start"); break; case "workout_overview": console.log("Workout overview:", data); break; case "error_occurred": console.log("Error:", data); break; default: console.log("Message:", type, data); break; } }; if (!permission) { return ( Camera permission required ); } return ( {!showKinestex ? "KinesteX is not activated" : allResourcesLoaded ? "All resources loaded" : "KinesteX is loading in background"} {showKinestex ? ( ) : ( )} ); }; export default CustomWorkoutScreen; ``` _Flutter_ ```dart import 'package:flutter/material.dart'; import 'package:kinestex_sdk_flutter/kinestex_sdk.dart'; import 'package:permission_handler/permission_handler.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); await KinesteXAIFramework.initialize( apiKey: "your_api_key", companyName: "your_company_name", userId: "your_user_id", ); runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({super.key}); @override State createState() => _MyAppState(); } class _MyAppState extends State { @override void dispose() { disposeKinesteXAIFramework(); super.dispose(); } Future disposeKinesteXAIFramework() async { await KinesteXAIFramework.dispose(); } @override Widget build(BuildContext context) { return MaterialApp( title: 'KinesteX Custom Workout', theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key}); @override State createState() => _MyHomePageState(); } class _MyHomePageState extends State { // The KinesteX view is ALWAYS mounted. We only toggle visibility. final ValueNotifier showKinesteX = ValueNotifier(false); // Signals when SDK reports all resources loaded. final ValueNotifier allResourcesLoaded = ValueNotifier(false); // Optional: SDK loading notifier. final ValueNotifier sdkLoading = ValueNotifier(false); bool permissionGranted = false; late final List customWorkoutExercises; @override void initState() { super.initState(); _checkCameraPermission(); customWorkoutExercises = const [ WorkoutSequenceExercise( exerciseId: "jz73VFlUyZ9nyd64OjRb", reps: 15, duration: null, includeRestPeriod: true, restDuration: 20, ), WorkoutSequenceExercise( exerciseId: "ZVMeLsaXQ9Tzr5JYXg29", reps: 10, duration: 30, includeRestPeriod: true, restDuration: 15, ), // Duplicate to create a set WorkoutSequenceExercise( exerciseId: "ZVMeLsaXQ9Tzr5JYXg29", reps: 10, duration: 30, includeRestPeriod: true, restDuration: 15, ), WorkoutSequenceExercise( exerciseId: "gJGOiZhCvJrhEP7sTy78", reps: 20, duration: null, includeRestPeriod: false, restDuration: 0, ), ]; } void _checkCameraPermission() async { if (await Permission.camera.request() != PermissionStatus.granted) { setState(() => permissionGranted = false); _showCameraAccessDeniedAlert(); } else { setState(() => permissionGranted = true); } } void _showCameraAccessDeniedAlert() { WidgetsBinding.instance.addPostFrameCallback((_) { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text("Camera Permission Denied"), content: const Text( "Camera access is required for this app to function properly.", ), actions: [ TextButton( child: const Text("OK"), onPressed: () => Navigator.of(context).pop(), ), ], ); }, ); }); } void handleWebViewMessage(WebViewMessage message) { if (message is ExitKinestex) { setState(() { allResourcesLoaded.value = false; showKinesteX.value = false; }); return; } try { final data = message.data; final type = data['type']; switch (type) { case 'all_resources_loaded': allResourcesLoaded.value = true; showKinesteX.value = true; KinesteXAIFramework.sendAction( "workout_activity_action", "start", ); break; case 'workout_exit_request': allResourcesLoaded.value = false; showKinesteX.value = false; break; case 'error_occurred': final errorMsg = data['message']?.toString() ?? 'Unknown error'; print('Error from KinesteX SDK: $errorMsg'); break; default: break; } } catch (_) { allResourcesLoaded.value = false; showKinesteX.value = false; } } Widget createCustomWorkoutView() { return Center( child: KinesteXAIFramework.createCustomWorkoutView( customWorkouts: customWorkoutExercises, isShowKinestex: showKinesteX, isLoading: sdkLoading, onMessageReceived: handleWebViewMessage, ), ); } Widget buildCornerIndicator() { return SafeArea( child: Padding( padding: const EdgeInsets.only(top: 8, right: 8), child: ValueListenableBuilder( valueListenable: allResourcesLoaded, builder: (context, loaded, _) { return ValueListenableBuilder( valueListenable: showKinesteX, builder: (context, visible, __) { Color bg = Colors.black.withOpacity(0.75); IconData icon = Icons.hourglass_bottom; String label = 'KinesteX loading...'; if (!permissionGranted) { bg = Colors.red.withOpacity(0.85); icon = Icons.videocam_off; label = 'Camera permission required'; } else if (!loaded) { bg = Colors.orange.withOpacity(0.85); icon = Icons.downloading; label = 'Loading in background'; } else if (loaded && visible) { bg = Colors.green.withOpacity(0.85); icon = Icons.check_circle; label = 'All resources loaded'; } else if (loaded && !visible) { bg = Colors.grey.withOpacity(0.85); icon = Icons.visibility_off; label = 'KinesteX hidden'; } return Container( constraints: const BoxConstraints(maxWidth: 260), padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 10, ), decoration: BoxDecoration( color: bg, borderRadius: BorderRadius.circular(12), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, color: Colors.white, size: 18), const SizedBox(width: 8), Expanded( child: Text( label, maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle( color: Colors.white, fontSize: 13, fontWeight: FontWeight.w500, ), ), ), if (!permissionGranted) TextButton( onPressed: _checkCameraPermission, child: const Text( 'Grant', style: TextStyle(color: Colors.white), ), ) else if (loaded && !visible) TextButton( onPressed: () { showKinesteX.value = true; KinesteXAIFramework.sendAction( "workout_activity_action", "start", ); }, child: const Text( 'Show', style: TextStyle(color: Colors.white), ), ) else if (!loaded) const SizedBox( height: 16, width: 16, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white, ), ), ], ), ); }, ); }, ), ), ); } @override Widget build(BuildContext context) { return Scaffold( body: Stack( children: [ ValueListenableBuilder( valueListenable: showKinesteX, builder: (context, isVisible, _) { return IgnorePointer( ignoring: !isVisible, child: AnimatedOpacity( duration: const Duration(milliseconds: 250), opacity: isVisible ? 1.0 : 0.0, child: createCustomWorkoutView(), ), ); }, ), Positioned(top: 0, right: 0, child: buildCornerIndicator()), ], ), ); } } ``` _HTML / JavaScript_ ```html KinesteX: Custom Workout
Click button to start custom workout
``` #### Camera Component KinesteX Motion Recognition: Real-Time Engagement. - **Interactive Tracking**: Advanced motion recognition for immersive fitness experiences - **Real-Time Feedback**: Instantly track reps, spot mistakes, and calculate calories burned - **Boost Motivation**: Keep users engaged with detailed exercise feedback - **Custom Integration**: Adapt camera placement to fit your app's design **Important — what to pass for `currentExercise` and `exercises`:** we **recommend exercise IDs** — they're stable, human-readable, and you already have them when listing exercises from the Content API. Set `exerciseFetchType: "exercise_id"` to use them. The Camera Component also accepts **model IDs** (the default, kept for backward compatibility — but they require an extra round-trip through the Content API to look up) and **exercise titles** (case-sensitive — convenient for prototyping, but title matching depends on locale-normalization and can mis-match similar exercises, so prefer IDs in production). See the **Fetching Exercises** section below. **Before showing the camera UI**, wait for both `model_warmedup` and `models_loaded` events to fire. See the **Preloading & Events** section below. **Quick Start** _Swift (iOS)_ ```swift // Model IDs come from the Content API or admin dashboard. // See "Camera: Model IDs" subsection. @State var currentExercise = "3" kinestex.createCameraView( exercises: ["3"], // preload every model ID you may switch to currentExercise: $currentExercise, user: nil, isLoading: $isLoading, onMessageReceived: { message in switch message { case .reps(let value): reps = value["value"] as? Int ?? 0 case .mistake(let value): mistake = value["value"] as? String ?? "--" default: break } } ) ``` _Kotlin (Android)_ ```kotlin // Model IDs come from the Content API or admin dashboard. // See "Camera: Model IDs" subsection. val cameraView = KinesteXSDK.createCameraComponent( context = this, currentExercise = "3", exercises = listOf("3"), user = userDetails, isLoading = viewModel.isLoading, onMessageReceived = { message -> when (message) { is WebViewMessage.Reps -> (message.data["value"] as? Int)?.let { reps = it } is WebViewMessage.Mistake -> (message.data["value"] as? String)?.let { mistake = it } else -> {} } }, permissionHandler = this ) ``` _React Native_ ```jsx // Model IDs come from the Content API or admin dashboard. // See "Camera: Model IDs" subsection. // postData seeds the SDK with INITIAL values. To change them at runtime, // call methods on kinestexSDKRef (e.g. changeExercise) — see "Camera: Controls". const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'YOUR_USER_ID', company: 'YOUR_COMPANY_NAME', currentExercise: '3', exercises: ['3'], // preload every model ID you may switch to style: { style: 'dark' }, }; { if (type === 'successful_repeat') setReps(data.value); if (type === 'mistake') setMistake(data.value); }} /> ``` _Flutter_ ```dart // Model IDs come from the Content API or admin dashboard. // See "Camera: Model IDs" subsection. KinesteXAIFramework.createCameraComponent( isShowKinestex: showKinesteX, exercises: ["3"], currentExercise: "3", isLoading: ValueNotifier(false), onMessageReceived: (message) { if (message.type == 'successful_repeat') { setState(() => reps = message.data['value']); } if (message.type == 'mistake') { setState(() => mistake = message.data['value']); } }, ) ``` _HTML / JavaScript_ ```html // Model IDs come from the Content API or admin dashboard. // See "Camera: Model IDs" subsection. const postData = { // ... your initial fields (key, userId, company, etc.) currentExercise: "3", exercises: ["3"], }; const srcURL = "https://ai.kinestex.com/camera"; webView.src = srcURL; webView.onload = () => sendMessage(postData); window.addEventListener("message", (event) => { if (event.origin !== "https://ai.kinestex.com") return; const msg = JSON.parse(event.data); if (msg.type === 'successful_repeat') console.log('Rep:', msg.value); if (msg.type === 'mistake') console.log('Mistake:', msg.data?.value); }); ``` _React (TypeScript)_ ```tsx // Model IDs come from the Content API or admin dashboard. // See "Camera: Model IDs" subsection. import { useRef } from 'react'; import { IntegrationOption, KinesteXSDK, type IPostData, type KinesteXSDKCamera, } from 'kinestex-sdk-react-ts'; const ref = useRef(null); // postData seeds the SDK with INITIAL values. To change them at runtime, // call methods on the ref (e.g. ref.current?.changeExercise) — see "Camera: Controls". const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'YOUR_USER_ID', company: 'YOUR_COMPANY_NAME', currentExercise: '3', exercises: ['3'], style: { style: 'dark' }, }; { if (type === 'successful_repeat') setReps(data.value as number); if (type === 'mistake') setMistake(data.value as string); }} /> ``` ##### Model IDs > **Recommendation:** prefer **exercise IDs** — they're easier to get (you already have them when listing exercises from the Content API) and don't require an extra round-trip just to look up a numeric model ID. Set `exerciseFetchType: "exercise_id"` and pass `exercise.id` directly. See the [Fetching Exercises](#camera-component-fetching-exercises) section. Model IDs are still supported (it's the default when `exerciseFetchType` is omitted) and useful when you already have one handy — e.g. from a stored `WorkoutModel.sequence` or the admin dashboard. They are numeric values like `"3"` for Squats or `"394"` for Jumping Jack. **Three ways to get a model ID:** 1. **Content API** — call `fetchExercises()` (Swift / Kotlin / Flutter SDK) or `GET /api/v1/exercises` (REST). Each `ExerciseModel` has a `model_id` field. See the [Content API](/docs/content-api) section for full details. *(Note: this is the extra round-trip that exercise IDs let you skip.)* 2. **Admin dashboard** — open the exercise in [admin.kinestex.com](https://admin.kinestex.com); the model ID is shown at the top of the page header. 3. **Workout sequences** — when iterating `WorkoutModel.sequence`, each `ExerciseModel` entry exposes its own `model_id`. **Fetch a Model ID** _Swift (iOS)_ ```swift Task { let result = await kinestex.fetchExercises(limit: 10) if case .success(let response) = result, let exercise = response.exercises.first { // exercise.modelId is what the Camera Component expects currentExercise = exercise.modelId } } ``` _Kotlin (Android)_ ```kotlin lifecycleScope.launch { val result = withContext(Dispatchers.IO) { KinesteXSDK.api.fetchAPIContentData( contentType = ContentType.EXERCISE, limit = 10 ) } if (result is APIContentResult.Exercises) { // exercise.modelId is what the Camera Component expects currentExercise = result.exercises.firstOrNull()?.modelId ?: "" } } ``` _React Native_ ```jsx const res = await fetch( 'https://admin.kinestex.com/api/v1/exercises?limit=10', { headers: { 'x-api-key': API_KEY, 'x-company-name': COMPANY_NAME } }, ); const { exercises } = await res.json(); // exercise.model_id is what the Camera Component expects. // Pass it as initial value, or switch later via ref.current?.changeExercise(...). const modelId = exercises[0].model_id; ``` _Flutter_ ```dart final result = await KinesteXAIFramework.apiService.fetchContent( contentType: ContentType.exercise, limit: 10, ); // exercise.modelId is what the Camera Component expects final modelId = result.exercises.first.modelId; ``` _HTML / JavaScript_ ```html const res = await fetch( 'https://admin.kinestex.com/api/v1/exercises?limit=10', { headers: { 'x-api-key': API_KEY, 'x-company-name': COMPANY_NAME } }, ); const { exercises } = await res.json(); // exercise.model_id is what the Camera Component expects const modelId = exercises[0].model_id; ``` _React (TypeScript)_ ```tsx const res = await fetch( 'https://admin.kinestex.com/api/v1/exercises?limit=10', { headers: { 'x-api-key': API_KEY, 'x-company-name': COMPANY_NAME } }, ); const { exercises } = await res.json() as { exercises: ExerciseModel[] }; // exercise.model_id is what the Camera Component expects const modelId = exercises[0].model_id; ``` ##### Fetching Exercises (by ID or Title) Besides model IDs, the Camera Component can fetch exercises by **exercise ID** (recommended) or **exercise title**. Set the `exerciseFetchType` parameter to choose the form: | `exerciseFetchType` | Meaning | What goes in `exercises` / `currentExercise` | |---|---|---| | `"exercise_id"` ✅ **recommended** | Exercise IDs from the Content API — easiest to use, no extra round-trip | e.g. `"squats_v2"` | | `"model_id"` *(default)* | Numeric model IDs — kept for backward compatibility; requires a Content API lookup to obtain | `"3"`, `"394"` | | `"exercise_title"` | Exercise titles (case-sensitive) — handy for prototyping, but title matching depends on locale-normalization and can mis-match similar exercises | `"Squats"`, `"Jumping Jack"` | Omitting `exerciseFetchType` keeps the default model-ID behavior — no migration needed for existing integrations. **Where to pass it:** - **React Native (SDK v1.3.1+):** directly in `postData`, alongside `exercises` and `currentExercise`. - **Swift, Kotlin, Flutter, HTML/JS, React (TypeScript):** inside `customParams` / `customParameters` along with `exercises` and `currentExercise`. **Keep one form per session:** use the same form for both `exercises` and `currentExercise`, and for any later switches. **Fetch by Exercise Title** _Swift (iOS)_ ```swift // exerciseFetchType goes inside customParams kinestex.createCameraView( exercises: ["Squats", "Jumping Jack"], currentExercise: $currentExercise, // e.g. "Squats" customParams: [ "exerciseFetchType": "exercise_title" // "model_id" (default) | "exercise_id" | "exercise_title" ] ) ``` _Kotlin (Android)_ ```kotlin // exerciseFetchType goes inside customParams KinesteXSDK.createCameraComponent( context = this, currentExercise = "Squats", exercises = listOf("Squats", "Jumping Jack"), customParams = mapOf( "exerciseFetchType" to "exercise_title" // "model_id" (default) | "exercise_id" | "exercise_title" ), permissionHandler = this ) ``` _React Native_ ```jsx // React Native v1.3.1+: exerciseFetchType is direct in postData const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'YOUR_USER_ID', company: 'YOUR_COMPANY_NAME', currentExercise: 'Squats', exercises: ['Squats', 'Jumping Jack'], exerciseFetchType: 'exercise_title', // "model_id" (default) | "exercise_id" | "exercise_title" }; ``` _Flutter_ ```dart // exerciseFetchType goes inside customParams KinesteXAIFramework.createCameraComponent( isShowKinestex: showKinesteX, exercises: ["Squats", "Jumping Jack"], currentExercise: "Squats", customParams: { "exerciseFetchType": "exercise_title", // "model_id" (default) | "exercise_id" | "exercise_title" }, ) ``` _HTML / JavaScript_ ```html // exerciseFetchType goes inside customParams const postData = { // ... your initial fields (key, userId, company, etc.) currentExercise: "Squats", exercises: ["Squats", "Jumping Jack"], customParams: { exerciseFetchType: "exercise_title", // "model_id" (default) | "exercise_id" | "exercise_title" }, }; ``` _React (TypeScript)_ ```tsx // exerciseFetchType goes inside customParameters const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'YOUR_USER_ID', company: 'YOUR_COMPANY_NAME', currentExercise: 'Squats', exercises: ['Squats', 'Jumping Jack'], customParameters: { exerciseFetchType: 'exercise_title', // "model_id" (default) | "exercise_id" | "exercise_title" }, }; ``` ##### Loading More Exercises at Runtime (React Native v1.3.1+) **Availability:** React Native SDK `v1.3.1+` only. Other platforms must pass the full `exercises` list at initialization. In React Native you can fetch and cache **additional** exercise models **after the session has started**, without re-mounting the camera. This is done by calling `sendAction` with the `"load_models"` action and an `extras` object containing the new identifiers. **Three-step flow:** 1. Send the `load_models` action with the identifiers you want to add. 2. Wait for the `models_loaded` event — `data.modelIds` echoes the identifiers that resolved. 3. Switch the active exercise with `changeExercise(...)` (do **not** repeat `exerciseFetchType`). If `exercises` is missing or empty, the SDK posts back `{ "type": "error_occurred", "message": "load_models: no exercises provided" }`. Per-identifier failures arrive as separate `error_occurred` messages; any identifiers that did resolve are still listed in `modelIds` and are switchable. **Load More Models, Then Switch** _React Native_ ```jsx // Step 1 — fetch additional models at runtime sdkRef.current?.sendAction( "workout_activity_action", "load_models", { exercises: ["Jumping Jack", "Lunges"], exerciseFetchType: "exercise_title", // match the form you used initially } ); // Step 2 — wait for models_loaded, then switch const handleMessage = (type: string, data: { [key: string]: any }) => { if (type === "models_loaded" && data.modelIds?.includes("Jumping Jack")) { // Step 3 — switch the active exercise. Do NOT repeat exerciseFetchType here. sdkRef.current?.changeExercise("Jumping Jack"); } if (type === "error_occurred") { console.warn("KinesteX error:", data.message); } }; ``` ##### Preloading & Events Two events fire as the component initializes. Wait for **both** before revealing the camera UI to the user. | Event | Meaning | |---|---| | `model_warmedup` | Pose-tracking (MediaPipe) model is ready | | `models_loaded` | All exercise models in `exercises` have downloaded | **Pattern:** mount the camera hidden (e.g. `opacity: 0`) with a loader on top. Reveal once both events have fired. **Wait for Both Events** _Swift (iOS)_ ```swift @State private var modelWarmedUp = false @State private var modelsLoaded = false var isReady: Bool { modelWarmedUp && modelsLoaded } kinestex.createCameraView( exercises: ["3"], currentExercise: $currentExercise, user: nil, isLoading: $isLoading, onMessageReceived: { message in if case .custom_type(let value) = message, let type = value["type"] as? String { if type == "model_warmedup" { modelWarmedUp = true } if type == "models_loaded" { modelsLoaded = true } } } ) .opacity(isReady ? 1 : 0) ``` _Kotlin (Android)_ ```kotlin var modelWarmedUp = false var modelsLoaded = false val cameraView = KinesteXSDK.createCameraComponent( context = this, currentExercise = "3", exercises = listOf("3"), user = null, isLoading = viewModel.isLoading, onMessageReceived = { message -> if (message is WebViewMessage.CustomType) { when (message.data["type"] as? String) { "model_warmedup" -> modelWarmedUp = true "models_loaded" -> modelsLoaded = true } cameraView.alpha = if (modelWarmedUp && modelsLoaded) 1f else 0f } }, permissionHandler = this ) ``` _React Native_ ```jsx const [modelWarmedUp, setModelWarmedUp] = useState(false); const [modelsLoaded, setModelsLoaded] = useState(false); const isReady = modelWarmedUp && modelsLoaded; { if (type === 'model_warmedup') setModelWarmedUp(true); if (type === 'models_loaded') setModelsLoaded(true); }} /> ``` _Flutter_ ```dart bool modelWarmedUp = false; bool modelsLoaded = false; KinesteXAIFramework.createCameraComponent( isShowKinestex: showKinesteX, exercises: ["3"], currentExercise: "3", isLoading: ValueNotifier(false), onMessageReceived: (message) { if (message.type == 'model_warmedup') { setState(() => modelWarmedUp = true); } if (message.type == 'models_loaded') { setState(() => modelsLoaded = true); } }, ) ``` _HTML / JavaScript_ ```html let modelWarmedUp = false, modelsLoaded = false; const reveal = () => { if (modelWarmedUp && modelsLoaded) iframe.style.opacity = '1'; }; iframe.style.opacity = '0'; window.addEventListener("message", (event) => { if (event.origin !== "https://ai.kinestex.com") return; const msg = JSON.parse(event.data); if (msg.type === 'model_warmedup') { modelWarmedUp = true; reveal(); } if (msg.type === 'models_loaded') { modelsLoaded = true; reveal(); } }); ``` _React (TypeScript)_ ```tsx const [modelWarmedUp, setModelWarmedUp] = useState(false); const [modelsLoaded, setModelsLoaded] = useState(false); const isReady = modelWarmedUp && modelsLoaded;
{ if (type === 'model_warmedup') setModelWarmedUp(true); if (type === 'models_loaded') setModelsLoaded(true); }} />
``` ##### Controls **Switching exercises in real time.** Set `currentExercise` to any model ID from the `exercises` array — the camera component swaps tracking immediately. **Control commands.** Send any of these strings as `currentExercise` to control the session: | Command | Effect | |---|---| | `"Pause Exercise"` | Pauses motion tracking; rep counter freezes | | `"Pause Audio"` | Mutes voice feedback | | `"Resume Audio"` | Re-enables voice feedback | | `"Workout Overview"` | Triggers a summary snapshot for the current session | | `"Stop Camera"` | ⚠️ **Destructive** — releases camera + models, fires `stop_camera`. Not recoverable without re-mounting the component (see warning below) | To resume tracking after a pause, set `currentExercise` back to a real model ID from `exercises`. > ⚠️ **`"Stop Camera"` is destructive — avoid it for normal flows.** It tears down the camera, MediaPipe, and all loaded exercise models, then fires the `stop_camera` event. **You cannot recover from it without unmounting and re-creating the entire camera component.** Only send it when the user is permanently leaving the camera screen. For temporary pauses, use `"Pause Exercise"` instead. **Switch Exercise and Send Control Commands** _Swift (iOS)_ ```swift // Switch exercise (Swift uses two-way binding via @State) currentExercise = "394" // Pause / resume tracking currentExercise = "Pause Exercise" currentExercise = "3" ``` _Kotlin (Android)_ ```kotlin // Switch exercise KinesteXSDK.updateCurrentExercise("394") // Pause / resume tracking KinesteXSDK.updateCurrentExercise("Pause Exercise") KinesteXSDK.updateCurrentExercise("3") ``` _React Native_ ```jsx // Switch exercise kinestexSDKRef.current?.changeExercise("394"); // Pause / resume tracking kinestexSDKRef.current?.changeExercise("Pause Exercise"); kinestexSDKRef.current?.changeExercise("3"); ``` _Flutter_ ```dart // All controls go through your updateExercise ValueNotifier updateExercise.value = "394"; updateExercise.value = "Pause Exercise"; updateExercise.value = "3"; ``` _HTML / JavaScript_ ```html // Switch exercise webView.contentWindow.postMessage( { currentExercise: "394" }, srcURL ); // Pause / resume tracking webView.contentWindow.postMessage({ currentExercise: "Pause Exercise" }, srcURL); webView.contentWindow.postMessage({ currentExercise: "3" }, srcURL); ``` _React (TypeScript)_ ```tsx // Switch exercise ref.current?.changeExercise("394"); // Pause / resume tracking ref.current?.changeExercise("Pause Exercise"); ref.current?.changeExercise("3"); ``` ##### Customization Pass any of these fields in `customParams` (Swift / Kotlin / Flutter) or directly in `postData` (HTML/JS, React Native, React TS) at initialization. | Field | Type | Description | |---|---|---| | `restSpeeches` | `string[]` | Audio phrases to preload (from `ExerciseModel.rest_speech`) | | `videoURL` | `string` | Use a video file instead of the live camera | | `landmarkColor` | `string` | Pose overlay color in hex with `#` (default `#14FF00`) | | `showSilhouette` | `boolean` | Show "get into frame" silhouette (default `true`) | | `includeRealtimeAccuracy` | `boolean` | (Beta) Stream live position-confidence alongside reps | | `includePoseData` | `string[]` | Any of `"angles"`, `"poseLandmarks"`, `"worldLandmarks"`. **Performance impact** — only enable for custom calculations | ##### Event Reference All events the Camera Component emits to the host app: | Event | Payload | When | |---|---|---| | `model_warmedup` | `{ message }` | Pose model is ready | | `models_loaded` | `{ message }` | All exercise models in `exercises` finished downloading | | `person_in_frame` | `{ message }` | User entered the silhouette frame | | `successful_repeat` | `{ exercise, value, accuracy }` | A rep was counted (`value` = total reps so far) | | `mistake` | `{ value }` | Form mistake detected | | `correct_position_accuracy` | `{ accuracy }` | (Beta) Live position confidence — only when `includeRealtimeAccuracy: true` | | `pose_landmarks` | `{ poseLandmarks }` | Per-frame screen-space landmarks — only when `includePoseData` includes `"poseLandmarks"` | | `world_landmarks` | `{ worldLandmarks }` | Per-frame world-space landmarks — only when `includePoseData` includes `"worldLandmarks"` | | `speech_fetch_complete` | `{ successCount, failureCount }` | All `restSpeeches` finished loading | | `error_occurred` | `{ message }` or `{ data, error }` | Any error (model fetch fail, phrase fail, etc.) | | `warning` | `{ data }` | Non-fatal config issue (e.g. no model IDs provided) | | `stop_camera` | `{ message }` | Confirms the `"Stop Camera"` command finished | ##### Pose Data When `includePoseData` contains `"poseLandmarks"` or `"worldLandmarks"`, the camera emits per-frame events with raw skeleton data. **Only enable this if you're doing custom calculations — there is a performance cost.** **Two coordinate spaces:** - `poseLandmarks` — values 0–1, normalized to the camera frame. - `worldLandmarks` — meters, relative to the hips (best Z accuracy). Each landmark has `{ x, y, z, visibility }` (all 0–1). **Available landmarks** (same names in both spaces): `nose`, `leftEyeInner`, `leftEye`, `leftEyeOuter`, `rightEyeInner`, `rightEye`, `rightEyeOuter`, `leftEar`, `rightEar`, `mouthLeft`, `mouthRight`, `leftShoulder`, `rightShoulder`, `leftElbow`, `rightElbow`, `leftWrist`, `rightWrist`, `leftPinky`, `rightPinky`, `leftIndex`, `rightIndex`, `leftThumb`, `rightThumb`, `leftHip`, `rightHip`, `leftKnee`, `rightKnee`, `leftAnkle`, `rightAnkle`, `leftHeel`, `rightHeel`, `leftFootIndex`, `rightFootIndex`. **Available angles** (when `"angles"` is included — both 2D and 3D versions are emitted): `leftKneeAngle`, `rightKneeAngle`, `leftHipAngle`, `rightHipAngle`, `leftShoulderAngle`, `rightShoulderAngle`, `leftElbowAngle`, `rightElbowAngle`, `leftWristAngle`, `rightWristAngle`, `leftAnkleAngle`, `rightAnkleAngle`, `leftArmpitAngle`, `rightArmpitAngle`. ##### Complete Example Minimal working example with **Next** / **Previous** buttons that cycle between exercises and a live rep counter in the UI. **Complete Implementation** _Swift (iOS)_ ```swift import SwiftUI import KinesteXAIKit struct CameraScreen: View { let kinestex = KinesteXAIKit( apiKey: "YOUR_API_KEY", companyName: "YOUR_COMPANY_NAME", userId: "YOUR_USER_ID" ) // 3 = Squats, 394 = Jumping Jack let exerciseIds = ["3", "394"] @State private var index = 0 @State private var currentExercise = "3" @State private var reps = 0 @State private var isLoading = false var body: some View { VStack(spacing: 16) { Text("Reps: \(reps)") .font(.title) .padding(.top) kinestex.createCameraView( exercises: exerciseIds, currentExercise: $currentExercise, user: nil, isLoading: $isLoading, onMessageReceived: { message in if case .reps(let value) = message { reps = value["value"] as? Int ?? 0 } } ) HStack(spacing: 24) { Button("Previous") { switchTo(index - 1) } Button("Next") { switchTo(index + 1) } } .padding(.bottom) } } private func switchTo(_ newIndex: Int) { index = (newIndex + exerciseIds.count) % exerciseIds.count currentExercise = exerciseIds[index] reps = 0 } } ``` _Kotlin (Android)_ ```kotlin import android.Manifest import android.os.Bundle import android.widget.Button import android.widget.LinearLayout import android.widget.TextView import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import com.kinestex.kinestexsdkkotlin.GenericWebView import com.kinestex.kinestexsdkkotlin.KinesteXSDK import com.kinestex.kinestexsdkkotlin.PermissionHandler import com.kinestex.kinestexsdkkotlin.WebViewMessage import kotlinx.coroutines.flow.MutableStateFlow class CameraActivity : AppCompatActivity(), PermissionHandler { // 3 = Squats, 394 = Jumping Jack private val exerciseIds = listOf("3", "394") private var index = 0 private val isLoading = MutableStateFlow(false) private lateinit var camera: GenericWebView private lateinit var tvReps: TextView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_camera) tvReps = findViewById(R.id.tvReps) camera = KinesteXSDK.createCameraComponent( context = this, currentExercise = exerciseIds[0], exercises = exerciseIds, user = null, isLoading = isLoading, onMessageReceived = { msg -> if (msg is WebViewMessage.Reps) { val v = msg.data["value"] as? Int ?: 0 runOnUiThread { tvReps.text = "Reps: $v" } } }, permissionHandler = this ) as GenericWebView findViewById(R.id.cameraContainer).addView(camera) findViewById ``` _React (TypeScript)_ ```tsx import { useRef, useState } from 'react'; import { IntegrationOption, KinesteXSDK, type IPostData, type KinesteXSDKCamera, } from 'kinestex-sdk-react-ts'; // 3 = Squats, 394 = Jumping Jack const exerciseIds = ['3', '394']; export default function CameraScreen() { const ref = useRef(null); const [index, setIndex] = useState(0); const [reps, setReps] = useState(0); const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'YOUR_USER_ID', company: 'YOUR_COMPANY_NAME', currentExercise: exerciseIds[0], exercises: exerciseIds, style: { style: 'dark' }, }; const switchTo = (newIndex: number) => { const wrapped = (newIndex + exerciseIds.length) % exerciseIds.length; setIndex(wrapped); setReps(0); ref.current?.changeExercise(exerciseIds[wrapped]); }; return (

Reps: {reps}

{ if (type === 'successful_repeat') setReps(data.value as number); }} />
); } ``` #### Admin Workout Editor Opens the KinesteX admin editor for content management. Use this to provide admins or content creators with direct access to workout and exercise management tools. **Parameters:** - `organization` (required): Your organization identifier - `contentType` (optional): `.workout`, `.plan`, or `.exercise` - `contentId` (optional): Specific content ID to edit - `customQueries` (optional): Additional query parameters **Note:** This feature is currently available for Swift SDK only. **Admin Workout Editor Setup** _Swift (iOS)_ ```swift // Open main admin dashboard kinestex.createAdminWorkoutEditor( organization: "YourOrg", isLoading: $isLoading, onMessageReceived: { message in switch message { case .exit_kinestex(_): showEditor = false default: print("Message received: \(message)") } } ) // Open specific workout for editing kinestex.createAdminWorkoutEditor( organization: "YourOrg", contentType: .workout, contentId: "workout123", isLoading: $isLoading, onMessageReceived: { message in /* handle messages */ } ) // Open specific exercise for editing kinestex.createAdminWorkoutEditor( organization: "YourOrg", contentType: .exercise, contentId: "exercise456", customQueries: ["language": "en"], isLoading: $isLoading, onMessageReceived: { message in /* handle messages */ } ) ``` ##### Complete Example Full implementation with state management and navigation to the admin editor. **Complete Admin Editor Implementation** _Swift (iOS)_ ```swift import SwiftUI import KinesteXAIKit struct AdminEditorView: View { @State private var showEditor = false @State private var isLoading = false // Initialize KinesteXAIKit with your credentials let kinestex = KinesteXAIKit( apiKey: "YOUR_API_KEY", companyName: "YOUR_COMPANY_NAME", userId: "YOUR_USER_ID" ) var body: some View { VStack { Text("Admin Editor") .font(.title) .padding() Spacer() Button(action: { showEditor.toggle() }) { Text("Open Admin Editor") .font(.title3) .foregroundColor(.white) .bold() .padding() .frame(maxWidth: .infinity) .background(Color.blue.cornerRadius(10)) .padding(.horizontal) } Spacer() } .fullScreenCover(isPresented: $showEditor) { kinestex.createAdminWorkoutEditor( organization: "YourOrg", contentType: nil, contentId: nil, customQueries: nil, isLoading: $isLoading, onMessageReceived: { message in switch message { case .exit_kinestex(_): showEditor = false case .error_occurred(let data): print("Error: \(data)") default: print("Message received: \(message)") } } ) } } } #Preview { AdminEditorView() } ``` --- ## Data Points PostMessage events sent from KinesteX SDK for integration with native apps and external systems. Events are sent in real-time, work safely offline, and provide comprehensive tracking for workouts, exercises, and health assessments. ### Receiving Data Each platform has a specific pattern for receiving data from KinesteX. **SDK Platforms (Swift, Kotlin, React Native, React-TS, Flutter):** Use the callback function provided by the SDK with typed message enums. **HTML/JS:** Set up a message event listener manually since there's no SDK wrapper. **Platform-Specific Data Handlers** _Flutter_ ```dart void handleWebViewMessage(WebViewMessage message) { if (message is KinestexLaunched) { print("KinesteX launched at: ${message}"); } else if (message is ExitKinestex) { print("Exited KinesteX at: ${message} seconds."); } else if (message is PlanUnlocked) { print("Plan Unlocked: ${message}"); } else if (message is WorkoutOpened) { print("Workout Opened: ${message}"); } else if (message is WorkoutStarted) { print("Workout Started: ${message}"); } else if (message is ExerciseCompleted) { print("Exercise: ${message}"); } else if (message is TotalActiveSeconds) { print("Active seconds: ${message}"); } else if (message is LeftCameraFrame) { print("User left camera frame at time: ${message}"); } else if (message is ReturnedCameraFrame) { print("User returned to camera frame at time: ${message}"); } else if (message is WorkoutOverview) { print("Workout Overview: ${message}"); } else if (message is ExerciseOverview) { print("Exercise Overview: ${message}"); } else if (message is WorkoutCompleted) { print("Workout Completed: ${message}"); } else { print("Other data points: ${message.data}"); } } ``` _React Native_ ```jsx const handleMessage = (type: string, data: { [key: string]: any }) => { switch (type) { case "kinestex_launched": console.log('Launched at:', data); break; case "exit_kinestex": console.log('Exited, time spent:', data.time_spent); break; case "workout_overview": console.log('Workout stats:', data); break; case "exercise_completed": console.log('Exercise done:', data.exercise_title); break; case "workout_completed": console.log('Workout finished:', data); break; case "error_occurred": console.error('Error:', data); break; default: console.log('Other message type:', type, data); break; } }; ``` _React (TypeScript)_ ```tsx const handleMessage = (type: string, data: { [key: string]: any }) => { switch (type) { case "kinestex_launched": console.log('Launched at:', data); break; case "exit_kinestex": console.log('Exited, time spent:', data.time_spent); break; case "workout_overview": console.log('Workout stats:', data); break; case "exercise_completed": console.log('Exercise done:', data.exercise_title); break; case "workout_completed": console.log('Workout finished:', data); break; case "error_occurred": console.error('Error:', data); break; default: console.log('Other message type:', type, data); break; } }; ``` _Kotlin (Android)_ ```kotlin private fun handleWebViewMessage(message: WebViewMessage) { when (message) { is WebViewMessage.KinestexLaunched -> { println("KinesteX launched at: $message") } is WebViewMessage.FinishedWorkout -> { println("Finished Workout: $message") } is WebViewMessage.ErrorOccurred -> { println("Error Occurred: $message") } is WebViewMessage.ExerciseCompleted -> { println("Exercise Completed: $message") } is WebViewMessage.ExitKinestex -> { println("Exited KinesteX at: $message") } is WebViewMessage.WorkoutOpened -> { println("Workout Opened: $message") } is WebViewMessage.WorkoutStarted -> { println("Workout Started: $message") } is WebViewMessage.PlanUnlocked -> { println("Plan Unlocked: $message") } is WebViewMessage.WorkoutOverview -> { println("Workout Overview: $message") } is WebViewMessage.ExerciseOverview -> { println("Exercise Overview: $message") } is WebViewMessage.WorkoutCompleted -> { println("Workout Completed: $message") } // Camera Component Specific is WebViewMessage.Reps -> { println("Reps: $message") } is WebViewMessage.Mistake -> { println("Mistake: $message") } is WebViewMessage.CustomType -> { println("Any other message: $message") } } } ``` _Swift (iOS)_ ```swift // onMessageReceived callback passes WebViewMessage enum // Available message types: // kinestex_launched([String: Any]) - KinesteX View launched // finished_workout([String: Any]) - Workout completed // error_occurred([String: Any]) - Errors (e.g., missing camera) // exercise_completed([String: Any]) - Exercise finished // exit_kinestex([String: Any]) - User exits KinesteX view // workout_opened([String: Any]) - Workout description viewed // workout_started([String: Any]) - Workout begins // plan_unlocked([String: Any]) - Workout plan unlocked // custom_type([String: Any]) - Unrecognized messages // reps([String: Any]) - Successful repetitions // mistake([String: Any]) - Detected mistakes // left_camera_frame([String: Any]) - User left camera frame // returned_camera_frame([String: Any]) - User returned to frame // workout_overview([String: Any]) - Workout summary // exercise_overview([String: Any]) - Exercise summary // workout_completed([String: Any]) - Workout done, overview exited ``` _HTML / JavaScript_ ```html // HTML/JS requires manual event listener setup window.addEventListener("message", (event) => { // Security: only accept messages from KinesteX if (event.origin !== "https://ai.kinestex.com") return; try { const message = JSON.parse(event.data); switch (message.type) { case "kinestex_launched": console.log("Launched:", message.data); break; case "exit_kinestex": console.log("Exited, time spent:", message.time_spent); break; case "workout_overview": console.log("Workout stats:", message.data); break; case "exercise_completed": console.log("Exercise done:", message.data); break; case "error_occurred": console.error("Error:", message.data || message.message); break; default: console.log("Message:", message.type, message); } } catch (e) { console.error("Failed to parse message:", e); } }); ``` ### Application Lifecycle Events for app startup, loading, and exit. | Event | Data Fields | Description | |-------|-------------|-------------| | kinestex_launched | data: string ("dd mm yyyy hh:mm:ss") | KinesteX application is launched | | kinestex_loaded | date: string (ISO format) | KinesteX fully loaded and ready | | exit_kinestex | date: Date, time_spent: string ("hh:mm:ss") | User exits with total time spent | | main_page_opened | date: string (ISO format) | Main/home page is opened | | home_page_opened | date: string (ISO format) | Home Page integration screen opened. Fires exactly once per mount | | streak_extended | data: object | User's daily streak was extended by completing a qualifying activity (workout, challenge, plan day, assessment) | **streak_extended Data Structure:** ``` { current_streak: number, // Current streak count (days) longest_streak: number, // User's longest streak ever last_activity_date: string // ISO date of the activity that extended the streak } ``` ### Workout Events Events for workout lifecycle and statistics. | Event | Data Fields | Description | |-------|-------------|-------------| | workout_opened | title: string, id: string, date: string | Workout details page opened | | workout_started | id: string, date: string | Workout session started | | workout_started (alt) | workoutId: string | Alternative workout start format | | workout_completed | workout: string, date: string | Workout finished, user exits overview | | workout_ended | id: string, exit_type: string, date: string | Workout session ended (see exit_type values below) | | workout_overview | data: object | Complete workout summary statistics | **workout_ended exit_type values:** | Value | Meaning | |-------|---------| | complete | User finished the entire workout including outro | | exit | User abandoned the workout mid-session | | outro | User exited from the outro/cooldown screen after completing all exercises | **workout_overview Data Structure:** ``` { workout_title: string, // Workout name workout_id: string, // Unique workout ID target_duration_seconds: number, // Target workout duration (seconds) workout_duration_seconds: number, // Total wall-clock session time // (includes rest, transitions, pauses). // For challenges/assessments this equals // total_time_spent (no wall-clock concept) total_time_spent: number, // Active exercise time only (seconds) completed_reps_count: number, // Total completed reps target_reps_count: number, // Total target reps calories_burned: number, // Calories (2 decimal places) completion_percentage: number, // Completion % (2 decimals) total_mistakes: number, // Total mistake count accuracy_score: number, // Overall accuracy (0-100) efficiency_score: number, // Efficiency metric (0-100) total_exercise: number, // Number of exercises actual_hold_time_seconds: number, // Time in correct position target_hold_time_seconds: number // Target hold time } ``` **Note:** Use `workout_duration_seconds` to display or log the full session time (including rest periods). Use `total_time_spent` if you only need active exercise time. **Handling Workout Overview** _Swift (iOS)_ ```swift case .workout_overview(let data): if let calories = data["calories_burned"] as? Double, let completion = data["completion_percentage"] as? Double { print("Burned \(calories) cal, \(completion)% complete") } ``` _Kotlin (Android)_ ```kotlin is WebViewMessage.WorkoutOverview -> { val calories = message.caloriesBurned val completion = message.completionPercentage Log.d("Workout", "Burned $calories cal, $completion% complete") } ``` _React Native_ ```jsx case "workout_overview": const { calories_burned, completion_percentage, accuracy_score } = data; console.log(`Workout: ${completion_percentage}% complete`); console.log(`Calories: ${calories_burned}, Accuracy: ${accuracy_score}`); break; ``` _Flutter_ ```dart if (message is WorkoutOverview) { print("Calories: ${message.caloriesBurned}"); print("Completion: ${message.completionPercentage}%"); print("Accuracy: ${message.accuracyScore}"); } ``` _HTML / JavaScript_ ```html case "workout_overview": const stats = message.data; console.log("Workout:", stats.workout_title); console.log("Calories:", stats.calories_burned); console.log("Accuracy:", stats.accuracy_score); break; ``` _React (TypeScript)_ ```tsx case "workout_overview": const { calories_burned, completion_percentage, accuracy_score } = data; console.log(`Workout: ${completion_percentage}% complete`); console.log(`Calories: ${calories_burned}, Accuracy: ${accuracy_score}`); break; ``` ### Exercise Events Events for individual exercise tracking. | Event | Description | |-------|-------------| | exercise_completed | Individual exercise completed | | exercise_overview | All exercises summary (array) | **exercise_completed Data Structure:** ``` { exercise_title: string, // Exercise name time_spent: number, // Seconds spent repeats: number, // Reps completed total_reps: number, // Required reps total_duration: number, // Countdown time perfect_hold_position: number, // Time in perfect hold position (seconds). // 0 for non-hold exercises calories: number, // Calories burned exercise_id: string, // Exercise ID exercise_index: number, // 1-based position of the completed exercise total_exercises: number, // Total number of exercises in the workout mistakes: Array<{ // Mistakes made mistake: string, count: number }>, average_accuracy?: number // Average accuracy (0-1, optional) } ``` **exercise_overview Item Structure:** ``` { exercise_title: string, // Exercise name exercise_id: string, // Unique exercise ID time_spent: number, // Time on exercise (seconds) perfect_hold_position: number, // Time in correct position (timer-based) repeats: number, // Reps completed total_required_reps: number, // Target reps total_required_time: number, // Target time (seconds) calories: number, // Calories (2 decimal places) mistakes: Array<{ // Detailed mistake breakdown mistake: string, count: number }>, mistake_count: number, // Total mistakes for exercise accuracy_reps?: number[], // Per-rep accuracy scores (optional) average_accuracy?: number // Average accuracy 0-100 (optional) } ``` ### Camera & Frame Events Events for camera tracking and frame detection. | Event | Data Fields | Description | |-------|-------------|-------------| | left_camera_frame | date: string ("dd mm yyyy hh:mm:ss") | User left camera view | | returned_camera_frame | date: string ("dd mm yyyy hh:mm:ss") | User returned to camera view | | check_frame_completed | message: string ("Person stepped into frame") | Frame check completed | | camera_selector_opened | message: array (available cameras) | Camera selector opened | | camera_selected | id: string, label: string, isMirrorCamera: boolean | Camera selected by user | ### Plans & Programs Events for workout plans and programs. | Event | Data Fields | Description | |-------|-------------|-------------| | plan_unlocked | id: string, img: string, title: string, date: string | Plan unlocked/selected | | plan_opened | id: string | Plan result screen rendered. Fires for both goal-based and personalized plans | | plan_onboarding_plan_created | data: { plan_id: string, plan_type: string } | A new plan was created from onboarding/assessment. Does NOT fire on revisits to an existing personalized plan | | plan_progression_saved | data: object | Plan day progression was saved successfully after a plan workout finished | | plan_progression_failed | data: object | Plan progression save failed (network/server error) | | personalized_plan_exit | workout: string, date: string | Exit from personalized plan | | remind_me_later_clicked | - | User tapped "Remind me later" on the Assessment screen inside the plan-onboarding flow. Only fires from the plan-onboarding page | **Note on plan tracking:** When you launch a plan workout directly via the SDK, you can pass `planId`, `planType`, and `progressWorkoutId` in the initial PostMessage configuration so the SDK associates the workout session with the correct plan. After the workout finishes, you'll receive either `plan_progression_saved` (success) or `plan_progression_failed` (error). See [Plan Context configuration](/docs/customization-parameters#plan-context) for details. ### Challenge Events Events for challenge mode. | Event | Data Fields | Description | |-------|-------------|-------------| | challenge_started | exerciseId: string | Challenge exercise started | | challenge_completed | repCount: number, mistakes: number | Challenge completed | | challenge_exit | workout: string, date: string | Exit from challenge | ### Leaderboard Events Events for leaderboard functionality. | Event | Data Fields | Description | |-------|-------------|-------------| | highlighted_user | data: object | User's leaderboard position | **highlighted_user Data Structure:** ``` { username: string, // User's username score: number, // User's score position: number // Leaderboard position (1-based) } ``` ### Navigation Events Events for app navigation. | Event | Data Fields | Description | |-------|-------------|-------------| | kinestex_home_exit | workout: string, date: string | Exit from KinesteX home | | navigation_back | data: { exercise_index: number, total_exercises: number } | User navigated back to a previous exercise during a workout | ### Feedback Events Events dispatched when a user submits feedback. | Event | Description | |-------|-------------| | feedback_submitted | User submitted training rating or per-exercise feedback | **Training Feedback Payload (source: "training_feedback"):** ``` { type: "feedback_submitted", source: "training_feedback", rating: number, // User rating (e.g., 1-5) is_like: boolean, // Whether the user liked the workout description: string, // Optional text feedback workout_id: string, // Workout ID workout_title: string // Workout name } ``` **Per-Exercise Feedback Payload (source: "exercise_feedback"):** ``` { type: "feedback_submitted", source: "exercise_feedback", workout_id: string, // Workout ID workout_title: string, // Workout name feedbacks: Array<{ // Per-exercise feedback entries exercise_id: string, // Exercise ID exercise_title: string, // Exercise name is_like: boolean, // Whether the user liked the exercise description: string // Optional text feedback }> } ``` ### Session & Upload Events Events related to workout session saving and motion recording uploads. These events are only dispatched when \`shouldSendStats: true\` is passed in the SDK configuration. | Event | Data Fields | Description | |-------|-------------|-------------| | workout_session_saved | data: object | Workout session successfully saved to backend | | session_save_complete | - | Motion recording uploads finished successfully | | motion_upload_progress | data: { completed: number, total: number } | Motion recording upload progress | | motion_upload_error | data: { error: string } | Motion recording upload failed or timed out | | workout_completion_overlay_dismissed | - | User dismissed the workout completion celebration overlay | **workout_session_saved Data Structure:** ``` { session_id: number, // Backend-assigned session ID workout_title: string, // Name of the workout accuracy_score: number, // Overall accuracy (0-100) efficiency_score: number, // Efficiency score (0-100) completion_percentage: number, // How much of the workout was completed (0-100) completed_reps_count: number, // Total reps completed calories_burned: number // Estimated calories burned } ``` **motion_upload_progress Data Structure:** ``` { completed: number, // Number of exercise recordings uploaded so far total: number // Total number of exercise recordings to upload } ``` ### Error & Status Events Events for errors, warnings, and active time tracking. | Event | Data Fields | Description | |-------|-------------|-------------| | error_occurred | data: string | General error message | | error_occurred | message: string | Alternative error format | | error_occurred | data: string, error: any | Error with details | | warning | data: string | Warning message | | total_active_seconds | number | Active workout time (sent every 5s, pauses when user leaves camera frame) | | ios_video_fallback_activated | reason, videoUrl, readyState, userAgent | iOS detected a stuck video decoder and switched to image-based playback. The exercise still runs in a degraded-but-functional mode (no native video controls) | **ios_video_fallback_activated Payload:** ``` { type: "ios_video_fallback_activated", reason: string, // e.g. "video_stuck_at_metadata" videoUrl: string, // URL of the affected video readyState: number, // HTMLMediaElement readyState at the time of fallback userAgent: string // Device user agent string } ``` Use this event for analytics or to surface a notice in your UI when iOS playback degrades. Only listen for it if you need visibility into iOS playback issues — no integrator action is required. ### Assessment Exit Events Events dispatched when a user exits an assessment before completing it. | Event | Data Fields | Description | |-------|-------------|-------------| | assessment_exit | exerciseId: string | User exited the assessment early (before results) | | assessment_exit_results | exerciseId: string | User exited the assessment from the results screen | **assessment_exit Payload:** ``` { type: "assessment_exit", data: { exerciseId: string // The assessment exercise identifier } } ``` **assessment_exit_results Payload:** ``` { type: "assessment_exit_results", data: { exerciseId: string // The assessment exercise identifier } } ``` Use \`assessment_exit\` to detect when users abandon an assessment mid-session, and \`assessment_exit_results\` to detect when they leave after viewing their results. ### Assessment Overview AI-powered health assessments with two event types: - **assessment_overview**: Sent when results page loads - **assessment_completed**: Sent when user clicks restart or finish **Common Fields (All Assessments):** | Field | Type | Description | |-------|------|-------------| | type | string | "assessment_overview" or "assessment_completed" | | assessmentType | string | Assessment identifier (e.g., "tug", "sls") | | date | Date | Timestamp when completed | | time | number | Total assessment time (seconds) | | steps | number? | Estimated step count (walking assessments) | **Assessment Types:** - **Mobility**: TUG (tug), Gait Speed Test (gaitspeedtest) - **Balance**: SLS (sls), SBSS (sbss), STSS (stss), Full Tandem (fulltandem) - **Functional**: STS (sts), Five Times STS (fivetimessts), FRT (frt) - **Range of Motion**: Shoulder ROM (romshoulder) - **Games**: Balloon Pop (balloonpop), Color Chase (colorchase), Alien Squat Shooter (aliensquatshooter) **Risk Levels:** | Value | Meaning | |-------|---------| | low | Good performance, minimal fall/balance risk | | moderate | Some limitations, may benefit from training | | high | Significant limitations, balance training recommended | ### Mobility Assessments **TUG (Timed Up and Go) - assessmentType: "tug"** Stand-walk-turn-return-sit timing test. | Field | Type | Description | |-------|------|-------------| | time | number | Total completion time (seconds) | | steps | number | Estimated step count | | standingUpTime | number | Time to stand from seated (seconds) | | sittingDownTime | number | Time to sit at end (seconds) | | walkingForwardTime | number | Time walking to 3m marker (seconds) | | walkingBackwardTime | number | Time walking back (seconds) | | turningTime | number | Time spent turning (seconds) | | backBendingAngleSitting | number[] | Back angles during sitting countdown (degrees) | | backBendingAngleStanding | number[] | Back angles during movement (degrees) | | averageSpeedMs_tug | number | Average walking speed. Formula: 6m / time | | avgBackBendingSitting | number? | Average back angle sitting (degrees) | | avgBackBendingStanding | number? | Average back angle standing (degrees) | --- **Gait Speed Test - assessmentType: "gaitspeedtest"** Walking speed measurement over 4 meters. | Field | Type | Description | |-------|------|-------------| | time | number | Total test time (seconds) | | steps | number | Estimated step count | | standingTime | number | Time in standing phase (seconds) | | walkingTime | number | Active walking time (seconds) | | averageGaitSpeed | number | Gait speed. Formula: 4m / time | ### Balance Assessments **SLS (Single Leg Stand) - assessmentType: "sls"** | Field | Type | Description | |-------|------|-------------| | rightTime | number | Duration on right leg (seconds) | | leftTime | number | Duration on left leg (seconds) | | symmetryScore_sls | number? | Leg symmetry % (0-100). Formula: (min/max) * 100 | | riskLevel_sls | string | "low", "moderate", or "high" | **Risk Calculation:** - Low: Min time >=20s AND symmetry good (difference <=5s) - Moderate: Min time >=10s AND average >=15s - High: All other cases --- **SBSS (Side-by-Side Stand) - assessmentType: "sbss"** | Field | Type | Description | |-------|------|-------------| | timeInProperPosition_sbss | number | Time in correct stance (max 10s) | | maxShoulderShift_sbss | number | Max lateral shoulder shift (% of shoulder width) | | maxHipShift_sbss | number | Max lateral hip shift (% of hip width) | | feetMoved_sbss | boolean | Whether feet moved | | riskLevel_sbss | string | "low", "moderate", or "high" | **Risk Calculation:** - High: Feet moved OR (time <7s AND sway >=30%) - Moderate: Time >=7s AND sway <30% - Low: Time >=9.5s AND sway <15% --- **STSS (Semi-Tandem Stand) - assessmentType: "stss"** | Field | Type | Description | |-------|------|-------------| | timeInProperPosition_stss | number | Time in correct stance (max 10s) | | maxShoulderShift_stss | number | Max shoulder shift (% of width) | | maxHipShift_stss | number | Max hip shift (% of projection) | | feetMoved_stss | boolean | Whether feet moved | | riskLevel_stss | string | "low", "moderate", or "high" | Risk calculation same as SBSS. --- **Full Tandem Stand - assessmentType: "fulltandem"** | Field | Type | Description | |-------|------|-------------| | time | number | Total test time (seconds) | | timeInProperPosition_fulltandem | number | Time in heel-to-toe stance (max 10s) | | maxShoulderShift_fulltandem | number | Max shoulder shift (%) | | maxHipShift_fulltandem | number | Max hip shift (%) | | feetMoved_fulltandem | boolean | Whether feet moved | | testFailed_fulltandem | boolean | Whether test was terminated early | | terminationReason_fulltandem | string? | Reason for early termination | | riskLevel_fulltandem | string | "low", "moderate", or "high" | **Risk Calculation:** - Low: Time >=10s AND no feet movement - Moderate: Time >=5s - High: Time <5s ### Functional Assessments **STS (30-Second Sit-to-Stand) - assessmentType: "sts"** | Field | Type | Description | |-------|------|-------------| | reps | number | Total reps completed in 30 seconds | | averageSittingTime | number | Average time sitting per rep (seconds) | | averageStandingTime | number | Average time standing per rep (seconds) | | avgTimePerRep_sts | number? | Average seconds per rep. Formula: 30 / reps | | repTimeVariance_minRepTime | number? | Fastest rep time (seconds) | | repTimeVariance_maxRepTime | number? | Slowest rep time (seconds) | | repTimeVariance_minRepIndex | number? | Which rep was fastest (1-indexed) | | repTimeVariance_maxRepIndex | number? | Which rep was slowest (1-indexed) | --- **Five Times STS - assessmentType: "fivetimessts"** | Field | Type | Description | |-------|------|-------------| | time | number | Total completion time for 5 reps (seconds) | | averageSittingTime | number | Average time sitting (seconds) | | averageStandingTime | number | Average time standing (seconds) | --- **FRT (Functional Reach Test) - assessmentType: "frt"** | Field | Type | Description | |-------|------|-------------| | reach | number | Maximum forward reach (cm) | | maxHeelLift | number | Maximum heel lift detected (cm) | | heelLiftCount | number | Count of heel lift violations | | legLiftCount | number | Count of leg lift violations | | testCompleted | boolean | Whether test finished normally | | endReason | string? | Reason for early termination | | riskLevel_frt | string? | "low", "moderate", or "high" | **End Reason Values:** - "Feet moved out of zone" - "User left zone" - "User turned forward" - "Arm dropped completely" - "Reference lost" - "Unhandled state" **Risk Calculation:** - High: Test not completed OR reach <=15cm - Moderate: Reach 15-25cm - Low: Reach >25cm ### Range of Motion Assessments **Shoulder ROM - assessmentType: "romshoulder"** A quick range-of-motion check for the shoulders. The user stands facing the camera and lifts one arm at a time straight out to the side (shoulder abduction), as high as comfortably possible. The system tracks the peak abduction angle reached by each arm, compares left and right sides, and flags meaningful asymmetry. Useful for tracking recovery, spotting asymmetry, and observing mobility improvements session over session. | Field | Type | Description | |-------|------|-------------| | romMovement | string | Type of ROM movement assessed. Primary value: "shoulder_abduction" | | romMaxLeft | number | Maximum ROM achieved on the left side (degrees, rounded to 0 decimals, range 0-180+) | | romMaxRight | number | Maximum ROM achieved on the right side (degrees, rounded to 0 decimals, range 0-180+) | | romMinLeft | number | Minimum ROM recorded on the left side (degrees, rounded to 0 decimals, range 0-180+) | | romMinRight | number | Minimum ROM recorded on the right side (degrees, rounded to 0 decimals, range 0-180+) | | romSymmetryDelta | number | Absolute difference between max left and max right (degrees). Formula: abs(romMaxLeft - romMaxRight) | | romSymmetryFlagDegrees | number | Asymmetry threshold (degrees). Defaults to 10 if not provided | | romAsymmetric | boolean | true if romSymmetryDelta > romSymmetryFlagDegrees, else false | **Notes:** - All angle measurements are in degrees. - Numeric values are rounded to 0 decimal places. - `romAsymmetric` is computed by comparing `romSymmetryDelta` against `romSymmetryFlagDegrees`. ### Game Assessments **Balloon Pop - assessmentType: "balloonpop"** | Field | Type | Description | |-------|------|-------------| | gameScore | number | Total balloons popped | | averageReactionTime | string | Average time to pop (seconds) | | maxIdleTime | string | Max time without action (seconds) | | averageSpeed_balloonpop | number | Balloons/second. Formula: gameScore / 30 | | masteryTitle_balloonpop | string | Achievement tier | **Mastery Titles:** | Score | Title | |-------|-------| | >=30 | pop_tastic_hero | | >=25 | magic_popper | | >=20 | super_duper_popper | | >=15 | sparkly_popper | | >=10 | giggly_popper | | >=5 | bouncy_bubbler | | <5 | tiny_popper | --- **Color Chase - assessmentType: "colorchase"** | Field | Type | Description | |-------|------|-------------| | gameScore | number | Total score achieved | | levelReached | number | Highest level completed | | totalDuration | string | Total game duration (seconds) | | averageReactionTime | string | Average reaction time per tap (seconds) | | maxIdleTime | string | Max time between taps (seconds) | | masteryTitle_colorchase | string | Achievement tier | **Mastery Titles:** | Level | Title | |-------|-------| | >=10 | color_chase_legend | | >=8 | magic_color_wizard | | >=6 | super_color_star | | >=4 | shiny_sequencer | | >=2 | rainbow_chaser | | >=1 | color_buddy | | <1 | little_color_finder | --- **Alien Squat Shooter - assessmentType: "aliensquatshooter"** | Field | Type | Description | |-------|------|-------------| | gameScore | number | Total aliens destroyed | | squatsPerformed | number | Number of squats completed | | totalDuration | string | Total game duration (seconds) | | averageSquatRate | string | Squats per second | | maxTimeBetweenSquats | string | Max idle between squats (seconds) | | averageAlienDestroyTime | string | Average time to destroy alien (seconds) | | masteryTitle_aliensquatshooter | string | Achievement tier | **Mastery Titles:** | Score | Title | |-------|-------| | >=25 | alien_annihilator | | >=20 | cosmic_blaster | | >=15 | star_shooter | | >=10 | galactic_gunner | | >=5 | space_squatter | | <5 | rookie_defender | --- **Health Benefits (All Games)** Included in payload for all game types. ``` healthBenefits: { heartDiseaseReduction: number, // Estimated % reduction (max 15) diabetesReduction: number, // Estimated % reduction (max 20) obesityReduction: number, // Estimated % reduction (max 10) depressionReduction: number // Estimated % reduction (max 15) } ``` **Health Benefit Calculation:** `reduction = min((gameScore / 100) * maxReduction, maxReduction)` ### Event Flow Examples **Complete Workout Flow:** 1. `kinestex_launched` - Application starts 2. `workout_opened` - User views workout details 3. `workout_started` - Workout begins 4. `returned_camera_frame` / `left_camera_frame` - Frame tracking 5. Multiple `exercise_completed` - Each exercise finished 6. `workout_overview` - Summary statistics 7. `exercise_overview` - All exercises summary 8. `workout_completed` - User exits statistics 9. `exit_kinestex` - Application closed **Challenge Flow:** 1. `challenge_started` - Challenge begins 2. `exercise_completed` - Exercise finished 3. `challenge_completed` - Challenge complete 4. `challenge_exit` - Exit from challenge **Assessment Flow:** 1. `kinestex_launched` - Application starts 2. Assessment performed (user follows on-screen instructions) 3. `assessment_overview` - Results page loads with all metrics 4. `assessment_completed` - User clicks restart or finish 5. `assessment_exit_results` - User exits from results screen (or `assessment_exit` if they exit early) 6. `exit_kinestex` - Application closed --- ## Customization Parameters This section describes all available customization parameters that can be passed to the KinesteX SDK to customize the user experience. Parameters are organized by category, with examples showing which have direct SDK support vs. requiring `customParams`. **Parameter Passing Methods:** - **Direct SDK Support**: Pass directly to SDK initialization or view creation methods - **customParams / customParameters**: Additional parameters passed via a custom parameters object - **HTML/JS postData**: All parameters passed as flat object via postMessage API ### Required Parameters These parameters are mandatory for successful SDK initialization. | Parameter | Type | Description | |-----------|------|-------------| | userId | string | Unique identifier for the user. Must be at least 2 characters. Used for tracking progress, analytics, and personalization | | company | string | Company name associated with the API key. Determines theme defaults and content access | | key | string | API key for authentication. Required for all API calls and content access | **SDK Support:** All platforms support these as direct parameters. **Required Parameters Setup** _Swift (iOS)_ ```swift // Direct SDK support let kinestex = KinesteXAIKit( apiKey: "YOUR_API_KEY", companyName: "YOUR_COMPANY", userId: "unique-user-id" ) ``` _Kotlin (Android)_ ```kotlin // Direct SDK support KinesteXSDK.initialize( context = this, apiKey = "YOUR_API_KEY", companyName = "YOUR_COMPANY", userId = "unique-user-id" ) ``` _React Native_ ```jsx // Direct support in postData const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'unique-user-id', company: 'YOUR_COMPANY', }; ``` _Flutter_ ```dart // Direct SDK support await KinesteXAIFramework.initialize( apiKey: "YOUR_API_KEY", companyName: "YOUR_COMPANY", userId: "unique-user-id", ); ``` _HTML / JavaScript_ ```html // Direct in postData object const postData = { userId: "unique-user-id", company: "YOUR_COMPANY", key: "YOUR_API_KEY", }; ``` _React (TypeScript)_ ```tsx // Direct support in postData const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'unique-user-id', company: 'YOUR_COMPANY', }; ``` ### User Profile Parameters that define user characteristics for personalized content and recommendations. | Parameter | Type | Description | Effect | |-----------|------|-------------|--------| | age | number | User's age in years | Affects workout intensity recommendations and exercise selection | | gender | string | User's gender (male, female, other) | Affects BMI calculations and content personalization | | height | number | User's height (in cm or inches based on locale) | Used for BMI calculation and exercise calibration | | weight | number | User's weight (in kg or lbs based on locale) | Used for BMI calculation and calorie estimations | | fitness_level | string | User's fitness level | Determines workout difficulty and progression | | lifestyle | string | User's lifestyle type (e.g., sedentary, active) | Affects personalized plan recommendations | | body_parts | string[] | Target body parts for workouts | Filters and prioritizes exercises targeting specific areas | | plan_type | string | Type of workout plan | Determines the structure and focus of generated plans | **SDK Support:** Most platforms have direct support via UserDetails object or postData fields. **User Profile Configuration** _Swift (iOS)_ ```swift // Direct SDK support via UserDetails let user = UserDetails( age: 30, height: 180, weight: 75, gender: .Male, lifestyle: .Active ) kinestex.createView( user: user, // ... other params ) ``` _Kotlin (Android)_ ```kotlin // Direct SDK support via UserDetails val userDetails = UserDetails( age = 30, height = 180, weight = 75, gender = Gender.MALE, lifestyle = Lifestyle.ACTIVE ) KinesteXSDK.createView( user = userDetails, // ... other params ) ``` _React Native_ ```jsx // Direct support in postData const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', // User profile - direct support age: 30, height: 180, // cm weight: 75, // kg gender: 'Male', lifestyle: Lifestyle.Active, }; ``` _Flutter_ ```dart // Direct SDK support via UserDetails final userDetails = UserDetails( age: 30, height: 180, weight: 75, gender: Gender.male, lifestyle: Lifestyle.active, ); KinesteXAIFramework.createView( user: userDetails, // ... other params ); ``` _HTML / JavaScript_ ```html // Direct in postData object const postData = { userId: "user-123", company: "YOUR_COMPANY", key: "YOUR_API_KEY", // User profile age: 30, height: 180, weight: 75, gender: "Male", lifestyle: "active", }; ``` _React (TypeScript)_ ```tsx // Direct support in postData const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', // User profile - direct support age: 30, height: 180, // cm weight: 75, // kg gender: 'Male', lifestyle: Lifestyle.Active, }; ``` ### Theme & Appearance Control the visual appearance of the application. | Parameter | Type | Default | Description | Effect | |-----------|------|---------|-------------|--------| | style | "dark" | "dark" | Applies style to the UI | Changes the entire UI color scheme to dark or light mode | Theme mode | | themeName | string | Company name | Custom theme identifier | Loads a specific theme configuration (e.g., branded themes) | **Note:** Theme can also be set via URL parameter `?style=dark` or `?style=light` **SDK Support:** - **Swift:** Direct support via `IStyle` class passed to view creation methods (hex values with #) - **Flutter:** Direct support via `IStyle` class passed to view creation methods - **Kotlin:** Direct support via `IStyle` data class passed to view creation methods (hex values without #) - **React Native/React:** Direct support via `style` object in postData - **HTML/JS:** Direct in postData object **Theme Configuration** _Swift (iOS)_ ```swift // Direct SDK support via IStyle class let customStyle = IStyle( style: "light", themeName: "CustomBrand" ) kinestex.createWorkoutView( workout: "Fitness Lite", user: user, style: customStyle, isLoading: $isLoading, onMessageReceived: { /* ... */ } ) ``` _Kotlin (Android)_ ```kotlin // Direct SDK support via IStyle class KinesteXSDK.createWorkoutView( context = this, workoutName = "Fitness Lite", style = IStyle( style = "light", themeName = "Your Theme Name from Admin Dashboard (default company name)" ), isLoading = viewModel.isLoading, onMessageReceived = { message -> handleWebViewMessage(message) }, permissionHandler = this ) ``` _React Native_ ```jsx // Direct support via style object const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', style: { style: 'light', // 'dark' or 'light' loadingBackgroundColor: 'FFFFFF', // hex without # }, customParameters: { themeName: 'CustomBrand', // via customParameters }, }; ``` _Flutter_ ```dart // Direct SDK support via IStyle class KinesteXAIFramework.createWorkoutView( workoutName: "Fitness Lite", isShowKinestex: showKinesteX, isLoading: ValueNotifier(false), style: IStyle( style: 'light', // 'dark' or 'light' themeName: 'CustomBrand', ), onMessageReceived: (message) { handleWebViewMessage(message); }, ); ``` _HTML / JavaScript_ ```html // Direct in postData object const postData = { userId: "user-123", company: "YOUR_COMPANY", key: "YOUR_API_KEY", style: "light", themeName: "CustomBrand", }; ``` _React (TypeScript)_ ```tsx // Direct support via style object const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', style: { style: 'light', // 'dark' or 'light' loadingBackgroundColor: 'FFFFFF', // hex without # }, customParameters: { themeName: 'CustomBrand', // via customParameters }, }; ``` ### Language & Localization Configure the language for all UI text, voice prompts, and content. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | language | string | "en" | Language code for localization | | voiceActor | string | - | Specific voice actor for audio feedback | | content_gender | string | - | Gender preference for content/instructors shown | **Supported Languages:** | Code | Language | RTL Support | |------|----------|-------------| | en | English | No | | es | Spanish | No | | fr | French | No | | de | German | No | | nl | Dutch | No | | it | Italian | No | | pt | Portuguese | No | | ru | Russian | No | | ar | Arabic | Yes | | he | Hebrew | Yes | | hi | Hindi | No | | bn | Bengali | No | | id | Indonesian | No | | da | Danish | No | | el | Greek | No | | nl | Dutch | No | | zh | Chinese (Simplified) | No | **SDK Support:** Requires customParams on most platforms. **Language Configuration** _Swift (iOS)_ ```swift // Via customParams kinestex.createView( customParams: [ "language": "es", "content_gender": "female" ], // ... other params ) ``` _Kotlin (Android)_ ```kotlin // Via customParams KinesteXSDK.createView( customParams = mapOf( "language" to "es", "content_gender" to "female" ), // ... other params ) ``` _React Native_ ```jsx // Via customParameters const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', customParameters: { language: 'es', content_gender: 'female', }, }; ``` _Flutter_ ```dart // Via customParams KinesteXAIFramework.createView( customParams: { "language": "es", "content_gender": "female", }, // ... other params ); ``` _HTML / JavaScript_ ```html // Direct in postData object const postData = { userId: "user-123", company: "YOUR_COMPANY", key: "YOUR_API_KEY", language: "es", content_gender: "female", }; ``` _React (TypeScript)_ ```tsx // Via customParameters const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', customParameters: { language: 'es', content_gender: 'female', }, }; ``` ### Workout Configuration Parameters for configuring workout behavior and progression. | Parameter | Type | Description | Effect | |-----------|------|-------------|--------| | planC | string | Plan configuration identifier | Specifies which workout plan to load | | exercises | string[] | Array of exercise identifiers | Defines the specific exercises to include in a session | | currentExercise | string | Current exercise identifier | Sets the active exercise for camera component | | completed_exercises | string[] | Previously completed exercises | Allows resuming a workout from a specific point | | start_from_exercise | string | Exercise to start from | Skips to a specific exercise in the workout | | start_from_rest | boolean | Start from rest period | If true, begins at rest screen before the specified exercise | | resetPlanProgress | boolean | Reset all plan progress | Clears all saved progress for the user's plans | | on_start_url | string | URL to call on workout start | Webhook URL triggered when workout begins | **Note:** Exercise IDs can be retrieved from the [Content API](/docs/content-api). **Workout Configuration** _Swift (iOS)_ ```swift // Direct support for some, customParams for others kinestex.createCameraView( exercises: ["Squats", "Lunges", "Pushups"], // direct currentExercise: $currentExercise, // direct customParams: [ "start_from_exercise": "Lunges", "start_from_rest": true, "resetPlanProgress": false ] ) ``` _Kotlin (Android)_ ```kotlin // Direct support for some, customParams for others KinesteXSDK.createCameraComponent( exercises = listOf("Squats", "Lunges", "Pushups"), // direct currentExercise = "Squats", // direct customParams = mapOf( "start_from_exercise" to "Lunges", "start_from_rest" to true ) ) ``` _React Native_ ```jsx // Direct support in postData const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', // Direct support exercises: ['Squats', 'Lunges', 'Pushups'], currentExercise: 'Squats', // Via customParameters customParameters: { start_from_exercise: 'Lunges', start_from_rest: true, resetPlanProgress: false, }, }; ``` _Flutter_ ```dart // Direct support for some, customParams for others KinesteXAIFramework.createCameraComponent( exercises: ["Squats", "Lunges", "Pushups"], // direct currentExercise: "Squats", // direct customParams: { "start_from_exercise": "Lunges", "start_from_rest": true, }, ); ``` _HTML / JavaScript_ ```html // Direct in postData object const postData = { userId: "user-123", company: "YOUR_COMPANY", key: "YOUR_API_KEY", exercises: ["Squats", "Lunges", "Pushups"], currentExercise: "Squats", start_from_exercise: "Lunges", start_from_rest: true, resetPlanProgress: false, }; ``` _React (TypeScript)_ ```tsx // Direct support in postData const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', // Direct support exercises: ['Squats', 'Lunges', 'Pushups'], currentExercise: 'Squats', // Via customParameters customParameters: { start_from_exercise: 'Lunges', start_from_rest: true, }, }; ``` ### Camera & Pose Detection Fine-tune the camera and pose detection system. | Parameter | Type | Default | Description | Effect | |-----------|------|---------|-------------|--------| | shouldAskCamera | boolean | true | Prompt for camera permission | Shows camera permission dialog before starting | | shouldShowCameraSelector | boolean | false | Show camera selection UI | Allows user to choose between available cameras | | shouldShowOpenCameraSettings | boolean | false | Show settings button | Displays button to open device camera settings | | cameraId | string | - | Specific camera device ID | Forces use of a specific camera | | cameraLabel | string | - | Camera label for display | Shows custom label for the selected camera | | minPoseDetectionConfidence | number | 0.5 | Minimum detection confidence (0-1) | Lower values detect poses more easily but may be less accurate | | minTrackingConfidence | number | 0.5 | Minimum tracking confidence (0-1) | Affects how persistently poses are tracked between frames | | minPosePresenceConfidence | number | 0.5 | Minimum presence confidence (0-1) | Threshold for determining if a person is in frame | | mediapipeModel | "full" "heavy" "light" | "full" | MediaPipe model variant | light: Faster, less accurate. full: Balanced. heavy: Most accurate, slower | | defaultDelegate | "GPU" "CPU" | Auto | Processing delegate | Forces GPU or CPU processing for pose detection | | landmarkColor | string | "#14FF00" | Pose landmark color | Changes the color of skeleton overlay (hex color) | | showSilhouette | boolean | true | Show body silhouette | Displays silhouette guide overlay during exercises | | includePoseData | boolean | - | Include raw pose data | Sends pose landmark data in postMessage events | | includePoseBorders | boolean | true | Show pose boundary guides | Displays borders indicating optimal pose positioning | | includeRealtimeAccuracy | boolean | - | Send real-time accuracy | Broadcasts accuracy scores in real-time via postMessage | | videoFit | "contain" | "cover" | Camera feed display mode | When set to "contain", shows the full camera frame instead of zooming in to fill the area. Useful for maximizing available space for motion tracking | **Note:** `defaultDelegate` can also be set via URL parameter `?delegate=GPU` or `?delegate=CPU` **Camera & Pose Detection Settings** _Swift (iOS)_ ```swift // Via customParams kinestex.createCameraView( exercises: exerciseList, currentExercise: $currentExercise, customParams: [ "landmarkColor": "#FF5500", "showSilhouette": true, "mediapipeModel": "heavy", "defaultDelegate": "GPU", "includePoseData": true, "includeRealtimeAccuracy": true, "shouldShowCameraSelector": true, "videoFit": "contain" // show full camera frame ] ) ``` _Kotlin (Android)_ ```kotlin // Via customParams KinesteXSDK.createCameraComponent( exercises = exerciseList, currentExercise = "Squats", customParams = mapOf( "landmarkColor" to "#FF5500", "showSilhouette" to true, "mediapipeModel" to "heavy", "defaultDelegate" to "GPU", "includePoseData" to true, "includeRealtimeAccuracy" to true, "videoFit" to "contain" // show full camera frame ) ) ``` _React Native_ ```jsx // Via customParameters const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', exercises: ['Squats', 'Lunges'], currentExercise: 'Squats', customParameters: { landmarkColor: '#FF5500', showSilhouette: true, mediapipeModel: 'heavy', defaultDelegate: 'GPU', includePoseData: true, includeRealtimeAccuracy: true, shouldShowCameraSelector: true, videoFit: 'contain', // show full camera frame }, }; ``` _Flutter_ ```dart // Via customParams KinesteXAIFramework.createCameraComponent( exercises: ["Squats", "Lunges"], currentExercise: "Squats", customParams: { "landmarkColor": "#FF5500", "showSilhouette": true, "mediapipeModel": "heavy", "defaultDelegate": "GPU", "includePoseData": true, "includeRealtimeAccuracy": true, "videoFit": "contain", // show full camera frame }, ); ``` _HTML / JavaScript_ ```html // Direct in postData object const postData = { userId: "user-123", company: "YOUR_COMPANY", key: "YOUR_API_KEY", exercises: ["Squats", "Lunges"], currentExercise: "Squats", landmarkColor: "#FF5500", showSilhouette: true, mediapipeModel: "heavy", defaultDelegate: "GPU", includePoseData: true, includeRealtimeAccuracy: true, shouldShowCameraSelector: true, videoFit: "contain", // show full camera frame }; ``` _React (TypeScript)_ ```tsx // Via customParameters const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', exercises: ['Squats', 'Lunges'], currentExercise: 'Squats', customParameters: { landmarkColor: '#FF5500', showSilhouette: true, mediapipeModel: 'heavy', defaultDelegate: 'GPU', includePoseData: true, includeRealtimeAccuracy: true, videoFit: 'contain', // show full camera frame }, }; ``` ### UI Controls Control visibility and behavior of UI elements. | Parameter | Type | Default | Description | Effect | |-----------|------|---------|-------------|--------| | isHideHeaderMain | boolean | false | Hide main header | Removes the top navigation header | | hideFeelingDialog | boolean | false | Hide feeling dialog | Skips the post-workout feeling prompt | | hideMusicIcon | boolean | - | Hide music control | Removes the music toggle button | | hideMistakesFeedback | boolean | - | Hide mistake feedback | Disables on-screen form correction prompts | | showModalWarmUp | boolean | - | Show warm-up modal | Displays warm-up recommendation before workout | | showSettings | boolean | - | Show settings button | Displays settings access in the UI | | isDrawingPose | boolean | - | Enable pose drawing | Activates skeleton visualization on camera feed | | isOnboarding | boolean | true | Enable onboarding flow | Shows/hides the initial onboarding experience | | hideCompletionOverlay | boolean | false | Hide workout completion overlay | Skips the workout completion summary screen shown at the end of a workout | | preventGestureControl | boolean | false | Disable gesture control | Prevents users from pausing and resuming workouts using hand gestures | | disableGuide | boolean | false | Disable onboarding guide | Suppresses the guide entirely for embedded contexts where onboarding isn't needed | | disableCookies | boolean | false | Disable all cookies | Automatically declines all cookies, hides the cookie management button and consent link. Useful for GDPR compliance or privacy-sensitive environments | | hideStatisticsHeader | boolean | false | Hide statistics header | Hides the header on the workout statistics/results screen | | nativeParentScroll | boolean | false | Native scroll delegation | Delegates scroll behavior of the statistics screen to the native parent container instead of using internal scroll. Enable if your native app manages its own scrolling | **UI Controls Configuration** _Swift (iOS)_ ```swift // Via customParams kinestex.createView( customParams: [ "isHideHeaderMain": true, "hideFeelingDialog": true, "hideMusicIcon": true, "hideMistakesFeedback": false, "isOnboarding": false, "hideCompletionOverlay": true, "preventGestureControl": true, "disableGuide": true, "disableCookies": true, "hideStatisticsHeader": false, "nativeParentScroll": false ] ) ``` _Kotlin (Android)_ ```kotlin // Via customParams KinesteXSDK.createView( customParams = mapOf( "isHideHeaderMain" to true, "hideFeelingDialog" to true, "hideMusicIcon" to true, "hideMistakesFeedback" to false, "isOnboarding" to false, "hideCompletionOverlay" to true, "preventGestureControl" to true, "disableGuide" to true, "disableCookies" to true, "hideStatisticsHeader" to false, "nativeParentScroll" to false ) ) ``` _React Native_ ```jsx // Via customParameters const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', customParameters: { isHideHeaderMain: true, hideFeelingDialog: true, hideMusicIcon: true, hideMistakesFeedback: false, isOnboarding: false, hideCompletionOverlay: true, preventGestureControl: true, disableGuide: true, disableCookies: true, hideStatisticsHeader: false, nativeParentScroll: false, }, }; ``` _Flutter_ ```dart // Via customParams KinesteXAIFramework.createView( customParams: { "isHideHeaderMain": true, "hideFeelingDialog": true, "hideMusicIcon": true, "hideMistakesFeedback": false, "isOnboarding": false, "hideCompletionOverlay": true, "preventGestureControl": true, "disableGuide": true, "disableCookies": true, "hideStatisticsHeader": false, "nativeParentScroll": false, }, ); ``` _HTML / JavaScript_ ```html // Direct in postData object const postData = { userId: "user-123", company: "YOUR_COMPANY", key: "YOUR_API_KEY", isHideHeaderMain: true, hideFeelingDialog: true, hideMusicIcon: true, hideMistakesFeedback: false, isOnboarding: false, hideCompletionOverlay: true, preventGestureControl: true, disableGuide: true, disableCookies: true, hideStatisticsHeader: false, nativeParentScroll: false, }; ``` _React (TypeScript)_ ```tsx // Via customParameters const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', customParameters: { isHideHeaderMain: true, hideFeelingDialog: true, hideMusicIcon: true, hideMistakesFeedback: false, isOnboarding: false, hideCompletionOverlay: true, preventGestureControl: true, disableGuide: true, disableCookies: true, hideStatisticsHeader: false, nativeParentScroll: false, }, }; ``` ### Challenge Mode Configure challenge-specific parameters for direct challenge launches. | Parameter | Type | Description | Effect | |-----------|------|-------------|--------| | exercise | string | Challenge exercise identifier | Specifies which exercise to use for the challenge | | countdown | number | Countdown duration in seconds | Sets the preparation countdown before challenge starts | | reps | number | Target repetition count | Sets the goal number of reps for the challenge | | gameTotalRounds | number | Total number of rounds (Color Chase only) | Controls how many rounds the Color Chase game lasts. Default is 10. Rounds beyond 10 are procedurally generated with increasing difficulty | **Balloon Pop — rounds mode (special case):** When you launch the **Balloon Pop** game and pass BOTH `reps` and `countdown`, the game switches from the default 30-second timer mode into a **rounds mode**: - `reps` = number of rounds - `countdown` = number of balloons spawned per round The game ends after all rounds are completed. The HUD shows a round counter instead of a timer, and the difficulty stage indicator is hidden. | Configuration | Behavior | |---------------|----------| | `reps: 4, countdown: 2` | 4 rounds × 2 balloons = 8 total pops | | `reps: 3, countdown: 3` | 3 rounds × 3 balloons = 9 total pops | | `reps: 6, countdown: 1` | 6 rounds × 1 balloon = 6 total pops (very easy) | | `reps` not provided | Default timer mode (30s, escalating difficulty) | **Challenge Mode Configuration** _Swift (iOS)_ ```swift // Via customParams for challenge integration kinestex.createChallengeView( exercise: exerciseId, // direct customParams: [ "countdown": 5, "reps": 20, // Color Chase only: "gameTotalRounds": 15 // Balloon Pop rounds-mode example: // "reps": 4, "countdown": 2 → 4 rounds × 2 balloons ] ) ``` _Kotlin (Android)_ ```kotlin // Via customParams for challenge integration KinesteXSDK.createChallengeView( exercise = exerciseId, // direct customParams = mapOf( "countdown" to 5, "reps" to 20, // Color Chase only: "gameTotalRounds" to 15 // Balloon Pop rounds-mode example: // "reps" to 4, "countdown" to 2 → 4 rounds × 2 balloons ) ) ``` _React Native_ ```jsx // Via postData for challenge integration const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', customParameters: { exercise: 'exerciseId', countdown: 5, reps: 20, // Color Chase only: gameTotalRounds: 15, // Balloon Pop rounds-mode example: // reps: 4, countdown: 2 → 4 rounds × 2 balloons }, }; ``` _Flutter_ ```dart // Via customParams for challenge integration KinesteXAIFramework.createChallengeView( exercise: exerciseId, // direct customParams: { "countdown": 5, "reps": 20, // Color Chase only: "gameTotalRounds": 15, // Balloon Pop rounds-mode example: // "reps": 4, "countdown": 2 → 4 rounds × 2 balloons }, ); ``` _HTML / JavaScript_ ```html // Direct in postData object const postData = { userId: "user-123", company: "YOUR_COMPANY", key: "YOUR_API_KEY", exercise: exerciseId, countdown: 5, reps: 20, // Color Chase only: gameTotalRounds: 15, // Balloon Pop rounds-mode example: // reps: 4, countdown: 2 → 4 rounds × 2 balloons }; ``` _React (TypeScript)_ ```tsx // Via postData for challenge integration const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', customParameters: { exercise: 'exerciseId', countdown: 5, reps: 20, // Color Chase only: gameTotalRounds: 15, // Balloon Pop rounds-mode example: // reps: 4, countdown: 2 → 4 rounds × 2 balloons }, }; ``` ### Complete UX Customization Customize the Complete UX (Main View) home page experience. | Parameter | Type | Description | Effect | |-----------|------|-------------|--------| | challenges_home | array | Custom challenges/games for home screen | Defines the two challenge/game options shown on the Complete UX home page | **challenges_home Configuration:** The `challenges_home` parameter allows you to customize the challenges and games displayed on the home screen of the Complete UX experience. You must pass exactly **2 objects** in the array. **Array Object Structure:** | Property | Type | Required | Description | |----------|------|----------|-------------| | id | string | Yes | Exercise ID for challenges, or game ID for games | | name | string | Yes | Display name shown in the UI | | isGame | boolean | Yes | Set to `true` for games, `false` for challenges | **Available Games:** - `balloonpop` - Balloon Pop game - `aliensquatshooter` - Alien Squat Shooter game - `colorchase` - Color Chase game **Combination Options:** - Two challenges (both with `isGame: false`) - Two games (both with `isGame: true`) - One challenge + one game (mixed `isGame` values) **Note:** You can pass any exercise ID as a challenge. When specifying a game, ensure `isGame` is set to `true`. **Complete UX Home Page Customization** _Swift (iOS)_ ```swift // Customize challenges on Complete UX home page let exerciseId = "your-exercise-id" // Get from Content API let exerciseName = "Your Exercise Name" kinestex.createMainView( customParams: [ "challenges_home": [ ["id": exerciseId, "name": exerciseName, "isGame": false], ["id": "balloonpop", "name": "Balloon Pop", "isGame": true] ] ] ) ``` _Kotlin (Android)_ ```kotlin // Customize challenges on Complete UX home page val exerciseId = "your-exercise-id" // Get from Content API val exerciseName = "Your Exercise Name" KinesteXSDK.createMainView( customParams = mapOf( "challenges_home" to listOf( mapOf("id" to exerciseId, "name" to exerciseName, "isGame" to false), mapOf("id" to "balloonpop", "name" to "Balloon Pop", "isGame" to true) ) ) ) ``` _React Native_ ```jsx // Customize challenges on Complete UX home page const exerciseId = 'your-exercise-id'; // Get from Content API const exerciseName = 'Your Exercise Name'; const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', customParameters: { challenges_home: [ { id: exerciseId, name: exerciseName, isGame: false }, { id: 'balloonpop', name: 'Balloon Pop', isGame: true }, ], }, }; ``` _Flutter_ ```dart // Customize challenges on Complete UX home page final exerciseId = "your-exercise-id"; // Get from Content API final exerciseName = "Your Exercise Name"; KinesteXAIFramework.createMainView( customParams: { "challenges_home": [ {"id": exerciseId, "name": exerciseName, "isGame": false}, {"id": "balloonpop", "name": "Balloon Pop", "isGame": true}, ], }, ); ``` _HTML / JavaScript_ ```html // Customize challenges on Complete UX home page const exerciseId = "your-exercise-id"; // Get from Content API const exerciseName = "Your Exercise Name"; const postData = { userId: "user-123", company: "YOUR_COMPANY", key: "YOUR_API_KEY", challenges_home: [ { id: exerciseId, name: exerciseName, isGame: false }, { id: "balloonpop", name: "Balloon Pop", isGame: true }, ], }; ``` _React (TypeScript)_ ```tsx // Customize challenges on Complete UX home page const exerciseId = 'your-exercise-id'; // Get from Content API const exerciseName = 'Your Exercise Name'; const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', customParameters: { challenges_home: [ { id: exerciseId, name: exerciseName, isGame: false }, { id: 'balloonpop', name: 'Balloon Pop', isGame: true }, ], }, }; ``` ### Leaderboard Configure leaderboard functionality. | Parameter | Type | Default | Description | Effect | |-----------|------|---------|-------------|--------| | showLeaderboard | boolean | - | Show leaderboard UI | Enables/disables leaderboard visibility | | username | string | - | Display name for leaderboard | Sets the user's name shown on leaderboards | | autoSubmitLeaderboard | boolean | false | Silent leaderboard submission (Challenge only) | When true, Challenge results are submitted to the leaderboard without showing the submission modal. The display name is read from the previously stored `leaderboard_username` (or falls back to the user ID) | **Note:** Username is automatically saved to localStorage for future sessions. **`autoSubmitLeaderboard` use case:** Use this only for Challenge integrations where you want every completion silently posted to the leaderboard (e.g., when your host app already manages display names and doesn't want a second prompt). Leave it unset (or `false`) to keep the standard modal-based flow. **Leaderboard Configuration** _Swift (iOS)_ ```swift // Via customParams kinestex.createView( customParams: [ "showLeaderboard": true, "username": "FitnessPro123", "autoSubmitLeaderboard": true // Challenge-only: skip submit modal ] ) ``` _Kotlin (Android)_ ```kotlin // Via customParams KinesteXSDK.createView( customParams = mapOf( "showLeaderboard" to true, "username" to "FitnessPro123", "autoSubmitLeaderboard" to true // Challenge-only: skip submit modal ) ) ``` _React Native_ ```jsx // Via customParameters const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', customParameters: { showLeaderboard: true, username: 'FitnessPro123', autoSubmitLeaderboard: true, // Challenge-only: skip submit modal }, }; ``` _Flutter_ ```dart // Via customParams KinesteXAIFramework.createView( customParams: { "showLeaderboard": true, "username": "FitnessPro123", "autoSubmitLeaderboard": true, // Challenge-only: skip submit modal }, ); ``` _HTML / JavaScript_ ```html // Direct in postData object const postData = { userId: "user-123", company: "YOUR_COMPANY", key: "YOUR_API_KEY", showLeaderboard: true, username: "FitnessPro123", autoSubmitLeaderboard: true, // Challenge-only: skip submit modal }; ``` _React (TypeScript)_ ```tsx // Via customParameters const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', customParameters: { showLeaderboard: true, username: 'FitnessPro123', autoSubmitLeaderboard: true, // Challenge-only: skip submit modal }, }; ``` ### Loading Screen Customize the loading screen appearance (includes native overlay color that is displayed during initial loading phase). | Parameter | Type | Default | Description | Effect | |-----------|------|---------|-------------|--------| | loadingStickmanColor | string | Theme default | Stickman animation color | Changes the color of the loading animation character | | loadingBackgroundColor | string | Theme default | Background color | Sets the loading screen background color | | loadingTextColor | string | Theme default | Text color | Sets the color of loading text and messages | **SDK Support:** - **Swift:** Direct support via `IStyle` class (hex values with #) - **Flutter:** Direct support via `IStyle` class (hex values without #) - **Kotlin:** Direct support via `IStyle` data class (hex values without #) - **React Native/React:** Direct support via `style` object (hex values without #) - **HTML/JS:** Direct in postData object **Native Loading Overlay (Swift):** The SDK displays a native overlay on top of the WebView until content is fully loaded, preventing users from seeing a blank screen. - Overlay automatically hides when `KinestexLoaded` message is received - Overlay color priority: 1. Uses `loadingBackgroundColor` if set (hex color with #) 2. Uses white (#FFFFFF) if `style = "light"` 3. Uses black (#000000) if `style = "dark"` (default) **Style Properties:** | Property | Type | Default | Description | |----------|------|---------|-------------| | style | String? | "dark" | Base theme style ("dark" or "light") | | themeName | String? | null | Custom theme name | | loadingStickmanColor | String? | null | Color for the loading animation stickman (hex without #) | | loadingBackgroundColor | String? | null | Background color during loading (hex without #) | | loadingTextColor | String? | null | Text color during loading (hex without #) | **Loading Screen Customization** _Swift (iOS)_ ```swift // Direct SDK support via IStyle class let customStyle = IStyle( style: "dark", loadingBackgroundColor: "#1A1A2E", loadingStickmanColor: "#FF6B00", loadingTextColor: "#FFFFFF" ) kinestex.createWorkoutView( workout: "Fitness Lite", user: user, style: customStyle, isLoading: $isLoading, onMessageReceived: { /* ... */ } ) ``` _Kotlin (Android)_ ```kotlin // Direct SDK support via IStyle class KinesteXSDK.createWorkoutView( context = this, workoutName = "Fitness Lite", style = IStyle( style = "dark", loadingBackgroundColor = "1A1A2E", // hex value without # loadingStickmanColor = "FF6B00", loadingTextColor = "FFFFFF" ), isLoading = viewModel.isLoading, onMessageReceived = { message -> handleWebViewMessage(message) }, permissionHandler = this ) ``` _React Native_ ```jsx // Direct support via style object const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', style: { style: 'dark', loadingBackgroundColor: '1A1A2E', // hex without # loadingTextColor: 'FFFFFF', }, customParameters: { loadingStickmanColor: '#FF6B00', // via customParams }, }; ``` _Flutter_ ```dart // Direct SDK support via IStyle class KinesteXAIFramework.createWorkoutView( workoutName: "Fitness Lite", isShowKinestex: showKinesteX, isLoading: ValueNotifier(false), style: IStyle( style: 'dark', loadingBackgroundColor: '1A1A2E', // hex without # loadingStickmanColor: 'FF6B00', loadingTextColor: 'FFFFFF', ), onMessageReceived: (message) { handleWebViewMessage(message); }, ); ``` _HTML / JavaScript_ ```html // Direct in postData object const postData = { userId: "user-123", company: "YOUR_COMPANY", key: "YOUR_API_KEY", loadingStickmanColor: "#FF6B00", loadingBackgroundColor: "#1A1A2E", loadingTextColor: "#FFFFFF", }; ``` _React (TypeScript)_ ```tsx // Direct support via style object const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', style: { style: 'dark', loadingBackgroundColor: '1A1A2E', // hex without # loadingTextColor: 'FFFFFF', }, customParameters: { loadingStickmanColor: '#FF6B00', // via customParams }, }; ``` ### Motion Tracking Settings Control AI-powered motion tracking behavior. | Parameter | Type | Default | Description | Effect | |-----------|------|---------|-------------|--------| | motionTrackingSettingOn | boolean | - | Show motion tracking toggle | Displays the AI tracking on/off setting | | motionTrackingEnabled | boolean | - | Enable motion tracking | Session-level override for AI tracking (clears localStorage preference) | | motionDataEnabled | boolean | true | Record per-frame motion data | When set to `false`, the SDK does NOT collect per-frame pose landmark data during the session. Session replay will be empty for affected sessions, but all other workout functionality is unaffected | **Note:** When `motionTrackingEnabled` is explicitly set, it clears any saved user preference and uses the provided value for the session. **`motionDataEnabled` — only use this if you know why it's necessary.** This flag exists for memory-constrained scenarios (e.g., long workouts with many unique exercise videos on older iOS devices where peak memory pressure can cause the WebView to crash). Disabling motion data reduces memory usage at the cost of losing session replay. The recorder state resets on every verification, so the flag does not leak across sessions. Default behavior is unchanged if you omit the parameter. **Motion Tracking Settings** _Swift (iOS)_ ```swift // Via customParams kinestex.createView( customParams: [ "motionTrackingSettingOn": true, "motionTrackingEnabled": true, "motionDataEnabled": false // Only set when memory-constrained ] ) ``` _Kotlin (Android)_ ```kotlin // Via customParams KinesteXSDK.createView( customParams = mapOf( "motionTrackingSettingOn" to true, "motionTrackingEnabled" to true, "motionDataEnabled" to false // Only set when memory-constrained ) ) ``` _React Native_ ```jsx // Via customParameters const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', customParameters: { motionTrackingSettingOn: true, motionTrackingEnabled: true, motionDataEnabled: false, // Only set when memory-constrained }, }; ``` _Flutter_ ```dart // Via customParams KinesteXAIFramework.createView( customParams: { "motionTrackingSettingOn": true, "motionTrackingEnabled": true, "motionDataEnabled": false, // Only set when memory-constrained }, ); ``` _HTML / JavaScript_ ```html // Direct in postData object const postData = { userId: "user-123", company: "YOUR_COMPANY", key: "YOUR_API_KEY", motionTrackingSettingOn: true, motionTrackingEnabled: true, motionDataEnabled: false, // Only set when memory-constrained }; ``` _React (TypeScript)_ ```tsx // Via customParameters const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', customParameters: { motionTrackingSettingOn: true, motionTrackingEnabled: true, motionDataEnabled: false, // Only set when memory-constrained }, }; ``` ### Debug & Development Parameters for debugging and development purposes. | Parameter | Type | Default | Description | Effect | |-----------|------|---------|-------------|--------| | showDebugRecording | boolean | false | Show debug recording UI | Displays recording controls for debugging | | showNetworkDebugTool | boolean | - | Show network debug panel | Displays network request monitoring tool | | newModelId | string | - | Test model identifier | Loads a specific ML model version for testing | **Note:** `showDebugRecording` can also be enabled via URL parameter `?debug=true` **Debug & Development Settings** _Swift (iOS)_ ```swift // Via customParams kinestex.createView( customParams: [ "showDebugRecording": true, "showNetworkDebugTool": true, "newModelId": "pose_model_v2_beta" ] ) ``` _Kotlin (Android)_ ```kotlin // Via customParams KinesteXSDK.createView( customParams = mapOf( "showDebugRecording" to true, "showNetworkDebugTool" to true, "newModelId" to "pose_model_v2_beta" ) ) ``` _React Native_ ```jsx // Via customParameters const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', customParameters: { showDebugRecording: true, showNetworkDebugTool: true, newModelId: 'pose_model_v2_beta', }, }; ``` _Flutter_ ```dart // Via customParams KinesteXAIFramework.createView( customParams: { "showDebugRecording": true, "showNetworkDebugTool": true, "newModelId": "pose_model_v2_beta", }, ); ``` _HTML / JavaScript_ ```html // Direct in postData object const postData = { userId: "user-123", company: "YOUR_COMPANY", key: "YOUR_API_KEY", showDebugRecording: true, showNetworkDebugTool: true, newModelId: "pose_model_v2_beta", }; ``` _React (TypeScript)_ ```tsx // Via customParameters const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', customParameters: { showDebugRecording: true, showNetworkDebugTool: true, newModelId: 'pose_model_v2_beta', }, }; ``` ### Custom Workout Configure custom workout sequences. For complete custom workout implementation, see [Custom Integration](/docs/integration/custom-integration). | Parameter | Type | Description | Effect | |-----------|------|-------------|--------| | customWorkoutExercises | array | Array of exercise configurations | Defines a custom sequence of exercises with their parameters | | restSpeeches | string[] | Rest period audio identifiers | Custom audio to play during rest periods | | currentRestSpeech | string | Current rest speech identifier | Sets the active rest period audio | | videoURL | string | Custom video URL | URL for custom exercise demonstration video | **Custom Workout Flow:** 1. Pass `customWorkoutExercises` during initial verification 2. Send `workout_activity_action: "start"` message to begin the workout 3. The system will navigate to the workout flow automatically **Custom Workout Parameters** _Swift (iOS)_ ```swift // Via customParams for additional settings let customExercises = [ WorkoutSequenceExercise( exerciseId: "exercise-id-1", reps: 15, duration: nil, includeRestPeriod: true, restDuration: 20 ) ] kinestex.createCustomWorkoutView( customWorkouts: customExercises, // direct customParams: [ "restSpeeches": ["rest_speech_1", "rest_speech_2"], "videoURL": "https://example.com/demo.mp4" ] ) ``` _Kotlin (Android)_ ```kotlin // Via customParams for additional settings val customExercises = listOf( WorkoutSequenceExercise( exerciseId = "exercise-id-1", reps = 15, duration = null, includeRestPeriod = true, restDuration = 20 ) ) KinesteXSDK.createCustomWorkoutView( customWorkouts = customExercises, // direct customParams = mapOf( "restSpeeches" to listOf("rest_speech_1", "rest_speech_2"), "videoURL" to "https://example.com/demo.mp4" ) ) ``` _React Native_ ```jsx // Direct support in postData const customWorkoutExercises = [ { exerciseId: 'exercise-id-1', reps: 15, duration: null, includeRestPeriod: true, restDuration: 20, }, ]; const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', customWorkoutExercises: customWorkoutExercises, // direct customParameters: { restSpeeches: ['rest_speech_1', 'rest_speech_2'], videoURL: 'https://example.com/demo.mp4', }, }; ``` _Flutter_ ```dart // Via customParams for additional settings final customExercises = [ WorkoutSequenceExercise( exerciseId: "exercise-id-1", reps: 15, duration: null, includeRestPeriod: true, restDuration: 20, ), ]; KinesteXAIFramework.createCustomWorkoutView( customWorkouts: customExercises, // direct customParams: { "restSpeeches": ["rest_speech_1", "rest_speech_2"], "videoURL": "https://example.com/demo.mp4", }, ); ``` _HTML / JavaScript_ ```html // Direct in postData object const customWorkoutExercises = [ { exerciseId: "exercise-id-1", reps: 15, duration: null, includeRestPeriod: true, restDuration: 20, }, ]; const postData = { userId: "user-123", company: "YOUR_COMPANY", key: "YOUR_API_KEY", customWorkoutExercises: customWorkoutExercises, restSpeeches: ["rest_speech_1", "rest_speech_2"], videoURL: "https://example.com/demo.mp4", }; ``` _React (TypeScript)_ ```tsx // Direct support in postData const customWorkoutExercises: WorkoutSequenceExercise[] = [ { exerciseId: 'exercise-id-1', reps: 15, duration: null, includeRestPeriod: true, restDuration: 20, }, ]; const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', customWorkoutExercises: customWorkoutExercises, // direct customParameters: { restSpeeches: ['rest_speech_1', 'rest_speech_2'], videoURL: 'https://example.com/demo.mp4', }, }; ``` ### Workout Activity Actions Control workout state programmatically via postMessage using the `workout_activity_action` property. | Action | Description | |--------|-------------| | pause_workout | Pauses the workout (video, timer, and tracking) | | resume_workout | Resumes a paused workout | | mute_workout | Mutes all audio (speech, sounds, and music) | | unmute_workout | Unmutes all audio | | mute_speech | Mutes only speech feedback (sounds still play) | | unmute_speech | Unmutes speech feedback | **Note:** These actions are sent via postMessage to the KinesteX iframe/webview after the workout has started. **Workout Activity Actions** _Swift (iOS)_ ```swift // Send workout activity action using @State binding // First, declare the state variable and pass it to the view: // @State var workoutAction: [String: Any]? = nil // kinestex.createWorkoutView(..., workoutAction: $workoutAction, ...) // Pause the workout workoutAction = ["workout_activity_action": "pause_workout"] // Resume the workout workoutAction = ["workout_activity_action": "resume_workout"] // Mute all audio workoutAction = ["workout_activity_action": "mute_workout"] // Unmute all audio workoutAction = ["workout_activity_action": "unmute_workout"] // Mute only speech (sounds still play) workoutAction = ["workout_activity_action": "mute_speech"] // Unmute speech workoutAction = ["workout_activity_action": "unmute_speech"] ``` _Kotlin (Android)_ ```kotlin // Send workout activity action // Pause the workout KinesteXSDK.sendAction("workout_activity_action", "pause_workout") // Resume the workout KinesteXSDK.sendAction("workout_activity_action", "resume_workout") // Mute all audio KinesteXSDK.sendAction("workout_activity_action", "mute_workout") // Unmute all audio KinesteXSDK.sendAction("workout_activity_action", "unmute_workout") // Mute only speech (sounds still play) KinesteXSDK.sendAction("workout_activity_action", "mute_speech") // Unmute speech KinesteXSDK.sendAction("workout_activity_action", "unmute_speech") ``` _React Native_ ```jsx // Send workout activity action const kinestexSDKRef = useRef(null); // Pause the workout kinestexSDKRef.current?.sendAction("workout_activity_action", "pause_workout"); // Resume the workout kinestexSDKRef.current?.sendAction("workout_activity_action", "resume_workout"); // Mute all audio kinestexSDKRef.current?.sendAction("workout_activity_action", "mute_workout"); // Unmute all audio kinestexSDKRef.current?.sendAction("workout_activity_action", "unmute_workout"); // Mute only speech (sounds still play) kinestexSDKRef.current?.sendAction("workout_activity_action", "mute_speech"); // Unmute speech kinestexSDKRef.current?.sendAction("workout_activity_action", "unmute_speech"); ``` _Flutter_ ```dart // Send workout activity action // Pause the workout KinesteXAIFramework.sendAction("workout_activity_action", "pause_workout"); // Resume the workout KinesteXAIFramework.sendAction("workout_activity_action", "resume_workout"); // Mute all audio KinesteXAIFramework.sendAction("workout_activity_action", "mute_workout"); // Unmute all audio KinesteXAIFramework.sendAction("workout_activity_action", "unmute_workout"); // Mute only speech (sounds still play) KinesteXAIFramework.sendAction("workout_activity_action", "mute_speech"); // Unmute speech KinesteXAIFramework.sendAction("workout_activity_action", "unmute_speech"); ``` _HTML / JavaScript_ ```html // Send workout activity action via postMessage const iframe = document.getElementById('kinestex-iframe'); // Pause the workout iframe.contentWindow.postMessage({ workout_activity_action: "pause_workout" }, "*"); // Resume the workout iframe.contentWindow.postMessage({ workout_activity_action: "resume_workout" }, "*"); // Mute all audio iframe.contentWindow.postMessage({ workout_activity_action: "mute_workout" }, "*"); // Unmute all audio iframe.contentWindow.postMessage({ workout_activity_action: "unmute_workout" }, "*"); // Mute only speech (sounds still play) iframe.contentWindow.postMessage({ workout_activity_action: "mute_speech" }, "*"); // Unmute speech iframe.contentWindow.postMessage({ workout_activity_action: "unmute_speech" }, "*"); ``` _React (TypeScript)_ ```tsx // Send workout activity action import { useRef } from 'react'; import { type KinesteXSDKCamera } from 'kinestex-sdk-react-ts'; const ref = useRef(null); // Pause the workout ref.current?.sendAction("workout_activity_action", "pause_workout"); // Resume the workout ref.current?.sendAction("workout_activity_action", "resume_workout"); // Mute all audio ref.current?.sendAction("workout_activity_action", "mute_workout"); // Unmute all audio ref.current?.sendAction("workout_activity_action", "unmute_workout"); // Mute only speech (sounds still play) ref.current?.sendAction("workout_activity_action", "mute_speech"); // Unmute speech ref.current?.sendAction("workout_activity_action", "unmute_speech"); ``` ### Navigation Control navigation and routing behavior. | Parameter | Type | Description | Effect | |-----------|------|-------------|--------| | instantRedirect | string | Path to redirect to | Immediately navigates to specified route after verification | **Note:** Path will be normalized (leading `/` added if missing). **Navigation Control** _Swift (iOS)_ ```swift // Via customParams kinestex.createView( customParams: [ "instantRedirect": "/workout/start" ] ) ``` _Kotlin (Android)_ ```kotlin // Via customParams KinesteXSDK.createView( customParams = mapOf( "instantRedirect" to "/workout/start" ) ) ``` _React Native_ ```jsx // Via customParameters const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', customParameters: { instantRedirect: '/workout/start', }, }; ``` _Flutter_ ```dart // Via customParams KinesteXAIFramework.createView( customParams: { "instantRedirect": "/workout/start", }, ); ``` _HTML / JavaScript_ ```html // Direct in postData object const postData = { userId: "user-123", company: "YOUR_COMPANY", key: "YOUR_API_KEY", instantRedirect: "/workout/start", }; ``` _React (TypeScript)_ ```tsx // Via customParameters const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', customParameters: { instantRedirect: '/workout/start', }, }; ``` ### Assessment Configuration Parameters specific to assessment modes (TUG test, balance tests, etc.). For complete assessment data structures, see [Data Points](/docs/data-points). | Parameter | Type | Default | Description | Effect | |-----------|------|---------|-------------|--------| | tugMinRequiredSpace | number | - | Minimum space requirement | Sets the required distance (in meters) for TUG assessment | **Note:** For balance assessments (semitandemstand, fulltandem, sidebysidestand), the system automatically uses the "heavy" MediaPipe model for better accuracy. **Assessment Configuration** _Swift (iOS)_ ```swift // Via customParams for TUG assessment kinestex.createAssessmentView( exercise: "tugtest", // direct customParams: [ "tugMinRequiredSpace": 3 ] ) ``` _Kotlin (Android)_ ```kotlin // Via customParams for TUG assessment KinesteXSDK.createAssessmentView( exercise = "tugtest", // direct customParams = mapOf( "tugMinRequiredSpace" to 3 ) ) ``` _React Native_ ```jsx // Via customParameters const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', customParameters: { exercise: 'tugtest', tugMinRequiredSpace: 3, }, }; ``` _Flutter_ ```dart // Via customParams KinesteXAIFramework.createAssessmentView( exercise: "tugtest", // direct customParams: { "tugMinRequiredSpace": 3, }, ); ``` _HTML / JavaScript_ ```html // Direct in postData object const postData = { userId: "user-123", company: "YOUR_COMPANY", key: "YOUR_API_KEY", exercise: "tugtest", tugMinRequiredSpace: 3, }; ``` _React (TypeScript)_ ```tsx // Via customParameters const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', customParameters: { exercise: 'tugtest', tugMinRequiredSpace: 3, }, }; ``` ### Audio Configuration Control audio playback settings. | Parameter | Type | Default | Description | Effect | |-----------|------|---------|-------------|--------| | enableM4a | boolean | false | Force M4A audio format | Forces the audio system to use M4A format instead of default | **Audio Configuration** _Swift (iOS)_ ```swift // Via customParams kinestex.createView( customParams: [ "enableM4a": true ] ) ``` _Kotlin (Android)_ ```kotlin // Via customParams KinesteXSDK.createView( customParams = mapOf( "enableM4a" to true ) ) ``` _React Native_ ```jsx // Via customParameters const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', customParameters: { enableM4a: true, }, }; ``` _Flutter_ ```dart // Via customParams KinesteXAIFramework.createView( customParams: { "enableM4a": true, }, ); ``` _HTML / JavaScript_ ```html // Direct in postData object const postData = { userId: "user-123", company: "YOUR_COMPANY", key: "YOUR_API_KEY", enableM4a: true, }; ``` _React (TypeScript)_ ```tsx // Via customParameters const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', customParameters: { enableM4a: true, }, }; ``` ### Session & Data Saving Control session saving and data upload behavior. | Parameter | Type | Default | Description | Effect | |-----------|------|---------|-------------|--------| | shouldSendStats | boolean | false | Enable session saving | When enabled, workout sessions are automatically saved to the backend upon completion — including per-exercise stats, accuracy scores, calories, and motion recording data | **Session Saving Configuration** _Swift (iOS)_ ```swift // Via customParams kinestex.createWorkoutView( workout: "Full Body Burn", customParams: [ "shouldSendStats": true ] ) ``` _Kotlin (Android)_ ```kotlin // Via customParams KinesteXSDK.createWorkoutView( workout = "Full Body Burn", customParams = mapOf( "shouldSendStats" to true ) ) ``` _React Native_ ```jsx // Via customParameters const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', customParameters: { shouldSendStats: true, }, }; ``` _Flutter_ ```dart // Via customParams KinesteXAIFramework.createWorkoutView( workout: "Full Body Burn", customParams: { "shouldSendStats": true, }, ); ``` _HTML / JavaScript_ ```html // Direct in postData object const postData = { userId: "user-123", company: "YOUR_COMPANY", key: "YOUR_API_KEY", shouldSendStats: true, }; ``` _React (TypeScript)_ ```tsx // Via customParameters const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', customParameters: { shouldSendStats: true, }, }; ``` ### Plan Context Pass plan progression context so the SDK associates a directly-launched workout with the correct plan. After the workout finishes, the SDK posts `plan_progression_saved` (or `plan_progression_failed` on error). See [Plans & Programs events](/docs/data-points#plans-programs). | Parameter | Type | Description | |-----------|------|-------------| | planId | string | The ID of the plan the workout belongs to | | planType | string | Type of plan (e.g., goal-based plan type, or "personalized") | | progressWorkoutId | string | The plan-day workout ID used to record progression | **When to use:** Only when you launch a workout **directly** via the SDK (not through the in-app plan UI) and want it to count toward plan progression. The in-app plan flow handles this automatically — you don't need to pass these fields if the user navigates from the plan dashboard. **Note:** If you launch a workout without these fields, it is treated as a standalone session and won't update plan progression. **Plan Context Configuration** _Swift (iOS)_ ```swift // Pass plan context when launching a workout directly kinestex.createWorkoutView( workout: "Day 3 Workout", customParams: [ "planId": "plan_abc123", "planType": "personalized", "progressWorkoutId": "plan_day_3" ] ) ``` _Kotlin (Android)_ ```kotlin // Pass plan context when launching a workout directly KinesteXSDK.createWorkoutView( workout = "Day 3 Workout", customParams = mapOf( "planId" to "plan_abc123", "planType" to "personalized", "progressWorkoutId" to "plan_day_3" ) ) ``` _React Native_ ```jsx // Pass plan context via customParameters const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', customParameters: { planId: 'plan_abc123', planType: 'personalized', progressWorkoutId: 'plan_day_3', }, }; ``` _Flutter_ ```dart // Pass plan context via customParams KinesteXAIFramework.createWorkoutView( workoutName: "Day 3 Workout", customParams: { "planId": "plan_abc123", "planType": "personalized", "progressWorkoutId": "plan_day_3", }, ); ``` _HTML / JavaScript_ ```html // Direct in postData object const postData = { userId: "user-123", company: "YOUR_COMPANY", key: "YOUR_API_KEY", planId: "plan_abc123", planType: "personalized", progressWorkoutId: "plan_day_3", }; ``` _React (TypeScript)_ ```tsx // Pass plan context via customParameters const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', customParameters: { planId: 'plan_abc123', planType: 'personalized', progressWorkoutId: 'plan_day_3', }, }; ``` ### Plan Onboarding Prefill Pre-fill or skip onboarding survey questions for personalized plans. All fields are optional — only provided fields will be pre-filled and their corresponding screens will be skipped. If all answers are provided for a goal-based plan, the user goes straight to results. **Availability:** This parameter is used with \`createCustomComponentView\` (Swift, Kotlin, Flutter) or the \`CUSTOM_COMPONENT\` integration option (React Native) with \`route: "plan-onboarding"\`. | Parameter | Type | Description | |-----------|------|-------------| | route | string | Must be \`"plan-onboarding"\` for this feature to work | | planOnboardingPrefill | object | Object containing prefill values for onboarding survey | | planOnboardingPrefill.goal | string | User's fitness goal (e.g., "weight_loss") | | planOnboardingPrefill.healthIssues | string[] | List of health issues (e.g., ["back_pain"]) | | planOnboardingPrefill.injuries | string[] | List of injuries (empty array for none) | | planOnboardingPrefill.duration | number | Preferred workout duration in minutes | | planOnboardingPrefill.lifestyle | string | User's activity level (e.g., "sedentary", "active") | | planOnboardingPrefill.assessmentOnly | boolean | When \`true\`, all survey screens (goal, health issues, injuries, duration, lifestyle) are skipped and the user lands directly on the fitness assessment. Any previously stored plan ID is cleared so a fresh personalized plan is generated after the assessment completes. All other prefill fields are ignored when this is set | **\`assessmentOnly\` use case:** Use this for **reassessment flows** — when the user already has a profile (goal, lifestyle, etc.) recorded in your host app and you only want them to perform a fresh fitness assessment to regenerate their personalized plan. Don't combine it with the other prefill fields; they will be ignored. **\`remind_me_later_clicked\` event:** When the user is on the Assessment screen inside the plan-onboarding flow and taps "Remind me later", the SDK posts a \`remind_me_later_clicked\` PostMessage so your host app can dismiss the SDK and schedule a follow-up prompt. This event is only fired from the plan-onboarding page. **Plan Onboarding Prefill Configuration** _Swift (iOS)_ ```swift // Use createCustomComponentView with route "plan-onboarding" kinestex.createCustomComponentView( route: "plan-onboarding", customParams: [ "planOnboardingPrefill": [ "goal": "weight_loss", "healthIssues": ["back_pain"], "injuries": [], "duration": 30, "lifestyle": "sedentary" ] ] ) // Reassessment-only flow (skips the entire survey) kinestex.createCustomComponentView( route: "plan-onboarding", customParams: [ "planOnboardingPrefill": [ "assessmentOnly": true ] ] ) ``` _Kotlin (Android)_ ```kotlin // Use createCustomComponentView with route "plan-onboarding" KinesteXSDK.createCustomComponentView( route = "plan-onboarding", customParams = mapOf( "planOnboardingPrefill" to mapOf( "goal" to "weight_loss", "healthIssues" to listOf("back_pain"), "injuries" to emptyList(), "duration" to 30, "lifestyle" to "sedentary" ) ) ) ``` _React Native_ ```jsx // Use CUSTOM_COMPONENT integration with route "plan-onboarding" const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', customParameters: { planOnboardingPrefill: { goal: 'weight_loss', healthIssues: ['back_pain'], injuries: [], duration: 30, lifestyle: 'sedentary', }, }, }; ``` _Flutter_ ```dart // Use createCustomComponentView with route "plan-onboarding" KinesteXAIFramework.createCustomComponentView( route: "plan-onboarding", customParams: { "planOnboardingPrefill": { "goal": "weight_loss", "healthIssues": ["back_pain"], "injuries": [], "duration": 30, "lifestyle": "sedentary", }, }, ); ``` _HTML / JavaScript_ ```html const srcURL = "https://ai.kinestex.com/plan-onboarding"; const postData = { userId: "user-123", company: "YOUR_COMPANY", key: "YOUR_API_KEY", planOnboardingPrefill: { goal: "weight_loss", healthIssues: ["back_pain"], injuries: [], duration: 30, lifestyle: "sedentary", }, }; // Reassessment-only flow (skips the entire survey) const reassessmentPostData = { userId: "user-123", company: "YOUR_COMPANY", key: "YOUR_API_KEY", planOnboardingPrefill: { assessmentOnly: true, }, }; ``` _React (TypeScript)_ ```tsx // Use CUSTOM_COMPONENT integration with route "plan-onboarding" const postData: IPostData = { key: 'YOUR_API_KEY', userId: 'user-123', company: 'YOUR_COMPANY', customParameters: { planOnboardingPrefill: { goal: 'weight_loss', healthIssues: ['back_pain'], injuries: [], duration: 30, lifestyle: 'sedentary', }, }, }; ``` ### URL Parameters Some parameters can also be passed via URL query string for HTML/JS integrations: | URL Parameter | Equivalent Config | Example | |---------------|-------------------|---------| | style | style | ?style=light | | delegate | defaultDelegate | ?delegate=GPU | | debug | showDebugRecording | ?debug=true | URL parameters serve as defaults and can be overridden by parameters passed in the postMessage data. ### Complete Example Here's a comprehensive example combining multiple parameter categories: **Complete Configuration Example** _Swift (iOS)_ ```swift // Complete configuration example let kinestex = KinesteXAIKit( apiKey: "YOUR_API_KEY", companyName: "MyFitnessApp", userId: "user_12345" ) let user = UserDetails( age: 28, height: 165, weight: 60, gender: .Female, lifestyle: .Active ) // Theme & Loading via IStyle class (hex values with #) let customStyle = IStyle( style: "dark", loadingBackgroundColor: "#1A1A2E", loadingStickmanColor: "#00FF88", loadingTextColor: "#FFFFFF" ) kinestex.createWorkoutView( workout: "Fitness Lite", user: user, style: customStyle, isLoading: $isLoading, // Other customization via customParams customParams: [ // Language "language": "es", // UI Controls "isOnboarding": false, "hideFeelingDialog": true, // Camera "landmarkColor": "#00FF88", "showSilhouette": true, "shouldShowCameraSelector": true, // Leaderboard "showLeaderboard": true, "username": "FitUser28" ], onMessageReceived: { message in // Handle messages } ) ``` _Kotlin (Android)_ ```kotlin // Complete configuration example KinesteXSDK.initialize( context = this, apiKey = "YOUR_API_KEY", companyName = "MyFitnessApp", userId = "user_12345" ) val userDetails = UserDetails( age = 28, height = 165, weight = 60, gender = Gender.FEMALE, lifestyle = Lifestyle.ACTIVE ) KinesteXSDK.createWorkoutView( context = this, workoutName = workoutId, user = userDetails, // Theme & Loading via IStyle class (hex values without #) style = IStyle( style = "dark", loadingBackgroundColor = "1A1A2E", loadingStickmanColor = "e94560", loadingTextColor = "FFFFFF" ), isLoading = viewModel.isLoading, // Other customization via customParams customParams = mapOf( // Language "language" to "es", // UI Controls "isOnboarding" to false, "hideFeelingDialog" to true, // Camera "landmarkColor" to "#00FF88", "showSilhouette" to true, // Leaderboard "showLeaderboard" to true, "username" to "FitUser28" ), onMessageReceived = { message -> handleWebViewMessage(message) }, permissionHandler = this ) ``` _React Native_ ```jsx // Complete configuration example const postData: IPostData = { // Required key: 'YOUR_API_KEY', userId: 'user_12345', company: 'MyFitnessApp', // User Profile (direct support) age: 28, height: 165, weight: 60, gender: 'Female', lifestyle: Lifestyle.Active, // Theme (direct support) style: { style: 'dark', loadingBackgroundColor: '1A1A2E', loadingTextColor: 'FFFFFF', }, // Additional parameters via customParameters customParameters: { // Language language: 'es', // UI Controls isOnboarding: false, hideFeelingDialog: true, // Camera landmarkColor: '#00FF88', showSilhouette: true, shouldShowCameraSelector: true, // Leaderboard showLeaderboard: true, username: 'FitUser28', }, }; ``` _Flutter_ ```dart // Complete configuration example await KinesteXAIFramework.initialize( apiKey: "YOUR_API_KEY", companyName: "MyFitnessApp", userId: "user_12345", ); final userDetails = UserDetails( age: 28, height: 165, weight: 60, gender: Gender.female, lifestyle: Lifestyle.active, ); KinesteXAIFramework.createWorkoutView( workoutName: "Fitness Lite", user: userDetails, isShowKinestex: showKinesteX, isLoading: ValueNotifier(false), // Theme & Loading via IStyle class style: IStyle( style: 'dark', loadingBackgroundColor: '1A1A2E', // hex without # loadingStickmanColor: 'e94560', loadingTextColor: 'FFFFFF', ), // Other customization via customParams customParams: { // Language "language": "es", // UI Controls "isOnboarding": false, "hideFeelingDialog": true, // Camera "landmarkColor": "#00FF88", "showSilhouette": true, // Leaderboard "showLeaderboard": true, "username": "FitUser28", }, onMessageReceived: (message) { handleWebViewMessage(message); }, ); ``` _HTML / JavaScript_ ```html // Complete configuration example // All parameters passed as flat object const postData = { // Required userId: "user_12345", company: "MyFitnessApp", key: "YOUR_API_KEY", // User Profile age: 28, height: 165, weight: 60, gender: "Female", // Theme style: "dark", // Language language: "es", // UI Controls isOnboarding: false, hideFeelingDialog: true, // Camera landmarkColor: "#00FF88", showSilhouette: true, shouldShowCameraSelector: true, // Leaderboard showLeaderboard: true, username: "FitUser28", // Loading loadingBackgroundColor: "#1A1A2E", loadingTextColor: "#FFFFFF", }; // Send to iframe webView.contentWindow.postMessage(postData, srcURL); ``` _React (TypeScript)_ ```tsx // Complete configuration example const postData: IPostData = { // Required key: 'YOUR_API_KEY', userId: 'user_12345', company: 'MyFitnessApp', // User Profile (direct support) age: 28, height: 165, weight: 60, gender: 'Female', lifestyle: Lifestyle.Active, // Theme (direct support) style: { style: 'dark', loadingBackgroundColor: '1A1A2E', loadingTextColor: 'FFFFFF', }, // Additional parameters via customParameters customParameters: { // Language language: 'es', // UI Controls isOnboarding: false, hideFeelingDialog: true, // Camera landmarkColor: '#00FF88', showSilhouette: true, shouldShowCameraSelector: true, // Leaderboard showLeaderboard: true, username: 'FitUser28', }, }; ``` --- ## Content API The KinesteX Content API provides access to workout plans, individual workouts, and exercises. Use it to build custom content browsers, create personalized workout recommendations, or integrate KinesteX content into your app. **SDK vs REST API:** - **Swift, Kotlin, Flutter**: Use the built-in SDK convenience methods (recommended) - **React Native, React TypeScript, HTML/JS**: Use direct REST API calls **Base URL:** `https://admin.kinestex.com/api/v1/` **Available Endpoints:** | Endpoint | Description | |----------|-------------| | /workouts | Fetch workout content | | /plans | Fetch workout plan content | | /exercises | Fetch exercise content | **Headers (REST API only):** | Header | Description | |--------|-------------| | x-api-key | Your API key for authentication | | x-company-name | The name of your company | ### Getting Started Before using the Content API, ensure your SDK is properly initialized. The API methods are only available after initialization. **For SDK platforms (Swift, Kotlin, Flutter):** Initialize the SDK with your credentials **For REST platforms (React Native, React TypeScript, HTML/JS):** Set up your request headers **Initialize SDK / Setup Headers** _Swift (iOS)_ ```swift import KinesteXAIKit // Initialize KinesteXAIKit with your credentials let kinestex = KinesteXAIKit( apiKey: "YOUR_API_KEY", companyName: "YOUR_COMPANY", userId: "user_123" ) // Now you can use the Content API methods: // - kinestex.fetchWorkouts() // - kinestex.fetchPlans() // - kinestex.fetchExercises() // - kinestex.fetchWorkout(id:) // - kinestex.fetchPlan(id:) // - kinestex.fetchExercise(id:) // - kinestex.fetchContent(contentType:, ...) ``` _Kotlin (Android)_ ```kotlin import com.kinestex.kinestexsdkkotlin.KinesteXSDK // SDK must be initialized before using Content API // This is typically done in your Application class or Activity // Access Content API through KinesteXSDK.api // Available method: // KinesteXSDK.api.fetchAPIContentData( // contentType: ContentType, // id: String? = null, // title: String? = null, // category: String? = null, // bodyParts: List? = null, // lastDocId: String? = null, // limit: Int? = null // ): APIContentResult ``` _Flutter_ ```dart import 'package:kinestex_sdk_flutter/kinestex_sdk.dart'; // Initialize the SDK before using Content API await KinesteXAIFramework.initialize( apiKey: "YOUR_API_KEY", companyName: "YOUR_COMPANY", userId: "user_123", ); // Access Content API through: // KinesteXAIFramework.apiService.fetchContent(...) ``` _React Native_ ```jsx // Set up headers for REST API calls const API_KEY = 'YOUR_API_KEY'; const COMPANY_NAME = 'YOUR_COMPANY'; const BASE_URL = 'https://admin.kinestex.com/api/v1'; const headers = { 'x-api-key': API_KEY, 'x-company-name': COMPANY_NAME, }; // Use these headers in all fetch requests ``` _HTML / JavaScript_ ```html // Set up headers for REST API calls const API_KEY = 'YOUR_API_KEY'; const COMPANY_NAME = 'YOUR_COMPANY'; const BASE_URL = 'https://admin.kinestex.com/api/v1'; const headers = { 'x-api-key': API_KEY, 'x-company-name': COMPANY_NAME, }; // Use these headers in all fetch requests ``` _React (TypeScript)_ ```tsx // Set up headers for REST API calls const API_KEY = 'YOUR_API_KEY'; const COMPANY_NAME = 'YOUR_COMPANY'; const BASE_URL = 'https://admin.kinestex.com/api/v1'; const headers: HeadersInit = { 'x-api-key': API_KEY, 'x-company-name': COMPANY_NAME, }; // TypeScript interfaces for API responses interface WorkoutModel { id: string; title: string; category: string; calories: number; total_minutes: number; body_parts: string[]; dif_level: string; description: string; workout_desc_img: string; sequence: ExerciseModel[]; } interface ExerciseModel { id: string; title: string; body_parts: string[]; video_url: string; thumbnail_url: string; model_id: string; } interface PlanModel { id: string; title: string; img_url: string; category: Record; levels: Record; } ``` **Available SDK Methods** — Swift (iOS) KinesteXAIKit provides convenient methods that handle all the complexity of API calls for you. _Convenience Methods (Recommended)_ ```swift // Fetch lists with optional filters func fetchWorkouts(category: String? = nil, bodyParts: [BodyPart]? = nil, limit: Int? = 10, lastDocId: String? = nil, lang: String = "en") async -> Result func fetchExercises(bodyParts: [BodyPart]? = nil, limit: Int? = 10, lastDocId: String? = nil, lang: String = "en") async -> Result func fetchPlans(category: String? = nil, limit: Int? = 10, lastDocId: String? = nil, lang: String = "en") async -> Result // Fetch single items by ID func fetchWorkout(id: String, lang: String = "en") async -> Result func fetchExercise(id: String, lang: String = "en") async -> Result func fetchPlan(id: String, lang: String = "en") async -> Result ``` _Advanced Method (Full Control)_ ```swift // Use fetchContent for advanced filtering or when you need the raw result type func fetchContent( contentType: ContentType, // .workout, .plan, .exercise id: String? = nil, title: String? = nil, lang: String = "en", category: String? = nil, bodyParts: [BodyPart]? = nil, lastDocId: String? = nil, limit: Int? = nil ) async -> APIContentResult ``` **ContentType Enum** — Kotlin (Android) Use these values to specify what type of content to fetch. _Available Content Types_ ```kotlin enum class ContentType { WORKOUT, // Fetch workouts PLAN, // Fetch workout plans EXERCISE // Fetch exercises } ``` **ContentType Enum** — Flutter Use these values to specify what type of content to fetch. _Available Content Types_ ```dart enum ContentType { workout, // Fetch workouts plan, // Fetch workout plans exercise // Fetch exercises } ``` ### Fetching Content Lists Fetch lists of workouts, plans, or exercises with optional filtering. **Parameters:** | Parameter | Type | Description | |-----------|------|-------------| | category | String | Filter by category. **Required for plans** in Flutter, Swift, and Kotlin SDKs. Optional for workouts and exercises. | | bodyParts | [BodyPart] | Filter by targeted body parts (optional) | | include_kinestex | Bool | Include KinesteX workout library in results (default: true) | | limit | Int | Number of results to return (default: 10) | | lang | String | Language code (default: "en") | | lastDocId | String | For pagination (optional) | | translation_languages | String | Filter by available translations. Comma-separated language codes (e.g. "es,fr") or repeated query parameters (optional, REST API only) | **Fetch Workouts** _Swift (iOS)_ ```swift // Fetch workouts with optional filters Task { let result = await kinestex.fetchWorkouts( category: "Fitness", // or "Rehabilitation" limit: 10 ) switch result { case .success(let response): let workouts = response.workouts print("Fetched \(workouts.count) workouts") for workout in workouts { print("- \(workout.title): \(workout.totalMinutes ?? 0) mins") } // Store lastDocId for pagination let nextPageId = response.lastDocId case .failure(let error): print("Error: \(error.localizedDescription)") } } ``` _Kotlin (Android)_ ```kotlin // Fetch workouts using coroutines lifecycleScope.launch { val result = withContext(Dispatchers.IO) { KinesteXSDK.api.fetchAPIContentData( contentType = ContentType.WORKOUT, category = "Fitness", // or "Rehabilitation" limit = 10 ) } when (result) { is APIContentResult.Workouts -> { val workouts = result.workouts Log.d("API", "Fetched ${workouts.size} workouts") workouts.forEach { workout -> Log.d("API", "- ${workout.title}") } // Store lastDocId for pagination val nextPageId = result.lastDocId } is APIContentResult.Error -> { Log.e("API", "Error: ${result.message}") } else -> { Log.w("API", "Unexpected result type") } } } ``` _Flutter_ ```dart // Fetch workouts Future fetchWorkouts() async { final result = await KinesteXAIFramework.apiService.fetchContent( contentType: ContentType.workout, category: "Fitness", // or "Rehabilitation" limit: 10, ); switch (result) { case WorkoutsResult(:final response): final workouts = response.workouts; print('Fetched ${workouts.length} workouts'); for (final workout in workouts) { print('- ${workout.title}'); } // Store lastDocId for pagination final nextPageId = response.lastDocId; case ErrorResult(:final message): print('Error: $message'); default: print('Unexpected result type'); } } ``` _React Native_ ```jsx // Fetch workouts using fetch API const fetchWorkouts = async ( category?: string, limit: number = 10 ): Promise => { const params = new URLSearchParams({ limit: String(limit), }); if (category) { params.append('category', category); } const response = await fetch( `${BASE_URL}/workouts?${params}`, { headers } ); if (!response.ok) { throw new Error(`API Error: ${response.status}`); } const data = await response.json(); return data.workouts; }; // Usage const workouts = await fetchWorkouts('Fitness', 10); console.log(`Fetched ${workouts.length} workouts`); ``` _HTML / JavaScript_ ```html // Fetch workouts using fetch API async function fetchWorkouts(category, limit = 10) { const params = new URLSearchParams({ limit: String(limit) }); if (category) { params.append('category', category); } const response = await fetch( `${BASE_URL}/workouts?${params}`, { headers } ); if (!response.ok) { throw new Error(`API Error: ${response.status}`); } const data = await response.json(); return data.workouts; } // Usage fetchWorkouts('Fitness', 10) .then(workouts => console.log(`Fetched ${workouts.length} workouts`)) .catch(error => console.error('Error:', error)); ``` _React (TypeScript)_ ```tsx // Fetch workouts with TypeScript interface WorkoutsResponse { workouts: WorkoutModel[]; lastDocId?: string; } const fetchWorkouts = async ( category?: string, limit: number = 10 ): Promise => { const params = new URLSearchParams({ limit: String(limit), }); if (category) { params.append('category', category); } const response = await fetch( `${BASE_URL}/workouts?${params}`, { headers } ); if (!response.ok) { throw new Error(`API Error: ${response.status}`); } return response.json(); }; // Usage const { workouts, lastDocId } = await fetchWorkouts('Fitness', 10); console.log(`Fetched ${workouts.length} workouts`); ``` **Fetch Plans** _Swift (iOS)_ ```swift // Fetch workout plans Task { let result = await kinestex.fetchPlans( category: "Strength", // Rehabilitation, Weight Management, Cardio, Strength limit: 5 ) switch result { case .success(let response): let plans = response.plans print("Fetched \(plans.count) plans") case .failure(let error): print("Error: \(error.localizedDescription)") } } ``` _Kotlin (Android)_ ```kotlin // Fetch workout plans // IMPORTANT: Always provide 'category' when fetching plans. lifecycleScope.launch { val result = withContext(Dispatchers.IO) { KinesteXSDK.api.fetchAPIContentData( contentType = ContentType.PLAN, category = "Strength", // Required: Rehabilitation, Weight Management, Cardio, Strength limit = 5 ) } when (result) { is APIContentResult.Plans -> { val plans = result.plans Log.d("API", "Fetched ${plans.size} plans") } is APIContentResult.Error -> { Log.e("API", "Error: ${result.message}") } else -> {} } } ``` _Flutter_ ```dart // Fetch workout plans // IMPORTANT: Always provide 'category' when fetching plans. // If category is null, the SDK may interpret the response as a single PlanResult // instead of PlansResult (list). Future fetchPlans() async { final result = await KinesteXAIFramework.apiService.fetchContent( contentType: ContentType.plan, category: "Strength", // Required: Rehabilitation, Weight Management, Cardio, Strength limit: 5, ); switch (result) { case PlansResult(:final response): final plans = response.plans; print('Fetched ${plans.length} plans'); case ErrorResult(:final message): print('Error: $message'); default: break; } } ``` _React Native_ ```jsx // Fetch workout plans const fetchPlans = async ( category?: string, limit: number = 5 ): Promise => { const params = new URLSearchParams({ limit: String(limit) }); if (category) { params.append('category', category); } const response = await fetch( `${BASE_URL}/plans?${params}`, { headers } ); if (!response.ok) { throw new Error(`API Error: ${response.status}`); } const data = await response.json(); return data.plans; }; // Usage - Plan categories: Rehabilitation, Weight Management, Cardio, Strength const plans = await fetchPlans('Strength', 5); ``` _HTML / JavaScript_ ```html // Fetch workout plans async function fetchPlans(category, limit = 5) { const params = new URLSearchParams({ limit: String(limit) }); if (category) { params.append('category', category); } const response = await fetch( `${BASE_URL}/plans?${params}`, { headers } ); if (!response.ok) { throw new Error(`API Error: ${response.status}`); } const data = await response.json(); return data.plans; } // Usage - Plan categories: Rehabilitation, Weight Management, Cardio, Strength fetchPlans('Strength', 5).then(plans => console.log(plans)); ``` _React (TypeScript)_ ```tsx // Fetch workout plans interface PlansResponse { plans: PlanModel[]; lastDocId?: string; } const fetchPlans = async ( category?: string, limit: number = 5 ): Promise => { const params = new URLSearchParams({ limit: String(limit) }); if (category) { params.append('category', category); } const response = await fetch( `${BASE_URL}/plans?${params}`, { headers } ); if (!response.ok) { throw new Error(`API Error: ${response.status}`); } return response.json(); }; // Usage - Plan categories: Rehabilitation, Weight Management, Cardio, Strength const { plans } = await fetchPlans('Strength', 5); ``` **Fetch Exercises** _Swift (iOS)_ ```swift // Fetch exercises filtered by body parts Task { let result = await kinestex.fetchExercises( bodyParts: [.abs, .glutes], limit: 10 ) switch result { case .success(let response): let exercises = response.exercises print("Fetched \(exercises.count) exercises") for exercise in exercises { print("- \(exercise.title): \(exercise.bodyParts.joined(separator: ", "))") } case .failure(let error): print("Error: \(error.localizedDescription)") } } ``` _Kotlin (Android)_ ```kotlin // Fetch exercises filtered by body parts lifecycleScope.launch { val result = withContext(Dispatchers.IO) { KinesteXSDK.api.fetchAPIContentData( contentType = ContentType.EXERCISE, bodyParts = listOf(BodyPart.ABS, BodyPart.GLUTES), limit = 10 ) } when (result) { is APIContentResult.Exercises -> { val exercises = result.exercises Log.d("API", "Fetched ${exercises.size} exercises") exercises.forEach { exercise -> Log.d("API", "- ${exercise.title}") } } is APIContentResult.Error -> { Log.e("API", "Error: ${result.message}") } else -> {} } } ``` _Flutter_ ```dart // Fetch exercises filtered by body parts Future fetchExercises() async { final result = await KinesteXAIFramework.apiService.fetchContent( contentType: ContentType.exercise, bodyParts: [BodyPart.abs, BodyPart.glutes], limit: 10, ); switch (result) { case ExercisesResult(:final response): final exercises = response.exercises; print('Fetched ${exercises.length} exercises'); for (final exercise in exercises) { print('- ${exercise.title}'); } case ErrorResult(:final message): print('Error: $message'); default: break; } } ``` _React Native_ ```jsx // Fetch exercises filtered by body parts const fetchExercises = async ( bodyParts?: string[], limit: number = 10 ): Promise => { const params = new URLSearchParams({ limit: String(limit) }); if (bodyParts && bodyParts.length > 0) { params.append('body_parts', bodyParts.join(',')); } const response = await fetch( `${BASE_URL}/exercises?${params}`, { headers } ); if (!response.ok) { throw new Error(`API Error: ${response.status}`); } const data = await response.json(); return data.exercises; }; // Usage const exercises = await fetchExercises(['Abs', 'Glutes'], 10); ``` _HTML / JavaScript_ ```html // Fetch exercises filtered by body parts async function fetchExercises(bodyParts, limit = 10) { const params = new URLSearchParams({ limit: String(limit) }); if (bodyParts && bodyParts.length > 0) { params.append('body_parts', bodyParts.join(',')); } const response = await fetch( `${BASE_URL}/exercises?${params}`, { headers } ); if (!response.ok) { throw new Error(`API Error: ${response.status}`); } const data = await response.json(); return data.exercises; } // Usage fetchExercises(['Abs', 'Glutes'], 10).then(exercises => console.log(exercises)); ``` _React (TypeScript)_ ```tsx // Fetch exercises filtered by body parts interface ExercisesResponse { exercises: ExerciseModel[]; lastDocId?: string; } const fetchExercises = async ( bodyParts?: string[], limit: number = 10 ): Promise => { const params = new URLSearchParams({ limit: String(limit) }); if (bodyParts && bodyParts.length > 0) { params.append('body_parts', bodyParts.join(',')); } const response = await fetch( `${BASE_URL}/exercises?${params}`, { headers } ); if (!response.ok) { throw new Error(`API Error: ${response.status}`); } return response.json(); }; // Usage const { exercises } = await fetchExercises(['Abs', 'Glutes'], 10); ``` ### Fetching Single Items Fetch a specific workout, plan, or exercise by ID or title. **By ID:** Use the unique document ID for exact match **By Title:** Use the content title (case-insensitive, returns first match) **Fetch by ID** _Swift (iOS)_ ```swift // Fetch a specific workout by ID Task { let result = await kinestex.fetchWorkout(id: "9zE1kzOzpU5d5dAJrPOY") switch result { case .success(let workout): print("Workout: \(workout.title)") print("Duration: \(workout.totalMinutes ?? 0) minutes") print("Calories: \(workout.totalCalories ?? 0)") print("Exercises: \(workout.sequence.count)") case .failure(let error): print("Error: \(error.localizedDescription)") } } // Fetch a specific exercise by ID Task { let result = await kinestex.fetchExercise(id: "jz73VFlUyZ9nyd64OjRb") switch result { case .success(let exercise): print("Exercise: \(exercise.title)") print("Model ID: \(exercise.modelId)") case .failure(let error): print("Error: \(error.localizedDescription)") } } // Fetch a specific plan by ID Task { let result = await kinestex.fetchPlan(id: "22B3qRU2r75hVXHgGiGx") switch result { case .success(let plan): print("Plan: \(plan.title)") case .failure(let error): print("Error: \(error.localizedDescription)") } } ``` _Kotlin (Android)_ ```kotlin // Fetch a specific workout by ID lifecycleScope.launch { val result = withContext(Dispatchers.IO) { KinesteXSDK.api.fetchAPIContentData( contentType = ContentType.WORKOUT, id = "9zE1kzOzpU5d5dAJrPOY" ) } when (result) { is APIContentResult.Workout -> { val workout = result.workout Log.d("API", "Workout: ${workout.title}") Log.d("API", "Duration: ${workout.totalMinutes} minutes") } is APIContentResult.Error -> { Log.e("API", "Error: ${result.message}") } else -> {} } } // Fetch a specific exercise by ID lifecycleScope.launch { val result = withContext(Dispatchers.IO) { KinesteXSDK.api.fetchAPIContentData( contentType = ContentType.EXERCISE, id = "jz73VFlUyZ9nyd64OjRb" ) } when (result) { is APIContentResult.Exercise -> { val exercise = result.exercise Log.d("API", "Exercise: ${exercise.title}") } is APIContentResult.Error -> { Log.e("API", "Error: ${result.message}") } else -> {} } } ``` _Flutter_ ```dart // Fetch a specific workout by ID Future fetchWorkoutById() async { final result = await KinesteXAIFramework.apiService.fetchContent( contentType: ContentType.workout, id: "9zE1kzOzpU5d5dAJrPOY", ); switch (result) { case WorkoutResult(:final workout): print('Workout: ${workout.title}'); print('Duration: ${workout.totalMinutes} minutes'); print('Exercises: ${workout.sequence.length}'); case ErrorResult(:final message): print('Error: $message'); default: break; } } // Fetch a specific exercise by ID Future fetchExerciseById() async { final result = await KinesteXAIFramework.apiService.fetchContent( contentType: ContentType.exercise, id: "jz73VFlUyZ9nyd64OjRb", ); switch (result) { case ExerciseResult(:final exercise): print('Exercise: ${exercise.title}'); print('Model ID: ${exercise.modelId}'); case ErrorResult(:final message): print('Error: $message'); default: break; } } ``` _React Native_ ```jsx // Fetch a specific workout by ID const fetchWorkoutById = async (id: string): Promise => { const response = await fetch( `${BASE_URL}/workouts/${id}`, { headers } ); if (!response.ok) { throw new Error(`API Error: ${response.status}`); } return response.json(); }; // Fetch a specific exercise by ID const fetchExerciseById = async (id: string): Promise => { const response = await fetch( `${BASE_URL}/exercises/${id}`, { headers } ); if (!response.ok) { throw new Error(`API Error: ${response.status}`); } return response.json(); }; // Fetch a specific plan by ID const fetchPlanById = async (id: string): Promise => { const response = await fetch( `${BASE_URL}/plans/${id}`, { headers } ); if (!response.ok) { throw new Error(`API Error: ${response.status}`); } return response.json(); }; // Usage const workout = await fetchWorkoutById('9zE1kzOzpU5d5dAJrPOY'); const exercise = await fetchExerciseById('jz73VFlUyZ9nyd64OjRb'); const plan = await fetchPlanById('22B3qRU2r75hVXHgGiGx'); ``` _HTML / JavaScript_ ```html // Fetch a specific workout by ID async function fetchWorkoutById(id) { const response = await fetch( `${BASE_URL}/workouts/${id}`, { headers } ); if (!response.ok) { throw new Error(`API Error: ${response.status}`); } return response.json(); } // Fetch a specific exercise by ID async function fetchExerciseById(id) { const response = await fetch( `${BASE_URL}/exercises/${id}`, { headers } ); if (!response.ok) { throw new Error(`API Error: ${response.status}`); } return response.json(); } // Fetch a specific plan by ID async function fetchPlanById(id) { const response = await fetch( `${BASE_URL}/plans/${id}`, { headers } ); if (!response.ok) { throw new Error(`API Error: ${response.status}`); } return response.json(); } // Usage fetchWorkoutById('9zE1kzOzpU5d5dAJrPOY').then(workout => console.log(workout)); ``` _React (TypeScript)_ ```tsx // Fetch a specific workout by ID const fetchWorkoutById = async (id: string): Promise => { const response = await fetch( `${BASE_URL}/workouts/${id}`, { headers } ); if (!response.ok) { throw new Error(`API Error: ${response.status}`); } return response.json(); }; // Fetch a specific exercise by ID const fetchExerciseById = async (id: string): Promise => { const response = await fetch( `${BASE_URL}/exercises/${id}`, { headers } ); if (!response.ok) { throw new Error(`API Error: ${response.status}`); } return response.json(); }; // Fetch a specific plan by ID const fetchPlanById = async (id: string): Promise => { const response = await fetch( `${BASE_URL}/plans/${id}`, { headers } ); if (!response.ok) { throw new Error(`API Error: ${response.status}`); } return response.json(); }; // Usage const workout = await fetchWorkoutById('9zE1kzOzpU5d5dAJrPOY'); console.log(`Workout: ${workout.title}`); ``` **Fetch by Title** _Swift (iOS)_ ```swift // Fetch content by title (returns first match) Task { let result = await kinestex.fetchContent( contentType: .workout, title: "Fitness Lite" ) switch result { case .workout(let workout): print("Found workout: \(workout.title)") case .error(let message): print("Error: \(message)") default: print("Unexpected result type") } } ``` _Kotlin (Android)_ ```kotlin // Fetch content by title (returns first match) lifecycleScope.launch { val result = withContext(Dispatchers.IO) { KinesteXSDK.api.fetchAPIContentData( contentType = ContentType.WORKOUT, title = "Fitness Lite" ) } when (result) { is APIContentResult.Workout -> { Log.d("API", "Found workout: ${result.workout.title}") } is APIContentResult.Error -> { Log.e("API", "Error: ${result.message}") } else -> {} } } ``` _Flutter_ ```dart // Fetch content by title (returns first match) Future fetchByTitle() async { final result = await KinesteXAIFramework.apiService.fetchContent( contentType: ContentType.workout, title: "Fitness Lite", ); switch (result) { case WorkoutResult(:final workout): print('Found workout: ${workout.title}'); case ErrorResult(:final message): print('Error: $message'); default: break; } } ``` _React Native_ ```jsx // Fetch content by title (returns first match) const fetchWorkoutByTitle = async (title: string): Promise => { const response = await fetch( `${BASE_URL}/workouts/${encodeURIComponent(title)}`, { headers } ); if (!response.ok) { throw new Error(`API Error: ${response.status}`); } return response.json(); }; // Usage const workout = await fetchWorkoutByTitle('Fitness Lite'); ``` _HTML / JavaScript_ ```html // Fetch content by title (returns first match) async function fetchWorkoutByTitle(title) { const response = await fetch( `${BASE_URL}/workouts/${encodeURIComponent(title)}`, { headers } ); if (!response.ok) { throw new Error(`API Error: ${response.status}`); } return response.json(); } // Usage fetchWorkoutByTitle('Fitness Lite').then(workout => console.log(workout)); ``` _React (TypeScript)_ ```tsx // Fetch content by title (returns first match) const fetchWorkoutByTitle = async (title: string): Promise => { const response = await fetch( `${BASE_URL}/workouts/${encodeURIComponent(title)}`, { headers } ); if (!response.ok) { throw new Error(`API Error: ${response.status}`); } return response.json(); }; // Usage const workout = await fetchWorkoutByTitle('Fitness Lite'); ``` ### Filtering & Parameters Filter content by category and body parts for targeted results. **Workout Categories:** Fitness, Rehabilitation **Plan Categories:** Rehabilitation, Weight Management, Cardio, Strength > **Important (Flutter, Swift & Kotlin SDKs):** When fetching plans, always provide the \`category\` parameter. **Include KinesteX Library:** | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | include_kinestex | Bool | true | When set to \`true\`, results include workouts, plans, and exercises from the KinesteX workout library. Set to \`false\` to exclude KinesteX library content and only return your own custom content. | **Filter by Translation Languages (REST API):** | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | translation_languages | String | — | Filter content by available translations. Pass a comma-separated list of language codes (e.g. \`es,fr\`) or repeat the parameter for each language (e.g. \`translation_languages=es&translation_languages=fr\`). Only content with translations in **all** specified languages is returned. Optional — omit to return all content regardless of translations. | **Body Parts (BodyPart enum):** | SDK Value (Swift) | SDK Value (Kotlin) | SDK Value (Flutter) | REST API Value | |-------------------|--------------------|--------------------|----------------| | .abs | ABS | BodyPart.abs | Abs | | .biceps | BICEPS | BodyPart.biceps | Biceps | | .calves | CALVES | BodyPart.calves | Calves | | .chest | CHEST | BodyPart.chest | Chest | | .externalOblique | EXTERNAL_OBLIQUE | BodyPart.externalOblique | External Oblique | | .forearms | FOREARMS | BodyPart.forearms | Forearms | | .glutes | GLUTES | BodyPart.glutes | Glutes | | .hamstrings | HAMSTRINGS | BodyPart.hamstrings | Hamstrings | | .lats | LATS | BodyPart.lats | Lats | | .lowerBack | LOWER_BACK | BodyPart.lowerBack | Lower Back | | .neck | NECK | BodyPart.neck | Neck | | .quads | QUADS | BodyPart.quads | Quads | | .shoulders | SHOULDERS | BodyPart.shoulders | Shoulders | | .traps | TRAPS | BodyPart.traps | Traps | | .triceps | TRICEPS | BodyPart.triceps | Triceps | | .fullBody | FULL_BODY | BodyPart.fullBody | Full Body | **Filter by Category and Body Parts** _Swift (iOS)_ ```swift // Combine category and body parts filters Task { let result = await kinestex.fetchContent( contentType: .workout, category: "Fitness", bodyParts: [.abs, .glutes, .quads], limit: 10 ) switch result { case .workouts(let response): let workouts = response.workouts print("Found \(workouts.count) workouts targeting abs, glutes, and quads") case .error(let message): print("Error: \(message)") default: break } } ``` _Kotlin (Android)_ ```kotlin // Combine category and body parts filters lifecycleScope.launch { val result = withContext(Dispatchers.IO) { KinesteXSDK.api.fetchAPIContentData( contentType = ContentType.WORKOUT, category = "Fitness", bodyParts = listOf(BodyPart.ABS, BodyPart.GLUTES, BodyPart.QUADS), limit = 10 ) } when (result) { is APIContentResult.Workouts -> { val workouts = result.workouts Log.d("API", "Found ${workouts.size} workouts") } is APIContentResult.Error -> { Log.e("API", "Error: ${result.message}") } else -> {} } } ``` _Flutter_ ```dart // Combine category and body parts filters Future fetchFilteredWorkouts() async { final result = await KinesteXAIFramework.apiService.fetchContent( contentType: ContentType.workout, category: "Fitness", bodyParts: [BodyPart.abs, BodyPart.glutes, BodyPart.quads], limit: 10, ); switch (result) { case WorkoutsResult(:final response): print('Found ${response.workouts.length} workouts'); case ErrorResult(:final message): print('Error: $message'); default: break; } } ``` _React Native_ ```jsx // Combine category and body parts filters const fetchFilteredWorkouts = async ( category: string, bodyParts: string[], limit: number = 10 ): Promise => { const params = new URLSearchParams({ category, body_parts: bodyParts.join(','), limit: String(limit), }); const response = await fetch( `${BASE_URL}/workouts?${params}`, { headers } ); if (!response.ok) { throw new Error(`API Error: ${response.status}`); } const data = await response.json(); return data.workouts; }; // Usage const workouts = await fetchFilteredWorkouts( 'Fitness', ['Abs', 'Glutes', 'Quads'], 10 ); ``` _HTML / JavaScript_ ```html // Combine category and body parts filters async function fetchFilteredWorkouts(category, bodyParts, limit = 10) { const params = new URLSearchParams({ category, body_parts: bodyParts.join(','), limit: String(limit), }); const response = await fetch( `${BASE_URL}/workouts?${params}`, { headers } ); if (!response.ok) { throw new Error(`API Error: ${response.status}`); } const data = await response.json(); return data.workouts; } // Usage fetchFilteredWorkouts('Fitness', ['Abs', 'Glutes', 'Quads'], 10) .then(workouts => console.log(workouts)); ``` _React (TypeScript)_ ```tsx // Combine category and body parts filters const fetchFilteredWorkouts = async ( category: string, bodyParts: string[], limit: number = 10 ): Promise => { const params = new URLSearchParams({ category, body_parts: bodyParts.join(','), limit: String(limit), }); const response = await fetch( `${BASE_URL}/workouts?${params}`, { headers } ); if (!response.ok) { throw new Error(`API Error: ${response.status}`); } const data = await response.json(); return data.workouts; }; // Usage const workouts = await fetchFilteredWorkouts( 'Fitness', ['Abs', 'Glutes', 'Quads'], 10 ); ``` **Exclude KinesteX Library Content** _Swift (iOS)_ ```swift // Fetch only your custom workouts (exclude KinesteX library) Task { let result = await kinestex.fetchWorkouts( category: "Fitness", includeKinestex: false, // Exclude KinesteX library content limit: 10 ) switch result { case .success(let response): let workouts = response.workouts print("Fetched \(workouts.count) custom workouts") case .failure(let error): print("Error: \(error.localizedDescription)") } } ``` _Kotlin (Android)_ ```kotlin // Fetch only your custom workouts (exclude KinesteX library) lifecycleScope.launch { val result = withContext(Dispatchers.IO) { KinesteXSDK.api.fetchAPIContentData( contentType = ContentType.WORKOUT, category = "Fitness", includeKinestex = false, // Exclude KinesteX library content limit = 10 ) } when (result) { is APIContentResult.Workouts -> { val workouts = result.workouts Log.d("API", "Fetched ${workouts.size} custom workouts") } is APIContentResult.Error -> { Log.e("API", "Error: ${result.message}") } else -> {} } } ``` _Flutter_ ```dart // Fetch only your custom workouts (exclude KinesteX library) Future fetchCustomWorkouts() async { final result = await KinesteXAIFramework.apiService.fetchContent( contentType: ContentType.workout, category: "Fitness", includeKinestex: false, // Exclude KinesteX library content limit: 10, ); switch (result) { case WorkoutsResult(:final response): print('Fetched ${response.workouts.length} custom workouts'); case ErrorResult(:final message): print('Error: $message'); default: break; } } ``` _React Native_ ```jsx // Fetch only your custom workouts (exclude KinesteX library) const fetchCustomWorkouts = async ( category: string, limit: number = 10 ): Promise => { const params = new URLSearchParams({ category, include_kinestex: 'false', // Exclude KinesteX library content limit: String(limit), }); const response = await fetch( `${BASE_URL}/workouts?${params}`, { headers } ); if (!response.ok) { throw new Error(`API Error: ${response.status}`); } const data = await response.json(); return data.workouts; }; // Usage - returns only your custom content const customWorkouts = await fetchCustomWorkouts('Fitness', 10); ``` _HTML / JavaScript_ ```html // Fetch only your custom workouts (exclude KinesteX library) async function fetchCustomWorkouts(category, limit = 10) { const params = new URLSearchParams({ category, include_kinestex: 'false', // Exclude KinesteX library content limit: String(limit), }); const response = await fetch( `${BASE_URL}/workouts?${params}`, { headers } ); if (!response.ok) { throw new Error(`API Error: ${response.status}`); } const data = await response.json(); return data.workouts; } // Usage - returns only your custom content fetchCustomWorkouts('Fitness', 10) .then(workouts => console.log(workouts)); ``` _React (TypeScript)_ ```tsx // Fetch only your custom workouts (exclude KinesteX library) const fetchCustomWorkouts = async ( category: string, limit: number = 10 ): Promise => { const params = new URLSearchParams({ category, include_kinestex: 'false', // Exclude KinesteX library content limit: String(limit), }); const response = await fetch( `${BASE_URL}/workouts?${params}`, { headers } ); if (!response.ok) { throw new Error(`API Error: ${response.status}`); } const data = await response.json(); return data.workouts; }; // Usage - returns only your custom content const customWorkouts = await fetchCustomWorkouts('Fitness', 10); ``` ### Pagination For large result sets, use pagination with the `lastDocId` parameter to fetch subsequent pages. **How it works:** 1. **First request:** Omit `lastDocId` to get the first page 2. **Store the ID:** Save the `lastDocId` from the response 3. **Next request:** Pass the saved ID as `lastDocId` to get the next page 4. **Repeat:** Continue until no more results are returned **Paginated Fetching** _Swift (iOS)_ ```swift // Fetch all workouts with pagination func fetchAllWorkouts() async throws -> [WorkoutModel] { var allWorkouts: [WorkoutModel] = [] var lastDocId: String? = nil repeat { let result = await kinestex.fetchWorkouts( category: "Fitness", limit: 10, lastDocId: lastDocId ) switch result { case .success(let response): allWorkouts.append(contentsOf: response.workouts) lastDocId = response.lastDocId // If lastDocId is empty or nil, we've reached the end if lastDocId?.isEmpty ?? true { lastDocId = nil } print("Fetched page with \(response.workouts.count) workouts") case .failure(let error): throw error } } while lastDocId != nil print("Total workouts fetched: \(allWorkouts.count)") return allWorkouts } ``` _Kotlin (Android)_ ```kotlin // Fetch all workouts with pagination suspend fun fetchAllWorkouts(): List { val allWorkouts = mutableListOf() var lastDocId: String? = null do { val result = KinesteXSDK.api.fetchAPIContentData( contentType = ContentType.WORKOUT, category = "Fitness", limit = 10, lastDocId = lastDocId ) when (result) { is APIContentResult.Workouts -> { allWorkouts.addAll(result.workouts) lastDocId = result.lastDocId?.takeIf { it.isNotEmpty() } Log.d("API", "Fetched page with ${result.workouts.size} workouts") } is APIContentResult.Error -> { throw Exception(result.message) } else -> { lastDocId = null } } } while (lastDocId != null) Log.d("API", "Total workouts fetched: ${allWorkouts.size}") return allWorkouts } ``` _Flutter_ ```dart // Fetch all workouts with pagination Future> fetchAllWorkouts() async { final allWorkouts = []; String? lastDocId; do { final result = await KinesteXAIFramework.apiService.fetchContent( contentType: ContentType.workout, category: "Fitness", limit: 10, lastDocId: lastDocId, ); switch (result) { case WorkoutsResult(:final response): allWorkouts.addAll(response.workouts); lastDocId = response.lastDocId.isNotEmpty ? response.lastDocId : null; print('Fetched page with ${response.workouts.length} workouts'); case ErrorResult(:final message): throw Exception(message); default: lastDocId = null; } } while (lastDocId != null); print('Total workouts fetched: ${allWorkouts.length}'); return allWorkouts; } ``` _React Native_ ```jsx // Fetch all workouts with pagination const fetchAllWorkouts = async (category: string): Promise => { const allWorkouts: WorkoutModel[] = []; let lastDocId: string | undefined = undefined; do { const params = new URLSearchParams({ category, limit: '10', }); if (lastDocId) { params.append('lastDocId', lastDocId); } const response = await fetch( `${BASE_URL}/workouts?${params}`, { headers } ); if (!response.ok) { throw new Error(`API Error: ${response.status}`); } const data = await response.json(); allWorkouts.push(...data.workouts); lastDocId = data.lastDocId || undefined; console.log(`Fetched page with ${data.workouts.length} workouts`); } while (lastDocId); console.log(`Total workouts fetched: ${allWorkouts.length}`); return allWorkouts; }; ``` _HTML / JavaScript_ ```html // Fetch all workouts with pagination async function fetchAllWorkouts(category) { const allWorkouts = []; let lastDocId = null; do { const params = new URLSearchParams({ category, limit: '10', }); if (lastDocId) { params.append('lastDocId', lastDocId); } const response = await fetch( `${BASE_URL}/workouts?${params}`, { headers } ); if (!response.ok) { throw new Error(`API Error: ${response.status}`); } const data = await response.json(); allWorkouts.push(...data.workouts); lastDocId = data.lastDocId || null; console.log(`Fetched page with ${data.workouts.length} workouts`); } while (lastDocId); console.log(`Total workouts fetched: ${allWorkouts.length}`); return allWorkouts; } ``` _React (TypeScript)_ ```tsx // Fetch all workouts with pagination const fetchAllWorkouts = async (category: string): Promise => { const allWorkouts: WorkoutModel[] = []; let lastDocId: string | undefined = undefined; do { const params = new URLSearchParams({ category, limit: '10', }); if (lastDocId) { params.append('lastDocId', lastDocId); } const response = await fetch( `${BASE_URL}/workouts?${params}`, { headers } ); if (!response.ok) { throw new Error(`API Error: ${response.status}`); } const data: WorkoutsResponse = await response.json(); allWorkouts.push(...data.workouts); lastDocId = data.lastDocId || undefined; console.log(`Fetched page with ${data.workouts.length} workouts`); } while (lastDocId); console.log(`Total workouts fetched: ${allWorkouts.length}`); return allWorkouts; }; ``` ### Error Handling Handle API errors gracefully in your application. **Response Codes:** | Status | Description | |--------|-------------| | 200/201 | Request successful | | 400 | Validation error (check parameters) | | 401 | Unauthorized (invalid API key) | | 404 | Content not found | | 500 | Internal server error | **Error Handling Patterns** _Swift (iOS)_ ```swift // Comprehensive error handling with Swift SDK Task { let result = await kinestex.fetchWorkouts(category: "Fitness", limit: 10) switch result { case .success(let response): // Handle successful response let workouts = response.workouts print("Success: Fetched \(workouts.count) workouts") case .failure(let error): // Handle different error types if let urlError = error as? URLError { switch urlError.code { case .notConnectedToInternet: print("No internet connection") case .timedOut: print("Request timed out") default: print("Network error: \(urlError.localizedDescription)") } } else { print("Error: \(error.localizedDescription)") } } } // Using fetchContent for advanced error handling Task { let result = await kinestex.fetchContent( contentType: .workout, id: "invalid_id" ) switch result { case .workout(let workout): print("Found: \(workout.title)") case .error(let message): // API returned an error message print("API Error: \(message)") case .rawData(let data, let errorMessage): // Parsing failed, but raw data is available print("Parse error: \(errorMessage ?? "Unknown")") print("Raw data: \(data)") default: print("Unexpected result type") } } ``` _Kotlin (Android)_ ```kotlin // Comprehensive error handling with Kotlin SDK lifecycleScope.launch { try { val result = withContext(Dispatchers.IO) { KinesteXSDK.api.fetchAPIContentData( contentType = ContentType.WORKOUT, category = "Fitness", limit = 10 ) } when (result) { is APIContentResult.Workouts -> { // Handle successful response val workouts = result.workouts Log.d("API", "Success: Fetched ${workouts.size} workouts") } is APIContentResult.Error -> { // API returned an error Log.e("API", "API Error: ${result.message}") // Show user-friendly message Toast.makeText( this@MainActivity, "Failed to load workouts: ${result.message}", Toast.LENGTH_LONG ).show() } else -> { Log.w("API", "Unexpected result type") } } } catch (e: Exception) { // Handle network or other exceptions Log.e("API", "Exception: ${e.message}") when (e) { is java.net.UnknownHostException -> { Toast.makeText(this@MainActivity, "No internet connection", Toast.LENGTH_SHORT).show() } is java.net.SocketTimeoutException -> { Toast.makeText(this@MainActivity, "Request timed out", Toast.LENGTH_SHORT).show() } else -> { Toast.makeText(this@MainActivity, "Error: ${e.message}", Toast.LENGTH_SHORT).show() } } } } ``` _Flutter_ ```dart // Comprehensive error handling with Flutter SDK Future fetchWithErrorHandling() async { try { final result = await KinesteXAIFramework.apiService.fetchContent( contentType: ContentType.workout, category: "Fitness", limit: 10, ); switch (result) { case WorkoutsResult(:final response): // Handle successful response print('Success: Fetched ${response.workouts.length} workouts'); case ErrorResult(:final message): // API returned an error print('API Error: $message'); // Show user-friendly message ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Failed to load workouts: $message')), ); case RawDataResult(:final data, :final errorMessage): // Parsing failed, but raw data is available print('Parse error: ${errorMessage ?? "Unknown"}'); print('Raw data keys: ${data.keys}'); default: print('Unexpected result type'); } } on SocketException { // No internet connection print('No internet connection'); } on TimeoutException { // Request timed out print('Request timed out'); } catch (e) { // Other errors print('Error: $e'); } } ``` _React Native_ ```jsx // Comprehensive error handling with fetch API const fetchWithErrorHandling = async (): Promise => { try { const response = await fetch( `${BASE_URL}/workouts?category=Fitness&limit=10`, { headers } ); if (!response.ok) { // Handle HTTP errors switch (response.status) { case 400: throw new Error('Invalid request parameters'); case 401: throw new Error('Invalid API key'); case 404: throw new Error('Content not found'); case 500: throw new Error('Server error - please try again later'); default: throw new Error(`HTTP Error: ${response.status}`); } } const data = await response.json(); // Check for API-level errors if (data.error) { throw new Error(data.error); } return data.workouts; } catch (error) { if (error instanceof TypeError && error.message === 'Network request failed') { // No internet connection console.error('No internet connection'); throw new Error('Please check your internet connection'); } // Re-throw the error throw error; } }; // Usage with error handling try { const workouts = await fetchWithErrorHandling(); console.log(`Fetched ${workouts.length} workouts`); } catch (error) { Alert.alert('Error', error.message); } ``` _HTML / JavaScript_ ```html // Comprehensive error handling with fetch API async function fetchWithErrorHandling() { try { const response = await fetch( `${BASE_URL}/workouts?category=Fitness&limit=10`, { headers } ); if (!response.ok) { // Handle HTTP errors switch (response.status) { case 400: throw new Error('Invalid request parameters'); case 401: throw new Error('Invalid API key'); case 404: throw new Error('Content not found'); case 500: throw new Error('Server error - please try again later'); default: throw new Error(`HTTP Error: ${response.status}`); } } const data = await response.json(); // Check for API-level errors if (data.error) { throw new Error(data.error); } return data.workouts; } catch (error) { if (error instanceof TypeError && error.message === 'Failed to fetch') { // No internet connection or CORS error console.error('Network error'); throw new Error('Please check your internet connection'); } // Re-throw the error throw error; } } // Usage with error handling fetchWithErrorHandling() .then(workouts => console.log(`Fetched ${workouts.length} workouts`)) .catch(error => alert(`Error: ${error.message}`)); ``` _React (TypeScript)_ ```tsx // Comprehensive error handling with TypeScript class APIError extends Error { constructor( message: string, public statusCode?: number, public originalError?: unknown ) { super(message); this.name = 'APIError'; } } const fetchWithErrorHandling = async (): Promise => { try { const response = await fetch( `${BASE_URL}/workouts?category=Fitness&limit=10`, { headers } ); if (!response.ok) { // Handle HTTP errors const errorMessages: Record = { 400: 'Invalid request parameters', 401: 'Invalid API key', 404: 'Content not found', 500: 'Server error - please try again later', }; throw new APIError( errorMessages[response.status] || `HTTP Error: ${response.status}`, response.status ); } const data = await response.json(); // Check for API-level errors if (data.error) { throw new APIError(data.error); } return data.workouts; } catch (error) { if (error instanceof APIError) { throw error; } if (error instanceof TypeError) { throw new APIError('Please check your internet connection', undefined, error); } throw new APIError('An unexpected error occurred', undefined, error); } }; // Usage with error handling try { const workouts = await fetchWithErrorHandling(); console.log(`Fetched ${workouts.length} workouts`); } catch (error) { if (error instanceof APIError) { console.error(`API Error (${error.statusCode}): ${error.message}`); } } ``` ### Data Models Reference for the data structures returned by the Content API. **WorkoutModel:** | Field | Type | Description | |-------|------|-------------| | id | String | Unique identifier | | title | String | Workout name | | category | String | Fitness or Rehabilitation | | calories | Int? | Estimated calories burned | | totalMinutes | Int? | Total duration in minutes | | bodyParts | [String] | Targeted body parts | | difficultyLevel | String? | Difficulty level | | description | String | Workout description | | imgURL | String | Workout thumbnail image | | sequence | [ExerciseModel] | List of exercises | **ExerciseModel:** | Field | Type | Description | |-------|------|-------------| | id | String | Unique identifier | | title | String | Exercise name | | bodyParts | [String] | Targeted body parts | | videoURL | String | Demo video URL | | thumbnailURL | String | Thumbnail image URL | | modelId | String | Motion tracking model ID (use in Camera Component) | | description | String | Exercise description | | steps | [String] | Step-by-step instructions | | commonMistakes | String | Common mistakes to avoid | | tips | String | Tips for proper form | **PlanModel:** | Field | Type | Description | |-------|------|-------------| | id | String | Unique identifier | | title | String | Plan name | | imgURL | String | Plan thumbnail image | | category | PlanModelCategory | Category with description and levels | | levels | [String: PlanLevel] | Dictionary of levels (1, 2, 3, etc.) | | createdBy | String | Creator identifier | **PlanLevel:** | Field | Type | Description | |-------|------|-------------| | title | String | Level title | | description | String | Level description | | days | [String: PlanDay] | Dictionary of days | **PlanDay:** | Field | Type | Description | |-------|------|-------------| | title | String | Day title | | description | String | Day description | | workouts | [WorkoutSummary]? | List of workouts for this day | **Working with Models** _Swift (iOS)_ ```swift // Accessing workout model properties Task { let result = await kinestex.fetchWorkout(id: "9zE1kzOzpU5d5dAJrPOY") switch result { case .success(let workout): // Basic properties print("Title: \(workout.title)") print("Category: \(workout.category ?? "N/A")") print("Duration: \(workout.totalMinutes ?? 0) minutes") print("Calories: \(workout.totalCalories ?? 0)") print("Difficulty: \(workout.difficultyLevel ?? "N/A")") // Body parts print("Targets: \(workout.bodyParts.joined(separator: ", "))") // Exercise sequence print("\nExercises (\(workout.sequence.count)):") for (index, exercise) in workout.sequence.enumerated() { print("\(index + 1). \(exercise.title)") print(" Model ID: \(exercise.modelId)") // Use for Camera Component print(" Reps: \(exercise.workoutReps ?? exercise.averageReps ?? 0)") } // Access raw JSON if needed if let rawJSON = workout.rawJSON { print("\nRaw JSON available: \(rawJSON.keys.count) keys") } case .failure(let error): print("Error: \(error.localizedDescription)") } } // Working with plan structure Task { let result = await kinestex.fetchPlan(id: "22B3qRU2r75hVXHgGiGx") switch result { case .success(let plan): print("Plan: \(plan.title)") print("Category: \(plan.category.description)") // Iterate through levels for (levelKey, level) in plan.levels { print("\nLevel \(levelKey): \(level.title)") // Iterate through days for (dayKey, day) in level.days { print(" Day \(dayKey): \(day.title)") // List workouts for this day if let workouts = day.workouts { for workout in workouts { print(" - \(workout.title) (\(workout.totalMinutes) min)") } } } } case .failure(let error): print("Error: \(error.localizedDescription)") } } ``` _Kotlin (Android)_ ```kotlin // Accessing workout model properties lifecycleScope.launch { val result = withContext(Dispatchers.IO) { KinesteXSDK.api.fetchAPIContentData( contentType = ContentType.WORKOUT, id = "9zE1kzOzpU5d5dAJrPOY" ) } when (result) { is APIContentResult.Workout -> { val workout = result.workout // Basic properties Log.d("API", "Title: ${workout.title}") Log.d("API", "Category: ${workout.category}") Log.d("API", "Duration: ${workout.totalMinutes} minutes") Log.d("API", "Calories: ${workout.calories}") // Body parts Log.d("API", "Targets: ${workout.bodyParts.joinToString(", ")}") // Exercise sequence Log.d("API", "Exercises (${workout.sequence.size}):") workout.sequence.forEachIndexed { index, exercise -> Log.d("API", "${index + 1}. ${exercise.title}") Log.d("API", " Model ID: ${exercise.modelId}") // Use for Camera Component } // Pretty print as JSON val gson = GsonBuilder().setPrettyPrinting().create() val prettyJson = gson.toJson(workout) Log.d("API", "JSON:\n$prettyJson") } is APIContentResult.Error -> { Log.e("API", "Error: ${result.message}") } else -> {} } } ``` _Flutter_ ```dart // Accessing workout model properties Future workWithModels() async { final result = await KinesteXAIFramework.apiService.fetchContent( contentType: ContentType.workout, id: "9zE1kzOzpU5d5dAJrPOY", ); switch (result) { case WorkoutResult(:final workout): // Basic properties print('Title: ${workout.title}'); print('Category: ${workout.category}'); print('Duration: ${workout.totalMinutes} minutes'); print('Calories: ${workout.totalCalories}'); print('Difficulty: ${workout.difficultyLevel}'); // Body parts print('Targets: ${workout.bodyParts.join(", ")}'); // Exercise sequence print('\nExercises (${workout.sequence.length}):'); for (var i = 0; i < workout.sequence.length; i++) { final exercise = workout.sequence[i]; print('${i + 1}. ${exercise.title}'); print(' Model ID: ${exercise.modelId}'); // Use for Camera Component } // Access raw JSON if needed if (workout.rawJSON != null) { print('\nRaw JSON available: ${workout.rawJSON!.keys.length} keys'); } case ErrorResult(:final message): print('Error: $message'); default: break; } } // Working with plan structure Future workWithPlan() async { final result = await KinesteXAIFramework.apiService.fetchContent( contentType: ContentType.plan, id: "22B3qRU2r75hVXHgGiGx", ); switch (result) { case PlanResult(:final plan): print('Plan: ${plan.title}'); print('Category: ${plan.category.description}'); // Iterate through levels plan.levels.forEach((levelKey, level) { print('\nLevel $levelKey: ${level.title}'); // Iterate through days level.days.forEach((dayKey, day) { print(' Day $dayKey: ${day.title}'); // List workouts for this day day.workouts?.forEach((workout) { print(' - ${workout.title} (${workout.totalMinutes} min)'); }); }); }); case ErrorResult(:final message): print('Error: $message'); default: break; } } ``` _React Native_ ```jsx // TypeScript interfaces for Content API models interface WorkoutModel { id: string; title: string; category: string; calories: number; total_minutes: number; body_parts: string[]; dif_level: string; description: string; workout_desc_img: string; sequence: ExerciseModel[]; } interface ExerciseModel { id: string; title: string; body_parts: string[]; video_url: string; male_video_url: string; thumbnail_url: string; male_thumbnail_url: string; model_id: string; description: string; steps: string[]; common_mistakes: string; tips: string; workout_reps?: number; workout_countdown?: number; average_reps?: number; average_countdown?: number; rest_duration?: number; } interface PlanModel { id: string; title: string; img_url: string; category: { description: string; levels: Record; }; levels: Record; created_by: string; } interface PlanLevel { title: string; description: string; days: Record; } interface PlanDay { title: string; description: string; workouts?: WorkoutSummary[]; } interface WorkoutSummary { id: string; img_url: string; title: string; calories?: number; total_minutes: number; } // Working with fetched data const displayWorkout = (workout: WorkoutModel) => { console.log(`Title: ${workout.title}`); console.log(`Duration: ${workout.total_minutes} minutes`); console.log(`Calories: ${workout.calories}`); console.log(`Targets: ${workout.body_parts.join(', ')}`); console.log(`\nExercises (${workout.sequence.length}):`); workout.sequence.forEach((exercise, index) => { console.log(`${index + 1}. ${exercise.title}`); console.log(` Model ID: ${exercise.model_id}`); // Use for Camera Component }); }; ``` _HTML / JavaScript_ ```html // Working with fetched workout data function displayWorkout(workout) { console.log(`Title: ${workout.title}`); console.log(`Duration: ${workout.total_minutes} minutes`); console.log(`Calories: ${workout.calories}`); console.log(`Targets: ${workout.body_parts.join(', ')}`); console.log(`\nExercises (${workout.sequence.length}):`); workout.sequence.forEach((exercise, index) => { console.log(`${index + 1}. ${exercise.title}`); console.log(` Model ID: ${exercise.model_id}`); // Use for Camera Component }); } // Working with plan structure function displayPlan(plan) { console.log(`Plan: ${plan.title}`); console.log(`Category: ${plan.category.description}`); // Iterate through levels Object.entries(plan.levels).forEach(([levelKey, level]) => { console.log(`\nLevel ${levelKey}: ${level.title}`); // Iterate through days Object.entries(level.days).forEach(([dayKey, day]) => { console.log(` Day ${dayKey}: ${day.title}`); // List workouts for this day if (day.workouts) { day.workouts.forEach(workout => { console.log(` - ${workout.title} (${workout.total_minutes} min)`); }); } }); }); } ``` _React (TypeScript)_ ```tsx // Full TypeScript interfaces for Content API models interface WorkoutModel { id: string; title: string; category: string; calories: number; total_minutes: number; body_parts: string[]; dif_level: string; description: string; workout_desc_img: string; sequence: ExerciseModel[]; } interface ExerciseModel { id: string; title: string; body_parts: string[]; video_url: string; male_video_url: string; thumbnail_url: string; male_thumbnail_url: string; model_id: string; description: string; steps: string[]; common_mistakes: string; tips: string; workout_reps?: number; workout_countdown?: number; average_reps?: number; average_countdown?: number; rest_duration?: number; } interface PlanModel { id: string; title: string; img_url: string; category: PlanCategory; levels: Record; created_by: string; } interface PlanCategory { description: string; levels: Record; } interface PlanLevel { title: string; description: string; days: Record; } interface PlanDay { title: string; description: string; workouts?: WorkoutSummary[]; } interface WorkoutSummary { id: string; img_url: string; title: string; calories?: number; total_minutes: number; } // Helper function to display workout const displayWorkout = (workout: WorkoutModel): void => { console.log(`Title: ${workout.title}`); console.log(`Duration: ${workout.total_minutes} minutes`); console.log(`Calories: ${workout.calories}`); console.log(`Targets: ${workout.body_parts.join(', ')}`); console.log(`\nExercises (${workout.sequence.length}):`); workout.sequence.forEach((exercise, index) => { console.log(`${index + 1}. ${exercise.title}`); console.log(` Model ID: ${exercise.model_id}`); // Use for Camera Component }); }; ``` --- ## Support **Contact:** • support@kinestex.com - Technical support • hello@kinestex.com - Sales & demos **Repositories:** • [Swift/iOS](https://github.com/KinesteX/KinesteX-Swift-Demo) • [Kotlin/Android](https://github.com/KinesteX/KinesteX-SDK-Kotlin) • [React Native](https://github.com/KinesteX/KinesteX-SDK-ReactNative) • [Flutter](https://github.com/KinesteX/KinesteX-SDK-Flutter) • [HTML/JS](https://github.com/KinesteX/KinesteX-SDK-HTML-JS) ---