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

View File

@@ -21,7 +21,7 @@ import {
import { Href, router, useFocusEffect } from "expo-router";
import { useAtom } from "jotai";
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 { AudioTrackSelector } from "./AudioTrackSelector";
import { Bitrate, BitrateSelector } from "./BitrateSelector";
@@ -30,6 +30,7 @@ import { Text } from "./common/Text";
import { Loader } from "./Loader";
import { MediaSourceSelector } from "./MediaSourceSelector";
import ProgressCircle from "./ProgressCircle";
import { RoundButton } from "./RoundButton";
import { SubtitleTrackSelector } from "./SubtitleTrackSelector";
interface DownloadProps extends ViewProps {
@@ -38,6 +39,7 @@ interface DownloadProps extends ViewProps {
DownloadedIconComponent: () => React.ReactElement;
title?: string;
subtitle?: string;
size?: "default" | "large";
}
export const DownloadItems: React.FC<DownloadProps> = ({
@@ -46,6 +48,7 @@ export const DownloadItems: React.FC<DownloadProps> = ({
DownloadedIconComponent,
title = "Download",
subtitle = "",
size = "default",
...props
}) => {
const [api] = useAtom(apiAtom);
@@ -75,9 +78,6 @@ export const DownloadItems: React.FC<DownloadProps> = ({
[settings]
);
/**
* Bottom sheet
*/
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
const handlePresentModalPress = useCallback(() => {
@@ -92,7 +92,6 @@ export const DownloadItems: React.FC<DownloadProps> = ({
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(
() =>
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))
);
}, [queue, itemsNotDownloaded]);
// endregion computed
// region helper functions
const navigateToDownloads = () => router.push("/downloads");
const onDownloadedPress = () => {
@@ -172,17 +168,12 @@ export const DownloadItems: React.FC<DownloadProps> = ({
itemsNotDownloaded,
usingOptimizedServer,
userCanDownload,
// Need to be reference at the time async lambda is created for initiateDownload
maxBitrate,
selectedMediaSource,
selectedAudioStream,
selectedSubtitleStream,
]);
/**
* Start download
*/
const initiateDownload = useCallback(
async (...items: BaseItemDto[]) => {
if (
@@ -265,9 +256,6 @@ export const DownloadItems: React.FC<DownloadProps> = ({
),
[]
);
// endregion helper functions
// Allow to select & set settings for single download
useFocusEffect(
useCallback(() => {
if (!settings) return;
@@ -275,7 +263,6 @@ export const DownloadItems: React.FC<DownloadProps> = ({
const { bitrate, mediaSource, audioIndex, subtitleIndex } =
getDefaultPlaySettings(items[0], settings);
// 4. Set states
setSelectedMediaSource(mediaSource ?? undefined);
setSelectedAudioStream(audioIndex ?? 0);
setSelectedSubtitleStream(subtitleIndex ?? -1);
@@ -283,40 +270,47 @@ export const DownloadItems: React.FC<DownloadProps> = ({
}, [items, itemsNotDownloaded, settings])
);
return (
<View
className="bg-neutral-800/80 rounded-full h-9 w-9 flex items-center justify-center"
{...props}
>
{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>
const renderButtonContent = () => {
if (processes && itemsProcesses.length > 0) {
return progress === 0 ? (
<Loader />
) : (
<TouchableOpacity onPress={handlePresentModalPress}>
{MissingDownloadIconComponent()}
</TouchableOpacity>
)}
<View className="-rotate-45">
<ProgressCircle
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
ref={bottomSheetModalRef}
enableDynamicSizing
@@ -390,11 +384,13 @@ export const DownloadItems: React.FC<DownloadProps> = ({
);
};
export const DownloadSingleItem: React.FC<{ item: BaseItemDto }> = ({
item,
}) => {
export const DownloadSingleItem: React.FC<{
size?: "default" | "large";
item: BaseItemDto;
}> = ({ item, size = "default" }) => {
return (
<DownloadItems
size={size}
title="Download Episode"
subtitle={item.Name!}
items={[item]}

View File

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

View File

@@ -8,6 +8,7 @@ import * as Haptics from "expo-haptics";
import { useAtom } from "jotai";
import React from "react";
import { TouchableOpacity, View, ViewProps } from "react-native";
import { RoundButton } from "./RoundButton";
interface Props extends ViewProps {
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 (
<View
className=" bg-neutral-800/80 rounded-full h-10 w-10 flex items-center justify-center"
{...props}
>
{item.UserData?.Played ? (
<TouchableOpacity
onPress={async () => {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
await markAsNotPlayed({
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 {...props}>
<RoundButton
icon={
item.UserData?.Played
? "checkmark-circle"
: "checkmark-circle-outline"
}
onPress={() => handlePress(item.UserData?.Played || false)}
size="large"
/>
</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