1import React, {Component} from 'react'
2import openURL from '../util/open-url'
3import {fontSizeToSizeStyle, lineClamp, metaData} from './text.meta.native'
4import * as Styles from '../styles'
5import shallowEqual from 'shallowequal'
6import {NativeClipboard, NativeText, NativeAlert} from './native-wrappers.native'
7import {Props, TextType} from './text'
8
9const modes = ['positive', 'negative']
10
11const styles = Styles.styleSheetCreate(() =>
12  Object.keys(metaData()).reduce<any>(
13    (map, type) => {
14      const meta = metaData()[type as TextType]
15      modes.forEach(mode => {
16        map[`${type}:${mode}`] = {
17          ...fontSizeToSizeStyle(meta.fontSize),
18          color: meta.colorForBackground[mode] || Styles.globalColors.black,
19          ...meta.styleOverride,
20        }
21      })
22      return map
23    },
24    {center: {textAlign: 'center'}}
25  )
26)
27
28// Init common styles for perf
29
30class Text extends Component<Props> {
31  static defaultProps = {
32    allowFontScaling: false,
33  }
34  _nativeText: any
35
36  highlightText() {
37    // ignored
38  }
39
40  focus() {
41    if (this._nativeText) {
42      this._nativeText.focus()
43    }
44  }
45
46  _urlClick = () => {
47    this.props.onClickURL && openURL(this.props.onClickURL)
48  }
49
50  _urlCopy = (url: string | null) => {
51    if (!url) return
52    NativeClipboard.setString(url)
53  }
54
55  _urlChooseOption = () => {
56    const url = this.props.onLongPressURL
57    if (!url) return
58    NativeAlert.alert('', url, [
59      {style: 'cancel', text: 'Cancel'},
60      {onPress: () => openURL(url), text: 'Open Link'},
61      {onPress: () => this._urlCopy(url), text: 'Copy Link'},
62    ])
63  }
64
65  shouldComponentUpdate(nextProps: Props): boolean {
66    return !shallowEqual(this.props, nextProps, (obj, oth, key) => {
67      if (key === 'style') {
68        return shallowEqual(obj, oth)
69      } else if (key === 'children' && this.props.plainText && nextProps.plainText) {
70        // child will be plain text
71        return shallowEqual(obj, oth)
72      }
73      return undefined
74    })
75  }
76
77  render() {
78    const baseStyle = styles[`${this.props.type}:${this.props.negative ? 'negative' : 'positive'}`]
79    const dynamicStyle = this.props.negative
80      ? _getStyle(
81          this.props.type,
82          this.props.negative,
83          this.props.lineClamp,
84          !!this.props.onClick,
85          !!this.props.underline
86        )
87      : {}
88
89    let style
90    if (!Object.keys(dynamicStyle).length) {
91      style =
92        this.props.style || this.props.center
93          ? [baseStyle, this.props.center && styles.center, this.props.style]
94          : baseStyle
95    } else {
96      style = [baseStyle, dynamicStyle, this.props.center && styles.center, this.props.style]
97    }
98
99    const onPress =
100      this.props.onClick ||
101      (this.props.onClickURL ? this._urlClick : undefined) ||
102      // If selectable and there isn't already an onClick handler,
103      // make a dummy one so that it shows the selection (on iOS).
104      (this.props.selectable ? () => {} : undefined)
105
106    const onLongPress =
107      this.props.onLongPress || (this.props.onLongPressURL ? this._urlChooseOption : undefined)
108
109    return (
110      <NativeText
111        ref={ref => {
112          this._nativeText = ref
113        }}
114        selectable={this.props.selectable}
115        textBreakStrategy={this.props.textBreakStrategy}
116        style={style}
117        {...lineClamp(this.props.lineClamp || undefined, this.props.ellipsizeMode || undefined)}
118        onPress={onPress}
119        onLongPress={onLongPress}
120        allowFontScaling={this.props.allowFontScaling}
121      >
122        {this.props.children}
123      </NativeText>
124    )
125  }
126}
127
128// external things call this so leave the original alone
129function _getStyle(
130  type: TextType,
131  negative?: boolean,
132  _?: number | null,
133  __?: boolean | null,
134  // @ts-ignore the order of these parameters because this is used in a lot
135  // of places
136  forceUnderline: boolean
137) {
138  if (!negative) {
139    return forceUnderline ? {textDecorationLine: 'underline'} : {}
140  }
141  // negative === true
142  const meta = metaData()[type]
143  const colorStyle = {color: meta.colorForBackground.negative}
144  const textDecoration = meta.isLink ? {textDecorationLine: 'underline'} : {}
145
146  return {
147    ...colorStyle,
148    ...textDecoration,
149  }
150}
151function getStyle(type: TextType, negative?: boolean, _?: number | null, __?: boolean | null) {
152  const meta = metaData()[type]
153  const sizeStyle = fontSizeToSizeStyle(meta.fontSize)
154  const colorStyle = {color: meta.colorForBackground[negative ? 'negative' : 'positive']}
155  const textDecoration = meta.isLink && negative ? {textDecorationLine: 'underline'} : {}
156
157  return {
158    ...sizeStyle,
159    ...colorStyle,
160    ...textDecoration,
161    ...meta.styleOverride,
162  }
163}
164
165export default Text
166export {getStyle}
167export {Text as TextMixed}
168export {allTextTypes} from './text.shared'
169