Fix all type errors

zio/stable
Paul Frazee 2023-01-26 11:25:52 -06:00
parent c4ba5e7fd5
commit 7e3f6f0306
45 changed files with 377 additions and 294 deletions

View File

@ -6,7 +6,7 @@ const BottomSheetModalContext = React.createContext(null)
const BottomSheetModalProvider = (props: any) => { const BottomSheetModalProvider = (props: any) => {
return <BottomSheetModalContext.Provider {...props} value={{}} /> return <BottomSheetModalContext.Provider {...props} value={{}} />
} }
class BottomSheet extends React.Component { class BottomSheet extends React.Component<{onClose?: () => void}> {
snapToIndex() {} snapToIndex() {}
snapToPosition() {} snapToPosition() {}
expand() {} expand() {}

View File

@ -9,11 +9,9 @@ import {MeModel} from '../src/state/models/me'
import {OnboardModel} from '../src/state/models/onboard' import {OnboardModel} from '../src/state/models/onboard'
import {ProfilesViewModel} from '../src/state/models/profiles-view' import {ProfilesViewModel} from '../src/state/models/profiles-view'
import {LinkMetasViewModel} from '../src/state/models/link-metas-view' import {LinkMetasViewModel} from '../src/state/models/link-metas-view'
import {MembershipsViewModel} from '../src/state/models/memberships-view'
import {FeedModel} from '../src/state/models/feed-view' import {FeedModel} from '../src/state/models/feed-view'
import {NotificationsViewModel} from '../src/state/models/notifications-view' import {NotificationsViewModel} from '../src/state/models/notifications-view'
import {ProfileViewModel} from '../src/state/models/profile-view' import {ProfileViewModel} from '../src/state/models/profile-view'
import {MembersViewModel} from '../src/state/models/members-view'
import {ProfileUiModel, Sections} from '../src/state/models/profile-ui' import {ProfileUiModel, Sections} from '../src/state/models/profile-ui'
import {SessionServiceClient} from '@atproto/api' import {SessionServiceClient} from '@atproto/api'
import {UserAutocompleteViewModel} from '../src/state/models/user-autocomplete-view' import {UserAutocompleteViewModel} from '../src/state/models/user-autocomplete-view'
@ -70,95 +68,13 @@ export const mockedProfileStore = {
// unknown required because of the missing private methods: _xLoading, _xIdle, _load, _replaceAll // unknown required because of the missing private methods: _xLoading, _xIdle, _load, _replaceAll
} as unknown as ProfileViewModel } as unknown as ProfileViewModel
export const mockedMembersStore = {
isLoading: false,
isRefreshing: false,
hasLoaded: true,
error: '',
params: {
actor: 'test actor',
},
subject: {
did: 'test did',
handle: '',
displayName: '',
declaration: {
cid: '',
actorType: '',
},
avatar: undefined,
},
members: [
{
did: 'test did2',
declaration: {
cid: '',
actorType: '',
},
handle: 'testhandle',
displayName: 'test name',
indexedAt: '',
},
],
rootStore: {} as RootStoreModel,
hasContent: true,
hasError: false,
isEmpty: false,
isMember: jest.fn(),
setup: jest.fn().mockResolvedValue({aborted: false}),
refresh: jest.fn().mockResolvedValue({}),
loadMore: jest.fn(),
removeMember: jest.fn(),
// unknown required because of the missing private methods: _xLoading, _xIdle, _fetch, _replaceAll, _append
} as unknown as MembersViewModel
export const mockedMembershipsStore = {
isLoading: false,
isRefreshing: false,
hasLoaded: true,
error: '',
params: {
actor: '',
limit: 1,
before: '',
},
subject: {
did: 'test did',
handle: '',
displayName: '',
declaration: {cid: '', actorType: ''},
avatar: undefined,
},
memberships: [
{
did: 'test did',
declaration: {
cid: '',
actorType: 'app.bsky.system.actorUser',
},
handle: ',',
displayName: '',
createdAt: '',
indexedAt: '',
_reactKey: 'item-1',
},
],
rootStore: {} as RootStoreModel,
hasContent: true,
hasError: false,
isEmpty: false,
isMemberOf: jest.fn(),
setup: jest.fn().mockResolvedValue({aborted: false}),
refresh: jest.fn().mockResolvedValue({}),
loadMore: jest.fn(),
// unknown required because of the missing private methods: _xLoading, _xIdle, _fetch, _replaceAll, _append
} as unknown as MembershipsViewModel
export const mockedFeedItemStore = { export const mockedFeedItemStore = {
_reactKey: 'item-1', _reactKey: 'item-1',
_isThreadParent: false, _isThreadParent: false,
_isThreadChildElided: false, _isThreadChildElided: false,
_isThreadChild: false, _isThreadChild: false,
_hideParent: false,
_isRenderingAsThread: false,
post: { post: {
uri: 'testuri', uri: 'testuri',
cid: 'test cid', cid: 'test cid',
@ -475,13 +391,13 @@ export const mockedSessionStore = {
export const mockedNavigationTabStore = { export const mockedNavigationTabStore = {
serialize: jest.fn(), serialize: jest.fn(),
hydrate: jest.fn(), hydrate: jest.fn(),
id: 0, id: '0',
history: [ history: [
{ {
url: '', url: '',
ts: 0, ts: 0,
title: '', title: '',
id: 0, id: '0',
}, },
], ],
index: 0, index: 0,
@ -490,7 +406,7 @@ export const mockedNavigationTabStore = {
url: '', url: '',
ts: 0, ts: 0,
title: '', title: '',
id: 0, id: '0',
}, },
canGoBack: false, canGoBack: false,
canGoForward: false, canGoForward: false,
@ -499,7 +415,7 @@ export const mockedNavigationTabStore = {
url: '', url: '',
title: '', title: '',
index: 0, index: 0,
id: 0, id: '0',
}, },
], ],
forwardTen: [ forwardTen: [
@ -507,7 +423,7 @@ export const mockedNavigationTabStore = {
url: '', url: '',
title: '', title: '',
index: 0, index: 0,
id: 0, id: '0',
}, },
], ],
navigate: jest.fn(), navigate: jest.fn(),
@ -524,7 +440,7 @@ export const mockedNavigationTabStore = {
url: '/', url: '/',
title: '', title: '',
index: 1, index: 1,
id: 1, id: '1',
}, },
], ],
getForwardList: jest.fn(), getForwardList: jest.fn(),
@ -582,13 +498,13 @@ export const mockedMeStore = {
avatar: '', avatar: '',
notificationCount: 0, notificationCount: 0,
rootStore: {} as RootStoreModel, rootStore: {} as RootStoreModel,
memberships: mockedMembershipsStore,
mainFeed: mockedFeedStore, mainFeed: mockedFeedStore,
notifications: mockedNotificationsStore, notifications: mockedNotificationsStore,
clear: jest.fn(), clear: jest.fn(),
load: jest.fn(), load: jest.fn(),
clearNotificationCount: jest.fn(), clearNotificationCount: jest.fn(),
fetchNotifications: jest.fn(), fetchNotifications: jest.fn(),
bgFetchNotifications: jest.fn(),
refreshMemberships: jest.fn(), refreshMemberships: jest.fn(),
} as MeModel } as MeModel
@ -650,6 +566,11 @@ export const mockedRootStore = {
hydrate: jest.fn(), hydrate: jest.fn(),
fetchStateUpdate: jest.fn(), fetchStateUpdate: jest.fn(),
clearAll: jest.fn(), clearAll: jest.fn(),
onPostDeleted: jest.fn(),
emitPostDeleted: jest.fn(),
initBgFetch: jest.fn(),
onBgFetch: jest.fn(),
onBgFetchTimeout: jest.fn(),
session: mockedSessionStore, session: mockedSessionStore,
nav: mockedNavigationStore, nav: mockedNavigationStore,
shell: mockedShellStore, shell: mockedShellStore,
@ -663,8 +584,6 @@ export const mockedRootStore = {
export const mockedProfileUiStore = { export const mockedProfileUiStore = {
profile: mockedProfileStore, profile: mockedProfileStore,
feed: mockedFeedStore, feed: mockedFeedStore,
memberships: mockedMembershipsStore,
members: mockedMembersStore,
selectedViewIndex: 0, selectedViewIndex: 0,
rootStore: mockedRootStore, rootStore: mockedRootStore,
params: { params: {
@ -675,7 +594,7 @@ export const mockedProfileUiStore = {
isRefreshing: false, isRefreshing: false,
isUser: true, isUser: true,
isScene: false, isScene: false,
selectorItems: [Sections.Posts, Sections.PostsWithReplies, Sections.Scenes], selectorItems: [Sections.Posts, Sections.PostsWithReplies],
selectedView: Sections.Posts, selectedView: Sections.Posts,
setSelectedViewIndex: jest.fn(), setSelectedViewIndex: jest.fn(),
setup: jest.fn().mockResolvedValue({aborted: false}), setup: jest.fn().mockResolvedValue({aborted: false}),

View File

@ -41,6 +41,7 @@ describe('extractHtmlMeta', () => {
it.each(cases)( it.each(cases)(
'given the html tag %p, returns %p', 'given the html tag %p, returns %p',
// @ts-ignore not worth fixing -prf
(input, expectedResult) => { (input, expectedResult) => {
const output = extractHtmlMeta({html: input as string, hostname: ''}) const output = extractHtmlMeta({html: input as string, hostname: ''})
expect(output).toEqual(expectedResult) expect(output).toEqual(expectedResult)
@ -86,6 +87,7 @@ describe('extractHtmlMeta', () => {
title: '@bluesky on Twitter', title: '@bluesky on Twitter',
} }
const output = extractHtmlMeta({ const output = extractHtmlMeta({
html: '',
hostname: 'twitter.com', hostname: 'twitter.com',
pathname: '/bluesky', pathname: '/bluesky',
}) })
@ -97,6 +99,7 @@ describe('extractHtmlMeta', () => {
title: 'Tweet by @bluesky', title: 'Tweet by @bluesky',
} }
const output = extractHtmlMeta({ const output = extractHtmlMeta({
html: '',
hostname: 'twitter.com', hostname: 'twitter.com',
pathname: '/bluesky/status/1582437529969917953', pathname: '/bluesky/status/1582437529969917953',
}) })
@ -108,6 +111,7 @@ describe('extractHtmlMeta', () => {
title: 'Twitter', title: 'Twitter',
} }
const output = extractHtmlMeta({ const output = extractHtmlMeta({
html: '',
hostname: 'twitter.com', hostname: 'twitter.com',
pathname: '/i/articles/follows/-1675653703?time_window=24', pathname: '/i/articles/follows/-1675653703?time_window=24',
}) })

View File

@ -5,7 +5,7 @@ import {mockedRootStore, mockedShellStore} from '../../../__mocks__/state-mock'
describe('useOnMainScroll', () => { describe('useOnMainScroll', () => {
const mockedProps = { const mockedProps = {
navIdx: [0, 0] as [number, number], navIdx: '0-0',
params: {}, params: {},
visible: true, visible: true,
} }

View File

@ -5,7 +5,7 @@ import {cleanup, fireEvent, render} from '../../../jest/test-utils'
describe('Search', () => { describe('Search', () => {
jest.useFakeTimers() jest.useFakeTimers()
const mockedProps = { const mockedProps = {
navIdx: [0, 0] as [number, number], navIdx: '0-0',
params: { params: {
name: 'test name', name: 'test name',
}, },

View File

@ -179,7 +179,7 @@ async function genMockData(pdsUrl: string): Promise<TestUsers> {
did: subject.did, did: subject.did,
declarationCid: subject.declarationCid, declarationCid: subject.declarationCid,
}, },
createdAt: date.next().value, createdAt: date.next().value || '',
}, },
) )
} }

View File

@ -2,7 +2,7 @@ import React, {useState, useEffect} from 'react'
import * as view from './view/index' import * as view from './view/index'
import {RootStoreModel, setupState, RootStoreProvider} from './state' import {RootStoreModel, setupState, RootStoreProvider} from './state'
import {DesktopWebShell} from './view/shell/desktop-web' import {DesktopWebShell} from './view/shell/desktop-web'
import Toast from 'react-native-root-toast' // import Toast from 'react-native-root-toast' TODO
function App() { function App() {
const [rootStore, setRootStore] = useState<RootStoreModel | undefined>( const [rootStore, setRootStore] = useState<RootStoreModel | undefined>(
@ -23,9 +23,9 @@ function App() {
return ( return (
<RootStoreProvider value={rootStore}> <RootStoreProvider value={rootStore}>
<DesktopWebShell /> <DesktopWebShell />
<Toast.ToastContainer />
</RootStoreProvider> </RootStoreProvider>
) )
// <Toast.ToastContainer /> TODO
} }
export default App export default App

View File

@ -63,7 +63,7 @@ export const extractHtmlMeta = ({
// Workaround for some websites not having a title or description in the meta tags in the initial serve // Workaround for some websites not having a title or description in the meta tags in the initial serve
if (isYoutubeUrl) { if (isYoutubeUrl) {
res = {...res, ...extractYoutubeMeta(html)} res = {...res, ...extractYoutubeMeta(html)}
} else if (isTwitterUrl) { } else if (isTwitterUrl && pathname) {
res = {...extractTwitterMeta({pathname})} res = {...extractTwitterMeta({pathname})}
} }

View File

@ -98,8 +98,6 @@ export class ProfileUiModel {
const view = this.currentView const view = this.currentView
if (view instanceof FeedModel) { if (view instanceof FeedModel) {
await view.update() await view.update()
} else {
await view.refresh()
} }
} }

View File

@ -16,7 +16,10 @@ import PasteInput, {
PasteInputRef, PasteInputRef,
} from '@mattermost/react-native-paste-input' } from '@mattermost/react-native-paste-input'
import LinearGradient from 'react-native-linear-gradient' import LinearGradient from 'react-native-linear-gradient'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {
FontAwesomeIcon,
FontAwesomeIconStyle,
} from '@fortawesome/react-native-fontawesome'
import {useAnalytics} from '@segment/analytics-react-native' import {useAnalytics} from '@segment/analytics-react-native'
import {UserAutocompleteViewModel} from '../../../state/models/user-autocomplete-view' import {UserAutocompleteViewModel} from '../../../state/models/user-autocomplete-view'
import {Autocomplete} from './Autocomplete' import {Autocomplete} from './Autocomplete'
@ -438,7 +441,11 @@ export const ComposePost = observer(function ComposePost({
hitSlop={HITSLOP}> hitSlop={HITSLOP}>
<FontAwesomeIcon <FontAwesomeIcon
icon={['far', 'image']} icon={['far', 'image']}
style={selectedPhotos.length < 4 ? pal.link : pal.textLight} style={
(selectedPhotos.length < 4
? pal.link
: pal.textLight) as FontAwesomeIconStyle
}
size={24} size={24}
/> />
</TouchableOpacity> </TouchableOpacity>

View File

@ -1,6 +1,9 @@
import React, {useCallback} from 'react' import React, {useCallback} from 'react'
import {Image, StyleSheet, TouchableOpacity, ScrollView} from 'react-native' import {Image, StyleSheet, TouchableOpacity, ScrollView} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {
FontAwesomeIcon,
FontAwesomeIconStyle,
} from '@fortawesome/react-native-fontawesome'
import { import {
openPicker, openPicker,
openCamera, openCamera,
@ -131,13 +134,21 @@ export const PhotoCarouselPicker = ({
testID="openCameraButton" testID="openCameraButton"
style={[styles.galleryButton, pal.border, styles.photo]} style={[styles.galleryButton, pal.border, styles.photo]}
onPress={handleOpenCamera}> onPress={handleOpenCamera}>
<FontAwesomeIcon icon="camera" size={24} style={pal.link} /> <FontAwesomeIcon
icon="camera"
size={24}
style={pal.link as FontAwesomeIconStyle}
/>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity <TouchableOpacity
testID="openGalleryButton" testID="openGalleryButton"
style={[styles.galleryButton, pal.border, styles.photo]} style={[styles.galleryButton, pal.border, styles.photo]}
onPress={handleOpenGallery}> onPress={handleOpenGallery}>
<FontAwesomeIcon icon="image" style={pal.link} size={24} /> <FontAwesomeIcon
icon="image"
style={pal.link as FontAwesomeIconStyle}
size={24}
/>
</TouchableOpacity> </TouchableOpacity>
{localPhotos.photos.map((item: PhotoIdentifier, index: number) => ( {localPhotos.photos.map((item: PhotoIdentifier, index: number) => (
<TouchableOpacity <TouchableOpacity

View File

@ -7,7 +7,10 @@ import {
View, View,
} from 'react-native' } from 'react-native'
import LinearGradient from 'react-native-linear-gradient' import LinearGradient from 'react-native-linear-gradient'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {
FontAwesomeIcon,
FontAwesomeIconStyle,
} from '@fortawesome/react-native-fontawesome'
import {observer} from 'mobx-react-lite' import {observer} from 'mobx-react-lite'
import _omit from 'lodash.omit' import _omit from 'lodash.omit'
import {ErrorScreen} from '../util/error/ErrorScreen' import {ErrorScreen} from '../util/error/ErrorScreen'
@ -199,7 +202,7 @@ const User = ({
style={[styles.btn, styles.gradientBtn]}> style={[styles.btn, styles.gradientBtn]}>
<FontAwesomeIcon <FontAwesomeIcon
icon="plus" icon="plus"
style={[s.white, s.mr5]} style={[s.white as FontAwesomeIconStyle, s.mr5]}
size={15} size={15}
/> />
<Text style={[s.white, s.fw600, s.f15]}>Follow</Text> <Text style={[s.white, s.fw600, s.f15]}>Follow</Text>

View File

@ -0,0 +1,21 @@
// default implementation fallback for web
import React from 'react'
import {View} from 'react-native'
import {ImageSource} from '../../@types'
type Props = {
imageSrc: ImageSource
onRequestClose: () => void
onZoom: (scaled: boolean) => void
onLongPress: (image: ImageSource) => void
delayLongPress: number
swipeToCloseEnabled?: boolean
doubleTapToZoomEnabled?: boolean
}
const ImageItem = (_props: Props) => {
return <View />
}
export default React.memo(ImageItem)

View File

@ -47,8 +47,8 @@ const useImageDimensions = (image: ImageSource): Dimensions | null => {
if (imageDimensions) { if (imageDimensions) {
resolve(imageDimensions) resolve(imageDimensions)
} else { } else {
// @ts-ignore
Image.getSizeWithHeaders( Image.getSizeWithHeaders(
// @ts-ignore
source.uri, source.uri,
source.headers, source.headers,
(width: number, height: number) => { (width: number, height: number) => {

View File

@ -61,7 +61,7 @@ const usePanResponder = ({
let tmpTranslate: Position | null = null let tmpTranslate: Position | null = null
let isDoubleTapPerformed = false let isDoubleTapPerformed = false
let lastTapTS: number | null = null let lastTapTS: number | null = null
let longPressHandlerRef: number | null = null let longPressHandlerRef: NodeJS.Timeout | null = null
const meaningfulShift = MIN_DIMENSION * 0.01 const meaningfulShift = MIN_DIMENSION * 0.01
const scaleValue = new Animated.Value(initialScale) const scaleValue = new Animated.Value(initialScale)

View File

@ -77,6 +77,7 @@ export const getImageStyles = (
const transform = translate.getTranslateTransform() const transform = translate.getTranslateTransform()
if (scale) { if (scale) {
// @ts-ignore TODO - is scale incorrect? might need to remove -prf
transform.push({scale}, {perspective: new Animated.Value(1000)}) transform.push({scale}, {perspective: new Animated.Value(1000)})
} }

View File

@ -5,6 +5,7 @@ import ImageView from './ImageViewing'
import {useStores} from '../../../state' import {useStores} from '../../../state'
import * as models from '../../../state/models/shell-ui' import * as models from '../../../state/models/shell-ui'
import {saveImageModal} from '../../../lib/images' import {saveImageModal} from '../../../lib/images'
import {ImageSource} from './ImageViewing/@types'
export const Lightbox = observer(function Lightbox() { export const Lightbox = observer(function Lightbox() {
const store = useStores() const store = useStores()
@ -15,8 +16,14 @@ export const Lightbox = observer(function Lightbox() {
const onClose = () => { const onClose = () => {
store.shell.closeLightbox() store.shell.closeLightbox()
} }
const onLongPress = ({uri}: {uri: string}) => { const onLongPress = (image: ImageSource) => {
saveImageModal({uri}) if (
typeof image === 'object' &&
'uri' in image &&
typeof image.uri === 'string'
) {
saveImageModal({uri: image.uri})
}
} }
if (store.shell.activeLightbox?.name === 'profile-image') { if (store.shell.activeLightbox?.name === 'profile-image') {

View File

@ -9,7 +9,10 @@ import {
TouchableOpacity, TouchableOpacity,
View, View,
} from 'react-native' } from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {
FontAwesomeIcon,
FontAwesomeIconStyle,
} from '@fortawesome/react-native-fontawesome'
import {ComAtprotoAccountCreate} from '@atproto/api' import {ComAtprotoAccountCreate} from '@atproto/api'
import * as EmailValidator from 'email-validator' import * as EmailValidator from 'email-validator'
import {useAnalytics} from '@segment/analytics-react-native' import {useAnalytics} from '@segment/analytics-react-native'
@ -264,7 +267,7 @@ export const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => {
<Picker <Picker
style={[pal.text, styles.picker]} style={[pal.text, styles.picker]}
labelStyle={styles.pickerLabel} labelStyle={styles.pickerLabel}
iconStyle={pal.textLight} iconStyle={pal.textLight as FontAwesomeIconStyle}
value={userDomain} value={userDomain}
items={serviceDescription.availableUserDomains.map(d => ({ items={serviceDescription.availableUserDomains.map(d => ({
label: `.${d}`, label: `.${d}`,
@ -371,7 +374,11 @@ const Policies = ({
return ( return (
<View style={styles.policies}> <View style={styles.policies}>
<View style={[styles.errorIcon, {borderColor: pal.colors.text}, s.mt2]}> <View style={[styles.errorIcon, {borderColor: pal.colors.text}, s.mt2]}>
<FontAwesomeIcon icon="exclamation" style={pal.textLight} size={10} /> <FontAwesomeIcon
icon="exclamation"
style={pal.textLight as FontAwesomeIconStyle}
size={10}
/>
</View> </View>
<Text style={[pal.textLight, s.pl5, s.flex1]}> <Text style={[pal.textLight, s.pl5, s.flex1]}>
This service has not provided terms of service or a privacy policy. This service has not provided terms of service or a privacy policy.

View File

@ -8,7 +8,10 @@ import {
TouchableOpacity, TouchableOpacity,
View, View,
} from 'react-native' } from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {
FontAwesomeIcon,
FontAwesomeIconStyle,
} from '@fortawesome/react-native-fontawesome'
import * as EmailValidator from 'email-validator' import * as EmailValidator from 'email-validator'
import {sessionClient as AtpApi, SessionServiceClient} from '@atproto/api' import {sessionClient as AtpApi, SessionServiceClient} from '@atproto/api'
import {useAnalytics} from '@segment/analytics-react-native' import {useAnalytics} from '@segment/analytics-react-native'
@ -337,7 +340,11 @@ const LoginForm = ({
{toNiceDomain(serviceUrl)} {toNiceDomain(serviceUrl)}
</Text> </Text>
<View style={[pal.btn, styles.textBtnFakeInnerBtn]}> <View style={[pal.btn, styles.textBtnFakeInnerBtn]}>
<FontAwesomeIcon icon="pen" size={12} style={pal.textLight} /> <FontAwesomeIcon
icon="pen"
size={12}
style={pal.textLight as FontAwesomeIconStyle}
/>
</View> </View>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
@ -514,7 +521,11 @@ const ForgotPasswordForm = ({
{toNiceDomain(serviceUrl)} {toNiceDomain(serviceUrl)}
</Text> </Text>
<View style={[pal.btn, styles.textBtnFakeInnerBtn]}> <View style={[pal.btn, styles.textBtnFakeInnerBtn]}>
<FontAwesomeIcon icon="pen" size={12} style={pal.text} /> <FontAwesomeIcon
icon="pen"
size={12}
style={pal.text as FontAwesomeIconStyle}
/>
</View> </View>
</TouchableOpacity> </TouchableOpacity>
<View style={[pal.borderDark, styles.groupContent]}> <View style={[pal.borderDark, styles.groupContent]}>

View File

@ -108,7 +108,6 @@ export function Component({
<UserBanner <UserBanner
banner={userBanner} banner={userBanner}
onSelectNewBanner={onSelectNewBanner} onSelectNewBanner={onSelectNewBanner}
handle={profileView.handle}
/> />
<View style={styles.avi}> <View style={styles.avi}>
<UserAvatar <UserAvatar

View File

@ -62,18 +62,10 @@ export const Modal = observer(function Modal() {
) )
} else if (store.shell.activeModal?.name === 'report-post') { } else if (store.shell.activeModal?.name === 'report-post') {
snapPoints = ReportPostModal.snapPoints snapPoints = ReportPostModal.snapPoints
element = ( element = <ReportPostModal.Component />
<ReportPostModal.Component
{...(store.shell.activeModal as models.ReportPostModal)}
/>
)
} else if (store.shell.activeModal?.name === 'report-account') { } else if (store.shell.activeModal?.name === 'report-account') {
snapPoints = ReportAccountModal.snapPoints snapPoints = ReportAccountModal.snapPoints
element = ( element = <ReportAccountModal.Component />
<ReportAccountModal.Component
{...(store.shell.activeModal as models.ReportAccountModal)}
/>
)
} else { } else {
element = <View /> element = <View />
} }

View File

@ -1,6 +1,9 @@
import React, {useState} from 'react' import React, {useState} from 'react'
import {Platform, StyleSheet, TouchableOpacity, View} from 'react-native' import {Platform, StyleSheet, TouchableOpacity, View} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {
FontAwesomeIcon,
FontAwesomeIconStyle,
} from '@fortawesome/react-native-fontawesome'
import {BottomSheetScrollView, BottomSheetTextInput} from '@gorhom/bottom-sheet' import {BottomSheetScrollView, BottomSheetTextInput} from '@gorhom/bottom-sheet'
import {Text} from '../util/text/Text' import {Text} from '../util/text/Text'
import {useStores} from '../../../state' import {useStores} from '../../../state'
@ -37,13 +40,19 @@ export function Component({onSelect}: {onSelect: (url: string) => void}) {
style={styles.btn} style={styles.btn}
onPress={() => doSelect(LOCAL_DEV_SERVICE)}> onPress={() => doSelect(LOCAL_DEV_SERVICE)}>
<Text style={styles.btnText}>Local dev server</Text> <Text style={styles.btnText}>Local dev server</Text>
<FontAwesomeIcon icon="arrow-right" style={s.white} /> <FontAwesomeIcon
icon="arrow-right"
style={s.white as FontAwesomeIconStyle}
/>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity <TouchableOpacity
style={styles.btn} style={styles.btn}
onPress={() => doSelect(STAGING_SERVICE)}> onPress={() => doSelect(STAGING_SERVICE)}>
<Text style={styles.btnText}>Staging</Text> <Text style={styles.btnText}>Staging</Text>
<FontAwesomeIcon icon="arrow-right" style={s.white} /> <FontAwesomeIcon
icon="arrow-right"
style={s.white as FontAwesomeIconStyle}
/>
</TouchableOpacity> </TouchableOpacity>
</> </>
) : undefined} ) : undefined}
@ -51,7 +60,10 @@ export function Component({onSelect}: {onSelect: (url: string) => void}) {
style={styles.btn} style={styles.btn}
onPress={() => doSelect(PROD_SERVICE)}> onPress={() => doSelect(PROD_SERVICE)}>
<Text style={styles.btnText}>Bluesky.Social</Text> <Text style={styles.btnText}>Bluesky.Social</Text>
<FontAwesomeIcon icon="arrow-right" style={s.white} /> <FontAwesomeIcon
icon="arrow-right"
style={s.white as FontAwesomeIconStyle}
/>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
<View style={styles.group}> <View style={styles.group}>
@ -74,7 +86,7 @@ export function Component({onSelect}: {onSelect: (url: string) => void}) {
onPress={() => doSelect(customUrl)}> onPress={() => doSelect(customUrl)}>
<FontAwesomeIcon <FontAwesomeIcon
icon="check" icon="check"
style={[s.black, styles.checkIcon]} style={[s.black as FontAwesomeIconStyle, styles.checkIcon]}
size={18} size={18}
/> />
</TouchableOpacity> </TouchableOpacity>

View File

@ -9,7 +9,11 @@ import {
} from 'react-native' } from 'react-native'
import {AppBskyEmbedImages} from '@atproto/api' import {AppBskyEmbedImages} from '@atproto/api'
import {AtUri} from '../../../third-party/uri' import {AtUri} from '../../../third-party/uri'
import {FontAwesomeIcon, Props} from '@fortawesome/react-native-fontawesome' import {
FontAwesomeIcon,
FontAwesomeIconStyle,
Props,
} from '@fortawesome/react-native-fontawesome'
import {NotificationsViewItemModel} from '../../../state/models/notifications-view' import {NotificationsViewItemModel} from '../../../state/models/notifications-view'
import {PostThreadViewModel} from '../../../state/models/post-thread-view' import {PostThreadViewModel} from '../../../state/models/post-thread-view'
import {s, colors} from '../../lib/styles' import {s, colors} from '../../lib/styles'
@ -98,18 +102,21 @@ export const FeedItem = observer(function FeedItem({
if (item.isUpvote) { if (item.isUpvote) {
action = 'liked your post' action = 'liked your post'
icon = 'HeartIconSolid' icon = 'HeartIconSolid'
iconStyle = [s.red3, {position: 'relative', top: -4}] iconStyle = [
s.red3 as FontAwesomeIconStyle,
{position: 'relative', top: -4},
]
} else if (item.isRepost) { } else if (item.isRepost) {
action = 'reposted your post' action = 'reposted your post'
icon = 'retweet' icon = 'retweet'
iconStyle = [s.green3] iconStyle = [s.green3 as FontAwesomeIconStyle]
} else if (item.isReply) { } else if (item.isReply) {
action = 'replied to your post' action = 'replied to your post'
icon = ['far', 'comment'] icon = ['far', 'comment']
} else if (item.isFollow) { } else if (item.isFollow) {
action = 'followed you' action = 'followed you'
icon = 'user-plus' icon = 'user-plus'
iconStyle = [s.blue3] iconStyle = [s.blue3 as FontAwesomeIconStyle]
} else { } else {
return <></> return <></>
} }
@ -292,7 +299,6 @@ function ExpandedAuthorsList({
authors.length * (EXPANDED_AUTHOR_EL_HEIGHT + 10) /*10=margin*/ authors.length * (EXPANDED_AUTHOR_EL_HEIGHT + 10) /*10=margin*/
const heightStyle = { const heightStyle = {
height: Animated.multiply(heightInterp, targetHeight), height: Animated.multiply(heightInterp, targetHeight),
overflow: 'hidden',
} }
React.useEffect(() => { React.useEffect(() => {
Animated.timing(heightInterp, { Animated.timing(heightInterp, {
@ -302,7 +308,12 @@ function ExpandedAuthorsList({
}).start() }).start()
}, [heightInterp, visible]) }, [heightInterp, visible])
return ( return (
<Animated.View style={[heightStyle, visible ? s.mb10 : undefined]}> <Animated.View
style={[
heightStyle,
styles.overflowHidden,
visible ? s.mb10 : undefined,
]}>
{authors.map(author => ( {authors.map(author => (
<Link <Link
key={author.href} key={author.href}
@ -360,6 +371,10 @@ function AdditionalPostText({
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
overflowHidden: {
overflow: 'hidden',
},
outer: { outer: {
padding: 10, padding: 10,
paddingRight: 15, paddingRight: 15,

View File

@ -9,13 +9,23 @@ import {
View, View,
} from 'react-native' } from 'react-native'
import {TabView, SceneMap, Route, TabBarProps} from 'react-native-tab-view' import {TabView, SceneMap, Route, TabBarProps} from 'react-native-tab-view'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {
FontAwesomeIcon,
FontAwesomeIconStyle,
} from '@fortawesome/react-native-fontawesome'
import {Text} from '../util/text/Text' import {Text} from '../util/text/Text'
import {useStores} from '../../../state' import {useStores} from '../../../state'
import {s} from '../../lib/styles' import {s} from '../../lib/styles'
import {TABS_EXPLAINER} from '../../lib/assets' import {TABS_EXPLAINER} from '../../lib/assets'
import {TABS_ENABLED} from '../../../build-flags' import {TABS_ENABLED} from '../../../build-flags'
const ROUTES = TABS_ENABLED
? [
{key: 'intro', title: 'Intro'},
{key: 'tabs', title: 'Tabs'},
]
: [{key: 'intro', title: 'Intro'}]
const Intro = () => ( const Intro = () => (
<View style={styles.explainer}> <View style={styles.explainer}>
<Text <Text
@ -37,7 +47,7 @@ const Tabs = () => (
<View style={s.flex1} /> <View style={s.flex1} />
<FontAwesomeIcon <FontAwesomeIcon
icon={['far', 'clone']} icon={['far', 'clone']}
style={[s.black, s.mb5]} style={[s.black as FontAwesomeIconStyle, s.mb5]}
size={36} size={36}
/> />
<View style={s.flex1} /> <View style={s.flex1} />
@ -62,14 +72,10 @@ export const FeatureExplainer = () => {
const layout = useWindowDimensions() const layout = useWindowDimensions()
const store = useStores() const store = useStores()
const [index, setIndex] = useState(0) const [index, setIndex] = useState(0)
const routes = [
{key: 'intro', title: 'Intro'},
TABS_ENABLED ? {key: 'tabs', title: 'Tabs'} : undefined,
].filter(Boolean)
const onPressSkip = () => store.onboard.next() const onPressSkip = () => store.onboard.next()
const onPressNext = () => { const onPressNext = () => {
if (index >= routes.length - 1) { if (index >= ROUTES.length - 1) {
store.onboard.next() store.onboard.next()
} else { } else {
setIndex(index + 1) setIndex(index + 1)
@ -103,12 +109,12 @@ export const FeatureExplainer = () => {
) )
} }
const FirstExplainer = SCENE_MAP[routes[0]?.key as keyof typeof SCENE_MAP] const FirstExplainer = SCENE_MAP[ROUTES[0]?.key as keyof typeof SCENE_MAP]
return ( return (
<SafeAreaView style={styles.container}> <SafeAreaView style={styles.container}>
{routes.length > 1 ? ( {ROUTES.length > 1 ? (
<TabView <TabView
navigationState={{index, routes}} navigationState={{index, routes: ROUTES}}
renderScene={renderScene} renderScene={renderScene}
renderTabBar={renderTabBar} renderTabBar={renderTabBar}
onIndexChange={setIndex} onIndexChange={setIndex}

View File

@ -3,7 +3,10 @@ import {observer} from 'mobx-react-lite'
import {StyleSheet, View} from 'react-native' import {StyleSheet, View} from 'react-native'
import Clipboard from '@react-native-clipboard/clipboard' import Clipboard from '@react-native-clipboard/clipboard'
import {AtUri} from '../../../third-party/uri' import {AtUri} from '../../../third-party/uri'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {
FontAwesomeIcon,
FontAwesomeIconStyle,
} from '@fortawesome/react-native-fontawesome'
import {PostThreadViewPostModel} from '../../../state/models/post-thread-view' import {PostThreadViewPostModel} from '../../../state/models/post-thread-view'
import {Link} from '../util/Link' import {Link} from '../util/Link'
import {RichText} from '../util/text/RichText' import {RichText} from '../util/text/RichText'
@ -59,7 +62,7 @@ export const PostThreadItem = observer(function PostThreadItem({
replyTo: { replyTo: {
uri: item.post.uri, uri: item.post.uri,
cid: item.post.cid, cid: item.post.cid,
text: record.text as string, text: record?.text as string,
author: { author: {
handle: item.post.author.handle, handle: item.post.author.handle,
displayName: item.post.author.displayName, displayName: item.post.author.displayName,
@ -103,7 +106,10 @@ export const PostThreadItem = observer(function PostThreadItem({
if (deleted) { if (deleted) {
return ( return (
<View style={[styles.outer, pal.border, pal.view, s.p20, s.flexRow]}> <View style={[styles.outer, pal.border, pal.view, s.p20, s.flexRow]}>
<FontAwesomeIcon icon={['far', 'trash-can']} style={pal.icon} /> <FontAwesomeIcon
icon={['far', 'trash-can']}
style={pal.icon as FontAwesomeIconStyle}
/>
<Text style={[pal.textLight, s.ml10]}>This post has been deleted.</Text> <Text style={[pal.textLight, s.ml10]}>This post has been deleted.</Text>
</View> </View>
) )

View File

@ -1,7 +1,7 @@
import React, {useEffect} from 'react' import React, {useEffect} from 'react'
import {observer} from 'mobx-react-lite' import {observer} from 'mobx-react-lite'
import {ActivityIndicator, FlatList, StyleSheet, View} from 'react-native' import {ActivityIndicator, FlatList, StyleSheet, View} from 'react-native'
import {VotesViewModel, VotesItem} from '../../../state/models/votes-view' import {VotesViewModel, VoteItem} from '../../../state/models/votes-view'
import {Link} from '../util/Link' import {Link} from '../util/Link'
import {Text} from '../util/text/Text' import {Text} from '../util/text/Text'
import {ErrorMessage} from '../util/error/ErrorMessage' import {ErrorMessage} from '../util/error/ErrorMessage'
@ -56,7 +56,7 @@ export const PostVotedBy = observer(function PostVotedBy({
// loaded // loaded
// = // =
const renderItem = ({item}: {item: VotesItem}) => <LikedByItem item={item} /> const renderItem = ({item}: {item: VoteItem}) => <LikedByItem item={item} />
return ( return (
<FlatList <FlatList
data={view.votes} data={view.votes}
@ -76,7 +76,7 @@ export const PostVotedBy = observer(function PostVotedBy({
) )
}) })
const LikedByItem = ({item}: {item: VotesItem}) => { const LikedByItem = ({item}: {item: VoteItem}) => {
const pal = usePalette('default') const pal = usePalette('default')
return ( return (

View File

@ -1,6 +1,6 @@
import React, {useState, useEffect} from 'react' import React, {useState, useEffect} from 'react'
import {observer} from 'mobx-react-lite' import {observer} from 'mobx-react-lite'
import {StyleSheet, View} from 'react-native' import {StyleProp, StyleSheet, TextStyle, View} from 'react-native'
import {LoadingPlaceholder} from '../util/LoadingPlaceholder' import {LoadingPlaceholder} from '../util/LoadingPlaceholder'
import {ErrorMessage} from '../util/error/ErrorMessage' import {ErrorMessage} from '../util/error/ErrorMessage'
import {Text} from '../util/text/Text' import {Text} from '../util/text/Text'
@ -12,7 +12,7 @@ export const PostText = observer(function PostText({
style, style,
}: { }: {
uri: string uri: string
style?: StyleProp style?: StyleProp<TextStyle>
}) { }) {
const store = useStores() const store = useStores()
const [model, setModel] = useState<PostModel | undefined>() const [model, setModel] = useState<PostModel | undefined>()

View File

@ -4,7 +4,10 @@ import {StyleSheet, View} from 'react-native'
import Clipboard from '@react-native-clipboard/clipboard' import Clipboard from '@react-native-clipboard/clipboard'
import Svg, {Circle, Line} from 'react-native-svg' import Svg, {Circle, Line} from 'react-native-svg'
import {AtUri} from '../../../third-party/uri' import {AtUri} from '../../../third-party/uri'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {
FontAwesomeIcon,
FontAwesomeIconStyle,
} from '@fortawesome/react-native-fontawesome'
import {FeedItemModel} from '../../../state/models/feed-view' import {FeedItemModel} from '../../../state/models/feed-view'
import {Link} from '../util/Link' import {Link} from '../util/Link'
import {Text} from '../util/text/Text' import {Text} from '../util/text/Text'
@ -137,7 +140,10 @@ export const FeedItem = observer(function ({
}> }>
<FontAwesomeIcon <FontAwesomeIcon
icon="retweet" icon="retweet"
style={[styles.includeReasonIcon, {color: pal.colors.textLight}]} style={[
styles.includeReasonIcon,
{color: pal.colors.textLight} as FontAwesomeIconStyle,
]}
/> />
<Text type="sm-bold" style={pal.textLight}> <Text type="sm-bold" style={pal.textLight}>
Reposted by{' '} Reposted by{' '}
@ -167,7 +173,10 @@ export const FeedItem = observer(function ({
<FontAwesomeIcon <FontAwesomeIcon
icon="reply" icon="reply"
size={9} size={9}
style={[{color: pal.colors.textLight}, s.mr5]} style={[
{color: pal.colors.textLight} as FontAwesomeIconStyle,
s.mr5,
]}
/> />
<Text type="md" style={[pal.textLight, s.mr2]}> <Text type="md" style={[pal.textLight, s.mr2]}>
Reply to Reply to

View File

@ -97,7 +97,11 @@ const User = ({item}: {item: FollowItem}) => {
size={40} size={40}
displayName={item.displayName} displayName={item.displayName}
handle={item.handle} handle={item.handle}
avatar={item.avatar} avatar={
item.avatar as
| string
| undefined /* HACK: type signature is wrong in the api */
}
/> />
</View> </View>
<View style={styles.layoutContent}> <View style={styles.layoutContent}>

View File

@ -8,7 +8,10 @@ import {
View, View,
} from 'react-native' } from 'react-native'
import LinearGradient from 'react-native-linear-gradient' import LinearGradient from 'react-native-linear-gradient'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {
FontAwesomeIcon,
FontAwesomeIconStyle,
} from '@fortawesome/react-native-fontawesome'
import {BlurView} from '@react-native-community/blur' import {BlurView} from '@react-native-community/blur'
import {ProfileViewModel} from '../../../state/models/profile-view' import {ProfileViewModel} from '../../../state/models/profile-view'
import {useStores} from '../../../state' import {useStores} from '../../../state'
@ -142,7 +145,7 @@ export const ProfileHeader = observer(function ProfileHeader({
} }
return ( return (
<View style={pal.view}> <View style={pal.view}>
<UserBanner handle={view.handle} banner={view.banner} /> <UserBanner banner={view.banner} />
<View style={styles.content}> <View style={styles.content}>
<View style={[styles.buttonsLine]}> <View style={[styles.buttonsLine]}>
{isMe ? ( {isMe ? (
@ -181,7 +184,10 @@ export const ProfileHeader = observer(function ProfileHeader({
start={{x: 0, y: 0}} start={{x: 0, y: 0}}
end={{x: 1, y: 1}} end={{x: 1, y: 1}}
style={[styles.btn, styles.gradientBtn]}> style={[styles.btn, styles.gradientBtn]}>
<FontAwesomeIcon icon="plus" style={[s.white, s.mr5]} /> <FontAwesomeIcon
icon="plus"
style={[s.white as FontAwesomeIconStyle, s.mr5]}
/>
<Text type="button" style={[s.white, s.bold]}> <Text type="button" style={[s.white, s.bold]}>
Follow Follow
</Text> </Text>

View File

@ -1,7 +1,10 @@
import React from 'react' import React from 'react'
import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native' import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native'
import {IconProp} from '@fortawesome/fontawesome-svg-core' import {IconProp} from '@fortawesome/fontawesome-svg-core'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {
FontAwesomeIcon,
FontAwesomeIconStyle,
} from '@fortawesome/react-native-fontawesome'
import {Text} from './text/Text' import {Text} from './text/Text'
import {UserGroupIcon} from '../../lib/icons' import {UserGroupIcon} from '../../lib/icons'
import {usePalette} from '../../lib/hooks/usePalette' import {usePalette} from '../../lib/hooks/usePalette'
@ -25,7 +28,10 @@ export function EmptyState({
<FontAwesomeIcon <FontAwesomeIcon
icon={icon} icon={icon}
size={64} size={64}
style={[styles.icon, {color: pal.colors.emptyStateIcon}]} style={[
styles.icon,
{color: pal.colors.emptyStateIcon} as FontAwesomeIconStyle,
]}
/> />
)} )}
</View> </View>

View File

@ -63,7 +63,7 @@ export function PostLoadingPlaceholder({
</View> </View>
<View style={s.flex1}> <View style={s.flex1}>
<HeartIcon <HeartIcon
style={{color: theme.palette.default.icon}} style={{color: theme.palette.default.icon} as ViewStyle}
size={17} size={17}
strokeWidth={1.7} strokeWidth={1.7}
/> />

View File

@ -7,7 +7,10 @@ import {
View, View,
ViewStyle, ViewStyle,
} from 'react-native' } from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {
FontAwesomeIcon,
FontAwesomeIconStyle,
} from '@fortawesome/react-native-fontawesome'
import ReactNativeHapticFeedback from 'react-native-haptic-feedback' import ReactNativeHapticFeedback from 'react-native-haptic-feedback'
import {Text} from './text/Text' import {Text} from './text/Text'
import {PostDropdownBtn} from './forms/DropdownButton' import {PostDropdownBtn} from './forms/DropdownButton'
@ -147,7 +150,9 @@ export function PostCtrls(opts: PostCtrlsOpts) {
<Animated.View style={anim1Style}> <Animated.View style={anim1Style}>
<RepostIcon <RepostIcon
style={ style={
opts.isReposted ? styles.ctrlIconReposted : defaultCtrlColor (opts.isReposted
? styles.ctrlIconReposted
: defaultCtrlColor) as ViewStyle
} }
strokeWidth={2.4} strokeWidth={2.4}
size={opts.big ? 24 : 20} size={opts.big ? 24 : 20}
@ -173,12 +178,15 @@ export function PostCtrls(opts: PostCtrlsOpts) {
<Animated.View style={anim2Style}> <Animated.View style={anim2Style}>
{opts.isUpvoted ? ( {opts.isUpvoted ? (
<HeartIconSolid <HeartIconSolid
style={[styles.ctrlIconUpvoted]} style={styles.ctrlIconUpvoted as ViewStyle}
size={opts.big ? 22 : 16} size={opts.big ? 22 : 16}
/> />
) : ( ) : (
<HeartIcon <HeartIcon
style={[defaultCtrlColor, opts.big ? styles.mt1 : undefined]} style={[
defaultCtrlColor as ViewStyle,
opts.big ? styles.mt1 : undefined,
]}
strokeWidth={3} strokeWidth={3}
size={opts.big ? 20 : 16} size={opts.big ? 20 : 16}
/> />
@ -214,7 +222,7 @@ export function PostCtrls(opts: PostCtrlsOpts) {
{ {
color: color:
theme.colorScheme === 'light' ? colors.gray4 : colors.gray5, theme.colorScheme === 'light' ? colors.gray4 : colors.gray5,
}, } as FontAwesomeIconStyle,
]} ]}
/> />
</PostDropdownBtn> </PostDropdownBtn>

View File

@ -6,7 +6,10 @@ import {
TouchableOpacity, TouchableOpacity,
View, View,
} from 'react-native' } from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {
FontAwesomeIcon,
FontAwesomeIconStyle,
} from '@fortawesome/react-native-fontawesome'
import {UserAvatar} from './UserAvatar' import {UserAvatar} from './UserAvatar'
import {Text} from './text/Text' import {Text} from './text/Text'
import {MagnifyingGlassIcon} from '../../lib/icons' import {MagnifyingGlassIcon} from '../../lib/icons'
@ -92,7 +95,11 @@ export const ViewHeader = observer(function ViewHeader({
<ActivityIndicator /> <ActivityIndicator />
) : ( ) : (
<> <>
<FontAwesomeIcon icon="signal" style={pal.text} size={16} /> <FontAwesomeIcon
icon="signal"
style={pal.text as FontAwesomeIconStyle}
size={16}
/>
<FontAwesomeIcon <FontAwesomeIcon
icon="x" icon="x"
style={[ style={[

View File

@ -6,7 +6,10 @@ import {
View, View,
ViewStyle, ViewStyle,
} from 'react-native' } from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {
FontAwesomeIcon,
FontAwesomeIconStyle,
} from '@fortawesome/react-native-fontawesome'
import {Text} from '../text/Text' import {Text} from '../text/Text'
import {useTheme} from '../../../lib/ThemeContext' import {useTheme} from '../../../lib/ThemeContext'
import {usePalette} from '../../../lib/hooks/usePalette' import {usePalette} from '../../../lib/hooks/usePalette'
@ -28,7 +31,11 @@ export function ErrorMessage({
<View testID="errorMessageView" style={[styles.outer, pal.view, style]}> <View testID="errorMessageView" style={[styles.outer, pal.view, style]}>
<View <View
style={[styles.errorIcon, {backgroundColor: theme.palette.error.icon}]}> style={[styles.errorIcon, {backgroundColor: theme.palette.error.icon}]}>
<FontAwesomeIcon icon="exclamation" style={pal.text} size={16} /> <FontAwesomeIcon
icon="exclamation"
style={pal.text as FontAwesomeIconStyle}
size={16}
/>
</View> </View>
<Text <Text
type="sm" type="sm"

View File

@ -1,6 +1,9 @@
import React from 'react' import React from 'react'
import {StyleSheet, TouchableOpacity, View} from 'react-native' import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {
FontAwesomeIcon,
FontAwesomeIconStyle,
} from '@fortawesome/react-native-fontawesome'
import {Text} from '../text/Text' import {Text} from '../text/Text'
import {colors} from '../../../lib/styles' import {colors} from '../../../lib/styles'
import {useTheme} from '../../../lib/ThemeContext' import {useTheme} from '../../../lib/ThemeContext'
@ -58,7 +61,11 @@ export function ErrorScreen({
testID="errorScreenTryAgainButton" testID="errorScreenTryAgainButton"
style={[styles.btn, {backgroundColor: theme.palette.error.icon}]} style={[styles.btn, {backgroundColor: theme.palette.error.icon}]}
onPress={onPressTryAgain}> onPress={onPressTryAgain}>
<FontAwesomeIcon icon="arrows-rotate" style={pal.text} size={16} /> <FontAwesomeIcon
icon="arrows-rotate"
style={pal.text as FontAwesomeIconStyle}
size={16}
/>
<Text type="button" style={[styles.btnText, pal.text]}> <Text type="button" style={[styles.btnText, pal.text]}>
Try again Try again
</Text> </Text>

View File

@ -37,7 +37,7 @@ export function DropdownButton({
menuWidth, menuWidth,
children, children,
}: { }: {
type: DropdownButtonType type?: DropdownButtonType
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
items: DropdownItem[] items: DropdownItem[]
label?: string label?: string

View File

@ -30,6 +30,7 @@ export function AutoSizedImage({
}: { }: {
uri: string uri: string
onPress?: () => void onPress?: () => void
onLongPress?: () => void
style?: StyleProp<ImageStyle> style?: StyleProp<ImageStyle>
containerStyle?: StyleProp<ViewStyle> containerStyle?: StyleProp<ViewStyle>
}) { }) {
@ -68,7 +69,7 @@ export function AutoSizedImage({
}) })
} }
let calculatedStyle: StyleProp<ViewStyle> | undefined let calculatedStyle: StyleProp<ImageStyle> | undefined
if (imgInfo && containerInfo) { if (imgInfo && containerInfo) {
// imgInfo.height / imgInfo.width = x / containerInfo.width // imgInfo.height / imgInfo.width = x / containerInfo.width
// x = imgInfo.height / imgInfo.width * containerInfo.width // x = imgInfo.height / imgInfo.width * containerInfo.width

View File

@ -13,7 +13,7 @@ import {DELAY_PRESS_IN} from './constants'
interface Dim { interface Dim {
width: number width: number
height: numberPressIn height: number
} }
export type ImageLayoutGridType = 'two' | 'three' | 'four' export type ImageLayoutGridType = 'two' | 'three' | 'four'
@ -28,6 +28,7 @@ export function ImageLayoutGrid({
type: ImageLayoutGridType type: ImageLayoutGridType
uris: string[] uris: string[]
onPress?: (index: number) => void onPress?: (index: number) => void
onLongPress?: (index: number) => void
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
}) { }) {
const [containerInfo, setContainerInfo] = React.useState<Dim | undefined>() const [containerInfo, setContainerInfo] = React.useState<Dim | undefined>()
@ -64,6 +65,7 @@ function ImageLayoutGridInner({
type: ImageLayoutGridType type: ImageLayoutGridType
uris: string[] uris: string[]
onPress?: (index: number) => void onPress?: (index: number) => void
onLongPress?: (index: number) => void
containerInfo: Dim containerInfo: Dim
}) { }) {
const size1 = React.useMemo<ImageStyle>(() => { const size1 = React.useMemo<ImageStyle>(() => {
@ -91,14 +93,14 @@ function ImageLayoutGridInner({
<TouchableOpacity <TouchableOpacity
delayPressIn={DELAY_PRESS_IN} delayPressIn={DELAY_PRESS_IN}
onPress={() => onPress?.(0)} onPress={() => onPress?.(0)}
onLongPress={() => onLongPress(0)}> onLongPress={() => onLongPress?.(0)}>
<Image source={{uri: uris[0]}} style={size1} /> <Image source={{uri: uris[0]}} style={size1} />
</TouchableOpacity> </TouchableOpacity>
<View style={styles.wSpace} /> <View style={styles.wSpace} />
<TouchableOpacity <TouchableOpacity
delayPressIn={DELAY_PRESS_IN} delayPressIn={DELAY_PRESS_IN}
onPress={() => onPress?.(1)} onPress={() => onPress?.(1)}
onLongPress={() => onLongPress(1)}> onLongPress={() => onLongPress?.(1)}>
<Image source={{uri: uris[1]}} style={size1} /> <Image source={{uri: uris[1]}} style={size1} />
</TouchableOpacity> </TouchableOpacity>
</View> </View>
@ -110,7 +112,7 @@ function ImageLayoutGridInner({
<TouchableOpacity <TouchableOpacity
delayPressIn={DELAY_PRESS_IN} delayPressIn={DELAY_PRESS_IN}
onPress={() => onPress?.(0)} onPress={() => onPress?.(0)}
onLongPress={() => onLongPress(0)}> onLongPress={() => onLongPress?.(0)}>
<Image source={{uri: uris[0]}} style={size2} /> <Image source={{uri: uris[0]}} style={size2} />
</TouchableOpacity> </TouchableOpacity>
<View style={styles.wSpace} /> <View style={styles.wSpace} />
@ -118,14 +120,14 @@ function ImageLayoutGridInner({
<TouchableOpacity <TouchableOpacity
delayPressIn={DELAY_PRESS_IN} delayPressIn={DELAY_PRESS_IN}
onPress={() => onPress?.(1)} onPress={() => onPress?.(1)}
onLongPress={() => onLongPress(1)}> onLongPress={() => onLongPress?.(1)}>
<Image source={{uri: uris[1]}} style={size1} /> <Image source={{uri: uris[1]}} style={size1} />
</TouchableOpacity> </TouchableOpacity>
<View style={styles.hSpace} /> <View style={styles.hSpace} />
<TouchableOpacity <TouchableOpacity
delayPressIn={DELAY_PRESS_IN} delayPressIn={DELAY_PRESS_IN}
onPress={() => onPress?.(2)} onPress={() => onPress?.(2)}
onLongPress={() => onLongPress(2)}> onLongPress={() => onLongPress?.(2)}>
<Image source={{uri: uris[2]}} style={size1} /> <Image source={{uri: uris[2]}} style={size1} />
</TouchableOpacity> </TouchableOpacity>
</View> </View>
@ -139,14 +141,14 @@ function ImageLayoutGridInner({
<TouchableOpacity <TouchableOpacity
delayPressIn={DELAY_PRESS_IN} delayPressIn={DELAY_PRESS_IN}
onPress={() => onPress?.(0)} onPress={() => onPress?.(0)}
onLongPress={() => onLongPress(0)}> onLongPress={() => onLongPress?.(0)}>
<Image source={{uri: uris[0]}} style={size1} /> <Image source={{uri: uris[0]}} style={size1} />
</TouchableOpacity> </TouchableOpacity>
<View style={styles.hSpace} /> <View style={styles.hSpace} />
<TouchableOpacity <TouchableOpacity
delayPressIn={DELAY_PRESS_IN} delayPressIn={DELAY_PRESS_IN}
onPress={() => onPress?.(1)} onPress={() => onPress?.(1)}
onLongPress={() => onLongPress(1)}> onLongPress={() => onLongPress?.(1)}>
<Image source={{uri: uris[1]}} style={size1} /> <Image source={{uri: uris[1]}} style={size1} />
</TouchableOpacity> </TouchableOpacity>
</View> </View>
@ -155,14 +157,14 @@ function ImageLayoutGridInner({
<TouchableOpacity <TouchableOpacity
delayPressIn={DELAY_PRESS_IN} delayPressIn={DELAY_PRESS_IN}
onPress={() => onPress?.(2)} onPress={() => onPress?.(2)}
onLongPress={() => onLongPress(2)}> onLongPress={() => onLongPress?.(2)}>
<Image source={{uri: uris[2]}} style={size1} /> <Image source={{uri: uris[2]}} style={size1} />
</TouchableOpacity> </TouchableOpacity>
<View style={styles.hSpace} /> <View style={styles.hSpace} />
<TouchableOpacity <TouchableOpacity
delayPressIn={DELAY_PRESS_IN} delayPressIn={DELAY_PRESS_IN}
onPress={() => onPress?.(3)} onPress={() => onPress?.(3)}
onLongPress={() => onLongPress(3)}> onLongPress={() => onLongPress?.(3)}>
<Image source={{uri: uris[3]}} style={size1} /> <Image source={{uri: uris[3]}} style={size1} />
</TouchableOpacity> </TouchableOpacity>
</View> </View>

View File

@ -1,5 +1,5 @@
import React from 'react' import React from 'react'
import {StyleProp, ViewStyle} from 'react-native' import {StyleProp, TextStyle, ViewStyle} from 'react-native'
import Svg, {Path} from 'react-native-svg' import Svg, {Path} from 'react-native-svg'
export function GridIcon({ export function GridIcon({
@ -428,12 +428,21 @@ export function CommentBottomArrow({
size?: string | number size?: string | number
strokeWidth?: number strokeWidth?: number
}) { }) {
let color = 'currentColor'
if (
style &&
typeof style === 'object' &&
'color' in style &&
typeof style.color === 'string'
) {
color = style.color
}
return ( return (
<Svg <Svg
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
strokeWidth={strokeWidth || 2.5} strokeWidth={strokeWidth || 2.5}
stroke={style?.color || 'currentColor'} stroke={color}
width={size || 24} width={size || 24}
height={size || 24} height={size || 24}
style={style}> style={style}>

View File

@ -21,7 +21,7 @@ export type ScreenParams = {
navIdx: string navIdx: string
params: Record<string, any> params: Record<string, any>
visible: boolean visible: boolean
scrollElRef?: MutableRefObject<FlatList<any> | undefined> scrollElRef?: MutableRefObject<FlatList<any> | null>
} }
export type Route = [React.FC<ScreenParams>, string, IconProp, RegExp] export type Route = [React.FC<ScreenParams>, string, IconProp, RegExp]
export type MatchResult = { export type MatchResult = {

View File

@ -5,14 +5,13 @@ import {ThemeProvider} from '../lib/ThemeContext'
import {PaletteColorName} from '../lib/ThemeContext' import {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 {DEF_AVATAR} from '../lib/assets'
import {displayNotification} from '../lib/notifee' import {displayNotification} from '../lib/notifee'
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'
import {EmptyState} from '../com/util/EmptyState' import {EmptyState} from '../com/util/EmptyState'
import * as LoadingPlaceholder from '../com/util/LoadingPlaceholder' import * as LoadingPlaceholder from '../com/util/LoadingPlaceholder'
import {Button} from '../com/util/forms/Button' import {Button, ButtonType} from '../com/util/forms/Button'
import {DropdownButton, DropdownItem} from '../com/util/forms/DropdownButton' import {DropdownButton, DropdownItem} from '../com/util/forms/DropdownButton'
import {ToggleButton} from '../com/util/forms/ToggleButton' import {ToggleButton} from '../com/util/forms/ToggleButton'
import {RadioGroup} from '../com/util/forms/RadioGroup' import {RadioGroup} from '../com/util/forms/RadioGroup'
@ -48,9 +47,9 @@ function DebugInner({
const [currentView, setCurrentView] = React.useState<number>(0) const [currentView, setCurrentView] = React.useState<number>(0)
const pal = usePalette('default') const pal = usePalette('default')
const renderItem = (item, i) => { const renderItem = (item: any) => {
return ( return (
<View key={`view-${i}`}> <View key={`view-${item.currentView}`}>
<View style={[s.pt10, s.pl10, s.pr10]}> <View style={[s.pt10, s.pl10, s.pr10]}>
<ToggleButton <ToggleButton
type="default-light" type="default-light"
@ -179,18 +178,10 @@ function NotifsView() {
"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 triggerImg = () => {
displayNotification(
'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.",
DEF_AVATAR,
)
}
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={trigger} label="Trigger" />
<Button onPress={triggerImg} label="Trigger w/image" style={s.ml5} />
</View> </View>
</View> </View>
) )
@ -484,14 +475,14 @@ const RADIO_BUTTON_ITEMS = [
] ]
function RadioButtonsView() { function RadioButtonsView() {
const defaultPal = usePalette('default') const defaultPal = usePalette('default')
const [rgType, setRgType] = React.useState('default-light') const [rgType, setRgType] = React.useState<ButtonType>('default-light')
return ( return (
<View style={[defaultPal.view]}> <View style={[defaultPal.view]}>
<RadioGroup <RadioGroup
type={rgType} type={rgType}
items={RADIO_BUTTON_ITEMS} items={RADIO_BUTTON_ITEMS}
initialSelection="default-light" initialSelection="default-light"
onSelect={setRgType} onSelect={v => setRgType(v as ButtonType)}
/> />
</View> </View>
) )

View File

@ -1,55 +1,57 @@
import React from 'react' import React from 'react'
import {Pressable, View, StyleSheet} from 'react-native' import {View} from 'react-native'
export const NavItem: React.FC<{label: string; screen: string}> = ({ // export const NavItem: React.FC<{label: string; screen: string}> = ({
label, // label,
screen, // screen,
}) => { // }) => {
const Link = <></> // TODO // const Link = <></> // TODO
return ( // return (
<View> // <View>
<Pressable // <Pressable
style={state => [ // style={state => [
// @ts-ignore it does exist! (react-native-web) -prf // // @ts-ignore it does exist! (react-native-web) -prf
state.hovered && styles.navItemHovered, // state.hovered && styles.navItemHovered,
]}> // ]}>
<Link // <Link
style={[ // style={[
styles.navItemLink, // styles.navItemLink,
false /* TODO route.name === screen*/ && styles.navItemLinkSelected, // false /* TODO route.name === screen*/ && styles.navItemLinkSelected,
]} // ]}
to={{screen, params: {}}}> // to={{screen, params: {}}}>
{label} // {label}
</Link> // </Link>
</Pressable> // </Pressable>
</View> // </View>
) // )
} // }
export const DesktopLeftColumn: React.FC = () => { export const DesktopLeftColumn: React.FC = () => {
return ( // TODO
<View style={styles.container}> return <View />
<NavItem screen="Home" label="Home" /> // return (
<NavItem screen="Search" label="Search" /> // <View style={styles.container}>
<NavItem screen="Notifications" label="Notifications" /> // <NavItem screen="Home" label="Home" />
</View> // <NavItem screen="Search" label="Search" />
) // <NavItem screen="Notifications" label="Notifications" />
// </View>
// )
} }
const styles = StyleSheet.create({ // const styles = StyleSheet.create({
container: { // container: {
position: 'absolute', // position: 'absolute',
left: 'calc(50vw - 500px)', // left: 'calc(50vw - 500px)',
width: '200px', // width: '200px',
height: '100%', // height: '100%',
}, // },
navItemHovered: { // navItemHovered: {
backgroundColor: 'gray', // backgroundColor: 'gray',
}, // },
navItemLink: { // navItemLink: {
padding: '1rem', // padding: '1rem',
}, // },
navItemLinkSelected: { // navItemLinkSelected: {
color: 'blue', // color: 'blue',
}, // },
}) // })

View File

@ -36,11 +36,12 @@ export const TabsSelector = observer(
undefined, undefined,
) )
const closeInterp = useAnimatedValue(0) const closeInterp = useAnimatedValue(0)
const tabsContainerRef = useRef<View>(null)
const tabsRef = useRef<ScrollView>(null) const tabsRef = useRef<ScrollView>(null)
const tabRefs = useMemo( const tabRefs = useMemo(
() => () =>
Array.from({length: store.nav.tabs.length}).map(() => Array.from({length: store.nav.tabs.length}).map(() =>
createRef<Animated.View>(), createRef<View>(),
), ),
[store.nav.tabs.length], [store.nav.tabs.length],
) )
@ -90,9 +91,9 @@ export const TabsSelector = observer(
const onLayout = () => { const onLayout = () => {
// focus the current tab // focus the current tab
const targetTab = tabRefs[store.nav.tabIndex] const targetTab = tabRefs[store.nav.tabIndex]
if (tabsRef.current && targetTab.current) { if (tabsContainerRef.current && tabsRef.current && targetTab.current) {
targetTab.current.measureLayout?.( targetTab.current.measureLayout?.(
tabsRef.current, tabsContainerRef.current,
(_left: number, top: number) => { (_left: number, top: number) => {
tabsRef.current?.scrollTo({y: top, animated: false}) tabsRef.current?.scrollTo({y: top, animated: false})
}, },
@ -162,7 +163,9 @@ export const TabsSelector = observer(
</TouchableWithoutFeedback> </TouchableWithoutFeedback>
</View> </View>
</View> </View>
<View style={[s.p10, styles.section, styles.sectionGrayBg]}> <View
ref={tabsContainerRef}
style={[s.p10, styles.section, styles.sectionGrayBg]}>
<ScrollView ref={tabsRef} style={styles.tabs}> <ScrollView ref={tabsRef} style={styles.tabs}>
{store.nav.tabs.map((tab, tabIndex) => { {store.nav.tabs.map((tab, tabIndex) => {
const {icon} = match(tab.current.url) const {icon} = match(tab.current.url)

View File

@ -67,29 +67,36 @@ const Btn = ({
onLongPress?: (event: GestureResponderEvent) => void onLongPress?: (event: GestureResponderEvent) => void
}) => { }) => {
const pal = usePalette('default') const pal = usePalette('default')
let size = 24 let iconEl
let addedStyles
let IconEl
if (icon === 'menu') { if (icon === 'menu') {
IconEl = GridIcon iconEl = <GridIcon style={[styles.ctrlIcon, pal.text]} />
} else if (icon === 'menu-solid') { } else if (icon === 'menu-solid') {
IconEl = GridIconSolid iconEl = <GridIconSolid style={[styles.ctrlIcon, pal.text]} />
} else if (icon === 'home') { } else if (icon === 'home') {
IconEl = HomeIcon iconEl = <HomeIcon size={27} style={[styles.ctrlIcon, pal.text]} />
size = 27
} else if (icon === 'home-solid') { } else if (icon === 'home-solid') {
IconEl = HomeIconSolid iconEl = <HomeIconSolid size={27} style={[styles.ctrlIcon, pal.text]} />
size = 27
} else if (icon === 'bell') { } else if (icon === 'bell') {
IconEl = BellIcon const addedStyles = {position: 'relative', top: -1} as ViewStyle
size = 27 iconEl = (
addedStyles = {position: 'relative', top: -1} as ViewStyle <BellIcon size={27} style={[styles.ctrlIcon, pal.text, addedStyles]} />
)
} else if (icon === 'bell-solid') { } else if (icon === 'bell-solid') {
IconEl = BellIconSolid const addedStyles = {position: 'relative', top: -1} as ViewStyle
size = 27 iconEl = (
addedStyles = {position: 'relative', top: -1} as ViewStyle <BellIconSolid
size={27}
style={[styles.ctrlIcon, pal.text, addedStyles]}
/>
)
} else { } else {
IconEl = FontAwesomeIcon iconEl = (
<FontAwesomeIcon
size={24}
icon={icon}
style={[styles.ctrlIcon, pal.text]}
/>
)
} }
return ( return (
@ -108,11 +115,7 @@ const Btn = ({
<Text style={styles.tabCountLabel}>{tabCount}</Text> <Text style={styles.tabCountLabel}>{tabCount}</Text>
</View> </View>
) : undefined} ) : undefined}
<IconEl {iconEl}
size={size}
style={[styles.ctrlIcon, pal.text, addedStyles]}
icon={icon}
/>
</TouchableOpacity> </TouchableOpacity>
) )
} }
@ -122,7 +125,7 @@ export const MobileShell: React.FC = observer(() => {
const pal = usePalette('default') const pal = usePalette('default')
const store = useStores() const store = useStores()
const [isTabsSelectorActive, setTabsSelectorActive] = useState(false) const [isTabsSelectorActive, setTabsSelectorActive] = useState(false)
const scrollElRef = useRef<FlatList | undefined>() const scrollElRef = useRef<FlatList>(null)
const winDim = useWindowDimensions() const winDim = useWindowDimensions()
const [menuSwipingDirection, setMenuSwipingDirection] = useState(0) const [menuSwipingDirection, setMenuSwipingDirection] = useState(0)
const swipeGestureInterp = useAnimatedValue(0) const swipeGestureInterp = useAnimatedValue(0)
@ -292,9 +295,11 @@ export const MobileShell: React.FC = observer(() => {
) )
shouldRenderMenu = true shouldRenderMenu = true
} }
const menuSwipeTransform = { const menuSwipeTransform = menuTranslateX
transform: [{translateX: menuTranslateX}], ? {
} transform: [{translateX: menuTranslateX}],
}
: undefined
const swipeOpacity = { const swipeOpacity = {
opacity: swipeGestureInterp.interpolate({ opacity: swipeGestureInterp.interpolate({
inputRange: [-1, 0, 1], inputRange: [-1, 0, 1],
@ -417,10 +422,7 @@ export const MobileShell: React.FC = observer(() => {
) : undefined} ) : undefined}
{shouldRenderMenu && ( {shouldRenderMenu && (
<Animated.View style={[styles.menuDrawer, menuSwipeTransform]}> <Animated.View style={[styles.menuDrawer, menuSwipeTransform]}>
<Menu <Menu onClose={() => store.shell.setMainMenuOpen(false)} />
visible={isMenuActive}
onClose={() => store.shell.setMainMenuOpen(false)}
/>
</Animated.View> </Animated.View>
)} )}
</HorzSwipe> </HorzSwipe>