diff --git a/lib/screens/channel/chat/chat.dart b/lib/screens/channel/chat/chat.dart index b8f7765e..4af73766 100644 --- a/lib/screens/channel/chat/chat.dart +++ b/lib/screens/channel/chat/chat.dart @@ -108,7 +108,7 @@ class Chat extends StatelessWidget { ), ), ), - if (chatStore.settings.showBottomBar) ChatBottomBar(chatStore: chatStore), + if (chatStore.settings.showBottomBar && !chatStore.settings.fullScreen) ChatBottomBar(chatStore: chatStore), if (chatStore.assetsStore.showEmoteMenu) SizedBox( height: MediaQuery.of(context).size.height / 3, diff --git a/lib/screens/channel/chat/widgets/chat_bottom_bar.dart b/lib/screens/channel/chat/widgets/chat_bottom_bar.dart index 2efc169a..7b8235b1 100644 --- a/lib/screens/channel/chat/widgets/chat_bottom_bar.dart +++ b/lib/screens/channel/chat/widgets/chat_bottom_bar.dart @@ -73,42 +73,54 @@ class ChatBottomBar extends StatelessWidget { ), ), ), - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0), - child: TextField( - textInputAction: TextInputAction.send, - focusNode: chatStore.textFieldFocusNode, - minLines: 1, - maxLines: 5, - enabled: chatStore.auth.isLoggedIn ? true : false, - decoration: InputDecoration( - suffixIcon: IconButton( - color: chatStore.assetsStore.showEmoteMenu ? Colors.deepPurple : null, - tooltip: 'Emote Menu', - icon: const Icon(Icons.emoji_emotions_outlined), - onPressed: () { - FocusScope.of(context).unfocus(); - chatStore.assetsStore.showEmoteMenu = !chatStore.assetsStore.showEmoteMenu; - }, + if (!chatStore.expandChat && + chatStore.settings.landscapeChatWidth < 0.3 && + MediaQuery.of(context).orientation == Orientation.landscape) + IconButton( + tooltip: 'Send a Message', + onPressed: () { + chatStore.expandChat = true; + chatStore.textFieldFocusNode.requestFocus(); + }, + icon: const Icon(Icons.chat)) + else + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0), + child: TextField( + textInputAction: TextInputAction.send, + focusNode: chatStore.textFieldFocusNode, + minLines: 1, + maxLines: 5, + enabled: chatStore.auth.isLoggedIn ? true : false, + decoration: InputDecoration( + suffixIcon: IconButton( + color: chatStore.assetsStore.showEmoteMenu ? Colors.deepPurple : null, + tooltip: 'Emote Menu', + icon: const Icon(Icons.emoji_emotions_outlined), + onPressed: () { + FocusScope.of(context).unfocus(); + chatStore.assetsStore.showEmoteMenu = !chatStore.assetsStore.showEmoteMenu; + }, + ), + hintMaxLines: 1, + contentPadding: const EdgeInsets.all(10.0), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(15.0)), + ), + labelText: chatStore.auth.isLoggedIn ? 'Send a message' : 'Log in to chat', ), - hintMaxLines: 1, - contentPadding: const EdgeInsets.all(10.0), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(15.0)), - ), - labelText: chatStore.auth.isLoggedIn ? 'Send a message' : 'Log in to chat', + controller: chatStore.textController, + onSubmitted: chatStore.sendMessage, ), - controller: chatStore.textController, - onSubmitted: chatStore.sendMessage, ), ), - ), - IconButton( - tooltip: 'Send', - icon: const Icon(Icons.send), - onPressed: chatStore.auth.isLoggedIn ? () => chatStore.sendMessage(chatStore.textController.text) : null, - ) + if (chatStore.settings.landscapeChatWidth > 0.3 || chatStore.expandChat || MediaQuery.of(context).orientation == Orientation.portrait) + IconButton( + tooltip: 'Send', + icon: const Icon(Icons.send), + onPressed: chatStore.auth.isLoggedIn ? () => chatStore.sendMessage(chatStore.textController.text) : null, + ) ], ), ], diff --git a/lib/screens/channel/stores/chat_store.dart b/lib/screens/channel/stores/chat_store.dart index 442d8079..2595daeb 100644 --- a/lib/screens/channel/stores/chat_store.dart +++ b/lib/screens/channel/stores/chat_store.dart @@ -78,6 +78,9 @@ abstract class ChatStoreBase with Store { @readonly var _userState = const USERSTATE(); + @observable + var expandChat = false; + final reactions = []; ChatStoreBase({ @@ -116,6 +119,8 @@ abstract class ChatStoreBase with Store { // Add a listener to the textfield focus that will hide the emote menu if it is currently shown. textFieldFocusNode.addListener(() { if (textFieldFocusNode.hasFocus && assetsStore.showEmoteMenu) assetsStore.showEmoteMenu = false; + + if (!textFieldFocusNode.hasFocus) expandChat = false; }); // Add a listener to the textfield that will show/hide the autocomplete bar if focused. diff --git a/lib/screens/channel/stores/chat_store.g.dart b/lib/screens/channel/stores/chat_store.g.dart index 26670a84..f363ce6d 100644 --- a/lib/screens/channel/stores/chat_store.g.dart +++ b/lib/screens/channel/stores/chat_store.g.dart @@ -81,6 +81,22 @@ mixin _$ChatStore on ChatStoreBase, Store { }); } + late final _$expandChatAtom = + Atom(name: 'ChatStoreBase.expandChat', context: context); + + @override + bool get expandChat { + _$expandChatAtom.reportRead(); + return super.expandChat; + } + + @override + set expandChat(bool value) { + _$expandChatAtom.reportWrite(value, super.expandChat, () { + super.expandChat = value; + }); + } + late final _$ChatStoreBaseActionController = ActionController(name: 'ChatStoreBase', context: context); @@ -142,7 +158,7 @@ mixin _$ChatStore on ChatStoreBase, Store { @override String toString() { return ''' - +expandChat: ${expandChat} '''; } } diff --git a/lib/screens/channel/stores/video_store.dart b/lib/screens/channel/stores/video_store.dart index f33b38da..15731507 100644 --- a/lib/screens/channel/stores/video_store.dart +++ b/lib/screens/channel/stores/video_store.dart @@ -105,6 +105,10 @@ abstract class VideoStoreBase with Store { _streamInfo = await twitchApi.getStream(userLogin: userLogin, headers: authStore.headersTwitch); } catch (e) { debugPrint(e.toString()); + + _streamInfo = null; + _overlayTimer.cancel(); + _overlayVisible = true; } } diff --git a/lib/screens/channel/video/video_overlay.dart b/lib/screens/channel/video/video_overlay.dart index 4249d176..e5562de8 100644 --- a/lib/screens/channel/video/video_overlay.dart +++ b/lib/screens/channel/video/video_overlay.dart @@ -117,6 +117,13 @@ class VideoOverlay extends StatelessWidget { ), ); + final chatOverlayButton = IconButton( + tooltip: 'Toggle Chat Overlay', + onPressed: () => videoStore.settingsStore.fullScreenChatOverlay = !videoStore.settingsStore.fullScreenChatOverlay, + icon: const Icon(Icons.chat_bubble_outline), + color: Colors.white, + ); + final refreshButton = IconButton( tooltip: 'Refresh', icon: const Icon( @@ -167,6 +174,7 @@ class VideoOverlay extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ + if (videoStore.settingsStore.fullScreen && orientation == Orientation.landscape) chatOverlayButton, refreshButton, if (orientation == Orientation.landscape) fullScreenButton, ], @@ -292,6 +300,7 @@ class VideoOverlay extends StatelessWidget { ), ), ), + if (videoStore.settingsStore.fullScreen) chatOverlayButton, refreshButton, if (Platform.isIOS && videoStore.settingsStore.pictureInPicture) IconButton( diff --git a/lib/screens/channel/video_chat.dart b/lib/screens/channel/video_chat.dart index ec7c9bd4..b0c88e8a 100644 --- a/lib/screens/channel/video_chat.dart +++ b/lib/screens/channel/video_chat.dart @@ -154,45 +154,60 @@ class _VideoChatState extends State { SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky); return Observer( - builder: (context) => ColoredBox( - color: settingsStore.showVideo ? Colors.black : Theme.of(context).scaffoldBackgroundColor, - child: SafeArea( - bottom: false, - child: settingsStore.showVideo - ? settingsStore.fullScreen - ? Stack( - children: [ - Visibility( - visible: false, - maintainState: true, - child: chat, - ), - video, - ], - ) - : Row( - children: [ - Flexible( - flex: 2, - child: video, - ), - Flexible( - flex: 1, - child: ColoredBox( - color: Theme.of(context).scaffoldBackgroundColor, - child: chat, + builder: (context) { + final landscapeChat = AnimatedContainer( + duration: const Duration(milliseconds: 200), + width: _chatStore.expandChat + ? MediaQuery.of(context).size.width / 2 + : MediaQuery.of(context).size.width * _chatStore.settings.landscapeChatWidth, + curve: Curves.ease, + color: _chatStore.settings.fullScreen ? null : Theme.of(context).scaffoldBackgroundColor, + child: chat, + ); + + return ColoredBox( + color: settingsStore.showVideo ? Colors.black : Theme.of(context).scaffoldBackgroundColor, + child: SafeArea( + bottom: false, + child: settingsStore.showVideo + ? settingsStore.fullScreen + ? Stack( + alignment: settingsStore.landscapeChatLeftSide ? Alignment.topLeft : Alignment.topRight, + children: [ + video, + IgnorePointer( + ignoring: _videoStore.overlayVisible, + child: Visibility( + visible: settingsStore.fullScreenChatOverlay, + maintainState: true, + child: DefaultTextStyle( + style: const TextStyle(color: Colors.white), + child: landscapeChat, + ), + ), ), - ), - ], - ) - : Column( - children: [ - appBar, - Expanded(child: chat), - ], - ), - ), - ), + ], + ) + : Row( + children: settingsStore.landscapeChatLeftSide + ? [ + landscapeChat, + Expanded(child: video), + ] + : [ + Expanded(child: video), + landscapeChat, + ], + ) + : Column( + children: [ + appBar, + Expanded(child: chat), + ], + ), + ), + ); + }, ); } diff --git a/lib/screens/settings/sections/chat_settings.dart b/lib/screens/settings/sections/chat_settings.dart index 4723b70f..81478c1a 100644 --- a/lib/screens/settings/sections/chat_settings.dart +++ b/lib/screens/settings/sections/chat_settings.dart @@ -90,6 +90,27 @@ class _ChatSettingsState extends State { .toList(), ), ), + SwitchListTile.adaptive( + title: const Text('Landscape Chat on Left Side'), + value: settingsStore.landscapeChatLeftSide, + onChanged: (newValue) => settingsStore.landscapeChatLeftSide = newValue, + ), + ListTile( + title: Row( + children: [ + const Text('Landscape Chat Width'), + const Spacer(), + Text('${(settingsStore.landscapeChatWidth * 100).toInt()}%'), + ], + ), + subtitle: Slider.adaptive( + value: settingsStore.landscapeChatWidth, + min: 0.2, + max: 0.8, + divisions: 12, + onChanged: (newValue) => settingsStore.landscapeChatWidth = newValue, + ), + ), const SizedBox(height: 10.0), const SectionHeader('Message Appearance'), ExpansionTile( diff --git a/lib/screens/settings/stores/settings_store.dart b/lib/screens/settings/stores/settings_store.dart index 10aa31d4..f81832e4 100644 --- a/lib/screens/settings/stores/settings_store.dart +++ b/lib/screens/settings/stores/settings_store.dart @@ -87,6 +87,10 @@ abstract class _SettingsStoreBase with Store { static const defaultEmoteAutocomplete = true; + static const defaultLandscapeChatWidth = 0.35; + + static const defaultLandscapeChatLeftSide = false; + @JsonKey(defaultValue: defaultShowBottomBar) @observable var showBottomBar = defaultShowBottomBar; @@ -135,6 +139,14 @@ abstract class _SettingsStoreBase with Store { @observable var emoteAutocomplete = defaultEmoteAutocomplete; + @JsonKey(defaultValue: defaultLandscapeChatWidth) + @observable + var landscapeChatWidth = defaultLandscapeChatWidth; + + @JsonKey(defaultValue: defaultLandscapeChatLeftSide) + @observable + var landscapeChatLeftSide = defaultLandscapeChatLeftSide; + // * Other settings static const defaultSendCrashLogs = true; @@ -147,6 +159,8 @@ abstract class _SettingsStoreBase with Store { static const defaultExpandInfo = true; + static const defaultFullScreenChatOverlay = false; + @JsonKey(defaultValue: defaultFullScreen) @observable var fullScreen = defaultFullScreen; @@ -155,6 +169,10 @@ abstract class _SettingsStoreBase with Store { @observable var expandInfo = defaultExpandInfo; + @JsonKey(defaultValue: defaultFullScreenChatOverlay) + @observable + var fullScreenChatOverlay = defaultFullScreenChatOverlay; + @action void reset() { // * General Settings @@ -182,6 +200,8 @@ abstract class _SettingsStoreBase with Store { badgeScale = defaultBadgeScale; emoteScale = defaultEmoteScale; emoteAutocomplete = defaultEmoteAutocomplete; + landscapeChatWidth = defaultLandscapeChatWidth; + landscapeChatLeftSide = false; // * Other settings sendCrashLogs = defaultSendCrashLogs; diff --git a/lib/screens/settings/stores/settings_store.g.dart b/lib/screens/settings/stores/settings_store.g.dart index 74d81b66..1e7f78ef 100644 --- a/lib/screens/settings/stores/settings_store.g.dart +++ b/lib/screens/settings/stores/settings_store.g.dart @@ -34,9 +34,13 @@ SettingsStore _$SettingsStoreFromJson(Map json) => ..badgeScale = (json['badgeScale'] as num?)?.toDouble() ?? 1.0 ..emoteScale = (json['emoteScale'] as num?)?.toDouble() ?? 1.0 ..emoteAutocomplete = json['emoteAutocomplete'] as bool? ?? true + ..landscapeChatWidth = + (json['landscapeChatWidth'] as num?)?.toDouble() ?? 0.35 + ..landscapeChatLeftSide = json['landscapeChatLeftSide'] as bool? ?? false ..sendCrashLogs = json['sendCrashLogs'] as bool? ?? true ..fullScreen = json['fullScreen'] as bool? ?? false - ..expandInfo = json['expandInfo'] as bool? ?? true; + ..expandInfo = json['expandInfo'] as bool? ?? true + ..fullScreenChatOverlay = json['fullScreenChatOverlay'] as bool? ?? false; Map _$SettingsStoreToJson(SettingsStore instance) => { @@ -60,9 +64,12 @@ Map _$SettingsStoreToJson(SettingsStore instance) => 'badgeScale': instance.badgeScale, 'emoteScale': instance.emoteScale, 'emoteAutocomplete': instance.emoteAutocomplete, + 'landscapeChatWidth': instance.landscapeChatWidth, + 'landscapeChatLeftSide': instance.landscapeChatLeftSide, 'sendCrashLogs': instance.sendCrashLogs, 'fullScreen': instance.fullScreen, 'expandInfo': instance.expandInfo, + 'fullScreenChatOverlay': instance.fullScreenChatOverlay, }; const _$ThemeTypeEnumMap = { @@ -406,6 +413,39 @@ mixin _$SettingsStore on _SettingsStoreBase, Store { }); } + late final _$landscapeChatWidthAtom = + Atom(name: '_SettingsStoreBase.landscapeChatWidth', context: context); + + @override + double get landscapeChatWidth { + _$landscapeChatWidthAtom.reportRead(); + return super.landscapeChatWidth; + } + + @override + set landscapeChatWidth(double value) { + _$landscapeChatWidthAtom.reportWrite(value, super.landscapeChatWidth, () { + super.landscapeChatWidth = value; + }); + } + + late final _$landscapeChatLeftSideAtom = + Atom(name: '_SettingsStoreBase.landscapeChatLeftSide', context: context); + + @override + bool get landscapeChatLeftSide { + _$landscapeChatLeftSideAtom.reportRead(); + return super.landscapeChatLeftSide; + } + + @override + set landscapeChatLeftSide(bool value) { + _$landscapeChatLeftSideAtom.reportWrite(value, super.landscapeChatLeftSide, + () { + super.landscapeChatLeftSide = value; + }); + } + late final _$sendCrashLogsAtom = Atom(name: '_SettingsStoreBase.sendCrashLogs', context: context); @@ -454,6 +494,23 @@ mixin _$SettingsStore on _SettingsStoreBase, Store { }); } + late final _$fullScreenChatOverlayAtom = + Atom(name: '_SettingsStoreBase.fullScreenChatOverlay', context: context); + + @override + bool get fullScreenChatOverlay { + _$fullScreenChatOverlayAtom.reportRead(); + return super.fullScreenChatOverlay; + } + + @override + set fullScreenChatOverlay(bool value) { + _$fullScreenChatOverlayAtom.reportWrite(value, super.fullScreenChatOverlay, + () { + super.fullScreenChatOverlay = value; + }); + } + late final _$_SettingsStoreBaseActionController = ActionController(name: '_SettingsStoreBase', context: context); @@ -491,9 +548,12 @@ messageScale: ${messageScale}, badgeScale: ${badgeScale}, emoteScale: ${emoteScale}, emoteAutocomplete: ${emoteAutocomplete}, +landscapeChatWidth: ${landscapeChatWidth}, +landscapeChatLeftSide: ${landscapeChatLeftSide}, sendCrashLogs: ${sendCrashLogs}, fullScreen: ${fullScreen}, -expandInfo: ${expandInfo} +expandInfo: ${expandInfo}, +fullScreenChatOverlay: ${fullScreenChatOverlay} '''; } }