mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: update ui
This commit is contained in:
parent
971a5e245a
commit
db5b3e3bd3
@ -1,3 +1,8 @@
|
||||
# 0.0.3
|
||||
* Support customize UI
|
||||
* Update example
|
||||
* Add AppFlowy style widget
|
||||
|
||||
## 0.0.2
|
||||
|
||||
* Update documentation
|
||||
|
@ -6,30 +6,25 @@ The **appflowy_board** is a package that is used in [AppFlowy](https://github.co
|
||||
**appflowy_board** will be a standard git repository when it becomes stable.
|
||||
## Getting Started
|
||||
|
||||
<p>
|
||||
<img src="" width="180" title="AppFlowyBoard">
|
||||
</p>
|
||||
|
||||
```dart
|
||||
@override
|
||||
void initState() {
|
||||
final column1 = BoardColumnData(id: "1", items: [
|
||||
TextItem("a"),
|
||||
TextItem("b"),
|
||||
TextItem("c"),
|
||||
TextItem("d"),
|
||||
final column1 = BoardColumnData(id: "To Do", items: [
|
||||
TextItem("Card 1"),
|
||||
TextItem("Card 2"),
|
||||
TextItem("Card 3"),
|
||||
TextItem("Card 4"),
|
||||
]);
|
||||
final column2 = BoardColumnData(id: "2", items: [
|
||||
TextItem("1"),
|
||||
TextItem("2"),
|
||||
TextItem("3"),
|
||||
TextItem("4"),
|
||||
TextItem("5"),
|
||||
final column2 = BoardColumnData(id: "In Progress", items: [
|
||||
TextItem("Card 5"),
|
||||
TextItem("Card 6"),
|
||||
]);
|
||||
|
||||
final column3 = BoardColumnData(id: "3", items: [
|
||||
TextItem("A"),
|
||||
TextItem("B"),
|
||||
TextItem("C"),
|
||||
TextItem("D"),
|
||||
]);
|
||||
final column3 = BoardColumnData(id: "Done", items: []);
|
||||
|
||||
boardDataController.addColumn(column1);
|
||||
boardDataController.addColumn(column2);
|
||||
@ -40,25 +35,52 @@ The **appflowy_board** is a package that is used in [AppFlowy](https://github.co
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Board(
|
||||
dataController: boardDataController,
|
||||
background: Container(color: Colors.red),
|
||||
footBuilder: (context, columnData) {
|
||||
return Container(
|
||||
color: Colors.purple,
|
||||
height: 30,
|
||||
);
|
||||
},
|
||||
headerBuilder: (context, columnData) {
|
||||
return Container(
|
||||
color: Colors.yellow,
|
||||
height: 30,
|
||||
);
|
||||
},
|
||||
cardBuilder: (context, item) {
|
||||
return _RowWidget(item: item as TextItem, key: ObjectKey(item));
|
||||
},
|
||||
columnConstraints: const BoxConstraints.tightFor(width: 240),
|
||||
final config = BoardConfig(
|
||||
columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
|
||||
);
|
||||
return Container(
|
||||
color: Colors.white,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
|
||||
child: Board(
|
||||
dataController: boardDataController,
|
||||
footBuilder: (context, columnData) {
|
||||
return AppFlowyColumnFooter(
|
||||
icon: const Icon(Icons.add, size: 20),
|
||||
title: const Text('New'),
|
||||
height: 50,
|
||||
margin: config.columnItemPadding,
|
||||
);
|
||||
},
|
||||
headerBuilder: (context, columnData) {
|
||||
return AppFlowyColumnHeader(
|
||||
icon: const Icon(Icons.lightbulb_circle),
|
||||
title: Text(columnData.id),
|
||||
addIcon: const Icon(Icons.add, size: 20),
|
||||
moreIcon: const Icon(Icons.more_horiz, size: 20),
|
||||
height: 50,
|
||||
margin: config.columnItemPadding,
|
||||
);
|
||||
},
|
||||
cardBuilder: (context, item) {
|
||||
final textItem = item as TextItem;
|
||||
return AppFlowyColumnItemCard(
|
||||
key: ObjectKey(item),
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Text(textItem.s),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
columnConstraints: const BoxConstraints.tightFor(width: 240),
|
||||
config: BoardConfig(
|
||||
columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
```
|
Binary file not shown.
After Width: | Height: | Size: 69 KiB |
@ -32,7 +32,7 @@ class _MyAppState extends State<MyApp> {
|
||||
return MaterialApp(
|
||||
home: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('FlowyBoard example'),
|
||||
title: const Text('AppFlowy Board'),
|
||||
),
|
||||
body: _examples[_currentIndex],
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
@ -43,10 +43,10 @@ class _MyAppState extends State<MyApp> {
|
||||
items: [
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.grid_on, color: _bottomNavigationColor),
|
||||
label: "MultiBoardList"),
|
||||
label: "MultiColumn"),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.grid_on, color: _bottomNavigationColor),
|
||||
label: "SingleBoardList"),
|
||||
label: "SingleColumn"),
|
||||
],
|
||||
onTap: (int index) {
|
||||
setState(() {
|
||||
|
@ -23,26 +23,18 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final column1 = BoardColumnData(id: "1", items: [
|
||||
TextItem("a"),
|
||||
TextItem("b"),
|
||||
TextItem("c"),
|
||||
TextItem("d"),
|
||||
final column1 = BoardColumnData(id: "To Do", items: [
|
||||
TextItem("Card 1"),
|
||||
TextItem("Card 2"),
|
||||
TextItem("Card 3"),
|
||||
TextItem("Card 4"),
|
||||
]);
|
||||
final column2 = BoardColumnData(id: "2", items: [
|
||||
TextItem("1"),
|
||||
TextItem("2"),
|
||||
TextItem("3"),
|
||||
TextItem("4"),
|
||||
TextItem("5"),
|
||||
final column2 = BoardColumnData(id: "In Progress", items: [
|
||||
TextItem("Card 5"),
|
||||
TextItem("Card 6"),
|
||||
]);
|
||||
|
||||
final column3 = BoardColumnData(id: "3", items: [
|
||||
TextItem("A"),
|
||||
TextItem("B"),
|
||||
TextItem("C"),
|
||||
TextItem("D"),
|
||||
]);
|
||||
final column3 = BoardColumnData(id: "Done", items: []);
|
||||
|
||||
boardDataController.addColumn(column1);
|
||||
boardDataController.addColumn(column2);
|
||||
@ -53,40 +45,52 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Board(
|
||||
dataController: boardDataController,
|
||||
background: Container(color: Colors.red),
|
||||
footBuilder: (context, columnData) {
|
||||
return Container(
|
||||
color: Colors.purple,
|
||||
height: 30,
|
||||
);
|
||||
},
|
||||
headerBuilder: (context, columnData) {
|
||||
return Container(
|
||||
color: Colors.yellow,
|
||||
height: 30,
|
||||
);
|
||||
},
|
||||
cardBuilder: (context, item) {
|
||||
return _RowWidget(item: item as TextItem, key: ObjectKey(item));
|
||||
},
|
||||
columnConstraints: const BoxConstraints.tightFor(width: 240),
|
||||
final config = BoardConfig(
|
||||
columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RowWidget extends StatelessWidget {
|
||||
final TextItem item;
|
||||
const _RowWidget({Key? key, required this.item}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
key: ObjectKey(item),
|
||||
height: 60,
|
||||
color: Colors.green,
|
||||
child: Center(child: Text(item.s)),
|
||||
color: Colors.white,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
|
||||
child: Board(
|
||||
dataController: boardDataController,
|
||||
footBuilder: (context, columnData) {
|
||||
return AppFlowyColumnFooter(
|
||||
icon: const Icon(Icons.add, size: 20),
|
||||
title: const Text('New'),
|
||||
height: 50,
|
||||
margin: config.columnItemPadding,
|
||||
);
|
||||
},
|
||||
headerBuilder: (context, columnData) {
|
||||
return AppFlowyColumnHeader(
|
||||
icon: const Icon(Icons.lightbulb_circle),
|
||||
title: Text(columnData.id),
|
||||
addIcon: const Icon(Icons.add, size: 20),
|
||||
moreIcon: const Icon(Icons.more_horiz, size: 20),
|
||||
height: 50,
|
||||
margin: config.columnItemPadding,
|
||||
);
|
||||
},
|
||||
cardBuilder: (context, item) {
|
||||
final textItem = item as TextItem;
|
||||
return AppFlowyColumnItemCard(
|
||||
key: ObjectKey(item),
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Text(textItem.s),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
columnConstraints: const BoxConstraints.tightFor(width: 240),
|
||||
config: BoardConfig(
|
||||
columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -99,3 +103,12 @@ class TextItem extends ColumnItem {
|
||||
@override
|
||||
String get id => s;
|
||||
}
|
||||
|
||||
extension HexColor on Color {
|
||||
static Color fromHex(String hexString) {
|
||||
final buffer = StringBuffer();
|
||||
if (hexString.length == 6 || hexString.length == 7) buffer.write('ff');
|
||||
buffer.write(hexString.replaceFirst('#', ''));
|
||||
return Color(int.parse(buffer.toString(), radix: 16));
|
||||
}
|
||||
}
|
||||
|
@ -2,4 +2,5 @@ library appflowy_board;
|
||||
|
||||
export 'src/widgets/board_column/board_column_data.dart';
|
||||
export 'src/widgets/board_data.dart';
|
||||
export 'src/widgets/styled_widgets/appflowy_styled_widgets.dart';
|
||||
export 'src/widgets/board.dart';
|
||||
|
@ -6,7 +6,7 @@ const DART_LOG = "Dart_LOG";
|
||||
class Log {
|
||||
// static const enableLog = bool.hasEnvironment(DART_LOG);
|
||||
// static final shared = Log();
|
||||
static const enableLog = false;
|
||||
static const enableLog = true;
|
||||
|
||||
static void info(String? message) {
|
||||
if (enableLog) {
|
||||
|
@ -3,23 +3,29 @@ import 'package:provider/provider.dart';
|
||||
import 'board_column/board_column.dart';
|
||||
import 'board_column/board_column_data.dart';
|
||||
import 'board_data.dart';
|
||||
import 'flex/drag_target_inteceptor.dart';
|
||||
import 'flex/reorder_flex.dart';
|
||||
import 'phantom/phantom_controller.dart';
|
||||
import 'reorder_flex/drag_target_inteceptor.dart';
|
||||
import 'reorder_flex/reorder_flex.dart';
|
||||
import 'reorder_phantom/phantom_controller.dart';
|
||||
import '../rendering/board_overlay.dart';
|
||||
|
||||
class BoardConfig {
|
||||
final double cornerRadius;
|
||||
final EdgeInsets columnPadding;
|
||||
final EdgeInsets columnItemPadding;
|
||||
final Color columnBackgroundColor;
|
||||
|
||||
const BoardConfig({
|
||||
this.cornerRadius = 6.0,
|
||||
this.columnPadding = const EdgeInsets.symmetric(horizontal: 8),
|
||||
this.columnItemPadding = const EdgeInsets.symmetric(horizontal: 10),
|
||||
this.columnBackgroundColor = Colors.transparent,
|
||||
});
|
||||
}
|
||||
|
||||
class Board extends StatelessWidget {
|
||||
/// The direction to use as the main axis.
|
||||
final Axis direction = Axis.vertical;
|
||||
|
||||
/// How much space to place between children in a run in the main axis.
|
||||
/// Defaults to 10.0.
|
||||
final double spacing;
|
||||
|
||||
/// How much space to place between the runs themselves in the cross axis.
|
||||
/// Defaults to 0.0.
|
||||
final double runSpacing;
|
||||
|
||||
///
|
||||
final Widget? background;
|
||||
|
||||
@ -40,15 +46,16 @@ class Board extends StatelessWidget {
|
||||
///
|
||||
final BoardPhantomController phantomController;
|
||||
|
||||
final BoardConfig config;
|
||||
|
||||
Board({
|
||||
required this.dataController,
|
||||
required this.cardBuilder,
|
||||
this.spacing = 10.0,
|
||||
this.runSpacing = 0.0,
|
||||
this.background,
|
||||
this.footBuilder,
|
||||
this.headerBuilder,
|
||||
this.columnConstraints = const BoxConstraints(maxWidth: 200),
|
||||
this.config = const BoardConfig(),
|
||||
Key? key,
|
||||
}) : phantomController = BoardPhantomController(delegate: dataController),
|
||||
super(key: key);
|
||||
@ -60,9 +67,9 @@ class Board extends StatelessWidget {
|
||||
child: Consumer<BoardDataController>(
|
||||
builder: (context, notifier, child) {
|
||||
return BoardContent(
|
||||
config: config,
|
||||
dataController: dataController,
|
||||
background: background,
|
||||
spacing: spacing,
|
||||
delegate: phantomController,
|
||||
columnConstraints: columnConstraints,
|
||||
cardBuilder: cardBuilder,
|
||||
@ -84,8 +91,8 @@ class BoardContent extends StatefulWidget {
|
||||
final OnDragEnded? onDragEnded;
|
||||
final BoardDataController dataController;
|
||||
final Widget? background;
|
||||
final double spacing;
|
||||
final ReorderFlexConfig config;
|
||||
final BoardConfig config;
|
||||
final ReorderFlexConfig reorderFlexConfig;
|
||||
final BoxConstraints columnConstraints;
|
||||
|
||||
///
|
||||
@ -101,7 +108,8 @@ class BoardContent extends StatefulWidget {
|
||||
|
||||
final BoardPhantomController phantomController;
|
||||
|
||||
BoardContent({
|
||||
const BoardContent({
|
||||
required this.config,
|
||||
required this.onReorder,
|
||||
required this.delegate,
|
||||
required this.dataController,
|
||||
@ -109,14 +117,13 @@ class BoardContent extends StatefulWidget {
|
||||
this.onDragEnded,
|
||||
this.scrollController,
|
||||
this.background,
|
||||
this.spacing = 10.0,
|
||||
required this.columnConstraints,
|
||||
required this.cardBuilder,
|
||||
this.footBuilder,
|
||||
this.headerBuilder,
|
||||
required this.phantomController,
|
||||
Key? key,
|
||||
}) : config = ReorderFlexConfig(spacing: spacing),
|
||||
}) : reorderFlexConfig = const ReorderFlexConfig(),
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
@ -140,7 +147,7 @@ class _BoardContentState extends State<BoardContent> {
|
||||
|
||||
final reorderFlex = ReorderFlex(
|
||||
key: widget.key,
|
||||
config: widget.config,
|
||||
config: widget.reorderFlexConfig,
|
||||
scrollController: widget.scrollController,
|
||||
onDragStarted: widget.onDragStarted,
|
||||
onReorder: widget.onReorder,
|
||||
@ -154,7 +161,15 @@ class _BoardContentState extends State<BoardContent> {
|
||||
return Stack(
|
||||
alignment: AlignmentDirectional.topStart,
|
||||
children: [
|
||||
if (widget.background != null) widget.background!,
|
||||
if (widget.background != null)
|
||||
Container(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
BorderRadius.circular(widget.config.cornerRadius),
|
||||
),
|
||||
child: widget.background,
|
||||
),
|
||||
reorderFlex,
|
||||
],
|
||||
);
|
||||
@ -173,8 +188,12 @@ class _BoardContentState extends State<BoardContent> {
|
||||
}
|
||||
|
||||
List<Widget> _buildColumns() {
|
||||
final List<Widget> children = widget.dataController.columnDatas.map(
|
||||
(columnData) {
|
||||
final List<Widget> children =
|
||||
widget.dataController.columnDatas.asMap().entries.map(
|
||||
(item) {
|
||||
final columnData = item.value;
|
||||
final columnIndex = item.key;
|
||||
|
||||
final dataSource = _BoardColumnDataSourceImpl(
|
||||
columnId: columnData.id,
|
||||
dataController: widget.dataController,
|
||||
@ -188,6 +207,8 @@ class _BoardContentState extends State<BoardContent> {
|
||||
return ConstrainedBox(
|
||||
constraints: widget.columnConstraints,
|
||||
child: BoardColumnWidget(
|
||||
margin: _marginFromIndex(columnIndex),
|
||||
itemMargin: widget.config.columnItemPadding,
|
||||
headerBuilder: widget.headerBuilder,
|
||||
footBuilder: widget.footBuilder,
|
||||
cardBuilder: widget.cardBuilder,
|
||||
@ -195,7 +216,8 @@ class _BoardContentState extends State<BoardContent> {
|
||||
scrollController: ScrollController(),
|
||||
phantomController: widget.phantomController,
|
||||
onReorder: widget.dataController.moveColumnItem,
|
||||
spacing: 10,
|
||||
cornerRadius: widget.config.cornerRadius,
|
||||
backgroundColor: widget.config.columnBackgroundColor,
|
||||
),
|
||||
);
|
||||
},
|
||||
@ -206,6 +228,22 @@ class _BoardContentState extends State<BoardContent> {
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
EdgeInsets _marginFromIndex(int index) {
|
||||
if (widget.dataController.columnDatas.isEmpty) {
|
||||
return widget.config.columnPadding;
|
||||
}
|
||||
|
||||
if (index == 0) {
|
||||
return EdgeInsets.only(right: widget.config.columnPadding.right);
|
||||
}
|
||||
|
||||
if (index == widget.dataController.columnDatas.length - 1) {
|
||||
return EdgeInsets.only(left: widget.config.columnPadding.left);
|
||||
}
|
||||
|
||||
return widget.config.columnPadding;
|
||||
}
|
||||
}
|
||||
|
||||
class _BoardColumnDataSourceImpl extends BoardColumnDataDataSource {
|
||||
|
@ -3,9 +3,9 @@ import 'dart:collection';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../rendering/board_overlay.dart';
|
||||
import '../../utils/log.dart';
|
||||
import '../phantom/phantom_controller.dart';
|
||||
import '../flex/reorder_flex.dart';
|
||||
import '../flex/drag_target_inteceptor.dart';
|
||||
import '../reorder_phantom/phantom_controller.dart';
|
||||
import '../reorder_flex/reorder_flex.dart';
|
||||
import '../reorder_flex/drag_target_inteceptor.dart';
|
||||
import 'board_column_data.dart';
|
||||
|
||||
typedef OnColumnDragStarted = void Function(int index);
|
||||
@ -79,7 +79,15 @@ class BoardColumnWidget extends StatefulWidget {
|
||||
|
||||
final BoardColumnFooterBuilder? footBuilder;
|
||||
|
||||
BoardColumnWidget({
|
||||
final EdgeInsets margin;
|
||||
|
||||
final EdgeInsets itemMargin;
|
||||
|
||||
final double cornerRadius;
|
||||
|
||||
final Color backgroundColor;
|
||||
|
||||
const BoardColumnWidget({
|
||||
Key? key,
|
||||
this.headerBuilder,
|
||||
this.footBuilder,
|
||||
@ -90,8 +98,11 @@ class BoardColumnWidget extends StatefulWidget {
|
||||
this.onDragStarted,
|
||||
this.scrollController,
|
||||
this.onDragEnded,
|
||||
double? spacing,
|
||||
}) : config = ReorderFlexConfig(spacing: spacing),
|
||||
this.margin = EdgeInsets.zero,
|
||||
this.itemMargin = EdgeInsets.zero,
|
||||
this.cornerRadius = 0.0,
|
||||
this.backgroundColor = Colors.transparent,
|
||||
}) : config = const ReorderFlexConfig(),
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
@ -149,12 +160,25 @@ class _BoardColumnWidgetState extends State<BoardColumnWidget> {
|
||||
children: children,
|
||||
);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
if (header != null) header,
|
||||
Expanded(child: reorderFlex),
|
||||
if (footer != null) footer,
|
||||
],
|
||||
return Container(
|
||||
margin: widget.margin,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
color: widget.backgroundColor,
|
||||
borderRadius: BorderRadius.circular(widget.cornerRadius),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
if (header != null) header,
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: widget.itemMargin,
|
||||
child: reorderFlex,
|
||||
),
|
||||
),
|
||||
if (footer != null) footer,
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
opaque: false,
|
||||
|
@ -3,7 +3,7 @@ import 'dart:collection';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../utils/log.dart';
|
||||
import '../flex/reorder_flex.dart';
|
||||
import '../reorder_flex/reorder_flex.dart';
|
||||
|
||||
abstract class ColumnItem extends ReoderFlexItem {
|
||||
bool get isPhantom => false;
|
||||
@ -92,10 +92,16 @@ class BoardColumnDataController extends ChangeNotifier with EquatableMixin {
|
||||
|
||||
/// Replace the item at index with the [newItem].
|
||||
void replace(int index, ColumnItem newItem) {
|
||||
final removedItem = columnData._items.removeAt(index);
|
||||
columnData._items.insert(index, newItem);
|
||||
Log.debug(
|
||||
'[$BoardColumnDataController] $columnData replace $removedItem with $newItem at $index');
|
||||
if (columnData._items.isEmpty) {
|
||||
columnData._items.add(newItem);
|
||||
Log.debug('[$BoardColumnDataController] $columnData add $newItem');
|
||||
} else {
|
||||
final removedItem = columnData._items.removeAt(index);
|
||||
columnData._items.insert(index, newItem);
|
||||
Log.debug(
|
||||
'[$BoardColumnDataController] $columnData replace $removedItem with $newItem at $index');
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
@ -119,6 +125,6 @@ class BoardColumnData extends ReoderFlexItem with EquatableMixin {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Column$id';
|
||||
return 'Column:[$id]';
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,9 @@ import 'package:equatable/equatable.dart';
|
||||
|
||||
import '../utils/log.dart';
|
||||
import 'board_column/board_column_data.dart';
|
||||
import 'flex/reorder_flex.dart';
|
||||
import 'reorder_flex/reorder_flex.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'phantom/phantom_controller.dart';
|
||||
import 'reorder_phantom/phantom_controller.dart';
|
||||
|
||||
typedef OnMoveColumn = void Function(int fromIndex, int toIndex);
|
||||
|
||||
@ -79,8 +79,11 @@ class BoardDataController extends ChangeNotifier
|
||||
int toColumnIndex,
|
||||
) {
|
||||
final item = columnController(fromColumnId).removeAt(fromColumnIndex);
|
||||
assert(
|
||||
columnController(toColumnId).items[toColumnIndex] is PhantomColumnItem);
|
||||
|
||||
if (columnController(toColumnId).items.length > toColumnIndex) {
|
||||
assert(columnController(toColumnId).items[toColumnIndex]
|
||||
is PhantomColumnItem);
|
||||
}
|
||||
|
||||
columnController(toColumnId).replace(toColumnIndex, item);
|
||||
|
||||
@ -120,7 +123,7 @@ class BoardDataController extends ChangeNotifier
|
||||
columnController.removeAt(index);
|
||||
|
||||
Log.debug(
|
||||
'[$BoardDataController] Column$columnId remove phantom, current count: ${columnController.items.length}');
|
||||
'[$BoardDataController] Column:[$columnId] remove phantom, current count: ${columnController.items.length}');
|
||||
}
|
||||
return isExist;
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../transitions.dart';
|
||||
|
||||
abstract class DragTargetData {
|
||||
@ -65,6 +67,8 @@ class ReorderDragTarget<T extends DragTargetData> extends StatefulWidget {
|
||||
final AnimationController insertAnimationController;
|
||||
final AnimationController deleteAnimationController;
|
||||
|
||||
final bool useMoveAnimation;
|
||||
|
||||
ReorderDragTarget({
|
||||
Key? key,
|
||||
required this.child,
|
||||
@ -74,6 +78,7 @@ class ReorderDragTarget<T extends DragTargetData> extends StatefulWidget {
|
||||
required this.onWillAccept,
|
||||
required this.insertAnimationController,
|
||||
required this.deleteAnimationController,
|
||||
required this.useMoveAnimation,
|
||||
this.onAccept,
|
||||
this.onLeave,
|
||||
this.draggableTargetBuilder,
|
||||
@ -140,7 +145,10 @@ class _ReorderDragTargetState<T extends DragTargetData>
|
||||
data: widget.dragTargetData,
|
||||
ignoringFeedbackSemantics: false,
|
||||
feedback: feedbackBuilder,
|
||||
childWhenDragging: IgnorePointerWidget(child: widget.child),
|
||||
childWhenDragging: IgnorePointerWidget(
|
||||
useIntrinsicSize: !widget.useMoveAnimation,
|
||||
child: widget.child,
|
||||
),
|
||||
onDragStarted: () {
|
||||
_draggingFeedbackSize = widget._indexGlobalKey.currentContext?.size;
|
||||
widget.onDragStarted(
|
||||
@ -174,11 +182,13 @@ class _ReorderDragTargetState<T extends DragTargetData>
|
||||
transform: Matrix4.rotationZ(0),
|
||||
alignment: FractionalOffset.topLeft,
|
||||
child: Material(
|
||||
elevation: 3.0,
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.zero,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: ConstrainedBox(constraints: constraints, child: child),
|
||||
child: ConstrainedBox(
|
||||
constraints: constraints,
|
||||
child: Opacity(opacity: 0.6, child: child),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -254,10 +264,12 @@ class IgnorePointerWidget extends StatelessWidget {
|
||||
final sizedChild = useIntrinsicSize
|
||||
? child
|
||||
: SizedBox(width: 0.0, height: 0.0, child: child);
|
||||
|
||||
final opacity = useIntrinsicSize ? 0.3 : 0.0;
|
||||
return IgnorePointer(
|
||||
ignoring: true,
|
||||
child: Opacity(
|
||||
opacity: 0,
|
||||
opacity: opacity,
|
||||
child: sizedChild,
|
||||
),
|
||||
);
|
||||
@ -282,6 +294,82 @@ class PhantomWidget extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
abstract class DragTargetMovePlaceholderDelegate {
|
||||
void registerPlaceholder(
|
||||
int dragTargetIndex,
|
||||
void Function(int currentDragTargetIndex) callback,
|
||||
);
|
||||
|
||||
void unregisterPlaceholder(int dragTargetIndex);
|
||||
}
|
||||
|
||||
class DragTargeMovePlaceholder extends StatefulWidget {
|
||||
final double height;
|
||||
final Color color;
|
||||
final Color highlightColor;
|
||||
final int dragTargetIndex;
|
||||
final DragTargetMovePlaceholderDelegate delegate;
|
||||
|
||||
const DragTargeMovePlaceholder({
|
||||
required this.delegate,
|
||||
required this.dragTargetIndex,
|
||||
this.height = 4,
|
||||
this.color = Colors.transparent,
|
||||
this.highlightColor = Colors.lightBlue,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<DragTargeMovePlaceholder> createState() =>
|
||||
_DragTargeMovePlaceholderState();
|
||||
}
|
||||
|
||||
class _DragTargeMovePlaceholderState extends State<DragTargeMovePlaceholder> {
|
||||
ValueNotifier<bool> isHighlight = ValueNotifier(false);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
widget.delegate.registerPlaceholder(
|
||||
widget.dragTargetIndex,
|
||||
(currentDragTargetIndex) {
|
||||
if (!mounted) return;
|
||||
|
||||
SchedulerBinding.instance.addPostFrameCallback((Duration duration) {
|
||||
if (currentDragTargetIndex == -1) {
|
||||
isHighlight.value = false;
|
||||
} else {
|
||||
isHighlight.value =
|
||||
widget.dragTargetIndex == currentDragTargetIndex;
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
isHighlight.dispose();
|
||||
widget.delegate.unregisterPlaceholder(widget.dragTargetIndex);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProvider.value(
|
||||
value: isHighlight,
|
||||
child: Consumer<ValueNotifier<bool>>(
|
||||
builder: (context, notifier, child) {
|
||||
return Container(
|
||||
height: widget.height,
|
||||
color: notifier.value ? widget.highlightColor : widget.color,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class FakeDragTargetEventTrigger {
|
||||
void fakeOnDragEnded(VoidCallback callback);
|
||||
}
|
||||
|
@ -30,12 +30,14 @@ abstract class DragTargetInterceptor {
|
||||
}
|
||||
|
||||
abstract class OverlapDragTargetDelegate {
|
||||
void didReturnOriginalDragTarget();
|
||||
void didCrossOtherDragTarget(
|
||||
void cancel();
|
||||
void moveTo(
|
||||
String reorderFlexId,
|
||||
FlexDragTargetData dragTargetData,
|
||||
int dragTargetIndex,
|
||||
);
|
||||
|
||||
bool canMoveTo(String dragTargetId);
|
||||
}
|
||||
|
||||
/// [OverlappingDragTargetInteceptor] is used to receive the overlapping
|
||||
@ -68,13 +70,11 @@ class OverlappingDragTargetInteceptor extends DragTargetInterceptor {
|
||||
required String dragTargetId,
|
||||
required int dragTargetIndex}) {
|
||||
if (dragTargetId == dragTargetData.reorderFlexId) {
|
||||
delegate.didReturnOriginalDragTarget();
|
||||
delegate.cancel();
|
||||
} else {
|
||||
delegate.didCrossOtherDragTarget(
|
||||
dragTargetId,
|
||||
dragTargetData,
|
||||
dragTargetIndex,
|
||||
);
|
||||
if (delegate.canMoveTo(dragTargetId)) {
|
||||
delegate.moveTo(dragTargetId, dragTargetData, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -128,13 +128,13 @@ class CrossReorderFlexDragTargetInterceptor extends DragTargetInterceptor {
|
||||
@override
|
||||
void onAccept(FlexDragTargetData dragTargetData) {
|
||||
Log.trace(
|
||||
'[$CrossReorderFlexDragTargetInterceptor] Column$reorderFlexId on onAccept');
|
||||
'[$CrossReorderFlexDragTargetInterceptor] Column:[$reorderFlexId] on onAccept');
|
||||
}
|
||||
|
||||
@override
|
||||
void onLeave(FlexDragTargetData dragTargetData) {
|
||||
Log.trace(
|
||||
'[$CrossReorderFlexDragTargetInterceptor] Column$reorderFlexId on leave');
|
||||
'[$CrossReorderFlexDragTargetInterceptor] Column:[$reorderFlexId] on leave');
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -41,16 +41,19 @@ class ReorderFlexConfig {
|
||||
// How long an animation to scroll to an off-screen element
|
||||
final Duration scrollAnimationDuration = const Duration(milliseconds: 250);
|
||||
|
||||
final double? spacing;
|
||||
final bool useMoveAnimation;
|
||||
|
||||
const ReorderFlexConfig({this.spacing});
|
||||
final bool useMovePlaceholder;
|
||||
|
||||
const ReorderFlexConfig({
|
||||
this.useMoveAnimation = true,
|
||||
}) : useMovePlaceholder = !useMoveAnimation;
|
||||
}
|
||||
|
||||
class ReorderFlex extends StatefulWidget {
|
||||
final ReorderFlexConfig config;
|
||||
|
||||
final List<Widget> children;
|
||||
final EdgeInsets? padding;
|
||||
|
||||
/// [direction] How to place the children, default is Axis.vertical
|
||||
final Axis direction;
|
||||
@ -81,7 +84,6 @@ class ReorderFlex extends StatefulWidget {
|
||||
this.onDragStarted,
|
||||
this.onDragEnded,
|
||||
this.interceptor,
|
||||
this.padding,
|
||||
this.direction = Axis.vertical,
|
||||
}) : super(key: key);
|
||||
|
||||
@ -108,8 +110,11 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
/// [_animation] controls the dragging animations
|
||||
late DragTargetAnimation _animation;
|
||||
|
||||
late ReorderFlexNotifier _notifier;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_notifier = ReorderFlexNotifier();
|
||||
dragState = DraggingState(widget.reorderFlexId);
|
||||
|
||||
_animation = DragTargetAnimation(
|
||||
@ -154,13 +159,14 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
|
||||
for (int i = 0; i < widget.children.length; i += 1) {
|
||||
Widget child = widget.children[i];
|
||||
children.add(_wrap(child, i));
|
||||
|
||||
if (widget.config.spacing != null) {
|
||||
children.add(SizedBox(width: widget.config.spacing!));
|
||||
}
|
||||
|
||||
final wrapChild = _wrap(child, i);
|
||||
children.add(wrapChild);
|
||||
// if (widget.config.useMovePlaceholder) {
|
||||
// children.add(DragTargeMovePlaceholder(
|
||||
// dragTargetIndex: i,
|
||||
// delegate: _notifier,
|
||||
// ));
|
||||
// }
|
||||
}
|
||||
|
||||
final child = _wrapContainer(children);
|
||||
@ -199,7 +205,8 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
/// [childIndex]: the index of the child in a list
|
||||
Widget _wrap(Widget child, int childIndex) {
|
||||
return Builder(builder: (context) {
|
||||
final dragTarget = _buildDragTarget(context, child, childIndex);
|
||||
final ReorderDragTarget dragTarget =
|
||||
_buildDragTarget(context, child, childIndex);
|
||||
int shiftedIndex = childIndex;
|
||||
|
||||
if (dragState.isOverlapWithPhantom()) {
|
||||
@ -207,7 +214,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
}
|
||||
|
||||
Log.trace(
|
||||
'Rebuild: Column${dragState.id} ${dragState.toString()}, childIndex: $childIndex shiftedIndex: $shiftedIndex');
|
||||
'Rebuild: Column:[${dragState.id}] ${dragState.toString()}, childIndex: $childIndex shiftedIndex: $shiftedIndex');
|
||||
final currentIndex = dragState.currentIndex;
|
||||
final dragPhantomIndex = dragState.phantomIndex;
|
||||
|
||||
@ -234,15 +241,18 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
}
|
||||
|
||||
/// Determine the size of the drop area to show under the dragging widget.
|
||||
final feedbackSize = dragState.feedbackSize;
|
||||
Size? feedbackSize = Size.zero;
|
||||
if (widget.config.useMoveAnimation) {
|
||||
feedbackSize = dragState.feedbackSize;
|
||||
}
|
||||
|
||||
Widget appearSpace = _makeAppearSpace(dragSpace, feedbackSize);
|
||||
Widget disappearSpace = _makeDisappearSpace(dragSpace, feedbackSize);
|
||||
|
||||
/// When start dragging, the dragTarget, [ReorderDragTarget], will
|
||||
/// return a [IgnorePointerWidget] which size is zero.
|
||||
if (dragState.isPhantomAboveDragTarget()) {
|
||||
//the phantom is moving down, i.e. the tile below the phantom is moving up
|
||||
Log.trace('index:$childIndex item moving up / phantom moving down');
|
||||
_notifier.updateDragTargetIndex(currentIndex);
|
||||
if (shiftedIndex == currentIndex && childIndex == dragPhantomIndex) {
|
||||
return _buildDraggingContainer(children: [
|
||||
disappearSpace,
|
||||
@ -264,8 +274,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
|
||||
///
|
||||
if (dragState.isPhantomBelowDragTarget()) {
|
||||
//the phantom is moving up, i.e. the tile above the phantom is moving down
|
||||
Log.trace('index:$childIndex item moving down / phantom moving up');
|
||||
_notifier.updateDragTargetIndex(currentIndex);
|
||||
if (shiftedIndex == currentIndex && childIndex == dragPhantomIndex) {
|
||||
return _buildDraggingContainer(children: [
|
||||
appearSpace,
|
||||
@ -303,10 +312,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
}
|
||||
|
||||
ReorderDragTarget _buildDragTarget(
|
||||
BuildContext builderContext,
|
||||
Widget child,
|
||||
int dragTargetIndex,
|
||||
) {
|
||||
BuildContext builderContext, Widget child, int dragTargetIndex) {
|
||||
final ReoderFlexItem reorderFlexItem =
|
||||
widget.dataSource.items[dragTargetIndex];
|
||||
return ReorderDragTarget<FlexDragTargetData>(
|
||||
@ -319,14 +325,14 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
),
|
||||
onDragStarted: (draggingWidget, draggingIndex, size) {
|
||||
Log.debug(
|
||||
"[DragTarget] Column${widget.dataSource.identifier} start dragging item at $draggingIndex");
|
||||
"[DragTarget] Column:[${widget.dataSource.identifier}] start dragging item at $draggingIndex");
|
||||
_startDragging(draggingWidget, draggingIndex, size);
|
||||
widget.onDragStarted?.call(draggingIndex);
|
||||
},
|
||||
onDragEnded: (dragTargetData) {
|
||||
Log.debug(
|
||||
"[DragTarget]: Column${widget.dataSource.identifier} end dragging");
|
||||
|
||||
"[DragTarget]: Column:[${widget.dataSource.identifier}] end dragging");
|
||||
_notifier.updateDragTargetIndex(-1);
|
||||
setState(() {
|
||||
if (dragTargetData.reorderFlexId == widget.reorderFlexId) {
|
||||
_onReordered(
|
||||
@ -340,14 +346,11 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
});
|
||||
},
|
||||
onWillAccept: (FlexDragTargetData dragTargetData) {
|
||||
Log.debug('Insert animation: ${_animation.deleteController.status}');
|
||||
|
||||
if (_animation.deleteController.isAnimating) {
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(widget.dataSource.items.length > dragTargetIndex);
|
||||
|
||||
if (_interceptDragTarget(
|
||||
dragTargetData,
|
||||
(interceptor) => interceptor.onWillAccept(
|
||||
@ -370,6 +373,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
);
|
||||
},
|
||||
onLeave: (dragTargetData) {
|
||||
_notifier.updateDragTargetIndex(-1);
|
||||
_interceptDragTarget(
|
||||
dragTargetData,
|
||||
(interceptor) => interceptor.onLeave(dragTargetData),
|
||||
@ -378,6 +382,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
insertAnimationController: _animation.insertController,
|
||||
deleteAnimationController: _animation.deleteController,
|
||||
draggableTargetBuilder: widget.interceptor?.draggableTargetBuilder,
|
||||
useMoveAnimation: widget.config.useMoveAnimation,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
@ -430,7 +435,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
/// The [willAccept] will be true if the dargTarget is the widget that gets
|
||||
/// dragged and it is dragged on top of the other dragTargets.
|
||||
///
|
||||
Log.trace(
|
||||
Log.debug(
|
||||
'[$ReorderDragTarget] ${widget.dataSource.identifier} on will accept, dragIndex:$dragIndex, dragTargetIndex:$dragTargetIndex, count: ${widget.dataSource.items.length}');
|
||||
|
||||
bool willAccept =
|
||||
@ -442,7 +447,6 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
} else {
|
||||
dragState.updateNextIndex(dragTargetIndex);
|
||||
}
|
||||
|
||||
_requestAnimationToNextIndex(isAcceptingNewTarget: true);
|
||||
});
|
||||
|
||||
@ -467,7 +471,6 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
} else {
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: widget.direction,
|
||||
padding: widget.padding,
|
||||
controller: _scrollController,
|
||||
child: child,
|
||||
);
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import '../transitions.dart';
|
||||
import 'drag_target.dart';
|
||||
|
||||
mixin ReorderFlexMinxi {
|
||||
@protected
|
||||
@ -86,3 +87,56 @@ extension CurveAnimationController on AnimationController {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ReorderFlexNotifier extends DragTargetMovePlaceholderDelegate {
|
||||
Map<int, DragTargetEventNotifier> dragTargeEventNotifier = {};
|
||||
|
||||
void updateDragTargetIndex(int index) {
|
||||
for (var notifier in dragTargeEventNotifier.values) {
|
||||
notifier.setDragTargetIndex(index);
|
||||
}
|
||||
}
|
||||
|
||||
DragTargetEventNotifier _notifierFromIndex(int dragTargetIndex) {
|
||||
DragTargetEventNotifier? notifier = dragTargeEventNotifier[dragTargetIndex];
|
||||
if (notifier == null) {
|
||||
final newNotifier = DragTargetEventNotifier();
|
||||
dragTargeEventNotifier[dragTargetIndex] = newNotifier;
|
||||
notifier = newNotifier;
|
||||
}
|
||||
|
||||
return notifier;
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
for (var notifier in dragTargeEventNotifier.values) {
|
||||
notifier.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void registerPlaceholder(
|
||||
int dragTargetIndex,
|
||||
void Function(int dragTargetIndex) callback,
|
||||
) {
|
||||
_notifierFromIndex(dragTargetIndex).addListener(() {
|
||||
callback.call(_notifierFromIndex(dragTargetIndex).currentDragTargetIndex);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void unregisterPlaceholder(int dragTargetIndex) {
|
||||
dragTargeEventNotifier.remove(dragTargetIndex);
|
||||
}
|
||||
}
|
||||
|
||||
class DragTargetEventNotifier extends ChangeNotifier {
|
||||
int currentDragTargetIndex = -1;
|
||||
|
||||
void setDragTargetIndex(int index) {
|
||||
if (currentDragTargetIndex != index) {
|
||||
currentDragTargetIndex = index;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../utils/log.dart';
|
||||
import '../board_column/board_column_data.dart';
|
||||
import '../flex/drag_state.dart';
|
||||
import '../flex/drag_target.dart';
|
||||
import '../flex/drag_target_inteceptor.dart';
|
||||
import '../reorder_flex/drag_state.dart';
|
||||
import '../reorder_flex/drag_target.dart';
|
||||
import '../reorder_flex/drag_target_inteceptor.dart';
|
||||
import 'phantom_state.dart';
|
||||
|
||||
abstract class BoardPhantomControllerDelegate {
|
||||
@ -127,8 +129,8 @@ class BoardPhantomController extends OverlapDragTargetDelegate
|
||||
FlexDragTargetData dragTargetData,
|
||||
int dragTargetIndex,
|
||||
) {
|
||||
// Log.debug('[$BoardPhantomController] move Column${dragTargetData.reorderFlexId}:${dragTargetData.draggingIndex} '
|
||||
// 'to Column$columnId:$index');
|
||||
// Log.debug('[$BoardPhantomController] move Column:[${dragTargetData.reorderFlexId}]:${dragTargetData.draggingIndex} '
|
||||
// 'to Column:[$columnId]:$index');
|
||||
|
||||
phantomRecord = PhantomRecord(
|
||||
toColumnId: columnId,
|
||||
@ -177,7 +179,7 @@ class BoardPhantomController extends OverlapDragTargetDelegate
|
||||
}
|
||||
|
||||
@override
|
||||
void didReturnOriginalDragTarget() {
|
||||
void cancel() {
|
||||
if (phantomRecord == null) {
|
||||
return;
|
||||
}
|
||||
@ -188,7 +190,7 @@ class BoardPhantomController extends OverlapDragTargetDelegate
|
||||
}
|
||||
|
||||
@override
|
||||
void didCrossOtherDragTarget(
|
||||
void moveTo(
|
||||
String reorderFlexId,
|
||||
FlexDragTargetData dragTargetData,
|
||||
int dragTargetIndex,
|
||||
@ -199,6 +201,12 @@ class BoardPhantomController extends OverlapDragTargetDelegate
|
||||
dragTargetIndex,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool canMoveTo(String dragTargetId) {
|
||||
// TODO: implement shouldReceive
|
||||
return delegate.controller(dragTargetId)?.columnData.items.length == 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// Use [PhantomRecord] to record where to remove the column item and where to
|
||||
@ -228,7 +236,7 @@ class PhantomRecord {
|
||||
return;
|
||||
}
|
||||
Log.debug(
|
||||
'[$PhantomRecord] Update Column$fromColumnId remove position to $index');
|
||||
'[$PhantomRecord] Update Column:[$fromColumnId] remove position to $index');
|
||||
fromColumnIndex = index;
|
||||
}
|
||||
|
||||
@ -238,13 +246,13 @@ class PhantomRecord {
|
||||
}
|
||||
|
||||
Log.debug(
|
||||
'[$PhantomRecord] Column$toColumnId update position $toColumnIndex -> $index');
|
||||
'[$PhantomRecord] Column:[$toColumnId] update position $toColumnIndex -> $index');
|
||||
toColumnIndex = index;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Column$fromColumnId:$fromColumnIndex to Column$toColumnId:$toColumnIndex';
|
||||
return 'Column:[$fromColumnId]:$fromColumnIndex to Column:[$toColumnId]:$toColumnIndex';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,3 @@
|
||||
export 'card.dart';
|
||||
export 'footer.dart';
|
||||
export 'header.dart';
|
@ -0,0 +1,37 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AppFlowyColumnItemCard extends StatefulWidget {
|
||||
final Widget? child;
|
||||
final Color backgroundColor;
|
||||
final double cornerRadius;
|
||||
final BoxConstraints boxConstraints;
|
||||
|
||||
const AppFlowyColumnItemCard({
|
||||
this.child,
|
||||
this.backgroundColor = Colors.white,
|
||||
this.cornerRadius = 0.0,
|
||||
this.boxConstraints = const BoxConstraints.tightFor(height: 60),
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<AppFlowyColumnItemCard> createState() => _AppFlowyColumnItemCardState();
|
||||
}
|
||||
|
||||
class _AppFlowyColumnItemCardState extends State<AppFlowyColumnItemCard> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: Container(
|
||||
constraints: widget.boxConstraints,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
color: widget.backgroundColor,
|
||||
borderRadius: BorderRadius.circular(widget.cornerRadius),
|
||||
),
|
||||
child: widget.child,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
typedef OnFooterAddButtonClick = void Function();
|
||||
|
||||
class AppFlowyColumnFooter extends StatefulWidget {
|
||||
final double height;
|
||||
final Widget? icon;
|
||||
final Widget? title;
|
||||
final EdgeInsets margin;
|
||||
final OnFooterAddButtonClick? onAddButtonClick;
|
||||
|
||||
const AppFlowyColumnFooter({
|
||||
this.icon,
|
||||
this.title,
|
||||
this.margin = EdgeInsets.zero,
|
||||
required this.height,
|
||||
this.onAddButtonClick,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<AppFlowyColumnFooter> createState() => _AppFlowyColumnFooterState();
|
||||
}
|
||||
|
||||
class _AppFlowyColumnFooterState extends State<AppFlowyColumnFooter> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: widget.onAddButtonClick,
|
||||
child: SizedBox(
|
||||
height: widget.height,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (widget.icon != null) widget.icon!,
|
||||
if (widget.title != null) widget.title!,
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
typedef OnHeaderAddButtonClick = void Function();
|
||||
typedef OnHeaderMoreButtonClick = void Function();
|
||||
|
||||
class AppFlowyColumnHeader extends StatefulWidget {
|
||||
final double height;
|
||||
final Widget? icon;
|
||||
final Widget? title;
|
||||
final Widget? addIcon;
|
||||
final Widget? moreIcon;
|
||||
final EdgeInsets margin;
|
||||
final OnHeaderAddButtonClick? onAddButtonClick;
|
||||
final OnHeaderMoreButtonClick? onMoreButtonClick;
|
||||
|
||||
const AppFlowyColumnHeader({
|
||||
required this.height,
|
||||
this.icon,
|
||||
this.title,
|
||||
this.addIcon,
|
||||
this.moreIcon,
|
||||
this.margin = EdgeInsets.zero,
|
||||
this.onAddButtonClick,
|
||||
this.onMoreButtonClick,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<AppFlowyColumnHeader> createState() => _AppFlowyColumnHeaderState();
|
||||
}
|
||||
|
||||
class _AppFlowyColumnHeaderState extends State<AppFlowyColumnHeader> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List<Widget> children = [];
|
||||
|
||||
if (widget.icon != null) {
|
||||
children.add(widget.icon!);
|
||||
children.add(_hSpace());
|
||||
}
|
||||
|
||||
if (widget.title != null) {
|
||||
children.add(widget.title!);
|
||||
children.add(_hSpace());
|
||||
}
|
||||
|
||||
if (widget.moreIcon != null) {
|
||||
children.add(const Spacer());
|
||||
children.add(
|
||||
IconButton(onPressed: widget.onMoreButtonClick, icon: widget.moreIcon!),
|
||||
);
|
||||
}
|
||||
|
||||
if (widget.addIcon != null) {
|
||||
children.add(
|
||||
IconButton(onPressed: widget.onAddButtonClick, icon: widget.addIcon!),
|
||||
);
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
height: widget.height,
|
||||
child: Padding(
|
||||
padding: widget.margin,
|
||||
child: Row(
|
||||
children: children,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _hSpace() {
|
||||
return const SizedBox(width: 6);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user