您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

AvatarImage.native.js 5.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. import React, { Component } from 'react';
  2. import { Image, View } from 'react-native';
  3. import { Platform } from '../../react';
  4. /**
  5. * The default avatar to be used, in case the requested URI is not available
  6. * or fails to load. It is an inline version of images/avatar2.png.
  7. *
  8. * @type {string}
  9. */
  10. const DEFAULT_AVATAR = require('./defaultAvatar.png');
  11. /**
  12. * The number of milliseconds to wait when the avatar URI is undefined before we
  13. * start showing a default locally generated one. Note that since we have no
  14. * URI, we have nothing we can cache, so the color will be random.
  15. *
  16. * @type {number}
  17. */
  18. const UNDEFINED_AVATAR_TIMEOUT = 1000;
  19. /**
  20. * Implements an Image component wrapper, which returns a default image if the
  21. * requested one fails to load. The default image background is chosen by
  22. * hashing the URL of the image.
  23. */
  24. export default class AvatarImage extends Component {
  25. /**
  26. * AvatarImage component's property types.
  27. *
  28. * @static
  29. */
  30. static propTypes = {
  31. /**
  32. * If set to <tt>true</tt> it will not load the URL, but will use the
  33. * default instead.
  34. */
  35. forceDefault: React.PropTypes.bool,
  36. /**
  37. * The source the {@link Image}.
  38. */
  39. source: React.PropTypes.object,
  40. /**
  41. * The optional style to add to the {@link Image} in order to customize
  42. * its base look (and feel).
  43. */
  44. style: React.PropTypes.object
  45. };
  46. /**
  47. * Initializes new AvatarImage component.
  48. *
  49. * @param {Object} props - Component props.
  50. */
  51. constructor(props) {
  52. super(props);
  53. this.state = {
  54. failed: false,
  55. showDefault: false
  56. };
  57. this.componentWillReceiveProps(props);
  58. this._onError = this._onError.bind(this);
  59. }
  60. /**
  61. * Notifies this mounted React Component that it will receive new props.
  62. * If the URI is undefined, wait {@code UNDEFINED_AVATAR_TIMEOUT} ms and
  63. * start showing a default locally generated avatar afterwards.
  64. *
  65. * Once a URI is passed, it will be rendered instead, except if loading it
  66. * fails, in which case we fallback to a locally generated avatar again.
  67. *
  68. * @inheritdoc
  69. * @param {Object} nextProps - The read-only React Component props that this
  70. * instance will receive.
  71. * @returns {void}
  72. */
  73. componentWillReceiveProps(nextProps) {
  74. const prevSource = this.props.source;
  75. const prevURI = prevSource && prevSource.uri;
  76. const nextSource = nextProps.source;
  77. const nextURI = nextSource && nextSource.uri;
  78. if (typeof prevURI === 'undefined') {
  79. clearTimeout(this._timeout);
  80. if (typeof nextURI === 'undefined') {
  81. this._timeout
  82. = setTimeout(
  83. () => this.setState({ showDefault: true }),
  84. UNDEFINED_AVATAR_TIMEOUT);
  85. } else {
  86. this.setState({ showDefault: nextProps.forceDefault });
  87. }
  88. }
  89. }
  90. /**
  91. * Clear the timer just in case. See {@code componentWillReceiveProps} for
  92. * details.
  93. *
  94. * @inheritdoc
  95. */
  96. componentWillUnmount() {
  97. clearTimeout(this._timeout);
  98. }
  99. /**
  100. * Computes a hash over the URI and returns a HSL background color. We use
  101. * 75% as lightness, for nice pastel style colors.
  102. *
  103. * @private
  104. * @returns {string} - The HSL CSS property.
  105. */
  106. _getBackgroundColor() {
  107. const uri = this.props.source.uri;
  108. let hash = 0;
  109. // If we have no URI yet we have no data to hash from, so use a random
  110. // value.
  111. if (typeof uri === 'undefined') {
  112. hash = Math.floor(Math.random() * 360);
  113. } else {
  114. /* eslint-disable no-bitwise */
  115. for (let i = 0; i < uri.length; i++) {
  116. hash = uri.charCodeAt(i) + ((hash << 5) - hash);
  117. hash |= 0; // Convert to 32bit integer
  118. }
  119. /* eslint-enable no-bitwise */
  120. }
  121. return `hsl(${hash % 360}, 100%, 75%)`;
  122. }
  123. /**
  124. * Error handler for image loading. When an image fails to load we'll mark
  125. * it as failed and load the default URI instead.
  126. *
  127. * @private
  128. * @returns {void}
  129. */
  130. _onError() {
  131. this.setState({ failed: true });
  132. }
  133. /**
  134. * Implements React's {@link Component#render()}.
  135. *
  136. * @inheritdoc
  137. */
  138. render() {
  139. const { failed, showDefault } = this.state;
  140. const {
  141. // The following is/are forked in state:
  142. forceDefault, // eslint-disable-line no-unused-vars
  143. source,
  144. style,
  145. ...props
  146. } = this.props;
  147. if (failed || showDefault) {
  148. const coloredBackground = {
  149. ...style,
  150. backgroundColor: this._getBackgroundColor(),
  151. overflow: 'hidden'
  152. };
  153. // We need to wrap the Image in a View because of a bug in React
  154. // Native for Android:
  155. // https://github.com/facebook/react-native/issues/3198
  156. const workaround3198 = Platform.OS === 'android';
  157. let element = React.createElement(Image, {
  158. ...props,
  159. source: DEFAULT_AVATAR,
  160. style: workaround3198 ? style : coloredBackground
  161. });
  162. if (workaround3198) {
  163. element
  164. = React.createElement(
  165. View,
  166. { style: coloredBackground },
  167. element);
  168. }
  169. return element;
  170. } else if (typeof source.uri === 'undefined') {
  171. return null;
  172. }
  173. // We have a URI and it's time to render it.
  174. return (
  175. <Image
  176. { ...props }
  177. onError = { this._onError }
  178. source = { source }
  179. style = { style } />
  180. );
  181. }
  182. }