Added progress bar, progress modal and bubble loader
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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} />} />
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
56
mobile/src/Modal/ProgressModal.js
Normal file
56
mobile/src/Modal/ProgressModal.js
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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 : ""}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
77
mobile/src/ui/BubbleLoader.js
Normal file
77
mobile/src/ui/BubbleLoader.js
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
86
mobile/src/ui/ProgressBar.js
Normal file
86
mobile/src/ui/ProgressBar.js
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
Reference in New Issue
Block a user