first commit

This commit is contained in:
2026-02-07 15:57:09 +07:00
commit 157096f164
1153 changed files with 415766 additions and 0 deletions

View File

@@ -0,0 +1,217 @@
import 'dart:ui';
import 'package:mobile_pos/generated/l10n.dart' as lang;
import 'package:flutter/material.dart';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:mobile_pos/Screens/Products/Widgets/selected_button.dart';
import 'package:mobile_pos/constant.dart';
class AcnooMultiSelectDropdown<T> extends StatefulWidget {
AcnooMultiSelectDropdown({
super.key,
this.decoration,
this.menuItemStyleData,
this.buttonStyleData,
this.iconStyleData,
this.dropdownStyleData,
required this.items,
this.values,
this.onChanged,
required this.labelText,
}) : assert(
items.isEmpty ||
values == null ||
items.where((item) {
return values.contains(item.value);
}).length ==
values.length,
"There should be exactly one item with [AcnooMultiSelectDropdown]'s value in the items list. "
'Either zero or 2 or more [MultiSelectDropdownMenuItem]s were detected with the same value',
);
final List<MultiSelectDropdownMenuItem<T?>> items;
final List<T?>? values;
final void Function(List<T>? values)? onChanged;
final InputDecoration? decoration;
final MenuItemStyleData? menuItemStyleData;
final ButtonStyleData? buttonStyleData;
final IconStyleData? iconStyleData;
final DropdownStyleData? dropdownStyleData;
final String labelText;
@override
State<AcnooMultiSelectDropdown<T>> createState() => _AcnooMultiSelectDropdownState<T>();
}
class _AcnooMultiSelectDropdownState<T> extends State<AcnooMultiSelectDropdown<T>> {
bool isOpen = false;
void listenMenuChange(bool value) {
setState(() {
isOpen = value;
if (!value) {
widget.onChanged?.call(
selectedItems.map((e) => e.value!).toList(),
);
}
});
}
late List<MultiSelectDropdownMenuItem<T?>> selectedItems;
@override
void initState() {
super.initState();
selectedItems = widget.items.where((element) => widget.values?.contains(element.value) ?? false).toList();
}
@override
Widget build(BuildContext context) {
final _theme = Theme.of(context);
return DropdownButtonFormField2<T>(
decoration: (widget.decoration ?? const InputDecoration()).copyWith(
labelText: widget.labelText,
hintText: '',
),
menuItemStyleData: widget.menuItemStyleData ?? const MenuItemStyleData(),
buttonStyleData: widget.buttonStyleData ?? const ButtonStyleData(),
iconStyleData: widget.iconStyleData ?? const IconStyleData(),
dropdownStyleData: widget.dropdownStyleData ?? const DropdownStyleData(),
onMenuStateChange: listenMenuChange,
customButton: _buildCustomButton(context),
items: widget.items.map((item) {
return DropdownMenuItem<T>(
value: item.value,
enabled: false,
child: _buildMenuItem(context, item, _theme),
);
}).toList(),
onChanged: (_) {},
);
}
// --------------- CHANGE IS HERE ---------------- //
Widget _buildCustomButton(BuildContext context) {
const _itemPadding = EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
);
if (selectedItems.isEmpty) {
final _iconWidget = widget.iconStyleData?.icon ?? Icon(Icons.keyboard_arrow_down_outlined);
return Padding(
padding: _itemPadding,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
widget.decoration?.hintText ?? lang.S.of(context).selectItems,
style: widget.decoration?.hintStyle,
),
_iconWidget,
],
),
);
}
return ScrollConfiguration(
behavior: const ScrollBehavior().copyWith(
dragDevices: {
PointerDeviceKind.mouse,
PointerDeviceKind.trackpad,
PointerDeviceKind.touch,
},
),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: selectedItems.reversed.map((item) {
return Padding(
padding: const EdgeInsets.only(right: 10),
child: SelectedItemButton(
padding: _itemPadding,
labelText: item.labelText,
onTap: () {
// 1. Remove item from local state
setState(() {
selectedItems.remove(item);
});
// 2. Trigger the onChanged callback immediately
widget.onChanged?.call(
selectedItems.map((e) => e.value!).toList(),
);
},
),
);
}).toList(),
),
),
);
}
// ----------------------------------------------- //
Widget _buildMenuItem(
BuildContext context,
MultiSelectDropdownMenuItem<T?> item,
ThemeData _theme,
) {
return StatefulBuilder(
builder: (context, itemState) {
final _isSelected = selectedItems.contains(item);
return InkWell(
onTap: () {
_isSelected ? selectedItems.remove(item) : selectedItems.add(item);
widget.onChanged?.call(
selectedItems.map((e) => e.value!).toList(),
);
setState(() {});
itemState(() {});
},
child: Container(
constraints: const BoxConstraints(minHeight: 48),
alignment: AlignmentDirectional.center,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(item.labelText),
),
if (_isSelected)
Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.check_circle,
color: kMainColor,
),
const SizedBox(width: 8),
],
),
],
),
),
);
},
);
}
}
class MultiSelectDropdownMenuItem<T> {
MultiSelectDropdownMenuItem({
required this.labelText,
this.value,
});
final String labelText;
final T? value;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is MultiSelectDropdownMenuItem<T> &&
runtimeType == other.runtimeType &&
labelText == other.labelText &&
value == other.value;
@override
int get hashCode => labelText.hashCode ^ value.hashCode;
}

