feat: live feed toggle

This commit is contained in:
codytseng 2026-01-04 20:42:20 +08:00
parent 89bb9ad2d0
commit 917fcd9839
26 changed files with 130 additions and 26 deletions

View file

@ -0,0 +1,28 @@
import { Button } from '@/components/ui/button'
import { cn } from '@/lib/utils'
import { useUserPreferences } from '@/providers/UserPreferencesProvider'
import { Radio } from 'lucide-react'
import { useTranslation } from 'react-i18next'
export function LiveFeedToggle() {
const { t } = useTranslation()
const { enableLiveFeed, updateEnableLiveFeed } = useUserPreferences()
return (
<Button
variant="ghost"
size="titlebar-icon"
title={t(enableLiveFeed ? 'Disable live feed' : 'Enable live feed')}
onClick={() => updateEnableLiveFeed(!enableLiveFeed)}
>
<Radio
className={cn(
'size-4',
enableLiveFeed
? 'text-green-400 focus:text-green-300 animate-pulse'
: 'text-muted-foreground focus:text-foreground'
)}
/>
</Button>
)
}

View file

@ -9,6 +9,8 @@ import { TFeedSubRequest, TNoteListMode } from '@/types'
import { useMemo, useRef, useState } from 'react' import { useMemo, useRef, useState } from 'react'
import KindFilter from '../KindFilter' import KindFilter from '../KindFilter'
import { RefreshButton } from '../RefreshButton' import { RefreshButton } from '../RefreshButton'
import { LiveFeedToggle } from '../LiveFeedToggle'
import { useUserPreferences } from '@/providers/UserPreferencesProvider'
export default function NormalFeed({ export default function NormalFeed({
subRequests, subRequests,
@ -28,6 +30,7 @@ export default function NormalFeed({
isPubkeyFeed?: boolean isPubkeyFeed?: boolean
}) { }) {
const { showKinds } = useKindFilter() const { showKinds } = useKindFilter()
const { enableLiveFeed } = useUserPreferences()
const [temporaryShowKinds, setTemporaryShowKinds] = useState(showKinds) const [temporaryShowKinds, setTemporaryShowKinds] = useState(showKinds)
const [listMode, setListMode] = useState<TNoteListMode>(() => storage.getNoteListMode()) const [listMode, setListMode] = useState<TNoteListMode>(() => storage.getNoteListMode())
const supportTouch = useMemo(() => isTouchDevice(), []) const supportTouch = useMemo(() => isTouchDevice(), [])
@ -85,7 +88,8 @@ export default function NormalFeed({
}} }}
/> />
)} )}
<TrustScoreFilter onOpenChange={handleTrustFilterOpenChange} /> <LiveFeedToggle />
{!isPubkeyFeed && <TrustScoreFilter onOpenChange={handleTrustFilterOpenChange} />}
{showKindsFilter && ( {showKindsFilter && (
<KindFilter <KindFilter
showKinds={temporaryShowKinds} showKinds={temporaryShowKinds}
@ -105,6 +109,7 @@ export default function NormalFeed({
areAlgoRelays={areAlgoRelays} areAlgoRelays={areAlgoRelays}
showRelayCloseReason={showRelayCloseReason} showRelayCloseReason={showRelayCloseReason}
isPubkeyFeed={isPubkeyFeed} isPubkeyFeed={isPubkeyFeed}
showNewNotesDirectly={enableLiveFeed}
/> />
) : ( ) : (
<NoteList <NoteList
@ -115,6 +120,7 @@ export default function NormalFeed({
areAlgoRelays={areAlgoRelays} areAlgoRelays={areAlgoRelays}
showRelayCloseReason={showRelayCloseReason} showRelayCloseReason={showRelayCloseReason}
isPubkeyFeed={isPubkeyFeed} isPubkeyFeed={isPubkeyFeed}
showNewNotesDirectly={enableLiveFeed}
/> />
)} )}
</> </>

View file

@ -6,12 +6,14 @@ import { generateBech32IdFromETag } from '@/lib/tag'
import { isTouchDevice } from '@/lib/utils' import { isTouchDevice } from '@/lib/utils'
import { useKindFilter } from '@/providers/KindFilterProvider' import { useKindFilter } from '@/providers/KindFilterProvider'
import { useNostr } from '@/providers/NostrProvider' import { useNostr } from '@/providers/NostrProvider'
import { useUserPreferences } from '@/providers/UserPreferencesProvider'
import client from '@/services/client.service' import client from '@/services/client.service'
import storage from '@/services/local-storage.service' import storage from '@/services/local-storage.service'
import relayInfoService from '@/services/relay-info.service' import relayInfoService from '@/services/relay-info.service'
import { TFeedSubRequest, TNoteListMode } from '@/types' import { TFeedSubRequest, TNoteListMode } from '@/types'
import { NostrEvent } from 'nostr-tools' import { NostrEvent } from 'nostr-tools'
import { useEffect, useMemo, useRef, useState } from 'react' import { useEffect, useMemo, useRef, useState } from 'react'
import { LiveFeedToggle } from '../LiveFeedToggle'
import { RefreshButton } from '../RefreshButton' import { RefreshButton } from '../RefreshButton'
export default function ProfileFeed({ export default function ProfileFeed({
@ -24,6 +26,7 @@ export default function ProfileFeed({
search?: string search?: string
}) { }) {
const { pubkey: myPubkey, pinListEvent: myPinListEvent } = useNostr() const { pubkey: myPubkey, pinListEvent: myPinListEvent } = useNostr()
const { enableLiveFeed } = useUserPreferences()
const { showKinds } = useKindFilter() const { showKinds } = useKindFilter()
const [temporaryShowKinds, setTemporaryShowKinds] = useState(showKinds) const [temporaryShowKinds, setTemporaryShowKinds] = useState(showKinds)
const [listMode, setListMode] = useState<TNoteListMode>(() => { const [listMode, setListMode] = useState<TNoteListMode>(() => {
@ -164,6 +167,7 @@ export default function ProfileFeed({
options={ options={
<> <>
{!supportTouch && <RefreshButton onClick={() => noteListRef.current?.refresh()} />} {!supportTouch && <RefreshButton onClick={() => noteListRef.current?.refresh()} />}
<LiveFeedToggle />
<KindFilter showKinds={temporaryShowKinds} onShowKindsChange={handleShowKindsChange} /> <KindFilter showKinds={temporaryShowKinds} onShowKindsChange={handleShowKindsChange} />
</> </>
} }
@ -175,7 +179,7 @@ export default function ProfileFeed({
hideReplies={listMode === 'posts'} hideReplies={listMode === 'posts'}
filterMutedNotes={false} filterMutedNotes={false}
pinnedEventIds={listMode === 'you' || !!search ? [] : pinnedEventIds} pinnedEventIds={listMode === 'you' || !!search ? [] : pinnedEventIds}
showNewNotesDirectly={myPubkey === pubkey} showNewNotesDirectly={myPubkey === pubkey || enableLiveFeed}
/> />
</> </>
) )

View file

@ -91,7 +91,7 @@ export default function TrustScoreFilter({
> >
{minTrustScore < 100 ? <Shield size={16} /> : <ShieldCheck size={16} />} {minTrustScore < 100 ? <Shield size={16} /> : <ShieldCheck size={16} />}
{minTrustScore > 0 && minTrustScore < 100 && ( {minTrustScore > 0 && minTrustScore < 100 && (
<div className="absolute inset-0 w-full h-full flex flex-col items-center justify-center text-[0.55rem] font-mono font-bold"> <div className="absolute inset-0 w-full h-full flex flex-col items-center justify-center text-[0.5rem] font-mono font-bold">
{minTrustScore} {minTrustScore}
</div> </div>
)} )}

View file

@ -54,6 +54,7 @@ const UserAggregationList = forwardRef<
areAlgoRelays?: boolean areAlgoRelays?: boolean
showRelayCloseReason?: boolean showRelayCloseReason?: boolean
isPubkeyFeed?: boolean isPubkeyFeed?: boolean
showNewNotesDirectly?: boolean
} }
>( >(
( (
@ -63,7 +64,8 @@ const UserAggregationList = forwardRef<
filterMutedNotes = true, filterMutedNotes = true,
areAlgoRelays = false, areAlgoRelays = false,
showRelayCloseReason = false, showRelayCloseReason = false,
isPubkeyFeed = false isPubkeyFeed = false,
showNewNotesDirectly = false
}, },
ref ref
) => { ) => {
@ -176,9 +178,13 @@ const UserAggregationList = forwardRef<
} }
}, },
onNew: (event) => { onNew: (event) => {
setNewEvents((oldEvents) => if (showNewNotesDirectly) {
[event, ...oldEvents].sort((a, b) => b.created_at - a.created_at) setEvents((oldEvents) => [event, ...oldEvents])
) } else {
setNewEvents((oldEvents) =>
[event, ...oldEvents].sort((a, b) => b.created_at - a.created_at)
)
}
threadService.addRepliesToThread([event]) threadService.addRepliesToThread([event])
}, },
onClose: (url, reason) => { onClose: (url, reason) => {

View file

@ -42,6 +42,7 @@ export const StorageKey = {
QUICK_REACTION_EMOJI: 'quickReactionEmoji', QUICK_REACTION_EMOJI: 'quickReactionEmoji',
NSFW_DISPLAY_POLICY: 'nsfwDisplayPolicy', NSFW_DISPLAY_POLICY: 'nsfwDisplayPolicy',
MIN_TRUST_SCORE: 'minTrustScore', MIN_TRUST_SCORE: 'minTrustScore',
ENABLE_LIVE_FEED: 'enableLiveFeed',
HIDE_UNTRUSTED_NOTES: 'hideUntrustedNotes', // deprecated HIDE_UNTRUSTED_NOTES: 'hideUntrustedNotes', // deprecated
HIDE_UNTRUSTED_INTERACTIONS: 'hideUntrustedInteractions', // deprecated HIDE_UNTRUSTED_INTERACTIONS: 'hideUntrustedInteractions', // deprecated
HIDE_UNTRUSTED_NOTIFICATIONS: 'hideUntrustedNotifications', // deprecated HIDE_UNTRUSTED_NOTIFICATIONS: 'hideUntrustedNotifications', // deprecated

View file

@ -651,6 +651,8 @@ export default {
'trust-filter.hide-bottom-percent': 'trust-filter.hide-bottom-percent':
'تصفية أدنى {{score}}٪ من المستخدمين حسب تصنيف الثقة', 'تصفية أدنى {{score}}٪ من المستخدمين حسب تصنيف الثقة',
'trust-filter.trust-score-description': 'محسوبة بناءً على سمعة المستخدم والنسبة المئوية للشبكة الاجتماعية', 'trust-filter.trust-score-description': 'محسوبة بناءً على سمعة المستخدم والنسبة المئوية للشبكة الاجتماعية',
'Auto-load profile pictures': 'تحميل صور الملف الشخصي تلقائيًا' 'Auto-load profile pictures': 'تحميل صور الملف الشخصي تلقائيًا',
'Disable live feed': 'تعطيل التغذية المباشرة',
'Enable live feed': 'تفعيل التغذية المباشرة'
} }
} }

View file

@ -673,6 +673,8 @@ export default {
'Untere {{score}}% der Benutzer nach Vertrauensrang filtern', 'Untere {{score}}% der Benutzer nach Vertrauensrang filtern',
'trust-filter.trust-score-description': 'trust-filter.trust-score-description':
'Berechnet basierend auf Benutzerreputation und sozialem Netzwerk-Perzentil', 'Berechnet basierend auf Benutzerreputation und sozialem Netzwerk-Perzentil',
'Auto-load profile pictures': 'Profilbilder automatisch laden' 'Auto-load profile pictures': 'Profilbilder automatisch laden',
'Disable live feed': 'Live-Feed deaktivieren',
'Enable live feed': 'Live-Feed aktivieren'
} }
} }

View file

@ -656,6 +656,8 @@ export default {
'trust-filter.hide-bottom-percent': 'trust-filter.hide-bottom-percent':
'Filter out bottom {{score}}% of users by trust rank', 'Filter out bottom {{score}}% of users by trust rank',
'trust-filter.trust-score-description': 'Calculated based on user reputation and social network percentile', 'trust-filter.trust-score-description': 'Calculated based on user reputation and social network percentile',
'Auto-load profile pictures': 'Auto-load profile pictures' 'Auto-load profile pictures': 'Auto-load profile pictures',
'Disable live feed': 'Disable live feed',
'Enable live feed': 'Enable live feed'
} }
} }

View file

@ -667,6 +667,8 @@ export default {
'Filtrar el {{score}}% inferior de usuarios por clasificación de confianza', 'Filtrar el {{score}}% inferior de usuarios por clasificación de confianza',
'trust-filter.trust-score-description': 'trust-filter.trust-score-description':
'Calculado según la reputación del usuario y el percentil de la red social', 'Calculado según la reputación del usuario y el percentil de la red social',
'Auto-load profile pictures': 'Cargar imágenes de perfil automáticamente' 'Auto-load profile pictures': 'Cargar imágenes de perfil automáticamente',
'Disable live feed': 'Desactivar feed en vivo',
'Enable live feed': 'Activar feed en vivo'
} }
} }

View file

@ -663,6 +663,8 @@ export default {
'فیلتر کردن {{score}}٪ پایین‌ترین کاربران بر اساس رتبه اعتماد', 'فیلتر کردن {{score}}٪ پایین‌ترین کاربران بر اساس رتبه اعتماد',
'trust-filter.trust-score-description': 'trust-filter.trust-score-description':
'بر اساس شهرت کاربر و صدک شبکه اجتماعی محاسبه می‌شود', 'بر اساس شهرت کاربر و صدک شبکه اجتماعی محاسبه می‌شود',
'Auto-load profile pictures': 'بارگذاری خودکار تصاویر پروفایل' 'Auto-load profile pictures': 'بارگذاری خودکار تصاویر پروفایل',
'Disable live feed': 'غیرفعال کردن فید زنده',
'Enable live feed': 'فعال کردن فید زنده'
} }
} }

