Skip to content

Commit bb45455

Browse files
committed
Init to win it.
1 parent e14c05a commit bb45455

File tree

11 files changed

+2562
-9
lines changed

11 files changed

+2562
-9
lines changed

app.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@
55
"bundler": "metro"
66
},
77
"name": "myCloudApp",
8-
"slug": "myCloudApp"
8+
"slug": "myCloudApp",
9+
"plugins": [
10+
[
11+
"expo-image-picker",
12+
{
13+
"photosPermission": "Selecct and upload images.",
14+
"cameraPermission": "Allow $(PRODUCT_NAME) to access your camera."
15+
}
16+
]
17+
]
918
}
1019
}

app/(auth)/_layout.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { Stack } from 'expo-router';
2+
3+
const StackLayout = () => {
4+
return (
5+
<Stack
6+
screenOptions={{
7+
headerStyle: {
8+
backgroundColor: '#0f0f0f',
9+
},
10+
headerTintColor: '#fff',
11+
}}>
12+
<Stack.Screen
13+
name="list"
14+
options={{
15+
headerTitle: 'My Files',
16+
}}></Stack.Screen>
17+
</Stack>
18+
);
19+
};
20+
21+
export default StackLayout;

app/(auth)/list.tsx

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
import { View, Text, StyleSheet, TouchableOpacity, Image, FlatList } from 'react-native';
2+
import React, { useEffect, useState } from 'react';
3+
import { Ionicons } from '@expo/vector-icons';
4+
import * as ImagePicker from 'expo-image-picker';
5+
import { useAuth } from '../../provider/AuthProvider';
6+
import Uppy from '@uppy/core';
7+
import Tus from '@uppy/tus';
8+
import { TusFileReader } from '../../helper/tusFileReader';
9+
import * as FileSystem from 'expo-file-system';
10+
import { decode } from 'base64-arraybuffer';
11+
12+
const SUPABASE_STORAGE_URL = 'https://kolrncrjvromhaivenfp.supabase.co/storage/v1/';
13+
14+
const list = () => {
15+
const { session } = useAuth();
16+
const [uppy, setUppy] = useState<Uppy>(null);
17+
const [progress, setProgress] = useState(0);
18+
const [uploading, setUploading] = useState(false);
19+
const [files, setFiles] = useState([]);
20+
21+
useEffect(() => {
22+
if (!session) return;
23+
24+
const uppyInstance = new Uppy({ debug: true, autoProceed: false })
25+
.use(Tus, {
26+
endpoint: `${SUPABASE_STORAGE_URL}object/files/${session.user.id}`,
27+
// fileReader: new TusFileReader(), <- this is not working
28+
// retryDelays: [0, 3000, 5000, 10000, 20000],
29+
// uploadDataDuringCreation: true,
30+
headers: {
31+
authorization: `Bearer ${session.access_token}`,
32+
'x-upsert': 'true',
33+
},
34+
chunkSize: 6 * 1024 * 1024,
35+
allowedMetaFields: ['bucketName', 'objectName', 'contentType', 'cacheControl'],
36+
})
37+
38+
.on('file-added', (file) => {
39+
file.meta = {
40+
...file.meta,
41+
bucketName: `files`,
42+
objectName: `${session.user.id}/${file.name}`,
43+
contentType: file.type,
44+
};
45+
console.log('file-added:', file);
46+
})
47+
.on('upload-progress', (file, progress) => {
48+
console.log('upload-progress:', progress);
49+
50+
setProgress(progress.bytesUploaded / progress.bytesTotal);
51+
})
52+
.on('complete', (result) => {
53+
console.log('complete:', result);
54+
setFiles([]);
55+
setUploading(false);
56+
})
57+
.on('progress', (progress) => {
58+
console.log('progress:', progress);
59+
})
60+
.on('error', (error) => {
61+
console.log('error:', JSON.stringify(error));
62+
});
63+
64+
setUppy(uppyInstance);
65+
}, [session]);
66+
67+
const onSelectImage = async () => {
68+
const options: ImagePicker.ImagePickerOptions = {
69+
mediaTypes: ImagePicker.MediaTypeOptions.Images,
70+
allowsEditing: true,
71+
};
72+
73+
const result = await ImagePicker.launchImageLibraryAsync(options);
74+
75+
// Save image if not cancelled
76+
if (!result.canceled) {
77+
// UPPY APPROACH
78+
const img = result.assets[0];
79+
const fetchResponse = await fetch(img.uri);
80+
const blob = await fetchResponse.blob();
81+
console.log('🚀 ~ file: list.tsx:83 ~ onSelectImage ~ blob:', blob);
82+
const contentType = img.type === 'image' ? 'image/png' : 'video/mp4';
83+
const fileName = `${new Date().getTime()}.${img.type === 'image' ? 'png' : 'mp4'}`;
84+
console.log('🚀 ~ file: list.tsx:87 ~ onSelectImage ~ fileName:', fileName);
85+
86+
// Add file to uppy with blob data
87+
uppy.addFile({
88+
id: fileName,
89+
name: fileName,
90+
type: blob.type,
91+
data: blob,
92+
size: blob.size,
93+
});
94+
95+
// Add image to local files array
96+
setFiles((old) => [...old, { uri: img.uri, fileName, contentType }]);
97+
98+
// SUPABASE STANDARD APPROACH
99+
// THIS WORKS
100+
// const img = result.assets[0];
101+
// const base64 = await FileSystem.readAsStringAsync(img.uri, { encoding: 'base64' });
102+
// const filePath = `${user.id}/${new Date().getTime()}.${img.type === 'image' ? 'png' : 'mp4'}`;
103+
// const contentType = img.type === 'image' ? 'image/png' : 'video/mp4';
104+
// let { error, data } = await supabase.storage.from('files').upload(filePath, decode(base64), { contentType });
105+
// console.log('error', error);
106+
// console.log('data', data);
107+
}
108+
};
109+
110+
const onUpload = async () => {
111+
uppy.upload();
112+
setUploading(true);
113+
};
114+
115+
const onPause = async () => {
116+
uppy.pauseAll();
117+
setUploading(false);
118+
};
119+
120+
const onResume = async () => {
121+
uppy.resumeAll();
122+
setUploading(true);
123+
};
124+
125+
// Render image list item
126+
const renderItem = ({ item }: { item: any }) => {
127+
return (
128+
<View style={{ flexDirection: 'row', margin: 1, alignItems: 'center', gap: 5 }}>
129+
<Image style={{ width: 80, height: 80 }} source={{ uri: item.uri }} />
130+
<Text style={{ flex: 1, color: '#fff' }}>{item.fileName}</Text>
131+
</View>
132+
);
133+
};
134+
135+
return (
136+
<View style={styles.container}>
137+
{/* Progress bar */}
138+
{uploading && <View style={{ width: `${progress}%`, height: 4, backgroundColor: '#fff', marginBottom: 10, borderRadius: 2 }} />}
139+
140+
{/* Row with 3 buttons to start, pause and resume uploads */}
141+
<View style={{ flexDirection: 'row', justifyContent: 'space-evenly' }}>
142+
<TouchableOpacity onPress={onUpload} style={styles.button}>
143+
<Text style={{ color: '#fff' }}>Upload</Text>
144+
</TouchableOpacity>
145+
<TouchableOpacity onPress={onPause} style={styles.button}>
146+
<Text style={{ color: '#fff' }}>Pause</Text>
147+
</TouchableOpacity>
148+
<TouchableOpacity onPress={onResume} style={styles.button}>
149+
<Text style={{ color: '#fff' }}>Resume</Text>
150+
</TouchableOpacity>
151+
</View>
152+
153+
{/* List of images to upload */}
154+
<FlatList data={files} renderItem={renderItem} style={{ marginTop: 50 }} />
155+
156+
{/* FAB to add images */}
157+
<TouchableOpacity onPress={onSelectImage} style={styles.fab}>
158+
<Ionicons name="camera-outline" size={30} color={'#fff'} />
159+
</TouchableOpacity>
160+
</View>
161+
);
162+
};
163+
164+
const styles = StyleSheet.create({
165+
container: {
166+
flex: 1,
167+
padding: 20,
168+
backgroundColor: '#151515',
169+
},
170+
fab: {
171+
borderWidth: 1,
172+
alignItems: 'center',
173+
justifyContent: 'center',
174+
width: 70,
175+
position: 'absolute',
176+
bottom: 40,
177+
right: 30,
178+
height: 70,
179+
backgroundColor: '#2b825b',
180+
borderRadius: 100,
181+
},
182+
button: {
183+
borderWidth: 2,
184+
borderColor: '#2b825b',
185+
padding: 12,
186+
borderRadius: 4,
187+
},
188+
});
189+
190+
export default list;

