diff --git a/app/components/UserDetail.js b/app/components/UserDetail.js index 67c21621..c59bba44 100644 --- a/app/components/UserDetail.js +++ b/app/components/UserDetail.js @@ -1,17 +1,16 @@ -/** - * @prettier - */ import React from 'react' import { View, Text, StyleSheet, TouchableOpacity } from 'react-native' import * as CSS from '../res/css' import { ConnectedShockAvatar } from './ShockAvatar' +import TimeText from './time-text' /** * @typedef {object} Props * @prop {string=} alternateText * @prop {boolean=} alternateTextBold + * @prop {boolean=} alternateTextIsTimestamp * @prop {React.ReactNode} lowerText * @prop {import('react-native').TextInputProps['style']=} lowerTextStyle * @prop {string} id @@ -33,13 +32,11 @@ export default class UserDetail extends React.PureComponent { onPress && onPress(id) } - /** @type {number|null} */ - intervalID = 0 - render() { const { alternateText, alternateTextBold, + alternateTextIsTimestamp, nameBold, lowerText, lowerTextStyle, @@ -65,15 +62,31 @@ export default class UserDetail extends React.PureComponent { style={nameBold ? styles.nameBold : styles.name} > {name} - - {alternateText && ` ${alternateText}`} - + {alternateText && alternateTextIsTimestamp ? ( + + {' '} + + {Number(alternateText)} + + + ) : null} + {!alternateTextIsTimestamp ? ( + + {alternateText && ` ${alternateText}`} + + ) : null} {typeof lowerText === 'string' ? ( diff --git a/app/components/time-text.tsx b/app/components/time-text.tsx index 9bad1e33..d9caf58d 100644 --- a/app/components/time-text.tsx +++ b/app/components/time-text.tsx @@ -2,10 +2,40 @@ import React from 'react' import { Text, TextProps } from 'react-native' import Moment from 'moment' -const TimeText: React.FC = React.memo( - ({ children: timestamp, ...props }) => { - return {Moment(timestamp).fromNow()} - }, -) +import * as Services from '../services' -export default TimeText +interface Props extends TextProps { + children: number + displayHHMM?: boolean +} + +export default class TimeText extends React.PureComponent { + intervalID: null | ReturnType = null + + componentDidMount() { + this.intervalID = setInterval(() => { + this.forceUpdate() + }, 60000) + } + + componentWillUnmount() { + if (this.intervalID) { + clearInterval(this.intervalID) + this.intervalID = null + } + } + + render() { + const { children: timestamp, displayHHMM, ...props } = this.props + + const msTimestamp = Services.normalizeTimestampToMs(timestamp) + + return ( + + {displayHHMM + ? Moment(msTimestamp).format('hh:mm') + : Moment(msTimestamp).fromNow()} + + ) + } +} diff --git a/app/screens/Advanced/Accordion/Invoice.js b/app/screens/Advanced/Accordion/Invoice.js index 16067292..f076aeff 100644 --- a/app/screens/Advanced/Accordion/Invoice.js +++ b/app/screens/Advanced/Accordion/Invoice.js @@ -1,8 +1,8 @@ import React from 'react' import { View, Text, StyleSheet, Image } from 'react-native' -import Moment from 'moment' import * as CSS from '../../../res/css' +import TimeText from '../../../components/time-text' /** * @typedef {import('../../../services/wallet').Invoice} IInvoice */ @@ -38,9 +38,9 @@ const _Invoice = ({ data }) => (( - - {Moment(data.settle_date).fromNow()} ago - + + {Number(data.settle_date)} + +{data.amt_paid_sat} {(Number(data.amt_paid_sat) / 100).toFixed(4)} USD diff --git a/app/screens/Advanced/Accordion/Transaction.js b/app/screens/Advanced/Accordion/Transaction.js index 68062097..7296fe1e 100644 --- a/app/screens/Advanced/Accordion/Transaction.js +++ b/app/screens/Advanced/Accordion/Transaction.js @@ -1,9 +1,9 @@ import React from 'react' import { View, Text, StyleSheet, Clipboard, ToastAndroid } from 'react-native' -import Moment from 'moment' import EntypoIcons from 'react-native-vector-icons/Entypo' import * as CSS from '../../../res/css' +import TimeText from '../../../components/time-text' /** * @typedef {import('../../../services/wallet').Transaction} ITransaction */ @@ -71,9 +71,9 @@ const _Transaction = ({ data }) => (( > {data.amount} - - {Moment.utc(parseInt(data.time_stamp, 10) * 1000).fromNow()} - + + {Number(data.time_stamp)} + )) diff --git a/app/screens/Chat/ChatInvoice.js b/app/screens/Chat/ChatInvoice.js index 0ec47237..ccd7fe83 100644 --- a/app/screens/Chat/ChatInvoice.js +++ b/app/screens/Chat/ChatInvoice.js @@ -36,19 +36,6 @@ import TXBase from './TXBase' * @augments React.PureComponent */ export default class ChatInvoice extends React.PureComponent { - componentDidMount() { - /** - * Force-updates every minute so moment-formatted dates refresh. - */ - this.intervalID = setInterval(() => { - this.forceUpdate() - }, 60000) - } - - componentWillUnmount() { - typeof this.intervalID === 'number' && clearInterval(this.intervalID) - } - onPress = () => { const { id, onPressUnpaidIncomingInvoice } = this.props const { paymentStatus } = this.props diff --git a/app/screens/Chat/ChatMessage.js b/app/screens/Chat/ChatMessage.js index 8ecee372..ec45887e 100644 --- a/app/screens/Chat/ChatMessage.js +++ b/app/screens/Chat/ChatMessage.js @@ -1,10 +1,10 @@ import React from 'react' import { View, Text, StyleSheet, TouchableOpacity } from 'react-native' -import moment from 'moment' import { Svg, Polygon } from 'react-native-svg' import { Colors, WIDTH } from '../../res/css' import Pad from '../../components/Pad' +import TimeText from '../../components/time-text' import ChatAvatar from './ChatAvatar' @@ -27,19 +27,6 @@ const BUBBLE_TRIANGLE_VERTICAL_OFFSET = 6 * @augments React.PureComponent */ export default class ChatMessage extends React.PureComponent { - componentDidMount() { - /** - * Force-updates every minute so moment-formatted dates refresh. - */ - this.intervalID = setInterval(() => { - this.forceUpdate() - }, 60000) - } - - componentWillUnmount() { - typeof this.intervalID === 'number' && clearInterval(this.intervalID) - } - onPress = () => { const { id, onPress } = this.props onPress && onPress(id) @@ -48,8 +35,6 @@ export default class ChatMessage extends React.PureComponent { render() { const { body, outgoing, timestamp } = this.props - const formattedTime = moment(timestamp).format('hh:mm') - const SVG_EDGE = 25 const UNIT = SVG_EDGE / 5 const ZERO = UNIT * 0 @@ -74,7 +59,7 @@ export default class ChatMessage extends React.PureComponent { {outgoing && ( <> - {formattedTime} + {timestamp} )} @@ -106,7 +91,9 @@ export default class ChatMessage extends React.PureComponent { {!outgoing && ( <> - {formattedTime} + + {timestamp} + )} diff --git a/app/screens/Chat/SpontPayment.js b/app/screens/Chat/SpontPayment.js index dfc448dc..e1b0b876 100644 --- a/app/screens/Chat/SpontPayment.js +++ b/app/screens/Chat/SpontPayment.js @@ -20,19 +20,6 @@ import TXBase from './TXBase' * @augments React.PureComponent */ export default class SpontPayment extends React.PureComponent { - componentDidMount() { - /** - * Force-updates every minute so moment-formatted dates refresh. - */ - this.intervalID = setInterval(() => { - this.forceUpdate() - }, 60000) - } - - componentWillUnmount() { - typeof this.intervalID === 'number' && clearInterval(this.intervalID) - } - onPress = () => { const { onPress, id } = this.props onPress && onPress(id) diff --git a/app/screens/Chat/TXBase.js b/app/screens/Chat/TXBase.js index 9d8e70b1..aeb010fe 100644 --- a/app/screens/Chat/TXBase.js +++ b/app/screens/Chat/TXBase.js @@ -6,7 +6,6 @@ import { TouchableWithoutFeedback, View, } from 'react-native' -import moment from 'moment' import Octicons from 'react-native-vector-icons/Octicons' import FontAwesome5 from 'react-native-vector-icons/FontAwesome5' import MaterialIcons from 'react-native-vector-icons/MaterialIcons' @@ -15,6 +14,7 @@ import Feather from 'react-native-vector-icons/Feather' import * as CSS from '../../res/css' import * as RES from '../../res' import Pad from '../../components/Pad' +import TimeText from '../../components/time-text' /** * @typedef {'payment-received'|'invoice'|'invoice-unk'|'invoice-settling'|'invoice-settled'|'invoice-err'|'payment-sending'|'payment-sent'|'payment-err'} Type @@ -37,19 +37,6 @@ import Pad from '../../components/Pad' * @augments React.PureComponent */ export default class TXBase extends React.PureComponent { - componentDidMount() { - /** - * Force-updates every minute so moment-formatted dates refresh. - */ - this.intervalID = setInterval(() => { - this.forceUpdate() - }, 60000) - } - - componentWillUnmount() { - typeof this.intervalID === 'number' && clearInterval(this.intervalID) - } - render() { const { amt, @@ -191,9 +178,9 @@ export default class TXBase extends React.PureComponent { - - {moment(timestamp).format('hh:mm')} - + + {timestamp} + {typeof amt === 'number' && ( {`${amt.toString()} Sats`} diff --git a/app/screens/Chats/View.js b/app/screens/Chats/View.js index 4533ba27..f5cbf06f 100644 --- a/app/screens/Chats/View.js +++ b/app/screens/Chats/View.js @@ -6,7 +6,6 @@ import { StyleSheet, TouchableOpacity, } from 'react-native' -import moment from 'moment' import { Divider, Icon } from 'react-native-elements' import Ionicons from 'react-native-vector-icons/Ionicons' import Logger from 'react-native-file-log' @@ -165,7 +164,8 @@ export default class ChatsView extends React.PureComponent { { @@ -215,8 +215,9 @@ export default class ChatsView extends React.PureComponent { { if (isSending) { diff --git a/app/screens/WalletOverview/UnifiedTransaction.tsx b/app/screens/WalletOverview/UnifiedTransaction.tsx index c4fee190..c1980180 100644 --- a/app/screens/WalletOverview/UnifiedTransaction.tsx +++ b/app/screens/WalletOverview/UnifiedTransaction.tsx @@ -15,11 +15,11 @@ import React from 'react' import { StyleSheet, Text, TouchableOpacity, View } from 'react-native' import Ionicons from 'react-native-vector-icons/Ionicons' -import moment from 'moment' import { Schema } from 'shock-common' import { connect } from 'react-redux' import Entypo from 'react-native-vector-icons/Entypo' import isFinite from 'lodash/isFinite' +import moment from 'moment' import { ConnectedShockAvatar } from '../../components/ShockAvatar' import * as CSS from '../../res/css' @@ -28,6 +28,7 @@ import Pad from '../../components/Pad' import * as Store from '../../store' import { Tip } from '../../schema' import * as Services from '../../services' +import TimeText from '../../components/time-text' const OUTBOUND_INDICATOR_RADIUS = 20 @@ -72,7 +73,6 @@ export class UnifiedTransaction extends React.PureComponent { return {err} } - const formattedTimestamp = moment(timestamp).fromNow() const convertedBalance = ( Math.round( btcConvert(value.toString(), 'Satoshi', 'BTC') * USDRate * 100, @@ -138,7 +138,7 @@ export class UnifiedTransaction extends React.PureComponent { - {formattedTimestamp} + {timestamp} {(() => { if (status === 'sent') { @@ -222,7 +222,7 @@ const makeMapStateToProps = () => { asChainTX.time_stamp || moment.now().toString() - return Services.normalizeTimestamp(Number(t)) + return Services.normalizeTimestampToMs(Number(t)) })() const value = Math.abs( diff --git a/app/services/utils.ts b/app/services/utils.ts index fe877117..2e4370fd 100644 --- a/app/services/utils.ts +++ b/app/services/utils.ts @@ -80,7 +80,13 @@ export const SET_LAST_SEEN_APP_INTERVAL = 15000 export const isOnline = (lastSeen: number): boolean => Date.now() - lastSeen < SET_LAST_SEEN_APP_INTERVAL * 2 -export function normalizeTimestamp(timestamp: number): number { +/** + * Converts seconds/microseconds timestamps to milliseconds, leaves milliseconds + * timestamps untouched. Works for timestamps no older than 2001. + * @timestamp A timestamp that can be seconds, milliseconds or microseconds. + * Should be no older than 2001. + */ +export function normalizeTimestampToMs(timestamp: number): number { if (timestamp === 0) { return timestamp } @@ -92,10 +98,10 @@ export function normalizeTimestamp(timestamp: number): number { return Number(t) * 1000 } else if (t.length === 13) { // is milliseconds - return Number(t) * 1000 + return Number(t) } else if (t.length === 16) { // is microseconds - return Number(t) * 1000 * 1000 + return Number(t) / 1000 } Logger.log('normalizeTimestamp() -> could not interpret timestamp')