fix: refactor buttons

This commit is contained in:
Fredrik Burmester
2024-12-21 12:22:53 +01:00
parent 6908620f4e
commit f3f260625f
6 changed files with 170 additions and 123 deletions

View File

@@ -10,6 +10,7 @@ import GoogleCast, {
useMediaStatus, useMediaStatus,
useRemoteMediaClient, useRemoteMediaClient,
} from "react-native-google-cast"; } from "react-native-google-cast";
import { RoundButton } from "./RoundButton";
interface Props extends ViewProps { interface Props extends ViewProps {
width?: number; width?: number;
@@ -53,51 +54,30 @@ export const Chromecast: React.FC<Props> = ({
if (background === "transparent") if (background === "transparent")
return ( return (
<> <RoundButton
<TouchableOpacity size="large"
onPress={() => { className="mr-2"
if (mediaStatus?.currentItemId) CastContext.showExpandedControls(); background={false}
else CastContext.showCastDialog();
}}
className="rounded-full h-10 w-10 flex items-center justify-center b"
{...props}
>
<Feather name="cast" size={22} color={"white"} />
</TouchableOpacity>
<AndroidCastButton />
</>
);
if (Platform.OS === "android")
return (
<TouchableOpacity
onPress={() => { onPress={() => {
if (mediaStatus?.currentItemId) CastContext.showExpandedControls(); if (mediaStatus?.currentItemId) CastContext.showExpandedControls();
else CastContext.showCastDialog(); else CastContext.showCastDialog();
}} }}
className="rounded-full h-10 w-10 flex items-center justify-center bg-neutral-800/80"
{...props} {...props}
> >
<Feather name="cast" size={22} color={"white"} /> <Feather name="cast" size={22} color={"white"} />
</TouchableOpacity> </RoundButton>
); );
return ( return (
<TouchableOpacity <RoundButton
size="large"
onPress={() => { onPress={() => {
if (mediaStatus?.currentItemId) CastContext.showExpandedControls(); if (mediaStatus?.currentItemId) CastContext.showExpandedControls();
else CastContext.showCastDialog(); else CastContext.showCastDialog();
}} }}
{...props} {...props}
> >
<BlurView <Feather name="cast" size={22} color={"white"} />
intensity={100} </RoundButton>
className="rounded-full overflow-hidden h-10 aspect-square flex items-center justify-center"
{...props}
>
<Feather name="cast" size={22} color={"white"} />
</BlurView>
<AndroidCastButton />
</TouchableOpacity>
); );
}; };

View File

@@ -21,7 +21,7 @@ import {
import { Href, router, useFocusEffect } from "expo-router"; import { Href, router, useFocusEffect } from "expo-router";
import { useAtom } from "jotai"; import { useAtom } from "jotai";
import React, { useCallback, useMemo, useRef, useState } from "react"; import React, { useCallback, useMemo, useRef, useState } from "react";
import { Alert, TouchableOpacity, View, ViewProps } from "react-native"; import { Alert, View, ViewProps } from "react-native";
import { toast } from "sonner-native"; import { toast } from "sonner-native";
import { AudioTrackSelector } from "./AudioTrackSelector"; import { AudioTrackSelector } from "./AudioTrackSelector";
import { Bitrate, BitrateSelector } from "./BitrateSelector"; import { Bitrate, BitrateSelector } from "./BitrateSelector";
@@ -30,6 +30,7 @@ import { Text } from "./common/Text";
import { Loader } from "./Loader"; import { Loader } from "./Loader";
import { MediaSourceSelector } from "./MediaSourceSelector"; import { MediaSourceSelector } from "./MediaSourceSelector";
import ProgressCircle from "./ProgressCircle"; import ProgressCircle from "./ProgressCircle";
import { RoundButton } from "./RoundButton";
import { SubtitleTrackSelector } from "./SubtitleTrackSelector"; import { SubtitleTrackSelector } from "./SubtitleTrackSelector";
interface DownloadProps extends ViewProps { interface DownloadProps extends ViewProps {
@@ -38,6 +39,7 @@ interface DownloadProps extends ViewProps {
DownloadedIconComponent: () => React.ReactElement; DownloadedIconComponent: () => React.ReactElement;
title?: string; title?: string;
subtitle?: string; subtitle?: string;
size?: "default" | "large";
} }
export const DownloadItems: React.FC<DownloadProps> = ({ export const DownloadItems: React.FC<DownloadProps> = ({
@@ -46,6 +48,7 @@ export const DownloadItems: React.FC<DownloadProps> = ({
DownloadedIconComponent, DownloadedIconComponent,
title = "Download", title = "Download",
subtitle = "", subtitle = "",
size = "default",
...props ...props
}) => { }) => {
const [api] = useAtom(apiAtom); const [api] = useAtom(apiAtom);
@@ -75,9 +78,6 @@ export const DownloadItems: React.FC<DownloadProps> = ({
[settings] [settings]
); );
/**
* Bottom sheet
*/
const bottomSheetModalRef = useRef<BottomSheetModal>(null); const bottomSheetModalRef = useRef<BottomSheetModal>(null);
const handlePresentModalPress = useCallback(() => { const handlePresentModalPress = useCallback(() => {
@@ -92,7 +92,6 @@ export const DownloadItems: React.FC<DownloadProps> = ({
const itemIds = useMemo(() => items.map((i) => i.Id), [items]); const itemIds = useMemo(() => items.map((i) => i.Id), [items]);
// Get a list of all items that are not downloaded - based on the items passed in as props
const itemsNotDownloaded = useMemo( const itemsNotDownloaded = useMemo(
() => () =>
items.filter((i) => !downloadedFiles?.some((f) => f.item.Id === i.Id)), items.filter((i) => !downloadedFiles?.some((f) => f.item.Id === i.Id)),
@@ -125,9 +124,6 @@ export const DownloadItems: React.FC<DownloadProps> = ({
itemsNotDownloaded.every((p) => queue.some((q) => p.Id == q.item.Id)) itemsNotDownloaded.every((p) => queue.some((q) => p.Id == q.item.Id))
); );
}, [queue, itemsNotDownloaded]); }, [queue, itemsNotDownloaded]);
// endregion computed
// region helper functions
const navigateToDownloads = () => router.push("/downloads"); const navigateToDownloads = () => router.push("/downloads");
const onDownloadedPress = () => { const onDownloadedPress = () => {
@@ -172,17 +168,12 @@ export const DownloadItems: React.FC<DownloadProps> = ({
itemsNotDownloaded, itemsNotDownloaded,
usingOptimizedServer, usingOptimizedServer,
userCanDownload, userCanDownload,
// Need to be reference at the time async lambda is created for initiateDownload
maxBitrate, maxBitrate,
selectedMediaSource, selectedMediaSource,
selectedAudioStream, selectedAudioStream,
selectedSubtitleStream, selectedSubtitleStream,
]); ]);
/**
* Start download
*/
const initiateDownload = useCallback( const initiateDownload = useCallback(
async (...items: BaseItemDto[]) => { async (...items: BaseItemDto[]) => {
if ( if (
@@ -265,9 +256,6 @@ export const DownloadItems: React.FC<DownloadProps> = ({
), ),
[] []
); );
// endregion helper functions
// Allow to select & set settings for single download
useFocusEffect( useFocusEffect(
useCallback(() => { useCallback(() => {
if (!settings) return; if (!settings) return;
@@ -275,7 +263,6 @@ export const DownloadItems: React.FC<DownloadProps> = ({
const { bitrate, mediaSource, audioIndex, subtitleIndex } = const { bitrate, mediaSource, audioIndex, subtitleIndex } =
getDefaultPlaySettings(items[0], settings); getDefaultPlaySettings(items[0], settings);
// 4. Set states
setSelectedMediaSource(mediaSource ?? undefined); setSelectedMediaSource(mediaSource ?? undefined);
setSelectedAudioStream(audioIndex ?? 0); setSelectedAudioStream(audioIndex ?? 0);
setSelectedSubtitleStream(subtitleIndex ?? -1); setSelectedSubtitleStream(subtitleIndex ?? -1);
@@ -283,40 +270,47 @@ export const DownloadItems: React.FC<DownloadProps> = ({
}, [items, itemsNotDownloaded, settings]) }, [items, itemsNotDownloaded, settings])
); );
return ( const renderButtonContent = () => {
<View if (processes && itemsProcesses.length > 0) {
className="bg-neutral-800/80 rounded-full h-9 w-9 flex items-center justify-center" return progress === 0 ? (
{...props} <Loader />
>
{processes && itemsProcesses.length > 0 ? (
<TouchableOpacity onPress={navigateToDownloads}>
{progress === 0 ? (
<Loader />
) : (
<View className="-rotate-45">
<ProgressCircle
size={24}
fill={progress}
width={4}
tintColor="#9334E9"
backgroundColor="#bdc3c7"
/>
</View>
)}
</TouchableOpacity>
) : itemsQueued ? (
<TouchableOpacity onPress={navigateToDownloads}>
<Ionicons name="hourglass" size={24} color="white" />
</TouchableOpacity>
) : allItemsDownloaded ? (
<TouchableOpacity onPress={onDownloadedPress}>
{DownloadedIconComponent()}
</TouchableOpacity>
) : ( ) : (
<TouchableOpacity onPress={handlePresentModalPress}> <View className="-rotate-45">
{MissingDownloadIconComponent()} <ProgressCircle
</TouchableOpacity> size={24}
)} fill={progress}
width={4}
tintColor="#9334E9"
backgroundColor="#bdc3c7"
/>
</View>
);
} else if (itemsQueued) {
return <Ionicons name="hourglass" size={24} color="white" />;
} else if (allItemsDownloaded) {
return <DownloadedIconComponent />;
} else {
return <MissingDownloadIconComponent />;
}
};
const onButtonPress = () => {
if (processes && itemsProcesses.length > 0) {
navigateToDownloads();
} else if (itemsQueued) {
navigateToDownloads();
} else if (allItemsDownloaded) {
onDownloadedPress();
} else {
handlePresentModalPress();
}
};
return (
<View {...props}>
<RoundButton size={size} onPress={onButtonPress}>
{renderButtonContent()}
</RoundButton>
<BottomSheetModal <BottomSheetModal
ref={bottomSheetModalRef} ref={bottomSheetModalRef}
enableDynamicSizing enableDynamicSizing
@@ -390,11 +384,13 @@ export const DownloadItems: React.FC<DownloadProps> = ({
); );
}; };
export const DownloadSingleItem: React.FC<{ item: BaseItemDto }> = ({ export const DownloadSingleItem: React.FC<{
item, size?: "default" | "large";
}) => { item: BaseItemDto;
}> = ({ item, size = "default" }) => {
return ( return (
<DownloadItems <DownloadItems
size={size}
title="Download Episode" title="Download Episode"
subtitle={item.Name!} subtitle={item.Name!}
items={[item]} items={[item]}

View File

@@ -90,7 +90,7 @@ export const ItemContent: React.FC<{ item: BaseItemDto }> = React.memo(
<Chromecast background="blur" width={22} height={22} /> <Chromecast background="blur" width={22} height={22} />
{item.Type !== "Program" && ( {item.Type !== "Program" && (
<View className="flex flex-row items-center space-x-2"> <View className="flex flex-row items-center space-x-2">
<DownloadSingleItem item={item} /> <DownloadSingleItem item={item} size="large" />
<PlayedStatus item={item} /> <PlayedStatus item={item} />
</View> </View>
)} )}
@@ -286,8 +286,6 @@ export const ItemContent: React.FC<{ item: BaseItemDto }> = React.memo(
<SimilarItems itemId={item.Id} /> <SimilarItems itemId={item.Id} />
</> </>
)} )}
<View className="h-16"></View>
</View> </View>
</ParallaxScrollView> </ParallaxScrollView>
</View> </View>

View File

@@ -8,6 +8,7 @@ import * as Haptics from "expo-haptics";
import { useAtom } from "jotai"; import { useAtom } from "jotai";
import React from "react"; import React from "react";
import { TouchableOpacity, View, ViewProps } from "react-native"; import { TouchableOpacity, View, ViewProps } from "react-native";
import { RoundButton } from "./RoundButton";
interface Props extends ViewProps { interface Props extends ViewProps {
item: BaseItemDto; item: BaseItemDto;
@@ -46,44 +47,35 @@ export const PlayedStatus: React.FC<Props> = ({ item, ...props }) => {
}); });
}; };
const handlePress = async (played: boolean) => {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
if (played) {
await markAsNotPlayed({
api: api,
itemId: item?.Id,
userId: user?.Id,
});
} else {
await markAsPlayed({
api: api,
item: item,
userId: user?.Id,
});
}
invalidateQueries();
};
return ( return (
<View <View {...props}>
className=" bg-neutral-800/80 rounded-full h-10 w-10 flex items-center justify-center" <RoundButton
{...props} icon={
> item.UserData?.Played
{item.UserData?.Played ? ( ? "checkmark-circle"
<TouchableOpacity : "checkmark-circle-outline"
onPress={async () => { }
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); onPress={() => handlePress(item.UserData?.Played || false)}
await markAsNotPlayed({ size="large"
api: api, />
itemId: item?.Id,
userId: user?.Id,
});
invalidateQueries();
}}
>
<View className="rounded h-10 aspect-square flex items-center justify-center">
<Ionicons name="checkmark-circle" size={24} color="white" />
</View>
</TouchableOpacity>
) : (
<TouchableOpacity
onPress={async () => {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
await markAsPlayed({
api: api,
item: item,
userId: user?.Id,
});
invalidateQueries();
}}
>
<View className="rounded h-10 aspect-square flex items-center justify-center">
<Ionicons name="checkmark-circle-outline" size={24} color="white" />
</View>
</TouchableOpacity>
)}
</View> </View>
); );
}; };

View File

@@ -0,0 +1,81 @@
import { Ionicons } from "@expo/vector-icons";
import { BlurView } from "expo-blur";
import { PropsWithChildren } from "react";
import {
Platform,
TouchableOpacity,
TouchableOpacityProps,
} from "react-native";
interface Props extends TouchableOpacityProps {
onPress: () => void;
icon?: keyof typeof Ionicons.glyphMap;
background?: boolean;
size?: "default" | "large";
}
export const RoundButton: React.FC<PropsWithChildren<Props>> = ({
background = true,
icon,
onPress,
children,
size = "default",
...props
}) => {
const buttonSize = size === "large" ? "h-10 w-10" : "h-9 w-9";
if (background === false)
return (
<TouchableOpacity
onPress={onPress}
className={`rounded-full ${buttonSize} flex items-center justify-center`}
{...props}
>
{icon ? (
<Ionicons
name={icon}
size={size === "large" ? 22 : 18}
color={"white"}
/>
) : null}
{children ? children : null}
</TouchableOpacity>
);
if (Platform.OS === "android")
return (
<TouchableOpacity
onPress={onPress}
className={`rounded-full ${buttonSize} flex items-center justify-center bg-neutral-800/80`}
{...props}
>
{icon ? (
<Ionicons
name={icon}
size={size === "large" ? 22 : 18}
color={"white"}
/>
) : null}
{children ? children : null}
</TouchableOpacity>
);
return (
<TouchableOpacity onPress={onPress} {...props}>
<BlurView
intensity={90}
className={`rounded-full overflow-hidden ${buttonSize} flex items-center justify-center`}
{...props}
>
{icon ? (
<Ionicons
name={icon}
size={size === "large" ? 22 : 18}
color={"white"}
/>
) : null}
{children ? children : null}
</BlurView>
</TouchableOpacity>
);
};

0
svenska_kyrkan.sql Normal file
View File