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)}>
+
{!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,
},
{