tangled
alpha
login
or
join now
teal.fm
/
teal
110
fork
atom
Your music, beautifully tracked. All yours. (coming soon)
teal.fm
teal-fm
atproto
110
fork
atom
overview
issues
pulls
pipelines
cheeky format
Natalie B
10 months ago
18a0d2f9
14b70559
+89
-65
5 changed files
expand all
collapse all
unified
split
apps
amethyst
app
(tabs)
_layout.tsx
profile
[handle].tsx
components
ui
textarea.tsx
aqua
src
did
web.ts
xrpc
route.ts
+29
-21
apps/amethyst/app/(tabs)/_layout.tsx
···
1
1
-
2
2
-
import React from 'react';
1
1
+
import React from "react";
3
2
4
3
import {
5
4
FilePen,
···
8
7
Search,
9
8
Settings,
10
9
type LucideIcon,
11
11
-
} from 'lucide-react-native';
12
12
-
import { Link, Tabs } from 'expo-router';
13
13
-
import { Pressable } from 'react-native';
10
10
+
} from "lucide-react-native";
11
11
+
import { Link, Tabs } from "expo-router";
12
12
+
import { Pressable } from "react-native";
14
13
15
15
-
import Colors from '../../constants/Colors';
16
16
-
import { Icon, iconWithClassName } from '../../lib/icons/iconWithClassName';
14
14
+
import Colors from "../../constants/Colors";
15
15
+
import { Icon, iconWithClassName } from "../../lib/icons/iconWithClassName";
17
16
//import useIsMobile from "@/hooks/useIsMobile";
18
18
-
import { useStore } from '@/stores/mainStore';
19
19
-
import { useColorScheme } from 'nativewind';
20
20
-
import AuthOptions from '../auth/options';
21
21
-
import useIsMobile from '@/hooks/useIsMobile';
17
17
+
import { useStore } from "@/stores/mainStore";
18
18
+
import { useColorScheme } from "nativewind";
19
19
+
import AuthOptions from "../auth/options";
20
20
+
import useIsMobile from "@/hooks/useIsMobile";
22
21
23
22
function TabBarIcon(props: { name: LucideIcon; color: string }) {
24
23
const Name = props.name;
···
31
30
const authStatus = useStore((state) => state.status);
32
31
const isMobile = useIsMobile();
33
32
// if we are on web but not native and web width is greater than 1024px
34
34
-
const hideTabBar = authStatus !== 'loggedIn'; // || useIsMobile()
33
33
+
const hideTabBar = authStatus !== "loggedIn"; // || useIsMobile()
35
34
36
35
const j = useStore((state) => state.status);
37
36
// @me
38
37
const agent = useStore((state) => state.pdsAgent);
39
39
-
const profile = useStore((state) => state.profiles[agent?.did ?? '']);
38
38
+
const profile = useStore((state) => state.profiles[agent?.did ?? ""]);
40
39
41
41
-
if (j !== 'loggedIn') {
40
40
+
if (j !== "loggedIn") {
42
41
return <AuthOptions />;
43
42
}
44
43
45
44
return (
46
45
<Tabs
47
46
screenOptions={{
48
48
-
title: 'Home',
49
49
-
tabBarActiveTintColor: Colors[colorScheme ?? 'light'].tint,
47
47
+
title: "Home",
48
48
+
tabBarActiveTintColor: Colors[colorScheme ?? "light"].tint,
50
49
// Disable the static render of the header on web
51
50
// to prevent a hydration error in
52
51
// React Navigation v6.
···
58
57
59
58
tabBarStyle: {
60
59
//height: 75,
61
61
-
display: hideTabBar ? 'none' : 'flex',
60
60
+
display: hideTabBar ? "none" : "flex",
62
61
},
63
62
}}
64
63
>
65
64
<Tabs.Screen
66
65
name="index"
67
66
options={{
68
68
-
title: 'Home',
67
67
+
title: "Home",
69
68
tabBarIcon: ({ color }) => <TabBarIcon name={Home} color={color} />,
70
69
headerRight: () => (
71
70
<Link href="/auth/logoutModal" asChild>
···
85
84
<Tabs.Screen
86
85
name="search/index"
87
86
options={{
88
88
-
title: 'Search',
87
87
+
title: "Search",
89
88
tabBarIcon: ({ color }) => <TabBarIcon name={Search} color={color} />,
90
89
}}
91
90
/>
92
91
<Tabs.Screen
93
92
name="(stamp)"
94
93
options={{
95
95
-
title: 'Stamp',
94
94
+
title: "Stamp",
96
95
tabBarIcon: ({ color }) => (
97
96
<TabBarIcon name={FilePen} color={color} />
98
97
),
···
101
100
<Tabs.Screen
102
101
name="settings/index"
103
102
options={{
104
104
-
title: 'Settings',
103
103
+
title: "Settings",
105
104
106
105
tabBarIcon: ({ color }) => (
107
106
<TabBarIcon name={Settings} color={color} />
107
107
+
),
108
108
+
}}
109
109
+
/>
110
110
+
<Tabs.Screen
111
111
+
name="/profile/undefined"
112
112
+
options={{
113
113
+
title: "Stamp",
114
114
+
tabBarIcon: ({ color }) => (
115
115
+
<TabBarIcon name={FilePen} color={color} />
108
116
),
109
117
}}
110
118
/>
+14
-10
apps/amethyst/app/(tabs)/profile/[handle].tsx
···
1
1
-
import ActorView from '@/components/actor/actorView';
2
2
-
import { Text } from '@/components/ui/text';
3
3
-
import { resolveHandle } from '@/lib/atp/pid';
4
4
-
import { useStore } from '@/stores/mainStore';
5
5
-
import { Stack, useLocalSearchParams } from 'expo-router';
6
6
-
import { useEffect, useState } from 'react';
7
7
-
import { ActivityIndicator, ScrollView, View } from 'react-native';
1
1
+
import ActorView from "@/components/actor/actorView";
2
2
+
import { Text } from "@/components/ui/text";
3
3
+
import { resolveHandle } from "@/lib/atp/pid";
4
4
+
import { useStore } from "@/stores/mainStore";
5
5
+
import { Stack, useLocalSearchParams } from "expo-router";
6
6
+
import { useEffect, useState } from "react";
7
7
+
import { ActivityIndicator, ScrollView, View } from "react-native";
8
8
9
9
export default function Handle() {
10
10
let { handle } = useLocalSearchParams();
···
18
18
const agent = await resolveHandle(handle);
19
19
setDid(agent);
20
20
};
21
21
-
fetchAgent();
21
21
+
if (handle !== "undefined") fetchAgent();
22
22
}, [handle]);
23
23
24
24
+
if (handle === "undefined") {
25
25
+
return <Text>Handle is undefined</Text>;
26
26
+
}
27
27
+
24
28
if (!did) return <ActivityIndicator size="large" color="#0000ff" />;
25
29
26
30
return (
27
31
<ScrollView className="flex-1 justify-start items-center gap-5 bg-background w-full">
28
32
<Stack.Screen
29
33
options={{
30
30
-
title: 'Home',
31
31
-
headerBackButtonDisplayMode: 'minimal',
34
34
+
title: "Home",
35
35
+
headerBackButtonDisplayMode: "minimal",
32
36
headerShown: false,
33
37
}}
34
38
/>
+24
-12
apps/amethyst/components/ui/textarea.tsx
···
1
1
-
import * as React from 'react';
2
2
-
import { TextInput, type TextInputProps } from 'react-native';
3
3
-
import { cn } from '~/lib/utils';
1
1
+
import * as React from "react";
2
2
+
import { TextInput, type TextInputProps } from "react-native";
3
3
+
import { cn } from "~/lib/utils";
4
4
5
5
-
const Textarea = React.forwardRef<React.ElementRef<typeof TextInput>, TextInputProps>(
6
6
-
({ className, multiline = true, numberOfLines = 4, placeholderClassName, ...props }, ref) => {
5
5
+
const Textarea = React.forwardRef<
6
6
+
React.ElementRef<typeof TextInput>,
7
7
+
TextInputProps
8
8
+
>(
9
9
+
(
10
10
+
{
11
11
+
className,
12
12
+
multiline = true,
13
13
+
numberOfLines = 4,
14
14
+
placeholderClassName,
15
15
+
...props
16
16
+
},
17
17
+
ref,
18
18
+
) => {
7
19
return (
8
20
<TextInput
9
21
ref={ref}
10
22
className={cn(
11
11
-
'web:flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-base lg:text-sm native:text-lg native:leading-[1.25] text-foreground web:ring-offset-background placeholder:text-muted-foreground web:focus-visible:outline-none web:focus-visible:ring-2 web:focus-visible:ring-ring web:focus-visible:ring-offset-2',
12
12
-
props.editable === false && 'opacity-50 web:cursor-not-allowed',
13
13
-
className
23
23
+
"font-sans web:flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-base lg:text-sm native:text-lg native:leading-[1.25] text-foreground web:ring-offset-background placeholder:text-muted-foreground web:focus-visible:outline-none web:focus-visible:ring-2 web:focus-visible:ring-ring web:focus-visible:ring-offset-2",
24
24
+
props.editable === false && "opacity-50 web:cursor-not-allowed",
25
25
+
className,
14
26
)}
15
15
-
placeholderClassName={cn('text-muted-foreground', placeholderClassName)}
27
27
+
placeholderClassName={cn("text-muted-foreground", placeholderClassName)}
16
28
multiline={multiline}
17
29
numberOfLines={numberOfLines}
18
18
-
textAlignVertical='top'
30
30
+
textAlignVertical="top"
19
31
{...props}
20
32
/>
21
33
);
22
22
-
}
34
34
+
},
23
35
);
24
36
25
25
-
Textarea.displayName = 'Textarea';
37
37
+
Textarea.displayName = "Textarea";
26
38
27
39
export { Textarea };
+9
-12
apps/aqua/src/did/web.ts
···
2
2
3
3
/// Responds with a did:web with a TealFmAppview service at the given domain.
4
4
export function createDidWeb(domain: string, pubKey: string) {
5
5
+
console.log("Requested did web");
5
6
return {
6
6
-
'@context': [
7
7
-
'https://www.w3.org/ns/did/v1',
8
8
-
'https://w3id.org/security/multikey/v1',
9
9
-
'https://w3id.org/security/suites/secp256k1-2019/v1',
10
10
-
],
11
11
-
id: 'did:web:' + domain,
7
7
+
"@context": ["https://www.w3.org/ns/did/v1"],
8
8
+
id: "did:web:" + domain,
12
9
verificationMethod: [
13
10
{
14
14
-
id: 'did:web:' + domain + '#atproto',
15
15
-
type: 'Multikey',
16
16
-
controller: 'did:web:' + domain,
11
11
+
id: "did:web:" + domain + "#atproto",
12
12
+
type: "Multikey",
13
13
+
controller: "did:web:" + domain,
17
14
publicKeyMultibase: pubKey,
18
15
},
19
16
],
20
17
service: [
21
18
{
22
22
-
id: '#teal_fm_appview',
23
23
-
type: 'TealFmAppView',
24
24
-
serviceEndpoint: 'https://' + domain,
19
19
+
id: "#teal_fm_appview",
20
20
+
type: "TealFmAppView",
21
21
+
serviceEndpoint: "https://" + domain,
25
22
},
26
23
],
27
24
};
+13
-10
apps/aqua/src/xrpc/route.ts
···
1
1
-
import { EnvWithCtx } from '@/ctx';
2
2
-
import { Hono } from 'hono';
3
3
-
import getPlay from './feed/getPlay';
4
4
-
import getActorFeed from './feed/getActorFeed';
5
5
-
import getProfile from './actor/getProfile';
6
6
-
import searchActors from './actor/searchActors';
1
1
+
import { EnvWithCtx } from "@/ctx";
2
2
+
import { Hono } from "hono";
3
3
+
import { logger } from "hono/logger";
4
4
+
import getPlay from "./feed/getPlay";
5
5
+
import getActorFeed from "./feed/getActorFeed";
6
6
+
import getProfile from "./actor/getProfile";
7
7
+
import searchActors from "./actor/searchActors";
7
8
8
9
// mount this on /xrpc
9
10
const app = new Hono<EnvWithCtx>();
10
11
11
11
-
app.get('fm.teal.alpha.feed.getPlay', async (c) => c.json(await getPlay(c)));
12
12
-
app.get('fm.teal.alpha.feed.getActorFeed', async (c) =>
12
12
+
app.use(logger());
13
13
+
14
14
+
app.get("fm.teal.alpha.feed.getPlay", async (c) => c.json(await getPlay(c)));
15
15
+
app.get("fm.teal.alpha.feed.getActorFeed", async (c) =>
13
16
c.json(await getActorFeed(c)),
14
17
);
15
18
16
16
-
app.get('fm.teal.alpha.actor.getProfile', async (c) =>
19
19
+
app.get("fm.teal.alpha.actor.getProfile", async (c) =>
17
20
c.json(await getProfile(c)),
18
21
);
19
22
20
20
-
app.get('fm.teal.alpha.actor.searchActors', async (c) =>
23
23
+
app.get("fm.teal.alpha.actor.searchActors", async (c) =>
21
24
c.json(await searchActors(c)),
22
25
);
23
26