Skip to content

Commit

Permalink
fix app intent, refactor shortcut listener
Browse files Browse the repository at this point in the history
  • Loading branch information
lukesthl committed Dec 27, 2023
1 parent 7fabda7 commit 5d2e187
Show file tree
Hide file tree
Showing 11 changed files with 247 additions and 210 deletions.
4 changes: 2 additions & 2 deletions apps/expo/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import type { ConfigContext, ExpoConfig } from "expo/config";

export default ({ config }: ConfigContext): ExpoConfig => ({
...config,
name: "digitalbreak",
name: "Digital Break",
slug: "digitalbreak",
version: "1.0.0",
orientation: "portrait",
icon: "./assets/images/default.png",
scheme: "myapp",
scheme: "digitalbreak",
userInterfaceStyle: "automatic",
splash: {
image: "./assets/images/splash.png",
Expand Down
2 changes: 1 addition & 1 deletion apps/expo/app/(tabs)/overview/weekly.summary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const WeeklySummary = () => {
to: dayjs().weekday(-7).endOf("week").valueOf(),
});
const difference = savedThisWeek - savedLastWeek;
const savedThisWeekInPercentage = difference !== 0 ? (difference / savedLastWeek) * 100 : 0;
const savedThisWeekInPercentage = difference !== 0 && savedLastWeek !== 0 ? (difference / savedLastWeek) * 100 : 0;
return (
<ShadowCard>
<XStack justifyContent="space-between">
Expand Down
2 changes: 1 addition & 1 deletion apps/expo/app/break/[appShortcutName].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const Break = observer(() => {
<View flex={1} marginBottom={"$12"} justifyContent="center" alignItems="center">
<AnimatedLottieView
progress={animationProgress.current}
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-assignment
// eslint-disable-next-line @typescript-eslint/no-var-requires
source={require("../../assets/water-drop-animation.json") as AnimationObject}
style={{
height: 300,
Expand Down
6 changes: 4 additions & 2 deletions apps/expo/data/apps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ interface IAppSettings {

// TODO add more apps / test all apps
export const deepLinks: Record<SupportedApp, string> = {
instagram: "instagram",
instagram: "instagram://",
twitter: "twitter://user?screen_name=USERNAME",
facebook: "fb://profile/USER_ID",
youtube: "youtube://www.youtube.com/channel/CHANNEL_ID",
Expand Down Expand Up @@ -56,6 +56,7 @@ export const deepLinks: Record<SupportedApp, string> = {
toonblast: "https://www.google.com",
tumblr: "https://www.google.com",
zalandofashion: "https://www.google.com",
applenews: "applenews://",
};

export type SupportedApp =
Expand Down Expand Up @@ -94,7 +95,8 @@ export type SupportedApp =
| "tinder"
| "toonblast"
| "tumblr"
| "zalandofashion";
| "zalandofashion"
| "applenews";

export const defaultAppSettings: IAppSettings = {
breakDurationSeconds: 10,
Expand Down
5 changes: 5 additions & 0 deletions apps/expo/data/break.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as ExpoExitApp from "../../../packages/expo-exit-app";
import { AppStatisticsStore } from "./app.statistics";
import type { App } from "./apps";
import { AppsStore, deepLinks } from "./apps";
import { clearOpenedApp, updateOpenedApp } from "./shortcut.listener";

const isRunningInExpoGo = Constants.appOwnership === AppOwnership.Expo;

Expand Down Expand Up @@ -51,6 +52,8 @@ export class BreakStoreSingleton {
throw new Error("App not initialized");
}
await this.appStatisticsStore.trackEvent({ appId: this.app.id, type: "app-reopen" });
await updateOpenedApp(this.app.key, "app-reopen");
// await AsyncStorage.setItem("openedApp", `${this.app.key}_${Date.now()}_app-reopen`);
await Linking.openURL(deepLinks[this.app.key as keyof typeof deepLinks]);
}

Expand All @@ -59,6 +62,8 @@ export class BreakStoreSingleton {
throw new Error("App not initialized");
}
await this.appStatisticsStore.trackEvent({ appId: this.app.id, type: "app-close" });
// await AsyncStorage.setItem("openedApp", `${this.app.key}_${Date.now()}_app-reopen`);
await clearOpenedApp();
ExpoExitApp.exit();
}

Expand Down
95 changes: 42 additions & 53 deletions apps/expo/data/shortcut.listener.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,30 @@
import * as FileSystem from "expo-file-system";

import { appConfig } from "../constants/app.config";

const MAX_TRY_COUNT = 10;
let intervalId: NodeJS.Timeout | null = null;
const storageKey = "openedApp";
const pathSplitted = FileSystem.documentDirectory?.split("/");
const appPath = pathSplitted?.slice(0, pathSplitted.length - 2).join("/");
const dataPath = `${appPath}/Library/Application Support/${appConfig.bundleIdentifier}/RCTAsyncLocalStorage_V1/manifest.json`;

// why FileSystem? because AsyncStorage doesnt work in combination with the App Intent.
// It seems like AsyncStorage caches the value and not directly writes it to the file system.
export const listenForShortcut = async (): Promise<{ app: string }> => {
let tryCount = 0;
return new Promise((resolve, reject) => {
const time = new Date().getTime();
intervalId = setInterval(() => {
try {
const pathSplitted = FileSystem.documentDirectory?.split("/");
const appPath = pathSplitted?.slice(0, pathSplitted.length - 2).join("/");
const dataPath = `${appPath}/Library/Application Support/com.lukesthl.digitalbreak/RCTAsyncLocalStorage_V1/manifest.json`;
FileSystem.readAsStringAsync(dataPath)
.then((string) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const manifest = JSON.parse(string);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const openedApp = manifest[storageKey] as string | undefined;
if (openedApp) {
console.log("clearing interval");
console.log("interval after with hit", JSON.stringify(intervalId));
getOpenedApp()
.then((appPayload) => {
if (appPayload && appPayload.event === "break-start") {
clearInterval(intervalId ?? 0);
console.log(`took ${new Date().getTime() - time}ms`);
console.log(`openedApp: ${openedApp}`);

resolve({ app: openedApp });
const withoutApp = manifest as Record<string, unknown>;
delete withoutApp[storageKey];
void FileSystem.writeAsStringAsync(dataPath, JSON.stringify(withoutApp));
console.log(`openedApp: ${appPayload.app}`);

resolve({ app: appPayload.app });
} else {
throw new Error("no app");
}
Expand Down Expand Up @@ -60,40 +55,34 @@ export const clearShortcutListener = () => {
}
};

// import AsyncStorage from "@react-native-async-storage/async-storage";

// const MAX_TRY_COUNT = 10;
// let intervalId: NodeJS.Timeout | null = null;
// const storageKey = "openedApp";
export const getOpenedApp = async (): Promise<{ app: string; timestamp: string; event: string } | null> => {
const string = await FileSystem.readAsStringAsync(dataPath);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const manifest = JSON.parse(string);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const openedAppPayload = manifest[storageKey] as string | undefined;
if (openedAppPayload) {
const [openedApp, timestamp, event] = openedAppPayload.split("_") as [string, string, string];
return { app: openedApp, timestamp, event };
} else {
return null;
}
};

// export const listenForShortcut = async (): Promise<{ app: string }> => {
// let tryCount = 0;
// return new Promise((resolve, reject) => {
// const time = new Date().getTime();
// intervalId = setInterval(() => {
// void AsyncStorage.getItem(storageKey).then((value) => {
// console.log(`value: ${value}`);
// if (value) {
// clearInterval(intervalId ?? 0);
// void AsyncStorage.removeItem(storageKey);
// console.log(`took ${new Date().getTime() - time}ms`);
// resolve({ app: value });
// } else {
// if (tryCount >= MAX_TRY_COUNT) {
// clearInterval(intervalId ?? 0);
// reject("max try count reached");
// } else {
// console.log("increase try count: " + (tryCount + 1));
// tryCount++;
// }
// }
// });
// }, 500);
// });
// };
export const updateOpenedApp = async (app: string, event: string) => {
const string = await FileSystem.readAsStringAsync(dataPath);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const manifest = JSON.parse(string);
const withoutApp = manifest as Record<string, unknown>;
withoutApp.openedApp = `${app}_${Date.now()}_${event}`;
await FileSystem.writeAsStringAsync(dataPath, JSON.stringify(withoutApp));
};

// export const clearShortcutListener = () => {
// if (intervalId) {
// clearInterval(intervalId);
// }
// };
export const clearOpenedApp = async () => {
const string = await FileSystem.readAsStringAsync(dataPath);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const manifest = JSON.parse(string);
const withoutApp = manifest as Record<string, unknown>;
delete withoutApp.openedApp;
await FileSystem.writeAsStringAsync(dataPath, JSON.stringify(withoutApp));
};
Loading

0 comments on commit 5d2e187

Please sign in to comment.