View file

@ -671,6 +671,8 @@ export default {
'Filtrer les {{score}}% inférieurs des utilisateurs par classement de confiance', 'Filtrer les {{score}}% inférieurs des utilisateurs par classement de confiance',
'trust-filter.trust-score-description': 'trust-filter.trust-score-description':
"Calculé en fonction de la réputation et du réseau social de l'utilisateur", "Calculé en fonction de la réputation et du réseau social de l'utilisateur",
'Auto-load profile pictures': 'Charger les images de profil automatiquement' 'Auto-load profile pictures': 'Charger les images de profil automatiquement',
'Disable live feed': 'Désactiver le flux en direct',
'Enable live feed': 'Activer le flux en direct'
} }
} }

View file

@ -663,6 +663,8 @@ export default {
'विश्वास रैंक द्वारा निचले {{score}}% उपयोगकर्ताओं को फ़िल्टर करें', 'विश्वास रैंक द्वारा निचले {{score}}% उपयोगकर्ताओं को फ़िल्टर करें',
'trust-filter.trust-score-description': 'trust-filter.trust-score-description':
'उपयोगकर्ता की प्रतिष्ठा और सामाजिक नेटवर्क प्रतिशतक के आधार पर गणना की गई', 'उपयोगकर्ता की प्रतिष्ठा और सामाजिक नेटवर्क प्रतिशतक के आधार पर गणना की गई',
'Auto-load profile pictures': 'प्रोफ़ाइल चित्र स्वतः लोड करें' 'Auto-load profile pictures': 'प्रोफ़ाइल चित्र स्वतः लोड करें',
'Disable live feed': 'लाइव फ़ीड अक्षम करें',
'Enable live feed': 'लाइव फ़ीड सक्षम करें'
} }
} }

