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 Api from './src/util/api';
import BottomNav from './src/nav/bottomNav';
import Loading from './src/component/loading';
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() {
return (
<View style={localStyle.wrap}>
<Loading visible={this.state.isLoading}></Loading>
<Animated.View style={localStyle.content}>
{this.state.isLoginPage
? (<LoginForm setLoading={this._setLoading.bind(this)}></LoginForm>)
: (<RegisterForm></RegisterForm>)}
? (<LoginForm
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}>
<TouchableOpacity onPress={this._switchForm.bind(this)}>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,18 @@
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 {Button} from 'react-native-elements';
import ActionSheet from 'react-native-actionsheet-api';
import Color from '../style/color'
import Api from '../util/api';
@ -15,11 +27,132 @@ export default class HomePage extends Component {
super(props);
this.dataSource = new HomeDiaryData();
let splash = props.splash;
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
}
}
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() {
Api.getTodayTopic()
.then(topic => {
@ -54,12 +187,17 @@ export default class HomePage extends Component {
render() {
return (
<View style={localStyle.wrap}>
{
this.state.showSplash ? this.renderModal() : (
<DiaryList ref={(r) => this.list = r}
dataSource={this.dataSource}
listHeader={this.renderHeader.bind(this)}
refreshHeader={this.refreshTopic.bind(this)}
{...this.props}
></DiaryList>
)
}
<ActionSheet/>
</View>
);
}
@ -78,6 +216,36 @@ export default class HomePage extends Component {
</View>
) : 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({
@ -116,5 +284,30 @@ const localStyle = StyleSheet.create({
textShadowOffset: { width: 1, height: 1 },
textShadowRadius: 2,
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 Api from '../util/api'
import TokenManager from '../util/token';
import Token from '../util/token';
import Event from '../util/event';
import Msg from '../util/msg';
@ -34,7 +34,7 @@ export default class PasswordPage extends Component {
}
componentDidMount() {
TokenManager.getLoginPassword()
Token.getLoginPassword()
.then(pwd => {
if(this.props.type == 'setting') {
if(this.props.operation == 'setnew') {
@ -99,7 +99,7 @@ export default class PasswordPage extends Component {
});
} else {
TokenManager.setLoginPassword(password)
Token.setLoginPassword(password)
.then(() => {
Keyboard.dismiss();
Msg.showMsg('密码已设置');
@ -142,7 +142,7 @@ export default class PasswordPage extends Component {
return;
}
TokenManager.setLoginPassword('')
Token.setLoginPassword('')
.then(() => {
Keyboard.dismiss();
Msg.showMsg('密码已清除');

View file

@ -13,7 +13,7 @@ import {Navigation} from 'react-native-navigation';
import Ionicons from 'react-native-vector-icons/Ionicons';
import Api from '../util/api';
import TokenManager from '../util/token';
import Token from '../util/token';
import Color from '../style/color';
@ -50,7 +50,7 @@ export default class SettingPage extends Component {
}
refreshPasswordState() {
TokenManager.getLoginPassword()
Token.getLoginPassword()
.then((pwd) => this.setState({
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 Ionicons from 'react-native-vector-icons/Ionicons';
import BottomNav from '../nav/bottomNav';
import {Icon} from '../style/icon';
import Color from '../style/color';
import Api from '../util/api';

View file

@ -18,5 +18,6 @@ UserInfoEdit: require("./UserInfoEditPage.js").default,
UserIntroEdit: require("./UserIntroEditPage.js").default,
UserNameEdit: require("./UserNameEditPage.js").default,
User: require("./UserPage.js").default,
WebView: require("./WebViewPage.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 {isIphoneX} from 'react-native-iphone-x-helper'
import TokenManager from './token'
import Token from './token'
const IS_ANDROID = Platform.OS === 'android';
@ -22,19 +22,19 @@ const v2Url = 'http://v2.timepill.net/api';
async function login(username, password) {
const token = TokenManager.generateToken(username, password);
await TokenManager.setUserToken(token);
const token = Token.generateToken(username, password);
await Token.setUserToken(token);
try {
const userInfo = await getSelfInfo();
await TokenManager.setUserInfo(userInfo);
await TokenManager.setLoginPassword('');
await Token.setUserInfo(userInfo);
await Token.setLoginPassword('');
return userInfo;
} catch(err) {
await TokenManager.setUserToken('');
await Token.setUserToken('');
if (err.code && err.code === 401) {
return false;
}
@ -44,9 +44,38 @@ async function login(username, password) {
}
async function logout() {
TokenManager.setUserToken('');
TokenManager.setUserInfo(false);
TokenManager.setLoginPassword('');
Token.setUserToken('');
Token.setUserInfo(false);
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() {
@ -54,7 +83,7 @@ async function getSelfInfo() {
}
async function getSelfInfoByStore() {
return await TokenManager.getUserInfo();
return await Token.getUserInfo();
}
async function getUserInfo(id) {
@ -258,7 +287,7 @@ async function feedback(content) {
async function upload(method, api, body) {
let token = await TokenManager.getUserToken();
let token = await Token.getUserToken();
let formData = new FormData();
for(let prop of Object.keys(body)) {
formData.append(prop, body[prop]);
@ -286,7 +315,7 @@ async function upload(method, api, body) {
}
async function call(method, api, body = null, _timeout = 10000) {
let token = await TokenManager.getUserToken();
let token = await Token.getUserToken();
return timeout(fetch(baseUrl + api, {
method: method,
headers: {
@ -308,7 +337,7 @@ async function call(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, {
method: method,
headers: {
@ -397,6 +426,8 @@ export default {
login,
logout,
getSplashByStore,
syncSplash,
getSelfInfoByStore,
getUserInfo,