1import { LinearGradient } from 'expo-linear-gradient';
2import React from 'react';
3import { Image, Platform, Animated, ScrollView, StyleSheet, Text, View } from 'react-native';
4
5import MonoText from '../components/MonoText';
6
7// https://github.com/expo/expo/issues/10599
8const AnimatedLinearGradient = Animated.createAnimatedComponent(LinearGradient);
9
10function incrementColor(color: string, step: number) {
11  const intColor = parseInt(color.substr(1), 16);
12  const newIntColor = (intColor + step).toString(16);
13  return `#${'0'.repeat(6 - newIntColor.length)}${newIntColor}`;
14}
15
16type State = {
17  count: number;
18  colorTop: string;
19  colorBottom: string;
20};
21
22// See: https://github.com/expo/expo/pull/10229#discussion_r490961694
23// eslint-disable-next-line @typescript-eslint/ban-types
24export default class LinearGradientScreen extends React.Component<{}, State> {
25  static navigationOptions = {
26    title: 'LinearGradient',
27  };
28
29  state = {
30    count: 0,
31    colorTop: '#000000',
32    colorBottom: '#cccccc',
33  };
34
35  _interval?: number;
36
37  componentDidMount() {
38    // @ts-expect-error: TS resolves node types first
39    this._interval = setInterval(() => {
40      this.setState(state => ({
41        count: state.count + 1,
42        colorTop: incrementColor(state.colorTop, 1),
43        colorBottom: incrementColor(state.colorBottom, -1),
44      }));
45    }, 100);
46  }
47
48  componentWillUnmount() {
49    clearInterval(this._interval);
50  }
51
52  render() {
53    const location = Math.sin(this.state.count / 100) * 0.5;
54    const position = Math.sin(this.state.count / 100);
55    return (
56      <ScrollView
57        style={{ flex: 1 }}
58        contentContainerStyle={{
59          alignItems: 'stretch',
60          paddingVertical: 10,
61        }}>
62        <AnimatedLinearGradient
63          style={{ display: 'none' }}
64          colors={[this.state.colorTop, this.state.colorBottom]}
65        />
66        <ColorsTest colors={[this.state.colorTop, this.state.colorBottom]} />
67        <LocationsTest locations={[location, 1.0 - location]} />
68        <ControlPointTest start={[position, 0]} />
69        {Platform.OS !== 'web' && <SnapshotTest />}
70      </ScrollView>
71    );
72  }
73}
74
75const Container: React.FunctionComponent<{ title: string }> = ({ title, children }) => (
76  <View style={styles.container}>
77    <Text style={styles.containerTitle}>{title}</Text>
78    {children}
79  </View>
80);
81
82const SnapshotTest = () => (
83  <Container title="Snapshot">
84    <View style={{ flexDirection: 'row', alignSelf: 'stretch', justifyContent: 'space-evenly' }}>
85      <LinearGradient
86        colors={['white', 'red']}
87        start={[0.5, 0.5]}
88        end={[1, 1]}
89        style={{
90          width: 100,
91          maxHeight: 200,
92          minHeight: 200,
93          borderWidth: 1,
94          marginVertical: 20,
95          borderColor: 'black',
96        }}
97      />
98      <Image
99        source={require('../../assets/images/confusing_gradient.png')}
100        style={{ width: 100, height: 200, marginVertical: 20 }}
101      />
102    </View>
103    <Text style={{ marginHorizontal: 20 }}>The gradients above should look the same.</Text>
104  </Container>
105);
106
107const ControlPointTest: React.FunctionComponent<{
108  start?: [number, number];
109  end?: [number, number];
110}> = ({ start = [0.5, 0], end = [0, 1] }) => {
111  const startInfo = `start={[${start.map(point => +point.toFixed(2)).join(', ')}]}`;
112  const endInfo = `end={[${end.map(point => +point.toFixed(2)).join(', ')}]}`;
113
114  return (
115    <Container title="Control Points">
116      <View>
117        {[startInfo, endInfo].map((pointInfo, index) => (
118          <MonoText key={'--' + index}>{pointInfo}</MonoText>
119        ))}
120      </View>
121      <LinearGradient
122        start={start}
123        end={end}
124        locations={[0.5, 0.5]}
125        colors={['blue', 'lime']}
126        style={styles.gradient}
127      />
128    </Container>
129  );
130};
131
132const ColorsTest = ({ colors }: { colors: string[] }) => {
133  const info = colors.map(value => `"${value}"`).join(', ');
134  return (
135    <Container title="Colors">
136      <MonoText>{`colors={[${info}]}`}</MonoText>
137      <LinearGradient colors={colors} style={styles.gradient} />
138    </Container>
139  );
140};
141
142const LocationsTest: React.FunctionComponent<{ locations: number[] }> = ({ locations }) => {
143  const locationsInfo = locations.map(location => +location.toFixed(2)).join(', ');
144  return (
145    <Container title="Locations">
146      <MonoText>{`locations={[${locationsInfo}]}`}</MonoText>
147      <LinearGradient colors={['red', 'blue']} locations={locations} style={styles.gradient} />
148    </Container>
149  );
150};
151
152const styles = StyleSheet.create({
153  container: {
154    padding: 8,
155    flexShrink: 0,
156  },
157  containerTitle: {
158    fontSize: 14,
159    fontWeight: '600',
160    textAlign: 'center',
161    marginBottom: 8,
162  },
163  gradient: {
164    flex: 1,
165    flexShrink: 0,
166    minHeight: 200,
167    maxHeight: 200,
168  },
169});
170