Merge remote-tracking branch 'upstream/main' into Improve-notification-localization
This commit is contained in:
+3
-11
@@ -12,7 +12,8 @@ import {
|
|||||||
isExternalUrl,
|
isExternalUrl,
|
||||||
linkRequiresWarning,
|
linkRequiresWarning,
|
||||||
} from '#/lib/strings/url-helpers'
|
} from '#/lib/strings/url-helpers'
|
||||||
import {isNative, isWeb} from '#/platform/detection'
|
import {isNative} from '#/platform/detection'
|
||||||
|
import {shouldClickOpenNewTab} from '#/platform/urls'
|
||||||
import {useModalControls} from '#/state/modals'
|
import {useModalControls} from '#/state/modals'
|
||||||
import {useOpenLink} from '#/state/preferences/in-app-browser'
|
import {useOpenLink} from '#/state/preferences/in-app-browser'
|
||||||
import {useNavigationDeduped} from 'lib/hooks/useNavigationDeduped'
|
import {useNavigationDeduped} from 'lib/hooks/useNavigationDeduped'
|
||||||
@@ -116,16 +117,7 @@ export function useLink({
|
|||||||
if (isExternal) {
|
if (isExternal) {
|
||||||
openLink(href)
|
openLink(href)
|
||||||
} else {
|
} else {
|
||||||
/**
|
const shouldOpenInNewTab = shouldClickOpenNewTab(e)
|
||||||
* A `GestureResponderEvent`, but cast to `any` to avoid using a bunch
|
|
||||||
* of @ts-ignore below.
|
|
||||||
*/
|
|
||||||
const event = e as any
|
|
||||||
const isMiddleClick = isWeb && event.button === 1
|
|
||||||
const isMetaKey =
|
|
||||||
isWeb &&
|
|
||||||
(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey)
|
|
||||||
const shouldOpenInNewTab = isMetaKey || isMiddleClick
|
|
||||||
|
|
||||||
if (isBskyDownloadUrl(href)) {
|
if (isBskyDownloadUrl(href)) {
|
||||||
shareUrl(BSKY_DOWNLOAD_URL)
|
shareUrl(BSKY_DOWNLOAD_URL)
|
||||||
|
|||||||
@@ -1,23 +1,15 @@
|
|||||||
import {interpolate, useAnimatedStyle} from 'react-native-reanimated'
|
import {interpolate, useAnimatedStyle} from 'react-native-reanimated'
|
||||||
import {useMinimalShellMode as useMinimalShellModeState} from '#/state/shell/minimal-mode'
|
|
||||||
|
import {useMinimalShellMode} from '#/state/shell/minimal-mode'
|
||||||
import {useShellLayout} from '#/state/shell/shell-layout'
|
import {useShellLayout} from '#/state/shell/shell-layout'
|
||||||
|
|
||||||
export function useMinimalShellMode() {
|
// Keep these separated so that we only pay for useAnimatedStyle that gets used.
|
||||||
const mode = useMinimalShellModeState()
|
|
||||||
const {footerHeight, headerHeight} = useShellLayout()
|
|
||||||
|
|
||||||
const footerMinimalShellTransform = useAnimatedStyle(() => {
|
export function useMinimalShellHeaderTransform() {
|
||||||
return {
|
const mode = useMinimalShellMode()
|
||||||
pointerEvents: mode.value === 0 ? 'auto' : 'none',
|
const {headerHeight} = useShellLayout()
|
||||||
opacity: Math.pow(1 - mode.value, 2),
|
|
||||||
transform: [
|
const headerTransform = useAnimatedStyle(() => {
|
||||||
{
|
|
||||||
translateY: interpolate(mode.value, [0, 1], [0, footerHeight.value]),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const headerMinimalShellTransform = useAnimatedStyle(() => {
|
|
||||||
return {
|
return {
|
||||||
pointerEvents: mode.value === 0 ? 'auto' : 'none',
|
pointerEvents: mode.value === 0 ? 'auto' : 'none',
|
||||||
opacity: Math.pow(1 - mode.value, 2),
|
opacity: Math.pow(1 - mode.value, 2),
|
||||||
@@ -28,7 +20,32 @@ export function useMinimalShellMode() {
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const fabMinimalShellTransform = useAnimatedStyle(() => {
|
|
||||||
|
return headerTransform
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useMinimalShellFooterTransform() {
|
||||||
|
const mode = useMinimalShellMode()
|
||||||
|
const {footerHeight} = useShellLayout()
|
||||||
|
|
||||||
|
const footerTransform = useAnimatedStyle(() => {
|
||||||
|
return {
|
||||||
|
pointerEvents: mode.value === 0 ? 'auto' : 'none',
|
||||||
|
opacity: Math.pow(1 - mode.value, 2),
|
||||||
|
transform: [
|
||||||
|
{
|
||||||
|
translateY: interpolate(mode.value, [0, 1], [0, footerHeight.value]),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return footerTransform
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useMinimalShellFabTransform() {
|
||||||
|
const mode = useMinimalShellMode()
|
||||||
|
|
||||||
|
const fabTransform = useAnimatedStyle(() => {
|
||||||
return {
|
return {
|
||||||
transform: [
|
transform: [
|
||||||
{
|
{
|
||||||
@@ -37,9 +54,5 @@ export function useMinimalShellMode() {
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return {
|
return fabTransform
|
||||||
footerMinimalShellTransform,
|
|
||||||
headerMinimalShellTransform,
|
|
||||||
fabMinimalShellTransform,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -72,14 +72,17 @@ export function useNotificationsRegistration() {
|
|||||||
|
|
||||||
export function useRequestNotificationsPermission() {
|
export function useRequestNotificationsPermission() {
|
||||||
const gate = useGate()
|
const gate = useGate()
|
||||||
|
const {currentAccount} = useSession()
|
||||||
|
|
||||||
return async (context: 'StartOnboarding' | 'AfterOnboarding' | 'Login') => {
|
return async (
|
||||||
|
context: 'StartOnboarding' | 'AfterOnboarding' | 'Login' | 'Home',
|
||||||
|
) => {
|
||||||
const permissions = await Notifications.getPermissionsAsync()
|
const permissions = await Notifications.getPermissionsAsync()
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!isNative ||
|
!isNative ||
|
||||||
permissions?.status === 'granted' ||
|
permissions?.status === 'granted' ||
|
||||||
permissions?.status === 'denied'
|
(permissions?.status === 'denied' && !permissions.canAskAgain)
|
||||||
) {
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -95,6 +98,9 @@ export function useRequestNotificationsPermission() {
|
|||||||
) {
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (context === 'Home' && !currentAccount) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const res = await Notifications.requestPermissionsAsync()
|
const res = await Notifications.requestPermissionsAsync()
|
||||||
logEvent('notifications:request', {
|
logEvent('notifications:request', {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export type LogEvents = {
|
|||||||
}
|
}
|
||||||
'notifications:openApp': {}
|
'notifications:openApp': {}
|
||||||
'notifications:request': {
|
'notifications:request': {
|
||||||
context: 'StartOnboarding' | 'AfterOnboarding' | 'Login'
|
context: 'StartOnboarding' | 'AfterOnboarding' | 'Login' | 'Home'
|
||||||
status: 'granted' | 'denied' | 'undetermined'
|
status: 'granted' | 'denied' | 'undetermined'
|
||||||
}
|
}
|
||||||
'state:background:sampled': {
|
'state:background:sampled': {
|
||||||
|
|||||||
@@ -3,4 +3,3 @@ export type Gate =
|
|||||||
| 'request_notifications_permission_after_onboarding_v2'
|
| 'request_notifications_permission_after_onboarding_v2'
|
||||||
| 'show_avi_follow_button'
|
| 'show_avi_follow_button'
|
||||||
| 'show_follow_back_label_v2'
|
| 'show_follow_back_label_v2'
|
||||||
| 'show_notification_badge_mobile_web'
|
|
||||||
|
|||||||
+18
-1
@@ -116,7 +116,7 @@ export function sanitizeAppLanguageSetting(appLanguage: string): AppLanguage {
|
|||||||
const langs = appLanguage.split(',').filter(Boolean)
|
const langs = appLanguage.split(',').filter(Boolean)
|
||||||
|
|
||||||
for (const lang of langs) {
|
for (const lang of langs) {
|
||||||
switch (lang) {
|
switch (fixLegacyLanguageCode(lang)) {
|
||||||
case 'en':
|
case 'en':
|
||||||
return AppLanguage.en
|
return AppLanguage.en
|
||||||
case 'ca':
|
case 'ca':
|
||||||
@@ -157,3 +157,20 @@ export function sanitizeAppLanguageSetting(appLanguage: string): AppLanguage {
|
|||||||
}
|
}
|
||||||
return AppLanguage.en
|
return AppLanguage.en
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function fixLegacyLanguageCode(code: string | null): string | null {
|
||||||
|
// handle some legacy code conversions, see https://xml.coverpages.org/iso639a.html
|
||||||
|
if (code === 'in') {
|
||||||
|
// indonesian
|
||||||
|
return 'id'
|
||||||
|
}
|
||||||
|
if (code === 'iw') {
|
||||||
|
// hebrew
|
||||||
|
return 'he'
|
||||||
|
}
|
||||||
|
if (code === 'ji') {
|
||||||
|
// yiddish
|
||||||
|
return 'yi'
|
||||||
|
}
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {Platform} from 'react-native'
|
|||||||
import {isReducedMotion} from 'react-native-reanimated'
|
import {isReducedMotion} from 'react-native-reanimated'
|
||||||
import {getLocales} from 'expo-localization'
|
import {getLocales} from 'expo-localization'
|
||||||
|
|
||||||
|
import {fixLegacyLanguageCode} from '#/locale/helpers'
|
||||||
import {dedupArray} from 'lib/functions'
|
import {dedupArray} from 'lib/functions'
|
||||||
|
|
||||||
export const isIOS = Platform.OS === 'ios'
|
export const isIOS = Platform.OS === 'ios'
|
||||||
@@ -17,7 +18,7 @@ export const isMobileWeb =
|
|||||||
|
|
||||||
export const deviceLocales = dedupArray(
|
export const deviceLocales = dedupArray(
|
||||||
getLocales?.()
|
getLocales?.()
|
||||||
.map?.(locale => locale.languageCode)
|
.map?.(locale => fixLegacyLanguageCode(locale.languageCode))
|
||||||
.filter(code => typeof code === 'string'),
|
.filter(code => typeof code === 'string'),
|
||||||
) as string[]
|
) as string[]
|
||||||
|
|
||||||
|
|||||||
+14
-1
@@ -1,4 +1,5 @@
|
|||||||
import {Linking} from 'react-native'
|
import {GestureResponderEvent, Linking} from 'react-native'
|
||||||
|
|
||||||
import {isNative, isWeb} from './detection'
|
import {isNative, isWeb} from './detection'
|
||||||
|
|
||||||
export async function getInitialURL(): Promise<string | undefined> {
|
export async function getInitialURL(): Promise<string | undefined> {
|
||||||
@@ -23,3 +24,15 @@ export function clearHash() {
|
|||||||
window.location.hash = ''
|
window.location.hash = ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function shouldClickOpenNewTab(e: GestureResponderEvent) {
|
||||||
|
/**
|
||||||
|
* A `GestureResponderEvent`, but cast to `any` to avoid using a bunch
|
||||||
|
* of @ts-ignore below.
|
||||||
|
*/
|
||||||
|
const event = e as any
|
||||||
|
const isMiddleClick = isWeb && event.button === 1
|
||||||
|
const isMetaKey =
|
||||||
|
isWeb && (event.metaKey || event.altKey || event.ctrlKey || event.shiftKey)
|
||||||
|
return isMetaKey || isMiddleClick
|
||||||
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export const ProfileFeedSection = React.forwardRef<
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
const renderPostsEmpty = React.useCallback(() => {
|
const renderPostsEmpty = React.useCallback(() => {
|
||||||
return <EmptyState icon="feed" message={_(msg`This feed is empty!`)} />
|
return <EmptyState icon="growth" message={_(msg`No posts yet.`)} />
|
||||||
}, [_])
|
}, [_])
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
|||||||
@@ -13,7 +13,13 @@ export async function write(value: Schema) {
|
|||||||
export async function read(): Promise<Schema | undefined> {
|
export async function read(): Promise<Schema | undefined> {
|
||||||
const rawData = await AsyncStorage.getItem(BSKY_STORAGE)
|
const rawData = await AsyncStorage.getItem(BSKY_STORAGE)
|
||||||
const objData = rawData ? JSON.parse(rawData) : undefined
|
const objData = rawData ? JSON.parse(rawData) : undefined
|
||||||
|
|
||||||
|
// new user
|
||||||
|
if (!objData) return undefined
|
||||||
|
|
||||||
|
// existing user, validate
|
||||||
const parsed = schema.safeParse(objData)
|
const parsed = schema.safeParse(objData)
|
||||||
|
|
||||||
if (parsed.success) {
|
if (parsed.success) {
|
||||||
return objData
|
return objData
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
useInfiniteQuery,
|
useInfiniteQuery,
|
||||||
useQueryClient,
|
useQueryClient,
|
||||||
} from '@tanstack/react-query'
|
} from '@tanstack/react-query'
|
||||||
|
import throttle from 'lodash.throttle'
|
||||||
|
|
||||||
import {useCurrentConvoId} from '#/state/messages/current-convo-id'
|
import {useCurrentConvoId} from '#/state/messages/current-convo-id'
|
||||||
import {useMessagesEventBus} from '#/state/messages/events'
|
import {useMessagesEventBus} from '#/state/messages/events'
|
||||||
@@ -89,6 +90,15 @@ export function ListConvosProviderInner({
|
|||||||
const {currentConvoId} = useCurrentConvoId()
|
const {currentConvoId} = useCurrentConvoId()
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
|
|
||||||
|
const debouncedRefetch = useMemo(
|
||||||
|
() =>
|
||||||
|
throttle(refetch, 500, {
|
||||||
|
leading: true,
|
||||||
|
trailing: true,
|
||||||
|
}),
|
||||||
|
[refetch],
|
||||||
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unsub = messagesBus.on(
|
const unsub = messagesBus.on(
|
||||||
events => {
|
events => {
|
||||||
@@ -96,7 +106,7 @@ export function ListConvosProviderInner({
|
|||||||
|
|
||||||
events.logs.forEach(log => {
|
events.logs.forEach(log => {
|
||||||
if (ChatBskyConvoDefs.isLogBeginConvo(log)) {
|
if (ChatBskyConvoDefs.isLogBeginConvo(log)) {
|
||||||
refetch()
|
debouncedRefetch()
|
||||||
} else if (ChatBskyConvoDefs.isLogLeaveConvo(log)) {
|
} else if (ChatBskyConvoDefs.isLogLeaveConvo(log)) {
|
||||||
queryClient.setQueryData(RQKEY, (old: ConvoListQueryData) =>
|
queryClient.setQueryData(RQKEY, (old: ConvoListQueryData) =>
|
||||||
optimisticDelete(log.convoId, old),
|
optimisticDelete(log.convoId, old),
|
||||||
@@ -170,7 +180,7 @@ export function ListConvosProviderInner({
|
|||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
refetch()
|
debouncedRefetch()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -183,7 +193,14 @@ export function ListConvosProviderInner({
|
|||||||
)
|
)
|
||||||
|
|
||||||
return () => unsub()
|
return () => unsub()
|
||||||
}, [messagesBus, currentConvoId, refetch, queryClient, currentAccount?.did])
|
}, [
|
||||||
|
messagesBus,
|
||||||
|
currentConvoId,
|
||||||
|
refetch,
|
||||||
|
queryClient,
|
||||||
|
currentAccount?.did,
|
||||||
|
debouncedRefetch,
|
||||||
|
])
|
||||||
|
|
||||||
const ctx = useMemo(() => {
|
const ctx = useMemo(() => {
|
||||||
return data?.pages.flatMap(page => page.convos) ?? []
|
return data?.pages.flatMap(page => page.convos) ?? []
|
||||||
|
|||||||
@@ -305,7 +305,13 @@ export const ComposePost = observer(function ComposePost({
|
|||||||
localThumb: undefined,
|
localThumb: undefined,
|
||||||
} as apilib.ExternalEmbedDraft)
|
} as apilib.ExternalEmbedDraft)
|
||||||
}
|
}
|
||||||
setError(cleanError(e.message))
|
let err = cleanError(e.message)
|
||||||
|
if (err.includes('not locate record')) {
|
||||||
|
err = _(
|
||||||
|
msg`We're sorry! The post you are replying to has been deleted.`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
setError(err)
|
||||||
setIsProcessing(false)
|
setIsProcessing(false)
|
||||||
return
|
return
|
||||||
} finally {
|
} finally {
|
||||||
@@ -785,11 +791,12 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
errorLine: {
|
errorLine: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
backgroundColor: colors.red1,
|
backgroundColor: colors.red1,
|
||||||
borderRadius: 6,
|
borderRadius: 6,
|
||||||
marginHorizontal: 16,
|
marginHorizontal: 16,
|
||||||
paddingHorizontal: 8,
|
paddingHorizontal: 12,
|
||||||
paddingVertical: 6,
|
paddingVertical: 10,
|
||||||
marginBottom: 8,
|
marginBottom: 8,
|
||||||
},
|
},
|
||||||
reminderLine: {
|
reminderLine: {
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Pressable, StyleProp, StyleSheet, View, ViewStyle} from 'react-native'
|
import {
|
||||||
|
Linking,
|
||||||
|
Pressable,
|
||||||
|
StyleProp,
|
||||||
|
StyleSheet,
|
||||||
|
View,
|
||||||
|
ViewStyle,
|
||||||
|
} from 'react-native'
|
||||||
import {AtUri} from '@atproto/api'
|
import {AtUri} from '@atproto/api'
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||||
import {msg, Plural, Trans} from '@lingui/macro'
|
import {msg, Plural, Trans} from '@lingui/macro'
|
||||||
@@ -26,6 +33,7 @@ import {RichText} from '#/components/RichText'
|
|||||||
import {Text} from '../util/text/Text'
|
import {Text} from '../util/text/Text'
|
||||||
import {UserAvatar} from '../util/UserAvatar'
|
import {UserAvatar} from '../util/UserAvatar'
|
||||||
import hairlineWidth = StyleSheet.hairlineWidth
|
import hairlineWidth = StyleSheet.hairlineWidth
|
||||||
|
import {shouldClickOpenNewTab} from '#/platform/urls'
|
||||||
|
|
||||||
export function FeedSourceCard({
|
export function FeedSourceCard({
|
||||||
feedUri,
|
feedUri,
|
||||||
@@ -203,17 +211,30 @@ export function FeedSourceCardLoaded({
|
|||||||
style,
|
style,
|
||||||
{borderTopWidth: hideTopBorder ? 0 : hairlineWidth},
|
{borderTopWidth: hideTopBorder ? 0 : hairlineWidth},
|
||||||
]}
|
]}
|
||||||
onPress={() => {
|
onPress={e => {
|
||||||
|
const shouldOpenInNewTab = shouldClickOpenNewTab(e)
|
||||||
if (feed.type === 'feed') {
|
if (feed.type === 'feed') {
|
||||||
navigation.push('ProfileFeed', {
|
if (shouldOpenInNewTab) {
|
||||||
name: feed.creatorDid,
|
Linking.openURL(
|
||||||
rkey: new AtUri(feed.uri).rkey,
|
`/profile/${feed.creatorDid}/feed/${new AtUri(feed.uri).rkey}`,
|
||||||
})
|
)
|
||||||
|
} else {
|
||||||
|
navigation.push('ProfileFeed', {
|
||||||
|
name: feed.creatorDid,
|
||||||
|
rkey: new AtUri(feed.uri).rkey,
|
||||||
|
})
|
||||||
|
}
|
||||||
} else if (feed.type === 'list') {
|
} else if (feed.type === 'list') {
|
||||||
navigation.push('ProfileList', {
|
if (shouldOpenInNewTab) {
|
||||||
name: feed.creatorDid,
|
Linking.openURL(
|
||||||
rkey: new AtUri(feed.uri).rkey,
|
`/profile/${feed.creatorDid}/lists/${new AtUri(feed.uri).rkey}`,
|
||||||
})
|
)
|
||||||
|
} else {
|
||||||
|
navigation.push('ProfileList', {
|
||||||
|
name: feed.creatorDid,
|
||||||
|
rkey: new AtUri(feed.uri).rkey,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
key={feed.uri}>
|
key={feed.uri}>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {useLingui} from '@lingui/react'
|
|||||||
import {CogIcon} from '#/lib/icons'
|
import {CogIcon} from '#/lib/icons'
|
||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
import {useShellLayout} from '#/state/shell/shell-layout'
|
import {useShellLayout} from '#/state/shell/shell-layout'
|
||||||
import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
|
import {useMinimalShellHeaderTransform} from 'lib/hooks/useMinimalShellTransform'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||||
import {Logo} from '#/view/icons/Logo'
|
import {Logo} from '#/view/icons/Logo'
|
||||||
@@ -39,7 +39,7 @@ function HomeHeaderLayoutDesktopAndTablet({
|
|||||||
tabBarAnchor: JSX.Element | null | undefined
|
tabBarAnchor: JSX.Element | null | undefined
|
||||||
}) {
|
}) {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {headerMinimalShellTransform} = useMinimalShellMode()
|
const headerMinimalShellTransform = useMinimalShellHeaderTransform()
|
||||||
const {headerHeight} = useShellLayout()
|
const {headerHeight} = useShellLayout()
|
||||||
const {hasSession} = useSession()
|
const {hasSession} = useSession()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {useSession} from '#/state/session'
|
|||||||
import {useSetDrawerOpen} from '#/state/shell/drawer-open'
|
import {useSetDrawerOpen} from '#/state/shell/drawer-open'
|
||||||
import {useShellLayout} from '#/state/shell/shell-layout'
|
import {useShellLayout} from '#/state/shell/shell-layout'
|
||||||
import {HITSLOP_10} from 'lib/constants'
|
import {HITSLOP_10} from 'lib/constants'
|
||||||
import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
|
import {useMinimalShellHeaderTransform} from 'lib/hooks/useMinimalShellTransform'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {isWeb} from 'platform/detection'
|
import {isWeb} from 'platform/detection'
|
||||||
import {Logo} from '#/view/icons/Logo'
|
import {Logo} from '#/view/icons/Logo'
|
||||||
@@ -30,7 +30,7 @@ export function HomeHeaderLayoutMobile({
|
|||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const setDrawerOpen = useSetDrawerOpen()
|
const setDrawerOpen = useSetDrawerOpen()
|
||||||
const {headerHeight} = useShellLayout()
|
const {headerHeight} = useShellLayout()
|
||||||
const {headerMinimalShellTransform} = useMinimalShellMode()
|
const headerMinimalShellTransform = useMinimalShellHeaderTransform()
|
||||||
const {hasSession} = useSession()
|
const {hasSession} = useSession()
|
||||||
|
|
||||||
const onPressAvi = React.useCallback(() => {
|
const onPressAvi = React.useCallback(() => {
|
||||||
|
|||||||
@@ -49,8 +49,7 @@ export function FeedErrorMessage({
|
|||||||
if (
|
if (
|
||||||
typeof knownError !== 'undefined' &&
|
typeof knownError !== 'undefined' &&
|
||||||
knownError !== KnownError.Unknown &&
|
knownError !== KnownError.Unknown &&
|
||||||
(savedFeedConfig?.type === 'feed' ||
|
feedDesc.startsWith('feedgen')
|
||||||
knownError === KnownError.FeedNSFPublic)
|
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<FeedgenErrorMessage
|
<FeedgenErrorMessage
|
||||||
@@ -272,7 +271,7 @@ function detectKnownError(
|
|||||||
) {
|
) {
|
||||||
return KnownError.FeedgenMisconfigured
|
return KnownError.FeedgenMisconfigured
|
||||||
}
|
}
|
||||||
if (error.includes('feed provided an invalid response')) {
|
if (error.includes('invalid response')) {
|
||||||
return KnownError.FeedgenBadResponse
|
return KnownError.FeedgenBadResponse
|
||||||
}
|
}
|
||||||
return KnownError.FeedgenUnknown
|
return KnownError.FeedgenUnknown
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ import {
|
|||||||
FontAwesomeIcon,
|
FontAwesomeIcon,
|
||||||
FontAwesomeIconStyle,
|
FontAwesomeIconStyle,
|
||||||
} from '@fortawesome/react-native-fontawesome'
|
} from '@fortawesome/react-native-fontawesome'
|
||||||
import {Text} from './text/Text'
|
|
||||||
import {UserGroupIcon} from 'lib/icons'
|
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
|
import {UserGroupIcon} from 'lib/icons'
|
||||||
import {isWeb} from 'platform/detection'
|
import {isWeb} from 'platform/detection'
|
||||||
|
import {Growth_Stroke2_Corner0_Rounded as Growth} from '#/components/icons/Growth'
|
||||||
|
import {Text} from './text/Text'
|
||||||
|
|
||||||
export function EmptyState({
|
export function EmptyState({
|
||||||
testID,
|
testID,
|
||||||
@@ -17,32 +20,41 @@ export function EmptyState({
|
|||||||
style,
|
style,
|
||||||
}: {
|
}: {
|
||||||
testID?: string
|
testID?: string
|
||||||
icon: IconProp | 'user-group'
|
icon: IconProp | 'user-group' | 'growth'
|
||||||
message: string
|
message: string
|
||||||
style?: StyleProp<ViewStyle>
|
style?: StyleProp<ViewStyle>
|
||||||
}) {
|
}) {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
|
const {isTabletOrDesktop} = useWebMediaQueries()
|
||||||
|
const iconSize = isTabletOrDesktop ? 80 : 64
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
testID={testID}
|
testID={testID}
|
||||||
style={[styles.container, isWeb && pal.border, style]}>
|
style={[
|
||||||
<View style={styles.iconContainer}>
|
styles.container,
|
||||||
|
isWeb && pal.border,
|
||||||
|
isTabletOrDesktop && {paddingRight: 20},
|
||||||
|
style,
|
||||||
|
]}>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.iconContainer,
|
||||||
|
isTabletOrDesktop && styles.iconContainerBig,
|
||||||
|
pal.viewLight,
|
||||||
|
]}>
|
||||||
{icon === 'user-group' ? (
|
{icon === 'user-group' ? (
|
||||||
<UserGroupIcon size="64" style={styles.icon} />
|
<UserGroupIcon size={iconSize} />
|
||||||
|
) : icon === 'growth' ? (
|
||||||
|
<Growth width={iconSize} fill={pal.colors.emptyStateIcon} />
|
||||||
) : (
|
) : (
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={icon}
|
icon={icon}
|
||||||
size={64}
|
size={iconSize}
|
||||||
style={[
|
style={[{color: pal.colors.emptyStateIcon} as FontAwesomeIconStyle]}
|
||||||
styles.icon,
|
|
||||||
{color: pal.colors.emptyStateIcon} as FontAwesomeIconStyle,
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
<Text
|
<Text type="xl" style={[{color: pal.colors.textLight}, styles.text]}>
|
||||||
type="xl-medium"
|
|
||||||
style={[{color: pal.colors.textVeryLight}, styles.text]}>
|
|
||||||
{message}
|
{message}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
@@ -51,16 +63,23 @@ export function EmptyState({
|
|||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
paddingVertical: 24,
|
|
||||||
paddingHorizontal: 36,
|
|
||||||
borderTopWidth: isWeb ? 1 : undefined,
|
borderTopWidth: isWeb ? 1 : undefined,
|
||||||
},
|
},
|
||||||
iconContainer: {
|
iconContainer: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
},
|
alignItems: 'center',
|
||||||
icon: {
|
justifyContent: 'center',
|
||||||
|
height: 100,
|
||||||
|
width: 100,
|
||||||
marginLeft: 'auto',
|
marginLeft: 'auto',
|
||||||
marginRight: 'auto',
|
marginRight: 'auto',
|
||||||
|
borderRadius: 80,
|
||||||
|
marginTop: 30,
|
||||||
|
},
|
||||||
|
iconContainerBig: {
|
||||||
|
width: 140,
|
||||||
|
height: 140,
|
||||||
|
marginTop: 50,
|
||||||
},
|
},
|
||||||
text: {
|
text: {
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {useNavigation} from '@react-navigation/native'
|
|||||||
|
|
||||||
import {useSetDrawerOpen} from '#/state/shell'
|
import {useSetDrawerOpen} from '#/state/shell'
|
||||||
import {useAnalytics} from 'lib/analytics/analytics'
|
import {useAnalytics} from 'lib/analytics/analytics'
|
||||||
import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
|
import {useMinimalShellHeaderTransform} from 'lib/hooks/useMinimalShellTransform'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||||
import {NavigationProp} from 'lib/routes/types'
|
import {NavigationProp} from 'lib/routes/types'
|
||||||
@@ -197,7 +197,7 @@ function Container({
|
|||||||
showBorder?: boolean
|
showBorder?: boolean
|
||||||
}) {
|
}) {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {headerMinimalShellTransform} = useMinimalShellMode()
|
const headerMinimalShellTransform = useMinimalShellHeaderTransform()
|
||||||
|
|
||||||
if (!hideOnScroll) {
|
if (!hideOnScroll) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import Animated, {useAnimatedStyle, withTiming} from 'react-native-reanimated'
|
|||||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||||
import {LinearGradient} from 'expo-linear-gradient'
|
import {LinearGradient} from 'expo-linear-gradient'
|
||||||
|
|
||||||
import {useMinimalShellMode} from '#/lib/hooks/useMinimalShellMode'
|
import {useMinimalShellFabTransform} from '#/lib/hooks/useMinimalShellTransform'
|
||||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||||
import {clamp} from '#/lib/numbers'
|
import {clamp} from '#/lib/numbers'
|
||||||
import {gradients} from '#/lib/styles'
|
import {gradients} from '#/lib/styles'
|
||||||
@@ -20,7 +20,7 @@ export interface FABProps
|
|||||||
export function FABInner({testID, icon, ...props}: FABProps) {
|
export function FABInner({testID, icon, ...props}: FABProps) {
|
||||||
const insets = useSafeAreaInsets()
|
const insets = useSafeAreaInsets()
|
||||||
const {isMobile, isTablet} = useWebMediaQueries()
|
const {isMobile, isTablet} = useWebMediaQueries()
|
||||||
const {fabMinimalShellTransform} = useMinimalShellMode()
|
const fabMinimalShellTransform = useMinimalShellFabTransform()
|
||||||
const {
|
const {
|
||||||
state: pressed,
|
state: pressed,
|
||||||
onIn: onPressIn,
|
onIn: onPressIn,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
|||||||
import {useMediaQuery} from 'react-responsive'
|
import {useMediaQuery} from 'react-responsive'
|
||||||
|
|
||||||
import {HITSLOP_20} from '#/lib/constants'
|
import {HITSLOP_20} from '#/lib/constants'
|
||||||
import {useMinimalShellMode} from '#/lib/hooks/useMinimalShellMode'
|
import {useMinimalShellFabTransform} from '#/lib/hooks/useMinimalShellTransform'
|
||||||
import {usePalette} from '#/lib/hooks/usePalette'
|
import {usePalette} from '#/lib/hooks/usePalette'
|
||||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||||
import {clamp} from '#/lib/numbers'
|
import {clamp} from '#/lib/numbers'
|
||||||
@@ -30,7 +30,7 @@ export function LoadLatestBtn({
|
|||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {hasSession} = useSession()
|
const {hasSession} = useSession()
|
||||||
const {isDesktop, isTablet, isMobile, isTabletOrMobile} = useWebMediaQueries()
|
const {isDesktop, isTablet, isMobile, isTabletOrMobile} = useWebMediaQueries()
|
||||||
const {fabMinimalShellTransform} = useMinimalShellMode()
|
const fabMinimalShellTransform = useMinimalShellFabTransform()
|
||||||
const insets = useSafeAreaInsets()
|
const insets = useSafeAreaInsets()
|
||||||
|
|
||||||
// move button inline if it starts overlapping the left nav
|
// move button inline if it starts overlapping the left nav
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {View} from 'react-native'
|
|||||||
import {msg, plural} from '@lingui/macro'
|
import {msg, plural} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {HITSLOP_10, HITSLOP_20} from '#/lib/constants'
|
||||||
import {useHaptics} from '#/lib/haptics'
|
import {useHaptics} from '#/lib/haptics'
|
||||||
import {useRequireAuth} from '#/state/session'
|
import {useRequireAuth} from '#/state/session'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
@@ -64,7 +65,8 @@ let RepostButton = ({
|
|||||||
} (${plural(repostCount || 0, {one: '# repost', other: '# reposts'})})`}
|
} (${plural(repostCount || 0, {one: '# repost', other: '# reposts'})})`}
|
||||||
shape="round"
|
shape="round"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
color="secondary">
|
color="secondary"
|
||||||
|
hitSlop={big ? HITSLOP_20 : HITSLOP_10}>
|
||||||
<Repost style={color} width={big ? 22 : 18} />
|
<Repost style={color} width={big ? 22 : 18} />
|
||||||
{typeof repostCount !== 'undefined' && repostCount > 0 ? (
|
{typeof repostCount !== 'undefined' && repostCount > 0 ? (
|
||||||
<Text
|
<Text
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
} from '#/state/shell'
|
} from '#/state/shell'
|
||||||
import {useSelectedFeed, useSetSelectedFeed} from '#/state/shell/selected-feed'
|
import {useSelectedFeed, useSetSelectedFeed} from '#/state/shell/selected-feed'
|
||||||
import {useOTAUpdates} from 'lib/hooks/useOTAUpdates'
|
import {useOTAUpdates} from 'lib/hooks/useOTAUpdates'
|
||||||
|
import {useRequestNotificationsPermission} from 'lib/notifications/notifications'
|
||||||
import {HomeTabNavigatorParams, NativeStackScreenProps} from 'lib/routes/types'
|
import {HomeTabNavigatorParams, NativeStackScreenProps} from 'lib/routes/types'
|
||||||
import {FeedPage} from 'view/com/feeds/FeedPage'
|
import {FeedPage} from 'view/com/feeds/FeedPage'
|
||||||
import {Pager, PagerRef, RenderTabBarFnProps} from 'view/com/pager/Pager'
|
import {Pager, PagerRef, RenderTabBarFnProps} from 'view/com/pager/Pager'
|
||||||
@@ -67,10 +68,15 @@ function HomeScreenReady({
|
|||||||
const maybeFoundIndex = allFeeds.indexOf(rawSelectedFeed)
|
const maybeFoundIndex = allFeeds.indexOf(rawSelectedFeed)
|
||||||
const selectedIndex = Math.max(0, maybeFoundIndex)
|
const selectedIndex = Math.max(0, maybeFoundIndex)
|
||||||
const selectedFeed = allFeeds[selectedIndex]
|
const selectedFeed = allFeeds[selectedIndex]
|
||||||
|
const requestNotificationsPermission = useRequestNotificationsPermission()
|
||||||
|
|
||||||
useSetTitle(pinnedFeedInfos[selectedIndex]?.displayName)
|
useSetTitle(pinnedFeedInfos[selectedIndex]?.displayName)
|
||||||
useOTAUpdates()
|
useOTAUpdates()
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
requestNotificationsPermission('Home')
|
||||||
|
}, [requestNotificationsPermission])
|
||||||
|
|
||||||
const pagerRef = React.useRef<PagerRef>(null)
|
const pagerRef = React.useRef<PagerRef>(null)
|
||||||
const lastPagerReportedIndexRef = React.useRef(selectedIndex)
|
const lastPagerReportedIndexRef = React.useRef(selectedIndex)
|
||||||
React.useLayoutEffect(() => {
|
React.useLayoutEffect(() => {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import {TextLink} from 'view/com/util/Link'
|
|||||||
import {ListMethods} from 'view/com/util/List'
|
import {ListMethods} from 'view/com/util/List'
|
||||||
import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn'
|
import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn'
|
||||||
import {CenteredView} from 'view/com/util/Views'
|
import {CenteredView} from 'view/com/util/Views'
|
||||||
|
import {Loader} from '#/components/Loader'
|
||||||
import {Feed} from '../com/notifications/Feed'
|
import {Feed} from '../com/notifications/Feed'
|
||||||
import {FAB} from '../com/util/fab/FAB'
|
import {FAB} from '../com/util/fab/FAB'
|
||||||
import {MainScrollProvider} from '../com/util/MainScrollProvider'
|
import {MainScrollProvider} from '../com/util/MainScrollProvider'
|
||||||
@@ -43,6 +44,7 @@ export function NotificationsScreen({}: Props) {
|
|||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const setMinimalShellMode = useSetMinimalShellMode()
|
const setMinimalShellMode = useSetMinimalShellMode()
|
||||||
const [isScrolledDown, setIsScrolledDown] = React.useState(false)
|
const [isScrolledDown, setIsScrolledDown] = React.useState(false)
|
||||||
|
const [isLoadingLatest, setIsLoadingLatest] = React.useState(false)
|
||||||
const scrollElRef = React.useRef<ListMethods>(null)
|
const scrollElRef = React.useRef<ListMethods>(null)
|
||||||
const {screen} = useAnalytics()
|
const {screen} = useAnalytics()
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
@@ -68,9 +70,13 @@ export function NotificationsScreen({}: Props) {
|
|||||||
truncateAndInvalidate(queryClient, NOTIFS_RQKEY())
|
truncateAndInvalidate(queryClient, NOTIFS_RQKEY())
|
||||||
} else {
|
} else {
|
||||||
// check with the server
|
// check with the server
|
||||||
unreadApi.checkUnread({invalidate: true})
|
setIsLoadingLatest(true)
|
||||||
|
unreadApi
|
||||||
|
.checkUnread({invalidate: true})
|
||||||
|
.catch(() => undefined)
|
||||||
|
.then(() => setIsLoadingLatest(false))
|
||||||
}
|
}
|
||||||
}, [scrollToTop, queryClient, unreadApi, hasNew])
|
}, [scrollToTop, queryClient, unreadApi, hasNew, setIsLoadingLatest])
|
||||||
|
|
||||||
const onFocusCheckLatest = useNonReactiveCallback(() => {
|
const onFocusCheckLatest = useNonReactiveCallback(() => {
|
||||||
// on focus, check for latest, but only invalidate if the user
|
// on focus, check for latest, but only invalidate if the user
|
||||||
@@ -139,11 +145,20 @@ export function NotificationsScreen({}: Props) {
|
|||||||
}
|
}
|
||||||
onPress={emitSoftReset}
|
onPress={emitSoftReset}
|
||||||
/>
|
/>
|
||||||
|
{isLoadingLatest ? <Loader size="md" /> : <></>}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return <></>
|
return <></>
|
||||||
}, [isDesktop, pal, hasNew])
|
}, [isDesktop, pal, hasNew, isLoadingLatest])
|
||||||
|
|
||||||
|
const renderHeaderSpinner = React.useCallback(() => {
|
||||||
|
return (
|
||||||
|
<View style={{width: 30, height: 20, alignItems: 'flex-end'}}>
|
||||||
|
{isLoadingLatest ? <Loader width={20} /> : <></>}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}, [isLoadingLatest])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CenteredView
|
<CenteredView
|
||||||
@@ -154,6 +169,7 @@ export function NotificationsScreen({}: Props) {
|
|||||||
title={_(msg`Notifications`)}
|
title={_(msg`Notifications`)}
|
||||||
canGoBack={false}
|
canGoBack={false}
|
||||||
showBorder={true}
|
showBorder={true}
|
||||||
|
renderButton={renderHeaderSpinner}
|
||||||
/>
|
/>
|
||||||
<MainScrollProvider>
|
<MainScrollProvider>
|
||||||
<Feed
|
<Feed
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
import {useSetMinimalShellMode} from '#/state/shell'
|
import {useSetMinimalShellMode} from '#/state/shell'
|
||||||
import {useComposerControls} from '#/state/shell/composer'
|
import {useComposerControls} from '#/state/shell/composer'
|
||||||
import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
|
import {useMinimalShellFabTransform} from 'lib/hooks/useMinimalShellTransform'
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||||
import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types'
|
import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types'
|
||||||
import {makeRecordUri} from 'lib/strings/url-helpers'
|
import {makeRecordUri} from 'lib/strings/url-helpers'
|
||||||
@@ -26,7 +26,7 @@ type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostThread'>
|
|||||||
export function PostThreadScreen({route}: Props) {
|
export function PostThreadScreen({route}: Props) {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const {hasSession} = useSession()
|
const {hasSession} = useSession()
|
||||||
const {fabMinimalShellTransform} = useMinimalShellMode()
|
const fabMinimalShellTransform = useMinimalShellFabTransform()
|
||||||
const setMinimalShellMode = useSetMinimalShellMode()
|
const setMinimalShellMode = useSetMinimalShellMode()
|
||||||
const {openComposer} = useComposerControls()
|
const {openComposer} = useComposerControls()
|
||||||
const safeAreaInsets = useSafeAreaInsets()
|
const safeAreaInsets = useSafeAreaInsets()
|
||||||
|
|||||||
@@ -468,7 +468,7 @@ const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
|
|||||||
}, [onScrollToTop, isScreenFocused])
|
}, [onScrollToTop, isScreenFocused])
|
||||||
|
|
||||||
const renderPostsEmpty = useCallback(() => {
|
const renderPostsEmpty = useCallback(() => {
|
||||||
return <EmptyState icon="feed" message={_(msg`This feed is empty!`)} />
|
return <EmptyState icon="hashtag" message={_(msg`This feed is empty.`)} />
|
||||||
}, [_])
|
}, [_])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -726,7 +726,7 @@ const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
|
|||||||
}, [onScrollToTop, isScreenFocused])
|
}, [onScrollToTop, isScreenFocused])
|
||||||
|
|
||||||
const renderPostsEmpty = useCallback(() => {
|
const renderPostsEmpty = useCallback(() => {
|
||||||
return <EmptyState icon="feed" message={_(msg`This feed is empty!`)} />
|
return <EmptyState icon="hashtag" message={_(msg`This feed is empty.`)} />
|
||||||
}, [_])
|
}, [_])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {StackActions} from '@react-navigation/native'
|
|||||||
import {useAnalytics} from '#/lib/analytics/analytics'
|
import {useAnalytics} from '#/lib/analytics/analytics'
|
||||||
import {useHaptics} from '#/lib/haptics'
|
import {useHaptics} from '#/lib/haptics'
|
||||||
import {useDedupe} from '#/lib/hooks/useDedupe'
|
import {useDedupe} from '#/lib/hooks/useDedupe'
|
||||||
import {useMinimalShellMode} from '#/lib/hooks/useMinimalShellMode'
|
import {useMinimalShellFooterTransform} from '#/lib/hooks/useMinimalShellTransform'
|
||||||
import {useNavigationTabState} from '#/lib/hooks/useNavigationTabState'
|
import {useNavigationTabState} from '#/lib/hooks/useNavigationTabState'
|
||||||
import {usePalette} from '#/lib/hooks/usePalette'
|
import {usePalette} from '#/lib/hooks/usePalette'
|
||||||
import {clamp} from '#/lib/numbers'
|
import {clamp} from '#/lib/numbers'
|
||||||
@@ -66,7 +66,7 @@ export function BottomBar({navigation}: BottomTabBarProps) {
|
|||||||
useNavigationTabState()
|
useNavigationTabState()
|
||||||
const numUnreadNotifications = useUnreadNotifications()
|
const numUnreadNotifications = useUnreadNotifications()
|
||||||
const numUnreadMessages = useUnreadMessageCount()
|
const numUnreadMessages = useUnreadMessageCount()
|
||||||
const {footerMinimalShellTransform} = useMinimalShellMode()
|
const footerMinimalShellTransform = useMinimalShellFooterTransform()
|
||||||
const {data: profile} = useProfileQuery({did: currentAccount?.did})
|
const {data: profile} = useProfileQuery({did: currentAccount?.did})
|
||||||
const {requestSwitchToAccount} = useLoggedOutViewControls()
|
const {requestSwitchToAccount} = useLoggedOutViewControls()
|
||||||
const closeAllActiveElements = useCloseAllActiveElements()
|
const closeAllActiveElements = useCloseAllActiveElements()
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {msg, Trans} from '@lingui/macro'
|
|||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {useNavigationState} from '@react-navigation/native'
|
import {useNavigationState} from '@react-navigation/native'
|
||||||
|
|
||||||
import {useMinimalShellMode} from '#/lib/hooks/useMinimalShellMode'
|
import {useMinimalShellFooterTransform} from '#/lib/hooks/useMinimalShellTransform'
|
||||||
import {usePalette} from '#/lib/hooks/usePalette'
|
import {usePalette} from '#/lib/hooks/usePalette'
|
||||||
import {clamp} from '#/lib/numbers'
|
import {clamp} from '#/lib/numbers'
|
||||||
import {getCurrentRoute, isTab} from '#/lib/routes/helpers'
|
import {getCurrentRoute, isTab} from '#/lib/routes/helpers'
|
||||||
@@ -16,7 +16,6 @@ import {s} from '#/lib/styles'
|
|||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
|
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
|
||||||
import {useCloseAllActiveElements} from '#/state/util'
|
import {useCloseAllActiveElements} from '#/state/util'
|
||||||
import {useGate} from 'lib/statsig/statsig'
|
|
||||||
import {useUnreadMessageCount} from 'state/queries/messages/list-converations'
|
import {useUnreadMessageCount} from 'state/queries/messages/list-converations'
|
||||||
import {useUnreadNotifications} from 'state/queries/notifications/unread'
|
import {useUnreadNotifications} from 'state/queries/notifications/unread'
|
||||||
import {Button} from '#/view/com/util/forms/Button'
|
import {Button} from '#/view/com/util/forms/Button'
|
||||||
@@ -49,8 +48,7 @@ export function BottomBarWeb() {
|
|||||||
const {hasSession, currentAccount} = useSession()
|
const {hasSession, currentAccount} = useSession()
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const safeAreaInsets = useSafeAreaInsets()
|
const safeAreaInsets = useSafeAreaInsets()
|
||||||
const gate = useGate()
|
const footerMinimalShellTransform = useMinimalShellFooterTransform()
|
||||||
const {footerMinimalShellTransform} = useMinimalShellMode()
|
|
||||||
const {requestSwitchToAccount} = useLoggedOutViewControls()
|
const {requestSwitchToAccount} = useLoggedOutViewControls()
|
||||||
const closeAllActiveElements = useCloseAllActiveElements()
|
const closeAllActiveElements = useCloseAllActiveElements()
|
||||||
const iconWidth = 26
|
const iconWidth = 26
|
||||||
@@ -115,14 +113,13 @@ export function BottomBarWeb() {
|
|||||||
width={iconWidth - 1}
|
width={iconWidth - 1}
|
||||||
style={[styles.ctrlIcon, pal.text, styles.messagesIcon]}
|
style={[styles.ctrlIcon, pal.text, styles.messagesIcon]}
|
||||||
/>
|
/>
|
||||||
{unreadMessageCount.count > 0 &&
|
{unreadMessageCount.count > 0 && (
|
||||||
gate('show_notification_badge_mobile_web') && (
|
<View style={styles.notificationCount}>
|
||||||
<View style={styles.notificationCount}>
|
<Text style={styles.notificationCountLabel}>
|
||||||
<Text style={styles.notificationCountLabel}>
|
{unreadMessageCount.numUnread}
|
||||||
{unreadMessageCount.numUnread}
|
</Text>
|
||||||
</Text>
|
</View>
|
||||||
</View>
|
)}
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
@@ -136,14 +133,13 @@ export function BottomBarWeb() {
|
|||||||
width={iconWidth}
|
width={iconWidth}
|
||||||
style={[styles.ctrlIcon, pal.text, styles.bellIcon]}
|
style={[styles.ctrlIcon, pal.text, styles.bellIcon]}
|
||||||
/>
|
/>
|
||||||
{notificationCountStr !== '' &&
|
{notificationCountStr !== '' && (
|
||||||
gate('show_notification_badge_mobile_web') && (
|
<View style={styles.notificationCount}>
|
||||||
<View style={styles.notificationCount}>
|
<Text style={styles.notificationCountLabel}>
|
||||||
<Text style={styles.notificationCountLabel}>
|
{notificationCountStr}
|
||||||
{notificationCountStr}
|
</Text>
|
||||||
</Text>
|
</View>
|
||||||
</View>
|
)}
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
|||||||
Reference in New Issue
Block a user