+9
LICENSE.md
+9
LICENSE.md
···
1
+
MIT License
2
+
3
+
Copyright (c) 2025 Ruben Grimm
4
+
5
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-35
app/(tabs)/_layout.tsx
-35
app/(tabs)/_layout.tsx
···
1
-
import { Tabs } from 'expo-router';
2
-
import React from 'react';
3
-
4
-
import { HapticTab } from '@/components/haptic-tab';
5
-
import { IconSymbol } from '@/components/ui/icon-symbol';
6
-
import { Colors } from '@/constants/theme';
7
-
import { useColorScheme } from '@/hooks/use-color-scheme';
8
-
9
-
export default function TabLayout() {
10
-
const colorScheme = useColorScheme();
11
-
12
-
return (
13
-
<Tabs
14
-
screenOptions={{
15
-
tabBarActiveTintColor: Colors[colorScheme ?? 'light'].tint,
16
-
headerShown: false,
17
-
tabBarButton: HapticTab,
18
-
}}>
19
-
<Tabs.Screen
20
-
name="index"
21
-
options={{
22
-
title: 'Home',
23
-
tabBarIcon: ({ color }) => <IconSymbol size={28} name="house.fill" color={color} />,
24
-
}}
25
-
/>
26
-
<Tabs.Screen
27
-
name="explore"
28
-
options={{
29
-
title: 'Explore',
30
-
tabBarIcon: ({ color }) => <IconSymbol size={28} name="paperplane.fill" color={color} />,
31
-
}}
32
-
/>
33
-
</Tabs>
34
-
);
35
-
}
-112
app/(tabs)/explore.tsx
-112
app/(tabs)/explore.tsx
···
1
-
import { Image } from 'expo-image';
2
-
import { Platform, StyleSheet } from 'react-native';
3
-
4
-
import { Collapsible } from '@/components/ui/collapsible';
5
-
import { ExternalLink } from '@/components/external-link';
6
-
import ParallaxScrollView from '@/components/parallax-scroll-view';
7
-
import { ThemedText } from '@/components/themed-text';
8
-
import { ThemedView } from '@/components/themed-view';
9
-
import { IconSymbol } from '@/components/ui/icon-symbol';
10
-
import { Fonts } from '@/constants/theme';
11
-
12
-
export default function TabTwoScreen() {
13
-
return (
14
-
<ParallaxScrollView
15
-
headerBackgroundColor={{ light: '#D0D0D0', dark: '#353636' }}
16
-
headerImage={
17
-
<IconSymbol
18
-
size={310}
19
-
color="#808080"
20
-
name="chevron.left.forwardslash.chevron.right"
21
-
style={styles.headerImage}
22
-
/>
23
-
}>
24
-
<ThemedView style={styles.titleContainer}>
25
-
<ThemedText
26
-
type="title"
27
-
style={{
28
-
fontFamily: Fonts.rounded,
29
-
}}>
30
-
Explore
31
-
</ThemedText>
32
-
</ThemedView>
33
-
<ThemedText>This app includes example code to help you get started.</ThemedText>
34
-
<Collapsible title="File-based routing">
35
-
<ThemedText>
36
-
This app has two screens:{' '}
37
-
<ThemedText type="defaultSemiBold">app/(tabs)/index.tsx</ThemedText> and{' '}
38
-
<ThemedText type="defaultSemiBold">app/(tabs)/explore.tsx</ThemedText>
39
-
</ThemedText>
40
-
<ThemedText>
41
-
The layout file in <ThemedText type="defaultSemiBold">app/(tabs)/_layout.tsx</ThemedText>{' '}
42
-
sets up the tab navigator.
43
-
</ThemedText>
44
-
<ExternalLink href="https://docs.expo.dev/router/introduction">
45
-
<ThemedText type="link">Learn more</ThemedText>
46
-
</ExternalLink>
47
-
</Collapsible>
48
-
<Collapsible title="Android, iOS, and web support">
49
-
<ThemedText>
50
-
You can open this project on Android, iOS, and the web. To open the web version, press{' '}
51
-
<ThemedText type="defaultSemiBold">w</ThemedText> in the terminal running this project.
52
-
</ThemedText>
53
-
</Collapsible>
54
-
<Collapsible title="Images">
55
-
<ThemedText>
56
-
For static images, you can use the <ThemedText type="defaultSemiBold">@2x</ThemedText> and{' '}
57
-
<ThemedText type="defaultSemiBold">@3x</ThemedText> suffixes to provide files for
58
-
different screen densities
59
-
</ThemedText>
60
-
<Image
61
-
source={require('@/assets/images/react-logo.png')}
62
-
style={{ width: 100, height: 100, alignSelf: 'center' }}
63
-
/>
64
-
<ExternalLink href="https://reactnative.dev/docs/images">
65
-
<ThemedText type="link">Learn more</ThemedText>
66
-
</ExternalLink>
67
-
</Collapsible>
68
-
<Collapsible title="Light and dark mode components">
69
-
<ThemedText>
70
-
This template has light and dark mode support. The{' '}
71
-
<ThemedText type="defaultSemiBold">useColorScheme()</ThemedText> hook lets you inspect
72
-
what the user's current color scheme is, and so you can adjust UI colors accordingly.
73
-
</ThemedText>
74
-
<ExternalLink href="https://docs.expo.dev/develop/user-interface/color-themes/">
75
-
<ThemedText type="link">Learn more</ThemedText>
76
-
</ExternalLink>
77
-
</Collapsible>
78
-
<Collapsible title="Animations">
79
-
<ThemedText>
80
-
This template includes an example of an animated component. The{' '}
81
-
<ThemedText type="defaultSemiBold">components/HelloWave.tsx</ThemedText> component uses
82
-
the powerful{' '}
83
-
<ThemedText type="defaultSemiBold" style={{ fontFamily: Fonts.mono }}>
84
-
react-native-reanimated
85
-
</ThemedText>{' '}
86
-
library to create a waving hand animation.
87
-
</ThemedText>
88
-
{Platform.select({
89
-
ios: (
90
-
<ThemedText>
91
-
The <ThemedText type="defaultSemiBold">components/ParallaxScrollView.tsx</ThemedText>{' '}
92
-
component provides a parallax effect for the header image.
93
-
</ThemedText>
94
-
),
95
-
})}
96
-
</Collapsible>
97
-
</ParallaxScrollView>
98
-
);
99
-
}
100
-
101
-
const styles = StyleSheet.create({
102
-
headerImage: {
103
-
color: '#808080',
104
-
bottom: -90,
105
-
left: -35,
106
-
position: 'absolute',
107
-
},
108
-
titleContainer: {
109
-
flexDirection: 'row',
110
-
gap: 8,
111
-
},
112
-
});
-98
app/(tabs)/index.tsx
-98
app/(tabs)/index.tsx
···
1
-
import { Image } from 'expo-image';
2
-
import { Platform, StyleSheet } from 'react-native';
3
-
4
-
import { HelloWave } from '@/components/hello-wave';
5
-
import ParallaxScrollView from '@/components/parallax-scroll-view';
6
-
import { ThemedText } from '@/components/themed-text';
7
-
import { ThemedView } from '@/components/themed-view';
8
-
import { Link } from 'expo-router';
9
-
10
-
export default function HomeScreen() {
11
-
return (
12
-
<ParallaxScrollView
13
-
headerBackgroundColor={{ light: '#A1CEDC', dark: '#1D3D47' }}
14
-
headerImage={
15
-
<Image
16
-
source={require('@/assets/images/partial-react-logo.png')}
17
-
style={styles.reactLogo}
18
-
/>
19
-
}>
20
-
<ThemedView style={styles.titleContainer}>
21
-
<ThemedText type="title">Welcome!</ThemedText>
22
-
<HelloWave />
23
-
</ThemedView>
24
-
<ThemedView style={styles.stepContainer}>
25
-
<ThemedText type="subtitle">Step 1: Try it</ThemedText>
26
-
<ThemedText>
27
-
Edit <ThemedText type="defaultSemiBold">app/(tabs)/index.tsx</ThemedText> to see changes.
28
-
Press{' '}
29
-
<ThemedText type="defaultSemiBold">
30
-
{Platform.select({
31
-
ios: 'cmd + d',
32
-
android: 'cmd + m',
33
-
web: 'F12',
34
-
})}
35
-
</ThemedText>{' '}
36
-
to open developer tools.
37
-
</ThemedText>
38
-
</ThemedView>
39
-
<ThemedView style={styles.stepContainer}>
40
-
<Link href="/modal">
41
-
<Link.Trigger>
42
-
<ThemedText type="subtitle">Step 2: Explore</ThemedText>
43
-
</Link.Trigger>
44
-
<Link.Preview />
45
-
<Link.Menu>
46
-
<Link.MenuAction title="Action" icon="cube" onPress={() => alert('Action pressed')} />
47
-
<Link.MenuAction
48
-
title="Share"
49
-
icon="square.and.arrow.up"
50
-
onPress={() => alert('Share pressed')}
51
-
/>
52
-
<Link.Menu title="More" icon="ellipsis">
53
-
<Link.MenuAction
54
-
title="Delete"
55
-
icon="trash"
56
-
destructive
57
-
onPress={() => alert('Delete pressed')}
58
-
/>
59
-
</Link.Menu>
60
-
</Link.Menu>
61
-
</Link>
62
-
63
-
<ThemedText>
64
-
{`Tap the Explore tab to learn more about what's included in this starter app.`}
65
-
</ThemedText>
66
-
</ThemedView>
67
-
<ThemedView style={styles.stepContainer}>
68
-
<ThemedText type="subtitle">Step 3: Get a fresh start</ThemedText>
69
-
<ThemedText>
70
-
{`When you're ready, run `}
71
-
<ThemedText type="defaultSemiBold">npm run reset-project</ThemedText> to get a fresh{' '}
72
-
<ThemedText type="defaultSemiBold">app</ThemedText> directory. This will move the current{' '}
73
-
<ThemedText type="defaultSemiBold">app</ThemedText> to{' '}
74
-
<ThemedText type="defaultSemiBold">app-example</ThemedText>.
75
-
</ThemedText>
76
-
</ThemedView>
77
-
</ParallaxScrollView>
78
-
);
79
-
}
80
-
81
-
const styles = StyleSheet.create({
82
-
titleContainer: {
83
-
flexDirection: 'row',
84
-
alignItems: 'center',
85
-
gap: 8,
86
-
},
87
-
stepContainer: {
88
-
gap: 8,
89
-
marginBottom: 8,
90
-
},
91
-
reactLogo: {
92
-
height: 178,
93
-
width: 290,
94
-
bottom: 0,
95
-
left: 0,
96
-
position: 'absolute',
97
-
},
98
-
});
+6
-19
app/_layout.tsx
+6
-19
app/_layout.tsx
···
1
-
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';
2
-
import { Stack } from 'expo-router';
3
-
import { StatusBar } from 'expo-status-bar';
4
-
import 'react-native-reanimated';
5
-
6
-
import { useColorScheme } from '@/hooks/use-color-scheme';
7
-
8
-
export const unstable_settings = {
9
-
anchor: '(tabs)',
10
-
};
1
+
import { Stack } from "expo-router";
2
+
import "react-native-reanimated";
11
3
12
4
export default function RootLayout() {
13
-
const colorScheme = useColorScheme();
14
-
15
5
return (
16
-
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
17
-
<Stack>
18
-
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
19
-
<Stack.Screen name="modal" options={{ presentation: 'modal', title: 'Modal' }} />
20
-
</Stack>
21
-
<StatusBar style="auto" />
22
-
</ThemeProvider>
6
+
<Stack>
7
+
<Stack.Screen name="home" options={{ headerShown: false }} />
8
+
<Stack.Screen name="music" />
9
+
</Stack>
23
10
);
24
11
}
+59
app/index.tsx
+59
app/index.tsx
···
1
+
import { BrowserOAuthClient } from "@atproto/oauth-client-browser";
2
+
import { useRouter } from "expo-router";
3
+
import { useEffect, useRef } from "react";
4
+
import { Button, TextInput } from "react-native";
5
+
6
+
const redirect_uri = "http://127.0.0.1:8081";
7
+
const scope = "atproto transition:generic";
8
+
9
+
export default function HomeScreen() {
10
+
const client = useRef<BrowserOAuthClient>(null);
11
+
const router = useRouter();
12
+
13
+
useEffect(() => {
14
+
if (client.current) return;
15
+
16
+
async function initializeClient() {
17
+
client.current = new BrowserOAuthClient({
18
+
handleResolver: "https://bsky.social",
19
+
// Note that the origin of the "client_id" URL must be "http://localhost" when
20
+
// using this configuration, regardless of the actual hostname ("127.0.0.1" or
21
+
// "[::1]"), port or pathname. Only the `redirect_uris` must contain the
22
+
// actual url that will be used to redirect the user back to the application.
23
+
clientMetadata: {
24
+
client_id: `http://localhost?redirect_uri=${encodeURIComponent(
25
+
redirect_uri
26
+
)}&scope=${encodeURIComponent(scope)}`,
27
+
redirect_uris: [redirect_uri],
28
+
scope,
29
+
token_endpoint_auth_method: "none",
30
+
},
31
+
});
32
+
const session = await client.current!.init();
33
+
if (session) {
34
+
router.replace("/music");
35
+
}
36
+
}
37
+
initializeClient().catch(console.error);
38
+
}, [router]);
39
+
40
+
const handlValueRef = useRef<string>("");
41
+
const startAuth = async () => {
42
+
const handle = handlValueRef.current;
43
+
await client.current!.signIn(handle, {
44
+
state: "this-is-state",
45
+
redirect_uri,
46
+
scope,
47
+
});
48
+
};
49
+
50
+
return (
51
+
<>
52
+
<TextInput
53
+
onChangeText={(text) => (handlValueRef.current = text)}
54
+
placeholder="Enter your Bluesky Handle"
55
+
/>
56
+
<Button title="Login with Bluesky" onPress={startAuth} />
57
+
</>
58
+
);
59
+
}
+65
app/music.tsx
+65
app/music.tsx
···
1
+
import { Agent } from "@atproto/api";
2
+
import {
3
+
BrowserOAuthClient,
4
+
type OAuthSession,
5
+
} from "@atproto/oauth-client-browser";
6
+
import * as DocumentPicker from "expo-document-picker";
7
+
import { useRouter } from "expo-router";
8
+
import { useEffect, useRef, useState } from "react";
9
+
import { Button } from "react-native";
10
+
11
+
const redirect_uri = "http://127.0.0.1:8081";
12
+
const scope = "atproto transition:generic";
13
+
14
+
export default function MusicScreen() {
15
+
const client = useRef<BrowserOAuthClient>(null);
16
+
const [session, setSession] = useState<OAuthSession | null>(null);
17
+
const router = useRouter();
18
+
19
+
useEffect(() => {
20
+
if (client.current) return;
21
+
22
+
async function initializeClient() {
23
+
client.current = new BrowserOAuthClient({
24
+
handleResolver: "https://bsky.social",
25
+
// Note that the origin of the "client_id" URL must be "http://localhost" when
26
+
// using this configuration, regardless of the actual hostname ("127.0.0.1" or
27
+
// "[::1]"), port or pathname. Only the `redirect_uris` must contain the
28
+
// actual url that will be used to redirect the user back to the application.
29
+
clientMetadata: {
30
+
client_id: `http://localhost?redirect_uri=${encodeURIComponent(
31
+
redirect_uri
32
+
)}&scope=${encodeURIComponent(scope)}`,
33
+
redirect_uris: [redirect_uri],
34
+
scope,
35
+
token_endpoint_auth_method: "none",
36
+
},
37
+
});
38
+
const res = await client.current!.init();
39
+
if (!res?.session) {
40
+
router.replace("/");
41
+
} else {
42
+
setSession(res.session);
43
+
}
44
+
}
45
+
initializeClient().catch(console.error);
46
+
}, [router]);
47
+
48
+
const uploadMusic = async () => {
49
+
if (!session) return;
50
+
51
+
const doc = await DocumentPicker.getDocumentAsync({ type: "audio/*" });
52
+
const agent = new Agent(session);
53
+
const blob = await agent.com.atproto.repo.uploadBlob(
54
+
await doc?.assets?.[0]?.file?.bytes()
55
+
);
56
+
await agent.com.atproto.repo.putRecord({
57
+
repo: session.did,
58
+
collection: "social.drips.music.track",
59
+
rkey: "test-track",
60
+
record: { blob: blob.data.blob },
61
+
});
62
+
};
63
+
64
+
return <Button onPress={uploadMusic} title="Musik hochladen" />;
65
+
}
+380
-1
package-lock.json
+380
-1
package-lock.json
···
8
8
"name": "atproto-music-poc",
9
9
"version": "1.0.0",
10
10
"dependencies": {
11
+
"@atproto/api": "^0.16.11",
12
+
"@atproto/identity": "^0.4.9",
13
+
"@atproto/oauth-client-browser": "^0.3.32",
11
14
"@expo/metro-runtime": "~6.1.2",
12
15
"@expo/vector-icons": "^15.0.2",
13
16
"@react-navigation/bottom-tabs": "^7.4.0",
14
17
"@react-navigation/elements": "^2.6.3",
15
18
"@react-navigation/native": "^7.1.8",
16
19
"expo": "~54.0.10",
20
+
"expo-audio": "^1.0.13",
21
+
"expo-auth-session": "^7.0.8",
17
22
"expo-constants": "~18.0.9",
23
+
"expo-document-picker": "^14.0.7",
18
24
"expo-font": "~14.0.8",
19
25
"expo-haptics": "~15.0.7",
20
26
"expo-image": "~3.0.8",
···
56
62
}
57
63
}
58
64
},
65
+
"node_modules/@atproto-labs/did-resolver": {
66
+
"version": "0.2.1",
67
+
"resolved": "https://registry.npmjs.org/@atproto-labs/did-resolver/-/did-resolver-0.2.1.tgz",
68
+
"integrity": "sha512-zSoHyqwwRYUtMNLW+RrWsImt1U5S47nJv5FfmAXTmon6wVKjxKD/PFrD1pg/4G6THqJmQHTs1Hj+54XVupYnvQ==",
69
+
"license": "MIT",
70
+
"dependencies": {
71
+
"@atproto-labs/fetch": "0.2.3",
72
+
"@atproto-labs/pipe": "0.1.1",
73
+
"@atproto-labs/simple-store": "0.3.0",
74
+
"@atproto-labs/simple-store-memory": "0.1.4",
75
+
"@atproto/did": "0.2.0",
76
+
"zod": "^3.23.8"
77
+
}
78
+
},
79
+
"node_modules/@atproto-labs/fetch": {
80
+
"version": "0.2.3",
81
+
"resolved": "https://registry.npmjs.org/@atproto-labs/fetch/-/fetch-0.2.3.tgz",
82
+
"integrity": "sha512-NZtbJOCbxKUFRFKMpamT38PUQMY0hX0p7TG5AEYOPhZKZEP7dHZ1K2s1aB8MdVH0qxmqX7nQleNrrvLf09Zfdw==",
83
+
"license": "MIT",
84
+
"dependencies": {
85
+
"@atproto-labs/pipe": "0.1.1"
86
+
}
87
+
},
88
+
"node_modules/@atproto-labs/handle-resolver": {
89
+
"version": "0.3.1",
90
+
"resolved": "https://registry.npmjs.org/@atproto-labs/handle-resolver/-/handle-resolver-0.3.1.tgz",
91
+
"integrity": "sha512-mLZdMNvwomgnn9sffKO1/xr02ctgeiT0FUVw7JekbchTckub2RM7qMu8Rw1mC4bpCpW+i7DXDiOxpoajkppwYQ==",
92
+
"license": "MIT",
93
+
"dependencies": {
94
+
"@atproto-labs/simple-store": "0.3.0",
95
+
"@atproto-labs/simple-store-memory": "0.1.4",
96
+
"@atproto/did": "0.2.0",
97
+
"zod": "^3.23.8"
98
+
}
99
+
},
100
+
"node_modules/@atproto-labs/identity-resolver": {
101
+
"version": "0.3.1",
102
+
"resolved": "https://registry.npmjs.org/@atproto-labs/identity-resolver/-/identity-resolver-0.3.1.tgz",
103
+
"integrity": "sha512-jCgotRRqPykPwh4gh0FBLOqeofv1G8OH/DZ5s88HWm7biUZeksZwDrEvL5TnqEFUpXT3O9Hcyp/XEpfCAplRoQ==",
104
+
"license": "MIT",
105
+
"dependencies": {
106
+
"@atproto-labs/did-resolver": "0.2.1",
107
+
"@atproto-labs/handle-resolver": "0.3.1"
108
+
}
109
+
},
110
+
"node_modules/@atproto-labs/pipe": {
111
+
"version": "0.1.1",
112
+
"resolved": "https://registry.npmjs.org/@atproto-labs/pipe/-/pipe-0.1.1.tgz",
113
+
"integrity": "sha512-hdNw2oUs2B6BN1lp+32pF7cp8EMKuIN5Qok2Vvv/aOpG/3tNSJ9YkvfI0k6Zd188LeDDYRUpYpxcoFIcGH/FNg==",
114
+
"license": "MIT"
115
+
},
116
+
"node_modules/@atproto-labs/simple-store": {
117
+
"version": "0.3.0",
118
+
"resolved": "https://registry.npmjs.org/@atproto-labs/simple-store/-/simple-store-0.3.0.tgz",
119
+
"integrity": "sha512-nOb6ONKBRJHRlukW1sVawUkBqReLlLx6hT35VS3imaNPwiXDxLnTK7lxw3Lrl9k5yugSBDQAkZAq3MPTEFSUBQ==",
120
+
"license": "MIT"
121
+
},
122
+
"node_modules/@atproto-labs/simple-store-memory": {
123
+
"version": "0.1.4",
124
+
"resolved": "https://registry.npmjs.org/@atproto-labs/simple-store-memory/-/simple-store-memory-0.1.4.tgz",
125
+
"integrity": "sha512-3mKY4dP8I7yKPFj9VKpYyCRzGJOi5CEpOLPlRhoJyLmgs3J4RzDrjn323Oakjz2Aj2JzRU/AIvWRAZVhpYNJHw==",
126
+
"license": "MIT",
127
+
"dependencies": {
128
+
"@atproto-labs/simple-store": "0.3.0",
129
+
"lru-cache": "^10.2.0"
130
+
}
131
+
},
132
+
"node_modules/@atproto-labs/simple-store-memory/node_modules/lru-cache": {
133
+
"version": "10.4.3",
134
+
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
135
+
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
136
+
"license": "ISC"
137
+
},
138
+
"node_modules/@atproto/api": {
139
+
"version": "0.16.11",
140
+
"resolved": "https://registry.npmjs.org/@atproto/api/-/api-0.16.11.tgz",
141
+
"integrity": "sha512-1dhfQNHiclb102RW+Ea8Nft5olfqU0Ev/vlQaSX6mWNo1aP5zT+sPODJ8+BTUOYk3vcuvL7QMkqA/rLYy2PMyw==",
142
+
"license": "MIT",
143
+
"dependencies": {
144
+
"@atproto/common-web": "^0.4.3",
145
+
"@atproto/lexicon": "^0.5.1",
146
+
"@atproto/syntax": "^0.4.1",
147
+
"@atproto/xrpc": "^0.7.5",
148
+
"await-lock": "^2.2.2",
149
+
"multiformats": "^9.9.0",
150
+
"tlds": "^1.234.0",
151
+
"zod": "^3.23.8"
152
+
}
153
+
},
154
+
"node_modules/@atproto/common-web": {
155
+
"version": "0.4.3",
156
+
"resolved": "https://registry.npmjs.org/@atproto/common-web/-/common-web-0.4.3.tgz",
157
+
"integrity": "sha512-nRDINmSe4VycJzPo6fP/hEltBcULFxt9Kw7fQk6405FyAWZiTluYHlXOnU7GkQfeUK44OENG1qFTBcmCJ7e8pg==",
158
+
"license": "MIT",
159
+
"dependencies": {
160
+
"graphemer": "^1.4.0",
161
+
"multiformats": "^9.9.0",
162
+
"uint8arrays": "3.0.0",
163
+
"zod": "^3.23.8"
164
+
}
165
+
},
166
+
"node_modules/@atproto/crypto": {
167
+
"version": "0.4.4",
168
+
"resolved": "https://registry.npmjs.org/@atproto/crypto/-/crypto-0.4.4.tgz",
169
+
"integrity": "sha512-Yq9+crJ7WQl7sxStVpHgie5Z51R05etaK9DLWYG/7bR5T4bhdcIgF6IfklLShtZwLYdVVj+K15s0BqW9a8PSDA==",
170
+
"license": "MIT",
171
+
"dependencies": {
172
+
"@noble/curves": "^1.7.0",
173
+
"@noble/hashes": "^1.6.1",
174
+
"uint8arrays": "3.0.0"
175
+
},
176
+
"engines": {
177
+
"node": ">=18.7.0"
178
+
}
179
+
},
180
+
"node_modules/@atproto/did": {
181
+
"version": "0.2.0",
182
+
"resolved": "https://registry.npmjs.org/@atproto/did/-/did-0.2.0.tgz",
183
+
"integrity": "sha512-BskT39KYbwY1DUsWekkHh47xS+wvJpFq5F9acsicNfYniinyAMnNTzGKQEhnjQuG7K0qQItg/SnmC+y0tJXV7Q==",
184
+
"license": "MIT",
185
+
"dependencies": {
186
+
"zod": "^3.23.8"
187
+
}
188
+
},
189
+
"node_modules/@atproto/identity": {
190
+
"version": "0.4.9",
191
+
"resolved": "https://registry.npmjs.org/@atproto/identity/-/identity-0.4.9.tgz",
192
+
"integrity": "sha512-pRYCaeaEJMZ4vQlRQYYTrF3cMiRp21n/k/pUT1o7dgKby56zuLErDmFXkbKfKWPf7SgWRgamSaNmsGLqAOD7lQ==",
193
+
"license": "MIT",
194
+
"dependencies": {
195
+
"@atproto/common-web": "^0.4.3",
196
+
"@atproto/crypto": "^0.4.4"
197
+
},
198
+
"engines": {
199
+
"node": ">=18.7.0"
200
+
}
201
+
},
202
+
"node_modules/@atproto/jwk": {
203
+
"version": "0.5.0",
204
+
"resolved": "https://registry.npmjs.org/@atproto/jwk/-/jwk-0.5.0.tgz",
205
+
"integrity": "sha512-Qi2NtEqhkG+uz3CKia4+H05WMV/z//dz3ESo5+cyBKrOnxVTJ5ZubMyltWjoYvy6v/jLhorXdDWcjn07yky7MQ==",
206
+
"license": "MIT",
207
+
"dependencies": {
208
+
"multiformats": "^9.9.0",
209
+
"zod": "^3.23.8"
210
+
}
211
+
},
212
+
"node_modules/@atproto/jwk-jose": {
213
+
"version": "0.1.10",
214
+
"resolved": "https://registry.npmjs.org/@atproto/jwk-jose/-/jwk-jose-0.1.10.tgz",
215
+
"integrity": "sha512-Eiu/u4tZHz3IIhHZt0zneYEffSAO3Oqk/ToKwlu1TqKte6sjtPs/4uquSiAAGFYozqgo92JC/AQclWzzkHI5QQ==",
216
+
"license": "MIT",
217
+
"dependencies": {
218
+
"@atproto/jwk": "0.5.0",
219
+
"jose": "^5.2.0"
220
+
}
221
+
},
222
+
"node_modules/@atproto/jwk-webcrypto": {
223
+
"version": "0.1.10",
224
+
"resolved": "https://registry.npmjs.org/@atproto/jwk-webcrypto/-/jwk-webcrypto-0.1.10.tgz",
225
+
"integrity": "sha512-JZsavs6JiSmw5rgcjkGDwzr1aCJGdybZOjVfYH+m9sXRU1BrUCA30uwNfZY7eFyWXyRAnCFiYiGVZgypXyKotw==",
226
+
"license": "MIT",
227
+
"dependencies": {
228
+
"@atproto/jwk": "0.5.0",
229
+
"@atproto/jwk-jose": "0.1.10",
230
+
"zod": "^3.23.8"
231
+
}
232
+
},
233
+
"node_modules/@atproto/lexicon": {
234
+
"version": "0.5.1",
235
+
"resolved": "https://registry.npmjs.org/@atproto/lexicon/-/lexicon-0.5.1.tgz",
236
+
"integrity": "sha512-y8AEtYmfgVl4fqFxqXAeGvhesiGkxiy3CWoJIfsFDDdTlZUC8DFnZrYhcqkIop3OlCkkljvpSJi1hbeC1tbi8A==",
237
+
"license": "MIT",
238
+
"dependencies": {
239
+
"@atproto/common-web": "^0.4.3",
240
+
"@atproto/syntax": "^0.4.1",
241
+
"iso-datestring-validator": "^2.2.2",
242
+
"multiformats": "^9.9.0",
243
+
"zod": "^3.23.8"
244
+
}
245
+
},
246
+
"node_modules/@atproto/oauth-client": {
247
+
"version": "0.5.6",
248
+
"resolved": "https://registry.npmjs.org/@atproto/oauth-client/-/oauth-client-0.5.6.tgz",
249
+
"integrity": "sha512-O1S9lPptJxWPcNd2kODaLgWntz+A7PzskU2hP4IFa7hVLs4aEnEt9dKq5wJE97tDli8mgyh/ndPQhxUaCVQ5iQ==",
250
+
"license": "MIT",
251
+
"dependencies": {
252
+
"@atproto-labs/did-resolver": "0.2.1",
253
+
"@atproto-labs/fetch": "0.2.3",
254
+
"@atproto-labs/handle-resolver": "0.3.1",
255
+
"@atproto-labs/identity-resolver": "0.3.1",
256
+
"@atproto-labs/simple-store": "0.3.0",
257
+
"@atproto-labs/simple-store-memory": "0.1.4",
258
+
"@atproto/did": "0.2.0",
259
+
"@atproto/jwk": "0.5.0",
260
+
"@atproto/oauth-types": "0.4.1",
261
+
"@atproto/xrpc": "0.7.5",
262
+
"multiformats": "^9.9.0",
263
+
"zod": "^3.23.8"
264
+
}
265
+
},
266
+
"node_modules/@atproto/oauth-client-browser": {
267
+
"version": "0.3.32",
268
+
"resolved": "https://registry.npmjs.org/@atproto/oauth-client-browser/-/oauth-client-browser-0.3.32.tgz",
269
+
"integrity": "sha512-h6Rsa/LgMnugaVKkMtHbQ1DSlhhIhL4HbjW1egg7z0BQdWJzczJ8nX3guHN5r/YioL6vOigxbMim+p/Z1LeG9g==",
270
+
"license": "MIT",
271
+
"dependencies": {
272
+
"@atproto-labs/did-resolver": "0.2.1",
273
+
"@atproto-labs/handle-resolver": "0.3.1",
274
+
"@atproto-labs/simple-store": "0.3.0",
275
+
"@atproto/did": "0.2.0",
276
+
"@atproto/jwk": "0.5.0",
277
+
"@atproto/jwk-webcrypto": "0.1.10",
278
+
"@atproto/oauth-client": "0.5.6",
279
+
"@atproto/oauth-types": "0.4.1"
280
+
}
281
+
},
282
+
"node_modules/@atproto/oauth-types": {
283
+
"version": "0.4.1",
284
+
"resolved": "https://registry.npmjs.org/@atproto/oauth-types/-/oauth-types-0.4.1.tgz",
285
+
"integrity": "sha512-c5ixf2ZOzcltOu1fDBnO/tok6Wj7JDDK66+Z0q/+bAr8LXgOnxP7zQfJ+DD4gTkB+saTqsqWtVv8qvx/IEtm1g==",
286
+
"license": "MIT",
287
+
"dependencies": {
288
+
"@atproto/jwk": "0.5.0",
289
+
"zod": "^3.23.8"
290
+
}
291
+
},
292
+
"node_modules/@atproto/syntax": {
293
+
"version": "0.4.1",
294
+
"resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.4.1.tgz",
295
+
"integrity": "sha512-CJdImtLAiFO+0z3BWTtxwk6aY5w4t8orHTMVJgkf++QRJWTxPbIFko/0hrkADB7n2EruDxDSeAgfUGehpH6ngw==",
296
+
"license": "MIT"
297
+
},
298
+
"node_modules/@atproto/xrpc": {
299
+
"version": "0.7.5",
300
+
"resolved": "https://registry.npmjs.org/@atproto/xrpc/-/xrpc-0.7.5.tgz",
301
+
"integrity": "sha512-MUYNn5d2hv8yVegRL0ccHvTHAVj5JSnW07bkbiaz96UH45lvYNRVwt44z+yYVnb0/mvBzyD3/ZQ55TRGt7fHkA==",
302
+
"license": "MIT",
303
+
"dependencies": {
304
+
"@atproto/lexicon": "^0.5.1",
305
+
"zod": "^3.23.8"
306
+
}
307
+
},
59
308
"node_modules/@babel/code-frame": {
60
309
"version": "7.27.1",
61
310
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
···
2637
2886
"@tybys/wasm-util": "^0.10.0"
2638
2887
}
2639
2888
},
2889
+
"node_modules/@noble/curves": {
2890
+
"version": "1.9.7",
2891
+
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz",
2892
+
"integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==",
2893
+
"license": "MIT",
2894
+
"dependencies": {
2895
+
"@noble/hashes": "1.8.0"
2896
+
},
2897
+
"engines": {
2898
+
"node": "^14.21.3 || >=16"
2899
+
},
2900
+
"funding": {
2901
+
"url": "https://paulmillr.com/funding/"
2902
+
}
2903
+
},
2904
+
"node_modules/@noble/hashes": {
2905
+
"version": "1.8.0",
2906
+
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
2907
+
"integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
2908
+
"license": "MIT",
2909
+
"engines": {
2910
+
"node": "^14.21.3 || >=16"
2911
+
},
2912
+
"funding": {
2913
+
"url": "https://paulmillr.com/funding/"
2914
+
}
2915
+
},
2640
2916
"node_modules/@nodelib/fs.scandir": {
2641
2917
"version": "2.1.5",
2642
2918
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
···
4677
4953
"url": "https://github.com/sponsors/ljharb"
4678
4954
}
4679
4955
},
4956
+
"node_modules/await-lock": {
4957
+
"version": "2.2.2",
4958
+
"resolved": "https://registry.npmjs.org/await-lock/-/await-lock-2.2.2.tgz",
4959
+
"integrity": "sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==",
4960
+
"license": "MIT"
4961
+
},
4680
4962
"node_modules/babel-jest": {
4681
4963
"version": "29.7.0",
4682
4964
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
···
6657
6939
"react-native": "*"
6658
6940
}
6659
6941
},
6942
+
"node_modules/expo-audio": {
6943
+
"version": "1.0.13",
6944
+
"resolved": "https://registry.npmjs.org/expo-audio/-/expo-audio-1.0.13.tgz",
6945
+
"integrity": "sha512-Z91jMYv/BMpn5Dkyu204RyNl2EpXRx8HgScTYlwRRYewtfTH8NN+bwZQAApn33NwGhW1cIlcuG/XCAeJBr9l4Q==",
6946
+
"license": "MIT",
6947
+
"peerDependencies": {
6948
+
"expo": "*",
6949
+
"react": "*",
6950
+
"react-native": "*"
6951
+
}
6952
+
},
6953
+
"node_modules/expo-auth-session": {
6954
+
"version": "7.0.8",
6955
+
"resolved": "https://registry.npmjs.org/expo-auth-session/-/expo-auth-session-7.0.8.tgz",
6956
+
"integrity": "sha512-kpo2Jva+6uVjk6TmNqWAoqTnULXZaEVa9l4uf8JH32uDMt/iZQhM0fauy7Ww+y910Euhv5djCP7cPj8KWv6cmQ==",
6957
+
"license": "MIT",
6958
+
"dependencies": {
6959
+
"expo-application": "~7.0.7",
6960
+
"expo-constants": "~18.0.8",
6961
+
"expo-crypto": "~15.0.7",
6962
+
"expo-linking": "~8.0.8",
6963
+
"expo-web-browser": "~15.0.7",
6964
+
"invariant": "^2.2.4"
6965
+
},
6966
+
"peerDependencies": {
6967
+
"react": "*",
6968
+
"react-native": "*"
6969
+
}
6970
+
},
6971
+
"node_modules/expo-auth-session/node_modules/expo-application": {
6972
+
"version": "7.0.7",
6973
+
"resolved": "https://registry.npmjs.org/expo-application/-/expo-application-7.0.7.tgz",
6974
+
"integrity": "sha512-Jt1/qqnoDUbZ+bK91+dHaZ1vrPDtRBOltRa681EeedkisqguuEeUx4UHqwVyDK2oHWsK6lO3ojetoA4h8OmNcg==",
6975
+
"license": "MIT",
6976
+
"peerDependencies": {
6977
+
"expo": "*"
6978
+
}
6979
+
},
6980
+
"node_modules/expo-auth-session/node_modules/expo-crypto": {
6981
+
"version": "15.0.7",
6982
+
"resolved": "https://registry.npmjs.org/expo-crypto/-/expo-crypto-15.0.7.tgz",
6983
+
"integrity": "sha512-FUo41TwwGT2e5rA45PsjezI868Ch3M6wbCZsmqTWdF/hr+HyPcrp1L//dsh/hsrsyrQdpY/U96Lu71/wXePJeg==",
6984
+
"license": "MIT",
6985
+
"dependencies": {
6986
+
"base64-js": "^1.3.0"
6987
+
},
6988
+
"peerDependencies": {
6989
+
"expo": "*"
6990
+
}
6991
+
},
6660
6992
"node_modules/expo-constants": {
6661
6993
"version": "18.0.9",
6662
6994
"resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-18.0.9.tgz",
···
6671
7003
"react-native": "*"
6672
7004
}
6673
7005
},
7006
+
"node_modules/expo-document-picker": {
7007
+
"version": "14.0.7",
7008
+
"resolved": "https://registry.npmjs.org/expo-document-picker/-/expo-document-picker-14.0.7.tgz",
7009
+
"integrity": "sha512-81Jh8RDD0GYBUoSTmIBq30hXXjmkDV1ZY2BNIp1+3HR5PDSh2WmdhD/Ezz5YFsv46hIXHsQc+Kh1q8vn6OLT9Q==",
7010
+
"license": "MIT",
7011
+
"peerDependencies": {
7012
+
"expo": "*"
7013
+
}
7014
+
},
6674
7015
"node_modules/expo-file-system": {
6675
7016
"version": "19.0.15",
6676
7017
"resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-19.0.15.tgz",
···
7727
8068
"version": "1.4.0",
7728
8069
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
7729
8070
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
7730
-
"dev": true,
7731
8071
"license": "MIT"
7732
8072
},
7733
8073
"node_modules/has-bigints": {
···
8526
8866
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
8527
8867
"license": "ISC"
8528
8868
},
8869
+
"node_modules/iso-datestring-validator": {
8870
+
"version": "2.2.2",
8871
+
"resolved": "https://registry.npmjs.org/iso-datestring-validator/-/iso-datestring-validator-2.2.2.tgz",
8872
+
"integrity": "sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA==",
8873
+
"license": "MIT"
8874
+
},
8529
8875
"node_modules/istanbul-lib-coverage": {
8530
8876
"version": "3.2.2",
8531
8877
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
···
8762
9108
"resolved": "https://registry.npmjs.org/jimp-compact/-/jimp-compact-0.16.1.tgz",
8763
9109
"integrity": "sha512-dZ6Ra7u1G8c4Letq/B5EzAxj4tLFHL+cGtdpR+PVm4yzPDj+lCk+AbivWt1eOM+ikzkowtyV7qSqX6qr3t71Ww==",
8764
9110
"license": "MIT"
9111
+
},
9112
+
"node_modules/jose": {
9113
+
"version": "5.10.0",
9114
+
"resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz",
9115
+
"integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==",
9116
+
"license": "MIT",
9117
+
"funding": {
9118
+
"url": "https://github.com/sponsors/panva"
9119
+
}
8765
9120
},
8766
9121
"node_modules/js-tokens": {
8767
9122
"version": "4.0.0",
···
9754
10109
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
9755
10110
"license": "MIT"
9756
10111
},
10112
+
"node_modules/multiformats": {
10113
+
"version": "9.9.0",
10114
+
"resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz",
10115
+
"integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==",
10116
+
"license": "(Apache-2.0 AND MIT)"
10117
+
},
9757
10118
"node_modules/mz": {
9758
10119
"version": "2.7.0",
9759
10120
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
···
12436
12797
"url": "https://github.com/sponsors/jonschlinkert"
12437
12798
}
12438
12799
},
12800
+
"node_modules/tlds": {
12801
+
"version": "1.260.0",
12802
+
"resolved": "https://registry.npmjs.org/tlds/-/tlds-1.260.0.tgz",
12803
+
"integrity": "sha512-78+28EWBhCEE7qlyaHA9OR3IPvbCLiDh3Ckla593TksfFc9vfTsgvH7eS+dr3o9qr31gwGbogcI16yN91PoRjQ==",
12804
+
"license": "MIT",
12805
+
"bin": {
12806
+
"tlds": "bin.js"
12807
+
}
12808
+
},
12439
12809
"node_modules/tmpl": {
12440
12810
"version": "1.0.5",
12441
12811
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
···
12667
13037
},
12668
13038
"engines": {
12669
13039
"node": "*"
13040
+
}
13041
+
},
13042
+
"node_modules/uint8arrays": {
13043
+
"version": "3.0.0",
13044
+
"resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.0.0.tgz",
13045
+
"integrity": "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==",
13046
+
"license": "MIT",
13047
+
"dependencies": {
13048
+
"multiformats": "^9.4.2"
12670
13049
}
12671
13050
},
12672
13051
"node_modules/unbox-primitive": {
+11
-5
package.json
+11
-5
package.json
···
11
11
"lint": "expo lint"
12
12
},
13
13
"dependencies": {
14
-
"@expo/vector-icons": "^15.0.2",
14
+
"@atproto/api": "^0.16.11",
15
+
"@atproto/identity": "^0.4.9",
16
+
"@atproto/oauth-client-browser": "^0.3.32",
15
17
"@expo/metro-runtime": "~6.1.2",
18
+
"@expo/vector-icons": "^15.0.2",
16
19
"@react-navigation/bottom-tabs": "^7.4.0",
17
20
"@react-navigation/elements": "^2.6.3",
18
21
"@react-navigation/native": "^7.1.8",
19
22
"expo": "~54.0.10",
23
+
"expo-audio": "^1.0.13",
24
+
"expo-auth-session": "^7.0.8",
20
25
"expo-constants": "~18.0.9",
26
+
"expo-document-picker": "^14.0.7",
21
27
"expo-font": "~14.0.8",
22
28
"expo-haptics": "~15.0.7",
23
29
"expo-image": "~3.0.8",
···
32
38
"react-dom": "19.1.0",
33
39
"react-native": "0.81.4",
34
40
"react-native-gesture-handler": "~2.28.0",
35
-
"react-native-worklets": "0.5.1",
36
41
"react-native-reanimated": "~4.1.1",
37
42
"react-native-safe-area-context": "~5.6.0",
38
43
"react-native-screens": "~4.16.0",
39
-
"react-native-web": "~0.21.0"
44
+
"react-native-web": "~0.21.0",
45
+
"react-native-worklets": "0.5.1"
40
46
},
41
47
"devDependencies": {
42
48
"@types/react": "~19.1.0",
43
-
"typescript": "~5.9.2",
44
49
"eslint": "^9.25.0",
45
-
"eslint-config-expo": "~10.0.0"
50
+
"eslint-config-expo": "~10.0.0",
51
+
"typescript": "~5.9.2"
46
52
},
47
53
"private": true
48
54
}