books
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
typedef OnDropSelected = void Function(int index);
|
||||
|
||||
class DropSelectableWidget extends StatefulWidget {
|
||||
final List<String> data;
|
||||
final OnDropSelected? onDropSelected;
|
||||
final Color disableColor;
|
||||
final double iconSize;
|
||||
final double height;
|
||||
final double width;
|
||||
final double fontSize;
|
||||
final String value;
|
||||
|
||||
const DropSelectableWidget(
|
||||
{Key? key,
|
||||
this.data = const [],
|
||||
this.onDropSelected,
|
||||
this.disableColor = Colors.black,
|
||||
this.iconSize = 24,
|
||||
required this.value,
|
||||
this.height = 30,
|
||||
this.width = 200,
|
||||
this.fontSize = 14,
|
||||
})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
_DropSelectableWidgetState createState() => _DropSelectableWidgetState();
|
||||
}
|
||||
|
||||
class _DropSelectableWidgetState extends State<DropSelectableWidget>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late FocusNode _node;
|
||||
bool _focused = false;
|
||||
late FocusAttachment _nodeAttachment;
|
||||
OverlayEntry? _overlayEntry;
|
||||
late AnimationController _ctrl;
|
||||
late Animation<double> animation;
|
||||
final LayerLink layerLink = LayerLink();
|
||||
|
||||
// int _selectedIndex = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_ctrl = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
|
||||
animation = Tween<double>(begin: 0, end: pi).animate(_ctrl);
|
||||
_node = FocusNode()
|
||||
..addListener(() {
|
||||
if (_node.hasFocus != _focused) {
|
||||
if (!_focused) {
|
||||
_ctrl.forward();
|
||||
_showOverlay();
|
||||
} else {
|
||||
_hideOverlay();
|
||||
_ctrl.reverse();
|
||||
}
|
||||
setState(() {
|
||||
_focused = _node.hasFocus;
|
||||
});
|
||||
}
|
||||
});
|
||||
_nodeAttachment = _node.attach(context);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_node.dispose();
|
||||
_ctrl.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_nodeAttachment.reparent();
|
||||
return TapRegion(
|
||||
groupId: 'selector',
|
||||
onTapOutside: (_){
|
||||
_node.unfocus();
|
||||
},
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
if (_focused) {
|
||||
_node.unfocus();
|
||||
} else {
|
||||
_node.requestFocus();
|
||||
}
|
||||
},
|
||||
child: CompositedTransformTarget(
|
||||
link: layerLink,
|
||||
child: buildTarget(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showOverlay() {
|
||||
_overlayEntry = _createOverlayEntry();
|
||||
Overlay.of(context).insert(_overlayEntry!);
|
||||
}
|
||||
|
||||
void _hideOverlay() {
|
||||
_overlayEntry?.remove();
|
||||
}
|
||||
|
||||
Widget buildTarget() {
|
||||
return Container(
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
padding: const EdgeInsets.only(left: 10, right: 10),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(
|
||||
color: _focused ? Colors.blue : widget.disableColor,
|
||||
)),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text( widget.value ,style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: widget.fontSize
|
||||
),),
|
||||
AnimatedBuilder(
|
||||
animation: animation,
|
||||
builder: (_, child) => Transform.rotate(
|
||||
angle: animation.value,
|
||||
child: child,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.keyboard_arrow_down,
|
||||
size: widget.iconSize,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
OverlayEntry _createOverlayEntry() => OverlayEntry(
|
||||
builder: (BuildContext context) => UnconstrainedBox(
|
||||
child: CompositedTransformFollower(
|
||||
link: layerLink,
|
||||
targetAnchor: Alignment.bottomCenter,
|
||||
followerAnchor: Alignment.topCenter,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 4.0),
|
||||
child: Material(
|
||||
shape: const RoundedRectangleBorder(
|
||||
side: BorderSide.none,
|
||||
borderRadius: BorderRadius.all(Radius.circular(5))),
|
||||
elevation: 1,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
child: Container(
|
||||
height: 200,
|
||||
// alignment: Alignment.center,
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xffDAE3FF),
|
||||
),
|
||||
// padding: const EdgeInsets.only(top: 5),
|
||||
width: widget.width,
|
||||
child: CupertinoScrollbar(
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
// shrinkWrap: true,
|
||||
itemCount: widget.data.length,
|
||||
itemBuilder: _buildItem),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Widget _buildItem(BuildContext context, int index) {
|
||||
|
||||
bool active = widget.data[index] == widget.value;
|
||||
return TapRegion(
|
||||
groupId: 'selector',
|
||||
child: Material(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
if (!active) widget.onDropSelected?.call(index);
|
||||
_overlayEntry?.markNeedsBuild();
|
||||
_node.unfocus();
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
color: active ? Colors.blue.withOpacity(0.2) : Colors.transparent,
|
||||
child: Text(widget.data[index],style: TextStyle(fontSize: widget.fontSize),)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
117
packages/components/lib/toly_ui/popable/pop_menu.dart
Normal file
117
packages/components/lib/toly_ui/popable/pop_menu.dart
Normal file
@@ -0,0 +1,117 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'toly_pop_menu.dart';
|
||||
|
||||
class PopPanel<T> extends StatefulWidget {
|
||||
final Widget child;
|
||||
final Size size;
|
||||
final Offset offset;
|
||||
|
||||
/// Builds the context menu.
|
||||
final Widget panel;
|
||||
|
||||
const PopPanel({
|
||||
super.key,
|
||||
required this.child,
|
||||
required this.panel,
|
||||
this.offset = Offset.zero,
|
||||
this.size = const Size(250, 0),
|
||||
});
|
||||
|
||||
@override
|
||||
State<PopPanel> createState() => _PopPanelState();
|
||||
}
|
||||
|
||||
class _PopPanelState<T> extends State<PopPanel<T>> {
|
||||
final ContextMenuController _contextMenuController = ContextMenuController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: _onTap,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
|
||||
void _show() {
|
||||
TolyPopupMenuEntry<T> item = CustomTolyMenuItem(
|
||||
widget.panel,
|
||||
size: widget.size,
|
||||
);
|
||||
|
||||
final RenderBox button = context.findRenderObject()! as RenderBox;
|
||||
final RenderBox overlay =
|
||||
Navigator.of(context).overlay!.context.findRenderObject()! as RenderBox;
|
||||
final Offset offset = Offset(button.size.width / 2, button.size.height)+widget.offset;
|
||||
final RelativeRect position = RelativeRect.fromRect(
|
||||
Rect.fromPoints(
|
||||
button.localToGlobal(offset, ancestor: overlay),
|
||||
button.localToGlobal(button.size.bottomRight(Offset.zero) + offset,
|
||||
ancestor: overlay),
|
||||
),
|
||||
Offset.zero & overlay.size,
|
||||
);
|
||||
showTolyMenu<T?>(
|
||||
elevation: 0,
|
||||
color: Colors.transparent,
|
||||
context: context,
|
||||
items: [item],
|
||||
position: position,
|
||||
).then<void>((T? newValue) {
|
||||
if (!mounted) {
|
||||
return null;
|
||||
}
|
||||
if (newValue == null) {
|
||||
// widget.onCanceled?.call();
|
||||
return null;
|
||||
}
|
||||
// widget.onSelected?.call(newValue);
|
||||
});
|
||||
}
|
||||
|
||||
void _onTap() {
|
||||
_show();
|
||||
}
|
||||
}
|
||||
|
||||
class CustomTolyMenuItem<T> extends TolyPopupMenuEntry<T> {
|
||||
final Size size;
|
||||
final Widget content;
|
||||
|
||||
const CustomTolyMenuItem(this.content, {super.key, required this.size});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _CustomTolyMenuItemState();
|
||||
|
||||
@override
|
||||
// TODO: implement height
|
||||
double get height => kMinInteractiveDimension;
|
||||
|
||||
@override
|
||||
bool represents(value) => true;
|
||||
}
|
||||
|
||||
class _CustomTolyMenuItemState<T> extends State<CustomTolyMenuItem> {
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant CustomTolyMenuItem oldWidget) {
|
||||
print('============_CustomTolyMenuItemState#didUpdateWidget');
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: widget.size.width,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
boxShadow: [
|
||||
BoxShadow(color: Colors.black.withOpacity(0.1), blurRadius: 6)
|
||||
]),
|
||||
// padding: EdgeInsets.symmetric(horizontal: 12, vertical: 16),
|
||||
child: widget.content,
|
||||
// color: Colors.lightBlueAccent,
|
||||
);
|
||||
}
|
||||
}
|
||||
52
packages/components/lib/toly_ui/popable/popover.dart
Normal file
52
packages/components/lib/toly_ui/popable/popover.dart
Normal file
@@ -0,0 +1,52 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// A builder that includes an Offset to draw the context menu at.
|
||||
typedef ContextMenuBuilder = Widget Function(BuildContext context, Offset offset);
|
||||
|
||||
|
||||
class Popover extends StatefulWidget {
|
||||
final Widget child;
|
||||
/// Builds the context menu.
|
||||
final ContextMenuBuilder contextMenuBuilder;
|
||||
|
||||
const Popover({
|
||||
super.key,
|
||||
required this.child,
|
||||
required this.contextMenuBuilder,
|
||||
});
|
||||
|
||||
@override
|
||||
State<Popover> createState() => _PopoverState();
|
||||
}
|
||||
|
||||
class _PopoverState extends State<Popover> {
|
||||
|
||||
final ContextMenuController _contextMenuController = ContextMenuController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: _onTap,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
|
||||
void _show(Offset position) {
|
||||
_contextMenuController.show(
|
||||
context: context,
|
||||
contextMenuBuilder: (BuildContext context) {
|
||||
return widget.contextMenuBuilder(context, position);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
final double width = 200;
|
||||
|
||||
void _onTap() {
|
||||
final RenderBox button = context.findRenderObject()! as RenderBox;
|
||||
final RenderBox overlay = Navigator.of(context).overlay!.context.findRenderObject()! as RenderBox;
|
||||
final Offset offset = Offset(button.size.width / 2, button.size.height);
|
||||
Offset boxOffset = button.localToGlobal(offset, ancestor: overlay);
|
||||
_show(boxOffset.translate(-width, 0));
|
||||
}
|
||||
}
|
||||
1433
packages/components/lib/toly_ui/popable/toly_pop_menu.dart
Normal file
1433
packages/components/lib/toly_ui/popable/toly_pop_menu.dart
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user