Click to scroll on Quoted Message / Shorten Text #469
|
@ -0,0 +1,61 @@
|
|||
import 'package:cwtch/third_party/linkify/linkify.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
void modalOpenLink(BuildContext ctx, LinkableElement link) {
|
||||
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(30.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Text(AppLocalizations.of(bcontext)!.clickableLinksWarning),
|
||||
Flex(direction: Axis.horizontal, mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
|
||||
Container(
|
||||
margin: EdgeInsets.symmetric(vertical: 20, horizontal: 10),
|
||||
child: ElevatedButton(
|
||||
child: Text(AppLocalizations.of(bcontext)!.clickableLinksCopy, semanticsLabel: AppLocalizations.of(bcontext)!.clickableLinksCopy),
|
||||
onPressed: () {
|
||||
Clipboard.setData(new ClipboardData(text: link.url));
|
||||
|
||||
final snackBar = SnackBar(
|
||||
content: Text(AppLocalizations.of(bcontext)!.copiedToClipboardNotification),
|
||||
);
|
||||
|
||||
Navigator.pop(bcontext);
|
||||
ScaffoldMessenger.of(bcontext).showSnackBar(snackBar);
|
||||
},
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.symmetric(vertical: 20, horizontal: 10),
|
||||
child: ElevatedButton(
|
||||
child: Text(AppLocalizations.of(bcontext)!.clickableLinkOpen, semanticsLabel: AppLocalizations.of(bcontext)!.clickableLinkOpen),
|
||||
onPressed: () async {
|
||||
if (await canLaunch(link.url)) {
|
||||
await launch(link.url);
|
||||
Navigator.pop(bcontext);
|
||||
} else {
|
||||
final snackBar = SnackBar(
|
||||
content: Text(AppLocalizations.of(bcontext)!.clickableLinkError),
|
||||
);
|
||||
ScaffoldMessenger.of(bcontext).showSnackBar(snackBar);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
]),
|
||||
],
|
||||
)),
|
||||
));
|
||||
});
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:cwtch/widgets/messagerow.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
|
||||
import 'message.dart';
|
||||
import 'messagecache.dart';
|
||||
|
@ -44,6 +45,7 @@ class ContactInfoState extends ChangeNotifier {
|
|||
late Map<String, GlobalKey<MessageRowState>> keys;
|
||||
int _newMarkerMsgIndex = -1;
|
||||
late MessageCache messageCache;
|
||||
ItemScrollController messageScrollController = new ItemScrollController();
|
||||
|
||||
// todo: a nicer way to model contacts, groups and other "entities"
|
||||
late bool _isGroup;
|
||||
|
|
|
@ -8,10 +8,6 @@ import 'package:cwtch/widgets/quotedmessage.dart';
|
|||
import 'package:flutter/widgets.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../main.dart';
|
||||
import '../messagecache.dart';
|
||||
import '../profile.dart';
|
||||
|
||||
class QuotedMessageStructure {
|
||||
final String quotedHash;
|
||||
final String body;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:cwtch/models/message.dart';
|
||||
import 'package:cwtch/models/messages/malformedmessage.dart';
|
||||
import 'package:cwtch/widgets/malformedbubble.dart';
|
||||
|
@ -19,7 +21,7 @@ class TextMessage extends Message {
|
|||
return ChangeNotifierProvider.value(
|
||||
value: this.metadata,
|
||||
builder: (bcontext, child) {
|
||||
return SelectableText(this.content);
|
||||
return Text(this.content.substring(0, min(this.content.length, 50)));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -29,20 +29,21 @@ class ContactsView extends StatefulWidget {
|
|||
// selectConversation can be called from anywhere to set the active conversation
|
||||
void selectConversation(BuildContext context, int handle) {
|
||||
// requery instead of using contactinfostate directly because sometimes listview gets confused about data that resorts
|
||||
var initialIndex = Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(handle)!.unreadMessages;
|
||||
var unread = Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(handle)!.unreadMessages;
|
||||
var previouslySelected = Provider.of<AppState>(context, listen: false).selectedConversation;
|
||||
if (previouslySelected != null) {
|
||||
Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(previouslySelected)!.unselected();
|
||||
}
|
||||
Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(handle)!.selected();
|
||||
// triggers update in Double/TripleColumnView
|
||||
Provider.of<AppState>(context, listen: false).initialScrollIndex = initialIndex;
|
||||
Provider.of<AppState>(context, listen: false).initialScrollIndex = unread;
|
||||
Provider.of<AppState>(context, listen: false).selectedConversation = handle;
|
||||
Provider.of<AppState>(context, listen: false).selectedIndex = null;
|
||||
Provider.of<AppState>(context, listen: false).hoveredIndex = -1;
|
||||
// if in singlepane mode, push to the stack
|
||||
var isLandscape = Provider.of<AppState>(context, listen: false).isLandscape(context);
|
||||
if (Provider.of<Settings>(context, listen: false).uiColumns(isLandscape).length == 1) _pushMessageView(context, handle);
|
||||
|
||||
// Set last message seen time in backend
|
||||
Provider.of<FlwtchState>(context, listen: false)
|
||||
.cwtch
|
||||
|
|
|
@ -42,8 +42,8 @@ class _MessageViewState extends State<MessageView> {
|
|||
final focusNode = FocusNode();
|
||||
int selectedContact = -1;
|
||||
ItemPositionsListener scrollListener = ItemPositionsListener.create();
|
||||
ItemScrollController scrollController = ItemScrollController();
|
||||
File? imagePreview;
|
||||
bool showDown = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -54,6 +54,12 @@ class _MessageViewState extends State<MessageView> {
|
|||
Provider.of<AppState>(context, listen: false).initialScrollIndex = 0;
|
||||
Provider.of<AppState>(context, listen: false).unreadMessagesBelow = false;
|
||||
}
|
||||
|
||||
if (scrollListener.itemPositions.value.length != 0 && !scrollListener.itemPositions.value.any((element) => element.index == 0)) {
|
||||
showDown = true;
|
||||
} else {
|
||||
showDown = false;
|
||||
}
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
@ -128,13 +134,13 @@ class _MessageViewState extends State<MessageView> {
|
|||
onWillPop: _onWillPop,
|
||||
child: Scaffold(
|
||||
backgroundColor: Provider.of<Settings>(context).theme.backgroundMainColor,
|
||||
floatingActionButton: appState.unreadMessagesBelow
|
||||
floatingActionButton: showDown
|
||||
? FloatingActionButton(
|
||||
child: Icon(Icons.arrow_downward, color: Provider.of<Settings>(context).current().defaultButtonTextColor),
|
||||
onPressed: () {
|
||||
Provider.of<AppState>(context, listen: false).initialScrollIndex = 0;
|
||||
Provider.of<AppState>(context, listen: false).unreadMessagesBelow = false;
|
||||
scrollController.scrollTo(index: 0, duration: Duration(milliseconds: 600));
|
||||
Provider.of<ContactInfoState>(context).messageScrollController.scrollTo(index: 0, duration: Duration(milliseconds: 600));
|
||||
})
|
||||
: null,
|
||||
appBar: AppBar(
|
||||
|
@ -169,7 +175,11 @@ class _MessageViewState extends State<MessageView> {
|
|||
]),
|
||||
actions: appBarButtons,
|
||||
),
|
||||
body: Padding(padding: EdgeInsets.fromLTRB(8.0, 8.0, 8.0, 108.0), child: MessageList(scrollController, scrollListener)),
|
||||
body: Padding(
|
||||
padding: EdgeInsets.fromLTRB(8.0, 8.0, 8.0, 108.0),
|
||||
child: MessageList(
|
||||
scrollListener,
|
||||
)),
|
||||
bottomSheet: _buildComposeBox(),
|
||||
));
|
||||
}
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:cwtch/controllers/open_link_modal.dart';
|
||||
import 'package:cwtch/models/contact.dart';
|
||||
import 'package:cwtch/models/message.dart';
|
||||
import 'package:cwtch/third_party/linkify/flutter_linkify.dart';
|
||||
import 'package:cwtch/models/profile.dart';
|
||||
import 'package:cwtch/widgets/malformedbubble.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../settings.dart';
|
||||
import 'messagebubbledecorations.dart';
|
||||
|
@ -55,7 +52,7 @@ class MessageBubbleState extends State<MessageBubble> {
|
|||
linkifiers: [UrlLinkifier()],
|
||||
onOpen: showClickableLinks
|
||||
? (link) {
|
||||
_modalOpenLink(context, link);
|
||||
modalOpenLink(context, link);
|
||||
}
|
||||
: null,
|
||||
//key: Key(myKey),
|
||||
|
@ -104,59 +101,4 @@ class MessageBubbleState extends State<MessageBubble> {
|
|||
children: fromMe ? [wdgMessage, wdgDecorations] : [wdgSender, wdgMessage, wdgDecorations])))));
|
||||
});
|
||||
}
|
||||
|
||||
void _modalOpenLink(BuildContext ctx, LinkableElement link) {
|
||||
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(30.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Text(AppLocalizations.of(bcontext)!.clickableLinksWarning),
|
||||
Flex(direction: Axis.horizontal, mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
|
||||
Container(
|
||||
margin: EdgeInsets.symmetric(vertical: 20, horizontal: 10),
|
||||
child: ElevatedButton(
|
||||
child: Text(AppLocalizations.of(bcontext)!.clickableLinksCopy, semanticsLabel: AppLocalizations.of(bcontext)!.clickableLinksCopy),
|
||||
onPressed: () {
|
||||
Clipboard.setData(new ClipboardData(text: link.url));
|
||||
|
||||
final snackBar = SnackBar(
|
||||
content: Text(AppLocalizations.of(bcontext)!.copiedToClipboardNotification),
|
||||
);
|
||||
|
||||
Navigator.pop(bcontext);
|
||||
ScaffoldMessenger.of(bcontext).showSnackBar(snackBar);
|
||||
},
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.symmetric(vertical: 20, horizontal: 10),
|
||||
child: ElevatedButton(
|
||||
child: Text(AppLocalizations.of(bcontext)!.clickableLinkOpen, semanticsLabel: AppLocalizations.of(bcontext)!.clickableLinkOpen),
|
||||
onPressed: () async {
|
||||
if (await canLaunch(link.url)) {
|
||||
await launch(link.url);
|
||||
Navigator.pop(bcontext);
|
||||
} else {
|
||||
final snackBar = SnackBar(
|
||||
content: Text(AppLocalizations.of(bcontext)!.clickableLinkError),
|
||||
);
|
||||
ScaffoldMessenger.of(bcontext).showSnackBar(snackBar);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
]),
|
||||
],
|
||||
)),
|
||||
));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,9 +13,8 @@ import '../main.dart';
|
|||
import '../settings.dart';
|
||||
|
||||
class MessageList extends StatefulWidget {
|
||||
ItemScrollController scrollController;
|
||||
ItemPositionsListener scrollListener;
|
||||
MessageList(this.scrollController, this.scrollListener);
|
||||
MessageList(this.scrollListener);
|
||||
|
||||
@override
|
||||
_MessageListState createState() => _MessageListState();
|
||||
|
@ -30,7 +29,6 @@ class _MessageListState extends State<MessageList> {
|
|||
MessageCache? cache = Provider.of<ProfileInfoState>(outerContext, listen: false).contactList.getContact(conversationId)?.messageCache;
|
||||
ByIndex(0).loadUnsynced(Provider.of<FlwtchState>(context, listen: false).cwtch, Provider.of<AppState>(outerContext, listen: false).selectedProfile!, conversationId, cache!);
|
||||
}
|
||||
|
||||
var initi = Provider.of<AppState>(outerContext, listen: false).initialScrollIndex;
|
||||
bool isP2P = !Provider.of<ContactInfoState>(context).isGroup;
|
||||
bool isGroupAndSyncing = Provider.of<ContactInfoState>(context).isGroup == true && Provider.of<ContactInfoState>(context).status == "Authenticated";
|
||||
|
@ -82,7 +80,7 @@ class _MessageListState extends State<MessageList> {
|
|||
child: loadMessages
|
||||
? ScrollablePositionedList.builder(
|
||||
itemPositionsListener: widget.scrollListener,
|
||||
itemScrollController: widget.scrollController,
|
||||
itemScrollController: Provider.of<ContactInfoState>(outerContext).messageScrollController,
|
||||
initialScrollIndex: initi > 4 ? initi - 4 : 0,
|
||||
itemCount: Provider.of<ContactInfoState>(outerContext).totalMessages,
|
||||
reverse: true, // NOTE: There seems to be a bug in flutter that corrects the mouse wheel scroll, but not the drag direction...
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
import 'package:cwtch/controllers/open_link_modal.dart';
|
||||
import 'package:cwtch/models/appstate.dart';
|
||||
import 'package:cwtch/models/contact.dart';
|
||||
import 'package:cwtch/models/message.dart';
|
||||
import 'package:cwtch/models/profile.dart';
|
||||
import 'package:cwtch/third_party/linkify/flutter_linkify.dart';
|
||||
import 'package:cwtch/views/contactsview.dart';
|
||||
import 'package:cwtch/widgets/malformedbubble.dart';
|
||||
import 'package:cwtch/widgets/messageloadingbubble.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -43,12 +47,29 @@ class QuotedMessageBubbleState extends State<QuotedMessageBubble> {
|
|||
var wdgSender = SelectableText(senderDisplayStr,
|
||||
style: TextStyle(fontSize: 9.0, color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor : Provider.of<Settings>(context).theme.messageFromOtherTextColor));
|
||||
|
||||
var wdgMessage = SelectableText(
|
||||
widget.body + '\u202F',
|
||||
var showClickableLinks = Provider.of<Settings>(context).isExperimentEnabled(ClickableLinksExperiment);
|
||||
var formatMessages = Provider.of<Settings>(context).isExperimentEnabled(FormattingExperiment);
|
||||
|
||||
var wdgMessage = SelectableLinkify(
|
||||
text: widget.body + '\u202F',
|
||||
// TODO: onOpen breaks the "selectable" functionality. Maybe something to do with gesture handler?
|
||||
options: LinkifyOptions(messageFormatting: formatMessages, parseLinks: showClickableLinks, looseUrl: true, defaultToHttps: true),
|
||||
linkifiers: [UrlLinkifier()],
|
||||
onOpen: showClickableLinks
|
||||
? (link) {
|
||||
modalOpenLink(context, link);
|
||||
}
|
||||
: null,
|
||||
//key: Key(myKey),
|
||||
focusNode: _focus,
|
||||
style: TextStyle(
|
||||
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor : Provider.of<Settings>(context).theme.messageFromOtherTextColor,
|
||||
),
|
||||
linkStyle: TextStyle(color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor : Provider.of<Settings>(context).theme.messageFromOtherTextColor),
|
||||
codeStyle: TextStyle(
|
||||
// note: these colors are flipped
|
||||
color: fromMe ? Provider.of<Settings>(context).theme.messageFromOtherTextColor : Provider.of<Settings>(context).theme.messageFromMeTextColor,
|
||||
backgroundColor: fromMe ? Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor : Provider.of<Settings>(context).theme.messageFromMeBackgroundColor),
|
||||
textAlign: TextAlign.left,
|
||||
textWidthBasis: TextWidthBasis.longestLine,
|
||||
);
|
||||
|
@ -61,14 +82,23 @@ class QuotedMessageBubbleState extends State<QuotedMessageBubble> {
|
|||
var qMessage = (snapshot.data! as Message);
|
||||
// Swap the background color for quoted tweets..
|
||||
var qTextColor = fromMe ? Provider.of<Settings>(context).theme.messageFromOtherTextColor : Provider.of<Settings>(context).theme.messageFromMeTextColor;
|
||||
return Container(
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
var index = Provider.of<ContactInfoState>(context, listen: false).messageCache.cacheByHash[qMessage.getMetadata().contenthash];
|
||||
var totalMessages = Provider.of<ContactInfoState>(context, listen: false).totalMessages;
|
||||
// we have to reverse here because the list itself is reversed...
|
||||
Provider.of<ContactInfoState>(context).messageScrollController.scrollTo(index: totalMessages - index!, duration: Duration(milliseconds: 100));
|
||||
},
|
||||
child: Container(
|
||||
margin: EdgeInsets.all(5),
|
||||
padding: EdgeInsets.all(5),
|
||||
color: fromMe ? Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor : Provider.of<Settings>(context).theme.messageFromMeBackgroundColor,
|
||||
child: Wrap(runAlignment: WrapAlignment.spaceEvenly, alignment: WrapAlignment.spaceEvenly, runSpacing: 1.0, crossAxisAlignment: WrapCrossAlignment.center, children: [
|
||||
Center(widthFactor: 1, child: Padding(padding: EdgeInsets.all(10.0), child: Icon(Icons.reply, size: 32, color: qTextColor))),
|
||||
Center(widthFactor: 1.0, child: DefaultTextStyle(child: qMessage.getPreviewWidget(context), style: TextStyle(color: qTextColor)))
|
||||
]));
|
||||
]))));
|
||||
} catch (e) {
|
||||
print(e);
|
||||
return MalformedBubble();
|
||||
|
|
Loading…
Reference in New Issue