Skip to content

Commit 4a0d6b0

Browse files
authored
feat: picture upload (#124)
* feat: upload profile picture --------- Signed-off-by: Iuri Pereira <689440+iuricmp@users.noreply.github.com>
1 parent 3aca6a5 commit 4a0d6b0

17 files changed

+790
-592
lines changed

mobile/app.json

+7-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,13 @@
4444
"experiments": {
4545
"tsconfigPaths": true
4646
},
47-
"plugins": ["expo-router"],
47+
"plugins": ["expo-router", [
48+
"expo-image-picker",
49+
{
50+
"photosPermission": "The app accesses your photos to upload an avatar.",
51+
"cameraPermission": "The app accesses your camera to create an avatar."
52+
}
53+
]],
4854
"extra": {
4955
"router": {
5056
"origin": false

mobile/app/home/profile.tsx

+36-30
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Alert, StyleSheet, View } from "react-native";
1+
import { Alert, ScrollView, StyleSheet, View } from "react-native";
22
import { router, useNavigation } from "expo-router";
33
import { useEffect, useState } from "react";
44
import { KeyInfo, useGnoNativeContext } from "@gnolang/gnonative";
@@ -11,6 +11,7 @@ import Text from "@gno/components/text";
1111
import { useSearch } from "@gno/hooks/use-search";
1212
import { useNotificationContext } from "@gno/provider/notification-provider";
1313
import { onboarding } from "redux/features/signupSlice";
14+
import AvatarPicker from "@gno/components/avatar/avatar-picker";
1415
import { ProgressViewModal } from "@gno/components/view/progress";
1516

1617
export default function Page() {
@@ -103,35 +104,40 @@ export default function Page() {
103104
<>
104105
<Layout.Container>
105106
<Layout.Body>
106-
<>
107-
<AccountBalance activeAccount={activeAccount} />
108-
<Text.Subheadline>Chain ID:</Text.Subheadline>
109-
<Text.Body>{chainID}</Text.Body>
110-
<Text.Subheadline>Remote:</Text.Subheadline>
111-
<Text.Body>{remote}</Text.Body>
112-
<Text.Subheadline>Followers:</Text.Subheadline>
113-
<Text.Body>{followersCount.n_followers}</Text.Body>
114-
<Text.Subheadline>Following:</Text.Subheadline>
115-
<Text.Body>{followersCount.n_following}</Text.Body>
116-
<View></View>
117-
</>
118-
<Layout.Footer>
119-
<ProgressViewModal visible={modalVisible} onRequestClose={() => setModalVisible(false)} />
120-
<Button.TouchableOpacity title="Logs" onPress={() => setModalVisible(true)} variant="primary" />
121-
<Button.TouchableOpacity title="Onboard the current user" onPress={onboard} variant="primary" />
122-
<Button.TouchableOpacity
123-
title="Register to the notification service"
124-
onPress={onPressNotification}
125-
variant="primary"
126-
/>
127-
<Button.TouchableOpacity title="Logout" onPress={onPressLogout} style={styles.logout} variant="primary-red" />
128-
<Button.TouchableOpacity
129-
title="Remove Account"
130-
onPress={onRemoveAccount}
131-
style={styles.logout}
132-
variant="primary-red"
133-
/>
134-
</Layout.Footer>
107+
<ScrollView >
108+
<View style={{ paddingBottom: 20 }}>
109+
<AvatarPicker />
110+
</View>
111+
<>
112+
<AccountBalance activeAccount={activeAccount} />
113+
<Text.Subheadline>Chain ID:</Text.Subheadline>
114+
<Text.Body>{chainID}</Text.Body>
115+
<Text.Subheadline>Remote:</Text.Subheadline>
116+
<Text.Body>{remote}</Text.Body>
117+
<Text.Subheadline>Followers:</Text.Subheadline>
118+
<Text.Body>{followersCount.n_followers}</Text.Body>
119+
<Text.Subheadline>Following:</Text.Subheadline>
120+
<Text.Body>{followersCount.n_following}</Text.Body>
121+
<View></View>
122+
</>
123+
<Layout.Footer>
124+
<ProgressViewModal visible={modalVisible} onRequestClose={() => setModalVisible(false)} />
125+
<Button.TouchableOpacity title="Logs" onPress={() => setModalVisible(true)} variant="primary" />
126+
<Button.TouchableOpacity title="Onboard the current user" onPress={onboard} variant="primary" />
127+
<Button.TouchableOpacity
128+
title="Register to the notification service"
129+
onPress={onPressNotification}
130+
variant="primary"
131+
/>
132+
<Button.TouchableOpacity title="Logout" onPress={onPressLogout} style={styles.logout} variant="primary-red" />
133+
<Button.TouchableOpacity
134+
title="Remove Account"
135+
onPress={onRemoveAccount}
136+
style={styles.logout}
137+
variant="primary-red"
138+
/>
139+
</Layout.Footer>
140+
</ScrollView>
135141
</Layout.Body>
136142
</Layout.Container>
137143
<LoadingModal visible={loading} />
+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import React, { useState } from 'react';
2+
import { TouchableOpacity } from 'react-native';
3+
import * as ImagePicker from 'expo-image-picker';
4+
import { useGnoNativeContext } from '@gnolang/gnonative';
5+
import { compressImage } from '@gno/utils/file-utils';
6+
import { reloadAvatar, saveAvatar, selectAccount, selectAvatar, useAppDispatch, useAppSelector } from "@gno/redux";
7+
import Avatar from './avatar';
8+
9+
const AvatarPicker: React.FC = () => {
10+
const [base64Image, setBase64Image] = useState<string | null>(null);
11+
12+
const { gnonative } = useGnoNativeContext();
13+
14+
const account = useAppSelector(selectAccount);
15+
const avatarBase64 = useAppSelector(selectAvatar);
16+
17+
const dispatch = useAppDispatch();
18+
19+
const pickImage = async () => {
20+
let result = await ImagePicker.launchImageLibraryAsync({
21+
mediaTypes: ImagePicker.MediaTypeOptions.Images,
22+
allowsEditing: true,
23+
aspect: [4, 3],
24+
quality: 0.5, // compress image for smaller size
25+
});
26+
27+
if (!result.canceled) {
28+
29+
const imagePath = result.assets[0].uri;
30+
const mimeType = result.assets[0].mimeType;
31+
32+
const imageCompressed = await compressImage(imagePath)
33+
if (!imageCompressed || !mimeType || !imageCompressed.base64) {
34+
console.log("Error compressing image or missing data");
35+
return;
36+
}
37+
await dispatch(saveAvatar({ mimeType, base64: imageCompressed.base64 })).unwrap();
38+
39+
await dispatch(reloadAvatar()).unwrap();
40+
}
41+
}
42+
43+
return (
44+
<TouchableOpacity onPress={pickImage} >
45+
{base64Image ? <Avatar uri={base64Image} /> : null}
46+
{avatarBase64 ? <Avatar uri={avatarBase64} /> : null}
47+
</TouchableOpacity>
48+
)
49+
}
50+
51+
export default AvatarPicker

mobile/components/avatar/avatar.tsx

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { View, Image, StyleSheet, StyleProp, ImageStyle } from "react-native";
2+
3+
interface Props {
4+
uri: string;
5+
style?: StyleProp<ImageStyle>;
6+
}
7+
8+
const Avatar: React.FC<Props> = ({ uri, style }) => {
9+
return (
10+
<View>
11+
<Image source={{ uri }} style={[styles.image, style]} />
12+
</View>
13+
);
14+
};
15+
16+
const SIZE = 80;
17+
18+
const styles = StyleSheet.create({
19+
image: {
20+
width: SIZE,
21+
height: SIZE,
22+
borderRadius: SIZE,
23+
},
24+
});
25+
26+
export default Avatar;

mobile/components/avatar/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './avatar';

mobile/components/feed/post-row.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export function PostRow({ post, onPress = func, onGnod = func, showFooter = true
4343
<Pressable onPress={() => onPress(post)} style={styles.container}>
4444
<RepostLabel post={post} />
4545
<View style={styles.body}>
46-
<Image source={{ uri: post.user.image }} style={styles.image} />
46+
<Image source={{ uri: post.user.avatar }} style={styles.image} />
4747
<View style={styles.content}>
4848
<View style={{ flexDirection: "row", alignItems: "center" }}>
4949
<Pressable style={{ flexDirection: "row", alignItems: "center" }} onPress={() => nativgateToAccount(post?.user.name)}>

mobile/components/feed/repost-row.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export function RepostRow({ post, onPress = func, showFooter = true }: FeedProps
4040
<View style={styles.body}>
4141
<View style={styles.content}>
4242
<View style={{ flexDirection: "row", alignItems: "center" }}>
43-
<Image source={{ uri: post.user.image }} style={styles.image} />
43+
<Image source={{ uri: post.user.avatar }} style={styles.image} />
4444
<Pressable style={{ flexDirection: "row", alignItems: "center", paddingLeft: 8 }} onPress={onPressName}>
4545
<Text.Body style={[{ fontWeight: "bold", fontSize: 16, paddingRight: 8 }]}>@{post.user.name}</Text.Body>
4646
<TimeStampLabel timestamp={post.date} />

mobile/components/index.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from "./view";
2+
export * from "./avatar";

0 commit comments

Comments
 (0)