├── assets ├── icon.png ├── favicon.png ├── splash.png └── adaptive-icon.png ├── tsconfig.json ├── babel.config.js ├── .gitignore ├── App.tsx ├── app.json ├── package.json └── ProfileScreen.tsx /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iam-rohid/twitter-profile-clone/HEAD/assets/icon.png -------------------------------------------------------------------------------- /assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iam-rohid/twitter-profile-clone/HEAD/assets/favicon.png -------------------------------------------------------------------------------- /assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iam-rohid/twitter-profile-clone/HEAD/assets/splash.png -------------------------------------------------------------------------------- /assets/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iam-rohid/twitter-profile-clone/HEAD/assets/adaptive-icon.png -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "expo/tsconfig.base", 3 | "compilerOptions": { 4 | "strict": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | return { 4 | presets: ["babel-preset-expo"], 5 | plugins: ["react-native-reanimated/plugin"], 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .expo/ 3 | dist/ 4 | npm-debug.* 5 | *.jks 6 | *.p8 7 | *.p12 8 | *.key 9 | *.mobileprovision 10 | *.orig.* 11 | web-build/ 12 | 13 | # macOS 14 | .DS_Store 15 | 16 | # Temporary files created by Metro to check the health of the file watcher 17 | .metro-health-check* 18 | -------------------------------------------------------------------------------- /App.tsx: -------------------------------------------------------------------------------- 1 | import { StatusBar } from "expo-status-bar"; 2 | import { SafeAreaProvider } from "react-native-safe-area-context"; 3 | import "react-native-gesture-handler"; 4 | import ProfileScreen from "./ProfileScreen"; 5 | 6 | export default function App() { 7 | return ( 8 | 9 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "twitter-profile-clone", 4 | "slug": "twitter-profile-clone", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/icon.png", 8 | "userInterfaceStyle": "light", 9 | "splash": { 10 | "image": "./assets/splash.png", 11 | "resizeMode": "contain", 12 | "backgroundColor": "#ffffff" 13 | }, 14 | "assetBundlePatterns": [ 15 | "**/*" 16 | ], 17 | "ios": { 18 | "supportsTablet": true 19 | }, 20 | "android": { 21 | "adaptiveIcon": { 22 | "foregroundImage": "./assets/adaptive-icon.png", 23 | "backgroundColor": "#ffffff" 24 | } 25 | }, 26 | "web": { 27 | "favicon": "./assets/favicon.png" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "twitter-profile-clone", 3 | "version": "1.0.0", 4 | "main": "node_modules/expo/AppEntry.js", 5 | "scripts": { 6 | "start": "expo start", 7 | "android": "expo start --android", 8 | "ios": "expo start --ios", 9 | "web": "expo start --web" 10 | }, 11 | "dependencies": { 12 | "expo": "~48.0.9", 13 | "expo-status-bar": "~1.4.4", 14 | "react": "18.2.0", 15 | "react-native": "0.71.4", 16 | "react-native-reanimated": "~2.14.4", 17 | "react-native-gesture-handler": "~2.9.0", 18 | "react-native-safe-area-context": "4.5.0", 19 | "expo-blur": "~12.2.2" 20 | }, 21 | "devDependencies": { 22 | "@babel/core": "^7.20.0", 23 | "@types/react": "~18.0.14", 24 | "typescript": "^4.9.4" 25 | }, 26 | "private": true 27 | } 28 | -------------------------------------------------------------------------------- /ProfileScreen.tsx: -------------------------------------------------------------------------------- 1 | import { StyleSheet, View, Image, Text } from "react-native"; 2 | import React from "react"; 3 | import { useSafeAreaInsets } from "react-native-safe-area-context"; 4 | import Animated, { 5 | Extrapolate, 6 | interpolate, 7 | useAnimatedProps, 8 | useAnimatedScrollHandler, 9 | useAnimatedStyle, 10 | useSharedValue, 11 | } from "react-native-reanimated"; 12 | import { TouchableOpacity } from "react-native-gesture-handler"; 13 | import Icons from "@expo/vector-icons/MaterialIcons"; 14 | import { BlurView } from "expo-blur"; 15 | 16 | const COVER_PHOTO = `https://images.unsplash.com/photo-1541167760496-1628856ab772?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1080&q=80`; 17 | const PROFILE_PIC = 18 | "https://images.unsplash.com/photo-1600486913747-55e5470d6f40?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=512&h=512&q=80"; 19 | 20 | const APPBAR_HEIGHT = 52; 21 | const DEFAULT_PADDING = 44; 22 | const APPBAR_TITLE_TO_NAME_DISTANCE = 122; 23 | const APPBAR_INFO_OFFSET = 156; 24 | 25 | const AnimatedBlurView = Animated.createAnimatedComponent(BlurView); 26 | 27 | const ProfileScreen = () => { 28 | const insets = useSafeAreaInsets(); 29 | const appBarPaddingBottom = useSharedValue(DEFAULT_PADDING); 30 | const profileOffset = useSharedValue(0); 31 | const blurIntensity = useSharedValue(0); 32 | const appBarInfoOffset = useSharedValue(APPBAR_INFO_OFFSET); 33 | 34 | const scrollHandler = useAnimatedScrollHandler({ 35 | onScroll: (event) => { 36 | appBarPaddingBottom.value = Math.max( 37 | 0, 38 | DEFAULT_PADDING - event.contentOffset.y 39 | ); 40 | profileOffset.value = -event.contentOffset.y; 41 | 42 | blurIntensity.value = interpolate( 43 | event.contentOffset.y, 44 | [ 45 | -40, 46 | 0, 47 | APPBAR_TITLE_TO_NAME_DISTANCE, 48 | APPBAR_TITLE_TO_NAME_DISTANCE + 80, 49 | ], 50 | [40, 0, 0, 40], 51 | Extrapolate.CLAMP 52 | ); 53 | appBarInfoOffset.value = Math.max( 54 | 0, 55 | -event.contentOffset.y + APPBAR_INFO_OFFSET 56 | ); 57 | }, 58 | }); 59 | 60 | const appBarAnimatedStyles = useAnimatedStyle( 61 | () => ({ 62 | paddingBottom: appBarPaddingBottom.value, 63 | zIndex: appBarPaddingBottom.value === 0 ? 1 : -1, 64 | }), 65 | [] 66 | ); 67 | 68 | const profilePicStyles = useAnimatedStyle( 69 | () => ({ 70 | top: insets.top + APPBAR_HEIGHT + profileOffset.value, 71 | transform: [ 72 | { 73 | scale: interpolate( 74 | appBarPaddingBottom.value, 75 | [0, DEFAULT_PADDING], 76 | [0.65, 1], 77 | Extrapolate.CLAMP 78 | ), 79 | }, 80 | { 81 | translateY: interpolate( 82 | appBarPaddingBottom.value, 83 | [0, DEFAULT_PADDING], 84 | [DEFAULT_PADDING, 16], 85 | Extrapolate.CLAMP 86 | ), 87 | }, 88 | ], 89 | }), 90 | [] 91 | ); 92 | 93 | const appBarInfoAnimatedStyle = useAnimatedStyle( 94 | () => ({ 95 | transform: [ 96 | { 97 | translateY: appBarInfoOffset.value, 98 | }, 99 | ], 100 | }), 101 | [] 102 | ); 103 | 104 | const blurViewProps = useAnimatedProps(() => { 105 | return { 106 | intensity: blurIntensity.value, 107 | }; 108 | }); 109 | 110 | return ( 111 | 112 | 121 | 128 | 132 | 133 | 134 | 135 | 136 | 137 | 143 | 154 | Rohid 155 | 156 | 166 | 312 Tweets 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 186 | 192 | 193 | 198 | 201 | 208 | 209 | 210 | Rohid 211 | 212 | 215 | @rohiddev 216 | 217 | 218 | 227 | 228 | Edit Proifle 229 | 230 | 231 | 232 | 233 | 234 | Self-thought Full-Stack Developer 🧑‍💻. Learn more about me at{" "} 235 | rohid.dev 236 | 237 | 238 | 239 | 240 | 241 | Bangladesh 242 | 243 | 244 | 245 | rohid.dev 246 | 247 | 248 | 249 | Joined March 2021 250 | 251 | 252 | 253 | 254 | 255 | 56{" "} 256 | Following 257 | 258 | 259 | 69{" "} 260 | Followers 261 | 262 | 263 | 264 | {new Array(100).fill("Post").map((item, i) => ( 265 | 266 | Post 267 | 268 | ))} 269 | 270 | 271 | ); 272 | }; 273 | 274 | export default ProfileScreen; 275 | 276 | const styles = StyleSheet.create({ 277 | container: { 278 | flex: 1, 279 | }, 280 | appBarContainer: { 281 | position: "absolute", 282 | overflow: "hidden", 283 | top: 0, 284 | left: 0, 285 | right: 0, 286 | }, 287 | appBar: { 288 | flexDirection: "row", 289 | justifyContent: "space-between", 290 | paddingHorizontal: 16, 291 | height: APPBAR_HEIGHT, 292 | alignItems: "center", 293 | }, 294 | appBarIconButton: { 295 | width: 36, 296 | height: 36, 297 | alignItems: "center", 298 | justifyContent: "center", 299 | borderRadius: 40, 300 | backgroundColor: "rgba(0,0,0,0.5)", 301 | }, 302 | coverPhoto: { 303 | position: "absolute", 304 | left: 0, 305 | right: 0, 306 | top: 0, 307 | bottom: 0, 308 | }, 309 | profilePic: { 310 | borderRadius: 100, 311 | overflow: "hidden", 312 | borderWidth: 4, 313 | borderColor: "#fff", 314 | }, 315 | }); 316 | --------------------------------------------------------------------------------