fluffychat merge
This commit is contained in:
commit
46a8477172
13 changed files with 989 additions and 1057 deletions
|
|
@ -89,6 +89,8 @@ abstract class AppConfig {
|
|||
static String _privacyUrl = "https://www.pangeachat.com/privacy";
|
||||
//Pangea#
|
||||
|
||||
static const Set<String> defaultReactions = {'👍', '❤️', '😊'};
|
||||
|
||||
static String get privacyUrl => _privacyUrl;
|
||||
// #Pangea
|
||||
// static const String website = 'https://fluffychat.im';
|
||||
|
|
|
|||
|
|
@ -3240,6 +3240,7 @@
|
|||
"commandHint_logout": "Logout your current device",
|
||||
"commandHint_logoutall": "Logout all active devices",
|
||||
"displayNavigationRail": "Show navigation rail on mobile",
|
||||
"customReaction": "Custom reaction",
|
||||
"accountInformation": "Account information",
|
||||
"addGroupDescription": "Add a chat description",
|
||||
"addNewFriend": "Add new friend",
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
// ignore_for_file: depend_on_referenced_packages, implementation_imports
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
|
@ -251,8 +248,6 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
context.go('/rooms');
|
||||
}
|
||||
|
||||
EmojiPickerType emojiPickerType = EmojiPickerType.keyboard;
|
||||
|
||||
// #Pangea
|
||||
// void requestHistory([_]) async {
|
||||
Future<void> requestHistory() async {
|
||||
|
|
@ -924,7 +919,6 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
// editEventId: editEvent?.eventId,
|
||||
// parseCommands: parseCommands,
|
||||
// );
|
||||
|
||||
// If the message and the sendController text don't match, it's possible
|
||||
// that there was a delay in tokenization before send, and the user started
|
||||
// typing a new message. We don't want to erase that, so only reset the input
|
||||
|
|
@ -1031,7 +1025,6 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
// text: pendingText,
|
||||
// selection: const TextSelection.collapsed(offset: 0),
|
||||
// );
|
||||
// Pangea#
|
||||
|
||||
setState(() {
|
||||
// #Pangea
|
||||
|
|
@ -1226,13 +1219,11 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
} else {
|
||||
inputFocus.unfocus();
|
||||
}
|
||||
emojiPickerType = EmojiPickerType.keyboard;
|
||||
setState(() => showEmojiPicker = !showEmojiPicker);
|
||||
}
|
||||
|
||||
void _inputFocusListener() {
|
||||
if (showEmojiPicker && inputFocus.hasFocus) {
|
||||
emojiPickerType = EmojiPickerType.keyboard;
|
||||
setState(() => showEmojiPicker = false);
|
||||
}
|
||||
}
|
||||
|
|
@ -1431,19 +1422,6 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
return true;
|
||||
}
|
||||
|
||||
bool get canEditSelectedEvents {
|
||||
if (isArchived ||
|
||||
selectedEvents.length != 1 ||
|
||||
// #Pangea
|
||||
selectedEvents.single.messageType != MessageTypes.Text ||
|
||||
// Pangea#
|
||||
!selectedEvents.first.status.isSent) {
|
||||
return false;
|
||||
}
|
||||
return currentRoomBundle
|
||||
.any((cl) => selectedEvents.first.senderId == cl!.userID);
|
||||
}
|
||||
|
||||
void forwardEventsAction() async {
|
||||
if (selectedEvents.isEmpty) return;
|
||||
await showScaffoldDialog(
|
||||
|
|
@ -1567,34 +1545,8 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
}
|
||||
|
||||
void onEmojiSelected(_, Emoji? emoji) {
|
||||
switch (emojiPickerType) {
|
||||
case EmojiPickerType.reaction:
|
||||
senEmojiReaction(emoji);
|
||||
break;
|
||||
case EmojiPickerType.keyboard:
|
||||
typeEmoji(emoji);
|
||||
onInputBarChanged(sendController.text);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void senEmojiReaction(Emoji? emoji) {
|
||||
setState(() => showEmojiPicker = false);
|
||||
if (emoji == null) return;
|
||||
// make sure we don't send the same emoji twice
|
||||
if (_allReactionEvents.any(
|
||||
(e) => e.content.tryGetMap('m.relates_to')?['key'] == emoji.emoji,
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
// #Pangea
|
||||
// return sendEmojiAction(emoji.emoji);
|
||||
sendEmojiAction(emoji.emoji);
|
||||
|
||||
// don't need to clear these when sending while in select mode,
|
||||
// but do need to clear these when reacting from the large emoji picker
|
||||
setState(() => selectedEvents.clear());
|
||||
// Pangea#
|
||||
typeEmoji(emoji);
|
||||
onInputBarChanged(sendController.text);
|
||||
}
|
||||
|
||||
void typeEmoji(Emoji? emoji) {
|
||||
|
|
@ -1609,7 +1561,6 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
);
|
||||
}
|
||||
// Pangea#
|
||||
|
||||
final selection = sendController.selection;
|
||||
final newText = sendController.text.isEmpty
|
||||
? emoji.emoji
|
||||
|
|
@ -1623,54 +1574,12 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
);
|
||||
}
|
||||
|
||||
late Iterable<Event> _allReactionEvents;
|
||||
|
||||
void emojiPickerBackspace() {
|
||||
switch (emojiPickerType) {
|
||||
case EmojiPickerType.reaction:
|
||||
setState(() => showEmojiPicker = false);
|
||||
break;
|
||||
case EmojiPickerType.keyboard:
|
||||
sendController
|
||||
..text = sendController.text.characters.skipLast(1).toString()
|
||||
..selection = TextSelection.fromPosition(
|
||||
TextPosition(offset: sendController.text.length),
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void pickEmojiReactionAction(Iterable<Event> allReactionEvents) async {
|
||||
// #Pangea
|
||||
closeSelectionOverlay();
|
||||
// Pangea#
|
||||
_allReactionEvents = allReactionEvents;
|
||||
emojiPickerType = EmojiPickerType.reaction;
|
||||
setState(() => showEmojiPicker = true);
|
||||
}
|
||||
|
||||
void sendEmojiAction(String? emoji) async {
|
||||
final events = List<Event>.from(selectedEvents);
|
||||
// #Pangea
|
||||
// keep this event selected in case the user wants to send another emoji
|
||||
// setState(() => selectedEvents.clear());
|
||||
// Pangea#
|
||||
// if reaction already exists, don't send it again
|
||||
if (timeline == null ||
|
||||
events.any(
|
||||
(e) => e.aggregatedEvents(timeline!, RelationshipTypes.reaction).any(
|
||||
(re) => re.content.tryGetMap('m.relates_to')?['key'] == emoji,
|
||||
),
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (final event in events) {
|
||||
await room.sendReaction(
|
||||
event.eventId,
|
||||
emoji!,
|
||||
sendController
|
||||
..text = sendController.text.characters.skipLast(1).toString()
|
||||
..selection = TextSelection.fromPosition(
|
||||
TextPosition(offset: sendController.text.length),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// #Pangea
|
||||
|
|
@ -1702,7 +1611,6 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
selectedEvents.add(event);
|
||||
});
|
||||
}
|
||||
// Pangea#
|
||||
|
||||
void clearSingleSelectedEvent() {
|
||||
if (selectedEvents.length <= 1) {
|
||||
|
|
@ -1776,9 +1684,8 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
final matches = selectedEvents.where((e) => e.eventId == event.eventId);
|
||||
if (matches.isNotEmpty) {
|
||||
setState(() => selectedEvents.remove(matches.first));
|
||||
}
|
||||
// Pangea#
|
||||
else {
|
||||
// Pangea#
|
||||
} else {
|
||||
setState(
|
||||
() => selectedEvents.add(event),
|
||||
);
|
||||
|
|
@ -1883,7 +1790,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
}
|
||||
|
||||
Timer? _storeInputTimeoutTimer;
|
||||
Duration storeInputTimeout = const Duration(milliseconds: 500);
|
||||
static const Duration _storeInputTimeout = Duration(milliseconds: 500);
|
||||
|
||||
void onInputBarChanged(String text) {
|
||||
if (_inputTextIsEmpty != text.isEmpty) {
|
||||
|
|
@ -1893,7 +1800,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
}
|
||||
|
||||
_storeInputTimeoutTimer?.cancel();
|
||||
_storeInputTimeoutTimer = Timer(storeInputTimeout, () async {
|
||||
_storeInputTimeoutTimer = Timer(_storeInputTimeout, () async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString('draft_$roomId', text);
|
||||
});
|
||||
|
|
@ -1937,12 +1844,14 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
bool get isArchived =>
|
||||
{Membership.leave, Membership.ban}.contains(room.membership);
|
||||
|
||||
// #Pangea
|
||||
// void showEventInfo([Event? event]) =>
|
||||
// (event ?? selectedEvents.single).showInfoDialog(context);
|
||||
void showEventInfo([Event? event]) {
|
||||
(event ?? selectedEvents.single).showInfoDialog(context);
|
||||
// #Pangea
|
||||
clearSelectedEvents();
|
||||
// Pangea#
|
||||
}
|
||||
// Pangea#
|
||||
|
||||
void onPhoneButtonTap() async {
|
||||
// VoIP required Android SDK 21
|
||||
|
|
@ -2000,7 +1909,6 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
replyEvent = null;
|
||||
editEvent = null;
|
||||
});
|
||||
|
||||
// #Pangea
|
||||
String? get buttonEventID => timeline!.events
|
||||
.firstWhereOrNull(
|
||||
|
|
@ -2261,5 +2169,3 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
enum EmojiPickerType { reaction, keyboard }
|
||||
|
|
|
|||
|
|
@ -201,6 +201,10 @@ class ChatEventList extends StatelessWidget {
|
|||
// Pangea#
|
||||
selected: controller.selectedEvents
|
||||
.any((e) => e.eventId == event.eventId),
|
||||
singleSelected:
|
||||
controller.selectedEvents.singleOrNull?.eventId ==
|
||||
event.eventId,
|
||||
onEdit: () => controller.editSelectedEventAction(),
|
||||
timeline: timeline,
|
||||
displayReadMarker: i > 0 &&
|
||||
controller.readMarkerEventId == event.eventId,
|
||||
|
|
|
|||
|
|
@ -21,10 +21,6 @@ class ChatInputRow extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
if (controller.showEmojiPicker &&
|
||||
controller.emojiPickerType == EmojiPickerType.reaction) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
const height = 48.0;
|
||||
|
||||
if (!controller.room.otherPartyCanReceiveMessages) {
|
||||
|
|
@ -43,302 +39,238 @@ class ChatInputRow extends StatelessWidget {
|
|||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: controller.selectMode
|
||||
? <Widget>[
|
||||
if (controller.selectedEvents
|
||||
.every((event) => event.status == EventStatus.error))
|
||||
SizedBox(
|
||||
height: height,
|
||||
child: TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: theme.colorScheme.error,
|
||||
),
|
||||
onPressed: controller.deleteErrorEventsAction,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
const Icon(Icons.delete),
|
||||
Text(L10n.of(context).delete),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
SizedBox(
|
||||
height: height,
|
||||
child: TextButton(
|
||||
onPressed: controller.forwardEventsAction,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
const Icon(Icons.keyboard_arrow_left_outlined),
|
||||
Text(L10n.of(context).forward),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
controller.selectedEvents.length == 1
|
||||
? controller.selectedEvents.first
|
||||
.getDisplayEvent(controller.timeline!)
|
||||
.status
|
||||
.isSent
|
||||
? SizedBox(
|
||||
height: height,
|
||||
child: TextButton(
|
||||
onPressed: controller.replyAction,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Text(L10n.of(context).reply),
|
||||
const Icon(Icons.keyboard_arrow_right),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: SizedBox(
|
||||
height: height,
|
||||
child: TextButton(
|
||||
onPressed: controller.sendAgainAction,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Text(L10n.of(context).tryToSendAgain),
|
||||
const SizedBox(width: 4),
|
||||
const Icon(Icons.send_outlined, size: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
]
|
||||
: <Widget>[
|
||||
const SizedBox(width: 4),
|
||||
AnimatedContainer(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
width: controller.sendController.text.isNotEmpty ? 0 : height,
|
||||
height: height,
|
||||
alignment: Alignment.center,
|
||||
decoration: const BoxDecoration(),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: PopupMenuButton<String>(
|
||||
useRootNavigator: true,
|
||||
icon: const Icon(Icons.add_circle_outline),
|
||||
iconColor: theme.colorScheme.onPrimaryContainer,
|
||||
onSelected: controller.onAddPopupMenuButtonSelected,
|
||||
itemBuilder: (BuildContext context) =>
|
||||
<PopupMenuEntry<String>>[
|
||||
if (PlatformInfos.isMobile)
|
||||
PopupMenuItem<String>(
|
||||
value: 'location',
|
||||
child: ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundColor:
|
||||
theme.colorScheme.onPrimaryContainer,
|
||||
foregroundColor: theme.colorScheme.primaryContainer,
|
||||
child: const Icon(Icons.gps_fixed_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).shareLocation),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
PopupMenuItem<String>(
|
||||
value: 'image',
|
||||
child: ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: theme.colorScheme.onPrimaryContainer,
|
||||
foregroundColor: theme.colorScheme.primaryContainer,
|
||||
child: const Icon(Icons.photo_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).sendImage),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
PopupMenuItem<String>(
|
||||
value: 'video',
|
||||
child: ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: theme.colorScheme.onPrimaryContainer,
|
||||
foregroundColor: theme.colorScheme.primaryContainer,
|
||||
child: const Icon(Icons.video_camera_back_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).sendVideo),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
PopupMenuItem<String>(
|
||||
value: 'file',
|
||||
child: ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: theme.colorScheme.onPrimaryContainer,
|
||||
foregroundColor: theme.colorScheme.primaryContainer,
|
||||
child: const Icon(Icons.attachment_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).sendFile),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
children: <Widget>[
|
||||
const SizedBox(width: 4),
|
||||
AnimatedContainer(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
width: controller.sendController.text.isNotEmpty ? 0 : height,
|
||||
height: height,
|
||||
alignment: Alignment.center,
|
||||
decoration: const BoxDecoration(),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: PopupMenuButton<String>(
|
||||
useRootNavigator: true,
|
||||
enabled: !controller.selectMode,
|
||||
icon: const Icon(Icons.add_circle_outline),
|
||||
iconColor: theme.colorScheme.onPrimaryContainer,
|
||||
onSelected: controller.onAddPopupMenuButtonSelected,
|
||||
itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
|
||||
if (PlatformInfos.isMobile)
|
||||
AnimatedContainer(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
width: controller.sendController.text.isNotEmpty ? 0 : height,
|
||||
height: height,
|
||||
alignment: Alignment.center,
|
||||
decoration: const BoxDecoration(),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: PopupMenuButton(
|
||||
useRootNavigator: true,
|
||||
icon: const Icon(Icons.camera_alt_outlined),
|
||||
onSelected: controller.onAddPopupMenuButtonSelected,
|
||||
iconColor: theme.colorScheme.onPrimaryContainer,
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem<String>(
|
||||
value: 'camera-video',
|
||||
child: ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundColor:
|
||||
theme.colorScheme.onPrimaryContainer,
|
||||
foregroundColor: theme.colorScheme.primaryContainer,
|
||||
child: const Icon(Icons.videocam_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).recordAVideo),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
PopupMenuItem<String>(
|
||||
value: 'camera',
|
||||
child: ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundColor:
|
||||
theme.colorScheme.onPrimaryContainer,
|
||||
foregroundColor: theme.colorScheme.primaryContainer,
|
||||
child: const Icon(Icons.camera_alt_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).takeAPhoto),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
],
|
||||
PopupMenuItem<String>(
|
||||
value: 'location',
|
||||
child: ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: theme.colorScheme.onPrimaryContainer,
|
||||
foregroundColor: theme.colorScheme.primaryContainer,
|
||||
child: const Icon(Icons.gps_fixed_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).shareLocation),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: height,
|
||||
width: height,
|
||||
alignment: Alignment.center,
|
||||
child: IconButton(
|
||||
tooltip: L10n.of(context).emojis,
|
||||
color: theme.colorScheme.onPrimaryContainer,
|
||||
icon: PageTransitionSwitcher(
|
||||
transitionBuilder: (
|
||||
Widget child,
|
||||
Animation<double> primaryAnimation,
|
||||
Animation<double> secondaryAnimation,
|
||||
) {
|
||||
return SharedAxisTransition(
|
||||
animation: primaryAnimation,
|
||||
secondaryAnimation: secondaryAnimation,
|
||||
transitionType: SharedAxisTransitionType.scaled,
|
||||
fillColor: Colors.transparent,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: Icon(
|
||||
controller.showEmojiPicker
|
||||
? Icons.keyboard
|
||||
: Icons.add_reaction_outlined,
|
||||
key: ValueKey(controller.showEmojiPicker),
|
||||
),
|
||||
PopupMenuItem<String>(
|
||||
value: 'image',
|
||||
child: ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: theme.colorScheme.onPrimaryContainer,
|
||||
foregroundColor: theme.colorScheme.primaryContainer,
|
||||
child: const Icon(Icons.photo_outlined),
|
||||
),
|
||||
onPressed: controller.emojiPickerAction,
|
||||
title: Text(L10n.of(context).sendImage),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
if (Matrix.of(context).isMultiAccount &&
|
||||
Matrix.of(context).hasComplexBundles &&
|
||||
Matrix.of(context).currentBundle!.length > 1)
|
||||
Container(
|
||||
width: height,
|
||||
height: height,
|
||||
alignment: Alignment.center,
|
||||
child: _ChatAccountPicker(controller),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 0.0),
|
||||
child: InputBar(
|
||||
room: controller.room,
|
||||
minLines: 1,
|
||||
maxLines: 8,
|
||||
autofocus: !PlatformInfos.isMobile,
|
||||
keyboardType: TextInputType.multiline,
|
||||
textInputAction:
|
||||
AppConfig.sendOnEnter == true && PlatformInfos.isMobile
|
||||
? TextInputAction.send
|
||||
: null,
|
||||
// #Pangea
|
||||
// onSubmitted: controller.onInputBarSubmitted,
|
||||
onSubmitted: (value) =>
|
||||
controller.onInputBarSubmitted(value, context),
|
||||
// Pangea#
|
||||
onSubmitImage: controller.sendImageFromClipBoard,
|
||||
focusNode: controller.inputFocus,
|
||||
controller: controller.sendController,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.only(
|
||||
left: 6.0,
|
||||
right: 6.0,
|
||||
bottom: 6.0,
|
||||
top: 3.0,
|
||||
),
|
||||
hintText: L10n.of(context).writeAMessage,
|
||||
hintMaxLines: 1,
|
||||
border: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
filled: false,
|
||||
),
|
||||
onChanged: controller.onInputBarChanged,
|
||||
// #Pangea
|
||||
hintText: "",
|
||||
// Pangea#
|
||||
PopupMenuItem<String>(
|
||||
value: 'video',
|
||||
child: ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: theme.colorScheme.onPrimaryContainer,
|
||||
foregroundColor: theme.colorScheme.primaryContainer,
|
||||
child: const Icon(Icons.video_camera_back_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).sendVideo),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: height,
|
||||
width: height,
|
||||
alignment: Alignment.center,
|
||||
child: PlatformInfos.platformCanRecord &&
|
||||
controller.sendController.text.isEmpty
|
||||
? FloatingActionButton.small(
|
||||
tooltip: L10n.of(context).voiceMessage,
|
||||
onPressed: controller.voiceMessageAction,
|
||||
elevation: 0,
|
||||
heroTag: null,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(height),
|
||||
),
|
||||
backgroundColor: theme.bubbleColor,
|
||||
foregroundColor: theme.onBubbleColor,
|
||||
child: const Icon(Icons.mic_none_outlined),
|
||||
)
|
||||
: FloatingActionButton.small(
|
||||
tooltip: L10n.of(context).send,
|
||||
// #Pangea
|
||||
// onPressed: controller.send,
|
||||
onPressed: () => controller.send(
|
||||
message: controller.sendController.text,
|
||||
),
|
||||
// Pangea#
|
||||
elevation: 0,
|
||||
heroTag: null,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(height),
|
||||
),
|
||||
backgroundColor: theme.bubbleColor,
|
||||
foregroundColor: theme.onBubbleColor,
|
||||
child: const Icon(Icons.send_outlined),
|
||||
),
|
||||
PopupMenuItem<String>(
|
||||
value: 'file',
|
||||
child: ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: theme.colorScheme.onPrimaryContainer,
|
||||
foregroundColor: theme.colorScheme.primaryContainer,
|
||||
child: const Icon(Icons.attachment_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).sendFile),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (PlatformInfos.isMobile)
|
||||
AnimatedContainer(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
width: controller.sendController.text.isNotEmpty ? 0 : height,
|
||||
height: height,
|
||||
alignment: Alignment.center,
|
||||
decoration: const BoxDecoration(),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: PopupMenuButton(
|
||||
enabled: !controller.selectMode,
|
||||
useRootNavigator: true,
|
||||
icon: const Icon(Icons.camera_alt_outlined),
|
||||
onSelected: controller.onAddPopupMenuButtonSelected,
|
||||
iconColor: theme.colorScheme.onPrimaryContainer,
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem<String>(
|
||||
value: 'camera-video',
|
||||
child: ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: theme.colorScheme.onPrimaryContainer,
|
||||
foregroundColor: theme.colorScheme.primaryContainer,
|
||||
child: const Icon(Icons.videocam_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).recordAVideo),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
PopupMenuItem<String>(
|
||||
value: 'camera',
|
||||
child: ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: theme.colorScheme.onPrimaryContainer,
|
||||
foregroundColor: theme.colorScheme.primaryContainer,
|
||||
child: const Icon(Icons.camera_alt_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).takeAPhoto),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: height,
|
||||
width: height,
|
||||
alignment: Alignment.center,
|
||||
child: IconButton(
|
||||
tooltip: L10n.of(context).emojis,
|
||||
color: theme.colorScheme.onPrimaryContainer,
|
||||
icon: PageTransitionSwitcher(
|
||||
transitionBuilder: (
|
||||
Widget child,
|
||||
Animation<double> primaryAnimation,
|
||||
Animation<double> secondaryAnimation,
|
||||
) {
|
||||
return SharedAxisTransition(
|
||||
animation: primaryAnimation,
|
||||
secondaryAnimation: secondaryAnimation,
|
||||
transitionType: SharedAxisTransitionType.scaled,
|
||||
fillColor: Colors.transparent,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: Icon(
|
||||
controller.showEmojiPicker
|
||||
? Icons.keyboard
|
||||
: Icons.add_reaction_outlined,
|
||||
key: ValueKey(controller.showEmojiPicker),
|
||||
),
|
||||
),
|
||||
onPressed:
|
||||
controller.selectMode ? null : controller.emojiPickerAction,
|
||||
),
|
||||
),
|
||||
if (Matrix.of(context).isMultiAccount &&
|
||||
Matrix.of(context).hasComplexBundles &&
|
||||
Matrix.of(context).currentBundle!.length > 1)
|
||||
Container(
|
||||
width: height,
|
||||
height: height,
|
||||
alignment: Alignment.center,
|
||||
child: _ChatAccountPicker(controller),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 0.0),
|
||||
child: InputBar(
|
||||
room: controller.room,
|
||||
minLines: 1,
|
||||
readOnly: controller.selectMode,
|
||||
maxLines: 8,
|
||||
autofocus: !PlatformInfos.isMobile,
|
||||
keyboardType: TextInputType.multiline,
|
||||
textInputAction:
|
||||
AppConfig.sendOnEnter == true && PlatformInfos.isMobile
|
||||
? TextInputAction.send
|
||||
: null,
|
||||
// #Pangea
|
||||
// onSubmitted: controller.onInputBarSubmitted,
|
||||
onSubmitted: (c) => controller.onInputBarSubmitted(c, context),
|
||||
hintText: "",
|
||||
// Pangea#
|
||||
onSubmitImage: controller.sendImageFromClipBoard,
|
||||
focusNode: controller.inputFocus,
|
||||
controller: controller.sendController,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.only(
|
||||
left: 6.0,
|
||||
right: 6.0,
|
||||
bottom: 6.0,
|
||||
top: 3.0,
|
||||
),
|
||||
hintText: L10n.of(context).writeAMessage,
|
||||
hintMaxLines: 1,
|
||||
border: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
filled: false,
|
||||
),
|
||||
onChanged: controller.onInputBarChanged,
|
||||
),
|
||||
),
|
||||
),
|
||||
Opacity(
|
||||
opacity: controller.selectMode ? 0.66 : 1,
|
||||
child: Container(
|
||||
height: height,
|
||||
width: height,
|
||||
alignment: Alignment.center,
|
||||
child: PlatformInfos.platformCanRecord &&
|
||||
controller.sendController.text.isEmpty
|
||||
? FloatingActionButton.small(
|
||||
tooltip: L10n.of(context).voiceMessage,
|
||||
onPressed: controller.selectMode
|
||||
? null
|
||||
: controller.voiceMessageAction,
|
||||
elevation: 0,
|
||||
heroTag: null,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(height),
|
||||
),
|
||||
backgroundColor: theme.bubbleColor,
|
||||
foregroundColor: theme.onBubbleColor,
|
||||
child: const Icon(Icons.mic_none_outlined),
|
||||
)
|
||||
: FloatingActionButton.small(
|
||||
tooltip: L10n.of(context).send,
|
||||
// #Pangea
|
||||
// onPressed: controller.selectMode ? null : controller.send,
|
||||
onPressed: () {},
|
||||
// Pangea#
|
||||
elevation: 0,
|
||||
heroTag: null,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(height),
|
||||
),
|
||||
backgroundColor: theme.bubbleColor,
|
||||
foregroundColor: theme.onBubbleColor,
|
||||
child: const Icon(Icons.send_outlined),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,12 +34,6 @@ class ChatView extends StatelessWidget {
|
|||
List<Widget> _appBarActions(BuildContext context) {
|
||||
if (controller.selectMode) {
|
||||
return [
|
||||
if (controller.canEditSelectedEvents)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.edit_outlined),
|
||||
tooltip: L10n.of(context).edit,
|
||||
onPressed: controller.editSelectedEventAction,
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.copy_outlined),
|
||||
tooltip: L10n.of(context).copy,
|
||||
|
|
@ -310,13 +304,14 @@ class ChatView extends StatelessWidget {
|
|||
child: ChatEventList(controller: controller),
|
||||
),
|
||||
),
|
||||
if (controller.showScrollDownButton)
|
||||
Divider(
|
||||
height: 1,
|
||||
color: theme.dividerColor,
|
||||
),
|
||||
if (controller.room.isExtinct)
|
||||
Container(
|
||||
margin: EdgeInsets.only(
|
||||
bottom: bottomSheetPadding,
|
||||
left: bottomSheetPadding,
|
||||
right: bottomSheetPadding,
|
||||
),
|
||||
margin: EdgeInsets.all(bottomSheetPadding),
|
||||
width: double.infinity,
|
||||
child: ElevatedButton.icon(
|
||||
icon: const Icon(Icons.chevron_right),
|
||||
|
|
@ -327,11 +322,7 @@ class ChatView extends StatelessWidget {
|
|||
else if (controller.room.canSendDefaultMessages &&
|
||||
controller.room.membership == Membership.join)
|
||||
Container(
|
||||
margin: EdgeInsets.only(
|
||||
bottom: bottomSheetPadding,
|
||||
left: bottomSheetPadding,
|
||||
right: bottomSheetPadding,
|
||||
),
|
||||
margin: EdgeInsets.all(bottomSheetPadding),
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: FluffyThemes.columnWidth * 2.5,
|
||||
),
|
||||
|
|
@ -386,7 +377,6 @@ class ChatView extends StatelessWidget {
|
|||
// : Column(
|
||||
// mainAxisSize: MainAxisSize.min,
|
||||
// children: [
|
||||
// ReactionsPicker(controller),
|
||||
// ReplyDisplay(controller),
|
||||
// ChatInputRow(controller),
|
||||
// ChatEmojiPicker(controller),
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -519,6 +519,7 @@ class InputBar extends StatelessWidget {
|
|||
// builder: (context, controller, focusNode) => TextField(
|
||||
// controller: controller,
|
||||
// focusNode: focusNode,
|
||||
// readOnly: readOnly,
|
||||
// contextMenuBuilder: (c, e) => markdownContextBuilder(c, e, controller),
|
||||
// contentInsertionConfiguration: ContentInsertionConfiguration(
|
||||
// onContentInserted: (KeyboardInsertedContent content) {
|
||||
|
|
|
|||
|
|
@ -1,119 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/app_emojis.dart';
|
||||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import '../../config/themes.dart';
|
||||
|
||||
class ReactionsPicker extends StatelessWidget {
|
||||
final ChatController controller;
|
||||
|
||||
const ReactionsPicker(this.controller, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
if (controller.showEmojiPicker) return const SizedBox.shrink();
|
||||
final display = controller.editEvent == null &&
|
||||
controller.replyEvent == null &&
|
||||
controller.room.canSendDefaultMessages &&
|
||||
controller.selectedEvents.isNotEmpty;
|
||||
return AnimatedContainer(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
// #Pangea
|
||||
// height: (display) ? 56 : 0,
|
||||
height: (display) ? AppConfig.reactionsPickerHeight : 0,
|
||||
// Pangea#
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
if (!display) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
final emojis = List<String>.from(AppEmojis.emojis);
|
||||
final allReactionEvents = controller.selectedEvents.first
|
||||
.aggregatedEvents(
|
||||
controller.timeline!,
|
||||
RelationshipTypes.reaction,
|
||||
)
|
||||
.where(
|
||||
(event) =>
|
||||
event.senderId == event.room.client.userID &&
|
||||
event.type == 'm.reaction',
|
||||
);
|
||||
|
||||
for (final event in allReactionEvents) {
|
||||
try {
|
||||
emojis.remove(event.content.tryGetMap('m.relates_to')!['key']);
|
||||
} catch (_) {}
|
||||
}
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
// #Pangea
|
||||
// color: theme.colorScheme.onInverseSurface,
|
||||
// Pangea#
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomRight: Radius.circular(AppConfig.borderRadius),
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.only(right: 1),
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: emojis.length,
|
||||
itemBuilder: (c, i) => InkWell(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
onTap: () => controller.sendEmojiAction(emojis[i]),
|
||||
child: Container(
|
||||
// #Pangea
|
||||
// width: 56,
|
||||
// height: 56,
|
||||
width: AppConfig.reactionsPickerHeight,
|
||||
height: AppConfig.reactionsPickerHeight,
|
||||
// Pangea#
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
emojis[i],
|
||||
// #Pangea
|
||||
// style: const TextStyle(fontSize: 30),
|
||||
style: const TextStyle(fontSize: 20),
|
||||
// Pangea#
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
InkWell(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 8),
|
||||
width: 36,
|
||||
// #Pangea
|
||||
// height: 56,
|
||||
height: AppConfig.reactionsPickerHeight,
|
||||
// Pangea#
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.onInverseSurface,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(Icons.add_outlined),
|
||||
),
|
||||
onTap: () =>
|
||||
controller.pickEmojiReactionAction(allReactionEvents),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -124,8 +124,7 @@ class PangeaChatInputRowState extends State<PangeaChatInputRow> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
if (_controller.showEmojiPicker &&
|
||||
_controller.emojiPickerType == EmojiPickerType.reaction) {
|
||||
if (_controller.showEmojiPicker) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
const height = 48.0;
|
||||
|
|
|
|||
|
|
@ -39,7 +39,8 @@ class LemmaReactionPickerState extends State<LemmaReactionPicker> {
|
|||
}
|
||||
}
|
||||
|
||||
void setEmoji(String emoji) => widget.controller.sendEmojiAction(emoji);
|
||||
void setEmoji(String emoji) {}
|
||||
// widget.controller.sendEmojiAction(emoji);
|
||||
|
||||
Future<void> _refresh() async {
|
||||
setState(() {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import 'package:matrix/matrix.dart';
|
|||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pangea/events/extensions/pangea_event_extension.dart';
|
||||
import 'package:fluffychat/pangea/events/utils/report_message.dart';
|
||||
|
||||
class OverlayHeader extends StatefulWidget {
|
||||
|
|
@ -118,14 +117,14 @@ class OverlayHeaderState extends State<OverlayHeader> {
|
|||
color: theme.colorScheme.primary,
|
||||
),
|
||||
|
||||
if (controller.canEditSelectedEvents &&
|
||||
!controller.selectedEvents.first.isActivityMessage)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.edit_outlined),
|
||||
tooltip: l10n.edit,
|
||||
onPressed: controller.editSelectedEventAction,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
// if (controller.canEditSelectedEvents &&
|
||||
// !controller.selectedEvents.first.isActivityMessage)
|
||||
// IconButton(
|
||||
// icon: const Icon(Icons.edit_outlined),
|
||||
// tooltip: l10n.edit,
|
||||
// onPressed: controller.editSelectedEventAction,
|
||||
// color: theme.colorScheme.primary,
|
||||
// ),
|
||||
if (controller.canRedactSelectedEvents)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete_outlined),
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
|
||||
Future<void> showScaffoldDialog({
|
||||
Future<T?> showScaffoldDialog<T>({
|
||||
required BuildContext context,
|
||||
Color? barrierColor,
|
||||
Color? containerColor,
|
||||
|
|
@ -11,7 +11,7 @@ Future<void> showScaffoldDialog({
|
|||
double maxHeight = 720,
|
||||
required Widget Function(BuildContext context) builder,
|
||||
}) =>
|
||||
showDialog(
|
||||
showDialog<T>(
|
||||
context: context,
|
||||
useSafeArea: false,
|
||||
builder: FluffyThemes.isColumnMode(context)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue