From de04970b25db193301869953d39c50658b1c3b11 Mon Sep 17 00:00:00 2001 From: lukestahl Date: Wed, 3 Jan 2024 22:22:22 +0100 Subject: [PATCH] refactor stores, lint files --- apps/expo/app/_layout.tsx | 5 +- apps/expo/app/break/[appShortcutName].tsx | 2 +- apps/expo/app/setup/index.tsx | 14 +++--- apps/expo/data/break.store.ts | 58 ++--------------------- apps/expo/data/shortcut.listener.ts | 48 ++----------------- apps/expo/data/shortcut.payload.ts | 41 ++++++++++++++++ packages/shortcuts-generator/src/index.ts | 6 +++ 7 files changed, 66 insertions(+), 108 deletions(-) create mode 100644 apps/expo/data/shortcut.payload.ts diff --git a/apps/expo/app/_layout.tsx b/apps/expo/app/_layout.tsx index 8ed5746..7b07980 100644 --- a/apps/expo/app/_layout.tsx +++ b/apps/expo/app/_layout.tsx @@ -8,7 +8,8 @@ import { TamaguiProvider, useTheme as useThemeTamagui } from "tamagui"; import "../data/logger"; import { ThemeProvider, useTheme } from "../components/theme-provider"; -import { clearOpenedApp, clearShortcutListener, listenForShortcut } from "../data/shortcut.listener"; +import { clearShortcutListener, listenForShortcut } from "../data/shortcut.listener"; +import { ShortCutPayload } from "../data/shortcut.payload"; import config from "../tamagui.config"; export { ErrorBoundary } from "expo-router"; @@ -58,7 +59,7 @@ function RootLayoutNav() { console.log("App has come to the foreground!"); } else if (appState.current.match(/active/) && nextAppState === "background") { clearShortcutListener(); - void clearOpenedApp(); + void ShortCutPayload.clear(); console.log("App has come to the background!"); } appState.current = nextAppState; diff --git a/apps/expo/app/break/[appShortcutName].tsx b/apps/expo/app/break/[appShortcutName].tsx index f9528a9..e545b46 100644 --- a/apps/expo/app/break/[appShortcutName].tsx +++ b/apps/expo/app/break/[appShortcutName].tsx @@ -30,7 +30,7 @@ const Break = observer(() => { setLoaded(true); }); void OverviewStore.init(); - }, [loaded, searchParams.appShortcutName]); + }, [loaded, searchParams.appShortcutName, searchParams.timestamp]); const animationProgress = useRef(new Animated.Value(0)); const selectedApp = BreakStore.app; diff --git a/apps/expo/app/setup/index.tsx b/apps/expo/app/setup/index.tsx index 3b2bdb7..6bbf60f 100644 --- a/apps/expo/app/setup/index.tsx +++ b/apps/expo/app/setup/index.tsx @@ -100,9 +100,9 @@ const Setup = observer(() => {

- Select "App"-Trigger + Select "App"-Trigger

- Scroll down and select "App" as the trigger. + Scroll down and select "App" as the trigger.
@@ -125,10 +125,10 @@ const Setup = observer(() => {

Configure the trigger

- Select "Choose" and search for the app you want to use. + Select "Choose" and search for the app you want to use. Info: You need to create a trigger for every app you want to use. - Select "open directly" - Select "Next" when you are done. + Select "open directly" + Select "Next" when you are done. @@ -149,9 +149,9 @@ const Setup = observer(() => {

- Select "digital break" as the action + Select "digital break" as the action

- Search for "digital break" and select it. + Search for "digital break" and select it.
diff --git a/apps/expo/data/break.store.ts b/apps/expo/data/break.store.ts index 15b91b7..17b8287 100644 --- a/apps/expo/data/break.store.ts +++ b/apps/expo/data/break.store.ts @@ -1,6 +1,4 @@ -import { Vibration } from "react-native"; import Constants, { AppOwnership } from "expo-constants"; -import * as Haptics from "expo-haptics"; import * as Linking from "expo-linking"; import { router } from "expo-router"; import { makeAutoObservable } from "mobx"; @@ -9,7 +7,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"; +import { ShortCutPayload } from "./shortcut.payload"; const isRunningInExpoGo = Constants.appOwnership === AppOwnership.Expo; @@ -54,61 +52,14 @@ export class BreakStoreSingleton { void this.appStatisticsStore.trackEvent({ appId: app.id, type: "break-start" }); } } - private getHapticImpactEnum = (impact: string): Haptics.ImpactFeedbackStyle | undefined => { - switch (impact) { - case "heavy": - return Haptics.ImpactFeedbackStyle.Heavy; - case "medium": - return Haptics.ImpactFeedbackStyle.Medium; - case "light": - return Haptics.ImpactFeedbackStyle.Light; - } - }; - - public async hapticImpact(): Promise { - const pattern: ("light" | "medium" | "heavy" | "vibrate" | number)[] = []; - const duration = this.app?.settings.breakDurationSeconds ?? 0; - const durationInMs = duration * 1; - for (let i = 0; i < durationInMs; ++i) { - if (i < duration / 2) { - pattern.push("light"); - } else { - pattern.push("medium"); - } - pattern.push(100); - } - console.log("Haptic pattern", pattern); - for (let i = 0; i < pattern.length; ++i) { - const e = pattern[i]; - if (i % 2 === 0) { - // Vibration length, always 400 for iOS - if (typeof e === "number") { - Vibration.vibrate(e); - await new Promise((res) => setTimeout(res, e)); - // Default - } else if (e === "vibrate" || !e) { - Vibration.vibrate(); - // Use native impact type - } else { - console.log("Haptic impact", e); - await Haptics.impactAsync(this.getHapticImpactEnum(e) ?? Haptics.ImpactFeedbackStyle.Medium); - } - // Await for the pause - } else { - if (typeof e !== "number") return; - await new Promise((res) => setTimeout(res, e)); - } - } - } public async openApp(): Promise { if (!this.app) { 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 new Promise((resolve) => setTimeout(resolve, 500)); - // await AsyncStorage.setItem("openedApp", `${this.app.key}_${Date.now()}_app-reopen`); + await ShortCutPayload.update(this.app.key, "app-reopen"); + await new Promise((resolve) => setTimeout(resolve, 500)); // app intent isnt fast enough await Linking.openURL(deepLinks[this.app.key as keyof typeof deepLinks]); router.replace("/"); } @@ -118,8 +69,7 @@ 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(); + await ShortCutPayload.clear(); ExpoExitApp.exit(); } diff --git a/apps/expo/data/shortcut.listener.ts b/apps/expo/data/shortcut.listener.ts index 7cfac01..dc0391f 100644 --- a/apps/expo/data/shortcut.listener.ts +++ b/apps/expo/data/shortcut.listener.ts @@ -1,23 +1,17 @@ -import * as FileSystem from "expo-file-system"; - -import { appConfig } from "../constants/app.config"; +import { ShortCutPayload } from "./shortcut.payload"; 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/appintent.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. +// It seems like AsyncStorage caches the value in memory and not directly writes it to the file system. export const listenForShortcut = async (): Promise<{ app: string; timestamp: number }> => { let tryCount = 0; return new Promise((resolve, reject) => { const time = new Date().getTime(); intervalId = setInterval(() => { try { - getOpenedApp() + ShortCutPayload.getPayload() .then((appPayload) => { if (appPayload && appPayload.event === "break-start") { clearInterval(intervalId ?? 0); @@ -41,11 +35,9 @@ export const listenForShortcut = async (): Promise<{ app: string; timestamp: num } }); } catch (error) { - console.log("maybe something went wrong"); - console.log(JSON.stringify(error)); + console.log(error); } }, 500); - console.log("interval after interval", JSON.stringify(intervalId)); }); }; @@ -54,35 +46,3 @@ export const clearShortcutListener = () => { clearInterval(intervalId); } }; - -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 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; - withoutApp.openedApp = `${app}_${Date.now()}_${event}`; - await FileSystem.writeAsStringAsync(dataPath, JSON.stringify(withoutApp)); -}; - -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; - delete withoutApp.openedApp; - await FileSystem.writeAsStringAsync(dataPath, JSON.stringify(withoutApp)); -}; diff --git a/apps/expo/data/shortcut.payload.ts b/apps/expo/data/shortcut.payload.ts new file mode 100644 index 0000000..7a0cec8 --- /dev/null +++ b/apps/expo/data/shortcut.payload.ts @@ -0,0 +1,41 @@ +import * as FileSystem from "expo-file-system"; + +import { appConfig } from "../constants/app.config"; + +class ShortCutPayloadSingleton { + private readonly storageKey = "openedApp"; + private pathSplitted = FileSystem.documentDirectory?.split("/"); + private appPath = this.pathSplitted?.slice(0, this.pathSplitted.length - 2).join("/"); + private dataPath = `${this.appPath}/Library/Application Support/${appConfig.bundleIdentifier}/RCTAsyncLocalStorage_V1/appintent.json`; + + private loadAppIntentPayload = async (): Promise> => { + const string = await FileSystem.readAsStringAsync(this.dataPath); + const payload = JSON.parse(string) as Record; + return payload; + }; + + public getPayload = async (): Promise<{ app: string; timestamp: string; event: string } | null> => { + const payload = await this.loadAppIntentPayload(); + const openedAppPayload = payload[this.storageKey] as string | undefined; + if (openedAppPayload) { + const [openedApp, timestamp, event] = openedAppPayload.split("_") as [string, string, string]; + return { app: openedApp, timestamp, event }; + } else { + return null; + } + }; + + public update = async (app: string, event: string) => { + const appIntentPayload = await this.loadAppIntentPayload(); + appIntentPayload.openedApp = `${app}_${Date.now()}_${event}`; + await FileSystem.writeAsStringAsync(this.dataPath, JSON.stringify(appIntentPayload)); + }; + + public clear = async () => { + const appIntentPayload = await this.loadAppIntentPayload(); + delete appIntentPayload.openedApp; + await FileSystem.writeAsStringAsync(this.dataPath, JSON.stringify(appIntentPayload)); + }; +} + +export const ShortCutPayload = new ShortCutPayloadSingleton(); diff --git a/packages/shortcuts-generator/src/index.ts b/packages/shortcuts-generator/src/index.ts index 9dc8cb0..971308a 100644 --- a/packages/shortcuts-generator/src/index.ts +++ b/packages/shortcuts-generator/src/index.ts @@ -23,7 +23,13 @@ const generateShortcuts = async () => { ); }; +if (os.platform() !== "darwin") { + console.error("ERROR: shortcut generation stopped because you are not on macOS. Signing shortcuts requires macOS."); + process.exit(0); +} + const time = new Date().getTime(); + void generateShortcuts().then(() => { console.log(`took ${new Date().getTime() - time}ms`); });