Added progress bar, progress modal and bubble loader

This commit is contained in:
John Lyon-Smith
2018-05-11 13:51:14 -07:00
parent d087da2ce7
commit dadad6434f
11 changed files with 281 additions and 11 deletions

View File

@@ -65,7 +65,7 @@ export default class App extends React.Component {
path="/workItemList" path="/workItemList"
component={WorkItemList} component={WorkItemList}
/> />
<DefaultRoute /> <DefaultRoute redirect="/workItem" />
</Switch> </Switch>
</View> </View>
</NativeRouter> </NativeRouter>

View File

@@ -1,7 +1,14 @@
import React, { Fragment, Component } from "react" import React, { Fragment, Component } from "react"
import { Route, Redirect } from "react-router-native" import { Route, Redirect } from "react-router-native"
import PropTypes from "prop-types"
export const DefaultRoute = () => { export class DefaultRoute extends Component {
// NOTE: When working on the app, change this to the page you are working on static propTypes = {
return <Route render={() => <Redirect to={"/home"} />} /> redirect: PropTypes.string,
}
render() {
// NOTE: When working on the app, change this to the page you are working on
return <Route render={() => <Redirect to={this.props.redirect} />} />
}
} }

View File

@@ -0,0 +1,56 @@
import React, { Component } from "react"
import Modal from "react-native-modal"
import PropTypes from "prop-types"
import { View, Text, ActivityIndicator, TouchableOpacity } from "react-native"
import { ProgressBar } from "../ui"
import autobind from "autobind-decorator"
export class ProgressModal extends Component {
static propTypes = {
open: PropTypes.bool,
message: PropTypes.string.isRequired,
percent: PropTypes.number,
onCancel: PropTypes.func,
}
render() {
const { open, message, percent } = this.props
return (
<Modal isVisible={open}>
<View
style={{
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
backgroundColor: "transparent",
}}>
<ProgressBar height={30} value={percent} />
<Text
style={{
marginTop: 20,
fontSize: 18,
alignSelf: "center",
color: "#FFFFFF",
}}>
{message}
</Text>
<TouchableOpacity
onPress={this.props.onCancel}
style={{
alignSelf: "center",
marginTop: 30,
marginBottom: 10,
justifyContent: "center",
paddingHorizontal: 10,
height: 40,
width: 100,
backgroundColor: "#3BB0FD",
}}>
<Text style={{ alignSelf: "center", color: "black" }}>Cancel</Text>
</TouchableOpacity>
</View>
</Modal>
)
}
}

View File