View File

@@ -0,0 +1,228 @@
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:mobile_pos/constant.dart';
class AcnooDropdownStyle {
AcnooDropdownStyle(this.context);
final BuildContext context;
// Theme
ThemeData get _theme => Theme.of(context);
bool get _isDark => _theme.brightness == Brightness.dark;
// Button Style
ButtonStyleData get buttonStyle => const ButtonStyleData(width: 0);
// Dropdown Style
AcnooDropdownStyleData get dropdownStyle {
return AcnooDropdownStyleData(
maxHeight: 300,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
color: _theme.colorScheme.primaryContainer,
),
);
}
// Icon Style
AcnooDropdownIconData get iconStyle {
return AcnooDropdownIconData(
icon: Icon(
Icons.keyboard_arrow_down,
color: _isDark ? Colors.white : kMainColor,
),
);
}
// Menu Style
AcnooDropdownMenuItemStyleData get menuItemStyle {
return AcnooDropdownMenuItemStyleData(
overlayColor: WidgetStateProperty.all<Color>(
kMainColor.withValues(alpha: 0.25),
),
selectedMenuItemBuilder: (context, child) => DecoratedBox(
decoration: BoxDecoration(
color: kMainColor.withValues(alpha: 0.125),
),
child: child,
),
);
}
MenuItemStyleData get multiSelectMenuItemStyle {
return MenuItemStyleData(
overlayColor: WidgetStateProperty.all<Color>(
kMainColor.withValues(alpha: 0.25),
),
selectedMenuItemBuilder: (context, child) => DecoratedBox(
decoration: BoxDecoration(
color: kMainColor.withValues(alpha: 0.125),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
child,
Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.check_circle,
color: kMainColor,
),
const SizedBox(width: 8),
],
),
],
),
),
);
}
// Text Style
TextStyle? get textStyle => _theme.textTheme.bodyLarge;
/*
DropdownMenuItem<T> firstItem<T>({
required String title,
required String actionTitle,
void Function()? onTap,
T? value,
bool enabled = false,
}) {
return DropdownMenuItem(
value: value,
enabled: enabled,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
style: AcnooTextStyle.kSubtitleText.copyWith(
fontSize: 16,
color: AcnooAppColors.k03,
),
),
Text.rich(
TextSpan(
text: actionTitle,
recognizer: TapGestureRecognizer()..onTap = onTap,
),
style: AcnooTextStyle.kSubtitleText.copyWith(
fontSize: 14,
color: AcnooAppColors.kPrimary,
),
),
],
),
);
}
*/
}
@immutable
class AcnooDropdownStyleData extends DropdownStyleData {
const AcnooDropdownStyleData({
super.maxHeight,
super.width,
super.padding,
super.scrollPadding,
super.decoration,
super.elevation,
super.direction,
super.offset,
super.isOverButton,
super.useSafeArea,
super.isFullScreen,
super.useRootNavigator,
super.scrollbarTheme,
super.openInterval,
});
AcnooDropdownStyleData copyWith({
double? maxHeight,
double? width,
EdgeInsetsGeometry? padding,
EdgeInsetsGeometry? scrollPadding,
BoxDecoration? decoration,
int? elevation,
DropdownDirection? direction,
Offset? offset,
bool? isOverButton,
bool? useSafeArea,
bool? isFullScreen,
bool? useRootNavigator,
ScrollbarThemeData? scrollbarTheme,
Interval? openInterval,
}) {
return AcnooDropdownStyleData(
maxHeight: maxHeight ?? this.maxHeight,
width: width ?? this.width,
padding: padding ?? this.padding,
scrollPadding: scrollPadding ?? this.scrollPadding,
decoration: decoration ?? this.decoration,
elevation: elevation ?? this.elevation,
direction: direction ?? this.direction,
offset: offset ?? this.offset,
isOverButton: isOverButton ?? this.isOverButton,
useSafeArea: useSafeArea ?? this.useSafeArea,
isFullScreen: isFullScreen ?? this.useRootNavigator,
useRootNavigator: useRootNavigator ?? this.useRootNavigator,
scrollbarTheme: scrollbarTheme ?? this.scrollbarTheme,
openInterval: openInterval ?? this.openInterval,
);
}
}
@immutable
class AcnooDropdownIconData extends IconStyleData {
const AcnooDropdownIconData({
super.icon,
super.iconDisabledColor,
super.iconEnabledColor,
super.iconSize,
super.openMenuIcon,
});
AcnooDropdownIconData copyWith({
Widget? icon,
Color? iconDisabledColor,
Color? iconEnabledColor,
double? iconSize,
Widget? openMenuIcon,
}) {
return AcnooDropdownIconData(
icon: icon ?? this.icon,
iconDisabledColor: iconDisabledColor ?? this.iconDisabledColor,
iconEnabledColor: iconEnabledColor ?? this.iconEnabledColor,
iconSize: iconSize ?? this.iconSize,
openMenuIcon: openMenuIcon ?? this.openMenuIcon,
);
}
}
@immutable
class AcnooDropdownMenuItemStyleData extends MenuItemStyleData {
const AcnooDropdownMenuItemStyleData({
super.customHeights,
super.height,
super.overlayColor,
super.padding,
super.selectedMenuItemBuilder,
});
AcnooDropdownMenuItemStyleData copyWith({
List<double>? customHeights,
double? height,
Color? overlayColor,
EdgeInsetsGeometry? padding,
Widget Function(BuildContext, Widget)? selectedMenuItemBuilder,
}) {
return AcnooDropdownMenuItemStyleData(
customHeights: customHeights ?? this.customHeights,
height: height ?? this.height,
overlayColor: overlayColor != null ? WidgetStateProperty.all<Color?>(overlayColor) : this.overlayColor,
selectedMenuItemBuilder: selectedMenuItemBuilder ?? this.selectedMenuItemBuilder,
);
}
}

