fixed reanimated issue??
This commit is contained in:
parent
46d0cb300b
commit
f4be7b2743
6 changed files with 384 additions and 145 deletions
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
121
plugins/Last.fm/src/index.tsx
Normal file
121
plugins/Last.fm/src/index.tsx
Normal 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();
|
||||
}
|
||||
};
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue