[Clipclops] New routes with placeholder screens (#3725)
* add new routes with placeholder screens * gate content * add filled envelope style * swap filled state * switch to `useAgent`zio/stable
parent
1af59ca8a7
commit
ce85375c85
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M12 11.708 2.654 4.06A.998.998 0 0 1 3 4h18c.122 0 .238.022.346.061L12 11.708ZM2 19V6.11l9.367 7.664a1 1 0 0 0 1.266 0L22 6.11V19a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1Z" clip-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 303 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M7 3a1 1 0 0 1 1 1v1.126a4 4 0 0 1 0 7.748V20a1 1 0 1 1-2 0v-7.126a4 4 0 0 1 0-7.748V4a1 1 0 0 1 1-1Zm10 0a1 1 0 0 1 1 1v9.126a4 4 0 1 1-2 0V4a1 1 0 0 1 1-1ZM7 7a2 2 0 1 0 0 4 2 2 0 1 0 0-4Zm10 8a2 2 0 1 0 0 4 2 2 0 1 0 0-4Z" clip-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 367 B |
|
@ -200,6 +200,8 @@ func serve(cctx *cli.Context) error {
|
|||
e.GET("/support/community-guidelines", server.WebGeneric)
|
||||
e.GET("/support/copyright", server.WebGeneric)
|
||||
e.GET("/intent/compose", server.WebGeneric)
|
||||
e.GET("/messages", server.WebGeneric)
|
||||
e.GET("/messages/:conversation", server.WebGeneric)
|
||||
|
||||
// profile endpoints; only first populates info
|
||||
e.GET("/profile/:handleOrDID", server.WebProfile)
|
||||
|
|
|
@ -25,6 +25,7 @@ import {
|
|||
FeedsTabNavigatorParams,
|
||||
FlatNavigatorParams,
|
||||
HomeTabNavigatorParams,
|
||||
MessagesTabNavigatorParams,
|
||||
MyProfileTabNavigatorParams,
|
||||
NotificationsTabNavigatorParams,
|
||||
SearchTabNavigatorParams,
|
||||
|
@ -46,6 +47,9 @@ import {init as initAnalytics} from './lib/analytics/analytics'
|
|||
import {useWebScrollRestoration} from './lib/hooks/useWebScrollRestoration'
|
||||
import {attachRouteToLogEvents, logEvent} from './lib/statsig/statsig'
|
||||
import {router} from './routes'
|
||||
import {MessagesConversationScreen} from './screens/Messages/Conversation'
|
||||
import {MessagesListScreen} from './screens/Messages/List'
|
||||
import {MessagesSettingsScreen} from './screens/Messages/Settings'
|
||||
import {useModalControls} from './state/modals'
|
||||
import {useUnreadNotifications} from './state/queries/notifications/unread'
|
||||
import {useSession} from './state/session'
|
||||
|
@ -92,6 +96,8 @@ const NotificationsTab =
|
|||
createNativeStackNavigatorWithAuth<NotificationsTabNavigatorParams>()
|
||||
const MyProfileTab =
|
||||
createNativeStackNavigatorWithAuth<MyProfileTabNavigatorParams>()
|
||||
const MessagesTab =
|
||||
createNativeStackNavigatorWithAuth<MessagesTabNavigatorParams>()
|
||||
const Flat = createNativeStackNavigatorWithAuth<FlatNavigatorParams>()
|
||||
const Tab = createBottomTabNavigator<BottomTabNavigatorParams>()
|
||||
|
||||
|
@ -290,6 +296,16 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
|
|||
getComponent={() => HashtagScreen}
|
||||
options={{title: title(msg`Hashtag`)}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="MessagesConversation"
|
||||
getComponent={() => MessagesConversationScreen}
|
||||
options={{title: title(msg`Chat`), requireAuth: true}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="MessagesSettings"
|
||||
getComponent={() => MessagesSettingsScreen}
|
||||
options={{title: title(msg`Messaging settings`), requireAuth: true}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -323,6 +339,10 @@ function TabsNavigator() {
|
|||
name="MyProfileTab"
|
||||
getComponent={() => MyProfileTabNavigator}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name="MessagesTab"
|
||||
getComponent={() => MessagesTabNavigator}
|
||||
/>
|
||||
</Tab.Navigator>
|
||||
)
|
||||
}
|
||||
|
@ -429,6 +449,28 @@ function MyProfileTabNavigator() {
|
|||
)
|
||||
}
|
||||
|
||||
function MessagesTabNavigator() {
|
||||
const pal = usePalette('default')
|
||||
return (
|
||||
<MessagesTab.Navigator
|
||||
screenOptions={{
|
||||
animation: isAndroid ? 'none' : undefined,
|
||||
gestureEnabled: true,
|
||||
fullScreenGestureEnabled: true,
|
||||
headerShown: false,
|
||||
animationDuration: 250,
|
||||
contentStyle: pal.view,
|
||||
}}>
|
||||
<MessagesTab.Screen
|
||||
name="MessagesList"
|
||||
getComponent={() => MessagesListScreen}
|
||||
options={{requireAuth: true}}
|
||||
/>
|
||||
{commonScreens(MessagesTab as typeof HomeTab)}
|
||||
</MessagesTab.Navigator>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* The FlatNavigator is used by Web to represent the routes
|
||||
* in a single ("flat") stack.
|
||||
|
@ -469,6 +511,11 @@ const FlatNavigator = () => {
|
|||
getComponent={() => NotificationsScreen}
|
||||
options={{title: title(msg`Notifications`), requireAuth: true}}
|
||||
/>
|
||||
<Flat.Screen
|
||||
name="MessagesList"
|
||||
getComponent={() => MessagesListScreen}
|
||||
options={{title: title(msg`Messages`), requireAuth: true}}
|
||||
/>
|
||||
{commonScreens(Flat as typeof HomeTab, numUnread)}
|
||||
</Flat.Navigator>
|
||||
)
|
||||
|
@ -522,6 +569,9 @@ const LINKING = {
|
|||
if (name === 'Home') {
|
||||
return buildStateObject('HomeTab', 'Home', params)
|
||||
}
|
||||
if (name === 'Messages') {
|
||||
return buildStateObject('MessagesTab', 'MessagesList', params)
|
||||
}
|
||||
// if the path is something else, like a post, profile, or even settings, we need to initialize the home tab as pre-existing state otherwise the back button will not work
|
||||
return buildStateObject('HomeTab', name, params, [
|
||||
{
|
||||
|
|
|
@ -3,3 +3,7 @@ import {createSinglePathSVG} from './TEMPLATE'
|
|||
export const Envelope_Stroke2_Corner0_Rounded = createSinglePathSVG({
|
||||
path: 'M4.568 4h14.864c.252 0 .498 0 .706.017.229.019.499.063.77.201a2 2 0 0 1 .874.874c.138.271.182.541.201.77.017.208.017.454.017.706v10.864c0 .252 0 .498-.017.706a2.022 2.022 0 0 1-.201.77 2 2 0 0 1-.874.874 2.022 2.022 0 0 1-.77.201c-.208.017-.454.017-.706.017H4.568c-.252 0-.498 0-.706-.017a2.022 2.022 0 0 1-.77-.201 2 2 0 0 1-.874-.874 2.022 2.022 0 0 1-.201-.77C2 17.93 2 17.684 2 17.432V6.568c0-.252 0-.498.017-.706.019-.229.063-.499.201-.77a2 2 0 0 1 .874-.874c.271-.138.541-.182.77-.201C4.07 4 4.316 4 4.568 4Zm.456 2L12 11.708 18.976 6H5.024ZM20 7.747l-6.733 5.509a2 2 0 0 1-2.534 0L4 7.746V17.4a8.187 8.187 0 0 0 .011.589h.014c.116.01.278.011.575.011h14.8a8.207 8.207 0 0 0 .589-.012v-.013c.01-.116.011-.279.011-.575V7.747Z',
|
||||
})
|
||||
|
||||
export const Envelope_Filled_Stroke2_Corner0_Rounded = createSinglePathSVG({
|
||||
path: 'M12 11.708 2.654 4.06A.998.998 0 0 1 3 4h18c.122 0 .238.022.346.061L12 11.708ZM2 19V6.11l9.367 7.664a1 1 0 0 0 1.266 0L22 6.11V19a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1Z',
|
||||
})
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
import {createSinglePathSVG} from './TEMPLATE'
|
||||
|
||||
export const SettingsSliderVertical_Stroke2_Corner0_Rounded =
|
||||
createSinglePathSVG({
|
||||
path: 'M7 3a1 1 0 0 1 1 1v1.126a4 4 0 0 1 0 7.748V20a1 1 0 1 1-2 0v-7.126a4 4 0 0 1 0-7.748V4a1 1 0 0 1 1-1Zm10 0a1 1 0 0 1 1 1v9.126a4 4 0 1 1-2 0V4a1 1 0 0 1 1-1ZM7 7a2 2 0 1 0 0 4 2 2 0 1 0 0-4Zm10 8a2 2 0 1 0 0 4 2 2 0 1 0 0-4Z',
|
||||
})
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react'
|
||||
import Svg, {Path} from 'react-native-svg'
|
||||
|
||||
import {useCommonSVGProps, Props} from '#/components/icons/common'
|
||||
import {Props, useCommonSVGProps} from '#/components/icons/common'
|
||||
|
||||
export const IconTemplate_Stroke2_Corner0_Rounded = React.forwardRef(
|
||||
function LogoImpl(props: Props, ref) {
|
||||
|
|
|
@ -76,6 +76,7 @@ export type TrackPropertiesMap = {
|
|||
'MobileShell:SearchButtonPressed': {}
|
||||
'MobileShell:NotificationsButtonPressed': {}
|
||||
'MobileShell:FeedsButtonPressed': {}
|
||||
'MobileShell:MessagesButtonPressed': {}
|
||||
// NOTIFICATIONS events
|
||||
'Notificatons:OpenApp': {}
|
||||
// LISTS events
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import {useNavigationState} from '@react-navigation/native'
|
||||
|
||||
import {getTabState, TabState} from 'lib/routes/helpers'
|
||||
|
||||
export function useNavigationTabState() {
|
||||
|
@ -10,13 +11,15 @@ export function useNavigationTabState() {
|
|||
isAtNotifications:
|
||||
getTabState(state, 'Notifications') !== TabState.Outside,
|
||||
isAtMyProfile: getTabState(state, 'MyProfile') !== TabState.Outside,
|
||||
isAtMessages: getTabState(state, 'MessagesList') !== TabState.Outside,
|
||||
}
|
||||
if (
|
||||
!res.isAtHome &&
|
||||
!res.isAtSearch &&
|
||||
!res.isAtFeeds &&
|
||||
!res.isAtNotifications &&
|
||||
!res.isAtMyProfile
|
||||
!res.isAtMyProfile &&
|
||||
!res.isAtMessages
|
||||
) {
|
||||
// HACK for some reason useNavigationState will give us pre-hydration results
|
||||
// and not update after, so we force isAtHome if all came back false
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {RouteParams, Route} from './types'
|
||||
import {Route, RouteParams} from './types'
|
||||
|
||||
export class Router {
|
||||
routes: [string, Route][] = []
|
||||
|
|
|
@ -38,6 +38,8 @@ export type CommonNavigatorParams = {
|
|||
AccessibilitySettings: undefined
|
||||
Search: {q?: string}
|
||||
Hashtag: {tag: string; author?: string}
|
||||
MessagesConversation: {conversation: string}
|
||||
MessagesSettings: undefined
|
||||
}
|
||||
|
||||
export type BottomTabNavigatorParams = CommonNavigatorParams & {
|
||||
|
@ -46,6 +48,7 @@ export type BottomTabNavigatorParams = CommonNavigatorParams & {
|
|||
FeedsTab: undefined
|
||||
NotificationsTab: undefined
|
||||
MyProfileTab: undefined
|
||||
MessagesTab: undefined
|
||||
}
|
||||
|
||||
export type HomeTabNavigatorParams = CommonNavigatorParams & {
|
||||
|
@ -68,12 +71,17 @@ export type MyProfileTabNavigatorParams = CommonNavigatorParams & {
|
|||
MyProfile: undefined
|
||||
}
|
||||
|
||||
export type MessagesTabNavigatorParams = CommonNavigatorParams & {
|
||||
MessagesList: undefined
|
||||
}
|
||||
|
||||
export type FlatNavigatorParams = CommonNavigatorParams & {
|
||||
Home: undefined
|
||||
Search: {q?: string}
|
||||
Feeds: undefined
|
||||
Notifications: undefined
|
||||
Hashtag: {tag: string; author?: string}
|
||||
MessagesList: undefined
|
||||
}
|
||||
|
||||
export type AllNavigatorParams = CommonNavigatorParams & {
|
||||
|
@ -87,6 +95,8 @@ export type AllNavigatorParams = CommonNavigatorParams & {
|
|||
Notifications: undefined
|
||||
MyProfileTab: undefined
|
||||
Hashtag: {tag: string; author?: string}
|
||||
MessagesTab: undefined
|
||||
MessagesList: undefined
|
||||
}
|
||||
|
||||
// NOTE
|
||||
|
|
|
@ -3,6 +3,7 @@ export type Gate =
|
|||
| 'autoexpand_suggestions_on_profile_follow_v2'
|
||||
| 'disable_min_shell_on_foregrounding_v2'
|
||||
| 'disable_poll_on_discover_v2'
|
||||
| 'dms'
|
||||
| 'hide_vertical_scroll_indicators'
|
||||
| 'show_follow_back_label_v2'
|
||||
| 'start_session_with_following_v2'
|
||||
|
|
|
@ -37,4 +37,7 @@ export const router = new Router({
|
|||
CommunityGuidelines: '/support/community-guidelines',
|
||||
CopyrightPolicy: '/support/copyright',
|
||||
Hashtag: '/hashtag/:tag',
|
||||
MessagesList: '/messages',
|
||||
MessagesSettings: '/messages/settings',
|
||||
MessagesConversation: '/messages/:conversation',
|
||||
})
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {NativeStackScreenProps} from '@react-navigation/native-stack'
|
||||
|
||||
import {CommonNavigatorParams} from '#/lib/routes/types'
|
||||
import {useGate} from '#/lib/statsig/statsig'
|
||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||
import {ClipClopGate} from '../gate'
|
||||
|
||||
type Props = NativeStackScreenProps<
|
||||
CommonNavigatorParams,
|
||||
'MessagesConversation'
|
||||
>
|
||||
export function MessagesConversationScreen({route}: Props) {
|
||||
const chatId = route.params.conversation
|
||||
const {_} = useLingui()
|
||||
|
||||
const gate = useGate()
|
||||
if (!gate('dms')) return <ClipClopGate />
|
||||
|
||||
return (
|
||||
<View>
|
||||
<ViewHeader
|
||||
title={_(msg`Chat with ${chatId}`)}
|
||||
showOnDesktop
|
||||
showBorder
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,234 @@
|
|||
import React, {useCallback, useState} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {NativeStackScreenProps} from '@react-navigation/native-stack'
|
||||
import {useInfiniteQuery} from '@tanstack/react-query'
|
||||
|
||||
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
||||
import {MessagesTabNavigatorParams} from '#/lib/routes/types'
|
||||
import {useGate} from '#/lib/statsig/statsig'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {logger} from '#/logger'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {List} from '#/view/com/util/List'
|
||||
import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
|
||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||
import {useTheme} from '#/alf'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {SettingsSliderVertical_Stroke2_Corner0_Rounded as SettingsSlider} from '#/components/icons/SettingsSlider'
|
||||
import {Link} from '#/components/Link'
|
||||
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {ClipClopGate} from '../gate'
|
||||
|
||||
type Props = NativeStackScreenProps<MessagesTabNavigatorParams, 'MessagesList'>
|
||||
export function MessagesListScreen({}: Props) {
|
||||
const {_} = useLingui()
|
||||
const t = useTheme()
|
||||
|
||||
const renderButton = useCallback(() => {
|
||||
return (
|
||||
<Link
|
||||
to="/messages/settings"
|
||||
accessibilityLabel={_(msg`Message settings`)}
|
||||
accessibilityHint={_(msg`Opens the message settings page`)}>
|
||||
<SettingsSlider size="lg" style={t.atoms.text} />
|
||||
</Link>
|
||||
)
|
||||
}, [_, t.atoms.text])
|
||||
|
||||
const initialNumToRender = useInitialNumToRender()
|
||||
const [isPTRing, setIsPTRing] = useState(false)
|
||||
|
||||
const {
|
||||
data,
|
||||
isLoading,
|
||||
isFetchingNextPage,
|
||||
hasNextPage,
|
||||
fetchNextPage,
|
||||
error,
|
||||
refetch,
|
||||
} = usePlaceholderConversations()
|
||||
|
||||
const isError = !!error
|
||||
|
||||
const conversations = React.useMemo(() => {
|
||||
if (data?.pages) {
|
||||
return data.pages.flat()
|
||||
}
|
||||
return []
|
||||
}, [data])
|
||||
|
||||
const onRefresh = React.useCallback(async () => {
|
||||
setIsPTRing(true)
|
||||
try {
|
||||
await refetch()
|
||||
} catch (err) {
|
||||
logger.error('Failed to refresh conversations', {message: err})
|
||||
}
|
||||
setIsPTRing(false)
|
||||
}, [refetch, setIsPTRing])
|
||||
|
||||
const onEndReached = React.useCallback(async () => {
|
||||
if (isFetchingNextPage || !hasNextPage || isError) return
|
||||
try {
|
||||
await fetchNextPage()
|
||||
} catch (err) {
|
||||
logger.error('Failed to load more conversations', {message: err})
|
||||
}
|
||||
}, [isFetchingNextPage, hasNextPage, isError, fetchNextPage])
|
||||
|
||||
const gate = useGate()
|
||||
if (!gate('dms')) return <ClipClopGate />
|
||||
|
||||
if (conversations.length < 1) {
|
||||
return (
|
||||
<ListMaybePlaceholder
|
||||
isLoading={isLoading}
|
||||
isError={isError}
|
||||
emptyType="results"
|
||||
emptyMessage={_(
|
||||
msg`You have no messages yet. Start a conversation with someone!`,
|
||||
)}
|
||||
errorMessage={cleanError(error)}
|
||||
onRetry={isError ? refetch : undefined}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<View>
|
||||
<ViewHeader
|
||||
title={_(msg`Messages`)}
|
||||
showOnDesktop
|
||||
renderButton={renderButton}
|
||||
showBorder
|
||||
canGoBack={false}
|
||||
/>
|
||||
<List
|
||||
data={conversations}
|
||||
renderItem={({item}) => {
|
||||
return (
|
||||
<Link
|
||||
to={`/messages/${item.profile.handle}`}
|
||||
style={[a.flex_1, a.pl_md, a.py_sm, a.gap_md, a.pr_2xl]}>
|
||||
<PreviewableUserAvatar
|
||||
did={item.profile.did}
|
||||
handle={item.profile.handle}
|
||||
size={44}
|
||||
avatar={item.profile.avatar}
|
||||
/>
|
||||
<View style={[a.flex_1]}>
|
||||
<View
|
||||
style={[
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.justify_between,
|
||||
a.gap_lg,
|
||||
a.flex_1,
|
||||
]}>
|
||||
<Text numberOfLines={1}>
|
||||
<Text style={item.unread && a.font_bold}>
|
||||
{item.profile.displayName || item.profile.handle}
|
||||
</Text>{' '}
|
||||
<Text style={t.atoms.text_contrast_medium}>
|
||||
@{item.profile.handle}
|
||||
</Text>
|
||||
</Text>
|
||||
{item.unread && (
|
||||
<View
|
||||
style={[
|
||||
a.ml_2xl,
|
||||
{backgroundColor: t.palette.primary_500},
|
||||
a.rounded_full,
|
||||
{height: 7, width: 7},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<Text
|
||||
numberOfLines={2}
|
||||
style={[
|
||||
a.text_sm,
|
||||
item.unread ? a.font_bold : t.atoms.text_contrast_medium,
|
||||
]}>
|
||||
{item.lastMessage}
|
||||
</Text>
|
||||
</View>
|
||||
</Link>
|
||||
)
|
||||
}}
|
||||
keyExtractor={item => item.profile.did}
|
||||
refreshing={isPTRing}
|
||||
onRefresh={onRefresh}
|
||||
onEndReached={onEndReached}
|
||||
ListFooterComponent={
|
||||
<ListFooter
|
||||
isFetchingNextPage={isFetchingNextPage}
|
||||
error={cleanError(error)}
|
||||
onRetry={fetchNextPage}
|
||||
style={{borderColor: 'transparent'}}
|
||||
/>
|
||||
}
|
||||
onEndReachedThreshold={3}
|
||||
initialNumToRender={initialNumToRender}
|
||||
windowSize={11}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
function usePlaceholderConversations() {
|
||||
const {getAgent} = useAgent()
|
||||
|
||||
return useInfiniteQuery({
|
||||
queryKey: ['messages'],
|
||||
queryFn: async () => {
|
||||
const people = await getAgent().getProfiles({actors: PLACEHOLDER_PEOPLE})
|
||||
return people.data.profiles.map(profile => ({
|
||||
profile,
|
||||
unread: Math.random() > 0.5,
|
||||
lastMessage: getRandomPost(),
|
||||
}))
|
||||
},
|
||||
initialPageParam: undefined,
|
||||
getNextPageParam: () => undefined,
|
||||
})
|
||||
}
|
||||
|
||||
const PLACEHOLDER_PEOPLE = [
|
||||
'pfrazee.com',
|
||||
'haileyok.com',
|
||||
'danabra.mov',
|
||||
'esb.lol',
|
||||
'samuel.bsky.team',
|
||||
]
|
||||
|
||||
function getRandomPost() {
|
||||
const num = Math.floor(Math.random() * 10)
|
||||
switch (num) {
|
||||
case 0:
|
||||
return 'hello'
|
||||
case 1:
|
||||
return 'lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua'
|
||||
case 2:
|
||||
return 'banger post'
|
||||
case 3:
|
||||
return 'lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua'
|
||||
case 4:
|
||||
return 'lol look at this bug'
|
||||
case 5:
|
||||
return 'wow'
|
||||
case 6:
|
||||
return "that's pretty cool, wow!"
|
||||
case 7:
|
||||
return 'I think this is a bug'
|
||||
case 8:
|
||||
return 'Hello World!'
|
||||
case 9:
|
||||
return 'DMs when???'
|
||||
default:
|
||||
return 'this is unlikely'
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {NativeStackScreenProps} from '@react-navigation/native-stack'
|
||||
|
||||
import {CommonNavigatorParams} from '#/lib/routes/types'
|
||||
import {useGate} from '#/lib/statsig/statsig'
|
||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||
import {ClipClopGate} from '../gate'
|
||||
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'MessagesSettings'>
|
||||
export function MessagesSettingsScreen({}: Props) {
|
||||
const {_} = useLingui()
|
||||
|
||||
const gate = useGate()
|
||||
if (!gate('dms')) return <ClipClopGate />
|
||||
|
||||
return (
|
||||
<View>
|
||||
<ViewHeader title={_(msg`Settings`)} showOnDesktop />
|
||||
</View>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import React from 'react'
|
||||
import {Text, View} from 'react-native'
|
||||
|
||||
export function ClipClopGate() {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: 20,
|
||||
}}>
|
||||
<Text style={{fontSize: 50}}>🐴</Text>
|
||||
<Text style={{textAlign: 'center'}}>Nice try</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
|
@ -24,6 +24,7 @@ import {
|
|||
} from '#/lib/icons'
|
||||
import {clamp} from '#/lib/numbers'
|
||||
import {getTabState, TabState} from '#/lib/routes/helpers'
|
||||
import {useGate} from '#/lib/statsig/statsig'
|
||||
import {s} from '#/lib/styles'
|
||||
import {emitSoftReset} from '#/state/events'
|
||||
import {useUnreadNotifications} from '#/state/queries/notifications/unread'
|
||||
|
@ -39,9 +40,17 @@ import {Logo} from '#/view/icons/Logo'
|
|||
import {Logotype} from '#/view/icons/Logotype'
|
||||
import {useDialogControl} from '#/components/Dialog'
|
||||
import {SwitchAccountDialog} from '#/components/dialogs/SwitchAccount'
|
||||
import {Envelope_Stroke2_Corner0_Rounded as Envelope} from '#/components/icons/Envelope'
|
||||
import {Envelope_Filled_Stroke2_Corner0_Rounded as EnvelopeFilled} from '#/components/icons/Envelope'
|
||||
import {styles} from './BottomBarStyles'
|
||||
|
||||
type TabOptions = 'Home' | 'Search' | 'Notifications' | 'MyProfile' | 'Feeds'
|
||||
type TabOptions =
|
||||
| 'Home'
|
||||
| 'Search'
|
||||
| 'Notifications'
|
||||
| 'MyProfile'
|
||||
| 'Feeds'
|
||||
| 'Messages'
|
||||
|
||||
export function BottomBar({navigation}: BottomTabBarProps) {
|
||||
const {hasSession, currentAccount} = useSession()
|
||||
|
@ -50,8 +59,14 @@ export function BottomBar({navigation}: BottomTabBarProps) {
|
|||
const safeAreaInsets = useSafeAreaInsets()
|
||||
const {track} = useAnalytics()
|
||||
const {footerHeight} = useShellLayout()
|
||||
const {isAtHome, isAtSearch, isAtFeeds, isAtNotifications, isAtMyProfile} =
|
||||
useNavigationTabState()
|
||||
const {
|
||||
isAtHome,
|
||||
isAtSearch,
|
||||
isAtFeeds,
|
||||
isAtNotifications,
|
||||
isAtMyProfile,
|
||||
isAtMessages,
|
||||
} = useNavigationTabState()
|
||||
const numUnreadNotifications = useUnreadNotifications()
|
||||
const {footerMinimalShellTransform} = useMinimalShellMode()
|
||||
const {data: profile} = useProfileQuery({did: currentAccount?.did})
|
||||
|
@ -60,6 +75,7 @@ export function BottomBar({navigation}: BottomTabBarProps) {
|
|||
const dedupe = useDedupe()
|
||||
const accountSwitchControl = useDialogControl()
|
||||
const playHaptic = useHaptics()
|
||||
const gate = useGate()
|
||||
|
||||
const showSignIn = React.useCallback(() => {
|
||||
closeAllActiveElements()
|
||||
|
@ -104,6 +120,10 @@ export function BottomBar({navigation}: BottomTabBarProps) {
|
|||
onPressTab('MyProfile')
|
||||
}, [onPressTab])
|
||||
|
||||
const onPressMessages = React.useCallback(() => {
|
||||
onPressTab('Messages')
|
||||
}, [onPressTab])
|
||||
|
||||
const onLongPressProfile = React.useCallback(() => {
|
||||
playHaptic()
|
||||
accountSwitchControl.open()
|
||||
|
@ -220,6 +240,28 @@ export function BottomBar({navigation}: BottomTabBarProps) {
|
|||
: `${numUnreadNotifications} unread`
|
||||
}
|
||||
/>
|
||||
{gate('dms') && (
|
||||
<Btn
|
||||
testID="bottomBarMessagesBtn"
|
||||
icon={
|
||||
isAtMessages ? (
|
||||
<EnvelopeFilled
|
||||
size="lg"
|
||||
style={[styles.ctrlIcon, pal.text, styles.feedsIcon]}
|
||||
/>
|
||||
) : (
|
||||
<Envelope
|
||||
size="lg"
|
||||
style={[styles.ctrlIcon, pal.text, styles.feedsIcon]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
onPress={onPressMessages}
|
||||
accessibilityRole="tab"
|
||||
accessibilityLabel={_(msg`Messages`)}
|
||||
accessibilityHint=""
|
||||
/>
|
||||
)}
|
||||
<Btn
|
||||
testID="bottomBarProfileBtn"
|
||||
icon={
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import {StyleSheet} from 'react-native'
|
||||
|
||||
import {colors} from 'lib/styles'
|
||||
|
||||
export const styles = StyleSheet.create({
|
||||
|
@ -65,6 +66,9 @@ export const styles = StyleSheet.create({
|
|||
profileIcon: {
|
||||
top: -4,
|
||||
},
|
||||
messagesIcon: {
|
||||
top: 2,
|
||||
},
|
||||
onProfile: {
|
||||
borderWidth: 1,
|
||||
borderRadius: 100,
|
||||
|
|
|
@ -1,37 +1,41 @@
|
|||
import React from 'react'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {useNavigationState} from '@react-navigation/native'
|
||||
import {View} from 'react-native'
|
||||
import Animated from 'react-native-reanimated'
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||
import {View} from 'react-native'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {getCurrentRoute, isTab} from 'lib/routes/helpers'
|
||||
import {styles} from './BottomBarStyles'
|
||||
import {clamp} from 'lib/numbers'
|
||||
import {useNavigationState} from '@react-navigation/native'
|
||||
|
||||
import {useMinimalShellMode} from '#/lib/hooks/useMinimalShellMode'
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {
|
||||
BellIcon,
|
||||
BellIconSolid,
|
||||
HashtagIcon,
|
||||
HomeIcon,
|
||||
HomeIconSolid,
|
||||
MagnifyingGlassIcon2,
|
||||
MagnifyingGlassIcon2Solid,
|
||||
HashtagIcon,
|
||||
UserIcon,
|
||||
UserIconSolid,
|
||||
} from 'lib/icons'
|
||||
import {Link} from 'view/com/util/Link'
|
||||
import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
|
||||
import {makeProfileLink} from 'lib/routes/links'
|
||||
import {CommonNavigatorParams} from 'lib/routes/types'
|
||||
} from '#/lib/icons'
|
||||
import {clamp} from '#/lib/numbers'
|
||||
import {getCurrentRoute, isTab} from '#/lib/routes/helpers'
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {CommonNavigatorParams} from '#/lib/routes/types'
|
||||
import {useGate} from '#/lib/statsig/statsig'
|
||||
import {s} from '#/lib/styles'
|
||||
import {useSession} from '#/state/session'
|
||||
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
|
||||
import {useCloseAllActiveElements} from '#/state/util'
|
||||
import {Button} from '#/view/com/util/forms/Button'
|
||||
import {Text} from '#/view/com/util/text/Text'
|
||||
import {s} from 'lib/styles'
|
||||
import {Logo} from '#/view/icons/Logo'
|
||||
import {Logotype} from '#/view/icons/Logotype'
|
||||
import {Link} from 'view/com/util/Link'
|
||||
import {Envelope_Stroke2_Corner0_Rounded as Envelope} from '#/components/icons/Envelope'
|
||||
import {Envelope_Filled_Stroke2_Corner0_Rounded as EnvelopeFilled} from '#/components/icons/Envelope'
|
||||
import {styles} from './BottomBarStyles'
|
||||
|
||||
export function BottomBarWeb() {
|
||||
const {_} = useLingui()
|
||||
|
@ -41,6 +45,7 @@ export function BottomBarWeb() {
|
|||
const {footerMinimalShellTransform} = useMinimalShellMode()
|
||||
const {requestSwitchToAccount} = useLoggedOutViewControls()
|
||||
const closeAllActiveElements = useCloseAllActiveElements()
|
||||
const gate = useGate()
|
||||
|
||||
const showSignIn = React.useCallback(() => {
|
||||
closeAllActiveElements()
|
||||
|
@ -117,6 +122,19 @@ export function BottomBarWeb() {
|
|||
)
|
||||
}}
|
||||
</NavItem>
|
||||
{gate('dms') && (
|
||||
<NavItem routeName="Messages" href="/messages">
|
||||
{({isActive}) => {
|
||||
const Icon = isActive ? EnvelopeFilled : Envelope
|
||||
return (
|
||||
<Icon
|
||||
size="lg"
|
||||
style={[styles.ctrlIcon, pal.text, styles.messagesIcon]}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
</NavItem>
|
||||
)}
|
||||
<NavItem
|
||||
routeName="Profile"
|
||||
href={
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
useNavigationState,
|
||||
} from '@react-navigation/native'
|
||||
|
||||
import {useGate} from '#/lib/statsig/statsig'
|
||||
import {isInvalidHandle} from '#/lib/strings/handles'
|
||||
import {emitSoftReset} from '#/state/events'
|
||||
import {useFetchHandle} from '#/state/queries/handle'
|
||||
|
@ -46,6 +47,8 @@ import {LoadingPlaceholder} from 'view/com/util/LoadingPlaceholder'
|
|||
import {PressableWithHover} from 'view/com/util/PressableWithHover'
|
||||
import {Text} from 'view/com/util/text/Text'
|
||||
import {UserAvatar} from 'view/com/util/UserAvatar'
|
||||
import {Envelope_Stroke2_Corner0_Rounded as Envelope} from '#/components/icons/Envelope'
|
||||
import {Envelope_Filled_Stroke2_Corner0_Rounded as EnvelopeFilled} from '#/components/icons/Envelope'
|
||||
import {router} from '../../../routes'
|
||||
|
||||
function ProfileCard() {
|
||||
|
@ -272,6 +275,7 @@ export function DesktopLeftNav() {
|
|||
const {_} = useLingui()
|
||||
const {isDesktop, isTablet} = useWebMediaQueries()
|
||||
const numUnread = useUnreadNotifications()
|
||||
const gate = useGate()
|
||||
|
||||
if (!hasSession && !isDesktop) {
|
||||
return null
|
||||
|
@ -346,6 +350,16 @@ export function DesktopLeftNav() {
|
|||
}
|
||||
label={_(msg`Notifications`)}
|
||||
/>
|
||||
{gate('dms') && (
|
||||
<NavItem
|
||||
href="/messages"
|
||||
icon={<Envelope style={pal.text} width={isDesktop ? 26 : 30} />}
|
||||
iconFilled={
|
||||
<EnvelopeFilled style={pal.text} width={isDesktop ? 26 : 30} />
|
||||
}
|
||||
label={_(msg`Messages`)}
|
||||
/>
|
||||
)}
|
||||
<NavItem
|
||||
href="/feeds"
|
||||
icon={
|
||||
|
|
Loading…
Reference in New Issue