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    this._interval = setInterval(() => {
39      this.setState((state) => ({
40        count: state.count + 1,
41        colorTop: incrementColor(state.colorTop, 1),
42        colorBottom: incrementColor(state.colorBottom, -1),
43      }));
44    }, 100);
45  }
46
47  componentWillUnmount() {
48    clearInterval(this._interval);
49  }
50
51  render() {
52    const location = Math.sin(this.state.count / 100) * 0.5;
53    const position = Math.sin(this.state.count / 100);
54    return (
55      <ScrollView
56        style={{ flex: 1 }}
57        contentContainerStyle={{
58          alignItems: 'stretch',
59          paddingVertical: 10,
60        }}>
61        <AnimatedLinearGradient
62          style={{ display: 'none' }}
63          colors={[this.state.colorTop, this.state.colorBottom]}
64        />
65        <ColorsTest colors={[this.state.colorTop, this.state.colorBottom]} />
66        <LocationsTest locations={[location, 1.0 - location]} />
67        <ControlPointTest start={[position, 0]} />
68        {Platform.OS !== 'web' && <SnapshotTest />}
69      </ScrollView>
70    );
71  }
72}
73
74const Container: React.FunctionComponent<{ title: string; children?: React.ReactNode }> = ({
75  title,
76  children,
77}) => (
78  <View style={styles.container}>
79    <Text style={styles.containerTitle}>{title}</Text>
80    {children}
81  </View>
82);
83
84const SnapshotTest = () => (
85  <Container title="Snapshot">
86    <View style={{ flexDirection: 'row', alignSelf: 'stretch', justifyContent: 'space-evenly' }}>
87      <LinearGradient
88        colors={['white', 'red']}
89        start={[0.5, 0.5]}
90        end={[1, 1]}
91        style={{
92          width: 100,
93          maxHeight: 200,
94          minHeight: 200,
95          borderWidth: 1,
96          marginVertical: 20,
97          borderColor: 'black',
98        }}
99      />
100      <Image
101        source={require('../../assets/images/confusing_gradient.png')}
102        style={{ width: 100, height: 200, marginVertical: 20 }}
103      />
104    </View>
105    <Text style={{ marginHorizontal: 20 }}>The gradients above should look the same.</Text>
106  </Container>
107);
108
109const ControlPointTest: React.FunctionComponent<{
110  start?: [number, number];
111  end?: [number, number];
112}> = ({ start = [0.5, 0], end = [0, 1] }) => {
113  const startInfo = `start={[${start.map((point) => +point.toFixed(2)).join(', ')}]}`;
114  const endInfo = `end={[${end.map((point) => +point.toFixed(2)).join(', ')}]}`;
115
116  return (
117    <Container title="Control Points">
118      <View>
119        {[startInfo, endInfo].map((pointInfo, index) => (
120          <MonoText key={'--' + index}>{pointInfo}</MonoText>
121        ))}
122      </View>
123      <LinearGradient
124        start={start}
125        end={end}
126        locations={[0.5, 0.5]}
127        colors={['blue', 'lime']}
128        style={styles.gradient}
129      />
130    </Container>
131  );
132};
133
134const ColorsTest = ({ colors }: { colors: string[] }) => {
135  const info = colors.map((value) => `"${value}"`).join(', ');
136  return (
137    <Container title="Colors">
138      <MonoText>{`colors={[${info}]}`}</MonoText>
139      <LinearGradient colors={colors} style={styles.gradient} />
140    </Container>
141  );
142};
143
144const LocationsTest: React.FunctionComponent<{ locations: number[] }> = ({ locations }) => {
145  const locationsInfo = locations.map((location) => +location.toFixed(2)).join(', ');
146  return (
147    <Container title="Locations">
148      <MonoText>{`locations={[${locationsInfo}]}`}</MonoText>
149      <LinearGradient colors={['red', 'blue']} locations={locations} style={styles.gradient} />
150    </Container>
151  );
152};
153
154const styles = StyleSheet.create({
155  container: {
156    padding: 8,
157    flexShrink: 0,
158  },
159  containerTitle: {
160    fontSize: 14,
161    fontWeight: '600',
162    textAlign: 'center',
163    marginBottom: 8,
164  },
165  gradient: {
166    flex: 1,
167    flexShrink: 0,
168    minHeight: 200,
169    maxHeight: 200,
170  },
171});
172