Skip to content

Commit b4780be

Browse files
committedMar 15, 2022
setting up bottom half modal
1 parent ca88239 commit b4780be

14 files changed

+320
-22
lines changed
 

‎App.tsx

+22-8
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,37 @@
11
import 'react-native-gesture-handler';
2+
// import { registerRootComponent } from 'expo'
23
import { StatusBar } from 'expo-status-bar';
34
import React from 'react';
45
import { GestureHandlerRootView } from 'react-native-gesture-handler';
56
import RootNavigator from './navigations'
67
import useCustomFonts from './utils/loadFonts';
78
import TaskProvider from './dataManager/providers/taskProvider';
9+
import NavigationProvider from './dataManager/providers/navigationProvider';
10+
import BottomHalfModal from './components/navigation/bottomHalfModal';
811

912
export default function App() {
1013
const [fontLoaded] = useCustomFonts()
1114

1215
return (
13-
<TaskProvider>
14-
<React.Fragment>
15-
{
16-
fontLoaded && <RootNavigator />
17-
}
16+
<GestureHandlerRootView style={{flex: 1}}>
17+
<TaskProvider>
18+
<NavigationProvider>
19+
<React.Fragment>
20+
{
21+
fontLoaded && (
22+
<>
23+
<RootNavigator />
24+
<BottomHalfModal />
25+
</>
26+
)
27+
}
1828

19-
<StatusBar style='light' />
20-
</React.Fragment>
21-
</TaskProvider>
29+
<StatusBar style='light' />
30+
</React.Fragment>
31+
</NavigationProvider>
32+
</TaskProvider>
33+
</GestureHandlerRootView>
2234
);
2335
}
36+
37+
// registerRootComponent(App)

‎babel.config.js

