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 overflowszio/stable
parent
2f3fc4fe4e
commit
e74f94bc72
|
@ -70,7 +70,6 @@
|
||||||
"react-native-progress": "^5.0.0",
|
"react-native-progress": "^5.0.0",
|
||||||
"react-native-reanimated": "^2.9.1",
|
"react-native-reanimated": "^2.9.1",
|
||||||
"react-native-root-siblings": "^4.1.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-safe-area-context": "^4.4.1",
|
||||||
"react-native-screens": "^3.13.1",
|
"react-native-screens": "^3.13.1",
|
||||||
"react-native-splash-screen": "^3.3.0",
|
"react-native-splash-screen": "^3.3.0",
|
||||||
|
|
|
@ -15,7 +15,7 @@ export const colors = {
|
||||||
gray5: '#545664',
|
gray5: '#545664',
|
||||||
gray6: '#373942',
|
gray6: '#373942',
|
||||||
gray7: '#26272D',
|
gray7: '#26272D',
|
||||||
gray8: '#101013',
|
gray8: '#141417',
|
||||||
|
|
||||||
blue0: '#bfe1ff',
|
blue0: '#bfe1ff',
|
||||||
blue1: '#8bc7fd',
|
blue1: '#8bc7fd',
|
||||||
|
@ -24,6 +24,7 @@ export const colors = {
|
||||||
blue4: '#0062bd',
|
blue4: '#0062bd',
|
||||||
blue5: '#034581',
|
blue5: '#034581',
|
||||||
blue6: '#012561',
|
blue6: '#012561',
|
||||||
|
blue7: '#001040',
|
||||||
|
|
||||||
red1: '#ffe6f2',
|
red1: '#ffe6f2',
|
||||||
red2: '#fba2ce',
|
red2: '#fba2ce',
|
||||||
|
@ -64,6 +65,7 @@ export const s = StyleSheet.create({
|
||||||
// helpers
|
// helpers
|
||||||
footerSpacer: {height: 100},
|
footerSpacer: {height: 100},
|
||||||
contentContainer: {paddingBottom: 200},
|
contentContainer: {paddingBottom: 200},
|
||||||
|
contentContainerExtra: {paddingBottom: 300},
|
||||||
border1: {borderWidth: 1},
|
border1: {borderWidth: 1},
|
||||||
borderTop1: {borderTopWidth: 1},
|
borderTop1: {borderTopWidth: 1},
|
||||||
borderRight1: {borderRightWidth: 1},
|
borderRight1: {borderRightWidth: 1},
|
||||||
|
|
|
@ -21,6 +21,7 @@ export const defaultTheme: Theme = {
|
||||||
replyLine: colors.gray2,
|
replyLine: colors.gray2,
|
||||||
replyLineDot: colors.gray3,
|
replyLineDot: colors.gray3,
|
||||||
unreadNotifBg: '#ebf6ff',
|
unreadNotifBg: '#ebf6ff',
|
||||||
|
unreadNotifBorder: colors.blue1,
|
||||||
postCtrl: '#71768A',
|
postCtrl: '#71768A',
|
||||||
brandText: '#0066FF',
|
brandText: '#0066FF',
|
||||||
emptyStateIcon: '#B6B6C9',
|
emptyStateIcon: '#B6B6C9',
|
||||||
|
@ -296,15 +297,16 @@ export const darkTheme: Theme = {
|
||||||
textLight: colors.gray3,
|
textLight: colors.gray3,
|
||||||
textInverted: colors.black,
|
textInverted: colors.black,
|
||||||
link: colors.blue3,
|
link: colors.blue3,
|
||||||
border: colors.gray6,
|
border: colors.black,
|
||||||
borderDark: colors.gray5,
|
borderDark: colors.gray6,
|
||||||
icon: colors.gray4,
|
icon: colors.gray4,
|
||||||
|
|
||||||
// non-standard
|
// non-standard
|
||||||
textVeryLight: colors.gray4,
|
textVeryLight: colors.gray4,
|
||||||
replyLine: colors.gray5,
|
replyLine: colors.gray5,
|
||||||
replyLineDot: colors.gray6,
|
replyLineDot: colors.gray6,
|
||||||
unreadNotifBg: colors.blue5,
|
unreadNotifBg: colors.blue7,
|
||||||
|
unreadNotifBorder: colors.blue6,
|
||||||
postCtrl: '#61657A',
|
postCtrl: '#61657A',
|
||||||
brandText: '#0085ff',
|
brandText: '#0085ff',
|
||||||
emptyStateIcon: colors.gray4,
|
emptyStateIcon: colors.gray4,
|
||||||
|
|
|
@ -11,6 +11,8 @@ export class MeModel {
|
||||||
displayName: string = ''
|
displayName: string = ''
|
||||||
description: string = ''
|
description: string = ''
|
||||||
avatar: string = ''
|
avatar: string = ''
|
||||||
|
followsCount: number | undefined
|
||||||
|
followersCount: number | undefined
|
||||||
mainFeed: FeedModel
|
mainFeed: FeedModel
|
||||||
notifications: NotificationsViewModel
|
notifications: NotificationsViewModel
|
||||||
follows: MyFollowsModel
|
follows: MyFollowsModel
|
||||||
|
@ -90,10 +92,14 @@ export class MeModel {
|
||||||
this.displayName = profile.data.displayName || ''
|
this.displayName = profile.data.displayName || ''
|
||||||
this.description = profile.data.description || ''
|
this.description = profile.data.description || ''
|
||||||
this.avatar = profile.data.avatar || ''
|
this.avatar = profile.data.avatar || ''
|
||||||
|
this.followsCount = profile.data.followsCount
|
||||||
|
this.followersCount = profile.data.followersCount
|
||||||
} else {
|
} else {
|
||||||
this.displayName = ''
|
this.displayName = ''
|
||||||
this.description = ''
|
this.description = ''
|
||||||
this.avatar = ''
|
this.avatar = ''
|
||||||
|
this.followsCount = profile.data.followsCount
|
||||||
|
this.followersCount = undefined
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.mainFeed.clear()
|
this.mainFeed.clear()
|
||||||
|
|
|
@ -21,6 +21,8 @@ export class PostThreadViewPostModel {
|
||||||
_reactKey: string = ''
|
_reactKey: string = ''
|
||||||
_depth = 0
|
_depth = 0
|
||||||
_isHighlightedPost = false
|
_isHighlightedPost = false
|
||||||
|
_showParentReplyLine = false
|
||||||
|
_showChildReplyLine = false
|
||||||
_hasMore = false
|
_hasMore = false
|
||||||
|
|
||||||
// data
|
// data
|
||||||
|
@ -30,6 +32,14 @@ export class PostThreadViewPostModel {
|
||||||
replies?: (PostThreadViewPostModel | GetPostThread.NotFoundPost)[]
|
replies?: (PostThreadViewPostModel | GetPostThread.NotFoundPost)[]
|
||||||
richText?: RichText
|
richText?: RichText
|
||||||
|
|
||||||
|
get uri() {
|
||||||
|
return this.post.uri
|
||||||
|
}
|
||||||
|
|
||||||
|
get parentUri() {
|
||||||
|
return this.postRecord?.reply?.parent.uri
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public rootStore: RootStoreModel,
|
public rootStore: RootStoreModel,
|
||||||
reactKey: string,
|
reactKey: string,
|
||||||
|
@ -65,6 +75,7 @@ export class PostThreadViewPostModel {
|
||||||
assignTreeModels(
|
assignTreeModels(
|
||||||
keyGen: Generator<string>,
|
keyGen: Generator<string>,
|
||||||
v: GetPostThread.ThreadViewPost,
|
v: GetPostThread.ThreadViewPost,
|
||||||
|
higlightedPostUri: string,
|
||||||
includeParent = true,
|
includeParent = true,
|
||||||
includeChildren = true,
|
includeChildren = true,
|
||||||
) {
|
) {
|
||||||
|
@ -77,8 +88,16 @@ export class PostThreadViewPostModel {
|
||||||
v.parent,
|
v.parent,
|
||||||
)
|
)
|
||||||
parentModel._depth = this._depth - 1
|
parentModel._depth = this._depth - 1
|
||||||
|
parentModel._showChildReplyLine = true
|
||||||
if (v.parent.parent) {
|
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
|
this.parent = parentModel
|
||||||
} else if (GetPostThread.isNotFoundPost(v.parent)) {
|
} else if (GetPostThread.isNotFoundPost(v.parent)) {
|
||||||
|
@ -96,8 +115,17 @@ export class PostThreadViewPostModel {
|
||||||
item,
|
item,
|
||||||
)
|
)
|
||||||
itemModel._depth = this._depth + 1
|
itemModel._depth = this._depth + 1
|
||||||
if (item.replies) {
|
itemModel._showParentReplyLine =
|
||||||
itemModel.assignTreeModels(keyGen, item, false, true)
|
itemModel.parentUri !== higlightedPostUri
|
||||||
|
if (item.replies?.length) {
|
||||||
|
itemModel._showChildReplyLine = true
|
||||||
|
itemModel.assignTreeModels(
|
||||||
|
keyGen,
|
||||||
|
item,
|
||||||
|
higlightedPostUri,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
replies.push(itemModel)
|
replies.push(itemModel)
|
||||||
} else if (GetPostThread.isNotFoundPost(item)) {
|
} else if (GetPostThread.isNotFoundPost(item)) {
|
||||||
|
@ -333,6 +361,7 @@ export class PostThreadViewModel {
|
||||||
thread.assignTreeModels(
|
thread.assignTreeModels(
|
||||||
keyGen,
|
keyGen,
|
||||||
res.data.thread as GetPostThread.ThreadViewPost,
|
res.data.thread as GetPostThread.ThreadViewPost,
|
||||||
|
thread.uri,
|
||||||
)
|
)
|
||||||
this.thread = thread
|
this.thread = thread
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,91 +1,45 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {StyleSheet, TouchableOpacity, View} from 'react-native'
|
import {StyleSheet, TouchableOpacity} from 'react-native'
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
import {UserAvatar} from '../util/UserAvatar'
|
||||||
import {Text} from '../util/text/Text'
|
import {Text} from '../util/text/Text'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
|
import {useStores} from 'state/index'
|
||||||
|
|
||||||
export function ComposePrompt({
|
export function ComposePrompt({
|
||||||
text = "What's up?",
|
|
||||||
btn = 'Post',
|
|
||||||
isReply = false,
|
|
||||||
onPressCompose,
|
onPressCompose,
|
||||||
}: {
|
}: {
|
||||||
text?: string
|
|
||||||
btn?: string
|
|
||||||
isReply?: boolean
|
|
||||||
onPressCompose: (imagesOpen?: boolean) => void
|
onPressCompose: (imagesOpen?: boolean) => void
|
||||||
}) {
|
}) {
|
||||||
|
const store = useStores()
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
testID="composePromptButton"
|
testID="replyPromptBtn"
|
||||||
style={[
|
style={[pal.view, pal.border, styles.prompt]}
|
||||||
pal.view,
|
|
||||||
pal.border,
|
|
||||||
styles.container,
|
|
||||||
isReply ? styles.containerReply : undefined,
|
|
||||||
]}
|
|
||||||
onPress={() => onPressCompose()}>
|
onPress={() => onPressCompose()}>
|
||||||
{!isReply && (
|
<UserAvatar
|
||||||
<FontAwesomeIcon
|
handle={store.me.handle}
|
||||||
icon={['fas', 'pen-nib']}
|
avatar={store.me.avatar}
|
||||||
size={18}
|
displayName={store.me.displayName}
|
||||||
style={[pal.textLight, styles.iconLeft]}
|
size={38}
|
||||||
/>
|
/>
|
||||||
)}
|
<Text type="xl" style={[pal.text, styles.label]}>
|
||||||
<View style={styles.textContainer}>
|
Write your reply
|
||||||
<Text type={isReply ? 'lg' : 'lg-medium'} style={pal.textLight}>
|
</Text>
|
||||||
{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>
|
|
||||||
)}
|
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
iconLeft: {
|
prompt: {
|
||||||
marginLeft: 22,
|
paddingHorizontal: 20,
|
||||||
marginRight: 2,
|
paddingTop: 10,
|
||||||
},
|
paddingBottom: 10,
|
||||||
iconRight: {
|
|
||||||
marginRight: 20,
|
|
||||||
},
|
|
||||||
container: {
|
|
||||||
paddingVertical: 16,
|
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
borderTopWidth: 1,
|
borderTopWidth: 1,
|
||||||
},
|
},
|
||||||
containerReply: {
|
label: {
|
||||||
paddingVertical: 14,
|
paddingLeft: 12,
|
||||||
paddingHorizontal: 10,
|
|
||||||
},
|
|
||||||
avatar: {
|
|
||||||
width: 50,
|
|
||||||
},
|
|
||||||
textContainer: {
|
|
||||||
marginLeft: 10,
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
btn: {
|
|
||||||
paddingVertical: 6,
|
|
||||||
paddingHorizontal: 14,
|
|
||||||
borderRadius: 30,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -90,10 +90,10 @@ export const FeedItem = observer(function FeedItem({
|
||||||
style={
|
style={
|
||||||
item.isRead
|
item.isRead
|
||||||
? undefined
|
? undefined
|
||||||
: [
|
: {
|
||||||
styles.outerUnread,
|
backgroundColor: pal.colors.unreadNotifBg,
|
||||||
{backgroundColor: pal.colors.unreadNotifBg},
|
borderColor: pal.colors.unreadNotifBorder,
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -152,7 +152,10 @@ export const FeedItem = observer(function FeedItem({
|
||||||
pal.border,
|
pal.border,
|
||||||
item.isRead
|
item.isRead
|
||||||
? undefined
|
? undefined
|
||||||
: [styles.outerUnread, {backgroundColor: pal.colors.unreadNotifBg}],
|
: {
|
||||||
|
backgroundColor: pal.colors.unreadNotifBg,
|
||||||
|
borderColor: pal.colors.unreadNotifBorder,
|
||||||
|
},
|
||||||
]}
|
]}
|
||||||
href={itemHref}
|
href={itemHref}
|
||||||
title={itemTitle}
|
title={itemTitle}
|
||||||
|
@ -391,9 +394,6 @@ const styles = StyleSheet.create({
|
||||||
paddingRight: 15,
|
paddingRight: 15,
|
||||||
borderTopWidth: 1,
|
borderTopWidth: 1,
|
||||||
},
|
},
|
||||||
outerUnread: {
|
|
||||||
borderColor: colors.blue1,
|
|
||||||
},
|
|
||||||
layout: {
|
layout: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
},
|
},
|
||||||
|
|
|
@ -96,7 +96,7 @@ export const PostThread = observer(function PostThread({
|
||||||
onLayout={onLayout}
|
onLayout={onLayout}
|
||||||
onScrollToIndexFailed={onScrollToIndexFailed}
|
onScrollToIndexFailed={onScrollToIndexFailed}
|
||||||
style={s.hContentRegion}
|
style={s.hContentRegion}
|
||||||
contentContainerStyle={s.contentContainer}
|
contentContainerStyle={s.contentContainerExtra}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -21,8 +21,8 @@ import {useStores} from 'state/index'
|
||||||
import {PostMeta} from '../util/PostMeta'
|
import {PostMeta} from '../util/PostMeta'
|
||||||
import {PostEmbeds} from '../util/PostEmbeds'
|
import {PostEmbeds} from '../util/PostEmbeds'
|
||||||
import {PostCtrls} from '../util/PostCtrls'
|
import {PostCtrls} from '../util/PostCtrls'
|
||||||
|
import {PostMutedWrapper} from '../util/PostMuted'
|
||||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||||
import {ComposePrompt} from '../composer/Prompt'
|
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
|
|
||||||
const PARENT_REPLY_LINE_LENGTH = 8
|
const PARENT_REPLY_LINE_LENGTH = 8
|
||||||
|
@ -271,23 +271,17 @@ export const PostThreadItem = observer(function PostThreadItem({
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<ComposePrompt
|
|
||||||
isReply
|
|
||||||
text="Write your reply"
|
|
||||||
btn="Reply"
|
|
||||||
onPressCompose={onPressReply}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<>
|
<PostMutedWrapper isMuted={item.post.author.viewer?.muted === true}>
|
||||||
<Link
|
<Link
|
||||||
style={[styles.outer, {borderTopColor: pal.colors.border}, pal.view]}
|
style={[styles.outer, {borderTopColor: pal.colors.border}, pal.view]}
|
||||||
href={itemHref}
|
href={itemHref}
|
||||||
title={itemTitle}
|
title={itemTitle}
|
||||||
noFeedback>
|
noFeedback>
|
||||||
{record.reply && (
|
{item._showParentReplyLine && (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
styles.parentReplyLine,
|
styles.parentReplyLine,
|
||||||
|
@ -295,7 +289,7 @@ export const PostThreadItem = observer(function PostThreadItem({
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{item.replies?.length !== 0 && (
|
{item._showChildReplyLine && (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
styles.childReplyLine,
|
styles.childReplyLine,
|
||||||
|
@ -322,12 +316,7 @@ export const PostThreadItem = observer(function PostThreadItem({
|
||||||
did={item.post.author.did}
|
did={item.post.author.did}
|
||||||
declarationCid={item.post.author.declaration.cid}
|
declarationCid={item.post.author.declaration.cid}
|
||||||
/>
|
/>
|
||||||
{item.post.author.viewer?.muted ? (
|
{item.richText?.text ? (
|
||||||
<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}>
|
<View style={styles.postTextContainer}>
|
||||||
<RichText
|
<RichText
|
||||||
type="post-text"
|
type="post-text"
|
||||||
|
@ -384,7 +373,7 @@ export const PostThreadItem = observer(function PostThreadItem({
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
</>
|
</PostMutedWrapper>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -441,14 +430,6 @@ const styles = StyleSheet.create({
|
||||||
paddingRight: 5,
|
paddingRight: 5,
|
||||||
maxWidth: 240,
|
maxWidth: 240,
|
||||||
},
|
},
|
||||||
mutedWarning: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
padding: 10,
|
|
||||||
marginTop: 2,
|
|
||||||
marginBottom: 6,
|
|
||||||
borderRadius: 2,
|
|
||||||
},
|
|
||||||
postTextContainer: {
|
postTextContainer: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
|
|
@ -17,6 +17,7 @@ import {UserInfoText} from '../util/UserInfoText'
|
||||||
import {PostMeta} from '../util/PostMeta'
|
import {PostMeta} from '../util/PostMeta'
|
||||||
import {PostEmbeds} from '../util/PostEmbeds'
|
import {PostEmbeds} from '../util/PostEmbeds'
|
||||||
import {PostCtrls} from '../util/PostCtrls'
|
import {PostCtrls} from '../util/PostCtrls'
|
||||||
|
import {PostMutedWrapper} from '../util/PostMuted'
|
||||||
import {Text} from '../util/text/Text'
|
import {Text} from '../util/text/Text'
|
||||||
import {RichText} from '../util/text/RichText'
|
import {RichText} from '../util/text/RichText'
|
||||||
import * as Toast from '../util/Toast'
|
import * as Toast from '../util/Toast'
|
||||||
|
@ -140,92 +141,89 @@ export const Post = observer(function Post({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<PostMutedWrapper isMuted={item.post.author.viewer?.muted === true}>
|
||||||
style={[styles.outer, pal.view, pal.border, style]}
|
<Link
|
||||||
href={itemHref}
|
style={[styles.outer, pal.view, pal.border, style]}
|
||||||
title={itemTitle}
|
href={itemHref}
|
||||||
noFeedback>
|
title={itemTitle}
|
||||||
{showReplyLine && <View style={styles.replyLine} />}
|
noFeedback>
|
||||||
<View style={styles.layout}>
|
{showReplyLine && <View style={styles.replyLine} />}
|
||||||
<View style={styles.layoutAvi}>
|
<View style={styles.layout}>
|
||||||
<Link href={authorHref} title={authorTitle}>
|
<View style={styles.layoutAvi}>
|
||||||
<UserAvatar
|
<Link href={authorHref} title={authorTitle}>
|
||||||
size={52}
|
<UserAvatar
|
||||||
displayName={item.post.author.displayName}
|
size={52}
|
||||||
handle={item.post.author.handle}
|
displayName={item.post.author.displayName}
|
||||||
avatar={item.post.author.avatar}
|
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>
|
||||||
<View style={styles.layoutContent}>
|
</Link>
|
||||||
<PostMeta
|
</PostMutedWrapper>
|
||||||
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>
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -245,14 +243,6 @@ const styles = StyleSheet.create({
|
||||||
layoutContent: {
|
layoutContent: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
mutedWarning: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
padding: 10,
|
|
||||||
marginTop: 2,
|
|
||||||
marginBottom: 6,
|
|
||||||
borderRadius: 2,
|
|
||||||
},
|
|
||||||
postTextContainer: {
|
postTextContainer: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
|
|
@ -15,6 +15,7 @@ import {UserInfoText} from '../util/UserInfoText'
|
||||||
import {PostMeta} from '../util/PostMeta'
|
import {PostMeta} from '../util/PostMeta'
|
||||||
import {PostCtrls} from '../util/PostCtrls'
|
import {PostCtrls} from '../util/PostCtrls'
|
||||||
import {PostEmbeds} from '../util/PostEmbeds'
|
import {PostEmbeds} from '../util/PostEmbeds'
|
||||||
|
import {PostMutedWrapper} from '../util/PostMuted'
|
||||||
import {RichText} from '../util/text/RichText'
|
import {RichText} from '../util/text/RichText'
|
||||||
import * as Toast from '../util/Toast'
|
import * as Toast from '../util/Toast'
|
||||||
import {UserAvatar} from '../util/UserAvatar'
|
import {UserAvatar} from '../util/UserAvatar'
|
||||||
|
@ -113,6 +114,8 @@ export const FeedItem = observer(function ({
|
||||||
item._isThreadChild || (!item.reason && !item._hideParent && item.reply)
|
item._isThreadChild || (!item.reason && !item._hideParent && item.reply)
|
||||||
const isSmallTop = isChild && item._isThreadChild
|
const isSmallTop = isChild && item._isThreadChild
|
||||||
const isNoTop = isChild && !item._isThreadChild
|
const isNoTop = isChild && !item._isThreadChild
|
||||||
|
const isMuted =
|
||||||
|
item.post.author.viewer?.muted && ignoreMuteFor !== item.post.author.did
|
||||||
const outerStyles = [
|
const outerStyles = [
|
||||||
styles.outer,
|
styles.outer,
|
||||||
pal.view,
|
pal.view,
|
||||||
|
@ -123,7 +126,7 @@ export const FeedItem = observer(function ({
|
||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<PostMutedWrapper isMuted={isMuted}>
|
||||||
{isChild && !item._isThreadChild && item.replyParent ? (
|
{isChild && !item._isThreadChild && item.replyParent ? (
|
||||||
<FeedItem
|
<FeedItem
|
||||||
item={item.replyParent}
|
item={item.replyParent}
|
||||||
|
@ -160,7 +163,11 @@ export const FeedItem = observer(function ({
|
||||||
{color: pal.colors.textLight} as FontAwesomeIconStyle,
|
{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{' '}
|
Reposted by{' '}
|
||||||
{item.reasonRepost.by.displayName || item.reasonRepost.by.handle}
|
{item.reasonRepost.by.displayName || item.reasonRepost.by.handle}
|
||||||
</Text>
|
</Text>
|
||||||
|
@ -207,13 +214,7 @@ export const FeedItem = observer(function ({
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
{item.post.author.viewer?.muted &&
|
{item.richText?.text ? (
|
||||||
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 ? (
|
|
||||||
<View style={styles.postTextContainer}>
|
<View style={styles.postTextContainer}>
|
||||||
<RichText
|
<RichText
|
||||||
type="post-text"
|
type="post-text"
|
||||||
|
@ -222,9 +223,7 @@ export const FeedItem = observer(function ({
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
{item.post.embed ? (
|
<PostEmbeds embed={item.post.embed} style={styles.embed} />
|
||||||
<PostEmbeds embed={item.post.embed} style={styles.embed} />
|
|
||||||
) : null}
|
|
||||||
<PostCtrls
|
<PostCtrls
|
||||||
style={styles.ctrls}
|
style={styles.ctrls}
|
||||||
itemUri={itemUri}
|
itemUri={itemUri}
|
||||||
|
@ -280,7 +279,7 @@ export const FeedItem = observer(function ({
|
||||||
</Text>
|
</Text>
|
||||||
</Link>
|
</Link>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
</>
|
</PostMutedWrapper>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -319,6 +318,7 @@ const styles = StyleSheet.create({
|
||||||
includeReason: {
|
includeReason: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
paddingLeft: 50,
|
paddingLeft: 50,
|
||||||
|
paddingRight: 20,
|
||||||
marginTop: 2,
|
marginTop: 2,
|
||||||
marginBottom: 2,
|
marginBottom: 2,
|
||||||
},
|
},
|
||||||
|
@ -336,14 +336,6 @@ const styles = StyleSheet.create({
|
||||||
layoutContent: {
|
layoutContent: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
mutedWarning: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
padding: 10,
|
|
||||||
marginTop: 2,
|
|
||||||
marginBottom: 6,
|
|
||||||
borderRadius: 2,
|
|
||||||
},
|
|
||||||
postTextContainer: {
|
postTextContainer: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
|
|
@ -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) {
|
export function show(message: string) {
|
||||||
Toast.show(message, {
|
const item = new RootSiblings(<Toast message={message} />)
|
||||||
duration: Toast.durations.LONG,
|
setTimeout(() => {
|
||||||
position: 50,
|
item.destroy()
|
||||||
shadow: true,
|
}, TIMEOUT)
|
||||||
animation: true,
|
|
||||||
hideOnPress: true,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
let inner
|
||||||
if (didFail) {
|
if (didFail) {
|
||||||
inner = (
|
inner = (
|
||||||
<Text type={type} style={style}>
|
<Text type={type} style={style} numberOfLines={1}>
|
||||||
{failed}
|
{failed}
|
||||||
</Text>
|
</Text>
|
||||||
)
|
)
|
||||||
} else if (profile) {
|
} else if (profile) {
|
||||||
inner = (
|
inner = (
|
||||||
<Text type={type} style={style} lineHeight={1.2}>{`${prefix || ''}${
|
<Text type={type} style={style} lineHeight={1.2} numberOfLines={1}>{`${
|
||||||
profile[attr] || profile.handle
|
prefix || ''
|
||||||
}`}</Text>
|
}${profile[attr] || profile.handle}`}</Text>
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
inner = (
|
inner = (
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {ThemeProvider, PaletteColorName} from 'lib/ThemeContext'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {s} from 'lib/styles'
|
import {s} from 'lib/styles'
|
||||||
import {displayNotification} from 'lib/notifee'
|
import {displayNotification} from 'lib/notifee'
|
||||||
|
import * as Toast from 'view/com/util/Toast'
|
||||||
|
|
||||||
import {Text} from '../com/util/text/Text'
|
import {Text} from '../com/util/text/Text'
|
||||||
import {ViewSelector} from '../com/util/ViewSelector'
|
import {ViewSelector} from '../com/util/ViewSelector'
|
||||||
|
@ -171,16 +172,24 @@ function ErrorView() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function NotifsView() {
|
function NotifsView() {
|
||||||
const trigger = () => {
|
const triggerPush = () => {
|
||||||
displayNotification(
|
displayNotification(
|
||||||
'Paul Frazee liked your post',
|
'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.",
|
"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 (
|
return (
|
||||||
<View style={s.p10}>
|
<View style={s.p10}>
|
||||||
<View style={s.flexRow}>
|
<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>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
import React, {useEffect, useMemo} from 'react'
|
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 {makeRecordUri} from 'lib/strings/url-helpers'
|
||||||
import {ViewHeader} from '../com/util/ViewHeader'
|
import {ViewHeader} from '../com/util/ViewHeader'
|
||||||
import {PostThread as PostThreadComponent} from '../com/post-thread/PostThread'
|
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 {PostThreadViewModel} from 'state/models/post-thread-view'
|
||||||
import {ScreenParams} from '../routes'
|
import {ScreenParams} from '../routes'
|
||||||
import {useStores} from 'state/index'
|
import {useStores} from 'state/index'
|
||||||
import {s} from 'lib/styles'
|
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) => {
|
export const PostThread = ({navIdx, visible, params}: ScreenParams) => {
|
||||||
const store = useStores()
|
const store = useStores()
|
||||||
|
const safeAreaInsets = useSafeAreaInsets()
|
||||||
const {name, rkey} = params
|
const {name, rkey} = params
|
||||||
const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey)
|
const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey)
|
||||||
const view = useMemo<PostThreadViewModel>(
|
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])
|
}, [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 (
|
return (
|
||||||
<View style={s.hContentRegion}>
|
<View style={s.hContentRegion}>
|
||||||
<ViewHeader title="Post" />
|
<ViewHeader title="Post" />
|
||||||
<View style={s.hContentRegion}>
|
<View style={s.hContentRegion}>
|
||||||
<PostThreadComponent uri={uri} view={view} />
|
<PostThreadComponent uri={uri} view={view} />
|
||||||
</View>
|
</View>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.prompt,
|
||||||
|
{bottom: SHELL_FOOTER_HEIGHT + clamp(safeAreaInsets.bottom, 15, 30)},
|
||||||
|
]}>
|
||||||
|
<ComposePrompt onPressCompose={onPressReply} />
|
||||||
|
</View>
|
||||||
</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 {useTheme} from 'lib/ThemeContext'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {useAnalytics} from 'lib/analytics'
|
import {useAnalytics} from 'lib/analytics'
|
||||||
|
import {pluralize} from 'lib/strings/helpers'
|
||||||
|
|
||||||
export const Menu = observer(({onClose}: {onClose: () => void}) => {
|
export const Menu = observer(({onClose}: {onClose: () => void}) => {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
|
@ -138,6 +139,16 @@ export const Menu = observer(({onClose}: {onClose: () => void}) => {
|
||||||
<Text type="2xl" style={[pal.textLight, styles.profileCardHandle]}>
|
<Text type="2xl" style={[pal.textLight, styles.profileCardHandle]}>
|
||||||
@{store.me.handle}
|
@{store.me.handle}
|
||||||
</Text>
|
</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>
|
</TouchableOpacity>
|
||||||
<View style={s.flex1} />
|
<View style={s.flex1} />
|
||||||
<View>
|
<View>
|
||||||
|
@ -267,12 +278,12 @@ export const Menu = observer(({onClose}: {onClose: () => void}) => {
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
view: {
|
view: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
paddingTop: 10,
|
paddingTop: 20,
|
||||||
paddingBottom: 50,
|
paddingBottom: 50,
|
||||||
paddingLeft: 30,
|
paddingLeft: 30,
|
||||||
},
|
},
|
||||||
viewDarkMode: {
|
viewDarkMode: {
|
||||||
backgroundColor: '#202023',
|
backgroundColor: '#1B1919',
|
||||||
},
|
},
|
||||||
|
|
||||||
profileCardDisplayName: {
|
profileCardDisplayName: {
|
||||||
|
@ -283,6 +294,10 @@ const styles = StyleSheet.create({
|
||||||
marginTop: 4,
|
marginTop: 4,
|
||||||
paddingRight: 20,
|
paddingRight: 20,
|
||||||
},
|
},
|
||||||
|
profileCardFollowers: {
|
||||||
|
marginTop: 16,
|
||||||
|
paddingRight: 20,
|
||||||
|
},
|
||||||
|
|
||||||
menuItem: {
|
menuItem: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
@ -316,7 +331,7 @@ const styles = StyleSheet.create({
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
paddingRight: 30,
|
paddingRight: 30,
|
||||||
paddingTop: 20,
|
paddingTop: 80,
|
||||||
},
|
},
|
||||||
footerBtn: {
|
footerBtn: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
|
|
@ -157,7 +157,7 @@ export const MobileShell: React.FC = observer(() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const screenBg = {
|
const screenBg = {
|
||||||
backgroundColor: theme.colorScheme === 'dark' ? colors.gray7 : colors.gray1,
|
backgroundColor: theme.colorScheme === 'dark' ? colors.black : colors.gray1,
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<View testID="mobileShellView" style={[styles.outerContainer, pal.view]}>
|
<View testID="mobileShellView" style={[styles.outerContainer, pal.view]}>
|
||||||
|
@ -202,13 +202,7 @@ export const MobileShell: React.FC = observer(() => {
|
||||||
style={[
|
style={[
|
||||||
s.h100pct,
|
s.h100pct,
|
||||||
screenBg,
|
screenBg,
|
||||||
current
|
current ? [swipeTransform] : undefined,
|
||||||
? [
|
|
||||||
swipeTransform,
|
|
||||||
// tabMenuTransform, TODO
|
|
||||||
// isRunningNewTabAnim ? newTabTransform : undefined, TODO
|
|
||||||
]
|
|
||||||
: undefined,
|
|
||||||
]}>
|
]}>
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<Com
|
<Com
|
||||||
|
|
|
@ -9,7 +9,6 @@ const uncompiled_deps = [
|
||||||
'@bam.tech/react-native-image-resizer',
|
'@bam.tech/react-native-image-resizer',
|
||||||
'react-native-fs',
|
'react-native-fs',
|
||||||
'rn-fetch-blob',
|
'rn-fetch-blob',
|
||||||
'react-native-root-toast',
|
|
||||||
'react-native-root-siblings',
|
'react-native-root-siblings',
|
||||||
'react-native-linear-gradient',
|
'react-native-linear-gradient',
|
||||||
]
|
]
|
||||||
|
|
Loading…
Reference in New Issue