Lex refactor (#362)
* Remove the hackcheck for upgrades * Rename the PostEmbeds folder to match the codebase style * Updates to latest lex refactor * Update to use new bsky agent * Update to use api package's richtext library * Switch to upsertProfile * Add TextEncoder/TextDecoder polyfill * Add Intl.Segmenter polyfill * Update composer to calculate lengths by grapheme * Fix detox * Fix login in e2e * Create account e2e passing * Implement an e2e mocking framework * Don't use private methods on mobx models as mobx can't track them * Add tooling for e2e-specific builds and add e2e media-picker mock * Add some tests and fix some bugs around profile editing * Add shell tests * Add home screen tests * Add thread screen tests * Add tests for other user profile screens * Add search screen tests * Implement profile imagery change tools and tests * Update to new embed behaviors * Add post tests * Fix to profile-screen test * Fix session resumption * Update web composer to new api * 1.11.0 * Fix pagination cursor parameters * Add quote posts to notifications * Fix embed layouts * Remove youtube inline player and improve tap handling on link cards * Reset minimal shell mode on all screen loads and feed swipes (close #299) * Update podfile.lock * Improve post notfound UI (close #366) * Bump atproto packages
This commit is contained in:
parent
19f3a2fa92
commit
a3334a01a2
133 changed files with 3103 additions and 2839 deletions
62
src/view/com/util/post-embeds/ExternalLinkEmbed.tsx
Normal file
62
src/view/com/util/post-embeds/ExternalLinkEmbed.tsx
Normal file
|
@ -0,0 +1,62 @@
|
|||
import React from 'react'
|
||||
import {Text} from '../text/Text'
|
||||
import {AutoSizedImage} from '../images/AutoSizedImage'
|
||||
import {StyleSheet, View} from 'react-native'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {AppBskyEmbedExternal} from '@atproto/api'
|
||||
|
||||
export const ExternalLinkEmbed = ({
|
||||
link,
|
||||
imageChild,
|
||||
}: {
|
||||
link: AppBskyEmbedExternal.ViewExternal
|
||||
imageChild?: React.ReactNode
|
||||
}) => {
|
||||
const pal = usePalette('default')
|
||||
return (
|
||||
<>
|
||||
{link.thumb ? (
|
||||
<AutoSizedImage uri={link.thumb} style={styles.extImage}>
|
||||
{imageChild}
|
||||
</AutoSizedImage>
|
||||
) : undefined}
|
||||
<View style={styles.extInner}>
|
||||
<Text type="md-bold" numberOfLines={2} style={[pal.text]}>
|
||||
{link.title || link.uri}
|
||||
</Text>
|
||||
<Text
|
||||
type="sm"
|
||||
numberOfLines={1}
|
||||
style={[pal.textLight, styles.extUri]}>
|
||||
{link.uri}
|
||||
</Text>
|
||||
{link.description ? (
|
||||
<Text
|
||||
type="sm"
|
||||
numberOfLines={2}
|
||||
style={[pal.text, styles.extDescription]}>
|
||||
{link.description}
|
||||
</Text>
|
||||
) : undefined}
|
||||
</View>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
extInner: {
|
||||
padding: 10,
|
||||
},
|
||||
extImage: {
|
||||
borderTopLeftRadius: 6,
|
||||
borderTopRightRadius: 6,
|
||||
width: '100%',
|
||||
maxHeight: 200,
|
||||
},
|
||||
extUri: {
|
||||
marginTop: 2,
|
||||
},
|
||||
extDescription: {
|
||||
marginTop: 4,
|
||||
},
|
||||
})
|
81
src/view/com/util/post-embeds/QuoteEmbed.tsx
Normal file
81
src/view/com/util/post-embeds/QuoteEmbed.tsx
Normal file
|
@ -0,0 +1,81 @@
|
|||
import React from 'react'
|
||||
import {StyleProp, StyleSheet, ViewStyle} from 'react-native'
|
||||
import {AppBskyEmbedImages, AppBskyEmbedRecordWithMedia} from '@atproto/api'
|
||||
import {AtUri} from '../../../../third-party/uri'
|
||||
import {PostMeta} from '../PostMeta'
|
||||
import {Link} from '../Link'
|
||||
import {Text} from '../text/Text'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {ComposerOptsQuote} from 'state/models/ui/shell'
|
||||
import {PostEmbeds} from '.'
|
||||
|
||||
export function QuoteEmbed({
|
||||
quote,
|
||||
style,
|
||||
}: {
|
||||
quote: ComposerOptsQuote
|
||||
style?: StyleProp<ViewStyle>
|
||||
}) {
|
||||
const pal = usePalette('default')
|
||||
const itemUrip = new AtUri(quote.uri)
|
||||
const itemHref = `/profile/${quote.author.handle}/post/${itemUrip.rkey}`
|
||||
const itemTitle = `Post by ${quote.author.handle}`
|
||||
const isEmpty = React.useMemo(
|
||||
() => quote.text.trim().length === 0,
|
||||
[quote.text],
|
||||
)
|
||||
const imagesEmbed = React.useMemo(
|
||||
() =>
|
||||
quote.embeds?.find(
|
||||
embed =>
|
||||
AppBskyEmbedImages.isView(embed) ||
|
||||
AppBskyEmbedRecordWithMedia.isView(embed),
|
||||
),
|
||||
[quote.embeds],
|
||||
)
|
||||
return (
|
||||
<Link
|
||||
style={[styles.container, pal.border, style]}
|
||||
href={itemHref}
|
||||
title={itemTitle}>
|
||||
<PostMeta
|
||||
authorAvatar={quote.author.avatar}
|
||||
authorHandle={quote.author.handle}
|
||||
authorDisplayName={quote.author.displayName}
|
||||
postHref={itemHref}
|
||||
timestamp={quote.indexedAt}
|
||||
/>
|
||||
<Text type="post-text" style={pal.text} numberOfLines={6}>
|
||||
{isEmpty ? (
|
||||
<Text style={pal.link} lineHeight={1.5}>
|
||||
View post
|
||||
</Text>
|
||||
) : (
|
||||
quote.text
|
||||
)}
|
||||
</Text>
|
||||
{AppBskyEmbedImages.isView(imagesEmbed) && (
|
||||
<PostEmbeds embed={imagesEmbed} />
|
||||
)}
|
||||
{AppBskyEmbedRecordWithMedia.isView(imagesEmbed) && (
|
||||
<PostEmbeds embed={imagesEmbed.media} />
|
||||
)}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
export default QuoteEmbed
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
borderRadius: 8,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 12,
|
||||
borderWidth: 1,
|
||||
},
|
||||
quotePost: {
|
||||
flex: 1,
|
||||
paddingLeft: 13,
|
||||
paddingRight: 8,
|
||||
},
|
||||
})
|
55
src/view/com/util/post-embeds/YoutubeEmbed.tsx
Normal file
55
src/view/com/util/post-embeds/YoutubeEmbed.tsx
Normal file
|
@ -0,0 +1,55 @@
|
|||
import React from 'react'
|
||||
import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {ExternalLinkEmbed} from './ExternalLinkEmbed'
|
||||
import {AppBskyEmbedExternal} from '@atproto/api'
|
||||
import {Link} from '../Link'
|
||||
|
||||
export const YoutubeEmbed = ({
|
||||
link,
|
||||
style,
|
||||
}: {
|
||||
link: AppBskyEmbedExternal.ViewExternal
|
||||
style?: StyleProp<ViewStyle>
|
||||
}) => {
|
||||
const pal = usePalette('default')
|
||||
|
||||
const imageChild = (
|
||||
<View style={styles.playButton}>
|
||||
<FontAwesomeIcon icon="play" size={24} color="white" />
|
||||
</View>
|
||||
)
|
||||
|
||||
return (
|
||||
<Link
|
||||
style={[styles.extOuter, pal.view, pal.border, style]}
|
||||
href={link.uri}
|
||||
noFeedback>
|
||||
<ExternalLinkEmbed link={link} imageChild={imageChild} />
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
extOuter: {
|
||||
borderWidth: 1,
|
||||
borderRadius: 8,
|
||||
},
|
||||
playButton: {
|
||||
position: 'absolute',
|
||||
alignSelf: 'center',
|
||||
alignItems: 'center',
|
||||
top: '44%',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: 'black',
|
||||
padding: 10,
|
||||
borderRadius: 50,
|
||||
opacity: 0.8,
|
||||
},
|
||||
webView: {
|
||||
alignItems: 'center',
|
||||
alignContent: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
})
|
198
src/view/com/util/post-embeds/index.tsx
Normal file
198
src/view/com/util/post-embeds/index.tsx
Normal file
|
@ -0,0 +1,198 @@
|
|||
import React from 'react'
|
||||
import {
|
||||
StyleSheet,
|
||||
StyleProp,
|
||||
View,
|
||||
ViewStyle,
|
||||
Image as RNImage,
|
||||
} from 'react-native'
|
||||
import {
|
||||
AppBskyEmbedImages,
|
||||
AppBskyEmbedExternal,
|
||||
AppBskyEmbedRecord,
|
||||
AppBskyEmbedRecordWithMedia,
|
||||
AppBskyFeedPost,
|
||||
} from '@atproto/api'
|
||||
import {Link} from '../Link'
|
||||
import {AutoSizedImage} from '../images/AutoSizedImage'
|
||||
import {ImageLayoutGrid} from '../images/ImageLayoutGrid'
|
||||
import {ImagesLightbox} from 'state/models/ui/shell'
|
||||
import {useStores} from 'state/index'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {saveImageModal} from 'lib/media/manip'
|
||||
import {YoutubeEmbed} from './YoutubeEmbed'
|
||||
import {ExternalLinkEmbed} from './ExternalLinkEmbed'
|
||||
import {getYoutubeVideoId} from 'lib/strings/url-helpers'
|
||||
import QuoteEmbed from './QuoteEmbed'
|
||||
|
||||
type Embed =
|
||||
| AppBskyEmbedRecord.View
|
||||
| AppBskyEmbedImages.View
|
||||
| AppBskyEmbedExternal.View
|
||||
| AppBskyEmbedRecordWithMedia.View
|
||||
| {$type: string; [k: string]: unknown}
|
||||
|
||||
export function PostEmbeds({
|
||||
embed,
|
||||
style,
|
||||
}: {
|
||||
embed?: Embed
|
||||
style?: StyleProp<ViewStyle>
|
||||
}) {
|
||||
const pal = usePalette('default')
|
||||
const store = useStores()
|
||||
|
||||
if (
|
||||
AppBskyEmbedRecordWithMedia.isView(embed) &&
|
||||
AppBskyEmbedRecord.isViewRecord(embed.record.record) &&
|
||||
AppBskyFeedPost.isRecord(embed.record.record.value) &&
|
||||
AppBskyFeedPost.validateRecord(embed.record.record.value).success
|
||||
) {
|
||||
return (
|
||||
<View style={[styles.stackContainer, style]}>
|
||||
<PostEmbeds embed={embed.media} />
|
||||
<QuoteEmbed
|
||||
quote={{
|
||||
author: embed.record.record.author,
|
||||
cid: embed.record.record.cid,
|
||||
uri: embed.record.record.uri,
|
||||
indexedAt: embed.record.record.indexedAt,
|
||||
text: embed.record.record.value.text,
|
||||
embeds: embed.record.record.embeds,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
if (AppBskyEmbedRecord.isView(embed)) {
|
||||
if (
|
||||
AppBskyEmbedRecord.isViewRecord(embed.record) &&
|
||||
AppBskyFeedPost.isRecord(embed.record.value) &&
|
||||
AppBskyFeedPost.validateRecord(embed.record.value).success
|
||||
) {
|
||||
return (
|
||||
<QuoteEmbed
|
||||
quote={{
|
||||
author: embed.record.author,
|
||||
cid: embed.record.cid,
|
||||
uri: embed.record.uri,
|
||||
indexedAt: embed.record.indexedAt,
|
||||
text: embed.record.value.text,
|
||||
embeds: embed.record.embeds,
|
||||
}}
|
||||
style={style}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (AppBskyEmbedImages.isView(embed)) {
|
||||
if (embed.images.length > 0) {
|
||||
const uris = embed.images.map(img => img.fullsize)
|
||||
const openLightbox = (index: number) => {
|
||||
store.shell.openLightbox(new ImagesLightbox(uris, index))
|
||||
}
|
||||
const onLongPress = (index: number) => {
|
||||
saveImageModal({uri: uris[index]})
|
||||
}
|
||||
const onPressIn = (index: number) => {
|
||||
const firstImageToShow = uris[index]
|
||||
RNImage.prefetch(firstImageToShow)
|
||||
uris.forEach(uri => {
|
||||
if (firstImageToShow !== uri) {
|
||||
// First image already prefeched above
|
||||
RNImage.prefetch(uri)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (embed.images.length === 4) {
|
||||
return (
|
||||
<View style={[styles.imagesContainer, style]}>
|
||||
<ImageLayoutGrid
|
||||
type="four"
|
||||
uris={embed.images.map(img => img.thumb)}
|
||||
onPress={openLightbox}
|
||||
onLongPress={onLongPress}
|
||||
onPressIn={onPressIn}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
} else if (embed.images.length === 3) {
|
||||
return (
|
||||
<View style={[styles.imagesContainer, style]}>
|
||||
<ImageLayoutGrid
|
||||
type="three"
|
||||
uris={embed.images.map(img => img.thumb)}
|
||||
onPress={openLightbox}
|
||||
onLongPress={onLongPress}
|
||||
onPressIn={onPressIn}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
} else if (embed.images.length === 2) {
|
||||
return (
|
||||
<View style={[styles.imagesContainer, style]}>
|
||||
<ImageLayoutGrid
|
||||
type="two"
|
||||
uris={embed.images.map(img => img.thumb)}
|
||||
onPress={openLightbox}
|
||||
onLongPress={onLongPress}
|
||||
onPressIn={onPressIn}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<View style={[styles.imagesContainer, style]}>
|
||||
<AutoSizedImage
|
||||
uri={embed.images[0].thumb}
|
||||
onPress={() => openLightbox(0)}
|
||||
onLongPress={() => onLongPress(0)}
|
||||
onPressIn={() => onPressIn(0)}
|
||||
style={styles.singleImage}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (AppBskyEmbedExternal.isView(embed)) {
|
||||
const link = embed.external
|
||||
const youtubeVideoId = getYoutubeVideoId(link.uri)
|
||||
|
||||
if (youtubeVideoId) {
|
||||
return <YoutubeEmbed link={link} style={style} />
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
style={[styles.extOuter, pal.view, pal.border, style]}
|
||||
href={link.uri}
|
||||
noFeedback>
|
||||
<ExternalLinkEmbed link={link} />
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
return <View />
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
stackContainer: {
|
||||
gap: 6,
|
||||
},
|
||||
imagesContainer: {
|
||||
marginTop: 4,
|
||||
},
|
||||
singleImage: {
|
||||
borderRadius: 8,
|
||||
maxHeight: 500,
|
||||
},
|
||||
extOuter: {
|
||||
borderWidth: 1,
|
||||
borderRadius: 8,
|
||||
marginTop: 4,
|
||||
},
|
||||
})
|
Loading…
Add table
Add a link
Reference in a new issue