2021-06-24 23:10:45 +00:00
|
|
|
import 'dart:convert';
|
|
|
|
import 'dart:io';
|
2021-07-05 19:31:16 +00:00
|
|
|
import 'package:crypto/crypto.dart';
|
2022-04-06 01:36:18 +00:00
|
|
|
import 'package:cwtch/cwtch/cwtch.dart';
|
2021-06-24 23:10:45 +00:00
|
|
|
import 'package:cwtch/cwtch_icons_icons.dart';
|
2022-01-18 21:26:52 +00:00
|
|
|
import 'package:cwtch/models/appstate.dart';
|
|
|
|
import 'package:cwtch/models/chatmessage.dart';
|
|
|
|
import 'package:cwtch/models/contact.dart';
|
2021-07-06 19:46:39 +00:00
|
|
|
import 'package:cwtch/models/message.dart';
|
2022-04-27 04:29:56 +00:00
|
|
|
import 'package:cwtch/models/messagecache.dart';
|
2021-07-13 21:46:47 +00:00
|
|
|
import 'package:cwtch/models/messages/quotedmessage.dart';
|
2022-01-18 21:26:52 +00:00
|
|
|
import 'package:cwtch/models/profile.dart';
|
2021-12-18 00:32:22 +00:00
|
|
|
import 'package:cwtch/widgets/malformedbubble.dart';
|
2021-07-07 17:05:25 +00:00
|
|
|
import 'package:cwtch/widgets/messageloadingbubble.dart';
|
2021-06-24 23:10:45 +00:00
|
|
|
import 'package:cwtch/widgets/profileimage.dart';
|
2022-02-07 19:30:17 +00:00
|
|
|
import 'package:cwtch/controllers/filesharing.dart' as filesharing;
|
2021-09-21 21:57:40 +00:00
|
|
|
import 'package:file_picker/file_picker.dart';
|
|
|
|
|
2021-06-24 23:10:45 +00:00
|
|
|
import 'package:flutter/cupertino.dart';
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:cwtch/views/peersettingsview.dart';
|
|
|
|
import 'package:cwtch/widgets/DropdownContacts.dart';
|
2021-06-30 22:14:11 +00:00
|
|
|
import 'package:flutter/services.dart';
|
2021-06-24 23:10:45 +00:00
|
|
|
import 'package:provider/provider.dart';
|
|
|
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
2021-07-23 21:50:21 +00:00
|
|
|
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
2021-06-24 23:10:45 +00:00
|
|
|
|
2022-04-27 04:29:56 +00:00
|
|
|
import '../config.dart';
|
2022-02-07 19:30:17 +00:00
|
|
|
import '../constants.dart';
|
2021-06-24 23:10:45 +00:00
|
|
|
import '../main.dart';
|
|
|
|
import '../settings.dart';
|
|
|
|
import '../widgets/messagelist.dart';
|
|
|
|
import 'groupsettingsview.dart';
|
|
|
|
|
|
|
|
class MessageView extends StatefulWidget {
|
|
|
|
@override
|
|
|
|
_MessageViewState createState() => _MessageViewState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _MessageViewState extends State<MessageView> {
|
|
|
|
final ctrlrCompose = TextEditingController();
|
|
|
|
final focusNode = FocusNode();
|
2021-11-18 23:44:54 +00:00
|
|
|
int selectedContact = -1;
|
2021-07-23 21:50:21 +00:00
|
|
|
ItemPositionsListener scrollListener = ItemPositionsListener.create();
|
2021-12-18 00:32:22 +00:00
|
|
|
File? imagePreview;
|
2022-06-10 21:28:16 +00:00
|
|
|
bool showDown = false;
|
2021-06-24 23:10:45 +00:00
|
|
|
|
2021-07-23 21:50:21 +00:00
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
scrollListener.itemPositions.addListener(() {
|
2021-11-10 23:52:34 +00:00
|
|
|
if (scrollListener.itemPositions.value.length != 0 &&
|
2021-11-25 23:59:54 +00:00
|
|
|
Provider.of<AppState>(context, listen: false).unreadMessagesBelow == true &&
|
|
|
|
scrollListener.itemPositions.value.any((element) => element.index == 0)) {
|
|
|
|
Provider.of<AppState>(context, listen: false).initialScrollIndex = 0;
|
|
|
|
Provider.of<AppState>(context, listen: false).unreadMessagesBelow = false;
|
2021-07-23 21:50:21 +00:00
|
|
|
}
|
2022-06-10 21:28:16 +00:00
|
|
|
|
|
|
|
|
|
|
|
if (scrollListener.itemPositions.value.length != 0 &&
|
|
|
|
!scrollListener.itemPositions.value.any((element) => element.index == 0)) {
|
|
|
|
showDown = true;
|
|
|
|
} else {
|
|
|
|
showDown = false;
|
|
|
|
}
|
|
|
|
|
2021-07-23 21:50:21 +00:00
|
|
|
});
|
|
|
|
super.initState();
|
|
|
|
}
|
2021-06-24 23:10:45 +00:00
|
|
|
|
2021-08-16 23:09:03 +00:00
|
|
|
@override
|
|
|
|
void didChangeDependencies() {
|
|
|
|
var appState = Provider.of<AppState>(context, listen: false);
|
|
|
|
|
|
|
|
// using "8" because "# of messages that fit on one screen" isnt trivial to calculate at this point
|
2021-11-10 23:52:34 +00:00
|
|
|
if (appState.initialScrollIndex > 4 && appState.unreadMessagesBelow == false) {
|
2021-08-16 23:09:03 +00:00
|
|
|
WidgetsFlutterBinding.ensureInitialized().addPostFrameCallback((timeStamp) {
|
|
|
|
appState.unreadMessagesBelow = true;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
super.didChangeDependencies();
|
|
|
|
}
|
|
|
|
|
2021-06-24 23:10:45 +00:00
|
|
|
@override
|
|
|
|
void dispose() {
|
|
|
|
focusNode.dispose();
|
|
|
|
ctrlrCompose.dispose();
|
|
|
|
super.dispose();
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
2021-07-08 20:28:30 +00:00
|
|
|
// After leaving a conversation the selected conversation is set to null...
|
2022-06-09 20:49:15 +00:00
|
|
|
if (Provider.of<ContactInfoState>(context, listen: false).profileOnion == "") {
|
2021-07-08 20:28:30 +00:00
|
|
|
return Card(child: Center(child: Text(AppLocalizations.of(context)!.addContactFirst)));
|
|
|
|
}
|
|
|
|
|
2021-10-01 19:38:06 +00:00
|
|
|
var showFileSharing = Provider.of<Settings>(context).isExperimentEnabled(FileSharingExperiment);
|
2021-09-21 21:57:40 +00:00
|
|
|
var appBarButtons = <Widget>[];
|
|
|
|
if (Provider.of<ContactInfoState>(context).isOnline()) {
|
2021-10-01 19:38:06 +00:00
|
|
|
if (showFileSharing) {
|
|
|
|
appBarButtons.add(IconButton(
|
2022-01-14 22:19:35 +00:00
|
|
|
splashRadius: Material.defaultSplashRadius / 2,
|
2022-01-21 21:17:16 +00:00
|
|
|
icon: Icon(Icons.attach_file, size: 24, color: Provider.of<Settings>(context).theme.mainTextColor),
|
2021-10-01 19:38:06 +00:00
|
|
|
tooltip: AppLocalizations.of(context)!.tooltipSendFile,
|
2022-01-21 21:17:16 +00:00
|
|
|
onPressed: Provider.of<AppState>(context).disableFilePicker
|
|
|
|
? null
|
|
|
|
: () {
|
2022-02-07 19:30:17 +00:00
|
|
|
imagePreview = null;
|
2022-02-07 22:59:09 +00:00
|
|
|
filesharing.showFilePicker(context, MaxGeneralFileSharingSize, (File file) {
|
2022-02-07 19:30:17 +00:00
|
|
|
_confirmFileSend(context, file.path);
|
|
|
|
}, () {
|
|
|
|
final snackBar = SnackBar(
|
|
|
|
content: Text(AppLocalizations.of(context)!.msgFileTooBig),
|
|
|
|
duration: Duration(seconds: 4),
|
|
|
|
);
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
|
|
|
}, () {});
|
2022-01-21 21:17:16 +00:00
|
|
|
},
|
2021-10-01 19:38:06 +00:00
|
|
|
));
|
|
|
|
}
|
2021-09-21 21:57:40 +00:00
|
|
|
appBarButtons.add(IconButton(
|
2022-01-14 22:19:35 +00:00
|
|
|
splashRadius: Material.defaultSplashRadius / 2,
|
2021-09-30 00:20:35 +00:00
|
|
|
icon: Icon(CwtchIcons.send_invite, size: 24),
|
|
|
|
tooltip: AppLocalizations.of(context)!.sendInvite,
|
|
|
|
onPressed: () {
|
|
|
|
_modalSendInvitation(context);
|
|
|
|
}));
|
2021-09-21 21:57:40 +00:00
|
|
|
}
|
|
|
|
appBarButtons.add(IconButton(
|
2022-01-14 22:19:35 +00:00
|
|
|
splashRadius: Material.defaultSplashRadius / 2,
|
2021-09-30 00:20:35 +00:00
|
|
|
icon: Provider.of<ContactInfoState>(context, listen: false).isGroup == true ? Icon(CwtchIcons.group_settings_24px) : Icon(CwtchIcons.peer_settings_24px),
|
|
|
|
tooltip: AppLocalizations.of(context)!.conversationSettings,
|
|
|
|
onPressed: _pushContactSettings));
|
2021-09-21 21:57:40 +00:00
|
|
|
|
2021-06-24 23:10:45 +00:00
|
|
|
var appState = Provider.of<AppState>(context);
|
|
|
|
return WillPopScope(
|
|
|
|
onWillPop: _onWillPop,
|
|
|
|
child: Scaffold(
|
2021-12-10 04:22:55 +00:00
|
|
|
backgroundColor: Provider.of<Settings>(context).theme.backgroundMainColor,
|
2022-06-10 21:28:16 +00:00
|
|
|
floatingActionButton: showDown
|
2021-08-26 21:56:58 +00:00
|
|
|
? FloatingActionButton(
|
2022-04-20 00:00:19 +00:00
|
|
|
child: Icon(Icons.arrow_downward, color: Provider.of<Settings>(context).current().defaultButtonTextColor),
|
2021-08-26 21:56:58 +00:00
|
|
|
onPressed: () {
|
2021-11-10 23:52:34 +00:00
|
|
|
Provider.of<AppState>(context, listen: false).initialScrollIndex = 0;
|
|
|
|
Provider.of<AppState>(context, listen: false).unreadMessagesBelow = false;
|
2022-06-10 19:24:38 +00:00
|
|
|
Provider.of<ContactInfoState>(context).messageScrollController.scrollTo(index: 0, duration: Duration(milliseconds: 600));
|
2021-08-26 21:56:58 +00:00
|
|
|
})
|
|
|
|
: null,
|
2021-06-24 23:10:45 +00:00
|
|
|
appBar: AppBar(
|
2022-06-10 17:40:22 +00:00
|
|
|
// setting leading(Width) to null makes it do the default behaviour; container() hides it
|
|
|
|
leadingWidth: Provider.of<Settings>(context).uiColumns(appState.isLandscape(context)).length > 1 ? 0 : null,
|
|
|
|
leading: Provider.of<Settings>(context).uiColumns(appState.isLandscape(context)).length > 1
|
|
|
|
? Container(
|
|
|
|
padding: EdgeInsets.zero,
|
|
|
|
margin: EdgeInsets.zero,
|
|
|
|
width: 0,
|
|
|
|
height: 0,
|
|
|
|
)
|
|
|
|
: null,
|
2021-06-30 20:59:52 +00:00
|
|
|
title: Row(children: [
|
|
|
|
ProfileImage(
|
2022-02-07 22:26:14 +00:00
|
|
|
imagePath: Provider.of<Settings>(context).isExperimentEnabled(ImagePreviewsExperiment)
|
|
|
|
? Provider.of<ContactInfoState>(context).imagePath
|
|
|
|
: Provider.of<ContactInfoState>(context).defaultImagePath,
|
2021-06-30 20:59:52 +00:00
|
|
|
diameter: 42,
|
2021-12-10 04:22:55 +00:00
|
|
|
border: Provider.of<Settings>(context).current().portraitOnlineBorderColor,
|
2021-06-30 20:59:52 +00:00
|
|
|
badgeTextColor: Colors.red,
|
|
|
|
badgeColor: Colors.red,
|
|
|
|
),
|
|
|
|
SizedBox(
|
|
|
|
width: 10,
|
|
|
|
),
|
2021-07-08 20:28:30 +00:00
|
|
|
Expanded(
|
|
|
|
child: Text(
|
|
|
|
Provider.of<ContactInfoState>(context).nickname,
|
|
|
|
overflow: TextOverflow.ellipsis,
|
|
|
|
))
|
2021-06-30 20:59:52 +00:00
|
|
|
]),
|
2021-09-21 21:57:40 +00:00
|
|
|
actions: appBarButtons,
|
2021-06-24 23:10:45 +00:00
|
|
|
),
|
2022-06-10 19:10:17 +00:00
|
|
|
body: Padding(
|
|
|
|
padding: EdgeInsets.fromLTRB(8.0, 8.0, 8.0, 108.0),
|
|
|
|
child: MessageList(
|
|
|
|
scrollListener,
|
|
|
|
)),
|
2021-06-24 23:10:45 +00:00
|
|
|
bottomSheet: _buildComposeBox(),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<bool> _onWillPop() async {
|
|
|
|
Provider.of<ContactInfoState>(context, listen: false).unreadMessages = 0;
|
2022-04-28 23:19:32 +00:00
|
|
|
|
|
|
|
var previouslySelected = Provider.of<AppState>(context, listen: false).selectedConversation;
|
|
|
|
if (previouslySelected != null) {
|
2022-05-11 19:43:54 +00:00
|
|
|
Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(previouslySelected)!.unselected();
|
2022-04-28 23:19:32 +00:00
|
|
|
}
|
|
|
|
|
2021-07-23 21:50:21 +00:00
|
|
|
Provider.of<AppState>(context, listen: false).selectedConversation = null;
|
2021-06-24 23:10:45 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void _pushContactSettings() {
|
2022-02-08 21:59:24 +00:00
|
|
|
var profileInfoState = Provider.of<ProfileInfoState>(context, listen: false);
|
|
|
|
var contactInfoState = Provider.of<ContactInfoState>(context, listen: false);
|
|
|
|
|
|
|
|
if (Provider.of<ContactInfoState>(context, listen: false).isGroup == true) {
|
|
|
|
Navigator.of(context).push(MaterialPageRoute<void>(builder: (BuildContext bcontext) {
|
|
|
|
return MultiProvider(
|
|
|
|
providers: [ChangeNotifierProvider.value(value: profileInfoState), ChangeNotifierProvider.value(value: contactInfoState)],
|
|
|
|
child: GroupSettingsView(),
|
|
|
|
);
|
|
|
|
}));
|
|
|
|
} else {
|
|
|
|
Navigator.of(context).push(MaterialPageRoute<void>(builder: (BuildContext bcontext) {
|
|
|
|
return MultiProvider(
|
|
|
|
providers: [ChangeNotifierProvider.value(value: profileInfoState), ChangeNotifierProvider.value(value: contactInfoState)],
|
|
|
|
child: PeerSettingsView(),
|
|
|
|
);
|
|
|
|
}));
|
|
|
|
}
|
2021-06-24 23:10:45 +00:00
|
|
|
}
|
|
|
|
|
2022-01-26 20:25:03 +00:00
|
|
|
// todo: legacy groups currently have restricted message
|
|
|
|
// size because of the additional wrapping end encoding
|
|
|
|
// hybrid groups should allow these numbers to be the same.
|
|
|
|
static const P2PMessageLengthMax = 7000;
|
2022-01-27 20:58:16 +00:00
|
|
|
static const GroupMessageLengthMax = 1600;
|
2022-01-26 20:25:03 +00:00
|
|
|
|
2021-06-24 23:10:45 +00:00
|
|
|
void _sendMessage([String? ignoredParam]) {
|
2022-02-14 19:00:46 +00:00
|
|
|
// Trim message
|
|
|
|
final messageWithoutNewLine = ctrlrCompose.value.text.trimRight();
|
2022-03-21 16:18:17 +00:00
|
|
|
ctrlrCompose.value = TextEditingValue(text: messageWithoutNewLine, selection: TextSelection.fromPosition(TextPosition(offset: messageWithoutNewLine.length)));
|
|
|
|
|
|
|
|
// Do this after we trim to preserve enter-behaviour...
|
|
|
|
bool isOffline = Provider.of<ContactInfoState>(context).isOnline() == false;
|
|
|
|
if (isOffline) {
|
|
|
|
return;
|
|
|
|
}
|
2022-02-14 19:00:46 +00:00
|
|
|
|
2022-02-05 00:37:25 +00:00
|
|
|
var isGroup = Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(Provider.of<AppState>(context, listen: false).selectedConversation!)!.isGroup;
|
2022-01-26 20:25:03 +00:00
|
|
|
|
|
|
|
// peers and groups currently have different length constraints (servers can store less)...
|
2022-01-27 20:58:16 +00:00
|
|
|
var actualMessageLength = ctrlrCompose.value.text.length;
|
|
|
|
var lengthOk = (isGroup && actualMessageLength < GroupMessageLengthMax) || actualMessageLength <= P2PMessageLengthMax;
|
2022-01-26 20:25:03 +00:00
|
|
|
|
|
|
|
if (ctrlrCompose.value.text.isNotEmpty && lengthOk) {
|
2021-07-07 19:22:56 +00:00
|
|
|
if (Provider.of<AppState>(context, listen: false).selectedConversation != null && Provider.of<AppState>(context, listen: false).selectedIndex != null) {
|
2022-04-27 04:29:56 +00:00
|
|
|
var conversationId = Provider.of<AppState>(context, listen: false).selectedConversation!;
|
|
|
|
MessageCache? cache = Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(conversationId)?.messageCache;
|
2022-05-11 19:43:54 +00:00
|
|
|
ById(Provider.of<AppState>(context, listen: false).selectedIndex!)
|
|
|
|
.get(Provider.of<FlwtchState>(context, listen: false).cwtch, Provider.of<AppState>(context, listen: false).selectedProfile!, conversationId, cache!)
|
2022-04-27 04:29:56 +00:00
|
|
|
.then((MessageInfo? data) {
|
2021-07-06 19:46:39 +00:00
|
|
|
try {
|
2022-04-27 04:29:56 +00:00
|
|
|
var bytes1 = utf8.encode(data!.metadata.senderHandle + data.wrapper);
|
2021-07-06 19:46:39 +00:00
|
|
|
var digest1 = sha256.convert(bytes1);
|
|
|
|
var contentHash = base64Encode(digest1.bytes);
|
2021-07-13 21:46:47 +00:00
|
|
|
var quotedMessage = jsonEncode(QuotedMessageStructure(contentHash, ctrlrCompose.value.text));
|
2021-07-07 17:05:25 +00:00
|
|
|
ChatMessage cm = new ChatMessage(o: QuotedMessageOverlay, d: quotedMessage);
|
2021-07-06 19:46:39 +00:00
|
|
|
Provider.of<FlwtchState>(context, listen: false)
|
|
|
|
.cwtch
|
2022-04-06 21:35:10 +00:00
|
|
|
.SendMessage(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).identifier, jsonEncode(cm))
|
|
|
|
.then(_sendMessageHandler);
|
2022-04-27 04:29:56 +00:00
|
|
|
} catch (e) {
|
|
|
|
EnvironmentConfig.debugLog("Exception: reply to message could not be found: " + e.toString());
|
|
|
|
}
|
2021-07-06 19:46:39 +00:00
|
|
|
Provider.of<AppState>(context, listen: false).selectedIndex = null;
|
2021-07-05 19:31:16 +00:00
|
|
|
});
|
2021-07-06 19:46:39 +00:00
|
|
|
} else {
|
2021-07-07 17:05:25 +00:00
|
|
|
ChatMessage cm = new ChatMessage(o: TextMessageOverlay, d: ctrlrCompose.value.text);
|
2021-07-05 19:31:16 +00:00
|
|
|
Provider.of<FlwtchState>(context, listen: false)
|
|
|
|
.cwtch
|
2022-04-06 21:35:10 +00:00
|
|
|
.SendMessage(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).identifier, jsonEncode(cm))
|
|
|
|
.then(_sendMessageHandler);
|
2021-07-05 19:31:16 +00:00
|
|
|
}
|
2021-06-30 22:14:11 +00:00
|
|
|
}
|
2021-06-24 23:10:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void _sendInvitation([String? ignoredParam]) {
|
|
|
|
Provider.of<FlwtchState>(context, listen: false)
|
|
|
|
.cwtch
|
2022-04-06 21:35:10 +00:00
|
|
|
.SendInvitation(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).identifier, this.selectedContact)
|
|
|
|
.then(_sendMessageHandler);
|
2021-06-24 23:10:45 +00:00
|
|
|
}
|
|
|
|
|
2021-09-21 21:57:40 +00:00
|
|
|
void _sendFile(String filePath) {
|
|
|
|
Provider.of<FlwtchState>(context, listen: false)
|
|
|
|
.cwtch
|
2022-04-06 21:35:10 +00:00
|
|
|
.ShareFile(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).identifier, filePath)
|
|
|
|
.then(_sendMessageHandler);
|
2021-09-21 21:57:40 +00:00
|
|
|
}
|
|
|
|
|
2022-03-23 23:08:19 +00:00
|
|
|
void _sendMessageHandler(dynamic messageJson) {
|
|
|
|
var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
|
|
|
|
var identifier = Provider.of<ContactInfoState>(context, listen: false).identifier;
|
|
|
|
var profile = Provider.of<ProfileInfoState>(context, listen: false);
|
|
|
|
|
|
|
|
var messageInfo = messageJsonToInfo(profileOnion, identifier, messageJson);
|
|
|
|
if (messageInfo != null) {
|
|
|
|
profile.newMessage(
|
|
|
|
messageInfo.metadata.conversationIdentifier,
|
|
|
|
messageInfo.metadata.messageID,
|
|
|
|
messageInfo.metadata.timestamp,
|
|
|
|
messageInfo.metadata.senderHandle,
|
|
|
|
messageInfo.metadata.senderImage ?? "",
|
|
|
|
messageInfo.metadata.isAuto,
|
|
|
|
messageInfo.wrapper,
|
|
|
|
messageInfo.metadata.contenthash,
|
|
|
|
true,
|
|
|
|
true,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-06-24 23:10:45 +00:00
|
|
|
ctrlrCompose.clear();
|
|
|
|
focusNode.requestFocus();
|
2022-04-06 01:36:18 +00:00
|
|
|
|
|
|
|
Provider.of<FlwtchState>(context, listen: false).cwtch.SetConversationAttribute(profileOnion, identifier, LastMessageSeenTimeKey, DateTime.now().toIso8601String());
|
2021-06-24 23:10:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Widget _buildComposeBox() {
|
2021-07-12 20:18:39 +00:00
|
|
|
bool isOffline = Provider.of<ContactInfoState>(context).isOnline() == false;
|
2022-01-26 20:25:03 +00:00
|
|
|
bool isGroup = Provider.of<ContactInfoState>(context).isGroup;
|
2021-07-12 20:18:39 +00:00
|
|
|
|
2022-01-27 20:58:16 +00:00
|
|
|
var charLength = ctrlrCompose.value.text.characters.length;
|
|
|
|
var expectedLength = ctrlrCompose.value.text.length;
|
|
|
|
var numberOfBytesMoreThanChar = (expectedLength - charLength);
|
|
|
|
|
2021-07-05 19:31:16 +00:00
|
|
|
var composeBox = Container(
|
2021-12-10 04:22:55 +00:00
|
|
|
color: Provider.of<Settings>(context).theme.backgroundMainColor,
|
2021-06-24 23:10:45 +00:00
|
|
|
padding: EdgeInsets.all(2),
|
|
|
|
margin: EdgeInsets.all(2),
|
|
|
|
height: 100,
|
|
|
|
child: Row(
|
|
|
|
children: <Widget>[
|
|
|
|
Expanded(
|
2021-08-26 21:56:58 +00:00
|
|
|
child: Container(
|
2021-12-10 04:22:55 +00:00
|
|
|
decoration: BoxDecoration(border: Border(top: BorderSide(color: Provider.of<Settings>(context).theme.defaultButtonActiveColor))),
|
2021-08-26 21:56:58 +00:00
|
|
|
child: RawKeyboardListener(
|
|
|
|
focusNode: FocusNode(),
|
|
|
|
onKey: handleKeyPress,
|
|
|
|
child: Padding(
|
|
|
|
padding: EdgeInsets.all(8),
|
|
|
|
child: TextFormField(
|
|
|
|
key: Key('txtCompose'),
|
|
|
|
controller: ctrlrCompose,
|
|
|
|
focusNode: focusNode,
|
|
|
|
autofocus: !Platform.isAndroid,
|
|
|
|
textInputAction: TextInputAction.newline,
|
|
|
|
keyboardType: TextInputType.multiline,
|
|
|
|
enableIMEPersonalizedLearning: false,
|
|
|
|
minLines: 1,
|
2022-01-27 20:58:16 +00:00
|
|
|
maxLength: (isGroup ? GroupMessageLengthMax : P2PMessageLengthMax) - numberOfBytesMoreThanChar,
|
2022-01-26 20:25:03 +00:00
|
|
|
maxLengthEnforcement: MaxLengthEnforcement.enforced,
|
2021-08-26 21:56:58 +00:00
|
|
|
maxLines: null,
|
|
|
|
onFieldSubmitted: _sendMessage,
|
2022-03-21 16:18:17 +00:00
|
|
|
enabled: true, // always allow editing...
|
2022-01-27 20:58:16 +00:00
|
|
|
onChanged: (String x) {
|
|
|
|
setState(() {
|
|
|
|
// we need to force a rerender here to update the max length count
|
|
|
|
});
|
|
|
|
},
|
2021-08-26 21:56:58 +00:00
|
|
|
decoration: InputDecoration(
|
|
|
|
hintText: isOffline ? "" : AppLocalizations.of(context)!.placeholderEnterMessage,
|
2021-12-10 04:22:55 +00:00
|
|
|
hintStyle: TextStyle(color: Provider.of<Settings>(context).theme.sendHintTextColor),
|
2021-08-26 21:56:58 +00:00
|
|
|
enabledBorder: InputBorder.none,
|
|
|
|
focusedBorder: InputBorder.none,
|
|
|
|
enabled: true,
|
|
|
|
suffixIcon: ElevatedButton(
|
2022-02-05 00:37:25 +00:00
|
|
|
key: Key("btnSend"),
|
2022-01-14 22:19:35 +00:00
|
|
|
style: ElevatedButton.styleFrom(padding: EdgeInsets.all(0.0), shape: new RoundedRectangleBorder(borderRadius: new BorderRadius.circular(45.0))),
|
2021-12-10 04:22:55 +00:00
|
|
|
child: Icon(CwtchIcons.send_24px, size: 24, color: Provider.of<Settings>(context).theme.defaultButtonTextColor),
|
2021-08-26 21:56:58 +00:00
|
|
|
onPressed: isOffline ? null : _sendMessage,
|
|
|
|
))),
|
|
|
|
)))),
|
2021-06-24 23:10:45 +00:00
|
|
|
],
|
|
|
|
),
|
|
|
|
);
|
2021-07-05 19:31:16 +00:00
|
|
|
|
|
|
|
var children;
|
|
|
|
if (Provider.of<AppState>(context).selectedConversation != null && Provider.of<AppState>(context).selectedIndex != null) {
|
|
|
|
var quoted = FutureBuilder(
|
2022-01-20 20:58:14 +00:00
|
|
|
future: messageHandler(context, Provider.of<AppState>(context).selectedProfile!, Provider.of<AppState>(context).selectedConversation!, ById(Provider.of<AppState>(context).selectedIndex!)),
|
2021-07-05 19:31:16 +00:00
|
|
|
builder: (context, snapshot) {
|
|
|
|
if (snapshot.hasData) {
|
2021-07-06 19:46:39 +00:00
|
|
|
var message = snapshot.data! as Message;
|
|
|
|
return Container(
|
|
|
|
margin: EdgeInsets.all(5),
|
|
|
|
padding: EdgeInsets.all(5),
|
|
|
|
color: message.getMetadata().senderHandle != Provider.of<AppState>(context).selectedProfile
|
2021-12-10 04:22:55 +00:00
|
|
|
? Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor
|
|
|
|
: Provider.of<Settings>(context).theme.messageFromMeBackgroundColor,
|
2021-09-17 20:38:10 +00:00
|
|
|
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
|
|
|
Stack(children: [
|
|
|
|
Align(
|
|
|
|
alignment: Alignment.topRight,
|
|
|
|
child: IconButton(
|
|
|
|
icon: Icon(Icons.highlight_remove),
|
2022-01-14 22:19:35 +00:00
|
|
|
splashRadius: Material.defaultSplashRadius / 2,
|
2021-09-17 20:38:10 +00:00
|
|
|
tooltip: AppLocalizations.of(context)!.tooltipRemoveThisQuotedMessage,
|
|
|
|
onPressed: () {
|
|
|
|
Provider.of<AppState>(context, listen: false).selectedIndex = null;
|
|
|
|
},
|
|
|
|
)),
|
|
|
|
Align(
|
|
|
|
alignment: Alignment.topLeft,
|
|
|
|
child: Padding(padding: EdgeInsets.all(2.0), child: Icon(Icons.reply)),
|
|
|
|
)
|
|
|
|
]),
|
|
|
|
Wrap(
|
|
|
|
runAlignment: WrapAlignment.spaceEvenly,
|
|
|
|
alignment: WrapAlignment.center,
|
|
|
|
runSpacing: 1.0,
|
|
|
|
children: [Center(widthFactor: 1.0, child: Padding(padding: EdgeInsets.all(10.0), child: message.getPreviewWidget(context)))]),
|
2021-07-07 17:05:25 +00:00
|
|
|
]));
|
2021-07-05 19:31:16 +00:00
|
|
|
} else {
|
2021-07-07 17:05:25 +00:00
|
|
|
return MessageLoadingBubble();
|
2021-07-05 19:31:16 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
children = [quoted, composeBox];
|
|
|
|
} else {
|
|
|
|
children = [composeBox];
|
|
|
|
}
|
|
|
|
|
2021-12-15 22:29:27 +00:00
|
|
|
return Container(color: Provider.of<Settings>(context).theme.backgroundMainColor, child: Column(mainAxisSize: MainAxisSize.min, children: children));
|
2021-06-24 23:10:45 +00:00
|
|
|
}
|
|
|
|
|
2021-06-30 22:14:11 +00:00
|
|
|
// Send the message if enter is pressed without the shift key...
|
2022-02-14 20:20:25 +00:00
|
|
|
void handleKeyPress(RawKeyEvent event) {
|
|
|
|
var data = event.data;
|
2022-03-24 23:25:08 +00:00
|
|
|
if (event is RawKeyUpEvent) {
|
|
|
|
if ((data.logicalKey == LogicalKeyboardKey.enter && !event.isShiftPressed) || data.logicalKey == LogicalKeyboardKey.numpadEnter && !event.isShiftPressed) {
|
|
|
|
_sendMessage();
|
|
|
|
}
|
2021-07-05 19:31:16 +00:00
|
|
|
}
|
2021-06-30 22:14:11 +00:00
|
|
|
}
|
|
|
|
|
2021-06-24 23:10:45 +00:00
|
|
|
void placeHolder() => {};
|
|
|
|
|
|
|
|
// explicitly passing BuildContext ctx here is important, change at risk to own health
|
|
|
|
// otherwise some Providers will become inaccessible to subwidgets...?
|
|
|
|
// https://stackoverflow.com/a/63818697
|
|
|
|
void _modalSendInvitation(BuildContext ctx) {
|
|
|
|
showModalBottomSheet<void>(
|
|
|
|
context: ctx,
|
|
|
|
builder: (BuildContext bcontext) {
|
|
|
|
return Container(
|
|
|
|
height: 200, // bespoke value courtesy of the [TextField] docs
|
|
|
|
child: Center(
|
|
|
|
child: Padding(
|
|
|
|
padding: EdgeInsets.all(10.0),
|
|
|
|
child: Column(
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
children: <Widget>[
|
|
|
|
Text(AppLocalizations.of(bcontext)!.invitationLabel),
|
|
|
|
SizedBox(
|
|
|
|
height: 20,
|
|
|
|
),
|
|
|
|
ChangeNotifierProvider.value(
|
|
|
|
value: Provider.of<ProfileInfoState>(ctx, listen: false),
|
|
|
|
child: DropdownContacts(filter: (contact) {
|
|
|
|
return contact.onion != Provider.of<ContactInfoState>(context).onion;
|
|
|
|
}, onChanged: (newVal) {
|
|
|
|
setState(() {
|
2021-12-18 00:32:22 +00:00
|
|
|
this.selectedContact = Provider.of<ProfileInfoState>(context, listen: false).contactList.findContact(newVal)!.identifier;
|
2021-06-24 23:10:45 +00:00
|
|
|
});
|
|
|
|
})),
|
|
|
|
SizedBox(
|
|
|
|
height: 20,
|
|
|
|
),
|
|
|
|
ElevatedButton(
|
|
|
|
child: Text(AppLocalizations.of(bcontext)!.inviteBtn, semanticsLabel: AppLocalizations.of(bcontext)!.inviteBtn),
|
|
|
|
onPressed: () {
|
2021-12-01 12:17:48 +00:00
|
|
|
if (this.selectedContact != -1) {
|
2021-06-24 23:10:45 +00:00
|
|
|
this._sendInvitation();
|
|
|
|
}
|
|
|
|
Navigator.pop(bcontext);
|
|
|
|
},
|
|
|
|
),
|
|
|
|
],
|
|
|
|
)),
|
|
|
|
));
|
|
|
|
});
|
|
|
|
}
|
2021-09-21 21:57:40 +00:00
|
|
|
|
2021-12-18 00:32:22 +00:00
|
|
|
void _confirmFileSend(BuildContext ctx, String path) async {
|
|
|
|
showModalBottomSheet<void>(
|
|
|
|
context: ctx,
|
|
|
|
builder: (BuildContext bcontext) {
|
|
|
|
var showPreview = false;
|
2021-12-19 02:09:18 +00:00
|
|
|
if (Provider.of<Settings>(context, listen: false).shouldPreview(path)) {
|
2021-12-18 00:32:22 +00:00
|
|
|
showPreview = true;
|
|
|
|
if (imagePreview == null) {
|
|
|
|
imagePreview = new File(path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return Container(
|
|
|
|
height: 300, // bespoke value courtesy of the [TextField] docs
|
|
|
|
child: Center(
|
|
|
|
child: Padding(
|
|
|
|
padding: EdgeInsets.all(10.0),
|
|
|
|
child: Column(
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
children: <Widget>[
|
2021-12-19 02:09:18 +00:00
|
|
|
Text(AppLocalizations.of(context)!.msgConfirmSend + " $path?"),
|
2021-12-18 00:32:22 +00:00
|
|
|
SizedBox(
|
|
|
|
height: 20,
|
|
|
|
),
|
2022-01-10 20:28:12 +00:00
|
|
|
Visibility(
|
|
|
|
visible: showPreview,
|
|
|
|
child: showPreview
|
|
|
|
? Image.file(
|
|
|
|
imagePreview!,
|
|
|
|
cacheHeight: 150, // limit the amount of space the image can decode too, we keep this high-ish to allow quality previews...
|
|
|
|
filterQuality: FilterQuality.medium,
|
|
|
|
fit: BoxFit.fill,
|
|
|
|
alignment: Alignment.center,
|
|
|
|
height: 150,
|
|
|
|
isAntiAlias: false,
|
|
|
|
errorBuilder: (context, error, stackTrace) {
|
|
|
|
return MalformedBubble();
|
|
|
|
},
|
|
|
|
)
|
|
|
|
: Container()),
|
|
|
|
Visibility(
|
|
|
|
visible: showPreview,
|
|
|
|
child: SizedBox(
|
|
|
|
height: 10,
|
|
|
|
)),
|
2021-12-18 00:32:22 +00:00
|
|
|
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
|
|
|
|
ElevatedButton(
|
2021-12-19 02:09:18 +00:00
|
|
|
child: Text(AppLocalizations.of(context)!.cancel, semanticsLabel: AppLocalizations.of(context)!.cancel),
|
2021-12-18 00:32:22 +00:00
|
|
|
onPressed: () {
|
|
|
|
Navigator.pop(bcontext);
|
|
|
|
},
|
|
|
|
),
|
2022-01-10 20:28:12 +00:00
|
|
|
SizedBox(
|
|
|
|
width: 20,
|
|
|
|
),
|
2021-12-18 00:32:22 +00:00
|
|
|
ElevatedButton(
|
2021-12-19 02:09:18 +00:00
|
|
|
child: Text(AppLocalizations.of(context)!.btnSendFile, semanticsLabel: AppLocalizations.of(context)!.btnSendFile),
|
2021-12-18 00:32:22 +00:00
|
|
|
onPressed: () {
|
|
|
|
_sendFile(path);
|
|
|
|
Navigator.pop(bcontext);
|
|
|
|
},
|
|
|
|
),
|
|
|
|
]),
|
|
|
|
],
|
|
|
|
)),
|
|
|
|
));
|
|
|
|
});
|
|
|
|
}
|
2021-06-24 23:10:45 +00:00
|
|
|
}
|