View file

@ -656,6 +656,8 @@ export default {
'Alsó {{score}}% felhasználók szűrése bizalmi rangsor szerint', 'Alsó {{score}}% felhasználók szűrése bizalmi rangsor szerint',
'trust-filter.trust-score-description': 'trust-filter.trust-score-description':
'A felhasználó hírneve és a közösségi hálózat percentilise alapján számítva', 'A felhasználó hírneve és a közösségi hálózat percentilise alapján számítva',
'Auto-load profile pictures': 'Profilképek automatikus betöltése' 'Auto-load profile pictures': 'Profilképek automatikus betöltése',
'Disable live feed': 'Élő hírfolyam letiltása',
'Enable live feed': 'Élő hírfolyam engedélyezése'
} }
} }

View file

@ -667,6 +667,8 @@ export default {
'Filtra il {{score}}% inferiore degli utenti per classifica di fiducia', 'Filtra il {{score}}% inferiore degli utenti per classifica di fiducia',
'trust-filter.trust-score-description': 'trust-filter.trust-score-description':
"Calcolato in base alla reputazione dell'utente e al percentile del social network", "Calcolato in base alla reputazione dell'utente e al percentile del social network",
'Auto-load profile pictures': 'Caricamento automatico immagini di profilo' 'Auto-load profile pictures': 'Caricamento automatico immagini di profilo',
'Disable live feed': 'Disattiva feed live',
'Enable live feed': 'Attiva feed live'
} }
} }

