Line data Source code
1 : import 'package:cwtch/cwtch_icons_icons.dart';
2 : import 'package:cwtch/models/contact.dart';
3 : import 'package:cwtch/models/message.dart';
4 : import 'package:cwtch/models/profile.dart';
5 : import 'package:cwtch/themes/opaque.dart';
6 : import 'package:cwtch/widgets/malformedbubble.dart';
7 : import 'package:flutter/material.dart';
8 : import 'package:provider/provider.dart';
9 : import '../main.dart';
10 : import 'package:flutter_gen/gen_l10n/app_localizations.dart';
11 :
12 : import '../models/redaction.dart';
13 : import '../settings.dart';
14 : import 'messageBubbleWidgetHelpers.dart';
15 : import 'messagebubbledecorations.dart';
16 :
17 : // Like MessageBubble but for displaying chat overlay 100/101 invitations
18 : // Offers the user an accept/reject button if they don't have a matching contact already
19 : class InvitationBubble extends StatefulWidget {
20 : final int overlay;
21 : final String inviteTarget;
22 : final String inviteNick;
23 : final String invite;
24 :
25 0 : InvitationBubble(this.overlay, this.inviteTarget, this.inviteNick, this.invite);
26 :
27 0 : @override
28 0 : InvitationBubbleState createState() => InvitationBubbleState();
29 : }
30 :
31 : class InvitationBubbleState extends State<InvitationBubble> {
32 : bool rejected = false;
33 : bool isAccepted = false;
34 :
35 0 : @override
36 : Widget build(BuildContext context) {
37 0 : var fromMe = Provider.of<MessageMetadata>(context).senderHandle == Provider.of<ProfileInfoState>(context).onion;
38 0 : var isGroup = widget.overlay == InviteGroupOverlay;
39 0 : isAccepted = Provider.of<ProfileInfoState>(context).contactList.findContact(widget.inviteTarget) != null;
40 : var borderRadiousEh = 15.0;
41 0 : var showGroupInvite = Provider.of<Settings>(context).isExperimentEnabled(TapirGroupsExperiment);
42 0 : rejected = Provider.of<MessageMetadata>(context).attributes["rejected-invite"] == "true";
43 0 : DateTime messageDate = Provider.of<MessageMetadata>(context).timestamp;
44 :
45 : // If the sender is not us, then we want to give them a nickname...
46 : var senderDisplayStr = "";
47 : if (!fromMe) {
48 0 : ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.findContact(Provider.of<MessageMetadata>(context).senderHandle);
49 : if (contact != null) {
50 0 : senderDisplayStr = redactedNick(context, contact.onion, contact.nickname);
51 : } else {
52 0 : senderDisplayStr = Provider.of<MessageMetadata>(context).senderHandle;
53 : }
54 : }
55 0 : return LayoutBuilder(builder: (context, constraints) {
56 0 : var wdgSender = compileSenderWidget(context, constraints, fromMe, senderDisplayStr);
57 : // If we receive an invite for ourselves, treat it as a bug. The UI no longer allows this so it could have only come from
58 : // some kind of malfeasance.
59 0 : var selfInvite = widget.inviteNick == Provider.of<ProfileInfoState>(context).onion;
60 : if (selfInvite) {
61 0 : return MalformedBubble();
62 : }
63 :
64 : var wdgMessage = isGroup && !showGroupInvite
65 0 : ? Text(AppLocalizations.of(context)!.groupInviteSettingsWarning, style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle))
66 : : fromMe
67 0 : ? senderInviteChrome(
68 0 : AppLocalizations.of(context)!.sendAnInvitation, isGroup ? Provider.of<ProfileInfoState>(context).contactList.findContact(widget.inviteTarget)!.nickname : widget.inviteTarget)
69 0 : : (inviteChrome(isGroup ? AppLocalizations.of(context)!.inviteToGroup : AppLocalizations.of(context)!.contactSuggestion, widget.inviteNick, widget.inviteTarget));
70 :
71 : Widget wdgDecorations;
72 : if (isGroup && !showGroupInvite) {
73 0 : wdgDecorations = Text('\u202F');
74 : } else if (fromMe) {
75 0 : wdgDecorations = MessageBubbleDecoration(ackd: Provider.of<MessageMetadata>(context).ackd, errored: Provider.of<MessageMetadata>(context).error, fromMe: fromMe, messageDate: messageDate);
76 0 : } else if (isAccepted) {
77 0 : wdgDecorations = Text(AppLocalizations.of(context)!.accepted + '\u202F', style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle));
78 0 : } else if (this.rejected) {
79 0 : wdgDecorations = Text(AppLocalizations.of(context)!.rejected + '\u202F', style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle));
80 : } else {
81 0 : wdgDecorations = Center(
82 : widthFactor: 1,
83 0 : child: Wrap(children: [
84 0 : Padding(
85 0 : padding: EdgeInsets.all(5),
86 0 : child: ElevatedButton(
87 0 : child: Text(AppLocalizations.of(context)!.rejectGroupBtn + '\u202F', style: Provider.of<Settings>(context).scaleFonts(defaultTextButtonStyle)), onPressed: _btnReject)),
88 0 : Padding(
89 0 : padding: EdgeInsets.all(5),
90 0 : child: ElevatedButton(
91 0 : child: Text(AppLocalizations.of(context)!.acceptGroupBtn + '\u202F', style: Provider.of<Settings>(context).scaleFonts(defaultTextButtonStyle)), onPressed: _btnAccept)),
92 : ]));
93 : }
94 :
95 : //print(constraints.toString()+", "+constraints.maxWidth.toString());
96 0 : return Center(
97 : widthFactor: 1.0,
98 0 : child: Theme(
99 0 : data: Theme.of(context).copyWith(
100 0 : textSelectionTheme: TextSelectionThemeData(
101 0 : cursorColor: Provider.of<Settings>(context).theme.messageSelectionColor,
102 0 : selectionColor: Provider.of<Settings>(context).theme.messageSelectionColor,
103 0 : selectionHandleColor: Provider.of<Settings>(context).theme.messageSelectionColor),
104 :
105 : // Horrifying Hack: Flutter doesn't give us direct control over system menus but instead picks BG color from TextButtonThemeData ¯\_(ツ)_/¯
106 0 : textButtonTheme: TextButtonThemeData(
107 0 : style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.menuBackgroundColor)),
108 : ),
109 : ),
110 0 : child: Container(
111 0 : decoration: BoxDecoration(
112 0 : color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor,
113 : border:
114 0 : Border.all(color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor, width: 1),
115 0 : borderRadius: BorderRadius.only(
116 0 : topLeft: Radius.circular(borderRadiousEh),
117 0 : topRight: Radius.circular(borderRadiousEh),
118 0 : bottomLeft: fromMe ? Radius.circular(borderRadiousEh) : Radius.zero,
119 0 : bottomRight: fromMe ? Radius.zero : Radius.circular(borderRadiousEh),
120 : ),
121 : ),
122 0 : child: Center(
123 : widthFactor: 1.0,
124 0 : child: Padding(
125 0 : padding: EdgeInsets.all(9.0),
126 0 : child: Wrap(runAlignment: WrapAlignment.spaceEvenly, alignment: WrapAlignment.spaceEvenly, runSpacing: 1.0, crossAxisAlignment: WrapCrossAlignment.center, children: [
127 0 : Center(
128 : widthFactor: 1,
129 0 : child: Padding(padding: EdgeInsets.all(10.0), child: Icon(isGroup && !showGroupInvite ? CwtchIcons.enable_experiments : CwtchIcons.send_invite, size: 32))),
130 0 : Center(
131 : widthFactor: 1.0,
132 0 : child: Column(
133 : crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
134 : mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start,
135 : mainAxisSize: MainAxisSize.min,
136 0 : children: fromMe ? [wdgMessage, wdgDecorations] : [wdgSender, wdgMessage, wdgDecorations]),
137 : )
138 : ]))))));
139 : });
140 : }
141 :
142 0 : void _btnReject() {
143 0 : setState(() {
144 0 : var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
145 0 : var conversation = Provider.of<ContactInfoState>(context, listen: false).identifier;
146 0 : var idx = Provider.of<MessageMetadata>(context, listen: false).messageID;
147 0 : Provider.of<FlwtchState>(context, listen: false).cwtch.SetMessageAttribute(profileOnion, conversation, 0, idx, "rejected-invite", "true");
148 : //Provider.of<MessageMetadata>(context, listen: false).flags |= 0x01;
149 : });
150 : }
151 :
152 0 : void _btnAccept() {
153 0 : setState(() {
154 0 : var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
155 0 : Provider.of<FlwtchState>(context, listen: false).cwtch.ImportBundle(profileOnion, widget.invite);
156 0 : isAccepted = true;
157 : });
158 : }
159 :
160 : // Construct an invite chrome for the sender
161 0 : Widget senderInviteChrome(String chrome, String targetName) {
162 0 : var settings = Provider.of<Settings>(context);
163 :
164 0 : return Wrap(children: [
165 0 : SelectableText(
166 0 : chrome + '\u202F',
167 0 : style: settings.scaleFonts(defaultMessageTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromMeTextColor)),
168 : textAlign: TextAlign.left,
169 : maxLines: 2,
170 : textWidthBasis: TextWidthBasis.longestLine,
171 : ),
172 0 : SelectableText(
173 0 : targetName + '\u202F',
174 0 : style: settings.scaleFonts(defaultMessageTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromMeTextColor)),
175 : textAlign: TextAlign.left,
176 : maxLines: 2,
177 : textWidthBasis: TextWidthBasis.longestLine,
178 : )
179 : ]);
180 : }
181 :
182 : // Construct an invite chrome
183 0 : Widget inviteChrome(String chrome, String targetName, String targetId) {
184 0 : var settings = Provider.of<Settings>(context);
185 :
186 0 : return Wrap(children: [
187 0 : SelectableText(
188 0 : chrome + '\u202F',
189 0 : style: settings.scaleFonts(defaultMessageTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromOtherTextColor)),
190 : textAlign: TextAlign.left,
191 : textWidthBasis: TextWidthBasis.longestLine,
192 : maxLines: 2,
193 : ),
194 0 : SelectableText(
195 0 : targetName + '\u202F',
196 0 : style: settings.scaleFonts(defaultMessageTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromOtherTextColor)),
197 : textAlign: TextAlign.left,
198 : maxLines: 2,
199 : textWidthBasis: TextWidthBasis.longestLine,
200 : )
201 : ]);
202 : }
203 : }
|