@@ -2,6 +2,7 @@ import React, { Component } from "react"
import Modal from "react-native-modal" import Modal from "react-native-modal"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import { View, Text, ActivityIndicator } from "react-native" import { View, Text, ActivityIndicator } from "react-native"
import { BubbleLoader } from "../ui"
export class WaitModal extends Component { export class WaitModal extends Component {
static propTypes = { static propTypes = {
@@ -18,9 +19,10 @@ export class WaitModal extends Component {
style={{ style={{
flexDirection: "column", flexDirection: "column",
justifyContent: "center", justifyContent: "center",
alignItems: "center",
backgroundColor: "transparent", backgroundColor: "transparent",
}}> }}>
<ActivityIndicator size="large" color="#0000FF" /> <BubbleLoader size={25} />
<Text <Text
style={{ style={{
marginTop: 15, marginTop: 15,

View File

@@ -1,4 +1,5 @@
export { MessageModal } from "./MessageModal" export { MessageModal } from "./MessageModal"
export { ApiModal } from "./ApiModal" export { ApiModal } from "./ApiModal"
export { WaitModal } from "./WaitModal" export { WaitModal } from "./WaitModal"
export { ProgressModal } from "./ProgressModal"
export { ImageViewerModal } from "./ImageViewerModal" export { ImageViewerModal } from "./ImageViewerModal"

View File

@@ -21,7 +21,7 @@ import {
BoundOptionStrip, BoundOptionStrip,
BoundPhotoPanel, BoundPhotoPanel,
} from "../ui" } from "../ui"
import { MessageModal, WaitModal } from "../Modal" import { MessageModal, ProgressModal, WaitModal } from "../Modal"
import autobind from "autobind-decorator" import autobind from "autobind-decorator"
import { ifIphoneX, isIphoneX } from "react-native-iphone-x-helper" import { ifIphoneX, isIphoneX } from "react-native-iphone-x-helper"
import KeyboardSpacer from "react-native-keyboard-spacer" import KeyboardSpacer from "react-native-keyboard-spacer"
@@ -83,6 +83,7 @@ export class WorkItem extends React.Component {
binder: new FormBinder({}, WorkItem.bindings), binder: new FormBinder({}, WorkItem.bindings),
messageModal: null, messageModal: null,
waitModal: null, waitModal: null,
progressModal: null,
} }
const { search } = this.props.location const { search } = this.props.location
@@ -238,16 +239,45 @@ export class WorkItem extends React.Component {
@autobind @autobind
handleUploadStarted() { handleUploadStarted() {
this.setState({ waitModal: { message: "Uploading Photo..." } }) this.setState({
progressModal: { message: "Uploading Photo..." },
uploadPercent: 0,
})
} }
@autobind @autobind
handleUploadEnded() { handleUploadProgress(uploadData) {
this.setState({ waitModal: null }) console.log(uploadData)
if (this.state.progressModal) {
this.setState({
uploadPercent: Math.round(
uploadData.uploadedChunks / uploadData.numberOfChunks * 100
),
})
return true
} else {
return false
}
}
@autobind
handleUploadEnded(successful, uploadData) {
this.setState({ progressModal: null })
}
@autobind
handleUploadCanceled() {
this.setState({ progressModal: null })
} }
render() { render() {
const { binder, messageModal, waitModal } = this.state const {
binder,
messageModal,
waitModal,
progressModal,
uploadPercent,
} = this.state
return ( return (
<View style={{ flex: 1 }}> <View style={{ flex: 1 }}>
@@ -327,10 +357,17 @@ export class WorkItem extends React.Component {
binder={binder} binder={binder}
onUploadStarted={this.handleUploadStarted} onUploadStarted={this.handleUploadStarted}
onUploadEnded={this.handleUploadEnded} onUploadEnded={this.handleUploadEnded}
onUploadProgress={this.handleUploadProgress}
/> />
</View> </View>
{isIphoneX ? <View style={{ height: 30, width: "100%" }} /> : null} {isIphoneX ? <View style={{ height: 30, width: "100%" }} /> : null}
</ScrollView> </ScrollView>
<ProgressModal
open={!!progressModal}
message={progressModal ? progressModal.message : ""}
percent={uploadPercent}
onCancel={this.handleUploadCanceled}
/>
<WaitModal <WaitModal
open={!!waitModal} open={!!waitModal}
message={waitModal ? waitModal.message : ""} message={waitModal ? waitModal.message : ""}

View File

@@ -29,6 +29,7 @@ export class BoundPhotoPanel extends Component {
static propTypes = { static propTypes = {
onUploadStarted: PropTypes.func, onUploadStarted: PropTypes.func,
onUploadEnded: PropTypes.func, onUploadEnded: PropTypes.func,
onUploadProgress: PropTypes.func,
} }
constructor(props) { constructor(props) {
@@ -63,7 +64,7 @@ export class BoundPhotoPanel extends Component {
onUploadStarted() onUploadStarted()
} }
api api
.upload(response.data) .upload(response.data, this.props.onUploadProgress)
.then((uploadData) => { .then((uploadData) => {
if (onUploadEnded) { if (onUploadEnded) {
onUploadEnded(true, uploadData) onUploadEnded(true, uploadData)

View File

@@ -0,0 +1,77 @@
import React from "react"
import { View, Animated, Easing } from "react-native"
import PropTypes from "prop-types"
export class BubbleLoader extends React.Component {
static propTypes = {
size: PropTypes.number,
}
static defaultProps = {
size: 50,
}
constructor(props) {
super(props)
this.animatedScales = [0, 1, 2].map((i) => new Animated.Value(1.0))
Animated.loop(
Animated.parallel(
this.animatedScales.map((value, i) =>
Animated.sequence([
Animated.timing(value, {
toValue: 0.2,
duration: 800,
delay: 200 * i,
easing: Easing.out(Easing.sin),
useNativeDriver: true,
}),
Animated.timing(value, {
toValue: 1.0,
duration: 800,
easing: Easing.out(Easing.sin),
useNativeDriver: true,
}),
Animated.delay(200 * (2 - i)),
])
)
)
).start()
}
render() {
const { size } = this.props
const spacing = size / 5
return (
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
height: size,
width: size * 3 + spacing * 2,
}}>
{[0, 1, 2].map((i) => (
<Animated.View
key={i}
style={{
width: size,
height: size,
backgroundColor: "#dddddd",
borderWidth: 0,
borderRadius: size / 2,
transform: [
{
scaleX: this.animatedScales[i],
},
{
scaleY: this.animatedScales[i],
},
],
}}
/>
))}
</View>
)
}
}

View File

@@ -0,0 +1,86 @@
import React from "react"
import {
AppRegistry,
StyleSheet,
Text,
View,
Easing,
Animated,
} from "react-native"
import PropTypes from "prop-types"
var style = StyleSheet.create({
container: {
width: "100%",
flexDirection: "row",
overflow: "hidden",
borderWidth: 2,
borderColor: "#555555",
borderRadius: 8,
marginBottom: 5,
backgroundColor: "#FFFFFF",
},
bar: {
flexDirection: "row",
justifyContent: "flex-end",
alignItems: "center",
backgroundColor: "#3BB0FD",
},
value: {
position: "absolute",
right: 0,
paddingRight: 5,
backgroundColor: "#00000000",
color: "#FFFFFF",
},
})
export class ProgressBar extends React.Component {
static propInfo = {
height: PropTypes.number,
value: PropTypes.number,
}
static defaultProps = {
value: 0,
}
constructor(props) {
super(props)
this.state = {
animatedValue: new Animated.Value(props.value % 101),
}
}
componentWillReceiveProps(nextProps) {
if (this.props.value !== nextProps.value) {
Animated.timing(this.state.animatedValue, {
toValue: nextProps.value % 101,
easing: Easing.out(Easing.ease),
duration: 500,
}).start()
}
}
render() {
const { height } = this.props
const width = this.state.animatedValue.interpolate({
inputRange: [0, 100],
outputRange: ["0%", "100%"],
})
return (
<View style={[style.container, { height }]}>
<Animated.View
style={[
style.bar,
{
width,
},
]}>
<Text style={style.value}>{this.props.value}%</Text>
</Animated.View>
</View>
)
}
}

View File

@@ -1,6 +1,8 @@
export { Icon } from "./Icon" export { Icon } from "./Icon"
export { Header } from "./Header" export { Header } from "./Header"
export { OptionStrip } from "./OptionStrip" export { OptionStrip } from "./OptionStrip"
export { BubbleLoader } from "./BubbleLoader"
export { ProgressBar } from "./ProgressBar"
export { BoundSwitch } from "./BoundSwitch" export { BoundSwitch } from "./BoundSwitch"
export { BoundInput } from "./BoundInput" export { BoundInput } from "./BoundInput"
export { FormStaticInput } from "./FormStaticInput" export { FormStaticInput } from "./FormStaticInput"

View File

@@ -15,6 +15,7 @@ export class AuthRoutes {
constructor(container) { constructor(container) {
const app = container.app const app = container.app
this.log = container.log
this.mq = container.mq this.mq = container.mq
this.db = container.db this.db = container.db
this.maxEmailTokenAgeInHours = config.get("email.maxEmailTokenAgeInHours") this.maxEmailTokenAgeInHours = config.get("email.maxEmailTokenAgeInHours")