View file

@ -662,6 +662,8 @@ export default {
'信頼ランク下位 {{score}}% のユーザーをフィルタリング', '信頼ランク下位 {{score}}% のユーザーをフィルタリング',
'trust-filter.trust-score-description': 'trust-filter.trust-score-description':
'ユーザーの評判とソーシャルネットワークに基づいて信頼度パーセンタイルを計算', 'ユーザーの評判とソーシャルネットワークに基づいて信頼度パーセンタイルを計算',
'Auto-load profile pictures': 'プロフィール画像を自動読み込み' 'Auto-load profile pictures': 'プロフィール画像を自動読み込み',
'Disable live feed': 'ライブフィードを無効にする',
'Enable live feed': 'ライブフィードを有効にする'
} }
} }

View file

@ -656,6 +656,8 @@ export default {
'trust-filter.only-show-wot': '신뢰 네트워크만 표시 (팔로우 + 팔로우의 팔로우)', 'trust-filter.only-show-wot': '신뢰 네트워크만 표시 (팔로우 + 팔로우의 팔로우)',
'trust-filter.hide-bottom-percent': '신뢰도 하위 {{score}}% 사용자 필터링', 'trust-filter.hide-bottom-percent': '신뢰도 하위 {{score}}% 사용자 필터링',
'trust-filter.trust-score-description': '사용자의 평판과 소셜 네트워크를 기반으로 신뢰도 백분위수 계산', 'trust-filter.trust-score-description': '사용자의 평판과 소셜 네트워크를 기반으로 신뢰도 백분위수 계산',
'Auto-load profile pictures': '프로필 사진 자동 로드' 'Auto-load profile pictures': '프로필 사진 자동 로드',
'Disable live feed': '라이브 피드 비활성화',
'Enable live feed': '라이브 피드 활성화'
} }
} }

