diff --git a/mobile/components/Messages/index.js b/mobile/components/Messages/index.js index c2073c4a6c..fd9204982b 100644 --- a/mobile/components/Messages/index.js +++ b/mobile/components/Messages/index.js @@ -8,7 +8,7 @@ import Message from '../Message'; import InfiniteList from '../InfiniteList'; import { ThreadMargin } from '../../views/Thread/style'; import { sortAndGroupMessages } from '../../../shared/clients/group-messages'; -import { convertTimestampToDate } from '../../../src/helpers/utils'; +import { convertTimestampToDate } from '../../../shared/time-formatting'; import { withCurrentUser } from '../../components/WithCurrentUser'; import RoboText from './RoboText'; import Author from './Author'; diff --git a/mobile/views/Channel/index.js b/mobile/views/Channel/index.js index 24726fb796..388a29912b 100644 --- a/mobile/views/Channel/index.js +++ b/mobile/views/Channel/index.js @@ -25,11 +25,12 @@ import { } from './style'; import ErrorBoundary from '../../components/ErrorBoundary'; import { FullscreenNullState } from '../../components/NullStates'; +import type { NavigationProps } from 'react-navigation'; type Props = { isLoading: boolean, hasError: boolean, - navigation: Object, + navigation: NavigationProps, data: { channel?: GetChannelType, }, @@ -38,6 +39,23 @@ type Props = { const ChannelThreadFeed = compose(getChannelThreadConnection)(ThreadFeed); class Channel extends Component { + setTitle = () => { + const { data: { channel }, navigation } = this.props; + let title; + if (channel) { + title = channel.name; + } else { + title = 'Loading channel...'; + } + if (navigation.state.params.title === title) return; + navigation.setParams({ title }); + }; + componentDidUpdate() { + this.setTitle(); + } + componentDidMount() { + this.setTitle(); + } render() { const { data, isLoading, hasError, navigation } = this.props; if (data.channel) { diff --git a/mobile/views/Community/index.js b/mobile/views/Community/index.js index c87d34732d..3647c0f335 100644 --- a/mobile/views/Community/index.js +++ b/mobile/views/Community/index.js @@ -61,6 +61,23 @@ const RemoteThreadItem = compose(getThreadById, withNavigation)( const CommunityThreadFeed = compose(getCommunityThreads)(ThreadFeed); class Community extends Component { + setTitle = () => { + const { data: { community }, navigation } = this.props; + let title; + if (community) { + title = community.name; + } else { + title = 'Loading community...'; + } + if (navigation.state.params.title === title) return; + navigation.setParams({ title }); + }; + componentDidUpdate() { + this.setTitle(); + } + componentDidMount() { + this.setTitle(); + } render() { const { data: { community }, isLoading, hasError, navigation } = this.props; diff --git a/mobile/views/DirectMessageThread/components/DirectMessageThread.js b/mobile/views/DirectMessageThread/components/DirectMessageThread.js index 927cb4ed49..0000513b5a 100644 --- a/mobile/views/DirectMessageThread/components/DirectMessageThread.js +++ b/mobile/views/DirectMessageThread/components/DirectMessageThread.js @@ -40,6 +40,23 @@ type Props = { }; class DirectMessageThread extends Component { + setTitle = () => { + const { data: { directMessageThread }, navigation } = this.props; + let title = directMessageThread + ? sentencify(directMessageThread.participants.map(({ name }) => name)) + : 'Loading thread...'; + if (navigation.state.params.title === title) return; + navigation.setParams({ title }); + }; + + componentDidMount() { + this.setTitle(); + } + + componentDidUpdate() { + this.setTitle(); + } + sendMessage = text => { if (!this.props.data.directMessageThread) return; this.props.sendDirectMessage({ diff --git a/mobile/views/DirectMessageThread/index.js b/mobile/views/DirectMessageThread/index.js index d5dd1badfe..9668259d4d 100644 --- a/mobile/views/DirectMessageThread/index.js +++ b/mobile/views/DirectMessageThread/index.js @@ -8,16 +8,11 @@ import DirectMessageThread from './components/DirectMessageThread'; import { Wrapper } from './style'; import ErrorBoundary from '../../components/ErrorBoundary'; import type { GetUserType } from '../../../shared/graphql/queries/user/getUser'; +import type { NavigationProps } from 'react-navigation'; type Props = { currentUser: ?GetUserType, - navigation?: { - state: { - params: { - id: string, - }, - }, - }, + navigation: NavigationProps, }; class DirectMessageThreadView extends React.Component { diff --git a/mobile/views/TabBar/BaseStack.js b/mobile/views/TabBar/BaseStack.js index b0d83800a8..09d44d63b6 100644 --- a/mobile/views/TabBar/BaseStack.js +++ b/mobile/views/TabBar/BaseStack.js @@ -12,26 +12,26 @@ const BaseStack = { Thread: { screen: withMappedNavigationProps(Thread), navigationOptions: ({ navigation }: NavigationScreenConfigProps) => ({ - headerTitle: navigation.state.params.title || 'Thread', + headerTitle: navigation.state.params.title || null, tabBarVisible: false, }), }, Community: { screen: withMappedNavigationProps(Community), navigationOptions: ({ navigation }: NavigationScreenConfigProps) => ({ - headerTitle: null, + headerTitle: navigation.state.params.title || null, }), }, Channel: { screen: withMappedNavigationProps(Channel), navigationOptions: ({ navigation }: NavigationScreenConfigProps) => ({ - headerTitle: null, + headerTitle: navigation.state.params.title || null, }), }, User: { screen: withMappedNavigationProps(User), navigationOptions: ({ navigation }: NavigationScreenConfigProps) => ({ - header: null, + headerTitle: navigation.state.params.title || null, }), }, }; diff --git a/mobile/views/Thread/index.js b/mobile/views/Thread/index.js index 3ca01b3fd7..661ca91d31 100644 --- a/mobile/views/Thread/index.js +++ b/mobile/views/Thread/index.js @@ -12,7 +12,7 @@ import Messages from '../../components/Messages'; import ChatInput from '../../components/ChatInput'; import getThreadMessageConnection from '../../../shared/graphql/queries/thread/getThreadMessageConnection'; import sendMessageMutation from '../../../shared/graphql/mutations/message/sendMessage'; -import { convertTimestampToDate } from '../../../src/helpers/utils'; +import { convertTimestampToDate } from '../../../shared/time-formatting'; import { withCurrentUser } from '../../components/WithCurrentUser'; import CommunityHeader from './components/CommunityHeader'; import Byline from './components/Byline'; @@ -40,6 +40,26 @@ type Props = { }; class Thread extends Component { + setTitle = () => { + const { data: { thread }, navigation } = this.props; + let title; + if (thread) { + title = thread.content.title; + } else { + title = 'Loading thread...'; + } + if (navigation.state.params.title === title) return; + navigation.setParams({ title }); + }; + + componentDidMount() { + this.setTitle(); + } + + componentDidUpdate() { + this.setTitle(); + } + sendMessage = (body: string, user: Object) => { const { quotedMessage, data: { thread } } = this.props; if (!thread) return; diff --git a/mobile/views/User/index.js b/mobile/views/User/index.js index 15d64ae429..daa8a95870 100644 --- a/mobile/views/User/index.js +++ b/mobile/views/User/index.js @@ -7,9 +7,11 @@ import { } from '../../../shared/graphql/queries/user/getUser'; import ViewNetworkHandler from '../../components/ViewNetworkHandler'; import Profile from './profile'; +import type { NavigationProps } from 'react-navigation'; type Props = { id: ?string, + navigation: NavigationProps, }; type State = { diff --git a/mobile/views/User/profile.js b/mobile/views/User/profile.js index 33155b8cb1..16cf372a5d 100644 --- a/mobile/views/User/profile.js +++ b/mobile/views/User/profile.js @@ -42,13 +42,24 @@ const UserThreadFeed = compose(getUserThreadConnection)(ThreadFeed); class User extends Component { state = { feed: 'participant' }; - componentDidUpdate() { + setTitle = () => { const { data: { user }, navigation } = this.props; - if (!user) return; - const title = navigation.getParam('title'); - if (!title && user) return navigation.setParams({ title: user.name }); - if (title && title !== user.name) - return navigation.setParams({ title: user.name }); + let title; + if (user) { + title = `${user.name} (@${user.username})`; + } else { + title = 'Loading user...'; + } + if (navigation.state.params.title === title) return; + navigation.setParams({ title }); + }; + + componentDidMount() { + this.setTitle(); + } + + componentDidUpdate() { + this.setTitle(); } toggleFeed = (feed: string) => this.setState({ feed }); diff --git a/shared/time-formatting.js b/shared/time-formatting.js new file mode 100644 index 0000000000..006a9e1a49 --- /dev/null +++ b/shared/time-formatting.js @@ -0,0 +1,49 @@ +// @flow + +export const convertTimestampToDate = (timestamp: number) => { + let monthNames = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December', + ]; + let date = new Date(timestamp); + let day = date.getDate(); + let monthIndex = date.getMonth(); + let month = monthNames[monthIndex]; + let year = date.getFullYear(); + let hours = date.getHours() || 0; + let cleanHours; + if (hours === 0) { + cleanHours = 12; // if timestamp is between midnight and 1am, show 12:XX am + } else { + cleanHours = hours > 12 ? hours - 12 : hours; // else show proper am/pm -- todo: support 24hr time + } + let minutes = date.getMinutes(); + minutes = minutes >= 10 ? minutes : '0' + minutes.toString(); // turns 4 minutes into 04 minutes + let ampm = hours >= 12 ? 'pm' : 'am'; // todo: support 24hr time + return `${month} ${day}, ${year} · ${cleanHours}:${minutes}${ampm}`; +}; + +export const convertTimestampToTime = (timestamp: Date) => { + let date = new Date(timestamp); + let hours = date.getHours() || 0; + let cleanHours; + if (hours === 0) { + cleanHours = 12; // if timestamp is between midnight and 1am, show 12:XX am + } else { + cleanHours = hours > 12 ? hours - 12 : hours; // else show proper am/pm -- todo: support 24hr time + } + let minutes = date.getMinutes(); + minutes = minutes >= 10 ? minutes : '0' + minutes.toString(); // turns 4 minutes into 04 minutes + let ampm = hours >= 12 ? 'pm' : 'am'; // todo: support 24hr time + return `${cleanHours}:${minutes}${ampm}`; +}; diff --git a/src/components/listItems/index.js b/src/components/listItems/index.js index d526c4e4ad..b99ccad954 100644 --- a/src/components/listItems/index.js +++ b/src/components/listItems/index.js @@ -5,7 +5,7 @@ import compose from 'recompose/compose'; import Icon from '../icons'; import Badge from '../badges'; import Avatar from '../avatar'; -import { convertTimestampToDate } from '../../helpers/utils'; +import { convertTimestampToDate } from 'shared/time-formatting'; import Reputation from '../reputation'; import { Wrapper, diff --git a/src/components/messageGroup/index.js b/src/components/messageGroup/index.js index 67fe6cc236..e1ec336c5d 100644 --- a/src/components/messageGroup/index.js +++ b/src/components/messageGroup/index.js @@ -2,7 +2,7 @@ import React, { Component, Fragment } from 'react'; import { connect } from 'react-redux'; import Link from 'src/components/link'; -import { convertTimestampToDate } from '../../helpers/utils'; +import { convertTimestampToDate } from 'shared/time-formatting'; import Badge from '../badges'; import Avatar from '../avatar'; import Message from '../message'; diff --git a/src/helpers/utils.js b/src/helpers/utils.js index 98defd4358..c718d5ff21 100644 --- a/src/helpers/utils.js +++ b/src/helpers/utils.js @@ -2,54 +2,6 @@ import React from 'react'; import replace from 'string-replace-to-array'; -export const convertTimestampToDate = (timestamp: number) => { - let monthNames = [ - 'January', - 'February', - 'March', - 'April', - 'May', - 'June', - 'July', - 'August', - 'September', - 'October', - 'November', - 'December', - ]; - let date = new Date(timestamp); - let day = date.getDate(); - let monthIndex = date.getMonth(); - let month = monthNames[monthIndex]; - let year = date.getFullYear(); - let hours = date.getHours() || 0; - let cleanHours; - if (hours === 0) { - cleanHours = 12; // if timestamp is between midnight and 1am, show 12:XX am - } else { - cleanHours = hours > 12 ? hours - 12 : hours; // else show proper am/pm -- todo: support 24hr time - } - let minutes = date.getMinutes(); - minutes = minutes >= 10 ? minutes : '0' + minutes.toString(); // turns 4 minutes into 04 minutes - let ampm = hours >= 12 ? 'pm' : 'am'; // todo: support 24hr time - return `${month} ${day}, ${year} · ${cleanHours}:${minutes}${ampm}`; -}; - -export const convertTimestampToTime = (timestamp: Date) => { - let date = new Date(timestamp); - let hours = date.getHours() || 0; - let cleanHours; - if (hours === 0) { - cleanHours = 12; // if timestamp is between midnight and 1am, show 12:XX am - } else { - cleanHours = hours > 12 ? hours - 12 : hours; // else show proper am/pm -- todo: support 24hr time - } - let minutes = date.getMinutes(); - minutes = minutes >= 10 ? minutes : '0' + minutes.toString(); // turns 4 minutes into 04 minutes - let ampm = hours >= 12 ? 'pm' : 'am'; // todo: support 24hr time - return `${cleanHours}:${minutes}${ampm}`; -}; - /* Best guess at if user is on a mobile device. Used in the modal components to determine where the modal should be positioned, how it should close and diff --git a/src/views/thread/components/threadDetail.js b/src/views/thread/components/threadDetail.js index 2f17a68e7e..19728f16b8 100644 --- a/src/views/thread/components/threadDetail.js +++ b/src/views/thread/components/threadDetail.js @@ -4,10 +4,8 @@ import compose from 'recompose/compose'; import { connect } from 'react-redux'; import { withRouter } from 'react-router'; import Link from 'src/components/link'; -import { - getLinkPreviewFromUrl, - convertTimestampToDate, -} from '../../../helpers/utils'; +import { getLinkPreviewFromUrl } from '../../../helpers/utils'; +import { convertTimestampToDate } from 'shared/time-formatting'; import { timeDifference } from 'shared/time-difference'; import isURL from 'validator/lib/isURL'; import { URLS } from '../../../helpers/regexps'; diff --git a/src/views/userSettings/components/recurringPaymentsList.js b/src/views/userSettings/components/recurringPaymentsList.js index 6e31f2f278..342eca6386 100644 --- a/src/views/userSettings/components/recurringPaymentsList.js +++ b/src/views/userSettings/components/recurringPaymentsList.js @@ -6,7 +6,7 @@ import { BillingListItem } from 'src/components/listItems'; import { IconButton } from 'src/components/buttons'; import { UpsellUpgradeToPro } from 'src/components/upsell'; import { openModal } from 'src/actions/modals'; -import { convertTimestampToDate } from 'src/helpers/utils'; +import { convertTimestampToDate } from 'shared/time-formatting'; import getCurrentUserRecurringPayments from 'shared/graphql/queries/user/getCurrentUserRecurringPayments'; import type { GetCurrentUserRecurringPaymentsType } from 'shared/graphql/queries/user/getCurrentUserRecurringPayments'; import { displayLoadingCard } from 'src/components/loading';