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:
Paul Frazee 2023-03-31 13:17:26 -05:00 committed by GitHub
parent 19f3a2fa92
commit a3334a01a2
133 changed files with 3103 additions and 2839 deletions

View file

@ -1,4 +1,4 @@
import React, {useEffect, useRef, useState} from 'react'
import React from 'react'
import {observer} from 'mobx-react-lite'
import {
ActivityIndicator,
@ -13,6 +13,7 @@ import {
} from 'react-native'
import LinearGradient from 'react-native-linear-gradient'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {RichText} from '@atproto/api'
import {useAnalytics} from 'lib/analytics'
import {UserAutocompleteViewModel} from 'state/models/user-autocomplete-view'
import {ExternalEmbed} from './ExternalEmbed'
@ -30,11 +31,11 @@ import {SelectPhotoBtn} from './photos/SelectPhotoBtn'
import {OpenCameraBtn} from './photos/OpenCameraBtn'
import {SelectedPhotos} from './photos/SelectedPhotos'
import {usePalette} from 'lib/hooks/usePalette'
import QuoteEmbed from '../util/PostEmbeds/QuoteEmbed'
import QuoteEmbed from '../util/post-embeds/QuoteEmbed'
import {useExternalLinkFetch} from './useExternalLinkFetch'
import {isDesktopWeb} from 'platform/detection'
const MAX_TEXT_LENGTH = 256
const MAX_GRAPHEME_LENGTH = 300
export const ComposePost = observer(function ComposePost({
replyTo,
@ -50,17 +51,23 @@ export const ComposePost = observer(function ComposePost({
const {track} = useAnalytics()
const pal = usePalette('default')
const store = useStores()
const textInput = useRef<TextInputRef>(null)
const [isProcessing, setIsProcessing] = useState(false)
const [processingState, setProcessingState] = useState('')
const [error, setError] = useState('')
const [text, setText] = useState('')
const [quote, setQuote] = useState<ComposerOpts['quote'] | undefined>(
const textInput = React.useRef<TextInputRef>(null)
const [isProcessing, setIsProcessing] = React.useState(false)
const [processingState, setProcessingState] = React.useState('')
const [error, setError] = React.useState('')
const [richtext, setRichText] = React.useState(new RichText({text: ''}))
const graphemeLength = React.useMemo(
() => richtext.graphemeLength,
[richtext],
)
const [quote, setQuote] = React.useState<ComposerOpts['quote'] | undefined>(
initQuote,
)
const {extLink, setExtLink} = useExternalLinkFetch({setQuote})
const [suggestedLinks, setSuggestedLinks] = useState<Set<string>>(new Set())
const [selectedPhotos, setSelectedPhotos] = useState<string[]>([])
const [suggestedLinks, setSuggestedLinks] = React.useState<Set<string>>(
new Set(),
)
const [selectedPhotos, setSelectedPhotos] = React.useState<string[]>([])
const autocompleteView = React.useMemo<UserAutocompleteViewModel>(
() => new UserAutocompleteViewModel(store),
@ -78,11 +85,11 @@ export const ComposePost = observer(function ComposePost({
}, [textInput, onClose])
// initial setup
useEffect(() => {
React.useEffect(() => {
autocompleteView.setup()
}, [autocompleteView])
useEffect(() => {
React.useEffect(() => {
// HACK
// wait a moment before focusing the input to resolve some layout bugs with the keyboard-avoiding-view
// -prf
@ -132,18 +139,18 @@ export const ComposePost = observer(function ComposePost({
if (isProcessing) {
return
}
if (text.length > MAX_TEXT_LENGTH) {
if (richtext.graphemeLength > MAX_GRAPHEME_LENGTH) {
return
}
setError('')
if (text.trim().length === 0 && selectedPhotos.length === 0) {
if (richtext.text.trim().length === 0 && selectedPhotos.length === 0) {
setError('Did you want to say anything?')
return false
}
setIsProcessing(true)
try {
await apilib.post(store, {
rawText: text,
rawText: richtext.text,
replyTo: replyTo?.uri,
images: selectedPhotos,
quote: quote,
@ -172,7 +179,7 @@ export const ComposePost = observer(function ComposePost({
Toast.show(`Your ${replyTo ? 'reply' : 'post'} has been published`)
}, [
isProcessing,
text,
richtext,
setError,
setIsProcessing,
replyTo,
@ -187,7 +194,7 @@ export const ComposePost = observer(function ComposePost({
track,
])
const canPost = text.length <= MAX_TEXT_LENGTH
const canPost = graphemeLength <= MAX_GRAPHEME_LENGTH
const selectTextInputPlaceholder = replyTo
? 'Write your reply'
@ -215,7 +222,7 @@ export const ComposePost = observer(function ComposePost({
</View>
) : canPost ? (
<TouchableOpacity
testID="composerPublishButton"
testID="composerPublishBtn"
onPress={onPressPublish}>
<LinearGradient
colors={[gradients.blueLight.start, gradients.blueLight.end]}
@ -271,42 +278,41 @@ export const ComposePost = observer(function ComposePost({
<UserAvatar avatar={store.me.avatar} size={50} />
<TextInput
ref={textInput}
text={text}
richtext={richtext}
placeholder={selectTextInputPlaceholder}
suggestedLinks={suggestedLinks}
autocompleteView={autocompleteView}
onTextChanged={setText}
setRichText={setRichText}
onPhotoPasted={onPhotoPasted}
onSuggestedLinksChanged={setSuggestedLinks}
onError={setError}
/>
</View>
{quote ? (
<View style={s.mt5}>
<QuoteEmbed quote={quote} />
</View>
) : undefined}
<SelectedPhotos
selectedPhotos={selectedPhotos}
onSelectPhotos={onSelectPhotos}
/>
{!selectedPhotos.length && extLink && (
{selectedPhotos.length === 0 && extLink && (
<ExternalEmbed
link={extLink}
onRemove={() => setExtLink(undefined)}
/>
)}
{quote ? (
<View style={s.mt5}>
<QuoteEmbed quote={quote} />
</View>
) : undefined}
</ScrollView>
{!extLink &&
selectedPhotos.length === 0 &&
suggestedLinks.size > 0 &&
!quote ? (
suggestedLinks.size > 0 ? (
<View style={s.mb5}>
{Array.from(suggestedLinks).map(url => (
<TouchableOpacity
key={`suggested-${url}`}
testID="addLinkCardBtn"
style={[pal.borderDark, styles.addExtLinkBtn]}
onPress={() => onPressAddLinkCard(url)}>
<Text style={pal.text}>
@ -318,17 +324,17 @@ export const ComposePost = observer(function ComposePost({
) : null}
<View style={[pal.border, styles.bottomBar]}>
<SelectPhotoBtn
enabled={!quote && selectedPhotos.length < 4}
enabled={selectedPhotos.length < 4}
selectedPhotos={selectedPhotos}
onSelectPhotos={setSelectedPhotos}
/>
<OpenCameraBtn
enabled={!quote && selectedPhotos.length < 4}
enabled={selectedPhotos.length < 4}
selectedPhotos={selectedPhotos}
onSelectPhotos={setSelectedPhotos}
/>
<View style={s.flex1} />
<CharProgress count={text.length} />
<CharProgress count={graphemeLength} />
</View>
</SafeAreaView>
</TouchableWithoutFeedback>
@ -408,6 +414,7 @@ const styles = StyleSheet.create({
borderRadius: 24,
paddingHorizontal: 16,
paddingVertical: 12,
marginHorizontal: 10,
marginBottom: 4,
},
bottomBar: {