diff --git a/README.md b/README.md
index 9e3472d..37b5d22 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@ For all officially recommended clients, please visit http://bbs.uestc.edu.cn/for

-## Status (v1.5.2)
+## Status (v1.6.0)
[
](https://itunes.apple.com/cn/app/qing-shui-he-pan-stuhome/id1190564355)
@@ -44,6 +44,7 @@ For all officially recommended clients, please visit http://bbs.uestc.edu.cn/for
- [x] Favor topic
- [x] Upload images
- [x] Emoji
+ - [x] Show friend list to @
- [ ] Report objectionable content
- [ ] Vote
- [ ] Create vote
@@ -51,9 +52,10 @@ For all officially recommended clients, please visit http://bbs.uestc.edu.cn/for
- [x] View vote results
- [x] Search
- [x] Notifications
- - [x] View list mentioned(@) me
+ - [x] View list mentioned (@) me
- [x] View list replied me
- [x] View private messages
+ - [x] View system notifications
- [x] Notification alert
- [ ] Individual
- [x] View my recent topics
diff --git a/ios/stuhome/Info.plist b/ios/stuhome/Info.plist
index d362bf3..3da28ee 100644
--- a/ios/stuhome/Info.plist
+++ b/ios/stuhome/Info.plist
@@ -19,7 +19,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 1.5.2
+ 1.6.0
CFBundleSignature
????
CFBundleVersion
diff --git a/package-lock.json b/package-lock.json
index fd4a25d..af2e838 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "stuhome",
- "version": "1.5.2",
+ "version": "1.6.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -6034,10 +6034,15 @@
"react-native-scrollable-tab-view": "0.8.0"
}
},
+ "react-native-smart-timer-enhance": {
+ "version": "1.0.3",
+ "resolved": "http://registry.npm.taobao.org/react-native-smart-timer-enhance/download/react-native-smart-timer-enhance-1.0.3.tgz",
+ "integrity": "sha1-R93Bm+WmwZGERJA5SV8lVGvNlP4="
+ },
"react-native-sticky-keyboard-accessory": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/react-native-sticky-keyboard-accessory/-/react-native-sticky-keyboard-accessory-0.1.1.tgz",
- "integrity": "sha512-QgblDb4zyytv75enS+fvjTp9Bb01Rq6sItSy7YXJQIKHw0hXAZen/UoZhbdPwN0IrMe5moYPjfN4a0nnulHB6w==",
+ "integrity": "sha1-F2wpkyp+kptq8ULC1WUea5zrVH4=",
"requires": {
"react-native-iphone-x-helper": "1.0.2"
},
diff --git a/package.json b/package.json
index 70e8c69..d698310 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "stuhome",
- "version": "1.5.2",
+ "version": "1.6.0",
"description": "An iOS client for http://bbs.uestc.edu.cn/ written in React Native with Redux.",
"author": "just4fun ",
"scripts": {
@@ -34,6 +34,7 @@
"react-native-scrollable-tab-view": "0.8.0",
"react-native-search-bar": "3.1.0",
"react-native-smart-emoji-picker": "0.1.0",
+ "react-native-smart-timer-enhance": "^1.0.3",
"react-native-sticky-keyboard-accessory": "0.1.1",
"react-native-vector-icons": "4.4.3",
"react-navigation": "1.0.3",
diff --git a/src/actions/message/notifyListAction.js b/src/actions/message/notifyListAction.js
index ad59233..83f3ed7 100644
--- a/src/actions/message/notifyListAction.js
+++ b/src/actions/message/notifyListAction.js
@@ -4,11 +4,14 @@ export const REQUEST = Symbol();
export const INVALIDATE = Symbol();
export const MARK_AT_ME_AS_READ = Symbol();
export const MARK_REPLY_AS_READ = Symbol();
+export const MARK_SYSTEM_AS_READ = Symbol();
export const fetchNotifyList = createAction(REQUEST);
export const invalidateNotifyList = createAction(INVALIDATE);
const markAtMeAsRead = createAction(MARK_AT_ME_AS_READ);
const markReplyAsRead = createAction(MARK_REPLY_AS_READ);
+const markSystemAsRead = createAction(MARK_SYSTEM_AS_READ);
+
// Update unread message count immediately instead of
// clearing them with next poll after 0 ~ 15s.
export const successfulCallback = (payload) => {
@@ -17,6 +20,8 @@ export const successfulCallback = (payload) => {
return markAtMeAsRead();
case 'post':
return markReplyAsRead();
+ case 'system':
+ return markSystemAsRead();
}
}
diff --git a/src/actions/user/friendListAction.js b/src/actions/user/friendListAction.js
new file mode 100644
index 0000000..2cc13c5
--- /dev/null
+++ b/src/actions/user/friendListAction.js
@@ -0,0 +1,14 @@
+import { createAction } from 'redux-actions';
+
+export const REQUEST = Symbol();
+export const INVALIDATE = Symbol();
+export const fetchFriendList = createAction(REQUEST);
+export const invalidateFriendList = createAction(INVALIDATE);
+
+export const REQUEST_STARTED = Symbol();
+export const REQUEST_COMPELTED = Symbol();
+export const REQUEST_FAILED = Symbol();
+export const request = createAction(REQUEST_STARTED);
+// return 2nd argument as `meta` field
+export const success = createAction(REQUEST_COMPELTED, null, (...args) => args[1]);
+export const failure = createAction(REQUEST_FAILED);
diff --git a/src/components/3rd_party/LoadingSpinnerOverlay.js b/src/components/3rd_party/LoadingSpinnerOverlay.js
new file mode 100644
index 0000000..db655a5
--- /dev/null
+++ b/src/components/3rd_party/LoadingSpinnerOverlay.js
@@ -0,0 +1,210 @@
+import React, {
+ Component,
+} from 'react'
+import {
+ View,
+ Modal,
+ StyleSheet,
+ Animated,
+ Easing,
+ Dimensions,
+ ActivityIndicator,
+ ActivityIndicatorIOS,
+ ProgressBarAndroid,
+} from 'react-native'
+
+import TimerEnhance from 'react-native-smart-timer-enhance'
+
+const styles = StyleSheet.create({
+ overlay: {
+ position: 'absolute',
+ left: 0,
+ top: 0,
+ zIndex: 998,
+ },
+ container: {
+ backgroundColor: 'rgba(0, 0, 0, 0.8)',
+ position: 'absolute',
+ borderRadius: 8,
+ padding: 20.5,
+ left: -9999,
+ top: -9999,
+ zIndex: 999,
+ },
+})
+const noop = () => {}
+
+class LoadingSpinnerOverlay extends Component {
+
+ static defaultProps = {
+ duration: 255,
+ delay: 0,
+ marginTop: 0,
+ modal: true,
+ }
+
+ constructor(props) {
+ super(props)
+ this.state = {
+ visible: false,
+ opacity: new Animated.Value(0),
+ children: props.children,
+ modal: props.modal,
+ marginTop: props.marginTop,
+ }
+ this._loadingSpinnerWidth = null
+ this._loadingSpinnerHeight = null
+ this._loadingSpinnerShowAnimation = null
+ this._loadingSpinnerHideAnimation = null
+ this._loadingSpinnerAnimationToggle = null
+ }
+
+ render() {
+ let loadingSpinner = this._renderLoadingSpinner()
+ return this._renderOverLay(loadingSpinner)
+ }
+
+ _renderOverLay(loadingSpinner) {
+ let {width: deviceWidth, height: deviceHeight,} = Dimensions.get('window')
+ return (
+ this.state.modal ?
+ (this.state.marginTop === 0 ?
+
+ {loadingSpinner}
+ :
+ (this.state.visible ?
+
+ {loadingSpinner}
+ : null))
+ : loadingSpinner
+ )
+ }
+
+ _renderLoadingSpinner() {
+ let children
+ if(this.state.children == null) {
+ children = this._renderActivityIndicator()
+ }
+ else {
+ children = React.Children.map(this.state.children, (child) => {
+ if (!React.isValidElement(child)) {
+ return null
+ }
+ return child
+ })
+ }
+ return (
+ this.state.visible ?
+ this._container = component }
+ onLayout={this._onLoadingSpinnerLayout}
+ style={[styles.container, this.props.style, {opacity:this.state.opacity, }]}>
+ {children}
+ : null
+ )
+ }
+
+ show({modal = this.state.modal, marginTop = this.state.marginTop, children = this.state.children, duration = this.props.duration, easing = Easing.linear, delay = this.props.delay, animationEnd,}
+ = {modal: this.state.modal, marginTop: this.state.marginTop, children: this.state.children, duration: this.props.duration, easing: Easing.linear, delay: this.props.delay,}) {
+
+ this._loadingSpinnerShowAnimation && this._loadingSpinnerShowAnimation.stop()
+ this._loadingSpinnerHideAnimation && this._loadingSpinnerHideAnimation.stop()
+ this._loadingSpinnerAnimationToggle && this.clearTimeout(this._loadingSpinnerAnimationToggle)
+
+ if(this.state.visible) {
+ this._setLoadingSpinnerOverlayPosition({modal, marginTop})
+ }
+
+ this.setState({
+ children,
+ modal,
+ marginTop,
+ visible: true,
+ })
+
+ this._loadingSpinnerShowAnimation = Animated.timing(
+ this.state.opacity,
+ {
+ toValue: 1,
+ duration,
+ easing,
+ delay,
+ }
+ )
+ this._loadingSpinnerShowAnimation.start(() => {
+ this._loadingSpinnerShowAnimation = null
+ animationEnd && animationEnd()
+ })
+ }
+
+ hide({duration = this.props.duration, easing = Easing.linear, delay = this.props.delay, animationEnd,}
+ = {duration: this.props.duration, easing: Easing.linear, delay: this.props.delay,}) {
+
+ this._loadingSpinnerShowAnimation && this._loadingSpinnerShowAnimation.stop()
+ this._loadingSpinnerHideAnimation && this._loadingSpinnerHideAnimation.stop()
+ this.clearTimeout(this._loadingSpinnerAnimationToggle)
+
+ this._loadingSpinnerHideAnimation = Animated.timing(
+ this.state.opacity,
+ {
+ toValue: 0,
+ duration,
+ easing,
+ delay,
+ }
+ )
+ this._loadingSpinnerHideAnimation.start( () => {
+ this._loadingSpinnerHideAnimation = null
+ this.setState({
+ visible: false,
+ })
+ animationEnd && animationEnd()
+ })
+ }
+
+ _onLoadingSpinnerLayout = (e) => {
+ this._loadingSpinnerWidth = e.nativeEvent.layout.width
+ this._loadingSpinnerHeight = e.nativeEvent.layout.height
+ this._setLoadingSpinnerOverlayPosition()
+ }
+
+ _setLoadingSpinnerOverlayPosition({modal, marginTop} = {modal: this.state.modal, marginTop: this.state.marginTop}) {
+ if(!this._loadingSpinnerWidth || !this._loadingSpinnerHeight) {
+ return
+ }
+ let {width: deviceWidth, height: deviceHeight,} = Dimensions.get('window')
+ let left = (deviceWidth - this._loadingSpinnerWidth) / 2
+ let top = (deviceHeight - this._loadingSpinnerHeight) / 2 - (modal && marginTop === 0 ? 0 : marginTop)
+ this._container.setNativeProps({
+ style: {
+ left,
+ top,
+ }
+ })
+ }
+
+ _renderActivityIndicator() {
+ return ActivityIndicator ? (
+
+ ) : Platform.OS == 'android' ?
+ (
+
+
+ ) : (
+
+ )
+ }
+
+}
+
+export default TimerEnhance(LoadingSpinnerOverlay)
diff --git a/src/components/Comment.js b/src/components/Comment.js
index 53ed4cf..337b07c 100644
--- a/src/components/Comment.js
+++ b/src/components/Comment.js
@@ -35,9 +35,13 @@ export default class Comment extends Component {
'复制'
];
let isLoginUser = currentUserId === userId;
- if (!isLoginUser) {
+ // If userId is 0, it's anonymous user.
+ let canSendPrivateMessage = !isLoginUser && userId !== 0;
+ let editable =
+ isLoginUser && managePanel && managePanel.length > 0 && !!managePanel.find(item => item.title === '编辑');
+ if (canSendPrivateMessage) {
options.push('私信');
- } else {
+ } else if (editable) {
options.push('编辑');
}
options.push('取消');
@@ -70,13 +74,11 @@ export default class Comment extends Component {
});
break;
case 2:
- if (!isLoginUser) {
+ if (canSendPrivateMessage) {
navigation.navigate('PrivateMessage', { userId });
- } else if (managePanel && managePanel.length > 0) {
+ } else if (editable) {
let editAction = managePanel.find(item => item.title === '编辑');
- if (editAction) {
- SafariView.show(editAction.action);
- }
+ SafariView.show(editAction.action);
}
break;
}
diff --git a/src/components/FriendItem.js b/src/components/FriendItem.js
new file mode 100644
index 0000000..55b6766
--- /dev/null
+++ b/src/components/FriendItem.js
@@ -0,0 +1,46 @@
+import React, { Component } from 'react';
+import {
+ View,
+ Text,
+ TouchableHighlight
+} from 'react-native';
+import moment from 'moment';
+import Avatar from './Avatar';
+import { AVATAR_ROOT } from '../config';
+import colors from '../styles/common/_colors';
+import styles from '../styles/components/_FriendItem';
+
+export default class FriendItem extends Component {
+ render() {
+ let {
+ navigation,
+ currentUserId,
+ friend,
+ friend: {
+ name,
+ uid
+ }
+ } = this.props;
+ let avatar = `${AVATAR_ROOT}&uid=${uid}`;
+
+ return (
+ this.props.handleSelectFriend(friend)}>
+
+
+
+ {name}
+
+
+
+ );
+ }
+}
diff --git a/src/components/FriendList.js b/src/components/FriendList.js
new file mode 100644
index 0000000..3908ac9
--- /dev/null
+++ b/src/components/FriendList.js
@@ -0,0 +1,100 @@
+import React, { Component } from 'react';
+import {
+ View,
+ Text,
+ FlatList,
+ RefreshControl,
+ ActivityIndicator
+} from 'react-native';
+import listStyles from '../styles/common/_List';
+import indicatorStyles from '../styles/common/_Indicator';
+import FriendItem from './FriendItem';
+
+export default class FriendList extends Component {
+ endReached() {
+ let {
+ hasMore,
+ isRefreshing,
+ isEndReached,
+ page,
+ list
+ } = this.props.friendList;
+
+ if (!hasMore || isRefreshing || isEndReached) { return; }
+
+ this.props.refreshFriendList({
+ page: page + 1,
+ isEndReached: true
+ });
+ }
+
+ renderFooter() {
+ let {
+ hasMore,
+ isEndReached
+ } = this.props.friendList;
+
+ if (!hasMore || !isEndReached) { return ; }
+
+ return (
+
+
+
+ );
+ }
+
+ renderListEmptyComponent() {
+ return (
+
+
+ 暂无好友
+
+
+ );
+ }
+
+ render() {
+ let {
+ friendList,
+ navigation,
+ refreshFriendList,
+ currentUserId,
+ handleSelectFriend
+ } = this.props;
+ let realFriendList = [];
+ let isRefreshing = false;
+
+ if (friendList.list) {
+ realFriendList = friendList.list;
+ isRefreshing = friendList.isRefreshing;
+ };
+
+ return (
+ index}
+ removeClippedSubviews={false}
+ enableEmptySections={true}
+ renderItem={({ item: friend }) => {
+ return (
+
+ );
+ }}
+ onEndReached={() => this.endReached()}
+ onEndReachedThreshold={0}
+ ListFooterComponent={() => this.renderFooter()}
+ ListEmptyComponent={() => !isRefreshing && this.renderListEmptyComponent()}
+ refreshControl={
+ refreshFriendList({ page: 1 })}
+ refreshing={isRefreshing} />
+ } />
+ );
+ }
+}
diff --git a/src/components/ImageUploader.js b/src/components/ImageUploader.js
index dc989fa..7c2c684 100644
--- a/src/components/ImageUploader.js
+++ b/src/components/ImageUploader.js
@@ -23,7 +23,6 @@ export default class ImageUploader extends Component {
handleUploaderPress() {
if (this.props.disabled) { return; }
-
let takePhotoOptions = {
compressImageQuality: 0.5,
loadingLabelText: '处理中...'
diff --git a/src/components/LoadingSpinner.js b/src/components/LoadingSpinner.js
new file mode 100644
index 0000000..8ec53f9
--- /dev/null
+++ b/src/components/LoadingSpinner.js
@@ -0,0 +1,26 @@
+import React, { Component } from 'react';
+import {
+ View,
+ Text,
+ ActivityIndicator
+} from 'react-native';
+import mainStyles from '../styles/components/_Main';
+import indicatorStyles from '../styles/common/_Indicator';
+import styles from '../styles/components/_LoadingSpinner';
+import TIPS from '../constants/funnyTips';
+
+export default class LoadingSpinner extends Component {
+ render() {
+ let { text } = this.props;
+ return (
+
+
+
+
+ {text || TIPS[Math.floor(Math.random() * TIPS.length)]}
+
+
+
+ );
+ }
+}
diff --git a/src/components/NotifyList.js b/src/components/NotifyList.js
index 3e88060..493d0fb 100644
--- a/src/components/NotifyList.js
+++ b/src/components/NotifyList.js
@@ -9,6 +9,7 @@ import {
import listStyles from '../styles/common/_List';
import indicatorStyles from '../styles/common/_Indicator';
import NotifyItem from './NotifyItem';
+import NotifySystemItem from './NotifySystemItem';
export default class NotifyList extends Component {
componentDidMount() {
@@ -78,7 +79,15 @@ export default class NotifyList extends Component {
keyExtractor={(item, index) => index}
removeClippedSubviews={false}
enableEmptySections={true}
- renderItem={({ item: notification }) => {
+ renderItem={({ item: notification, index }) => {
+ if (notification.type === 'system') {
+ return (
+
+ );
+ }
+
return (
+
+
+
+
+ {'系统'}
+ {dateline}
+
+
+ {note}
+
+
+ );
+ }
+}
diff --git a/src/components/PmSessionList.js b/src/components/PmSessionList.js
index 841eaeb..8c4b283 100644
--- a/src/components/PmSessionList.js
+++ b/src/components/PmSessionList.js
@@ -12,7 +12,7 @@ import PmSessionItem from './PmSessionItem';
export default class PrivateList extends Component {
componentDidMount() {
- this.props.fetchPmSessionList();
+ this.props.fetchPmSessionList({ page: 1 });
}
endReached() {
diff --git a/src/components/ProgressImage.js b/src/components/ProgressImage.js
index 4414458..7d98ac6 100644
--- a/src/components/ProgressImage.js
+++ b/src/components/ProgressImage.js
@@ -2,6 +2,7 @@ import React, { Component } from 'react';
import {
View,
Image,
+ Text,
ActivityIndicator,
TouchableHighlight
} from 'react-native';
@@ -25,7 +26,8 @@ export default class ProgressImage extends Component {
super(props);
this.state = {
- isLoading: false
+ isLoading: false,
+ isLoadError: false
};
}
@@ -70,22 +72,35 @@ export default class ProgressImage extends Component {
render() {
let { style, thumbUri, originalUri } = this.props;
- let { isLoading } = this.state;
+ let { isLoading, isLoadError } = this.state;
return (
SafariView.show(originalUri)}>
- this.setState({ isLoading: true })}
- onLoadEnd={() => this.setState({ isLoading: false })}
- resizeMode={'contain'}
- // onLayout={event => this.handleLayout(event)}
- style={[styles.image, style]} />
- {isLoading && }
+ {isLoadError &&
+
+
+ 图片加载失败或图片已失效
+
+ ||
+
+ this.setState({ isLoading: true })}
+ onLoadEnd={() => this.setState({ isLoading: false })}
+ onError={() => this.setState({ isLoadError: true })}
+ resizeMode={'contain'}
+ // onLayout={event => this.handleLayout(event)}
+ style={[styles.image, style]} />
+ {isLoading && }
+
+ }
);
diff --git a/src/components/modal/ForumListModal.js b/src/components/modal/ForumListModal.js
index f9129ac..94059c2 100644
--- a/src/components/modal/ForumListModal.js
+++ b/src/components/modal/ForumListModal.js
@@ -1,8 +1,7 @@
import React, { Component } from 'react';
import {
View,
- Text,
- ActivityIndicator
+ Text
} from 'react-native';
import { connect } from 'react-redux';
import _ from 'lodash';
diff --git a/src/components/modal/FriendListModal.js b/src/components/modal/FriendListModal.js
new file mode 100644
index 0000000..6727b05
--- /dev/null
+++ b/src/components/modal/FriendListModal.js
@@ -0,0 +1,68 @@
+import React, { Component } from 'react';
+import {
+ View,
+ Text
+} from 'react-native';
+import { connect } from 'react-redux';
+import _ from 'lodash';
+import FriendList from '../FriendList';
+import mainStyles from '../../styles/components/_Main';
+import modalStyles from '../../styles/common/_Modal';
+import Header from '../Header';
+import { invalidateFriendList, fetchFriendList } from '../../actions/user/friendListAction';
+
+class FriendListModal extends Component {
+ constructor(props) {
+ super(props);
+ this.title = '选择好友';
+ }
+
+ componentDidMount() {
+ this.fetchFriendList();
+ }
+
+ fetchFriendList(options = {}) {
+ this.props.fetchFriendList(options);
+ }
+
+ refreshFriendList(options) {
+ this.props.invalidateFriendList();
+ this.fetchFriendList(options);
+ }
+
+ handleSelectFriend(friend) {
+ // Invoke callback to set @friend in TextInput.
+ this.props.navigation.state.params.callback(friend);
+ // Close modal.
+ this.props.navigation.goBack();
+ }
+
+ render() {
+ let { friendList, navigation } = this.props;
+
+ return (
+
+
+ this.handleSelectFriend()}>取消
+
+ this.handleSelectFriend(friend)}
+ refreshFriendList={(options) => this.refreshFriendList(options)} />
+
+ );
+ }
+}
+
+function mapStateToProps({ friendList }) {
+ return {
+ friendList
+ };
+}
+
+export default connect(mapStateToProps, {
+ invalidateFriendList,
+ fetchFriendList
+})(FriendListModal);
diff --git a/src/components/modal/LoginModal.js b/src/components/modal/LoginModal.js
index c0e498b..a7b902a 100644
--- a/src/components/modal/LoginModal.js
+++ b/src/components/modal/LoginModal.js
@@ -18,7 +18,8 @@ import Button from 'apsl-react-native-button';
import mainStyles from '../../styles/components/_Main';
import styles from '../../styles/components/modal/_LoginModal';
import Header from '../Header';
-import RegisterModal from './RegisterModal';
+import SafariView from '../../services/SafariView';
+import { REGISTER_URL } from '../../config';
import {
userLogin,
resetAuthrization,
@@ -100,7 +101,7 @@ class LoginModal extends Component {
navigation.navigate('RegisterModal')}>
+ onPress={() => SafariView.show(REGISTER_URL)}>
注册
diff --git a/src/components/modal/PublishModal.js b/src/components/modal/PublishModal.js
index 5b3a498..ee1b1ad 100644
--- a/src/components/modal/PublishModal.js
+++ b/src/components/modal/PublishModal.js
@@ -10,6 +10,7 @@ import {
ActivityIndicator,
LayoutAnimation
} from 'react-native';
+import LoadingSpinnerOverlay from '../3rd_party/LoadingSpinnerOverlay';
import { isIphoneX } from 'react-native-iphone-x-helper';
import { connect } from 'react-redux';
import { NavigationActions } from 'react-navigation';
@@ -126,7 +127,8 @@ class PublishModal extends Component {
// Hide keyboard.
this.hideKeyboard();
- this.setState({ isPublishing: true });
+ // this.setState({ isPublishing: true });
+ this.modalLoadingSpinnerOverLay.show();
api.uploadImages(this.state.images).then(data => {
let { typeId, title, content } = this.state;
return api.publishTopic({
@@ -157,7 +159,8 @@ class PublishModal extends Component {
}
}
}).finally(() => {
- this.setState({ isPublishing: false });
+ // this.setState({ isPublishing: false });
+ this.modalLoadingSpinnerOverLay.hide();
});
}
@@ -174,10 +177,12 @@ class PublishModal extends Component {
this.setState({ selectedPanel: item });
}
- handleEmojiPress = (emoji) => {
+ handleExtraContentPress = (extraContent) => {
+ if (this.state.isPublishing) { return; }
+
this.setState((prevState) => {
let newContent = prevState.content.substr(0, this.contentCursorLocation)
- + emoji.code
+ + extraContent
+ prevState.content.substr(this.contentCursorLocation);
return { content: newContent };
});
@@ -240,6 +245,17 @@ class PublishModal extends Component {
}
}
+ showFriendList() {
+ if (this.state.isPublishing) { return; }
+
+ this.props.navigation.navigate('FriendListModal', {
+ callback: (friend) => {
+ friend && this.handleExtraContentPress(`@${friend.name} `);
+ this.showKeyboard();
+ }
+ });
+ }
+
render() {
let {
typeId,
@@ -263,11 +279,18 @@ class PublishModal extends Component {
setSelection={typeId => this.setState({ typeId })} />
}
- this.handleCancel()}>
- 取消
-
+ {isPublishing &&
+
+ 取消
+
+ ||
+ this.handleCancel()}>
+ 取消
+
+ }
{this.isFormValid() &&
(isPublishing &&
@@ -375,6 +398,13 @@ class PublishModal extends Component {
onPress={() => this.handlePanelSelect('emoji')} />
)
}
+ {!isTitleFocused &&
+ this.showFriendList()} />
+ }
+ onEmojiPress={(emoji) => this.handleExtraContentPress(emoji.code)} />
+ this.modalLoadingSpinnerOverLay = component }/>
);
}
diff --git a/src/components/modal/RegisterModal.js b/src/components/modal/RegisterModal.js
deleted file mode 100644
index a95ba67..0000000
--- a/src/components/modal/RegisterModal.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import React, { Component } from 'react';
-import { View } from 'react-native';
-import WebPage from '../../containers/WebPage';
-import Header from '../../components/Header';
-import { PopButton } from '../../components/button';
-import { REGISTER_URL } from '../../config';
-import mainStyles from '../../styles/components/_Main';
-
-export default class RegisterModal extends Component {
- render() {
- let { navigation } = this.props;
- return (
-
-
-
-
- );
- }
-}
diff --git a/src/components/modal/ReplyModal.js b/src/components/modal/ReplyModal.js
index bf43965..714d5a1 100644
--- a/src/components/modal/ReplyModal.js
+++ b/src/components/modal/ReplyModal.js
@@ -10,6 +10,7 @@ import {
TouchableHighlight,
ActivityIndicator
} from 'react-native';
+import LoadingSpinnerOverlay from '../3rd_party/LoadingSpinnerOverlay';
import Icon from 'react-native-vector-icons/FontAwesome';
import KeyboardAccessory from 'react-native-sticky-keyboard-accessory';
import EmojiPicker from 'react-native-smart-emoji-picker';
@@ -119,7 +120,8 @@ class ReplyModal extends Component {
// Hide keyboard.
this.hideKeyboard();
- this.setState({ isPublishing: true });
+ // this.setState({ isPublishing: true });
+ this.modalLoadingSpinnerOverLay.show();
api.uploadImages(this.state.images).then(data => {
// Actually there is no need to pass `boardId` when we
// reply a topic.
@@ -151,7 +153,8 @@ class ReplyModal extends Component {
}
}
}).finally(() => {
- this.setState({ isPublishing: false });
+ // this.setState({ isPublishing: false });
+ this.modalLoadingSpinnerOverLay.hide();
});
}
@@ -180,10 +183,12 @@ class ReplyModal extends Component {
this.setState({ selectedPanel: item });
}
- handleEmojiPress = (emoji) => {
+ handleExtraContentPress = (extraContent) => {
+ if (this.state.isPublishing) { return; }
+
this.setState((prevState) => {
let newContent = prevState.replyContent.substr(0, this.contentCursorLocation)
- + emoji.code
+ + extraContent
+ prevState.replyContent.substr(this.contentCursorLocation);
return { replyContent: newContent };
});
@@ -218,6 +223,17 @@ class ReplyModal extends Component {
}
}
+ showFriendList() {
+ if (this.state.isPublishing) { return; }
+
+ this.props.navigation.navigate('FriendListModal', {
+ callback: (friend) => {
+ friend && this.handleExtraContentPress(`@${friend.name} `);
+ this.showKeyboard();
+ }
+ });
+ }
+
render() {
let {
replyContent,
@@ -229,11 +245,18 @@ class ReplyModal extends Component {
return (
- this.handleCancel()}>
- 取消
-
+ {isPublishing &&
+
+ 取消
+
+ ||
+ this.handleCancel()}>
+ 取消
+
+ }
{replyContent.length &&
(isPublishing &&
@@ -295,6 +318,11 @@ class ReplyModal extends Component {
size={30}
onPress={() => this.handlePanelSelect('emoji')} />
}
+ this.showFriendList()} />
+ onEmojiPress={(emoji) => this.handleExtraContentPress(emoji.code)} />
+ this.modalLoadingSpinnerOverLay = component }/>
);
}
diff --git a/src/config.js b/src/config.js
index 2555ae9..616781d 100644
--- a/src/config.js
+++ b/src/config.js
@@ -21,7 +21,7 @@ module.exports = {
MAX_UPLOAD_IMAGES_COUNT: 9,
- VERSION: 'v1.5.2',
+ VERSION: 'v1.6.0',
AUTHOR_ID: 32044,
diff --git a/src/constants/funnyTips.js b/src/constants/funnyTips.js
new file mode 100644
index 0000000..e749eb2
--- /dev/null
+++ b/src/constants/funnyTips.js
@@ -0,0 +1,12 @@
+export default [
+ '这款 APP 的大部分程序是在星巴克完成的',
+ '正在前往沙河',
+ '少刷河畔,多看看世界',
+ '正在前往顺江',
+ '有建议?在关于页面可以直接私信作者',
+ '你沉迷河畔的时候室友已经带女友出去嗨了',
+ '正在前往皮卡多',
+ '正在前往军事基地',
+ '年轻人,不要着急',
+ '楼主正在吃鸡,等 TA 一会儿'
+];
diff --git a/src/containers/Information.js b/src/containers/Information.js
index 1c874a7..49438fc 100644
--- a/src/containers/Information.js
+++ b/src/containers/Information.js
@@ -1,15 +1,13 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
-import {
- View,
- ActivityIndicator
-} from 'react-native';
+import { View } from 'react-native';
import _ from 'lodash';
import Icon from 'react-native-vector-icons/FontAwesome';
import ImagePicker from '../services/ImagePicker';
import { setAuthrization } from '../actions/authorizeAction';
import menus from '../constants/menus';
import SettingItem from '../components/SettingItem';
+import LoadingSpinner from '../components/LoadingSpinner';
import mainStyles from '../styles/components/_Main';
import indicatorStyles from '../styles/common/_Indicator';
import styles from '../styles/containers/_About';
@@ -82,11 +80,7 @@ class Information extends Component {
if (isFetching || !_.get(userItem, ['user', 'name'])) {
return (
-
-
-
-
-
+
);
}
diff --git a/src/containers/Message.js b/src/containers/Message.js
index 8ec9f44..a919fea 100644
--- a/src/containers/Message.js
+++ b/src/containers/Message.js
@@ -13,12 +13,13 @@ import ReplyModal from '../components/modal/ReplyModal';
import menus from '../constants/menus';
import { invalidateNotifyList, fetchNotifyList } from '../actions/message/notifyListAction';
import { invalidatePmSessionList, fetchPmSessionList, markPmAsRead } from '../actions/message/pmSessionListAction';
-import { getAtMeCount, getReplyCount, getPmCount } from '../selectors/alert';
+import { getAtMeCount, getReplyCount, getPmCount, getSystemCount } from '../selectors/alert';
const TABS = [
{ label: '@', type: 'at' },
{ label: '回复', type: 'post' },
- { label: '私信', type: 'private' }
+ { label: '私信', type: 'private' },
+ { label: '系统提醒', type: 'system' }
];
class Message extends Component {
@@ -62,10 +63,11 @@ class Message extends Component {
// for each tab of component.
getTabsWithAlertCount(tabs) {
let newTabs = [];
- let { atMeCount, replyCount, pmCount } = this.props;
+ let { atMeCount, replyCount, pmCount, systemCount } = this.props;
newTabs.push({ name: tabs[0], count: atMeCount });
newTabs.push({ name: tabs[1], count: replyCount });
newTabs.push({ name: tabs[2], count: pmCount });
+ newTabs.push({ name: tabs[3], count: systemCount });
return newTabs;
}
@@ -97,7 +99,7 @@ class Message extends Component {
navigation={navigation}
currentUserId={userId}
markPmAsRead={({ plid }) => this.props.markPmAsRead({ plid })}
- fetchPmSessionList={() => this.fetchPmSessionList({})}
+ fetchPmSessionList={({ page }) => this.fetchPmSessionList({ page })}
refreshPmSessionList={({ page, isEndReached }) => this.refreshPmSessionList({ page, isEndReached })} />
);
}
@@ -126,6 +128,7 @@ function mapStateToProps({ notifyList, pmSessionList, alert, user }) {
atMeCount: getAtMeCount(alert),
replyCount: getReplyCount(alert),
pmCount: getPmCount(alert),
+ systemCount: getSystemCount(alert),
userId: _.get(user, ['authrization', 'uid'])
};
}
diff --git a/src/containers/Navigator.js b/src/containers/Navigator.js
index 867364c..c68b1dc 100644
--- a/src/containers/Navigator.js
+++ b/src/containers/Navigator.js
@@ -17,10 +17,10 @@ import InformationScreen from './Information';
import SettingsScreen from './Settings';
import WebPageScreen from './WebPage';
import LoginModalScreen from '../components/modal/LoginModal';
-import RegisterModalScreen from '../components/modal/RegisterModal';
import PublishModalScreen from '../components/modal/PublishModal';
import ReplyModalScreen from '../components/modal/ReplyModal';
import ForumListModalScreen from '../components/modal/ForumListModal';
+import FriendListModalScreen from '../components/modal/FriendListModal';
import colors from '../styles/common/_colors';
import { getUserFromStorage } from '../actions/authorizeAction';
import { getSettingsFromStorage } from '../actions/settingsAction';
@@ -83,9 +83,6 @@ const AppNavigator = DrawerNavigator({
LoginModal: {
screen: LoginModalScreen
},
- RegisterModal: {
- screen: RegisterModalScreen
- },
PublishModal: {
screen: PublishModalScreen
},
@@ -94,6 +91,9 @@ const AppNavigator = DrawerNavigator({
},
ForumListModal: {
screen: ForumListModalScreen
+ },
+ FriendListModal: {
+ screen: FriendListModalScreen
}
}, {
// Without `headerMode: 'none'`, there will be two headers since there are two
diff --git a/src/containers/PmList.js b/src/containers/PmList.js
index 95ea105..4dac870 100644
--- a/src/containers/PmList.js
+++ b/src/containers/PmList.js
@@ -5,9 +5,9 @@ import {
Text,
Image,
AlertIOS,
- ScrollView,
- ActivityIndicator
+ ScrollView
} from 'react-native';
+import LoadingSpinner from '../components/LoadingSpinner';
import { GiftedChat } from 'react-native-gifted-chat';
import GiftedChatSendButton from '../components/3rd_party/GiftedChatSendButton';
import GiftedChatLoadEarlierButton from '../components/3rd_party/GiftedChatLoadEarlierButton';
@@ -21,7 +21,6 @@ import {
resetPmListResponseStatus
} from '../actions/message/pmListAction';
import mainStyles from '../styles/components/_Main';
-import indicatorStyles from '../styles/common/_Indicator';
import styles from '../styles/containers/_PmList';
import { PRIVATE_MESSAGE_POLL_FREQUENCY } from '../config';
@@ -157,11 +156,7 @@ class PmList extends Component {
if (isRefreshing && page === 0) {
return (
-
-
-
-
-
+
);
}
diff --git a/src/containers/Search.js b/src/containers/Search.js
index b8542c5..f493e14 100644
--- a/src/containers/Search.js
+++ b/src/containers/Search.js
@@ -1,14 +1,14 @@
import React, { Component } from 'react';
import {
View,
- AlertIOS,
- ActivityIndicator
+ AlertIOS
} from 'react-native';
import { connect } from 'react-redux';
import _ from 'lodash';
import mainStyles from '../styles/components/_Main';
import indicatorStyles from '../styles/common/_Indicator';
import TopicList from '../components/TopicList';
+import LoadingSpinner from '../components/LoadingSpinner';
import SearchBar from 'react-native-search-bar';
import menus from '../constants/menus';
import { fetchSearch, resetSearch } from '../actions/topic/searchAction';
@@ -92,9 +92,7 @@ class Search extends Component {
onSearchButtonPress={() => this.handleSearch()}
onCancelButtonPress={() => this.getSearchBarBlur()} />
{search.isRefreshing && (
-
-
-
+
) || (
this.searchList = component}
diff --git a/src/containers/TopicDetail.js b/src/containers/TopicDetail.js
index 0029b8c..3fe6659 100644
--- a/src/containers/TopicDetail.js
+++ b/src/containers/TopicDetail.js
@@ -27,6 +27,7 @@ import Comment from '../components/Comment';
import Content from '../components/Content';
import VoteList from '../components/VoteList';
import RewardList from '../components/RewardList';
+import LoadingSpinner from '../components/LoadingSpinner';
import MessageBar from '../services/MessageBar';
import SafariView from '../services/SafariView';
import colors from '../styles/common/_colors';
@@ -175,7 +176,7 @@ class TopicDetail extends Component {
authrization: { uid }
}
} = this.props;
- let { isFavoring } = this.state;
+ let { isFavoring, isVoting } = this.state;
let create_date = moment(+topic.create_date).startOf('minute').fromNow();
let commentHeaderText =
topic.replies > 0 ? (topic.replies + '条评论') : '还没有评论,快来抢沙发!';
@@ -242,6 +243,7 @@ class TopicDetail extends Component {
{topic.poll_info &&
this.publishVote(voteIds)} />
}
@@ -328,7 +330,9 @@ class TopicDetail extends Component {
}
} = this.props;
let isLoginUser = uid === user_id;
- if (isLoginUser) {
+ let editable =
+ isLoginUser && managePanel && managePanel.length > 0 && !!managePanel.find(item => item.title === '编辑');
+ if (editable) {
options.push('编辑帖子');
}
options.push('取消');
@@ -366,11 +370,9 @@ class TopicDetail extends Component {
});
break;
case 5:
- if (isLoginUser && managePanel && managePanel.length > 0) {
+ if (editable) {
let editAction = managePanel.find(item => item.title === '编辑');
- if (editAction) {
- SafariView.show(editAction.action);
- }
+ SafariView.show(editAction.action);
}
break;
}
@@ -378,28 +380,11 @@ class TopicDetail extends Component {
}
render() {
- let {
- topicItem,
- user: {
- authrization: { uid }
- },
- navigation
- } = this.props;
+ let { topicItem } = this.props;
- if (topicItem.isFetching) {
+ if (topicItem.isFetching || !_.get(topicItem, ['topic', 'topic_id'])) {
return (
-
-
-
-
-
- );
- }
-
- if (!_.get(topicItem, ['topic', 'topic_id'])) {
- return (
-
-
+
);
}
@@ -410,6 +395,10 @@ class TopicDetail extends Component {
topic_id,
replies
}
+ },
+ navigation,
+ user: {
+ authrization: { uid }
}
} = this.props;
diff --git a/src/images/image_fail.png b/src/images/image_fail.png
new file mode 100644
index 0000000..931e8c8
Binary files /dev/null and b/src/images/image_fail.png differ
diff --git a/src/reducers/index.js b/src/reducers/index.js
index 1f294d4..72257f6 100644
--- a/src/reducers/index.js
+++ b/src/reducers/index.js
@@ -12,6 +12,7 @@ import send from './message/send';
import alert from './message/alert';
import settings from './settings';
import userItem from './user/userItem';
+import friendList from './user/friendList';
export default combineReducers({
forumList,
@@ -26,6 +27,7 @@ export default combineReducers({
userItem,
topicItem,
search,
+ friendList,
userTopicList,
diff --git a/src/reducers/message/alert.js b/src/reducers/message/alert.js
index 005d27d..66f4d5d 100644
--- a/src/reducers/message/alert.js
+++ b/src/reducers/message/alert.js
@@ -7,7 +7,8 @@ import {
} from '../../actions/message/alertAction';
import {
MARK_AT_ME_AS_READ,
- MARK_REPLY_AS_READ
+ MARK_REPLY_AS_READ,
+ MARK_SYSTEM_AS_READ
} from '../../actions/message/notifyListAction';
import { MARK_PM_AS_READ } from '../../actions/message/pmSessionListAction';
import { REMOVE_CACHE } from '../../actions/authorizeAction';
@@ -17,7 +18,8 @@ const defaultAlertState = {
response: {
atMeInfo: { count: 0 },
replyInfo: { count: 0 },
- pmInfos: []
+ pmInfos: [],
+ systemInfo: { count: 0 }
}
};
@@ -42,55 +44,42 @@ export default function alert(state = defaultAlertState, action) {
};
}
case MARK_AT_ME_AS_READ: {
- let {
- response: {
- replyInfo,
- pmInfos
- }
- } = state;
return {
...state,
response: {
- atMeInfo: { count: 0 },
- // Keep another information.
- replyInfo,
- pmInfos
+ ...state.response,
+ atMeInfo: { count: 0 }
}
};
}
case MARK_REPLY_AS_READ: {
- let {
- response: {
- atMeInfo,
- pmInfos
- }
- } = state;
return {
...state,
response: {
- atMeInfo,
+ ...state.response,
replyInfo: { count: 0 },
- pmInfos
}
};
}
case MARK_PM_AS_READ: {
- let {
- response: {
- atMeInfo,
- replyInfo
- }
- } = state;
let { plid } = action.payload;
return {
...state,
response: {
- atMeInfo,
- replyInfo,
+ ...state.response,
pmInfos: state.response.pmInfos.filter(item => item.plid !== plid)
}
};
}
+ case MARK_SYSTEM_AS_READ: {
+ return {
+ ...state,
+ response: {
+ ...state.response,
+ systemInfo: { count: 0 },
+ }
+ };
+ }
case RESET:
case REMOVE_CACHE:
return defaultAlertState;
diff --git a/src/reducers/message/notifyList.js b/src/reducers/message/notifyList.js
index ecd0b28..dc0aa2d 100644
--- a/src/reducers/message/notifyList.js
+++ b/src/reducers/message/notifyList.js
@@ -53,6 +53,11 @@ export default function notifyList(state = defaultState, action) {
}
} = action;
+ // `list` is `[]` for `system` type.
+ if (notifyType === 'system') {
+ notifyList.list = notifyList.body.data;
+ }
+
return {
...state,
[notifyType]: {
diff --git a/src/reducers/user/friendList.js b/src/reducers/user/friendList.js
new file mode 100644
index 0000000..04d1679
--- /dev/null
+++ b/src/reducers/user/friendList.js
@@ -0,0 +1,63 @@
+import {
+ REQUEST_STARTED,
+ REQUEST_COMPELTED,
+ REQUEST_FAILED,
+ INVALIDATE
+} from '../../actions/user/friendListAction';
+
+const defaultSearchState = {
+ // indicate fetching via pull to refresh
+ isRefreshing: false,
+ // indicate fetching via end reached
+ isEndReached: false,
+ didInvalidate: false,
+ list: [],
+ hasMore: false,
+ page: 0,
+ errCode: ''
+};
+
+export default function search(state = defaultSearchState, action) {
+ switch (action.type) {
+ case INVALIDATE:
+ return {
+ ...state,
+ didInvalidate: true
+ };
+ case REQUEST_STARTED:
+ return {
+ ...state,
+ isRefreshing: !action.payload.isEndReached,
+ isEndReached: action.payload.isEndReached,
+ didInvalidate: false
+ };
+ case REQUEST_COMPELTED:
+ let {
+ payload: friendList
+ } = action;
+
+ if (friendList.page !== 1 && friendList.list) {
+ friendList.list = state.list.concat(friendList.list);
+ }
+
+ return {
+ ...state,
+ isRefreshing: false,
+ isEndReached: false,
+ didInvalidate: false,
+ list: friendList.list || [],
+ hasMore: !!friendList.has_next,
+ page: friendList.page,
+ errCode: friendList.errcode
+ };
+ case REQUEST_FAILED:
+ return {
+ ...state,
+ isRefreshing: false,
+ isEndReached: false,
+ didInvalidate: false
+ };
+ default:
+ return state;
+ }
+}
diff --git a/src/sagas/index.js b/src/sagas/index.js
index 060a464..04e4a46 100644
--- a/src/sagas/index.js
+++ b/src/sagas/index.js
@@ -14,6 +14,7 @@ import * as sendActions from '../actions/message/sendAction';
import * as alertActions from '../actions/message/alertAction';
import * as settingsActions from '../actions/settingsAction';
import * as userActions from '../actions/user/userAction';
+import * as friendListActions from '../actions/user/friendListAction';
import cacheManager from '../services/cacheManager';
import { fetchResource } from '../utils/sagaHelper';
@@ -31,6 +32,7 @@ const fetchPmListApi = fetchResource.bind(null, pmListActions, api.fetchPmList);
const sendMessageApi = fetchResource.bind(null, sendActions, api.sendMessage);
const fetchAlertsApi = fetchResource.bind(null, alertActions, api.fetchAlerts);
const fetchUserApi = fetchResource.bind(null, userActions, api.fetchUser);
+const fetchFriendListApi = fetchResource.bind(null, friendListActions, api.fetchFriendList);
// user login sagas
@@ -240,6 +242,23 @@ function* watchUsers() {
}
}
+// friend list sagas
+
+function* watchFriendList() {
+ while(true) {
+ const { payload } = yield take(friendListActions.REQUEST);
+ yield fork(fetchFriendList, payload);
+ }
+}
+
+function* fetchFriendList(payload) {
+ const state = yield select();
+
+ if (cacheManager.shouldFetchList(state, 'friendList')) {
+ yield fork(fetchFriendListApi, payload);
+ }
+}
+
export default function* rootSaga() {
yield fork(watchRetrieveUser);
yield fork(watchLogin);
@@ -256,4 +275,5 @@ export default function* rootSaga() {
yield fork(watchRetrieveSettings);
yield fork(watchStoreSettings);
yield fork(watchUsers);
+ yield fork(watchFriendList);
}
diff --git a/src/selectors/alert.js b/src/selectors/alert.js
index 3c6d443..5ed554f 100644
--- a/src/selectors/alert.js
+++ b/src/selectors/alert.js
@@ -3,7 +3,11 @@ import _ from 'lodash';
export const getAtMeCount = alertState => _.get(alertState, ['response', 'atMeInfo', 'count'], 0);
export const getReplyCount = alertState => _.get(alertState, ['response', 'replyInfo', 'count'], 0);
export const getPmCount = alertState => _.get(alertState, ['response', 'pmInfos', 'length'], 0);
+export const getSystemCount = alertState => _.get(alertState, ['response', 'systemInfo', 'count'], 0);
export const getAlertCount = alertState => {
- return getAtMeCount(alertState) + getReplyCount(alertState) + getPmCount(alertState);
+ return getAtMeCount(alertState)
+ + getReplyCount(alertState)
+ + getPmCount(alertState)
+ + getSystemCount(alertState);
};
diff --git a/src/services/api.js b/src/services/api.js
index 3649cd2..d5e6f68 100644
--- a/src/services/api.js
+++ b/src/services/api.js
@@ -287,10 +287,28 @@ export default {
},
fetchAlerts: () => {
- return callApi('message/heart');
+ // Specify `sdkVersion` to get `systemInfo` instead of `friendInfo`.
+ //
+ // API source code:
+ //
+ // if($_GET['sdkVersion']>='2.4.2'){
+ // // 获得系统消息
+ // $res['body']['systemInfo'] = $this->_getSystemInfo($uid);
+ // }else{
+ // // 获取好友通知
+ // $res['body']['friendInfo'] = $this->_getNotifyInfo($uid, 'friend');
+ // }
+ return callApi('message/heart&sdkVersion=2.4.2');
},
fetchUser: ({ userId }) => {
return callApi(`user/userinfo&userId=${userId}`);
+ },
+
+ fetchFriendList: ({
+ page = DEFAULT_PAGE,
+ pageSize = DEFAULT_PAGESIZE
+ }) => {
+ return callApi(`forum/atuserlist&page=${page}&pageSize=${pageSize}`);
}
};
diff --git a/src/styles/components/_FriendItem.js b/src/styles/components/_FriendItem.js
new file mode 100644
index 0000000..3309c56
--- /dev/null
+++ b/src/styles/components/_FriendItem.js
@@ -0,0 +1,31 @@
+import { StyleSheet } from 'react-native';
+import colors from '../common/_colors';
+
+export default StyleSheet.create({
+ container: {
+ backgroundColor: colors.white,
+ borderBottomWidth: 1,
+ borderBottomColor: colors.underlay
+ },
+ item: {
+ marginHorizontal: 20,
+ marginVertical: 10,
+ },
+ row: {
+ flexDirection: 'row',
+ },
+ avatar: {
+ height: 45,
+ width: 45,
+ borderRadius: 10,
+ },
+ content: {
+ flex: 1,
+ marginLeft: 20,
+ justifyContent: 'center',
+ },
+ name: {
+ fontSize: 16,
+ color: colors.significantField,
+ },
+});
diff --git a/src/styles/components/_FriendList.js b/src/styles/components/_FriendList.js
new file mode 100644
index 0000000..e69de29
diff --git a/src/styles/components/_LoadingSpinner.js b/src/styles/components/_LoadingSpinner.js
new file mode 100644
index 0000000..631b121
--- /dev/null
+++ b/src/styles/components/_LoadingSpinner.js
@@ -0,0 +1,12 @@
+import { StyleSheet } from 'react-native';
+import colors from '../common/_colors';
+
+export default StyleSheet.create({
+ container: {
+ padding: 10
+ },
+ text: {
+ marginTop: 10,
+ color: colors.mainField
+ },
+});
diff --git a/src/styles/components/_ProgressImage.js b/src/styles/components/_ProgressImage.js
index 82ea828..1b20a21 100644
--- a/src/styles/components/_ProgressImage.js
+++ b/src/styles/components/_ProgressImage.js
@@ -2,6 +2,7 @@ import {
StyleSheet,
Dimensions
} from 'react-native';
+import colors from '../common/_colors';
const window = Dimensions.get('window');
const IMAGE_HEIGHT = 250;
@@ -13,7 +14,14 @@ export default StyleSheet.create({
indicator: {
position: 'absolute',
top: IMAGE_HEIGHT / 2,
- // `20` is width for ActivityIndicator.
- left: window.width / 2 - 20
+ // window.width / 2 - (width of ActivityIndicator / 2 + margin of image)
+ left: window.width / 2 - (20 / 2 + 10)
+ },
+ text: {
+ position: 'absolute',
+ top: IMAGE_HEIGHT / 2 + 60,
+ // window.width / 2 - (width of `图片加载失败或图片已失效` / 2 + margin of image)
+ left: window.width / 2 - (168 / 2 + 10),
+ color: colors.mainField
}
});
diff --git a/src/utils/contentParser.js b/src/utils/contentParser.js
index c62a76e..b2d3fc1 100644
--- a/src/utils/contentParser.js
+++ b/src/utils/contentParser.js
@@ -20,7 +20,7 @@ export function parseContentWithEmoji(content, includeEmoji = true) {
return contentEmojiArray.filter(item => item.trim()).map((item, index) => {
// Handle custom emojis.
- if (/https?:\/\/.+(?:jpg|png|gif)/.test(item)) {
+ if (/^https?:\/\/.+(?:jpg|png|gif)$/.test(item)) {
// Exclude custom emoji because copy something like [mobcent_phiz=..]
// is useless as paste content.
if (!includeEmoji) { return ''; }
diff --git a/src/utils/request.js b/src/utils/request.js
index c1d42b9..f97f779 100644
--- a/src/utils/request.js
+++ b/src/utils/request.js
@@ -15,6 +15,24 @@ function checkStatus(response) {
throw error;
}
+function handleError(error) {
+ if (error) {
+ if (error.message === 'Network request failed') {
+ MessageBar.show({
+ message: '请检查网络是否通畅',
+ type: 'warning'
+ });
+ } else if (error.response && error.response.status >= 500) {
+ MessageBar.show({
+ message: '服务器开小差啦,请查看网页版能否登陆',
+ type: 'warning'
+ });
+ }
+ }
+
+ return { error };
+}
+
export default function request(url, options) {
return AsyncStorage.getItem('authrization')
.then(authrization => {
@@ -27,15 +45,6 @@ export default function request(url, options) {
.then(checkStatus)
.then(parseJSON)
.then(data => ({ data }))
- .catch(error => {
- if (error && error.message === 'Network request failed') {
- MessageBar.show({
- message: '同学,网络出错啦!',
- type: 'warning'
- });
- }
-
- return { error };
- });
+ .catch(handleError);
});
}