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