- Introduced new translation files for medication, personal, and weight management in Chinese. - Updated the main index file to include the new translation modules. - Enhanced the medication type definitions to include 'ointment'. - Refactored workout type labels to utilize i18n for better localization support. - Improved sleep quality descriptions and recommendations with i18n integration.
649 lines
19 KiB
TypeScript
649 lines
19 KiB
TypeScript
import { InfoModal, type SleepDetailData } from '@/components/sleep/InfoModal';
|
|
import { SleepStagesInfoModal } from '@/components/sleep/SleepStagesInfoModal';
|
|
import { SleepStageTimeline } from '@/components/sleep/SleepStageTimeline';
|
|
import { HeaderBar } from '@/components/ui/HeaderBar';
|
|
import { useColorScheme } from '@/hooks/useColorScheme';
|
|
import { useI18n } from '@/hooks/useI18n';
|
|
import {
|
|
fetchCompleteSleepData,
|
|
formatSleepTime,
|
|
getSleepStageColor,
|
|
SleepStage,
|
|
type CompleteSleepData
|
|
} from '@/utils/sleepHealthKit';
|
|
import { Ionicons } from '@expo/vector-icons';
|
|
import dayjs from 'dayjs';
|
|
import { GlassView, isLiquidGlassAvailable } from 'expo-glass-effect';
|
|
import { Image } from 'expo-image';
|
|
import { LinearGradient } from 'expo-linear-gradient';
|
|
import { router, useLocalSearchParams } from 'expo-router';
|
|
import React, { useCallback, useEffect, useState } from 'react';
|
|
import {
|
|
ActivityIndicator,
|
|
Dimensions,
|
|
ScrollView,
|
|
StatusBar,
|
|
StyleSheet,
|
|
Text,
|
|
TouchableOpacity,
|
|
View
|
|
} from 'react-native';
|
|
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
|
|
const { width } = Dimensions.get('window');
|
|
const HERO_HEIGHT = width * 0.76;
|
|
|
|
export default function SleepDetailScreen() {
|
|
const { t } = useI18n();
|
|
const insets = useSafeAreaInsets();
|
|
const theme = (useColorScheme() ?? 'light') as 'light' | 'dark';
|
|
|
|
const [sleepData, setSleepData] = useState<CompleteSleepData | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
// 从导航参数获取日期,如果没有则使用今天
|
|
const { date: dateParam } = useLocalSearchParams<{ date?: string }>();
|
|
const [selectedDate] = useState(() => {
|
|
if (dateParam) {
|
|
return dayjs(dateParam).toDate();
|
|
}
|
|
return dayjs().toDate();
|
|
});
|
|
|
|
const [infoModal, setInfoModal] = useState<{ visible: boolean; title: string; type: 'sleep-time' | 'sleep-quality' | null }>({
|
|
visible: false,
|
|
title: '',
|
|
type: null
|
|
});
|
|
|
|
const [sleepStagesModal, setSleepStagesModal] = useState({
|
|
visible: false
|
|
});
|
|
|
|
const loadSleepData = useCallback(async () => {
|
|
try {
|
|
setLoading(true);
|
|
const data = await fetchCompleteSleepData(selectedDate);
|
|
setSleepData(data);
|
|
} catch (error) {
|
|
console.error('Failed to load sleep data:', error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [selectedDate]);
|
|
|
|
useEffect(() => {
|
|
loadSleepData();
|
|
}, [loadSleepData]);
|
|
|
|
// 如果没有数据,使用默认数据结构
|
|
const displayData: CompleteSleepData = sleepData || {
|
|
sleepScore: 0,
|
|
totalSleepTime: 0,
|
|
sleepQualityPercentage: 0,
|
|
bedtime: '',
|
|
wakeupTime: '',
|
|
timeInBed: 0,
|
|
sleepStages: [],
|
|
rawSleepSamples: [],
|
|
averageHeartRate: null,
|
|
sleepHeartRateData: [],
|
|
sleepEfficiency: 0,
|
|
qualityDescription: t('sleepDetail.noData'),
|
|
recommendation: t('sleepDetail.noDataRecommendation')
|
|
};
|
|
|
|
const formatDateTitle = (date: Date) => {
|
|
if (dayjs(date).isSame(dayjs(), 'day')) {
|
|
return t('sleepDetail.today');
|
|
}
|
|
return dayjs(date).format('M月D日');
|
|
};
|
|
|
|
const getScoreColor = (score: number) => {
|
|
if (score >= 85) return '#10B981'; // Green
|
|
if (score >= 70) return '#3B82F6'; // Blue
|
|
if (score >= 60) return '#F59E0B'; // Yellow
|
|
};
|
|
|
|
// 加载状态
|
|
if (loading) {
|
|
return (
|
|
<SafeAreaView style={[styles.safeArea, { backgroundColor: '#f3f4fb' }]}>
|
|
<HeaderBar title={formatDateTitle(selectedDate)} onBack={() => router.back()} withSafeTop transparent={false} />
|
|
<View style={styles.missingContainer}>
|
|
<ActivityIndicator color="#5E8BFF" size="large" />
|
|
<Text style={styles.missingText}>{t('sleepDetail.loading')}</Text>
|
|
</View>
|
|
</SafeAreaView>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
<StatusBar barStyle="light-content" />
|
|
|
|
{/* 顶部导航覆盖层 */}
|
|
<View pointerEvents="box-none" style={[styles.headerOverlay, { paddingTop: insets.top }]}>
|
|
<HeaderBar
|
|
title=""
|
|
tone="light"
|
|
transparent
|
|
withSafeTop={false}
|
|
right={
|
|
<View style={styles.headerButtons}>
|
|
{isLiquidGlassAvailable() ? (
|
|
<TouchableOpacity
|
|
onPress={() => {}}
|
|
activeOpacity={0.7}
|
|
>
|
|
<GlassView
|
|
style={styles.iconButton}
|
|
glassEffectStyle="clear"
|
|
tintColor="rgba(255, 255, 255, 0.3)"
|
|
isInteractive={true}
|
|
>
|
|
<Ionicons name="share-social-outline" size={20} color="#ffffff" />
|
|
</GlassView>
|
|
</TouchableOpacity>
|
|
) : (
|
|
<TouchableOpacity
|
|
style={[styles.iconButton, styles.fallbackIconButton]}
|
|
activeOpacity={0.7}
|
|
>
|
|
<Ionicons name="share-social-outline" size={20} color="#ffffff" />
|
|
</TouchableOpacity>
|
|
)}
|
|
</View>
|
|
}
|
|
/>
|
|
</View>
|
|
|
|
<ScrollView
|
|
style={styles.scrollView}
|
|
bounces={true}
|
|
showsVerticalScrollIndicator={false}
|
|
contentContainerStyle={[
|
|
styles.scrollContent,
|
|
{ paddingBottom: 40 + insets.bottom },
|
|
]}
|
|
>
|
|
{/* Hero 区域 */}
|
|
<View style={styles.heroContainer}>
|
|
<Image
|
|
source='https://plates-1251306435.cos.ap-guangzhou.myqcloud.com/images/challenge/sleep-pig.jpeg'
|
|
style={styles.heroImage}
|
|
cachePolicy={'memory-disk'}
|
|
/>
|
|
<LinearGradient
|
|
colors={['rgba(21, 23, 44, 0.4)', 'rgba(21, 23, 44, 0.1)', '#f3f4fb']}
|
|
style={StyleSheet.absoluteFillObject}
|
|
locations={[0, 0.6, 1]}
|
|
/>
|
|
<LinearGradient
|
|
colors={['transparent', '#f3f4fb']}
|
|
style={[StyleSheet.absoluteFillObject, { top: '60%' }]}
|
|
locations={[0, 1]}
|
|
/>
|
|
</View>
|
|
|
|
{/* 头部文本块 */}
|
|
<View style={styles.headerTextBlock}>
|
|
<View style={styles.scoreCircle}>
|
|
<Text style={[styles.scoreValue, { color: getScoreColor(displayData.sleepScore) }]}>
|
|
{displayData.sleepScore}
|
|
</Text>
|
|
<Text style={styles.scoreLabel}>{formatDateTitle(selectedDate)}{t('sleepDetail.sleepScore')}</Text>
|
|
</View>
|
|
<Text style={styles.summary}>{displayData.qualityDescription}</Text>
|
|
<Text style={styles.recommendation}>{displayData.recommendation}</Text>
|
|
</View>
|
|
|
|
{/* 核心数据卡片 */}
|
|
<View style={styles.detailCard}>
|
|
{/* 睡眠时长 */}
|
|
<View style={styles.detailRow}>
|
|
<View style={styles.detailIconWrapper}>
|
|
<Ionicons name="moon" size={22} color="#5E8BFF" />
|
|
</View>
|
|
<View style={styles.detailTextWrapper}>
|
|
<View style={styles.detailHeader}>
|
|
<Text style={styles.detailLabel}>{t('sleepDetail.sleepDuration')}</Text>
|
|
<TouchableOpacity
|
|
onPress={() => setInfoModal({ visible: true, title: t('sleepDetail.infoModalTitles.sleepTime'), type: 'sleep-time' })}
|
|
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
|
>
|
|
<Ionicons name="information-circle-outline" size={16} color="#A0AEC0" />
|
|
</TouchableOpacity>
|
|
</View>
|
|
<Text style={styles.detailValue}>
|
|
{displayData.totalSleepTime > 0 ? formatSleepTime(displayData.totalSleepTime) : '--'}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
|
|
{/* 分割线 */}
|
|
<View style={styles.divider} />
|
|
|
|
{/* 睡眠质量 */}
|
|
<View style={styles.detailRow}>
|
|
<View style={[styles.detailIconWrapper, { backgroundColor: '#EEF2FF' }]}>
|
|
<Ionicons name="star" size={22} color="#6B6CFF" />
|
|
</View>
|
|
<View style={styles.detailTextWrapper}>
|
|
<View style={styles.detailHeader}>
|
|
<Text style={styles.detailLabel}>{t('sleepDetail.sleepQuality')}</Text>
|
|
<TouchableOpacity
|
|
onPress={() => setInfoModal({ visible: true, title: t('sleepDetail.infoModalTitles.sleepQuality'), type: 'sleep-quality' })}
|
|
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
|
>
|
|
<Ionicons name="information-circle-outline" size={16} color="#A0AEC0" />
|
|
</TouchableOpacity>
|
|
</View>
|
|
<Text style={styles.detailValue}>
|
|
{displayData.sleepQualityPercentage > 0 ? `${displayData.sleepQualityPercentage}%` : '--%'}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
|
|
{/* 睡眠阶段时间轴 */}
|
|
<View style={styles.sectionHeader}>
|
|
<Text style={styles.sectionTitle}>{t('sleepDetail.sleepStages')}</Text>
|
|
<TouchableOpacity onPress={() => setSleepStagesModal({ visible: true })}>
|
|
<Text style={styles.sectionAction}>{t('sleepDetail.learnMore')}</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
<View style={styles.timelineCard}>
|
|
<SleepStageTimeline
|
|
sleepSamples={displayData.rawSleepSamples}
|
|
bedtime={displayData.bedtime}
|
|
wakeupTime={displayData.wakeupTime}
|
|
hideHeader
|
|
style={styles.timelineInner}
|
|
/>
|
|
</View>
|
|
|
|
{/* 睡眠阶段统计网格 */}
|
|
<View style={styles.stagesGridContainer}>
|
|
{(() => {
|
|
let stagesToDisplay;
|
|
if (displayData.sleepStages.length > 0) {
|
|
const existingStages = new Map(displayData.sleepStages.map(s => [s.stage, s]));
|
|
stagesToDisplay = [
|
|
existingStages.get(SleepStage.Awake) || { stage: SleepStage.Awake, duration: 0, percentage: 0 },
|
|
existingStages.get(SleepStage.REM) || { stage: SleepStage.REM, duration: 0, percentage: 0 },
|
|
existingStages.get(SleepStage.Core) || { stage: SleepStage.Core, duration: 0, percentage: 0 },
|
|
existingStages.get(SleepStage.Deep) || { stage: SleepStage.Deep, duration: 0, percentage: 0 }
|
|
];
|
|
} else {
|
|
stagesToDisplay = [
|
|
{ stage: SleepStage.Awake, duration: 0, percentage: 0 },
|
|
{ stage: SleepStage.REM, duration: 0, percentage: 0 },
|
|
{ stage: SleepStage.Core, duration: 0, percentage: 0 },
|
|
{ stage: SleepStage.Deep, duration: 0, percentage: 0 }
|
|
];
|
|
}
|
|
return stagesToDisplay;
|
|
})().map((stageData, index) => {
|
|
const getStageName = (stage: SleepStage) => {
|
|
switch (stage) {
|
|
case SleepStage.Awake: return t('sleepDetail.awake');
|
|
case SleepStage.REM: return t('sleepDetail.rem');
|
|
case SleepStage.Core: return t('sleepDetail.core');
|
|
case SleepStage.Deep: return t('sleepDetail.deep');
|
|
default: return t('sleepDetail.unknown');
|
|
}
|
|
};
|
|
|
|
const stageColor = getSleepStageColor(stageData.stage);
|
|
|
|
return (
|
|
<View key={index} style={styles.stageCard}>
|
|
<View style={styles.stageHeader}>
|
|
<View style={[styles.stageDot, { backgroundColor: stageColor }]} />
|
|
<Text style={[styles.stageTitle, { color: stageColor }]}>
|
|
{getStageName(stageData.stage)}
|
|
</Text>
|
|
</View>
|
|
<Text style={styles.stageValue}>
|
|
{formatSleepTime(stageData.duration)}
|
|
</Text>
|
|
<View style={styles.stageProgressBg}>
|
|
<View
|
|
style={[
|
|
styles.stageProgressFill,
|
|
{
|
|
width: `${Math.min(100, stageData.percentage)}%`,
|
|
backgroundColor: stageColor
|
|
}
|
|
]}
|
|
/>
|
|
</View>
|
|
<Text style={styles.stagePercentage}>
|
|
{stageData.percentage}%
|
|
</Text>
|
|
</View>
|
|
);
|
|
})}
|
|
</View>
|
|
|
|
{/* 原始数据列表 (如果有大量数据) */}
|
|
{sleepData && sleepData.rawSleepSamples && sleepData.rawSleepSamples.length > 0 && (
|
|
<View style={styles.rawSamplesCard}>
|
|
<View style={styles.rawSamplesHeader}>
|
|
<Text style={styles.rawSamplesTitle}>
|
|
{t('sleepDetail.rawData')} ({sleepData.rawSleepSamples.length})
|
|
</Text>
|
|
<Ionicons name="list-outline" size={20} color="#6f7ba7" />
|
|
</View>
|
|
{/* 这里可以考虑是否真的需要显示长列表,或者只显示摘要 */}
|
|
<Text style={styles.rawSamplesSubtitle}>
|
|
{t('sleepDetail.rawDataDescription', { count: sleepData.rawSleepSamples.length })}
|
|
</Text>
|
|
</View>
|
|
)}
|
|
|
|
</ScrollView>
|
|
|
|
{/* Modals */}
|
|
{infoModal.type && (
|
|
<InfoModal
|
|
visible={infoModal.visible}
|
|
onClose={() => setInfoModal({ ...infoModal, visible: false })}
|
|
title={infoModal.title}
|
|
type={infoModal.type}
|
|
sleepData={displayData as SleepDetailData}
|
|
/>
|
|
)}
|
|
|
|
<SleepStagesInfoModal
|
|
visible={sleepStagesModal.visible}
|
|
onClose={() => setSleepStagesModal({ visible: false })}
|
|
/>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
backgroundColor: '#f3f4fb',
|
|
},
|
|
safeArea: {
|
|
flex: 1,
|
|
},
|
|
headerOverlay: {
|
|
position: 'absolute',
|
|
left: 0,
|
|
right: 0,
|
|
top: 0,
|
|
zIndex: 20,
|
|
},
|
|
heroContainer: {
|
|
height: HERO_HEIGHT,
|
|
width: '100%',
|
|
overflow: 'hidden',
|
|
position: 'absolute',
|
|
top: 0,
|
|
},
|
|
heroImage: {
|
|
width: '100%',
|
|
height: '100%',
|
|
},
|
|
scrollView: {
|
|
flex: 1,
|
|
},
|
|
scrollContent: {
|
|
paddingBottom: 40,
|
|
},
|
|
headerTextBlock: {
|
|
paddingHorizontal: 24,
|
|
marginTop: HERO_HEIGHT - 60,
|
|
alignItems: 'center',
|
|
},
|
|
scoreCircle: {
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
scoreValue: {
|
|
fontSize: 48,
|
|
fontWeight: '800',
|
|
fontFamily: 'AliBold',
|
|
lineHeight: 56,
|
|
textShadowColor: 'rgba(255, 255, 255, 0.5)',
|
|
textShadowOffset: { width: 0, height: 2 },
|
|
textShadowRadius: 4,
|
|
},
|
|
scoreLabel: {
|
|
fontSize: 14,
|
|
color: '#596095',
|
|
marginTop: 4,
|
|
fontFamily: 'AliRegular',
|
|
letterSpacing: 0.5,
|
|
},
|
|
summary: {
|
|
marginTop: 16,
|
|
fontSize: 18,
|
|
fontWeight: '600',
|
|
color: '#1c1f3a',
|
|
textAlign: 'center',
|
|
fontFamily: 'AliBold',
|
|
lineHeight: 24,
|
|
},
|
|
recommendation: {
|
|
marginTop: 8,
|
|
fontSize: 14,
|
|
lineHeight: 20,
|
|
color: '#7080b4',
|
|
textAlign: 'center',
|
|
fontFamily: 'AliRegular',
|
|
paddingHorizontal: 10,
|
|
},
|
|
detailCard: {
|
|
marginTop: 28,
|
|
marginHorizontal: 20,
|
|
padding: 20,
|
|
borderRadius: 28,
|
|
backgroundColor: '#ffffff',
|
|
shadowColor: 'rgba(30, 41, 59, 0.1)',
|
|
shadowOpacity: 0.2,
|
|
shadowRadius: 20,
|
|
shadowOffset: { width: 0, height: 10 },
|
|
elevation: 8,
|
|
},
|
|
detailRow: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
paddingVertical: 12,
|
|
},
|
|
detailIconWrapper: {
|
|
width: 46,
|
|
height: 46,
|
|
borderRadius: 23,
|
|
backgroundColor: '#EEF0FF',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
detailTextWrapper: {
|
|
marginLeft: 16,
|
|
flex: 1,
|
|
},
|
|
detailHeader: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 6,
|
|
},
|
|
detailLabel: {
|
|
fontSize: 14,
|
|
color: '#6f7ba7',
|
|
fontFamily: 'AliRegular',
|
|
},
|
|
detailValue: {
|
|
fontSize: 20,
|
|
fontWeight: '700',
|
|
color: '#1c1f3a',
|
|
marginTop: 4,
|
|
fontFamily: 'AliBold',
|
|
},
|
|
divider: {
|
|
height: 1,
|
|
backgroundColor: '#F0F2F9',
|
|
marginVertical: 4,
|
|
marginLeft: 62, // align with text
|
|
},
|
|
sectionHeader: {
|
|
marginTop: 32,
|
|
marginHorizontal: 24,
|
|
marginBottom: 16,
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
},
|
|
sectionTitle: {
|
|
fontSize: 18,
|
|
fontWeight: '700',
|
|
color: '#1c1f3a',
|
|
fontFamily: 'AliBold',
|
|
},
|
|
sectionAction: {
|
|
fontSize: 13,
|
|
fontWeight: '600',
|
|
color: '#5F6BF0',
|
|
fontFamily: 'AliBold',
|
|
},
|
|
timelineCard: {
|
|
marginHorizontal: 20,
|
|
borderRadius: 24,
|
|
backgroundColor: '#ffffff',
|
|
paddingVertical: 8,
|
|
paddingHorizontal: 4,
|
|
shadowColor: 'rgba(30, 41, 59, 0.08)',
|
|
shadowOpacity: 0.15,
|
|
shadowRadius: 16,
|
|
shadowOffset: { width: 0, height: 8 },
|
|
elevation: 4,
|
|
overflow: 'hidden',
|
|
},
|
|
timelineInner: {
|
|
backgroundColor: 'transparent',
|
|
shadowOpacity: 0,
|
|
elevation: 0,
|
|
padding: 0,
|
|
marginBottom: 0,
|
|
marginHorizontal: 0,
|
|
},
|
|
stagesGridContainer: {
|
|
flexDirection: 'row',
|
|
flexWrap: 'wrap',
|
|
gap: 12,
|
|
paddingHorizontal: 20,
|
|
marginTop: 20,
|
|
},
|
|
stageCard: {
|
|
width: (width - 52) / 2, // 20*2 margin + 12 gap
|
|
backgroundColor: '#ffffff',
|
|
borderRadius: 20,
|
|
padding: 16,
|
|
shadowColor: 'rgba(30, 41, 59, 0.06)',
|
|
shadowOffset: { width: 0, height: 4 },
|
|
shadowOpacity: 0.1,
|
|
shadowRadius: 12,
|
|
elevation: 3,
|
|
},
|
|
stageHeader: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
marginBottom: 12,
|
|
},
|
|
stageDot: {
|
|
width: 8,
|
|
height: 8,
|
|
borderRadius: 4,
|
|
marginRight: 8,
|
|
},
|
|
stageTitle: {
|
|
fontSize: 13,
|
|
fontWeight: '600',
|
|
fontFamily: 'AliBold',
|
|
},
|
|
stageValue: {
|
|
fontSize: 18,
|
|
fontWeight: '700',
|
|
color: '#1c1f3a',
|
|
fontFamily: 'AliBold',
|
|
marginBottom: 8,
|
|
},
|
|
stageProgressBg: {
|
|
height: 4,
|
|
backgroundColor: '#F0F2F9',
|
|
borderRadius: 2,
|
|
marginBottom: 8,
|
|
overflow: 'hidden',
|
|
},
|
|
stageProgressFill: {
|
|
height: '100%',
|
|
borderRadius: 2,
|
|
},
|
|
stagePercentage: {
|
|
fontSize: 12,
|
|
color: '#6f7ba7',
|
|
fontFamily: 'AliRegular',
|
|
},
|
|
rawSamplesCard: {
|
|
marginTop: 24,
|
|
marginHorizontal: 20,
|
|
padding: 20,
|
|
borderRadius: 24,
|
|
backgroundColor: '#ffffff',
|
|
opacity: 0.8,
|
|
},
|
|
rawSamplesHeader: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
marginBottom: 8,
|
|
},
|
|
rawSamplesTitle: {
|
|
fontSize: 15,
|
|
fontWeight: '600',
|
|
color: '#1c1f3a',
|
|
fontFamily: 'AliBold',
|
|
},
|
|
rawSamplesSubtitle: {
|
|
fontSize: 12,
|
|
color: '#6f7ba7',
|
|
fontFamily: 'AliRegular',
|
|
},
|
|
missingContainer: {
|
|
flex: 1,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
paddingHorizontal: 32,
|
|
},
|
|
missingText: {
|
|
fontSize: 16,
|
|
color: '#6f7ba7',
|
|
marginTop: 16,
|
|
fontFamily: 'AliRegular',
|
|
},
|
|
headerButtons: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 12,
|
|
},
|
|
iconButton: {
|
|
width: 40,
|
|
height: 40,
|
|
borderRadius: 20,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
overflow: 'hidden',
|
|
},
|
|
fallbackIconButton: {
|
|
backgroundColor: 'rgba(255, 255, 255, 0.2)',
|
|
borderWidth: 1,
|
|
borderColor: 'rgba(255, 255, 255, 0.3)',
|
|
},
|
|
}); |