From 33c63d95cf486c4e15978bee4e903926684444ec Mon Sep 17 00:00:00 2001 From: Tommy Chow Date: Wed, 29 Dec 2021 19:54:32 -0500 Subject: [PATCH] Add timestap option to chat --- lib/core/settings/account_settings.dart | 17 ++++------ lib/core/settings/chat_settings.dart | 14 +++++++-- lib/core/settings/settings_store.dart | 10 ++++++ lib/core/settings/settings_store.g.dart | 36 +++++++++++++++++++++- lib/core/settings/video_settings.dart | 6 ++-- lib/models/irc_message.dart | 32 +++++++++++++++++++ lib/screens/channel/chat/chat.dart | 15 ++++++--- lib/screens/channel/chat/chat_message.dart | 8 +++++ 8 files changed, 116 insertions(+), 22 deletions(-) diff --git a/lib/core/settings/account_settings.dart b/lib/core/settings/account_settings.dart index ea54be6b..a303ea2e 100644 --- a/lib/core/settings/account_settings.dart +++ b/lib/core/settings/account_settings.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:frosty/core/auth/auth_store.dart'; import 'package:frosty/core/settings/profile_card.dart'; import 'package:frosty/core/settings/settings_store.dart'; @@ -17,16 +16,12 @@ class AccountSettings extends StatelessWidget { @override Widget build(BuildContext context) { - return Observer( - builder: (context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SectionHeader('ACCOUNT'), - ProfileCard(authStore: authStore), - ], - ); - }, + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SectionHeader('ACCOUNT'), + ProfileCard(authStore: authStore), + ], ); } } diff --git a/lib/core/settings/chat_settings.dart b/lib/core/settings/chat_settings.dart index d24d67f1..176c6d0c 100644 --- a/lib/core/settings/chat_settings.dart +++ b/lib/core/settings/chat_settings.dart @@ -17,15 +17,25 @@ class ChatSettings extends StatelessWidget { children: [ const SectionHeader('CHAT'), SwitchListTile.adaptive( - title: const Text('Hide Banned Messages'), + title: const Text('Hide banned messages'), value: settingsStore.hideBannedMessages, onChanged: (newValue) => settingsStore.hideBannedMessages = newValue, ), SwitchListTile.adaptive( - title: const Text('Enable Zero-Width Emotes'), + title: const Text('Zero-width emotes'), value: settingsStore.zeroWidthEnabled, onChanged: (newValue) => settingsStore.zeroWidthEnabled = newValue, ), + SwitchListTile.adaptive( + title: const Text('Timestamps'), + value: settingsStore.timeStampsEnabled, + onChanged: (newValue) => settingsStore.timeStampsEnabled = newValue, + ), + SwitchListTile.adaptive( + title: const Text('12-hour timestamps'), + value: settingsStore.twelveHourTimeStamp, + onChanged: settingsStore.timeStampsEnabled ? (newValue) => settingsStore.twelveHourTimeStamp = newValue : null, + ), ], ); }, diff --git a/lib/core/settings/settings_store.dart b/lib/core/settings/settings_store.dart index cd0a4090..8ee6fa28 100644 --- a/lib/core/settings/settings_store.dart +++ b/lib/core/settings/settings_store.dart @@ -25,6 +25,12 @@ abstract class _SettingsStoreBase with Store { @observable var expandInfo = false; + @observable + var timeStampsEnabled = false; + + @observable + var twelveHourTimeStamp = false; + Future init() async { // Retrieve the instance that will allow us to store and persist settings. final prefs = await SharedPreferences.getInstance(); @@ -35,6 +41,8 @@ abstract class _SettingsStoreBase with Store { hideBannedMessages = prefs.getBool('hide_banned_messages') ?? hideBannedMessages; zeroWidthEnabled = prefs.getBool('zero_width_enabled') ?? zeroWidthEnabled; expandInfo = prefs.getBool('expand_info') ?? expandInfo; + timeStampsEnabled = prefs.getBool('time_stamps_enabled') ?? timeStampsEnabled; + twelveHourTimeStamp = prefs.getBool('twelve_hour_time_stamps') ?? twelveHourTimeStamp; // Set up reactions to store setting anytime they're changed. // The ReactionDisposer will not be needed since settings will always exist. @@ -43,6 +51,8 @@ abstract class _SettingsStoreBase with Store { reaction((_) => zeroWidthEnabled, (bool newValue) => prefs.setBool('zero_width_enabled', newValue)); reaction((_) => hideBannedMessages, (bool newValue) => prefs.setBool('hide_banned_messages', newValue)); reaction((_) => expandInfo, (bool newValue) => prefs.setBool('expand_info', newValue)); + reaction((_) => timeStampsEnabled, (bool newValue) => prefs.setBool('time_stamps_enabled', newValue)); + reaction((_) => twelveHourTimeStamp, (bool newValue) => prefs.setBool('twelve_hour_time_stamps', newValue)); // A reaction that will enable immersive mode when entering fullscreen and disable it when exiting. reaction( diff --git a/lib/core/settings/settings_store.g.dart b/lib/core/settings/settings_store.g.dart index 8616721e..faa61b03 100644 --- a/lib/core/settings/settings_store.g.dart +++ b/lib/core/settings/settings_store.g.dart @@ -101,6 +101,38 @@ mixin _$SettingsStore on _SettingsStoreBase, Store { }); } + final _$timeStampsEnabledAtom = + Atom(name: '_SettingsStoreBase.timeStampsEnabled'); + + @override + bool get timeStampsEnabled { + _$timeStampsEnabledAtom.reportRead(); + return super.timeStampsEnabled; + } + + @override + set timeStampsEnabled(bool value) { + _$timeStampsEnabledAtom.reportWrite(value, super.timeStampsEnabled, () { + super.timeStampsEnabled = value; + }); + } + + final _$twelveHourTimeStampAtom = + Atom(name: '_SettingsStoreBase.twelveHourTimeStamp'); + + @override + bool get twelveHourTimeStamp { + _$twelveHourTimeStampAtom.reportRead(); + return super.twelveHourTimeStamp; + } + + @override + set twelveHourTimeStamp(bool value) { + _$twelveHourTimeStampAtom.reportWrite(value, super.twelveHourTimeStamp, () { + super.twelveHourTimeStamp = value; + }); + } + @override String toString() { return ''' @@ -109,7 +141,9 @@ overlayEnabled: ${overlayEnabled}, hideBannedMessages: ${hideBannedMessages}, zeroWidthEnabled: ${zeroWidthEnabled}, fullScreen: ${fullScreen}, -expandInfo: ${expandInfo} +expandInfo: ${expandInfo}, +timeStampsEnabled: ${timeStampsEnabled}, +twelveHourTimeStamp: ${twelveHourTimeStamp} '''; } } diff --git a/lib/core/settings/video_settings.dart b/lib/core/settings/video_settings.dart index 73637af6..eb94b7e8 100644 --- a/lib/core/settings/video_settings.dart +++ b/lib/core/settings/video_settings.dart @@ -17,14 +17,14 @@ class VideoSettings extends StatelessWidget { children: [ const SectionHeader('VIDEO'), SwitchListTile.adaptive( - title: const Text('Enable Video'), + title: const Text('Video'), value: settingsStore.videoEnabled, onChanged: (newValue) => settingsStore.videoEnabled = newValue, ), SwitchListTile.adaptive( - title: const Text('Enable Video Overlay'), + title: const Text('Video overlay'), value: settingsStore.overlayEnabled, - onChanged: (newValue) => settingsStore.overlayEnabled = newValue, + onChanged: settingsStore.videoEnabled ? (newValue) => settingsStore.overlayEnabled = newValue : null, ), ], ); diff --git a/lib/models/irc_message.dart b/lib/models/irc_message.dart index 122ed6db..3f927e32 100644 --- a/lib/models/irc_message.dart +++ b/lib/models/irc_message.dart @@ -2,6 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:frosty/models/badges.dart'; import 'package:frosty/models/emotes.dart'; +import 'package:intl/intl.dart'; /// The object representation of a Twitch IRC message. class IRCMessage { @@ -68,14 +69,39 @@ class IRCMessage { /// Returns an [InlineSpan] list that corresponds to the badges, username, words, and emotes of the given [IRCMessage]. List generateSpan({ + required TextStyle? style, required Map emoteToObject, required Map badgeToObject, bool hideMessage = false, bool zeroWidthEnabled = false, + Timestamp timestamp = Timestamp.none, }) { // The span list that will be used to render the chat message final span = []; + if (timestamp != Timestamp.none) { + final time = tags['tmi-sent-ts'] ?? DateTime.now().millisecondsSinceEpoch.toString(); + final parsedTime = DateTime.fromMillisecondsSinceEpoch(int.parse(time)); + + if (timestamp == Timestamp.twentyFour) { + span.add( + TextSpan( + text: '${DateFormat.Hm().format(parsedTime)} ', + style: style?.copyWith(color: style.color?.withOpacity(0.5)), + ), + ); + } + + if (timestamp == Timestamp.twelve) { + span.add( + TextSpan( + text: '${DateFormat('h:mm').format(parsedTime)} ', + style: style?.copyWith(color: style.color?.withOpacity(0.5)), + ), + ); + } + } + // Add any badges to the span. final badges = tags['badges']; if (badges != null) { @@ -472,3 +498,9 @@ enum Command { globalUserState, none, } + +enum Timestamp { + none, + twelve, + twentyFour, +} diff --git a/lib/screens/channel/chat/chat.dart b/lib/screens/channel/chat/chat.dart index 4244ec2e..625111ad 100644 --- a/lib/screens/channel/chat/chat.dart +++ b/lib/screens/channel/chat/chat.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:frosty/models/irc_message.dart'; import 'package:frosty/screens/channel/chat/chat_bottom_bar.dart'; import 'package:frosty/screens/channel/chat/chat_message.dart'; import 'package:frosty/screens/channel/chat/chat_store.dart'; @@ -52,11 +53,15 @@ class _ChatState extends State with WidgetsBindingObserver { controller: chatStore.scrollController, itemBuilder: (context, index) => Observer( builder: (context) => ChatMessage( - ircMessage: chatStore.messages[index], - assetsStore: chatStore.assetsStore, - hideMessageIfBanned: chatStore.settings.hideBannedMessages, - zeroWidth: chatStore.settings.zeroWidthEnabled, - ), + ircMessage: chatStore.messages[index], + assetsStore: chatStore.assetsStore, + hideMessageIfBanned: chatStore.settings.hideBannedMessages, + zeroWidth: chatStore.settings.zeroWidthEnabled, + timestamp: chatStore.settings.timeStampsEnabled + ? chatStore.settings.twelveHourTimeStamp + ? Timestamp.twelve + : Timestamp.twentyFour + : Timestamp.none), ), ), ), diff --git a/lib/screens/channel/chat/chat_message.dart b/lib/screens/channel/chat/chat_message.dart index 1164d149..90045982 100644 --- a/lib/screens/channel/chat/chat_message.dart +++ b/lib/screens/channel/chat/chat_message.dart @@ -7,6 +7,7 @@ class ChatMessage extends StatelessWidget { final ChatAssetsStore assetsStore; final bool hideMessageIfBanned; final bool zeroWidth; + final Timestamp timestamp; const ChatMessage({ Key? key, @@ -14,6 +15,7 @@ class ChatMessage extends StatelessWidget { required this.assetsStore, this.hideMessageIfBanned = true, this.zeroWidth = false, + required this.timestamp, }) : super(key: key); @override @@ -29,9 +31,11 @@ class ChatMessage extends StatelessWidget { child: Text.rich( TextSpan( children: ircMessage.generateSpan( + style: DefaultTextStyle.of(context).style, emoteToObject: assetsStore.emoteToObject, badgeToObject: assetsStore.badgesToObject, zeroWidthEnabled: zeroWidth, + timestamp: timestamp, ), ), ), @@ -50,10 +54,12 @@ class ChatMessage extends StatelessWidget { Text.rich( TextSpan( children: ircMessage.generateSpan( + style: DefaultTextStyle.of(context).style, emoteToObject: assetsStore.emoteToObject, badgeToObject: assetsStore.badgesToObject, hideMessage: hideMessageIfBanned, zeroWidthEnabled: zeroWidth, + timestamp: timestamp, ), ), ), @@ -97,9 +103,11 @@ class ChatMessage extends StatelessWidget { Text.rich( TextSpan( children: ircMessage.generateSpan( + style: DefaultTextStyle.of(context).style, emoteToObject: assetsStore.emoteToObject, badgeToObject: assetsStore.badgesToObject, zeroWidthEnabled: zeroWidth, + timestamp: timestamp, ), ), ),