View file

@ -668,6 +668,8 @@ export default {
'Filtruj dolne {{score}}% użytkowników według rankingu zaufania', 'Filtruj dolne {{score}}% użytkowników według rankingu zaufania',
'trust-filter.trust-score-description': 'trust-filter.trust-score-description':
'Obliczany na podstawie reputacji użytkownika i percentyla sieci społecznościowej', 'Obliczany na podstawie reputacji użytkownika i percentyla sieci społecznościowej',
'Auto-load profile pictures': 'Automatyczne ładowanie zdjęć profilowych' 'Auto-load profile pictures': 'Automatyczne ładowanie zdjęć profilowych',
'Disable live feed': 'Wyłącz kanał na żywo',
'Enable live feed': 'Włącz kanał na żywo'
} }
} }

View file

@ -664,6 +664,8 @@ export default {
'Filtrar os {{score}}% inferiores de usuários por classificação de confiança', 'Filtrar os {{score}}% inferiores de usuários por classificação de confiança',
'trust-filter.trust-score-description': 'trust-filter.trust-score-description':
'Calculado com base na reputação do usuário e no percentil da rede social', 'Calculado com base na reputação do usuário e no percentil da rede social',
'Auto-load profile pictures': 'Carregar fotos de perfil automaticamente' 'Auto-load profile pictures': 'Carregar fotos de perfil automaticamente',
'Disable live feed': 'Desativar feed ao vivo',
'Enable live feed': 'Ativar feed ao vivo'
} }
} }