View File

@@ -0,0 +1,58 @@
import 'package:flutter/material.dart';
import 'package:mobile_pos/constant.dart';
class SelectedItemButton extends StatelessWidget {
const SelectedItemButton({
super.key,
required this.labelText,
this.padding,
this.onTap,
this.showCloseButton = true,
});
final String labelText;
final EdgeInsetsGeometry? padding;
final void Function()? onTap;
final bool showCloseButton;
@override
Widget build(BuildContext context) {
final _theme = Theme.of(context);
return Container(
padding: padding ??
const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
decoration: BoxDecoration(
color: kDarkWhite,
borderRadius: BorderRadius.circular(4),
),
child: Text.rich(
TextSpan(
text: labelText,
style: _theme.textTheme.titleSmall?.copyWith(
color: kTitleColor,
),
children: [
if (showCloseButton)
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: Padding(
padding: const EdgeInsets.only(left: 8),
child: InkWell(
onTap: onTap,
child: const Icon(
Icons.close,
size: 12,
color: Colors.black,
),
),
),
),
],
),
style: TextStyle(color: _theme.colorScheme.onPrimary),
),
);
}
}

View File

@@ -0,0 +1,37 @@
// 🐦 Flutter imports:
import 'package:flutter/material.dart';
import 'package:mobile_pos/generated/l10n.dart' as lang;
class TextFieldLabelWrapper extends StatelessWidget {
const TextFieldLabelWrapper({
super.key,
this.labelText,
this.label,
this.labelStyle,
required this.inputField,
});
final String? labelText;
final Widget? label;
final TextStyle? labelStyle;
final Widget inputField;
@override
Widget build(BuildContext context) {
final _theme = Theme.of(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
// Label
if (label == null)
Text(
labelText ?? lang.S.of(context).enterLabelText,
style: labelStyle ?? _theme.inputDecorationTheme.floatingLabelStyle,
)
else
label!,
const SizedBox(height: 8),
inputField,
],
);
}
}