[DEV] Done with the Chip selector
This commit is contained in:
20
lib/Infrastructure/list_extensions.dart
Normal file
20
lib/Infrastructure/list_extensions.dart
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
extension ListExtension<E> on List<E> {
|
||||||
|
bool containsAll(List<E> otherList) {
|
||||||
|
List<bool> checks = [];
|
||||||
|
//
|
||||||
|
for (E thisElement in otherList) {
|
||||||
|
checks.add(contains(thisElement));
|
||||||
|
}
|
||||||
|
return !checks.contains(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<E> getUnique() {
|
||||||
|
List<E> uniqueItems = [];
|
||||||
|
for (E thisElement in this) {
|
||||||
|
if (!uniqueItems.contains(thisElement)) {
|
||||||
|
uniqueItems.add(thisElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return uniqueItems;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
//s2 Core Packages Imports
|
//s2 Core Packages Imports
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'src/configuration.dart';
|
|
||||||
import 'src/radio.selector.dart';
|
import 'src/radio.selector.dart';
|
||||||
|
import 'src/chip.selector.dart';
|
||||||
|
import 'src/configuration.dart';
|
||||||
|
|
||||||
class AstromicSelectors {
|
class AstromicSelectors {
|
||||||
//S1 -- Radio
|
//S1 -- Radio
|
||||||
@@ -21,7 +22,7 @@ class AstromicSelectors {
|
|||||||
initialSelectedValue: initialSelectedValue,
|
initialSelectedValue: initialSelectedValue,
|
||||||
onChanged: onChanged,
|
onChanged: onChanged,
|
||||||
//
|
//
|
||||||
configurations: configurations ?? const AstromicSelectorConfiguration(),
|
configuration: configurations ?? const AstromicSelectorConfiguration(),
|
||||||
//
|
//
|
||||||
itemSpacing: itemSpacing ?? 8.0,
|
itemSpacing: itemSpacing ?? 8.0,
|
||||||
//
|
//
|
||||||
@@ -29,4 +30,37 @@ class AstromicSelectors {
|
|||||||
disabledItemBuilder: disabledItemBuilder,
|
disabledItemBuilder: disabledItemBuilder,
|
||||||
items: items,
|
items: items,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
//S1 -- Chip
|
||||||
|
static Widget chip<T>({
|
||||||
|
List<T>? initialSelectedValues,
|
||||||
|
void Function(List<T> selectedItems)? onChanged,
|
||||||
|
//
|
||||||
|
AstromicSelectorConfiguration? configuration,
|
||||||
|
//
|
||||||
|
double? itemSpacing = 4,
|
||||||
|
double? runSpacing = 8,
|
||||||
|
double? clearSpacing = 4,
|
||||||
|
//
|
||||||
|
TextStyle? labelStyle,
|
||||||
|
TextStyle? selectedLabelStyle,
|
||||||
|
TextStyle? disabledLabelStyle,
|
||||||
|
//
|
||||||
|
required List<(T item, bool isEnabled)> items,
|
||||||
|
required Widget Function(T item, {bool isSelected, VoidCallback? onTap, VoidCallback? onClearTapped}) itemBuilder,
|
||||||
|
required Widget Function(T item) disabledItemBuilder,
|
||||||
|
}) =>
|
||||||
|
AstromicChipSelector(
|
||||||
|
initialSelectedValues: initialSelectedValues,
|
||||||
|
onChanged: onChanged,
|
||||||
|
//
|
||||||
|
configuration: configuration,
|
||||||
|
//
|
||||||
|
itemSpacing: itemSpacing,
|
||||||
|
runSpacing: runSpacing,
|
||||||
|
//
|
||||||
|
items: items,
|
||||||
|
itemBuilder: itemBuilder,
|
||||||
|
disabledItemBuilder: disabledItemBuilder,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,212 @@
|
|||||||
|
//SECTION - Imports
|
||||||
|
//
|
||||||
|
//s1 PACKAGES
|
||||||
|
//---------------
|
||||||
|
//s2 CORE
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
//s2 3RD-PARTY
|
||||||
|
//
|
||||||
|
//s1 DEPENDENCIES
|
||||||
|
//---------------
|
||||||
|
//s2 SERVICES
|
||||||
|
//s2 MODELS
|
||||||
|
import '../../../Infrastructure/list_extensions.dart';
|
||||||
|
import 'configuration.dart';
|
||||||
|
|
||||||
|
//s2 MISC
|
||||||
|
//!SECTION - Imports
|
||||||
|
//
|
||||||
|
//SECTION - Exports
|
||||||
|
//!SECTION - Exports
|
||||||
|
//
|
||||||
|
class AstromicChipSelector<T> extends StatefulWidget {
|
||||||
|
//SECTION - Widget Arguments
|
||||||
|
//s1 -- Functionality
|
||||||
|
final List<T>? initialSelectedValues;
|
||||||
|
final Function(List<T> selectedItems)? onChanged;
|
||||||
|
//s1 -- Configuration
|
||||||
|
final AstromicSelectorConfiguration? configuration;
|
||||||
|
//s1 -- Style
|
||||||
|
final double? itemSpacing;
|
||||||
|
final double? runSpacing;
|
||||||
|
//
|
||||||
|
//s1 -- Content
|
||||||
|
final List<(T item, bool isEnabled)> items;
|
||||||
|
//
|
||||||
|
final Widget Function(T item, {bool isSelected, VoidCallback? onTap, VoidCallback? onClearTapped}) itemBuilder;
|
||||||
|
final Widget Function(T item) disabledItemBuilder;
|
||||||
|
//!SECTION
|
||||||
|
//
|
||||||
|
AstromicChipSelector({
|
||||||
|
Key? key,
|
||||||
|
//s1 -- Functionality
|
||||||
|
this.initialSelectedValues,
|
||||||
|
this.onChanged,
|
||||||
|
//s1 -- Configuration
|
||||||
|
this.configuration,
|
||||||
|
//s1 -- Style
|
||||||
|
this.itemSpacing = 8,
|
||||||
|
this.runSpacing = 8,
|
||||||
|
//s1 -- Content
|
||||||
|
required this.items,
|
||||||
|
required this.itemBuilder,
|
||||||
|
required this.disabledItemBuilder,
|
||||||
|
}) : assert(
|
||||||
|
(configuration?.isNullable ?? true) || (initialSelectedValues != null && items.map((i) => i.$1).toList().containsAll(initialSelectedValues)),
|
||||||
|
"Initial values are not all present in the items!",
|
||||||
|
),
|
||||||
|
super(
|
||||||
|
key: key,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AstromicChipSelector<T>> createState() => _AstromicChipSelectorState<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AstromicChipSelectorState<T> extends State<AstromicChipSelector<T>> {
|
||||||
|
//
|
||||||
|
//SECTION - State Variables
|
||||||
|
//s1 --Controllers
|
||||||
|
late ScrollController _scrollController;
|
||||||
|
//s1 --Controllers
|
||||||
|
//
|
||||||
|
//s1 --State
|
||||||
|
late AstromicSelectorConfiguration _configuration;
|
||||||
|
late List<T> selectedItems;
|
||||||
|
//s1 --State
|
||||||
|
//
|
||||||
|
//s1 --Constants
|
||||||
|
//s1 --Constants
|
||||||
|
//!SECTION
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
//
|
||||||
|
//SECTION - State Variables initializations & Listeners
|
||||||
|
//s1 --Controllers & Listeners
|
||||||
|
_scrollController = ScrollController();
|
||||||
|
//s1 --Controllers & Listeners
|
||||||
|
//
|
||||||
|
//s1 --State
|
||||||
|
_configuration = widget.configuration ?? const AstromicSelectorConfiguration();
|
||||||
|
selectedItems = widget.initialSelectedValues ?? [];
|
||||||
|
//s1 --State
|
||||||
|
//
|
||||||
|
//s1 --Late & Async Initializers
|
||||||
|
//s1 --Late & Async Initializers
|
||||||
|
//!SECTION
|
||||||
|
}
|
||||||
|
|
||||||
|
//SECTION - Stateless functions
|
||||||
|
//!SECTION
|
||||||
|
|
||||||
|
//SECTION - Action Callbacks
|
||||||
|
_onTap(T value) {
|
||||||
|
setState(() {
|
||||||
|
if (selectedItems.contains(value)) {
|
||||||
|
//---- if item selected
|
||||||
|
selectedItems.remove(value);
|
||||||
|
//---- !if item selected
|
||||||
|
} else {
|
||||||
|
//---- if item Not selected
|
||||||
|
selectedItems.add(value);
|
||||||
|
//---- !if item Not selected
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (widget.onChanged != null) {
|
||||||
|
widget.onChanged!(selectedItems);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----
|
||||||
|
_onTapClear(T value) {
|
||||||
|
setState(() {
|
||||||
|
if (selectedItems.contains(value)) {
|
||||||
|
selectedItems.remove(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (widget.onChanged != null) {
|
||||||
|
widget.onChanged!(selectedItems);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//!SECTION
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
//SECTION - Build Setup
|
||||||
|
//s1 -Values
|
||||||
|
//s1 -Values
|
||||||
|
//
|
||||||
|
//s1 -Widgets
|
||||||
|
List<Widget> baseChildren = widget.items.map((currentItem) {
|
||||||
|
int currentIndex = widget.items.indexOf(currentItem);
|
||||||
|
T? previousItem = currentIndex == 0 ? null : widget.items[widget.items.indexOf(currentItem) - 1].$1;
|
||||||
|
T? nextItem = currentIndex == widget.items.length - 1 ? null : widget.items[widget.items.indexOf(currentItem) + 1].$1;
|
||||||
|
bool isConsequent = _configuration.isConsequent;
|
||||||
|
//
|
||||||
|
bool isEnabled = currentItem.$2;
|
||||||
|
bool isSelected = selectedItems.contains(currentItem);
|
||||||
|
bool isPreviousItemSelected = selectedItems.contains(previousItem);
|
||||||
|
bool isNextItemSelected = selectedItems.contains(nextItem);
|
||||||
|
// ---
|
||||||
|
bool canBeSelected = (isConsequent && isEnabled && !isSelected && selectedItems.length < (_configuration.maxSelectedItems) && (isPreviousItemSelected || currentIndex == 0)) ||
|
||||||
|
(!isConsequent && isEnabled && !isSelected && selectedItems.length < (_configuration.maxSelectedItems));
|
||||||
|
bool canbeUnselected = (isConsequent && isEnabled && isSelected && (!isNextItemSelected || currentIndex == widget.items.length - 1)) || (!isConsequent && isEnabled && isSelected);
|
||||||
|
// ---
|
||||||
|
bool tapable = (isSelected && canbeUnselected) || (!isSelected && canBeSelected);
|
||||||
|
// ---
|
||||||
|
bool canShowClear = _configuration.withClearButton && canbeUnselected;
|
||||||
|
// ---
|
||||||
|
//
|
||||||
|
return isEnabled
|
||||||
|
? widget.itemBuilder(
|
||||||
|
currentItem.$1,
|
||||||
|
isSelected: isSelected,
|
||||||
|
onTap: tapable ? () => _onTap(currentItem.$1) : null,
|
||||||
|
onClearTapped: canShowClear ? _onTapClear(currentItem.$1) : null,
|
||||||
|
)
|
||||||
|
: widget.disabledItemBuilder(currentItem.$1);
|
||||||
|
}).toList();
|
||||||
|
//s1 -Widgets
|
||||||
|
//!SECTION
|
||||||
|
|
||||||
|
//SECTION - Build Return
|
||||||
|
return _configuration.isWrap
|
||||||
|
? Wrap(
|
||||||
|
spacing: widget.itemSpacing!,
|
||||||
|
runSpacing: widget.runSpacing!,
|
||||||
|
//
|
||||||
|
alignment: _configuration.wrapMainAllignment,
|
||||||
|
crossAxisAlignment: _configuration.wrapCrossAllignment,
|
||||||
|
//
|
||||||
|
children: baseChildren,
|
||||||
|
)
|
||||||
|
: GridView.builder(
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
controller: _scrollController,
|
||||||
|
shrinkWrap: true,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
mainAxisExtent: _configuration.fixedRowHeight,
|
||||||
|
// number of items per row
|
||||||
|
crossAxisCount: widget.configuration?.crossAxisCount ?? 1,
|
||||||
|
// vertical spacing between the items
|
||||||
|
mainAxisSpacing: widget.runSpacing!,
|
||||||
|
// the horizontal spacing between the items
|
||||||
|
crossAxisSpacing: widget.itemSpacing!,
|
||||||
|
),
|
||||||
|
// number of items in your list
|
||||||
|
itemCount: baseChildren.length,
|
||||||
|
itemBuilder: (BuildContext context, int index) => baseChildren[index],
|
||||||
|
);
|
||||||
|
//!SECTION
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
//SECTION - Disposable variables
|
||||||
|
//!SECTION
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,20 +5,28 @@ class AstromicSelectorConfiguration {
|
|||||||
final Axis axis;
|
final Axis axis;
|
||||||
final bool isNullable;
|
final bool isNullable;
|
||||||
//s1 Chip Specific
|
//s1 Chip Specific
|
||||||
|
final bool isWrap;
|
||||||
|
final WrapAlignment wrapMainAllignment;
|
||||||
|
final WrapCrossAlignment wrapCrossAllignment;
|
||||||
final bool isConsequent;
|
final bool isConsequent;
|
||||||
final bool withClearButton;
|
final bool withClearButton;
|
||||||
final int maxSelectedItems;
|
final int maxSelectedItems;
|
||||||
final int crossAxisCount;
|
final int crossAxisCount;
|
||||||
|
final double? fixedRowHeight;
|
||||||
//s1 Radio Specific
|
//s1 Radio Specific
|
||||||
final bool withExpandedSpace;
|
final bool withExpandedSpace;
|
||||||
const AstromicSelectorConfiguration({
|
const AstromicSelectorConfiguration({
|
||||||
this.axis = Axis.horizontal,
|
this.axis = Axis.horizontal,
|
||||||
this.isNullable = true,
|
this.isNullable = true,
|
||||||
//
|
//
|
||||||
|
this.isWrap = true,
|
||||||
|
this.wrapMainAllignment = WrapAlignment.start,
|
||||||
|
this.wrapCrossAllignment = WrapCrossAlignment.center,
|
||||||
this.isConsequent = false,
|
this.isConsequent = false,
|
||||||
this.withClearButton = false,
|
this.withClearButton = false,
|
||||||
this.maxSelectedItems = 10000,
|
this.maxSelectedItems = 10000,
|
||||||
this.crossAxisCount = 3,
|
this.crossAxisCount = 3,
|
||||||
|
this.fixedRowHeight,
|
||||||
//
|
//
|
||||||
this.withExpandedSpace = false,
|
this.withExpandedSpace = false,
|
||||||
});
|
});
|
||||||
@@ -26,20 +34,28 @@ class AstromicSelectorConfiguration {
|
|||||||
AstromicSelectorConfiguration copyWith({
|
AstromicSelectorConfiguration copyWith({
|
||||||
Axis? axis,
|
Axis? axis,
|
||||||
bool? isNullable,
|
bool? isNullable,
|
||||||
|
bool? isWrap,
|
||||||
|
WrapAlignment? wrapMainAllignment,
|
||||||
|
WrapCrossAlignment? wrapCrossAllignment,
|
||||||
bool? isConsequent,
|
bool? isConsequent,
|
||||||
bool? withClearButton,
|
bool? withClearButton,
|
||||||
int? maxSelectedItems,
|
int? maxSelectedItems,
|
||||||
int? crossAxisCount,
|
int? crossAxisCount,
|
||||||
bool? withExpandedSpace,
|
bool? withExpandedSpace,
|
||||||
|
double? fixedRowHeight,
|
||||||
}) {
|
}) {
|
||||||
return AstromicSelectorConfiguration(
|
return AstromicSelectorConfiguration(
|
||||||
axis: axis ?? this.axis,
|
axis: axis ?? this.axis,
|
||||||
isNullable: isNullable ?? this.isNullable,
|
isNullable: isNullable ?? this.isNullable,
|
||||||
|
isWrap: isWrap ?? this.isWrap,
|
||||||
|
wrapMainAllignment: wrapMainAllignment ?? this.wrapMainAllignment,
|
||||||
|
wrapCrossAllignment: wrapCrossAllignment ?? this.wrapCrossAllignment,
|
||||||
isConsequent: isConsequent ?? this.isConsequent,
|
isConsequent: isConsequent ?? this.isConsequent,
|
||||||
withClearButton: withClearButton ?? this.withClearButton,
|
withClearButton: withClearButton ?? this.withClearButton,
|
||||||
maxSelectedItems: maxSelectedItems ?? this.maxSelectedItems,
|
maxSelectedItems: maxSelectedItems ?? this.maxSelectedItems,
|
||||||
crossAxisCount: crossAxisCount ?? this.crossAxisCount,
|
crossAxisCount: crossAxisCount ?? this.crossAxisCount,
|
||||||
withExpandedSpace: withExpandedSpace ?? this.withExpandedSpace,
|
withExpandedSpace: withExpandedSpace ?? this.withExpandedSpace,
|
||||||
|
fixedRowHeight: fixedRowHeight ?? this.fixedRowHeight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class AstromicRadioSelector<T> extends StatefulWidget {
|
|||||||
final T? initialSelectedValue;
|
final T? initialSelectedValue;
|
||||||
final Function(T selectedItem)? onChanged;
|
final Function(T selectedItem)? onChanged;
|
||||||
//s1 -- Configuration
|
//s1 -- Configuration
|
||||||
final AstromicSelectorConfiguration configurations;
|
final AstromicSelectorConfiguration configuration;
|
||||||
//s1 -- Style
|
//s1 -- Style
|
||||||
final double itemSpacing;
|
final double itemSpacing;
|
||||||
//s1 -- Content
|
//s1 -- Content
|
||||||
@@ -43,15 +43,15 @@ class AstromicRadioSelector<T> extends StatefulWidget {
|
|||||||
required this.items,
|
required this.items,
|
||||||
this.onChanged,
|
this.onChanged,
|
||||||
//s1 -- Configuration
|
//s1 -- Configuration
|
||||||
required this.configurations,
|
required this.configuration,
|
||||||
//s1 -- Style
|
//s1 -- Style
|
||||||
required this.itemSpacing,
|
required this.itemSpacing,
|
||||||
//s1 -- Content
|
//s1 -- Content
|
||||||
required this.itemBuilder,
|
required this.itemBuilder,
|
||||||
this.disabledItemBuilder,
|
this.disabledItemBuilder,
|
||||||
}) : assert(configurations.isNullable || initialSelectedValue != null, 'You need to supply an initial value if not nullable!'),
|
}) : assert(configuration.isNullable || initialSelectedValue != null, 'You need to supply an initial value if not nullable!'),
|
||||||
assert(
|
assert(
|
||||||
configurations.isNullable ||
|
configuration.isNullable ||
|
||||||
(items
|
(items
|
||||||
.map(
|
.map(
|
||||||
(e) => e.$1,
|
(e) => e.$1,
|
||||||
@@ -135,10 +135,10 @@ class _AstromicRadioSelectorState<T> extends State<AstromicRadioSelector<T>> {
|
|||||||
bool isSelected = currentItem.$1 == selectedItem;
|
bool isSelected = currentItem.$1 == selectedItem;
|
||||||
//
|
//
|
||||||
return !isEnabled && widget.disabledItemBuilder != null
|
return !isEnabled && widget.disabledItemBuilder != null
|
||||||
? widget.configurations.withExpandedSpace && widget.configurations.axis == Axis.horizontal
|
? widget.configuration.withExpandedSpace && widget.configuration.axis == Axis.horizontal
|
||||||
? Expanded(child: widget.disabledItemBuilder!(currentItem.$1))
|
? Expanded(child: widget.disabledItemBuilder!(currentItem.$1))
|
||||||
: widget.disabledItemBuilder!(currentItem.$1)
|
: widget.disabledItemBuilder!(currentItem.$1)
|
||||||
: widget.configurations.withExpandedSpace && widget.configurations.axis == Axis.horizontal
|
: widget.configuration.withExpandedSpace && widget.configuration.axis == Axis.horizontal
|
||||||
? Expanded(child: widget.itemBuilder(currentItem.$1, onTap: isEnabled ? () => _onTap(currentItem.$1) : null, isSelected: isSelected))
|
? Expanded(child: widget.itemBuilder(currentItem.$1, onTap: isEnabled ? () => _onTap(currentItem.$1) : null, isSelected: isSelected))
|
||||||
: widget.itemBuilder(currentItem.$1, onTap: isEnabled ? () => _onTap(currentItem.$1) : null, isSelected: isSelected);
|
: widget.itemBuilder(currentItem.$1, onTap: isEnabled ? () => _onTap(currentItem.$1) : null, isSelected: isSelected);
|
||||||
}).toList();
|
}).toList();
|
||||||
@@ -149,7 +149,7 @@ class _AstromicRadioSelectorState<T> extends State<AstromicRadioSelector<T>> {
|
|||||||
return
|
return
|
||||||
// widget.configurations.withExpandedSpace
|
// widget.configurations.withExpandedSpace
|
||||||
// ?
|
// ?
|
||||||
widget.configurations.axis == Axis.horizontal
|
widget.configuration.axis == Axis.horizontal
|
||||||
? separatedRow(
|
? separatedRow(
|
||||||
baseChildren,
|
baseChildren,
|
||||||
AstromicSpacing.hsb(widget.itemSpacing),
|
AstromicSpacing.hsb(widget.itemSpacing),
|
||||||
@@ -157,7 +157,7 @@ class _AstromicRadioSelectorState<T> extends State<AstromicRadioSelector<T>> {
|
|||||||
: separatedColumn(
|
: separatedColumn(
|
||||||
baseChildren,
|
baseChildren,
|
||||||
AstromicSpacing.vsb(widget.itemSpacing),
|
AstromicSpacing.vsb(widget.itemSpacing),
|
||||||
widget.configurations.withExpandedSpace,
|
widget.configuration.withExpandedSpace,
|
||||||
);
|
);
|
||||||
// : Wrap(
|
// : Wrap(
|
||||||
// direction: widget.configurations.axis,
|
// direction: widget.configurations.axis,
|
||||||
|
|||||||
Reference in New Issue
Block a user