View file

@ -667,6 +667,8 @@ export default {
'Filtrar os {{score}}% inferiores de utilizadores por classificação de confiança', 'Filtrar os {{score}}% inferiores de utilizadores por classificação de confiança',
'trust-filter.trust-score-description': 'trust-filter.trust-score-description':
'Calculado com base na reputação do utilizador e no percentil da rede social', 'Calculado com base na reputação do utilizador e no percentil da rede social',
'Auto-load profile pictures': 'Carregar fotos de perfil automaticamente' 'Auto-load profile pictures': 'Carregar fotos de perfil automaticamente',
'Disable live feed': 'Desativar feed ao vivo',
'Enable live feed': 'Ativar feed ao vivo'
} }
} }

View file

@ -667,6 +667,8 @@ export default {
'Отфильтровать нижние {{score}}% пользователей по рейтингу доверия', 'Отфильтровать нижние {{score}}% пользователей по рейтингу доверия',
'trust-filter.trust-score-description': 'trust-filter.trust-score-description':
'Рассчитывается на основе репутации пользователя и процентиля социальной сети', 'Рассчитывается на основе репутации пользователя и процентиля социальной сети',
'Auto-load profile pictures': 'Автозагрузка аватаров' 'Auto-load profile pictures': 'Автозагрузка аватаров',
'Disable live feed': 'Отключить прямую трансляцию',
'Enable live feed': 'Включить прямую трансляцию'
} }
} }

View file

@ -653,6 +653,8 @@ export default {
'กรอง {{score}}% ล่างสุดของผู้ใช้ตามอันดับความไว้วางใจ', 'กรอง {{score}}% ล่างสุดของผู้ใช้ตามอันดับความไว้วางใจ',
'trust-filter.trust-score-description': 'trust-filter.trust-score-description':
'คำนวณจากชื่อเสียงของผู้ใช้และเปอร์เซ็นไทล์ของเครือข่ายสังคม', 'คำนวณจากชื่อเสียงของผู้ใช้และเปอร์เซ็นไทล์ของเครือข่ายสังคม',
'Auto-load profile pictures': 'โหลดรูปโปรไฟล์อัตโนมัติ' 'Auto-load profile pictures': 'โหลดรูปโปรไฟล์อัตโนมัติ',
'Disable live feed': 'ปิดฟีดสด',
'Enable live feed': 'เปิดฟีดสด'
} }
} }

View file

@ -636,6 +636,8 @@ export default {
'trust-filter.only-show-wot': '僅顯示你的信任網路(關注的人 + 他們關注的人)', 'trust-filter.only-show-wot': '僅顯示你的信任網路(關注的人 + 他們關注的人)',
'trust-filter.hide-bottom-percent': '過濾掉信任度排名後 {{score}}% 的使用者', 'trust-filter.hide-bottom-percent': '過濾掉信任度排名後 {{score}}% 的使用者',
'trust-filter.trust-score-description': '基於使用者的聲譽和社交網路計算信任度百分位', 'trust-filter.trust-score-description': '基於使用者的聲譽和社交網路計算信任度百分位',
'Auto-load profile pictures': '自動載入大頭照' 'Auto-load profile pictures': '自動載入大頭照',
'Disable live feed': '停用即時推送',
'Enable live feed': '啟用即時推送'
} }
} }

View file

@ -641,6 +641,8 @@ export default {
'trust-filter.only-show-wot': '仅显示你的信任网络(关注的人 + 他们关注的人)', 'trust-filter.only-show-wot': '仅显示你的信任网络(关注的人 + 他们关注的人)',
'trust-filter.hide-bottom-percent': '过滤掉信任度排名后 {{score}}% 的用户', 'trust-filter.hide-bottom-percent': '过滤掉信任度排名后 {{score}}% 的用户',
'trust-filter.trust-score-description': '基于用户的声誉和社交网络计算信任度百分位', 'trust-filter.trust-score-description': '基于用户的声誉和社交网络计算信任度百分位',
'Auto-load profile pictures': '自动加载头像' 'Auto-load profile pictures': '自动加载头像',
'Disable live feed': '禁用实时推送',
'Enable live feed': '启用实时推送'
} }
} }

