11import { CursorProps , GradientProps , SlideAreaChart , ToolTipProps , ToolTipTextRenderersInput , YAxisProps } from '@connectedcars/react-native-slide-charts'
22import { asArray , asEither , asNumber , asObject , asString , asTuple } from 'cleaners'
33import * as React from 'react'
4- import { LayoutChangeEvent , Platform , View } from 'react-native'
4+ import { Dimensions , LayoutChangeEvent , Platform , View } from 'react-native'
55import { cacheStyles } from 'react-native-patina'
66import Animated , { Easing , SharedValue , useAnimatedProps , useAnimatedStyle , useSharedValue , withDelay , withRepeat , withTiming } from 'react-native-reanimated'
77import Svg , { Circle , CircleProps , LinearGradient , Stop } from 'react-native-svg'
@@ -14,7 +14,6 @@ import { formatFiatString } from '../../hooks/useFiatText'
1414import { useHandler } from '../../hooks/useHandler'
1515import { formatDate } from '../../locales/intl'
1616import { lstrings } from '../../locales/strings'
17- import { fixSides , mapSides , sidesToMargin } from '../../util/sides'
1817import { MinimalButton } from '../buttons/MinimalButton'
1918import { FillLoader } from '../progress-indicators/FillLoader'
2019import { showWarning } from '../services/AirshipInstance'
@@ -30,12 +29,6 @@ interface Props {
3029 assetId : string
3130 currencyCode : string
3231 fiatCurrencyCode : string
33- /**
34- * Typically we don't want to add custom margins to break consistent design,
35- * but for this particular component, we sometimes want to adjust how the line
36- * chart itself, minus the timeframe buttons, is laid out.
37- */
38- marginRem ?: number [ ] | number
3932}
4033interface ChartDataPoint {
4134 x : Date
@@ -137,9 +130,7 @@ const reduceChartData = (chartData: ChartDataPoint[], timespan: Timespan): Chart
137130const SwipeChartComponent = ( params : Props ) => {
138131 const theme = useTheme ( )
139132 const styles = getStyles ( theme )
140- const { assetId, marginRem, currencyCode, fiatCurrencyCode } = params
141-
142- const customMargin = sidesToMargin ( mapSides ( fixSides ( marginRem , 0 ) , theme . rem ) )
133+ const { assetId, currencyCode, fiatCurrencyCode } = params
143134
144135 // #region Chart setup
145136
@@ -156,9 +147,6 @@ const SwipeChartComponent = (params: Props) => {
156147 const [ queryFromTimeOffset , setQueryFromTimeOffset ] = React . useState ( UNIX_SECONDS_MONTH_OFFSET )
157148 const [ isLoading , setIsLoading ] = React . useState ( false )
158149
159- const chartWidth = React . useRef ( 0 )
160- const chartHeight = React . useRef ( 0 )
161-
162150 const fiatSymbol = React . useMemo ( ( ) => getFiatSymbol ( fiatCurrencyCode ) , [ fiatCurrencyCode ] )
163151
164152 // Min/Max Price Calcs
@@ -167,9 +155,7 @@ const SwipeChartComponent = (params: Props) => {
167155 const maxPrice = Math . max ( ...prices )
168156
169157 const sMinPriceLabelX = useSharedValue ( 0 )
170- const sMinPriceLabelY = useSharedValue ( 0 )
171158 const sMaxPriceLabelX = useSharedValue ( 0 )
172- const sMaxPriceLabelY = useSharedValue ( 0 )
173159
174160 const sMinPriceString = useSharedValue ( `` )
175161 const sMaxPriceString = useSharedValue ( `` )
@@ -179,6 +165,14 @@ const SwipeChartComponent = (params: Props) => {
179165 const minPriceDataPoint = React . useMemo ( ( ) => chartData . find ( point => point . y === minPrice ) , [ chartData , minPrice ] )
180166 const maxPriceDataPoint = React . useMemo ( ( ) => chartData . find ( point => point . y === maxPrice ) , [ chartData , maxPrice ] )
181167
168+ // The chart component defaults to the phone width.
169+ // To fit the chart into its parent view,
170+ // we measure the parent and pass that width in.
171+ // The chart will freeze the whole app if the width is too narrow,
172+ // so start with the window width:
173+ const [ chartWidth , setChartWidth ] = React . useState ( Dimensions . get ( 'window' ) . width )
174+ const chartHeight = theme . rem ( CHART_HEIGHT_REM )
175+
182176 // Fetch/cache chart data, set shared animation transition values
183177 useAsyncEffect (
184178 async ( ) => {
@@ -303,14 +297,15 @@ const SwipeChartComponent = (params: Props) => {
303297
304298 // A delayed fadein for the max/min labels, to ensure the labels don't get
305299 // rendered before the price line. Also hidden when gesture is active
300+ const minPriceLabelY = Platform . OS === 'ios' ? chartHeight - theme . rem ( 2.5 ) : chartHeight - theme . rem ( 2.75 )
306301 const aMinLabelStyle = useAnimatedStyle ( ( ) => ( {
307302 left : sMinPriceLabelX . value ,
308- top : sMinPriceLabelY . value ,
303+ top : minPriceLabelY ,
309304 opacity : sMinMaxOpacity . value * ( 1 - sCursorOpacity . value )
310305 } ) )
311306 const aMaxLabelStyle = useAnimatedStyle ( ( ) => ( {
312307 left : sMaxPriceLabelX . value ,
313- top : sMaxPriceLabelY . value ,
308+ top : 0 ,
314309 opacity : sMinMaxOpacity . value * ( 1 - sCursorOpacity . value )
315310 } ) )
316311
@@ -356,6 +351,10 @@ const SwipeChartComponent = (params: Props) => {
356351
357352 // #region Handlers
358353
354+ const handleLayout = useHandler ( ( event : LayoutChangeEvent ) => {
355+ setChartWidth ( event . nativeEvent . layout . width )
356+ } )
357+
359358 const handleGradient = useHandler ( ( props : GradientProps ) => {
360359 return (
361360 < LinearGradient x1 = "50%" y1 = "0%" x2 = "50%" y2 = "100%" { ...props } >
@@ -365,16 +364,6 @@ const SwipeChartComponent = (params: Props) => {
365364 )
366365 } )
367366
368- /**
369- * Handle the layout event on the chart, set the min price label Y value.
370- */
371- const handleSetChartDimensions = useHandler ( ( event : LayoutChangeEvent ) => {
372- const { width, height } = event . nativeEvent . layout
373- chartWidth . current = width
374- chartHeight . current = height
375- sMinPriceLabelY . value = Platform . OS === 'ios' ? chartHeight . current - theme . rem ( 2.5 ) : chartHeight . current - theme . rem ( 2.75 )
376- } )
377-
378367 /**
379368 * Handle the tap and hold gesture event on the chart.
380369 *
@@ -436,7 +425,7 @@ const SwipeChartComponent = (params: Props) => {
436425 const setMinMaxLabelsX = ( xSharedVal : SharedValue < number > , priceDatapoint ?: ChartDataPoint ) => ( layoutChangeEvent : LayoutChangeEvent ) => {
437426 if ( layoutChangeEvent != null && layoutChangeEvent . nativeEvent != null && minPriceDataPoint != null && chartData != null && priceDatapoint != null ) {
438427 const xIndex = chartData . indexOf ( priceDatapoint )
439- const xPosition = ( chartWidth . current / ( chartData . length - 1 ) ) * xIndex
428+ const xPosition = ( chartWidth / ( chartData . length - 1 ) ) * xIndex
440429 const labelWidth = layoutChangeEvent . nativeEvent . layout . width
441430 const isRightJustified = xPosition > chartData . length / 2
442431
@@ -536,7 +525,7 @@ const SwipeChartComponent = (params: Props) => {
536525
537526 // Main Render
538527 return (
539- < View style = { styles . container } >
528+ < View style = { styles . container } onLayout = { handleLayout } >
540529 { /* Timespan control bar */ }
541530 < View style = { styles . controlBar } >
542531 { renderTimespanButton ( lstrings . coin_rank_hour , 'hour' , handleSetTimespanH ) }
@@ -548,22 +537,24 @@ const SwipeChartComponent = (params: Props) => {
548537
549538 { /* Chart */ }
550539 { chartData . length === 0 || isLoading ? (
551- < View style = { styles . loader } onLayout = { handleSetChartDimensions } >
540+ < View style = { styles . loader } >
552541 < FillLoader />
553542 </ View >
554543 ) : (
555544 < View >
556545 < SlideAreaChart
557546 data = { chartData }
558547 animated
559- height = { theme . rem ( 11 ) }
548+ height = { theme . rem ( CHART_HEIGHT_REM ) }
549+ width = { chartWidth }
560550 chartLineColor = { theme . iconTappable }
561551 chartLineWidth = { 1.5 }
562552 renderFillGradient = { handleGradient }
553+ // Create space for our min label:
563554 paddingBottom = { theme . rem ( 1.5 ) }
564- // Price line has weird uneven margins when unadjusted
565- paddingRight = { theme . rem ( 2 ) }
566- paddingLeft = { theme . rem ( - 0.5 ) }
555+ // The default padding is 8, which we don't want:
556+ paddingRight = { 0 }
557+ paddingLeft = { 0 }
567558 yRange = { chartYRange }
568559 xScale = "linear"
569560 yAxisProps = { Y_AXIS_PROPS }
@@ -577,7 +568,7 @@ const SwipeChartComponent = (params: Props) => {
577568 toolTipProps = { tooltipProps }
578569 // #endregion ToolTip
579570
580- style = { [ styles . baseChart , customMargin ] }
571+ style = { styles . baseChart }
581572 />
582573
583574 { /* Min/Max price labels */ }
@@ -613,6 +604,8 @@ const SwipeChartComponent = (params: Props) => {
613604 // #endregion Components
614605}
615606
607+ const CHART_HEIGHT_REM = 11
608+
616609const getStyles = cacheStyles ( ( theme : Theme ) => {
617610 return {
618611 baseChart : {
@@ -625,7 +618,10 @@ const getStyles = cacheStyles((theme: Theme) => {
625618 position : 'absolute'
626619 } ,
627620 container : {
628- margin : theme . rem ( 0.5 )
621+ margin : theme . rem ( 0.5 ) ,
622+ // The chart starts off at the phone width,
623+ // so hide the extra until we can adjust the layout:
624+ overflow : 'hidden'
629625 } ,
630626 controlBar : {
631627 justifyContent : 'center' ,
@@ -637,7 +633,7 @@ const getStyles = cacheStyles((theme: Theme) => {
637633 } ,
638634 loader : {
639635 marginTop : theme . rem ( 0 ) ,
640- height : theme . rem ( 11 )
636+ height : theme . rem ( CHART_HEIGHT_REM )
641637 } ,
642638 label : {
643639 color : theme . primaryText ,
0 commit comments