Compare commits
2
Commits
8f96a12b18
...
383f641ac2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
383f641ac2 | ||
|
|
4a07556a5d |
@@ -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} />
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>;
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user