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