Compare commits

..
2 Commits
4 changed files with 57 additions and 15 deletions
+3 -2
View File
@@ -4,7 +4,7 @@ import { Ionicons } from "@expo/vector-icons";
import { MainTabParamList } from "./types"; import { MainTabParamList } from "./types";
import { WorkoutNavigator } from "./WorkoutNavigator"; import { WorkoutNavigator } from "./WorkoutNavigator";
import { HistoryNavigator } from "./HistoryNavigator"; import { HistoryNavigator } from "./HistoryNavigator";
import { SettingsScreen } from "../features/settings/screens/SettingsScreen"; import { SettingsNavigator } from "./SettingsNavigator";
const Tab = createBottomTabNavigator<MainTabParamList>(); const Tab = createBottomTabNavigator<MainTabParamList>();
@@ -45,8 +45,9 @@ export function MainTabNavigator() {
/> />
<Tab.Screen <Tab.Screen
name="Settings" name="Settings"
component={SettingsScreen} component={SettingsNavigator}
options={{ options={{
headerShown: false,
tabBarLabel: t("settings"), tabBarLabel: t("settings"),
tabBarIcon: ({ color, size }) => ( tabBarIcon: ({ color, size }) => (
<Ionicons name="settings-outline" size={size} color={color} /> <Ionicons name="settings-outline" size={size} color={color} />
+26
View File
@@ -0,0 +1,26 @@
import { useTranslation } from "react-i18next";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import { SettingsStackParamList } from "./types";
import { SettingsScreen } from "../features/settings/screens/SettingsScreen";
const Stack = createNativeStackNavigator<SettingsStackParamList>();
export function SettingsNavigator() {
const { t } = useTranslation("navigation");
return (
<Stack.Navigator
screenOptions={{
headerStyle: { backgroundColor: "#0ea5e9" },
headerTintColor: "#fff",
headerTitleStyle: { fontWeight: "bold" },
}}
>
<Stack.Screen
name="SettingsMain"
component={SettingsScreen}
options={{ title: t("settings") }}
/>
</Stack.Navigator>
);
}
+4
View File
@@ -26,6 +26,10 @@ export type HistoryStackParamList = {
SessionDetail: { sessionId: string }; SessionDetail: { sessionId: string };
}; };
export type SettingsStackParamList = {
SettingsMain: undefined;
};
export type RootStackProps<T extends keyof RootStackParamList> = export type RootStackProps<T extends keyof RootStackParamList> =
NativeStackScreenProps<RootStackParamList, T>; NativeStackScreenProps<RootStackParamList, T>;
+24 -13
View File
@@ -393,32 +393,44 @@ class WorkoutManager: NSObject, ObservableObject, HKLiveWorkoutBuilderDelegate {
healthStore.execute(query) healthStore.execute(query)
} }
// MARK: - Lap Detection (Accelerometer) // MARK: - Lap Detection (CMDeviceMotion)
private var filteredUserAccel: Double = 0
private var filteredRotationX: Double = 0
private let filterAlpha: Double = 0.3
private func startLapDetection() { private func startLapDetection() {
guard motionManager.isAccelerometerAvailable else { return } guard motionManager.isDeviceMotionAvailable else { return }
motionManager.accelerometerUpdateInterval = 0.1 // 10 Hz motionManager.deviceMotionUpdateInterval = 0.1 // 10 Hz
motionManager.startAccelerometerUpdates() motionManager.startDeviceMotionUpdates(using: .xMagneticNorthZVertical)
lapDetectionTimer = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true) { [weak self] _ in lapDetectionTimer = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true) { [weak self] _ in
self?.processAccelerometerData() self?.processDeviceMotion()
} }
} }
private func stopLapDetection() { private func stopLapDetection() {
lapDetectionTimer?.invalidate() lapDetectionTimer?.invalidate()
lapDetectionTimer = nil lapDetectionTimer = nil
motionManager.stopAccelerometerUpdates() motionManager.stopDeviceMotionUpdates()
} }
private func processAccelerometerData() { private func processDeviceMotion() {
guard let data = motionManager.accelerometerData else { return } guard let motion = motionManager.deviceMotion else { return }
let magnitude = sqrt(data.acceleration.x * data.acceleration.x + // userAcceleration has gravity removed — clean movement signal
data.acceleration.y * data.acceleration.y + let rawUserAccel = sqrt(motion.userAcceleration.x * motion.userAcceleration.x +
data.acceleration.z * data.acceleration.z) motion.userAcceleration.y * motion.userAcceleration.y +
motion.userAcceleration.z * motion.userAcceleration.z)
let isStationary = magnitude < 1.1 // Near rest (~1g from gravity) // Low-pass filter for smoothing
filteredUserAccel = filterAlpha * rawUserAccel + (1 - filterAlpha) * filteredUserAccel
// Rotation rate on x-axis detects the flip/turn at the wall
let rawRotationX = abs(motion.rotationRate.x)
filteredRotationX = filterAlpha * rawRotationX + (1 - filterAlpha) * filteredRotationX
let isStationary = filteredUserAccel < 0.05
let now = Date() let now = Date()
if isStationary { if isStationary {
@@ -439,7 +451,6 @@ class WorkoutManager: NSObject, ObservableObject, HKLiveWorkoutBuilderDelegate {
self.lapDetected = true self.lapDetected = true
self.lastLapDetectionTime = now self.lastLapDetectionTime = now
} }
// Reset after detection
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
self.lapDetected = false self.lapDetected = false
} }