Skip to content

Commit

Permalink
feat: player settings
Browse files Browse the repository at this point in the history
  • Loading branch information
hoangvu12 committed Nov 22, 2023
1 parent 026db3b commit 806be62
Show file tree
Hide file tree
Showing 11 changed files with 227 additions and 10 deletions.
6 changes: 3 additions & 3 deletions src/gql/gql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const documents = {
types.TagListMediaFragmentDoc,
'\n fragment InfoScreenMedia on Media {\n relations {\n edges {\n ...SpecialRelationListMedia\n ...RelationListMedia\n }\n }\n recommendations(page: 1, perPage: 15) {\n ...RecommendationListMedia\n }\n characters {\n ...CharacterListMedia\n }\n staff {\n ...StaffListMedia\n }\n tags {\n ...TagListMedia\n }\n description\n trailer {\n id\n site\n }\n synonyms\n ...InfoSectionMedia\n }\n':
types.InfoScreenMediaFragmentDoc,
'\n query AnimeWatchScreenQuery($mediaId: Int!) {\n Media(id: $mediaId) {\n title {\n userPreferred\n }\n idMal\n ...UseAnimeEpisode\n }\n }\n':
'\n query AnimeWatchScreenQuery($mediaId: Int!) {\n Media(id: $mediaId) {\n title {\n userPreferred\n }\n isAdult\n idMal\n ...UseAnimeEpisode\n }\n }\n':
types.AnimeWatchScreenQueryDocument,
'\n fragment DetailsCard on Media {\n id\n title {\n userPreferred\n }\n genres\n averageScore\n favourites\n coverImage {\n large\n }\n }\n':
types.DetailsCardFragmentDoc,
Expand Down Expand Up @@ -237,8 +237,8 @@ export function graphql(
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(
source: '\n query AnimeWatchScreenQuery($mediaId: Int!) {\n Media(id: $mediaId) {\n title {\n userPreferred\n }\n idMal\n ...UseAnimeEpisode\n }\n }\n'
): (typeof documents)['\n query AnimeWatchScreenQuery($mediaId: Int!) {\n Media(id: $mediaId) {\n title {\n userPreferred\n }\n idMal\n ...UseAnimeEpisode\n }\n }\n'];
source: '\n query AnimeWatchScreenQuery($mediaId: Int!) {\n Media(id: $mediaId) {\n title {\n userPreferred\n }\n isAdult\n idMal\n ...UseAnimeEpisode\n }\n }\n'
): (typeof documents)['\n query AnimeWatchScreenQuery($mediaId: Int!) {\n Media(id: $mediaId) {\n title {\n userPreferred\n }\n isAdult\n idMal\n ...UseAnimeEpisode\n }\n }\n'];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
Expand Down
2 changes: 2 additions & 0 deletions src/gql/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5018,6 +5018,7 @@ export type AnimeWatchScreenQueryQuery = {
Media?:
| ({
__typename?: 'Media';
isAdult?: boolean | null;
idMal?: number | null;
title?: {
__typename?: 'MediaTitle';
Expand Down Expand Up @@ -9720,6 +9721,7 @@ export const AnimeWatchScreenQueryDocument = {
],
},
},
{ kind: 'Field', name: { kind: 'Name', value: 'isAdult' } },
{ kind: 'Field', name: { kind: 'Name', value: 'idMal' } },
{
kind: 'FragmentSpread',
Expand Down
9 changes: 8 additions & 1 deletion src/screens/anime/watch/components/media-container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import colors from '@/ui/theme/colors';

import type { useAnimeEpisodeFragment } from '../../details/screens/episode-screen/hooks/use-episodes';
import useEpisodes from '../../details/screens/episode-screen/hooks/use-episodes';
import { mediaIdAtom } from '../store';
import { isAdultAtom, mediaIdAtom } from '../store';
import EpisodesContainer from './episodes-container';
import ErrorMessage from './error-message';

Expand All @@ -16,15 +16,18 @@ interface MediaContainerProps {
currentEpisodeId: string;
anilistId: number;
malId: number | undefined;
isAdult: boolean;
}

const MediaContainer: React.FC<MediaContainerProps> = ({
mediaFragment,
currentEpisodeId,
anilistId,
malId,
isAdult,
}) => {
const setMediaId = useSetAtom(mediaIdAtom);
const setIsAdult = useSetAtom(isAdultAtom);

useEffect(() => {
setMediaId({
Expand All @@ -33,6 +36,10 @@ const MediaContainer: React.FC<MediaContainerProps> = ({
});
}, [setMediaId, anilistId, malId]);

useEffect(() => {
setIsAdult(isAdult);
}, [setIsAdult, isAdult]);

const { data, isLoading } = useEpisodes(mediaFragment);

if (isLoading) {
Expand Down
22 changes: 20 additions & 2 deletions src/screens/anime/watch/components/media-player.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ import RNVideo from 'react-native-video';

import { VideoFormat } from '@/core/video';
import providers from '@/providers';
import {
shouldSyncAdultAtom,
syncPercentageAtom,
} from '@/screens/settings/store';
import { getWatchedEpisode, markEpisodeAsWatched } from '@/storage/episode';
import type { ProviderType } from '@/storage/provider';
import { getProviders } from '@/storage/provider';
Expand All @@ -29,6 +33,7 @@ import {
currentSourceAtom,
currentTimeAtom,
durationAtom,
isAdultAtom,
isBufferingAtom,
mediaIdAtom,
pausedAtom,
Expand Down Expand Up @@ -78,6 +83,8 @@ const MediaPlayer: React.FC<MediaPlayerProps> = ({
const setQualityList = useSetAtom(qualityListAtom);
const setVideoSize = useSetAtom(videoSizeAtom);

const shouldSyncAdult = useAtomValue(shouldSyncAdultAtom);

const currentEpisode = useAtomValue(currentEpisodeAtom);
const mediaId = useAtomValue(mediaIdAtom);

Expand All @@ -93,6 +100,8 @@ const MediaPlayer: React.FC<MediaPlayerProps> = ({
const setIsBuffering = useSetAtom(isBufferingAtom);
const playBackRate = useAtomValue(playBackRateAtom);
const volume = useAtomValue(volumeAtom);
const isAdult = useAtomValue(isAdultAtom);
const syncPercentage = useAtomValue(syncPercentageAtom);

const showBufferingTimeout = useRef<NodeJS.Timeout | null>(null);
const shouldMaintainTime = useRef(false);
Expand All @@ -116,6 +125,7 @@ const MediaPlayer: React.FC<MediaPlayerProps> = ({
const handleSync = useCallback(
(currentTime: number) => {
if (!shouldSync) return;
if (isAdult && !shouldSyncAdult) return;

if (hasSyncProviders.current) return;

Expand All @@ -129,7 +139,7 @@ const MediaPlayer: React.FC<MediaPlayerProps> = ({

if (duration < 10) return;

if (currentTime >= duration * 0.75) {
if (currentTime >= duration * syncPercentage ?? 0.75) {
hasSyncProviders.current = true;

const storageProviders = getProviders();
Expand All @@ -151,7 +161,15 @@ const MediaPlayer: React.FC<MediaPlayerProps> = ({
});
}
},
[currentEpisode?.number, duration, mediaId, shouldSync]
[
currentEpisode?.number,
duration,
isAdult,
mediaId,
shouldSync,
shouldSyncAdult,
syncPercentage,
]
);

const handleProgress = useCallback(
Expand Down
11 changes: 9 additions & 2 deletions src/screens/anime/watch/components/player-container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useAtom, useAtomValue, useSetAtom } from 'jotai/react';
import React, { useEffect } from 'react';
import Modal from 'react-native-modal';

import { shouldAskForSyncingAtom } from '@/screens/settings/store';
import type { VideoServer } from '@/types';
import { ActivityIndicator, Button, colors, Text, View } from '@/ui';

Expand Down Expand Up @@ -39,6 +40,8 @@ const PlayerContainer: React.FC<PlayerContainerProps> = ({
);
});

const shouldAskForSyncingSetting = useAtomValue(shouldAskForSyncingAtom);

const setTimestamps = useSetAtom(timestampsAtom);

useEffect(() => {
Expand All @@ -59,7 +62,7 @@ const PlayerContainer: React.FC<PlayerContainerProps> = ({
setShouldAskForSyncing(shouldAsk);
}, [mediaId.anilistId, shouldNotSyncList, shouldSyncList]);

if (shouldAskForSyncing) {
if (shouldAskForSyncing && shouldAskForSyncingSetting) {
return (
<Modal isVisible={true}>
<View className="absolute inset-0 flex items-center justify-center">
Expand Down Expand Up @@ -109,7 +112,11 @@ const PlayerContainer: React.FC<PlayerContainerProps> = ({

{container?.videos?.length && !isLoading ? (
<MediaPlayer
shouldSync={shouldSyncList.includes(mediaId.anilistId)}
shouldSync={
shouldAskForSyncingSetting
? shouldSyncList.includes(mediaId.anilistId)
: true
}
videos={container.videos}
/>
) : null}
Expand Down
2 changes: 2 additions & 0 deletions src/screens/anime/watch/screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const document = graphql(`
title {
userPreferred
}
isAdult
idMal
...UseAnimeEpisode
}
Expand Down Expand Up @@ -85,6 +86,7 @@ export const AnimeWatchScreen: React.FC<Props> = ({ route }) => {
currentEpisodeId={episodeId}
mediaFragment={media.Media}
anilistId={mediaId}
isAdult={media.Media.isAdult || false}
/>
);
};
1 change: 1 addition & 0 deletions src/screens/anime/watch/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ export const mediaIdAtom = atom<{
anilistId: 0,
malId: 0,
});
export const isAdultAtom = atom(false);

export const timestampsAtom = atom<Timestamp[]>([]);

Expand Down
138 changes: 138 additions & 0 deletions src/screens/settings/components/player-settings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { useAtom, useAtomValue } from 'jotai/react';
import { MinusIcon, PlusIcon } from 'lucide-react-native';
import React, { memo, useState } from 'react';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, {
runOnJS,
useAnimatedStyle,
useSharedValue,
} from 'react-native-reanimated';

import { Button, Text, View } from '@/ui';
import Switch from '@/ui/core/switch';

import {
shouldAskForSyncingAtom,
shouldSyncAdultAtom,
syncPercentageAtom,
} from '../store';

const PlayerSettings = () => {
const [shouldAskForSyncing, setShouldAskForSyncing] = useAtom(
shouldAskForSyncingAtom
);
const [shouldSyncAdult, setShouldSyncAdult] = useAtom(shouldSyncAdultAtom);
const syncPercentage = useAtomValue(syncPercentageAtom);

return (
<View>
<Text className="mb-2 text-xl" weight="medium">
Player
</Text>

<View className="space-y-1">
<View className="flex flex-row items-center justify-between">
<Text weight="normal">Should ask for syncing</Text>

<Switch
value={shouldAskForSyncing}
onValueChange={setShouldAskForSyncing}
/>
</View>
<View className="flex flex-row items-center justify-between">
<Text weight="normal">Should sync adult</Text>

<Switch value={shouldSyncAdult} onValueChange={setShouldSyncAdult} />
</View>
<View>
<Text weight="normal" className="mb-1">
Sync percentage ({Math.floor(syncPercentage * 100)}%)
</Text>

<SyncPercentageSlider />
</View>
</View>
</View>
);
};

const AnimatedView = Animated.createAnimatedComponent(View);

const clamp = (value: number, lowerBound: number, upperBound: number) => {
'worklet';

return Math.min(Math.max(lowerBound, value), upperBound);
};

export const SyncPercentageSlider = memo(() => {
const [syncPercentage, setSyncPercentage] = useAtom(syncPercentageAtom);
const animateValue = useSharedValue(syncPercentage);
const [containerWidth, setContainerWidth] = useState(0);

const gesture = Gesture.Pan()
.onStart((e) => {
const x = e.x;

animateValue.value = clamp(x / containerWidth, 0, 1);
})
.onUpdate((e) => {
const x = e.x;

animateValue.value = clamp(x / containerWidth, 0, 1);
})
.onFinalize((e) => {
const x = e.x;

const value = x / containerWidth;

runOnJS(setSyncPercentage)(clamp(value, 0, 1));
});

const styles = useAnimatedStyle(() => {
return {
width: `${animateValue.value * 100}%`,
};
});

return (
<View className="flex flex-row">
<GestureDetector gesture={gesture}>
<View
onLayout={(event) => {
setContainerWidth(event.nativeEvent.layout.width);
}}
className="relative flex h-10 grow flex-row items-center rounded-md border border-neutral-400"
>
<AnimatedView
className="absolute left-0 h-full overflow-hidden rounded-md bg-primary-500"
style={styles}
/>
<Text className="absolute left-4 z-10 text-gray-300">0%</Text>
<Text className="absolute right-4 z-10 text-gray-300">100%</Text>
</View>
</GestureDetector>

<Button
variant="outline"
onPress={() => {
setSyncPercentage(syncPercentage + 0.01);
}}
className="mx-1 p-1"
>
<PlusIcon size={24} color="white" />
</Button>

<Button
variant="outline"
className="p-1"
onPress={() => {
setSyncPercentage(syncPercentage - 0.01);
}}
>
<MinusIcon size={24} color="white" />
</Button>
</View>
);
});

export default PlayerSettings;
11 changes: 9 additions & 2 deletions src/screens/settings/screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React from 'react';
import { Text, View } from '@/ui';

import AccountSettings from './components/account-settings';
import PlayerSettings from './components/player-settings';

const SettingsScreen = () => {
return (
Expand All @@ -11,8 +12,14 @@ const SettingsScreen = () => {
Settings
</Text>

<View className="mt-4">
<AccountSettings />
<View className="mt-4 space-y-8">
<View>
<AccountSettings />
</View>

<View>
<PlayerSettings />
</View>
</View>
</View>
);
Expand Down
14 changes: 14 additions & 0 deletions src/screens/settings/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { atomWithMMKV } from '@/core/storage';

export const shouldAskForSyncingAtom = atomWithMMKV(
'player_settings__should-ask-for-syncing',
true
);
export const shouldSyncAdultAtom = atomWithMMKV(
'player_settings__should-sync-adult',
false
);
export const syncPercentageAtom = atomWithMMKV(
'player_settings__sync-percentage',
0.75
);
Loading

0 comments on commit 806be62

Please sign in to comment.