feat: 集成后台任务管理功能及相关组件
- 新增后台任务管理器,支持任务的注册、执行和状态监控 - 实现自定义Hook,简化后台任务的使用和管理 - 添加示例任务,包括数据同步、健康数据更新和通知检查等 - 更新文档,详细描述后台任务系统的实现和使用方法 - 优化相关组件,确保用户体验和界面一致性
This commit is contained in:
298
components/MoodIntensitySlider.tsx
Normal file
298
components/MoodIntensitySlider.tsx
Normal file
@@ -0,0 +1,298 @@
|
||||
import * as Haptics from 'expo-haptics';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import React from 'react';
|
||||
import {
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {
|
||||
PanGestureHandler,
|
||||
PanGestureHandlerGestureEvent,
|
||||
} from 'react-native-gesture-handler';
|
||||
import Animated, {
|
||||
interpolate,
|
||||
interpolateColor,
|
||||
runOnJS,
|
||||
useAnimatedGestureHandler,
|
||||
useAnimatedStyle,
|
||||
useSharedValue,
|
||||
withSpring,
|
||||
} from 'react-native-reanimated';
|
||||
|
||||
interface MoodIntensitySliderProps {
|
||||
value: number;
|
||||
onValueChange: (value: number) => void;
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
}
|
||||
|
||||
export default function MoodIntensitySlider({
|
||||
value,
|
||||
onValueChange,
|
||||
min = 1,
|
||||
max = 10,
|
||||
step = 1,
|
||||
width = 320,
|
||||
height = 16, // 更粗的进度条
|
||||
}: MoodIntensitySliderProps) {
|
||||
const translateX = useSharedValue(0);
|
||||
const sliderWidth = width - 40; // 减去thumb的宽度
|
||||
const thumbSize = 36; // 更大的thumb
|
||||
|
||||
// 计算初始位置
|
||||
React.useEffect(() => {
|
||||
const initialPosition = ((value - min) / (max - min)) * sliderWidth;
|
||||
translateX.value = withSpring(initialPosition);
|
||||
}, [value, min, max, sliderWidth, translateX]);
|
||||
|
||||
const triggerHaptics = () => {
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
|
||||
};
|
||||
|
||||
const gestureHandler = useAnimatedGestureHandler<
|
||||
PanGestureHandlerGestureEvent,
|
||||
{ startX: number; lastValue: number }
|
||||
>({
|
||||
onStart: (_, context) => {
|
||||
context.startX = translateX.value;
|
||||
context.lastValue = value;
|
||||
runOnJS(triggerHaptics)();
|
||||
},
|
||||
onActive: (event, context) => {
|
||||
const newX = context.startX + event.translationX;
|
||||
const clampedX = Math.max(0, Math.min(sliderWidth, newX));
|
||||
translateX.value = clampedX;
|
||||
|
||||
// 计算当前值
|
||||
const currentValue = Math.round((clampedX / sliderWidth) * (max - min) + min);
|
||||
|
||||
// 当值改变时触发震动和回调
|
||||
if (currentValue !== context.lastValue) {
|
||||
context.lastValue = currentValue;
|
||||
runOnJS(triggerHaptics)();
|
||||
runOnJS(onValueChange)(currentValue);
|
||||
}
|
||||
},
|
||||
onEnd: () => {
|
||||
// 计算最终值并吸附到最近的步长
|
||||
const currentValue = Math.round((translateX.value / sliderWidth) * (max - min) + min);
|
||||
const snapPosition = ((currentValue - min) / (max - min)) * sliderWidth;
|
||||
|
||||
translateX.value = withSpring(snapPosition);
|
||||
runOnJS(triggerHaptics)();
|
||||
runOnJS(onValueChange)(currentValue);
|
||||
},
|
||||
});
|
||||
|
||||
const thumbStyle = useAnimatedStyle(() => {
|
||||
const scale = interpolate(
|
||||
translateX.value,
|
||||
[0, sliderWidth],
|
||||
[1, 1.05]
|
||||
);
|
||||
|
||||
return {
|
||||
transform: [
|
||||
{ translateX: translateX.value },
|
||||
{ scale: withSpring(scale) }
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
const progressStyle = useAnimatedStyle(() => {
|
||||
const progressWidth = translateX.value + thumbSize / 2;
|
||||
return {
|
||||
width: progressWidth,
|
||||
};
|
||||
});
|
||||
|
||||
// 动态颜色配置 - 根据进度变化颜色
|
||||
const getProgressColors = (progress: number) => {
|
||||
if (progress <= 0.25) {
|
||||
return ['#22c55e', '#84cc16'] as const; // 绿色到浅绿色
|
||||
} else if (progress <= 0.5) {
|
||||
return ['#84cc16', '#eab308'] as const; // 浅绿色到黄色
|
||||
} else if (progress <= 0.75) {
|
||||
return ['#eab308', '#f97316'] as const; // 黄色到橙色
|
||||
} else {
|
||||
return ['#f97316', '#ef4444'] as const; // 橙色到红色
|
||||
}
|
||||
};
|
||||
|
||||
const progressColorsStyle = useAnimatedStyle(() => {
|
||||
const progress = translateX.value / sliderWidth;
|
||||
return {
|
||||
backgroundColor: interpolateColor(
|
||||
progress,
|
||||
[0, 0.25, 0.5, 0.75, 1],
|
||||
['#22c55e', '#84cc16', '#eab308', '#f97316', '#ef4444']
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={[styles.sliderContainer, { width: width }]}>
|
||||
{/* 背景轨道 - 更粗的灰色轨道 */}
|
||||
<View style={[styles.track, { height }]}>
|
||||
<LinearGradient
|
||||
colors={['#f3f4f6', '#e5e7eb']}
|
||||
style={[styles.trackGradient, { height }]}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 0 }}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* 进度条 - 动态颜色 */}
|
||||
<Animated.View style={[styles.progress, { height }, progressStyle, progressColorsStyle]}>
|
||||
<LinearGradient
|
||||
colors={getProgressColors(translateX.value / sliderWidth)}
|
||||
style={[styles.progressGradient, { height }]}
|
||||
start={{ x: 1, y: 0 }}
|
||||
end={{ x: 0, y: 0 }}
|
||||
/>
|
||||
</Animated.View>
|
||||
|
||||
{/* 可拖拽的thumb */}
|
||||
<PanGestureHandler onGestureEvent={gestureHandler}>
|
||||
<Animated.View style={[styles.thumb, { width: thumbSize, height: thumbSize }, thumbStyle]}>
|
||||
{/* <LinearGradient
|
||||
colors={['#ffffff', '#f8fafc']}
|
||||
style={styles.thumbGradient}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 0, y: 1 }}
|
||||
/> */}
|
||||
<View style={styles.thumbInner} />
|
||||
</Animated.View>
|
||||
</PanGestureHandler>
|
||||
</View>
|
||||
|
||||
{/* 标签 */}
|
||||
<View style={[styles.labelsContainer, { width: width }]}>
|
||||
<Text style={styles.labelText}>轻微</Text>
|
||||
<Text style={styles.labelText}>强烈</Text>
|
||||
</View>
|
||||
|
||||
{/* 刻度 */}
|
||||
<View style={[styles.scaleContainer, { width: width }]}>
|
||||
{Array.from({ length: max - min + 1 }, (_, i) => i + min).map((num) => (
|
||||
<View key={num} style={styles.scaleItem}>
|
||||
<View style={[styles.scaleMark, value === num && styles.scaleMarkActive]} />
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
alignItems: 'center',
|
||||
paddingVertical: 16,
|
||||
},
|
||||
sliderContainer: {
|
||||
height: 50,
|
||||
justifyContent: 'center',
|
||||
position: 'relative',
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
track: {
|
||||
position: 'absolute',
|
||||
left: 20,
|
||||
right: 20,
|
||||
borderRadius: 8,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
trackGradient: {
|
||||
flex: 1,
|
||||
borderRadius: 8,
|
||||
},
|
||||
progress: {
|
||||
position: 'absolute',
|
||||
left: 20,
|
||||
borderRadius: 8,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
progressGradient: {
|
||||
flex: 1,
|
||||
borderRadius: 8,
|
||||
},
|
||||
thumb: {
|
||||
position: 'absolute',
|
||||
left: 22,
|
||||
elevation: 6,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
thumbGradient: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
thumbInner: {
|
||||
width: 16,
|
||||
height: 32,
|
||||
borderRadius: 8,
|
||||
backgroundColor: '#ffffff',
|
||||
shadowColor: '#ffffff',
|
||||
shadowOffset: { width: 0, height: 1 },
|
||||
shadowOpacity: 0.3,
|
||||
shadowRadius: 2,
|
||||
elevation: 2,
|
||||
},
|
||||
valueContainer: {
|
||||
marginTop: 20,
|
||||
marginBottom: 12,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 10,
|
||||
backgroundColor: '#7a5af8',
|
||||
borderRadius: 16,
|
||||
shadowColor: '#7a5af8',
|
||||
shadowOffset: { width: 0, height: 3 },
|
||||
shadowOpacity: 0.3,
|
||||
shadowRadius: 6,
|
||||
elevation: 6,
|
||||
},
|
||||
valueText: {
|
||||
fontSize: 20,
|
||||
fontWeight: '800',
|
||||
color: '#ffffff',
|
||||
textAlign: 'center',
|
||||
minWidth: 28,
|
||||
},
|
||||
labelsContainer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
marginTop: 8,
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
labelText: {
|
||||
fontSize: 14,
|
||||
color: '#5d6676',
|
||||
fontWeight: '600',
|
||||
},
|
||||
scaleContainer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
marginTop: 16,
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
scaleItem: {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
},
|
||||
scaleMark: {
|
||||
width: 2,
|
||||
height: 10,
|
||||
backgroundColor: '#e5e7eb',
|
||||
borderRadius: 1,
|
||||
},
|
||||
scaleMarkActive: {
|
||||
backgroundColor: '#7a5af8',
|
||||
width: 3,
|
||||
height: 12,
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user