fixed reanimated issue??

This commit is contained in:
kmmiio99o 2025-09-13 00:12:49 +02:00
parent 46d0cb300b
commit f4be7b2743
6 changed files with 384 additions and 145 deletions

View file

@ -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"
}
}

View file

@ -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();
}
};

View file

@ -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 (
<ScrollView style={{ padding: 12 }}>
<FormText style={{ color: "#FF0000" }}>
Failed to load Last.fm settings. Please try reloading the plugin.
</FormText>
</ScrollView>
);
}
return (
<Suspense fallback={
<ScrollView style={{ padding: 12 }}>
<FormText>Loading Last.fm settings...</FormText>
</ScrollView>
}>
{children}
</Suspense>
);
}
// 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: () => (
<ScrollView style={{ padding: 12 }}>
<FormText style={{ color: "#FF0000" }}>
Error: Could not load Last.fm settings component.
{"\n"}Please check console for details.
</FormText>
</ScrollView>
)
};
});
} catch (error) {
console.error("Critical error in lazy loading:", error);
// Return fallback component for critical errors
return Promise.resolve({
default: () => (
<ScrollView style={{ padding: 12 }}>
<FormText style={{ color: "#FF0000" }}>
Critical error loading settings. Please restart the app.
</FormText>
</ScrollView>
)
});
}
});
// Wrapped settings component
function WrappedSettings() {
return (
<SettingsErrorBoundary>
<LazySettings />
</SettingsErrorBoundary>
);
}
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();
}
};

View file

@ -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 (
<ScrollView>
<Codeblock selectable style={{ margin: 12 }}>
{debugInfo}
</Codeblock>
<FormText style={{ margin: 12 }}>
No debug information available
</FormText>
</ScrollView>
);
}
return (
<ScrollView>
<Codeblock selectable style={{ margin: 12 }}>
{debugInfo}
</Codeblock>
</ScrollView>
);
}
} catch (error) {
logComponentError("Debug", error);
console.error("Debug component error:", error);
return (
<ScrollView>
<FormText style={{ margin: 12, color: "#FF0000" }}>
Error loading debug information: {String(error)}
</FormText>
</ScrollView>
);
}
});

View file

@ -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 <TouchableOpacity onPress={onPressCallback}>
<FormText style={{ marginRight: 12 }}>UPDATE</FormText>
</TouchableOpacity>;
return (
<TouchableOpacity onPress={onPressCallback}>
<FormText style={{ marginRight: 12 }}>UPDATE</FormText>
</TouchableOpacity>
);
}
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 (
<ScrollView>
<FormInput
value={settings.appName || undefined}
onChangeText={(value: string) => settings.appName = value.trim()}
title="Discord Application Name"
placeholder={Constants.DEFAULT_APP_NAME}
returnKeyType="done"
/>
<FormDivider />
<FormInput required
value={settings.username || undefined}
onChangeText={(value: string) => settings.username = value.trim()}
title="Last.fm username"
helpText={!settings.username && <Text style={{ color: "#FF0000" }}>{"This field is required!"}</Text>}
placeholder="wumpus123"
returnKeyType="done"
/>
<FormDivider />
<FormInput
value={settings.timeInterval}
onChangeText={(value: string) => settings.timeInterval = value}
title="Update interval (in seconds)"
placeholder={Constants.DEFAULT_TIME_INTERVAL.toString()}
keyboardType="numeric"
returnKeyType="done"
/>
<FormDivider />
<FormSwitchRow
label="Show time elapsed"
subLabel="Show the time elapsed since the song started playing"
leading={<FormIcon source={getAssetIDByName("clock")} />}
value={settings.showTimestamp}
onValueChange={(value: boolean) => settings.showTimestamp = value}
/>
<FormDivider />
<FormSwitchRow
label="Set status as listening"
subLabel='Set your status as "Listening to" instead of "Playing"'
leading={<FormIcon source={getAssetIDByName("ic_headset_neutral")} />}
value={settings.listeningTo}
onValueChange={(value: boolean) => settings.listeningTo = value}
/>
<FormDivider />
<FormSwitchRow
label="Hide when Spotify is running"
subLabel="Hide the status when a Spotify activity is detected"
leading={<FormIcon source={getAssetIDByName("img_account_sync_spotify_light_and_dark")} />}
value={settings.ignoreSpotify}
onValueChange={(value: boolean) => settings.ignoreSpotify = value}
/>
<FormDivider />
<FormRow
label="Debug"
subLabel="View debug information"
leading={<FormIcon source={getAssetIDByName("debug")} />}
trailing={FormRow.Arrow}
onPress={() => {
navigation.push("VendettaCustomPage", {
title: "Debug",
render: lazy(() => import("./Debug"))
});
}}
/>
</ScrollView>
<ScrollView>
<FormText>Loading...</FormText>
</ScrollView>
);
}
useEffect(() => {
try {
navigation.setOptions({
title: "Last.fm Configuration",
headerRight: () => <UpdateButton />,
});
} catch (error) {
logComponentError("Settings-Navigation", error);
}
}, []);
return (
<ScrollView>
<FormInput
value={settings.appName || undefined}
onChangeText={(value: string) => (settings.appName = value.trim())}
title="Discord Application Name"
placeholder={Constants.DEFAULT_APP_NAME}
returnKeyType="done"
/>
<FormDivider />
<FormInput
required
value={settings.username || undefined}
onChangeText={(value: string) => (settings.username = value.trim())}
title="Last.fm username"
helpText={
!settings.username && (
<Text style={{ color: "#FF0000" }}>
{"This field is required!"}
</Text>
)
}
placeholder="wumpus123"
returnKeyType="done"
/>
<FormDivider />
<FormInput
value={settings.timeInterval}
onChangeText={(value: string) => (settings.timeInterval = value)}
title="Update interval (in seconds)"
placeholder={Constants.DEFAULT_TIME_INTERVAL.toString()}
keyboardType="numeric"
returnKeyType="done"
/>
<FormDivider />
<FormSwitchRow
label="Show time elapsed"
subLabel="Show the time elapsed since the song started playing"
leading={<FormIcon source={getAssetIDByName("clock")} />}
value={settings.showTimestamp}
onValueChange={(value: boolean) => (settings.showTimestamp = value)}
/>
<FormDivider />
<FormSwitchRow
label="Set status as listening"
subLabel='Set your status as "Listening to" instead of "Playing"'
leading={<FormIcon source={getAssetIDByName("ic_headset_neutral")} />}
value={settings.listeningTo}
onValueChange={(value: boolean) => (settings.listeningTo = value)}
/>
<FormDivider />
<FormSwitchRow
label="Hide when Spotify is running"
subLabel="Hide the status when a Spotify activity is detected"
leading={
<FormIcon
source={getAssetIDByName("img_account_sync_spotify_light_and_dark")}
/>
}
value={settings.ignoreSpotify}
onValueChange={(value: boolean) => (settings.ignoreSpotify = value)}
/>
<FormDivider />
<FormRow
label="Debug"
subLabel="View debug information"
leading={<FormIcon source={getAssetIDByName("debug")} />}
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"));
}
}}
/>
</ScrollView>
);
});

View file

@ -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,
);
}
}