feat: improve mobile experience

This commit is contained in:
codytseng 2025-01-02 21:57:14 +08:00
parent 8ec0d46d58
commit 3946e603b3
98 changed files with 2508 additions and 1058 deletions

View file

@ -0,0 +1,74 @@
import FeedSwitcher from '@/components/FeedSwitcher'
import { Drawer, DrawerContent } from '@/components/ui/drawer'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import { simplifyUrl } from '@/lib/url'
import { useFeed } from '@/providers/FeedProvider'
import { useRelaySettings } from '@/providers/RelaySettingsProvider'
import { useScreenSize } from '@/providers/ScreenSizeProvider'
import { ChevronDown, Server, UsersRound } from 'lucide-react'
import { forwardRef, HTMLAttributes, useState } from 'react'
import { useTranslation } from 'react-i18next'
export default function FeedButton() {
const { isSmallScreen } = useScreenSize()
const [open, setOpen] = useState(false)
if (isSmallScreen) {
return (
<>
<FeedSwitcherTrigger onClick={() => setOpen(true)} />
<Drawer open={open} onOpenChange={setOpen}>
<DrawerContent className="max-h-[80vh]">
<div className="p-4 overflow-auto">
<FeedSwitcher close={() => setOpen(false)} />
</div>
</DrawerContent>
</Drawer>
</>
)
}
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<FeedSwitcherTrigger />
</PopoverTrigger>
<PopoverContent side="bottom" className="w-96 p-4 max-h-[80vh] overflow-auto">
<FeedSwitcher close={() => setOpen(false)} />
</PopoverContent>
</Popover>
)
}
const FeedSwitcherTrigger = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
(props, ref) => {
const { t } = useTranslation()
const { feedType } = useFeed()
const { relayGroups, temporaryRelayUrls } = useRelaySettings()
const activeGroup = relayGroups.find((group) => group.isActive)
const title =
feedType === 'following'
? t('Following')
: temporaryRelayUrls.length > 0
? temporaryRelayUrls.length === 1
? simplifyUrl(temporaryRelayUrls[0])
: t('Temporary')
: activeGroup
? activeGroup.relayUrls.length === 1
? simplifyUrl(activeGroup.relayUrls[0])
: activeGroup.groupName
: t('Choose a relay collection')
return (
<div
className="flex items-center gap-2 clickable px-3 h-full rounded-lg"
ref={ref}
{...props}
>
{feedType === 'following' ? <UsersRound /> : <Server />}
<div className="text-lg font-semibold">{title}</div>
<ChevronDown />
</div>
)
}
)

View file

@ -0,0 +1,17 @@
import { SearchDialog } from '@/components/SearchDialog'
import { Button } from '@/components/ui/button'
import { Search } from 'lucide-react'
import { useState } from 'react'
export default function SearchButton() {
const [open, setOpen] = useState(false)
return (
<>
<Button variant="ghost" size="titlebar-icon" onClick={() => setOpen(true)}>
<Search />
</Button>
<SearchDialog open={open} setOpen={setOpen} />
</>
)
}

View file

@ -1,48 +1,69 @@
import NoteList from '@/components/NoteList'
import RelaySettings from '@/components/RelaySettings'
import { Button } from '@/components/ui/button'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import { ScrollArea } from '@/components/ui/scroll-area'
import { BIG_RELAY_URLS } from '@/constants'
import PrimaryPageLayout from '@/layouts/PrimaryPageLayout'
import { useFeed } from '@/providers/FeedProvider'
import { useNostr } from '@/providers/NostrProvider'
import { useRelaySettings } from '@/providers/RelaySettingsProvider'
import { useEffect, useRef } from 'react'
import { useEffect, useMemo, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import FeedButton from './FeedButton'
import SearchButton from './SearchButton'
export default function NoteListPage() {
const { t } = useTranslation()
const layoutRef = useRef<{ scrollToTop: () => void }>(null)
const { relayUrls } = useRelaySettings()
const relayUrlsString = JSON.stringify(relayUrls)
const { feedType } = useFeed()
const { relayUrls, temporaryRelayUrls } = useRelaySettings()
const { pubkey, relayList, followings } = useNostr()
const urls = useMemo(() => {
return feedType === 'following'
? relayList?.read.length
? relayList.read.slice(0, 4)
: BIG_RELAY_URLS
: temporaryRelayUrls.length > 0
? temporaryRelayUrls
: relayUrls
}, [feedType, relayUrls, relayList, temporaryRelayUrls])
useEffect(() => {
if (layoutRef.current) {
layoutRef.current.scrollToTop()
}
}, [relayUrlsString])
if (!relayUrls.length) {
return (
<PrimaryPageLayout>
<div className="w-full text-center">
<Popover>
<PopoverTrigger asChild>
<Button title="relay settings" size="lg">
Choose a relay group
</Button>
</PopoverTrigger>
<PopoverContent className="w-96 h-[450px] p-0">
<ScrollArea className="h-full">
<div className="p-4">
<RelaySettings />
</div>
</ScrollArea>
</PopoverContent>
</Popover>
</div>
</PrimaryPageLayout>
)
}
}, [JSON.stringify(relayUrls), feedType])
return (
<PrimaryPageLayout ref={layoutRef}>
<NoteList relayUrls={relayUrls} />
<PrimaryPageLayout
pageName="home"
ref={layoutRef}
titlebar={<NoteListPageTitlebar />}
displayScrollToTopButton
>
{!!urls.length && (feedType === 'relays' || (relayList && followings)) ? (
<NoteList
relayUrls={urls}
filter={
feedType === 'following'
? {
authors:
pubkey && !followings?.includes(pubkey)
? [...(followings ?? []), pubkey]
: (followings ?? [])
}
: {}
}
/>
) : (
<div className="text-center text-sm text-muted-foreground">{t('loading...')}</div>
)}
</PrimaryPageLayout>
)
}
function NoteListPageTitlebar() {
return (
<div className="flex gap-1 items-center h-full justify-between">
<FeedButton />
<SearchButton />
</div>
)
}