diff --git a/components/Chromecast.tsx b/components/Chromecast.tsx index 6ba0e089..3e5c8546 100644 --- a/components/Chromecast.tsx +++ b/components/Chromecast.tsx @@ -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 = ({ if (background === "transparent") return ( - <> - { - if (mediaStatus?.currentItemId) CastContext.showExpandedControls(); - else CastContext.showCastDialog(); - }} - className="rounded-full h-10 w-10 flex items-center justify-center b" - {...props} - > - - - - - ); - - if (Platform.OS === "android") - return ( - { 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} > - + ); return ( - { if (mediaStatus?.currentItemId) CastContext.showExpandedControls(); else CastContext.showCastDialog(); }} {...props} > - - - - - + + ); }; diff --git a/components/DownloadItem.tsx b/components/DownloadItem.tsx index fb479260..4618bb4f 100644 --- a/components/DownloadItem.tsx +++ b/components/DownloadItem.tsx @@ -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 = ({ @@ -46,6 +48,7 @@ export const DownloadItems: React.FC = ({ DownloadedIconComponent, title = "Download", subtitle = "", + size = "default", ...props }) => { const [api] = useAtom(apiAtom); @@ -75,9 +78,6 @@ export const DownloadItems: React.FC = ({ [settings] ); - /** - * Bottom sheet - */ const bottomSheetModalRef = useRef(null); const handlePresentModalPress = useCallback(() => { @@ -92,7 +92,6 @@ export const DownloadItems: React.FC = ({ 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 = ({ 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 = ({ 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 = ({ ), [] ); - // 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 = ({ 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 = ({ }, [items, itemsNotDownloaded, settings]) ); - return ( - - {processes && itemsProcesses.length > 0 ? ( - - {progress === 0 ? ( - - ) : ( - - - - )} - - ) : itemsQueued ? ( - - - - ) : allItemsDownloaded ? ( - - {DownloadedIconComponent()} - + const renderButtonContent = () => { + if (processes && itemsProcesses.length > 0) { + return progress === 0 ? ( + ) : ( - - {MissingDownloadIconComponent()} - - )} + + + + ); + } else if (itemsQueued) { + return ; + } else if (allItemsDownloaded) { + return ; + } else { + return ; + } + }; + + const onButtonPress = () => { + if (processes && itemsProcesses.length > 0) { + navigateToDownloads(); + } else if (itemsQueued) { + navigateToDownloads(); + } else if (allItemsDownloaded) { + onDownloadedPress(); + } else { + handlePresentModalPress(); + } + }; + + return ( + + + {renderButtonContent()} + = ({ ); }; -export const DownloadSingleItem: React.FC<{ item: BaseItemDto }> = ({ - item, -}) => { +export const DownloadSingleItem: React.FC<{ + size?: "default" | "large"; + item: BaseItemDto; +}> = ({ item, size = "default" }) => { return ( = React.memo( {item.Type !== "Program" && ( - + )} @@ -286,8 +286,6 @@ export const ItemContent: React.FC<{ item: BaseItemDto }> = React.memo( )} - - diff --git a/components/PlayedStatus.tsx b/components/PlayedStatus.tsx index 0d483858..cb59a963 100644 --- a/components/PlayedStatus.tsx +++ b/components/PlayedStatus.tsx @@ -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 = ({ 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 ( - - {item.UserData?.Played ? ( - { - Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); - await markAsNotPlayed({ - api: api, - itemId: item?.Id, - userId: user?.Id, - }); - invalidateQueries(); - }} - > - - - - - ) : ( - { - Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); - await markAsPlayed({ - api: api, - item: item, - userId: user?.Id, - }); - invalidateQueries(); - }} - > - - - - - )} + + handlePress(item.UserData?.Played || false)} + size="large" + /> ); }; diff --git a/components/RoundButton.tsx b/components/RoundButton.tsx new file mode 100644 index 00000000..90d96e3e --- /dev/null +++ b/components/RoundButton.tsx @@ -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> = ({ + background = true, + icon, + onPress, + children, + size = "default", + ...props +}) => { + const buttonSize = size === "large" ? "h-10 w-10" : "h-9 w-9"; + + if (background === false) + return ( + + {icon ? ( + + ) : null} + {children ? children : null} + + ); + + if (Platform.OS === "android") + return ( + + {icon ? ( + + ) : null} + {children ? children : null} + + ); + + return ( + + + {icon ? ( + + ) : null} + {children ? children : null} + + + ); +}; diff --git a/svenska_kyrkan.sql b/svenska_kyrkan.sql new file mode 100644 index 00000000..e69de29b