├── 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 |
--------------------------------------------------------------------------------