Skip to content

Commit

Permalink
fix: Dealer search and feedback stuff
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
lukashaertel committed Sep 14, 2024
1 parent cec9e45 commit 3d369db
Show file tree
Hide file tree
Showing 11 changed files with 107 additions and 32 deletions.
33 changes: 33 additions & 0 deletions src/components/generic/atoms/TabLabel.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Label type="bold" style={[focused ? styles.focused : styles.unfocused, wide && styles.wide]} numberOfLines={1} ellipsizeMode="tail">
{children}
</Label>
);
};

const styles = StyleSheet.create({
wide: {
maxWidth: tabLabelMaxWidth,
paddingHorizontal: 5,
},
unfocused: {
maxWidth: tabLabelMaxWidth,
opacity: 0.5,
},
focused: {
maxWidth: tabLabelMaxWidth,
opacity: 1,
},
});
11 changes: 8 additions & 3 deletions src/components/viewer/ViewerImageRecord.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -30,9 +31,13 @@ export const ViewerImageRecord: FC<ViewerImageRecordProps> = ({ 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 (
<View style={StyleSheet.absoluteFill}>
<Header secondaryIcon="share" secondaryPress={() => image && title && shareImage(image.Url, title)}>
<Header secondaryIcon="share" secondaryPress={share}>
{title}
</Header>
{!image ? null : (
Expand All @@ -42,7 +47,7 @@ export const ViewerImageRecord: FC<ViewerImageRecordProps> = ({ id }) => {
contentHeight={image.Height + viewerPadding * 2}
minZoom={minZoom}
maxZoom={maxZoom}
initialZoom={minZoom}
initialZoom={minZoom * 1.2}
bindToBorders={true}
>
<View style={styleContainer}>
Expand Down
14 changes: 8 additions & 6 deletions src/components/viewer/ViewerUrl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -24,13 +25,11 @@ export const ViewerUrl: FC<ViewerUrlProps> = ({ 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);
Expand All @@ -42,9 +41,13 @@ export const ViewerUrl: FC<ViewerUrlProps> = ({ url, title }) => {
const minZoom = minZoomFor(width, height, viewerPadding);
const maxZoom = minZoom * 5;

const share = useAsyncCallbackOnce(async () => {
if (title) await shareImage(url, title);
}, [title]);

return (
<View style={StyleSheet.absoluteFill}>
<Header secondaryIcon="share" secondaryPress={() => title && shareImage(url, title)}>
<Header secondaryIcon="share" secondaryPress={share}>
{title ?? t("unspecified")}
</Header>

Expand All @@ -57,7 +60,7 @@ export const ViewerUrl: FC<ViewerUrlProps> = ({ url, title }) => {
contentHeight={height + viewerPadding * 2}
minZoom={minZoom}
maxZoom={maxZoom}
initialZoom={minZoom}
initialZoom={minZoom * 1.2}
bindToBorders={true}
>
<View style={{ width, height }}>
Expand All @@ -68,7 +71,6 @@ export const ViewerUrl: FC<ViewerUrlProps> = ({ 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);
}}
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/searching/useFuseIntegration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { RootState, useAppSelector } from "../../store";
* @param selector The selector from the app state.
* @param limit Maximum results.
*/
export const useFuseIntegration = <T extends object>(selector: (state: RootState) => Fuse<T>, limit = 30): [string, Dispatch<SetStateAction<string>>, T[] | null] => {
export const useFuseIntegration = <T extends object>(selector: (state: RootState) => Fuse<T>, limit = 100): [string, Dispatch<SetStateAction<string>>, T[] | null] => {
// Search state.
const fuse = useAppSelector(selector);
const [filter, setFilter] = useState("");
Expand Down
24 changes: 14 additions & 10 deletions src/hooks/util/useAsyncCallbackOnce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = <T>(callback: () => Promise<T>, deps: DependencyList): (() => Promise<T>) => {
const active = useRef<Promise<T> | null>();
return useCallback(() => {
if (active.current) return active.current;
export const useAsyncCallbackOnce = <T extends unknown[], R>(callback: (...args: T) => Promise<R>, deps: DependencyList): ((...args: T) => Promise<R>) => {
const active = useRef<Promise<R> | 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],
);
};
19 changes: 11 additions & 8 deletions src/routes/Profile.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -10,20 +10,23 @@ 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");

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.
Expand All @@ -35,7 +38,7 @@ export const Profile = () => {

return (
<ScrollView style={StyleSheet.absoluteFill} stickyHeaderIndices={[0]} stickyHeaderHiddenOnScroll>
<Header secondaryIcon="refresh" secondaryPress={isReloading ? () => undefined : doReload} loading={isReloading}>
<Header secondaryIcon="refresh" secondaryPress={isReloading ? () => undefined : reload} loading={isReloading}>
{t("header")}
</Header>
<Floater contentStyle={appStyles.trailer}>{!claims || !user ? null : <ProfileContent claims={claims} user={user} parentPad={padFloater} />}</Floater>
Expand Down
7 changes: 6 additions & 1 deletion src/routes/dealers/DealerItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";

/**
Expand All @@ -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 (
<ScrollView style={StyleSheet.absoluteFill} stickyHeaderIndices={[0]} stickyHeaderHiddenOnScroll>
<Header secondaryIcon="share" secondaryPress={() => dealer && shareDealer(dealer)}>
<Header secondaryIcon="share" secondaryPress={share}>
{dealer?.DisplayNameOrAttendeeNickname ?? t("viewing_dealer")}
</Header>
<Floater contentStyle={appStyles.trailer}>{!dealer ? null : <DealerContent dealer={dealer} parentPad={padFloater} updated={showUpdated} />}</Floater>
Expand Down
14 changes: 13 additions & 1 deletion src/routes/dealers/DealersRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -88,7 +89,18 @@ export const DealersRouter: FC<DealersRouterProps> = () => {
// If the screens require too much performance we should set detach to true again.
return (
<View style={StyleSheet.absoluteFill}>
<Tab.Navigator initialRouteName="All" initialLayout={layout} sceneContainerStyle={scene}>
<Tab.Navigator
initialRouteName="All"
initialLayout={layout}
sceneContainerStyle={scene}
screenOptions={{
tabBarLabel: ({ focused, children }) => (
<TabLabel wide={false} focused={focused}>
{children}
</TabLabel>
),
}}
>
{defaultScreens}
</Tab.Navigator>
</View>
Expand Down
7 changes: 6 additions & 1 deletion src/routes/events/EventItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";

/**
Expand All @@ -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 (
<ScrollView style={StyleSheet.absoluteFill} stickyHeaderIndices={[0]} stickyHeaderHiddenOnScroll>
<Header secondaryIcon="share" secondaryPress={() => event && shareEvent(event)}>
<Header secondaryIcon="share" secondaryPress={() => share}>
{event?.Title ?? "Viewing event"}
</Header>
<Floater contentStyle={appStyles.trailer}>{!event ? null : <EventContent event={event} parentPad={padFloater} updated={showUpdated} />}</Floater>
Expand Down
6 changes: 6 additions & 0 deletions src/routes/events/EventsRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { EventDayRecord } from "../../store/eurofurence/types";
import { AreasRouterParamsList } from "../AreasRouter";
import { IndexRouterParamsList } from "../IndexRouter";
import { conTimeZone } from "../../configuration";
import { TabLabel } from "../../components/generic/atoms/TabLabel";
import { PersonalSchedule, PersonalScheduleParams } from "./PersonalSchedule";
import { EventsSearch, EventsSearchParams } from "./EventsSearch";
import { EventsRouterContextProvider, useEventsRouterContext } from "./EventsRouterContext";
Expand Down Expand Up @@ -196,6 +197,11 @@ const EventsRouterContent: FC<EventsRouterProps> = ({ route }) => {
screenOptions={{
tabBarScrollEnabled: scroll,
tabBarItemStyle: scroll ? { width: 110 } : undefined,
tabBarLabel: ({ focused, children }) => (
<TabLabel wide={scroll} focused={focused}>
{children}
</TabLabel>
),
lazy: true,
lazyPreloadDistance: 3,
}}
Expand Down
2 changes: 1 addition & 1 deletion src/store/eurofurence/selectors/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const searchOptions: IFuseOptions<any> = {
*/
const dealerSearchProperties: FuseOptionKey<DealerDetails>[] = [
{
name: "FullName",
name: "DisplayNameOrAttendeeNickname",
weight: 2,
},
{
Expand Down

0 comments on commit 3d369db

Please sign in to comment.