kdxcxs
4 years ago
20 changed files with 8374 additions and 34 deletions
@ -0,0 +1,28 @@ |
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" |
|||
package="com.yoomoocrn"> |
|||
|
|||
<uses-permission android:name="android.permission.INTERNET" /> |
|||
|
|||
<application |
|||
android:name=".MainApplication" |
|||
android:label="@string/app_name" |
|||
android:icon="@mipmap/ic_launcher" |
|||
android:roundIcon="@mipmap/ic_launcher_round" |
|||
android:allowBackup="false" |
|||
android:theme="@style/AppTheme"> |
|||
<activity |
|||
android:name=".MainActivity" |
|||
android:label="@string/app_name" |
|||
android:screenOrientation="portrait" |
|||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode" |
|||
android:launchMode="singleTask" |
|||
android:windowSoftInputMode="adjustResize"> |
|||
<intent-filter> |
|||
<action android:name="android.intent.action.MAIN" /> |
|||
<category android:name="android.intent.category.LAUNCHER" /> |
|||
</intent-filter> |
|||
</activity> |
|||
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" /> |
|||
</application> |
|||
|
|||
</manifest> |
@ -0,0 +1,34 @@ |
|||
{ |
|||
"name": "YoomoocRN", |
|||
"version": "0.0.1", |
|||
"private": true, |
|||
"scripts": { |
|||
"android": "react-native run-android", |
|||
"ios": "react-native run-ios", |
|||
"start": "react-native start", |
|||
"test": "jest", |
|||
"lint": "eslint ." |
|||
}, |
|||
"dependencies": { |
|||
"@react-native-async-storage/async-storage": "^1.13.2", |
|||
"@react-native-community/cookies": "^5.0.1", |
|||
"buffer": "^6.0.3", |
|||
"iconv-lite": "^0.6.2", |
|||
"react": "16.13.1", |
|||
"react-native": "0.63.3", |
|||
"stream": "^0.0.2" |
|||
}, |
|||
"devDependencies": { |
|||
"@babel/core": "^7.12.9", |
|||
"@babel/runtime": "^7.12.5", |
|||
"@react-native-community/eslint-config": "^2.0.0", |
|||
"babel-jest": "^26.6.3", |
|||
"eslint": "^7.14.0", |
|||
"jest": "^26.6.3", |
|||
"metro-react-native-babel-preset": "^0.64.0", |
|||
"react-test-renderer": "16.13.1" |
|||
}, |
|||
"jest": { |
|||
"preset": "react-native" |
|||
} |
|||
} |
After Width: | Height: | Size: 451 KiB |
@ -0,0 +1,88 @@ |
|||
import React, {Component} from 'react'; |
|||
import {View, Animated, Dimensions} from 'react-native'; |
|||
import {YooLogin, YooForum, YooSplash} from './component/index'; |
|||
|
|||
const width = Dimensions.get('window').width; |
|||
|
|||
export default class YooRouter extends Component { |
|||
constructor(props) { |
|||
super(props); |
|||
this.state = { |
|||
currentPage: 'splash', |
|||
splashX: new Animated.Value(0), |
|||
loginX: new Animated.Value(width), |
|||
forumX: new Animated.Value(width), |
|||
}; |
|||
this.gotoForum = this.gotoForum.bind(this); |
|||
this.gotoLogin = this.gotoLogin.bind(this); |
|||
this.bindForumInitializer = this.bindForumInitializer.bind(this); |
|||
this.initForum = null; |
|||
} |
|||
|
|||
bindForumInitializer(forumRef) { |
|||
this.initForum = forumRef.initForum; |
|||
} |
|||
|
|||
gotoLogin() { |
|||
this.setState({currentPage: 'login'}); |
|||
Animated.parallel([ |
|||
Animated.timing(this.state.splashX, { |
|||
toValue: -width, |
|||
duration: 250, |
|||
useNativeDriver: false, |
|||
}), |
|||
Animated.timing(this.state.loginX, { |
|||
toValue: 0, |
|||
duration: 250, |
|||
useNativeDriver: false, |
|||
}), |
|||
]).start(); |
|||
} |
|||
|
|||
gotoForum() { |
|||
this.initForum(); |
|||
if (this.state.currentPage === 'splash') { |
|||
Animated.parallel([ |
|||
Animated.timing(this.state.splashX, { |
|||
toValue: -width, |
|||
duration: 250, |
|||
useNativeDriver: false, |
|||
}), |
|||
Animated.timing(this.state.forumX, { |
|||
toValue: 0, |
|||
duration: 250, |
|||
useNativeDriver: false, |
|||
}), |
|||
]).start(); |
|||
} else { |
|||
Animated.parallel([ |
|||
Animated.timing(this.state.loginX, { |
|||
toValue: -width, |
|||
duration: 250, |
|||
useNativeDriver: false, |
|||
}), |
|||
Animated.timing(this.state.forumX, { |
|||
toValue: 0, |
|||
duration: 250, |
|||
useNativeDriver: false, |
|||
}), |
|||
]).start(); |
|||
} |
|||
} |
|||
|
|||
render() { |
|||
return ( |
|||
<View> |
|||
<Animated.View style={{left: this.state.splashX}}> |
|||
<YooSplash gotoLogin={this.gotoLogin} gotoForum={this.gotoForum} /> |
|||
</Animated.View> |
|||
<Animated.View style={{left: this.state.loginX}}> |
|||
<YooLogin gotoForum={this.gotoForum} /> |
|||
</Animated.View> |
|||
<Animated.View style={{left: this.state.forumX}}> |
|||
<YooForum bindInitializer={this.bindForumInitializer} /> |
|||
</Animated.View> |
|||
</View> |
|||
); |
|||
} |
|||
} |
@ -0,0 +1,26 @@ |
|||
import iconv from 'iconv-lite'; |
|||
import {Buffer} from 'buffer'; |
|||
|
|||
function gbkFetch(method, url, data) { |
|||
return new Promise(function (resolve, reject) { |
|||
const request = new XMLHttpRequest(); |
|||
|
|||
request.onload = () => { |
|||
if (request.status === 200) { |
|||
resolve(iconv.decode(Buffer.from(request.response), 'gbk')); |
|||
} else { |
|||
reject(new Error(request.statusText)); |
|||
} |
|||
}; |
|||
request.onerror = () => reject(new Error(request.statusText)); |
|||
request.responseType = 'arraybuffer'; |
|||
|
|||
request.open(method, url); |
|||
for (let headerKey in data.headers) { |
|||
request.setRequestHeader(headerKey, data.headers[headerKey]); |
|||
} |
|||
request.send(data.body); |
|||
}); |
|||
} |
|||
|
|||
export {gbkFetch}; |
@ -0,0 +1,21 @@ |
|||
import {Dimensions, Platform, StatusBar} from 'react-native'; |
|||
|
|||
const X_WIDTH = 375; |
|||
const X_HEIGHT = 812; |
|||
|
|||
const XSMAX_WIDTH = 414; |
|||
const XSMAX_HEIGHT = 896; |
|||
|
|||
const {height, width} = Dimensions.get('window'); |
|||
|
|||
export const isIPhoneX = () => |
|||
Platform.OS === 'ios' && !Platform.isPad && !Platform.isTVOS |
|||
? (width === X_WIDTH && height === X_HEIGHT) || |
|||
(width === XSMAX_WIDTH && height === XSMAX_HEIGHT) |
|||
: false; |
|||
|
|||
export const StatusBarHeight = Platform.select({ |
|||
ios: isIPhoneX() ? 44 : 20, |
|||
android: StatusBar.currentHeight, |
|||
default: 0, |
|||
}); |
@ -0,0 +1,138 @@ |
|||
import React, {Component} from 'react'; |
|||
import {ToastAndroid} from 'react-native'; |
|||
import YooForumUI from '../ui/YooForumUI'; |
|||
import {gbkFetch} from '../api/HTTP'; |
|||
import CookieManager from '@react-native-community/cookies'; |
|||
import * as cheerio from 'cheerio'; |
|||
|
|||
const ShowErrorToast = () => |
|||
ToastAndroid.show('获取数据时出错', ToastAndroid.SHORT); |
|||
|
|||
export default class YooForum extends Component { |
|||
constructor(props) { |
|||
super(props); |
|||
this.state = { |
|||
hint: '准备中', |
|||
topics: [], |
|||
}; |
|||
this.initForum = this.initForum.bind(this); |
|||
this.getTopicList = this.getTopicList.bind(this); |
|||
} |
|||
|
|||
componentDidMount() { |
|||
this.props.bindInitializer(this); |
|||
} |
|||
|
|||
initForum() { |
|||
// get dwr session id
|
|||
gbkFetch( |
|||
'POST', |
|||
'http://eol.ctbu.edu.cn/meol/dwr/call/plaincall/__System.generateId.dwr', |
|||
{ |
|||
headers: { |
|||
Origin: 'http://eol.ctbu.edu.cn', |
|||
'Content-Type': 'text/plain', |
|||
'User-Agent': 'YooMooc', |
|||
Referer: |
|||
'http://eol.ctbu.edu.cn/meol/jpk/course/layout/newpage/index.jsp?courseId=46445', |
|||
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-US;q=0.7', |
|||
}, |
|||
body: |
|||
'callCount=1\n' + |
|||
'c0-scriptName=__System\n' + |
|||
'c0-methodName=generateId\n' + |
|||
'c0-id=0\n' + |
|||
'batchId=0\n' + |
|||
'instanceId=0\n' + |
|||
'page=%2Fmeol%2Fjpk%2Fcourse%2Flayout%2Fnewpage%2Findex.jsp%3FcourseId%3D46445\n' + |
|||
'scriptSessionId=\n' + |
|||
'windowName=\n', |
|||
}, |
|||
) |
|||
.then((response) => { |
|||
if (/[^"]*"\);/.test(response)) { |
|||
const exDate = new Date(); |
|||
exDate.setDate(exDate.getDate() + 1); |
|||
CookieManager.set( |
|||
'http://eol.ctbu.edu.cn/meol/jpk/course/layout/newpage/index.jsp?courseId=46445', |
|||
{ |
|||
name: 'DWRSESSIONID', |
|||
value: response.match(/[^"]*"\);/)[0].split('"')[0], |
|||
domain: 'eol.ctbu.edu.cn', |
|||
path: '/meol', |
|||
version: '1', |
|||
expires: exDate.toUTCString(), |
|||
}, |
|||
).then(() => { |
|||
// it is needed to request some pages before getting the topic list
|
|||
// maybe the server is judging which course the user is
|
|||
gbkFetch( |
|||
'GET', |
|||
'http://eol.ctbu.edu.cn/meol/jpk/course/layout/newpage/index.jsp?courseId=46445', |
|||
{ |
|||
headers: { |
|||
'Upgrade-Insecure-Requests': '1', |
|||
'User-Agent': 'YooMooc', |
|||
}, |
|||
}, |
|||
) |
|||
.then(() => { |
|||
gbkFetch( |
|||
'GET', |
|||
'http://eol.ctbu.edu.cn/meol/jpk/course/layout/newpage/default_demonstrate.jsp', |
|||
{ |
|||
'Upgrade-Insecure-Requests': '1', |
|||
'User-Agent': 'YooMooc', |
|||
Referer: |
|||
'http://eol.ctbu.edu.cn/meol/jpk/course/layout/newpage/index.jsp?courseId=46445', |
|||
}, |
|||
) |
|||
.then(() => { |
|||
this.getTopicList(); |
|||
}) |
|||
.catch(ShowErrorToast); |
|||
}) |
|||
.catch(ShowErrorToast); |
|||
}); |
|||
} else { |
|||
ShowErrorToast(); |
|||
} |
|||
}) |
|||
.catch(ShowErrorToast); |
|||
} |
|||
|
|||
getTopicList(page = 1) { |
|||
this.setState({hint: '获取数据中'}); |
|||
gbkFetch( |
|||
'GET', |
|||
`http://eol.ctbu.edu.cn/meol/common/faq/forum.jsp?viewtype=thread&forumid=102211&cateId=0&s_gotopage=${page}`, |
|||
{ |
|||
headers: { |
|||
'Upgrade-Insecure-Requests': '1', |
|||
'User-Agent': 'YooMooc', |
|||
Referer: |
|||
'http://eol.ctbu.edu.cn/meol/common/faq/forum.jsp?count=MODITIME&forumid=102211', |
|||
}, |
|||
}, |
|||
).then((response) => { |
|||
const newTopics = []; |
|||
const appendNewTopic = (threadID, title, owner) => { |
|||
newTopics.push({threadID, title, owner}); |
|||
}; |
|||
const $ = cheerio.load(response, {ignoreWhitespace: true}); |
|||
$('td.left').each(function () { |
|||
const current = $(this); |
|||
appendNewTopic( |
|||
current.children('a').attr('href').slice(20), |
|||
current.text(), |
|||
current.next().text(), |
|||
); |
|||
}); |
|||
this.setState({topics: [...this.state.topics, ...newTopics], hint: ''}); |
|||
}); |
|||
} |
|||
|
|||
render() { |
|||
return <YooForumUI topics={this.state.topics} hint={this.state.hint} />; |
|||
} |
|||
} |
@ -0,0 +1,56 @@ |
|||
import React, {Component} from 'react'; |
|||
import YooForumTopicUI from '../ui/YooForumTopicUI'; |
|||
import {gbkFetch} from '../api/HTTP'; |
|||
import * as cheerio from 'cheerio'; |
|||
|
|||
export default class YooForumTopic extends Component { |
|||
constructor(props) { |
|||
super(props); |
|||
this.state = { |
|||
replies: [], |
|||
}; |
|||
this.getReplies = this.getReplies.bind(this); |
|||
} |
|||
|
|||
componentDidMount() { |
|||
this.getReplies(); |
|||
} |
|||
|
|||
getReplies() { |
|||
gbkFetch( |
|||
'GET', |
|||
`http://eol.ctbu.edu.cn/meol/common/faq/thread.jsp?threadid=${this.props.topic.threadID}`, |
|||
{ |
|||
headers: 'YooMooc', |
|||
}, |
|||
).then((response) => { |
|||
const newReplies = []; |
|||
const appendReply = (username, content) => { |
|||
newReplies.push({username, content}); |
|||
}; |
|||
const $ = cheerio.load(response, {ignoreWhitespace: true}); |
|||
$('input[type=hidden]').each(function () { |
|||
const current = $(this); |
|||
const currentUserInfo = current.parent().parent().parent().parent(); |
|||
if (currentUserInfo.html().slice(2, 4) === 'td') { |
|||
appendReply( |
|||
currentUserInfo.find('h6').text().split(' ')[1], |
|||
cheerio.load(current.attr('value')).text(), |
|||
); |
|||
} |
|||
}); |
|||
this.setState({replies: [...this.state.replies, ...newReplies]}); |
|||
}); |
|||
} |
|||
|
|||
render() { |
|||
return ( |
|||
<YooForumTopicUI |
|||
topic={this.props.topic} |
|||
showDetail={this.props.showDetail} |
|||
hideDetail={this.props.hideDetail} |
|||
replies={this.state.replies} |
|||
/> |
|||
); |
|||
} |
|||
} |
@ -1,12 +1,69 @@ |
|||
import React, {Component} from 'react'; |
|||
import YooLoginUI from '../ui/YooLoginUI'; |
|||
import CookieManager from '@react-native-community/cookies'; |
|||
import {gbkFetch} from '../api/HTTP'; |
|||
|
|||
export default class YooLogin extends Component { |
|||
constructor(props) { |
|||
super(props); |
|||
this.state = { |
|||
username: '', |
|||
password: '', |
|||
}; |
|||
this.setUsername = this.setUsername.bind(this); |
|||
this.setPassword = this.setPassword.bind(this); |
|||
this.login = this.login.bind(this); |
|||
} |
|||
|
|||
setUsername(value) { |
|||
this.setState({username: value}); |
|||
} |
|||
|
|||
setPassword(value) { |
|||
this.setState({password: value}); |
|||
} |
|||
|
|||
login(onFail) { |
|||
gbkFetch('POST', 'http://eol.ctbu.edu.cn/meol/loginCheck.do', { |
|||
headers: { |
|||
'Cache-Control': 'max-age=0', |
|||
'Upgrade-Insecure-Requests': '1', |
|||
Origin: 'http://eol.ctbu.edu.cn', |
|||
'Content-Type': 'application/x-www-form-urlencoded', |
|||
'User-Agent': 'YooMooc', |
|||
Referer: |
|||
'http://eol.ctbu.edu.cn/meol/common/security/login.jsp?enterLid=46445', |
|||
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-US;q=0.7', |
|||
}, |
|||
body: |
|||
'logintoken=' + |
|||
new Date().getTime() + |
|||
'&enterLid=46445' + |
|||
'&IPT_LOGINUSERNAME=' + |
|||
this.state.username + |
|||
'&IPT_LOGINPASSWORD=' + |
|||
this.state.password, |
|||
}) |
|||
.then((response) => { |
|||
if (/<title>(.|\n)*用户登录(.|\n)*<\/title>/.test(response)) { |
|||
onFail('用户名或密码错误'); |
|||
CookieManager.clearAll(); |
|||
} else if (/<title>(.|\n)*网络课程(.|\n)*<\/title>/.test(response)) { |
|||
this.props.gotoForum(); |
|||
} |
|||
}) |
|||
.catch(() => onFail('登陆失败')); |
|||
} |
|||
|
|||
render() { |
|||
return <YooLoginUI />; |
|||
return ( |
|||
<YooLoginUI |
|||
username={this.state.username} |
|||
password={this.state.password} |
|||
setUsername={this.setUsername} |
|||
setPassword={this.setPassword} |
|||
loginCallback={this.login} |
|||
/> |
|||
); |
|||
} |
|||
} |
|||
|
@ -0,0 +1,35 @@ |
|||
import React, {Component} from 'react'; |
|||
import {ToastAndroid} from 'react-native'; |
|||
import YooSplashUI from '../ui/YooSplashUI'; |
|||
import {gbkFetch} from '../api/HTTP'; |
|||
import CookieManager from '@react-native-community/cookies'; |
|||
|
|||
export default class YooSplash extends Component { |
|||
componentDidMount() { |
|||
gbkFetch('POST', 'http://eol.ctbu.edu.cn/meol/loginCheck.do', { |
|||
headers: { |
|||
'Cache-Control': 'max-age=0', |
|||
'Upgrade-Insecure-Requests': '1', |
|||
Origin: 'http://eol.ctbu.edu.cn', |
|||
'Content-Type': 'application/x-www-form-urlencoded', |
|||
'User-Agent': 'YooMooc', |
|||
Referer: |
|||
'http://eol.ctbu.edu.cn/meol/common/security/login.jsp?enterLid=46445', |
|||
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-US;q=0.7', |
|||
}, |
|||
}) |
|||
.then((response) => { |
|||
if (/<title>(.|\n)*用户登录(.|\n)*<\/title>/.test(response)) { |
|||
this.props.gotoLogin(); |
|||
CookieManager.clearAll(); |
|||
} else if (/<title>(.|\n)*网络课程(.|\n)*<\/title>/.test(response)) { |
|||
this.props.gotoForum(); |
|||
} |
|||
}) |
|||
.catch(() => ToastAndroid.show('无法连接到服务器', ToastAndroid.SHORT)); |
|||
} |
|||
|
|||
render() { |
|||
return <YooSplashUI />; |
|||
} |
|||
} |
@ -0,0 +1,6 @@ |
|||
import YooLogin from './YooLogin'; |
|||
import YooForum from './YooForum'; |
|||
import YooBackground from '../ui/YooBackground'; |
|||
import YooSplash from './YooSplash'; |
|||
|
|||
export {YooLogin, YooForum, YooBackground, YooSplash}; |
@ -1,20 +1,34 @@ |
|||
import React from 'react'; |
|||
import {View, Image, StyleSheet} from 'react-native'; |
|||
import React, {useState} from 'react'; |
|||
import {Image, StyleSheet, Animated} from 'react-native'; |
|||
|
|||
export function YooBackground() { |
|||
export default function YooBackground() { |
|||
const imgPosition = useState(new Animated.ValueXY())[0]; |
|||
const styles = StyleSheet.create({ |
|||
backgroundContainer: { |
|||
width: '100%', |
|||
height: '100%', |
|||
transform: [{translateX: -1500}], |
|||
position: 'absolute', |
|||
zIndex: -1, |
|||
}, |
|||
}); |
|||
setInterval(() => { |
|||
Animated.timing(imgPosition, { |
|||
toValue: { |
|||
x: -Math.floor(Math.random() * 5000), |
|||
y: -Math.floor(Math.random() * 3000), |
|||
}, |
|||
duration: 10000, |
|||
useNativeDriver: true, |
|||
}).start(); |
|||
}, 10000); |
|||
return ( |
|||
<View style={styles.backgroundContainer}> |
|||
<Animated.View |
|||
style={[ |
|||
styles.backgroundContainer, |
|||
{transform: imgPosition.getTranslateTransform()}, |
|||
]}> |
|||
<Image |
|||
source={require('../../assets/pawel-czerwinski-aelD0Zrmsy0-unsplash.jpg')} |
|||
/> |
|||
</View> |
|||
</Animated.View> |
|||
); |
|||
} |
|||
|
@ -0,0 +1,77 @@ |
|||
import React, {Component, createRef} from 'react'; |
|||
import { |
|||
Animated, |
|||
View, |
|||
Text, |
|||
StyleSheet, |
|||
Pressable, |
|||
Dimensions, |
|||
} from 'react-native'; |
|||
|
|||
const screenWidth = Dimensions.get('window').width; |
|||
const styles = StyleSheet.create({ |
|||
container: { |
|||
backgroundColor: 'mintcream', |
|||
borderRadius: 8, |
|||
padding: 16, |
|||
margin: 8, |
|||
width: screenWidth - 16, |
|||
}, |
|||
title: { |
|||
fontSize: 24, |
|||
}, |
|||
owner: { |
|||
fontSize: 16, |
|||
}, |
|||
}); |
|||
const AnimatedPressable = Animated.createAnimatedComponent(Pressable); |
|||
|
|||
export default class YooForumTopicUI extends Component { |
|||
constructor(props) { |
|||
super(props); |
|||
this.state = { |
|||
detailShowing: false, |
|||
layoutY: 0, |
|||
translate: new Animated.ValueXY({x: 0, y: 0}), |
|||
}; |
|||
this.topicHeaderRef = createRef(); |
|||
this.onPress = this.onPress.bind(this); |
|||
this.onAnimationFinished = this.onAnimationFinished.bind(this); |
|||
} |
|||
|
|||
onAnimationFinished() { |
|||
this.setState({detailShowing: !this.state.detailShowing}); |
|||
} |
|||
|
|||
onPress() { |
|||
if (!this.state.detailShowing) { |
|||
this.props.showDetail( |
|||
this.state.translate, |
|||
this.state.layoutY, |
|||
this.onAnimationFinished, |
|||
this.props.replies, |
|||
); |
|||
} else { |
|||
this.props.hideDetail(this.state.translate, this.onAnimationFinished); |
|||
} |
|||
} |
|||
|
|||
render() { |
|||
return ( |
|||
<AnimatedPressable |
|||
style={[ |
|||
styles.container, |
|||
{transform: this.state.translate.getTranslateTransform()}, |
|||
]} |
|||
onPress={this.onPress} |
|||
onLayout={(event) => { |
|||
this.setState({layoutY: event.nativeEvent.layout.y}); |
|||
}}> |
|||
<View ref={this.topicHeaderRef}> |
|||
<Text style={styles.owner}>{this.props.topic.owner}</Text> |
|||
<Text style={styles.title}>{this.props.topic.title}</Text> |
|||
</View> |
|||
</AnimatedPressable> |
|||
); |
|||
} |
|||
} |
@ -0,0 +1,172 @@ |
|||
import React, {Component, createRef} from 'react'; |
|||
import { |
|||
View, |
|||
ScrollView, |
|||
Text, |
|||
StyleSheet, |
|||
ActivityIndicator, |
|||
Image, |
|||
Animated, |
|||
Dimensions, |
|||
} from 'react-native'; |
|||
import YooForumTopic from '../component/YooForumTopic'; |
|||
import YooReply from './YooReply'; |
|||
|
|||
const screenWidth = Dimensions.get('window').width; |
|||
const screenHeight = Dimensions.get('window').height; |
|||
const AnimatedScrollView = Animated.createAnimatedComponent(ScrollView); |
|||
const styles = StyleSheet.create({ |
|||
container: { |
|||
width: screenWidth * 2, |
|||
}, |
|||
repliesContainer: { |
|||
position: 'absolute', |
|||
left: screenWidth, |
|||
width: screenWidth, |
|||
height: screenHeight - 124, |
|||
}, |
|||
hintContainer: { |
|||
flex: 1, |
|||
alignItems: 'center', |
|||
left: -screenWidth / 2, |
|||
}, |
|||
logo: { |
|||
marginTop: 120, |
|||
width: 200, |
|||
height: 200, |
|||
marginBottom: 16, |
|||
}, |
|||
hint: { |
|||
fontSize: 24, |
|||
fontWeight: 'bold', |
|||
}, |
|||
indicator: { |
|||
marginTop: 64, |
|||
}, |
|||
}); |
|||
|
|||
export default class YooForumUI extends Component { |
|||
constructor(props) { |
|||
super(props); |
|||
this.state = { |
|||
scrollEnabled: true, |
|||
currentPosition: 0, |
|||
translateX: new Animated.Value(0), |
|||
currentReplies: [], |
|||
replyTranslateY: new Animated.Value(0), |
|||
}; |
|||
this.replyRef = createRef(); |
|||
this.showDetail = this.showDetail.bind(this); |
|||
this.hideDetail = this.hideDetail.bind(this); |
|||
} |
|||
|
|||
showDetail(translate, layoutY, onAnimationFinished, replies) { |
|||
this.replyRef.current.scrollTo({x: 0, y: 0, animated: false}); |
|||
this.setState({ |
|||
scrollEnabled: false, |
|||
currentReplies: replies, |
|||
}); |
|||
this.state.replyTranslateY.setValue( |
|||
this.state.currentPosition + 100 + screenHeight, |
|||
); |
|||
Animated.parallel([ |
|||
Animated.timing(translate, { |
|||
toValue: { |
|||
x: screenWidth, |
|||
y: this.state.currentPosition - layoutY + 8, |
|||
}, |
|||
duration: 250, |
|||
useNativeDriver: true, |
|||
}), |
|||
Animated.timing(this.state.translateX, { |
|||
toValue: -screenWidth, |
|||
duration: 250, |
|||
useNativeDriver: true, |
|||
}), |
|||
Animated.timing(this.state.replyTranslateY, { |
|||
toValue: this.state.currentPosition + 100, |
|||
duration: 250, |
|||
delay: 250, |
|||
useNativeDriver: true, |
|||
}), |
|||
]).start(onAnimationFinished); |
|||
} |
|||
|
|||
hideDetail(translate, onAnimationFinished) { |
|||
this.setState({scrollEnabled: true}); |
|||
Animated.parallel([ |
|||
Animated.timing(translate, { |
|||
toValue: { |
|||
x: 0, |
|||
y: 0, |
|||
}, |
|||
duration: 250, |
|||
useNativeDriver: true, |
|||
}), |
|||
Animated.timing(this.state.translateX, { |
|||
toValue: 0, |
|||
duration: 250, |
|||
useNativeDriver: true, |
|||
}), |
|||
Animated.timing(this.state.replyTranslateY, { |
|||
toValue: this.state.currentPosition + 100 + screenHeight, |
|||
duration: 250, |
|||
useNativeDriver: true, |
|||
}), |
|||
]).start(onAnimationFinished); |
|||
} |
|||
|
|||
render() { |
|||
return ( |
|||
<AnimatedScrollView |
|||
style={[ |
|||
styles.container, |
|||
{ |
|||
transform: [ |
|||
{ |
|||
translateX: this.state.translateX, |
|||
}, |
|||
], |
|||
}, |
|||
]} |
|||
scrollEnabled={this.state.scrollEnabled} |
|||
onScroll={(event) => |
|||
this.setState({currentPosition: event.nativeEvent.contentOffset.y}) |
|||
}> |
|||
{this.props.hint === '' ? ( |
|||
this.props.topics.map((topic, key) => ( |
|||
<YooForumTopic |
|||
key={key} |
|||
topic={topic} |
|||
showDetail={this.showDetail} |
|||
hideDetail={this.hideDetail} |
|||
/> |
|||
)) |
|||
) : ( |
|||
<View style={styles.hintContainer}> |
|||
<Image |
|||
source={require('../../assets/icon.png')} |
|||
style={styles.logo} |
|||
/> |
|||
<Text style={styles.hint}>{this.props.hint}</Text> |
|||
<ActivityIndicator |
|||
size="large" |
|||
color="#0000ff" |
|||
style={styles.indicator} |
|||
/> |
|||
</View> |
|||
)} |
|||
<AnimatedScrollView |
|||
ref={this.replyRef} |
|||
style={[ |
|||
styles.repliesContainer, |
|||
{transform: [{translateY: this.state.replyTranslateY}]}, |
|||
]}> |
|||
{this.state.currentReplies.map((reply, key) => ( |
|||
<YooReply key={key} reply={reply} /> |
|||
))} |
|||
</AnimatedScrollView> |
|||
</AnimatedScrollView> |
|||
); |
|||
} |
|||
} |
@ -0,0 +1,28 @@ |
|||
import React, {Component} from 'react'; |
|||
import {View, Text, StyleSheet} from 'react-native'; |
|||
|
|||
const styles = StyleSheet.create({ |
|||
container: { |
|||
backgroundColor: 'lightgray', |
|||
borderRadius: 8, |
|||
padding: 16, |
|||
margin: 8, |
|||
}, |
|||
username: { |
|||
fontSize: 16, |
|||
}, |
|||
content: { |
|||
fontSize: 24, |
|||
}, |
|||
}); |
|||
|
|||
export default class YooReply extends Component { |
|||
render() { |
|||
return ( |
|||
<View style={styles.container}> |
|||
<Text style={styles.username}>{this.props.reply.username}</Text> |
|||
<Text style={styles.content}>{this.props.reply.content}</Text> |
|||
</View> |
|||
); |
|||
} |
|||
} |
@ -0,0 +1,38 @@ |
|||
import React, {Component} from 'react'; |
|||
import {View, StyleSheet, Image, Text, ActivityIndicator} from 'react-native'; |
|||
|
|||
const styles = StyleSheet.create({ |
|||
container: { |
|||
flex: 1, |
|||
alignItems: 'center', |
|||
}, |
|||
logo: { |
|||
marginTop: 120, |
|||
width: 200, |
|||
height: 200, |
|||
marginBottom: 16, |
|||
}, |
|||
text: { |
|||
fontSize: 24, |
|||
fontWeight: 'bold', |
|||
}, |
|||
indicator: { |
|||
marginTop: 64, |
|||
}, |
|||
}); |
|||
|
|||
export default class YooSplashUI extends Component { |
|||
render() { |
|||
return ( |
|||
<View style={styles.container}> |
|||
<Image source={require('../../assets/icon.png')} style={styles.logo} /> |
|||
<Text style={styles.text}>正在检查网络,请稍后</Text> |
|||
<ActivityIndicator |
|||
size="large" |
|||
color="#0000ff" |
|||
style={styles.indicator} |
|||
/> |
|||
</View> |
|||
); |
|||
} |
|||
} |
File diff suppressed because it is too large
Loading…
Reference in new issue