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:
parent
350e089573
commit
7be8dcd63f
7 changed files with 143 additions and 93 deletions
|
|
@ -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),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ class AlertMessage extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue