mirror of
https://github.com/minibits-cash/minibits_wallet.git
synced 2026-10-05 11:18:24 +00:00
397 lines
11 KiB
TypeScript
397 lines
11 KiB
TypeScript
import React, { ReactElement } from "react"
|
|
import {
|
|
StyleProp,
|
|
TextStyle,
|
|
TouchableOpacity,
|
|
TouchableOpacityProps,
|
|
View,
|
|
ViewStyle,
|
|
StatusBar,
|
|
ColorValue,
|
|
Platform
|
|
} from "react-native"
|
|
import Animated, {
|
|
SharedValue,
|
|
useAnimatedStyle,
|
|
interpolate,
|
|
Extrapolation,
|
|
} from "react-native-reanimated"
|
|
import { useIsFocused } from '@react-navigation/native'
|
|
import useIsInternetReachable from '../utils/useIsInternetReachable'
|
|
import { translate } from "../i18n"
|
|
import { useThemeColor, spacing, colors, typography } from "../theme"
|
|
import { displayName as appName } from '../../app.json'
|
|
import { ExtendedEdge, useSafeAreaInsetsStyle } from "../utils/useSafeAreaInsetsStyle"
|
|
import { Icon, IconTypes } from "./Icon"
|
|
import { Text, TextProps } from "./Text"
|
|
|
|
|
|
export interface HeaderProps {
|
|
/**
|
|
* The layout of the title relative to the action components.
|
|
* - `center` will force the title to always be centered relative to the header. If the title or the action buttons are too long, the title will be cut off.
|
|
* - `flex` will attempt to center the title relative to the action buttons. If the action buttons are different widths, the title will be off-center relative to the header.
|
|
*/
|
|
titleMode?: "center" | "flex"
|
|
/**
|
|
* Optional title style override.
|
|
*/
|
|
titleStyle?: StyleProp<TextStyle>
|
|
/**
|
|
* Optional outer title container style override.
|
|
*/
|
|
titleContainerStyle?: StyleProp<ViewStyle>
|
|
/**
|
|
* Optional inner header wrapper style override.
|
|
*/
|
|
style?: StyleProp<ViewStyle>
|
|
/**
|
|
* Optional outer header container style override.
|
|
*/
|
|
containerStyle?: StyleProp<ViewStyle>
|
|
/**
|
|
* Background color
|
|
*/
|
|
backgroundColor?: ColorValue
|
|
/**
|
|
* Title action custom ReactElement.
|
|
* Overrides `title`, `titleTx`.
|
|
*/
|
|
TitleActionComponent?: ReactElement
|
|
/**
|
|
* Title text to display if not using `tx` or nested components.
|
|
*/
|
|
title?: TextProps["text"]
|
|
/**
|
|
* Title text which is looked up via i18n.
|
|
*/
|
|
titleTx?: TextProps["tx"]
|
|
/**
|
|
* Optional options to pass to i18n. Useful for interpolation
|
|
* as well as explicitly setting locale or translation fallbacks.
|
|
*/
|
|
titleTxOptions?: TextProps["txOptions"]
|
|
/**
|
|
* Icon that should appear on the left.
|
|
* Can be used with `onLeftPress`.
|
|
*/
|
|
leftIcon?: IconTypes
|
|
/**
|
|
* An optional tint color for the left icon
|
|
*/
|
|
leftIconColor?: string
|
|
/**
|
|
* Left action text to display if not using `leftTx`.
|
|
* Can be used with `onLeftPress`. Overrides `leftIcon`.
|
|
*/
|
|
leftText?: TextProps["text"]
|
|
/**
|
|
* Left action text text which is looked up via i18n.
|
|
* Can be used with `onLeftPress`. Overrides `leftIcon`.
|
|
*/
|
|
leftTx?: TextProps["tx"]
|
|
/**
|
|
* Left action custom ReactElement if the built in action props don't suffice.
|
|
* Overrides `leftIcon`, `leftTx` and `leftText`.
|
|
*/
|
|
LeftActionComponent?: ReactElement
|
|
/**
|
|
* Optional options to pass to i18n. Useful for interpolation
|
|
* as well as explicitly setting locale or translation fallbacks.
|
|
*/
|
|
leftTxOptions?: TextProps["txOptions"]
|
|
/**
|
|
* What happens when you press the left icon or text action.
|
|
*/
|
|
onLeftPress?: TouchableOpacityProps["onPress"]
|
|
/**
|
|
* Icon that should appear on the right.
|
|
* Can be used with `onRightPress`.
|
|
*/
|
|
rightIcon?: IconTypes
|
|
/**
|
|
* An optional tint color for the right icon
|
|
*/
|
|
rightIconColor?: ColorValue
|
|
/**
|
|
* Right action text to display if not using `rightTx`.
|
|
* Can be used with `onRightPress`. Overrides `rightIcon`.
|
|
*/
|
|
rightText?: TextProps["text"]
|
|
/**
|
|
* Right action text text which is looked up via i18n.
|
|
* Can be used with `onRightPress`. Overrides `rightIcon`.
|
|
*/
|
|
rightTx?: TextProps["tx"]
|
|
/**
|
|
* Right action custom ReactElement if the built in action props don't suffice.
|
|
* Overrides `rightIcon`, `rightTx` and `rightText`.
|
|
*/
|
|
RightActionComponent?: ReactElement
|
|
/**
|
|
* Optional options to pass to i18n. Useful for interpolation
|
|
* as well as explicitly setting locale or translation fallbacks.
|
|
*/
|
|
rightTxOptions?: TextProps["txOptions"]
|
|
/**
|
|
* What happens when you press the right icon or text action.
|
|
*/
|
|
onRightPress?: TouchableOpacityProps["onPress"]
|
|
/**
|
|
* Override the default edges for the safe area.
|
|
*/
|
|
safeAreaEdges?: ExtendedEdge[]
|
|
/**
|
|
* Shared value from scroll position for animated title.
|
|
* When provided, the title will fade in as the user scrolls.
|
|
*/
|
|
scrollY?: SharedValue<number>
|
|
/**
|
|
* The scroll distance over which the title animation occurs.
|
|
* Defaults to 60.
|
|
*/
|
|
scrollDistance?: number
|
|
}
|
|
|
|
interface HeaderActionProps {
|
|
backgroundColor?: ColorValue
|
|
icon?: IconTypes
|
|
iconColor?: string
|
|
text?: TextProps["text"]
|
|
tx?: TextProps["tx"]
|
|
txOptions?: TextProps["txOptions"]
|
|
onPress?: TouchableOpacityProps["onPress"]
|
|
ActionComponent?: ReactElement
|
|
}
|
|
|
|
|
|
/* Keeps status bar style in sync with navigation focus — only applies when this screen is active.
|
|
Uses React Native's StatusBar (status bar only) so it does not override the nav bar icon style
|
|
managed by the root SystemBars in AppNavigator. */
|
|
function FocusAwareStatusBar() {
|
|
const isFocused = useIsFocused()
|
|
return isFocused ? <StatusBar translucent barStyle="light-content" /> : null
|
|
}
|
|
|
|
/**
|
|
* Header that appears on many screens. Will hold navigation buttons and screen title.
|
|
* The Header is meant to be used with the `screenOptions.header` option on navigators, routes, or screen components via `navigation.setOptions({ header })`.
|
|
*
|
|
* - [Documentation and Examples](https://github.com/infinitered/ignite/blob/master/docs/Components-Header.md)
|
|
*/
|
|
export function Header(props: HeaderProps) {
|
|
//const isInternetReachable = useIsInternetReachable()
|
|
|
|
const {
|
|
backgroundColor = useThemeColor('header'),
|
|
LeftActionComponent,
|
|
leftIcon,
|
|
leftIconColor = "white",
|
|
leftText,
|
|
leftTx,
|
|
leftTxOptions,
|
|
onLeftPress,
|
|
onRightPress,
|
|
RightActionComponent,
|
|
rightIcon,
|
|
rightIconColor = "white",
|
|
rightText,
|
|
rightTx,
|
|
rightTxOptions,
|
|
safeAreaEdges = ["top"],
|
|
TitleActionComponent,
|
|
// title = appName,
|
|
title,
|
|
titleMode = "center",
|
|
titleTx,
|
|
titleTxOptions,
|
|
titleContainerStyle: $titleContainerStyleOverride,
|
|
style: $styleOverride,
|
|
titleStyle: $titleStyleOverride,
|
|
containerStyle: $containerStyleOverride,
|
|
scrollY,
|
|
scrollDistance = 60,
|
|
} = props
|
|
|
|
const $containerInsets = useSafeAreaInsetsStyle(safeAreaEdges)
|
|
const titleContent = !!TitleActionComponent ? undefined : titleTx ? translate(titleTx, titleTxOptions) : title
|
|
|
|
// Animated title opacity - fades in as user scrolls
|
|
const animatedTitleStyle = useAnimatedStyle(() => {
|
|
if (!scrollY) {
|
|
return {}
|
|
}
|
|
const opacity = interpolate(
|
|
scrollY.value,
|
|
[scrollDistance * 0.5, scrollDistance],
|
|
[0, 1],
|
|
Extrapolation.CLAMP
|
|
)
|
|
return { opacity }
|
|
})
|
|
|
|
return (
|
|
<View style={[$container, $containerInsets, { backgroundColor }, $containerStyleOverride]}>
|
|
<FocusAwareStatusBar />
|
|
<View style={[$wrapper, $styleOverride]}>
|
|
<HeaderAction
|
|
tx={leftTx}
|
|
text={leftText}
|
|
icon={leftIcon === "faArrowLeft" ? Platform.OS === 'ios' ? 'faChevronLeft' : leftIcon : leftIcon}
|
|
iconColor={leftIconColor as string}
|
|
onPress={onLeftPress}
|
|
txOptions={leftTxOptions}
|
|
backgroundColor={backgroundColor}
|
|
ActionComponent={LeftActionComponent}
|
|
/>
|
|
{!!TitleActionComponent && (
|
|
<View
|
|
style={[
|
|
titleMode === "center" && $titleWrapperCenter,
|
|
titleMode === "flex" && $titleWrapperFlex,
|
|
$titleContainerStyleOverride,
|
|
]}
|
|
pointerEvents="none"
|
|
>
|
|
{TitleActionComponent}
|
|
</View>
|
|
)}
|
|
{!!titleContent && (
|
|
<Animated.View
|
|
style={[
|
|
titleMode === "center" && $titleWrapperCenter,
|
|
titleMode === "flex" && $titleWrapperFlex,
|
|
$titleContainerStyleOverride,
|
|
scrollY ? animatedTitleStyle : undefined,
|
|
]}
|
|
pointerEvents="none"
|
|
>
|
|
<Text
|
|
weight="medium"
|
|
size="lg"
|
|
text={titleContent}
|
|
style={[$title, $titleStyleOverride]}
|
|
/>
|
|
</Animated.View>
|
|
)}
|
|
<HeaderAction
|
|
tx={rightTx}
|
|
text={rightText}
|
|
icon={rightIcon}
|
|
iconColor={rightIconColor as string}
|
|
onPress={onRightPress}
|
|
txOptions={rightTxOptions}
|
|
backgroundColor={backgroundColor}
|
|
ActionComponent={RightActionComponent}
|
|
/>
|
|
</View>
|
|
</View>
|
|
)
|
|
}
|
|
|
|
function HeaderAction(props: HeaderActionProps) {
|
|
const { backgroundColor, icon, text, tx, txOptions, onPress, ActionComponent, iconColor } = props
|
|
|
|
const content = tx ? translate(tx, txOptions) : text
|
|
|
|
if (ActionComponent) return ActionComponent
|
|
|
|
if (content) {
|
|
return (
|
|
<TouchableOpacity
|
|
style={[$actionTextContainer, { backgroundColor }]}
|
|
onPress={onPress}
|
|
disabled={!onPress}
|
|
activeOpacity={0.8}
|
|
>
|
|
<Text weight="medium" size="md" text={content} style={$actionText} />
|
|
</TouchableOpacity>
|
|
)
|
|
}
|
|
|
|
if (icon) {
|
|
return (
|
|
<Icon
|
|
size={20}
|
|
icon={icon}
|
|
color={iconColor}
|
|
onPress={onPress}
|
|
containerStyle={[$actionIconContainer, { backgroundColor }]}
|
|
/>
|
|
)
|
|
}
|
|
|
|
return <View style={[$actionFillerContainer, { backgroundColor }]} />
|
|
}
|
|
|
|
const $wrapper: ViewStyle = {
|
|
height: 56,
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
justifyContent: "space-between",
|
|
// overflow: 'visible',
|
|
// borderWidth: 1,
|
|
// borderColor: 'red',
|
|
}
|
|
|
|
const $container: ViewStyle = {
|
|
width: "100%",
|
|
}
|
|
|
|
const $title: TextStyle = {
|
|
textAlign: "center",
|
|
//fontFamily: typography.logo.normal,
|
|
color: 'white'
|
|
}
|
|
|
|
const $actionTextContainer: ViewStyle = {
|
|
flexGrow: 0,
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
height: "100%",
|
|
paddingHorizontal: spacing.medium,
|
|
zIndex: 2,
|
|
}
|
|
|
|
const $actionText: TextStyle = {
|
|
color: colors.light.tint,
|
|
}
|
|
|
|
const $actionIconContainer: ViewStyle = {
|
|
flexGrow: 0,
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
height: "100%",
|
|
paddingHorizontal: spacing.medium,
|
|
zIndex: 2,
|
|
}
|
|
|
|
const $actionFillerContainer: ViewStyle = {
|
|
width: 16,
|
|
}
|
|
|
|
const $titleWrapperCenter: ViewStyle = {
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
height: "100%",
|
|
width: "100%",
|
|
position: "absolute",
|
|
paddingHorizontal: spacing.huge,
|
|
zIndex: 1,
|
|
}
|
|
|
|
const $titleWrapperFlex: ViewStyle = {
|
|
justifyContent: "center",
|
|
flexGrow: 1,
|
|
}
|
|
|
|
/* const $offline: TextStyle = {
|
|
position: 'absolute',
|
|
top: 15,
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
fontSize: 10,
|
|
fontFamily: typography.code?.normal,
|
|
color: colors.palette.accent200,
|
|
}*/
|