mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-01-22 10:34:32 +00:00
custom dropdown button
This commit is contained in:
parent
c0ce75918c
commit
4d083fd3bc
2 changed files with 238 additions and 0 deletions
231
lib/widgets/custom_buttons/dropdown_button.dart
Normal file
231
lib/widgets/custom_buttons/dropdown_button.dart
Normal file
|
@ -0,0 +1,231 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:stackwallet/utilities/assets.dart';
|
||||||
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
|
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||||
|
import 'package:stackwallet/widgets/animated_widgets/rotate_icon.dart';
|
||||||
|
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
||||||
|
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||||
|
|
||||||
|
class JDropdownButton<T> extends StatefulWidget {
|
||||||
|
const JDropdownButton({
|
||||||
|
Key? key,
|
||||||
|
this.label,
|
||||||
|
required this.items,
|
||||||
|
this.width,
|
||||||
|
this.onSelectionChanged,
|
||||||
|
this.groupValue,
|
||||||
|
this.redrawOnScreenSizeChanged = false,
|
||||||
|
this.showIcon = false,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final String? label;
|
||||||
|
final double? width;
|
||||||
|
final void Function(T?)? onSelectionChanged;
|
||||||
|
final T? groupValue;
|
||||||
|
final Set<T> items;
|
||||||
|
final bool showIcon;
|
||||||
|
|
||||||
|
/// setting this to true should be done carefully
|
||||||
|
final bool redrawOnScreenSizeChanged;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<JDropdownButton<T>> createState() => _JDropdownButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _JDropdownButtonState<T> extends State<JDropdownButton<T>> {
|
||||||
|
final _key = GlobalKey();
|
||||||
|
final _rotateIconController = RotateIconController();
|
||||||
|
|
||||||
|
bool _isOpen = false;
|
||||||
|
|
||||||
|
OverlayEntry? _entry;
|
||||||
|
|
||||||
|
void close() {
|
||||||
|
if (_isOpen) {
|
||||||
|
_rotateIconController.reverse?.call();
|
||||||
|
_entry?.remove();
|
||||||
|
_isOpen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void open() {
|
||||||
|
final size = (_key.currentContext!.findRenderObject() as RenderBox).size;
|
||||||
|
_entry = OverlayEntry(
|
||||||
|
builder: (_) {
|
||||||
|
final position = (_key.currentContext!.findRenderObject() as RenderBox)
|
||||||
|
.localToGlobal(Offset.zero);
|
||||||
|
|
||||||
|
if (widget.redrawOnScreenSizeChanged) {
|
||||||
|
// trigger rebuild
|
||||||
|
MediaQuery.of(context).size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: close,
|
||||||
|
child: _JDropdownButtonMenu<T>(
|
||||||
|
size: size,
|
||||||
|
position: position,
|
||||||
|
items: widget.items
|
||||||
|
.map(
|
||||||
|
(e) => _JDropdownButtonItem<T>(
|
||||||
|
value: e,
|
||||||
|
groupValue: widget.groupValue,
|
||||||
|
onSelected: (T value) {
|
||||||
|
widget.onSelectionChanged?.call(value);
|
||||||
|
close();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
_rotateIconController.forward?.call();
|
||||||
|
Overlay.of(context, rootOverlay: true).insert(_entry!);
|
||||||
|
_isOpen = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (widget.redrawOnScreenSizeChanged && _isOpen) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
_entry?.markNeedsBuild();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return SecondaryButton(
|
||||||
|
key: _key,
|
||||||
|
buttonHeight: ButtonHeight.l,
|
||||||
|
trailingIcon: widget.showIcon
|
||||||
|
? RotateIcon(
|
||||||
|
icon: SvgPicture.asset(
|
||||||
|
Assets.svg.chevronDown,
|
||||||
|
width: 10,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.buttonTextSecondary,
|
||||||
|
),
|
||||||
|
curve: Curves.easeInOutCubic,
|
||||||
|
controller: _rotateIconController,
|
||||||
|
animationDurationMultiplier: 0.1,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
width: widget.width,
|
||||||
|
label: widget.label ?? widget.groupValue.toString(),
|
||||||
|
onPressed: _isOpen ? close : open,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
class _JDropdownButtonMenu<T> extends StatefulWidget {
|
||||||
|
const _JDropdownButtonMenu(
|
||||||
|
{Key? key,
|
||||||
|
required this.items,
|
||||||
|
required this.size,
|
||||||
|
required this.position})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
final List<_JDropdownButtonItem<T>> items;
|
||||||
|
final Size size;
|
||||||
|
final Offset position;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_JDropdownButtonMenu<T>> createState() => _JDropdownButtonMenuState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _JDropdownButtonMenuState<T> extends State<_JDropdownButtonMenu<T>> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
color: Colors.black.withOpacity(0.2),
|
||||||
|
// child: widget.content,
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
top: widget.size.height + widget.position.dy + 10,
|
||||||
|
left: widget.position.dx,
|
||||||
|
width: widget.size.width,
|
||||||
|
child: RoundedWhiteContainer(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
radiusMultiplier: 2.5,
|
||||||
|
boxShadow: [
|
||||||
|
Theme.of(context).extension<StackColors>()!.standardBoxShadow,
|
||||||
|
],
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
height: 20,
|
||||||
|
),
|
||||||
|
...widget.items,
|
||||||
|
const SizedBox(
|
||||||
|
height: 20,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
class _JDropdownButtonItem<T> extends StatelessWidget {
|
||||||
|
const _JDropdownButtonItem({
|
||||||
|
Key? key,
|
||||||
|
required this.value,
|
||||||
|
required this.groupValue,
|
||||||
|
required this.onSelected,
|
||||||
|
this.height = 53,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final T value;
|
||||||
|
final T? groupValue;
|
||||||
|
final double height;
|
||||||
|
final void Function(T) onSelected;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return RawMaterialButton(
|
||||||
|
fillColor: groupValue == value
|
||||||
|
? Theme.of(context).extension<StackColors>()!.textFieldDefaultBG
|
||||||
|
: Colors.transparent,
|
||||||
|
elevation: 0,
|
||||||
|
focusElevation: 0,
|
||||||
|
hoverElevation: 0,
|
||||||
|
highlightElevation: 0,
|
||||||
|
disabledElevation: 0,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
onPressed: () => onSelected(value),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
value.toString(),
|
||||||
|
style: STextStyles.desktopTextExtraSmall(context).copyWith(
|
||||||
|
color: Theme.of(context).extension<StackColors>()!.textDark,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,6 +13,7 @@ class SecondaryButton extends StatelessWidget {
|
||||||
this.height,
|
this.height,
|
||||||
this.label,
|
this.label,
|
||||||
this.icon,
|
this.icon,
|
||||||
|
this.trailingIcon,
|
||||||
this.onPressed,
|
this.onPressed,
|
||||||
this.enabled = true,
|
this.enabled = true,
|
||||||
this.buttonHeight,
|
this.buttonHeight,
|
||||||
|
@ -25,6 +26,7 @@ class SecondaryButton extends StatelessWidget {
|
||||||
final VoidCallback? onPressed;
|
final VoidCallback? onPressed;
|
||||||
final bool enabled;
|
final bool enabled;
|
||||||
final Widget? icon;
|
final Widget? icon;
|
||||||
|
final Widget? trailingIcon;
|
||||||
final ButtonHeight? buttonHeight;
|
final ButtonHeight? buttonHeight;
|
||||||
final double iconSpacing;
|
final double iconSpacing;
|
||||||
|
|
||||||
|
@ -178,6 +180,11 @@ class SecondaryButton extends StatelessWidget {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
if (trailingIcon != null)
|
||||||
|
SizedBox(
|
||||||
|
width: iconSpacing,
|
||||||
|
),
|
||||||
|
if (trailingIcon != null) trailingIcon!,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
Loading…
Reference in a new issue