diff --git a/README.md b/README.md index 415d16d..dceaa97 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ 哟慕课 APP 是一个第三方的优慕课客户端 -![screenshot](./screenshot.jpg) +screenshot ## 🎞 背景 @@ -18,9 +18,9 @@ ## 📄 TODO -- [ ] 弹出键盘不遮挡输入框 -- [ ] 根据像素比例设置登录背景图片大小 -- [ ] 添加应用图标 +- [x] 弹出键盘不遮挡输入框 +- [ ] ~~根据像素比例设置登录背景图片大小~~ +- [x] 添加应用图标 - [ ] 适配 iOS 登录失败 toast - [ ] 在启动时检测是否已登录 - [ ] Splash 检测网络出错后在 Splash UI 中显示错误并不跳转 @@ -29,5 +29,6 @@ - [ ] 解决当动画进行时点击话题标题动画会被打断的问题 - [ ] 支持显示回复图片 - [ ] 在回复中显示头像 -- [ ] 当滑动到页面底部时显示"加载更多" +- [x] 当滑动到页面底部时显示"加载更多" +- [x] 支持通过 Android BackHandler 从话题详情返回话题列表 diff --git a/src/component/YooForum.js b/src/component/YooForum.js index 521d21c..458c0bb 100644 --- a/src/component/YooForum.js +++ b/src/component/YooForum.js @@ -14,6 +14,8 @@ export default class YooForum extends Component { this.state = { hint: '准备中', topics: [], + fetchedListPage: 0, + gettingTopicList: false, }; this.initForum = this.initForum.bind(this); this.getTopicList = this.getTopicList.bind(this); @@ -87,9 +89,7 @@ export default class YooForum extends Component { 'http://eol.ctbu.edu.cn/meol/jpk/course/layout/newpage/index.jsp?courseId=46445', }, ) - .then(() => { - this.getTopicList(); - }) + .then(this.getTopicList) .catch(ShowErrorToast); }) .catch(ShowErrorToast); @@ -101,8 +101,10 @@ export default class YooForum extends Component { .catch(ShowErrorToast); } - getTopicList(page = 1) { - this.setState({hint: '获取数据中'}); + getTopicList() { + const page = this.state.fetchedListPage + 1; + page === 1 && this.setState({hint: '获取数据中'}); + this.setState({gettingTopicList: true}); gbkFetch( 'GET', `http://eol.ctbu.edu.cn/meol/common/faq/forum.jsp?viewtype=thread&forumid=102211&cateId=0&s_gotopage=${page}`, @@ -128,11 +130,23 @@ export default class YooForum extends Component { current.next().text(), ); }); - this.setState({topics: [...this.state.topics, ...newTopics], hint: ''}); + this.setState({ + topics: [...this.state.topics, ...newTopics], + hint: '', + fetchedListPage: page, + gettingTopicList: false, + }); }); } render() { - return ; + return ( + + ); } } diff --git a/src/ui/YooBackground.js b/src/ui/YooBackground.js index df8a9a6..ae44fbf 100644 --- a/src/ui/YooBackground.js +++ b/src/ui/YooBackground.js @@ -10,6 +10,17 @@ export default function YooBackground() { zIndex: -1, }, }); + // The animation won't play for the first 10 seconds + // if we just use setInterval. So we start the animation + // at the component mounted just for the first 10 seconds. + Animated.timing(imgPosition, { + toValue: { + x: -Math.floor(Math.random() * 5000), + y: -Math.floor(Math.random() * 3000), + }, + duration: 10000, + useNativeDriver: true, + }).start(); setInterval(() => { Animated.timing(imgPosition, { toValue: { diff --git a/src/ui/YooForumTopicUI.js b/src/ui/YooForumTopicUI.js index ef8e4ef..183df9b 100644 --- a/src/ui/YooForumTopicUI.js +++ b/src/ui/YooForumTopicUI.js @@ -32,6 +32,7 @@ export default class YooForumTopicUI extends Component { this.state = { detailShowing: false, layoutY: 0, + layoutHeight: 0, translate: new Animated.ValueXY({x: 0, y: 0}), }; this.topicHeaderRef = createRef(); @@ -45,14 +46,9 @@ export default class YooForumTopicUI extends Component { onPress() { if (!this.state.detailShowing) { - this.props.showDetail( - this.state.translate, - this.state.layoutY, - this.onAnimationFinished, - this.props.replies, - ); + this.props.showDetail(this); } else { - this.props.hideDetail(this.state.translate, this.onAnimationFinished); + this.props.hideDetail(); } } @@ -65,7 +61,10 @@ export default class YooForumTopicUI extends Component { ]} onPress={this.onPress} onLayout={(event) => { - this.setState({layoutY: event.nativeEvent.layout.y}); + this.setState({ + layoutY: event.nativeEvent.layout.y, + layoutHeight: event.nativeEvent.layout.height, + }); }}> {this.props.topic.owner} diff --git a/src/ui/YooForumUI.js b/src/ui/YooForumUI.js index 5c91e20..053de93 100644 --- a/src/ui/YooForumUI.js +++ b/src/ui/YooForumUI.js @@ -8,6 +8,9 @@ import { Image, Animated, Dimensions, + Pressable, + BackHandler, + ToastAndroid, } from 'react-native'; import YooForumTopic from '../component/YooForumTopic'; import YooReply from './YooReply'; @@ -43,8 +46,37 @@ const styles = StyleSheet.create({ indicator: { marginTop: 64, }, + showMoreText: { + fontSize: 18, + margin: 8, + }, }); +function ForumTopic(props) { + return props.topics.map((topic, key) => ( + + )); +} + +function ShowMore(props) { + return ( + props.getTopicList()}> + {props.gettingTopicList ? ( + 加载中 + ) : ( + 加载更多 + )} + + ); +} + export default class YooForumUI extends Component { constructor(props) { super(props); @@ -54,26 +86,55 @@ export default class YooForumUI extends Component { translateX: new Animated.Value(0), currentReplies: [], replyTranslateY: new Animated.Value(0), + lastPressingBack: 0, }; this.replyRef = createRef(); this.showDetail = this.showDetail.bind(this); this.hideDetail = this.hideDetail.bind(this); + this.handleAndroidBack = this.handleAndroidBack.bind(this); + this.currentForumTopicUI = null; } - showDetail(translate, layoutY, onAnimationFinished, replies) { + componentDidMount() { + this.backHandler = BackHandler.addEventListener( + 'hardwareBackPress', + this.handleAndroidBack, + ); + } + + handleAndroidBack() { + if (this.currentForumTopicUI) { + this.hideDetail(); + return true; + } else if (new Date().getTime() - this.state.lastPressingBack <= 1000) { + BackHandler.exitApp(); + } else { + this.setState({lastPressingBack: new Date().getTime()}); + ToastAndroid.show('再按一次以退出Yoomooc', ToastAndroid.SHORT); + return true; + } + } + + showDetail(ForumTopicUI) { + this.currentForumTopicUI = ForumTopicUI; this.replyRef.current.scrollTo({x: 0, y: 0, animated: false}); this.setState({ scrollEnabled: false, - currentReplies: replies, + currentReplies: this.currentForumTopicUI.props.replies, }); this.state.replyTranslateY.setValue( - this.state.currentPosition + 100 + screenHeight, + this.state.currentPosition + + this.currentForumTopicUI.state.layoutHeight + + screenHeight, ); Animated.parallel([ - Animated.timing(translate, { + Animated.timing(this.currentForumTopicUI.state.translate, { toValue: { x: screenWidth, - y: this.state.currentPosition - layoutY + 8, + y: + this.state.currentPosition - + this.currentForumTopicUI.state.layoutY + + 8, }, duration: 250, useNativeDriver: true, @@ -84,18 +145,21 @@ export default class YooForumUI extends Component { useNativeDriver: true, }), Animated.timing(this.state.replyTranslateY, { - toValue: this.state.currentPosition + 100, + toValue: + this.state.currentPosition + + this.currentForumTopicUI.state.layoutHeight + + 8, duration: 250, delay: 250, useNativeDriver: true, }), - ]).start(onAnimationFinished); + ]).start(this.currentForumTopicUI.onAnimationFinished); } - hideDetail(translate, onAnimationFinished) { + hideDetail() { this.setState({scrollEnabled: true}); Animated.parallel([ - Animated.timing(translate, { + Animated.timing(this.currentForumTopicUI.state.translate, { toValue: { x: 0, y: 0, @@ -113,7 +177,12 @@ export default class YooForumUI extends Component { duration: 250, useNativeDriver: true, }), - ]).start(onAnimationFinished); + ]).start( + function () { + this.currentForumTopicUI.onAnimationFinished(); + this.currentForumTopicUI = null; + }.bind(this), + ); } render() { @@ -134,14 +203,17 @@ export default class YooForumUI extends Component { this.setState({currentPosition: event.nativeEvent.contentOffset.y}) }> {this.props.hint === '' ? ( - this.props.topics.map((topic, key) => ( - + - )) + + ) : ( - - + this.props.setUsername(username)} /> - this.props.setPassword(password)} /> - + {this.state.loading ? ( ) : ( 登录 )} - + ); }