mirror of https://github.com/tooot-app/app
commit
d3f4b85515
|
@ -1,9 +1,15 @@
|
|||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback, useMemo, useRef, useState } from 'react'
|
||||
import { Pressable, StyleProp, StyleSheet, ViewStyle } from 'react-native'
|
||||
import {
|
||||
Image,
|
||||
ImageStyle,
|
||||
Pressable,
|
||||
StyleProp,
|
||||
StyleSheet,
|
||||
ViewStyle
|
||||
} from 'react-native'
|
||||
import { Blurhash } from 'react-native-blurhash'
|
||||
import FastImage, { ImageStyle } from 'react-native-fast-image'
|
||||
|
||||
// blurhas -> if blurhash, show before any loading succeed
|
||||
// original -> load original
|
||||
|
@ -70,7 +76,7 @@ const GracefullyImage = React.memo(
|
|||
const previewView = useMemo(
|
||||
() =>
|
||||
uri.preview && !imageLoaded ? (
|
||||
<FastImage
|
||||
<Image
|
||||
source={{ uri: uri.preview }}
|
||||
style={[{ flex: 1 }, imageStyle]}
|
||||
/>
|
||||
|
@ -79,14 +85,14 @@ const GracefullyImage = React.memo(
|
|||
)
|
||||
const originalView = useMemo(
|
||||
() => (
|
||||
<FastImage
|
||||
<Image
|
||||
source={source}
|
||||
style={[{ flex: imageLoaded ? 1 : undefined }, imageStyle]}
|
||||
style={[{ flex: 1 }, imageStyle]}
|
||||
onLoad={onLoad}
|
||||
onError={onError}
|
||||
/>
|
||||
),
|
||||
[source, imageLoaded]
|
||||
[source]
|
||||
)
|
||||
const blurhashView = useMemo(() => {
|
||||
return blurhash && (hidden || !imageLoaded) ? (
|
||||
|
|
|
@ -23,7 +23,7 @@ const HeaderCenter = React.memo(
|
|||
/>
|
||||
)
|
||||
},
|
||||
() => true
|
||||
(prev, next) => prev.content === next.content
|
||||
)
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
|
|
@ -340,6 +340,12 @@ const ScreenCompose: React.FC<ScreenComposeProp> = ({
|
|||
[totalTextCount, composeState]
|
||||
)
|
||||
|
||||
const headerContent = useMemo(() => {
|
||||
return `${totalTextCount} / ${maxTootChars}${
|
||||
__DEV__ ? ` Dirty: ${composeState.dirty.toString()}` : ''
|
||||
}`
|
||||
}, [totalTextCount, maxTootChars, composeState.dirty])
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
style={styles.base}
|
||||
|
@ -358,26 +364,26 @@ const ScreenCompose: React.FC<ScreenComposeProp> = ({
|
|||
name='Screen-Compose-Root'
|
||||
component={ComposeRoot}
|
||||
options={{
|
||||
...Platform.select({
|
||||
ios: {
|
||||
headerTitle: headerContent,
|
||||
headerTitleStyle: {
|
||||
fontWeight:
|
||||
totalTextCount > maxTootChars
|
||||
? StyleConstants.Font.Weight.Bold
|
||||
: StyleConstants.Font.Weight.Normal,
|
||||
fontSize: StyleConstants.Font.Size.M
|
||||
},
|
||||
headerTintColor:
|
||||
totalTextCount > maxTootChars
|
||||
? theme.red
|
||||
: theme.secondary
|
||||
},
|
||||
android: {
|
||||
headerCenter: () => <HeaderCenter content={headerContent} />
|
||||
}
|
||||
}),
|
||||
headerLeft,
|
||||
headerTitle: `${totalTextCount} / ${maxTootChars}${
|
||||
__DEV__ ? ` Dirty: ${composeState.dirty.toString()}` : ''
|
||||
}`,
|
||||
headerTitleStyle: {
|
||||
fontWeight:
|
||||
totalTextCount > maxTootChars
|
||||
? StyleConstants.Font.Weight.Bold
|
||||
: StyleConstants.Font.Weight.Normal,
|
||||
fontSize: StyleConstants.Font.Size.M
|
||||
},
|
||||
headerTintColor:
|
||||
totalTextCount > maxTootChars ? theme.red : theme.secondary,
|
||||
headerCenter: () => (
|
||||
<HeaderCenter
|
||||
content={`${totalTextCount} / ${maxTootChars}${
|
||||
__DEV__ ? ` Dirty: ${composeState.dirty.toString()}` : ''
|
||||
}`}
|
||||
/>
|
||||
),
|
||||
headerRight
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
} from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback, useContext, useEffect, useState } from 'react'
|
||||
import React, { useCallback, useContext, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
Dimensions,
|
||||
|
@ -42,12 +42,6 @@ const ComposeDraftsListRoot: React.FC<Props> = ({ timestamp }) => {
|
|||
draft => draft.timestamp !== timestamp
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (instanceDrafts?.length === 0) {
|
||||
navigation.goBack()
|
||||
}
|
||||
}, [instanceDrafts?.length])
|
||||
|
||||
const actionWidth =
|
||||
StyleConstants.Font.Size.L + StyleConstants.Spacing.Global.PagePadding * 4
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import AttachmentVideo from '@components/Timeline/Shared/Attachment/Video'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useContext, useMemo } from 'react'
|
||||
import React, { useContext, useMemo, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ScrollView, StyleSheet, Text, TextInput, View } from 'react-native'
|
||||
import ComposeContext from '../utils/createContext'
|
||||
|
@ -55,8 +55,10 @@ const ComposeEditAttachmentRoot: React.FC<Props> = ({ index }) => {
|
|||
}
|
||||
})
|
||||
|
||||
const scrollViewRef = useRef<ScrollView>(null)
|
||||
|
||||
return (
|
||||
<ScrollView>
|
||||
<ScrollView ref={scrollViewRef}>
|
||||
{mediaDisplay}
|
||||
<View style={styles.altTextContainer}>
|
||||
<Text style={[styles.altTextInputHeading, { color: theme.primary }]}>
|
||||
|
@ -67,6 +69,7 @@ const ComposeEditAttachmentRoot: React.FC<Props> = ({ index }) => {
|
|||
styles.altTextInput,
|
||||
{ borderColor: theme.border, color: theme.primary }
|
||||
]}
|
||||
onFocus={() => scrollViewRef.current?.scrollToEnd()}
|
||||
autoCapitalize='none'
|
||||
autoCorrect={false}
|
||||
maxLength={1500}
|
||||
|
|
|
@ -4,7 +4,13 @@ import { useSearchQuery } from '@utils/queryHooks/search'
|
|||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { forEach, groupBy, sortBy } from 'lodash'
|
||||
import React, { useCallback, useContext, useEffect, useMemo } from 'react'
|
||||
import React, {
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef
|
||||
} from 'react'
|
||||
import { FlatList, Image, StyleSheet, View } from 'react-native'
|
||||
import { Circle } from 'react-native-animated-spinkit'
|
||||
import ComposeActions from './Root/Actions'
|
||||
|
@ -30,90 +36,93 @@ const prefetchEmojis = (
|
|||
})
|
||||
}
|
||||
|
||||
const ComposeRoot: React.FC = () => {
|
||||
const { theme } = useTheme()
|
||||
const ComposeRoot = React.memo(
|
||||
() => {
|
||||
const { theme } = useTheme()
|
||||
|
||||
const { composeState, composeDispatch } = useContext(ComposeContext)
|
||||
const { composeState, composeDispatch } = useContext(ComposeContext)
|
||||
|
||||
const { isFetching, data, refetch } = useSearchQuery({
|
||||
type:
|
||||
composeState.tag?.type === 'accounts' ||
|
||||
composeState.tag?.type === 'hashtags'
|
||||
? composeState.tag.type
|
||||
: undefined,
|
||||
term: composeState.tag?.text.substring(1),
|
||||
options: { enabled: false }
|
||||
})
|
||||
const { isFetching, data, refetch } = useSearchQuery({
|
||||
type:
|
||||
composeState.tag?.type === 'accounts' ||
|
||||
composeState.tag?.type === 'hashtags'
|
||||
? composeState.tag.type
|
||||
: undefined,
|
||||
term: composeState.tag?.text.substring(1),
|
||||
options: { enabled: false }
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
(composeState.tag?.type === 'accounts' ||
|
||||
composeState.tag?.type === 'hashtags') &&
|
||||
composeState.tag?.text
|
||||
) {
|
||||
refetch()
|
||||
}
|
||||
}, [composeState.tag])
|
||||
useEffect(() => {
|
||||
if (
|
||||
(composeState.tag?.type === 'accounts' ||
|
||||
composeState.tag?.type === 'hashtags') &&
|
||||
composeState.tag?.text
|
||||
) {
|
||||
refetch()
|
||||
}
|
||||
}, [composeState.tag])
|
||||
|
||||
const { data: emojisData } = useEmojisQuery({})
|
||||
useEffect(() => {
|
||||
if (emojisData && emojisData.length) {
|
||||
let sortedEmojis: { title: string; data: Mastodon.Emoji[] }[] = []
|
||||
forEach(
|
||||
groupBy(sortBy(emojisData, ['category', 'shortcode']), 'category'),
|
||||
(value, key) => sortedEmojis.push({ title: key, data: value })
|
||||
)
|
||||
composeDispatch({
|
||||
type: 'emoji',
|
||||
payload: { ...composeState.emoji, emojis: sortedEmojis }
|
||||
})
|
||||
prefetchEmojis(sortedEmojis)
|
||||
}
|
||||
}, [emojisData])
|
||||
const { data: emojisData } = useEmojisQuery({})
|
||||
useEffect(() => {
|
||||
if (emojisData && emojisData.length) {
|
||||
let sortedEmojis: { title: string; data: Mastodon.Emoji[] }[] = []
|
||||
forEach(
|
||||
groupBy(sortBy(emojisData, ['category', 'shortcode']), 'category'),
|
||||
(value, key) => sortedEmojis.push({ title: key, data: value })
|
||||
)
|
||||
composeDispatch({
|
||||
type: 'emoji',
|
||||
payload: { ...composeState.emoji, emojis: sortedEmojis }
|
||||
})
|
||||
prefetchEmojis(sortedEmojis)
|
||||
}
|
||||
}, [emojisData])
|
||||
|
||||
const listEmpty = useMemo(() => {
|
||||
if (isFetching) {
|
||||
return (
|
||||
<View key='listEmpty' style={styles.loading}>
|
||||
<Circle
|
||||
size={StyleConstants.Font.Size.M * 1.25}
|
||||
color={theme.secondary}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
}, [isFetching])
|
||||
const listEmpty = useMemo(() => {
|
||||
if (isFetching) {
|
||||
return (
|
||||
<View key='listEmpty' style={styles.loading}>
|
||||
<Circle
|
||||
size={StyleConstants.Font.Size.M * 1.25}
|
||||
color={theme.secondary}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
}, [isFetching])
|
||||
|
||||
const listItem = useCallback(
|
||||
({ item }) => (
|
||||
<ComposeRootSuggestion
|
||||
item={item}
|
||||
composeState={composeState}
|
||||
composeDispatch={composeDispatch}
|
||||
/>
|
||||
),
|
||||
[composeState]
|
||||
)
|
||||
const listItem = useCallback(
|
||||
({ item }) => (
|
||||
<ComposeRootSuggestion
|
||||
item={item}
|
||||
composeState={composeState}
|
||||
composeDispatch={composeDispatch}
|
||||
/>
|
||||
),
|
||||
[composeState]
|
||||
)
|
||||
|
||||
return (
|
||||
<View style={styles.base}>
|
||||
<FlatList
|
||||
renderItem={listItem}
|
||||
ListEmptyComponent={listEmpty}
|
||||
keyboardShouldPersistTaps='handled'
|
||||
ListHeaderComponent={ComposeRootHeader}
|
||||
ListFooterComponent={ComposeRootFooter}
|
||||
ItemSeparatorComponent={ComponentSeparator}
|
||||
// @ts-ignore
|
||||
data={data ? data[composeState.tag?.type] : undefined}
|
||||
keyExtractor={() => Math.random().toString()}
|
||||
/>
|
||||
<ComposeActions />
|
||||
<ComposeDrafts />
|
||||
<ComposePosting />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<View style={styles.base}>
|
||||
<FlatList
|
||||
renderItem={listItem}
|
||||
ListEmptyComponent={listEmpty}
|
||||
keyboardShouldPersistTaps='always'
|
||||
ListHeaderComponent={ComposeRootHeader}
|
||||
ListFooterComponent={ComposeRootFooter}
|
||||
ItemSeparatorComponent={ComponentSeparator}
|
||||
// @ts-ignore
|
||||
data={data ? data[composeState.tag?.type] : undefined}
|
||||
keyExtractor={() => Math.random().toString()}
|
||||
/>
|
||||
<ComposeActions />
|
||||
<ComposeDrafts />
|
||||
<ComposePosting />
|
||||
</View>
|
||||
)
|
||||
},
|
||||
() => true
|
||||
)
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
base: {
|
||||
|
|
|
@ -246,7 +246,7 @@ const ComposeAttachments: React.FC = () => {
|
|||
snapToAlignment='center'
|
||||
renderItem={renderAttachment}
|
||||
snapToOffsets={snapToOffsets}
|
||||
keyboardShouldPersistTaps='handled'
|
||||
keyboardShouldPersistTaps='always'
|
||||
showsHorizontalScrollIndicator={false}
|
||||
data={composeState.attachments.uploads}
|
||||
keyExtractor={item =>
|
||||
|
|
|
@ -76,7 +76,7 @@ const ComposeEmojis: React.FC = () => {
|
|||
<View style={styles.base}>
|
||||
<SectionList
|
||||
horizontal
|
||||
keyboardShouldPersistTaps='handled'
|
||||
keyboardShouldPersistTaps='always'
|
||||
sections={composeState.emoji.emojis || []}
|
||||
keyExtractor={item => item.shortcode}
|
||||
renderSectionHeader={listHeader}
|
||||
|
|
|
@ -46,7 +46,7 @@ const ComposeSpoilerInput: React.FC = () => {
|
|||
})
|
||||
}}
|
||||
ref={composeState.textInputFocus.refs.spoiler}
|
||||
scrollEnabled
|
||||
scrollEnabled={false}
|
||||
onFocus={() =>
|
||||
composeDispatch({
|
||||
type: 'textInputFocus',
|
||||
|
|
|
@ -52,7 +52,7 @@ const ComposeTextInput: React.FC = () => {
|
|||
})
|
||||
}}
|
||||
ref={composeState.textInputFocus.refs.text}
|
||||
scrollEnabled
|
||||
scrollEnabled={false}
|
||||
>
|
||||
<Text>{composeState.text.formatted}</Text>
|
||||
</TextInput>
|
||||
|
|
|
@ -86,9 +86,9 @@ const HeaderComponent = React.memo(
|
|||
analytics('imageviewer_more_share_press')
|
||||
switch (Platform.OS) {
|
||||
case 'ios':
|
||||
return Share.share({ url: imageUrls[currentIndex].url })
|
||||
Share.share({ url: imageUrls[currentIndex].url })
|
||||
case 'android':
|
||||
return Share.share({ message: imageUrls[currentIndex].url })
|
||||
Share.share({ message: imageUrls[currentIndex].url })
|
||||
}
|
||||
break
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ const ScreenMeSwitchRoot: React.FC = () => {
|
|||
const instanceActive = useSelector(getInstanceActive)
|
||||
|
||||
return (
|
||||
<ScrollView style={styles.base} keyboardShouldPersistTaps='handled'>
|
||||
<ScrollView style={styles.base} keyboardShouldPersistTaps='always'>
|
||||
<View style={[styles.firstSection, { borderBottomColor: theme.border }]}>
|
||||
<Text style={[styles.header, { color: theme.primary }]}>
|
||||
{t('content.existing')}
|
||||
|
|
Loading…
Reference in New Issue