cwtch-ui/lib/views/contactsview.dart

659 lines
33 KiB
Dart

import 'dart:io';
import 'package:cwtch/cwtch/cwtch.dart';
import 'package:cwtch/cwtch_icons_icons.dart';
import 'package:cwtch/models/appstate.dart';
import 'package:cwtch/models/contact.dart';
import 'package:cwtch/models/contactlist.dart';
import 'package:cwtch/models/profile.dart';
import 'package:cwtch/models/profilelist.dart';
import 'package:cwtch/models/search.dart';
import 'package:cwtch/views/globalsettingsview.dart';
import 'package:cwtch/views/profileserversview.dart';
import 'package:flutter/material.dart';
import 'package:cwtch/widgets/contactrow.dart';
import 'package:cwtch/widgets/profileimage.dart';
import 'package:cwtch/widgets/textfield.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import '../config.dart';
import '../main.dart';
import '../models/redaction.dart';
import '../settings.dart';
import '../themes/opaque.dart';
import 'addcontactview.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'addeditprofileview.dart';
import 'messageview.dart';
enum ShareMenu { copyCode, qrcode }
enum ProfileStatusMenu { available, away, busy, appearOnline, appearOffline, allowUnknownContacts, blockUnknownContacts, enableProfile, disableProfile, editProfile }
class ContactsView extends StatefulWidget {
const ContactsView({Key? key}) : super(key: key);
@override
_ContactsViewState createState() => _ContactsViewState();
}
// selectConversation can be called from anywhere to set the active conversation
void selectConversation(BuildContext context, int handle, int? messageIndex) {
int? index;
if (messageIndex != null) {
// this message is loaded
Provider.of<AppState>(context, listen: false).selectedSearchMessage = messageIndex;
Provider.of<AppState>(context, listen: false).initialScrollIndex = messageIndex;
EnvironmentConfig.debugLog("Looked up index $messageIndex");
}
if (handle == Provider.of<AppState>(context, listen: false).selectedConversation) {
if (messageIndex != null) {
Provider.of<ContactInfoState>(context, listen: false).messageScrollController.scrollTo(index: messageIndex, duration: Duration(milliseconds: 100));
}
return;
}
// requery instead of using contactinfostate directly because sometimes listview gets confused about data that resorts
var unread = Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(handle)!.unreadMessages;
var previouslySelected = Provider.of<AppState>(context, listen: false).selectedConversation;
if (previouslySelected != null) {
Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(previouslySelected)!.unselected();
}
Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(handle)!.selected();
// triggers update in Double/TripleColumnView
Provider.of<ContactInfoState>(context, listen: false).hoveredIndex = -1;
Provider.of<AppState>(context, listen: false).selectedConversation = handle;
// if in singlepane mode, push to the stack
var isLandscape = Provider.of<AppState>(context, listen: false).isLandscape(context);
if (Provider.of<Settings>(context, listen: false).uiColumns(isLandscape).length == 1) _pushMessageView(context, handle);
// Set last message seen time in backend
Provider.of<FlwtchState>(context, listen: false)
.cwtch
.SetConversationAttribute(Provider.of<ProfileInfoState>(context, listen: false).onion, handle, LastMessageSeenTimeKey, DateTime.now().toUtc().toIso8601String());
}
void _pushMessageView(BuildContext context, int handle) {
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
Navigator.of(context).push(
PageRouteBuilder(
settings: RouteSettings(name: "messages"),
pageBuilder: (builderContext, a1, a2) {
var profile = Provider.of<FlwtchState>(builderContext).profs.getProfile(profileOnion)!;
return MultiProvider(
providers: [
ChangeNotifierProvider.value(value: profile),
ChangeNotifierProvider.value(value: profile.contactList.getContact(handle)!),
],
builder: (context, child) => MessageView(),
);
},
transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child),
transitionDuration: Duration(milliseconds: 200),
),
);
}
class _ContactsViewState extends State<ContactsView> {
late TextEditingController ctrlrFilter;
bool showSearchBar = false;
final scaffoldKey = GlobalKey<ScaffoldMessengerState>();
@override
void initState() {
super.initState();
ctrlrFilter = new TextEditingController(text: Provider.of<ContactListState>(context, listen: false).filter);
}
@override
Widget build(BuildContext context) {
var enabled = Provider.of<ProfileInfoState>(context, listen: false).enabled;
var appearOffline = Provider.of<ProfileInfoState>(context, listen: false).appearOffline;
var settings = Provider.of<Settings>(context, listen: false);
return ScaffoldMessenger(
key: scaffoldKey,
child: Scaffold(
backgroundColor: Provider.of<Settings>(context).theme.backgroundMainColor,
endDrawerEnableOpenDragGesture: false,
drawerEnableOpenDragGesture: false,
appBar: AppBar(
leading: Stack(children: [
Align(
alignment: Alignment.center,
child: IconButton(
icon: Icon(Icons.arrow_back),
tooltip: MaterialLocalizations.of(context).backButtonTooltip,
onPressed: () {
Provider.of<ProfileInfoState>(context, listen: false).recountUnread();
Provider.of<AppState>(context, listen: false).selectedProfile = "";
Navigator.of(context).pop();
},
)),
Positioned(
bottom: 5.0,
right: 5.0,
child: StreamBuilder<bool>(
stream: Provider.of<AppState>(context).getUnreadProfileNotifyStream(),
builder: (BuildContext context, AsyncSnapshot<bool> unreadCountSnapshot) {
int unreadCount = Provider.of<ProfileListState>(context).generateUnreadCount(Provider.of<AppState>(context).selectedProfile ?? "");
return Visibility(
visible: unreadCount > 0,
child: CircleAvatar(
radius: 10.0,
backgroundColor: Provider.of<Settings>(context).theme.portraitProfileBadgeColor,
child:
Text(unreadCount > 99 ? "99+" : unreadCount.toString(), style: TextStyle(color: Provider.of<Settings>(context).theme.portraitProfileBadgeTextColor, fontSize: 8.0)),
));
}),
)
]),
title: Row(children: [
PopupMenuButton<ProfileStatusMenu>(
icon: ProfileImage(
imagePath: Provider.of<Settings>(context).isExperimentEnabled(ImagePreviewsExperiment)
? Provider.of<ProfileInfoState>(context).imagePath
: Provider.of<ProfileInfoState>(context).defaultImagePath,
diameter: 42,
border: Provider.of<ProfileInfoState>(context).getBorderColor(Provider.of<Settings>(context).theme),
badgeTextColor: Colors.red,
badgeColor: Colors.red,
),
iconSize: 42,
tooltip: AppLocalizations.of(context)!.availabilityStatusTooltip,
splashRadius: Material.defaultSplashRadius / 2,
onSelected: (ProfileStatusMenu item) {
String onion = Provider.of<ProfileInfoState>(context, listen: false).onion;
switch (item) {
case ProfileStatusMenu.available:
Provider.of<FlwtchState>(context, listen: false).cwtch.SetProfileAttribute(onion, "profile.profile-status", "available");
break;
case ProfileStatusMenu.away:
Provider.of<FlwtchState>(context, listen: false).cwtch.SetProfileAttribute(onion, "profile.profile-status", "away");
break;
case ProfileStatusMenu.busy:
Provider.of<FlwtchState>(context, listen: false).cwtch.SetProfileAttribute(onion, "profile.profile-status", "busy");
break;
case ProfileStatusMenu.appearOffline:
Provider.of<ProfileInfoState>(context, listen: false).appearOffline = true;
Provider.of<ProfileInfoState>(context, listen: false).deactivatePeerEngine(context);
Provider.of<FlwtchState>(context, listen: false).cwtch.ConfigureConnections(onion, false, false, false);
break;
case ProfileStatusMenu.editProfile:
Navigator.of(context).push(
PageRouteBuilder(
pageBuilder: (bcontext, a1, a2) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(value: Provider.of<ProfileInfoState>(context, listen: false)),
],
builder: (context, widget) => AddEditProfileView(key: Key('addprofile')),
);
},
transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child),
transitionDuration: Duration(milliseconds: 200),
),
);
break;
case ProfileStatusMenu.appearOnline:
Provider.of<ProfileInfoState>(context, listen: false).appearOffline = false;
// we only need to toggle all connections on here..
Provider.of<FlwtchState>(context, listen: false).cwtch.ConfigureConnections(onion, true, true, true);
break;
case ProfileStatusMenu.allowUnknownContacts:
settings.blockUnknownConnections = false;
saveSettings(context);
break;
case ProfileStatusMenu.blockUnknownContacts:
settings.blockUnknownConnections = true;
saveSettings(context);
break;
case ProfileStatusMenu.enableProfile:
Provider.of<ProfileInfoState>(context, listen: false).enabled = true;
if (Provider.of<ProfileInfoState>(context, listen: false).appearOffline) {
Provider.of<FlwtchState>(context, listen: false).cwtch.ConfigureConnections(onion, false, false, false);
} else {
Provider.of<FlwtchState>(context, listen: false).cwtch.ConfigureConnections(onion, true, true, true);
}
break;
case ProfileStatusMenu.disableProfile:
Provider.of<ProfileInfoState>(context, listen: false).enabled = false;
Provider.of<ProfileInfoState>(context, listen: false).deactivatePeerEngine(context);
break;
}
},
itemBuilder: (BuildContext context) => <PopupMenuEntry<ProfileStatusMenu>>[
PopupMenuItem<ProfileStatusMenu>(
value: ProfileStatusMenu.available,
enabled: enabled,
child: Row(children: [
Icon(
CwtchIcons.account_circle_24px,
color: Colors.white,
),
Expanded(
child: Text(AppLocalizations.of(context)!.availabilityStatusAvailable,
textAlign: TextAlign.right, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle)))
]),
),
PopupMenuItem<ProfileStatusMenu>(
value: ProfileStatusMenu.away,
enabled: enabled,
child: Row(children: [
Icon(
CwtchIcons.account_circle_24px,
color: Colors.yellowAccent,
),
Expanded(
child: Text(AppLocalizations.of(context)!.availabilityStatusAway,
textAlign: TextAlign.right, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle)))
]),
),
PopupMenuItem<ProfileStatusMenu>(
value: ProfileStatusMenu.busy,
enabled: enabled,
child: Row(children: [
Icon(
CwtchIcons.account_circle_24px,
color: Colors.redAccent,
),
Expanded(
child: Text(AppLocalizations.of(context)!.availabilityStatusBusy,
textAlign: TextAlign.right, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle)))
]),
),
PopupMenuDivider(),
PopupMenuItem<ProfileStatusMenu>(
value: ProfileStatusMenu.appearOffline,
enabled: enabled && !appearOffline,
child: Row(children: [
Icon(CwtchIcons.disconnect_from_contact),
Expanded(
child: Text(AppLocalizations.of(context)!.profileAppearOffline,
textAlign: TextAlign.right, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle)))
]),
),
PopupMenuItem<ProfileStatusMenu>(
value: ProfileStatusMenu.appearOnline,
enabled: enabled && appearOffline,
child: Row(children: [
Icon(CwtchIcons.disconnect_from_contact),
Expanded(
child: Text(AppLocalizations.of(context)!.profileAppearOnline,
textAlign: TextAlign.right, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle)))
]),
),
PopupMenuDivider(),
PopupMenuItem<ProfileStatusMenu>(
value: !settings.blockUnknownConnections ? ProfileStatusMenu.blockUnknownContacts : ProfileStatusMenu.allowUnknownContacts,
child: Row(children: [
Icon(
CwtchIcons.block_unknown,
color: settings.theme.mainTextColor,
),
Expanded(
child: Text((settings.blockUnknownConnections ? AppLocalizations.of(context)!.profileAllowUnknownContacts : AppLocalizations.of(context)!.profileBlockUnknownContacts),
textAlign: TextAlign.right, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle)))
]),
),
PopupMenuDivider(),
PopupMenuItem<ProfileStatusMenu>(
value: enabled ? ProfileStatusMenu.disableProfile : ProfileStatusMenu.enableProfile,
child: Row(children: [
Icon(CwtchIcons.favorite_24dp, color: settings.theme.mainTextColor),
Expanded(
child: Text((enabled ? AppLocalizations.of(context)!.profileDisableProfile : AppLocalizations.of(context)!.profileEnableProfile),
textAlign: TextAlign.right, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle)))
]),
),
PopupMenuItem<ProfileStatusMenu>(
value: ProfileStatusMenu.editProfile,
enabled: true,
child: Row(children: [
Icon(
CwtchIcons.edit_24px,
color: settings.theme.mainTextColor,
),
Expanded(
child: Text(AppLocalizations.of(context)!.editProfile, textAlign: TextAlign.right, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle)))
]),
),
],
),
SizedBox(
width: 10,
),
Expanded(
child: Text(
"%1 » %2"
.replaceAll("%1", redactedNick(context, Provider.of<ProfileInfoState>(context).onion, Provider.of<ProfileInfoState>(context).nickname))
.replaceAll("%2", AppLocalizations.of(context)!.titleManageContacts),
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Provider.of<Settings>(context).current().mainTextColor,
fontFamily: "Inter",
fontWeight: FontWeight.bold,
fontSize: 14.0 * Provider.of<Settings>(context).fontScaling))),
]),
actions: getActions(context),
),
floatingActionButton: FloatingActionButton(
onPressed: _modalAddImportChoice,
tooltip: AppLocalizations.of(context)!.tooltipAddContact,
child: Icon(
CwtchIcons.person_add_alt_1_24px,
color: Provider.of<Settings>(context).theme.defaultButtonTextColor,
),
),
body: showSearchBar || Provider.of<ContactListState>(context).isFiltered ? _buildFilterable() : _buildContactList()));
}
List<Widget> getActions(context) {
var actions = List<Widget>.empty(growable: true);
if (Provider.of<Settings>(context).blockUnknownConnections) {
actions.add(Tooltip(message: AppLocalizations.of(context)!.blockUnknownConnectionsEnabledDescription, child: Icon(CwtchIcons.block_unknown)));
}
if (Provider.of<Settings>(context, listen: false).isExperimentEnabled(QRCodeExperiment)) {
actions.add(PopupMenuButton<ShareMenu>(
icon: Icon(CwtchIcons.address_copy),
tooltip: AppLocalizations.of(context)!.shareProfileMenuTooltop,
splashRadius: Material.defaultSplashRadius / 2,
onSelected: (ShareMenu item) {
switch (item) {
case ShareMenu.copyCode:
{
Clipboard.setData(new ClipboardData(text: Provider.of<ProfileInfoState>(context, listen: false).onion));
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.copiedToClipboardNotification));
scaffoldKey.currentState?.showSnackBar(snackBar);
}
break;
case ShareMenu.qrcode:
{
_showQRCode("cwtch:" + Provider.of<ProfileInfoState>(context, listen: false).onion);
}
break;
}
},
itemBuilder: (BuildContext context) => <PopupMenuEntry<ShareMenu>>[
PopupMenuItem<ShareMenu>(
value: ShareMenu.copyCode,
child: Text(AppLocalizations.of(context)!.copyAddress, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle)),
),
PopupMenuItem<ShareMenu>(
value: ShareMenu.qrcode,
child: Text(AppLocalizations.of(context)!.shareMenuQRCode, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle)),
),
],
));
} else {
actions.add(IconButton(
icon: Icon(CwtchIcons.address_copy),
tooltip: AppLocalizations.of(context)!.copyAddress,
splashRadius: Material.defaultSplashRadius / 2,
onPressed: () {
Clipboard.setData(new ClipboardData(text: Provider.of<ProfileInfoState>(context, listen: false).onion));
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.copiedToClipboardNotification));
scaffoldKey.currentState?.showSnackBar(snackBar);
}));
}
// Manage known Servers
if (Provider.of<Settings>(context, listen: false).isExperimentEnabled(TapirGroupsExperiment) || Provider.of<Settings>(context, listen: false).isExperimentEnabled(ServerManagementExperiment)) {
actions.add(IconButton(
icon: Icon(CwtchIcons.dns_24px),
tooltip: AppLocalizations.of(context)!.manageKnownServersButton,
splashRadius: Material.defaultSplashRadius / 2,
onPressed: () {
_pushServers();
}));
}
// Search contacts
actions.add(IconButton(
// need both conditions for displaying initial empty textfield and also allowing filters to be cleared if this widget gets lost/reset
icon: Icon(showSearchBar || Provider.of<ContactListState>(context).isFiltered ? Icons.search_off : Icons.search),
splashRadius: Material.defaultSplashRadius / 2,
onPressed: () {
Provider.of<ContactListState>(context, listen: false).filter = "";
setState(() {
showSearchBar = !showSearchBar;
});
}));
return actions;
}
Widget _buildFilterable() {
Widget txtfield = CwtchTextField(
controller: ctrlrFilter,
hintText: AppLocalizations.of(context)!.search,
onChanged: (newVal) {
String profileHandle = Provider.of<ProfileInfoState>(context, listen: false).onion;
Provider.of<FlwtchState>(context, listen: false).cwtch.SearchConversations(profileHandle, newVal).then((value) {
Provider.of<ProfileInfoState>(context, listen: false).newSearch(value);
});
Provider.of<ContactListState>(context, listen: false).filter = newVal;
},
);
return Column(children: [Padding(padding: EdgeInsets.all(8), child: txtfield), Expanded(child: _buildContactList())]);
}
Widget _buildContactList() {
var tilesSearchResult = Provider.of<ContactListState>(context).filteredList().map((ContactInfoState contact) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(value: contact),
ChangeNotifierProvider.value(value: Provider.of<ProfileInfoState>(context).serverList),
],
builder: (context, child) => ContactRow(),
);
});
var initialScroll =
Provider.of<ProfileInfoState>(context, listen: false).contactList.filteredList().indexWhere((element) => element.identifier == Provider.of<AppState>(context).selectedConversation);
if (initialScroll < 0) {
initialScroll = 0;
}
if (showSearchBar) {
List<SearchResult> searchResults = Provider.of<ProfileInfoState>(context).activeSearchResults;
tilesSearchResult = searchResults.map((SearchResult searchResult) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(value: Provider.of<ProfileInfoState>(context).contactList.getContact(searchResult.conversationIdentifier)),
ChangeNotifierProvider.value(value: Provider.of<ProfileInfoState>(context).serverList),
],
builder: (context, child) => ContactRow(messageIndex: searchResult.messageIndex),
);
});
} else {}
var contactList = ScrollablePositionedList.separated(
itemScrollController: Provider.of<ProfileInfoState>(context).contactListScrollController,
itemCount: tilesSearchResult.length,
initialScrollIndex: initialScroll,
shrinkWrap: true,
physics: BouncingScrollPhysics(),
semanticChildCount: tilesSearchResult.length,
itemBuilder: (context, index) {
if (tilesSearchResult.length > index) {
return tilesSearchResult.elementAt(index);
}
return Container();
},
separatorBuilder: (BuildContext context, int index) {
return Divider(height: 1);
},
);
return contactList;
}
void _pushAddContact(bool newGroup) {
// close modal
Navigator.popUntil(context, (route) => route.settings.name == "conversations");
Navigator.of(context).push(
PageRouteBuilder(
settings: RouteSettings(name: "addcontact"),
pageBuilder: (builderContext, a1, a2) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(value: Provider.of<ProfileInfoState>(context)),
],
child: AddContactView(newGroup: newGroup),
);
},
transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child),
transitionDuration: Duration(milliseconds: 200),
),
);
}
void _pushServers() {
var profileInfoState = Provider.of<ProfileInfoState>(context, listen: false);
Navigator.of(context).push(
PageRouteBuilder(
settings: RouteSettings(name: "profileremoteservers"),
pageBuilder: (bcontext, a1, a2) {
return MultiProvider(
providers: [ChangeNotifierProvider.value(value: profileInfoState), Provider.value(value: Provider.of<FlwtchState>(context))],
child: ProfileServersView(),
);
},
transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child),
transitionDuration: Duration(milliseconds: 200),
),
);
}
void _modalAddImportChoice() {
bool groupsEnabled = Provider.of<Settings>(context, listen: false).isExperimentEnabled(TapirGroupsExperiment);
showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
builder: (BuildContext context) {
return Padding(
padding: MediaQuery.of(context).viewInsets,
child: RepaintBoundary(
child: Container(
height: Platform.isAndroid ? 250 : 200, // bespoke value courtesy of the [TextField] docs
child: Center(
child: Padding(
padding: EdgeInsets.all(2.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
SizedBox(
height: 20,
),
Expanded(
child: Tooltip(
message: AppLocalizations.of(context)!.tooltipAddContact,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
minimumSize: Size.fromWidth(399),
maximumSize: Size.fromWidth(400),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))),
),
child: Text(
AppLocalizations.of(context)!.addContact,
semanticsLabel: AppLocalizations.of(context)!.addContact,
textAlign: TextAlign.center,
style: TextStyle(fontFamily: "Inter", fontSize: 10.0 * Provider.of<Settings>(context).fontScaling, fontWeight: FontWeight.bold),
),
onPressed: () {
_pushAddContact(false);
},
))),
SizedBox(
height: 20,
),
Expanded(
child: Tooltip(
message: groupsEnabled ? AppLocalizations.of(context)!.addServerTooltip : AppLocalizations.of(context)!.thisFeatureRequiresGroupExpermientsToBeEnabled,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
minimumSize: Size.fromWidth(399),
maximumSize: Size.fromWidth(400),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))),
),
child: Text(
AppLocalizations.of(context)!.addServerTitle,
semanticsLabel: AppLocalizations.of(context)!.addServerTitle,
textAlign: TextAlign.center,
style: TextStyle(fontFamily: "Inter", fontSize: 10.0 * Provider.of<Settings>(context).fontScaling, fontWeight: FontWeight.bold),
),
onPressed: groupsEnabled
? () {
_pushAddContact(false);
}
: null,
)),
),
SizedBox(
height: 20,
),
Expanded(
child: Tooltip(
message: groupsEnabled ? AppLocalizations.of(context)!.createGroupTitle : AppLocalizations.of(context)!.thisFeatureRequiresGroupExpermientsToBeEnabled,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
minimumSize: Size.fromWidth(399),
maximumSize: Size.fromWidth(400),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))),
),
child: Text(
AppLocalizations.of(context)!.createGroupTitle,
semanticsLabel: AppLocalizations.of(context)!.createGroupTitle,
textAlign: TextAlign.center,
style: TextStyle(fontFamily: "Inter", fontSize: 10.0 * Provider.of<Settings>(context).fontScaling, fontWeight: FontWeight.bold),
),
onPressed: groupsEnabled
? () {
_pushAddContact(true);
}
: null,
))),
SizedBox(
height: 20,
),
],
))),
)));
});
}
void _showQRCode(String profileCode) {
showModalBottomSheet<dynamic>(
context: context,
builder: (BuildContext context) {
return Wrap(children: <Widget>[
Center(
child: QrImageView(
data: profileCode,
version: QrVersions.auto,
size: 400.0,
backgroundColor: Provider.of<Settings>(context).theme.backgroundPaneColor,
foregroundColor: Provider.of<Settings>(context).theme.mainTextColor,
gapless: false,
),
)
]);
},
);
}
}