diff --git a/assets/icons/cc_filled_stroke2_corner0_rounded.svg b/assets/icons/cc_filled_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..58823ca80d --- /dev/null +++ b/assets/icons/cc_filled_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/cc_stroke2_corner0_rounded.svg b/assets/icons/cc_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..fcda1570f9 --- /dev/null +++ b/assets/icons/cc_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/icons/CC.tsx b/src/components/icons/CC.tsx new file mode 100644 index 0000000000..da2e7c5dba --- /dev/null +++ b/src/components/icons/CC.tsx @@ -0,0 +1,9 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const CC_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M3 4a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4Zm2 1v14h14V5H5Zm10.957 6.293a1 1 0 1 0 0 1.414 1 1 0 0 1 1.414 1.414 3 3 0 1 1 0-4.242 1 1 0 0 1-1.414 1.414Zm-6.331-.22a1 1 0 1 0 .331 1.634 1 1 0 0 1 1.414 1.414 3 3 0 1 1 0-4.242 1 1 0 0 1-1.414 1.414.994.994 0 0 0-.331-.22Z', +}) + +export const CC_Filled_Corner0_Rounded = createSinglePathSVG({ + path: 'M3 4a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4Zm11.543 7.293a1 1 0 0 1 1.414 0 1 1 0 0 0 1.414-1.414 3 3 0 1 0 0 4.242 1 1 0 0 0-1.414-1.414 1 1 0 0 1-1.414-1.414Zm-6 0a1 1 0 0 1 1.414 0 1 1 0 0 0 1.414-1.414 3 3 0 1 0 0 4.243 1 1 0 0 0-1.414-1.415 1 1 0 0 1-1.414-1.414Z', +}) diff --git a/src/state/persisted/schema.ts b/src/state/persisted/schema.ts index 88fc370a6f..c9a5564376 100644 --- a/src/state/persisted/schema.ts +++ b/src/state/persisted/schema.ts @@ -89,6 +89,7 @@ export const schema = z.object({ disableAutoplay: z.boolean().optional(), kawaii: z.boolean().optional(), hasCheckedForStarterPack: z.boolean().optional(), + subtitlesEnabled: z.boolean().optional(), /** @deprecated */ mutedThreads: z.array(z.string()), }) @@ -131,4 +132,5 @@ export const defaults: Schema = { disableAutoplay: prefersReducedMotion, kawaii: false, hasCheckedForStarterPack: false, + subtitlesEnabled: true, } diff --git a/src/state/preferences/index.tsx b/src/state/preferences/index.tsx index e6b53d5be0..c7eaf27261 100644 --- a/src/state/preferences/index.tsx +++ b/src/state/preferences/index.tsx @@ -9,6 +9,7 @@ import {Provider as InAppBrowserProvider} from './in-app-browser' import {Provider as KawaiiProvider} from './kawaii' import {Provider as LanguagesProvider} from './languages' import {Provider as LargeAltBadgeProvider} from './large-alt-badge' +import {Provider as SubtitlesProvider} from './subtitles' import {Provider as UsedStarterPacksProvider} from './used-starter-packs' export { @@ -24,6 +25,7 @@ export { export * from './hidden-posts' export {useLabelDefinitions} from './label-defs' export {useLanguagePrefs, useLanguagePrefsApi} from './languages' +export {useSetSubtitlesEnabled, useSubtitlesEnabled} from './subtitles' export function Provider({children}: React.PropsWithChildren<{}>) { return ( @@ -36,7 +38,9 @@ export function Provider({children}: React.PropsWithChildren<{}>) { - {children} + + {children} + diff --git a/src/state/preferences/subtitles.tsx b/src/state/preferences/subtitles.tsx new file mode 100644 index 0000000000..5fee218661 --- /dev/null +++ b/src/state/preferences/subtitles.tsx @@ -0,0 +1,42 @@ +import React from 'react' + +import * as persisted from '#/state/persisted' + +type StateContext = boolean +type SetContext = (v: boolean) => void + +const stateContext = React.createContext( + Boolean(persisted.defaults.subtitlesEnabled), +) +const setContext = React.createContext((_: boolean) => {}) + +export function Provider({children}: {children: React.ReactNode}) { + const [state, setState] = React.useState( + Boolean(persisted.get('subtitlesEnabled')), + ) + + const setStateWrapped = React.useCallback( + (subtitlesEnabled: persisted.Schema['subtitlesEnabled']) => { + setState(Boolean(subtitlesEnabled)) + persisted.write('subtitlesEnabled', subtitlesEnabled) + }, + [setState], + ) + + React.useEffect(() => { + return persisted.onUpdate(() => { + setState(Boolean(persisted.get('subtitlesEnabled'))) + }) + }, [setStateWrapped]) + + return ( + + + {children} + + + ) +} + +export const useSubtitlesEnabled = () => React.useContext(stateContext) +export const useSetSubtitlesEnabled = () => React.useContext(setContext) diff --git a/src/view/com/util/post-embeds/VideoWebControls.web.tsx b/src/view/com/util/post-embeds/VideoWebControls.web.tsx index 6b8489f74b..bbd6899f86 100644 --- a/src/view/com/util/post-embeds/VideoWebControls.web.tsx +++ b/src/view/com/util/post-embeds/VideoWebControls.web.tsx @@ -4,7 +4,11 @@ import Animated, {FadeIn, FadeOut} from 'react-native-reanimated' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {useAutoplayDisabled} from '#/state/preferences' +import { + useAutoplayDisabled, + useSetSubtitlesEnabled, + useSubtitlesEnabled, +} from '#/state/preferences' import {atoms as a, useTheme, web} from '#/alf' import {Button} from '#/components/Button' import {useInteractionState} from '#/components/hooks/useInteractionState' @@ -12,6 +16,10 @@ import { ArrowsDiagonalIn_Stroke2_Corner0_Rounded as ArrowsInIcon, ArrowsDiagonalOut_Stroke2_Corner0_Rounded as ArrowsOutIcon, } from '#/components/icons/ArrowsDiagonal' +import { + CC_Filled_Corner0_Rounded as CCActiveIcon, + CC_Stroke2_Corner0_Rounded as CCInactiveIcon, +} from '#/components/icons/CC' import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute' import {Pause_Filled_Corner0_Rounded as PauseIcon} from '#/components/icons/Pause' import {Play_Filled_Corner0_Rounded as PlayIcon} from '#/components/icons/Play' @@ -50,6 +58,8 @@ export function Controls({ } = useVideoUtils(videoRef) const t = useTheme() const {_} = useLingui() + const subtitlesEnabled = useSubtitlesEnabled() + const setSubtitlesEnabled = useSetSubtitlesEnabled() const { state: hovered, onIn: onMouseEnter, @@ -58,9 +68,11 @@ export function Controls({ const isFullscreen = useFullscreen() const {state: hasFocus, onIn: onFocus, onOut: onBlur} = useInteractionState() const [interactingViaKeypress, setInteractingViaKeypress] = useState(false) + const onKeyDown = useCallback(() => { setInteractingViaKeypress(true) }, []) + useEffect(() => { if (interactingViaKeypress) { document.addEventListener('click', () => setInteractingViaKeypress(false)) @@ -106,6 +118,9 @@ export function Controls({ const showControls = (focused && !playing) || (interactingViaKeypress ? hasFocus : hovered) + // TODO: get from embed + const videoHasSubtitles = true + return (
+ )}