diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/view/com/notifications/FeedItem.tsx | 14 | ||||
-rw-r--r-- | src/view/com/post-thread/PostLikedBy.tsx | 18 | ||||
-rw-r--r-- | src/view/com/post-thread/PostRepostedBy.tsx | 8 | ||||
-rw-r--r-- | src/view/com/post-thread/PostThreadItem.tsx | 20 | ||||
-rw-r--r-- | src/view/com/post/Post.tsx | 15 | ||||
-rw-r--r-- | src/view/com/posts/FeedItem.tsx | 20 | ||||
-rw-r--r-- | src/view/com/profile/ProfileFollowers.tsx | 14 | ||||
-rw-r--r-- | src/view/com/profile/ProfileFollows.tsx | 8 | ||||
-rw-r--r-- | src/view/com/profile/ProfileHeader.tsx | 15 | ||||
-rw-r--r-- | src/view/com/util/UserAvatar.tsx | 78 | ||||
-rw-r--r-- | src/view/screens/Settings.tsx | 10 | ||||
-rw-r--r-- | src/view/shell/mobile/MainMenu.tsx | 12 | ||||
-rw-r--r-- | src/view/shell/mobile/TabsSelector.tsx | 1 |
13 files changed, 159 insertions, 74 deletions
diff --git a/src/view/com/notifications/FeedItem.tsx b/src/view/com/notifications/FeedItem.tsx index c498dd607..8d53e921b 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}`}> - <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, diff --git a/src/view/com/post-thread/PostLikedBy.tsx b/src/view/com/post-thread/PostLikedBy.tsx index fbeb52eea..071e69fac 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}) => { <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> diff --git a/src/view/com/post-thread/PostRepostedBy.tsx b/src/view/com/post-thread/PostRepostedBy.tsx index 6ffe3a2c4..df462c1a1 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}) => { <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> diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx index e8fdd91af..4f0683f09 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({ <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, diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx index 42611a39a..b74bbfc42 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}) { <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, }, diff --git a/src/view/com/posts/FeedItem.tsx b/src/view/com/posts/FeedItem.tsx index 6eb2b38fb..cfb7d7ed7 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}> - <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, }, diff --git a/src/view/com/profile/ProfileFollowers.tsx b/src/view/com/profile/ProfileFollowers.tsx index 24f28f645..30145e7b5 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}) => { <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, diff --git a/src/view/com/profile/ProfileFollows.tsx b/src/view/com/profile/ProfileFollows.tsx index 719765edf..56a5371ba 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}) => { <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> diff --git a/src/view/com/profile/ProfileHeader.tsx b/src/view/com/profile/ProfileHeader.tsx index 9565ae4a8..6778663a3 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 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, diff --git a/src/view/com/util/UserAvatar.tsx b/src/view/com/util/UserAvatar.tsx new file mode 100644 index 000000000..b38f1158c --- /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 ( + <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) +} diff --git a/src/view/screens/Settings.tsx b/src/view/screens/Settings.tsx index 0f7d6b89b..2438ea75d 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) { </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> diff --git a/src/view/shell/mobile/MainMenu.tsx b/src/view/shell/mobile/MainMenu.tsx index ee09d4aa0..85cf23716 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( <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: { diff --git a/src/view/shell/mobile/TabsSelector.tsx b/src/view/shell/mobile/TabsSelector.tsx index 8e3fba41a..18c5f086b 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' |