+1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ module.exports = function(api) {
22
api.cache(true);
33
return {
44
presets: ['babel-preset-expo'],
5+
plugins: ['react-native-reanimated/plugin']
56
};
67
};
+162
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import {
2+
View,
3+
Text,
4+
useWindowDimensions,
5+
TouchableOpacity
6+
} from 'react-native'
7+
import Animated, {
8+
useSharedValue,
9+
useAnimatedStyle,
10+
useAnimatedGestureHandler,
11+
withSpring,
12+
interpolate,
13+
runOnJS
14+
} from 'react-native-reanimated'
15+
import {
16+
PanGestureHandler,
17+
ScrollView
18+
} from 'react-native-gesture-handler'
19+
import React, { useContext, useEffect } from 'react'
20+
import navigationContext from '../../dataManager/contexts/navigationContext'
21+
import styles from '../screens/home/styles/bottomHalfModalStyle'
22+
import Ionicons from '@expo/vector-icons/Ionicons'
23+
24+
type BottomHalfModalItemPropType = {
25+
icon: "trash" | "pencil",
26+
text: string,
27+
color: string
28+
}
29+
30+
const BottomHalfModalItem = ({ icon, text, color }: BottomHalfModalItemPropType) => {
31+
return (
32+
<TouchableOpacity onPress={() => {console.log("hello")}} style={styles.modalItem}>
33+
<React.Fragment>
34+
<Ionicons color={color} size={25} style={styles.modalItemIcon} name={icon} />
35+
36+
<Text style={[styles.modalItemText, { color }]}>{ text }</Text>
37+
</React.Fragment>
38+
</TouchableOpacity>
39+
)
40+
}
41+
42+
const BottomHalfModal = () => {
43+
const { modalVisible, changeModalVisible } = useContext(navigationContext)
44+
const dimensions = useWindowDimensions()
45+
46+
const HEIGHT = dimensions.height
47+
const MIDDLE = HEIGHT - 150
48+
49+
const top = useSharedValue(HEIGHT)
50+
51+
useEffect(() => {
52+
if (modalVisible) {
53+
top.value = MIDDLE
54+
}
55+
}, [modalVisible])
56+
57+
const style = useAnimatedStyle(() => {
58+
return {
59+
top: withSpring(
60+
interpolate(top.value,
61+
[0, HEIGHT],
62+
[-20, HEIGHT],
63+
"clamp"
64+
)
65+
),
66+
opacity: withSpring(
67+
interpolate(top.value,
68+
[MIDDLE, HEIGHT],
69+
[1, .6],
70+
"clamp"
71+
)
72+
)
73+
}
74+
})
75+
76+
const backgroundStyle = useAnimatedStyle(() => {
77+
return {
78+
opacity: withSpring(
79+
interpolate(top.value,
80+
[MIDDLE, HEIGHT - 120],
81+
[.6, 0],
82+
"clamp"
83+
)
84+
),
85+
transform: [
86+
{
87+
translateY: interpolate(top.value,
88+
[MIDDLE, HEIGHT],
89+
[1, 0]
90+
) > 0 ? 0 : dimensions.height,
91+
}
92+
]
93+
}
94+
})
95+
96+
const changeModalVisibleWrapper = () => {
97+
changeModalVisible()
98+
}
99+
100+
type contextType = {
101+
startTop: number
102+
}
103+
104+
const eventHandler = useAnimatedGestureHandler({
105+
onStart: (_, context: contextType) => {
106+
context.startTop = top.value
107+
},
108+
onActive: (event, context) => {
109+
top.value = context.startTop + event.translationY
110+
},
111+
onEnd: () => {
112+
if (top.value > MIDDLE + 100) {
113+
top.value = HEIGHT
114+
115+
runOnJS(changeModalVisibleWrapper)()
116+
} else if (top.value < MIDDLE - 100) {
117+
console.log("La")
118+
top.value = 0
119+
} else {
120+
top.value = MIDDLE
121+
}
122+
}
123+
})
124+
125+
return (
126+
<>
127+
<PanGestureHandler onGestureEvent={eventHandler}>
128+
<Animated.View
129+
style={[
130+
styles.sheet,
131+
style
132+
]}
133+
>
134+
<View style={styles.sheetIndicator} />
135+
136+
<ScrollView
137+
style={styles.modalContainer}
138+
>
139+
<BottomHalfModalItem icon="pencil" text="Update Task" color="#212529" />
140+
<BottomHalfModalItem icon="trash" text="Delete Task" color="#ef233c" />
141+
</ScrollView>
142+
</Animated.View>
143+
</PanGestureHandler>
144+
145+
<Animated.View
146+
style={[
147+
{
148+
position: "absolute",
149+
top: 0,
150+
left: 0,
151+
right: 0,
152+
bottom: 0,
153+
backgroundColor: "black"
154+
},
155+
backgroundStyle
156+
]}
157+
/>
158+
</>
159+
)
160+
}
161+
162+
export default BottomHalfModal
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { Dimensions, StyleSheet } from "react-native";
2+
3+
const styles = StyleSheet.create({
4+
sheet: {
5+
position: "absolute",
6+
left: 0,
7+
right: 0,
8+
bottom: 0,
9+
width: "100%",
10+
borderTopLeftRadius: 10,
11+
borderTopRightRadius: 10,
12+
backgroundColor: "#fff",
13+
elevation: 1,
14+
zIndex: 50,
15+
paddingHorizontal: 20,
16+
},
17+
sheetIndicator: {
18+
position: "absolute",
19+
top: 10,
20+
left: Dimensions.get("window").width / 2 - 40,
21+
width: 80,
22+
height: 10,
23+
borderRadius: 30,
24+
backgroundColor: "#cacaca"
25+
},
26+
modalContainer: {
27+
width: "100%",
28+
height: "100%",
29+
marginTop: 35
30+
},
31+
modalItem: {
32+
width: "100%",
33+
flexDirection: "row",
34+
justifyContent: "flex-start",
35+
alignItems: 'center'
36+
},
37+
modalItemIcon: {
38+
width: 40,
39+
height: 50,
40+
textAlignVertical: "center"
41+
},
42+
modalItemText: {
43+
fontSize: 16,
44+
fontFamily: "Poppins-Regular"
45+
}
46+
})
47+
48+
export default styles

‎components/screens/home/styles/taskEditorStyle.ts

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import { Dimensions, StyleSheet } from "react-native";
22

