Add paste button for copied messages and option to move notification to the bottom of chat (#176)

* Add paste button to "message copied" notification

* Add option to move notification to bottom of chat
This commit is contained in:
Tommy Chow 2022-08-05 22:53:24 -04:00 • committed by GitHub
parent 350e089573
commit 7be8dcd63f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 143 additions and 93 deletions

View file

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:frosty/screens/channel/chat/emote_menu/emote_menu.dart';
import 'package:frosty/screens/channel/chat/widgets/chat_bottom_bar.dart';
@ -16,99 +17,113 @@ class Chat extends StatelessWidget {
Widget build(BuildContext context) {
return Observer(
builder: (context) {
return Stack(
return Column(
children: [
Column(
children: [
Expanded(
child: GestureDetector(
onTap: () {
if (chatStore.assetsStore.showEmoteMenu) {
chatStore.assetsStore.showEmoteMenu = false;
} else if (chatStore.textFieldFocusNode.hasFocus) {
chatStore.textFieldFocusNode.unfocus();
}
},
child: Stack(
alignment: AlignmentDirectional.bottomCenter,
children: [
MediaQuery(
data: MediaQuery.of(context).copyWith(textScaleFactor: chatStore.settings.messageScale),
child: DefaultTextStyle(
style: DefaultTextStyle.of(context).style.copyWith(fontSize: chatStore.settings.fontSize),
child: Observer(
builder: (context) {
return ListView.builder(
reverse: true,
padding: EdgeInsets.zero,
addAutomaticKeepAlives: false,
controller: chatStore.scrollController,
itemCount: chatStore.renderMessages.length,
itemBuilder: (context, index) => ChatMessage(
ircMessage: chatStore.renderMessages.reversed.toList()[index],
chatStore: chatStore,
),
);
},
),
),
),
Padding(
padding: const EdgeInsets.all(5.0),
child: Observer(
builder: (_) => AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
switchInCurve: Curves.easeOutCubic,
switchOutCurve: Curves.easeInCubic,
child: chatStore.autoScroll
? null
: Button(
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0),
onPressed: chatStore.resumeScroll,
icon: const Icon(Icons.arrow_circle_down),
child: const Text('Resume Scroll'),
),
),
),
),
],
),
),
),
if (chatStore.settings.showBottomBar) ChatBottomBar(chatStore: chatStore),
AnimatedContainer(
curve: Curves.ease,
duration: const Duration(milliseconds: 200),
height: chatStore.assetsStore.showEmoteMenu ? MediaQuery.of(context).size.height / 3 : 0,
child: AnimatedOpacity(
curve: Curves.ease,
opacity: chatStore.assetsStore.showEmoteMenu ? 1 : 0,
duration: const Duration(milliseconds: 200),
child: EmoteMenu(chatStore: chatStore),
),
),
],
),
AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
child: chatStore.notification != null
? Align(
alignment: Alignment.topCenter,
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary.withOpacity(0.8),
borderRadius: BorderRadius.circular(30.0),
),
width: double.infinity,
padding: const EdgeInsets.all(10.0),
margin: const EdgeInsets.all(10.0),
child: AlertMessage(
message: chatStore.notification!,
color: Colors.white,
Expanded(
child: GestureDetector(
onTap: () {
if (chatStore.assetsStore.showEmoteMenu) {
chatStore.assetsStore.showEmoteMenu = false;
} else if (chatStore.textFieldFocusNode.hasFocus) {
chatStore.textFieldFocusNode.unfocus();
}
},
child: Stack(
alignment: AlignmentDirectional.bottomCenter,
children: [
MediaQuery(
data: MediaQuery.of(context).copyWith(textScaleFactor: chatStore.settings.messageScale),
child: DefaultTextStyle(
style: DefaultTextStyle.of(context).style.copyWith(fontSize: chatStore.settings.fontSize),
child: Observer(
builder: (context) {
return ListView.builder(
reverse: true,
padding: EdgeInsets.zero,
addAutomaticKeepAlives: false,
controller: chatStore.scrollController,
itemCount: chatStore.renderMessages.length,
itemBuilder: (context, index) => ChatMessage(
ircMessage: chatStore.renderMessages.reversed.toList()[index],
chatStore: chatStore,
),
);
},
),
),
)
: null,
),
AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
child: chatStore.notification != null
? Align(
alignment: chatStore.settings.chatNotificationsOnBottom ? Alignment.bottomCenter : Alignment.topCenter,
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary.withOpacity(0.8),
borderRadius: BorderRadius.circular(30.0),
),
margin: const EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.all(10.0),
child: AlertMessage(
message: chatStore.notification!,
color: Colors.white,
),
),
Button(
onPressed: () async {
// Paste clipboard text into the text controller.
final data = await Clipboard.getData(Clipboard.kTextPlain);
if (data != null) chatStore.textController.text = data.text!;
chatStore.notification = null;
},
fill: false,
child: const Text('Paste'),
),
],
),
),
)
: null,
),
Padding(
padding: const EdgeInsets.all(5.0),
child: Observer(
builder: (_) => AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
switchInCurve: Curves.easeOutCubic,
switchOutCurve: Curves.easeInCubic,
child: chatStore.autoScroll
? null
: Button(
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0),
onPressed: chatStore.resumeScroll,
icon: const Icon(Icons.arrow_circle_down),
child: const Text('Resume Scroll'),
),
),
),
),
],
),
),
),
if (chatStore.settings.showBottomBar) ChatBottomBar(chatStore: chatStore),
AnimatedContainer(
curve: Curves.ease,
duration: const Duration(milliseconds: 200),
height: chatStore.assetsStore.showEmoteMenu ? MediaQuery.of(context).size.height / 3 : 0,
child: AnimatedOpacity(
curve: Curves.ease,
opacity: chatStore.assetsStore.showEmoteMenu ? 1 : 0,
duration: const Duration(milliseconds: 200),
child: EmoteMenu(chatStore: chatStore),
),
),
],
);

