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')