diff --git a/plugins/Last.fm/manifest.json b/plugins/Last.fm/manifest.json index 6a9f6d3..9361d91 100644 --- a/plugins/Last.fm/manifest.json +++ b/plugins/Last.fm/manifest.json @@ -1,7 +1,8 @@ { - "name": "Last.fm", - "description": "Show off your last.fm status on your Discord profile.", - "vendetta": { - "icon": "ic_stage_music" - } + "name": "Last.fm", + "description": "Show off your last.fm status on your Discord profile.", + "main": "src/index.tsx", + "vendetta": { + "icon": "ic_stage_music" + } } diff --git a/plugins/Last.fm/src/index.ts b/plugins/Last.fm/src/index.ts deleted file mode 100644 index 5d51281..0000000 --- a/plugins/Last.fm/src/index.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { plugin } from "@vendetta"; -import { FluxDispatcher } from "@vendetta/metro/common"; - -import { lazy } from "react"; -import { Activity, LFMSettings } from "../../defs"; -import { flush, initialize } from "./manager"; -import { UserStore } from "./modules"; - -export const pluginState = {} as { - pluginStopped?: boolean, - lastActivity?: Activity, - updateInterval?: NodeJS.Timer, - lastTrackUrl?: string, -}; - -plugin.storage.ignoreSpotify ??= true; -export const currentSettings = { ...plugin.storage } as LFMSettings; - -export default { - settings: lazy(() => import("./ui/pages/Settings")), - onLoad() { - pluginState.pluginStopped = false; - - if (UserStore.getCurrentUser()) { - initialize().catch(console.error); - } else { - const callback = () => { - initialize().catch(console.error); - FluxDispatcher.unsubscribe(callback); - }; - - FluxDispatcher.subscribe("CONNECTION_OPEN", callback); - } - - }, - onUnload() { - pluginState.pluginStopped = true; - flush(); - } -}; diff --git a/plugins/Last.fm/src/index.tsx b/plugins/Last.fm/src/index.tsx new file mode 100644 index 0000000..9ddf6dd --- /dev/null +++ b/plugins/Last.fm/src/index.tsx @@ -0,0 +1,121 @@ +import { plugin } from "@vendetta"; +import { FluxDispatcher, React } from "@vendetta/metro/common"; + +import { lazy, Suspense } from "react"; +import { Activity, LFMSettings } from "../../defs"; +import { flush, initialize } from "./manager"; +import { UserStore } from "./modules"; +import { Forms } from "@vendetta/ui/components"; +import { ScrollView } from "react-native"; + +const { FormText } = Forms; + +// Error boundary component for settings +function SettingsErrorBoundary({ children }: { children: React.ReactNode }) { + const [hasError, setHasError] = React.useState(false); + + React.useEffect(() => { + const errorHandler = (error: any) => { + console.error("Settings component error:", error); + setHasError(true); + }; + + return () => { + setHasError(false); + }; + }, []); + + if (hasError) { + return ( + + + Failed to load Last.fm settings. Please try reloading the plugin. + + + ); + } + + return ( + + Loading Last.fm settings... + + }> + {children} + + ); +} + +// Lazy-loaded Settings component with error handling +const LazySettings = lazy(() => { + try { + return import("./ui/pages/Settings").catch((error) => { + console.error("Failed to load Settings component:", error); + // Return a fallback component + return { + default: () => ( + + + Error: Could not load Last.fm settings component. + {"\n"}Please check console for details. + + + ) + }; + }); + } catch (error) { + console.error("Critical error in lazy loading:", error); + // Return fallback component for critical errors + return Promise.resolve({ + default: () => ( + + + Critical error loading settings. Please restart the app. + + + ) + }); + } +}); + +// Wrapped settings component +function WrappedSettings() { + return ( + + + + ); +} + +export const pluginState = {} as { + pluginStopped?: boolean, + lastActivity?: Activity, + updateInterval?: NodeJS.Timer, + lastTrackUrl?: string, +}; + +plugin.storage.ignoreSpotify ??= true; +export const currentSettings = { ...plugin.storage } as LFMSettings; + +export default { + settings: WrappedSettings, + onLoad() { + pluginState.pluginStopped = false; + + if (UserStore.getCurrentUser()) { + initialize().catch(console.error); + } else { + const callback = () => { + initialize().catch(console.error); + FluxDispatcher.unsubscribe(callback); + }; + + FluxDispatcher.subscribe("CONNECTION_OPEN", callback); + } + + }, + onUnload() { + pluginState.pluginStopped = true; + flush(); + } +}; diff --git a/plugins/Last.fm/src/ui/pages/Debug.tsx b/plugins/Last.fm/src/ui/pages/Debug.tsx index 07b4917..43aa31f 100644 --- a/plugins/Last.fm/src/ui/pages/Debug.tsx +++ b/plugins/Last.fm/src/ui/pages/Debug.tsx @@ -1,15 +1,52 @@ -import { Codeblock } from "@vendetta/ui/components"; -import { ScrollView } from "react-native"; -import { useDebugInfo } from "../../utils/debug"; +import { React } from "@vendetta/metro/common"; +import { Codeblock, Forms } from "@vendetta/ui/components"; +import { ScrollView, Text } from "react-native"; +import { useEffect } from "react"; +import { + useDebugInfo, + logComponentMount, + logComponentError, +} from "../../utils/debug"; -export default function Debug() { +const { FormText } = Forms; + +export default React.memo(function Debug() { + // Log component mount + useEffect(() => { + logComponentMount("Debug"); + }, []); + + try { const debugInfo = useDebugInfo(); - return ( + // Safety check for debugInfo + if (!debugInfo) { + logComponentError("Debug", "useDebugInfo returned null or undefined"); + return ( - - {debugInfo} - + + No debug information available + + ); + } + + return ( + + + {debugInfo} + + ); -} + } catch (error) { + logComponentError("Debug", error); + console.error("Debug component error:", error); + return ( + + + Error loading debug information: {String(error)} + + + ); + } +}); diff --git a/plugins/Last.fm/src/ui/pages/Settings.tsx b/plugins/Last.fm/src/ui/pages/Settings.tsx index a06fd44..bf62a28 100644 --- a/plugins/Last.fm/src/ui/pages/Settings.tsx +++ b/plugins/Last.fm/src/ui/pages/Settings.tsx @@ -11,101 +11,152 @@ import { currentSettings } from "../.."; import { LFMSettings } from "../../../../defs"; import Constants from "../../constants"; import { initialize } from "../../manager"; +import { + logComponentMount, + logComponentError, + logNavigationError, + incrementSettingsLoad, +} from "../../utils/debug"; -const { FormRow, FormInput, FormDivider, FormSwitchRow, FormText, FormIcon } = Forms; +const { FormRow, FormInput, FormDivider, FormSwitchRow, FormText, FormIcon } = + Forms; function UpdateButton() { - async function onPressCallback() { - for (const key in storage) { - if (storage[key] != null) { - currentSettings[key] = storage[key]; - } + async function onPressCallback() { + try { + incrementSettingsLoad(); + for (const key in storage) { + if (storage[key] != null) { + currentSettings[key] = storage[key]; } + } - await initialize(); - showToast("Settings updated!", getAssetIDByName("Check")); + await initialize(); + showToast("Settings updated!", getAssetIDByName("Check")); + } catch (error) { + logComponentError("UpdateButton", error); + showToast("Failed to update settings", getAssetIDByName("Small")); } + } - return - UPDATE - ; + return ( + + UPDATE + + ); } export default React.memo(function Settings() { - const settings = useProxy(storage) as LFMSettings; - const navigation = NavigationNative.useNavigation(); + const settings = useProxy(storage) as LFMSettings; + const navigation = NavigationNative.useNavigation(); - useEffect(() => { - navigation.setOptions({ - title: "Last.fm Configuration", - headerRight: UpdateButton - }); - }, []); + // Log component mount + useEffect(() => { + logComponentMount("Settings"); + }, []); + // Safety check to ensure settings is properly initialized + if (!settings) { + logComponentError("Settings", "Settings proxy is null or undefined"); return ( - - settings.appName = value.trim()} - title="Discord Application Name" - placeholder={Constants.DEFAULT_APP_NAME} - returnKeyType="done" - /> - - settings.username = value.trim()} - title="Last.fm username" - helpText={!settings.username && {"This field is required!"}} - placeholder="wumpus123" - returnKeyType="done" - /> - - settings.timeInterval = value} - title="Update interval (in seconds)" - placeholder={Constants.DEFAULT_TIME_INTERVAL.toString()} - keyboardType="numeric" - returnKeyType="done" - /> - - } - value={settings.showTimestamp} - onValueChange={(value: boolean) => settings.showTimestamp = value} - /> - - } - value={settings.listeningTo} - onValueChange={(value: boolean) => settings.listeningTo = value} - /> - - } - value={settings.ignoreSpotify} - onValueChange={(value: boolean) => settings.ignoreSpotify = value} - /> - - } - trailing={FormRow.Arrow} - onPress={() => { - navigation.push("VendettaCustomPage", { - title: "Debug", - render: lazy(() => import("./Debug")) - }); - }} - /> - + + Loading... + ); + } + + useEffect(() => { + try { + navigation.setOptions({ + title: "Last.fm Configuration", + headerRight: () => , + }); + } catch (error) { + logComponentError("Settings-Navigation", error); + } + }, []); + + return ( + + (settings.appName = value.trim())} + title="Discord Application Name" + placeholder={Constants.DEFAULT_APP_NAME} + returnKeyType="done" + /> + + (settings.username = value.trim())} + title="Last.fm username" + helpText={ + !settings.username && ( + + {"This field is required!"} + + ) + } + placeholder="wumpus123" + returnKeyType="done" + /> + + (settings.timeInterval = value)} + title="Update interval (in seconds)" + placeholder={Constants.DEFAULT_TIME_INTERVAL.toString()} + keyboardType="numeric" + returnKeyType="done" + /> + + } + value={settings.showTimestamp} + onValueChange={(value: boolean) => (settings.showTimestamp = value)} + /> + + } + value={settings.listeningTo} + onValueChange={(value: boolean) => (settings.listeningTo = value)} + /> + + + } + value={settings.ignoreSpotify} + onValueChange={(value: boolean) => (settings.ignoreSpotify = value)} + /> + + } + trailing={FormRow.Arrow} + onPress={() => { + try { + navigation.push("VendettaCustomPage", { + title: "Debug", + render: lazy(() => import("./Debug")), + }); + } catch (error) { + logNavigationError(error); + console.error("Failed to navigate to Debug page:", error); + showToast("Failed to open debug page", getAssetIDByName("Small")); + } + }} + /> + + ); }); diff --git a/plugins/Last.fm/src/utils/debug.ts b/plugins/Last.fm/src/utils/debug.ts index ab6e029..9646054 100644 --- a/plugins/Last.fm/src/utils/debug.ts +++ b/plugins/Last.fm/src/utils/debug.ts @@ -4,18 +4,87 @@ import { Activity, Track } from "../../../defs"; let __forceUpdate: () => void; const debugInfo = {} as { - lastActivity?: Activity, - lastTrack?: Track, - lastAPIResponse?: any, - isSpotifyIgnored?: boolean, + lastActivity?: Activity; + lastTrack?: Track; + lastAPIResponse?: any; + isSpotifyIgnored?: boolean; + componentMountErrors?: string[]; + lastReanimatedError?: string; + componentMountCount?: number; + settingsLoadAttempts?: number; + lastNavigationError?: string; }; -export function setDebugInfo(key: keyof typeof debugInfo, value: typeof debugInfo[typeof key]) { - debugInfo[key] = value; - __forceUpdate?.(); +// Initialize debug counters +debugInfo.componentMountErrors = []; +debugInfo.componentMountCount = 0; +debugInfo.settingsLoadAttempts = 0; + +export function setDebugInfo( + key: keyof typeof debugInfo, + value: (typeof debugInfo)[typeof key], +) { + debugInfo[key] = value; + __forceUpdate?.(); +} + +export function logComponentMount(componentName: string) { + debugInfo.componentMountCount = (debugInfo.componentMountCount || 0) + 1; + console.log( + `[Last.fm Debug] Component mounted: ${componentName} (count: ${debugInfo.componentMountCount})`, + ); +} + +export function logComponentError(componentName: string, error: any) { + const errorMessage = `${componentName}: ${String(error)}`; + debugInfo.componentMountErrors = debugInfo.componentMountErrors || []; + debugInfo.componentMountErrors.push(errorMessage); + + if (String(error).includes("Reanimated")) { + debugInfo.lastReanimatedError = errorMessage; + } + + console.error(`[Last.fm Debug] Component error in ${componentName}:`, error); + __forceUpdate?.(); +} + +export function logNavigationError(error: any) { + debugInfo.lastNavigationError = String(error); + console.error(`[Last.fm Debug] Navigation error:`, error); + __forceUpdate?.(); +} + +export function incrementSettingsLoad() { + debugInfo.settingsLoadAttempts = (debugInfo.settingsLoadAttempts || 0) + 1; + console.log( + `[Last.fm Debug] Settings load attempt: ${debugInfo.settingsLoadAttempts}`, + ); } export function useDebugInfo(): string { + try { [, __forceUpdate] = useReducer((x) => ~x, 0); - return JSON.stringify(debugInfo, null, 4); + + // Add runtime information + const runtimeInfo = { + ...debugInfo, + timestamp: new Date().toISOString(), + reactVersion: require("react").version, + hasReanimatedError: !!debugInfo.lastReanimatedError, + totalErrors: debugInfo.componentMountErrors?.length || 0, + }; + + return JSON.stringify(runtimeInfo, null, 4); + } catch (error) { + console.error("[Last.fm Debug] Error in useDebugInfo:", error); + return JSON.stringify( + { + error: "Failed to generate debug info", + errorMessage: String(error), + timestamp: new Date().toISOString(), + }, + null, + 4, + ); + } }