View file

@ -21,6 +21,9 @@ type TUserPreferencesContext = {
quickReactionEmoji: string | TEmoji quickReactionEmoji: string | TEmoji
updateQuickReactionEmoji: (emoji: string | TEmoji) => void updateQuickReactionEmoji: (emoji: string | TEmoji) => void
enableLiveFeed: boolean
updateEnableLiveFeed: (enable: boolean) => void
} }
const UserPreferencesContext = createContext<TUserPreferencesContext | undefined>(undefined) const UserPreferencesContext = createContext<TUserPreferencesContext | undefined>(undefined)
@ -45,6 +48,7 @@ export function UserPreferencesProvider({ children }: { children: React.ReactNod
) )
const [quickReaction, setQuickReaction] = useState(storage.getQuickReaction()) const [quickReaction, setQuickReaction] = useState(storage.getQuickReaction())
const [quickReactionEmoji, setQuickReactionEmoji] = useState(storage.getQuickReactionEmoji()) const [quickReactionEmoji, setQuickReactionEmoji] = useState(storage.getQuickReactionEmoji())
const [enableLiveFeed, setEnableLiveFeed] = useState(storage.getEnableLiveFeed())
useEffect(() => { useEffect(() => {
if (!isSmallScreen && enableSingleColumnLayout) { if (!isSmallScreen && enableSingleColumnLayout) {
@ -79,6 +83,11 @@ export function UserPreferencesProvider({ children }: { children: React.ReactNod
storage.setQuickReactionEmoji(emoji) storage.setQuickReactionEmoji(emoji)
} }
const updateEnableLiveFeed = (enable: boolean) => {
setEnableLiveFeed(enable)
storage.setEnableLiveFeed(enable)
}
return ( return (
<UserPreferencesContext.Provider <UserPreferencesContext.Provider
value={{ value={{
@ -93,7 +102,9 @@ export function UserPreferencesProvider({ children }: { children: React.ReactNod
quickReaction, quickReaction,
updateQuickReaction, updateQuickReaction,
quickReactionEmoji, quickReactionEmoji,
updateQuickReactionEmoji updateQuickReactionEmoji,
enableLiveFeed,
updateEnableLiveFeed
}} }}
> >
{children} {children}

View file

@ -64,6 +64,7 @@ class LocalStorageService {
private quickReactionEmoji: string | TEmoji = '+' private quickReactionEmoji: string | TEmoji = '+'
private nsfwDisplayPolicy: TNsfwDisplayPolicy = NSFW_DISPLAY_POLICY.HIDE_CONTENT private nsfwDisplayPolicy: TNsfwDisplayPolicy = NSFW_DISPLAY_POLICY.HIDE_CONTENT
private minTrustScore: number = 40 private minTrustScore: number = 40
private enableLiveFeed: boolean = false
constructor() { constructor() {
if (!LocalStorageService.instance) { if (!LocalStorageService.instance) {
@ -275,6 +276,8 @@ class LocalStorageService {
} }
} }
this.enableLiveFeed = window.localStorage.getItem(StorageKey.ENABLE_LIVE_FEED) === 'true'
// Clean up deprecated data // Clean up deprecated data
window.localStorage.removeItem(StorageKey.PINNED_PUBKEYS) window.localStorage.removeItem(StorageKey.PINNED_PUBKEYS)
window.localStorage.removeItem(StorageKey.ACCOUNT_PROFILE_EVENT_MAP) window.localStorage.removeItem(StorageKey.ACCOUNT_PROFILE_EVENT_MAP)
@ -612,6 +615,15 @@ class LocalStorageService {
window.localStorage.setItem(StorageKey.MIN_TRUST_SCORE, score.toString()) window.localStorage.setItem(StorageKey.MIN_TRUST_SCORE, score.toString())
} }
} }
getEnableLiveFeed() {
return this.enableLiveFeed
}
setEnableLiveFeed(enable: boolean) {
this.enableLiveFeed = enable
window.localStorage.setItem(StorageKey.ENABLE_LIVE_FEED, enable.toString())
}
} }
const instance = new LocalStorageService() const instance = new LocalStorageService()