Big batch of UI updates (#276)
* Replace react-native-root-toast with a custom toast that fits the visual style * Tune dark mode colors * Tune colors a bit more * Move the reply prompt to a fixed position in the footer * Fully hide muted posts but give a control to show anyway (close #270) * Improve thread rendering (better clarity on reply lines) * Add follower/following counts to side menu * Fix issues with display name overflows
This commit is contained in:
parent
2f3fc4fe4e
commit
e74f94bc72
19 changed files with 381 additions and 249 deletions
|
@ -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,
|
||||
},
|
||||
})
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
|
|
|
@ -96,7 +96,7 @@ export const PostThread = observer(function PostThread({
|
|||
onLayout={onLayout}
|
||||
onScrollToIndexFailed={onScrollToIndexFailed}
|
||||
style={s.hContentRegion}
|
||||
contentContainerStyle={s.contentContainer}
|
||||
contentContainerStyle={s.contentContainerExtra}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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}
|
||||
<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}
|
||||
/>
|
||||
</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}
|
||||
/>
|
||||
</Link>
|
||||
{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 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.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}
|
||||
/>
|
||||
</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',
|
||||
|
|
|
@ -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',
|
||||
|
|
50
src/view/com/util/PostMuted.tsx
Normal file
50
src/view/com/util/PostMuted.tsx
Normal file
|
@ -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',
|
||||
},
|
||||
})
|
|
@ -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,
|
||||
},
|
||||
})
|
||||
|
|
|
@ -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 = (
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
})
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue