762 lines
23 KiB
Dart
762 lines
23 KiB
Dart
import 'package:collection/collection.dart';
|
|
import 'package:fbla_ui/api_logic.dart';
|
|
import 'package:fbla_ui/pages/business_detail.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_sticky_header/flutter_sticky_header.dart';
|
|
import 'package:pdf/pdf.dart';
|
|
import 'package:pdf/widgets.dart' as pw;
|
|
import 'package:sliver_tools/sliver_tools.dart';
|
|
import 'package:url_launcher/url_launcher.dart';
|
|
|
|
late String jwt;
|
|
Set<BusinessType> filters = <BusinessType>{};
|
|
Set<BusinessType> selectedChips = <BusinessType>{};
|
|
String searchFilter = '';
|
|
bool isFiltered = false;
|
|
Set<Business> selectedBusinesses = <Business>{};
|
|
Set<DataType> selectedDataTypes = <DataType>{};
|
|
Set<DataType> dataTypeFilters = <DataType>{};
|
|
|
|
enum DataType {
|
|
logo,
|
|
name,
|
|
description,
|
|
type,
|
|
website,
|
|
contactName,
|
|
contactEmail,
|
|
contactPhone,
|
|
notes,
|
|
}
|
|
|
|
Map<DataType, int> dataTypeValues = {
|
|
DataType.logo: 0,
|
|
DataType.name: 1,
|
|
DataType.description: 2,
|
|
DataType.type: 3,
|
|
DataType.website: 4,
|
|
DataType.contactName: 5,
|
|
DataType.contactEmail: 6,
|
|
DataType.contactPhone: 7,
|
|
DataType.notes: 8
|
|
};
|
|
|
|
Map<DataType, String> dataTypeFriendly = {
|
|
DataType.logo: 'Logo',
|
|
DataType.name: 'Name',
|
|
DataType.description: 'Description',
|
|
DataType.type: 'Type',
|
|
DataType.website: 'Website',
|
|
DataType.contactName: 'Contact Name',
|
|
DataType.contactEmail: 'Contact Email',
|
|
DataType.contactPhone: 'Contact Phone',
|
|
DataType.notes: 'Notes'
|
|
};
|
|
|
|
Set<DataType> sortDataTypes(Set<DataType> set) {
|
|
List<DataType> list = set.toList();
|
|
list.sort((a, b) {
|
|
return dataTypeValues[a]!.compareTo(dataTypeValues[b]!);
|
|
});
|
|
set = list.toSet();
|
|
return set;
|
|
}
|
|
|
|
enum BusinessType {
|
|
food,
|
|
shop,
|
|
outdoors,
|
|
manufacturing,
|
|
other,
|
|
}
|
|
|
|
class Business {
|
|
int id;
|
|
String name;
|
|
String description;
|
|
BusinessType type;
|
|
String website;
|
|
String contactName;
|
|
String contactEmail;
|
|
String contactPhone;
|
|
String notes;
|
|
String locationName;
|
|
String locationAddress;
|
|
|
|
Business({
|
|
required this.id,
|
|
required this.name,
|
|
required this.description,
|
|
required this.type,
|
|
required this.website,
|
|
required this.contactName,
|
|
required this.contactEmail,
|
|
required this.contactPhone,
|
|
required this.notes,
|
|
required this.locationName,
|
|
required this.locationAddress,
|
|
});
|
|
|
|
factory Business.fromJson(Map<String, dynamic> json) {
|
|
bool typeValid = true;
|
|
try {
|
|
BusinessType.values.byName(json['type']);
|
|
} catch (e) {
|
|
typeValid = false;
|
|
}
|
|
return Business(
|
|
id: json['id'],
|
|
name: json['name'],
|
|
description: json['description'],
|
|
type: typeValid
|
|
? BusinessType.values.byName(json['type'])
|
|
: BusinessType.other,
|
|
website: json['website'],
|
|
contactName: json['contactName'],
|
|
contactEmail: json['contactEmail'],
|
|
contactPhone: json['contactPhone'],
|
|
notes: json['notes'],
|
|
locationName: json['locationName'],
|
|
locationAddress: json['locationAddress'],
|
|
);
|
|
}
|
|
|
|
factory Business.copy(Business input) {
|
|
return Business(
|
|
id: input.id,
|
|
name: input.name,
|
|
description: input.description,
|
|
type: input.type,
|
|
website: input.website,
|
|
contactName: input.contactName,
|
|
contactEmail: input.contactEmail,
|
|
contactPhone: input.contactPhone,
|
|
notes: input.notes,
|
|
locationName: input.locationName,
|
|
locationAddress: input.locationAddress,
|
|
);
|
|
}
|
|
}
|
|
|
|
Map<BusinessType, List<Business>> groupBusinesses(List<Business> businesses) {
|
|
Map<BusinessType, List<Business>> groupedBusinesses =
|
|
groupBy<Business, BusinessType>(businesses, (business) => business.type);
|
|
|
|
return groupedBusinesses;
|
|
}
|
|
|
|
Icon getIconFromType(BusinessType type, double size, Color color) {
|
|
switch (type) {
|
|
case BusinessType.food:
|
|
return Icon(
|
|
Icons.restaurant,
|
|
size: size,
|
|
color: color,
|
|
);
|
|
case BusinessType.shop:
|
|
return Icon(
|
|
Icons.store,
|
|
size: size,
|
|
color: color,
|
|
);
|
|
case BusinessType.outdoors:
|
|
return Icon(
|
|
Icons.forest,
|
|
size: size,
|
|
color: color,
|
|
);
|
|
case BusinessType.manufacturing:
|
|
return Icon(
|
|
Icons.factory,
|
|
size: size,
|
|
color: color,
|
|
);
|
|
case BusinessType.other:
|
|
return Icon(
|
|
Icons.business,
|
|
size: size,
|
|
color: color,
|
|
);
|
|
}
|
|
}
|
|
|
|
pw.Icon getPwIconFromType(BusinessType type, double size, PdfColor color) {
|
|
switch (type) {
|
|
case BusinessType.food:
|
|
return pw.Icon(const pw.IconData(0xe56c), size: size, color: color);
|
|
case BusinessType.shop:
|
|
return pw.Icon(const pw.IconData(0xea12), size: size, color: color);
|
|
case BusinessType.outdoors:
|
|
return pw.Icon(const pw.IconData(0xea99), size: size, color: color);
|
|
case BusinessType.manufacturing:
|
|
return pw.Icon(const pw.IconData(0xebbc), size: size, color: color);
|
|
case BusinessType.other:
|
|
return pw.Icon(const pw.IconData(0xe0af), size: size, color: color);
|
|
}
|
|
}
|
|
|
|
Text getNameFromType(BusinessType type, Color color) {
|
|
switch (type) {
|
|
case BusinessType.food:
|
|
return Text('Food Related', style: TextStyle(color: color));
|
|
case BusinessType.shop:
|
|
return Text('Shops', style: TextStyle(color: color));
|
|
case BusinessType.outdoors:
|
|
return Text('Outdoors', style: TextStyle(color: color));
|
|
case BusinessType.manufacturing:
|
|
return Text('Manufacturing', style: TextStyle(color: color));
|
|
case BusinessType.other:
|
|
return Text('Other', style: TextStyle(color: color));
|
|
}
|
|
}
|
|
|
|
Icon getIconFromThemeMode(ThemeMode theme) {
|
|
switch (theme) {
|
|
case ThemeMode.dark:
|
|
return const Icon(Icons.dark_mode);
|
|
case ThemeMode.light:
|
|
return const Icon(Icons.light_mode);
|
|
case ThemeMode.system:
|
|
return const Icon(Icons.brightness_4);
|
|
}
|
|
}
|
|
|
|
class BusinessDisplayPanel extends StatefulWidget {
|
|
final List<Business> businesses;
|
|
final bool widescreen;
|
|
final bool selectable;
|
|
|
|
const BusinessDisplayPanel(
|
|
{super.key,
|
|
required this.businesses,
|
|
required this.widescreen,
|
|
required this.selectable});
|
|
|
|
@override
|
|
State<BusinessDisplayPanel> createState() => _BusinessDisplayPanelState();
|
|
}
|
|
|
|
class _BusinessDisplayPanelState extends State<BusinessDisplayPanel> {
|
|
Set<Business> selectedBusinesses = <Business>{};
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
List<BusinessHeader> headers = [];
|
|
List<Business> filteredBusinesses = [];
|
|
for (var business in widget.businesses) {
|
|
if (business.name.toLowerCase().contains(searchFilter.toLowerCase())) {
|
|
filteredBusinesses.add(business);
|
|
}
|
|
}
|
|
var groupedBusinesses = groupBusinesses(filteredBusinesses);
|
|
var businessTypes = groupedBusinesses.keys.toList();
|
|
|
|
for (var i = 0; i < businessTypes.length; i++) {
|
|
if (filters.contains(businessTypes[i])) {
|
|
isFiltered = true;
|
|
}
|
|
}
|
|
|
|
if (isFiltered) {
|
|
for (var i = 0; i < businessTypes.length; i++) {
|
|
if (filters.contains(businessTypes[i])) {
|
|
headers.add(BusinessHeader(
|
|
type: businessTypes[i],
|
|
widescreen: widget.widescreen,
|
|
selectable: widget.selectable,
|
|
selectedBusinesses: selectedBusinesses,
|
|
businesses: groupedBusinesses[businessTypes[i]]!));
|
|
}
|
|
}
|
|
} else {
|
|
for (var i = 0; i < businessTypes.length; i++) {
|
|
headers.add(BusinessHeader(
|
|
type: businessTypes[i],
|
|
widescreen: widget.widescreen,
|
|
selectable: widget.selectable,
|
|
selectedBusinesses: selectedBusinesses,
|
|
businesses: groupedBusinesses[businessTypes[i]]!));
|
|
}
|
|
}
|
|
headers.sort((a, b) => a.type.index.compareTo(b.type.index));
|
|
return MultiSliver(children: headers);
|
|
}
|
|
}
|
|
|
|
class BusinessHeader extends StatefulWidget {
|
|
final BusinessType type;
|
|
final List<Business> businesses;
|
|
final Set<Business> selectedBusinesses;
|
|
final bool widescreen;
|
|
final bool selectable;
|
|
|
|
const BusinessHeader({
|
|
super.key,
|
|
required this.type,
|
|
required this.businesses,
|
|
required this.selectedBusinesses,
|
|
required this.widescreen,
|
|
required this.selectable,
|
|
});
|
|
|
|
@override
|
|
State<BusinessHeader> createState() => _BusinessHeaderState();
|
|
}
|
|
|
|
class _BusinessHeaderState extends State<BusinessHeader> {
|
|
refresh() {
|
|
setState(() {});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return SliverStickyHeader(
|
|
header: Container(
|
|
height: 55.0,
|
|
color: Theme.of(context).colorScheme.primary,
|
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
|
alignment: Alignment.centerLeft,
|
|
child: _getHeaderRow(widget.selectable),
|
|
),
|
|
sliver: _getChildSliver(
|
|
widget.businesses, widget.widescreen, widget.selectable),
|
|
);
|
|
}
|
|
|
|
Widget _getHeaderRow(bool selectable) {
|
|
if (selectable) {
|
|
return Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.only(left: 4.0, right: 12.0),
|
|
child: getIconFromType(
|
|
widget.type, 24, Theme.of(context).colorScheme.onPrimary),
|
|
),
|
|
getNameFromType(
|
|
widget.type, Theme.of(context).colorScheme.onPrimary),
|
|
],
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.only(right: 12.0),
|
|
child: Checkbox(
|
|
checkColor: Theme.of(context).colorScheme.primary,
|
|
activeColor: Theme.of(context).colorScheme.onPrimary,
|
|
value: selectedBusinesses.containsAll(widget.businesses),
|
|
onChanged: (value) {
|
|
if (value!) {
|
|
setState(() {
|
|
selectedBusinesses.addAll(widget.businesses);
|
|
});
|
|
} else {
|
|
setState(() {
|
|
selectedBusinesses.removeAll(widget.businesses);
|
|
});
|
|
}
|
|
},
|
|
),
|
|
),
|
|
],
|
|
);
|
|
} else {
|
|
return Row(
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.only(left: 4.0, right: 12.0),
|
|
child: getIconFromType(
|
|
widget.type, 24, Theme.of(context).colorScheme.onPrimary),
|
|
),
|
|
getNameFromType(widget.type, Theme.of(context).colorScheme.onPrimary),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
Widget _getChildSliver(
|
|
List<Business> businesses, bool widescreen, bool selectable) {
|
|
if (widescreen) {
|
|
return SliverGrid(
|
|
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
|
mainAxisExtent: 250.0,
|
|
maxCrossAxisExtent: 400.0,
|
|
mainAxisSpacing: 10.0,
|
|
crossAxisSpacing: 10.0,
|
|
// childAspectRatio: 4.0,
|
|
),
|
|
delegate: SliverChildBuilderDelegate(
|
|
childCount: businesses.length,
|
|
(BuildContext context, int index) {
|
|
return BusinessCard(
|
|
business: businesses[index],
|
|
selectable: selectable,
|
|
widescreen: widescreen,
|
|
callback: refresh,
|
|
);
|
|
},
|
|
),
|
|
);
|
|
} else {
|
|
return SliverList(
|
|
delegate: SliverChildBuilderDelegate(
|
|
childCount: businesses.length,
|
|
(BuildContext context, int index) {
|
|
return BusinessCard(
|
|
business: businesses[index],
|
|
selectable: selectable,
|
|
widescreen: widescreen,
|
|
callback: refresh,
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
class BusinessCard extends StatefulWidget {
|
|
final Business business;
|
|
final bool widescreen;
|
|
final bool selectable;
|
|
final Function callback;
|
|
|
|
const BusinessCard(
|
|
{super.key,
|
|
required this.business,
|
|
required this.widescreen,
|
|
required this.selectable,
|
|
required this.callback});
|
|
|
|
@override
|
|
State<BusinessCard> createState() => _BusinessCardState();
|
|
}
|
|
|
|
class _BusinessCardState extends State<BusinessCard> {
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (widget.widescreen) {
|
|
return _businessTile(widget.business, widget.selectable);
|
|
} else {
|
|
return _businessListItem(
|
|
widget.business, widget.selectable, widget.callback);
|
|
}
|
|
}
|
|
|
|
Widget _businessTile(Business business, bool selectable) {
|
|
return MouseRegion(
|
|
cursor: SystemMouseCursors.click,
|
|
child: GestureDetector(
|
|
onTap: () {
|
|
Navigator.of(context).push(MaterialPageRoute(
|
|
builder: (context) => BusinessDetail(inputBusiness: business)));
|
|
},
|
|
child: Card(
|
|
clipBehavior: Clip.antiAlias,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
_getTileRow(business, selectable, widget.callback),
|
|
Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: Text(
|
|
business.description,
|
|
maxLines: selectable ? 7 : 5,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
const Spacer(),
|
|
Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: !selectable
|
|
? Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
children: [
|
|
IconButton(
|
|
icon: const Icon(Icons.link),
|
|
onPressed: () {
|
|
launchUrl(
|
|
Uri.parse('https://${business.website}'));
|
|
},
|
|
),
|
|
if (business.locationName.isNotEmpty)
|
|
IconButton(
|
|
icon: const Icon(Icons.location_on),
|
|
onPressed: () {
|
|
launchUrl(Uri.parse(Uri.encodeFull(
|
|
'https://www.google.com/maps/search/?api=1&query=${business.locationName}')));
|
|
},
|
|
),
|
|
if (business.contactPhone.isNotEmpty)
|
|
IconButton(
|
|
icon: const Icon(Icons.phone),
|
|
onPressed: () {
|
|
showDialog(
|
|
context: context,
|
|
builder: (BuildContext context) {
|
|
return AlertDialog(
|
|
backgroundColor: Theme.of(context)
|
|
.colorScheme
|
|
.background,
|
|
title: Text(
|
|
'Contact ${business.contactName}?'),
|
|
content: Text(
|
|
'Would you like to call or text ${business.contactName}?'),
|
|
actions: [
|
|
TextButton(
|
|
child: const Text('Text'),
|
|
onPressed: () {
|
|
launchUrl(Uri.parse(
|
|
'sms:${business.contactPhone}'));
|
|
Navigator.of(context).pop();
|
|
}),
|
|
TextButton(
|
|
child: const Text('Call'),
|
|
onPressed: () async {
|
|
launchUrl(Uri.parse(
|
|
'tel:${business.contactPhone}'));
|
|
Navigator.of(context).pop();
|
|
}),
|
|
],
|
|
);
|
|
});
|
|
},
|
|
),
|
|
if (business.contactEmail.isNotEmpty)
|
|
IconButton(
|
|
icon: const Icon(Icons.email),
|
|
onPressed: () {
|
|
launchUrl(Uri.parse(
|
|
'mailto:${business.contactEmail}'));
|
|
},
|
|
),
|
|
],
|
|
)
|
|
: null),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _getTileRow(Business business, bool selectable, Function callback) {
|
|
if (selectable) {
|
|
return Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: ClipRRect(
|
|
borderRadius: BorderRadius.circular(6.0),
|
|
child: Image.network('$apiAddress/logos/${business.id}',
|
|
height: 48, width: 48, errorBuilder: (BuildContext context,
|
|
Object exception, StackTrace? stackTrace) {
|
|
return getIconFromType(
|
|
business.type, 48, Theme.of(context).colorScheme.onSurface);
|
|
}),
|
|
),
|
|
),
|
|
Flexible(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: Text(
|
|
business.name,
|
|
style:
|
|
const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
|
maxLines: 2,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.only(right: 24.0),
|
|
child: _checkbox(callback),
|
|
)
|
|
],
|
|
);
|
|
} else {
|
|
return Row(
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: ClipRRect(
|
|
borderRadius: BorderRadius.circular(6.0),
|
|
child: Image.network('$apiAddress/logos/${business.id}',
|
|
height: 48, width: 48, errorBuilder: (BuildContext context,
|
|
Object exception, StackTrace? stackTrace) {
|
|
return getIconFromType(business.type, 48,
|
|
Theme.of(context).colorScheme.onSurface);
|
|
}),
|
|
)),
|
|
Flexible(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: Text(
|
|
business.name,
|
|
style:
|
|
const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
|
maxLines: 2,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
Widget _businessListItem(
|
|
Business business, bool selectable, Function callback) {
|
|
return Card(
|
|
child: ListTile(
|
|
leading: ClipRRect(
|
|
borderRadius: BorderRadius.circular(3.0),
|
|
child: Image.network('$apiAddress/logos/${business.id}',
|
|
height: 24, width: 24, errorBuilder: (BuildContext context,
|
|
Object exception, StackTrace? stackTrace) {
|
|
return getIconFromType(
|
|
business.type, 24, Theme.of(context).colorScheme.onSurface);
|
|
})),
|
|
title: Text(business.name),
|
|
subtitle: Text(business.description,
|
|
maxLines: 1, overflow: TextOverflow.ellipsis),
|
|
trailing: _getCheckbox(selectable, callback),
|
|
onTap: () {
|
|
Navigator.of(context).push(MaterialPageRoute(
|
|
builder: (context) => BusinessDetail(inputBusiness: business)));
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _checkbox(Function callback) {
|
|
return Checkbox(
|
|
value: selectedBusinesses.contains(widget.business),
|
|
onChanged: (value) {
|
|
if (value!) {
|
|
setState(() {
|
|
selectedBusinesses.add(widget.business);
|
|
});
|
|
} else {
|
|
setState(() {
|
|
selectedBusinesses.remove(widget.business);
|
|
});
|
|
}
|
|
callback();
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget? _getCheckbox(bool selectable, Function callback) {
|
|
if (selectable) {
|
|
return _checkbox(callback);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
class FilterChips extends StatefulWidget {
|
|
const FilterChips({super.key});
|
|
|
|
@override
|
|
State<FilterChips> createState() => _FilterChipsState();
|
|
}
|
|
|
|
class _FilterChipsState extends State<FilterChips> {
|
|
List<Padding> filterChips() {
|
|
List<Padding> chips = [];
|
|
|
|
for (var type in BusinessType.values) {
|
|
chips.add(Padding(
|
|
padding: const EdgeInsets.only(left: 4.0, right: 4.0),
|
|
child: FilterChip(
|
|
showCheckmark: false,
|
|
shape:
|
|
RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
|
label: Text(type.name),
|
|
selected: selectedChips.contains(type),
|
|
onSelected: (bool selected) {
|
|
setState(() {
|
|
if (selected) {
|
|
selectedChips.add(type);
|
|
} else {
|
|
selectedChips.remove(type);
|
|
}
|
|
});
|
|
}),
|
|
));
|
|
}
|
|
return chips;
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Wrap(
|
|
children: filterChips(),
|
|
);
|
|
}
|
|
}
|
|
|
|
class FilterDataTypeChips extends StatefulWidget {
|
|
const FilterDataTypeChips({super.key});
|
|
|
|
@override
|
|
State<FilterDataTypeChips> createState() => _FilterDataTypeChipsState();
|
|
}
|
|
|
|
class _FilterDataTypeChipsState extends State<FilterDataTypeChips> {
|
|
List<Padding> filterDataTypeChips() {
|
|
List<Padding> chips = [];
|
|
|
|
for (var type in DataType.values) {
|
|
chips.add(Padding(
|
|
padding:
|
|
const EdgeInsets.only(left: 3.0, right: 3.0, bottom: 3.0, top: 3.0),
|
|
// child: ActionChip(
|
|
// avatar: selectedDataTypes.contains(type) ? Icon(Icons.check_box) : Icon(Icons.check_box_outline_blank),
|
|
// label: Text(type.name),
|
|
// onPressed: () {
|
|
// if (!selectedDataTypes.contains(type)) {
|
|
// setState(() {
|
|
// selectedDataTypes.add(type);
|
|
// });
|
|
// } else {
|
|
// setState(() {
|
|
// selectedDataTypes.remove(type);
|
|
// });
|
|
// }
|
|
// },
|
|
|
|
// ),
|
|
child: FilterChip(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(20),
|
|
side:
|
|
BorderSide(color: Theme.of(context).colorScheme.secondary)),
|
|
label: Text(dataTypeFriendly[type]!),
|
|
showCheckmark: false,
|
|
selected: selectedDataTypes.contains(type),
|
|
onSelected: (bool selected) {
|
|
setState(() {
|
|
if (selected) {
|
|
selectedDataTypes.add(type);
|
|
} else {
|
|
selectedDataTypes.remove(type);
|
|
}
|
|
});
|
|
}),
|
|
));
|
|
}
|
|
return chips;
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Wrap(
|
|
children: filterDataTypeChips(),
|
|
);
|
|
}
|
|
}
|