diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist
index 9625e10..7c56964 100644
--- a/example/ios/Flutter/AppFrameworkInfo.plist
+++ b/example/ios/Flutter/AppFrameworkInfo.plist
@@ -21,6 +21,6 @@
CFBundleVersion
1.0
MinimumOSVersion
- 11.0
+ 12.0
diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj
index 572b8d6..9926ec5 100644
--- a/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/example/ios/Runner.xcodeproj/project.pbxproj
@@ -168,7 +168,7 @@
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
- LastUpgradeCheck = 1300;
+ LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
@@ -344,7 +344,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 11.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -471,7 +471,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 11.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -520,7 +520,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 11.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index e42adcb..8e3ca5d 100644
--- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -1,6 +1,6 @@
{
- get width =>
+ double get width =>
Platform.isWindows || Platform.isLinux || Platform.isMacOS ? 350 : 250;
+ final _controller = KanbanBoardController();
@override
Widget build(BuildContext context) {
+ final kanbanGroups = List.generate(
+ kanbanData.length,
+ (index) => KanbanBoardGroup(
+ id: kanbanData.keys.elementAt(index),
+ name: kanbanData.keys.elementAt(index),
+ items: (kanbanData.values.elementAt(index)['items'] as List)
+ .map(
+ (e) => KanbanCardImpl(
+ itemId: index.toString(),
+ title: e['title'],
+ date: e['date'],
+ avatar: persons[
+ kanbanData.values.elementAt(index)['items'].indexOf(e) % 4],
+ completedTasks:
+ int.parse(e['tasks'].toString().split('/').first),
+ totalTasks: int.parse(e['tasks'].toString().split('/').last),
+ ),
+ )
+ .toList(),
+ ),
+ );
return Scaffold(
backgroundColor: const Color.fromRGBO(249, 244, 240, 1),
body: SafeArea(
@@ -36,60 +58,74 @@ class _BoardBuilderState extends State {
),
Expanded(
child: KanbanBoard(
- List.generate(kanbanData.length, (index) {
- final element = kanbanData.values.elementAt(index);
- return BoardListsData(
- backgroundColor: const Color.fromRGBO(249, 244, 240, 1),
- width: width,
- footer: const ListFooter(),
- headerBackgroundColor:
- const Color.fromRGBO(249, 244, 240, 1),
- header: ListHeader(
- title: kanbanData.keys.elementAt(index),
- stateColor: element['color'],
+ controller: _controller,
+ groupHeaderBuilder: (context, groupId) => ListHeader(
+ updateBoard: _updateBoard,
+ controller: _controller,
+ stateColor: kanbanData[groupId]!['color'],
+ title: groupId,
),
- items: List.generate(element['items'].length, (index) {
- int totalTasks = int.parse(element['items'][index]
- ['tasks']
- .toString()
- .split('/')
- .last);
- int completedTasks = int.parse(element['items'][index]
- ['tasks']
- .toString()
- .split('/')
- .first);
-
- return KanbanCard(
- title: element['items'][index]['title'],
- completedTasks: completedTasks,
- totalTasks: totalTasks,
- date: element['items'][index]['date'],
- tasks: element['items'][index]['tasks'],
- avatar: persons[index % 4],
- );
- }));
- }),
- onItemLongPress: (cardIndex, listIndex) {},
- onItemReorder:
- (oldCardIndex, newCardIndex, oldListIndex, newListIndex) {},
- onListLongPress: (listIndex) {},
- onListReorder: (oldListIndex, newListIndex) {},
- onItemTap: (cardIndex, listIndex) {},
- onListTap: (listIndex) {},
- onListRename: (oldName, newName) {},
- backgroundColor: const Color.fromRGBO(249, 244, 240, 1),
- displacementY: 124,
- displacementX: 100,
- textStyle: const TextStyle(
- fontSize: 18,
- color: Colors.black,
- fontWeight: FontWeight.w500),
- ),
- ),
+ groupFooterBuilder: (context, groupId) => const ListFooter(),
+ boardDecoration: const BoxDecoration(
+ color: Color.fromRGBO(249, 244, 240, 1),
+ ),
+ groupDecoration: const BoxDecoration(
+ color: Color.fromRGBO(249, 244, 240, 1),
+ ),
+ groupConstraints: BoxConstraints(
+ minWidth: width, maxWidth: width, minHeight: 300),
+ itemGhost: DottedBorder(
+ child: const Center(
+ child: Text(
+ "Drop your task here",
+ style:
+ TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
+ )),
+ ),
+ groups: kanbanGroups,
+ groupItemBuilder: (context, groupId, itemIndex) {
+ final groupItem = kanbanGroups
+ .firstWhere((element) => element.id == groupId)
+ .items
+ .elementAt(itemIndex);
+ return GroupCard(
+ title: groupItem.title,
+ completedTasks: groupItem.completedTasks,
+ totalTasks: groupItem.totalTasks,
+ date: groupItem.date,
+ tasks: groupItem.totalTasks.toString(),
+ avatar: persons[itemIndex % 4],
+ );
+ }),
+ )
],
),
),
);
}
+
+ void _updateBoard() {
+ setState(() {});
+ }
+}
+
+class KanbanCardImpl extends KanbanBoardGroupItem {
+ final String itemId;
+ final String title;
+ final String date;
+ final String avatar;
+ final int completedTasks;
+ final int totalTasks;
+
+ KanbanCardImpl({
+ required this.itemId,
+ required this.title,
+ required this.date,
+ required this.avatar,
+ required this.completedTasks,
+ required this.totalTasks,
+ });
+
+ @override
+ String get id => itemId;
}
diff --git a/example/lib/kanban_data.dart b/example/lib/kanban_data.dart
index 4e30175..ada4f4b 100644
--- a/example/lib/kanban_data.dart
+++ b/example/lib/kanban_data.dart
@@ -2,10 +2,10 @@ import 'package:flutter/material.dart';
Map kanbanData = {
'Blocked': {
- 'color': const Color.fromRGBO(239, 147, 148, 1),
+ 'color': const Color.fromARGB(255, 0, 0, 0),
'items': [
{
- 'title': 'Making A New Trend In Poster',
+ 'title': 'Creative Outdoor Ads',
'date': '17 Dec 2022',
'tasks': '30/48'
},
@@ -39,9 +39,48 @@ Map kanbanData = {
{
'title': 'Advertising Outdoors',
'date': '17 Dec 2022',
- 'tasks': '53/70'
+ 'tasks': '53/70',
},
{'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
+ {'title': 'Create Remarkable', 'date': '17 Nov 2022', 'tasks': '15/56'},
{
'title': 'Manufacturing Equipment',
'date': '17 Dec 2022',
@@ -84,11 +123,81 @@ Map kanbanData = {
},
]
},
+ 'Testing': {
+ 'color': const Color.fromARGB(255, 235, 235, 148),
+ 'items': [
+ {
+ 'title': 'Creative Outdoor Ads',
+ 'date': '23 Dec 2022',
+ 'tasks': '20/20'
+ },
+ {
+ 'title': 'Promotional Advertising Speciality',
+ 'date': '17 Nov 2022',
+ 'tasks': '15/15'
+ },
+ {
+ 'title': 'Search Engine OPtimization',
+ 'date': '22 Oct 2022',
+ 'tasks': '67/67'
+ },
+ ]
+ },
+ 'Review': {
+ 'color': const Color.fromARGB(255, 250, 234, 89),
+ 'items': [
+ {
+ 'title': 'Creative Outdoor Ads',
+ 'date': '23 Dec 2022',
+ 'tasks': '20/20'
+ },
+ {
+ 'title': 'Promotional Advertising Speciality',
+ 'date': '17 Nov 2022',
+ 'tasks': '15/15'
+ },
+ {
+ 'title': 'Search Engine OPtimization',
+ 'date': '22 Oct 2022',
+ 'tasks': '67/67'
+ },
+ ]
+ },
+ 'Cancelled': {
+ 'color': const Color.fromARGB(255, 248, 41, 30),
+ 'items': [
+ {
+ 'title': 'Creative Outdoor Ads',
+ 'date': '23 Dec 2022',
+ 'tasks': '20/20'
+ },
+ ]
+ },
+ 'Production': {
+ 'color': const Color.fromARGB(255, 0, 248, 58),
+ 'items': [
+ {
+ 'title': 'Creative Outdoor Ads',
+ 'date': '23 Dec 2022',
+ 'tasks': '20/20'
+ },
+ ]
+ },
+ 'Staging': {
+ 'color': const Color.fromARGB(255, 8, 61, 232),
+ 'items': [
+ {
+ 'title': 'Creative Outdoor Ads',
+ 'date': '23 Dec 2022',
+ 'tasks': '20/20'
+ },
+ ]
+ },
};
- List persons = [
- 'assets/person1.jpg',
- 'assets/person2.jpg',
- 'assets/person3.jpg',
- 'assets/person4.jpg',
- ];
+List persons = [
+ 'assets/person1.jpg',
+ 'assets/person2.jpg',
+ 'assets/person3.jpg',
+ 'assets/person4.jpg',
+];
diff --git a/example/lib/models/group_more_action.dart b/example/lib/models/group_more_action.dart
new file mode 100644
index 0000000..9a57a4b
--- /dev/null
+++ b/example/lib/models/group_more_action.dart
@@ -0,0 +1,9 @@
+enum GroupMoreAction {
+ editTitle('Edit title'),
+ createNewTask('Create new task'),
+ deleteGroup('Delete group');
+
+ final String label;
+
+ const GroupMoreAction(this.label);
+}
diff --git a/example/lib/widgets/board_header.dart b/example/lib/widgets/board_header.dart
index b92b64a..5a05e79 100644
--- a/example/lib/widgets/board_header.dart
+++ b/example/lib/widgets/board_header.dart
@@ -1,5 +1,4 @@
import 'dart:io';
-
import 'package:flutter/material.dart';
class BoardHeader extends StatelessWidget {
@@ -52,11 +51,14 @@ class BoardHeader extends StatelessWidget {
child: const Row(
children: [
Icon(Icons.add_circle_outlined),
- Text("Create new",
- style: TextStyle(
- fontSize: 14,
- color: Colors.black,
- fontWeight: FontWeight.bold)),
+ Text(
+ "Create new",
+ style: TextStyle(
+ fontSize: 14,
+ color: Colors.black,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
],
),
),
diff --git a/example/lib/widgets/card.dart b/example/lib/widgets/group_card.dart
similarity index 93%
rename from example/lib/widgets/card.dart
rename to example/lib/widgets/group_card.dart
index 12a71a1..84123b6 100644
--- a/example/lib/widgets/card.dart
+++ b/example/lib/widgets/group_card.dart
@@ -1,12 +1,14 @@
import 'package:flutter/material.dart';
-class KanbanCard extends StatelessWidget {
- const KanbanCard(
+class GroupCard extends StatelessWidget {
+ const GroupCard(
{super.key,
required this.title,
required this.completedTasks,
required this.totalTasks,
- required this.date, required this.tasks, required this.avatar});
+ required this.date,
+ required this.tasks,
+ required this.avatar});
final String title;
final int completedTasks;
final int totalTasks;
@@ -89,8 +91,7 @@ class KanbanCard extends StatelessWidget {
width: 25,
decoration: BoxDecoration(
image: DecorationImage(
- fit: BoxFit.cover,
- image: Image.asset(avatar).image),
+ fit: BoxFit.cover, image: Image.asset(avatar).image),
color: const Color.fromRGBO(174, 122, 255, 1),
borderRadius: BorderRadius.circular(30)),
)
diff --git a/example/lib/widgets/list_header.dart b/example/lib/widgets/list_header.dart
index 20e3ef5..e117012 100644
--- a/example/lib/widgets/list_header.dart
+++ b/example/lib/widgets/list_header.dart
@@ -1,9 +1,19 @@
import 'package:flutter/material.dart';
+import 'package:kanban_board/kanban_board.dart';
class ListHeader extends StatelessWidget {
- const ListHeader({super.key, required this.title, required this.stateColor});
+ const ListHeader({
+ super.key,
+ required this.controller,
+ required this.title,
+ required this.stateColor,
+ required this.updateBoard,
+ });
final String title;
final Color stateColor;
+ final KanbanBoardController controller;
+ final VoidCallback updateBoard;
+
@override
Widget build(BuildContext context) {
return Container(
@@ -23,20 +33,6 @@ class ListHeader extends StatelessWidget {
color: Colors.black,
fontWeight: FontWeight.bold)),
const Spacer(),
- Container(
- height: 25,
- width: 25,
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(2),
- border: Border.all(
- color: Colors.black,
- )),
- child: const Icon(
- Icons.more_horiz,
- size: 16,
- color: Colors.black,
- ),
- )
],
),
);
diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj
index 6a9cc9c..a88578a 100644
--- a/example/macos/Runner.xcodeproj/project.pbxproj
+++ b/example/macos/Runner.xcodeproj/project.pbxproj
@@ -227,7 +227,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0920;
- LastUpgradeCheck = 1300;
+ LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C80D4294CF70F00263BE5 = {
diff --git a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index feac689..c8582ae 100644
--- a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -1,6 +1,6 @@
Bool {
return true
diff --git a/example/pubspec.lock b/example/pubspec.lock
index 64d6dc8..70e3c60 100644
--- a/example/pubspec.lock
+++ b/example/pubspec.lock
@@ -37,10 +37,18 @@ packages:
dependency: transitive
description:
name: collection
- sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
+ sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev"
source: hosted
- version: "1.17.1"
+ version: "1.18.0"
+ crypto:
+ dependency: transitive
+ description:
+ name: crypto
+ sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.5"
cupertino_icons:
dependency: "direct main"
description:
@@ -65,6 +73,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.1"
+ fixnum:
+ dependency: transitive
+ description:
+ name: fixnum
+ sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.0"
flutter:
dependency: "direct main"
description: flutter
@@ -91,14 +107,6 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
- js:
- dependency: transitive
- description:
- name: js
- sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
- url: "https://pub.dev"
- source: hosted
- version: "0.6.7"
kanban_board:
dependency: "direct main"
description:
@@ -106,6 +114,30 @@ packages:
relative: true
source: path
version: "0.1.0"
+ leak_tracker:
+ dependency: transitive
+ description:
+ name: leak_tracker
+ sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
+ url: "https://pub.dev"
+ source: hosted
+ version: "10.0.5"
+ leak_tracker_flutter_testing:
+ dependency: transitive
+ description:
+ name: leak_tracker_flutter_testing
+ sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.5"
+ leak_tracker_testing:
+ dependency: transitive
+ description:
+ name: leak_tracker_testing
+ sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.1"
lints:
dependency: transitive
description:
@@ -118,34 +150,34 @@ packages:
dependency: transitive
description:
name: matcher
- sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
+ sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev"
source: hosted
- version: "0.12.15"
+ version: "0.12.16+1"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
- sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
+ sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
- version: "0.2.0"
+ version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
- sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
+ sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
url: "https://pub.dev"
source: hosted
- version: "1.9.1"
+ version: "1.15.0"
path:
dependency: transitive
description:
name: path
- sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
+ sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev"
source: hosted
- version: "1.8.3"
+ version: "1.9.0"
path_drawing:
dependency: transitive
description:
@@ -179,18 +211,26 @@ packages:
dependency: transitive
description:
name: source_span
- sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
+ sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.10.0"
+ sprintf:
+ dependency: transitive
+ description:
+ name: sprintf
+ sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
url: "https://pub.dev"
source: hosted
- version: "1.9.1"
+ version: "7.0.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
- sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
+ sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev"
source: hosted
- version: "1.11.0"
+ version: "1.11.1"
state_notifier:
dependency: transitive
description:
@@ -203,10 +243,10 @@ packages:
dependency: transitive
description:
name: stream_channel
- sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
+ sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev"
source: hosted
- version: "2.1.1"
+ version: "2.1.2"
string_scanner:
dependency: transitive
description:
@@ -227,10 +267,26 @@ packages:
dependency: transitive
description:
name: test_api
- sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
+ sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.7.2"
+ typed_data:
+ dependency: transitive
+ description:
+ name: typed_data
+ sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.2"
+ uuid:
+ dependency: transitive
+ description:
+ name: uuid
+ sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
url: "https://pub.dev"
source: hosted
- version: "0.5.1"
+ version: "4.5.1"
vector_math:
dependency: transitive
description:
@@ -239,6 +295,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.4"
+ vm_service:
+ dependency: transitive
+ description:
+ name: vm_service
+ sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
+ url: "https://pub.dev"
+ source: hosted
+ version: "14.2.5"
sdks:
- dart: ">=3.0.5 <4.0.0"
- flutter: ">=3.0.0"
+ dart: ">=3.4.0 <4.0.0"
+ flutter: ">=3.18.0-18.0.pre.54"
diff --git a/lib/Provider/board_list_provider.dart b/lib/Provider/board_list_provider.dart
deleted file mode 100644
index 21b6d71..0000000
--- a/lib/Provider/board_list_provider.dart
+++ /dev/null
@@ -1,282 +0,0 @@
-import 'dart:developer';
-import 'package:flutter/material.dart';
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:kanban_board/constants.dart';
-import 'package:kanban_board/draggable/draggable_state.dart';
-import '../custom/list_item.dart';
-import '../custom/text_field.dart';
-import '../models/item_state.dart';
-import 'provider_list.dart';
-
-class BoardListProvider extends ChangeNotifier {
- BoardListProvider(ChangeNotifierProviderRef this.ref);
- Ref ref;
- var scrolling = false;
- var scrollingUp = false;
- var scrollingDown = false;
- var newList = false;
-
- void calculateSizePosition(
- {required int listIndex,
- required BuildContext context,
- required VoidCallback setstate}) {
- var prov = ref.read(ProviderList.boardProvider);
- prov.board.lists[listIndex].context = context;
- var box = context.findRenderObject() as RenderBox;
- var location = box.localToGlobal(Offset.zero);
- prov.board.lists[listIndex].x =
- location.dx - prov.board.displacementX! - 10;
- prov.board.lists[listIndex].setState = setstate;
- prov.board.lists[listIndex].y =
- location.dy - prov.board.displacementY! + 24;
- prov.board.lists[listIndex].width ??= box.size.width;
- prov.board.lists[listIndex].height ??= box.size.height;
- }
-
- Future addNewCard({required String position, required int listIndex}) async {
- var prov = ref.read(ProviderList.boardProvider);
- final cardState = prov.newCardState;
- if (cardState.isFocused == true) {
- ref.read(ProviderList.cardProvider).saveNewCard();
- }
-
- var scroll = prov.board.lists[listIndex].scrollController;
-
- // log("MAX EXTENT =${scroll.position.maxScrollExtent}");
-
- prov.board.lists[listIndex].items.insert(
- position == "TOP" ? 0 : prov.board.lists[listIndex].items.length,
- ListItem(
- child: Container(
- width: prov.board.lists[listIndex].width,
- color: Colors.white,
- margin: const EdgeInsets.only(bottom: 10),
- child: const TField()),
- listIndex: listIndex,
- isNew: true,
- index: prov.board.lists[listIndex].items.length,
- prevChild: Container(
- width: prov.board.lists[listIndex].width,
- color: Colors.white,
- margin: const EdgeInsets.only(bottom: 10),
- padding: const EdgeInsets.all(10),
- child: const TField()),
- ));
- position == "TOP" ? await scrollToMin(scroll) : scrollToMax(scroll);
- cardState.listIndex = listIndex;
- cardState.isFocused = true;
- cardState.cardIndex =
- position == "TOP" ? 0 : prov.board.lists[listIndex].items.length - 1;
- prov.board.lists[listIndex].setState!();
- }
-
- void onListLongpress(
- {required int listIndex,
- required BuildContext context,
- required VoidCallback setstate}) {
- var prov = ref.read(ProviderList.boardProvider);
- final draggableProv = ref.read(ProviderList.draggableNotifier.notifier);
- for (var element in prov.board.lists) {
- if (element.context == null) break;
- var of = (element.context!.findRenderObject() as RenderBox)
- .localToGlobal(Offset.zero);
- element.x = of.dx;
- element.width = element.context!.size!.width - LIST_GAP;
- element.height = element.context!.size!.height;
- element.y = of.dy;
- }
- var box = context.findRenderObject() as RenderBox;
- var location = box.localToGlobal(Offset.zero);
- prov.updateValue(
- dx: location.dx - prov.board.displacementX!,
- dy: location.dy - prov.board.displacementY!);
-
- prov.board.dragItemIndex = null;
- prov.board.dragItemOfListIndex = listIndex;
- prov.draggedItemState = DraggedItemState(
- child: Container(
- width: box.size.width - LIST_GAP,
- height: box.size.height,
- color: prov.board.lists[listIndex].backgroundColor,
- child: Column(children: [
- prov.board.lists[listIndex].header ??
- Container(
- padding: const EdgeInsets.only(left: 15, bottom: 10),
- alignment: Alignment.centerLeft,
- child: Text(
- prov.board.lists[listIndex].title,
- style: const TextStyle(
- fontSize: 20,
- color: Colors.black,
- fontWeight: FontWeight.bold),
- ),
- ),
- Expanded(
- child: MediaQuery.removePadding(
- context: context,
- removeTop: true,
- child: ListView.builder(
- physics: const ClampingScrollPhysics(),
- controller: null,
- itemCount: prov.board.lists[listIndex].items.length,
- shrinkWrap: true,
- itemBuilder: (ctx, index) {
- return Item(
- color: prov.board.lists[listIndex].items[index]
- .backgroundColor ??
- Colors.grey.shade200,
- itemIndex: index,
- listIndex: listIndex,
- );
- },
-
- // itemCount: prov.items.length,
- ),
- ),
- ),
- ]),
- ),
- listIndex: listIndex,
- itemIndex: null,
- height: box.size.height,
- width: box.size.width,
- x: location.dx - prov.board.displacementX!,
- y: location.dy - prov.board.displacementY!);
- prov.draggedItemState!.setState = () => setstate;
- prov.board.dragItemIndex = null;
- draggableProv.setDraggableType(DraggableType.list);
- prov.board.dragItemOfListIndex = listIndex;
- setstate();
- }
-
- Future scrollToMax(ScrollController controller) async {
- if (controller.position.pixels == controller.position.maxScrollExtent) {
- return;
- }
-
- //log(controller.position.extentAfter.toString());
- await controller.animateTo(
- controller.position.pixels + controller.position.extentAfter,
- duration: Duration(
- milliseconds: (int.parse(controller.position.extentAfter
- .toString()
- .substring(0, 3)
- .split('.')
- .first))),
- curve: Curves.linear,
- );
- scrollToMax(controller);
- }
-
- Future scrollToMin(ScrollController controller) async {
- if (controller.position.pixels == controller.position.minScrollExtent) {
- return;
- }
-
- log(controller.position.extentBefore.toString());
- await controller.animateTo(
- controller.position.pixels - controller.position.extentBefore,
- duration: Duration(
- milliseconds: (int.parse(controller.position.extentBefore
- .toString()
- .substring(0, 3)
- .split('.')
- .first))),
- curve: Curves.linear,
- );
- scrollToMin(controller);
- }
-
- void maybeListScroll() async {
- var prov = ref.read(ProviderList.boardProvider);
- final draggableProv = ref.read(ProviderList.draggableNotifier);
- if (!draggableProv.isCardDragged || scrolling) {
- return;
- }
- var controller =
- prov.board.lists[prov.board.dragItemOfListIndex!].scrollController;
- if (controller.offset < controller.position.maxScrollExtent &&
- prov.valueNotifier.value.dy >
- controller.position.viewportDimension - 50) {
- scrolling = true;
- scrollingDown = true;
- if (prov.board.listScrollConfig == null) {
- await controller.animateTo(controller.offset + 45,
- duration: const Duration(milliseconds: 250), curve: Curves.linear);
- } else {
- await controller.animateTo(
- prov.board.listScrollConfig!.offset + controller.offset,
- duration: prov.board.listScrollConfig!.duration,
- curve: prov.board.listScrollConfig!.curve);
- }
- scrolling = false;
- scrollingDown = false;
-
- maybeListScroll();
- } else if (controller.offset > 0 && prov.valueNotifier.value.dy < 100) {
- scrolling = true;
- scrollingUp = true;
- if (prov.board.listScrollConfig == null) {
- await controller.animateTo(controller.offset - 45,
- duration: const Duration(milliseconds: 250), curve: Curves.linear);
- } else {
- await controller.animateTo(
- controller.offset - prov.board.listScrollConfig!.offset,
- duration: prov.board.listScrollConfig!.duration,
- curve: prov.board.listScrollConfig!.curve);
- }
- scrolling = false;
- scrollingUp = false;
- maybeListScroll();
- } else {
- return;
- }
- }
-
- void moveListRight() {
- var prov = ref.read(ProviderList.boardProvider);
- if (prov.draggedItemState!.listIndex == prov.board.lists.length - 1) {
- return;
- }
- if (prov.valueNotifier.value.dx +
- prov.board.lists[prov.draggedItemState!.listIndex!].width! / 2 <
- prov.board.lists[prov.draggedItemState!.listIndex! + 1].x!) {
- return;
- }
- // dev.log("LIST RIGHT");
- prov.board.lists.insert(prov.draggedItemState!.listIndex! + 1,
- prov.board.lists.removeAt(prov.draggedItemState!.listIndex!));
- prov.draggedItemState!.listIndex = prov.draggedItemState!.listIndex! + 1;
- prov.board.dragItemOfListIndex = null;
- prov.board.dragItemIndex = null;
- prov.draggedItemState!.itemIndex = null;
- prov.board.lists[prov.draggedItemState!.listIndex! - 1].setState!();
- prov.board.lists[prov.draggedItemState!.listIndex!].setState!();
- }
-
- void moveListLeft() {
- var prov = ref.read(ProviderList.boardProvider);
- if (prov.draggedItemState!.listIndex == 0) {
- return;
- }
- if (prov.valueNotifier.value.dx >
- prov.board.lists[prov.draggedItemState!.listIndex! - 1].x! +
- (prov.board.lists[prov.draggedItemState!.listIndex! - 1].width! /
- 2)) {
- // dev.log(
- // "RETURN LEFT LIST ${prov.valueNotifier.value.dx} ${prov.board.lists[prov.draggedItemState!.listIndex! - 1].x! + (prov.board.lists[prov.draggedItemState!.listIndex! - 1].width! / 2)} ");
- return;
- }
- // dev.log("LIST LEFT ${prov.valueNotifier.value.dx} ${prov.board.lists[prov.draggedItemState!.listIndex! - 1].x! + (prov.board.lists[prov.draggedItemState!.listIndex! - 1].width! / 2)} ");
- prov.board.lists.insert(prov.draggedItemState!.listIndex! - 1,
- prov.board.lists.removeAt(prov.draggedItemState!.listIndex!));
- prov.draggedItemState!.listIndex = prov.draggedItemState!.listIndex! - 1;
- prov.board.dragItemOfListIndex = null;
- prov.board.dragItemIndex = null;
- prov.draggedItemState!.itemIndex = null;
- prov.board.lists[prov.draggedItemState!.listIndex!].setState!();
- prov.board.lists[prov.draggedItemState!.listIndex! + 1].setState!();
- }
-
- void createNewList() {}
-}
diff --git a/lib/Provider/board_provider.dart b/lib/Provider/board_provider.dart
deleted file mode 100644
index 674f8d8..0000000
--- a/lib/Provider/board_provider.dart
+++ /dev/null
@@ -1,167 +0,0 @@
-import 'dart:developer';
-
-import 'package:flutter/material.dart';
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:kanban_board/Provider/provider_list.dart';
-import 'package:kanban_board/draggable/card_draggable.dart';
-import 'package:kanban_board/draggable/draggable_state.dart';
-import 'package:kanban_board/draggable/list_draggable.dart';
-import '../models/inputs.dart';
-import '../models/board.dart';
-import '../models/board_list.dart';
-import '../models/item_state.dart';
-
-class BoardProvider extends ChangeNotifier {
- BoardProvider(ChangeNotifierProviderRef this.ref);
- Ref ref;
- ValueNotifier valueNotifier = ValueNotifier(Offset.zero);
- String move = "";
- DraggedItemState? draggedItemState;
- CardDraggable? cardDraggable;
- ListDraggable? listDraggable;
- NewCardState newCardState = NewCardState();
- Offset delta = const Offset(0, 0);
-
- late BoardState board;
- var scrolling = false;
- var scrollingRight = false;
- var scrollingLeft = false;
-
- void initializeBoard(
- {required List data,
- Color backgroundColor = Colors.white,
- TextStyle? textStyle,
- Function(int? itemIndex, int? listIndex)? onItemTap,
- Function(int? itemIndex, int? listIndex)? onItemLongPress,
- Function(int? listIndex)? onListTap,
- Function(int? listIndex)? onListLongPress,
- double? displacementX,
- double? displacementY,
- void Function(int? oldCardIndex, int? newCardIndex, int? oldListIndex,
- int? newListIndex)?
- onItemReorder,
- void Function(int? oldListIndex, int? newListIndex)? onListReorder,
- void Function(String? oldName, String? newName)? onListRename,
- void Function(String? cardIndex, String? listIndex, String? text)?
- onNewCardInsert,
- Decoration? boardDecoration,
- Decoration? listDecoration,
- Widget Function(Widget child, Animation animation)?
- listTransitionBuilder,
- Widget Function(Widget child, Animation animation)?
- cardTransitionBuilder,
- required Duration cardTransitionDuration,
- required Duration listTransitionDuration,
- Color? cardPlaceHolderColor,
- Color? listPlaceHolderColor,
- ScrollConfig? boardScrollConfig,
- ScrollConfig? listScrollConfig}) {
- board = BoardState(
- textStyle: textStyle,
- lists: [],
- displacementX: displacementX,
- displacementY: displacementY,
- onItemTap: onItemTap,
- onItemLongPress: onItemLongPress,
- onListTap: onListTap,
- onListLongPress: onListLongPress,
- onItemReorder: onItemReorder,
- onListReorder: onListReorder,
- onListRename: onListRename,
- onNewCardInsert: onNewCardInsert,
- boardScrollConfig: boardScrollConfig,
- listScrollConfig: listScrollConfig,
- transitionHandler: TransitionHandler(
- cardTransitionBuilder:
- cardTransitionBuilder ?? (child, animation) => child,
- listTransitionBuilder:
- listTransitionBuilder ?? (child, animation) => child,
- cardTransitionDuration: cardTransitionDuration,
- listTransitionDuration: listTransitionDuration),
- controller: ScrollController(),
- backgroundColor: backgroundColor,
- cardPlaceholderColor: cardPlaceHolderColor,
- listPlaceholderColor: listPlaceHolderColor,
- listDecoration: listDecoration,
- boardDecoration: boardDecoration);
-
- for (int i = 0; i < data.length; i++) {
- List listItems = [];
- for (int j = 0; j < data[i].items.length; j++) {
- listItems.add(ListItem(
- child: data[i].items[j],
- listIndex: i,
- index: j,
- prevChild: data[i].items[j]));
- }
- board.lists.add(BoardList(
- header: data[i].header,
- footer: data[i].footer,
- headerBackgroundColor: data[i].headerBackgroundColor,
- footerBackgroundColor: data[i].footerBackgroundColor,
- backgroundColor: data[i].backgroundColor,
- items: listItems,
- width: data[i].width,
- scrollController: ScrollController(),
- title: data[i].title ?? 'LIST ${i + 1}'));
- }
- }
-
- void updateValue({
- required double dx,
- required double dy,
- }) {
- valueNotifier.value = Offset(dx, dy);
- }
-
- void setsState() {
- notifyListeners();
- }
-
- void boardScroll() async {
- final draggableProv = ref.read(ProviderList.draggableNotifier);
- if ((draggableProv.draggableType == DraggableType.none) || scrolling) {
- return;
- }
- if (board.controller.offset < board.controller.position.maxScrollExtent &&
- valueNotifier.value.dx + (draggedItemState!.width / 2) >
- board.controller.position.viewportDimension - 100) {
- scrolling = true;
- scrollingRight = true;
- if (board.boardScrollConfig == null) {
- log("HEREEEE");
- await board.controller.animateTo(board.controller.offset + 100,
- duration: const Duration(milliseconds: 100), curve: Curves.linear);
- } else {
- await board.controller.animateTo(
- board.boardScrollConfig!.offset + board.controller.offset,
- duration: board.boardScrollConfig!.duration,
- curve: board.boardScrollConfig!.curve);
- }
- scrolling = false;
- scrollingRight = false;
- boardScroll();
- } else if (board.controller.offset > 0 && valueNotifier.value.dx <= 0) {
- scrolling = true;
- scrollingLeft = true;
-
- if (board.boardScrollConfig == null) {
- await board.controller.animateTo(board.controller.offset - 100,
- duration:
- Duration(milliseconds: valueNotifier.value.dx < 20 ? 50 : 100),
- curve: Curves.linear);
- } else {
- await board.controller.animateTo(
- board.controller.offset - board.boardScrollConfig!.offset,
- duration: board.boardScrollConfig!.duration,
- curve: board.boardScrollConfig!.curve);
- }
-
- scrolling = false;
- scrollingLeft = false;
- boardScroll();
- } else {
- return;
- }
- }
-}
diff --git a/lib/Provider/draggable_provider.dart b/lib/Provider/draggable_provider.dart
deleted file mode 100644
index 67242b6..0000000
--- a/lib/Provider/draggable_provider.dart
+++ /dev/null
@@ -1,39 +0,0 @@
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:kanban_board/draggable/card_draggable.dart';
-import 'package:kanban_board/draggable/draggable_state.dart';
-import 'package:kanban_board/draggable/list_draggable.dart';
-
-class DraggableProviderState {
- CardDraggable? cardDraggable;
- ListDraggable? listDraggable;
- DraggableType draggableType;
- bool get isCardDragged => draggableType == DraggableType.card;
- bool get isListDragged => draggableType == DraggableType.list;
-
- DraggableProviderState(
- {this.cardDraggable,
- this.draggableType = DraggableType.none,
- this.listDraggable});
-
- DraggableProviderState copyWith(
- {CardDraggable? cardDraggable,
- ListDraggable? listDraggable,
- DraggableType? draggableType}) {
- return DraggableProviderState(
- cardDraggable: cardDraggable ?? this.cardDraggable,
- listDraggable: listDraggable ?? this.listDraggable,
- draggableType: draggableType ?? this.draggableType);
- }
-}
-
-class DraggableNotfier extends StateNotifier {
- DraggableNotfier(this.ref) : super(DraggableProviderState());
- StateNotifierProviderRef ref;
- void setDraggableType(DraggableType draggableType) {
- state = state.copyWith(draggableType: draggableType);
- }
-
- void stopDragging() {
- setDraggableType(DraggableType.none);
- }
-}
diff --git a/lib/Provider/list_item_provider.dart b/lib/Provider/list_item_provider.dart
deleted file mode 100644
index 74e8462..0000000
--- a/lib/Provider/list_item_provider.dart
+++ /dev/null
@@ -1,573 +0,0 @@
-import 'dart:developer';
-import 'package:dotted_border/dotted_border.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:kanban_board/draggable/draggable_state.dart';
-import '../models/board_list.dart';
-import '../models/item_state.dart';
-import 'provider_list.dart';
-
-class ListItemProvider extends ChangeNotifier {
- ListItemProvider(ChangeNotifierProviderRef this.ref);
- Ref ref;
- TextEditingController newCardTextController = TextEditingController();
-
- void calculateCardPositionSize(
- {required int listIndex,
- required int itemIndex,
- required BuildContext context,
- required VoidCallback setsate}) {
- var prov = ref.read(ProviderList.boardProvider);
- if (!context.mounted) return;
- prov.board.lists[listIndex].items[itemIndex].context = context;
- var box = context.findRenderObject() as RenderBox;
- var location = box.localToGlobal(Offset.zero);
- prov.board.lists[listIndex].items[itemIndex].setState = setsate;
- prov.board.lists[listIndex].items[itemIndex].x =
- location.dx - prov.board.displacementX!;
- prov.board.lists[listIndex].items[itemIndex].y =
- location.dy - prov.board.displacementY!;
- prov.board.lists[listIndex].items[itemIndex].actualSize ??= box.size;
- prov.board.lists[listIndex].items[itemIndex].width = box.size.width;
- prov.board.lists[listIndex].items[itemIndex].height = box.size.height;
- }
-
- void resetCardWidget() {
- var prov = ref.read(ProviderList.boardProvider);
- prov.board.lists[prov.board.dragItemOfListIndex!]
- .items[prov.board.dragItemIndex!].placeHolderAt = PlaceHolderAt.none;
- prov.board.lists[prov.board.dragItemOfListIndex!]
- .items[prov.board.dragItemIndex!].placeHolderAt = PlaceHolderAt.none;
- prov.board.lists[prov.board.dragItemOfListIndex!]
- .items[prov.board.dragItemIndex!].child =
- prov.board.lists[prov.board.dragItemOfListIndex!]
- .items[prov.board.dragItemIndex!].prevChild;
- }
-
- bool calculateSizePosition({
- required int listIndex,
- required int itemIndex,
- }) {
- var prov = ref.read(ProviderList.boardProvider);
- var item = prov.board.lists[listIndex].items[itemIndex];
- var list = prov.board.lists[listIndex];
- if (item.context == null ||
- list.context == null ||
- !item.context!.mounted) {
- return true;
- }
- var box = item.context!.findRenderObject();
- var listBox = list.context!.findRenderObject();
- if (box == null || listBox == null) return true;
-
- box = box as RenderBox;
- listBox = listBox as RenderBox;
- var location = box.localToGlobal(Offset.zero);
- item.x = location.dx - prov.board.displacementX!;
- item.y = location.dy - prov.board.displacementY!;
-
- item.actualSize ??= box.size;
-
- // log("EXECUTED");
-
- item.width = box.size.width;
- item.height = box.size.height;
- list.x = listBox.localToGlobal(Offset.zero).dx - prov.board.displacementX!;
- list.y = listBox.localToGlobal(Offset.zero).dy - prov.board.displacementY!;
- return false;
- }
-
- void addPlaceHolder({required int listIndex, required int itemIndex}) {
- var prov = ref.read(ProviderList.boardProvider);
- var item = prov.board.lists[listIndex].items[itemIndex];
- item.child = Column(
- children: [
- // AnimatedOpacity(opacity: opacity, duration: duration)
- item.placeHolderAt != PlaceHolderAt.bottom
- ? TweenAnimationBuilder(
- duration: const Duration(milliseconds: 3000),
- curve: Curves.ease,
- tween: Tween(begin: 0, end: 1),
- builder: (context, value, child) {
- return Opacity(
- opacity: value,
- child: child,
- );
- },
- child: Container(
- // decoration: BoxDecoration(
- // border: Border.all(color: Colors.grey.shade200),
- // borderRadius: BorderRadius.circular(4),
- // color: item.backgroundColor ?? Colors.white,
- // ),
- margin: const EdgeInsets.only(
- bottom: 10,
- ),
- width: prov.draggedItemState!.width,
- height: prov.draggedItemState!.height,
- child: DottedBorder(
- child: const Center(
- child: Text(
- "Drop your task here ",
- style:
- TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
- )),
- ),
- ),
- )
- : Container(),
- // Container(
- // decoration: BoxDecoration(
- // borderRadius: BorderRadius.circular(4),
- // color: item.backgroundColor ?? Colors.white,
- // ),
- // width: item.actualSize!.width,
- // child: item.prevChild,
- // ),
- TweenAnimationBuilder(
- duration: const Duration(milliseconds: 500),
- curve: Curves.easeOut,
- tween: Tween(begin: item.actualSize!.height, end: 0),
- builder: (context, value, child) {
- return Transform.translate(
- offset: Offset(
- 0,
- item.placeHolderAt == PlaceHolderAt.bottom
- ? value
- : (-value)),
- child: child,
- );
- },
- child: Container(
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(4),
- color: item.backgroundColor ?? Colors.white,
- ),
- width: item.actualSize!.width,
- child: item.prevChild,
- ),
- ),
- item.placeHolderAt == PlaceHolderAt.bottom
- ? TweenAnimationBuilder(
- duration: const Duration(milliseconds: 3000),
- curve: Curves.ease,
- tween: Tween(begin: 0, end: 1),
- builder: (context, value, child) {
- return Opacity(
- opacity: value,
- child: child,
- );
- },
- child: Container(
- // decoration: BoxDecoration(
- // border: Border.all(color: Colors.grey.shade200),
- // borderRadius: BorderRadius.circular(4),
- // color: item.backgroundColor ?? Colors.white,
- // ),
- margin: const EdgeInsets.only(top: 10),
- width: prov.draggedItemState!.width,
- height: prov.draggedItemState!.height,
- child: DottedBorder(
- child: const Center(
- child: Text(
- "Drop your task here ",
- style:
- TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
- )),
- ),
- ),
- )
- : Container(),
- ],
- );
- }
-
- bool isPrevSystemCard({required int listIndex, required int itemIndex}) {
- var prov = ref.read(ProviderList.boardProvider);
- var item = prov.board.lists[listIndex].items[itemIndex];
- var isItemHidden = itemIndex - 1 >= 0 &&
- prov.draggedItemState!.itemIndex == itemIndex - 1 &&
- prov.draggedItemState!.listIndex == listIndex;
- if (prov.board.lists[prov.board.dragItemOfListIndex!]
- .items[prov.board.dragItemIndex!].addedBySystem ==
- true) {
- prov.board.lists[prov.board.dragItemOfListIndex!].items.removeAt(0);
- log("ITEM REMOVED");
-
- WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
- prov.board.lists[prov.board.dragItemOfListIndex!].setState!();
- if (isItemHidden) {
- // print("ITEM HIDDEN");
- prov.move = "DOWN";
- }
- prov.board.dragItemIndex = itemIndex;
- prov.board.dragItemOfListIndex = listIndex;
-
- // log("UPDATED | ITEM= ${prov.board.dragItemIndex} | LIST= $listIndex");
- item.setState!();
- });
-
- return true;
- }
- return false;
- }
-
- void checkForYAxisMovement({required int listIndex, required int itemIndex}) {
- var prov = ref.read(ProviderList.boardProvider);
- var item = prov.board.lists[listIndex].items[itemIndex];
-
- bool willPlaceHolderAtBottom =
- _bottomPlaceHolderPossibility(listIndex, itemIndex);
- // willPlaceHolderAtBottom = ((itemIndex ==
- // prov.board.lists[listIndex].items.length - 1) &&
- // ((prov.draggedItemState!.height * 0.6) + prov.valueNotifier.value.dy >
- // item.y! + item.height!) &&
- // item.placeHolderAt != PlaceHolderAt.bottom &&
- // prov.board.lists[listIndex].items[itemIndex].addedBySystem != true);
-
- // willPlaceHolderAtTop =
- // ((prov.valueNotifier.value.dy < item.y! + (item.height! * 0.5)) &&
- // (prov.draggedItemState!.height + prov.valueNotifier.value.dy >
- // item.y! + (item.height! * 0.5))) &&
- // prov.board.lists[listIndex].items[itemIndex].addedBySystem != true;
-
- // print(willPlaceHolderAtTop);
- // if (((willPlaceHolderAtTop || willPlaceHolderAtBottom) &&
- // prov.board.dragItemOfListIndex! == listIndex) &&
- // (prov.board.dragItemIndex != itemIndex ||
- // (willPlaceHolderAtBottom &&
- // item.placeHolderAt != PlaceHolderAt.bottom) ||
- // (item.placeHolderAt == PlaceHolderAt.bottom &&
- // (itemIndex == prov.board.lists[listIndex].items.length - 1))))
- if (getYAxisCondition(listIndex: listIndex, itemIndex: itemIndex)) {
- // log("UP/DOWNN");
- // print("BOTTOM PLACEHOLDER => ${willPlaceHolderAtBottom}");
- if (willPlaceHolderAtBottom &&
- item.placeHolderAt == PlaceHolderAt.bottom) {
- return;
- }
-
- if (prov.board.dragItemIndex! < itemIndex && prov.move != 'other') {
- prov.move = "DOWN";
- }
-
- resetCardWidget();
-
- item.placeHolderAt =
- willPlaceHolderAtBottom ? PlaceHolderAt.bottom : PlaceHolderAt.top;
-
- if (willPlaceHolderAtBottom) {
- prov.move = "LAST";
- }
- var isItemHidden = itemIndex - 1 >= 0 &&
- prov.draggedItemState!.itemIndex == itemIndex - 1 &&
- prov.draggedItemState!.listIndex == listIndex;
-
- if ((item.addedBySystem == null || !item.addedBySystem!)) {
- addPlaceHolder(listIndex: listIndex, itemIndex: itemIndex);
- // log("${item.placeHolderAt.name}=>${item.height}");
- }
- if (isPrevSystemCard(listIndex: listIndex, itemIndex: itemIndex)) return;
-
- var temp = prov.board.dragItemIndex;
- WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
- // log("PREVIOUS |${prov.board.dragItemOfListIndex}| LIST= ${prov.board.dragItemIndex}");
-
- if (!prov.board.lists[prov.board.dragItemOfListIndex!].items[temp!]
- .context!.mounted) return;
-
- if (isItemHidden) {
- prov.move = "DOWN";
- }
- if (itemIndex != prov.board.dragItemIndex &&
- prov.board.dragItemOfListIndex != listIndex) {
- prov.board.lists[prov.board.dragItemOfListIndex!].items[temp]
- .placeHolderAt = PlaceHolderAt.none;
- }
-
- prov.board.dragItemIndex = itemIndex;
- prov.board.dragItemOfListIndex = listIndex;
- prov.board.lists[prov.board.dragItemOfListIndex!].items[temp]
- .setState!();
- // log("UP/DOWN $listIndex $itemIndex");
- item.setState!();
- });
- }
- }
-
- bool isLastItemDragged({required int listIndex, required int itemIndex}) {
- var prov = ref.read(ProviderList.boardProvider);
- var item = prov.board.lists[listIndex].items[itemIndex];
- if (prov.draggedItemState!.itemIndex == itemIndex &&
- prov.draggedItemState!.listIndex == listIndex &&
- prov.board.lists[listIndex].items.length - 1 == itemIndex &&
- prov.board.dragItemIndex == itemIndex &&
- prov.board.dragItemOfListIndex == listIndex) {
- return true;
- }
-
- if ((prov.draggedItemState!.itemIndex == itemIndex &&
- prov.draggedItemState!.listIndex == listIndex &&
- prov.board.dragItemOfListIndex == listIndex &&
- prov.board.lists[listIndex].items.length - 1 == itemIndex &&
- ((prov.draggedItemState!.height * 0.6) + prov.valueNotifier.value.dy >
- item.y! + item.height!))) {
- WidgetsBinding.instance.addPostFrameCallback((timeStamp) {});
- WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
- // log("PREVIOUS |${prov.board.dragItemOfListIndex}| LIST= ${prov.board.dragItemIndex}");
-
- // if (isItemHidden) {
- // prov.move = "DOWN";
- // }
- prov.board.lists[prov.board.dragItemOfListIndex!]
- .items[prov.board.dragItemIndex!].child =
- prov.board.lists[prov.board.dragItemOfListIndex!]
- .items[prov.board.dragItemIndex!].prevChild;
- prov.board.lists[prov.board.dragItemOfListIndex!]
- .items[prov.board.dragItemIndex!].setState!();
- prov.board.dragItemIndex = itemIndex;
- prov.board.dragItemOfListIndex = listIndex;
-
- // log("UP/DOWN $listIndex $itemIndex");
- item.setState!();
- });
- return true;
- }
- return false;
- }
-
- bool _topPlaceHolderPossibility(int listIndex, int itemIndex) {
- var prov = ref.read(ProviderList.boardProvider);
- var item = prov.board.lists[listIndex].items[itemIndex];
-
- var willPlaceHolderAtTop = item.placeHolderAt == PlaceHolderAt.bottom
- ? (prov.valueNotifier.value.dy <
- item.y! + (item.actualSize!.height * 0.65))
- : ((prov.valueNotifier.value.dy <=
- item.y! + (item.actualSize!.height * 0.65)) &&
- (prov.draggedItemState!.height + prov.valueNotifier.value.dy >
- item.y! + (item.height!)));
-
- bool x = (item.placeHolderAt == PlaceHolderAt.bottom
- ? item.height != item.actualSize!.height
- : true);
- // if (item.placeHolderAt == PlaceHolderAt.bottom) {
- // print("BOTTOM TRUE");
- // }
-
- return willPlaceHolderAtTop &&
- prov.delta.dy < 0 &&
- item.placeHolderAt != PlaceHolderAt.top &&
- x &&
- prov.board.dragItemOfListIndex! == listIndex &&
- item.addedBySystem != true;
- }
-
- bool _bottomPlaceHolderPossibility(int listIndex, int itemIndex) {
- var prov = ref.read(ProviderList.boardProvider);
- var item = prov.board.lists[listIndex].items[itemIndex];
- var willPlaceHolderAtBottom = item.placeHolderAt == PlaceHolderAt.top
- ? (prov.draggedItemState!.height + prov.valueNotifier.value.dy >=
- item.y! + (item.height! * 0.8))
- : ((prov.draggedItemState!.height + prov.valueNotifier.value.dy >=
- item.y! + (item.height! * 0.5)) &&
- (prov.valueNotifier.value.dy < item.y!));
-
- bool x = (item.placeHolderAt == PlaceHolderAt.top
- ? item.height != item.actualSize!.height
- : true);
-
- return
- // false&&
- willPlaceHolderAtBottom &&
- item.placeHolderAt != PlaceHolderAt.bottom &&
- prov.board.dragItemOfListIndex! == listIndex &&
- item.addedBySystem != true &&
- x;
- }
-
- bool getYAxisCondition({required int listIndex, required int itemIndex}) {
- var prov = ref.read(ProviderList.boardProvider);
- var item = prov.board.lists[listIndex].items[itemIndex];
- bool value = (_topPlaceHolderPossibility(listIndex, itemIndex) ||
- _bottomPlaceHolderPossibility(listIndex, itemIndex)) &&
- prov.board.dragItemOfListIndex! == listIndex;
- return value && item.addedBySystem != true;
- }
-
- bool getXAxisCondition({required int listIndex, required int itemIndex}) {
- var prov = ref.read(ProviderList.boardProvider);
-
- var right = ((prov.draggedItemState!.width * 0.6) +
- prov.valueNotifier.value.dx >
- prov.board.lists[listIndex].x!) &&
- ((prov.board.lists[listIndex].x! + prov.board.lists[listIndex].width! >
- prov.draggedItemState!.width + prov.valueNotifier.value.dx)) &&
- (prov.board.dragItemOfListIndex != listIndex);
- var left = (((prov.draggedItemState!.width) + prov.valueNotifier.value.dx >
- prov.board.lists[listIndex].x! +
- prov.board.lists[listIndex].width!) &&
- ((prov.draggedItemState!.width * 0.6) + prov.valueNotifier.value.dx <
- prov.board.lists[listIndex].x! +
- prov.board.lists[listIndex].width!) &&
- (prov.board.dragItemOfListIndex != listIndex));
-
- return (left || right);
- }
-
- void checkForXAxisMovement({required int listIndex, required int itemIndex}) {
- var prov = ref.read(ProviderList.boardProvider);
- var item = prov.board.lists[listIndex].items[itemIndex];
-
- var canReplaceCurrent = ((prov.valueNotifier.value.dy >= item.y!) &&
- (item.height! + item.y! >
- prov.valueNotifier.value.dy + (prov.draggedItemState!.height / 2)));
- var willPlaceHolderAtBottom = (itemIndex ==
- prov.board.lists[listIndex].items.length - 1 &&
- ((prov.draggedItemState!.height * 0.6) + prov.valueNotifier.value.dy >
- item.y! + item.height!));
-
- if (canReplaceCurrent || willPlaceHolderAtBottom) {
- // log("X AXIS");
- prov.move = "other";
-
- resetCardWidget();
-
- item.placeHolderAt =
- willPlaceHolderAtBottom ? PlaceHolderAt.bottom : PlaceHolderAt.top;
- if (willPlaceHolderAtBottom) {
- prov.move = "LAST";
- // log("BOTTOM PLACEHOLDER X AXIS");
- }
-
- var isItemHidden = itemIndex - 1 >= 0 &&
- prov.draggedItemState!.itemIndex == itemIndex - 1 &&
- prov.draggedItemState!.listIndex == listIndex;
-
- // if (!isItemHidden) {
- addPlaceHolder(listIndex: listIndex, itemIndex: itemIndex);
- // }
- if (isPrevSystemCard(listIndex: listIndex, itemIndex: itemIndex)) return;
-
- WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
- prov.board.lists[prov.board.dragItemOfListIndex!]
- .items[prov.board.dragItemIndex!].setState!();
- if (isItemHidden) {
- prov.move = "DOWN";
- }
- prov.board.dragItemIndex = itemIndex;
- prov.board.dragItemOfListIndex = listIndex;
- // log("UPDATED | ITEM= $listIndex | LIST= $itemIndex");
- item.setState!();
- });
- }
- }
-
- void onLongpressCard(
- {required int listIndex,
- required int itemIndex,
- required BuildContext context,
- required VoidCallback setsate}) {
- var prov = ref.read(ProviderList.boardProvider);
- final draggableProv = ref.read(ProviderList.draggableNotifier.notifier);
- var box = context.findRenderObject() as RenderBox;
- var location = box.localToGlobal(Offset.zero);
- prov.board.lists[listIndex].items[itemIndex].x =
- location.dx - prov.board.displacementX!;
- prov.board.lists[listIndex].items[itemIndex].y =
- location.dy - prov.board.displacementY!;
- // prov.board.lists[listIndex].items[itemIndex].width = box.size.width;
- // prov.board.lists[listIndex].items[itemIndex].height = box.size.height;
- prov.updateValue(
- dx: location.dx - prov.board.displacementX!,
- dy: location.dy - prov.board.displacementY!);
- prov.board.dragItemIndex = itemIndex;
- prov.board.dragItemOfListIndex = listIndex;
- draggableProv.setDraggableType(DraggableType.card);
- prov.draggedItemState = DraggedItemState(
- child: SizedBox(
- width:
- prov.board.lists[listIndex].items[itemIndex].context!.size!.width,
- child: prov.board.lists[listIndex].items[itemIndex].child,
- ),
- listIndex: listIndex,
- itemIndex: itemIndex,
- height: box.size.height - 10,
- width: box.size.width,
- x: location.dx,
- y: location.dy);
- prov.draggedItemState!.setState = setsate;
- // log("${listIndex} ${itemIndex}");
- setsate();
- }
-
- bool isCurrentElementDragged(
- {required int listIndex, required int itemIndex}) {
- var prov = ref.read(ProviderList.boardProvider);
- final draggableProv = ref.read(ProviderList.draggableNotifier);
-
- return draggableProv.isCardDragged &&
- prov.draggedItemState!.itemIndex == itemIndex &&
- prov.draggedItemState!.listIndex == listIndex;
- }
-
- void saveNewCard() {
- var boardProv = ref.read(ProviderList.boardProvider);
- final cardState = boardProv.newCardState;
- boardProv.board.lists[cardState.listIndex!].items[cardState.cardIndex!]
- .child = Container(
- decoration: BoxDecoration(
- border: Border.all(color: Colors.grey.shade200),
- borderRadius: BorderRadius.circular(4),
- color: Colors.white,
- ),
- margin: const EdgeInsets.only(bottom: 10),
- padding: const EdgeInsets.all(12),
- child: Text(boardProv.newCardState.textController.text,
- style: boardProv.board.textStyle),
- );
- boardProv.board.lists[cardState.listIndex!].items[cardState.cardIndex!]
- .prevChild =
- boardProv.board.lists[cardState.listIndex!].items[cardState.cardIndex!]
- .child;
- cardState.isFocused = false;
- boardProv.board.lists[cardState.listIndex!].items[cardState.listIndex!]
- .isNew = false;
- boardProv.newCardState.textController.clear();
- boardProv.board.lists[cardState.listIndex!].items[cardState.listIndex!]
- .setState!();
- cardState.cardIndex = null;
- cardState.listIndex = null;
- log("TAPPED");
- }
-
- void reorderCard() {
- var boardProv = ref.read(ProviderList.boardProvider);
- BoardList list =
- boardProv.board.lists[boardProv.board.dragItemOfListIndex!];
- ListItem card = list.items[boardProv.board.dragItemIndex!];
- card.child = card.prevChild;
-
- if (boardProv.draggedItemState!.listIndex ==
- boardProv.board.dragItemOfListIndex!) {
- list.items.insert(
- boardProv.board.dragItemIndex!,
- boardProv.board.lists[boardProv.draggedItemState!.listIndex!].items
- .removeAt(boardProv.draggedItemState!.itemIndex!));
- } else {
- if (card.placeHolderAt == PlaceHolderAt.bottom) {
- list.items.insert(
- boardProv.board.dragItemIndex! + 1,
- boardProv.board.lists[boardProv.draggedItemState!.listIndex!].items
- .removeAt(boardProv.draggedItemState!.itemIndex!));
- } else {
- list.items.insert(
- boardProv.board.dragItemIndex!,
- boardProv.board.lists[boardProv.draggedItemState!.listIndex!].items
- .removeAt(boardProv.draggedItemState!.itemIndex!));
- }
- }
-
- card.placeHolderAt = PlaceHolderAt.none;
- }
-}
diff --git a/lib/Provider/provider_list.dart b/lib/Provider/provider_list.dart
deleted file mode 100755
index ce177b9..0000000
--- a/lib/Provider/provider_list.dart
+++ /dev/null
@@ -1,23 +0,0 @@
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:kanban_board/Provider/draggable_provider.dart';
-
-import 'list_item_provider.dart';
-
-import 'board_list_provider.dart';
-import 'board_provider.dart';
-
-class ProviderList {
- static final boardProvider = ChangeNotifierProvider(
- (ref) => BoardProvider(ref),
- );
- static final cardProvider = ChangeNotifierProvider(
- (ref) => ListItemProvider(ref),
- );
- static final boardListProvider = ChangeNotifierProvider(
- (ref) => BoardListProvider(ref),
- );
-
- static final draggableNotifier =
- StateNotifierProvider(
- (ref) => DraggableNotfier(ref));
-}
diff --git a/lib/constants.dart b/lib/constants.dart
deleted file mode 100644
index 15ba122..0000000
--- a/lib/constants.dart
+++ /dev/null
@@ -1,4 +0,0 @@
-// ignore_for_file: constant_identifier_names
-
-const double BOARD_PADDING = 20;
-const double LIST_GAP = 10;
diff --git a/lib/custom/board.dart b/lib/custom/board.dart
deleted file mode 100755
index a484d6b..0000000
--- a/lib/custom/board.dart
+++ /dev/null
@@ -1,480 +0,0 @@
-import 'package:dotted_border/dotted_border.dart';
-import 'package:flutter/gestures.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:kanban_board/constants.dart';
-import 'package:kanban_board/draggable/draggable_state.dart';
-import 'package:kanban_board/draggable/presentation/dragged_card.dart';
-import '../Provider/provider_list.dart';
-import '../models/board_list.dart' as board_list;
-import '../models/inputs.dart';
-import 'board_list.dart';
-import 'text_field.dart';
-
-class KanbanBoard extends StatefulWidget {
- const KanbanBoard(
- this.list, {
- this.backgroundColor = Colors.white,
- this.cardPlaceHolderColor,
- this.boardScrollConfig,
- this.listScrollConfig,
- this.listPlaceHolderColor,
- this.boardDecoration,
- this.cardTransitionBuilder,
- this.listTransitionBuilder,
- this.cardTransitionDuration = const Duration(milliseconds: 150),
- this.listTransitionDuration = const Duration(milliseconds: 150),
- this.listDecoration,
- this.textStyle,
- this.onItemTap,
- this.displacementX = 0.0,
- this.displacementY = 0.0,
- this.onItemReorder,
- this.onListReorder,
- this.onListRename,
- this.onNewCardInsert,
- this.onItemLongPress,
- this.onListTap,
- this.onListLongPress,
- super.key,
- });
- final List list;
- final Color backgroundColor;
- final ScrollConfig? boardScrollConfig;
- final ScrollConfig? listScrollConfig;
- final Color? cardPlaceHolderColor;
- final Color? listPlaceHolderColor;
- final TextStyle? textStyle;
- final Decoration? listDecoration;
- final Decoration? boardDecoration;
- final void Function(int? cardIndex, int? listIndex)? onItemTap;
- final void Function(int? cardIndex, int? listIndex)? onItemLongPress;
- final void Function(int? listIndex)? onListTap;
- final void Function(int? listIndex)? onListLongPress;
- final void Function(int? oldCardIndex, int? newCardIndex, int? oldListIndex,
- int? newListIndex)? onItemReorder;
- final void Function(int? oldListIndex, int? newListIndex)? onListReorder;
- final void Function(String? oldName, String? newName)? onListRename;
- final void Function(String? cardIndex, String? listIndex, String? text)?
- onNewCardInsert;
- final Widget Function(Widget child, Animation animation)?
- cardTransitionBuilder;
- final Widget Function(Widget child, Animation animation)?
- listTransitionBuilder;
- final double displacementX;
- final double displacementY;
- final Duration cardTransitionDuration;
- final Duration listTransitionDuration;
-
- @override
- State createState() => _KanbanBoardState();
-}
-
-class _KanbanBoardState extends State {
- @override
- Widget build(BuildContext context) {
- return ProviderScope(
- child: MaterialApp(
- debugShowCheckedModeBanner: false,
- home: Board(widget.list,
- displacementX: widget.displacementX,
- displacementY: widget.displacementY,
- backgroundColor: widget.backgroundColor,
- boardDecoration: widget.boardDecoration,
- cardPlaceHolderColor: widget.cardPlaceHolderColor,
- listPlaceHolderColor: widget.listPlaceHolderColor,
- listDecoration: widget.listDecoration,
- boardScrollConfig: widget.boardScrollConfig,
- listScrollConfig: widget.listScrollConfig,
- textStyle: widget.textStyle,
- onItemTap: widget.onItemTap,
- onItemLongPress: widget.onItemLongPress,
- onListTap: widget.onListTap,
- onListLongPress: widget.onListLongPress,
- onItemReorder: widget.onItemReorder,
- onListReorder: widget.onListReorder,
- onListRename: widget.onListRename,
- onNewCardInsert: widget.onNewCardInsert,
- cardTransitionBuilder: widget.cardTransitionBuilder,
- listTransitionBuilder: widget.listTransitionBuilder,
- cardTransitionDuration: widget.cardTransitionDuration,
- listTransitionDuration: widget.listTransitionDuration),
- ));
- }
-}
-
-class Board extends ConsumerStatefulWidget {
- const Board(
- this.list, {
- this.backgroundColor = Colors.white,
- this.cardPlaceHolderColor,
- this.listPlaceHolderColor,
- this.boardDecoration,
- this.boardScrollConfig,
- this.listScrollConfig,
- this.cardTransitionBuilder,
- this.listTransitionBuilder,
- this.cardTransitionDuration = const Duration(milliseconds: 150),
- this.listTransitionDuration = const Duration(milliseconds: 150),
- this.listDecoration,
- this.textStyle,
- this.onItemTap,
- this.displacementX = 0.0,
- this.displacementY = 0.0,
- this.onItemReorder,
- this.onListReorder,
- this.onListRename,
- this.onNewCardInsert,
- this.onItemLongPress,
- this.onListTap,
- this.onListLongPress,
- super.key,
- });
- final List list;
- final Color backgroundColor;
- final Color? cardPlaceHolderColor;
- final Color? listPlaceHolderColor;
- final TextStyle? textStyle;
- final Decoration? listDecoration;
- final Decoration? boardDecoration;
- final ScrollConfig? boardScrollConfig;
- final ScrollConfig? listScrollConfig;
- final void Function(int? cardIndex, int? listIndex)? onItemTap;
- final void Function(int? cardIndex, int? listIndex)? onItemLongPress;
- final void Function(int? listIndex)? onListTap;
- final void Function(int? listIndex)? onListLongPress;
- final void Function(int? oldCardIndex, int? newCardIndex, int? oldListIndex,
- int? newListIndex)? onItemReorder;
- final void Function(int? oldListIndex, int? newListIndex)? onListReorder;
- final void Function(String? oldName, String? newName)? onListRename;
- final void Function(String? cardIndex, String? listIndex, String? text)?
- onNewCardInsert;
- final Widget Function(Widget child, Animation animation)?
- cardTransitionBuilder;
- final Widget Function(Widget child, Animation animation)?
- listTransitionBuilder;
- final double displacementX;
- final double displacementY;
- final Duration cardTransitionDuration;
- final Duration listTransitionDuration;
-
- @override
- ConsumerState createState() => _BoardState();
-}
-
-class _BoardState extends ConsumerState {
- @override
- void initState() {
- var boardProv = ref.read(ProviderList.boardProvider);
- final draggableProv = ref.read(ProviderList.draggableNotifier);
- var boardListProv = ref.read(ProviderList.boardListProvider);
- boardProv.initializeBoard(
- data: widget.list,
- boardScrollConfig: widget.boardScrollConfig,
- listScrollConfig: widget.listScrollConfig,
- displacementX: widget.displacementX,
- displacementY: widget.displacementY,
- backgroundColor: widget.backgroundColor,
- boardDecoration: widget.boardDecoration,
- cardPlaceHolderColor: widget.cardPlaceHolderColor,
- listPlaceHolderColor: widget.listPlaceHolderColor,
- listDecoration: widget.listDecoration,
- textStyle: widget.textStyle,
- onItemTap: widget.onItemTap,
- onItemLongPress: widget.onItemLongPress,
- onListTap: widget.onListTap,
- onListLongPress: widget.onListLongPress,
- onItemReorder: widget.onItemReorder,
- onListReorder: widget.onListReorder,
- onListRename: widget.onListRename,
- onNewCardInsert: widget.onNewCardInsert,
- cardTransitionBuilder: widget.cardTransitionBuilder,
- listTransitionBuilder: widget.listTransitionBuilder,
- cardTransitionDuration: widget.cardTransitionDuration,
- listTransitionDuration: widget.listTransitionDuration);
-
- for (var element in boardProv.board.lists) {
- // List Scroll Listener
- element.scrollController.addListener(() {
- if (boardListProv.scrolling) {
- if (boardListProv.scrollingDown) {
- boardProv.valueNotifier.value = Offset(
- boardProv.valueNotifier.value.dx,
- boardProv.valueNotifier.value.dy + 0.00001);
- } else {
- boardProv.valueNotifier.value = Offset(
- boardProv.valueNotifier.value.dx,
- boardProv.valueNotifier.value.dy + 0.00001);
- }
- }
- });
- }
-
- // Board Scroll Listener
- boardProv.board.controller.addListener(() {
- if (boardProv.scrolling) {
- if (boardProv.scrollingLeft && draggableProv.isListDragged) {
- for (var element in boardProv.board.lists) {
- if (element.context == null) break;
- var of = (element.context!.findRenderObject() as RenderBox)
- .localToGlobal(Offset.zero);
- element.x = of.dx - boardProv.board.displacementX! - 10;
- element.width = element.context!.size!.width - 30;
- element.y = of.dy - widget.displacementY + 24;
- }
-
- boardListProv.moveListLeft();
- } else if (boardProv.scrollingRight && draggableProv.isListDragged) {
- for (var element in boardProv.board.lists) {
- if (element.context == null) break;
- var of = (element.context!.findRenderObject() as RenderBox)
- .localToGlobal(Offset.zero);
- element.x = of.dx - boardProv.board.displacementX! - 10;
- element.width = element.context!.size!.width - 30;
- element.y = of.dy - widget.displacementY + 24;
- }
- boardListProv.moveListRight();
- }
- }
- });
-
- super.initState();
- }
-
- @override
- Widget build(BuildContext context) {
- var boardProv = ref.read(ProviderList.boardProvider);
- var boardListProv = ref.read(ProviderList.boardListProvider);
- final draggableProv = ref.watch(ProviderList.draggableNotifier);
- final draggableNotifier = ref.read(ProviderList.draggableNotifier.notifier);
- double statusBarHeight = MediaQuery.of(context).padding.top;
- WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
- boardProv.board.setstate = () => setState(() {});
- var box = context.findRenderObject() as RenderBox;
- boardProv.board.displacementX =
- box.localToGlobal(Offset.zero).dx + BOARD_PADDING; //- margin
- boardProv.board.displacementY = box.localToGlobal(Offset.zero).dy -
- statusBarHeight +
- BOARD_PADDING; // statusbar
- });
- return Listener(
- onPointerUp: (event) {
- if (draggableProv.draggableType != DraggableType.none) {
- if (draggableProv.isCardDragged) {
- ref.read(ProviderList.cardProvider).reorderCard();
- }
- boardProv.move = "";
- draggableNotifier.stopDragging();
- setState(() {});
- }
- },
- onPointerMove: (event) {
- if (draggableProv.isCardDragged) {
- if (event.delta.dx > 0) {
- boardProv.boardScroll();
- } else {
- boardProv.boardScroll();
- }
- } else if (draggableProv.isListDragged) {
- if (event.delta.dx > 0) {
- boardProv.boardScroll();
- boardListProv.moveListRight();
- } else {
- boardProv.boardScroll();
- boardListProv.moveListLeft();
- }
- }
- boardProv.delta = event.delta;
- boardProv.valueNotifier.value = Offset(
- event.delta.dx + boardProv.valueNotifier.value.dx,
- event.delta.dy + boardProv.valueNotifier.value.dy);
- },
- child: GestureDetector(
- onTap: () {
- if (boardProv.newCardState.isFocused == true) {
- ref.read(ProviderList.cardProvider).saveNewCard();
- }
- },
- child: Scaffold(
- backgroundColor: Colors.white,
- body: Container(
- padding:
- const EdgeInsets.only(top: BOARD_PADDING, left: BOARD_PADDING),
- decoration: widget.boardDecoration ??
- BoxDecoration(color: widget.backgroundColor),
- child: Stack(
- fit: StackFit.passthrough,
- clipBehavior: Clip.none,
- children: [
- Row(
- children: [
- Expanded(
- child: SizedBox(
- height: 1200,
- child: ScrollConfiguration(
- behavior: ScrollConfiguration.of(context).copyWith(
- dragDevices: {
- PointerDeviceKind.mouse,
- PointerDeviceKind.touch,
- },
- ),
- child: SingleChildScrollView(
- controller: boardProv.board.controller,
- scrollDirection: Axis.horizontal,
- child: Row(
- children: boardProv.board.lists
- .map(
- (e) =>
- boardProv.board.lists.indexOf(e) !=
- boardProv.board.lists
- .length -
- 1
- ? BoardList(
- index: boardProv.board.lists
- .indexOf(e),
- )
- : Row(
- crossAxisAlignment:
- CrossAxisAlignment
- .start,
- children: [
- BoardList(
- index: boardProv
- .board.lists
- .indexOf(e),
- ),
- boardListProv.newList
- ? Container(
- margin:
- const EdgeInsets
- .only(
- top: 20,
- left: 30,
- ),
- padding:
- const EdgeInsets
- .only(
- bottom: 20,
- ),
- width: 300,
- color: const Color
- .fromARGB(
- 255,
- 247,
- 248,
- 252,
- ),
- child: Wrap(
- children: [
- SizedBox(
- height: 50,
- width: 300,
- child: Row(
- mainAxisAlignment:
- MainAxisAlignment
- .spaceBetween,
- children: [
- IconButton(
- onPressed:
- () {
- setState(() {
- boardListProv.newList = false;
- boardProv.newCardState.textController.clear();
- });
- },
- icon:
- const Icon(Icons.close),
- ),
- IconButton(
- onPressed:
- () {
- setState(() {
- boardListProv.newList = false;
- boardProv.board.lists.add(board_list.BoardList(
- width: 300,
- scrollController: ScrollController(),
- items: [],
- title: boardProv.newCardState.textController.text,
- ));
- boardProv.newCardState.textController.clear();
- });
- },
- icon:
- const Icon(Icons.done))
- ],
- ),
- ),
- Container(
- width:
- 300,
- color: Colors
- .white,
- margin: const EdgeInsets
- .only(
- top:
- 20,
- right:
- 10,
- left:
- 10),
- child:
- const TField()),
- ],
- ),
- )
- : GestureDetector(
- onTap: () {
- if (boardProv
- .newCardState
- .isFocused ==
- true) {
- ref
- .read(ProviderList
- .cardProvider)
- .saveNewCard();
- }
- boardListProv
- .newList =
- true;
- setState(() {});
- },
- child: Container(
- height: 50,
- width: 300,
- margin: const EdgeInsets
- .only(
- top: 10,
- left: 20),
- decoration: BoxDecoration(
- color: Colors
- .transparent,
- borderRadius:
- BorderRadius.circular(
- 6)),
- child:
- DottedBorder(
- child: Center(
- child: Text(
- "Add List",
- style:
- widget.textStyle)),
- )),
- )
- ],
- ))
- .toList()),
- ),
- ),
- ),
- ),
- ],
- ),
- const DraggedCard()
- ],
- ),
- ),
- ),
- ),
- );
- }
-}
diff --git a/lib/custom/board_list.dart b/lib/custom/board_list.dart
deleted file mode 100644
index 69d4631..0000000
--- a/lib/custom/board_list.dart
+++ /dev/null
@@ -1,378 +0,0 @@
-import 'dart:developer';
-import 'package:flutter/material.dart';
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:kanban_board/constants.dart';
-import 'package:kanban_board/draggable/draggable_state.dart';
-import '../../Provider/provider_list.dart';
-import '../models/item_state.dart';
-import 'list_item.dart';
-
-class BoardList extends ConsumerStatefulWidget {
- const BoardList({super.key, required this.index});
- final int index;
-
- @override
- ConsumerState createState() => _BoardListState();
-}
-
-class _BoardListState extends ConsumerState {
- Offset location = Offset.zero;
-
- @override
- Widget build(BuildContext context) {
- var prov = ref.read(ProviderList.boardProvider);
- var listProv = ref.read(ProviderList.boardListProvider);
- final draggableNotfier = ref.read(ProviderList.draggableNotifier);
- WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
- listProv.calculateSizePosition(
- listIndex: widget.index,
- context: context,
- setstate: () => setState(() {}));
- });
- return ValueListenableBuilder(
- valueListenable: prov.valueNotifier,
- builder: (context, a, b) {
- if (draggableNotfier.draggableType == DraggableType.card) {
- var draggedItemIndex = prov.board.dragItemIndex;
- var draggedItemListIndex = prov.board.dragItemOfListIndex;
- var list = prov.board.lists[widget.index];
- var box = context.findRenderObject();
- var listBox = list.context!.findRenderObject();
- if (box == null || listBox == null) return b!;
-
- box = box as RenderBox;
- listBox = listBox as RenderBox;
- location = box.localToGlobal(Offset.zero);
-
- list.x = listBox.localToGlobal(Offset.zero).dx -
- prov.board.displacementX!;
- list.y = listBox.localToGlobal(Offset.zero).dy -
- prov.board.displacementY!;
-
- if (((prov.draggedItemState!.width * 0.6) +
- prov.valueNotifier.value.dx >
- prov.board.lists[widget.index].x!) &&
- ((prov.board.lists[widget.index].x! +
- prov.board.lists[widget.index].width! >
- prov.draggedItemState!.width +
- prov.valueNotifier.value.dx)) &&
- (prov.board.dragItemOfListIndex! != widget.index)) {
- // print("RIGHT ->");
- // print(prov.board.lists[widget.index].items.length);
- // CASE: WHEN ELEMENT IS DRAGGED RIGHT SIDE AND LIST HAVE NO ELEMENT IN IT //
- if (prov.board.lists[widget.index].items.isEmpty) {
- log("LIST 0 RIGHT");
- prov.move = "REPLACE";
- prov.board.lists[widget.index].items.add(ListItem(
- child: Container(
- decoration: BoxDecoration(
- border: Border.all(color: Colors.grey.shade200),
- borderRadius: BorderRadius.circular(6),
- color: prov.board.cardPlaceholderColor ?? Colors.white,
- ),
- margin: const EdgeInsets.only(top: 5),
- width: prov.draggedItemState!.width,
- height: prov.draggedItemState!.height,
- ),
- prevChild:
- Container(), // WE HAVE ADDED THIS IN EMPTY LIST JUST TO SHOW PLACEHOLDER, so it should be hiddent
- listIndex: widget.index,
- index: 0,
- addedBySystem: true));
-
- WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
- prov.board.lists[prov.board.dragItemOfListIndex!]
- .items[prov.board.dragItemIndex!].child =
- prov.board.lists[prov.board.dragItemOfListIndex!]
- .items[prov.board.dragItemIndex!].prevChild;
- prov.board.lists[prov.board.dragItemOfListIndex!]
- .items[prov.board.dragItemIndex!].setState!();
- if (prov.board.lists[prov.board.dragItemOfListIndex!]
- .items[prov.board.dragItemIndex!].addedBySystem ==
- true) {
- prov.board.lists[prov.board.dragItemOfListIndex!].items
- .removeAt(0);
- log("ITEM REMOVED");
- prov.board.lists[prov.board.dragItemOfListIndex!]
- .setState!();
- }
- prov.board.dragItemIndex = 0;
- prov.board.dragItemOfListIndex = widget.index;
- setState(() {});
- });
- }
- // CASE WHEN LIST HAVE ONLY ONE ITEM AND IT IS PICKED, SO NOW IT IS HIDDEN, ITS SIZE IS 0 , SO WE NEED TO HANDLE IT EXPLICITLY //
- else if (prov.board.lists[widget.index].items.length == 1 &&
- prov.draggedItemState!.itemIndex == 0 &&
- prov.draggedItemState!.listIndex == widget.index) {
- // print("RIGHT LENGTH == 1");
- WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
- if (prov.board.lists[draggedItemListIndex!]
- .items[draggedItemIndex!].addedBySystem ==
- true) {
- prov.board.lists[draggedItemListIndex].items
- .removeAt(draggedItemIndex);
- prov.board.lists[draggedItemListIndex].setState!();
- } else {
- prov
- .board
- .lists[prov.board.dragItemOfListIndex!]
- .items[prov.board.dragItemIndex!]
- .placeHolderAt = PlaceHolderAt.none;
-
- prov.board.lists[prov.board.dragItemOfListIndex!]
- .items[prov.board.dragItemIndex!].child =
- prov.board.lists[prov.board.dragItemOfListIndex!]
- .items[prov.board.dragItemIndex!].prevChild;
-
- prov.board.lists[prov.board.dragItemOfListIndex!]
- .items[prov.board.dragItemIndex!].setState!();
- }
- prov.board.dragItemIndex = 0;
- prov.board.dragItemOfListIndex = widget.index;
- // log("UPDATED | ITEM= ${widget.itemIndex} | LIST= ${widget.listIndex}");
- prov.board.lists[prov.board.dragItemOfListIndex!]
- .items[prov.board.dragItemIndex!].setState!();
- });
- }
- } else if (((prov.draggedItemState!.width * 0.6) +
- prov.valueNotifier.value.dx <
- prov.board.lists[widget.index].x! +
- prov.board.lists[widget.index].width!) &&
- ((prov.board.lists[widget.index].x! +
- prov.board.lists[widget.index].width! <
- prov.draggedItemState!.width +
- prov.valueNotifier.value.dx)) &&
- (prov.board.dragItemOfListIndex! != widget.index)) {
- if (prov.board.lists[widget.index].items.isEmpty) {
- prov.move = "REPLACE";
- // print("LIST 0 LEFT");
-
- prov.board.lists[widget.index].items.add(ListItem(
- child: Container(
- decoration: BoxDecoration(
- border: Border.all(color: Colors.grey.shade200),
- borderRadius: BorderRadius.circular(6),
- color: prov.board.cardPlaceholderColor ?? Colors.white,
- ),
- margin: const EdgeInsets.only(top: 5),
- width: prov.draggedItemState!.width,
- height: prov.draggedItemState!.height,
- ),
- prevChild:
- Container(), // WE HAVE ADDED THIS IN EMPTY LIST JUST TO SHOW PLACEHOLDER, so it should be hiddent
- listIndex: widget.index,
- index: 0,
- addedBySystem: true));
-
- WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
- prov.board.lists[prov.board.dragItemOfListIndex!]
- .items[prov.board.dragItemIndex!].child =
- prov.board.lists[prov.board.dragItemOfListIndex!]
- .items[prov.board.dragItemIndex!].prevChild;
- prov.board.lists[prov.board.dragItemOfListIndex!]
- .items[prov.board.dragItemIndex!].setState!();
-
- if (prov.board.lists[prov.board.dragItemOfListIndex!]
- .items[prov.board.dragItemIndex!].addedBySystem ==
- true) {
- prov.board.lists[prov.board.dragItemOfListIndex!].items
- .removeAt(0);
- log("ITEM REMOVED");
- prov.board.lists[prov.board.dragItemOfListIndex!]
- .setState!();
- }
- prov.board.dragItemIndex = 0;
- prov.board.dragItemOfListIndex = widget.index;
- setState(() {});
- });
- }
- // CASE: WHEN LIST CONTAINS ONLY ONE ITEM, AND WHICH IS THE FIRST ITEM DRAGGED DURING A PARTICULAR SESSION, WHICH IS CURRENTLY HIDDEN //
-
- else if (prov.board.lists[widget.index].items.length == 1 &&
- prov.draggedItemState!.itemIndex == 0 &&
- prov.draggedItemState!.listIndex == widget.index) {
- // print("LEFT LENGTH == 1");
- WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
- // CASE : IF PREVIOUSLY PLACEHOLDER IS ADDED IN EMPTY LIST THEN EXPLICITLY REMOVE THAT PLACEHOLDER, AND MAKE THAT LIST EMPTY AGAIN //
- if (prov.board.lists[draggedItemListIndex!]
- .items[draggedItemIndex!].addedBySystem ==
- true) {
- prov.board.lists[draggedItemListIndex].items
- .removeAt(draggedItemIndex);
- prov.board.lists[draggedItemListIndex].setState!();
- } else {
- prov
- .board
- .lists[draggedItemListIndex]
- .items[draggedItemIndex]
- .placeHolderAt = PlaceHolderAt.none;
-
- prov.board.lists[draggedItemListIndex]
- .items[draggedItemIndex].child =
- prov.board.lists[draggedItemListIndex]
- .items[draggedItemIndex].prevChild;
-
- prov.board.lists[draggedItemListIndex]
- .items[draggedItemIndex].setState!();
- }
-
- // Placeholder is updated at current position //
- prov.board.dragItemIndex = 0;
- prov.board.dragItemOfListIndex = widget.index;
- // log("UPDATED | ITEM= ${widget.itemIndex} | LIST= ${widget.listIndex}");
- prov.board.lists[prov.board.dragItemOfListIndex!]
- .items[prov.board.dragItemIndex!].setState!();
- });
- }
- }
- }
- return b!;
- },
- child: Container(
- margin: const EdgeInsets.only(bottom: 15, right: LIST_GAP),
- width: prov.board.lists[widget.index].width!,
- decoration: prov.board.listDecoration ??
- BoxDecoration(
- color: prov.board.lists[widget.index].backgroundColor,
- ),
- child: draggableNotfier.draggableType == DraggableType.list &&
- prov.draggedItemState!.listIndex == widget.index
- ? TweenAnimationBuilder(
- duration: const Duration(milliseconds: 500),
- curve: Curves.ease,
- tween: Tween(begin: 0, end: 1),
- builder: (context, value, child) {
- return Opacity(
- opacity: value,
- child: child,
- );
- },
- child: Opacity(
- opacity: 0.4, child: prov.draggedItemState!.child))
- : Column(children: [
- GestureDetector(
- onLongPress: () {
- listProv.onListLongpress(
- listIndex: widget.index,
- context: context,
- setstate: () => setState(() {}));
- },
- child: prov.board.lists[widget.index].header ??
- Container(
- width: prov.board.lists[widget.index].width,
- color: prov
- .board.lists[widget.index].headerBackgroundColor,
- padding: const EdgeInsets.only(
- left: 15, bottom: 10, top: 10, right: 0),
- alignment: Alignment.centerLeft,
- child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Text(
- prov.board.lists[widget.index].title,
- style: prov.board.textStyle ??
- const TextStyle(
- fontSize: 17,
- color: Colors.black,
- fontWeight: FontWeight.w500),
- ),
- SizedBox(
- // padding: const EdgeInsets.all(5),
- child: PopupMenuButton(
- constraints: BoxConstraints(
- minWidth: prov.board.lists[widget.index]
- .width! *
- 0.7,
- maxWidth: prov.board.lists[widget.index]
- .width! *
- 0.7,
- ),
- itemBuilder: (ctx) {
- return [
- PopupMenuItem(
- value: 1,
- child: Text(
- "Add card",
- style: prov.board.textStyle,
- ),
- ),
- PopupMenuItem(
- value: 2,
- child: Text(
- "Delete List",
- style: prov.board.textStyle,
- ),
- ),
- ];
- },
- onSelected: (value) async {
- if (value == 1) {
- listProv.addNewCard(
- position: "TOP",
- listIndex: widget.index);
- } else if (value == 2) {
- prov.board.lists
- .removeAt(widget.index);
- prov.board.setstate!();
- }
- })),
- ],
- ),
- ),
- ),
- Flexible(
- child: ListView.builder(
- // physics: const ClampingScrollPhysics()
- controller:
- prov.board.lists[widget.index].scrollController,
- itemCount: prov.board.lists[widget.index].items.length,
- shrinkWrap: true,
- itemBuilder: (ctx, index) {
- return Item(
- itemIndex: index,
- listIndex: widget.index,
- );
- },
-
- // itemCount: prov.items.length,
- ),
- ),
- prov.board.lists[widget.index].footer ??
- Container(
- padding: const EdgeInsets.only(left: 15),
- height: 45,
- width: prov.board.lists[widget.index].width,
- color: prov
- .board.lists[widget.index].footerBackgroundColor,
- child: GestureDetector(
- onTap: () async {
- listProv.addNewCard(
- position: "LAST", listIndex: widget.index);
- },
- child: Row(
- children: [
- const Icon(
- Icons.add,
- color: Colors.black,
- size: 22,
- ),
- const SizedBox(
- width: 5,
- ),
- Text(
- 'NEW',
- style: prov.board.textStyle ??
- const TextStyle(
- color: Colors.black,
- fontSize: 17,
- fontWeight: FontWeight.w500),
- ),
- ],
- ),
- ),
- )
- ]),
- ));
- }
-}
diff --git a/lib/custom/list_item.dart b/lib/custom/list_item.dart
deleted file mode 100755
index 4ee9686..0000000
--- a/lib/custom/list_item.dart
+++ /dev/null
@@ -1,135 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-import '../Provider/provider_list.dart';
-
-class Item extends ConsumerStatefulWidget {
- const Item({
- super.key,
- required this.itemIndex,
- this.color = Colors.pink,
- required this.listIndex,
- });
- final int itemIndex;
- final int listIndex;
- final Color color;
- @override
- ConsumerState- createState() => _ItemState();
-}
-
-class _ItemState extends ConsumerState
- {
- Offset location = Offset.zero;
- bool newAdded = false;
- var node = FocusNode();
- bool temp = false;
-
- @override
- Widget build(BuildContext context) {
- // log("BUILDED ${widget.itemIndex}");
- var prov = ref.read(ProviderList.boardProvider);
- var cardProv = ref.read(ProviderList.cardProvider);
- var draggableProv = ref.read(ProviderList.draggableNotifier);
-
- WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
- cardProv.calculateCardPositionSize(
- listIndex: widget.listIndex,
- itemIndex: widget.itemIndex,
- context: context,
- setsate: () => {setState(() {})});
- });
- return ValueListenableBuilder(
- valueListenable: prov.valueNotifier,
- builder: (ctx, a, b) {
- if (draggableProv.isCardDragged) {
- // item added by system in empty list, its widget/UI should not be manipulated on movements //
- if (prov.board.lists[widget.listIndex].items.isEmpty) return b!;
-
- // CALCULATE SIZE AND POSITION OF ITEM //
- if (cardProv.calculateSizePosition(
- listIndex: widget.listIndex, itemIndex: widget.itemIndex)) {
- return b!;
- }
-
- // IF ITEM IS LAST ITEM OF LIST, DIFFERENT APPROACH IS USED //
-
- // if (cardProv.isLastItemDragged(
- // listIndex: widget.listIndex, itemIndex: widget.itemIndex)) {
- // // log("LAST ELEMENT DRAGGED");
- // return b!;
- // }
-
- // DO NOT COMPARE ANYTHING WITH DRAGGED ITEM, IT WILL CAUSE ERRORS BECUSE ITS HIDDEN //
- if ((prov.draggedItemState!.itemIndex == widget.itemIndex &&
- prov.draggedItemState!.listIndex == widget.listIndex)) {
- // log("HERE");
- return b!;
- }
-
- // if (widget.itemIndex - 1 >= 0 &&
- // prov.board.lists[widget.listIndex].items[widget.itemIndex - 1]
- // .containsPlaceholder ==
- // true) {
- // // log("FOR ${widget.listIndex} ${widget.itemIndex}");
- // prov.board.lists[widget.listIndex].items[widget.itemIndex].y =
- // prov.board.lists[widget.listIndex].items[widget.itemIndex]
- // .y! -
- // prov.board.lists[widget.listIndex].items[widget.itemIndex-1].actualSize!.height;
- // }
-
- if (cardProv.getYAxisCondition(
- listIndex: widget.listIndex, itemIndex: widget.itemIndex)) {
- // log("Y AXIS CONDITION");
- cardProv.checkForYAxisMovement(
- listIndex: widget.listIndex, itemIndex: widget.itemIndex);
- } else if (cardProv.getXAxisCondition(
- listIndex: widget.listIndex, itemIndex: widget.itemIndex)) {
- cardProv.checkForXAxisMovement(
- listIndex: widget.listIndex, itemIndex: widget.itemIndex);
- }
- }
- return b!;
- },
- child: GestureDetector(
- onLongPress: () {
- cardProv.onLongpressCard(
- listIndex: widget.listIndex,
- itemIndex: widget.itemIndex,
- context: context,
- setsate: () => {setState(() {})});
- },
- child: draggableProv.isCardDragged &&
- prov.board.dragItemIndex == widget.itemIndex &&
- prov.draggedItemState!.itemIndex == widget.itemIndex &&
- prov.draggedItemState!.listIndex == widget.listIndex &&
- prov.board.dragItemOfListIndex! == widget.listIndex
- ? Container(
- margin: const EdgeInsets.only(bottom: 10),
- decoration: BoxDecoration(
- border: Border.all(color: Colors.grey.shade200),
- borderRadius: BorderRadius.circular(4),
- color: prov.board.lists[widget.listIndex]
- .items[widget.itemIndex].backgroundColor ??
- Colors.white,
- ),
- width: prov.draggedItemState!.width,
- height: prov.draggedItemState!.height,
- )
- : cardProv.isCurrentElementDragged(
- listIndex: widget.listIndex,
- itemIndex: widget.itemIndex)
- ? Container()
- : GestureDetector(
- onTap: () {
- setState(() {
- temp = !temp;
- });
- },
- child: Container(
- margin: const EdgeInsets.only(bottom: 5),
- width: prov.board.lists[widget.listIndex]
- .items[widget.itemIndex].width,
- child: prov.board.lists[widget.listIndex]
- .items[widget.itemIndex].child,
- ),
- )));
- }
-}
diff --git a/lib/custom/text_field.dart b/lib/custom/text_field.dart
deleted file mode 100644
index 5354bc6..0000000
--- a/lib/custom/text_field.dart
+++ /dev/null
@@ -1,32 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-
-import '../Provider/provider_list.dart';
-
-class TField extends ConsumerStatefulWidget {
- const TField({super.key});
-
- @override
- ConsumerState createState() => _TFieldState();
-}
-
-class _TFieldState extends ConsumerState {
- var node = FocusNode();
- @override
- Widget build(BuildContext context) {
- var prov = ref.read(ProviderList.boardProvider);
- return TextFormField(
- decoration: const InputDecoration(
- enabledBorder: OutlineInputBorder(borderSide: BorderSide.none),
- focusedBorder: OutlineInputBorder(borderSide: BorderSide.none),
- disabledBorder: OutlineInputBorder(borderSide: BorderSide.none)),
- autofocus: true,
- enabled: true,
- maxLines: null,
- keyboardType: TextInputType.multiline,
- style: prov.board.textStyle,
- controller: prov.newCardState.textController,
- focusNode: node,
- );
- }
-}
diff --git a/lib/draggable/card_draggable.dart b/lib/draggable/card_draggable.dart
deleted file mode 100644
index a93510e..0000000
--- a/lib/draggable/card_draggable.dart
+++ /dev/null
@@ -1,26 +0,0 @@
-import 'package:kanban_board/draggable/draggable_state.dart';
-
-class CardDraggable extends DraggableState {
- late int _index;
- late int _listIndex;
-
- CardDraggable(
- {required int index,
- required listIndex,
- required super.height,
- required super.width,
- required super.context,
- required super.widget,
- required super.currPos}) {
- _index = index;
- _listIndex = listIndex;
- }
-
- int get index => _index;
- int get listIndex => _listIndex;
-
- void setDataPosition({required int index, required int listIndex}) {
- _index = index;
- _listIndex = listIndex;
- }
-}
diff --git a/lib/draggable/draggable_state.dart b/lib/draggable/draggable_state.dart
deleted file mode 100644
index 64a6e0f..0000000
--- a/lib/draggable/draggable_state.dart
+++ /dev/null
@@ -1,21 +0,0 @@
-import 'package:flutter/material.dart';
-
-enum DraggableType { card, list, none }
-
-abstract class DraggableState {
- DraggableState(
- {required this.height,
- required this.width,
- required this.context,
- required this.widget,
- required this.currPos});
- final double height;
- final double width;
- Offset currPos;
- final BuildContext context;
- final Widget widget;
-
- void setViewPosition(Offset currPos) {
- this.currPos = currPos;
- }
-}
diff --git a/lib/draggable/list_draggable.dart b/lib/draggable/list_draggable.dart
deleted file mode 100644
index 0fcdf57..0000000
--- a/lib/draggable/list_draggable.dart
+++ /dev/null
@@ -1,20 +0,0 @@
-import 'package:kanban_board/draggable/draggable_state.dart';
-
-class ListDraggable extends DraggableState {
- ListDraggable(
- {required int index,
- required listIndex,
- required super.height,
- required super.width,
- required super.context,
- required super.widget,
- required super.currPos}) {
- _index = index;
- }
- late int _index;
-
- int get index => _index;
- void setDataPosition(int index) {
- _index = index;
- }
-}
diff --git a/lib/draggable/presentation/dragged_card.dart b/lib/draggable/presentation/dragged_card.dart
deleted file mode 100644
index c6825dd..0000000
--- a/lib/draggable/presentation/dragged_card.dart
+++ /dev/null
@@ -1,39 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:kanban_board/Provider/provider_list.dart';
-import 'package:kanban_board/draggable/draggable_state.dart';
-
-class DraggedCard extends ConsumerStatefulWidget {
- const DraggedCard({super.key});
-
- @override
- ConsumerState createState() => _DraggedCardState();
-}
-
-class _DraggedCardState extends ConsumerState {
- @override
- Widget build(BuildContext context) {
- final boardProv = ref.read(ProviderList.boardProvider);
- final boardListProv = ref.read(ProviderList.boardListProvider);
- final draggableProv = ref.watch(ProviderList.draggableNotifier);
-
- return ValueListenableBuilder(
- valueListenable: boardProv.valueNotifier,
- builder: (ctx, Offset value, child) {
- if (draggableProv.isCardDragged) {
- boardListProv.maybeListScroll();
- }
- return draggableProv.draggableType != DraggableType.none
- ? Positioned(
- left: value.dx,
- top: value.dy,
- child: Opacity(
- opacity: 0.4,
- child: boardProv.draggedItemState!.child,
- ),
- )
- : Container();
- },
- );
- }
-}
diff --git a/lib/kanban_board.dart b/lib/kanban_board.dart
new file mode 100644
index 0000000..766285f
--- /dev/null
+++ b/lib/kanban_board.dart
@@ -0,0 +1,6 @@
+library kanban_board;
+
+export 'src/board.dart';
+export 'src/board_inputs.dart';
+export 'src/widgets/widgets.dart';
+export 'src/controllers/board_controller.dart';
diff --git a/lib/models/board.dart b/lib/models/board.dart
deleted file mode 100644
index bf5b6cf..0000000
--- a/lib/models/board.dart
+++ /dev/null
@@ -1,104 +0,0 @@
-import 'package:flutter/material.dart';
-import 'board_list.dart';
-import 'inputs.dart';
-
-abstract class BaseDecoration {
- Decoration decoration;
- BaseDecoration(this.decoration);
-}
-
-class BoardState {
- List lists = [];
- ScrollController controller;
- VoidCallback? setstate;
- int? dragListIndex = 0;
- int? dragItemIndex = 0;
- int? previousDragListIndex = 0;
- int? dragItemOfListIndex = 0;
- int? prevItemOfListIndex = 0;
- double? displacementX;
- double? displacementY;
- Function(int? itemIndex, int? listIndex)? onItemTap;
- Function(int? itemIndex, int? listIndex)? onItemLongPress;
- Function(int? listIndex)? onListTap;
- Function(int? listIndex)? onListLongPress;
- final void Function(int? oldCardIndex, int? newCardIndex, int? oldListIndex,
- int? newListIndex)? onItemReorder;
- final void Function(int? oldListIndex, int? newListIndex)? onListReorder;
- final void Function(String? oldName, String? newName)? onListRename;
- final void Function(String? cardIndex, String? listIndex, String? text)?
- onNewCardInsert;
- Color? backgroundColor;
- Color? cardPlaceholderColor;
- Color? listPlaceholderColor;
- final ScrollConfig? boardScrollConfig;
- final ScrollConfig? listScrollConfig;
- TextStyle? textStyle;
- Decoration? listDecoration;
- Decoration? boardDecoration;
- TransitionHandler transitionHandler;
-
- BoardState({
- required this.lists,
- required this.controller,
- this.dragListIndex,
- this.onItemTap,
- this.onItemLongPress,
- this.boardScrollConfig,
- this.listScrollConfig,
- this.setstate,
- this.onListTap,
- this.onItemReorder,
- this.onListReorder,
- this.onListRename,
- this.onNewCardInsert,
- this.displacementX,
- this.displacementY,
- this.onListLongPress,
- this.dragItemIndex,
- this.textStyle,
- this.backgroundColor = Colors.white,
- this.cardPlaceholderColor,
- this.listPlaceholderColor,
- this.listDecoration,
- this.boardDecoration,
- this.dragItemOfListIndex,
- required this.transitionHandler,
- }) {
- textStyle = textStyle ??
- TextStyle(
- color: Colors.grey.shade800,
- fontSize: 19,
- fontWeight: FontWeight.w400);
- }
-}
-
-class TransitionHandler {
- final Widget Function(Widget child, Animation animation)
- cardTransitionBuilder;
- final Widget Function(Widget child, Animation animation)
- listTransitionBuilder;
- final Duration cardTransitionDuration;
- final Duration listTransitionDuration;
-
- TransitionHandler(
- {required this.cardTransitionBuilder,
- required this.listTransitionBuilder,
- required this.cardTransitionDuration,
- required this.listTransitionDuration});
-}
-
-class NewCardState {
- bool? isFocused;
- int? listIndex;
- int? cardIndex;
- late TextEditingController textController;
-
- NewCardState({
- this.isFocused,
- this.listIndex,
- this.cardIndex,
- }) {
- textController = TextEditingController();
- }
-}
diff --git a/lib/models/board_list.dart b/lib/models/board_list.dart
deleted file mode 100644
index 8663964..0000000
--- a/lib/models/board_list.dart
+++ /dev/null
@@ -1,56 +0,0 @@
-import 'package:flutter/material.dart';
-import 'item_state.dart';
-
-class BoardList {
- BuildContext? context;
- double? x;
- double? y;
- double? height;
- double? width;
- Widget? child;
- Widget? header;
- Widget? footer;
- Color? headerBackgroundColor;
- Color? footerBackgroundColor;
- Color? backgroundColor;
- VoidCallback? setState;
- List items = [];
- TextEditingController nameController = TextEditingController();
- ScrollController scrollController;
-
- String title;
- BoardList(
- {required this.items,
- this.context,
- this.height,
- this.width,
- this.header,
- this.footer,
- this.setState,
- this.headerBackgroundColor = const Color.fromARGB(
- 255,
- 247,
- 248,
- 252,
- ),
- this.footerBackgroundColor = const Color.fromARGB(
- 255,
- 247,
- 248,
- 252,
- ),
- this.x,
- this.child,
- this.backgroundColor = const Color.fromARGB(
- 255,
- 247,
- 248,
- 252,
- ),
- this.y,
- required this.scrollController,
- required this.title}) {
- headerBackgroundColor = headerBackgroundColor ?? Colors.grey.shade300;
- footerBackgroundColor = footerBackgroundColor ?? Colors.grey.shade300;
- }
-}
diff --git a/lib/models/inputs.dart b/lib/models/inputs.dart
deleted file mode 100644
index 031b415..0000000
--- a/lib/models/inputs.dart
+++ /dev/null
@@ -1,90 +0,0 @@
-import 'package:flutter/material.dart';
-
-class BoardListsData {
- final String? title;
- Widget? header;
- Color? headerBackgroundColor;
- Color? footerBackgroundColor;
- Widget? footer;
- final List items;
- Color? backgroundColor;
- double width;
- BoardListsData({
- this.title,
- this.header,
- this.footer,
- required this.items,
- this.footerBackgroundColor = const Color.fromRGBO(247, 248, 252, 1),
- this.headerBackgroundColor = const Color.fromARGB(255, 247, 248, 252),
- this.backgroundColor = const Color.fromARGB(
- 255,
- 247,
- 248,
- 252,
- ),
- this.width = 300,
- }) {
- footer = footer ??
- Container(
- padding: const EdgeInsets.only(left: 15),
- height: 45,
- width: 300,
- color: footerBackgroundColor,
- child: const Row(
- children: [
- Icon(
- Icons.add,
- color: Colors.black,
- size: 22,
- ),
- SizedBox(
- width: 10,
- ),
- Text(
- 'NEW',
- style: TextStyle(
- color: Colors.black,
- fontSize: 19,
- fontWeight: FontWeight.bold),
- ),
- ],
- ),
- );
- header = header ??
- Container(
- width: 250,
- color: headerBackgroundColor,
- padding: const EdgeInsets.only(left: 0, bottom: 12, top: 12),
- alignment: Alignment.centerLeft,
- child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Text(
- title ?? '',
- style: const TextStyle(
- fontSize: 20,
- color: Colors.black,
- fontWeight: FontWeight.bold),
- ),
- GestureDetector(
- child: const SizedBox(
- // padding: const EdgeInsets.all(5),
- child: Icon(Icons.more_vert)),
- ),
- ],
- ),
- );
- }
-}
-
-class ScrollConfig {
- double offset;
- Duration duration;
- Curve curve;
-
- ScrollConfig({
- required this.offset,
- required this.duration,
- required this.curve,
- });
-}
diff --git a/lib/models/item_state.dart b/lib/models/item_state.dart
deleted file mode 100755
index 52ca83e..0000000
--- a/lib/models/item_state.dart
+++ /dev/null
@@ -1,61 +0,0 @@
-import 'package:flutter/material.dart';
-
-enum PlaceHolderAt { top, bottom, none }
-
-class ListItem {
- BuildContext? context;
- double? height;
- double? width;
- int listIndex;
- VoidCallback? setState;
- bool? isNew;
- Color? backgroundColor = Colors.white;
- int index;
- double? x;
- double? y;
- PlaceHolderAt placeHolderAt;
- Widget child;
- bool? addedBySystem = false;
- Size? actualSize;
- Widget prevChild;
- ListItem({
- this.context,
- required this.child,
- required this.prevChild,
- required this.listIndex,
- required this.index,
- this.actualSize,
- this.height,
- this.setState,
- this.addedBySystem,
- this.placeHolderAt = PlaceHolderAt.none,
- this.backgroundColor,
- this.width,
- this.x,
- this.y,
- this.isNew = false,
- });
-}
-
-class DraggedItemState {
- BuildContext? context;
- double height;
- double width;
- int? listIndex;
- int? itemIndex;
- double x;
- VoidCallback? setState;
- double y;
- Widget child;
- DraggedItemState({
- this.context,
- required this.child,
- this.setState,
- required this.listIndex,
- required this.itemIndex,
- required this.height,
- required this.width,
- required this.x,
- required this.y,
- });
-}
diff --git a/lib/src/board.dart b/lib/src/board.dart
new file mode 100644
index 0000000..376d162
--- /dev/null
+++ b/lib/src/board.dart
@@ -0,0 +1,359 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:kanban_board/src/controllers/states/scroll_state.dart';
+import 'package:kanban_board/src/widgets/board-group/board_groups_root.dart';
+import 'package:kanban_board/src/widgets/draggable/draggable_overlay.dart';
+import 'package:kanban_board/src/widgets/kanban_gesture_listener.dart';
+import 'board_inputs.dart';
+import 'constants/constants.dart';
+import 'controllers/controllers.dart';
+import 'helpers/board_state_controller_storage.dart';
+
+typedef CardTransitionBuilder = Widget Function(
+ Widget child, Animation animation);
+
+typedef ListTransitionBuilder = Widget Function(
+ Widget child, Animation animation);
+
+typedef OnGroupItemMove = void Function(
+ int? oldCardIndex, int? newCardIndex, int? oldListIndex, int? newListIndex);
+
+typedef OnGroupMove = void Function(int? oldListIndex, int? newListIndex);
+
+typedef GroupItemBuilder = Widget Function(
+ BuildContext context, String groupId, int itemIndex);
+
+typedef GroupHeaderBuilder = Widget Function(
+ BuildContext context, String groupId);
+
+typedef GroupFooterBuilder = Widget Function(
+ BuildContext context, String groupId);
+
+class KanbanBoard extends StatefulWidget {
+ const KanbanBoard({
+ this.boardScrollConfig = const BoardScrollConfig(),
+ this.boardDecoration,
+ required this.controller,
+ this.groups = const [],
+ required this.groupItemBuilder,
+ this.groupDecoration,
+ this.groupHeaderBuilder,
+ this.groupFooterBuilder,
+ this.groupGhost,
+ this.itemGhost,
+ this.onGroupItemMove,
+ this.onGroupMove,
+ this.groupScrollConfig = const GroupScrollConfig(),
+ this.trailing,
+ this.leading,
+ this.groupConstraints = const BoxConstraints(maxWidth: 300),
+ this.newCardWidget,
+ super.key,
+ });
+
+ /// It is the controller for the board.
+ /// It can be used to perform operations like adding a new group, adding a new item to a group, etc.
+ final KanbanBoardController controller;
+
+ /// It is the list of groups for the board.
+ /// Each group contains a list of group-items.
+ /// Each group-item is a card in the board.
+ final List groups;
+
+ /// This is called when a group-item is moved from one place to another.
+ /// It takes [oldCardIndex], [newCardIndex], [oldListIndex], [newListIndex] as input.
+ final OnGroupItemMove? onGroupItemMove;
+
+ /// This is called when a group is moved from one place to another.
+ /// It takes [oldListIndex], [newListIndex] as input.
+ final OnGroupMove? onGroupMove;
+
+ /// This is the configuration for the board scroll.
+ /// It is used to customize the scroll [speed], [curve], and [duration].
+ /// Takes [Offset], [Duration], [Curve] as input.
+ final ScrollConfig boardScrollConfig;
+
+ /// This is the configuration for the list scroll.
+ /// It is used to customize the scroll [speed], [curve], and [duration].
+ /// Takes [Offset], [Duration], [Curve] as input.
+ final ScrollConfig groupScrollConfig;
+
+ /// This is the decoration for the board.
+ final Decoration? boardDecoration;
+
+ /// This is the decoration for the group.
+ final Decoration? groupDecoration;
+
+ /// This is the builder for the group-header.
+ /// pass the [context], [groupId] to the builder.
+ /// it is called for each group.
+ final GroupHeaderBuilder? groupHeaderBuilder;
+
+ /// This is the builder for the group-footer.
+ /// pass the [context], [groupId] to the builder.
+ /// it is called for each group.
+ final GroupFooterBuilder? groupFooterBuilder;
+
+ /// This is the widget which is shown after the last group.
+ final Widget? trailing;
+
+ /// This is the widget which is shown before the first group.
+ final Widget? leading;
+
+ /// This is the builder for the group-item.
+ /// pass the [context], [groupId], and [itemIndex] to the builder.
+ final GroupItemBuilder groupItemBuilder;
+
+ /// This is the constraints for the group.
+ final BoxConstraints groupConstraints;
+
+ // It is the ghost widget for the group-item.
+ final Widget? itemGhost;
+
+ // It is the ghost widget for the group.
+ final Widget? groupGhost;
+
+ /// It's a builder for the new item added in
+ final Widget Function(BuildContext, String, String)? newCardWidget;
+
+ @override
+ State createState() => _KanbanBoardState();
+}
+
+class _KanbanBoardState extends State {
+ @override
+ Widget build(BuildContext context) {
+ return ProviderScope(
+ child: MaterialApp(
+ debugShowCheckedModeBanner: false,
+ home: Board(
+ groups: widget.groups,
+ groupItemBuilder: widget.groupItemBuilder,
+ controller: widget.controller,
+ onGroupItemMove: widget.onGroupItemMove,
+ onGroupMove: widget.onGroupMove,
+ boardScrollConfig: widget.boardScrollConfig,
+ groupScrollConfig: widget.groupScrollConfig,
+ boardDecoration: widget.boardDecoration,
+ trailing: widget.trailing,
+ leading: widget.leading,
+ groupConstraints: widget.groupConstraints,
+ groupDecoration: widget.groupDecoration,
+ groupHeaderBuilder: widget.groupHeaderBuilder,
+ groupFooterBuilder: widget.groupFooterBuilder,
+ groupGhost: widget.groupGhost,
+ itemGhost: widget.itemGhost,
+ ),
+ ));
+ }
+}
+
+class Board extends ConsumerStatefulWidget {
+ const Board({
+ this.groups = const [],
+ required this.controller,
+ required this.groupItemBuilder,
+ this.onGroupItemMove,
+ this.onGroupMove,
+ this.boardScrollConfig = const BoardScrollConfig(),
+ this.groupScrollConfig = const GroupScrollConfig(),
+ this.boardDecoration,
+ this.groupDecoration,
+ this.trailing,
+ this.leading,
+ this.groupConstraints = const BoxConstraints(maxWidth: 300),
+ this.groupHeaderBuilder,
+ this.groupFooterBuilder,
+ this.groupGhost,
+ this.itemGhost,
+ this.newCardWidget,
+ super.key,
+ });
+ final List groups;
+ final KanbanBoardController controller;
+ final GroupItemBuilder groupItemBuilder;
+ final OnGroupItemMove? onGroupItemMove;
+ final OnGroupMove? onGroupMove;
+ final ScrollConfig boardScrollConfig;
+ final ScrollConfig groupScrollConfig;
+ final Decoration? boardDecoration;
+ final Decoration? groupDecoration;
+ final Widget? trailing;
+ final Widget? leading;
+ final BoxConstraints groupConstraints;
+ final GroupHeaderBuilder? groupHeaderBuilder;
+ final GroupFooterBuilder? groupFooterBuilder;
+ final Widget? groupGhost;
+ final Widget? itemGhost;
+ final Widget? newCardWidget;
+
+ @override
+ ConsumerState createState() => _BoardState();
+}
+
+class _BoardState extends ConsumerState {
+ /// [_boardScrollController] is the controller for the board scroll.
+ final ScrollController _boardScrollController = ScrollController();
+
+ /// [_boardStateController] is the controller to manage the state of the board.
+ late ChangeNotifierProvider _boardStateController;
+ late ChangeNotifierProvider
+ _groupItemStateController;
+ late ChangeNotifierProvider _groupStateController;
+
+ /// [_getBoardOffset] is used to compute the offset of the board.
+ void _getBoardOffset() {
+ final double statusBarHeight = MediaQuery.of(context).padding.top;
+ var box = context.findRenderObject() as RenderBox;
+ ref.read(_boardStateController).boardOffset = Offset(
+ box.localToGlobal(Offset.zero).dx + BOARD_PADDING,
+ box.localToGlobal(Offset.zero).dy - statusBarHeight + BOARD_PADDING);
+ }
+
+ /// [_activateBoardScrollListeners] activates the board scroll listeners.
+ /// only when drgaggable offset is updated, it notifies the newly group-items, groups came into view.
+ void _activateBoardScrollListeners() {
+ final boardState = ref.read(_boardStateController);
+ // Group Scroll Listener
+ _boardScrollController.addListener(
+ () {
+ if (boardState.isScrolling) {
+ /// This is to notify newly group-items came into view.
+ /// about the dragging widget position & calculate their position & size to show placeholder.
+ boardState.draggingState.feedbackOffset.value = Offset(
+ boardState.draggingState.feedbackOffset.value.dx + 0.00001,
+ boardState.draggingState.feedbackOffset.value.dy);
+ }
+ },
+ );
+ }
+
+ /// [_initializeBoardGroups] is used to initialize the board groups.
+ List _initializeBoardGroups() {
+ List groups = [];
+ for (int index = 0; index < widget.groups.length; index++) {
+ final group = widget.groups[index];
+ List items = [];
+ for (int itemIndex = 0; itemIndex < group.items.length; itemIndex++) {
+ items.add(
+ IKanbanBoardGroupItem(
+ groupIndex: index,
+ id: group.items[itemIndex].id,
+ key: GlobalKey(),
+ itemWidget: widget.groupItemBuilder(context, group.id, itemIndex),
+ ghost: widget.groupItemBuilder(context, group.id, itemIndex),
+ index: itemIndex,
+ setState: () => {},
+ ),
+ );
+ }
+ groups.add(
+ IKanbanBoardGroup(
+ scrollController: ScrollController(),
+ id: group.id,
+ name: group.name,
+ items: items,
+ customData: group.customData,
+ index: index,
+ setState: () => {},
+ key: GlobalKey(),
+ ),
+ );
+ }
+ return groups;
+ }
+
+ @override
+ void initState() {
+ ///Initializing the [BoardStateController] provider.
+ _boardStateController = ChangeNotifierProvider(
+ (ref) => BoardStateController(
+ groups: _initializeBoardGroups(),
+ controller: widget.controller,
+ ),
+ );
+
+ //saving [_boardStateController] to the controller storage
+ BoardStateControllerStorage.I.addStateController(
+ widget.controller.boardId,
+ ref.read(_boardStateController),
+ );
+
+ ///Setting the ghost widgets for the group-item and item.
+ ref.read(_boardStateController)
+ ..itemGhost = widget.itemGhost
+ ..groupGhost = widget.groupGhost;
+
+ ///Initializing the [ListItemProvider] provider.
+ ///It is used to manage the state of the group-item.
+ _groupItemStateController =
+ ChangeNotifierProvider(
+ (ref) => GroupItemStateController(ref.read(_boardStateController)));
+
+ ///Initializing the [BoardListProvider] provider.
+ ///It is used to manage the state of the group.
+ _groupStateController = ChangeNotifierProvider(
+ (ref) => GroupStateController(ref.read(_boardStateController)));
+
+ _activateBoardScrollListeners();
+ super.initState();
+ }
+
+ @override
+ void didUpdateWidget(covariant Board oldWidget) {
+ if (oldWidget.groups != widget.groups) {
+ //TODO: Fix needed scrollcontroller breaking
+ // ref.read(_boardStateController).groups = _initializeBoardGroups();
+ }
+ super.didUpdateWidget(oldWidget);
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ WidgetsBinding.instance.addPostFrameCallback((_) => _getBoardOffset());
+ // TODO: check i've commented ignore: unused_local_variable
+ // final boardState = ref.watch(_boardStateController);
+ return Scaffold(
+ backgroundColor: Colors.white,
+ body: KanbanGestureListener(
+ boardgroupController: _groupStateController,
+ boardStateController: _boardStateController,
+ groupItemController: _groupItemStateController,
+ groupScrollConfig: widget.groupScrollConfig,
+ boardScrollConfig: widget.boardScrollConfig,
+ boardScrollController: _boardScrollController,
+ child: Container(
+ padding:
+ const EdgeInsets.only(top: BOARD_PADDING, left: BOARD_PADDING),
+ decoration: widget.boardDecoration,
+ child: Stack(
+ fit: StackFit.passthrough,
+ clipBehavior: Clip.none,
+ children: [
+ BoardGroupsRoot(
+ groupItemBuilder: widget.groupItemBuilder,
+ leading: widget.leading,
+ trailing: widget.trailing,
+ header: widget.groupHeaderBuilder,
+ footer: widget.groupFooterBuilder,
+ groupDecoration: widget.groupDecoration,
+ groupConstraints: widget.groupConstraints,
+ boardStateController: _boardStateController,
+ groupItemStateController: _groupItemStateController,
+ groupStateController: _groupStateController,
+ boardScrollController: _boardScrollController,
+ ),
+ DraggableOverlay(
+ boardState: _boardStateController,
+ groupState: _groupStateController,
+ boardScrollController: _boardScrollController,
+ groupScrollConfig: widget.groupScrollConfig,
+ boardScrollConfig: widget.boardScrollConfig,
+ )
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/src/board_inputs.dart b/lib/src/board_inputs.dart
new file mode 100644
index 0000000..407ddfa
--- /dev/null
+++ b/lib/src/board_inputs.dart
@@ -0,0 +1,31 @@
+/// [KanbanBoardGroup] is used for Board's group input.
+class KanbanBoardGroup {
+ /// It is the [id] of the group.
+ /// Every group should have a unique id.
+ String id;
+
+ /// It is the [name] of the group.
+ /// If headerWidget is not provided, this will be used to display the name of the group.
+ String name;
+
+ /// This is a [customData] that can be used to store any data.
+ T? customData;
+
+ /// This contains the list of [items] in the group.
+ List items = const [];
+
+ KanbanBoardGroup({
+ this.customData,
+ required this.id,
+ required this.name,
+ this.items = const [],
+ });
+}
+
+/// [KanbanBoardGroupItem] is used for Board's group item input.
+abstract class KanbanBoardGroupItem {
+ String get id;
+
+ @override
+ String toString() => 'KanbanBoardListItem(id: $id)';
+}
diff --git a/lib/src/constants/constants.dart b/lib/src/constants/constants.dart
new file mode 100644
index 0000000..9be37aa
--- /dev/null
+++ b/lib/src/constants/constants.dart
@@ -0,0 +1,30 @@
+// ignore_for_file: constant_identifier_names, non_constant_identifier_names
+
+const double BOARD_PADDING = 20;
+const double LIST_GAP = 10;
+const double CARD_GAP = 7;
+const double DEFAULT_GROUP_WIDTH = 350;
+// Group Scroll Boundary Threshold
+const double GROUP_NEAR_SCROLL_BOUNDARY = 0;
+const double GROUP_MID_SCROLL_BOUNDARY = 30;
+const double GROUP_FAR_SCROLL_BOUNDARY = 70;
+// Group Scroll speeds
+const double GROUP_NEAR_SCROLL_MOVE = 300;
+const double GROUP_MID_SCROLL_MOVE = 100;
+const double GROUP_FAR_SCROLL_MOVE = 50;
+// Group Scroll durations
+const Duration GROUP_NEAR_SCROLL_DURATION = Duration(milliseconds: 100);
+const Duration GROUP_MID_SCROLL_DURATION = Duration(milliseconds: 150);
+const Duration GROUP_FAR_SCROLL_DURATION = Duration(milliseconds: 250);
+// Board Scroll Boundary Threshold
+const double BOARD_NEAR_SCROLL_BOUNDARY = 0;
+const double BOARD_MID_SCROLL_BOUNDARY = 30;
+const double BOARD_FAR_SCROLL_BOUNDARY = 70;
+// Board Scroll speeds
+const double BOARD_NEAR_SCROLL_MOVE = 120;
+const double BOARD_MID_SCROLL_MOVE = 80;
+const double BOARD_FAR_SCROLL_MOVE = 50;
+// Board Scroll durations
+const Duration BOARD_NEAR_SCROLL_DURATION = Duration(milliseconds: 100);
+const Duration BOARD_MID_SCROLL_DURATION = Duration(milliseconds: 200);
+const Duration BOARD_FAR_SCROLL_DURATION = Duration(milliseconds: 250);
diff --git a/lib/src/constants/widget_styles.dart b/lib/src/constants/widget_styles.dart
new file mode 100644
index 0000000..42c58ee
--- /dev/null
+++ b/lib/src/constants/widget_styles.dart
@@ -0,0 +1,125 @@
+import 'package:flutter/material.dart';
+import 'package:kanban_board/src/constants/constants.dart';
+
+import '../controllers/controllers.dart';
+
+class DefaultStyles {
+ static TextStyle textStyle({
+ Color? color,
+ double? fontSize,
+ FontWeight? fontWeight,
+ }) {
+ return TextStyle(
+ fontFamily: "Raleway",
+ fontSize: fontSize ?? 16,
+ color: color ?? Colors.black,
+ fontWeight: fontWeight ?? FontWeight.normal);
+ }
+
+ static BoxDecoration groupDecoration() {
+ return const BoxDecoration(
+ color: Color.fromRGBO(247, 248, 249, 1),
+ borderRadius: BorderRadius.all(Radius.circular(5)),
+ );
+ }
+
+ static Widget groupHeader({
+ required IKanbanBoardGroup group,
+ required Function(GroupOperationType type) onOperationSelect,
+ }) {
+ return Container(
+ width: DEFAULT_GROUP_WIDTH,
+ decoration: groupDecoration(),
+ padding: const EdgeInsets.only(left: 15, bottom: 10, top: 10, right: 0),
+ alignment: Alignment.centerLeft,
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(
+ group.name,
+ style: textStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ SizedBox(
+ // padding: const EdgeInsets.all(5),
+ child: PopupMenuButton(
+ color: Colors.white,
+ icon: const Icon(
+ Icons.more_horiz,
+ color: Colors.black,
+ size: 18,
+ ),
+ itemBuilder: (ctx) {
+ return [
+ PopupMenuItem(
+ value: GroupOperationType.addItem,
+ child: Text(
+ "Add item",
+ style: textStyle(),
+ ),
+ ),
+ PopupMenuItem(
+ value: GroupOperationType.delete,
+ child: Text(
+ "Delete Group",
+ style: textStyle(),
+ ),
+ ),
+ ];
+ },
+ onSelected: (value) => onOperationSelect(value))),
+ ],
+ ),
+ );
+ }
+
+ static Widget groupFooter({
+ required Function() onAddNewGroup,
+ }) {
+ return Container(
+ width: DEFAULT_GROUP_WIDTH,
+ decoration: groupDecoration(),
+ padding: const EdgeInsets.only(
+ bottom: 10,
+ ),
+ alignment: Alignment.centerLeft,
+ child: Row(
+ children: [
+ IconButton(
+ onPressed: onAddNewGroup,
+ icon: const Icon(
+ Icons.add,
+ color: Colors.black,
+ ),
+ ),
+ Text(
+ "New",
+ style: textStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+
+ static groupItemGhost() {
+ return Container(
+ decoration: BoxDecoration(
+ color: Colors.white,
+ borderRadius: BorderRadius.circular(5),
+ boxShadow: [
+ BoxShadow(
+ color: Colors.black.withOpacity(0.2),
+ spreadRadius: 1,
+ blurRadius: 2,
+ offset: const Offset(0, 2), // changes position of shadow
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/src/controllers/board_controller.dart b/lib/src/controllers/board_controller.dart
new file mode 100644
index 0000000..18c8d8c
--- /dev/null
+++ b/lib/src/controllers/board_controller.dart
@@ -0,0 +1,119 @@
+import 'package:flutter/material.dart';
+import 'package:kanban_board/src/board_inputs.dart';
+import 'package:kanban_board/src/helpers/board_state_controller_storage.dart';
+import 'package:kanban_board/src/widgets/textfield.dart';
+import 'package:uuid/uuid.dart';
+
+import 'controllers.dart';
+
+class KanbanBoardController {
+ late final String boardId;
+
+ /// [KanbanBoardController] controls the behavior of some operations in [KanbanBoard].
+ /// operations include adding a [group], adding a [group-item], etc.
+ KanbanBoardController() : boardId = const Uuid().v4();
+
+ /// [addGroup] adds a group to the board.
+ void addGroup(String id, dynamic groupData) {}
+
+ /// [addGroupItem] adds a group-item to the board at a specific index. If the index is not provided, it will be added at the end.
+ void addGroupItem({
+ required String groupId,
+ required KanbanBoardGroupItem groupItem,
+ int? index,
+ }) {
+ final boardController =
+ BoardStateControllerStorage.I.getStateController(boardId);
+ if (boardController == null) {
+ throw Exception('Board controller not found');
+ }
+ final group = boardController.groups.firstWhere(
+ (element) => element.id == groupId,
+ orElse: (() => throw Exception('Group not found')));
+
+ group.items.insert(
+ index ?? group.items.length,
+ IKanbanBoardGroupItem(
+ groupIndex: group.index,
+ id: const Uuid().v4(),
+ index: index ?? 0,
+ key: GlobalKey(),
+ addedBySystem: true,
+ setState: () {},
+ ),
+ );
+
+ group.setState();
+ }
+
+ /// [removeGroup] removes a group from the board.
+ void removeGroup(String groupId) {
+ final boardController =
+ BoardStateControllerStorage.I.getStateController(boardId);
+ if (boardController == null) {
+ throw Exception('Board controller not found');
+ }
+ boardController.groups.removeWhere((element) => element.id == groupId);
+ boardController.notify();
+ }
+
+ /// Removes a item from the group in the board.
+ void removeGroupItem(String groupId, String itemId) {
+ final boardController =
+ BoardStateControllerStorage.I.getStateController(boardId);
+ if (boardController == null) {
+ throw Exception('Board controller not found');
+ }
+ final group = boardController.groups.firstWhere(
+ (element) => element.id == groupId,
+ orElse: (() => throw Exception('Group not found with id: $groupId')));
+
+ group.items.removeWhere((element) => element.id == itemId);
+ group.setState();
+ }
+
+ /// [showNewCard] shows a new card in the group.
+ void showNewCard({
+ required final String groupId,
+ final int? position,
+ final void Function(String)? onCardAdded,
+ }) {
+ final boardController =
+ BoardStateControllerStorage.I.getStateController(boardId);
+ if (boardController == null) {
+ throw Exception('Board controller not found');
+ }
+ final group = boardController.groups.firstWhere(
+ (element) => element.id == groupId,
+ orElse: (() => throw Exception('Group not found')));
+
+ group.items.insert(
+ position ?? group.items.length,
+ IKanbanBoardGroupItem(
+ groupIndex: group.index,
+ id: const Uuid().v4(),
+ index: position ?? 0,
+ key: GlobalKey(),
+ addedBySystem: true,
+ setState: () {},
+ //TODO: Why ghost is used?
+ ghost:
+ Container(
+ decoration: BoxDecoration(
+ color: Colors.white,
+ borderRadius: BorderRadius.circular(5),
+ ),
+ height: 120,
+ child: CardWithTextField(
+ onCompleteEditing: (value){
+ group.items.removeAt(position ?? group.items.length);
+ onCardAdded?.call(value);
+ }
+ ),
+ ),
+ ),
+ );
+
+ group.setState();
+ }
+}
diff --git a/lib/src/controllers/board_state_controller.dart b/lib/src/controllers/board_state_controller.dart
new file mode 100644
index 0000000..b7df21e
--- /dev/null
+++ b/lib/src/controllers/board_state_controller.dart
@@ -0,0 +1,35 @@
+import 'package:flutter/material.dart';
+import 'controllers.dart';
+
+class BoardStateController extends ChangeNotifier {
+ BoardStateController({
+ required this.controller,
+ this.groups = const [],
+ });
+
+ /// These are the list of internal group-state of the board.
+ /// Each group-state holds the state of group and its items.
+ List groups = [];
+
+ /// The controller for the board.
+ KanbanBoardController controller;
+
+ /// It holds whether the board is currently scrolling or not.
+ bool isScrolling = false;
+
+ /// It holds the state of widget currently being dragged.
+ DraggableState draggingState = DraggableState.initial();
+
+ /// It holds the board position
+ Offset boardOffset = Offset.zero;
+
+ // It holds the user's input ghost widget for the item.
+ Widget? itemGhost;
+
+ // It holds the user's input ghost widget for the group.
+ Widget? groupGhost;
+
+ void notify() {
+ notifyListeners();
+ }
+}
diff --git a/lib/src/controllers/controllers.dart b/lib/src/controllers/controllers.dart
new file mode 100644
index 0000000..d4e83d2
--- /dev/null
+++ b/lib/src/controllers/controllers.dart
@@ -0,0 +1,11 @@
+library controllers;
+
+/// All the controllers are exported here
+export './board_state_controller.dart';
+export './group_state_controller.dart';
+export './group_item_state_controller.dart';
+export './board_controller.dart';
+
+/// All the states are exported here
+export './states/board_internal_states.dart';
+export './states/draggable_state.dart';
diff --git a/lib/src/controllers/group_item_state_controller.dart b/lib/src/controllers/group_item_state_controller.dart
new file mode 100644
index 0000000..e568335
--- /dev/null
+++ b/lib/src/controllers/group_item_state_controller.dart
@@ -0,0 +1,576 @@
+import 'package:flutter/material.dart';
+import 'package:kanban_board/src/constants/constants.dart';
+import 'controllers.dart';
+
+class GroupItemStateController extends ChangeNotifier {
+ GroupItemStateController(this.boardState);
+ final BoardStateController boardState;
+ TextEditingController newCardTextController = TextEditingController();
+
+ /// [computeItemPositionSize] is used to compute the position and size of the groupItem.
+ /// On each build of the groupItem, the position and size of the groupItem is computed.
+ void computeItemPositionSize(
+ {required int groupIndex,
+ required int itemIndex,
+ required BuildContext context,
+ required VoidCallback setstate}) {
+ final groupItem = boardState.groups[groupIndex].items[itemIndex];
+ if (!context.mounted) return;
+ final itemRenderBox = context.findRenderObject() as RenderBox;
+ final location = itemRenderBox.localToGlobal(Offset.zero);
+ groupItem.updateWith(
+ setState: setstate,
+ position: Offset(location.dx - boardState.boardOffset.dx,
+ location.dy - boardState.boardOffset.dy),
+ size: groupItem.size == Size.zero
+ ? Size(itemRenderBox.size.width, itemRenderBox.size.height - CARD_GAP)
+ : null,
+ // actual size should not be updated, as groupItem might contain placeholder widget.
+ );
+
+ /// usecase: when item is not in view, and on scrolling it comes in view, then rebuild the widget to update the position and size.
+ /// to ensure key.currentContext is not null, for any widget that is in view.
+ if (groupItem.key.currentContext == null ||
+ !groupItem.key.currentContext!.mounted) {
+ setstate();
+ }
+ }
+
+ /// This method is called on the [LongPress] event of the group-groupItem widget.
+ /// It is responsible for setting the state of the dragged groupItem with updated position and size.
+ void onLongPressItem({
+ required int groupIndex,
+ required int itemIndex,
+ required BuildContext context,
+ required VoidCallback setState,
+ }) {
+ final groups = boardState.groups;
+ final groupItem = groups[groupIndex].items[itemIndex];
+ final draggingState = boardState.draggingState;
+ final box = context.findRenderObject() as RenderBox;
+ final location = box.localToGlobal(Offset.zero);
+ groupItem.updateWith(
+ position: Offset(location.dx - boardState.boardOffset.dx,
+ location.dy - boardState.boardOffset.dy),
+ size: Size(box.size.width, box.size.height - CARD_GAP),
+ actualSize: Size(box.size.width, box.size.height - CARD_GAP),
+ setState: setState,
+ );
+
+ //If the groupItem is added by the system, then restrict the drag operation.
+ if (groupItem.addedBySystem) return;
+
+ draggingState.feedbackOffset.value = groupItem.position!;
+ draggingState.updateWith(
+ feedbackSize: groupItem.size,
+ draggableType: DraggableType.item,
+ dragStartIndex: itemIndex,
+ dragStartGroupIndex: groupIndex,
+ currentIndex: itemIndex,
+ currentGroupIndex: groupIndex,
+ draggingWidget: Opacity(
+ opacity: 0.5,
+ child: SizedBox(
+ height: groupItem.size.height,
+ width: groupItem.size.width,
+ child: groupItem.itemWidget,
+ ),
+ ),
+ );
+ setState();
+ }
+
+ /// [resetItemWidget] is used to reset the placeholder widget of the groupItem.
+ /// This is called whenever the groupItem is moved to a different position, to remove the placeholder from the last position.
+ void resetItemWidget() {
+ wrapWithPlaceHolder(
+ groupIndex: boardState.draggingState.currentGroupIndex,
+ itemIndex: boardState.draggingState.currentIndex,
+ reset: true,
+ );
+ final groupItem = boardState
+ .groups[boardState.draggingState.currentGroupIndex]
+ .items[boardState.draggingState.currentIndex];
+ groupItem.placeHolderAt = PlaceHolderAt.none;
+ }
+
+ /// [calculateSizePosition] is used to calculate the size and position of the groupItem.
+ /// This is called on every move of dragging widget, to compute the position and size of the groupItem & group.
+ /// This helps in further computation of the placeholder widget.
+ bool calculateSizePosition({
+ required int groupIndex,
+ required int itemIndex,
+ }) {
+ final group = boardState.groups[groupIndex];
+ var groupItem = group.items[itemIndex];
+
+ /// This is used to check if the groupItem is in view or not.
+ if (groupItem.key.currentContext == null ||
+ group.key.currentContext == null ||
+ !groupItem.key.currentContext!.mounted) {
+ return true;
+ }
+ var itemRenderBox = groupItem.key.currentContext!.findRenderObject();
+ var groupRenderBox = group.key.currentContext!.findRenderObject();
+ if (itemRenderBox == null || groupRenderBox == null) return true;
+
+ itemRenderBox = itemRenderBox as RenderBox;
+ groupRenderBox = groupRenderBox as RenderBox;
+ final position = itemRenderBox.localToGlobal(Offset.zero);
+
+ // Update the size and position of the groupItem and group.
+ groupItem.updateWith(
+ size: itemRenderBox.size,
+ actualSize: groupItem.actualSize == Size.zero ? itemRenderBox.size : null,
+ position: Offset(position.dx - boardState.boardOffset.dx,
+ position.dy - boardState.boardOffset.dy),
+ );
+ group.updateWith(
+ position: Offset(
+ groupRenderBox.localToGlobal(Offset.zero).dx -
+ boardState.boardOffset.dx,
+ groupRenderBox.localToGlobal(Offset.zero).dy -
+ boardState.boardOffset.dy),
+ size: Size(
+ groupRenderBox.size.width - LIST_GAP, groupRenderBox.size.height));
+ return false;
+ }
+
+ /// [wrapWithPlaceHolder] add/remove the placeholder widget to the groupItem.
+ /// It wraps placeholder/actual-widget with respective animation.
+ void wrapWithPlaceHolder(
+ {required int groupIndex, required int itemIndex, bool reset = false}) {
+ final groups = boardState.groups;
+ final groupItem = groups[groupIndex].items[itemIndex];
+ final draggingState = boardState.draggingState;
+
+ // update the ghost widget with placeholder wrapped groupItem.
+ groupItem.ghost = Column(
+ children: [
+ groupItem.placeHolderAt == PlaceHolderAt.top && !reset
+ ? TweenAnimationBuilder(
+ duration: const Duration(milliseconds: 2000),
+ curve: Curves.ease,
+ tween: Tween(begin: 0, end: 1),
+ builder: (context, value, child) {
+ return Opacity(
+ opacity: value,
+ child: child,
+ );
+ },
+ child: Container(
+ margin: const EdgeInsets.only(bottom: CARD_GAP),
+ width: draggingState.feedbackSize.width,
+ height: draggingState.feedbackSize.height,
+ child: boardState.itemGhost),
+ )
+ : Container(),
+ SlideTransition(
+ position: Tween(
+ begin: Offset(
+ 0,
+ groupItem.placeHolderAt == PlaceHolderAt.bottom
+ ? 1
+ : groupItem.placeHolderAt == PlaceHolderAt.top
+ ? -1
+ : 0),
+ end: const Offset(0, 0))
+ .animate(
+ CurvedAnimation(
+ parent: groupItem.animationController!,
+ curve: Curves.easeInOut,
+ ),
+ ),
+ child: Container(
+ margin: const EdgeInsets.only(top: 0),
+ width: draggingState.feedbackSize.width,
+ height: draggingState.feedbackSize.height,
+ child: groupItem.itemWidget)),
+ groupItem.placeHolderAt == PlaceHolderAt.bottom && !reset
+ ? TweenAnimationBuilder(
+ duration: const Duration(milliseconds: 2000),
+ curve: Curves.ease,
+ tween: Tween(begin: 0, end: 1),
+ builder: (context, value, child) {
+ return Opacity(
+ opacity: value,
+ child: child,
+ );
+ },
+ child: Container(
+ margin: const EdgeInsets.only(top: CARD_GAP),
+ width: draggingState.feedbackSize.width,
+ height: draggingState.feedbackSize.height,
+ child: boardState.itemGhost),
+ )
+ : Container(),
+ ],
+ );
+ }
+
+ /// [isCurrentSystemItem] check's, if the current placeholder is added by the system.
+ /// Placeholder is added by the system, when dragging widget drag over the empty group.
+ /// It also removes the placeholder from the last position.
+ /// [Returns] true, if the current item is added by the system, else false.
+ bool isCurrentSystemItem({required int groupIndex, required int itemIndex}) {
+ final draggingState = boardState.draggingState;
+ final groups = boardState.groups;
+ var groupItem = groups[groupIndex].items[itemIndex];
+ if (groups[draggingState.currentGroupIndex]
+ .items[draggingState.currentIndex]
+ .addedBySystem) {
+ groups[draggingState.currentGroupIndex].items = [];
+ WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
+ groups[draggingState.currentGroupIndex].setState();
+ draggingState.currentIndex = itemIndex;
+ draggingState.currentGroupIndex = groupIndex;
+ groupItem.setState();
+ });
+
+ return true;
+ }
+ return false;
+ }
+
+ void handleSameGroupMove({required int groupIndex, required int itemIndex}) {
+ final groups = boardState.groups;
+ final groupItem = groups[groupIndex].items[itemIndex];
+ final draggingState = boardState.draggingState;
+ bool willPlaceHolderAtBottom =
+ _bottomPlaceHolderPossibility(groupIndex, itemIndex);
+ if (getYAxisCondition(groupIndex: groupIndex, itemIndex: itemIndex)) {
+ // Reset the placeholder from the last position.
+ resetItemWidget();
+
+ // Set the placeholder at the top/bottom of the groupItem.
+ groupItem.placeHolderAt =
+ willPlaceHolderAtBottom ? PlaceHolderAt.bottom : PlaceHolderAt.top;
+
+ // Wrap the groupItem with placeholder, to show the placeholder at the top/bottom of the groupItem.
+ if ((!groupItem.addedBySystem)) {
+ wrapWithPlaceHolder(
+ groupIndex: groupIndex,
+ itemIndex: itemIndex,
+ );
+ }
+ // Check if the last placeholder is added by the system, so there should not be any manipulation with the same column y-axis widgets.
+ if (isCurrentSystemItem(groupIndex: groupIndex, itemIndex: itemIndex)) {
+ return;
+ }
+
+ WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
+ /// Check if the groupItem is in view or not.
+ if (groups[draggingState.currentGroupIndex]
+ .items[draggingState.currentIndex]
+ .key
+ .currentContext ==
+ null ||
+ !groups[draggingState.currentGroupIndex]
+ .items[draggingState.currentIndex]
+ .key
+ .currentContext!
+ .mounted) return;
+
+ // Reset the placeholder from the last position.
+ if (itemIndex != draggingState.currentIndex &&
+ draggingState.currentGroupIndex != groupIndex) {
+ resetItemWidget();
+ }
+ // If the current item is animating, fasten the animation.
+ if (groups[draggingState.currentGroupIndex]
+ .items[draggingState.currentIndex]
+ .animationController!
+ .isAnimating) {
+ groups[draggingState.currentGroupIndex]
+ .items[draggingState.currentIndex]
+ .animationController!
+ .fling();
+ }
+ // rebuild the last groupItem with removed placeholder.
+ groups[draggingState.currentGroupIndex]
+ .items[draggingState.currentIndex]
+ .setState();
+
+ // Update the current index and current group index.
+ draggingState.currentIndex = itemIndex;
+ draggingState.currentGroupIndex = groupIndex;
+ // rebuild the current groupItem with added placeholder.
+ groupItem.setState();
+
+ // Start the animation of the groupItem.
+ if (groupItem.animationController!.isCompleted) {
+ groupItem.animationController?.forward(from: -1.0);
+ } else {
+ groupItem.animationController?.forward();
+ }
+ });
+ }
+ }
+
+ bool _topPlaceHolderPossibility(int groupIndex, int itemIndex) {
+ final groups = boardState.groups;
+ final groupItem = groups[groupIndex].items[itemIndex];
+ final draggingState = boardState.draggingState;
+
+ var willPlaceHolderAtTop = groupItem.placeHolderAt == PlaceHolderAt.bottom
+ ? (draggingState.feedbackOffset.value.dy <
+ groupItem.position!.dy + (groupItem.actualSize.height * 0.5))
+ : ((draggingState.feedbackOffset.value.dy <=
+ groupItem.position!.dy + (groupItem.actualSize.height * 0.5)) &&
+ (draggingState.feedbackSize.height +
+ draggingState.feedbackOffset.value.dy >
+ groupItem.position!.dy + (groupItem.actualSize.height)));
+
+ return willPlaceHolderAtTop &&
+ draggingState.axisDirection == AxisDirection.up &&
+ groupItem.placeHolderAt != PlaceHolderAt.top &&
+ draggingState.currentGroupIndex == groupIndex &&
+ groupItem.addedBySystem != true;
+ }
+
+ /// This method checks if the current groupItem can be combined with placeholder at bottom of it.
+ bool _bottomPlaceHolderPossibility(int groupIndex, int itemIndex) {
+ final groups = boardState.groups;
+ var groupItem = groups[groupIndex].items[itemIndex];
+ final draggingState = boardState.draggingState;
+
+ /// There are two cases for the bottom placeholder possibility:
+
+ var willPlaceHolderAtBottom =
+
+ /// 1. If there already exists a placeholder at the top of the groupItem:
+ /// In this case, the bottom placeholder will be placed only, if the draggable have crossed 80% height of the current item.
+ groupItem.placeHolderAt == PlaceHolderAt.top
+ ? (draggingState.feedbackSize.height +
+ draggingState.feedbackOffset.value.dy >=
+ groupItem.position!.dy + (groupItem.size.height * 0.75))
+
+ /// 2. If there is no placeholder attached to the groupItem:
+ /// In this case, the bottom placeholder will be placed only, if the draggable have crossed 50% height of the current item.
+ /// and the [draggable-top] is above the [groupItem-top].
+ /// This is to ensure that the placeholder should be attached to current item only, cause all validation is done on the basis of current item [size] [position].
+ : ((draggingState.feedbackSize.height +
+ draggingState.feedbackOffset.value.dy >=
+ groupItem.position!.dy +
+ (groupItem.actualSize.height * 0.5)) &&
+ (draggingState.feedbackOffset.value.dy <
+ groupItem.position!.dy));
+
+ // if (willPlaceHolderAtBottom) {
+ // log("${draggingState.feedbackSize.height} + ${draggingState.feedbackOffset.value.dy} >= ${groupItem.position!.dy} + ${groupItem.actualSize.height * 0.8}");
+ // }
+
+ /// This is the last validation to check if the placeholder should be attached to the current item.
+ return willPlaceHolderAtBottom &&
+ draggingState.axisDirection == AxisDirection.down &&
+
+ /// There should not exist bottom placeholder to the current item, if the current item is added by the system.
+ groupItem.placeHolderAt != PlaceHolderAt.bottom &&
+
+ /// The selected item for placeholder should be from the same group that is being dragged.
+ draggingState.currentGroupIndex == groupIndex &&
+
+ /// The current item should not be added by the system.
+ groupItem.addedBySystem != true;
+ }
+
+ bool getYAxisCondition({required int groupIndex, required int itemIndex}) {
+ final groups = boardState.groups;
+ final groupItem = groups[groupIndex].items[itemIndex];
+ final draggingState = boardState.draggingState;
+
+ var right = (draggingState.feedbackOffset.value.dx <=
+ groups[groupIndex].position!.dx +
+ (groups[groupIndex].size.width * 0.4)) &&
+ ((groups[groupIndex].position!.dx + groups[groupIndex].size.width <=
+ draggingState.feedbackSize.width +
+ draggingState.feedbackOffset.value.dx));
+
+ var left = ((draggingState.feedbackOffset.value.dx <=
+ groups[groupIndex].position!.dx) &&
+ (draggingState.feedbackSize.width +
+ draggingState.feedbackOffset.value.dx >=
+ groups[groupIndex].position!.dx +
+ (groups[groupIndex].size.width * 0.6)));
+
+ bool value = (_topPlaceHolderPossibility(groupIndex, itemIndex) ||
+ _bottomPlaceHolderPossibility(groupIndex, itemIndex)) &&
+ (right || left) &&
+ draggingState.currentGroupIndex == groupIndex;
+ return value && groupItem.addedBySystem != true;
+ }
+
+ bool getXAxisCondition({required int groupIndex, required int itemIndex}) {
+ final groups = boardState.groups;
+ final draggingState = boardState.draggingState;
+ var right = (draggingState.feedbackOffset.value.dx <=
+ groups[groupIndex].position!.dx +
+ (groups[groupIndex].size.width * 0.4)) &&
+ ((groups[groupIndex].position!.dx + groups[groupIndex].size.width <
+ draggingState.feedbackSize.width +
+ draggingState.feedbackOffset.value.dx));
+
+ var left = ((draggingState.feedbackOffset.value.dx <
+ groups[groupIndex].position!.dx) &&
+ (draggingState.feedbackSize.width +
+ draggingState.feedbackOffset.value.dx >=
+ groups[groupIndex].position!.dx +
+ (groups[groupIndex].size.width * 0.6)));
+ return (left || right) && draggingState.currentGroupIndex != groupIndex;
+ }
+
+ void handleDiffGroupMove({required int groupIndex, required int itemIndex}) {
+ final groups = boardState.groups;
+ final groupItem = groups[groupIndex].items[itemIndex];
+ final draggingState = boardState.draggingState;
+
+ var canReplaceCurrent =
+ ((draggingState.feedbackOffset.value.dy >= groupItem.position!.dy) &&
+ (groupItem.size.height + groupItem.position!.dy >
+ draggingState.feedbackOffset.value.dy +
+ (draggingState.feedbackSize.height / 2)));
+ var willPlaceHolderAtBottom =
+ (itemIndex == groups[groupIndex].items.length - 1 &&
+ ((draggingState.feedbackSize.height * 0.6) +
+ draggingState.feedbackOffset.value.dy >
+ groupItem.position!.dy + groupItem.size.height));
+
+ if (canReplaceCurrent || willPlaceHolderAtBottom) {
+ resetItemWidget();
+ groupItem.placeHolderAt =
+ willPlaceHolderAtBottom ? PlaceHolderAt.bottom : PlaceHolderAt.top;
+
+ wrapWithPlaceHolder(groupIndex: groupIndex, itemIndex: itemIndex);
+ if (isCurrentSystemItem(groupIndex: groupIndex, itemIndex: itemIndex)) {
+ return;
+ }
+
+ WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
+ if (itemIndex != draggingState.currentIndex &&
+ draggingState.currentGroupIndex != groupIndex) {
+ resetItemWidget();
+ }
+ final animationController = groups[draggingState.currentGroupIndex]
+ .items[draggingState.currentIndex]
+ .animationController;
+ if (animationController?.isAnimating == true) {
+ animationController!.fling();
+ }
+ groups[draggingState.currentGroupIndex]
+ .items[draggingState.currentIndex]
+ .setState();
+
+ draggingState.currentIndex = itemIndex;
+ draggingState.currentGroupIndex = groupIndex;
+
+ groupItem.setState();
+ if (groupItem.animationController!.isCompleted) {
+ groupItem.animationController?.forward(from: -1.0);
+ } else {
+ groupItem.animationController?.forward();
+ }
+ });
+ }
+ }
+
+ bool isCurrentElementDragged(
+ {required int groupIndex, required int itemIndex}) {
+ final draggingState = boardState.draggingState;
+
+ return draggingState.draggableType == DraggableType.item &&
+ draggingState.dragStartIndex == itemIndex &&
+ draggingState.dragStartGroupIndex == groupIndex;
+ }
+
+ void updateCardPlaceholder({
+ required int groupIndex,
+ required int itemIndex,
+ bool update = false,
+ }) {
+ resetItemWidget();
+ final groupItem = boardState.groups[groupIndex].items[itemIndex];
+ if (update) groupItem.setState();
+ }
+
+ /// This method is called on the onDragEnd event of the draggable widget
+ /// It is responsible for reordering the card in the board
+ void onDragEnd() {
+ final groups = boardState.groups;
+ final draggedState = boardState.draggingState;
+
+ IKanbanBoardGroup group = boardState.groups[draggedState.currentGroupIndex];
+ IKanbanBoardGroupItem groupItem = group.items[draggedState.currentIndex];
+
+ /// If the card is dropped in the same group.
+ if (draggedState.dragStartGroupIndex == draggedState.currentGroupIndex) {
+ /// Remove the groupItem from the index from where it was dragged, and insert it at the current index.
+ updateCardPlaceholder(
+ groupIndex: draggedState.currentGroupIndex,
+ itemIndex: draggedState.currentIndex,
+ update: true,
+ );
+ group.items.insert(
+ draggedState.currentIndex,
+ groups[draggedState.dragStartGroupIndex]
+ .items
+ .removeAt(draggedState.dragStartIndex),
+ );
+ }
+
+ /// If the card is dropped in a different group.
+ else {
+ if (groupItem.addedBySystem) {
+ group.items.removeAt(draggedState.currentIndex);
+ group.items.insert(
+ draggedState.currentIndex,
+ groups[draggedState.dragStartGroupIndex]
+ .items
+ .removeAt(draggedState.dragStartIndex));
+ }
+
+ /// If the placeholder is at the bottom of any groupItem, insert the groupItem at the current index + 1.
+ /// This is because the groupItem at the current index will be shifted to the previous index.
+ else if (groupItem.placeHolderAt == PlaceHolderAt.bottom) {
+ updateCardPlaceholder(
+ groupIndex: draggedState.currentGroupIndex,
+ itemIndex: draggedState.currentIndex,
+ update: true,
+ );
+ group.items.insert(
+ draggedState.currentIndex + 1,
+ groups[draggedState.dragStartGroupIndex]
+ .items
+ .removeAt(draggedState.dragStartIndex));
+ } else {
+ updateCardPlaceholder(
+ groupIndex: draggedState.currentGroupIndex,
+ itemIndex: draggedState.currentIndex,
+ update: true,
+ );
+
+ /// If the placeholder is at the top of any groupItem, insert the groupItem at the current index.
+ /// This is because the groupItem at the current index will be shifted to the next index.
+ group.items.insert(
+ draggedState.currentIndex,
+ groups[draggedState.dragStartGroupIndex]
+ .items
+ .removeAt(draggedState.dragStartIndex));
+ }
+ }
+
+ /// Reset the placeholder of the groupItem.
+ /// This is done to remove the placeholder from the groupItem.
+
+ /// Rebuild the current group to which the groupItem is dropped.
+ groups[draggedState.currentGroupIndex].setState();
+ // Rebuild the group from which the groupItem was dragged.
+ groups[draggedState.dragStartGroupIndex].setState();
+
+ /// Reset the dragging state to its initial state.
+ boardState.draggingState = DraggableState.initial(
+ feedbackOffset: boardState.draggingState.feedbackOffset,
+ );
+
+ boardState.notify();
+ }
+}
diff --git a/lib/src/controllers/group_state_controller.dart b/lib/src/controllers/group_state_controller.dart
new file mode 100644
index 0000000..3b938ad
--- /dev/null
+++ b/lib/src/controllers/group_state_controller.dart
@@ -0,0 +1,299 @@
+import 'dart:math';
+
+import 'package:dotted_border/dotted_border.dart';
+import 'package:flutter/material.dart';
+import 'package:kanban_board/src/board.dart';
+import 'package:kanban_board/src/controllers/board_state_controller.dart';
+import 'package:kanban_board/src/constants/constants.dart';
+import 'package:kanban_board/src/helpers/widget_detail.helper.dart';
+import 'states/board_internal_states.dart';
+import 'states/draggable_state.dart';
+
+class GroupStateController extends ChangeNotifier {
+ GroupStateController(this.boardState);
+ final BoardStateController boardState;
+ bool isScrolling = false;
+
+ void calculateSizePosition(
+ {required BoardStateController boardState,
+ required int groupIndex,
+ required BuildContext context,
+ required VoidCallback setstate}) {
+ final group = boardState.groups[groupIndex];
+ var groupRenderbox = context.findRenderObject() as RenderBox;
+ var position = groupRenderbox.localToGlobal(Offset.zero);
+ group
+ ..position = Offset(position.dx - boardState.boardOffset.dx,
+ position.dy - boardState.boardOffset.dy)
+ ..setState = setstate
+ ..size =
+ Size(groupRenderbox.size.width - LIST_GAP, groupRenderbox.size.height)
+ ..actualSize = group.actualSize == Size.zero
+ ? Size(
+ groupRenderbox.size.width - LIST_GAP, groupRenderbox.size.height)
+ : group.actualSize;
+ }
+
+ /// This method is called on the onDragEnd event of the draggable widget
+ /// It is responsible for reordering the card in the board
+ void onDragEnd() {
+ final groups = boardState.groups;
+ final draggedState = boardState.draggingState;
+
+ final pickedGroupCopy = groups[draggedState.dragStartGroupIndex].deepCopy();
+ final currentGroup = groups[draggedState.currentGroupIndex];
+
+ final placeholderAt = currentGroup.placeHolderAt;
+ groups.removeAt(draggedState.dragStartGroupIndex);
+ groups.insert(
+ placeholderAt == PlaceHolderAt.left
+ ? draggedState.dragStartGroupIndex < draggedState.currentGroupIndex
+ ? max(draggedState.currentGroupIndex - 1, 0)
+ : draggedState.currentGroupIndex
+ : draggedState.dragStartGroupIndex > draggedState.currentGroupIndex
+ ? min(draggedState.currentGroupIndex + 1, groups.length)
+ : draggedState.currentGroupIndex,
+ pickedGroupCopy.updateWith(key: GlobalKey()),
+ );
+
+ /// Remove the groupItem from the index from where it was dragged, and insert it at the current index.
+
+ /// Reset the placeholder of the groupItem.
+ /// This is done to remove the placeholder from the groupItem.
+ currentGroup.placeHolderAt = PlaceHolderAt.none;
+
+ /// Reset the dragging state to its initial state.
+ boardState.draggingState = DraggableState.initial(
+ feedbackOffset: boardState.draggingState.feedbackOffset,
+ );
+
+ boardState.notify();
+ }
+
+ void onGroupLongpress(
+ {required BoardStateController boardState,
+ required int groupIndex,
+ required BuildContext context,
+ required VoidCallback setstate,
+ GroupFooterBuilder? footer,
+ GroupHeaderBuilder? header}) {
+ for (final group in boardState.groups) {
+ if (group.key.currentState == null || !group.key.currentState!.mounted) {
+ break;
+ }
+ final itemRenderBox =
+ group.key.currentState!.context.findRenderObject() as RenderBox;
+ final position = itemRenderBox.localToGlobal(Offset.zero);
+ group
+ ..position = Offset(position.dx - boardState.boardOffset.dx,
+ position.dy - boardState.boardOffset.dy)
+ ..size = Size(
+ itemRenderBox.size.width - LIST_GAP, itemRenderBox.size.height);
+ }
+ boardState.groups[groupIndex].setState = setstate;
+ boardState.draggingState.feedbackOffset.value =
+ boardState.groups[groupIndex].position!;
+ final group = boardState.groups[groupIndex];
+ boardState.draggingState.updateWith(
+ draggingWidget: WidgetHelper.getDraggingGroup(
+ context: context,
+ group: group,
+ groupIndex: groupIndex,
+ footer: footer,
+ header: header),
+ draggableType: DraggableType.group,
+ feedbackSize: boardState.groups[groupIndex].size,
+ dragStartGroupIndex: groupIndex,
+ currentIndex: -1,
+ currentGroupIndex: groupIndex,
+ dragStartIndex: -1);
+ setstate();
+ }
+
+ /// [handleItemDragOverGroup] handles the placement of the dragged item, when it is dragged over a group.
+ /// It only handles two cases:
+ /// 1. When the item is dragged over a empty group.
+ /// 2. When the group from which the item is dragged gets empty || basically the only item in the group is dragged.
+ void handleItemDragOverGroup(
+ int groupIndex,
+ ) {
+ final draggingState = boardState.draggingState;
+ final group = boardState.groups[groupIndex];
+ if (!canItemDropOverGroup(groupIndex)) return;
+ if (group.items.isNotEmpty &&
+ !(group.items.length == 1 &&
+ draggingState.dragStartGroupIndex == groupIndex &&
+ draggingState.dragStartIndex == 0)) return;
+
+ // print("HREE ${draggingState.dragStartGroupIndex} ${groupIndex} ${draggingState.dragStartGroupIndex == groupIndex}");
+ if (group.items.isEmpty) {
+ /// Add the placeholder directly to the group as item.
+ group.items.add(IKanbanBoardGroupItem(
+ key: GlobalKey(),
+ id: 'system-added-placeholder',
+ index: 0,
+ setState: () => {},
+ placeHolderAt: PlaceHolderAt.none,
+ itemWidget: Container(
+ margin: const EdgeInsets.only(
+ bottom: 10,
+ ),
+ width: draggingState.feedbackSize.width,
+ height: draggingState.feedbackSize.height,
+ child: DottedBorder(
+ child: const Center(
+ child: Text(
+ "Drop your task here",
+ style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
+ )),
+ ),
+ ),
+ addedBySystem: true,
+ groupIndex: groupIndex));
+ }
+ WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
+ /// Remove the placeholder from the previous group-item.
+ boardState.groups[draggingState.currentGroupIndex]
+ .items[draggingState.currentIndex].placeHolderAt = PlaceHolderAt.none;
+
+ /// If the last placeholder was added by the system as item in the group, then remove it.
+ if (boardState.groups[draggingState.currentGroupIndex]
+ .items[draggingState.currentIndex].addedBySystem ==
+ true) {
+ boardState.groups[draggingState.currentGroupIndex].items.removeAt(0);
+ boardState.groups[draggingState.currentGroupIndex].setState();
+ }
+
+ /// Update the dragging state.
+ draggingState.currentIndex = 0;
+ draggingState.currentGroupIndex = groupIndex;
+
+ /// Rebuild the group.
+ group.setState();
+ });
+ }
+
+ void handleGroupDragOverGroup(int groupIndex) {
+ final draggingState = boardState.draggingState;
+ final groups = boardState.groups;
+ final group = boardState.groups[groupIndex];
+ final placeholderAt = canGroupDropOverGroup(groupIndex);
+ if (placeholderAt == PlaceHolderAt.none ||
+ group.animationController!.isAnimating) return;
+
+ /// Remove the placeholder from the previous group-item.
+ // wrapWithPlaceHolder(
+ // groupIndex: draggingState.currentGroupIndex, reset: true);
+ groups[draggingState.currentGroupIndex].animationOffset = Offset(
+ groups[draggingState.currentGroupIndex].placeHolderAt ==
+ PlaceHolderAt.left
+ ? -1
+ : groups[draggingState.currentGroupIndex].placeHolderAt ==
+ PlaceHolderAt.right
+ ? 1
+ : 0,
+ 0);
+ groups[draggingState.currentGroupIndex].placeHolderAt = PlaceHolderAt.none;
+
+ // wrapWithPlaceHolder(groupIndex: groupIndex);
+ groups[groupIndex].placeHolderAt = placeholderAt;
+
+ groups[groupIndex].animationOffset = Offset(
+ groups[groupIndex].placeHolderAt == PlaceHolderAt.left
+ ? -1
+ : groups[groupIndex].placeHolderAt == PlaceHolderAt.right
+ ? 1
+ : 0,
+ 0);
+
+ WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
+ if (groups[draggingState.currentGroupIndex]
+ .animationController!
+ .isAnimating) {
+ groups[draggingState.currentGroupIndex].animationController!.fling();
+ }
+ groups[draggingState.currentGroupIndex].setState();
+ draggingState.currentGroupIndex = groupIndex;
+ groups[groupIndex].setState();
+ if (group.animationController!.isCompleted) {
+ group.animationController?.forward(from: -1.0);
+ } else {
+ group.animationController?.forward();
+ }
+ });
+ }
+
+ /// [canItemDropOverGroup] checks if the dragged item can be dropped on the group or not.
+ /// It checks if the item is entering the group from the left or right side.
+ PlaceHolderAt canGroupDropOverGroup(int groupIndex) {
+ final draggingState = boardState.draggingState;
+ final group = boardState.groups[groupIndex];
+ final draggableOffset = draggingState.feedbackOffset.value;
+ // if (groupIndex == 2) {
+ // print(
+ // "DRAGGABLE OFFSET =${draggableOffset.dx} ${group.position!.dx} ${group.size.width} ${draggingState.feedbackSize.width}");
+ // }
+ bool canDrop = false;
+ PlaceHolderAt placeHolderAt = PlaceHolderAt.none;
+ if (group.placeHolderAt == PlaceHolderAt.left) {
+ final entringFromLeft =
+ (draggingState.feedbackSize.width + draggableOffset.dx >
+ group.position!.dx + (group.size.width * 0.75)) &&
+ (group.position!.dx < draggableOffset.dx);
+
+ canDrop = entringFromLeft;
+ placeHolderAt = PlaceHolderAt.right;
+ } else if (group.placeHolderAt == PlaceHolderAt.right) {
+ final entringFromRight = (draggableOffset.dx <
+ group.position!.dx + (group.actualSize.width * 0.5)) &&
+ (group.position!.dx + group.size.width >
+ draggingState.feedbackSize.width + draggableOffset.dx);
+ canDrop = entringFromRight;
+ placeHolderAt = PlaceHolderAt.left;
+ } else {
+ final bool entringFromRight = (draggableOffset.dx <
+ group.position!.dx + (group.size.width * 0.4)) &&
+ ((group.position!.dx + group.actualSize.width <
+ draggingState.feedbackSize.width + draggableOffset.dx));
+ final entringFromLeft =
+ (draggingState.feedbackSize.width + draggableOffset.dx >=
+ group.position!.dx + (group.size.width * 0.5)) &&
+ (draggableOffset.dx < group.position!.dx);
+ canDrop = entringFromLeft || entringFromRight;
+ placeHolderAt = entringFromLeft
+ ? PlaceHolderAt.right
+ : entringFromRight
+ ? PlaceHolderAt.left
+ : PlaceHolderAt.none;
+ }
+ if (canDrop && draggingState.dragStartGroupIndex != groupIndex) {
+ return placeHolderAt;
+ }
+ return PlaceHolderAt.none;
+ }
+
+ /// [canItemDropOverGroup] checks if the dragged item can be dropped on the group or not.
+ /// It checks if the item is entering the group from the left or right side.
+ bool canItemDropOverGroup(int groupIndex) {
+ final draggingState = boardState.draggingState;
+ final group = boardState.groups[groupIndex];
+ final draggableOffset = draggingState.feedbackOffset.value;
+
+ /// Check if the item is entering the group from the left side.
+ final bool entringFromLeft =
+ (draggableOffset.dx < group.position!.dx + (group.size.width * 0.4)) &&
+ ((group.position!.dx + group.size.width <
+ draggingState.feedbackSize.width + draggableOffset.dx));
+
+ /// Check if the item is entering the group from the right side.
+ final entringFromRight =
+ (draggingState.feedbackSize.width + draggableOffset.dx >
+ group.position!.dx + (group.size.width * 0.6)) &&
+ (group.position!.dx + group.size.width >
+ draggingState.feedbackSize.width + draggableOffset.dx);
+
+ /// For item to change group, it should not be in the same group.
+ return (entringFromLeft || entringFromRight) &&
+ draggingState.currentGroupIndex != groupIndex;
+ }
+}
diff --git a/lib/src/controllers/scroll_handler.dart b/lib/src/controllers/scroll_handler.dart
new file mode 100644
index 0000000..db34bdc
--- /dev/null
+++ b/lib/src/controllers/scroll_handler.dart
@@ -0,0 +1,353 @@
+import 'package:flutter/material.dart';
+import 'board_state_controller.dart';
+import 'states/board_internal_states.dart';
+import 'states/draggable_state.dart';
+import 'states/scroll_state.dart';
+
+/// [ScrollHandler] is used when the user is dragging a group-item near the edge of the group-end.
+/// It can variate the velocity of the scroll based on the distance of the draggable to the edge of the group.
+class ScrollHandler {
+ static Duration getDuration(
+ ScrollVelocity velocity, ScrollConfig scrollConfig) {
+ return velocity == ScrollVelocity.slow
+ ? scrollConfig.farBoundary.duration
+ : velocity == ScrollVelocity.medium
+ ? scrollConfig.midBoundary.duration
+ : scrollConfig.nearBoundary.duration;
+ }
+
+ static double getOffsetToMove(
+ ScrollVelocity velocity, ScrollConfig scrollConfig) {
+ return velocity == ScrollVelocity.fast
+ ? scrollConfig.nearBoundary.offset
+ : velocity == ScrollVelocity.medium
+ ? scrollConfig.midBoundary.offset
+ : scrollConfig.farBoundary.offset;
+ }
+
+ static ScrollVelocity getVelocity(
+ AxisDirection axisDirection,
+ double draggingWidgetPosition,
+ double maxViewport,
+ ScrollConfig scrollConfig) {
+ if (axisDirection == AxisDirection.down ||
+ axisDirection == AxisDirection.right) {
+ return draggingWidgetPosition >
+ maxViewport - scrollConfig.farBoundary.boundary
+ ? ScrollVelocity.fast
+ : draggingWidgetPosition >
+ maxViewport - scrollConfig.midBoundary.boundary
+ ? ScrollVelocity.medium
+ : ScrollVelocity.slow;
+ } else {
+ return draggingWidgetPosition < scrollConfig.farBoundary.boundary
+ ? ScrollVelocity.fast
+ : draggingWidgetPosition < scrollConfig.midBoundary.boundary
+ ? ScrollVelocity.medium
+ : ScrollVelocity.slow;
+ }
+ }
+
+ static Future _downsideScroll({
+ required BoardStateController boardState,
+ required ScrollConfig scrollConfig,
+ required ScrollController scrollController,
+ required bool isScrolling,
+ required void Function(bool value) setScrolling,
+ }) async {
+ final draggingState = boardState.draggingState;
+
+ /// If the draggable is not a group-item or the group is already scrolling, [return].
+ /// This is to prevent the group from scrolling while the group is already scrolling.
+ if (draggingState.draggableType != DraggableType.item || isScrolling) {
+ return;
+ }
+ final draggingWidgetBottomPosition = draggingState.feedbackOffset.value.dy +
+ draggingState.feedbackSize.height;
+
+ /// The maximum viewport of the scroll controller.
+ final scrollContext = scrollController.position.context.storageContext;
+ final scrollRenderBox = scrollContext.findRenderObject() as RenderBox;
+ final scrollStartPos = scrollRenderBox.localToGlobal(Offset.zero);
+ final maxViewport = scrollController.position.viewportDimension +
+ scrollStartPos.dy -
+ boardState.boardOffset.dy;
+
+ /// If the scroll controller is not at the end and the dragging widget is near the end of the viewport, scroll the controller.
+ if (scrollController.offset < scrollController.position.maxScrollExtent &&
+ draggingWidgetBottomPosition >
+ maxViewport - scrollConfig.farBoundary.boundary) {
+ setScrolling(true);
+
+ /// [velocity] is used to determine the speed of the scroll.
+ /// It is based on the distance of the dragging widget to the end of the viewport.
+ final velocity = draggingWidgetBottomPosition >
+ maxViewport - scrollConfig.nearBoundary.boundary
+ ? ScrollVelocity.fast
+ : draggingWidgetBottomPosition >
+ maxViewport - scrollConfig.midBoundary.boundary
+ ? ScrollVelocity.medium
+ : ScrollVelocity.slow;
+
+ await scrollController.animateTo(
+ scrollController.offset +
+ (velocity == ScrollVelocity.fast
+ ? scrollConfig.nearBoundary.offset
+ : velocity == ScrollVelocity.medium
+ ? scrollConfig.midBoundary.offset
+ : scrollConfig.farBoundary.offset),
+ duration: velocity == ScrollVelocity.slow
+ ? scrollConfig.farBoundary.duration
+ : velocity == ScrollVelocity.medium
+ ? scrollConfig.midBoundary.duration
+ : scrollConfig.nearBoundary.duration,
+ curve: scrollConfig.curve);
+ setScrolling(false);
+ _downsideScroll(
+ boardState: boardState,
+ scrollConfig: scrollConfig,
+ scrollController: scrollController,
+ isScrolling: false,
+ setScrolling: setScrolling);
+ }
+ return;
+ }
+
+ static Future _upsideScroll({
+ required BoardStateController boardState,
+ required ScrollConfig scrollConfig,
+ required ScrollController scrollController,
+ required bool isScrolling,
+ required void Function(bool value) setScrolling,
+ }) async {
+ final draggingState = boardState.draggingState;
+
+ /// If the draggable is not a group-item or the group is already scrolling, [return].
+ /// This is to prevent the group from scrolling while the group is already scrolling.
+ if (draggingState.draggableType != DraggableType.item || isScrolling) {
+ return;
+ }
+ final draggingWidgetTopPosition = draggingState.feedbackOffset.value.dy;
+
+ /// The minimum viewport of the scroll controller.
+ final scrollContext = scrollController.position.context.storageContext;
+ final scrollRenderBox = scrollContext.findRenderObject() as RenderBox;
+ final scrollStartPos = scrollRenderBox.localToGlobal(Offset.zero);
+ final minViewport = scrollStartPos.dy - boardState.boardOffset.dy;
+
+ /// If the scroll controller is not at the start and the dragging widget is near the start of the viewport, scroll the controller.
+ if (scrollController.offset > scrollController.position.minScrollExtent &&
+ draggingWidgetTopPosition <
+ minViewport + scrollConfig.farBoundary.boundary) {
+ setScrolling(true);
+
+ /// [velocity] is used to determine the speed of the scroll.
+ /// It is based on the distance of the dragging widget to the end of the viewport.
+ final velocity = draggingWidgetTopPosition <
+ minViewport + scrollConfig.nearBoundary.boundary
+ ? ScrollVelocity.fast
+ : draggingWidgetTopPosition <
+ minViewport + scrollConfig.midBoundary.boundary
+ ? ScrollVelocity.medium
+ : ScrollVelocity.slow;
+
+ await scrollController.animateTo(
+ scrollController.offset -
+ (velocity == ScrollVelocity.fast
+ ? scrollConfig.nearBoundary.offset
+ : velocity == ScrollVelocity.medium
+ ? scrollConfig.midBoundary.offset
+ : scrollConfig.farBoundary.offset),
+ duration: velocity == ScrollVelocity.slow
+ ? scrollConfig.farBoundary.duration
+ : velocity == ScrollVelocity.medium
+ ? scrollConfig.midBoundary.duration
+ : scrollConfig.nearBoundary.duration,
+ curve: scrollConfig.curve);
+
+ setScrolling(false);
+ _upsideScroll(
+ boardState: boardState,
+ scrollConfig: scrollConfig,
+ scrollController: scrollController,
+ isScrolling: false,
+ setScrolling: setScrolling);
+ }
+ return;
+ }
+
+ static Future _rightsideScroll({
+ required BoardStateController boardState,
+ required ScrollConfig scrollConfig,
+ required ScrollController scrollController,
+ required bool isScrolling,
+ required void Function(bool value) setScrolling,
+ }) async {
+ final draggingState = boardState.draggingState;
+
+ /// If the draggable is not a group-item or the group is already scrolling, [return].
+ /// This is to prevent the group from scrolling while the group is already scrolling.
+ if (draggingState.draggableType == DraggableType.none || isScrolling) {
+ return;
+ }
+ final draggingWidgetRightPosition = draggingState.feedbackOffset.value.dx +
+ draggingState.feedbackSize.width;
+
+ /// The maximum viewport of the scroll controller.
+ final scrollContext = scrollController.position.context.storageContext;
+ final scrollRenderBox = scrollContext.findRenderObject() as RenderBox;
+ final scrollStartPos = scrollRenderBox.localToGlobal(Offset.zero);
+ final maxViewport = scrollController.position.viewportDimension +
+ scrollStartPos.dx -
+ boardState.boardOffset.dx;
+
+ /// If the scroll controller is not at the end and the dragging widget is near the end of the viewport, scroll the controller.
+ if (scrollController.offset < scrollController.position.maxScrollExtent &&
+ draggingWidgetRightPosition >
+ maxViewport - scrollConfig.farBoundary.boundary) {
+ setScrolling(true);
+
+ /// [velocity] is used to determine the speed of the scroll.
+ /// It is based on the distance of the dragging widget to the end of the viewport.
+ final velocity = draggingWidgetRightPosition >
+ maxViewport - scrollConfig.nearBoundary.boundary
+ ? ScrollVelocity.fast
+ : draggingWidgetRightPosition >
+ maxViewport - scrollConfig.midBoundary.boundary
+ ? ScrollVelocity.medium
+ : ScrollVelocity.slow;
+
+ await scrollController.animateTo(
+ scrollController.offset +
+ (velocity == ScrollVelocity.fast
+ ? scrollConfig.nearBoundary.offset
+ : velocity == ScrollVelocity.medium
+ ? scrollConfig.midBoundary.offset
+ : scrollConfig.farBoundary.offset),
+ duration: velocity == ScrollVelocity.slow
+ ? scrollConfig.farBoundary.duration
+ : velocity == ScrollVelocity.medium
+ ? scrollConfig.midBoundary.duration
+ : scrollConfig.nearBoundary.duration,
+ curve: scrollConfig.curve);
+ setScrolling(false);
+ _rightsideScroll(
+ boardState: boardState,
+ scrollConfig: scrollConfig,
+ scrollController: scrollController,
+ isScrolling: false,
+ setScrolling: setScrolling);
+ }
+ return;
+ }
+
+ static Future _leftsideScroll({
+ required BoardStateController boardState,
+ required ScrollConfig scrollConfig,
+ required ScrollController scrollController,
+ required bool isScrolling,
+ required void Function(bool value) setScrolling,
+ }) async {
+ final draggingState = boardState.draggingState;
+
+ /// If the draggable is not a group-item or the group is already scrolling, [return].
+ /// This is to prevent the group from scrolling while the group is already scrolling.
+ if (draggingState.draggableType == DraggableType.none || isScrolling) {
+ return;
+ }
+ final draggingWidgetLeftPosition = draggingState.feedbackOffset.value.dx;
+
+ /// The minimum viewport of the scroll controller.
+ final minViewport = boardState.boardOffset.dx;
+
+ /// If the scroll controller is not at the start and the dragging widget is near the start of the viewport, scroll the controller.
+ if (scrollController.offset > scrollController.position.minScrollExtent &&
+ draggingWidgetLeftPosition <
+ minViewport + scrollConfig.farBoundary.boundary) {
+ setScrolling(true);
+
+ /// [velocity] is used to determine the speed of the scroll.
+ /// It is based on the distance of the dragging widget to the end of the viewport.
+ final velocity = draggingWidgetLeftPosition <
+ minViewport + scrollConfig.nearBoundary.boundary
+ ? ScrollVelocity.fast
+ : draggingWidgetLeftPosition <
+ minViewport + scrollConfig.midBoundary.boundary
+ ? ScrollVelocity.medium
+ : ScrollVelocity.slow;
+
+ await scrollController.animateTo(
+ scrollController.offset -
+ (velocity == ScrollVelocity.fast
+ ? scrollConfig.nearBoundary.offset
+ : velocity == ScrollVelocity.medium
+ ? scrollConfig.midBoundary.offset
+ : scrollConfig.farBoundary.offset),
+ duration: velocity == ScrollVelocity.slow
+ ? scrollConfig.farBoundary.duration
+ : velocity == ScrollVelocity.medium
+ ? scrollConfig.midBoundary.duration
+ : scrollConfig.nearBoundary.duration,
+ curve: scrollConfig.curve);
+
+ setScrolling(false);
+ _leftsideScroll(
+ boardState: boardState,
+ scrollConfig: scrollConfig,
+ scrollController: scrollController,
+ isScrolling: false,
+ setScrolling: setScrolling);
+ }
+ return;
+ }
+}
+
+class GroupScrollHandler {
+ static Future checkGroupScroll({
+ required BoardStateController boardState,
+ required ScrollConfig scrollConfig,
+ required ScrollController scrollController,
+ required bool isScrolling,
+ required void Function(bool value) setScrolling,
+ }) async {
+ await ScrollHandler._downsideScroll(
+ boardState: boardState,
+ scrollConfig: scrollConfig,
+ scrollController: scrollController,
+ isScrolling: isScrolling,
+ setScrolling: setScrolling,
+ );
+ await ScrollHandler._upsideScroll(
+ boardState: boardState,
+ scrollConfig: scrollConfig,
+ scrollController: scrollController,
+ isScrolling: isScrolling,
+ setScrolling: setScrolling,
+ );
+ }
+}
+
+class BoardScrollHandler {
+ static Future checkBoardScroll({
+ required BoardStateController boardState,
+ required ScrollConfig scrollConfig,
+ required ScrollController scrollController,
+ required bool isScrolling,
+ required void Function(bool value) setScrolling,
+ }) async {
+ await ScrollHandler._rightsideScroll(
+ boardState: boardState,
+ scrollConfig: scrollConfig,
+ scrollController: scrollController,
+ isScrolling: isScrolling,
+ setScrolling: setScrolling,
+ );
+ await ScrollHandler._leftsideScroll(
+ boardState: boardState,
+ scrollConfig: scrollConfig,
+ scrollController: scrollController,
+ isScrolling: isScrolling,
+ setScrolling: setScrolling,
+ );
+ }
+}
diff --git a/lib/src/controllers/states/board_internal_states.dart b/lib/src/controllers/states/board_internal_states.dart
new file mode 100644
index 0000000..b16a618
--- /dev/null
+++ b/lib/src/controllers/states/board_internal_states.dart
@@ -0,0 +1,221 @@
+import 'package:flutter/material.dart';
+import 'draggable_state.dart';
+
+/// [IKanbanBoardGroup] is used to manage the internal state of the [Groups].
+class IKanbanBoardGroup {
+ /// The [id] of the group.
+ String id;
+
+ /// This stores the [name] of the group.
+ String name;
+
+ /// This is a [customData] that can be used to store any data.
+ dynamic customData;
+
+ /// It is the index of the group in the board.
+ int index;
+
+ /// It is the [key] of the group.
+ /// This is used to identify the group and compute its position offset.
+ GlobalKey key;
+
+ /// This is the [ghost] widget that is used to show the dragging widget.
+ Widget? ghost;
+
+ /// [placeHolderAt] determines if the placeholder is attached to group or not, and its position.
+ PlaceHolderAt placeHolderAt = PlaceHolderAt.none;
+
+ /// It handles the group animation when dragging widget is near the group.
+ AnimationController? animationController;
+
+ late Offset animationOffset;
+
+ /// This holds the last computed [position] of the group.
+ Offset? position;
+
+ /// This holds the last computed [size] of the group.
+ Size size = Size.zero;
+
+ /// This holds the last computed [actualSize] of the group.
+ Size actualSize = Size.zero;
+
+ /// [setState] is used to update the state of the group.
+ /// It is often used to invoke rebuild of the widget.
+ VoidCallback setState;
+
+ /// This is the [scrollController] that is used to control the scroll of the group.
+ /// It is used to scroll the group when the dragging widget is near the edge of the group.
+ ScrollController scrollController = ScrollController();
+
+ /// This contains the list of [items] in the group.
+ List items = const [];
+
+ IKanbanBoardGroup({
+ required this.id,
+ required this.key,
+ required this.name,
+ required this.index,
+ required this.setState,
+ required this.scrollController,
+ this.animationController,
+ this.customData,
+ this.ghost,
+ this.items = const [],
+ this.placeHolderAt = PlaceHolderAt.none,
+ this.position,
+ this.size = const Size(0, 0),
+ this.actualSize = const Size(0, 0),
+ this.animationOffset = Offset.zero,
+ });
+
+ IKanbanBoardGroup updateWith({
+ String? id,
+ GlobalKey? key,
+ String? name,
+ int? index,
+ VoidCallback? setState,
+ dynamic customData,
+ Widget? ghost,
+ Offset? position,
+ Size? size,
+ Size? actualSize,
+ List? items,
+ Offset? animationOffset,
+ PlaceHolderAt? placeHolderAt,
+ ScrollController? scrollController,
+ AnimationController? animationController,
+ }) {
+ this.id = id ?? this.id;
+ this.key = key ?? this.key;
+ this.name = name ?? this.name;
+ this.index = index ?? this.index;
+ this.setState = setState ?? this.setState;
+ this.customData = customData ?? this.customData;
+ this.ghost = ghost ?? this.ghost;
+ this.position = position ?? this.position;
+ this.size = size ?? this.size;
+ this.actualSize = actualSize ?? this.actualSize;
+ this.items = items ?? this.items;
+ this.animationOffset = animationOffset ?? this.animationOffset;
+ this.placeHolderAt = placeHolderAt ?? this.placeHolderAt;
+ this.scrollController = scrollController ?? this.scrollController;
+ this.animationController = animationController ?? this.animationController;
+ return this;
+ }
+
+ // get deepcopy
+ IKanbanBoardGroup deepCopy() {
+ return IKanbanBoardGroup(
+ id: id,
+ key: key,
+ name: name,
+ index: index,
+ setState: setState,
+ scrollController: scrollController,
+ animationController: animationController,
+ customData: customData,
+ ghost: ghost,
+ items: items,
+ placeHolderAt: placeHolderAt,
+ position: position,
+ size: size,
+ actualSize: actualSize,
+ animationOffset: animationOffset,
+ );
+ }
+}
+
+/// [IKanbanBoardGroupItem] is used to manage the internal state of the [GroupItems].
+class IKanbanBoardGroupItem {
+ /// The [id] of the item.
+ String id;
+
+ /// It is the [index] of the item in the group.
+ int index;
+
+ /// It is the index of the group this item is associated with in the board.
+ int groupIndex;
+
+ /// This is the [key] of the item.
+ /// This is used to identify the item and compute its position offset.
+ GlobalKey key;
+
+ /// This is the [ghost] widget that is used to show the dragging widget.
+ Widget? ghost;
+
+ /// This is the [itemWidget] that is used to show the item.
+ Widget? itemWidget;
+
+ /// This holds the last computed [position] of the item.
+ Offset? position;
+
+ /// [setState] is used to update the state of the group-item.
+ /// It is often used to invoke rebuild of the widget.
+ VoidCallback setState;
+
+ /// This is used to determine if the item was added by the system.
+ /// This is used when group is empty, and the system adds an item to it, to show the placeholder.
+ bool addedBySystem = false;
+
+ /// [placeHolderAt] used to determine if the placeholder is attached to item or not.
+ /// This also defines the position of the placeholder. It can be at the start or at the end of the item.
+ PlaceHolderAt placeHolderAt = PlaceHolderAt.none;
+
+ /// This holds the last computed [size] of the item.
+ /// This will be affected by the placeholder.
+ Size size = Size.zero;
+
+ /// This holds the last computed [actualSize] of the item.
+ /// This will not be affected by the placeholder.
+ Size actualSize = Size.zero;
+
+ // This is used to animate the item when dragging widget is near the item.
+ late AnimationController? animationController;
+
+ IKanbanBoardGroupItem({
+ required this.key,
+ required this.id,
+ required this.index,
+ required this.setState,
+ required this.groupIndex,
+ this.ghost,
+ this.itemWidget,
+ this.position,
+ this.size = const Size(0, 0),
+ this.addedBySystem = false,
+ this.placeHolderAt = PlaceHolderAt.none,
+ this.animationController,
+ });
+
+ IKanbanBoardGroupItem updateWith({
+ GlobalKey? key,
+ String? id,
+ int? index,
+ int? groupIndex,
+ VoidCallback? setState,
+ Widget? ghost,
+ Widget? itemWidget,
+ PlaceHolderAt? placeHolderAt,
+ Size? size,
+ Size? actualSize,
+ Offset? position,
+ }) {
+ this.key = key ?? this.key;
+ this.id = id ?? this.id;
+ this.index = index ?? this.index;
+ this.groupIndex = groupIndex ?? this.groupIndex;
+ this.setState = setState ?? this.setState;
+ this.ghost = ghost ?? this.ghost;
+ this.itemWidget = itemWidget ?? this.itemWidget;
+ this.placeHolderAt = placeHolderAt ?? this.placeHolderAt;
+ this.size = size ?? this.size;
+ this.actualSize = actualSize ?? this.actualSize;
+ this.position = position ?? this.position;
+ return this;
+ }
+}
+
+/// [GroupOperationType] is used to determine the type of operation to be performed on the group.
+enum GroupOperationType { addItem, delete }
+
+enum ScrollVelocity { slow, medium, fast }
diff --git a/lib/src/controllers/states/draggable_state.dart b/lib/src/controllers/states/draggable_state.dart
new file mode 100644
index 0000000..bc7df07
--- /dev/null
+++ b/lib/src/controllers/states/draggable_state.dart
@@ -0,0 +1,92 @@
+import 'package:flutter/material.dart';
+
+/// [PlaceHolderAt] is used to determine the location of the placeholder widget.
+enum PlaceHolderAt { top, bottom, left, right, none }
+
+/// [DraggableType] is used to determine the type of the widget that is being dragged.
+enum DraggableType { item, group, none }
+
+/// It is used to manage the internal state of the [dragging-widget] widget.
+class DraggableState {
+ DraggableState._({
+ required this.feedbackOffset,
+ this.draggingWidget,
+ this.draggableType = DraggableType.none,
+ this.feedbackSize = Size.zero,
+ this.dragStartIndex = -1,
+ this.currentIndex = -1,
+ this.dragStartGroupIndex = -1,
+ this.currentGroupIndex = -1,
+ });
+
+ /// The widget currently being dragged.
+ Widget? draggingWidget;
+
+ /// The type [DraggableType] of the widget currently being dragged.
+ DraggableType draggableType = DraggableType.none;
+
+ /// The last computed size of the feedback widget being dragged.
+ Size feedbackSize = Size.zero;
+
+ /// The last computed offset of the feedback widget being dragged.
+ ValueNotifier feedbackOffset;
+
+ /// This is the last computed direction of the feedback widget being dragged.
+ /// This value is updated on every pixel movement of the feedback widget.
+ /// This value is used to determine the direction of the feedback widget, includes [AxisDirection.up], [AxisDirection.down], [AxisDirection.left], [AxisDirection.right].
+ AxisDirection? axisDirection;
+
+ /// The location that the dragging widget occupied before it started to drag.
+ int dragStartIndex = -1;
+
+ /// The index that the dragging widget currently occupies.
+ int currentIndex = -1;
+
+ /// The index of the group that the dragging widget started to drag from.
+ int dragStartGroupIndex = -1;
+
+ /// The index of the group that the dragging widget currently occupies.
+ int currentGroupIndex = -1;
+
+ bool hidePlaceholder = false;
+
+ /// It is set [DraggableState] to its initial state.
+ factory DraggableState.initial({
+ /// It's reference should not be changed, as it's being listened by the [ValueListenableBuilder].
+ /// so, if [DraggableState] is being updated with initial values, after initialization of the board, then it must be passed as a parameter.
+ ValueNotifier? feedbackOffset,
+ }) {
+ return DraggableState._(
+ feedbackOffset: feedbackOffset ?? ValueNotifier(Offset.zero),
+ draggingWidget: null,
+ draggableType: DraggableType.none,
+ feedbackSize: Size.zero,
+ dragStartIndex: -1,
+ currentIndex: -1,
+ dragStartGroupIndex: -1,
+ currentGroupIndex: -1,
+ );
+ }
+
+ /// It is used to update the state of the [DraggableState], with the new values.
+ DraggableState updateWith({
+ Widget? draggingWidget,
+ DraggableType? draggableType,
+ Size? feedbackSize,
+ int? dragStartIndex,
+ int? currentIndex,
+ int? dragStartGroupIndex,
+ int? currentGroupIndex,
+ bool? hidePlaceholder,
+ }) {
+ this.draggingWidget = draggingWidget ?? this.draggingWidget;
+ this.draggableType = draggableType ?? this.draggableType;
+ this.feedbackSize = feedbackSize ?? this.feedbackSize;
+ this.dragStartIndex = dragStartIndex ?? this.dragStartIndex;
+ this.currentIndex = currentIndex ?? this.currentIndex;
+ this.dragStartGroupIndex = dragStartGroupIndex ?? this.dragStartGroupIndex;
+ this.currentGroupIndex = currentGroupIndex ?? this.currentGroupIndex;
+ this.hidePlaceholder = hidePlaceholder ?? this.hidePlaceholder;
+ return this;
+ }
+}
diff --git a/lib/src/controllers/states/scroll_state.dart b/lib/src/controllers/states/scroll_state.dart
new file mode 100644
index 0000000..bbb3b84
--- /dev/null
+++ b/lib/src/controllers/states/scroll_state.dart
@@ -0,0 +1,104 @@
+import 'package:flutter/material.dart';
+import 'package:kanban_board/src/constants/constants.dart';
+
+/// [ScrollConfig] is used to configure the scroll behaviour.
+class ScrollConfig {
+ final Boundary nearBoundary;
+ final Boundary midBoundary;
+ final Boundary farBoundary;
+ final Curve curve;
+
+ const ScrollConfig({
+ required this.nearBoundary,
+ required this.midBoundary,
+ required this.farBoundary,
+ required this.curve,
+ });
+}
+
+class GroupScrollConfig extends ScrollConfig {
+ const GroupScrollConfig():super(
+ nearBoundary: const _GroupNearBoundary(),
+ midBoundary: const _GroupMidBoundary(),
+ farBoundary: const _GroupFarBoundary(),
+ curve: Curves.linear,
+ );
+}
+
+class BoardScrollConfig extends ScrollConfig {
+ const BoardScrollConfig():super(
+ nearBoundary: const _BoardNearBoundary(),
+ midBoundary: const _BoardMidBoundary(),
+ farBoundary: const _BoardFarBoundary(),
+ curve: Curves.linear,
+ );
+}
+
+/// [Boundary] is used to define the boundary of the scroll, with its respective offset and duration.
+class Boundary {
+ /// [boundary] is the distance from the edge of the group.
+ final double boundary;
+
+ /// [offset] is the distance to be scrolled.
+ final double offset;
+
+ /// [duration] is the duration of the scroll.
+ final Duration duration;
+
+ const Boundary({
+ required this.boundary,
+ required this.offset,
+ required this.duration,
+ });
+}
+
+// Group Scroll Boundaries
+class _GroupFarBoundary extends Boundary {
+ const _GroupFarBoundary({
+ double boundary = GROUP_FAR_SCROLL_BOUNDARY,
+ double offset = GROUP_FAR_SCROLL_MOVE,
+ Duration duration = GROUP_FAR_SCROLL_DURATION,
+ }) : super(boundary: boundary, offset: offset, duration: duration);
+}
+
+class _GroupMidBoundary extends Boundary {
+ const _GroupMidBoundary({
+ double boundary = GROUP_MID_SCROLL_BOUNDARY,
+ double offset = GROUP_MID_SCROLL_MOVE,
+ Duration duration = GROUP_MID_SCROLL_DURATION,
+ }) : super(boundary: boundary, offset: offset, duration: duration);
+}
+
+class _GroupNearBoundary extends Boundary {
+ const _GroupNearBoundary({
+ double boundary = GROUP_NEAR_SCROLL_BOUNDARY,
+ double offset = GROUP_NEAR_SCROLL_MOVE,
+ Duration duration = GROUP_NEAR_SCROLL_DURATION,
+ }) : super(boundary: boundary, offset: offset, duration: duration);
+}
+
+// Board Scroll Boundaries
+
+class _BoardFarBoundary extends Boundary {
+ const _BoardFarBoundary({
+ double boundary = BOARD_FAR_SCROLL_BOUNDARY,
+ double offset = BOARD_FAR_SCROLL_MOVE,
+ Duration duration = BOARD_FAR_SCROLL_DURATION,
+ }) : super(boundary: boundary, offset: offset, duration: duration);
+}
+
+class _BoardMidBoundary extends Boundary {
+ const _BoardMidBoundary({
+ double boundary = BOARD_MID_SCROLL_BOUNDARY,
+ double offset = BOARD_MID_SCROLL_MOVE,
+ Duration duration = BOARD_MID_SCROLL_DURATION,
+ }) : super(boundary: boundary, offset: offset, duration: duration);
+}
+
+class _BoardNearBoundary extends Boundary {
+ const _BoardNearBoundary({
+ double boundary = BOARD_NEAR_SCROLL_BOUNDARY,
+ double offset = BOARD_NEAR_SCROLL_MOVE,
+ Duration duration = BOARD_NEAR_SCROLL_DURATION,
+ }) : super(boundary: boundary, offset: offset, duration: duration);
+}
diff --git a/lib/src/helpers/board_state_controller_storage.dart b/lib/src/helpers/board_state_controller_storage.dart
new file mode 100644
index 0000000..6a72e01
--- /dev/null
+++ b/lib/src/helpers/board_state_controller_storage.dart
@@ -0,0 +1,23 @@
+
+import 'package:kanban_board/src/controllers/controllers.dart';
+
+class BoardStateControllerStorage{
+ BoardStateControllerStorage._();
+ static final BoardStateControllerStorage _instance = BoardStateControllerStorage._();
+ static BoardStateControllerStorage get I => _instance;
+
+
+ final Map _controllers = {};
+
+ void addStateController(String key, BoardStateController controller){
+ _controllers[key] = controller;
+ }
+
+ BoardStateController? getStateController(String key){
+ return _controllers[key];
+ }
+
+ void removeStateController(String key){
+ _controllers.remove(key);
+ }
+}
\ No newline at end of file
diff --git a/lib/src/helpers/widget_detail.helper.dart b/lib/src/helpers/widget_detail.helper.dart
new file mode 100644
index 0000000..17d560a
--- /dev/null
+++ b/lib/src/helpers/widget_detail.helper.dart
@@ -0,0 +1,43 @@
+import 'package:flutter/material.dart';
+import 'package:kanban_board/src/board.dart';
+import 'package:kanban_board/src/constants/widget_styles.dart';
+import 'package:kanban_board/src/controllers/controllers.dart';
+
+class WidgetHelper {
+ static Widget getDraggingGroup(
+ {required BuildContext context,
+ required IKanbanBoardGroup group,
+ required int groupIndex,
+ GroupHeaderBuilder? header,
+ GroupFooterBuilder? footer}) {
+ return SizedBox(
+ height: group.size.height,
+ width: group.size.width,
+ child: Column(mainAxisSize: MainAxisSize.min, children: [
+ /// This builds the header of the group.
+ /// If the [GroupHeaderBuilder] is not provided, then it uses the default header.
+ header != null
+ ? header(context, group.id)
+ : DefaultStyles.groupHeader(group: group, onOperationSelect: (_) {}),
+
+ /// This builds the body of the group.
+ /// This renders the list of items in the group.
+ Flexible(
+ child: ListView.builder(
+ itemCount: group.items.length,
+ shrinkWrap: true,
+ itemBuilder: (ctx, index) {
+ return group.items[index].itemWidget;
+ },
+ ),
+ ),
+
+ /// This builds the footer of the group.
+ /// If the [GroupFooterBuilder] is not provided, then it uses the default footer.
+ footer != null
+ ? footer(context, group.id)
+ : DefaultStyles.groupFooter(onAddNewGroup: () => {})
+ ]),
+ );
+ }
+}
diff --git a/lib/src/widgets/board-group/board_group.dart b/lib/src/widgets/board-group/board_group.dart
new file mode 100644
index 0000000..b029aa7
--- /dev/null
+++ b/lib/src/widgets/board-group/board_group.dart
@@ -0,0 +1,240 @@
+import 'dart:developer';
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:kanban_board/src/constants/constants.dart';
+import 'package:kanban_board/src/constants/widget_styles.dart';
+import 'package:kanban_board/src/board.dart';
+import 'package:kanban_board/src/controllers/controllers.dart';
+import 'package:kanban_board/src/widgets/widgets.dart';
+
+class BoardGroup extends ConsumerStatefulWidget {
+ const BoardGroup(
+ {required this.boardStateController,
+ required this.groupIndex,
+ required this.groupConstraints,
+ required this.groupDecoration,
+ required this.itemBuilder,
+ required this.groupItemStateContoller,
+ required this.groupStateController,
+ this.footer,
+ this.header,
+ super.key});
+ final int groupIndex;
+ final ChangeNotifierProvider boardStateController;
+ final ChangeNotifierProvider
+ groupItemStateContoller;
+ final ChangeNotifierProvider groupStateController;
+ final BoxConstraints groupConstraints;
+ final Decoration? groupDecoration;
+ final GroupFooterBuilder? footer;
+ final GroupHeaderBuilder? header;
+ final GroupItemBuilder? itemBuilder;
+
+ @override
+ ConsumerState createState() => _BoardGroupState();
+}
+
+class _BoardGroupState extends ConsumerState {
+ /// [_scrollController] is used to control the scroll of the group.
+ late ScrollController _scrollController;
+
+ bool resetPlaceholder = false;
+
+ /// It is used to handle the operations on the group.
+ /// It is called when an operation is selected on the group through default header popup menu.
+ void onOperationSelect(GroupOperationType type) {
+ log("Operation selected $type");
+ }
+
+ void _activateBoardGroupScrollListeners() {
+ final boardState = ref.read(widget.boardStateController);
+ final groupState = ref.read(widget.groupStateController);
+ for (final group in boardState.groups) {
+ // Group Scroll Listener
+ //TODO: remove listener on dispose
+ group.scrollController.addListener(() {
+ if (groupState.isScrolling) {
+ /// This is to notify newly group-items came into view.
+ /// about the dragging widget position & calculate their position & size to show placeholder.
+ boardState.draggingState.feedbackOffset.value = Offset(
+ boardState.draggingState.feedbackOffset.value.dx,
+ boardState.draggingState.feedbackOffset.value.dy + 0.00001);
+ }
+ });
+ }
+ }
+
+ void _onDragUpdate(Offset draggableOffset, VoidCallback setstate) {
+ final boardState = ref.read(widget.boardStateController);
+ final draggingState = ref.read(widget.boardStateController).draggingState;
+ final group = boardState.groups[widget.groupIndex];
+ group.setState = setstate;
+ if (draggingState.draggableType == DraggableType.none) return;
+ var box = context.findRenderObject();
+ var groupRenderBox = group.key.currentContext!.findRenderObject();
+ if (box == null || groupRenderBox == null) return;
+ box = box as RenderBox;
+ groupRenderBox = groupRenderBox as RenderBox;
+ final groupPosition = groupRenderBox.localToGlobal(Offset.zero);
+ group.position = Offset(groupPosition.dx - boardState.boardOffset.dx,
+ groupPosition.dy - boardState.boardOffset.dy);
+ group
+ ..size =
+ Size(groupRenderBox.size.width - LIST_GAP, groupRenderBox.size.height)
+ ..actualSize =
+ group.actualSize == Size.zero ? group.size : group.actualSize;
+ if (draggingState.draggableType == DraggableType.item) {
+ ref
+ .read(widget.groupStateController)
+ .handleItemDragOverGroup(widget.groupIndex);
+ } else {
+ ref
+ .read(widget.groupStateController)
+ .handleGroupDragOverGroup(widget.groupIndex);
+ }
+ }
+
+ @override
+ void initState() {
+ _scrollController = ref
+ .read(widget.boardStateController)
+ .groups[widget.groupIndex]
+ .scrollController;
+ _activateBoardGroupScrollListeners();
+
+ super.initState();
+ }
+
+ @override
+ void dispose() {
+ _scrollController.removeListener(() {});
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final group = ref.watch(widget.boardStateController
+ .select((value) => value.groups[widget.groupIndex]));
+ final draggingState = ref.read(widget.boardStateController).draggingState;
+
+ return ValueListenableBuilder(
+ key: group.key,
+ valueListenable: draggingState.feedbackOffset,
+ builder: (context, draggableOffset, b) {
+ _onDragUpdate(draggableOffset, () => setState(() {}));
+ return b!;
+ },
+ child: draggingState.dragStartGroupIndex == widget.groupIndex &&
+ draggingState.currentGroupIndex != widget.groupIndex
+ ? Container()
+ : GroupPlaceholderWrapper(
+ groupIndex: widget.groupIndex,
+ groupStateController: widget.groupStateController,
+ boardStateController: widget.boardStateController,
+ child: Align(
+ alignment: Alignment.topCenter,
+ child: Container(
+ margin: const EdgeInsets.only(bottom: 15, right: LIST_GAP),
+ width: widget.groupConstraints.maxWidth,
+ decoration:
+ widget.groupDecoration ?? DefaultStyles.groupDecoration(),
+
+ ///If the current draggable is [this] group and it is being dragged over the current group, then animate.
+ child: GestureDetector(
+ onLongPress: () =>
+ ref.read(widget.groupStateController).onGroupLongpress(
+ boardState: ref.read(widget.boardStateController),
+ groupIndex: widget.groupIndex,
+ context: context,
+ setstate: () => setState(() {}),
+ footer: widget.footer,
+ header: widget.header,
+ ),
+ child: Column(mainAxisSize: MainAxisSize.min, children: [
+ /// This builds the header of the group.
+ /// If the [GroupHeaderBuilder] is not provided, then it uses the default header.
+ _buildHeader(context, group),
+
+ /// This builds the body of the group.
+ /// This renders the list of items in the group.
+ Flexible(
+ child: ListView.builder(
+ controller: _scrollController,
+ itemCount: group.items.length,
+ itemBuilder: (ctx, index) {
+ return GroupItem(
+ boardState: widget.boardStateController,
+ boardGroupState: widget.groupStateController,
+ groupItemState: widget.groupItemStateContoller,
+ itemIndex: index,
+ groupIndex: widget.groupIndex,
+ );
+ },
+ ),
+ ),
+
+ /// This builds the footer of the group.
+ /// If the [GroupFooterBuilder] is not provided, then it uses the default footer.
+ _buildFooter(context, group),
+ ]),
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+
+ Widget _buildHeader(BuildContext context, IKanbanBoardGroup group) {
+ return widget.header != null
+ ? widget.header!(context, group.id)
+ : DefaultStyles.groupHeader(
+ group: group, onOperationSelect: onOperationSelect);
+ }
+
+ Widget _buildFooter(BuildContext context, IKanbanBoardGroup group) {
+ return widget.footer != null
+ ? widget.footer!(context, group.id)
+ : DefaultStyles.groupFooter(
+ onAddNewGroup: () =>
+ {onOperationSelect(GroupOperationType.addItem)});
+ }
+
+ void _scrollToMax() async {
+ if (_scrollController.position.pixels ==
+ _scrollController.position.maxScrollExtent) {
+ return;
+ }
+ await _scrollController.animateTo(
+ _scrollController.position.pixels +
+ _scrollController.position.extentAfter,
+ duration: Duration(
+ milliseconds: (int.parse(_scrollController.position.extentAfter
+ .toString()
+ .substring(0, 3)
+ .split('.')
+ .first))),
+ curve: Curves.linear,
+ );
+ _scrollToMax();
+ }
+
+ void scrollToMin() async {
+ if (_scrollController.position.pixels ==
+ _scrollController.position.minScrollExtent) {
+ return;
+ }
+ log(_scrollController.position.extentBefore.toString());
+ await _scrollController.animateTo(
+ _scrollController.position.pixels -
+ _scrollController.position.extentBefore,
+ duration: Duration(
+ milliseconds: (int.parse(_scrollController.position.extentBefore
+ .toString()
+ .substring(0, 3)
+ .split('.')
+ .first))),
+ curve: Curves.linear,
+ );
+ scrollToMin();
+ }
+}
diff --git a/lib/src/widgets/board-group/board_groups_root.dart b/lib/src/widgets/board-group/board_groups_root.dart
new file mode 100644
index 0000000..4c6e13e
--- /dev/null
+++ b/lib/src/widgets/board-group/board_groups_root.dart
@@ -0,0 +1,88 @@
+import 'package:flutter/gestures.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:kanban_board/src/controllers/group_state_controller.dart';
+import 'package:kanban_board/src/controllers/board_state_controller.dart';
+import 'package:kanban_board/src/controllers/group_item_state_controller.dart';
+import 'package:kanban_board/src/board.dart';
+import 'board_group.dart';
+
+class BoardGroupsRoot extends ConsumerStatefulWidget {
+ const BoardGroupsRoot(
+ {required this.boardStateController,
+ required this.groupItemBuilder,
+ required this.leading,
+ required this.trailing,
+ required this.groupConstraints,
+ required this.groupDecoration,
+ required this.groupItemStateController,
+ required this.groupStateController,
+ required this.boardScrollController,
+ this.header,
+ this.footer,
+ super.key});
+ final ChangeNotifierProvider boardStateController;
+ final ChangeNotifierProvider
+ groupItemStateController;
+ final ChangeNotifierProvider groupStateController;
+ final ScrollController boardScrollController;
+ final Widget? trailing;
+ final Widget? leading;
+ final BoxConstraints groupConstraints;
+ final Decoration? groupDecoration;
+ final GroupItemBuilder groupItemBuilder;
+ final GroupHeaderBuilder? header;
+ final GroupFooterBuilder? footer;
+ @override
+ ConsumerState createState() => _BoardGroupsRootState();
+}
+
+class _BoardGroupsRootState extends ConsumerState {
+ bool _showNewGroup = false;
+ void createNewGroup() {
+ setState(() {
+ _showNewGroup = true;
+ });
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final groups =
+ ref.watch(widget.boardStateController.select((value) => value.groups));
+ return ScrollConfiguration(
+ behavior: ScrollConfiguration.of(context).copyWith(
+ dragDevices: {
+ PointerDeviceKind.mouse,
+ PointerDeviceKind.touch,
+ },
+ ),
+ child: SingleChildScrollView(
+ controller: widget.boardScrollController,
+ scrollDirection: Axis.horizontal,
+ child: Row(children: [
+ widget.leading ?? Container(),
+ ...[
+ for (int index = 0; index < groups.length; index++)
+ BoardGroup(
+ groupItemStateContoller: widget.groupItemStateController,
+ groupStateController: widget.groupStateController,
+ header: widget.header,
+ footer: widget.footer,
+ itemBuilder: widget.groupItemBuilder,
+ groupDecoration: widget.groupDecoration,
+ boardStateController: widget.boardStateController,
+ groupIndex: index,
+ groupConstraints: widget.groupConstraints,
+ ),
+ ],
+ _showNewGroup
+ ? Container(
+ height: 300,
+ width: 300,
+ color: Colors.purple,
+ )
+ : widget.trailing ?? Container(),
+ ]),
+ ));
+ }
+}
diff --git a/lib/src/widgets/board-group/group_placeholder_wrapper.dart b/lib/src/widgets/board-group/group_placeholder_wrapper.dart
new file mode 100644
index 0000000..1cf4328
--- /dev/null
+++ b/lib/src/widgets/board-group/group_placeholder_wrapper.dart
@@ -0,0 +1,117 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:kanban_board/src/constants/constants.dart';
+import 'package:kanban_board/src/controllers/controllers.dart';
+
+class GroupPlaceholderWrapper extends ConsumerStatefulWidget {
+ const GroupPlaceholderWrapper(
+ {required this.groupIndex,
+ required this.groupStateController,
+ required this.boardStateController,
+ required this.child,
+ super.key});
+ final int groupIndex;
+ final ChangeNotifierProvider groupStateController;
+ final ChangeNotifierProvider boardStateController;
+ final Widget child;
+ @override
+ ConsumerState createState() =>
+ _GroupPlaceholderWrapperState();
+}
+
+class _GroupPlaceholderWrapperState
+ extends ConsumerState
+ with TickerProviderStateMixin {
+ late AnimationController? _animationController;
+
+ @override
+ void initState() {
+ final group =
+ ref.read(widget.boardStateController).groups[widget.groupIndex];
+
+ group.animationController = AnimationController(
+ vsync: this, duration: const Duration(milliseconds: 700));
+
+ group.animationOffset = Offset(
+ group.placeHolderAt == PlaceHolderAt.left
+ ? -1
+ : group.placeHolderAt == PlaceHolderAt.right
+ ? 1
+ : 0,
+ 0);
+ _animationController = group.animationController;
+
+ super.initState();
+ }
+
+ @override
+ void dispose() {
+ _animationController?.dispose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final boardState = ref.read(widget.boardStateController);
+ final draggingState = boardState.draggingState;
+ final group = boardState.groups[widget.groupIndex];
+
+ return Row(
+ children: [
+ group.placeHolderAt == PlaceHolderAt.left
+ ? TweenAnimationBuilder(
+ duration: const Duration(milliseconds: 700),
+ curve: Curves.easeOutSine,
+ tween: Tween(begin: 0, end: 1),
+ builder: (context, value, child) {
+ return Opacity(
+ opacity: value,
+ child: child,
+ );
+ },
+ child: Opacity(
+ opacity: 0.6,
+ child: Container(
+ margin: const EdgeInsets.only(right: LIST_GAP),
+ child: draggingState.draggingWidget,
+ ),
+ ))
+ : Container(),
+ SlideTransition(
+ position: Tween(
+ begin: group.animationOffset,
+ end: const Offset(0, 0),
+ ).animate(
+ CurvedAnimation(
+ parent: _animationController!,
+ curve: _animationController!.isAnimating
+ ? Curves.easeOutSine
+ : Curves.ease,
+ ),
+ ),
+ child: widget.child,
+ ),
+ group.placeHolderAt == PlaceHolderAt.right
+ ? TweenAnimationBuilder(
+ duration: const Duration(milliseconds: 700),
+ curve: Curves.easeOutSine,
+ tween: Tween(begin: 0, end: 1),
+ builder: (context, value, child) {
+ return Opacity(
+ opacity: value,
+ child: child,
+ );
+ },
+ child: Opacity(
+ opacity: 0.6,
+ child: Container(
+ margin: const EdgeInsets.only(right: LIST_GAP),
+ child: draggingState.draggingWidget,
+ ),
+ ),
+ )
+ : Container(),
+ ],
+ );
+ }
+}
diff --git a/lib/src/widgets/board/widgets/new_list.dart b/lib/src/widgets/board/widgets/new_list.dart
new file mode 100644
index 0000000..f2e832b
--- /dev/null
+++ b/lib/src/widgets/board/widgets/new_list.dart
@@ -0,0 +1,77 @@
+// import 'package:flutter/material.dart';
+// import 'package:flutter_riverpod/flutter_riverpod.dart';
+// import 'package:kanban_board/models/board_list.dart';
+// import 'package:kanban_board/provider/provider_list.dart';
+// import 'package:kanban_board/src/widgets/textfield.dart';
+
+// class NewList extends ConsumerStatefulWidget {
+// const NewList({super.key});
+
+// @override
+// ConsumerState createState() => _NewListState();
+// }
+
+// class _NewListState extends ConsumerState {
+// @override
+// Widget build(BuildContext context) {
+// final boardProv = ref.watch(ProviderList.boardProvider);
+// final boardListProv = ref.watch(ProviderList.boardListProvider);
+// return Container(
+// margin: const EdgeInsets.only(
+// top: 20,
+// left: 30,
+// ),
+// padding: const EdgeInsets.only(
+// bottom: 20,
+// ),
+// width: 300,
+// color: const Color.fromARGB(
+// 255,
+// 247,
+// 248,
+// 252,
+// ),
+// child: Wrap(
+// children: [
+// SizedBox(
+// height: 50,
+// width: 300,
+// child: Row(
+// mainAxisAlignment: MainAxisAlignment.spaceBetween,
+// children: [
+// IconButton(
+// onPressed: () {
+// setState(() {
+// boardListProv.showNewList = false;
+// boardProv.newCardState.textController.clear();
+// });
+// },
+// icon: const Icon(Icons.close),
+// ),
+// IconButton(
+// onPressed: () {
+// setState(() {
+// boardListProv.showNewList = false;
+// boardProv.board.groups.add(BoardList(
+// width: 300,
+// scrollController: ScrollController(),
+// items: [],
+// title: boardProv.newCardState.textController.text,
+// ));
+// boardProv.newCardState.textController.clear();
+// });
+// },
+// icon: const Icon(Icons.done))
+// ],
+// ),
+// ),
+// Container(
+// width: 300,
+// color: Colors.white,
+// margin: const EdgeInsets.only(top: 20, right: 10, left: 10),
+// child: const TField()),
+// ],
+// ),
+// );
+// }
+// }
diff --git a/lib/src/widgets/draggable/draggable_overlay.dart b/lib/src/widgets/draggable/draggable_overlay.dart
new file mode 100644
index 0000000..8aede4b
--- /dev/null
+++ b/lib/src/widgets/draggable/draggable_overlay.dart
@@ -0,0 +1,78 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:kanban_board/src/controllers/board_state_controller.dart';
+import 'package:kanban_board/src/controllers/group_state_controller.dart';
+import 'package:kanban_board/src/controllers/scroll_handler.dart';
+import 'package:kanban_board/src/controllers/states/draggable_state.dart';
+import 'package:kanban_board/src/controllers/states/scroll_state.dart';
+
+/// [DraggableOverlay] is used to show the dragging widget.
+/// It makes use of [ValueListenableBuilder] to listen to the [feedbackOffset] and update its position accordingly.
+class DraggableOverlay extends ConsumerStatefulWidget {
+ const DraggableOverlay(
+ {required this.boardState,
+ required this.groupState,
+ required this.boardScrollController,
+ required this.groupScrollConfig,
+ required this.boardScrollConfig,
+ super.key});
+ final ChangeNotifierProvider boardState;
+ final ChangeNotifierProvider groupState;
+ final ScrollController boardScrollController;
+ final ScrollConfig groupScrollConfig;
+ final ScrollConfig boardScrollConfig;
+
+ @override
+ ConsumerState createState() => _DraggableOverlayState();
+}
+
+class _DraggableOverlayState extends ConsumerState {
+ /// This method is called when the dragging widget position is updated.
+ /// It makes use of [GroupScrollHandler] and [BoardScrollHandler] to check if the group or board should scroll.
+ Future _onDragUpdate() async {
+ final boardState = ref.read(widget.boardState);
+ final draggingState = boardState.draggingState;
+ final groupState = ref.read(widget.groupState);
+
+ if (draggingState.draggableType == DraggableType.none) return;
+
+ /// Check if the group should scroll.
+ await GroupScrollHandler.checkGroupScroll(
+ boardState: boardState,
+ scrollConfig: widget.groupScrollConfig,
+ scrollController:
+ boardState.groups[draggingState.currentGroupIndex].scrollController,
+ isScrolling: groupState.isScrolling,
+ setScrolling: (value) => groupState.isScrolling = value);
+
+ /// Check if the board should scroll.
+ await BoardScrollHandler.checkBoardScroll(
+ boardState: boardState,
+ scrollConfig: widget.boardScrollConfig,
+ scrollController: widget.boardScrollController,
+ isScrolling: boardState.isScrolling,
+ setScrolling: (value) => boardState.isScrolling = value);
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final draggingState =
+ ref.watch(widget.boardState.select((value) => value.draggingState));
+ return ValueListenableBuilder(
+ valueListenable: draggingState.feedbackOffset,
+ builder: (ctx, Offset value, child) {
+ _onDragUpdate();
+ return draggingState.draggableType != DraggableType.none
+ ? Positioned(
+ left: value.dx,
+ top: value.dy,
+ child: Opacity(
+ opacity: 1,
+ child: draggingState.draggingWidget,
+ ),
+ )
+ : Container();
+ },
+ );
+ }
+}
diff --git a/lib/src/widgets/group-item/group_item.dart b/lib/src/widgets/group-item/group_item.dart
new file mode 100644
index 0000000..b2f71a4
--- /dev/null
+++ b/lib/src/widgets/group-item/group_item.dart
@@ -0,0 +1,128 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:kanban_board/src/constants/constants.dart';
+import 'package:kanban_board/src/controllers/board_state_controller.dart';
+import 'package:kanban_board/src/controllers/group_item_state_controller.dart';
+import 'package:kanban_board/src/controllers/group_state_controller.dart';
+import 'package:kanban_board/src/controllers/states/draggable_state.dart';
+
+class GroupItem extends ConsumerStatefulWidget {
+ const GroupItem({
+ required this.itemIndex,
+ required this.groupIndex,
+ required this.boardState,
+ required this.boardGroupState,
+ required this.groupItemState,
+ super.key,
+ });
+ final int itemIndex;
+ final int groupIndex;
+ final ChangeNotifierProvider boardState;
+ final ChangeNotifierProvider boardGroupState;
+ final ChangeNotifierProvider groupItemState;
+ @override
+ ConsumerState createState() => _GroupItemState();
+}
+
+class _GroupItemState extends ConsumerState
+ with TickerProviderStateMixin {
+ late AnimationController? _animationController;
+
+ @override
+ void initState() {
+ final groupItem = ref
+ .read(widget.boardState)
+ .groups[widget.groupIndex]
+ .items[widget.itemIndex];
+
+ groupItem.animationController = AnimationController(
+ vsync: this, duration: const Duration(milliseconds: 500));
+ _animationController = groupItem.animationController;
+
+ super.initState();
+ }
+
+ @override
+ void dispose() {
+ _animationController?.dispose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final itemState = ref.read(widget.groupItemState);
+ final boardState = ref.read(widget.boardState);
+ final draggingState = boardState.draggingState;
+ final groupItem =
+ boardState.groups[widget.groupIndex].items[widget.itemIndex];
+ WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
+ itemState.computeItemPositionSize(
+ groupIndex: widget.groupIndex,
+ itemIndex: widget.itemIndex,
+ context: context,
+ setstate: () => {setState(() {})});
+ });
+ return ValueListenableBuilder(
+ key: boardState.groups[widget.groupIndex].items[widget.itemIndex].key,
+ valueListenable: boardState.draggingState.feedbackOffset,
+ builder: (ctx, offset, child) {
+ if (draggingState.draggableType == DraggableType.item) {
+ final groups = boardState.groups;
+ // item added by system in empty list, its widget/UI should not be manipulated on movements //
+ if (groups[widget.groupIndex].items.isEmpty) return child!;
+
+ // CALCULATE SIZE AND POSITION OF ITEM //
+ if (itemState.calculateSizePosition(
+ groupIndex: widget.groupIndex, itemIndex: widget.itemIndex)) {
+ return child!;
+ }
+
+ // DO NOT COMPARE ANYTHING WITH DRAGGED ITEM, IT WILL CAUSE ERRORS BECUSE ITS HIDDEN //
+ if ((draggingState.dragStartIndex == widget.itemIndex &&
+ draggingState.dragStartGroupIndex == widget.groupIndex)) {
+ return child!;
+ }
+
+ if (itemState.getYAxisCondition(
+ groupIndex: widget.groupIndex, itemIndex: widget.itemIndex)) {
+ itemState.handleSameGroupMove(
+ groupIndex: widget.groupIndex, itemIndex: widget.itemIndex);
+ } else if (itemState.getXAxisCondition(
+ groupIndex: widget.groupIndex, itemIndex: widget.itemIndex)) {
+ itemState.handleDiffGroupMove(
+ groupIndex: widget.groupIndex, itemIndex: widget.itemIndex);
+ }
+ }
+ return child!;
+ },
+ child: GestureDetector(
+ onLongPress: () {
+ itemState.onLongPressItem(
+ groupIndex: widget.groupIndex,
+ itemIndex: widget.itemIndex,
+ context: context,
+ setState: () => {setState(() {})});
+ },
+ child:
+ // if item which is being dragged is same as current item, show feedback container
+ draggingState.draggableType == DraggableType.item &&
+ draggingState.currentIndex == widget.itemIndex &&
+ draggingState.dragStartIndex == widget.itemIndex &&
+ draggingState.currentGroupIndex == widget.groupIndex &&
+ draggingState.dragStartGroupIndex == widget.groupIndex
+ ? Container(
+ margin: const EdgeInsets.only(bottom: CARD_GAP),
+ width: draggingState.feedbackSize.width,
+ height: draggingState.feedbackSize.height,
+ child: boardState.itemGhost)
+ : itemState.isCurrentElementDragged(
+ groupIndex: widget.groupIndex,
+ itemIndex: widget.itemIndex)
+ ? Container()
+ : Container(
+ margin: const EdgeInsets.only(bottom: CARD_GAP),
+ child: groupItem.ghost),
+ ),
+ );
+ }
+}
diff --git a/lib/src/widgets/kanban_gesture_listener.dart b/lib/src/widgets/kanban_gesture_listener.dart
new file mode 100644
index 0000000..c2f10c8
--- /dev/null
+++ b/lib/src/widgets/kanban_gesture_listener.dart
@@ -0,0 +1,72 @@
+import 'package:flutter/material.dart';
+// Riverpod:
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:kanban_board/src/controllers/states/scroll_state.dart';
+import 'package:kanban_board/src/controllers/controllers.dart'
+ show
+ BoardStateController,
+ DraggableType,
+ GroupStateController,
+ GroupItemStateController;
+
+/// [KanbanGestureListener] is used to listen to the pointer events and update the position of dragging-widget accordingly.
+class KanbanGestureListener extends ConsumerStatefulWidget {
+ const KanbanGestureListener({
+ required this.boardStateController,
+ required this.boardgroupController,
+ required this.groupItemController,
+ required this.boardScrollController,
+ required this.groupScrollConfig,
+ required this.boardScrollConfig,
+ this.child,
+ super.key,
+ });
+ final ChangeNotifierProvider boardStateController;
+ final ChangeNotifierProvider boardgroupController;
+ final ChangeNotifierProvider groupItemController;
+ final ScrollController boardScrollController;
+ final ScrollConfig groupScrollConfig;
+ final ScrollConfig boardScrollConfig;
+ final Widget? child;
+ @override
+ ConsumerState createState() =>
+ _KanbanGestureListenerState();
+}
+
+class _KanbanGestureListenerState extends ConsumerState {
+ /// This method is called when the pointer is up.
+ void _onPointerUp(PointerUpEvent event) {
+ final boardState = ref.read(widget.boardStateController);
+
+ final draggingState = boardState.draggingState;
+
+ if (draggingState.draggableType == DraggableType.item) {
+ ref.read(widget.groupItemController).onDragEnd();
+ } else if (draggingState.draggableType == DraggableType.group) {
+ ref.read(widget.boardgroupController).onDragEnd();
+ }
+ }
+
+ /// This method is called when the pointer moves.
+ void _onPointerMove(PointerMoveEvent event) {
+ final boardState = ref.read(widget.boardStateController);
+
+ final previousOffset = boardState.draggingState.feedbackOffset.value;
+ if ((event.position.dx - previousOffset.dx).abs() >= 100 ||
+ (event.position.dy - previousOffset.dy).abs() >= 100) {
+ boardState.draggingState.axisDirection =
+ event.delta.dy > 0 ? AxisDirection.down : AxisDirection.up;
+ boardState.draggingState.feedbackOffset.value = Offset(
+ event.delta.dx + previousOffset.dx,
+ event.delta.dy + previousOffset.dy);
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Listener(
+ onPointerUp: _onPointerUp,
+ onPointerMove: _onPointerMove,
+ child: widget.child);
+ }
+}
diff --git a/lib/src/widgets/textfield.dart b/lib/src/widgets/textfield.dart
new file mode 100644
index 0000000..2609628
--- /dev/null
+++ b/lib/src/widgets/textfield.dart
@@ -0,0 +1,35 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+
+class CardWithTextField extends ConsumerStatefulWidget {
+ const CardWithTextField({
+ this.onCompleteEditing,
+ super.key,
+ });
+ final void Function(String)? onCompleteEditing;
+ @override
+ ConsumerState createState() => _TFieldState();
+}
+
+class _TFieldState extends ConsumerState {
+ var node = FocusNode();
+ final _controller = TextEditingController();
+ @override
+ Widget build(BuildContext context) {
+ return TextFormField(
+ decoration: const InputDecoration(
+ hintText: 'Enter your text here',
+ enabledBorder: OutlineInputBorder(borderSide: BorderSide.none),
+ focusedBorder: OutlineInputBorder(borderSide: BorderSide.none),
+ disabledBorder: OutlineInputBorder(borderSide: BorderSide.none),
+ ),
+ autofocus: true,
+ enabled: true,
+ maxLines: 3,
+ keyboardType: TextInputType.text,
+ controller: _controller,
+ focusNode: node,
+ onFieldSubmitted: (text) => widget.onCompleteEditing?.call(text),
+ );
+ }
+}
diff --git a/lib/src/widgets/widgets.dart b/lib/src/widgets/widgets.dart
new file mode 100644
index 0000000..0099ea8
--- /dev/null
+++ b/lib/src/widgets/widgets.dart
@@ -0,0 +1,12 @@
+library widgets;
+
+// Board Group
+export './board-group/board_groups_root.dart';
+export './board-group/board_group.dart';
+export './board-group/group_placeholder_wrapper.dart';
+// Board Item
+export './group-item/group_item.dart';
+// Draggable
+export './draggable/draggable_overlay.dart';
+// Gesture Listener
+export 'kanban_gesture_listener.dart';
diff --git a/pubspec.lock b/pubspec.lock
index f4878f5..49e6054 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -37,10 +37,18 @@ packages:
dependency: transitive
description:
name: collection
- sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
+ sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev"
source: hosted
- version: "1.17.1"
+ version: "1.18.0"
+ crypto:
+ dependency: transitive
+ description:
+ name: crypto
+ sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.5"
dotted_border:
dependency: "direct main"
description:
@@ -57,6 +65,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.1"
+ fixnum:
+ dependency: transitive
+ description:
+ name: fixnum
+ sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.0"
flutter:
dependency: "direct main"
description: flutter
@@ -83,14 +99,30 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
- js:
+ leak_tracker:
dependency: transitive
description:
- name: js
- sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
+ name: leak_tracker
+ sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
url: "https://pub.dev"
source: hosted
- version: "0.6.7"
+ version: "10.0.5"
+ leak_tracker_flutter_testing:
+ dependency: transitive
+ description:
+ name: leak_tracker_flutter_testing
+ sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.5"
+ leak_tracker_testing:
+ dependency: transitive
+ description:
+ name: leak_tracker_testing
+ sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.1"
lints:
dependency: transitive
description:
@@ -103,34 +135,34 @@ packages:
dependency: transitive
description:
name: matcher
- sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
+ sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev"
source: hosted
- version: "0.12.15"
+ version: "0.12.16+1"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
- sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
+ sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
- version: "0.2.0"
+ version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
- sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
+ sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
url: "https://pub.dev"
source: hosted
- version: "1.9.1"
+ version: "1.15.0"
path:
dependency: transitive
description:
name: path
- sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
+ sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev"
source: hosted
- version: "1.8.3"
+ version: "1.9.0"
path_drawing:
dependency: transitive
description:
@@ -164,18 +196,26 @@ packages:
dependency: transitive
description:
name: source_span
- sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
+ sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev"
source: hosted
- version: "1.9.1"
+ version: "1.10.0"
+ sprintf:
+ dependency: transitive
+ description:
+ name: sprintf
+ sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
+ url: "https://pub.dev"
+ source: hosted
+ version: "7.0.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
- sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
+ sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev"
source: hosted
- version: "1.11.0"
+ version: "1.11.1"
state_notifier:
dependency: transitive
description:
@@ -188,10 +228,10 @@ packages:
dependency: transitive
description:
name: stream_channel
- sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
+ sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev"
source: hosted
- version: "2.1.1"
+ version: "2.1.2"
string_scanner:
dependency: transitive
description:
@@ -212,10 +252,26 @@ packages:
dependency: transitive
description:
name: test_api
- sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
+ sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.7.2"
+ typed_data:
+ dependency: transitive
+ description:
+ name: typed_data
+ sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
url: "https://pub.dev"
source: hosted
- version: "0.5.1"
+ version: "1.3.2"
+ uuid:
+ dependency: "direct main"
+ description:
+ name: uuid
+ sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.5.1"
vector_math:
dependency: transitive
description:
@@ -224,6 +280,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.4"
+ vm_service:
+ dependency: transitive
+ description:
+ name: vm_service
+ sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
+ url: "https://pub.dev"
+ source: hosted
+ version: "14.2.5"
sdks:
- dart: ">=3.0.0 <4.0.0"
- flutter: ">=3.0.0"
+ dart: ">=3.4.0 <4.0.0"
+ flutter: ">=3.18.0-18.0.pre.54"
diff --git a/pubspec.yaml b/pubspec.yaml
index a72d520..0573e26 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -12,6 +12,7 @@ dependencies:
sdk: flutter
flutter_riverpod: ^2.3.6
dotted_border: ^2.1.0
+ uuid: ^4.5.1
dev_dependencies:
flutter_test: