# AI Experiences

AI-powered movement-based games and clinical assessments with real-time motion tracking.

**AI Games:**
| Game | Exercise ID |
|------|-------------|
| Balloon Pop | balloonpop |
| Color Chase | 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<String, Any>()
data["style"] = "dark"
data["exercise"] = "balloonpop" // exercise ID from table

KinesteXSDK.createExperiencesView(
    context = this,
    experienceName = "assessment",
    user = userDetails, // optional user details
    customParams = 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',
  },
};

<KinestexSDK
  data={postData}
  integrationOption={IntegrationOption.EXPERIENCE}
  experience="assessment" // experience type (passed as prop)
  handleMessage={handleMessage}
/>
```

_Flutter_
```dart
KinesteXAIFramework.createExperienceView(
  isShowKinestex: showKinesteX,
  experience: "assessment",
  customParams: {
    "style": "dark",
    "exercise": "balloonpop", // exercise ID from table
  },
  isLoading: ValueNotifier<bool>(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',
  },
};

<KinesteXSDK
  data={postData}
  integrationOption={IntegrationOption.EXPERIENCE}
  experience="assessment" // experience type (passed as prop)
  handleMessage={handleMessage}
/>
```

**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<String, Any>()

    // 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,
                experienceName = "assessment",
                user = userDetails,
                customParams = 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 from 'kinestex-sdk-react-native';
import { IntegrationOption, IPostData } from 'kinestex-sdk-react-native/src/types';

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 (
      <KinestexSDK
        data={postData}
        integrationOption={IntegrationOption.EXPERIENCE}
        experience="assessment"
        handleMessage={handleMessage}
      />
    );
  }

  return (
    <View>
      <Button title="Start AI Experience" onPress={() => setShowKinesteX(true)} />
    </View>
  );
}
```

_Flutter_
```dart
import 'package:flutter/material.dart';
import 'package:kinestex_sdk_flutter/kinestex_sdk.dart';
import 'package:permission_handler/permission_handler.dart';

Future<void> 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<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  void dispose() {
    disposeKinesteXAIFramework();
    super.dispose();
  }

  Future<void> disposeKinesteXAIFramework() async {
    await KinesteXAIFramework.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'KinesteX Experience',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  ValueNotifier<bool> showKinesteX = ValueNotifier<bool>(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: <Widget>[
            TextButton(
              child: const Text("OK"),
              onPressed: () => Navigator.of(context).pop(),
            ),
          ],
        );
      },
    );
  }

  void handleWebViewMessage(WebViewMessage message) {
    if (message is ExitKinestex) {
      setState(() {
        showKinesteX.value = false;
      });
    }
  }

  Widget createExperienceView() {
    return Center(
      child: KinesteXAIFramework.createExperienceView(
        isShowKinestex: showKinesteX,
        experience: "assessment",
        customParams: {
          "style": "dark",
          "exercise": "balloonpop",
        },
        isLoading: ValueNotifier<bool>(false),
        onMessageReceived: handleWebViewMessage,
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder(
      valueListenable: showKinesteX,
      builder: (context, isShowKinesteX, child) {
        return isShowKinesteX
            ? SafeArea(
                child: createExperienceView(),
              )
            : 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(
                      'Start AI Experience',
                      style: TextStyle(
                        fontSize: 20,
                        fontWeight: FontWeight.bold,
                        color: Colors.white,
                      ),
                    ),
                  ),
                ),
              );
      },
    );
  }
}
```

_HTML / JavaScript_
```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>KinesteX: AI Experience</title>
  </head>
  <body>
    <button id="startKinesteX">Start KinesteX</button>
    <div id="webViewContainer" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; display: none;">
      <iframe id="webView" frameborder="0" allow="camera; autoplay; accelerometer; gyroscope; magnetometer" sandbox="allow-same-origin allow-scripts" style="width: 100%; height: 100%"></iframe>
    </div>
    <script>
      const webView = document.getElementById("webView");
      const webViewContainer = document.getElementById("webViewContainer");

      const postData = {
        userId: "YOUR_USER_ID",
        company: "YOUR_COMPANY",
        key: "YOUR_API_KEY",
        style: "dark",
        exercise: "balloonpop", // AI game or balance assessment ID
      };

      // IMPORTANT: The experience type is part of the URL path
      // Use "assessment" for fitness assessments or specific game IDs
      const srcURL = "https://ai.kinestex.com/experiences/assessment";

      function sendMessage() {
        if (webView.contentWindow) {
          webView.contentWindow.postMessage(postData, srcURL);
        } else {
          setTimeout(() => {
            try {
              webView.contentWindow.postMessage(postData, srcURL);
            } catch {
              webView.contentWindow.postMessage(postData, srcURL);
            }
          }, 100);
        }
      }

      document.getElementById("startKinesteX").addEventListener("click", function () {
        webViewContainer.style.display = "block";
        webView.src = srcURL;
        webView.onload = sendMessage;
      });

      window.addEventListener("message", function (event) {
        if (event.origin !== "https://ai.kinestex.com") return;

        const message = JSON.parse(event.data);
        switch (message.type) {
          case "kinestex_launched":
            console.log("KinesteX is launched");
            break;
          case "kinestex_loaded":
            sendMessage();
            break;
          case "exit_kinestex":
            webViewContainer.style.display = "none";
            console.log("User exited the KinesteX view");
            break;
          // Handle other message types...
        }
      });
    </script>
  </body>
</html>
```

_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<string, unknown>) => {
    if (type === 'exit_kinestex') {
      setShowKinesteX(false);
    }
  };

  if (showKinesteX) {
    return (
      <div style={{ height: '100vh', width: '100vw' }}>
        <KinesteXSDK
          data={postData}
          integrationOption={IntegrationOption.EXPERIENCE}
          experience="assessment"
          handleMessage={handleMessage}
        />
      </div>
    );
  }

  return <button onClick={() => setShowKinesteX(true)}>Start AI Experience</button>;
}
```

---
Source: https://www.kinestex.com/docs/integration/experience-view · Index: https://www.kinestex.com/llms.txt
