Bpistle/src/pages/secondary/GeneralSettingsPage/index.tsx
codytseng 481603d0e8 feat: add video loop playback setting
Add a toggle in General Settings to enable/disable video loop playback,
following the same pattern as the existing autoplay setting.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 13:55:53 +08:00

212 lines
8.4 KiB
TypeScript

import Emoji from '@/components/Emoji'
import EmojiPickerDialog from '@/components/EmojiPickerDialog'
import { Button } from '@/components/ui/button'
import { Label } from '@/components/ui/label'
import { Select, SelectContent, SelectItem, SelectTrigger } from '@/components/ui/select'
import { Switch } from '@/components/ui/switch'
import {
MEDIA_AUTO_LOAD_POLICY,
NSFW_DISPLAY_POLICY,
PROFILE_PICTURE_AUTO_LOAD_POLICY
} from '@/constants'
import { LocalizedLanguageNames, TLanguage } from '@/i18n'
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
import { isSupportCheckConnectionType } from '@/lib/utils'
import { useContentPolicy } from '@/providers/ContentPolicyProvider'
import { useUserPreferences } from '@/providers/UserPreferencesProvider'
import { TMediaAutoLoadPolicy, TNsfwDisplayPolicy, TProfilePictureAutoLoadPolicy } from '@/types'
import { SelectValue } from '@radix-ui/react-select'
import { RotateCcw } from 'lucide-react'
import { forwardRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import DefaultTrustScoreFilter from './DefaultTrustScoreFilter'
import MutedWords from './MutedWords'
import SettingItem from './SettingItem'
const GeneralSettingsPage = forwardRef(({ index }: { index?: number }, ref) => {
const { t, i18n } = useTranslation()
const [language, setLanguage] = useState<TLanguage>(i18n.language as TLanguage)
const {
autoplay,
setAutoplay,
videoLoop,
setVideoLoop,
nsfwDisplayPolicy,
setNsfwDisplayPolicy,
hideContentMentioningMutedUsers,
setHideContentMentioningMutedUsers,
mediaAutoLoadPolicy,
setMediaAutoLoadPolicy,
profilePictureAutoLoadPolicy,
setProfilePictureAutoLoadPolicy
} = useContentPolicy()
const { quickReaction, updateQuickReaction, quickReactionEmoji, updateQuickReactionEmoji } =
useUserPreferences()
const handleLanguageChange = (value: TLanguage) => {
i18n.changeLanguage(value)
setLanguage(value)
}
return (
<SecondaryPageLayout ref={ref} index={index} title={t('General')}>
<div className="mt-3 space-y-4">
<SettingItem>
<Label htmlFor="languages" className="text-base font-normal">
{t('Languages')}
</Label>
<Select defaultValue="en" value={language} onValueChange={handleLanguageChange}>
<SelectTrigger id="languages" className="w-48">
<SelectValue />
</SelectTrigger>
<SelectContent>
{Object.entries(LocalizedLanguageNames).map(([key, value]) => (
<SelectItem key={key} value={key}>
{value}
</SelectItem>
))}
</SelectContent>
</Select>
</SettingItem>
<SettingItem>
<Label htmlFor="media-auto-load-policy" className="text-base font-normal">
{t('Auto-load media')}
</Label>
<Select
defaultValue="wifi-only"
value={mediaAutoLoadPolicy}
onValueChange={(value: TMediaAutoLoadPolicy) =>
setMediaAutoLoadPolicy(value as TMediaAutoLoadPolicy)
}
>
<SelectTrigger id="media-auto-load-policy" className="w-48">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value={MEDIA_AUTO_LOAD_POLICY.ALWAYS}>{t('Always')}</SelectItem>
{isSupportCheckConnectionType() && (
<SelectItem value={MEDIA_AUTO_LOAD_POLICY.WIFI_ONLY}>{t('Wi-Fi only')}</SelectItem>
)}
<SelectItem value={MEDIA_AUTO_LOAD_POLICY.NEVER}>{t('Never')}</SelectItem>
</SelectContent>
</Select>
</SettingItem>
<SettingItem>
<Label htmlFor="profile-picture-auto-load-policy" className="text-base font-normal">
{t('Auto-load profile pictures')}
</Label>
<Select
defaultValue="always"
value={profilePictureAutoLoadPolicy}
onValueChange={(value: TProfilePictureAutoLoadPolicy) =>
setProfilePictureAutoLoadPolicy(value as TProfilePictureAutoLoadPolicy)
}
>
<SelectTrigger id="profile-picture-auto-load-policy" className="w-48">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value={PROFILE_PICTURE_AUTO_LOAD_POLICY.ALWAYS}>{t('Always')}</SelectItem>
{isSupportCheckConnectionType() && (
<SelectItem value={PROFILE_PICTURE_AUTO_LOAD_POLICY.WIFI_ONLY}>
{t('Wi-Fi only')}
</SelectItem>
)}
<SelectItem value={PROFILE_PICTURE_AUTO_LOAD_POLICY.NEVER}>{t('Never')}</SelectItem>
</SelectContent>
</Select>
</SettingItem>
<SettingItem>
<Label htmlFor="autoplay" className="text-base font-normal">
<div>{t('Autoplay')}</div>
<div className="text-muted-foreground">{t('Enable video autoplay on this device')}</div>
</Label>
<Switch id="autoplay" checked={autoplay} onCheckedChange={setAutoplay} />
</SettingItem>
<SettingItem>
<Label htmlFor="video-loop" className="text-base font-normal">
<div>{t('Video loop')}</div>
<div className="text-muted-foreground">
{t('Automatically replay videos when they end')}
</div>
</Label>
<Switch id="video-loop" checked={videoLoop} onCheckedChange={setVideoLoop} />
</SettingItem>
<SettingItem>
<Label htmlFor="hide-content-mentioning-muted-users" className="text-base font-normal">
{t('Hide content mentioning muted users')}
</Label>
<Switch
id="hide-content-mentioning-muted-users"
checked={hideContentMentioningMutedUsers}
onCheckedChange={setHideContentMentioningMutedUsers}
/>
</SettingItem>
<SettingItem>
<Label htmlFor="nsfw-display-policy" className="text-base font-normal">
{t('NSFW content display')}
</Label>
<Select
value={nsfwDisplayPolicy}
onValueChange={(value: TNsfwDisplayPolicy) => setNsfwDisplayPolicy(value)}
>
<SelectTrigger id="nsfw-display-policy" className="w-48">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value={NSFW_DISPLAY_POLICY.HIDE}>{t('Hide completely')}</SelectItem>
<SelectItem value={NSFW_DISPLAY_POLICY.HIDE_CONTENT}>
{t('Show but hide content')}
</SelectItem>
<SelectItem value={NSFW_DISPLAY_POLICY.SHOW}>{t('Show directly')}</SelectItem>
</SelectContent>
</Select>
</SettingItem>
<DefaultTrustScoreFilter />
<SettingItem>
<Label htmlFor="quick-reaction" className="text-base font-normal">
<div>{t('Quick reaction')}</div>
<div className="text-muted-foreground">
{t('If enabled, you can react with a single click. Click and hold for more options')}
</div>
</Label>
<Switch
id="quick-reaction"
checked={quickReaction}
onCheckedChange={updateQuickReaction}
/>
</SettingItem>
{quickReaction && (
<SettingItem>
<Label htmlFor="quick-reaction-emoji" className="text-base font-normal">
{t('Quick reaction emoji')}
</Label>
<div className="flex items-center gap-2">
<Button
variant="ghost"
size="icon"
onClick={() => updateQuickReactionEmoji('+')}
className="text-muted-foreground hover:text-foreground"
>
<RotateCcw />
</Button>
<EmojiPickerDialog
onEmojiClick={(emoji) => {
if (!emoji) return
updateQuickReactionEmoji(emoji)
}}
>
<Button variant="ghost" size="icon" className="border">
<Emoji emoji={quickReactionEmoji} />
</Button>
</EmojiPickerDialog>
</div>
</SettingItem>
)}
<MutedWords />
</div>
</SecondaryPageLayout>
)
})
GeneralSettingsPage.displayName = 'GeneralSettingsPage'
export default GeneralSettingsPage