diff options
-rw-r--r-- | package.json | 1 | ||||
-rw-r--r-- | src/lib/styles.ts | 4 | ||||
-rw-r--r-- | src/lib/themes.ts | 8 | ||||
-rw-r--r-- | src/state/models/me.ts | 6 | ||||
-rw-r--r-- | src/state/models/post-thread-view.ts | 35 | ||||
-rw-r--r-- | src/view/com/composer/Prompt.tsx | 88 | ||||
-rw-r--r-- | src/view/com/notifications/FeedItem.tsx | 16 | ||||
-rw-r--r-- | src/view/com/post-thread/PostThread.tsx | 2 | ||||
-rw-r--r-- | src/view/com/post-thread/PostThreadItem.tsx | 31 | ||||
-rw-r--r-- | src/view/com/post/Post.tsx | 174 | ||||
-rw-r--r-- | src/view/com/posts/FeedItem.tsx | 34 | ||||
-rw-r--r-- | src/view/com/util/PostMuted.tsx | 50 | ||||
-rw-r--r-- | src/view/com/util/Toast.tsx | 84 | ||||
-rw-r--r-- | src/view/com/util/UserInfoText.tsx | 8 | ||||
-rw-r--r-- | src/view/screens/Debug.tsx | 13 | ||||
-rw-r--r-- | src/view/screens/PostThread.tsx | 42 | ||||
-rw-r--r-- | src/view/shell/mobile/Menu.tsx | 21 | ||||
-rw-r--r-- | src/view/shell/mobile/index.tsx | 10 | ||||
-rw-r--r-- | web/webpack.config.js | 1 |
19 files changed, 380 insertions, 248 deletions
diff --git a/package.json b/package.json index abc02683d..6f70f08b2 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,6 @@ "react-native-progress": "^5.0.0", "react-native-reanimated": "^2.9.1", "react-native-root-siblings": "^4.1.1", - "react-native-root-toast": "^3.4.0", "react-native-safe-area-context": "^4.4.1", "react-native-screens": "^3.13.1", "react-native-splash-screen": "^3.3.0", diff --git a/src/lib/styles.ts b/src/lib/styles.ts index a8c387616..dbce39178 100644 --- a/src/lib/styles.ts +++ b/src/lib/styles.ts @@ -15,7 +15,7 @@ export const colors = { gray5: '#545664', gray6: '#373942', gray7: '#26272D', - gray8: '#101013', + gray8: '#141417', blue0: '#bfe1ff', blue1: '#8bc7fd', @@ -24,6 +24,7 @@ export const colors = { blue4: '#0062bd', blue5: '#034581', blue6: '#012561', + blue7: '#001040', red1: '#ffe6f2', red2: '#fba2ce', @@ -64,6 +65,7 @@ export const s = StyleSheet.create({ // helpers footerSpacer: {height: 100}, contentContainer: {paddingBottom: 200}, + contentContainerExtra: {paddingBottom: 300}, border1: {borderWidth: 1}, borderTop1: {borderTopWidth: 1}, borderRight1: {borderRightWidth: 1}, diff --git a/src/lib/themes.ts b/src/lib/themes.ts index aa166e323..d7043ad2d 100644 --- a/src/lib/themes.ts +++ b/src/lib/themes.ts @@ -21,6 +21,7 @@ export const defaultTheme: Theme = { replyLine: colors.gray2, replyLineDot: colors.gray3, unreadNotifBg: '#ebf6ff', + unreadNotifBorder: colors.blue1, postCtrl: '#71768A', brandText: '#0066FF', emptyStateIcon: '#B6B6C9', @@ -296,15 +297,16 @@ export const darkTheme: Theme = { textLight: colors.gray3, textInverted: colors.black, link: colors.blue3, - border: colors.gray6, - borderDark: colors.gray5, + border: colors.black, + borderDark: colors.gray6, icon: colors.gray4, // non-standard textVeryLight: colors.gray4, replyLine: colors.gray5, replyLineDot: colors.gray6, - unreadNotifBg: colors.blue5, + unreadNotifBg: colors.blue7, + unreadNotifBorder: colors.blue6, postCtrl: '#61657A', brandText: '#0085ff', emptyStateIcon: colors.gray4, diff --git a/src/state/models/me.ts b/src/state/models/me.ts index ea35cd028..077c65595 100644 --- a/src/state/models/me.ts +++ b/src/state/models/me.ts @@ -11,6 +11,8 @@ export class MeModel { displayName: string = '' description: string = '' avatar: string = '' + followsCount: number | undefined + followersCount: number | undefined mainFeed: FeedModel notifications: NotificationsViewModel follows: MyFollowsModel @@ -90,10 +92,14 @@ export class MeModel { this.displayName = profile.data.displayName || '' this.description = profile.data.description || '' this.avatar = profile.data.avatar || '' + this.followsCount = profile.data.followsCount + this.followersCount = profile.data.followersCount } else { this.displayName = '' this.description = '' this.avatar = '' + this.followsCount = profile.data.followsCount + this.followersCount = undefined } }) this.mainFeed.clear() diff --git a/src/state/models/post-thread-view.ts b/src/state/models/post-thread-view.ts index ad989cc53..d58ee691b 100644 --- a/src/state/models/post-thread-view.ts +++ b/src/state/models/post-thread-view.ts @@ -21,6 +21,8 @@ export class PostThreadViewPostModel { _reactKey: string = '' _depth = 0 _isHighlightedPost = false + _showParentReplyLine = false + _showChildReplyLine = false _hasMore = false // data @@ -30,6 +32,14 @@ export class PostThreadViewPostModel { replies?: (PostThreadViewPostModel | GetPostThread.NotFoundPost)[] richText?: RichText + get uri() { + return this.post.uri + } + + get parentUri() { + return this.postRecord?.reply?.parent.uri + } + constructor( public rootStore: RootStoreModel, reactKey: string, @@ -65,6 +75,7 @@ export class PostThreadViewPostModel { assignTreeModels( keyGen: Generator<string>, v: GetPostThread.ThreadViewPost, + higlightedPostUri: string, includeParent = true, includeChildren = true, ) { @@ -77,8 +88,16 @@ export class PostThreadViewPostModel { v.parent, ) parentModel._depth = this._depth - 1 + parentModel._showChildReplyLine = true if (v.parent.parent) { - parentModel.assignTreeModels(keyGen, v.parent, true, false) + parentModel._showParentReplyLine = true //parentModel.uri !== higlightedPostUri + parentModel.assignTreeModels( + keyGen, + v.parent, + higlightedPostUri, + true, + false, + ) } this.parent = parentModel } else if (GetPostThread.isNotFoundPost(v.parent)) { @@ -96,8 +115,17 @@ export class PostThreadViewPostModel { item, ) itemModel._depth = this._depth + 1 - if (item.replies) { - itemModel.assignTreeModels(keyGen, item, false, true) + itemModel._showParentReplyLine = + itemModel.parentUri !== higlightedPostUri + if (item.replies?.length) { + itemModel._showChildReplyLine = true + itemModel.assignTreeModels( + keyGen, + item, + higlightedPostUri, + false, + true, + ) } replies.push(itemModel) } else if (GetPostThread.isNotFoundPost(item)) { @@ -333,6 +361,7 @@ export class PostThreadViewModel { thread.assignTreeModels( keyGen, res.data.thread as GetPostThread.ThreadViewPost, + thread.uri, ) this.thread = thread } diff --git a/src/view/com/composer/Prompt.tsx b/src/view/com/composer/Prompt.tsx index 46a0cec62..88d5de2bf 100644 --- a/src/view/com/composer/Prompt.tsx +++ b/src/view/com/composer/Prompt.tsx @@ -1,91 +1,45 @@ import React from 'react' -import {StyleSheet, TouchableOpacity, View} from 'react-native' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {StyleSheet, TouchableOpacity} from 'react-native' +import {UserAvatar} from '../util/UserAvatar' import {Text} from '../util/text/Text' import {usePalette} from 'lib/hooks/usePalette' +import {useStores} from 'state/index' export function ComposePrompt({ - text = "What's up?", - btn = 'Post', - isReply = false, onPressCompose, }: { - text?: string - btn?: string - isReply?: boolean onPressCompose: (imagesOpen?: boolean) => void }) { + const store = useStores() const pal = usePalette('default') return ( <TouchableOpacity - testID="composePromptButton" - style={[ - pal.view, - pal.border, - styles.container, - isReply ? styles.containerReply : undefined, - ]} + testID="replyPromptBtn" + style={[pal.view, pal.border, styles.prompt]} onPress={() => onPressCompose()}> - {!isReply && ( - <FontAwesomeIcon - icon={['fas', 'pen-nib']} - size={18} - style={[pal.textLight, styles.iconLeft]} - /> - )} - <View style={styles.textContainer}> - <Text type={isReply ? 'lg' : 'lg-medium'} style={pal.textLight}> - {text} - </Text> - </View> - {isReply ? ( - <View - style={[styles.btn, {backgroundColor: pal.colors.backgroundLight}]}> - <Text type="button" style={pal.textLight}> - {btn} - </Text> - </View> - ) : ( - <TouchableOpacity onPress={() => onPressCompose(true)}> - <FontAwesomeIcon - icon={['far', 'image']} - size={18} - style={[pal.textLight, styles.iconRight]} - /> - </TouchableOpacity> - )} + <UserAvatar + handle={store.me.handle} + avatar={store.me.avatar} + displayName={store.me.displayName} + size={38} + /> + <Text type="xl" style={[pal.text, styles.label]}> + Write your reply + </Text> </TouchableOpacity> ) } const styles = StyleSheet.create({ - iconLeft: { - marginLeft: 22, - marginRight: 2, - }, - iconRight: { - marginRight: 20, - }, - container: { - paddingVertical: 16, + prompt: { + paddingHorizontal: 20, + paddingTop: 10, + paddingBottom: 10, flexDirection: 'row', alignItems: 'center', borderTopWidth: 1, }, - containerReply: { - paddingVertical: 14, - paddingHorizontal: 10, - }, - avatar: { - width: 50, - }, - textContainer: { - marginLeft: 10, - flex: 1, - }, - btn: { - paddingVertical: 6, - paddingHorizontal: 14, - borderRadius: 30, + label: { + paddingLeft: 12, }, }) diff --git a/src/view/com/notifications/FeedItem.tsx b/src/view/com/notifications/FeedItem.tsx index 68f12b721..acd00a67d 100644 --- a/src/view/com/notifications/FeedItem.tsx +++ b/src/view/com/notifications/FeedItem.tsx @@ -90,10 +90,10 @@ export const FeedItem = observer(function FeedItem({ style={ item.isRead ? undefined - : [ - styles.outerUnread, - {backgroundColor: pal.colors.unreadNotifBg}, - ] + : { + backgroundColor: pal.colors.unreadNotifBg, + borderColor: pal.colors.unreadNotifBorder, + } } /> </Link> @@ -152,7 +152,10 @@ export const FeedItem = observer(function FeedItem({ pal.border, item.isRead ? undefined - : [styles.outerUnread, {backgroundColor: pal.colors.unreadNotifBg}], + : { + backgroundColor: pal.colors.unreadNotifBg, + borderColor: pal.colors.unreadNotifBorder, + }, ]} href={itemHref} title={itemTitle} @@ -391,9 +394,6 @@ const styles = StyleSheet.create({ paddingRight: 15, borderTopWidth: 1, }, - outerUnread: { - borderColor: colors.blue1, - }, layout: { flexDirection: 'row', }, diff --git a/src/view/com/post-thread/PostThread.tsx b/src/view/com/post-thread/PostThread.tsx index a417012b0..646d4b276 100644 --- a/src/view/com/post-thread/PostThread.tsx +++ b/src/view/com/post-thread/PostThread.tsx @@ -96,7 +96,7 @@ export const PostThread = observer(function PostThread({ onLayout={onLayout} onScrollToIndexFailed={onScrollToIndexFailed} style={s.hContentRegion} - contentContainerStyle={s.contentContainer} + contentContainerStyle={s.contentContainerExtra} /> ) }) diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx index 8eda0962a..1413148a9 100644 --- a/src/view/com/post-thread/PostThreadItem.tsx +++ b/src/view/com/post-thread/PostThreadItem.tsx @@ -21,8 +21,8 @@ import {useStores} from 'state/index' import {PostMeta} from '../util/PostMeta' import {PostEmbeds} from '../util/PostEmbeds' import {PostCtrls} from '../util/PostCtrls' +import {PostMutedWrapper} from '../util/PostMuted' import {ErrorMessage} from '../util/error/ErrorMessage' -import {ComposePrompt} from '../composer/Prompt' import {usePalette} from 'lib/hooks/usePalette' const PARENT_REPLY_LINE_LENGTH = 8 @@ -271,23 +271,17 @@ export const PostThreadItem = observer(function PostThreadItem({ </View> </View> </View> - <ComposePrompt - isReply - text="Write your reply" - btn="Reply" - onPressCompose={onPressReply} - /> </> ) } else { return ( - <> + <PostMutedWrapper isMuted={item.post.author.viewer?.muted === true}> <Link style={[styles.outer, {borderTopColor: pal.colors.border}, pal.view]} href={itemHref} title={itemTitle} noFeedback> - {record.reply && ( + {item._showParentReplyLine && ( <View style={[ styles.parentReplyLine, @@ -295,7 +289,7 @@ export const PostThreadItem = observer(function PostThreadItem({ ]} /> )} - {item.replies?.length !== 0 && ( + {item._showChildReplyLine && ( <View style={[ styles.childReplyLine, @@ -322,12 +316,7 @@ export const PostThreadItem = observer(function PostThreadItem({ did={item.post.author.did} declarationCid={item.post.author.declaration.cid} /> - {item.post.author.viewer?.muted ? ( - <View style={[styles.mutedWarning, pal.btn]}> - <FontAwesomeIcon icon={['far', 'eye-slash']} style={s.mr2} /> - <Text type="sm">This post is by a muted account.</Text> - </View> - ) : item.richText?.text ? ( + {item.richText?.text ? ( <View style={styles.postTextContainer}> <RichText type="post-text" @@ -384,7 +373,7 @@ export const PostThreadItem = observer(function PostThreadItem({ /> </Link> ) : undefined} - </> + </PostMutedWrapper> ) } }) @@ -441,14 +430,6 @@ const styles = StyleSheet.create({ paddingRight: 5, maxWidth: 240, }, - mutedWarning: { - flexDirection: 'row', - alignItems: 'center', - padding: 10, - marginTop: 2, - marginBottom: 6, - borderRadius: 2, - }, postTextContainer: { flexDirection: 'row', alignItems: 'center', diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx index bf8dfed05..7b4161afc 100644 --- a/src/view/com/post/Post.tsx +++ b/src/view/com/post/Post.tsx @@ -17,6 +17,7 @@ import {UserInfoText} from '../util/UserInfoText' import {PostMeta} from '../util/PostMeta' import {PostEmbeds} from '../util/PostEmbeds' import {PostCtrls} from '../util/PostCtrls' +import {PostMutedWrapper} from '../util/PostMuted' import {Text} from '../util/text/Text' import {RichText} from '../util/text/RichText' import * as Toast from '../util/Toast' @@ -140,92 +141,89 @@ export const Post = observer(function Post({ } return ( - <Link - style={[styles.outer, pal.view, pal.border, style]} - href={itemHref} - title={itemTitle} - noFeedback> - {showReplyLine && <View style={styles.replyLine} />} - <View style={styles.layout}> - <View style={styles.layoutAvi}> - <Link href={authorHref} title={authorTitle}> - <UserAvatar - size={52} - displayName={item.post.author.displayName} - handle={item.post.author.handle} - avatar={item.post.author.avatar} - /> - </Link> - </View> - <View style={styles.layoutContent}> - <PostMeta - authorHandle={item.post.author.handle} - authorDisplayName={item.post.author.displayName} - timestamp={item.post.indexedAt} - did={item.post.author.did} - declarationCid={item.post.author.declaration.cid} - /> - {replyAuthorDid !== '' && ( - <View style={[s.flexRow, s.mb2, s.alignCenter]}> - <FontAwesomeIcon - icon="reply" - size={9} - style={[pal.textLight, s.mr5]} - /> - <Text type="sm" style={[pal.textLight, s.mr2]} lineHeight={1.2}> - Reply to - </Text> - <UserInfoText - type="sm" - did={replyAuthorDid} - attr="displayName" - style={[pal.textLight]} + <PostMutedWrapper isMuted={item.post.author.viewer?.muted === true}> + <Link + style={[styles.outer, pal.view, pal.border, style]} + href={itemHref} + title={itemTitle} + noFeedback> + {showReplyLine && <View style={styles.replyLine} />} + <View style={styles.layout}> + <View style={styles.layoutAvi}> + <Link href={authorHref} title={authorTitle}> + <UserAvatar + size={52} + displayName={item.post.author.displayName} + handle={item.post.author.handle} + avatar={item.post.author.avatar} /> - </View> - )} - {item.post.author.viewer?.muted ? ( - <View style={[styles.mutedWarning, pal.btn]}> - <FontAwesomeIcon icon={['far', 'eye-slash']} style={s.mr2} /> - <Text type="sm">This post is by a muted account.</Text> - </View> - ) : item.richText?.text ? ( - <View style={styles.postTextContainer}> - <RichText - type="post-text" - richText={item.richText} - lineHeight={1.3} - /> - </View> - ) : undefined} - <PostEmbeds embed={item.post.embed} style={s.mb10} /> - <PostCtrls - itemUri={itemUri} - itemCid={itemCid} - itemHref={itemHref} - itemTitle={itemTitle} - author={{ - avatar: item.post.author.avatar!, - handle: item.post.author.handle, - displayName: item.post.author.displayName!, - }} - indexedAt={item.post.indexedAt} - text={item.richText?.text || record.text} - isAuthor={item.post.author.did === store.me.did} - replyCount={item.post.replyCount} - repostCount={item.post.repostCount} - upvoteCount={item.post.upvoteCount} - isReposted={!!item.post.viewer.repost} - isUpvoted={!!item.post.viewer.upvote} - onPressReply={onPressReply} - onPressToggleRepost={onPressToggleRepost} - onPressToggleUpvote={onPressToggleUpvote} - onCopyPostText={onCopyPostText} - onOpenTranslate={onOpenTranslate} - onDeletePost={onDeletePost} - /> + </Link> + </View> + <View style={styles.layoutContent}> + <PostMeta + authorHandle={item.post.author.handle} + authorDisplayName={item.post.author.displayName} + timestamp={item.post.indexedAt} + did={item.post.author.did} + declarationCid={item.post.author.declaration.cid} + /> + {replyAuthorDid !== '' && ( + <View style={[s.flexRow, s.mb2, s.alignCenter]}> + <FontAwesomeIcon + icon="reply" + size={9} + style={[pal.textLight, s.mr5]} + /> + <Text type="sm" style={[pal.textLight, s.mr2]} lineHeight={1.2}> + Reply to + </Text> + <UserInfoText + type="sm" + did={replyAuthorDid} + attr="displayName" + style={[pal.textLight]} + /> + </View> + )} + {item.richText?.text ? ( + <View style={styles.postTextContainer}> + <RichText + type="post-text" + richText={item.richText} + lineHeight={1.3} + /> + </View> + ) : undefined} + <PostEmbeds embed={item.post.embed} style={s.mb10} /> + <PostCtrls + itemUri={itemUri} + itemCid={itemCid} + itemHref={itemHref} + itemTitle={itemTitle} + author={{ + avatar: item.post.author.avatar!, + handle: item.post.author.handle, + displayName: item.post.author.displayName!, + }} + indexedAt={item.post.indexedAt} + text={item.richText?.text || record.text} + isAuthor={item.post.author.did === store.me.did} + replyCount={item.post.replyCount} + repostCount={item.post.repostCount} + upvoteCount={item.post.upvoteCount} + isReposted={!!item.post.viewer.repost} + isUpvoted={!!item.post.viewer.upvote} + onPressReply={onPressReply} + onPressToggleRepost={onPressToggleRepost} + onPressToggleUpvote={onPressToggleUpvote} + onCopyPostText={onCopyPostText} + onOpenTranslate={onOpenTranslate} + onDeletePost={onDeletePost} + /> + </View> </View> - </View> - </Link> + </Link> + </PostMutedWrapper> ) }) @@ -245,14 +243,6 @@ const styles = StyleSheet.create({ layoutContent: { flex: 1, }, - mutedWarning: { - flexDirection: 'row', - alignItems: 'center', - padding: 10, - marginTop: 2, - marginBottom: 6, - borderRadius: 2, - }, postTextContainer: { flexDirection: 'row', alignItems: 'center', diff --git a/src/view/com/posts/FeedItem.tsx b/src/view/com/posts/FeedItem.tsx index 1006645a9..8b9a6eb2c 100644 --- a/src/view/com/posts/FeedItem.tsx +++ b/src/view/com/posts/FeedItem.tsx @@ -15,6 +15,7 @@ import {UserInfoText} from '../util/UserInfoText' import {PostMeta} from '../util/PostMeta' import {PostCtrls} from '../util/PostCtrls' import {PostEmbeds} from '../util/PostEmbeds' +import {PostMutedWrapper} from '../util/PostMuted' import {RichText} from '../util/text/RichText' import * as Toast from '../util/Toast' import {UserAvatar} from '../util/UserAvatar' @@ -113,6 +114,8 @@ export const FeedItem = observer(function ({ item._isThreadChild || (!item.reason && !item._hideParent && item.reply) const isSmallTop = isChild && item._isThreadChild const isNoTop = isChild && !item._isThreadChild + const isMuted = + item.post.author.viewer?.muted && ignoreMuteFor !== item.post.author.did const outerStyles = [ styles.outer, pal.view, @@ -123,7 +126,7 @@ export const FeedItem = observer(function ({ ] return ( - <> + <PostMutedWrapper isMuted={isMuted}> {isChild && !item._isThreadChild && item.replyParent ? ( <FeedItem item={item.replyParent} @@ -160,7 +163,11 @@ export const FeedItem = observer(function ({ {color: pal.colors.textLight} as FontAwesomeIconStyle, ]} /> - <Text type="sm-bold" style={pal.textLight} lineHeight={1.2}> + <Text + type="sm-bold" + style={pal.textLight} + lineHeight={1.2} + numberOfLines={1}> Reposted by{' '} {item.reasonRepost.by.displayName || item.reasonRepost.by.handle} </Text> @@ -207,13 +214,7 @@ export const FeedItem = observer(function ({ /> </View> )} - {item.post.author.viewer?.muted && - ignoreMuteFor !== item.post.author.did ? ( - <View style={[styles.mutedWarning, pal.btn]}> - <FontAwesomeIcon icon={['far', 'eye-slash']} style={s.mr2} /> - <Text type="sm">This post is by a muted account.</Text> - </View> - ) : item.richText?.text ? ( + {item.richText?.text ? ( <View style={styles.postTextContainer}> <RichText type="post-text" @@ -222,9 +223,7 @@ export const FeedItem = observer(function ({ /> </View> ) : undefined} - {item.post.embed ? ( - <PostEmbeds embed={item.post.embed} style={styles.embed} /> - ) : null} + <PostEmbeds embed={item.post.embed} style={styles.embed} /> <PostCtrls style={styles.ctrls} itemUri={itemUri} @@ -280,7 +279,7 @@ export const FeedItem = observer(function ({ </Text> </Link> ) : undefined} - </> + </PostMutedWrapper> ) }) @@ -319,6 +318,7 @@ const styles = StyleSheet.create({ includeReason: { flexDirection: 'row', paddingLeft: 50, + paddingRight: 20, marginTop: 2, marginBottom: 2, }, @@ -336,14 +336,6 @@ const styles = StyleSheet.create({ layoutContent: { flex: 1, }, - mutedWarning: { - flexDirection: 'row', - alignItems: 'center', - padding: 10, - marginTop: 2, - marginBottom: 6, - borderRadius: 2, - }, postTextContainer: { flexDirection: 'row', alignItems: 'center', diff --git a/src/view/com/util/PostMuted.tsx b/src/view/com/util/PostMuted.tsx new file mode 100644 index 000000000..d8573bd56 --- /dev/null +++ b/src/view/com/util/PostMuted.tsx @@ -0,0 +1,50 @@ +import React from 'react' +import {StyleSheet, TouchableOpacity, View} from 'react-native' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {usePalette} from 'lib/hooks/usePalette' +import {Text} from './text/Text' + +export function PostMutedWrapper({ + isMuted, + children, +}: React.PropsWithChildren<{isMuted: boolean}>) { + const pal = usePalette('default') + const [override, setOverride] = React.useState(false) + if (!isMuted || override) { + return <>{children}</> + } + return ( + <View style={[styles.container, pal.view, pal.border]}> + <FontAwesomeIcon + icon={['far', 'eye-slash']} + style={[styles.icon, pal.text]} + /> + <Text type="md" style={pal.textLight}> + Post from an account you muted. + </Text> + <TouchableOpacity + style={styles.showBtn} + onPress={() => setOverride(true)}> + <Text type="md" style={pal.link}> + Show post + </Text> + </TouchableOpacity> + </View> + ) +} + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + alignItems: 'center', + paddingVertical: 14, + paddingHorizontal: 18, + borderTopWidth: 1, + }, + icon: { + marginRight: 10, + }, + showBtn: { + marginLeft: 'auto', + }, +}) diff --git a/src/view/com/util/Toast.tsx b/src/view/com/util/Toast.tsx index 197f47422..34a461f82 100644 --- a/src/view/com/util/Toast.tsx +++ b/src/view/com/util/Toast.tsx @@ -1,11 +1,81 @@ -import Toast from 'react-native-root-toast' +import RootSiblings from 'react-native-root-siblings' +import React from 'react' +import {Animated, StyleSheet, View} from 'react-native' +import {Text} from './text/Text' +import {colors} from 'lib/styles' +import {useTheme} from 'lib/ThemeContext' +import {usePalette} from 'lib/hooks/usePalette' +import {useAnimatedValue} from 'lib/hooks/useAnimatedValue' + +const TIMEOUT = 4e3 export function show(message: string) { - Toast.show(message, { - duration: Toast.durations.LONG, - position: 50, - shadow: true, - animation: true, - hideOnPress: true, + const item = new RootSiblings(<Toast message={message} />) + setTimeout(() => { + item.destroy() + }, TIMEOUT) +} + +function Toast({message}: {message: string}) { + const theme = useTheme() + const pal = usePalette('default') + const interp = useAnimatedValue(0) + + React.useEffect(() => { + Animated.sequence([ + Animated.timing(interp, { + toValue: 1, + duration: 150, + useNativeDriver: true, + }), + Animated.delay(3700), + Animated.timing(interp, { + toValue: 0, + duration: 150, + useNativeDriver: true, + }), + ]).start() }) + + const opacityStyle = {opacity: interp} + return ( + <View style={styles.container}> + <Animated.View + style={[ + pal.view, + pal.border, + styles.toast, + theme.colorScheme === 'dark' && styles.toastDark, + opacityStyle, + ]}> + <Text type="lg-medium" style={pal.text}> + {message} + </Text> + </Animated.View> + </View> + ) } + +const styles = StyleSheet.create({ + container: { + position: 'absolute', + top: 60, + left: 0, + right: 0, + alignItems: 'center', + }, + toast: { + paddingHorizontal: 18, + paddingVertical: 10, + borderRadius: 24, + borderWidth: 1, + shadowColor: '#000', + shadowOpacity: 0.1, + shadowOffset: {width: 0, height: 4}, + marginHorizontal: 6, + }, + toastDark: { + backgroundColor: colors.gray6, + shadowOpacity: 0.5, + }, +}) diff --git a/src/view/com/util/UserInfoText.tsx b/src/view/com/util/UserInfoText.tsx index 2655232fc..84170b3bf 100644 --- a/src/view/com/util/UserInfoText.tsx +++ b/src/view/com/util/UserInfoText.tsx @@ -58,15 +58,15 @@ export function UserInfoText({ let inner if (didFail) { inner = ( - <Text type={type} style={style}> + <Text type={type} style={style} numberOfLines={1}> {failed} </Text> ) } else if (profile) { inner = ( - <Text type={type} style={style} lineHeight={1.2}>{`${prefix || ''}${ - profile[attr] || profile.handle - }`}</Text> + <Text type={type} style={style} lineHeight={1.2} numberOfLines={1}>{`${ + prefix || '' + }${profile[attr] || profile.handle}`}</Text> ) } else { inner = ( diff --git a/src/view/screens/Debug.tsx b/src/view/screens/Debug.tsx index f2349195e..eb5ffe20f 100644 --- a/src/view/screens/Debug.tsx +++ b/src/view/screens/Debug.tsx @@ -5,6 +5,7 @@ import {ThemeProvider, PaletteColorName} from 'lib/ThemeContext' import {usePalette} from 'lib/hooks/usePalette' import {s} from 'lib/styles' import {displayNotification} from 'lib/notifee' +import * as Toast from 'view/com/util/Toast' import {Text} from '../com/util/text/Text' import {ViewSelector} from '../com/util/ViewSelector' @@ -171,16 +172,24 @@ function ErrorView() { } function NotifsView() { - const trigger = () => { + const triggerPush = () => { displayNotification( 'Paul Frazee liked your post', "Hello world! This is a test of the notifications card. The text is long to see how that's handled.", ) } + const triggerToast = () => { + Toast.show('The task has been completed') + } + const triggerToast2 = () => { + Toast.show('The task has been completed successfully and with no problems') + } return ( <View style={s.p10}> <View style={s.flexRow}> - <Button onPress={trigger} label="Trigger" /> + <Button onPress={triggerPush} label="Trigger Push" /> + <Button onPress={triggerToast} label="Trigger Toast" /> + <Button onPress={triggerToast2} label="Trigger Toast 2" /> </View> </View> ) diff --git a/src/view/screens/PostThread.tsx b/src/view/screens/PostThread.tsx index e93fcb1ab..0b6829735 100644 --- a/src/view/screens/PostThread.tsx +++ b/src/view/screens/PostThread.tsx @@ -1,15 +1,21 @@ import React, {useEffect, useMemo} from 'react' -import {View} from 'react-native' +import {StyleSheet, View} from 'react-native' import {makeRecordUri} from 'lib/strings/url-helpers' import {ViewHeader} from '../com/util/ViewHeader' import {PostThread as PostThreadComponent} from '../com/post-thread/PostThread' +import {ComposePrompt} from 'view/com/composer/Prompt' import {PostThreadViewModel} from 'state/models/post-thread-view' import {ScreenParams} from '../routes' import {useStores} from 'state/index' import {s} from 'lib/styles' +import {useSafeAreaInsets} from 'react-native-safe-area-context' +import {clamp} from 'lodash' + +const SHELL_FOOTER_HEIGHT = 44 export const PostThread = ({navIdx, visible, params}: ScreenParams) => { const store = useStores() + const safeAreaInsets = useSafeAreaInsets() const {name, rkey} = params const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey) const view = useMemo<PostThreadViewModel>( @@ -48,12 +54,46 @@ export const PostThread = ({navIdx, visible, params}: ScreenParams) => { } }, [visible, store.nav, store.log, store.shell, name, navIdx, view]) + const onPressReply = React.useCallback(() => { + if (!view.thread) { + return + } + store.shell.openComposer({ + replyTo: { + uri: view.thread.post.uri, + cid: view.thread.post.cid, + text: view.thread.postRecord?.text as string, + author: { + handle: view.thread.post.author.handle, + displayName: view.thread.post.author.displayName, + avatar: view.thread.post.author.avatar, + }, + }, + onPost: () => view.refresh(), + }) + }, [view, store]) + return ( <View style={s.hContentRegion}> <ViewHeader title="Post" /> <View style={s.hContentRegion}> <PostThreadComponent uri={uri} view={view} /> </View> + <View + style={[ + styles.prompt, + {bottom: SHELL_FOOTER_HEIGHT + clamp(safeAreaInsets.bottom, 15, 30)}, + ]}> + <ComposePrompt onPressCompose={onPressReply} /> + </View> </View> ) } + +const styles = StyleSheet.create({ + prompt: { + position: 'absolute', + left: 0, + right: 0, + }, +}) diff --git a/src/view/shell/mobile/Menu.tsx b/src/view/shell/mobile/Menu.tsx index bc487aee2..927e712e1 100644 --- a/src/view/shell/mobile/Menu.tsx +++ b/src/view/shell/mobile/Menu.tsx @@ -32,6 +32,7 @@ import {Text} from '../../com/util/text/Text' import {useTheme} from 'lib/ThemeContext' import {usePalette} from 'lib/hooks/usePalette' import {useAnalytics} from 'lib/analytics' +import {pluralize} from 'lib/strings/helpers' export const Menu = observer(({onClose}: {onClose: () => void}) => { const theme = useTheme() @@ -138,6 +139,16 @@ export const Menu = observer(({onClose}: {onClose: () => void}) => { <Text type="2xl" style={[pal.textLight, styles.profileCardHandle]}> @{store.me.handle} </Text> + <Text type="xl" style={[pal.textLight, styles.profileCardFollowers]}> + <Text type="xl-medium" style={pal.text}> + {store.me.followersCount || 0} + </Text>{' '} + {pluralize(store.me.followersCount || 0, 'follower')} ·{' '} + <Text type="xl-medium" style={pal.text}> + {store.me.followsCount || 0} + </Text>{' '} + following + </Text> </TouchableOpacity> <View style={s.flex1} /> <View> @@ -267,12 +278,12 @@ export const Menu = observer(({onClose}: {onClose: () => void}) => { const styles = StyleSheet.create({ view: { flex: 1, - paddingTop: 10, + paddingTop: 20, paddingBottom: 50, paddingLeft: 30, }, viewDarkMode: { - backgroundColor: '#202023', + backgroundColor: '#1B1919', }, profileCardDisplayName: { @@ -283,6 +294,10 @@ const styles = StyleSheet.create({ marginTop: 4, paddingRight: 20, }, + profileCardFollowers: { + marginTop: 16, + paddingRight: 20, + }, menuItem: { flexDirection: 'row', @@ -316,7 +331,7 @@ const styles = StyleSheet.create({ flexDirection: 'row', justifyContent: 'space-between', paddingRight: 30, - paddingTop: 20, + paddingTop: 80, }, footerBtn: { flexDirection: 'row', diff --git a/src/view/shell/mobile/index.tsx b/src/view/shell/mobile/index.tsx index b836bb76d..01df6c165 100644 --- a/src/view/shell/mobile/index.tsx +++ b/src/view/shell/mobile/index.tsx @@ -157,7 +157,7 @@ export const MobileShell: React.FC = observer(() => { } const screenBg = { - backgroundColor: theme.colorScheme === 'dark' ? colors.gray7 : colors.gray1, + backgroundColor: theme.colorScheme === 'dark' ? colors.black : colors.gray1, } return ( <View testID="mobileShellView" style={[styles.outerContainer, pal.view]}> @@ -202,13 +202,7 @@ export const MobileShell: React.FC = observer(() => { style={[ s.h100pct, screenBg, - current - ? [ - swipeTransform, - // tabMenuTransform, TODO - // isRunningNewTabAnim ? newTabTransform : undefined, TODO - ] - : undefined, + current ? [swipeTransform] : undefined, ]}> <ErrorBoundary> <Com diff --git a/web/webpack.config.js b/web/webpack.config.js index 18056190d..d74cdd542 100644 --- a/web/webpack.config.js +++ b/web/webpack.config.js @@ -9,7 +9,6 @@ const uncompiled_deps = [ '@bam.tech/react-native-image-resizer', 'react-native-fs', 'rn-fetch-blob', - 'react-native-root-toast', 'react-native-root-siblings', 'react-native-linear-gradient', ] |