1. splash用Modal实现

This commit is contained in:
xuwenyang 2019-06-06 01:02:10 +08:00
parent 703da75c5f
commit 487aecdf96
13 changed files with 391 additions and 38 deletions

19
App.js
View file

@ -23,6 +23,7 @@ import {Navigation} from 'react-native-navigation';
import Color from './src/style/color'; import Color from './src/style/color';
import Api from './src/util/api'; import Api from './src/util/api';
import BottomNav from './src/nav/bottomNav';
import Loading from './src/component/loading'; import Loading from './src/component/loading';
import LoginForm from './src/component/loginForm'; import LoginForm from './src/component/loginForm';
@ -55,14 +56,28 @@ export default class App extends Component {
}); });
} }
_onSucc() {
Api.getSplashByStore()
.then(splash => {
Navigation.setRoot(BottomNav.config(splash));
})
.done();
}
render() { render() {
return ( return (
<View style={localStyle.wrap}> <View style={localStyle.wrap}>
<Loading visible={this.state.isLoading}></Loading> <Loading visible={this.state.isLoading}></Loading>
<Animated.View style={localStyle.content}> <Animated.View style={localStyle.content}>
{this.state.isLoginPage {this.state.isLoginPage
? (<LoginForm setLoading={this._setLoading.bind(this)}></LoginForm>) ? (<LoginForm
: (<RegisterForm></RegisterForm>)} setLoading={this._setLoading.bind(this)}
onLoginSucc={this._onSucc.bind(this)}
></LoginForm>)
: (<RegisterForm
setLoading={this._setLoading.bind(this)}
onRegisterSucc={this._onSucc.bind(this)}
></RegisterForm>)}
<View style={localStyle.bottom}> <View style={localStyle.bottom}>
<TouchableOpacity onPress={this._switchForm.bind(this)}> <TouchableOpacity onPress={this._switchForm.bind(this)}>

View file

@ -9,10 +9,15 @@ import {Icon, loadIcon} from './src/style/icon';
import App from './App'; import App from './App';
import Token from './src/util/token'; import Token from './src/util/token';
import Api from './src/util/api';
import PageList from './src/page/_list'; import PageList from './src/page/_list';
import BottomNav from './src/nav/bottomNav'; import BottomNav from './src/nav/bottomNav';
// for debug
// console.disableYellowBox = true;
Navigation.registerComponent('Timepill', () => App); Navigation.registerComponent('Timepill', () => App);
// regist screens automatically // regist screens automatically
for(let pageName in PageList) { for(let pageName in PageList) {
@ -81,6 +86,11 @@ Navigation.events().registerAppLaunchedListener(async () => {
Alert.alert("loadIcon err: " + err.toString()); Alert.alert("loadIcon err: " + err.toString());
} }
try {
await Api.syncSplash();
} catch (err) {}
let token = await Token.getUserToken(); let token = await Token.getUserToken();
// let token; // let token;
if(!token) { if(!token) {

View file

@ -10,7 +10,6 @@ import {
} from 'react-native'; } from 'react-native';
import {Navigation} from 'react-native-navigation'; import {Navigation} from 'react-native-navigation';
import {Divider} from "react-native-elements"; import {Divider} from "react-native-elements";
import ActionSheet from 'react-native-actionsheet-api';
import Color from '../../style/color'; import Color from '../../style/color';
import Msg from '../../util/msg'; import Msg from '../../util/msg';
@ -248,7 +247,7 @@ export default class DiaryList extends Component {
onEndReached={this.state.hasMore ? this.loadMore.bind(this) : null} onEndReached={this.state.hasMore ? this.loadMore.bind(this) : null}
> >
</FlatList> </FlatList>
<ActionSheet/>
</View> </View>
); );
} }

View file

@ -37,7 +37,7 @@ export default class LoginForm extends Component {
_checkResult(result) { _checkResult(result) {
InteractionManager.runAfterInteractions(() => { InteractionManager.runAfterInteractions(() => {
if(result.isLoginSucc) { if(result.isLoginSucc) {
Navigation.setRoot(BottomNav.config()); this.props.onLoginSucc();
} else { } else {
Alert.alert( Alert.alert(

View file

@ -1,7 +1,7 @@
import Color from '../style/color' import Color from '../style/color'
import {Icon} from '../style/icon' import {Icon} from '../style/icon'
function config() { function config(splash) {
return { return {
root: { root: {
bottomTabs: { bottomTabs: {
@ -19,11 +19,23 @@ function config() {
name: 'Home', name: 'Home',
options: { options: {
topBar: { topBar: {
// visible: false, visible: false,
animate: false
/*
title: { title: {
text: '首页' text: '首页'
} }
*/
},
bottomTabs: {
visible: false,
animate: false
} }
},
passProps: {
splash: splash
} }
} }
}], }],

View file

@ -8,7 +8,7 @@ import {
} from 'react-native'; } from 'react-native';
import Api from '../util/api'; import Api from '../util/api';
import TokenManager from '../util/token'; import Token from '../util/token';
import Event from '../util/event'; import Event from '../util/event';
import Color from '../style/color'; import Color from '../style/color';
import UpdateInfo from '../updateInfo'; import UpdateInfo from '../updateInfo';
@ -36,7 +36,7 @@ export default class AboutPage extends Component {
} }
componentDidMount() { componentDidMount() {
TokenManager.setUpdateVersion(UpdateInfo.version) Token.setUpdateVersion(UpdateInfo.version)
.then(() => { .then(() => {
DeviceEventEmitter.emit(Event.updateNewsRead); DeviceEventEmitter.emit(Event.updateNewsRead);
}); });

View file

@ -1,6 +1,18 @@
import React, {Component} from 'react'; import React, {Component} from 'react';
import {StyleSheet, Text, View, TouchableOpacity, ImageBackground} from 'react-native'; import {
StyleSheet,
Text,
View,
TouchableOpacity,
ImageBackground,
Animated,
Modal,
TouchableWithoutFeedback,
InteractionManager
} from 'react-native';
import {Navigation} from 'react-native-navigation'; import {Navigation} from 'react-native-navigation';
import {Button} from 'react-native-elements';
import ActionSheet from 'react-native-actionsheet-api';
import Color from '../style/color' import Color from '../style/color'
import Api from '../util/api'; import Api from '../util/api';
@ -15,11 +27,132 @@ export default class HomePage extends Component {
super(props); super(props);
this.dataSource = new HomeDiaryData(); this.dataSource = new HomeDiaryData();
let splash = props.splash;
this.state = { this.state = {
hasSplash: splash ? true : false,
showSplash: true,
fadeInOpacity: new Animated.Value(0),
splashTime : 3,
splashImage: splash ? splash.image_url : null,
splashLink: splash ? splash.link : null,
topic: null topic: null
} }
} }
componentDidMount() {
if(this.state.hasSplash) {
this.openSplash();
} else {
this.closeSplash();
}
}
startTimer() {
this.timer = setInterval(() => {
let newTime = this.state.splashTime - 1;
this.setState({
splashTime: newTime
});
if(newTime == 0) {
this.closeSplash();
}
}, 1000);
}
openSplash() {
Animated.timing(
this.state.fadeInOpacity,
{
toValue: 1,
duration: 1000,
}
).start(() => {
this.startTimer();
});
}
closeSplash() {
if(this.timer) {
clearTimeout(this.timer);
}
Animated.timing(
this.state.fadeInOpacity,
{
toValue: 0,
duration: 500,
}
).start(() => {
Navigation.mergeOptions(this.props.componentId, {
topBar: {
visible: true,
title: {
text: '首页'
}
},
bottomTabs: {
visible: true
}
});
this.setState({
showSplash: false
})
});
}
onSplashPress() {
if(this.state.splashLink) {
if(this.timer) {
clearTimeout(this.timer);
}
Navigation.mergeOptions(this.props.componentId, {
topBar: {
visible: true,
title: {
text: '首页'
}
},
bottomTabs: {
visible: true
}
});
Navigation.push(this.props.componentId, {
component: {
name: 'WebView',
options: {
bottomTabs: {
visible: false,
// hide bottom tab for android
drawBehind: true,
animate: true
}
},
passProps: this.state.splashLink.passProps
}
}).then(() => {
this.setState({
showSplash: false,
fadeInOpacity: new Animated.Value(0)
});
});
}
}
refreshTopic() { refreshTopic() {
Api.getTodayTopic() Api.getTodayTopic()
.then(topic => { .then(topic => {
@ -54,12 +187,17 @@ export default class HomePage extends Component {
render() { render() {
return ( return (
<View style={localStyle.wrap}> <View style={localStyle.wrap}>
{
this.state.showSplash ? this.renderModal() : (
<DiaryList ref={(r) => this.list = r} <DiaryList ref={(r) => this.list = r}
dataSource={this.dataSource} dataSource={this.dataSource}
listHeader={this.renderHeader.bind(this)} listHeader={this.renderHeader.bind(this)}
refreshHeader={this.refreshTopic.bind(this)} refreshHeader={this.refreshTopic.bind(this)}
{...this.props} {...this.props}
></DiaryList> ></DiaryList>
)
}
<ActionSheet/>
</View> </View>
); );
} }
@ -78,6 +216,36 @@ export default class HomePage extends Component {
</View> </View>
) : null; ) : null;
} }
renderModal() {
return (
<Modal visible={this.state.hasSplash}
onShow={() => {}}
onRequestClose={() => {}}
>
<Animated.View style={{flex: 1, opacity: this.state.fadeInOpacity}}>
<TouchableWithoutFeedback style={{flex: 1}} onPress={this.onSplashPress.bind(this)}>
<ImageBackground
style={{flex: 1, width: '100%', height: '100%'}}
source={{uri: this.state.splashImage}}
>
<View style={localStyle.closeButtonWrap}>
<View style={localStyle.closeButtonContainer}>
<Button
buttonStyle={localStyle.closeButton}
title={'关闭 ' + this.state.splashTime}
onPress={this.closeSplash.bind(this)}
textStyle={localStyle.closeButtonText}
/>
</View>
</View>
</ImageBackground>
</TouchableWithoutFeedback>
</Animated.View>
</Modal>
);
}
} }
const localStyle = StyleSheet.create({ const localStyle = StyleSheet.create({
@ -116,5 +284,30 @@ const localStyle = StyleSheet.create({
textShadowOffset: { width: 1, height: 1 }, textShadowOffset: { width: 1, height: 1 },
textShadowRadius: 2, textShadowRadius: 2,
shadowOpacity: 0.5 shadowOpacity: 0.5
},
closeButtonWrap: {
flexDirection: 'row-reverse',
marginTop: Api.IS_IOS ? (Api.IS_IPHONEX ? 50 : 30) : 20
},
closeButtonContainer: {
width: 80,
backgroundColor: 'black',
opacity: 0.75,
borderRadius: 40,
marginRight: 15
},
closeButton: {
borderWidth: 1,
borderColor: 'black',
paddingVertical: 5,
paddingHorizontal: 0,
backgroundColor: 'black'
},
closeButtonText: {
fontWeight: 'bold',
fontSize: 14,
color: 'white',
fontFamily: 'Helvetica'
} }
}); });

View file

@ -11,7 +11,7 @@ import {Navigation} from 'react-native-navigation';
import Color from '../style/color'; import Color from '../style/color';
import Api from '../util/api' import Api from '../util/api'
import TokenManager from '../util/token'; import Token from '../util/token';
import Event from '../util/event'; import Event from '../util/event';
import Msg from '../util/msg'; import Msg from '../util/msg';
@ -34,7 +34,7 @@ export default class PasswordPage extends Component {
} }
componentDidMount() { componentDidMount() {
TokenManager.getLoginPassword() Token.getLoginPassword()
.then(pwd => { .then(pwd => {
if(this.props.type == 'setting') { if(this.props.type == 'setting') {
if(this.props.operation == 'setnew') { if(this.props.operation == 'setnew') {
@ -99,7 +99,7 @@ export default class PasswordPage extends Component {
}); });
} else { } else {
TokenManager.setLoginPassword(password) Token.setLoginPassword(password)
.then(() => { .then(() => {
Keyboard.dismiss(); Keyboard.dismiss();
Msg.showMsg('密码已设置'); Msg.showMsg('密码已设置');
@ -142,7 +142,7 @@ export default class PasswordPage extends Component {
return; return;
} }
TokenManager.setLoginPassword('') Token.setLoginPassword('')
.then(() => { .then(() => {
Keyboard.dismiss(); Keyboard.dismiss();
Msg.showMsg('密码已清除'); Msg.showMsg('密码已清除');

View file

@ -13,7 +13,7 @@ import {Navigation} from 'react-native-navigation';
import Ionicons from 'react-native-vector-icons/Ionicons'; import Ionicons from 'react-native-vector-icons/Ionicons';
import Api from '../util/api'; import Api from '../util/api';
import TokenManager from '../util/token'; import Token from '../util/token';
import Color from '../style/color'; import Color from '../style/color';
@ -50,7 +50,7 @@ export default class SettingPage extends Component {
} }
refreshPasswordState() { refreshPasswordState() {
TokenManager.getLoginPassword() Token.getLoginPassword()
.then((pwd) => this.setState({ .then((pwd) => this.setState({
hasPassword: pwd != null && pwd.length > 0 hasPassword: pwd != null && pwd.length > 0
})); }));

93
src/page/WebViewPage.js Normal file
View file

@ -0,0 +1,93 @@
import React, {Component} from 'react';
import {
WebView,
Linking,
BackHandler
} from 'react-native';
import {Navigation} from 'react-native-navigation';
import {Icon} from '../style/icon'
export default class WebViewPage extends Component {
constructor(props) {
super(props);
Navigation.events().bindComponent(this);
this.webViewState = {
canGoBack: false,
canGoForward: false,
loading: true,
target: 0,
url: this.props.uri
};
}
static options(passProps) {
return {
topBar: {
title: {
text: '加载中...'
},
leftButtons: [{
id: 'back', icon: Icon.navButtonBack
}],
rightButtons: [{
id: "open",
icon: Icon.navButtonOpen
}]
}
};
}
componentWillMount() {
BackHandler.addEventListener('hardwareBackPress', this.goBack);
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.goBack)
}
navigationButtonPressed({buttonId}) {
if(buttonId == 'back') {
this.goBack();
} else if(buttonId == 'open') {
Linking.openURL(this.webViewState.url);
}
}
goBack() {
if (this.webViewState.canGoBack) {
this.webView.injectJavaScript('window.history.back();');
} else {
Navigation.pop(this.props.componentId);
}
}
onNavigationStateChange(event) {
this.webViewState = event;
Navigation.mergeOptions(this.props.componentId, {
topBar: {
title: {
text: event.title
}
}
});
}
render() {
return (
<WebView ref={(r) => this.webView = r}
style={{flex: 1}}
source={{uri: this.props.uri}}
onNavigationStateChange={this.onNavigationStateChange.bind(this)}
/>
);
}
}

View file

@ -18,7 +18,6 @@ import {Navigation} from 'react-native-navigation';
import KeyboardSpacer from "react-native-keyboard-spacer"; import KeyboardSpacer from "react-native-keyboard-spacer";
import Ionicons from 'react-native-vector-icons/Ionicons'; import Ionicons from 'react-native-vector-icons/Ionicons';
import BottomNav from '../nav/bottomNav';
import {Icon} from '../style/icon'; import {Icon} from '../style/icon';
import Color from '../style/color'; import Color from '../style/color';
import Api from '../util/api'; import Api from '../util/api';

View file

@ -18,5 +18,6 @@ UserInfoEdit: require("./UserInfoEditPage.js").default,
UserIntroEdit: require("./UserIntroEditPage.js").default, UserIntroEdit: require("./UserIntroEditPage.js").default,
UserNameEdit: require("./UserNameEditPage.js").default, UserNameEdit: require("./UserNameEditPage.js").default,
User: require("./UserPage.js").default, User: require("./UserPage.js").default,
WebView: require("./WebViewPage.js").default,
Write: require("./WritePage.js").default Write: require("./WritePage.js").default
} }

View file

@ -2,7 +2,7 @@ import {Platform, Dimensions} from 'react-native'
import DeviceInfo from 'react-native-device-info'; import DeviceInfo from 'react-native-device-info';
import {isIphoneX} from 'react-native-iphone-x-helper' import {isIphoneX} from 'react-native-iphone-x-helper'
import TokenManager from './token' import Token from './token'
const IS_ANDROID = Platform.OS === 'android'; const IS_ANDROID = Platform.OS === 'android';
@ -22,19 +22,19 @@ const v2Url = 'http://v2.timepill.net/api';
async function login(username, password) { async function login(username, password) {
const token = TokenManager.generateToken(username, password); const token = Token.generateToken(username, password);
await TokenManager.setUserToken(token); await Token.setUserToken(token);
try { try {
const userInfo = await getSelfInfo(); const userInfo = await getSelfInfo();
await TokenManager.setUserInfo(userInfo); await Token.setUserInfo(userInfo);
await TokenManager.setLoginPassword(''); await Token.setLoginPassword('');
return userInfo; return userInfo;
} catch(err) { } catch(err) {
await TokenManager.setUserToken(''); await Token.setUserToken('');
if (err.code && err.code === 401) { if (err.code && err.code === 401) {
return false; return false;
} }
@ -44,9 +44,38 @@ async function login(username, password) {
} }
async function logout() { async function logout() {
TokenManager.setUserToken(''); Token.setUserToken('');
TokenManager.setUserInfo(false); Token.setUserInfo(false);
TokenManager.setLoginPassword(''); Token.setLoginPassword('');
}
async function getSplashByStore() {
try {
let info = await Token.get('splash');
console.log('api get splash:', info);
if(info) {
const splash = JSON.parse(info);
const now = Date.parse(new Date()) / 1000;
if((splash.start_time && splash.start_time > now) ||
(splash.end_time && now > splash.end_time)) {
return null;
}
return splash;
}
} catch (e) {}
return null;
}
async function syncSplash() {
const splash = await callV2('GET', '/splash');
await Token.set('splash', JSON.stringify(splash));
return splash;
} }
async function getSelfInfo() { async function getSelfInfo() {
@ -54,7 +83,7 @@ async function getSelfInfo() {
} }
async function getSelfInfoByStore() { async function getSelfInfoByStore() {
return await TokenManager.getUserInfo(); return await Token.getUserInfo();
} }
async function getUserInfo(id) { async function getUserInfo(id) {
@ -258,7 +287,7 @@ async function feedback(content) {
async function upload(method, api, body) { async function upload(method, api, body) {
let token = await TokenManager.getUserToken(); let token = await Token.getUserToken();
let formData = new FormData(); let formData = new FormData();
for(let prop of Object.keys(body)) { for(let prop of Object.keys(body)) {
formData.append(prop, body[prop]); formData.append(prop, body[prop]);
@ -286,7 +315,7 @@ async function upload(method, api, body) {
} }
async function call(method, api, body = null, _timeout = 10000) { async function call(method, api, body = null, _timeout = 10000) {
let token = await TokenManager.getUserToken(); let token = await Token.getUserToken();
return timeout(fetch(baseUrl + api, { return timeout(fetch(baseUrl + api, {
method: method, method: method,
headers: { headers: {
@ -308,7 +337,7 @@ async function call(method, api, body = null, _timeout = 10000) {
} }
async function callV2(method, api, body = null, _timeout = 10000) { async function callV2(method, api, body = null, _timeout = 10000) {
let token = await TokenManager.getToken(); let token = await Token.getUserToken();
return timeout(fetch(v2Url + api, { return timeout(fetch(v2Url + api, {
method: method, method: method,
headers: { headers: {
@ -397,6 +426,8 @@ export default {
login, login,
logout, logout,
getSplashByStore,
syncSplash,
getSelfInfoByStore, getSelfInfoByStore,
getUserInfo, getUserInfo,