diff --git a/src/view/com/notifications/FeedItem.tsx b/src/view/com/notifications/FeedItem.tsx
index c498dd6072..8d53e921b7 100644
--- a/src/view/com/notifications/FeedItem.tsx
+++ b/src/view/com/notifications/FeedItem.tsx
@@ -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}`}>
-
+
))}
{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,
diff --git a/src/view/com/post-thread/PostLikedBy.tsx b/src/view/com/post-thread/PostLikedBy.tsx
index fbeb52eea4..071e69fac4 100644
--- a/src/view/com/post-thread/PostLikedBy.tsx
+++ b/src/view/com/post-thread/PostLikedBy.tsx
@@ -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}) => {
-
+
{item.displayName}
diff --git a/src/view/com/post-thread/PostRepostedBy.tsx b/src/view/com/post-thread/PostRepostedBy.tsx
index 6ffe3a2c4c..df462c1a10 100644
--- a/src/view/com/post-thread/PostRepostedBy.tsx
+++ b/src/view/com/post-thread/PostRepostedBy.tsx
@@ -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}) => {
-
+
{item.displayName}
diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx
index e8fdd91af1..4f0683f09d 100644
--- a/src/view/com/post-thread/PostThreadItem.tsx
+++ b/src/view/com/post-thread/PostThreadItem.tsx
@@ -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({
-
+
@@ -231,7 +235,11 @@ export const PostThreadItem = observer(function PostThreadItem({
)}
-
+
{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,
diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx
index 42611a39ad..b74bbfc429 100644
--- a/src/view/com/post/Post.tsx
+++ b/src/view/com/post/Post.tsx
@@ -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}) {
-
+
@@ -185,12 +188,6 @@ const styles = StyleSheet.create({
layoutAvi: {
width: 60,
},
- avi: {
- width: 50,
- height: 50,
- borderRadius: 25,
- resizeMode: 'cover',
- },
layoutContent: {
flex: 1,
},
diff --git a/src/view/com/posts/FeedItem.tsx b/src/view/com/posts/FeedItem.tsx
index 6eb2b38fb6..cfb7d7ed79 100644
--- a/src/view/com/posts/FeedItem.tsx
+++ b/src/view/com/posts/FeedItem.tsx
@@ -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}>
-
+
@@ -199,12 +203,6 @@ const styles = StyleSheet.create({
width: 60,
paddingTop: 5,
},
- avi: {
- width: 50,
- height: 50,
- borderRadius: 25,
- resizeMode: 'cover',
- },
layoutContent: {
flex: 1,
},
diff --git a/src/view/com/profile/ProfileFollowers.tsx b/src/view/com/profile/ProfileFollowers.tsx
index 24f28f6457..30145e7b5a 100644
--- a/src/view/com/profile/ProfileFollowers.tsx
+++ b/src/view/com/profile/ProfileFollowers.tsx
@@ -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}) => {
-
+
{item.displayName}
@@ -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,
diff --git a/src/view/com/profile/ProfileFollows.tsx b/src/view/com/profile/ProfileFollows.tsx
index 719765edf9..56a5371ba3 100644
--- a/src/view/com/profile/ProfileFollows.tsx
+++ b/src/view/com/profile/ProfileFollows.tsx
@@ -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}) => {
-
+
{item.displayName}
diff --git a/src/view/com/profile/ProfileHeader.tsx b/src/view/com/profile/ProfileHeader.tsx
index 9565ae4a8d..6778663a31 100644
--- a/src/view/com/profile/ProfileHeader.tsx
+++ b/src/view/com/profile/ProfileHeader.tsx
@@ -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.displayName}
@@ -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,
diff --git a/src/view/com/util/UserAvatar.tsx b/src/view/com/util/UserAvatar.tsx
new file mode 100644
index 0000000000..b38f1158c1
--- /dev/null
+++ b/src/view/com/util/UserAvatar.tsx
@@ -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 (
+
+ )
+}
+
+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)
+}
diff --git a/src/view/screens/Settings.tsx b/src/view/screens/Settings.tsx
index 0f7d6b89bc..2438ea75df 100644
--- a/src/view/screens/Settings.tsx
+++ b/src/view/screens/Settings.tsx
@@ -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) {
-
-
+
+
{store.me.displayName}
@{store.me.name}
diff --git a/src/view/shell/mobile/MainMenu.tsx b/src/view/shell/mobile/MainMenu.tsx
index ee09d4aa08..85cf237168 100644
--- a/src/view/shell/mobile/MainMenu.tsx
+++ b/src/view/shell/mobile/MainMenu.tsx
@@ -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(
onNavigate(`/profile/${store.me.name || ''}`)}>
-
+
+
+
{store.me.displayName || store.me.name || 'My profile'}
@@ -231,9 +238,6 @@ const styles = StyleSheet.create({
alignItems: 'center',
},
profileImage: {
- borderRadius: 15,
- width: 30,
- height: 30,
marginRight: 8,
},
profileText: {
diff --git a/src/view/shell/mobile/TabsSelector.tsx b/src/view/shell/mobile/TabsSelector.tsx
index 8e3fba41aa..18c5f086bf 100644
--- a/src/view/shell/mobile/TabsSelector.tsx
+++ b/src/view/shell/mobile/TabsSelector.tsx
@@ -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'
diff --git a/todos.txt b/todos.txt
index 8f1a892ea9..d0f3dc057c 100644
--- a/todos.txt
+++ b/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