From 81715b2c30a94619af373ba44a67a29e6349f06f Mon Sep 17 00:00:00 2001 From: Lukas Haertel Date: Sat, 14 Sep 2024 12:12:09 +0200 Subject: [PATCH] fix: Dealer search and feedback stuff * Tab label added to control how tab navigator stuff is displayed. * Viewers initial zoom changed, fixed share debounce. * Fuse integration increase limit. * Dealer and event share debounced. * Dealer and event router new tab label used. * Profile reload method turned into async callback. * Fixed search issue where an old property name was used. --- src/components/generic/atoms/TabLabel.tsx | 33 +++++++++++++++++++++ src/components/viewer/ViewerImageRecord.tsx | 11 +++++-- src/components/viewer/ViewerUrl.tsx | 14 +++++---- src/hooks/searching/useFuseIntegration.ts | 2 +- src/hooks/util/useAsyncCallbackOnce.ts | 24 ++++++++------- src/routes/Profile.tsx | 19 +++++++----- src/routes/dealers/DealerItem.tsx | 7 ++++- src/routes/dealers/DealersRouter.tsx | 14 ++++++++- src/routes/events/EventItem.tsx | 7 ++++- src/routes/events/EventsRouter.tsx | 9 +++++- src/store/eurofurence/selectors/search.ts | 2 +- 11 files changed, 109 insertions(+), 33 deletions(-) create mode 100644 src/components/generic/atoms/TabLabel.tsx diff --git a/src/components/generic/atoms/TabLabel.tsx b/src/components/generic/atoms/TabLabel.tsx new file mode 100644 index 00000000..f33ec85c --- /dev/null +++ b/src/components/generic/atoms/TabLabel.tsx @@ -0,0 +1,33 @@ +import { StyleSheet } from "react-native"; +import { Label } from "./Label"; + +export const tabLabelMaxWidth = 110; + +export type TabLabelProps = { + focused: boolean; + children: string; + wide: boolean; +}; + +export const TabLabel = ({ focused, children, wide }: TabLabelProps) => { + return ( + + ); +}; + +const styles = StyleSheet.create({ + wide: { + maxWidth: tabLabelMaxWidth, + paddingHorizontal: 5, + }, + unfocused: { + maxWidth: tabLabelMaxWidth, + opacity: 0.5, + }, + focused: { + maxWidth: tabLabelMaxWidth, + opacity: 1, + }, +}); diff --git a/src/components/viewer/ViewerImageRecord.tsx b/src/components/viewer/ViewerImageRecord.tsx index f9f84f11..6f8f05d6 100644 --- a/src/components/viewer/ViewerImageRecord.tsx +++ b/src/components/viewer/ViewerImageRecord.tsx @@ -9,9 +9,10 @@ import { imagesSelectors } from "../../store/eurofurence/selectors/records"; import { RecordId } from "../../store/eurofurence/types"; import { Image } from "../generic/atoms/Image"; import { Header } from "../generic/containers/Header"; +import { useAsyncCallbackOnce } from "../../hooks/util/useAsyncCallbackOnce"; import { minZoomFor, shareImage } from "./Viewer.common"; -const viewerPadding = 40; +const viewerPadding = 20; export type ViewerImageRecordProps = { id: RecordId; @@ -30,9 +31,13 @@ export const ViewerImageRecord: FC = ({ id }) => { const minZoom = minZoomFor(image?.Width ?? 0, image?.Height ?? 0, viewerPadding); const maxZoom = minZoom * 5; + const share = useAsyncCallbackOnce(async () => { + if (image && title) await shareImage(image.Url, title); + }, [image, title]); + return ( -
image && title && shareImage(image.Url, title)}> +
{title}
{!image ? null : ( @@ -42,7 +47,7 @@ export const ViewerImageRecord: FC = ({ id }) => { contentHeight={image.Height + viewerPadding * 2} minZoom={minZoom} maxZoom={maxZoom} - initialZoom={minZoom} + initialZoom={minZoom * 1.2} bindToBorders={true} > diff --git a/src/components/viewer/ViewerUrl.tsx b/src/components/viewer/ViewerUrl.tsx index 52c1a5c1..dfc7ae4f 100644 --- a/src/components/viewer/ViewerUrl.tsx +++ b/src/components/viewer/ViewerUrl.tsx @@ -5,9 +5,10 @@ import { StyleSheet, View, Image as ReactImage } from "react-native"; import { Image } from "../generic/atoms/Image"; import { Header } from "../generic/containers/Header"; +import { useAsyncCallbackOnce } from "../../hooks/util/useAsyncCallbackOnce"; import { minZoomFor, shareImage } from "./Viewer.common"; -const viewerPadding = 40; +const viewerPadding = 20; export type ViewerUrlProps = { url: string; @@ -24,13 +25,11 @@ export const ViewerUrl: FC = ({ url, title }) => { ReactImage.getSize( url, (width, height) => { - console.log("GOT SIZE", width, height); // Gotten successfully. setWidth(width); setHeight(height); }, () => { - console.log("SIZE FAIL"); // Failed, set to 0 so that expo image starts loading. setWidth(0); setHeight(0); @@ -42,9 +41,13 @@ export const ViewerUrl: FC = ({ url, title }) => { const minZoom = minZoomFor(width, height, viewerPadding); const maxZoom = minZoom * 5; + const share = useAsyncCallbackOnce(async () => { + if (title) await shareImage(url, title); + }, [title]); + return ( -
title && shareImage(url, title)}> +
{title ?? t("unspecified")}
@@ -57,7 +60,7 @@ export const ViewerUrl: FC = ({ url, title }) => { contentHeight={height + viewerPadding * 2} minZoom={minZoom} maxZoom={maxZoom} - initialZoom={minZoom} + initialZoom={minZoom * 1.2} bindToBorders={true} > @@ -68,7 +71,6 @@ export const ViewerUrl: FC = ({ url, title }) => { source={url} priority="high" onLoad={(event) => { - console.log("EXPO GOT SIZE", event.source.width, event.source.height); setWidth(event.source.width); setHeight(event.source.height); }} diff --git a/src/hooks/searching/useFuseIntegration.ts b/src/hooks/searching/useFuseIntegration.ts index 60c39b0c..ea3ed6f3 100644 --- a/src/hooks/searching/useFuseIntegration.ts +++ b/src/hooks/searching/useFuseIntegration.ts @@ -8,7 +8,7 @@ import { RootState, useAppSelector } from "../../store"; * @param selector The selector from the app state. * @param limit Maximum results. */ -export const useFuseIntegration = (selector: (state: RootState) => Fuse, limit = 30): [string, Dispatch>, T[] | null] => { +export const useFuseIntegration = (selector: (state: RootState) => Fuse, limit = 100): [string, Dispatch>, T[] | null] => { // Search state. const fuse = useAppSelector(selector); const [filter, setFilter] = useState(""); diff --git a/src/hooks/util/useAsyncCallbackOnce.ts b/src/hooks/util/useAsyncCallbackOnce.ts index 3109b410..770c0c7d 100644 --- a/src/hooks/util/useAsyncCallbackOnce.ts +++ b/src/hooks/util/useAsyncCallbackOnce.ts @@ -7,15 +7,19 @@ import { DependencyList, useCallback, useRef } from "react"; * @param callback The callback function. * @param deps The dependencies of the callback function. */ -export const useAsyncCallbackOnce = (callback: () => Promise, deps: DependencyList): (() => Promise) => { - const active = useRef | null>(); - return useCallback(() => { - if (active.current) return active.current; +export const useAsyncCallbackOnce = (callback: (...args: T) => Promise, deps: DependencyList): ((...args: T) => Promise) => { + const active = useRef | null>(); + return useCallback( + (...args: T) => { + if (active.current) return active.current; - active.current = callback(); - active.current.finally(() => { - active.current = null; - }); - return active.current; - }, [deps]); + const result = callback(...args); + active.current = result; + result.finally(() => { + active.current = null; + }); + return result; + }, + [deps], + ); }; diff --git a/src/routes/Profile.tsx b/src/routes/Profile.tsx index 753e702a..ed24f8c8 100644 --- a/src/routes/Profile.tsx +++ b/src/routes/Profile.tsx @@ -1,5 +1,5 @@ import { captureException } from "@sentry/react-native"; -import React, { useCallback, useEffect, useState } from "react"; +import React, { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { StyleSheet } from "react-native"; import { ScrollView } from "react-native-gesture-handler"; @@ -10,6 +10,7 @@ import { Floater, padFloater } from "../components/generic/containers/Floater"; import { Header } from "../components/generic/containers/Header"; import { useAuthContext } from "../context/AuthContext"; import { useAppNavigation } from "../hooks/nav/useAppNavigation"; +import { useAsyncCallbackOnce } from "../hooks/util/useAsyncCallbackOnce"; export const Profile = () => { const navigation = useAppNavigation("Profile"); @@ -17,13 +18,15 @@ export const Profile = () => { const { t } = useTranslation("Profile"); const { refresh, loggedIn, claims, user } = useAuthContext(); const [isReloading, setIsReloading] = useState(false); - const doReload = useCallback(() => { + const reload = useAsyncCallbackOnce(async () => { setIsReloading(true); - refresh() - .catch(captureException) - .finally(() => { - setIsReloading(false); - }); + try { + await refresh(); + } catch (error) { + captureException(error); + } finally { + setIsReloading(false); + } }, [refresh]); // Pop if not logged in or unable to retrieve proper user data. @@ -35,7 +38,7 @@ export const Profile = () => { return ( -
undefined : doReload} loading={isReloading}> +
undefined : reload} loading={isReloading}> {t("header")}
{!claims || !user ? null : } diff --git a/src/routes/dealers/DealerItem.tsx b/src/routes/dealers/DealerItem.tsx index d54773cc..e02d2e84 100644 --- a/src/routes/dealers/DealerItem.tsx +++ b/src/routes/dealers/DealerItem.tsx @@ -11,6 +11,7 @@ import { useUpdateSinceNote } from "../../hooks/records/useUpdateSinceNote"; import { useLatchTrue } from "../../hooks/util/useLatchTrue"; import { useAppSelector } from "../../store"; import { dealersSelectors } from "../../store/eurofurence/selectors/records"; +import { useAsyncCallbackOnce } from "../../hooks/util/useAsyncCallbackOnce"; import { shareDealer } from "./Dealers.common"; /** @@ -32,9 +33,13 @@ export const DealerItem = () => { const updated = useUpdateSinceNote(dealer); const showUpdated = useLatchTrue(updated); + const share = useAsyncCallbackOnce(async () => { + if (dealer) await shareDealer(dealer); + }, [dealer]); + return ( -
dealer && shareDealer(dealer)}> +
{dealer?.DisplayNameOrAttendeeNickname ?? t("viewing_dealer")}
{!dealer ? null : } diff --git a/src/routes/dealers/DealersRouter.tsx b/src/routes/dealers/DealersRouter.tsx index 35905498..3a8dc74b 100644 --- a/src/routes/dealers/DealersRouter.tsx +++ b/src/routes/dealers/DealersRouter.tsx @@ -11,6 +11,7 @@ import { Icon } from "../../components/generic/atoms/Icon"; import { useTabStyles } from "../../components/generic/nav/useTabStyles"; import { AreasRouterParamsList } from "../AreasRouter"; import { IndexRouterParamsList } from "../IndexRouter"; +import { TabLabel } from "../../components/generic/atoms/TabLabel"; import { DealersAd, DealersAdParams } from "./DealersAd"; import { DealersAll, DealersAllParams } from "./DealersAll"; import { DealersAlpha, DealersAlphaParams } from "./DealersAlpha"; @@ -88,7 +89,18 @@ export const DealersRouter: FC = () => { // If the screens require too much performance we should set detach to true again. return ( - + ( + + {children} + + ), + }} + > {defaultScreens} diff --git a/src/routes/events/EventItem.tsx b/src/routes/events/EventItem.tsx index f1c7166f..2b67395d 100644 --- a/src/routes/events/EventItem.tsx +++ b/src/routes/events/EventItem.tsx @@ -10,6 +10,7 @@ import { useUpdateSinceNote } from "../../hooks/records/useUpdateSinceNote"; import { useLatchTrue } from "../../hooks/util/useLatchTrue"; import { useAppSelector } from "../../store"; import { eventsSelector } from "../../store/eurofurence/selectors/records"; +import { useAsyncCallbackOnce } from "../../hooks/util/useAsyncCallbackOnce"; import { shareEvent } from "./Events.common"; /** @@ -30,9 +31,13 @@ export const EventItem = () => { const updated = useUpdateSinceNote(event); const showUpdated = useLatchTrue(updated); + const share = useAsyncCallbackOnce(async () => { + if (event) await shareEvent(event); + }, [event]); + return ( -
event && shareEvent(event)}> +
share}> {event?.Title ?? "Viewing event"}
{!event ? null : } diff --git a/src/routes/events/EventsRouter.tsx b/src/routes/events/EventsRouter.tsx index 874cdb60..606fc5bf 100644 --- a/src/routes/events/EventsRouter.tsx +++ b/src/routes/events/EventsRouter.tsx @@ -5,7 +5,7 @@ import { NavigatorScreenParams } from "@react-navigation/native"; import { StackScreenProps } from "@react-navigation/stack"; import moment from "moment-timezone"; import { FC, useCallback, useEffect, useMemo } from "react"; -import { BackHandler, Platform, StyleSheet, useWindowDimensions, View } from "react-native"; +import { BackHandler, Platform, StyleSheet, Text, useWindowDimensions, View } from "react-native"; import { EventActionsSheet } from "../../components/events/EventActionsSheet"; import { Icon } from "../../components/generic/atoms/Icon"; @@ -17,6 +17,8 @@ import { EventDayRecord } from "../../store/eurofurence/types"; import { AreasRouterParamsList } from "../AreasRouter"; import { IndexRouterParamsList } from "../IndexRouter"; import { conTimeZone } from "../../configuration"; +import { Label } from "../../components/generic/atoms/Label"; +import { TabLabel } from "../../components/generic/atoms/TabLabel"; import { PersonalSchedule, PersonalScheduleParams } from "./PersonalSchedule"; import { EventsSearch, EventsSearchParams } from "./EventsSearch"; import { EventsRouterContextProvider, useEventsRouterContext } from "./EventsRouterContext"; @@ -196,6 +198,11 @@ const EventsRouterContent: FC = ({ route }) => { screenOptions={{ tabBarScrollEnabled: scroll, tabBarItemStyle: scroll ? { width: 110 } : undefined, + tabBarLabel: ({ focused, children }) => ( + + {children} + + ), lazy: true, lazyPreloadDistance: 3, }} diff --git a/src/store/eurofurence/selectors/search.ts b/src/store/eurofurence/selectors/search.ts index 73ac23e4..74573480 100644 --- a/src/store/eurofurence/selectors/search.ts +++ b/src/store/eurofurence/selectors/search.ts @@ -25,7 +25,7 @@ const searchOptions: IFuseOptions = { */ const dealerSearchProperties: FuseOptionKey[] = [ { - name: "FullName", + name: "DisplayNameOrAttendeeNickname", weight: 2, }, {