Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
Dominic Wrege committed Aug 5, 2024
2 parents 7db5411 + 1de433e commit 7e2f272
Show file tree
Hide file tree
Showing 15 changed files with 252 additions and 55 deletions.
6 changes: 4 additions & 2 deletions App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ import { Tariff } from "./types/tariff";
import { CloseButton } from "./components/header/closeButton";
import { DetailHeader } from "./components/detail/detailHeader";

import { useFetchAppData } from "./hooks/fetchAppData";
import { useFetchAppData } from "./hooks/usefetchAppData";
import { useCustomFonts } from "./hooks/customFont";
import { scale } from "react-native-size-matters";
import { FeedbackView } from "./screens/feedbackView";
import { ToastNotification } from "./components/detail/feedbackView/toastNotification";
import { useAopMetrics } from "./hooks/useAppMetrics";

const queryClient = new QueryClient();
const RootStack = createStackNavigator();
Expand All @@ -46,7 +47,7 @@ function AppWrapper(): JSX.Element {
useEffect(() => {
const subscription = AppState.addEventListener(
"change",
onAppStateChange
onAppStateChange,
);

return () => {
Expand All @@ -55,6 +56,7 @@ function AppWrapper(): JSX.Element {
}, [onAppStateChange]);

useFetchAppData();
useAopMetrics();

const fontLoaded = useCustomFonts();
if (!fontLoaded) {
Expand Down
6 changes: 3 additions & 3 deletions app.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"expo": {
"name": "Ladefuchs",
"slug": "ladefuchs",
"version": "2.2.0",
"version": "2.2.2",
"orientation": "portrait",
"scheme": "com.ladefuchs.app",
"icon": "./assets/fuchs/app_icon.png",
Expand All @@ -14,7 +14,7 @@
},
"assetBundlePatterns": ["**/*"],
"ios": {
"buildNumber": "17",
"buildNumber": "5",
"supportsTablet": false,
"jsEngine": "jsc",
"bundleIdentifier": "app.ladefuchs.Ladefuchs",
Expand All @@ -33,7 +33,7 @@
"resizeMode": "contain",
"backgroundColor": "#F3EEE2"
},
"versionCode": 237,
"versionCode": 238,
"adaptiveIcon": {
"foregroundImage": "./assets/fuchs/android_logo.png",
"backgroundColor": "#F3EEE2"
Expand Down
5 changes: 4 additions & 1 deletion components/detail/detailHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ export function DetailHeader({ tariff, navigation }: Props): JSX.Element {
<Text style={styles.providerName}>{tariff.providerName}</Text>
</View>
<View>
<CloseButton onPress={() => navigation.goBack()} />
<CloseButton
onPress={() => navigation.goBack()}
backgroundColor={colors.ladefuchsDarkBackground}
/>
</View>
</View>
);
Expand Down
7 changes: 1 addition & 6 deletions components/header/appHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,6 @@ export function AppHeader(): JSX.Element {
const navigation = useNavigation();
const reloadBanner = useAppStore((state) => state.reloadBanner);

const handleLongPress = () => {
console.log("handleLongPress reloadBanner");
reloadBanner();
};

return (
<SafeAreaView style={styles.headerContainer}>
<StatusBar
Expand All @@ -46,7 +41,7 @@ export function AppHeader(): JSX.Element {
<View style={{ marginBottom: -scale(10) }}>
<TouchableOpacity
activeOpacity={1}
onLongPress={handleLongPress}
onLongPress={() => reloadBanner()}
>
<AppLogo size={85} />
</TouchableOpacity>
Expand Down
21 changes: 16 additions & 5 deletions components/header/closeButton.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,38 @@
import { TouchableOpacity, ViewStyle, View } from "react-native";
import Svg, { Path } from "react-native-svg";
import { colors } from "../../theme";
import React from "react";
import { scale } from "react-native-size-matters";

interface Props {
onPress: () => void;
backgroundColor?: string;
style?: ViewStyle;
}

export function CloseButton({ onPress, style }: Props): JSX.Element {
export function CloseButton({
onPress,
style,
backgroundColor = colors.ladefuchsDarkGrayBackground,
}: Props): JSX.Element {
const size = scale(19);
return (
<TouchableOpacity
activeOpacity={0.6}
onPress={onPress}
style={{ ...style, padding: 4 }}
style={{
...style,
padding: 4,
}}
>
<View
style={{
backgroundColor,
borderRadius: 100,
backgroundColor: colors.ladefuchsDarkGrayBackground,
padding: 6,
padding: scale(4),
}}
>
<Svg width={14} height={14} viewBox="0 0 320 512">
<Svg width={size} height={size} viewBox="0 0 320 512">
<Path
fill={colors.ladefuchsLightBackground}
d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z"
Expand Down
87 changes: 83 additions & 4 deletions functions/api.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Platform } from "react-native";
import { BannerData, ChargeConditionData } from "../state/state";
import {
getBannerType,
retrieveFromStorage,
saveToStorage,
} from "../state/storage";
import { Banner, LadefuchsBanner } from "../types/banner";
import { Banner, ImpressionRequest, LadefuchsBanner } from "../types/banner";
import {
ChargeMode,
ChargingCondition,
Expand All @@ -14,7 +15,17 @@ import {
import { FeedbackRequest } from "../types/feedback";
import { Operator, OperatorsResponse } from "../types/operator";
import { Tariff, TariffResponse } from "../types/tariff";
import { fetchWithTimeout } from "./util";
import {
appVersionNumber,
fetchWithTimeout,
getMinutes,
isDebug,
} from "./util";
import {
AppMetricCache,
AppMetricResponse,
AppMetricsRequest,
} from "../types/metrics";

const apiPath = "https://api.ladefuchs.app";
export const authHeader = {
Expand Down Expand Up @@ -143,7 +154,6 @@ export async function fetchChargePriceAdBanner(): Promise<Banner | null> {
}
}

// todo error handling, and forward errors and show in the UI
export async function sendFeedback(request: FeedbackRequest): Promise<void> {
const response = await fetchWithTimeout(`${apiPath}/v3/feedback`, {
method: "POST",
Expand All @@ -154,7 +164,7 @@ export async function sendFeedback(request: FeedbackRequest): Promise<void> {
},
body: JSON.stringify(request),
});
if (response.status > 299) {
if (!response.ok) {
throw Error("could not send feedback, got an bad status code");
}
}
Expand All @@ -164,6 +174,75 @@ const storageSet = {
chargeConditionData: "chargeConditionData",
};

export async function postBannerImpression(
banner: Banner | null,
): Promise<void> {
if (isDebug) {
return;
}

if (!banner?.identifier) {
return;
}
const response = await fetchWithTimeout(
`${apiPath}/v3/banners/impression`,
{
method: "POST",
headers: {
...authHeader.headers,
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify({
bannerId: banner.identifier,
platform: Platform.OS,
} satisfies ImpressionRequest),
},
);
if (!response.ok) {
throw new Error("Network response was not ok");
}
}

export async function postAppMetric(): Promise<void> {
if (isDebug) {
return;
}
const cacheKey = "appMetric";
const cache = await retrieveFromStorage<AppMetricCache>(cacheKey);

if (cache?.lastUpdated) {
const updatedDevice = Date.parse(cache?.lastUpdated);
const oneHourInMs = getMinutes(20);
if (Date.now() - updatedDevice < oneHourInMs) {
return;
}
}

const response = await fetchWithTimeout(`${apiPath}/v3/app/metrics`, {
method: "POST",
headers: {
...authHeader.headers,
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify({
deviceId: cache?.deviceId ?? null,
version: appVersionNumber(),
platform: Platform.OS,
} satisfies AppMetricsRequest),
});
if (!response.ok) {
throw new Error("Network response was not ok");
}
const json: AppMetricResponse = await response.json();

await saveToStorage(cacheKey, {
deviceId: json.deviceId,
lastUpdated: new Date().toISOString(),
} satisfies AppMetricCache);
}

export async function getBanners({
writeToCache,
}: {
Expand Down
18 changes: 15 additions & 3 deletions functions/util.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
import Constants from "expo-constants";

export const isDebug = __DEV__;

export function getMinutes(minutes: number): number {
return minutes * 60 * 1000;
}

export function appVersionNumber(): number {
return parseInt(Constants.expoConfig.version.replaceAll(".", ""));
}

export function fill<T>(list1: T[], list2: T[]): [T[], T[]] {
const len1 = list1.length;
const len2 = list2.length;
Expand Down Expand Up @@ -43,15 +55,15 @@ function compose<T>(...functions: ((arg: T) => T)[]): (arg: T) => T {
export const shuffleAndPickOne = compose(
repeatItemsByFrequency,
shuffle,
pickRandom
pickRandom,
);

function repeatNTimes<T>(element: T, times: number): T[] {
return Array.from({ length: times }, () => element);
}

export function repeatItemsByFrequency<T extends { frequency: number }>(
items: T[]
items: T[],
): T[] {
return items.flatMap((item) => repeatNTimes(item, item.frequency));
}
Expand All @@ -72,7 +84,7 @@ export function hyphenText(input: string): string {
export async function fetchWithTimeout(
url: string,
options: RequestInit = null,
timeout = 2800
timeout = 2700,
) {
const controller = new AbortController();
options.signal = controller.signal;
Expand Down
54 changes: 54 additions & 0 deletions hooks/useAppMetrics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { useMutation } from "@tanstack/react-query";
import { useEffect } from "react";
import { AppStateStatus, AppState } from "react-native";
import { postAppMetric, postBannerImpression } from "../functions/api";
import { useShallow } from "zustand/react/shallow";
import { useAppStore } from "../state/state";

export function useAopMetrics() {
const [banner] = useAppStore(useShallow((state) => [state.banner]));

const sendBannerImpression = useMutation({
mutationKey: [banner?.imageUrl ?? ""],
mutationFn: async () => await postBannerImpression(banner),
retry: 1,
});

useEffect(() => {
if (banner?.bannerType !== "ladefuchs") {
return;
}
if (!sendBannerImpression.isIdle) {
return;
}
sendBannerImpression.mutateAsync();
}, [banner]);

const sendAppMetric = useMutation({
mutationFn: async () => await postAppMetric(),
retry: 1,
});

useEffect(() => {
const handleAppStateChange = (nextAppState: AppStateStatus) => {
if (nextAppState !== "active") {
return;
}
if (!sendAppMetric.isIdle) {
return;
}
setTimeout(async () => {
await sendAppMetric.mutateAsync();
sendAppMetric.reset();
}, 1000);
};
const subscription = AppState.addEventListener(
"change",
handleAppStateChange,
);

return () => {
subscription.remove();
};
}, [sendAppMetric]);
}
3 changes: 2 additions & 1 deletion hooks/fetchAppData.ts → hooks/usefetchAppData.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useQuery } from "@tanstack/react-query";
import { useEffect } from "react";
import { getAllChargeConditions, getBanners } from "../functions/api";

import { useShallow } from "zustand/react/shallow";
import { useAppStore } from "../state/state";

Expand All @@ -13,7 +14,7 @@ export function useFetchAppData(): void {
state.operators,
state.setBanners,
state.ladefuchsBanners,
])
]),
);
const allChargeConditionsQuery = useQuery({
queryKey: ["appChargeConditions"],
Expand Down
7 changes: 3 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 7e2f272

Please sign in to comment.