Merge branch 'main' into custom-algos

This commit is contained in:
Paul Frazee 2023-05-17 12:30:54 -05:00
commit 7aa1d9010e
99 changed files with 4234 additions and 716 deletions

View file

@ -14,7 +14,8 @@ export const BlurView = ({
...props
}: React.PropsWithChildren<BlurViewProps>) => {
// @ts-ignore using an RNW-specific attribute here -prf
style = addStyle(style, {backdropFilter: `blur(${blurAmount || 10}px`})
let blur = `blur(${blurAmount || 10}px`
style = addStyle(style, {backdropFilter: blur, WebkitBackdropFilter: blur})
if (blurType === 'dark') {
style = addStyle(style, styles.dark)
} else {

View file

@ -10,17 +10,19 @@ import {UserGroupIcon} from 'lib/icons'
import {usePalette} from 'lib/hooks/usePalette'
export function EmptyState({
testID,
icon,
message,
style,
}: {
testID?: string
icon: IconProp | 'user-group'
message: string
style?: StyleProp<ViewStyle>
}) {
const pal = usePalette('default')
return (
<View style={[styles.container, style]}>
<View testID={testID} style={[styles.container, style]}>
<View style={styles.iconContainer}>
{icon === 'user-group' ? (
<UserGroupIcon size="64" style={styles.icon} />

View file

@ -0,0 +1,88 @@
import React from 'react'
import {StyleSheet, View} from 'react-native'
import {
FontAwesomeIcon,
FontAwesomeIconStyle,
} from '@fortawesome/react-native-fontawesome'
import {IconProp} from '@fortawesome/fontawesome-svg-core'
import {Text} from './text/Text'
import {Button} from './forms/Button'
import {usePalette} from 'lib/hooks/usePalette'
import {s} from 'lib/styles'
interface Props {
testID?: string
icon: IconProp
message: string
buttonLabel: string
onPress: () => void
}
export function EmptyStateWithButton(props: Props) {
const pal = usePalette('default')
const palInverted = usePalette('inverted')
return (
<View testID={props.testID} style={styles.container}>
<View style={styles.iconContainer}>
<FontAwesomeIcon
icon={props.icon}
style={[styles.icon, pal.text]}
size={62}
/>
</View>
<Text type="xl-medium" style={[s.textCenter, pal.text]}>
{props.message}
</Text>
<View style={styles.btns}>
<Button
testID={props.testID ? `${props.testID}-button` : undefined}
type="inverted"
style={styles.btn}
onPress={props.onPress}>
<FontAwesomeIcon
icon="plus"
style={palInverted.text as FontAwesomeIconStyle}
size={14}
/>
<Text type="lg-medium" style={palInverted.text}>
{props.buttonLabel}
</Text>
</Button>
</View>
</View>
)
}
const styles = StyleSheet.create({
container: {
height: '100%',
paddingVertical: 40,
paddingHorizontal: 30,
},
iconContainer: {
marginBottom: 16,
},
icon: {
marginLeft: 'auto',
marginRight: 'auto',
},
btns: {
flexDirection: 'row',
justifyContent: 'center',
},
btn: {
gap: 10,
marginVertical: 20,
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 14,
paddingHorizontal: 24,
borderRadius: 30,
},
notice: {
borderRadius: 12,
paddingHorizontal: 12,
paddingVertical: 10,
marginHorizontal: 30,
},
})

View file

@ -66,6 +66,7 @@ export function UserAvatar({
if (!(await requestCameraAccessIfNeeded())) {
return
}
onSelectNewAvatar?.(
await openCamera(store, {
width: 1000,
@ -83,20 +84,21 @@ export function UserAvatar({
if (!(await requestPhotoAccessIfNeeded())) {
return
}
const items = await openPicker(store, {
aspect: [1, 1],
})
const item = items[0]
const croppedImage = await openCropper(store, {
mediaType: 'photo',
multiple: false,
cropperCircleOverlay: true,
height: item.height,
width: item.width,
path: item.path,
})
onSelectNewAvatar?.(
await openCropper(store, {
mediaType: 'photo',
path: items[0].path,
width: 1000,
height: 1000,
cropperCircleOverlay: true,
}),
)
onSelectNewAvatar?.(croppedImage)
},
},
{

View file

@ -55,10 +55,8 @@ export function UserBanner({
if (!(await requestPhotoAccessIfNeeded())) {
return
}
const items = await openPicker(store, {
mediaType: 'photo',
multiple: false,
})
const items = await openPicker(store)
onSelectNewBanner?.(
await openCropper(store, {
mediaType: 'photo',

View file

@ -20,11 +20,13 @@ export const ViewHeader = observer(function ({
canGoBack,
hideOnScroll,
showOnDesktop,
renderButton,
}: {
title: string
canGoBack?: boolean
hideOnScroll?: boolean
showOnDesktop?: boolean
renderButton?: () => JSX.Element
}) {
const pal = usePalette('default')
const store = useStores()
@ -46,7 +48,7 @@ export const ViewHeader = observer(function ({
if (isDesktopWeb) {
if (showOnDesktop) {
return <DesktopWebHeader title={title} />
return <DesktopWebHeader title={title} renderButton={renderButton} />
}
return null
} else {
@ -79,13 +81,23 @@ export const ViewHeader = observer(function ({
{title}
</Text>
</View>
<View style={canGoBack ? styles.backBtn : styles.backBtnWide} />
{renderButton ? (
renderButton()
) : (
<View style={canGoBack ? styles.backBtn : styles.backBtnWide} />
)}
</Container>
)
}
})
function DesktopWebHeader({title}: {title: string}) {
function DesktopWebHeader({
title,
renderButton,
}: {
title: string
renderButton?: () => JSX.Element
}) {
const pal = usePalette('default')
return (
<CenteredView style={[styles.header, styles.desktopHeader, pal.border]}>
@ -94,6 +106,7 @@ function DesktopWebHeader({title}: {title: string}) {
{title}
</Text>
</View>
{renderButton?.()}
</CenteredView>
)
}

View file

@ -22,7 +22,7 @@ import {
View,
ViewProps,
} from 'react-native'
import {addStyle, colors} from 'lib/styles'
import {addStyle} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette'
interface AddedProps {
@ -124,12 +124,6 @@ const styles = StyleSheet.create({
marginLeft: 'auto',
marginRight: 'auto',
},
containerLight: {
backgroundColor: colors.gray1,
},
containerDark: {
backgroundColor: colors.gray7,
},
fixedHeight: {
height: '100vh',
},

View file

@ -38,6 +38,7 @@ export function Button({
accessibilityLabel,
accessibilityHint,
accessibilityLabelledBy,
onAccessibilityEscape,
}: React.PropsWithChildren<{
type?: ButtonType
label?: string
@ -48,6 +49,7 @@ export function Button({
accessibilityLabel?: string
accessibilityHint?: string
accessibilityLabelledBy?: string
onAccessibilityEscape?: () => void
}>) {
const theme = useTheme()
const typeOuterStyle = choose<ViewStyle, Record<ButtonType, ViewStyle>>(
@ -126,6 +128,7 @@ export function Button({
},
},
)
const onPressWrapped = React.useCallback(
(event: Event) => {
event.stopPropagation()
@ -134,15 +137,30 @@ export function Button({
},
[onPress],
)
const getStyle = React.useCallback(
state => {
const arr = [typeOuterStyle, styles.outer, style]
if (state.pressed) {
arr.push({opacity: 0.6})
} else if (state.hovered) {
arr.push({opacity: 0.8})
}
return arr
},
[typeOuterStyle, style],
)
return (
<Pressable
style={[typeOuterStyle, styles.outer, style]}
style={getStyle}
onPress={onPressWrapped}
testID={testID}
accessibilityRole="button"
accessibilityLabel={accessibilityLabel}
accessibilityHint={accessibilityHint}
accessibilityLabelledBy={accessibilityLabelledBy}>
accessibilityLabelledBy={accessibilityLabelledBy}
onAccessibilityEscape={onAccessibilityEscape}>
{label ? (
<Text type="button" style={[typeLabelStyle, labelStyle]}>
{label}

View file

@ -209,7 +209,7 @@ export function PostDropdownBtn({
},
},
{sep: true},
{
!isAuthor && {
testID: 'postDropdownReportBtn',
icon: 'circle-exclamation',
label: 'Report post',
@ -339,7 +339,9 @@ const DropdownItems = ({
color={pal.text.color as string}
/>
)}
<Text style={[styles.label, pal.text]}>{item.label}</Text>
<Text style={[styles.label, pal.text]} numberOfLines={1}>
{item.label}
</Text>
</TouchableOpacity>
)
} else if (isSep(item)) {

View file

@ -63,6 +63,5 @@ const styles = StyleSheet.create({
position: 'absolute',
left: 6,
bottom: 6,
width: 46,
},
})

View file

@ -1,4 +1,4 @@
import React from 'react'
import React, {useCallback} from 'react'
import {
StyleProp,
StyleSheet,
@ -18,18 +18,14 @@ import ReactNativeHapticFeedback, {
// TriggerableAnimated,
// TriggerableAnimatedRef,
// } from './anim/TriggerableAnimated'
import {Text} from './text/Text'
import {PostDropdownBtn} from './forms/DropdownButton'
import {
HeartIcon,
HeartIconSolid,
RepostIcon,
CommentBottomArrow,
} from 'lib/icons'
import {Text} from '../text/Text'
import {PostDropdownBtn} from '../forms/DropdownButton'
import {HeartIcon, HeartIconSolid, CommentBottomArrow} from 'lib/icons'
import {s, colors} from 'lib/styles'
import {useTheme} from 'lib/ThemeContext'
import {useStores} from 'state/index'
import {isIOS} from 'platform/detection'
import {isIOS, isNative} from 'platform/detection'
import {RepostButton} from './RepostButton'
interface PostCtrlsOpts {
itemUri: string
@ -112,10 +108,12 @@ export function PostCtrls(opts: PostCtrlsOpts) {
// DISABLED see #135
// const repostRef = React.useRef<TriggerableAnimatedRef | null>(null)
// const likeRef = React.useRef<TriggerableAnimatedRef | null>(null)
const onRepost = () => {
const onRepost = useCallback(() => {
store.shell.closeModal()
if (!opts.isReposted) {
ReactNativeHapticFeedback.trigger(hapticImpact)
if (isNative) {
ReactNativeHapticFeedback.trigger(hapticImpact)
}
opts.onPressToggleRepost().catch(_e => undefined)
// DISABLED see #135
// repostRef.current?.trigger(
@ -128,9 +126,9 @@ export function PostCtrls(opts: PostCtrlsOpts) {
} else {
opts.onPressToggleRepost().catch(_e => undefined)
}
}
}, [opts, store.shell])
const onQuote = () => {
const onQuote = useCallback(() => {
store.shell.closeModal()
store.shell.openComposer({
quote: {
@ -141,17 +139,18 @@ export function PostCtrls(opts: PostCtrlsOpts) {
indexedAt: opts.indexedAt,
},
})
ReactNativeHapticFeedback.trigger(hapticImpact)
}
const onPressToggleRepostWrapper = () => {
store.shell.openModal({
name: 'repost',
onRepost: onRepost,
onQuote: onQuote,
isReposted: opts.isReposted,
})
}
if (isNative) {
ReactNativeHapticFeedback.trigger(hapticImpact)
}
}, [
opts.author,
opts.indexedAt,
opts.itemCid,
opts.itemUri,
opts.text,
store.shell,
])
const onPressToggleLikeWrapper = async () => {
if (!opts.isLiked) {
@ -181,7 +180,7 @@ export function PostCtrls(opts: PostCtrlsOpts) {
onPress={opts.onPressReply}
accessibilityRole="button"
accessibilityLabel="Reply"
accessibilityHint="Opens reply composer">
accessibilityHint="reply composer">
<CommentBottomArrow
style={[defaultCtrlColor, opts.big ? s.mt2 : styles.mt1]}
strokeWidth={3}
@ -193,39 +192,7 @@ export function PostCtrls(opts: PostCtrlsOpts) {
</Text>
) : undefined}
</TouchableOpacity>
<TouchableOpacity
testID="repostBtn"
hitSlop={HITSLOP}
onPress={onPressToggleRepostWrapper}
style={styles.ctrl}
accessibilityRole="button"
accessibilityLabel={opts.isReposted ? 'Undo repost' : 'Repost'}
accessibilityHint={
opts.isReposted
? `Remove your repost of ${opts.author}'s post`
: `Repost or quote post ${opts.author}'s post`
}>
<RepostIcon
style={
opts.isReposted
? (styles.ctrlIconReposted as StyleProp<ViewStyle>)
: defaultCtrlColor
}
strokeWidth={2.4}
size={opts.big ? 24 : 20}
/>
{typeof opts.repostCount !== 'undefined' ? (
<Text
testID="repostCount"
style={
opts.isReposted
? [s.bold, s.green3, s.f15, s.ml5]
: [defaultCtrlColor, s.f15, s.ml5]
}>
{opts.repostCount}
</Text>
) : undefined}
</TouchableOpacity>
<RepostButton {...opts} onRepost={onRepost} onQuote={onQuote} />
<TouchableOpacity
testID="likeBtn"
style={styles.ctrl}
@ -234,9 +201,7 @@ export function PostCtrls(opts: PostCtrlsOpts) {
accessibilityRole="button"
accessibilityLabel={opts.isLiked ? 'Unlike' : 'Like'}
accessibilityHint={
opts.isReposted
? `Removes like from ${opts.author}'s post`
: `Like ${opts.author}'s post`
opts.isReposted ? `Removes like from the post` : `Like the post`
}>
{opts.isLiked ? (
<HeartIconSolid
@ -309,9 +274,6 @@ const styles = StyleSheet.create({
padding: 5,
margin: -5,
},
ctrlIconReposted: {
color: colors.green3,
},
ctrlIconLiked: {
color: colors.red3,
},

View file

@ -0,0 +1,95 @@
import React, {useCallback} from 'react'
import {StyleProp, StyleSheet, TouchableOpacity, ViewStyle} from 'react-native'
import {RepostIcon} from 'lib/icons'
import {s, colors} from 'lib/styles'
import {useTheme} from 'lib/ThemeContext'
import {Text} from '../text/Text'
import {useStores} from 'state/index'
const HITSLOP = {top: 5, left: 5, bottom: 5, right: 5}
interface Props {
isReposted: boolean
repostCount?: number
big?: boolean
onRepost: () => void
onQuote: () => void
}
export const RepostButton = ({
isReposted,
repostCount,
big,
onRepost,
onQuote,
}: Props) => {
const store = useStores()
const theme = useTheme()
const defaultControlColor = React.useMemo(
() => ({
color: theme.palette.default.postCtrl,
}),
[theme],
)
const onPressToggleRepostWrapper = useCallback(() => {
store.shell.openModal({
name: 'repost',
onRepost: onRepost,
onQuote: onQuote,
isReposted,
})
}, [onRepost, onQuote, isReposted, store.shell])
return (
<TouchableOpacity
testID="repostBtn"
hitSlop={HITSLOP}
onPress={onPressToggleRepostWrapper}
style={styles.control}
accessibilityRole="button"
accessibilityLabel={isReposted ? 'Undo repost' : 'Repost'}
accessibilityHint={
isReposted
? `Remove your repost of the post`
: `Repost or quote post the post`
}>
<RepostIcon
style={
isReposted
? (styles.reposted as StyleProp<ViewStyle>)
: defaultControlColor
}
strokeWidth={2.4}
size={big ? 24 : 20}
/>
{typeof repostCount !== 'undefined' ? (
<Text
testID="repostCount"
style={
isReposted
? [s.bold, s.green3, s.f15, s.ml5]
: [defaultControlColor, s.f15, s.ml5]
}>
{repostCount}
</Text>
) : undefined}
</TouchableOpacity>
)
}
const styles = StyleSheet.create({
control: {
flexDirection: 'row',
alignItems: 'center',
padding: 5,
margin: -5,
},
reposted: {
color: colors.green3,
},
repostCount: {
color: 'currentColor',
},
})

View file

@ -0,0 +1,86 @@
import React, {useMemo} from 'react'
import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native'
import {RepostIcon} from 'lib/icons'
import {DropdownButton} from '../forms/DropdownButton'
import {colors} from 'lib/styles'
import {useTheme} from 'lib/ThemeContext'
import {Text} from '../text/Text'
interface Props {
isReposted: boolean
repostCount?: number
big?: boolean
onRepost: () => void
onQuote: () => void
}
export const RepostButton = ({
isReposted,
repostCount,
big,
onRepost,
onQuote,
}: Props) => {
const theme = useTheme()
const defaultControlColor = React.useMemo(
() => ({
color: theme.palette.default.postCtrl,
}),
[theme],
)
const items = useMemo(
() => [
{
label: isReposted ? 'Undo repost' : 'Repost',
icon: 'retweet' as const,
onPress: onRepost,
},
{label: 'Quote post', icon: 'quote-left' as const, onPress: onQuote},
],
[isReposted, onRepost, onQuote],
)
return (
<DropdownButton
type="bare"
items={items}
bottomOffset={4}
openToRight
rightOffset={-40}>
<View
style={[
styles.control,
(isReposted
? styles.reposted
: defaultControlColor) as StyleProp<ViewStyle>,
]}>
<RepostIcon strokeWidth={2.4} size={big ? 24 : 20} />
{typeof repostCount !== 'undefined' ? (
<Text
testID="repostCount"
type={isReposted ? 'md-bold' : 'md-medium'}
style={styles.repostCount}>
{repostCount ?? 0}
</Text>
) : undefined}
</View>
</DropdownButton>
)
}
const styles = StyleSheet.create({
control: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
gap: 4,
},
reposted: {
color: colors.green3,
},
repostCount: {
color: 'currentColor',
},
})

View file

@ -210,6 +210,5 @@ const styles = StyleSheet.create({
position: 'absolute',
left: 6,
bottom: 6,
width: 46,
},
})