33
const styles = StyleSheet.create({
44
container: {
5+
position: "absolute",
6+
top: 80,
7+
left: 0,
58
width: "100%",
69
height: "auto",
710
paddingHorizontal: 10,

‎components/screens/home/styles/taskListStyle.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { StyleSheet } from "react-native";
33
const styles = StyleSheet.create({
44
container: {
55
width: "100%",
6-
flex: 1
6+
flex: 1,
7+
marginTop: 170
78
},
89
title: {
910
marginLeft: 10,

‎components/screens/home/styles/taskStyle.ts

+8
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Dimensions, StyleSheet } from "react-native";
22

33
const styles = StyleSheet.create({
44
container: {
5+
position: "relative",
56
width: "100%",
67
flexDirection: "row",
78
justifyContent: "space-between",
@@ -30,7 +31,14 @@ const styles = StyleSheet.create({
3031
color: "#828282"
3132
},
3233
taskMenu: {
34+
width: 50,
35+
height: 50,
36+
alignItems: "center",
37+
justifyContent: "center",
3338
marginRight: 10,
39+
borderRadius: 100
40+
},
41+
taskMenuIcon: {
3442
fontSize: 20,
3543
}
3644
})

‎components/screens/home/task.tsx

+20-4
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,31 @@
1-
import { Switch, Text, View } from "react-native"
1+
import { Switch, Text, TouchableOpacity, View } from "react-native"
22
import Ionicons from '@expo/vector-icons/Ionicons'
33
import styles from "./styles/taskStyle"
44
import TaskEntity from '../../../entities/task'
55
import { useContext } from "react"
66
import taskContext from "../../../dataManager/contexts/taskContext"
77
import { markTask } from "../../../dataManager/data/actions"
8+
import Animated, { SlideInRight, SlideOutLeft } from "react-native-reanimated"
9+
import navigationContext from "../../../dataManager/contexts/navigationContext"
810

911
type TaskPropType = ({task}: {task: TaskEntity}) => JSX.Element
1012

1113
const Task: TaskPropType = ({ task }) => {
1214
// Get data from the global state
1315
const { dispatch } = useContext(taskContext)
16+
const { changeModalVisible } = useContext(navigationContext)
1417

1518
const handleMarkTask = () => {
1619
// Change the state of the task
1720
dispatch(markTask(task.getId, !task.getMarked))
1821
}
1922

2023
return (
21-
<View style={styles.container}>
24+
<Animated.View
25+
style={styles.container}
26+
entering={SlideInRight}
27+
exiting={SlideOutLeft}
28+
>
2229
<View style={styles.leftSection}>
2330
<Switch
2431
value={task.getMarked}
@@ -32,8 +39,17 @@ const Task: TaskPropType = ({ task }) => {
3239
<Text style={[styles.taskText, task.getMarked && styles.taskMaked]}>{ task.getValue }</Text>
3340
</View>
3441

35-
<Ionicons style={styles.taskMenu} name="ellipsis-vertical" />
36-
</View>
42+
<TouchableOpacity
43+
style={styles.taskMenu}
44+
onPress={() => changeModalVisible()}
45+
activeOpacity={.6}
46+
>
47+
<Ionicons
48+
style={styles.taskMenuIcon}
49+
name="ellipsis-vertical"
50+
/>
51+
</TouchableOpacity>
52+
</Animated.View>
3753
)
3854
}
3955

‎components/screens/home/taskList.tsx

+15-2
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,28 @@
1-
import { useContext } from "react"
1+
import { useContext, useEffect, useRef } from "react"
22
import { ScrollView, Text, View } from "react-native"
33
import taskContext from "../../../dataManager/contexts/taskContext"
44
import styles from "./styles/taskListStyle"
55
import Task from "./task"
66

7+
export type ScrollViewRef = ScrollView & {
8+
flashScrollIndicators: () => void;
9+
};
10+
711
const TaskList = () => {
812
// Get data from the global state
913
const { tasks } = useContext(taskContext)
1014

15+
// UseRef section
16+
const scrollViewRef = useRef<ScrollViewRef>(null)
17+
18+
// UseEffect section
19+
useEffect(() => {
20+
if (scrollViewRef.current)
21+
scrollViewRef.current.scrollToEnd({ animated: true })
22+
}, [tasks])
23+
1124
return (
12-
<ScrollView style={styles.container}>
25+
<ScrollView ref={scrollViewRef} style={styles.container}>
1326
<Text style={styles.title}>List of task</Text>
1427

1528
<View style={styles.taskList}>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { createContext } from 'react';
2+
3+
const NavigationContext = createContext({
4+
modalVisible: true,
5+
changeModalVisible: () => {}
6+
})
7+
8+
export default NavigationContext
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { useState } from "react"
2+
import NavigationContext from "../contexts/navigationContext"
3+
4+
type NavigationProviderPropType = {
5+
children: JSX.Element
6+
}
7+
8+
const NavigationProvider = ({ children }: NavigationProviderPropType) => {
9+
const [modalVisible, setModalVisible] = useState(false)
10+
11+
const navigationContextValue = {
12+
modalVisible,
13+
changeModalVisible: () => setModalVisible(prev => !prev)
14+
}
15+
16+
return (
17+
<NavigationContext.Provider value={navigationContextValue}>
18+
{ children }
19+
</NavigationContext.Provider>
20+
)
21+
}
22+
23+
export default NavigationProvider

‎navigations/StackNavigator.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ const StackNavigator = () => {
2424
color: "#fff"
2525
},
2626
headerTitle: "Tasks Manager",
27-
headerShadowVisible: false
27+
headerShadowVisible: false,
28+
headerShown: true
2829
}}
2930
></Stack.Screen>
3031
</Stack.Navigator>

‎package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"react": "17.0.1",
1919
"react-dom": "17.0.1",
2020
"react-native": "0.64.3",
21-
"react-native-gesture-handler": "^2.3.1",
21+
"react-native-gesture-handler": "~2.1.0",
2222
"react-native-reanimated": "^2.4.1",
2323
"react-native-safe-area-context": "^4.1.2",
2424
"react-native-screens": "^3.13.1",

‎yarn.lock

+5-5
Original file line numberDiff line numberDiff line change
@@ -4531,10 +4531,10 @@ react-native-codegen@^0.0.6:
45314531
jscodeshift "^0.11.0"
45324532
nullthrows "^1.1.1"
45334533

4534-
react-native-gesture-handler@^2.3.1:
4535-
version "2.3.1"
4536-
resolved "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.3.1.tgz"
4537-
integrity sha512-Yq7cov7BMX9CTZ9oEIc6iX/XWgU5i88zSky7Vaq7deRQTq2s1REH4Idh0APEFLdHFWN/NKZMkHBRT7vaQAgvqg==
4534+
react-native-gesture-handler@~2.1.0:
4535+
version "2.1.3"
4536+
resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-2.1.3.tgz#b96f1e61932d5062cb1023259e1649d65f78338b"
4537+
integrity sha512-y5W2MVB+J6vjIT/mUidDv0BqVRbWXn0cP7R2o6fsSYsHh9M0btT979+bCI7nPuhmRHjkhg5xCm4HNMIH0IQO4w==
45384538
dependencies:
45394539
"@egjs/hammerjs" "^2.0.17"
45404540
hoist-non-react-statics "^3.3.0"
@@ -4544,7 +4544,7 @@ react-native-gesture-handler@^2.3.1:
45444544

45454545
react-native-reanimated@^2.4.1:
45464546
version "2.4.1"
4547-
resolved "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-2.4.1.tgz"
4547+
resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-2.4.1.tgz#4e33876fba525ce60ac13ab3c81fc3a9f8b132fe"
45484548
integrity sha512-kvf7ylGlwa5hxMQ+wpPFjQrI2c6eexf53/xRo+dvXBNefGmSYaYR5sFtD0XMMzIPQlkCB9tJ0Pu9+2WCQUY7Cg==
45494549
dependencies:
45504550
"@babel/plugin-transform-object-assign" "^7.10.4"

0 commit comments

Comments
 (0)
Please sign in to comment.