@boneframework/native-components 1.0.24 → 1.0.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/Image.tsx +3 -11
- package/hooks/useCache.js +7 -0
- package/hooks/useNavigationTheme.js +19 -0
- package/package.json +3 -2
- package/screens/EditProfileScreen.tsx +249 -0
package/components/Image.tsx
CHANGED
|
@@ -4,16 +4,12 @@ import {Image as ExpoImage} from "expo-image";
|
|
|
4
4
|
import useAuth from "../hooks/useAuth";
|
|
5
5
|
import settings from '../../../../config/api';
|
|
6
6
|
|
|
7
|
-
function Image({style, uri, onPress, handleError, source, ...rest}) {
|
|
7
|
+
function Image({style, uri, onPress, handleError = error => console.error, source, ...rest}) {
|
|
8
8
|
const {user} = useAuth();
|
|
9
9
|
|
|
10
10
|
const tryAgain = async error => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
setTimeout(() => {
|
|
15
|
-
|
|
16
|
-
}, 1000);
|
|
11
|
+
handleError();
|
|
12
|
+
setTimeout(() => {}, 1000);
|
|
17
13
|
};
|
|
18
14
|
let imageSource;
|
|
19
15
|
let protectedUri = false;
|
|
@@ -49,10 +45,6 @@ function Image({style, uri, onPress, handleError, source, ...rest}) {
|
|
|
49
45
|
}
|
|
50
46
|
}
|
|
51
47
|
|
|
52
|
-
const styles = StyleSheet.create({
|
|
53
|
-
container: {}
|
|
54
|
-
})
|
|
55
|
-
|
|
56
48
|
export default Image;
|
|
57
49
|
|
|
58
50
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {DarkTheme, DefaultTheme} from "@react-navigation/native";
|
|
2
|
+
|
|
3
|
+
import useStyle from "./useStyle";
|
|
4
|
+
import colors from "../../../../config/colors";
|
|
5
|
+
|
|
6
|
+
export default useNavigationTheme = () => {
|
|
7
|
+
const style = useStyle();
|
|
8
|
+
const theme = style.dark ? DarkTheme : DefaultTheme
|
|
9
|
+
return {
|
|
10
|
+
...theme,
|
|
11
|
+
colors: {
|
|
12
|
+
...theme.colors,
|
|
13
|
+
primary: style.text.color,
|
|
14
|
+
background: style.backgroundColor,
|
|
15
|
+
card: style.backgroundColor
|
|
16
|
+
},
|
|
17
|
+
dark: style.dark
|
|
18
|
+
}
|
|
19
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@boneframework/native-components",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.26",
|
|
4
4
|
"description": "Expo Components for Bone Framework",
|
|
5
5
|
"main": "src/Bone.ts",
|
|
6
6
|
"scripts": {
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
"lottie-react-native": "^6.7.2",
|
|
47
47
|
"react": "18.2.0",
|
|
48
48
|
"react-native-progress": "^5.0.1",
|
|
49
|
-
"yup": "^1.4.0"
|
|
49
|
+
"yup": "^1.4.0",
|
|
50
|
+
"expo-location": "~17.0.1"
|
|
50
51
|
}
|
|
51
52
|
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import React, {useEffect, useState} from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Alert,
|
|
4
|
+
Button,
|
|
5
|
+
KeyboardAvoidingView,
|
|
6
|
+
Platform,
|
|
7
|
+
ScrollView,
|
|
8
|
+
StyleSheet,
|
|
9
|
+
TouchableWithoutFeedback,
|
|
10
|
+
View
|
|
11
|
+
} from "react-native"
|
|
12
|
+
import {Field} from "formik";
|
|
13
|
+
import * as FileSystem from 'expo-file-system';
|
|
14
|
+
import {useAsyncStorage} from "@react-native-async-storage/async-storage";
|
|
15
|
+
import * as Yup from "yup";
|
|
16
|
+
|
|
17
|
+
import colors from '../../../../config/colors';
|
|
18
|
+
import ImageInput from '../components/ImageInput';
|
|
19
|
+
import Image from '../components/Image';
|
|
20
|
+
import Screen from '../components/Screen';
|
|
21
|
+
import Text from '../components/Text';
|
|
22
|
+
import {Form, FormDateTimePicker, FormField, SubmitButton} from "../components/forms";
|
|
23
|
+
import ActivityIndicator from "../components/ActivityIndicator";
|
|
24
|
+
import UploadScreen from "./UploadScreen";
|
|
25
|
+
import useApi from "../hooks/useApi";
|
|
26
|
+
import userApi from "../api/users";
|
|
27
|
+
import useAuth from "../hooks/useAuth";
|
|
28
|
+
import useCache from "../hooks/useCache";
|
|
29
|
+
import useCamera from "../hooks/useCamera";
|
|
30
|
+
import usePhotos from "../hooks/usePhotos";
|
|
31
|
+
import useStyle from "../hooks/useStyle";
|
|
32
|
+
|
|
33
|
+
const validationSchema = Yup.object().shape({
|
|
34
|
+
firstname: Yup.string().required().min(2).max(60).label('First name'),
|
|
35
|
+
middlename: Yup.string().min(1).max(60).label('Middle name'),
|
|
36
|
+
lastname: Yup.string().required().min(1).max(60).label('Last name'),
|
|
37
|
+
aka: Yup.string().min(1).max(50).label('Display name'),
|
|
38
|
+
dob: Yup.date().required().label('Date of birth'),
|
|
39
|
+
birthplace: Yup.string().required().label('Birthplace'),
|
|
40
|
+
country: Yup.string().required().min(2).max(3).label('Country')
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
function EditProfileScreen(props) {
|
|
44
|
+
const [progressVisible, setProgressVisible] = useState(false);
|
|
45
|
+
const [progress, setProgress] = useState(0);
|
|
46
|
+
const [profileImage, setProfileImage] = useState(null);
|
|
47
|
+
const [profileBackground, setProfileBackground] = useState(null);
|
|
48
|
+
const style = useStyle();
|
|
49
|
+
|
|
50
|
+
const { updateUser, user} = useAuth();
|
|
51
|
+
const updateProfileApi = useApi(userApi.updateProfile);
|
|
52
|
+
const userImageUploadApi = useApi(userApi.uploadUserImage);
|
|
53
|
+
const userImageApi = useApi(userApi.userImage);
|
|
54
|
+
const userBackgroundImageUploadApi = useApi(userApi.uploadUserBackgroundImage);
|
|
55
|
+
const userBackgroundImageApi = useApi(userApi.userBackgroundImage);
|
|
56
|
+
const camera = useCamera();
|
|
57
|
+
const photos = usePhotos();
|
|
58
|
+
const person = user.person;
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if(!profileImage) {
|
|
62
|
+
setProfileImage(user.person.image);
|
|
63
|
+
}
|
|
64
|
+
if(!profileImage) {
|
|
65
|
+
setProfileBackground(user.person.backgroundImage);
|
|
66
|
+
}
|
|
67
|
+
}, [])
|
|
68
|
+
|
|
69
|
+
const handleSubmit = values => {
|
|
70
|
+
updateProfileApi.request(values)
|
|
71
|
+
.then(data => {
|
|
72
|
+
user.person = data.data;
|
|
73
|
+
updateUser(user);
|
|
74
|
+
|
|
75
|
+
})
|
|
76
|
+
.catch(console.error)
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const cameraOrPhotos = phptoOrBackground => {
|
|
80
|
+
const title = phptoOrBackground === 'image' ? 'Edit Profile Photo' : 'Edit Profile Background'
|
|
81
|
+
Alert.alert(
|
|
82
|
+
title,
|
|
83
|
+
null,
|
|
84
|
+
[
|
|
85
|
+
{ text: 'Photos', onPress: () => selectImage('photos', phptoOrBackground) },
|
|
86
|
+
{ text: 'Camera', onPress: () => selectImage('camera', phptoOrBackground) },
|
|
87
|
+
{ text: 'Cancel', style: 'cancel' }
|
|
88
|
+
]
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const selectImage = async (pickerType, phptoOrBackground) => {
|
|
93
|
+
try {
|
|
94
|
+
if (pickerType === 'camera') {
|
|
95
|
+
const result = await camera.takePhoto({
|
|
96
|
+
allowsEditing: true,
|
|
97
|
+
base64: true,
|
|
98
|
+
quality: 0.5
|
|
99
|
+
});
|
|
100
|
+
if (!result.canceled) {
|
|
101
|
+
handleSelection(result.assets[0].uri, phptoOrBackground);
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
const result = await photos.selectImage({
|
|
105
|
+
quality: 0.5,
|
|
106
|
+
base64: true
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
if (!result.canceled) {
|
|
110
|
+
handleSelection(result.assets[0].uri, phptoOrBackground);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
} catch (error) {
|
|
115
|
+
Alert.alert('Image error', 'Error reading image');
|
|
116
|
+
console.error(error);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const handleSelection = (uri, phptoOrBackground) => {
|
|
121
|
+
if (phptoOrBackground === 'background-image') {
|
|
122
|
+
uploadImage(uri, userBackgroundImageUploadApi, 'background');
|
|
123
|
+
setProfileBackground(uri);
|
|
124
|
+
user.person.backgroundImage = uri;
|
|
125
|
+
} else {
|
|
126
|
+
uploadImage(uri, userImageUploadApi, 'avatar');
|
|
127
|
+
setProfileImage(uri);
|
|
128
|
+
user.person.image = uri;
|
|
129
|
+
}
|
|
130
|
+
updateUser(user);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const uploadImage = async (url, api, fieldName) => {
|
|
134
|
+
let formData = new FormData();
|
|
135
|
+
let filename = url.split('/').pop();
|
|
136
|
+
let match = /\.(\w+)$/.exec(filename);
|
|
137
|
+
let type = match ? `image/${match[1]}` : `image`;
|
|
138
|
+
formData.append(fieldName, { uri: url, name: filename, type });
|
|
139
|
+
const response = await api.request(formData).catch(e => {
|
|
140
|
+
console.error('upload error', e);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const styles = StyleSheet.create({
|
|
145
|
+
container: {
|
|
146
|
+
backgroundColor: style.backgroundColor
|
|
147
|
+
},
|
|
148
|
+
wallpaper: {
|
|
149
|
+
height: 240,
|
|
150
|
+
width: '100%',
|
|
151
|
+
backgroundColor: colors.secondary
|
|
152
|
+
},
|
|
153
|
+
image: {
|
|
154
|
+
width: 150,
|
|
155
|
+
height: 150,
|
|
156
|
+
borderRadius: 75,
|
|
157
|
+
marginTop: -75,
|
|
158
|
+
borderColor: colors.white,
|
|
159
|
+
borderWidth: 7,
|
|
160
|
+
backgroundColor: colors.light,
|
|
161
|
+
},
|
|
162
|
+
fullWidth: {
|
|
163
|
+
width: '100%'
|
|
164
|
+
},
|
|
165
|
+
centred: {
|
|
166
|
+
width: '100%',
|
|
167
|
+
},
|
|
168
|
+
imageContainer: {
|
|
169
|
+
alignItems: "center",
|
|
170
|
+
width: '100%',
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
return (
|
|
175
|
+
<>
|
|
176
|
+
<KeyboardAvoidingView
|
|
177
|
+
behavior="position"
|
|
178
|
+
keyboardVerticalOffset={Platform.OS === "ios" ? 0 : 140}
|
|
179
|
+
style={styles.container}
|
|
180
|
+
>
|
|
181
|
+
<ActivityIndicator visible={updateProfileApi.loading || userImageApi.loading || userImageUploadApi.loading || userBackgroundImageApi.loading || userBackgroundImageUploadApi.loading } type={'overlay'}/>
|
|
182
|
+
<UploadScreen onDone={() => setProgressVisible(false)} progress={progress} visible={progressVisible}/>
|
|
183
|
+
<ScrollView contentContainerStyle={styles.centred}>
|
|
184
|
+
<TouchableWithoutFeedback onPress={() => {cameraOrPhotos('background-image')}} >
|
|
185
|
+
<View style={styles.wallpaper}>
|
|
186
|
+
<Image style={styles.wallpaper} uri={profileBackground} />
|
|
187
|
+
</View>
|
|
188
|
+
</TouchableWithoutFeedback>
|
|
189
|
+
|
|
190
|
+
<TouchableWithoutFeedback onPress={() => {cameraOrPhotos('image')}} >
|
|
191
|
+
<View style={styles.imageContainer}>
|
|
192
|
+
<Image style={styles.image} uri={profileImage} />
|
|
193
|
+
</View>
|
|
194
|
+
</TouchableWithoutFeedback>
|
|
195
|
+
|
|
196
|
+
<Form
|
|
197
|
+
initialValues={{
|
|
198
|
+
firstname: person.firstname,
|
|
199
|
+
middlename: person.middlename,
|
|
200
|
+
lastname: person.lastname,
|
|
201
|
+
aka: person.aka,
|
|
202
|
+
dob: person.dob ? new Date(person.dob) : new Date(),
|
|
203
|
+
birthplace: person.birthplace,
|
|
204
|
+
image: person.image,
|
|
205
|
+
country: person.country?.iso,
|
|
206
|
+
}}
|
|
207
|
+
onSubmit={handleSubmit}
|
|
208
|
+
validationSchema={validationSchema}
|
|
209
|
+
>
|
|
210
|
+
<FormField
|
|
211
|
+
name="firstname"
|
|
212
|
+
placeholder="First name"
|
|
213
|
+
maxLength={60}
|
|
214
|
+
/>
|
|
215
|
+
<FormField
|
|
216
|
+
name="middlename"
|
|
217
|
+
placeholder="Middle name"
|
|
218
|
+
maxLength={60}
|
|
219
|
+
/>
|
|
220
|
+
<FormField
|
|
221
|
+
name="lastname"
|
|
222
|
+
placeholder="Last name"
|
|
223
|
+
maxLength={60}
|
|
224
|
+
/>
|
|
225
|
+
<FormField
|
|
226
|
+
name="aka"
|
|
227
|
+
placeholder="Display name"
|
|
228
|
+
maxLength={50}
|
|
229
|
+
/>
|
|
230
|
+
<FormDateTimePicker name={'dob'}/>
|
|
231
|
+
<FormField
|
|
232
|
+
name="birthplace"
|
|
233
|
+
placeholder="Birthplace"
|
|
234
|
+
maxLength={50}
|
|
235
|
+
/>
|
|
236
|
+
<FormField
|
|
237
|
+
name="country"
|
|
238
|
+
placeholder="Country"
|
|
239
|
+
maxLength={3}
|
|
240
|
+
/>
|
|
241
|
+
<SubmitButton color="primary" title="Update profile"/>
|
|
242
|
+
</Form>
|
|
243
|
+
</ScrollView>
|
|
244
|
+
</KeyboardAvoidingView>
|
|
245
|
+
</>
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export default EditProfileScreen;
|