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,23 @@
import { TFeedType } from '@/types'
import { createContext, useContext, useState } from 'react'
type TFeedContext = {
feedType: TFeedType
setFeedType: (feedType: TFeedType) => void
}
const FeedContext = createContext<TFeedContext | undefined>(undefined)
export const useFeed = () => {
const context = useContext(FeedContext)
if (!context) {
throw new Error('useFeed must be used within a FeedProvider')
}
return context
}
export function FeedProvider({ children }: { children: React.ReactNode }) {
const [feedType, setFeedType] = useState<TFeedType>('relays')
return <FeedContext.Provider value={{ feedType, setFeedType }}>{children}</FeedContext.Provider>
}

View File

@@ -9,7 +9,7 @@ import { useNostr } from './NostrProvider'
type TFollowListContext = {
followListEvent: Event | undefined
followings: string[]
isReady: boolean
isFetching: boolean
follow: (pubkey: string) => Promise<void>
unfollow: (pubkey: string) => Promise<void>
}
@@ -27,33 +27,35 @@ export const useFollowList = () => {
export function FollowListProvider({ children }: { children: React.ReactNode }) {
const { pubkey: accountPubkey, publish } = useNostr()
const [followListEvent, setFollowListEvent] = useState<Event | undefined>(undefined)
const [isReady, setIsReady] = useState(false)
const followings = useMemo(
() =>
followListEvent?.tags
.filter(tagNameEquals('p'))
.map(([, pubkey]) => pubkey)
.filter(Boolean)
.reverse() ?? [],
[followListEvent]
)
const [isFetching, setIsFetching] = useState(true)
const followings = useMemo(() => {
return Array.from(
new Set(
followListEvent?.tags
.filter(tagNameEquals('p'))
.map(([, pubkey]) => pubkey)
.filter(Boolean)
.reverse() ?? []
)
)
}, [followListEvent])
useEffect(() => {
if (!accountPubkey) return
const init = async () => {
setIsReady(false)
setIsFetching(true)
setFollowListEvent(undefined)
const event = await client.fetchFollowListEvent(accountPubkey)
setFollowListEvent(event)
setIsReady(true)
setIsFetching(false)
}
init()
}, [accountPubkey])
const follow = async (pubkey: string) => {
if (!isReady || !accountPubkey) return
if (isFetching || !accountPubkey) return
const newFollowListDraftEvent: TDraftEvent = {
kind: kinds.Contacts,
@@ -67,7 +69,7 @@ export function FollowListProvider({ children }: { children: React.ReactNode })
}
const unfollow = async (pubkey: string) => {
if (!isReady || !accountPubkey || !followListEvent) return
if (isFetching || !accountPubkey || !followListEvent) return
const newFollowListDraftEvent: TDraftEvent = {
kind: kinds.Contacts,
@@ -87,7 +89,7 @@ export function FollowListProvider({ children }: { children: React.ReactNode })
value={{
followListEvent,
followings,
isReady,
isFetching,
follow,
unfollow
}}

View File

@@ -1,9 +1,9 @@
import LoginDialog from '@/components/LoginDialog'
import { useToast } from '@/hooks'
import { useFetchFollowings, useToast } from '@/hooks'
import { useFetchRelayList } from '@/hooks/useFetchRelayList'
import client from '@/services/client.service'
import storage from '@/services/storage.service'
import { ISigner, TAccount, TAccountPointer, TDraftEvent } from '@/types'
import { ISigner, TAccount, TAccountPointer, TDraftEvent, TRelayList } from '@/types'
import dayjs from 'dayjs'
import { Event, kinds } from 'nostr-tools'
import { createContext, useContext, useEffect, useState } from 'react'
@@ -14,6 +14,8 @@ import { NsecSigner } from './nsec.signer'
type TNostrContext = {
pubkey: string | null
relayList: TRelayList | null
followings: string[] | null
account: TAccountPointer | null
accounts: TAccountPointer[]
switchAccount: (account: TAccountPointer | null) => Promise<void>
@@ -46,7 +48,8 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
const [signer, setSigner] = useState<ISigner | null>(null)
const [openLoginDialog, setOpenLoginDialog] = useState(false)
const { relayUrls: currentRelayUrls } = useRelaySettings()
const { relayList } = useFetchRelayList(account?.pubkey)
const { relayList, isFetching: isFetchingRelayList } = useFetchRelayList(account?.pubkey)
const { followings, isFetching: isFetchingFollowings } = useFetchFollowings(account?.pubkey)
useEffect(() => {
const init = async () => {
@@ -224,6 +227,8 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
<NostrContext.Provider
value={{
pubkey: account?.pubkey ?? null,
relayList: isFetchingRelayList ? null : relayList,
followings: isFetchingFollowings ? null : followings,
account,
accounts: storage
.getAccounts()

View File

@@ -5,6 +5,7 @@ import client from '@/services/client.service'
import storage from '@/services/storage.service'
import { TRelayGroup } from '@/types'
import { createContext, Dispatch, useContext, useEffect, useState } from 'react'
import { useFeed } from './FeedProvider'
type TRelaySettingsContext = {
relayGroups: TRelayGroup[]
@@ -31,6 +32,7 @@ export const useRelaySettings = () => {
}
export function RelaySettingsProvider({ children }: { children: React.ReactNode }) {
const { setFeedType } = useFeed()
const [relayGroups, setRelayGroups] = useState<TRelayGroup[]>([])
const [temporaryRelayUrls, setTemporaryRelayUrls] = useState<string[]>([])
const [relayUrls, setRelayUrls] = useState<string[]>(
@@ -49,6 +51,7 @@ export function RelaySettingsProvider({ children }: { children: React.ReactNode
.map((url) => normalizeUrl(url))
if (tempRelays.length) {
setTemporaryRelayUrls(tempRelays)
setFeedType('relays')
}
const storedGroups = storage.getRelayGroups()
setRelayGroups(storedGroups)
@@ -93,6 +96,7 @@ export function RelaySettingsProvider({ children }: { children: React.ReactNode
isActive: group.groupName === groupName
}))
)
setFeedType('relays')
setTemporaryRelayUrls([])
}

View File

@@ -12,7 +12,7 @@ type ThemeProviderState = {
setThemeSetting: (themeSetting: TThemeSetting) => Promise<void>
}
async function getSystemTheme() {
function getSystemTheme() {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
}
@@ -26,9 +26,9 @@ export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
useEffect(() => {
const init = async () => {
const themeSetting = await storage.getThemeSetting()
const themeSetting = storage.getThemeSetting()
if (themeSetting === 'system') {
setTheme(await getSystemTheme())
setTheme(getSystemTheme())
return
}
setTheme(themeSetting)
@@ -65,10 +65,10 @@ export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
const value = {
themeSetting: themeSetting,
setThemeSetting: async (themeSetting: TThemeSetting) => {
await storage.setThemeSetting(themeSetting)
storage.setThemeSetting(themeSetting)
setThemeSetting(themeSetting)
if (themeSetting === 'system') {
setTheme(await getSystemTheme())
setTheme(getSystemTheme())
return
}
setTheme(themeSetting)