mirror of
https://github.com/timepill/timepill-app.git
synced 2025-04-30 09:59:31 +08:00
1. splash用Modal实现
This commit is contained in:
parent
703da75c5f
commit
487aecdf96
13 changed files with 391 additions and 38 deletions
19
App.js
19
App.js
|
@ -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)}>
|
||||
|
|
10
index.js
10
index.js
|
@ -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) {
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}],
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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 => {
|
||||
|
@ -53,14 +186,19 @@ export default class HomePage extends Component {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<View style={localStyle.wrap}>
|
||||
<DiaryList ref={(r) => this.list = r}
|
||||
dataSource={this.dataSource}
|
||||
listHeader={this.renderHeader.bind(this)}
|
||||
refreshHeader={this.refreshTopic.bind(this)}
|
||||
{...this.props}
|
||||
></DiaryList>
|
||||
</View>
|
||||
<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'
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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('密码已清除');
|
||||
|
|
|
@ -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
93
src/page/WebViewPage.js
Normal 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)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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,
|
||||
|
||||
|
|
Loading…
Reference in a new issue