template_0205

This commit is contained in:
Sofio
2026-02-05 13:16:05 +08:00
commit d93e4d9c9f
197 changed files with 52810 additions and 0 deletions

View File

@@ -0,0 +1,76 @@
import { Component, ErrorInfo, ReactNode } from "react"
import { ErrorDetails } from "./ErrorDetails"
interface Props {
children: ReactNode
catchErrors: "always" | "dev" | "prod" | "never"
}
interface State {
error: Error | null
errorInfo: ErrorInfo | null
}
/**
* This component handles whenever the user encounters a JS error in the
* app. It follows the "error boundary" pattern in React. We're using a
* class component because according to the documentation, only class
* components can be error boundaries.
* @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/concept/Error-Boundary/}
* @see [React Error Boundaries]{@link https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary}
* @param {Props} props - The props for the `ErrorBoundary` component.
* @returns {JSX.Element} The rendered `ErrorBoundary` component.
*/
export class ErrorBoundary extends Component<Props, State> {
state = { error: null, errorInfo: null }
// If an error in a child is encountered, this will run
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
// Only set errors if enabled
if (!this.isEnabled()) {
return
}
// Catch errors in any components below and re-render with error message
this.setState({
error,
errorInfo,
})
// You can also log error messages to an error reporting service here
// This is a great place to put BugSnag, Sentry, crashlytics, etc:
// reportCrash(error)
}
// Reset the error back to null
resetError = () => {
this.setState({ error: null, errorInfo: null })
}
// To avoid unnecessary re-renders
shouldComponentUpdate(nextProps: Readonly<Props>, nextState: Readonly<State>): boolean {
return nextState.error !== this.state.error
}
// Only enable if we're catching errors in the right environment
isEnabled(): boolean {
return (
this.props.catchErrors === "always" ||
(this.props.catchErrors === "dev" && __DEV__) ||
(this.props.catchErrors === "prod" && !__DEV__)
)
}
// Render an error UI if there's an error; otherwise, render children
render() {
return this.isEnabled() && this.state.error ? (
<ErrorDetails
onReset={this.resetError}
error={this.state.error}
errorInfo={this.state.errorInfo}
/>
) : (
this.props.children
)
}
}

View File

@@ -0,0 +1,99 @@
import { ErrorInfo } from "react"
import { ScrollView, TextStyle, View, ViewStyle } from "react-native"
import { Button } from "@/components/Button"
import { Icon } from "@/components/Icon"
import { Screen } from "@/components/Screen"
import { Text } from "@/components/Text"
import { useAppTheme } from "@/theme/context"
import type { ThemedStyle } from "@/theme/types"
import { s } from "@/utils/responsive"
export interface ErrorDetailsProps {
error: Error
errorInfo: ErrorInfo | null
onReset(): void
}
/**
* Renders the error details screen.
* @param {ErrorDetailsProps} props - The props for the `ErrorDetails` component.
* @returns {JSX.Element} The rendered `ErrorDetails` component.
*/
export function ErrorDetails(props: ErrorDetailsProps) {
const { themed } = useAppTheme()
return (
<Screen
preset="fixed"
safeAreaEdges={["top", "bottom"]}
contentContainerStyle={themed($contentContainer)}
>
<View style={$topSection}>
<Icon icon="ladybug" size={s(64)} />
<Text style={themed($heading)} preset="subheading" tx="errorScreen:title" />
<Text tx="errorScreen:friendlySubtitle" />
</View>
<ScrollView
style={themed($errorSection)}
contentContainerStyle={themed($errorSectionContentContainer)}
>
<Text style={themed($errorContent)} weight="bold" text={`${props.error}`.trim()} />
<Text
selectable
style={themed($errorBacktrace)}
text={`${props.errorInfo?.componentStack ?? ""}`.trim()}
/>
</ScrollView>
<Button
preset="reversed"
style={themed($resetButton)}
onPress={props.onReset}
tx="errorScreen:reset"
/>
</Screen>
)
}
const $contentContainer: ThemedStyle<ViewStyle> = ({ spacing }) => ({
alignItems: "center",
paddingHorizontal: spacing.lg,
paddingTop: spacing.xl,
flex: 1,
})
const $topSection: ViewStyle = {
flex: 1,
alignItems: "center",
}
const $heading: ThemedStyle<TextStyle> = ({ colors, spacing }) => ({
color: colors.error,
marginBottom: spacing.md,
})
const $errorSection: ThemedStyle<ViewStyle> = ({ colors, spacing }) => ({
flex: 2,
backgroundColor: colors.separator,
marginVertical: spacing.md,
borderRadius: s(6),
})
const $errorSectionContentContainer: ThemedStyle<ViewStyle> = ({ spacing }) => ({
padding: spacing.md,
})
const $errorContent: ThemedStyle<TextStyle> = ({ colors }) => ({
color: colors.error,
})
const $errorBacktrace: ThemedStyle<TextStyle> = ({ colors, spacing }) => ({
marginTop: spacing.md,
color: colors.textDim,
})
const $resetButton: ThemedStyle<ViewStyle> = ({ colors, spacing }) => ({
backgroundColor: colors.error,
paddingHorizontal: spacing.xxl,
})