feat(water): 重构饮水模块并新增自定义提醒设置功能
- 新增饮水详情页面 `/water/detail` 展示每日饮水记录与统计 - 新增饮水设置页面 `/water/settings` 支持目标与快速添加配置 - 新增喝水提醒设置页面 `/water/reminder-settings` 支持自定义时间段与间隔 - 重构 `useWaterData` Hook,支持按日期查询与实时刷新 - 新增 `WaterNotificationHelpers.scheduleCustomWaterReminders` 实现个性化提醒 - 优化心情编辑页键盘体验,新增 `KeyboardAvoidingView` 与滚动逻辑 - 升级版本号至 1.0.14 并补充路由常量 - 补充用户偏好存储字段 `waterReminderEnabled/startTime/endTime/interval` - 废弃后台定时任务中的旧版喝水提醒逻辑,改为用户手动管理
This commit is contained in:
585
app/water/settings.tsx
Normal file
585
app/water/settings.tsx
Normal file
@@ -0,0 +1,585 @@
|
||||
import { Colors } from '@/constants/Colors';
|
||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||
import { getQuickWaterAmount, getWaterReminderSettings, setQuickWaterAmount } from '@/utils/userPreferences';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { Picker } from '@react-native-picker/picker';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import { router } from 'expo-router';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
import {
|
||||
Alert,
|
||||
KeyboardAvoidingView,
|
||||
Modal,
|
||||
Platform,
|
||||
Pressable,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Switch,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from 'react-native';
|
||||
|
||||
import { HeaderBar } from '@/components/ui/HeaderBar';
|
||||
|
||||
const WaterSettings: React.FC = () => {
|
||||
const theme = (useColorScheme() ?? 'light') as 'light' | 'dark';
|
||||
const colorTokens = Colors[theme];
|
||||
|
||||
const [quickAddAmount, setQuickAddAmount] = useState<string>('250');
|
||||
|
||||
// 喝水提醒设置状态(用于显示当前设置)
|
||||
const [waterReminderSettings, setWaterReminderSettings] = useState({
|
||||
enabled: false,
|
||||
startTime: '08:00',
|
||||
endTime: '22:00',
|
||||
interval: 60,
|
||||
});
|
||||
|
||||
// 弹窗状态
|
||||
const [goalModalVisible, setGoalModalVisible] = useState(false);
|
||||
const [quickAddModalVisible, setQuickAddModalVisible] = useState(false);
|
||||
|
||||
// 临时选中值
|
||||
const [tempGoal, setTempGoal] = useState<number>(2000);
|
||||
const [tempQuickAdd, setTempQuickAdd] = useState<number>(250);
|
||||
|
||||
|
||||
// 当前饮水目标(从本地存储获取)
|
||||
const [currentWaterGoal, setCurrentWaterGoal] = useState(2000);
|
||||
|
||||
// 打开饮水目标弹窗时初始化临时值
|
||||
const openGoalModal = () => {
|
||||
setTempGoal(currentWaterGoal);
|
||||
setGoalModalVisible(true);
|
||||
};
|
||||
|
||||
// 打开快速添加弹窗时初始化临时值
|
||||
const openQuickAddModal = () => {
|
||||
setTempQuickAdd(parseInt(quickAddAmount));
|
||||
setQuickAddModalVisible(true);
|
||||
};
|
||||
|
||||
// 打开喝水提醒页面
|
||||
const openWaterReminderSettings = () => {
|
||||
router.push('/water/reminder-settings');
|
||||
};
|
||||
|
||||
|
||||
// 处理饮水目标确认
|
||||
const handleGoalConfirm = async () => {
|
||||
setCurrentWaterGoal(tempGoal);
|
||||
setGoalModalVisible(false);
|
||||
|
||||
// 这里可以添加保存到本地存储或发送到后端的逻辑
|
||||
Alert.alert('设置成功', `每日饮水目标已设置为 ${tempGoal}ml`);
|
||||
};
|
||||
|
||||
// 处理快速添加默认值确认
|
||||
const handleQuickAddConfirm = async () => {
|
||||
setQuickAddAmount(tempQuickAdd.toString());
|
||||
setQuickAddModalVisible(false);
|
||||
|
||||
try {
|
||||
await setQuickWaterAmount(tempQuickAdd);
|
||||
Alert.alert('设置成功', `快速添加默认值已设置为 ${tempQuickAdd}ml`);
|
||||
} catch {
|
||||
Alert.alert('设置失败', '无法保存快速添加默认值,请重试');
|
||||
}
|
||||
};
|
||||
|
||||
// 加载用户偏好设置
|
||||
const loadUserPreferences = useCallback(async () => {
|
||||
try {
|
||||
const amount = await getQuickWaterAmount();
|
||||
setQuickAddAmount(amount.toString());
|
||||
setTempQuickAdd(amount);
|
||||
|
||||
// 加载喝水提醒设置来显示当前设置状态
|
||||
const reminderSettings = await getWaterReminderSettings();
|
||||
setWaterReminderSettings(reminderSettings);
|
||||
} catch (error) {
|
||||
console.error('加载用户偏好设置失败:', error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 初始化加载
|
||||
useEffect(() => {
|
||||
loadUserPreferences();
|
||||
}, [loadUserPreferences]);
|
||||
|
||||
// 页面聚焦时重新加载设置(从提醒设置页面返回时)
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
loadUserPreferences();
|
||||
}, [loadUserPreferences])
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{/* 背景渐变 */}
|
||||
<LinearGradient
|
||||
colors={['#f5e5fbff', '#e5fcfeff', '#eefdffff', '#e6f6fcff']}
|
||||
style={styles.gradientBackground}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 0, y: 1 }}
|
||||
/>
|
||||
|
||||
{/* 装饰性圆圈 */}
|
||||
<View style={styles.decorativeCircle1} />
|
||||
<View style={styles.decorativeCircle2} />
|
||||
|
||||
<HeaderBar
|
||||
title="饮水设置"
|
||||
onBack={() => {
|
||||
router.back();
|
||||
}}
|
||||
/>
|
||||
|
||||
<KeyboardAvoidingView
|
||||
style={styles.keyboardAvoidingView}
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
>
|
||||
<ScrollView
|
||||
style={styles.scrollView}
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
{/* 设置列表 */}
|
||||
<View style={styles.section}>
|
||||
<View style={styles.settingsMenuContainer}>
|
||||
<TouchableOpacity style={styles.settingsMenuItem} onPress={openGoalModal}>
|
||||
<View style={styles.settingsMenuItemLeft}>
|
||||
<View style={[styles.settingsIconContainer, { backgroundColor: 'rgba(147, 112, 219, 0.1)' }]}>
|
||||
<Ionicons name="flag-outline" size={20} color="#9370DB" />
|
||||
</View>
|
||||
<View style={styles.settingsMenuItemContent}>
|
||||
<Text style={[styles.settingsMenuItemTitle, { color: colorTokens.text }]}>每日饮水目标</Text>
|
||||
<Text style={[styles.settingsMenuItemValue, { color: colorTokens.textSecondary }]}>{currentWaterGoal}ml</Text>
|
||||
</View>
|
||||
</View>
|
||||
<Ionicons name="chevron-forward" size={20} color="#CCCCCC" />
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity style={styles.settingsMenuItem} onPress={openQuickAddModal}>
|
||||
<View style={styles.settingsMenuItemLeft}>
|
||||
<View style={[styles.settingsIconContainer, { backgroundColor: 'rgba(147, 112, 219, 0.1)' }]}>
|
||||
<Ionicons name="add-outline" size={20} color="#9370DB" />
|
||||
</View>
|
||||
<View style={styles.settingsMenuItemContent}>
|
||||
<Text style={[styles.settingsMenuItemTitle, { color: colorTokens.text }]}>快速添加默认值</Text>
|
||||
<Text style={[styles.settingsMenuItemSubtitle, { color: colorTokens.textSecondary }]}>
|
||||
设置点击"+"按钮时添加的默认饮水量
|
||||
</Text>
|
||||
<Text style={[styles.settingsMenuItemValue, { color: colorTokens.textSecondary }]}>{quickAddAmount}ml</Text>
|
||||
</View>
|
||||
</View>
|
||||
<Ionicons name="chevron-forward" size={20} color="#CCCCCC" />
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity style={[styles.settingsMenuItem, { borderBottomWidth: 0 }]} onPress={openWaterReminderSettings}>
|
||||
<View style={styles.settingsMenuItemLeft}>
|
||||
<View style={[styles.settingsIconContainer, { backgroundColor: 'rgba(52, 152, 219, 0.1)' }]}>
|
||||
<Ionicons name="notifications-outline" size={20} color="#3498DB" />
|
||||
</View>
|
||||
<View style={styles.settingsMenuItemContent}>
|
||||
<Text style={[styles.settingsMenuItemTitle, { color: colorTokens.text }]}>喝水提醒</Text>
|
||||
<Text style={[styles.settingsMenuItemSubtitle, { color: colorTokens.textSecondary }]}>
|
||||
设置定时提醒您补充水分
|
||||
</Text>
|
||||
<Text style={[styles.settingsMenuItemValue, { color: colorTokens.textSecondary }]}>
|
||||
{waterReminderSettings.enabled ? `${waterReminderSettings.startTime}-${waterReminderSettings.endTime}, 每${waterReminderSettings.interval}分钟` : '已关闭'}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<Ionicons name="chevron-forward" size={20} color="#CCCCCC" />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
|
||||
{/* 饮水目标编辑弹窗 */}
|
||||
<Modal
|
||||
visible={goalModalVisible}
|
||||
transparent
|
||||
animationType="fade"
|
||||
onRequestClose={() => setGoalModalVisible(false)}
|
||||
>
|
||||
<Pressable style={styles.modalBackdrop} onPress={() => setGoalModalVisible(false)} />
|
||||
<View style={styles.modalSheet}>
|
||||
<View style={styles.modalHandle} />
|
||||
<Text style={[styles.modalTitle, { color: colorTokens.text }]}>每日饮水目标</Text>
|
||||
<View style={styles.pickerContainer}>
|
||||
<Picker
|
||||
selectedValue={tempGoal}
|
||||
onValueChange={(value) => setTempGoal(value)}
|
||||
style={styles.picker}
|
||||
>
|
||||
{Array.from({ length: 96 }, (_, i) => 500 + i * 100).map(goal => (
|
||||
<Picker.Item key={goal} label={`${goal}ml`} value={goal} />
|
||||
))}
|
||||
</Picker>
|
||||
</View>
|
||||
<View style={styles.modalActions}>
|
||||
<Pressable
|
||||
onPress={() => setGoalModalVisible(false)}
|
||||
style={[styles.modalBtn, { backgroundColor: colorTokens.pageBackgroundEmphasis }]}
|
||||
>
|
||||
<Text style={[styles.modalBtnText, { color: colorTokens.text }]}>取消</Text>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
onPress={handleGoalConfirm}
|
||||
style={[styles.modalBtn, styles.modalBtnPrimary, { backgroundColor: colorTokens.primary }]}
|
||||
>
|
||||
<Text style={[styles.modalBtnText, styles.modalBtnTextPrimary, { color: colorTokens.onPrimary }]}>确定</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
|
||||
{/* 快速添加默认值编辑弹窗 */}
|
||||
<Modal
|
||||
visible={quickAddModalVisible}
|
||||
transparent
|
||||
animationType="fade"
|
||||
onRequestClose={() => setQuickAddModalVisible(false)}
|
||||
>
|
||||
<Pressable style={styles.modalBackdrop} onPress={() => setQuickAddModalVisible(false)} />
|
||||
<View style={styles.modalSheet}>
|
||||
<View style={styles.modalHandle} />
|
||||
<Text style={[styles.modalTitle, { color: colorTokens.text }]}>快速添加默认值</Text>
|
||||
<View style={styles.pickerContainer}>
|
||||
<Picker
|
||||
selectedValue={tempQuickAdd}
|
||||
onValueChange={(value) => setTempQuickAdd(value)}
|
||||
style={styles.picker}
|
||||
>
|
||||
{Array.from({ length: 41 }, (_, i) => 50 + i * 10).map(amount => (
|
||||
<Picker.Item key={amount} label={`${amount}ml`} value={amount} />
|
||||
))}
|
||||
</Picker>
|
||||
</View>
|
||||
<View style={styles.modalActions}>
|
||||
<Pressable
|
||||
onPress={() => setQuickAddModalVisible(false)}
|
||||
style={[styles.modalBtn, { backgroundColor: colorTokens.pageBackgroundEmphasis }]}
|
||||
>
|
||||
<Text style={[styles.modalBtnText, { color: colorTokens.text }]}>取消</Text>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
onPress={handleQuickAddConfirm}
|
||||
style={[styles.modalBtn, styles.modalBtnPrimary, { backgroundColor: colorTokens.primary }]}
|
||||
>
|
||||
<Text style={[styles.modalBtnText, styles.modalBtnTextPrimary, { color: colorTokens.onPrimary }]}>确定</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
gradientBackground: {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
decorativeCircle1: {
|
||||
position: 'absolute',
|
||||
top: 40,
|
||||
right: 20,
|
||||
width: 60,
|
||||
height: 60,
|
||||
borderRadius: 30,
|
||||
backgroundColor: '#0EA5E9',
|
||||
opacity: 0.1,
|
||||
},
|
||||
decorativeCircle2: {
|
||||
position: 'absolute',
|
||||
bottom: -15,
|
||||
left: -15,
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 20,
|
||||
backgroundColor: '#0EA5E9',
|
||||
opacity: 0.05,
|
||||
},
|
||||
keyboardAvoidingView: {
|
||||
flex: 1,
|
||||
},
|
||||
scrollView: {
|
||||
flex: 1,
|
||||
},
|
||||
scrollContent: {
|
||||
padding: 20,
|
||||
},
|
||||
section: {
|
||||
marginBottom: 32,
|
||||
},
|
||||
settingsMenuContainer: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
borderRadius: 12,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 1 },
|
||||
shadowOpacity: 0.05,
|
||||
shadowRadius: 4,
|
||||
elevation: 2,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
settingsMenuItem: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
paddingVertical: 16,
|
||||
paddingHorizontal: 16,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#F1F3F4',
|
||||
},
|
||||
settingsMenuItemLeft: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
},
|
||||
settingsIconContainer: {
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: 6,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginRight: 12,
|
||||
},
|
||||
settingsMenuItemContent: {
|
||||
flex: 1,
|
||||
},
|
||||
settingsMenuItemTitle: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
marginBottom: 4,
|
||||
},
|
||||
settingsMenuItemSubtitle: {
|
||||
fontSize: 13,
|
||||
marginBottom: 6,
|
||||
},
|
||||
settingsMenuItemValue: {
|
||||
fontSize: 14,
|
||||
fontWeight: '500',
|
||||
},
|
||||
modalBackdrop: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
backgroundColor: 'rgba(0,0,0,0.4)',
|
||||
},
|
||||
modalSheet: {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
padding: 16,
|
||||
backgroundColor: '#FFFFFF',
|
||||
borderTopLeftRadius: 16,
|
||||
borderTopRightRadius: 16,
|
||||
shadowColor: '#000000',
|
||||
shadowOffset: { width: 0, height: -2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 8,
|
||||
elevation: 16,
|
||||
},
|
||||
modalHandle: {
|
||||
width: 36,
|
||||
height: 4,
|
||||
backgroundColor: '#E0E0E0',
|
||||
borderRadius: 2,
|
||||
alignSelf: 'center',
|
||||
marginBottom: 20,
|
||||
},
|
||||
modalTitle: {
|
||||
fontSize: 20,
|
||||
fontWeight: '600',
|
||||
textAlign: 'center',
|
||||
marginBottom: 20,
|
||||
},
|
||||
pickerContainer: {
|
||||
height: 200,
|
||||
marginBottom: 20,
|
||||
},
|
||||
picker: {
|
||||
height: 200,
|
||||
},
|
||||
modalActions: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-end',
|
||||
gap: 12,
|
||||
},
|
||||
modalBtn: {
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 10,
|
||||
borderRadius: 10,
|
||||
minWidth: 80,
|
||||
alignItems: 'center',
|
||||
},
|
||||
modalBtnPrimary: {
|
||||
// backgroundColor will be set dynamically
|
||||
},
|
||||
modalBtnText: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
},
|
||||
modalBtnTextPrimary: {
|
||||
// color will be set dynamically
|
||||
},
|
||||
// 喝水提醒配置弹窗样式
|
||||
waterReminderModalSheet: {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
padding: 16,
|
||||
backgroundColor: '#FFFFFF',
|
||||
borderTopLeftRadius: 16,
|
||||
borderTopRightRadius: 16,
|
||||
maxHeight: '80%',
|
||||
shadowColor: '#000000',
|
||||
shadowOffset: { width: 0, height: -2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 8,
|
||||
elevation: 16,
|
||||
},
|
||||
waterReminderContent: {
|
||||
flex: 1,
|
||||
marginBottom: 20,
|
||||
},
|
||||
waterReminderSection: {
|
||||
marginBottom: 24,
|
||||
},
|
||||
waterReminderSectionHeader: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
marginBottom: 8,
|
||||
},
|
||||
waterReminderSectionTitleContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
},
|
||||
waterReminderSectionTitle: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
},
|
||||
waterReminderSectionDesc: {
|
||||
fontSize: 14,
|
||||
lineHeight: 20,
|
||||
marginTop: 4,
|
||||
},
|
||||
timeRangeContainer: {
|
||||
flexDirection: 'row',
|
||||
gap: 16,
|
||||
marginTop: 16,
|
||||
},
|
||||
timePickerContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
timeLabel: {
|
||||
fontSize: 14,
|
||||
fontWeight: '500',
|
||||
marginBottom: 8,
|
||||
},
|
||||
timePicker: {
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 16,
|
||||
borderRadius: 8,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: 8,
|
||||
},
|
||||
timePickerText: {
|
||||
fontSize: 16,
|
||||
fontWeight: '500',
|
||||
},
|
||||
timePickerIcon: {
|
||||
opacity: 0.6,
|
||||
},
|
||||
intervalContainer: {
|
||||
marginTop: 16,
|
||||
},
|
||||
intervalPickerContainer: {
|
||||
backgroundColor: '#F8F9FA',
|
||||
borderRadius: 8,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
intervalPicker: {
|
||||
height: 120,
|
||||
},
|
||||
// 时间选择器弹窗样式
|
||||
timePickerModalSheet: {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
padding: 16,
|
||||
backgroundColor: '#FFFFFF',
|
||||
borderTopLeftRadius: 16,
|
||||
borderTopRightRadius: 16,
|
||||
maxHeight: '60%',
|
||||
shadowColor: '#000000',
|
||||
shadowOffset: { width: 0, height: -2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 8,
|
||||
elevation: 16,
|
||||
},
|
||||
timePickerContent: {
|
||||
flex: 1,
|
||||
marginBottom: 20,
|
||||
},
|
||||
timePickerSection: {
|
||||
marginBottom: 20,
|
||||
},
|
||||
timePickerLabel: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
marginBottom: 12,
|
||||
textAlign: 'center',
|
||||
},
|
||||
hourPickerContainer: {
|
||||
backgroundColor: '#F8F9FA',
|
||||
borderRadius: 8,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
hourPicker: {
|
||||
height: 160,
|
||||
},
|
||||
timeRangePreview: {
|
||||
backgroundColor: '#F0F8FF',
|
||||
borderRadius: 8,
|
||||
padding: 16,
|
||||
marginTop: 16,
|
||||
alignItems: 'center',
|
||||
},
|
||||
timeRangePreviewLabel: {
|
||||
fontSize: 12,
|
||||
fontWeight: '500',
|
||||
marginBottom: 4,
|
||||
},
|
||||
timeRangePreviewText: {
|
||||
fontSize: 18,
|
||||
fontWeight: '600',
|
||||
marginBottom: 8,
|
||||
},
|
||||
timeRangeWarning: {
|
||||
fontSize: 12,
|
||||
color: '#FF6B6B',
|
||||
textAlign: 'center',
|
||||
lineHeight: 18,
|
||||
},
|
||||
});
|
||||
|
||||
export default WaterSettings;
|
||||
Reference in New Issue
Block a user