View file

@ -41,10 +41,10 @@ class ChatMessage extends StatelessWidget {
);
}
void onLongPressMessage() {
Future<void> onLongPressMessage() async {
HapticFeedback.lightImpact();
Clipboard.setData(ClipboardData(text: ircMessage.message));
await Clipboard.setData(ClipboardData(text: ircMessage.message));
chatStore.notification = 'Message copied';
}

View file

@ -94,6 +94,13 @@ class _ChatSettingsState extends State<ChatSettings> {
value: settingsStore.landscapeChatLeftSide,
onChanged: (newValue) => settingsStore.landscapeChatLeftSide = newValue,
),
SwitchListTile.adaptive(
isThreeLine: true,
title: const Text('Notifications on bottom'),
subtitle: const Text('Shows notifications (e.g., "Message copied") on the bottom of the chat.'),
value: settingsStore.chatNotificationsOnBottom,
onChanged: (newValue) => settingsStore.chatNotificationsOnBottom = newValue,
),
ListTile(
isThreeLine: true,
title: const Text('Landscape fill cutout side'),

View file

@ -34,7 +34,7 @@ class VideoSettings extends StatelessWidget {
),
SwitchListTile.adaptive(
isThreeLine: true,
title: const Text('Picture-in-picture Button (experimental)'),
title: const Text('Picture-in-picture button (experimental)'),
subtitle: const Text('Adds a button to enter PiP mode on the bottom right of the overlay (may cause freezes/crashes).'),
value: settingsStore.pictureInPicture,
onChanged: settingsStore.showVideo && settingsStore.showOverlay ? (newValue) => settingsStore.pictureInPicture = newValue : null,

View file

@ -92,6 +92,7 @@ abstract class _SettingsStoreBase with Store {
static const defaultShowBottomBar = true;
static const defaultLandscapeChatLeftSide = false;
static const defaultChatNotificationsOnBottom = false;
static const defaultChatWidth = 0.3;
static const defaultFullScreenChatOverlayOpacity = 0.5;
@ -128,6 +129,10 @@ abstract class _SettingsStoreBase with Store {
@observable
var landscapeChatLeftSide = defaultLandscapeChatLeftSide;
@JsonKey(defaultValue: defaultChatNotificationsOnBottom)
@observable
var chatNotificationsOnBottom = defaultChatNotificationsOnBottom;
@JsonKey(defaultValue: defaultLandscapeCutout)
@observable
var landscapeCutout = defaultLandscapeCutout;
@ -187,6 +192,7 @@ abstract class _SettingsStoreBase with Store {
autocomplete = defaultAutocomplete;
showBottomBar = defaultShowBottomBar;
landscapeChatLeftSide = defaultLandscapeChatLeftSide;
chatNotificationsOnBottom = defaultChatNotificationsOnBottom;
landscapeCutout = defaultLandscapeCutout;
chatWidth = defaultChatWidth;
fullScreenChatOverlayOpacity = defaultFullScreenChatOverlayOpacity;

View file

@ -25,6 +25,8 @@ SettingsStore _$SettingsStoreFromJson(Map<String, dynamic> json) =>
..autocomplete = json['autocomplete'] as bool? ?? true
..showBottomBar = json['showBottomBar'] as bool? ?? true
..landscapeChatLeftSide = json['landscapeChatLeftSide'] as bool? ?? false
..chatNotificationsOnBottom =
json['chatNotificationsOnBottom'] as bool? ?? false
..landscapeCutout = $enumDecodeNullable(
_$LandscapeCutoutTypeEnumMap, json['landscapeCutout']) ??
LandscapeCutoutType.none
@ -67,6 +69,7 @@ Map<String, dynamic> _$SettingsStoreToJson(SettingsStore instance) =>
'autocomplete': instance.autocomplete,
'showBottomBar': instance.showBottomBar,
'landscapeChatLeftSide': instance.landscapeChatLeftSide,
'chatNotificationsOnBottom': instance.chatNotificationsOnBottom,
'landscapeCutout':
_$LandscapeCutoutTypeEnumMap[instance.landscapeCutout]!,
'chatWidth': instance.chatWidth,
@ -356,6 +359,23 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
});
}
late final _$chatNotificationsOnBottomAtom = Atom(
name: '_SettingsStoreBase.chatNotificationsOnBottom', context: context);
@override
bool get chatNotificationsOnBottom {
_$chatNotificationsOnBottomAtom.reportRead();
return super.chatNotificationsOnBottom;
}
@override
set chatNotificationsOnBottom(bool value) {
_$chatNotificationsOnBottomAtom
.reportWrite(value, super.chatNotificationsOnBottom, () {
super.chatNotificationsOnBottom = value;
});
}
late final _$landscapeCutoutAtom =
Atom(name: '_SettingsStoreBase.landscapeCutout', context: context);
@ -719,6 +739,7 @@ chatOnlyPreventSleep: ${chatOnlyPreventSleep},
autocomplete: ${autocomplete},
showBottomBar: ${showBottomBar},
landscapeChatLeftSide: ${landscapeChatLeftSide},
chatNotificationsOnBottom: ${chatNotificationsOnBottom},
landscapeCutout: ${landscapeCutout},
chatWidth: ${chatWidth},
fullScreenChatOverlayOpacity: ${fullScreenChatOverlayOpacity},

View file

@ -16,6 +16,7 @@ class AlertMessage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(