app/_layout.tsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { Slot, useRouter, useSegments } from 'expo-router';
2+
import { useEffect } from 'react';
3+
import { AuthProvider, useAuth } from '../provider/AuthProvider';
4+
5+
const InitialLayout = () => {
6+
const { session, initialized } = useAuth();
7+
const segments = useSegments();
8+
const router = useRouter();
9+
10+
useEffect(() => {
11+
if (!initialized) return;
12+
13+
const inAuthGroup = segments[0] === '(auth)';
14+
15+
// console.log('CHANGED: ', session);
16+
17+
if (session && !inAuthGroup) {
18+
console.log('REDIRECTING TO LIST');
19+
20+
router.replace('/list');
21+
} else if (!session) {
22+
console.log('REDIRECTING TO LOGIN');
23+
24+
router.replace('/');
25+
}
26+
}, [session, initialized]);
27+
28+
return <Slot />;
29+
};
30+
31+
const RootLayout = () => {
32+
return (
33+
<AuthProvider>
34+
<InitialLayout />
35+
</AuthProvider>
36+
);
37+
};
38+
39+
export default RootLayout;

app/index.tsx

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { Alert, View, Button, TextInput, StyleSheet, Text, TouchableOpacity } from 'react-native';
2+
import { useState } from 'react';
3+
import React from 'react';
4+
import Spinner from 'react-native-loading-spinner-overlay';
5+
import { supabase } from '../config/initSupabase';
6+
7+
const Login = () => {
8+
const [email, setEmail] = useState('[email protected]');
9+
const [password, setPassword] = useState('123456');
10+
const [loading, setLoading] = useState(false);
11+
12+
const onSignInPress = async () => {
13+
setLoading(true);
14+
15+
const { error } = await supabase.auth.signInWithPassword({
16+
email,
17+
password,
18+
});
19+
20+
if (error) Alert.alert(error.message);
21+
setLoading(false);
22+
};
23+
24+
const onSignUpPress = async () => {
25+
setLoading(true);
26+
const { error } = await supabase.auth.signUp({
27+
email: email,
28+
password: password,
29+
});
30+
31+
if (error) Alert.alert(error.message);
32+
setLoading(false);
33+
};
34+
35+
return (
36+
<View style={styles.container}>
37+
<Spinner visible={loading} />
38+
39+
<Text style={styles.header}>My Cloud</Text>
40+
41+
<TextInput autoCapitalize="none" placeholder="[email protected]" value={email} onChangeText={setEmail} style={styles.inputField} />
42+
<TextInput placeholder="password" value={password} onChangeText={setPassword} secureTextEntry style={styles.inputField} />
43+
44+
<TouchableOpacity onPress={onSignInPress} style={styles.button}>
45+
<Text style={{ color: '#fff' }}>Sign in</Text>
46+
</TouchableOpacity>
47+
<Button onPress={onSignUpPress} title="Create Account" color={'#fff'}></Button>
48+
</View>
49+
);
50+
};
51+
52+
const styles = StyleSheet.create({
53+
container: {
54+
flex: 1,
55+
paddingTop: 200,
56+
padding: 20,
57+
backgroundColor: '#151515',
58+
},
59+
header: {
60+
fontSize: 30,
61+
textAlign: 'center',
62+
margin: 50,
63+
color: '#fff',
64+
},
65+
inputField: {
66+
marginVertical: 4,
67+
height: 50,
68+
borderWidth: 1,
69+
borderColor: '#2b825b',
70+
borderRadius: 4,
71+
padding: 10,
72+
color: '#fff',
73+
backgroundColor: '#363636',
74+
},
75+
button: {
76+
marginTop: 10,
77+
alignItems: 'center',
78+
backgroundColor: '#2b825b',
79+
padding: 12,
80+
borderRadius: 4,
81+
},
82+
});
83+
84+
export default Login;

config/initSupabase.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import * as SecureStore from 'expo-secure-store';
2+
import 'react-native-url-polyfill/auto';
3+
4+
import { createClient } from '@supabase/supabase-js';
5+
6+
const ExpoSecureStoreAdapter = {
7+
getItem: (key: string) => {
8+
return SecureStore.getItemAsync(key);
9+
},
10+
setItem: (key: string, value: string) => {
11+
SecureStore.setItemAsync(key, value);
12+
},
13+
removeItem: (key: string) => {
14+
SecureStore.deleteItemAsync(key);
15+
},
16+
};
17+
18+
export const supabase = createClient(
19+
'https://kolrncrjvromhaivenfp.supabase.co',
20+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtvbHJuY3JqdnJvbWhhaXZlbmZwIiwicm9sZSI6ImFub24iLCJpYXQiOjE2ODg0Nzc1MTAsImV4cCI6MjAwNDA1MzUxMH0.QlBUWRQByC9NibQFivFDSeT2FPkq5r_Owyj0MQSQbLQ',
21+
{
22+
auth: {
23+
storage: ExpoSecureStoreAdapter as any,
24+
detectSessionInUrl: false,
25+
},
26+
}
27+
);

0 commit comments

Comments
 (0)