Switch to autogenerated avis for now
parent
eceef67d46
commit
fd6a2b1b40
|
@ -6,7 +6,7 @@ import {FontAwesomeIcon, Props} from '@fortawesome/react-native-fontawesome'
|
|||
import {NotificationsViewItemModel} from '../../../state/models/notifications-view'
|
||||
import {s, colors} from '../../lib/styles'
|
||||
import {ago, pluralize} from '../../lib/strings'
|
||||
import {DEF_AVATER} from '../../lib/assets'
|
||||
import {UserAvatar} from '../util/UserAvatar'
|
||||
import {PostText} from '../post/PostText'
|
||||
import {Post} from '../post/Post'
|
||||
import {Link} from '../util/Link'
|
||||
|
@ -114,7 +114,11 @@ export const FeedItem = observer(function FeedItem({
|
|||
key={author.href}
|
||||
href={author.href}
|
||||
title={`@${author.name}`}>
|
||||
<Image style={styles.avi} source={DEF_AVATER} />
|
||||
<UserAvatar
|
||||
size={30}
|
||||
displayName={author.displayName}
|
||||
name={author.name}
|
||||
/>
|
||||
</Link>
|
||||
))}
|
||||
{authors.length > MAX_AUTHORS ? (
|
||||
|
@ -197,12 +201,6 @@ const styles = StyleSheet.create({
|
|||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
avi: {
|
||||
width: 30,
|
||||
height: 30,
|
||||
borderRadius: 15,
|
||||
resizeMode: 'cover',
|
||||
},
|
||||
aviExtraCount: {
|
||||
fontWeight: 'bold',
|
||||
paddingLeft: 6,
|
||||
|
|
|
@ -1,22 +1,14 @@
|
|||
import React, {useState, useEffect} from 'react'
|
||||
import {observer} from 'mobx-react-lite'
|
||||
import {
|
||||
ActivityIndicator,
|
||||
FlatList,
|
||||
Image,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native'
|
||||
import {ActivityIndicator, FlatList, StyleSheet, Text, View} from 'react-native'
|
||||
import {
|
||||
LikedByViewModel,
|
||||
LikedByViewItemModel,
|
||||
} from '../../../state/models/liked-by-view'
|
||||
import {Link} from '../util/Link'
|
||||
import {UserAvatar} from '../util/UserAvatar'
|
||||
import {useStores} from '../../../state'
|
||||
import {s, colors} from '../../lib/styles'
|
||||
import {DEF_AVATER} from '../../lib/assets'
|
||||
|
||||
export const PostLikedBy = observer(function PostLikedBy({uri}: {uri: string}) {
|
||||
const store = useStores()
|
||||
|
@ -78,7 +70,11 @@ const LikedByItem = ({item}: {item: LikedByViewItemModel}) => {
|
|||
<Link style={styles.outer} href={`/profile/${item.name}`} title={item.name}>
|
||||
<View style={styles.layout}>
|
||||
<View style={styles.layoutAvi}>
|
||||
<Image style={styles.avi} source={DEF_AVATER} />
|
||||
<UserAvatar
|
||||
size={40}
|
||||
displayName={item.displayName}
|
||||
name={item.name}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.layoutContent}>
|
||||
<Text style={[s.f15, s.bold]}>{item.displayName}</Text>
|
||||
|
|
|
@ -12,10 +12,10 @@ import {
|
|||
RepostedByViewModel,
|
||||
RepostedByViewItemModel,
|
||||
} from '../../../state/models/reposted-by-view'
|
||||
import {UserAvatar} from '../util/UserAvatar'
|
||||
import {Link} from '../util/Link'
|
||||
import {useStores} from '../../../state'
|
||||
import {s, colors} from '../../lib/styles'
|
||||
import {DEF_AVATER} from '../../lib/assets'
|
||||
|
||||
export const PostRepostedBy = observer(function PostRepostedBy({
|
||||
uri,
|
||||
|
@ -83,7 +83,11 @@ const RepostedByItem = ({item}: {item: RepostedByViewItemModel}) => {
|
|||
<Link style={styles.outer} href={`/profile/${item.name}`} title={item.name}>
|
||||
<View style={styles.layout}>
|
||||
<View style={styles.layoutAvi}>
|
||||
<Image style={styles.avi} source={DEF_AVATER} />
|
||||
<UserAvatar
|
||||
size={40}
|
||||
displayName={item.displayName}
|
||||
name={item.name}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.layoutContent}>
|
||||
<Text style={[s.f15, s.bold]}>{item.displayName}</Text>
|
||||
|
|
|
@ -10,9 +10,9 @@ import {ComposePostModel} from '../../../state/models/shell'
|
|||
import {Link} from '../util/Link'
|
||||
import {RichText} from '../util/RichText'
|
||||
import {PostDropdownBtn} from '../util/DropdownBtn'
|
||||
import {UserAvatar} from '../util/UserAvatar'
|
||||
import {s, colors} from '../../lib/styles'
|
||||
import {ago, pluralize} from '../../lib/strings'
|
||||
import {DEF_AVATER} from '../../lib/assets'
|
||||
import {useStores} from '../../../state'
|
||||
|
||||
const PARENT_REPLY_LINE_LENGTH = 8
|
||||
|
@ -116,7 +116,11 @@ export const PostThreadItem = observer(function PostThreadItem({
|
|||
<View style={styles.outer}>
|
||||
<View style={styles.layout}>
|
||||
<Link style={styles.layoutAvi} href={authorHref} title={authorTitle}>
|
||||
<Image style={styles.avi} source={DEF_AVATER} />
|
||||
<UserAvatar
|
||||
size={50}
|
||||
displayName={item.author.displayName}
|
||||
name={item.author.name}
|
||||
/>
|
||||
</Link>
|
||||
<View style={styles.layoutContent}>
|
||||
<View style={[styles.meta, s.mt5]}>
|
||||
|
@ -231,7 +235,11 @@ export const PostThreadItem = observer(function PostThreadItem({
|
|||
)}
|
||||
<View style={styles.layout}>
|
||||
<Link style={styles.layoutAvi} href={authorHref} title={authorTitle}>
|
||||
<Image style={styles.avi} source={DEF_AVATER} />
|
||||
<UserAvatar
|
||||
size={50}
|
||||
displayName={item.author.displayName}
|
||||
name={item.author.name}
|
||||
/>
|
||||
</Link>
|
||||
<View style={styles.layoutContent}>
|
||||
{item.replyingToAuthor &&
|
||||
|
@ -321,12 +329,6 @@ const styles = StyleSheet.create({
|
|||
paddingTop: 10,
|
||||
paddingBottom: 10,
|
||||
},
|
||||
avi: {
|
||||
width: 50,
|
||||
height: 50,
|
||||
borderRadius: 25,
|
||||
resizeMode: 'cover',
|
||||
},
|
||||
layoutContent: {
|
||||
flex: 1,
|
||||
paddingRight: 10,
|
||||
|
|
|
@ -4,7 +4,6 @@ import {AtUri} from '../../../third-party/uri'
|
|||
import * as PostType from '../../../third-party/api/src/types/app/bsky/post'
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Image,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
|
@ -16,10 +15,10 @@ import {ComposePostModel} from '../../../state/models/shell'
|
|||
import {Link} from '../util/Link'
|
||||
import {UserInfoText} from '../util/UserInfoText'
|
||||
import {RichText} from '../util/RichText'
|
||||
import {UserAvatar} from '../util/UserAvatar'
|
||||
import {useStores} from '../../../state'
|
||||
import {s, colors} from '../../lib/styles'
|
||||
import {ago} from '../../lib/strings'
|
||||
import {DEF_AVATER} from '../../lib/assets'
|
||||
|
||||
export const Post = observer(function Post({uri}: {uri: string}) {
|
||||
const store = useStores()
|
||||
|
@ -91,7 +90,11 @@ export const Post = observer(function Post({uri}: {uri: string}) {
|
|||
<Link style={styles.outer} href={itemHref} title={itemTitle}>
|
||||
<View style={styles.layout}>
|
||||
<Link style={styles.layoutAvi} href={authorHref} title={authorTitle}>
|
||||
<Image style={styles.avi} source={DEF_AVATER} />
|
||||
<UserAvatar
|
||||
size={50}
|
||||
displayName={item.author.displayName}
|
||||
name={item.author.name}
|
||||
/>
|
||||
</Link>
|
||||
<View style={styles.layoutContent}>
|
||||
<View style={styles.meta}>
|
||||
|
@ -185,12 +188,6 @@ const styles = StyleSheet.create({
|
|||
layoutAvi: {
|
||||
width: 60,
|
||||
},
|
||||
avi: {
|
||||
width: 50,
|
||||
height: 50,
|
||||
borderRadius: 25,
|
||||
resizeMode: 'cover',
|
||||
},
|
||||
layoutContent: {
|
||||
flex: 1,
|
||||
},
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
import React, {useMemo} from 'react'
|
||||
import {observer} from 'mobx-react-lite'
|
||||
import {Image, StyleSheet, Text, TouchableOpacity, View} from 'react-native'
|
||||
import {StyleSheet, Text, TouchableOpacity, View} from 'react-native'
|
||||
import {AtUri} from '../../../third-party/uri'
|
||||
import * as PostType from '../../../third-party/api/src/types/app/bsky/post'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {FeedViewItemModel} from '../../../state/models/feed-view'
|
||||
import {FeedItemModel} from '../../../state/models/feed-view'
|
||||
import {ComposePostModel, SharePostModel} from '../../../state/models/shell'
|
||||
import {Link} from '../util/Link'
|
||||
import {PostDropdownBtn} from '../util/DropdownBtn'
|
||||
import {UserInfoText} from '../util/UserInfoText'
|
||||
import {RichText} from '../util/RichText'
|
||||
import {UserAvatar} from '../util/UserAvatar'
|
||||
import {s, colors} from '../../lib/styles'
|
||||
import {ago} from '../../lib/strings'
|
||||
import {DEF_AVATER} from '../../lib/assets'
|
||||
import {useStores} from '../../../state'
|
||||
|
||||
export const FeedItem = observer(function FeedItem({
|
||||
item,
|
||||
}: {
|
||||
item: FeedViewItemModel
|
||||
item: FeedItemModel
|
||||
}) {
|
||||
const store = useStores()
|
||||
const record = item.record as unknown as PostType.Record
|
||||
|
@ -73,7 +73,11 @@ export const FeedItem = observer(function FeedItem({
|
|||
style={styles.layoutAvi}
|
||||
href={authorHref}
|
||||
title={item.author.name}>
|
||||
<Image style={styles.avi} source={DEF_AVATER} />
|
||||
<UserAvatar
|
||||
size={50}
|
||||
displayName={item.author.displayName}
|
||||
name={item.author.name}
|
||||
/>
|
||||
</Link>
|
||||
<View style={styles.layoutContent}>
|
||||
<View style={styles.meta}>
|
||||
|
@ -199,12 +203,6 @@ const styles = StyleSheet.create({
|
|||
width: 60,
|
||||
paddingTop: 5,
|
||||
},
|
||||
avi: {
|
||||
width: 50,
|
||||
height: 50,
|
||||
borderRadius: 25,
|
||||
resizeMode: 'cover',
|
||||
},
|
||||
layoutContent: {
|
||||
flex: 1,
|
||||
},
|
||||
|
|
|
@ -13,9 +13,9 @@ import {
|
|||
FollowerItem,
|
||||
} from '../../../state/models/user-followers-view'
|
||||
import {Link} from '../util/Link'
|
||||
import {UserAvatar} from '../util/UserAvatar'
|
||||
import {useStores} from '../../../state'
|
||||
import {s, colors} from '../../lib/styles'
|
||||
import {DEF_AVATER} from '../../lib/assets'
|
||||
|
||||
export const ProfileFollowers = observer(function ProfileFollowers({
|
||||
name,
|
||||
|
@ -81,7 +81,11 @@ const User = ({item}: {item: FollowerItem}) => {
|
|||
<Link style={styles.outer} href={`/profile/${item.name}`} title={item.name}>
|
||||
<View style={styles.layout}>
|
||||
<View style={styles.layoutAvi}>
|
||||
<Image style={styles.avi} source={DEF_AVATER} />
|
||||
<UserAvatar
|
||||
size={40}
|
||||
displayName={item.displayName}
|
||||
name={item.name}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.layoutContent}>
|
||||
<Text style={[s.f15, s.bold]}>{item.displayName}</Text>
|
||||
|
@ -106,12 +110,6 @@ const styles = StyleSheet.create({
|
|||
paddingTop: 10,
|
||||
paddingBottom: 10,
|
||||
},
|
||||
avi: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 20,
|
||||
resizeMode: 'cover',
|
||||
},
|
||||
layoutContent: {
|
||||
flex: 1,
|
||||
paddingRight: 10,
|
||||
|
|
|
@ -14,8 +14,8 @@ import {
|
|||
} from '../../../state/models/user-follows-view'
|
||||
import {useStores} from '../../../state'
|
||||
import {Link} from '../util/Link'
|
||||
import {UserAvatar} from '../util/UserAvatar'
|
||||
import {s, colors} from '../../lib/styles'
|
||||
import {DEF_AVATER} from '../../lib/assets'
|
||||
|
||||
export const ProfileFollows = observer(function ProfileFollows({
|
||||
name,
|
||||
|
@ -81,7 +81,11 @@ const User = ({item}: {item: FollowItem}) => {
|
|||
<Link style={styles.outer} href={`/profile/${item.name}`} title={item.name}>
|
||||
<View style={styles.layout}>
|
||||
<View style={styles.layoutAvi}>
|
||||
<Image style={styles.avi} source={DEF_AVATER} />
|
||||
<UserAvatar
|
||||
size={40}
|
||||
displayName={item.displayName}
|
||||
name={item.name}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.layoutContent}>
|
||||
<Text style={[s.f15, s.bold]}>{item.displayName}</Text>
|
||||
|
|
|
@ -15,8 +15,9 @@ import {useStores} from '../../../state'
|
|||
import {EditProfileModel} from '../../../state/models/shell'
|
||||
import {pluralize} from '../../lib/strings'
|
||||
import {s, gradients, colors} from '../../lib/styles'
|
||||
import {DEF_AVATER, BANNER} from '../../lib/assets'
|
||||
import {BANNER} from '../../lib/assets'
|
||||
import Toast from '../util/Toast'
|
||||
import {UserAvatar} from '../util/UserAvatar'
|
||||
import {Link} from '../util/Link'
|
||||
|
||||
export const ProfileHeader = observer(function ProfileHeader({
|
||||
|
@ -81,7 +82,9 @@ export const ProfileHeader = observer(function ProfileHeader({
|
|||
return (
|
||||
<View style={styles.outer}>
|
||||
<Image style={styles.banner} source={BANNER} />
|
||||
<Image style={styles.avi} source={DEF_AVATER} />
|
||||
<View style={styles.avi}>
|
||||
<UserAvatar size={80} displayName={view.displayName} name={view.name} />
|
||||
</View>
|
||||
<View style={styles.content}>
|
||||
<View style={[styles.displayNameLine]}>
|
||||
<Text style={styles.displayName}>{view.displayName}</Text>
|
||||
|
@ -178,12 +181,12 @@ const styles = StyleSheet.create({
|
|||
position: 'absolute',
|
||||
top: 80,
|
||||
left: 10,
|
||||
width: 80,
|
||||
height: 80,
|
||||
borderRadius: 40,
|
||||
resizeMode: 'cover',
|
||||
width: 84,
|
||||
height: 84,
|
||||
borderRadius: 42,
|
||||
borderWidth: 2,
|
||||
borderColor: colors.white,
|
||||
backgroundColor: colors.white,
|
||||
},
|
||||
content: {
|
||||
paddingTop: 8,
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
import React from 'react'
|
||||
import Svg, {Circle, Text, Defs, LinearGradient, Stop} from 'react-native-svg'
|
||||
import {colors} from '../../lib/styles'
|
||||
|
||||
const GRADIENTS = [
|
||||
[colors.pink3, colors.purple3],
|
||||
[colors.purple3, colors.blue3],
|
||||
[colors.blue3, colors.green3],
|
||||
[colors.red3, colors.pink3],
|
||||
]
|
||||
|
||||
export function UserAvatar({
|
||||
size,
|
||||
displayName,
|
||||
name,
|
||||
}: {
|
||||
size: number
|
||||
displayName: string | undefined
|
||||
name: string
|
||||
}) {
|
||||
const initials = getInitials(displayName || name)
|
||||
const gi = cyrb53(name) % GRADIENTS.length
|
||||
return (
|
||||
<Svg width={size} height={size} viewBox="0 0 100 100">
|
||||
<Defs>
|
||||
<LinearGradient id="grad" x1="0" y1="0" x2="1" y2="1">
|
||||
<Stop offset="0" stopColor={GRADIENTS[gi][0]} stopOpacity="1" />
|
||||
<Stop offset="1" stopColor={GRADIENTS[gi][1]} stopOpacity="1" />
|
||||
</LinearGradient>
|
||||
</Defs>
|
||||
<Circle cx="50" cy="50" r="50" fill="url(#grad)" />
|
||||
<Text
|
||||
fill="white"
|
||||
fontSize="50"
|
||||
fontWeight="bold"
|
||||
x="50"
|
||||
y="67"
|
||||
textAnchor="middle">
|
||||
{initials}
|
||||
</Text>
|
||||
</Svg>
|
||||
)
|
||||
}
|
||||
|
||||
function getInitials(str: string): string {
|
||||
const tokens = str
|
||||
.split(' ')
|
||||
.filter(Boolean)
|
||||
.map(v => v.trim())
|
||||
if (tokens.length >= 2 && tokens[0][0] && tokens[0][1]) {
|
||||
return tokens[0][0].toUpperCase() + tokens[1][0].toUpperCase()
|
||||
}
|
||||
if (tokens.length === 1 && tokens[0][0]) {
|
||||
return tokens[0][0].toUpperCase()
|
||||
}
|
||||
return 'X'
|
||||
}
|
||||
|
||||
// deterministic string->hash
|
||||
// https://stackoverflow.com/a/52171480
|
||||
function cyrb53(str: string, seed = 0): number {
|
||||
let h1 = 0xdeadbeef ^ seed,
|
||||
h2 = 0x41c6ce57 ^ seed
|
||||
for (let i = 0, ch; i < str.length; i++) {
|
||||
ch = str.charCodeAt(i)
|
||||
h1 = Math.imul(h1 ^ ch, 2654435761)
|
||||
h2 = Math.imul(h2 ^ ch, 1597334677)
|
||||
}
|
||||
|
||||
h1 =
|
||||
Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^
|
||||
Math.imul(h2 ^ (h2 >>> 13), 3266489909)
|
||||
h2 =
|
||||
Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^
|
||||
Math.imul(h1 ^ (h1 >>> 13), 3266489909)
|
||||
|
||||
return 4294967296 * (2097151 & h2) + (h1 >>> 0)
|
||||
}
|
|
@ -4,8 +4,8 @@ import {observer} from 'mobx-react-lite'
|
|||
import {useStores} from '../../state'
|
||||
import {ScreenParams} from '../routes'
|
||||
import {s, colors} from '../lib/styles'
|
||||
import {DEF_AVATER} from '../lib/assets'
|
||||
import {Link} from '../com/util/Link'
|
||||
import {UserAvatar} from '../com/util/UserAvatar'
|
||||
|
||||
export const Settings = observer(function Settings({visible}: ScreenParams) {
|
||||
const store = useStores()
|
||||
|
@ -33,8 +33,12 @@ export const Settings = observer(function Settings({visible}: ScreenParams) {
|
|||
</View>
|
||||
<Link href={`/profile/${store.me.name}`} title="Your profile">
|
||||
<View style={styles.profile}>
|
||||
<Image style={styles.avi} source={DEF_AVATER} />
|
||||
<View>
|
||||
<UserAvatar
|
||||
size={40}
|
||||
displayName={store.me.displayName}
|
||||
name={store.me.name || ''}
|
||||
/>
|
||||
<View style={[s.ml10]}>
|
||||
<Text style={[s.f18]}>{store.me.displayName}</Text>
|
||||
<Text style={[s.gray5]}>@{store.me.name}</Text>
|
||||
</View>
|
||||
|
|
|
@ -19,6 +19,7 @@ import Animated, {
|
|||
import {IconProp} from '@fortawesome/fontawesome-svg-core'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {HomeIcon, UserGroupIcon} from '../../lib/icons'
|
||||
import {UserAvatar} from '../../com/util/UserAvatar'
|
||||
import {useStores} from '../../../state'
|
||||
import {s, colors} from '../../lib/styles'
|
||||
import {DEF_AVATER} from '../../lib/assets'
|
||||
|
@ -131,7 +132,13 @@ export const MainMenu = observer(
|
|||
<TouchableOpacity
|
||||
style={styles.profile}
|
||||
onPress={() => onNavigate(`/profile/${store.me.name || ''}`)}>
|
||||
<Image style={styles.profileImage} source={DEF_AVATER} />
|
||||
<View style={styles.profileImage}>
|
||||
<UserAvatar
|
||||
size={30}
|
||||
displayName={store.me.displayName}
|
||||
name={store.me.name || ''}
|
||||
/>
|
||||
</View>
|
||||
<Text style={styles.profileText} numberOfLines={1}>
|
||||
{store.me.displayName || store.me.name || 'My profile'}
|
||||
</Text>
|
||||
|
@ -231,9 +238,6 @@ const styles = StyleSheet.create({
|
|||
alignItems: 'center',
|
||||
},
|
||||
profileImage: {
|
||||
borderRadius: 15,
|
||||
width: 30,
|
||||
height: 30,
|
||||
marginRight: 8,
|
||||
},
|
||||
profileText: {
|
||||
|
|
|
@ -21,7 +21,6 @@ import Swipeable from 'react-native-gesture-handler/Swipeable'
|
|||
import LinearGradient from 'react-native-linear-gradient'
|
||||
import {useStores} from '../../../state'
|
||||
import {s, colors, gradients} from '../../lib/styles'
|
||||
import {DEF_AVATER} from '../../lib/assets'
|
||||
import {match} from '../../routes'
|
||||
import {LinkActionsModel} from '../../../state/models/shell'
|
||||
|
||||
|
|
30
todos.txt
30
todos.txt
|
@ -4,11 +4,35 @@ Paul's todo list
|
|||
- Update to RN 0.70
|
||||
- Cache some profile/userinfo lookups
|
||||
- Cursor behaviors on all views
|
||||
- Home button should scroll to top
|
||||
- Update swipe behaviors: edge always goes back, leftmost always goes back, main connects to selector if present
|
||||
- Onboarding flow
|
||||
- *
|
||||
- Private beta
|
||||
- Users list
|
||||
- Avatars
|
||||
- SVG generate
|
||||
- Main menu
|
||||
- Scenes list
|
||||
- Create scene view
|
||||
- *
|
||||
- Discover scenes view
|
||||
- *
|
||||
- User profile
|
||||
- Distinguish by declared type
|
||||
- User
|
||||
- List scenes
|
||||
- Invite to scene
|
||||
- Remove from scene
|
||||
- Scene
|
||||
- Trending
|
||||
- Members
|
||||
- Profile header
|
||||
- Invite to scene
|
||||
- Remove from scene
|
||||
- Edit profile
|
||||
- Notifications
|
||||
- Scene invite
|
||||
- Reply gating
|
||||
- Composer
|
||||
- View on post
|
||||
- Linking
|
||||
- Web linking
|
||||
- App linking
|
||||
|
|
Loading…
Reference in New Issue