[READ ONLY MIRROR] Open Source TikTok alternative built on AT Protocol github.com/sprksocial/client
flutter atproto video dart
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

essa porra

C3B e02c8631 8df2683d

+42 -304
-1
app/(tabs)/index.tsx
··· 12 12 import { PostProps } from '@/types/Interfaces'; 13 13 import { fetchTrendingPosts } from '@/api/feedServices'; 14 14 import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs'; 15 - import { router } from 'expo-router'; 16 15 17 16 export default function HomeScreen() { 18 17 const flatListRef = useRef<FlatList<PostProps>>(null);
+25 -3
components/Video/VideoTop.tsx
··· 272 272 title: string, 273 273 selected: boolean, 274 274 key: string, 275 + index: number, 275 276 ) => { 276 277 return ( 277 - <TouchableOpacity key={key}> 278 - <ThemedText type='defaultBold' darkColor={Colors.dark.text} lightColor={Colors.dark.text} style={[styles.text, selected ? { color: Colors.light.background } : {}]}>{title}</ThemedText> 278 + <TouchableOpacity 279 + key={key} 280 + onPress={() => { 281 + const newFeeds = [...customFeeds]; 282 + newFeeds.forEach(feed => feed.selected = false); 283 + newFeeds[index].selected = true; 284 + setCustomFeeds(newFeeds); 285 + }} 286 + > 287 + <ThemedText 288 + type='defaultBold' 289 + darkColor={Colors.dark.text} 290 + lightColor={Colors.dark.text} 291 + style={[ 292 + styles.text, 293 + { 294 + color: Colors.light.background, 295 + opacity: selected ? 1 : 0.5 296 + } 297 + ]} 298 + > 299 + {title} 300 + </ThemedText> 279 301 </TouchableOpacity> 280 302 ); 281 303 }; ··· 363 385 { 364 386 customFeeds.map((feed, index) => ( 365 387 feed.enabled && 366 - generateFeedTab(feed.name, feed.selected, `feedTab_${index}`) 388 + generateFeedTab(feed.name, feed.selected, `feedTab_${index}`, index) 367 389 )) 368 390 } 369 391
-135
components/global/BottomSlider.tsx
··· 1 - import React, { useEffect, useRef, useState } from 'react'; 2 - import { 3 - View, 4 - StyleSheet, 5 - useColorScheme, 6 - TouchableOpacity, 7 - Dimensions, 8 - SafeAreaView, 9 - ScrollView, 10 - Animated, 11 - StyleProp, 12 - ViewStyle, 13 - } from 'react-native'; 14 - import { ThemedText } from '../ThemedText'; 15 - import { Colors } from '@/constants/Colors'; 16 - import { Ionicons } from '@expo/vector-icons'; 17 - 18 - interface BottomSliderProps { 19 - visible: boolean; 20 - onClose?: () => void; 21 - title?: string; 22 - renderHeader?: () => React.ReactNode; 23 - animationDuration?: number; 24 - containerStyle?: StyleProp<ViewStyle>; 25 - headerStyle?: StyleProp<ViewStyle>; 26 - contentStyle?: StyleProp<ViewStyle>; 27 - children?: React.ReactNode; 28 - } 29 - 30 - const BottomSlider: React.FC<BottomSliderProps> = ({ 31 - visible, 32 - onClose, 33 - title = 'Content Settings', 34 - renderHeader, 35 - animationDuration = 150, 36 - containerStyle, 37 - headerStyle, 38 - contentStyle, 39 - children, 40 - }) => { 41 - const screenHeight = Dimensions.get('screen').height; 42 - const colorScheme: 'light' | 'dark' = useColorScheme() as 'light' | 'dark'; 43 - 44 - const translateY = useRef(new Animated.Value(screenHeight)).current; 45 - 46 - 47 - useEffect(() => { 48 - if (visible) { 49 - Animated.timing(translateY, { 50 - toValue: 0, 51 - duration: animationDuration, 52 - useNativeDriver: true, 53 - }).start(); 54 - } else { 55 - Animated.timing(translateY, { 56 - toValue: screenHeight, 57 - duration: animationDuration, 58 - useNativeDriver: true, 59 - }).start(({ finished }) => { 60 - if (finished && onClose) { 61 - onClose(); 62 - } 63 - }); 64 - } 65 - }, [visible, onClose, translateY, animationDuration, screenHeight]); 66 - 67 - const handleClose = () => { 68 - if (onClose) { 69 - onClose(); 70 - } 71 - }; 72 - 73 - const styles = StyleSheet.create({ 74 - wrapper: { 75 - position: 'absolute', 76 - width: '100%', 77 - height: '100%', 78 - zIndex: 999, 79 - }, 80 - sliderContainer: { 81 - backgroundColor: Colors[colorScheme ?? 'light'].background, 82 - width: '100%', 83 - height: screenHeight, 84 - transform: [{ translateY }], 85 - }, 86 - headerContainer: { 87 - width: '100%', 88 - flexDirection: 'row', 89 - justifyContent: 'space-between', 90 - alignItems: 'center', 91 - paddingHorizontal: 10, 92 - paddingTop: 10, 93 - }, 94 - closeButton: { 95 - padding: 5, 96 - borderRadius: 50, 97 - }, 98 - sliderContent: { 99 - width: '100%', 100 - height: '100%', 101 - padding: 10, 102 - marginBottom: 20, 103 - }, 104 - }); 105 - 106 - const defaultHeader = ( 107 - <View style={[styles.headerContainer, headerStyle]}> 108 - <ThemedText type="title">{title}</ThemedText> 109 - <TouchableOpacity style={styles.closeButton} onPress={handleClose}> 110 - <Ionicons 111 - name="close" 112 - size={24} 113 - color={Colors[colorScheme ?? 'light'].text} 114 - /> 115 - </TouchableOpacity> 116 - </View> 117 - ); 118 - 119 - const header = renderHeader ? renderHeader() : defaultHeader; 120 - 121 - return ( 122 - <View pointerEvents={visible ? 'auto' : 'none'} style={[styles.wrapper, containerStyle]}> 123 - <Animated.View style={styles.sliderContainer}> 124 - <SafeAreaView> 125 - {header} 126 - <ScrollView style={[styles.sliderContent, contentStyle]}> 127 - {children} 128 - </ScrollView> 129 - </SafeAreaView> 130 - </Animated.View> 131 - </View> 132 - ); 133 - }; 134 - 135 - export default BottomSlider;
+17 -165
components/global/InputArea.tsx
··· 11 11 ViewStyle, 12 12 TextStyle, 13 13 KeyboardTypeOptions, 14 - Platform, 15 - Modal, 16 - Dimensions, 17 - SafeAreaView 18 14 } from 'react-native'; 19 - import DateTimePicker from '@react-native-community/datetimepicker'; 20 15 import { ThemedText } from '../ThemedText'; 21 - import BottomSlider from './BottomSlider'; 22 - import ActionButton from './ActionButton'; 23 16 24 - interface BaseInputAreaProps { 17 + interface InputAreaProps { 25 18 placeholder: string; 26 19 value: string; 27 20 onChangeText: (text: string) => void; 21 + type?: 'text' | 'email' | 'password' | 'username'; 28 22 icon?: string; 29 23 label?: string; 30 24 error?: string; ··· 35 29 autoCapitalize?: 'none' | 'sentences' | 'words' | 'characters'; 36 30 } 37 31 38 - interface NonBirthDateProps extends BaseInputAreaProps { 39 - type?: 'text' | 'email' | 'password' | 'username'; 40 - format?: never; 41 - } 42 - 43 - interface BirthDateProps extends BaseInputAreaProps { 44 - type: 'birthDate'; 45 - format: 'ddmmyy' | 'mmddyy' | 'ddmmyyyy' | 'mmddyyyy'; 46 - } 47 - 48 - export type InputAreaProps = NonBirthDateProps | BirthDateProps; 49 - 50 32 const InputArea: React.FC<InputAreaProps> = ({ 51 33 placeholder, 52 34 value, ··· 60 42 inputStyle, 61 43 maxLength, 62 44 autoCapitalize = 'none', 63 - format, 64 45 }) => { 65 46 const colorScheme = useColorScheme(); 66 47 const [secureTextEntry, setSecureTextEntry] = useState(type === 'password'); 67 - const [showDatePicker, setShowDatePicker] = useState(false); 68 - const [date, setDate] = useState(new Date()); 69 48 70 49 const getKeyboardType = (): KeyboardTypeOptions => { 71 50 switch (type) { ··· 74 53 case 'username': 75 54 case 'text': 76 55 return 'default'; 77 - case 'birthDate': 78 - return 'numeric'; 79 56 default: 80 57 return 'default'; 81 58 } 82 59 }; 83 60 84 - const formatDate = (date: Date): string => { 85 - const day = date.getDate().toString().padStart(2, '0'); 86 - const month = (date.getMonth() + 1).toString().padStart(2, '0'); 87 - const fullYear = date.getFullYear().toString(); 88 - const year = fullYear.slice(2); 89 - 90 - switch (format) { 91 - case 'ddmmyy': 92 - return `${day}/${month}/${year}`; 93 - case 'mmddyy': 94 - return `${month}/${day}/${year}`; 95 - case 'ddmmyyyy': 96 - return `${day}/${month}/${fullYear}`; 97 - case 'mmddyyyy': 98 - return `${month}/${day}/${fullYear}`; 99 - default: 100 - return type === 'birthDate' ? `${day}/${month}/${fullYear}` : ''; 101 - } 102 - }; 103 - 104 - const handleDateChange = (event: any, selectedDate?: Date) => { 105 - const currentDate = selectedDate || date; 106 - 107 - if (Platform.OS === 'android') { 108 - setShowDatePicker(false); 109 - } 110 - 111 - setDate(currentDate); 112 - onChangeText(formatDate(currentDate)); 113 - }; 114 - 115 61 const styles = StyleSheet.create({ 116 62 container: { 117 63 marginBottom: 16, ··· 154 100 passwordIcon: { 155 101 padding: 4, 156 102 }, 157 - datePickerButton: { 158 - flex: 1, 159 - justifyContent: 'center', 160 - height: '40%', 161 - width: '90%', 162 - }, 163 - modalContent: { 164 - width: '100%', 165 - height: '100%', 166 - position: 'absolute', 167 - bottom: 0, 168 - zIndex: 1, 169 - }, 170 103 }); 171 104 172 - const renderDatePicker = () => { 173 - if (Platform.OS === 'ios') { 174 - return ( 175 - showDatePicker && 176 - <SafeAreaView style={styles.modalContent}> 177 - <View style={{ 178 - position: 'absolute', 179 - bottom: -100, 180 - left: 0, 181 - width: '100%', 182 - height: Dimensions.get('screen').height / 3, 183 - backgroundColor: Colors[colorScheme ?? 'light'].background, 184 - justifyContent: 'center', 185 - alignItems: 'center', 186 - }}> 187 - <DateTimePicker 188 - value={date} 189 - mode="date" 190 - display="spinner" 191 - onChange={handleDateChange} 192 - maximumDate={new Date()} 193 - /> 194 - <ActionButton 195 - title="Done" 196 - onPress={() => setShowDatePicker(false)} 197 - width={'100%'} 198 - /> 199 - </View> 200 - </SafeAreaView> 201 - ); 202 - } 203 - 204 - return showDatePicker && ( 205 - <DateTimePicker 206 - value={date} 207 - mode="date" 208 - display="default" 209 - onChange={handleDateChange} 210 - maximumDate={new Date()} 211 - /> 212 - ); 213 - }; 214 - 215 105 return ( 216 106 <View style={[styles.container, style]}> 217 107 {label && <ThemedText type='description'>{label}</ThemedText>} ··· 226 116 /> 227 117 )} 228 118 229 - {type === 'birthDate' ? ( 230 - <TouchableOpacity 231 - style={styles.datePickerButton} 232 - onPress={() => !disabled && setShowDatePicker(true)} 233 - disabled={disabled} 234 - > 235 - <Text 236 - style={[ 237 - styles.input, 238 - inputStyle, 239 - !value && { color: Colors[colorScheme ?? 'light'].textGray } 240 - ]} 241 - > 242 - {value || placeholder} 243 - </Text> 244 - </TouchableOpacity> 245 - ) : ( 246 - <TextInput 247 - placeholder={placeholder} 248 - placeholderTextColor={Colors[colorScheme ?? 'light'].textGray} 249 - value={value} 250 - onChangeText={onChangeText} 251 - secureTextEntry={secureTextEntry && type === 'password'} 252 - keyboardType={getKeyboardType()} 253 - editable={!disabled} 254 - autoCapitalize={autoCapitalize} 255 - autoComplete={type === 'password' || (type as string) === 'birthDate' ? 'off' : 'name'} 256 - autoCorrect={false} 257 - maxLength={maxLength} 258 - style={[styles.input, inputStyle]} 259 - /> 260 - )} 119 + <TextInput 120 + placeholder={placeholder} 121 + placeholderTextColor={Colors[colorScheme ?? 'light'].textGray} 122 + value={value} 123 + onChangeText={onChangeText} 124 + secureTextEntry={secureTextEntry && type === 'password'} 125 + keyboardType={getKeyboardType()} 126 + editable={!disabled} 127 + autoCapitalize={autoCapitalize} 128 + autoComplete={type === 'password' ? 'off' : 'name'} 129 + autoCorrect={false} 130 + maxLength={maxLength} 131 + style={[styles.input, inputStyle]} 132 + /> 261 133 262 134 {type === 'password' && ( 263 135 <TouchableOpacity 264 136 onPress={() => setSecureTextEntry(!secureTextEntry)} 265 137 style={styles.passwordIcon} 266 - > 138 + > 267 139 <Ionicons 268 140 name={secureTextEntry ? 'eye-outline' : 'eye-off-outline'} 269 141 size={20} ··· 271 143 /> 272 144 </TouchableOpacity> 273 145 )} 274 - 275 - {type === 'birthDate' && ( 276 - <TouchableOpacity 277 - onPress={() => !disabled && setShowDatePicker(true)} 278 - style={styles.passwordIcon} 279 - disabled={disabled} 280 - > 281 - <Ionicons 282 - name="calendar-outline" 283 - size={20} 284 - color={Colors[colorScheme ?? 'light'].icon} 285 - /> 286 - </TouchableOpacity> 287 - )} 288 146 </View> 289 147 290 148 {error && <Text style={styles.errorText}>{error}</Text>} 291 - {type === 'birthDate' && ( 292 - <View style={{ 293 - }}> 294 - {renderDatePicker()} 295 - </View> 296 - )} 297 149 </View> 298 150 ); 299 151 };