[DEV] Done with the Chip selector

This commit is contained in:
2024-05-15 16:44:34 +03:00
parent 3f72e040a0
commit 2f8b4e6fb2
5 changed files with 292 additions and 10 deletions

View File

@@ -1,7 +1,8 @@
//s2 Core Packages Imports
import 'package:flutter/widgets.dart';
import 'src/configuration.dart';
import 'src/radio.selector.dart';
import 'src/chip.selector.dart';
import 'src/configuration.dart';
class AstromicSelectors {
//S1 -- Radio
@@ -21,7 +22,7 @@ class AstromicSelectors {
initialSelectedValue: initialSelectedValue,
onChanged: onChanged,
//
configurations: configurations ?? const AstromicSelectorConfiguration(),
configuration: configurations ?? const AstromicSelectorConfiguration(),
//
itemSpacing: itemSpacing ?? 8.0,
//
@@ -29,4 +30,37 @@ class AstromicSelectors {
disabledItemBuilder: disabledItemBuilder,
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,
);
}

View File

@@ -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();
}
}

View File

@@ -5,20 +5,28 @@ class AstromicSelectorConfiguration {
final Axis axis;
final bool isNullable;
//s1 Chip Specific
final bool isWrap;
final WrapAlignment wrapMainAllignment;
final WrapCrossAlignment wrapCrossAllignment;
final bool isConsequent;
final bool withClearButton;
final int maxSelectedItems;
final int crossAxisCount;
final double? fixedRowHeight;
//s1 Radio Specific
final bool withExpandedSpace;
const AstromicSelectorConfiguration({
this.axis = Axis.horizontal,
this.isNullable = true,
//
this.isWrap = true,
this.wrapMainAllignment = WrapAlignment.start,
this.wrapCrossAllignment = WrapCrossAlignment.center,
this.isConsequent = false,
this.withClearButton = false,
this.maxSelectedItems = 10000,
this.crossAxisCount = 3,
this.fixedRowHeight,
//
this.withExpandedSpace = false,
});
@@ -26,20 +34,28 @@ class AstromicSelectorConfiguration {
AstromicSelectorConfiguration copyWith({
Axis? axis,
bool? isNullable,
bool? isWrap,
WrapAlignment? wrapMainAllignment,
WrapCrossAlignment? wrapCrossAllignment,
bool? isConsequent,
bool? withClearButton,
int? maxSelectedItems,
int? crossAxisCount,
bool? withExpandedSpace,
double? fixedRowHeight,
}) {
return AstromicSelectorConfiguration(
axis: axis ?? this.axis,
isNullable: isNullable ?? this.isNullable,
isWrap: isWrap ?? this.isWrap,
wrapMainAllignment: wrapMainAllignment ?? this.wrapMainAllignment,
wrapCrossAllignment: wrapCrossAllignment ?? this.wrapCrossAllignment,
isConsequent: isConsequent ?? this.isConsequent,
withClearButton: withClearButton ?? this.withClearButton,
maxSelectedItems: maxSelectedItems ?? this.maxSelectedItems,
crossAxisCount: crossAxisCount ?? this.crossAxisCount,
withExpandedSpace: withExpandedSpace ?? this.withExpandedSpace,
fixedRowHeight: fixedRowHeight ?? this.fixedRowHeight,
);
}
}

View File

@@ -26,7 +26,7 @@ class AstromicRadioSelector<T> extends StatefulWidget {
final T? initialSelectedValue;
final Function(T selectedItem)? onChanged;
//s1 -- Configuration
final AstromicSelectorConfiguration configurations;
final AstromicSelectorConfiguration configuration;
//s1 -- Style
final double itemSpacing;
//s1 -- Content
@@ -43,15 +43,15 @@ class AstromicRadioSelector<T> extends StatefulWidget {
required this.items,
this.onChanged,
//s1 -- Configuration
required this.configurations,
required this.configuration,
//s1 -- Style
required this.itemSpacing,
//s1 -- Content
required this.itemBuilder,
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(
configurations.isNullable ||
configuration.isNullable ||
(items
.map(
(e) => e.$1,
@@ -135,10 +135,10 @@ class _AstromicRadioSelectorState<T> extends State<AstromicRadioSelector<T>> {
bool isSelected = currentItem.$1 == selectedItem;
//
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))
: 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))
: widget.itemBuilder(currentItem.$1, onTap: isEnabled ? () => _onTap(currentItem.$1) : null, isSelected: isSelected);
}).toList();
@@ -149,7 +149,7 @@ class _AstromicRadioSelectorState<T> extends State<AstromicRadioSelector<T>> {
return
// widget.configurations.withExpandedSpace
// ?
widget.configurations.axis == Axis.horizontal
widget.configuration.axis == Axis.horizontal
? separatedRow(
baseChildren,
AstromicSpacing.hsb(widget.itemSpacing),
@@ -157,7 +157,7 @@ class _AstromicRadioSelectorState<T> extends State<AstromicRadioSelector<T>> {
: separatedColumn(
baseChildren,
AstromicSpacing.vsb(widget.itemSpacing),
widget.configurations.withExpandedSpace,
widget.configuration.withExpandedSpace,
);
// : Wrap(
// direction: widget.configurations.axis,