Line data Source code
1 : import 'dart:io';
2 :
3 : import 'package:cwtch/config.dart';
4 : import 'package:cwtch/cwtch_icons_icons.dart';
5 : import 'package:cwtch/controllers/filesharing.dart' as filesharing;
6 : import 'package:cwtch/settings.dart';
7 : import 'package:cwtch/models/appstate.dart';
8 : import 'package:cwtch/models/contact.dart';
9 : import 'package:cwtch/models/contactlist.dart';
10 : import 'package:cwtch/models/groupmembers.dart';
11 : import 'package:cwtch/models/profile.dart';
12 : import 'package:cwtch/widgets/buttontextfield.dart';
13 : import 'package:cwtch/widgets/cwtchlabel.dart';
14 : import 'package:cwtch/widgets/DropdownContacts.dart';
15 : import 'package:cwtch/widgets/profileimage.dart';
16 : import 'package:flutter/services.dart';
17 : import 'package:flutter/material.dart';
18 : import 'package:provider/provider.dart';
19 : import 'package:cwtch/l10n/app_localizations.dart';
20 :
21 : import '../constants.dart';
22 : import '../main.dart';
23 : import '../themes/opaque.dart';
24 :
25 : class EditGroupView extends StatefulWidget {
26 0 : @override
27 0 : _EditGroupViewState createState() => _EditGroupViewState();
28 : }
29 :
30 : class _EditGroupViewState extends State<EditGroupView> {
31 : bool maskControlsOpen = false;
32 :
33 0 : @override
34 : void dispose() {
35 0 : super.dispose();
36 : }
37 :
38 : final ctrlrNick = TextEditingController(text: "");
39 : ScrollController metaScrollController = ScrollController();
40 : ScrollController modeScrollController = ScrollController();
41 : ScrollController maskScrollController = ScrollController();
42 : final ctrlrRemoteName = TextEditingController(text: "");
43 : final ctrlrAttr1 = TextEditingController(text: "");
44 : final ctrlrAttr2 = TextEditingController(text: "");
45 : final ctrlrAttr3 = TextEditingController(text: "");
46 :
47 0 : @override
48 : void initState() {
49 0 : super.initState();
50 0 : final nickname = Provider.of<ContactInfoState>(context, listen: false).nickname;
51 0 : if (nickname.isNotEmpty) {
52 0 : ctrlrNick.text = nickname;
53 : }
54 0 : if (Provider.of<ContactInfoState>(context, listen: false).remoteNickname != null) {
55 0 : ctrlrRemoteName.text = Provider.of<ContactInfoState>(context, listen: false).remoteNickname!;
56 : }
57 0 : if (Provider.of<ContactInfoState>(context, listen: false).attributes[0] != null) {
58 0 : ctrlrAttr1.text = Provider.of<ContactInfoState>(context, listen: false).attributes[0]!;
59 : }
60 0 : if (Provider.of<ContactInfoState>(context, listen: false).attributes[1] != null) {
61 0 : ctrlrAttr2.text = Provider.of<ContactInfoState>(context, listen: false).attributes[1]!;
62 : }
63 0 : if (Provider.of<ContactInfoState>(context, listen: false).attributes[2] != null) {
64 0 : ctrlrAttr3.text = Provider.of<ContactInfoState>(context, listen: false).attributes[2]!;
65 : }
66 : }
67 :
68 0 : @override
69 : Widget build(BuildContext context) {
70 0 : var handle = Provider.of<ContactInfoState>(context).nickname;
71 0 : if (handle.isEmpty) {
72 0 : handle = Provider.of<ContactInfoState>(context).onion;
73 : }
74 0 : return DefaultTabController(
75 : length: 3,
76 0 : child: Scaffold(
77 0 : appBar: AppBar(
78 0 : title: Container(
79 0 : height: Provider.of<Settings>(context).fontScaling * 24.0,
80 : clipBehavior: Clip.hardEdge,
81 0 : decoration: BoxDecoration(),
82 0 : child: Text(handle + " ยป " + AppLocalizations.of(context)!.editGroupTitle),
83 : ),
84 0 : bottom: TabBar(
85 : isScrollable: true,
86 0 : tabs: [
87 0 : Tab(key: Key("OpenGroupMetadata"), icon: Icon(CwtchIcons.edit_24px), text: AppLocalizations.of(context)!.editGroupMetadataTitle),
88 0 : Tab(key: Key("OpenGroupMode"), icon: Icon(CwtchIcons.anti_spam_1), text: AppLocalizations.of(context)!.editGroupModeTitle),
89 0 : Tab(key: Key("OpenGroupMask"), icon: Icon(CwtchIcons.member_mod), text: AppLocalizations.of(context)!.editGroupMaskTitle),
90 : ],
91 : ),
92 : ),
93 0 : body: _buildGroupEditor(),
94 : ),
95 : );
96 : }
97 :
98 0 : Widget _buildGroupEditor() {
99 0 : return Consumer<Settings>(
100 0 : builder: (context, settings, child) {
101 0 : return LayoutBuilder(
102 0 : builder: (BuildContext context, BoxConstraints viewportConstraints) {
103 0 : return TabBarView(
104 0 : children: [
105 0 : _tabHelper(_buildMetaTab(settings), "EditGroupMeta", this.metaScrollController),
106 0 : _tabHelper(_buildModeTab(settings), "EditGroupMode", this.modeScrollController),
107 0 : _tabHelper(_buildMaskTab(settings), "EditGroupMask", this.maskScrollController),
108 : ],
109 : );
110 : },
111 : );
112 : },
113 : );
114 : }
115 :
116 0 : Widget _tabHelper(Widget tab, String scrollKey, ScrollController sc) {
117 0 : return Consumer<Settings>(
118 0 : builder: (ccontext, settings, child) {
119 0 : return LayoutBuilder(
120 0 : builder: (BuildContext context, BoxConstraints viewportConstraints) {
121 0 : return Scrollbar(
122 0 : key: Key(scrollKey),
123 : trackVisibility: true,
124 : controller: sc,
125 0 : child: SingleChildScrollView(
126 : clipBehavior: Clip.antiAlias,
127 : controller: sc,
128 0 : child: ConstrainedBox(
129 0 : constraints: BoxConstraints(minHeight: viewportConstraints.maxHeight, maxWidth: viewportConstraints.maxWidth),
130 0 : child: Material(
131 0 : color: settings.theme.backgroundPaneColor,
132 0 : child: Container(width: viewportConstraints.maxWidth / 2, margin: EdgeInsetsGeometry.all(16.0), child: tab),
133 : ),
134 : ),
135 : ),
136 : );
137 : },
138 : );
139 : },
140 : );
141 : }
142 :
143 0 : Widget _buildMetaTab(Settings settings) {
144 0 : bool pending = Provider.of<ContactInfoState>(context).checkOpPending(context);
145 0 : bool isOpen = Provider.of<ContactInfoState>(context).modeLine.contains('o');
146 :
147 0 : return Column(
148 : mainAxisAlignment: MainAxisAlignment.start,
149 : crossAxisAlignment: CrossAxisAlignment.center,
150 0 : children: [
151 0 : Column(
152 : crossAxisAlignment: CrossAxisAlignment.center,
153 0 : children: [
154 0 : _buildGroupPictureBox(),
155 0 : SizedBox(width: MediaQuery.of(context).size.width / 2, child: !pending && CanUpdateMetadata() ? _buildMetadataEditor() : _buildMetadataViewer()),
156 : ],
157 : ),
158 :
159 0 : Column(
160 : mainAxisAlignment: MainAxisAlignment.start,
161 : crossAxisAlignment: CrossAxisAlignment.start,
162 0 : children: [
163 0 : CwtchLabel(label: AppLocalizations.of(context)!.displayNameLocalOnly),
164 0 : SizedBox(height: 20),
165 0 : CwtchButtonTextField(
166 0 : controller: ctrlrNick,
167 : readonly: false,
168 0 : onPressed: () {
169 0 : var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
170 0 : var conversation = Provider.of<ContactInfoState>(context, listen: false).identifier;
171 0 : Provider.of<ContactInfoState>(context, listen: false).localNickname = ctrlrNick.text;
172 0 : Provider.of<FlwtchState>(context, listen: false).cwtch.SetConversationAttribute(profileOnion, conversation, "profile.name", ctrlrNick.text);
173 0 : final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.nickChangeSuccess));
174 0 : ScaffoldMessenger.of(context).showSnackBar(snackBar);
175 : },
176 0 : icon: Icon(Icons.save),
177 0 : tooltip: AppLocalizations.of(context)!.saveBtn,
178 : ),
179 : ],
180 : ),
181 :
182 0 : SizedBox(height: 20),
183 0 : CwtchLabel(label: AppLocalizations.of(context)!.addressLabel),
184 0 : SizedBox(height: 20),
185 :
186 : // Address Copy Button
187 0 : Visibility(
188 0 : visible: settings.streamerMode == false && isOpen,
189 0 : child: Column(
190 : mainAxisAlignment: MainAxisAlignment.start,
191 : crossAxisAlignment: CrossAxisAlignment.start,
192 0 : children: [
193 0 : Text(AppLocalizations.of(context)!.anyoneCanJoin),
194 0 : CwtchButtonTextField(
195 0 : controller: TextEditingController(text: "grwp-" + Provider.of<ContactInfoState>(context, listen: false).onion),
196 0 : onPressed: isOpen ? _copyOnion : null,
197 0 : icon: Icon(CwtchIcons.copy_address),
198 0 : tooltip: AppLocalizations.of(context)!.copyBtn,
199 : ),
200 : ],
201 : ),
202 : ),
203 0 : Visibility(visible: !isOpen, child: Text(AppLocalizations.of(context)!.inviteRequired)),
204 : ],
205 : );
206 : }
207 :
208 0 : Widget _buildModeTab(Settings settings) {
209 0 : return Column(
210 : mainAxisAlignment: MainAxisAlignment.start,
211 : crossAxisAlignment: CrossAxisAlignment.start,
212 0 : children: [_buildModeBox(Provider.of<ContactInfoState>(context).modeLine, Provider.of<ContactInfoState>(context).modeMask)],
213 : );
214 : }
215 :
216 0 : Widget _buildMaskTab(Settings settings) {
217 0 : return Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [_buildModeMaskBox(Provider.of<ContactInfoState>(context).modeMask)]);
218 : }
219 :
220 0 : void _copyOnion() {
221 0 : Clipboard.setData(new ClipboardData(text: "grwp-" + Provider.of<ContactInfoState>(context, listen: false).onion));
222 0 : final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.copiedToClipboardNotification));
223 0 : ScaffoldMessenger.of(context).showSnackBar(snackBar);
224 : }
225 :
226 0 : Widget _buildGroupPictureBox() {
227 0 : return Consumer<Settings>(
228 0 : builder: (context, theme, child) {
229 : var isStatic =
230 0 : Provider.of<AppState>(context, listen: false).disableFilePicker ||
231 0 : !Provider.of<Settings>(context, listen: false).isExperimentEnabled(FileSharingExperiment) ||
232 0 : !Provider.of<Settings>(context, listen: false).isExperimentEnabled(ImagePreviewsExperiment) ||
233 0 : !Provider.of<ContactInfoState>(context, listen: false).modeLine.contains('f') ||
234 0 : !CanUpdateMetadata() ||
235 0 : Provider.of<ContactInfoState>(context, listen: false).checkOpPending(context);
236 :
237 0 : return MouseRegion(
238 : cursor: !isStatic ? SystemMouseCursors.click : SystemMouseCursors.basic,
239 0 : child: GestureDetector(
240 : // don't allow setting of images if the image previews experiment is disabled.
241 : onTap: isStatic
242 : ? null
243 0 : : () {
244 0 : filesharing.showFilePicker(
245 : context,
246 : MaxImageFileSharingSize,
247 0 : (File file) {
248 0 : Provider.of<FlwtchState>(
249 : context,
250 : listen: false,
251 0 : ).cwtch.UpdateGroupPicture(Provider.of<ProfileInfoState>(context, listen: false).onion, Provider.of<ContactInfoState>(context, listen: false).identifier, file.path);
252 : // update the image cache locally
253 : // TODO: should put a spinner here instead, so the user knows something is happening while it syncs
254 : // Provider.of<ProfileInfoState>(context, listen: false).imagePath = file.path;
255 : },
256 0 : () {
257 0 : final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.msgFileTooBig), duration: Duration(seconds: 4));
258 0 : ScaffoldMessenger.of(context).showSnackBar(snackBar);
259 : },
260 0 : () {},
261 : );
262 : },
263 0 : child: ProfileImage(
264 0 : imagePath: Provider.of<Settings>(context).isExperimentEnabled(ImagePreviewsExperiment)
265 0 : ? Provider.of<ContactInfoState>(context).imagePath
266 0 : : Provider.of<ContactInfoState>(context).defaultImagePath,
267 : diameter: 120,
268 0 : tooltip: isStatic ? "" : AppLocalizations.of(context)!.changeGroupPicture,
269 : maskOut: false,
270 0 : border: theme.theme.portraitOnlineBorderColor,
271 0 : badgeTextColor: theme.theme.portraitContactBadgeTextColor,
272 0 : badgeColor: theme.theme.portraitContactBadgeColor,
273 : badgeEdit: !isStatic,
274 : managed: true,
275 : ),
276 : ),
277 : );
278 : },
279 : );
280 : }
281 :
282 0 : Widget _buildMetadataViewer() {
283 0 : return Column(
284 0 : children: [
285 0 : Padding(
286 0 : padding: EdgeInsets.all(1),
287 0 : child: CwtchLabel(label: Provider.of<ContactInfoState>(context).remoteNickname ?? ""),
288 : ),
289 0 : Padding(
290 0 : padding: EdgeInsets.all(1),
291 0 : child: SelectableText(Provider.of<ContactInfoState>(context).attributes[0] ?? "", textAlign: TextAlign.center),
292 : ),
293 0 : Padding(
294 0 : padding: EdgeInsets.all(1),
295 0 : child: SelectableText(Provider.of<ContactInfoState>(context).attributes[1] ?? "", textAlign: TextAlign.center),
296 : ),
297 0 : Padding(
298 0 : padding: EdgeInsets.all(1),
299 0 : child: SelectableText(Provider.of<ContactInfoState>(context).attributes[2] ?? "", textAlign: TextAlign.center),
300 : ),
301 : ],
302 : );
303 : }
304 :
305 0 : Widget _buildMetadataEditor() {
306 0 : return Column(
307 0 : children: [
308 0 : CwtchLabel(label: AppLocalizations.of(context)!.groupInfoName),
309 0 : Padding(
310 0 : padding: EdgeInsets.all(5.0),
311 0 : child: CwtchButtonTextField(controller: ctrlrRemoteName, readonly: false, onPressed: _saveMetadataPressed, icon: Icon(Icons.save), tooltip: AppLocalizations.of(context)!.saveBtn),
312 : ),
313 0 : CwtchLabel(label: AppLocalizations.of(context)!.groupInfoTopic),
314 0 : Padding(
315 0 : padding: EdgeInsets.all(5.0),
316 0 : child: CwtchButtonTextField(controller: ctrlrAttr1, readonly: false, onPressed: _saveMetadataPressed, icon: Icon(Icons.save), tooltip: AppLocalizations.of(context)!.saveBtn),
317 : ),
318 0 : CwtchLabel(label: AppLocalizations.of(context)!.groupInfoExtra1),
319 0 : Padding(
320 0 : padding: EdgeInsets.all(5.0),
321 0 : child: CwtchButtonTextField(controller: ctrlrAttr2, readonly: false, onPressed: _saveMetadataPressed, icon: Icon(Icons.save), tooltip: AppLocalizations.of(context)!.saveBtn),
322 : ),
323 0 : CwtchLabel(label: AppLocalizations.of(context)!.groupInfoExtra2),
324 0 : Padding(
325 0 : padding: EdgeInsets.all(5.0),
326 0 : child: CwtchButtonTextField(controller: ctrlrAttr3, readonly: false, onPressed: _saveMetadataPressed, icon: Icon(Icons.save), tooltip: AppLocalizations.of(context)!.saveBtn),
327 : ),
328 : ],
329 : );
330 : }
331 :
332 0 : void _saveMetadataPressed() {
333 0 : Provider.of<ContactInfoState>(context, listen: false).isOpPending = true;
334 0 : Provider.of<FlwtchState>(context, listen: false).cwtch.UpdateGroupMetadata(
335 0 : Provider.of<ContactInfoState>(context, listen: false).profileOnion,
336 0 : Provider.of<ContactInfoState>(context, listen: false).identifier,
337 0 : ctrlrRemoteName.text,
338 0 : ctrlrAttr1.text,
339 0 : ctrlrAttr2.text,
340 0 : ctrlrAttr3.text,
341 : );
342 : //final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.nickChangeSuccess));
343 : //ScaffoldMessenger.of(context).showSnackBar(snackBar);
344 : }
345 :
346 0 : Widget _buildModeBox(String mode, String mask) {
347 0 : return Column(
348 0 : children: [
349 0 : CwtchLabel(label: AppLocalizations.of(context)!.editGroupModeTitle + ": +" + mode),
350 0 : _buildModeBoxEntry(AppLocalizations.of(context)!.groupModeF, mode, "f", mask, false),
351 0 : _buildModeBoxEntry(AppLocalizations.of(context)!.groupModeS, mode, "s", mask, false),
352 0 : _buildModeBoxEntry(AppLocalizations.of(context)!.groupModeI, mode, "i", mask, false),
353 0 : _buildModeBoxEntry(AppLocalizations.of(context)!.groupModeO, mode, "o", mask, false),
354 0 : _buildModeBoxEntry(AppLocalizations.of(context)!.groupModeV, mode, "v", mask, false),
355 0 : _buildModeBoxEntry(AppLocalizations.of(context)!.groupModeM, mode, "m", mask, false),
356 0 : _buildModeBoxEntry(AppLocalizations.of(context)!.groupModeW, mode, "w", mask, false),
357 0 : _buildModeBoxEntry(AppLocalizations.of(context)!.groupModeE, mode, "e", mask, false),
358 0 : Text(AppLocalizations.of(context)!.footnoteMask),
359 : ],
360 : );
361 : }
362 :
363 0 : Widget _buildModeMaskBox(String mask) {
364 0 : return Column(
365 0 : children: [
366 0 : CwtchLabel(label: AppLocalizations.of(context)!.editGroupMaskTitle + ": +" + mask),
367 0 : Text(AppLocalizations.of(context)!.explainerMasks),
368 0 : _buildModeBoxEntry(AppLocalizations.of(context)!.groupModeF, "", "f", mask, true),
369 0 : _buildModeBoxEntry(AppLocalizations.of(context)!.groupModeS, "", "s", mask, true),
370 0 : _buildModeBoxEntry(AppLocalizations.of(context)!.groupModeI, "", "i", mask, true),
371 0 : _buildModeBoxEntry(AppLocalizations.of(context)!.groupModeO, "", "o", mask, true),
372 0 : _buildModeBoxEntry(AppLocalizations.of(context)!.groupModeV, "", "v", mask, true),
373 0 : _buildModeBoxEntry(AppLocalizations.of(context)!.groupModeM, "", "m", mask, true),
374 0 : _buildModeBoxEntry(AppLocalizations.of(context)!.groupModeW, "", "w", mask, true),
375 0 : _buildModeBoxEntry(AppLocalizations.of(context)!.groupModeE, "", "e", mask, true),
376 : ],
377 : );
378 : }
379 :
380 0 : Widget _buildModeBoxEntry(String label, String modeLine, String modeBit, String modeMask, bool isMaskBox) {
381 : String checkStr = isMaskBox ? modeMask : modeLine;
382 0 : bool pending = Provider.of<ContactInfoState>(context).checkOpPending(context);
383 :
384 0 : return SwitchListTile(
385 0 : title: Text(label + (!isMaskBox && modeMask.contains(modeBit) ? "*" : ""), style: TextStyle(color: Provider.of<Settings>(context).current().mainTextColor)),
386 0 : subtitle: Text(_descriptionOf(modeBit)),
387 0 : value: checkStr.contains(modeBit),
388 0 : onChanged: !CanToggleMode(modeBit, isMaskBox) || pending
389 : ? null
390 0 : : (bool newVal) {
391 0 : _toggleModePressed(modeBit, newVal, isMaskBox);
392 : },
393 0 : activeTrackColor: Provider.of<Settings>(context).theme.defaultButtonColor,
394 0 : inactiveTrackColor: Provider.of<Settings>(context).theme.defaultButtonDisabledColor,
395 0 : secondary: _iconFor(modeBit),
396 : );
397 : }
398 :
399 0 : void _toggleModePressed(String modeBit, bool enable, bool isMaskBox) {
400 0 : Provider.of<ContactInfoState>(context, listen: false).isOpPending = true;
401 : if (isMaskBox) {
402 0 : Provider.of<FlwtchState>(
403 0 : context,
404 : listen: false,
405 0 : ).cwtch.ChangeGroupMask(Provider.of<ProfileInfoState>(context, listen: false).onion, Provider.of<ContactInfoState>(context, listen: false).identifier, (enable ? "+" : "-") + modeBit);
406 : } else {
407 0 : Provider.of<FlwtchState>(
408 0 : context,
409 : listen: false,
410 0 : ).cwtch.ChangeGroupMode(Provider.of<ProfileInfoState>(context, listen: false).onion, Provider.of<ContactInfoState>(context, listen: false).identifier, (enable ? "+" : "-") + modeBit);
411 : }
412 : }
413 :
414 0 : String? MyRole() {
415 0 : return Provider.of<ContactInfoState>(context).members.firstWhere((x) => x.onion == Provider.of<ProfileInfoState>(context, listen: false).onion).role;
416 : }
417 :
418 0 : bool CanUpdateMetadata() {
419 0 : return Provider.of<ContactInfoState>(context).isOnline() && ['admin', 'op'].contains(MyRole());
420 : }
421 :
422 0 : bool CanToggleMode(String modeBit, bool isMaskBox) {
423 : if (isMaskBox) {
424 0 : return Provider.of<ContactInfoState>(context).isOnline() && ['admin', 'op'].contains(MyRole());
425 : } else {
426 0 : return Provider.of<ContactInfoState>(context).isOnline() && (['admin', 'op'].contains(MyRole()) || (MyRole() == 'mod' && Provider.of<ContactInfoState>(context).modeMask.contains(modeBit)));
427 : }
428 : }
429 :
430 0 : Icon _iconFor(String modeBit) {
431 : switch (modeBit) {
432 0 : case 'f':
433 0 : return Icon(CwtchIcons.attach_file_24px, color: Provider.of<Settings>(context).current().mainTextColor);
434 0 : case 's':
435 0 : return Icon(Icons.hourglass_empty, color: Provider.of<Settings>(context).current().mainTextColor);
436 0 : case 'i':
437 0 : return Icon(CwtchIcons.send_invitation, color: Provider.of<Settings>(context).current().mainTextColor);
438 0 : case 'o':
439 0 : return Icon(CwtchIcons.person_add_alt_1_24px, color: Provider.of<Settings>(context).current().mainTextColor);
440 0 : case 'v':
441 0 : return Icon(CwtchIcons.member_voice, color: Provider.of<Settings>(context).current().mainTextColor);
442 0 : case 'm':
443 0 : return Icon(CwtchIcons.eye_closed, color: Provider.of<Settings>(context).current().mainTextColor);
444 0 : case 'w':
445 0 : return Icon(Icons.how_to_vote, color: Provider.of<Settings>(context).current().mainTextColor);
446 0 : case 'e':
447 0 : return Icon(CwtchIcons.swap_horiz, color: Provider.of<Settings>(context).current().mainTextColor);
448 : default:
449 0 : return Icon(CwtchIcons.block_peer, color: Provider.of<Settings>(context).current().mainTextColor);
450 : }
451 : }
452 :
453 0 : String _descriptionOf(String modeBit) {
454 : switch (modeBit) {
455 0 : case 'f':
456 0 : return AppLocalizations.of(context)!.groupModeFDescription;
457 0 : case 's':
458 0 : return AppLocalizations.of(context)!.groupModeSDescription;
459 0 : case 'i':
460 0 : return AppLocalizations.of(context)!.groupModeIDescription;
461 0 : case 'o':
462 0 : return AppLocalizations.of(context)!.groupModeODescription;
463 0 : case 'v':
464 0 : return AppLocalizations.of(context)!.groupModeVDescription;
465 0 : case 'm':
466 0 : return AppLocalizations.of(context)!.groupModeMDescription;
467 0 : case 'w':
468 0 : return AppLocalizations.of(context)!.groupModeWDescription;
469 0 : case 'e':
470 0 : return AppLocalizations.of(context)!.groupModeEDescription;
471 : default:
472 : return '';
473 : }
474 : }
475 : }
|