diff --git a/app/containers/MessageBox/ReplyPreview.tsx b/app/containers/MessageBox/ReplyPreview.tsx index a16e51cbc..ad4c59de4 100644 --- a/app/containers/MessageBox/ReplyPreview.tsx +++ b/app/containers/MessageBox/ReplyPreview.tsx @@ -52,17 +52,25 @@ interface IMessageBoxReplyPreview { username: string; getCustomEmoji: Function; useRealName: boolean; + timeFormat: number; } const ReplyPreview = React.memo( - ({ message, Message_TimeFormat, replying, close, useRealName }: IMessageBoxReplyPreview) => { + ({ message, Message_TimeFormat, replying, close, useRealName, timeFormat }: IMessageBoxReplyPreview) => { const { theme } = useTheme(); + const getTimeFormat = () => { + const timeFormats = ['h:mm A', 'H:mm']; + if (timeFormat) { + return timeFormats[timeFormat - 1]; + } + return Message_TimeFormat; + }; + if (!replying) { return null; } - - const time = moment(message.ts).format(Message_TimeFormat); + const time = moment(message.ts).format(getTimeFormat()); return ( diff --git a/app/containers/MessageBox/index.tsx b/app/containers/MessageBox/index.tsx index 6b6b1deb7..ba8cbfad6 100644 --- a/app/containers/MessageBox/index.tsx +++ b/app/containers/MessageBox/index.tsx @@ -1077,6 +1077,7 @@ class MessageBox extends Component { username={user.username} replying={replying} getCustomEmoji={getCustomEmoji} + timeFormat={user.timeFormat} /> ) : null; diff --git a/app/definitions/ILoggedUser.ts b/app/definitions/ILoggedUser.ts index cf5d65788..be95294ec 100644 --- a/app/definitions/ILoggedUser.ts +++ b/app/definitions/ILoggedUser.ts @@ -21,6 +21,7 @@ export interface ILoggedUser { showMessageInMainThread?: boolean; isFromWebView?: boolean; enableMessageParserEarlyAdoption: boolean; + timeFormat: number; } export interface ILoggedUserResultFromServer diff --git a/app/definitions/IUser.ts b/app/definitions/IUser.ts index 14328f230..2acad821e 100644 --- a/app/definitions/IUser.ts +++ b/app/definitions/IUser.ts @@ -112,6 +112,7 @@ export interface INotificationPreferences { pushNotifications: TNotifications; emailNotificationMode?: 'mentions' | 'nothing'; language?: string; + clockMode?: number; } export interface IUserPreferences { diff --git a/app/i18n/locales/en.json b/app/i18n/locales/en.json index a89c45e31..da6ae833d 100644 --- a/app/i18n/locales/en.json +++ b/app/i18n/locales/en.json @@ -1,6 +1,8 @@ { "1_person_reacted": "1 person reacted", "1_user": "1 user", + "12_Hour": "12-hour clock", + "24_Hour": "24-hour clock", "error-action-not-allowed": "{{action}} is not allowed", "error-application-not-found": "Application not found", "error-archived-duplicate-name": "There's an archived channel with name {{room_name}}", @@ -327,6 +329,7 @@ "Message": "Message", "Messages": "Messages", "Message_Reported": "Message reported", + "Message_time_format": "Time format", "Microphone_Permission_Message": "Rocket.Chat needs access to your microphone so you can send audio message.", "Microphone_Permission": "Microphone Permission", "Mute": "Mute", diff --git a/app/lib/database/model/servers/User.js b/app/lib/database/model/servers/User.js index 5d3438237..40afd2e79 100644 --- a/app/lib/database/model/servers/User.js +++ b/app/lib/database/model/servers/User.js @@ -29,4 +29,6 @@ export default class User extends Model { @field('is_from_webview') isFromWebView; @field('enable_message_parser_early_adoption') enableMessageParserEarlyAdoption; + + @field('time_format') timeFormat; } diff --git a/app/lib/database/model/servers/migrations.js b/app/lib/database/model/servers/migrations.js index d1f24f125..e0cc41843 100644 --- a/app/lib/database/model/servers/migrations.js +++ b/app/lib/database/model/servers/migrations.js @@ -103,6 +103,15 @@ export default schemaMigrations({ columns: [{ name: 'enable_message_parser_early_adoption', type: 'boolean', isOptional: true }] }) ] + }, + { + toVersion: 13, + steps: [ + addColumns({ + table: 'users', + columns: [{ name: 'time_format', type: 'number', isOptional: true }] + }) + ] } ] }); diff --git a/app/lib/database/schema/servers.js b/app/lib/database/schema/servers.js index 68eddb036..1738130a0 100644 --- a/app/lib/database/schema/servers.js +++ b/app/lib/database/schema/servers.js @@ -1,7 +1,7 @@ import { appSchema, tableSchema } from '@nozbe/watermelondb'; export default appSchema({ - version: 12, + version: 13, tables: [ tableSchema({ name: 'users', @@ -17,7 +17,8 @@ export default appSchema({ { name: 'show_message_in_main_thread', type: 'boolean', isOptional: true }, { name: 'avatar_etag', type: 'string', isOptional: true }, { name: 'is_from_webview', type: 'boolean', isOptional: true }, - { name: 'enable_message_parser_early_adoption', type: 'boolean', isOptional: true } + { name: 'enable_message_parser_early_adoption', type: 'boolean', isOptional: true }, + { name: 'time_format', type: 'number', isOptional: true } ] }), tableSchema({ diff --git a/app/lib/methods/subscriptions/rooms.ts b/app/lib/methods/subscriptions/rooms.ts index 7172cb458..5847306a0 100644 --- a/app/lib/methods/subscriptions/rooms.ts +++ b/app/lib/methods/subscriptions/rooms.ts @@ -295,6 +295,9 @@ export default function subscribeRooms() { if ((['settings.preferences.showMessageInMainThread'] as any) in diff) { store.dispatch(setUser({ showMessageInMainThread: diff['settings.preferences.showMessageInMainThread'] })); } + if ((['settings.preferences.clockMode'] as any) in diff) { + store.dispatch(setUser({ timeFormat: diff['settings.preferences.clockMode'] })); + } } if (/subscriptions/.test(ev)) { if (type === 'removed') { diff --git a/app/lib/services/connect.ts b/app/lib/services/connect.ts index caca9caaa..629ace9f1 100644 --- a/app/lib/services/connect.ts +++ b/app/lib/services/connect.ts @@ -285,7 +285,8 @@ async function login(credentials: ICredentials, isFromWebView = false): Promise< avatarETag: result.me.avatarETag, isFromWebView, showMessageInMainThread: result.me.settings?.preferences?.showMessageInMainThread ?? true, - enableMessageParserEarlyAdoption: result.me.settings?.preferences?.enableMessageParserEarlyAdoption ?? true + enableMessageParserEarlyAdoption: result.me.settings?.preferences?.enableMessageParserEarlyAdoption ?? true, + timeFormat: result.me.settings?.preferences?.clockMode ?? 0 }; return user; } diff --git a/app/views/RoomView/index.tsx b/app/views/RoomView/index.tsx index 82aa963f0..3e8797367 100644 --- a/app/views/RoomView/index.tsx +++ b/app/views/RoomView/index.tsx @@ -139,7 +139,7 @@ const roomAttrsUpdate = [ ] as TRoomUpdate[]; interface IRoomViewProps extends IBaseScreen { - user: Pick; + user: Pick; appState: string; useRealName?: boolean; isAuthenticated: boolean; @@ -1183,10 +1183,19 @@ class RoomView extends React.Component { }); }; + getMessageTimeFormat = () => { + const { user, Message_TimeFormat } = this.props; + const timeFormats = ['h:mm A', 'H:mm']; + if (user.timeFormat) { + return timeFormats[user.timeFormat - 1]; + } + return Message_TimeFormat; + }; + renderItem = (item: TAnyMessageModel, previousItem: TAnyMessageModel, highlightedMessage?: string) => { const { room, lastOpen, canAutoTranslate } = this.state; - const { user, Message_GroupingPeriod, Message_TimeFormat, useRealName, baseUrl, Message_Read_Receipt_Enabled, theme } = - this.props; + const { user, Message_GroupingPeriod, useRealName, baseUrl, Message_Read_Receipt_Enabled, theme } = this.props; + const Message_TimeFormat = this.getMessageTimeFormat(); let dateSeparator = null; let showUnreadSeparator = false; diff --git a/app/views/UserPreferencesView/index.tsx b/app/views/UserPreferencesView/index.tsx index bb6b45dcd..123eebddc 100644 --- a/app/views/UserPreferencesView/index.tsx +++ b/app/views/UserPreferencesView/index.tsx @@ -14,14 +14,33 @@ import { getUserSelector } from '../../selectors/login'; import { ProfileStackParamList } from '../../stacks/types'; import { Services } from '../../lib/services'; import { useAppSelector } from '../../lib/hooks'; +import { useTheme } from '../../theme'; interface IUserPreferencesViewProps { navigation: StackNavigationProp; + theme: string; } const UserPreferencesView = ({ navigation }: IUserPreferencesViewProps): JSX.Element => { - const { enableMessageParserEarlyAdoption, id } = useAppSelector(state => getUserSelector(state)); + const { enableMessageParserEarlyAdoption, timeFormat, id } = useAppSelector(state => getUserSelector(state)); const dispatch = useDispatch(); + const { colors } = useTheme(); + + interface ITimeFormats { + label: string; + value: number; + } + + const timeFormats: ITimeFormats[] = [ + { + label: '12_Hour', + value: 1 + }, + { + label: '24_Hour', + value: 2 + } + ]; useEffect(() => { navigation.setOptions({ @@ -47,6 +66,17 @@ const UserPreferencesView = ({ navigation }: IUserPreferencesViewProps): JSX.Ele ); + const renderIcon = () => ; + + const onChangeTimeFormat = async (item: ITimeFormats) => { + try { + dispatch(setUser({ timeFormat: item.value })); + await Services.saveUserPreferences({ id, clockMode: item.value }); + } catch (e) { + log(e); + } + }; + return ( @@ -70,6 +100,21 @@ const UserPreferencesView = ({ navigation }: IUserPreferencesViewProps): JSX.Ele /> + + + onChangeTimeFormat(timeFormats[0])} + right={() => (timeFormat === timeFormats[0].value ? renderIcon() : null)} + /> + + onChangeTimeFormat(timeFormats[1])} + right={() => (timeFormat === timeFormats[1].value ? renderIcon() : null)} + /> + + );