Line data Source code
1 : import 'dart:async';
2 : import 'dart:io';
3 :
4 : import 'package:cwtch/config.dart';
5 : import 'package:cwtch/cwtch_icons_icons.dart';
6 : import 'package:cwtch/models/contact.dart';
7 : import 'package:cwtch/models/message.dart';
8 : import 'package:cwtch/models/profile.dart';
9 : import 'package:cwtch/themes/opaque.dart';
10 : import 'package:cwtch/third_party/base32/base32.dart';
11 : import 'package:cwtch/views/contactsview.dart';
12 : import 'package:cwtch/widgets/staticmessagebubble.dart';
13 : import 'package:flutter/material.dart';
14 : import 'package:cwtch/widgets/profileimage.dart';
15 : import 'package:flutter/physics.dart';
16 : import 'package:provider/provider.dart';
17 : import 'package:flutter_gen/gen_l10n/app_localizations.dart';
18 :
19 : import '../main.dart';
20 : import '../models/messagecache.dart';
21 : import '../models/redaction.dart';
22 : import '../settings.dart';
23 :
24 : class MessageRow extends StatefulWidget {
25 : final Widget child;
26 : final int index;
27 :
28 0 : MessageRow(this.child, this.index, {Key? key}) : super(key: key);
29 :
30 0 : @override
31 0 : MessageRowState createState() => MessageRowState();
32 : }
33 :
34 : class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMixin {
35 : bool showBlockedMessage = false;
36 : late AnimationController _controller;
37 : late Animation<Alignment> _animation;
38 : late Alignment _dragAlignment = Alignment.center;
39 : Alignment _dragAffinity = Alignment.center;
40 :
41 0 : @override
42 : void initState() {
43 0 : super.initState();
44 0 : _controller = AnimationController(vsync: this);
45 0 : _controller.addListener(() {
46 0 : setState(() {
47 0 : _dragAlignment = _animation.value;
48 : });
49 : });
50 : }
51 :
52 0 : @override
53 : void dispose() {
54 0 : _controller.dispose();
55 0 : super.dispose();
56 : }
57 :
58 0 : @override
59 : Widget build(BuildContext context) {
60 0 : var fromMe = Provider.of<MessageMetadata>(context).senderHandle == Provider.of<ProfileInfoState>(context).onion;
61 0 : var isContact = Provider.of<ProfileInfoState>(context).contactList.findContact(Provider.of<MessageMetadata>(context).senderHandle) != null;
62 0 : var isGroup = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageMetadata>(context, listen: false).conversationIdentifier)!.isGroup;
63 0 : var isBlocked = isContact ? Provider.of<ProfileInfoState>(context).contactList.findContact(Provider.of<MessageMetadata>(context).senderHandle)!.isBlocked : false;
64 0 : var actualMessage = Flexible(flex: Platform.isAndroid ? 10 : 100, fit: FlexFit.loose, child: widget.child);
65 :
66 0 : _dragAffinity = fromMe ? Alignment.centerRight : Alignment.centerLeft;
67 0 : _dragAlignment = fromMe ? Alignment.centerRight : Alignment.centerLeft;
68 :
69 : var senderDisplayStr = "";
70 : if (!fromMe) {
71 0 : ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.findContact(Provider.of<MessageMetadata>(context).senderHandle);
72 : if (contact != null) {
73 0 : senderDisplayStr = redactedNick(context, contact.onion, contact.nickname);
74 : } else {
75 0 : senderDisplayStr = Provider.of<MessageMetadata>(context).senderHandle;
76 : }
77 : }
78 :
79 0 : Widget wdgReply = Platform.isAndroid
80 0 : ? SizedBox.shrink()
81 0 : : Visibility(
82 0 : visible: EnvironmentConfig.TEST_MODE || Provider.of<ContactInfoState>(context).hoveredIndex == Provider.of<MessageMetadata>(context).messageID,
83 : maintainSize: true,
84 : maintainAnimation: true,
85 : maintainState: true,
86 : maintainInteractivity: false,
87 0 : child: IconButton(
88 0 : tooltip: AppLocalizations.of(context)!.tooltipReplyToThisMessage,
89 0 : splashRadius: Material.defaultSplashRadius / 2,
90 0 : onPressed: () {
91 0 : Provider.of<ContactInfoState>(context, listen: false).messageDraft.quotedReference = Provider.of<MessageMetadata>(context, listen: false).messageID;
92 0 : Provider.of<ContactInfoState>(context, listen: false).notifyMessageDraftUpdate();
93 0 : setState(() {});
94 : },
95 0 : icon: Icon(Icons.reply, color: Provider.of<Settings>(context).theme.chatReactionIconColor)));
96 :
97 0 : var settings = Provider.of<Settings>(context);
98 0 : var pis = Provider.of<ProfileInfoState>(context);
99 0 : var cis = Provider.of<ContactInfoState>(context);
100 0 : var borderColor = Provider.of<Settings>(context).theme.portraitOnlineBorderColor;
101 0 : var messageID = Provider.of<MessageMetadata>(context).messageID;
102 0 : var cache = Provider.of<ContactInfoState>(context).messageCache;
103 :
104 0 : Widget wdgSeeReplies = Platform.isAndroid
105 0 : ? SizedBox.shrink()
106 0 : : Visibility(
107 0 : visible: EnvironmentConfig.TEST_MODE || Provider.of<ContactInfoState>(context).hoveredIndex == Provider.of<MessageMetadata>(context).messageID,
108 : maintainSize: true,
109 : maintainAnimation: true,
110 : maintainState: true,
111 : maintainInteractivity: false,
112 0 : child: IconButton(
113 0 : tooltip: AppLocalizations.of(context)!.viewReplies,
114 0 : splashRadius: Material.defaultSplashRadius / 2,
115 0 : onPressed: () {
116 0 : modalShowReplies(context, AppLocalizations.of(context)!.headingReplies, AppLocalizations.of(context)!.messageNoReplies, settings, pis, cis, borderColor, cache, messageID);
117 : },
118 0 : icon: Icon(CwtchIcons.view_replies, color: Provider.of<Settings>(context).theme.chatReactionIconColor)));
119 :
120 0 : var profile = Provider.of<ProfileInfoState>(context, listen: false);
121 0 : var conversation = Provider.of<ContactInfoState>(context, listen: false);
122 0 : var message = Provider.of<MessageMetadata>(context, listen: false);
123 :
124 0 : Widget wdgTranslateMessage = Platform.isAndroid
125 0 : ? SizedBox.shrink()
126 0 : : Visibility(
127 0 : visible: Provider.of<FlwtchState>(context, listen: false).cwtch.IsBlodeuweddSupported() &&
128 0 : Provider.of<Settings>(context).isExperimentEnabled(BlodeuweddExperiment) &&
129 0 : (EnvironmentConfig.TEST_MODE || Provider.of<ContactInfoState>(context).hoveredIndex == Provider.of<MessageMetadata>(context).messageID),
130 : maintainSize: true,
131 : maintainAnimation: true,
132 : maintainState: true,
133 : maintainInteractivity: false,
134 0 : child: IconButton(
135 0 : tooltip: AppLocalizations.of(context)!.blodeuweddTranslate,
136 0 : splashRadius: Material.defaultSplashRadius / 2,
137 0 : onPressed: () {
138 0 : Provider.of<MessageMetadata>(context, listen: false).translation = "";
139 0 : Provider.of<FlwtchState>(context, listen: false).cwtch.TranslateMessage(profile.onion, conversation.identifier, message.messageID, "French");
140 0 : modalShowTranslation(context, profile, settings);
141 : },
142 0 : icon: Icon(Icons.translate, color: Provider.of<Settings>(context).theme.chatReactionIconColor)));
143 :
144 0 : Widget wdgSpacer = Flexible(flex: 1, child: SizedBox(width: Platform.isAndroid ? 20 : 60, height: 10));
145 0 : var widgetRow = <Widget>[];
146 :
147 : if (fromMe) {
148 0 : widgetRow = <Widget>[
149 : wdgSpacer,
150 : wdgTranslateMessage,
151 : wdgSeeReplies,
152 : wdgReply,
153 : actualMessage,
154 : ];
155 0 : } else if (isBlocked && !showBlockedMessage) {
156 0 : Color blockedMessageBackground = Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor;
157 0 : Widget wdgPortrait = Padding(padding: EdgeInsets.all(4.0), child: Icon(CwtchIcons.account_blocked));
158 0 : widgetRow = <Widget>[
159 : wdgPortrait,
160 0 : Container(
161 0 : padding: EdgeInsets.all(2.0),
162 0 : decoration: BoxDecoration(
163 : color: blockedMessageBackground,
164 0 : border: Border.all(color: blockedMessageBackground, width: 2),
165 0 : borderRadius: BorderRadius.only(
166 0 : topLeft: Radius.circular(15.0),
167 0 : topRight: Radius.circular(15.0),
168 0 : bottomLeft: Radius.circular(15.0),
169 0 : bottomRight: Radius.circular(15.0),
170 : )),
171 0 : child: Padding(
172 0 : padding: EdgeInsets.all(9.0),
173 0 : child: Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
174 0 : SelectableText(
175 0 : AppLocalizations.of(context)!.blockedMessageMessage,
176 : //key: Key(myKey),
177 0 : style: TextStyle(
178 0 : color: Provider.of<Settings>(context).theme.messageFromOtherTextColor,
179 : ),
180 : textAlign: TextAlign.center,
181 : textWidthBasis: TextWidthBasis.longestLine,
182 : ),
183 0 : Padding(
184 0 : padding: EdgeInsets.all(1.0),
185 0 : child: TextButton(
186 0 : style: ButtonStyle(
187 0 : backgroundColor: MaterialStateProperty.all(blockedMessageBackground),
188 : ),
189 0 : child: Text(
190 0 : AppLocalizations.of(context)!.showMessageButton + '\u202F',
191 0 : style: TextStyle(decoration: TextDecoration.underline),
192 : ),
193 0 : onPressed: () {
194 0 : setState(() {
195 0 : this.showBlockedMessage = true;
196 : });
197 : })),
198 : ]))),
199 : wdgSpacer,
200 : ];
201 : } else {
202 0 : var contact = Provider.of<ContactInfoState>(context);
203 0 : ContactInfoState? sender = Provider.of<ProfileInfoState>(context).contactList.findContact(Provider.of<MessageMetadata>(context).senderHandle);
204 :
205 0 : String imagePath = Provider.of<MessageMetadata>(context).senderImage!;
206 : if (sender != null) {
207 0 : imagePath = Provider.of<Settings>(context).isExperimentEnabled(ImagePreviewsExperiment) ? sender.imagePath : sender.defaultImagePath;
208 : } else {
209 0 : imagePath = RandomProfileImage(Provider.of<MessageMetadata>(context).senderHandle);
210 : }
211 0 : Widget wdgPortrait = GestureDetector(
212 : onTap: !isGroup
213 : ? null
214 : : isContact
215 0 : ? _btnGoto
216 0 : : _btnAdd,
217 0 : child: Padding(
218 0 : padding: EdgeInsets.all(4.0),
219 0 : child: ProfileImage(
220 : diameter: 48.0,
221 : // default to the contact image...otherwise use a derived sender image...
222 : imagePath: imagePath,
223 0 : border: contact.status == "Authenticated" ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor : Provider.of<Settings>(context).theme.portraitOfflineBorderColor,
224 : badgeTextColor: Colors.red,
225 : badgeColor: Colors.red,
226 : tooltip: !isGroup
227 : ? ""
228 : : isContact
229 0 : ? AppLocalizations.of(context)!.contactGoto.replaceFirst("%1", senderDisplayStr)
230 0 : : AppLocalizations.of(context)!.addContact,
231 : )));
232 :
233 0 : widgetRow = <Widget>[
234 : wdgPortrait,
235 : actualMessage,
236 : wdgReply,
237 : wdgSeeReplies,
238 : wdgTranslateMessage,
239 : wdgSpacer,
240 : ];
241 : }
242 0 : var size = MediaQuery.of(context).size;
243 0 : var mr = MouseRegion(
244 : // For desktop...
245 0 : onHover: (event) {
246 0 : if (Provider.of<ContactInfoState>(context, listen: false).hoveredIndex != Provider.of<MessageMetadata>(context, listen: false).messageID) {
247 0 : Provider.of<ContactInfoState>(context, listen: false).hoveredIndex = Provider.of<MessageMetadata>(context, listen: false).messageID;
248 : }
249 : },
250 0 : onExit: (event) {
251 0 : Provider.of<ContactInfoState>(context, listen: false).hoveredIndex = -1;
252 : },
253 0 : child: GestureDetector(
254 0 : onPanUpdate: (details) {
255 0 : setState(() {
256 0 : _dragAlignment += Alignment(
257 0 : details.delta.dx / (size.width * 0.5),
258 : 0,
259 : );
260 : });
261 : },
262 0 : onPanDown: (details) {
263 0 : _controller.stop();
264 : },
265 0 : onPanEnd: (details) {
266 0 : _runAnimation(details.velocity.pixelsPerSecond, size);
267 0 : if (Platform.isAndroid) {
268 0 : Provider.of<ContactInfoState>(context, listen: false).messageDraft.quotedReference = Provider.of<MessageMetadata>(context, listen: false).messageID;
269 0 : Provider.of<ContactInfoState>(context, listen: false).notifyMessageDraftUpdate();
270 0 : setState(() {});
271 : }
272 : },
273 0 : onLongPress: () async {
274 0 : if (Platform.isAndroid) {
275 0 : modalShowReplies(context, AppLocalizations.of(context)!.headingReplies, AppLocalizations.of(context)!.messageNoReplies, settings, pis, cis, borderColor, cache, messageID);
276 : }
277 : },
278 0 : child: Padding(
279 0 : padding: EdgeInsets.all(2),
280 0 : child: Align(
281 : widthFactor: 1,
282 0 : alignment: _dragAlignment,
283 0 : child: Row(
284 : mainAxisSize: MainAxisSize.min,
285 : mainAxisAlignment: MainAxisAlignment.center,
286 : children: widgetRow,
287 : )))));
288 :
289 0 : if (Provider.of<ContactInfoState>(context).newMarkerMsgIndex == widget.index) {
290 0 : return Column(
291 : crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
292 0 : children: [Align(alignment: Alignment.center, child: Padding(padding: EdgeInsets.all(5.0), child: _bubbleNew())), mr]);
293 : } else {
294 : return mr;
295 : }
296 : }
297 :
298 0 : Widget _bubbleNew() {
299 0 : return Container(
300 0 : decoration: BoxDecoration(
301 0 : color: Provider.of<Settings>(context).theme.messageFromMeBackgroundColor,
302 0 : border: Border.all(color: Provider.of<Settings>(context).theme.messageFromMeBackgroundColor, width: 1),
303 0 : borderRadius: BorderRadius.only(
304 0 : topLeft: Radius.circular(8),
305 0 : topRight: Radius.circular(8),
306 0 : bottomLeft: Radius.circular(8),
307 0 : bottomRight: Radius.circular(8),
308 : ),
309 : ),
310 0 : child: Padding(padding: EdgeInsets.all(9.0), child: Text(AppLocalizations.of(context)!.newMessagesLabel, style: Provider.of<Settings>(context).scaleFonts(defaultTextButtonStyle))));
311 : }
312 :
313 0 : void _runAnimation(Offset pixelsPerSecond, Size size) {
314 0 : _animation = _controller.drive(
315 0 : AlignmentTween(
316 0 : begin: _dragAlignment,
317 0 : end: _dragAffinity,
318 : ),
319 : );
320 : // Calculate the velocity relative to the unit interval, [0,1],
321 : // used by the animation controller.
322 0 : final unitsPerSecondX = pixelsPerSecond.dx / size.width;
323 0 : final unitsPerSecondY = pixelsPerSecond.dy / size.height;
324 0 : final unitsPerSecond = Offset(unitsPerSecondX, unitsPerSecondY);
325 0 : final unitVelocity = unitsPerSecond.distance;
326 :
327 : const spring = SpringDescription(
328 : mass: 30,
329 : stiffness: 1,
330 : damping: 1,
331 : );
332 :
333 0 : final simulation = SpringSimulation(spring, 0, 1, -unitVelocity);
334 0 : _controller.animateWith(simulation);
335 : }
336 :
337 0 : void _btnGoto() {
338 0 : var id = Provider.of<ProfileInfoState>(context, listen: false).contactList.findContact(Provider.of<MessageMetadata>(context, listen: false).senderHandle)?.identifier;
339 : if (id == null) {
340 : // Can't happen
341 : } else {
342 0 : selectConversation(context, id, null);
343 0 : var contactIndex = Provider.of<ProfileInfoState>(context, listen: false).contactList.filteredList().indexWhere((element) => element.identifier == id);
344 0 : Provider.of<ProfileInfoState>(context, listen: false).contactListScrollController.jumpTo(index: contactIndex);
345 : }
346 : }
347 :
348 0 : void _btnAdd() {
349 0 : var sender = Provider.of<MessageMetadata>(context, listen: false).senderHandle;
350 0 : if (sender == "") {
351 0 : print("sender not yet loaded");
352 : return;
353 : }
354 0 : var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
355 :
356 0 : showAddContactConfirmAlertDialog(context, profileOnion, sender);
357 : }
358 :
359 0 : showAddContactConfirmAlertDialog(BuildContext context, String profileOnion, String senderOnion) {
360 : // set up the buttons
361 0 : Widget cancelButton = ElevatedButton(
362 0 : child: Text(AppLocalizations.of(context)!.cancel),
363 0 : style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))),
364 0 : onPressed: () {
365 0 : Navigator.of(context).pop(); // dismiss dialog
366 : },
367 : );
368 0 : Widget continueButton = ElevatedButton(
369 0 : style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))),
370 0 : child: Text(AppLocalizations.of(context)!.addContact),
371 0 : onPressed: () {
372 0 : Provider.of<FlwtchState>(context, listen: false).cwtch.ImportBundle(profileOnion, senderOnion);
373 0 : final snackBar = SnackBar(
374 0 : content: Text(AppLocalizations.of(context)!.successfullAddedContact),
375 0 : duration: Duration(seconds: 2),
376 : );
377 0 : ScaffoldMessenger.of(context).showSnackBar(snackBar);
378 0 : Navigator.of(context).pop(); // dismiss dialog
379 : },
380 : );
381 :
382 : // set up the AlertDialog
383 0 : AlertDialog alert = AlertDialog(
384 0 : title: Text(AppLocalizations.of(context)!.addContactConfirm.replaceFirst("%1", senderOnion)),
385 0 : actions: [
386 : cancelButton,
387 : continueButton,
388 : ],
389 : );
390 :
391 : // show the dialog
392 0 : showDialog(
393 : context: context,
394 0 : builder: (BuildContext context) {
395 : return alert;
396 : },
397 : );
398 : }
399 : }
400 :
401 0 : void modalShowReplies(
402 : BuildContext ctx, String replyHeader, String noRepliesText, Settings settings, ProfileInfoState profile, ContactInfoState cis, Color borderColor, MessageCache cache, int messageID,
403 : {bool showImage = true}) {
404 0 : showModalBottomSheet<void>(
405 : context: ctx,
406 0 : builder: (BuildContext bcontext) {
407 0 : List<Message> replies = getReplies(cache, messageID);
408 0 : ScrollController controller = ScrollController();
409 :
410 0 : return ChangeNotifierProvider.value(
411 : value: profile,
412 0 : builder: (bcontext, child) {
413 0 : return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) {
414 0 : var replyWidgets = replies.map((e) {
415 0 : var fromMe = e.getMetadata().senderHandle == profile.onion;
416 :
417 0 : var bubble = StaticMessageBubble(profile, settings, e.getMetadata(), Row(children: [Flexible(child: e.getPreviewWidget(context))]));
418 :
419 0 : String imagePath = e.getMetadata().senderImage!;
420 0 : var sender = profile.contactList.findContact(e.getMetadata().senderHandle);
421 : if (sender != null) {
422 0 : imagePath = showImage ? sender.imagePath : sender.defaultImagePath;
423 : } else {
424 0 : imagePath = RandomProfileImage(e.getMetadata().senderHandle);
425 : }
426 :
427 : if (fromMe) {
428 0 : imagePath = profile.imagePath;
429 : }
430 :
431 0 : var image = Padding(
432 0 : padding: EdgeInsets.all(4.0),
433 0 : child: ProfileImage(
434 : imagePath: imagePath,
435 : diameter: 48.0,
436 : border: borderColor,
437 : badgeTextColor: Colors.red,
438 : badgeColor: Colors.red,
439 : ));
440 :
441 0 : return Padding(
442 0 : padding: EdgeInsets.all(10.0),
443 0 : child: Row(
444 : mainAxisSize: MainAxisSize.min,
445 0 : children: [image, Flexible(child: bubble)],
446 : ));
447 0 : }).toList();
448 :
449 : var withHeader = replyWidgets;
450 :
451 : var original =
452 0 : StaticMessageBubble(profile, settings, cache.cache[messageID]!.metadata, Row(children: [Flexible(child: compileOverlay(cache.cache[messageID]!).getPreviewWidget(context))]));
453 :
454 0 : withHeader.insert(0, Padding(padding: EdgeInsets.fromLTRB(10.0, 10.0, 2.0, 15.0), child: Center(child: original)));
455 :
456 0 : withHeader.insert(
457 : 1,
458 0 : Padding(
459 0 : padding: EdgeInsets.fromLTRB(10.0, 10.0, 2.0, 15.0),
460 0 : child: Divider(
461 0 : color: settings.theme.mainTextColor,
462 : )));
463 :
464 0 : if (replies.isNotEmpty) {
465 0 : withHeader.insert(2, Padding(padding: EdgeInsets.fromLTRB(10.0, 10.0, 2.0, 15.0), child: Text(replyHeader, style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold))));
466 : } else {
467 0 : withHeader.insert(
468 0 : 2, Padding(padding: EdgeInsets.fromLTRB(10.0, 10.0, 2.0, 15.0), child: Center(child: Text(noRepliesText, style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)))));
469 : }
470 :
471 0 : return Scrollbar(
472 : trackVisibility: true,
473 : controller: controller,
474 0 : child: SingleChildScrollView(
475 : clipBehavior: Clip.antiAlias,
476 0 : child: ConstrainedBox(
477 0 : constraints: BoxConstraints(
478 0 : minHeight: viewportConstraints.maxHeight,
479 : ),
480 0 : child: Padding(
481 0 : padding: EdgeInsets.symmetric(vertical: 0.0, horizontal: 20.0),
482 0 : child: Column(
483 : crossAxisAlignment: CrossAxisAlignment.start,
484 : children: withHeader,
485 : )))));
486 : });
487 : });
488 : });
489 : }
490 :
491 0 : void modalShowTranslation(BuildContext context, ProfileInfoState profile, Settings settings) async {
492 0 : showModalBottomSheet<void>(
493 0 : builder: (
494 : BuildContext bcontext,
495 : ) {
496 0 : return StatefulBuilder(builder: (BuildContext scontext, StateSetter setState /*You can rename this!*/) {
497 0 : if (scontext.mounted) {
498 0 : new Timer.periodic(Duration(seconds: 1), (Timer t) {
499 0 : if (scontext.mounted) {
500 0 : setState(() {});
501 : }
502 : });
503 : }
504 :
505 0 : var bubble = StaticMessageBubble(
506 : profile,
507 : settings,
508 0 : MessageMetadata(profile.onion, Provider.of<ContactInfoState>(context, listen: false).identifier, 1, DateTime.now(), "blodeuwedd", null, null, null, true, false, false, ""),
509 0 : Row(children: [
510 0 : Provider.of<MessageMetadata>(context).translation == ""
511 0 : ? Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
512 0 : CircularProgressIndicator(color: settings.theme.defaultButtonActiveColor),
513 0 : Padding(padding: EdgeInsets.all(5.0), child: Text(AppLocalizations.of(context)!.blodeuweddProcessing))
514 : ])
515 0 : : Flexible(child: SelectableText(Provider.of<MessageMetadata>(context).translation))
516 : ]));
517 :
518 0 : var image = Padding(
519 0 : padding: EdgeInsets.all(4.0),
520 0 : child: ProfileImage(
521 : imagePath: "assets/blodeuwedd.png",
522 : diameter: 48.0,
523 0 : border: settings.theme.portraitOnlineBorderColor,
524 : badgeTextColor: Colors.red,
525 : badgeColor: Colors.red,
526 : ));
527 :
528 0 : return Container(
529 : height: 300, // bespoke value courtesy of the [TextField] docs
530 0 : child: Container(
531 : alignment: Alignment.center,
532 0 : child: Padding(
533 0 : padding: EdgeInsets.all(10.0),
534 0 : child: Padding(
535 0 : padding: EdgeInsets.all(10.0),
536 0 : child: Row(
537 : mainAxisSize: MainAxisSize.min,
538 0 : children: [image, Flexible(child: bubble)],
539 : )))));
540 : });
541 : },
542 : context: context);
543 : }
544 :
545 : // temporary until we do real picture selection
546 0 : String RandomProfileImage(String onion) {
547 0 : var choices = [
548 : "001-centaur",
549 : "002-kraken",
550 : "003-dinosaur",
551 : "004-tree-1",
552 : "005-hand",
553 : "006-echidna",
554 : "007-robot",
555 : "008-mushroom",
556 : "009-harpy",
557 : "010-phoenix",
558 : "011-dragon-1",
559 : "012-devil",
560 : "013-troll",
561 : "014-alien",
562 : "015-minotaur",
563 : "016-madre-monte",
564 : "017-satyr",
565 : "018-karakasakozou",
566 : "019-pirate",
567 : "020-werewolf",
568 : "021-scarecrow",
569 : "022-valkyrie",
570 : "023-curupira",
571 : "024-loch-ness-monster",
572 : "025-tree",
573 : "026-cerberus",
574 : "027-gryphon",
575 : "028-mermaid",
576 : "029-vampire",
577 : "030-goblin",
578 : "031-yeti",
579 : "032-leprechaun",
580 : "033-medusa",
581 : "034-chimera",
582 : "035-elf",
583 : "036-hydra",
584 : "037-cyclops",
585 : "038-pegasus",
586 : "039-narwhal",
587 : "040-woodcutter",
588 : "041-zombie",
589 : "042-dragon",
590 : "043-frankenstein",
591 : "044-witch",
592 : "045-fairy",
593 : "046-genie",
594 : "047-pinocchio",
595 : "048-ghost",
596 : "049-wizard",
597 : "050-unicorn"
598 : ];
599 0 : var encoding = base32.decode(onion.toUpperCase());
600 0 : return "assets/profiles/" + choices[encoding[33] % choices.length] + ".png";
601 : }
|