mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: support link/number/multiselect/checkbox board cell
This commit is contained in:
parent
9d72e36c19
commit
2282aa948e
@ -1,5 +1,9 @@
|
||||
import 'package:app_flowy/plugins/board/application/card/board_checkbox_cell_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class BoardCheckboxCell extends StatefulWidget {
|
||||
final GridCellControllerBuilder cellControllerBuilder;
|
||||
@ -14,8 +18,42 @@ class BoardCheckboxCell extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _BoardCheckboxCellState extends State<BoardCheckboxCell> {
|
||||
late BoardCheckboxCellBloc _cellBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as GridCheckboxCellController;
|
||||
_cellBloc = BoardCheckboxCellBloc(cellController: cellController);
|
||||
_cellBloc.add(const BoardCheckboxCellEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container();
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<BoardCheckboxCellBloc, BoardCheckboxCellState>(
|
||||
builder: (context, state) {
|
||||
final icon = state.isSelected
|
||||
? svgWidget('editor/editor_check')
|
||||
: svgWidget('editor/editor_uncheck');
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FlowyIconButton(
|
||||
iconPadding: EdgeInsets.zero,
|
||||
icon: icon,
|
||||
width: 20,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
_cellBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -38,14 +38,11 @@ class _BoardDateCellState extends State<BoardDateCell> {
|
||||
if (state.dateStr.isEmpty) {
|
||||
return const SizedBox();
|
||||
} else {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FlowyText.regular(
|
||||
state.dateStr,
|
||||
fontSize: 14,
|
||||
),
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FlowyText.regular(
|
||||
state.dateStr,
|
||||
fontSize: 14,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -38,14 +38,11 @@ class _BoardNumberCellState extends State<BoardNumberCell> {
|
||||
if (state.content.isEmpty) {
|
||||
return const SizedBox();
|
||||
} else {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FlowyText.regular(
|
||||
state.content,
|
||||
fontSize: 14,
|
||||
),
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FlowyText.regular(
|
||||
state.content,
|
||||
fontSize: 14,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -40,12 +40,9 @@ class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
|
||||
option: option,
|
||||
))
|
||||
.toList();
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Wrap(children: children, spacing: 4, runSpacing: 2),
|
||||
),
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Wrap(children: children, spacing: 4, runSpacing: 2),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
@ -35,14 +35,11 @@ class _BoardTextCellState extends State<BoardTextCell> {
|
||||
if (state.content.isEmpty) {
|
||||
return const SizedBox();
|
||||
} else {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FlowyText.regular(
|
||||
state.content,
|
||||
fontSize: 14,
|
||||
),
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FlowyText.regular(
|
||||
state.content,
|
||||
fontSize: 14,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,8 @@
|
||||
import 'package:app_flowy/plugins/board/application/card/board_url_cell_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class BoardUrlCell extends StatefulWidget {
|
||||
final GridCellControllerBuilder cellControllerBuilder;
|
||||
@ -14,8 +17,48 @@ class BoardUrlCell extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _BoardUrlCellState extends State<BoardUrlCell> {
|
||||
late BoardURLCellBloc _cellBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as GridURLCellController;
|
||||
_cellBloc = BoardURLCellBloc(cellController: cellController);
|
||||
_cellBloc.add(const BoardURLCellEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container();
|
||||
final theme = context.watch<AppTheme>();
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<BoardURLCellBloc, BoardURLCellState>(
|
||||
builder: (context, state) {
|
||||
final richText = RichText(
|
||||
textAlign: TextAlign.left,
|
||||
text: TextSpan(
|
||||
text: state.content,
|
||||
style: TextStyle(
|
||||
color: theme.main2,
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: richText,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
_cellBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -40,8 +40,11 @@ class _BoardCardState extends State<BoardCard> {
|
||||
value: _cardBloc,
|
||||
child: BlocBuilder<BoardCardBloc, BoardCardState>(
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
children: _makeCells(context, state.gridCellMap),
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
children: _makeCells(context, state.gridCellMap),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
@ -53,7 +56,10 @@ class _BoardCardState extends State<BoardCard> {
|
||||
(cellId) {
|
||||
final child = widget.cellBuilder.buildCell(cellId);
|
||||
|
||||
return child;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
).toList();
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
part of 'cell_service.dart';
|
||||
|
||||
typedef GridCellController = IGridCellController<String, String>;
|
||||
typedef GridCheckboxCellController = IGridCellController<String, String>;
|
||||
typedef GridNumberCellController = IGridCellController<String, String>;
|
||||
typedef GridSelectOptionCellController
|
||||
= IGridCellController<SelectOptionCellDataPB, String>;
|
||||
|
@ -6,7 +6,7 @@ import 'cell_service/cell_service.dart';
|
||||
part 'checkbox_cell_bloc.freezed.dart';
|
||||
|
||||
class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
|
||||
final GridCellController cellController;
|
||||
final GridCheckboxCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
|
||||
CheckboxCellBloc({
|
||||
|
@ -22,7 +22,8 @@ class _CheckboxCellState extends GridCellState<GridCheckboxCell> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final cellController = widget.cellControllerBuilder.build();
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as GridCheckboxCellController;
|
||||
_cellBloc = getIt<CheckboxCellBloc>(param1: cellController)
|
||||
..add(const CheckboxCellEvent.initial());
|
||||
super.initState();
|
||||
|
@ -90,9 +90,9 @@ class _GridURLCellState extends GridCellState<GridURLCell> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final cellContext =
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as GridURLCellController;
|
||||
_cellBloc = URLCellBloc(cellController: cellContext);
|
||||
_cellBloc = URLCellBloc(cellController: cellController);
|
||||
_cellBloc.add(const URLCellEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
|
@ -202,7 +202,7 @@ class _BoardContentState extends State<BoardContent> {
|
||||
return ChangeNotifierProvider.value(
|
||||
key: ValueKey(columnData.id),
|
||||
value: widget.dataController.columnController(columnData.id),
|
||||
child: Consumer<BoardColumnDataController>(
|
||||
child: Consumer<AFBoardColumnDataController>(
|
||||
builder: (context, value, child) {
|
||||
return ConstrainedBox(
|
||||
constraints: widget.columnConstraints,
|
||||
|
@ -12,7 +12,7 @@ abstract class AFColumnItem extends ReoderFlexItem {
|
||||
String toString() => id;
|
||||
}
|
||||
|
||||
/// [BoardColumnDataController] is used to handle the [AFBoardColumnData].
|
||||
/// [AFBoardColumnDataController] is used to handle the [AFBoardColumnData].
|
||||
/// * Remove an item by calling [removeAt] method.
|
||||
/// * Move item to another position by calling [move] method.
|
||||
/// * Insert item to index by calling [insert] method
|
||||
@ -20,10 +20,10 @@ abstract class AFColumnItem extends ReoderFlexItem {
|
||||
///
|
||||
/// All there operations will notify listeners by default.
|
||||
///
|
||||
class BoardColumnDataController extends ChangeNotifier with EquatableMixin {
|
||||
class AFBoardColumnDataController extends ChangeNotifier with EquatableMixin {
|
||||
final AFBoardColumnData columnData;
|
||||
|
||||
BoardColumnDataController({
|
||||
AFBoardColumnDataController({
|
||||
required this.columnData,
|
||||
});
|
||||
|
||||
@ -42,7 +42,8 @@ class BoardColumnDataController extends ChangeNotifier with EquatableMixin {
|
||||
AFColumnItem removeAt(int index, {bool notify = true}) {
|
||||
assert(index >= 0);
|
||||
|
||||
Log.debug('[$BoardColumnDataController] $columnData remove item at $index');
|
||||
Log.debug(
|
||||
'[$AFBoardColumnDataController] $columnData remove item at $index');
|
||||
final item = columnData._items.removeAt(index);
|
||||
if (notify) {
|
||||
notifyListeners();
|
||||
@ -64,7 +65,7 @@ class BoardColumnDataController extends ChangeNotifier with EquatableMixin {
|
||||
return false;
|
||||
}
|
||||
Log.debug(
|
||||
'[$BoardColumnDataController] $columnData move item from $fromIndex to $toIndex');
|
||||
'[$AFBoardColumnDataController] $columnData move item from $fromIndex to $toIndex');
|
||||
final item = columnData._items.removeAt(fromIndex);
|
||||
columnData._items.insert(toIndex, item);
|
||||
notifyListeners();
|
||||
@ -78,7 +79,7 @@ class BoardColumnDataController extends ChangeNotifier with EquatableMixin {
|
||||
bool insert(int index, AFColumnItem item, {bool notify = true}) {
|
||||
assert(index >= 0);
|
||||
Log.debug(
|
||||
'[$BoardColumnDataController] $columnData insert $item at $index');
|
||||
'[$AFBoardColumnDataController] $columnData insert $item at $index');
|
||||
|
||||
if (columnData._items.length > index) {
|
||||
columnData._items.insert(index, item);
|
||||
@ -100,12 +101,12 @@ class BoardColumnDataController extends ChangeNotifier with EquatableMixin {
|
||||
void replace(int index, AFColumnItem newItem) {
|
||||
if (columnData._items.isEmpty) {
|
||||
columnData._items.add(newItem);
|
||||
Log.debug('[$BoardColumnDataController] $columnData add $newItem');
|
||||
Log.debug('[$AFBoardColumnDataController] $columnData add $newItem');
|
||||
} else {
|
||||
final removedItem = columnData._items.removeAt(index);
|
||||
columnData._items.insert(index, newItem);
|
||||
Log.debug(
|
||||
'[$BoardColumnDataController] $columnData replace $removedItem with $newItem at $index');
|
||||
'[$AFBoardColumnDataController] $columnData replace $removedItem with $newItem at $index');
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
|
@ -35,7 +35,7 @@ class AFBoardDataController extends ChangeNotifier
|
||||
List<String> get columnIds =>
|
||||
_columnDatas.map((columnData) => columnData.id).toList();
|
||||
|
||||
final LinkedHashMap<String, BoardColumnDataController> _columnControllers =
|
||||
final LinkedHashMap<String, AFBoardColumnDataController> _columnControllers =
|
||||
LinkedHashMap();
|
||||
|
||||
AFBoardDataController({
|
||||
@ -47,7 +47,7 @@ class AFBoardDataController extends ChangeNotifier
|
||||
void addColumn(AFBoardColumnData columnData, {bool notify = true}) {
|
||||
if (_columnControllers[columnData.id] != null) return;
|
||||
|
||||
final controller = BoardColumnDataController(columnData: columnData);
|
||||
final controller = AFBoardColumnDataController(columnData: columnData);
|
||||
_columnDatas.add(columnData);
|
||||
_columnControllers[columnData.id] = controller;
|
||||
if (notify) notifyListeners();
|
||||
@ -84,11 +84,11 @@ class AFBoardDataController extends ChangeNotifier
|
||||
if (columnIds.isNotEmpty && notify) notifyListeners();
|
||||
}
|
||||
|
||||
BoardColumnDataController columnController(String columnId) {
|
||||
AFBoardColumnDataController columnController(String columnId) {
|
||||
return _columnControllers[columnId]!;
|
||||
}
|
||||
|
||||
BoardColumnDataController? getColumnController(String columnId) {
|
||||
AFBoardColumnDataController? getColumnController(String columnId) {
|
||||
final columnController = _columnControllers[columnId];
|
||||
if (columnController == null) {
|
||||
Log.warn('Column:[$columnId] \'s controller is not exist');
|
||||
@ -153,7 +153,7 @@ class AFBoardDataController extends ChangeNotifier
|
||||
}
|
||||
|
||||
@override
|
||||
BoardColumnDataController? controller(String columnId) {
|
||||
AFBoardColumnDataController? controller(String columnId) {
|
||||
return _columnControllers[columnId];
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ import '../reorder_flex/drag_target_inteceptor.dart';
|
||||
import 'phantom_state.dart';
|
||||
|
||||
abstract class BoardPhantomControllerDelegate {
|
||||
BoardColumnDataController? controller(String columnId);
|
||||
AFBoardColumnDataController? controller(String columnId);
|
||||
|
||||
bool removePhantom(String columnId);
|
||||
|
||||
|
@ -97,7 +97,7 @@ pub struct MultiSelectTypeOptionBuilder(MultiSelectTypeOptionPB);
|
||||
impl_into_box_type_option_builder!(MultiSelectTypeOptionBuilder);
|
||||
impl_builder_from_json_str_and_from_bytes!(MultiSelectTypeOptionBuilder, MultiSelectTypeOptionPB);
|
||||
impl MultiSelectTypeOptionBuilder {
|
||||
pub fn option(mut self, opt: SelectOptionPB) -> Self {
|
||||
pub fn add_option(mut self, opt: SelectOptionPB) -> Self {
|
||||
self.0.options.push(opt);
|
||||
self
|
||||
}
|
||||
@ -127,9 +127,9 @@ mod tests {
|
||||
let facebook_option = SelectOptionPB::new("Facebook");
|
||||
let twitter_option = SelectOptionPB::new("Twitter");
|
||||
let multi_select = MultiSelectTypeOptionBuilder::default()
|
||||
.option(google_option.clone())
|
||||
.option(facebook_option.clone())
|
||||
.option(twitter_option);
|
||||
.add_option(google_option.clone())
|
||||
.add_option(facebook_option.clone())
|
||||
.add_option(twitter_option);
|
||||
|
||||
let field_rev = FieldBuilder::new(multi_select)
|
||||
.name("Platform")
|
||||
|
@ -156,8 +156,8 @@ mod tests {
|
||||
let ids = vec![google_option.id.clone(), facebook_option.id.clone()].join(SELECTION_IDS_SEPARATOR);
|
||||
let cell_data_changeset = SelectOptionCellChangeset::from_insert(&ids).to_str();
|
||||
let multi_select = MultiSelectTypeOptionBuilder::default()
|
||||
.option(google_option.clone())
|
||||
.option(facebook_option.clone());
|
||||
.add_option(google_option.clone())
|
||||
.add_option(facebook_option.clone());
|
||||
let multi_select_field_rev = FieldBuilder::new(multi_select).build();
|
||||
let multi_type_option = MultiSelectTypeOptionPB::from(&multi_select_field_rev);
|
||||
let cell_data = multi_type_option
|
||||
|
@ -69,12 +69,66 @@ pub fn make_default_board() -> BuildGridContext {
|
||||
let single_select_field_id = single_select_field.id.clone();
|
||||
grid_builder.add_field(single_select_field);
|
||||
|
||||
// MultiSelect
|
||||
let apple_option = SelectOptionPB::new("Apple");
|
||||
let banana_option = SelectOptionPB::new("Banana");
|
||||
let pear_option = SelectOptionPB::new("Pear");
|
||||
let multi_select_type_option = MultiSelectTypeOptionBuilder::default()
|
||||
.add_option(banana_option.clone())
|
||||
.add_option(apple_option.clone())
|
||||
.add_option(pear_option.clone());
|
||||
let multi_select_field = FieldBuilder::new(multi_select_type_option)
|
||||
.name("Fruit")
|
||||
.visibility(true)
|
||||
.build();
|
||||
let multi_select_field_id = multi_select_field.id.clone();
|
||||
grid_builder.add_field(multi_select_field);
|
||||
|
||||
// Number
|
||||
let number_type_option = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD);
|
||||
let number_field = FieldBuilder::new(number_type_option)
|
||||
.name("Price")
|
||||
.visibility(true)
|
||||
.build();
|
||||
let number_field_id = number_field.id.clone();
|
||||
grid_builder.add_field(number_field);
|
||||
|
||||
// Checkbox
|
||||
let checkbox_type_option = CheckboxTypeOptionBuilder::default();
|
||||
let checkbox_field = FieldBuilder::new(checkbox_type_option).name("Reimbursement").build();
|
||||
let checkbox_field_id = checkbox_field.id.clone();
|
||||
grid_builder.add_field(checkbox_field);
|
||||
|
||||
// Url
|
||||
let url_type_option = URLTypeOptionBuilder::default();
|
||||
let url_field = FieldBuilder::new(url_type_option).name("Shop Link").build();
|
||||
let url_field_id = url_field.id.clone();
|
||||
grid_builder.add_field(url_field);
|
||||
|
||||
// Insert rows
|
||||
for i in 0..10 {
|
||||
// insert single select
|
||||
let mut row_builder = RowRevisionBuilder::new(grid_builder.block_id(), grid_builder.field_revs());
|
||||
row_builder.insert_select_option_cell(&single_select_field_id, not_started_option.id.clone());
|
||||
// insert multi select
|
||||
row_builder.insert_select_option_cell(&multi_select_field_id, apple_option.id.clone());
|
||||
row_builder.insert_select_option_cell(&multi_select_field_id, banana_option.id.clone());
|
||||
// insert text
|
||||
row_builder.insert_cell(&text_field_id, format!("Card {}", i));
|
||||
// insert date
|
||||
row_builder.insert_date_cell(&date_field_id, timestamp);
|
||||
// number
|
||||
row_builder.insert_cell(&number_field_id, format!("{}", i));
|
||||
// checkbox
|
||||
let is_check = if i % 2 == 0 {
|
||||
CHECK.to_string()
|
||||
} else {
|
||||
UNCHECK.to_string()
|
||||
};
|
||||
row_builder.insert_cell(&checkbox_field_id, is_check);
|
||||
// url
|
||||
row_builder.insert_cell(&url_field_id, "https://appflowy.io".to_string());
|
||||
|
||||
let row = row_builder.build();
|
||||
grid_builder.add_row(row);
|
||||
}
|
||||
|
@ -147,9 +147,9 @@ fn make_test_grid() -> BuildGridContext {
|
||||
FieldType::MultiSelect => {
|
||||
// MultiSelect
|
||||
let multi_select = MultiSelectTypeOptionBuilder::default()
|
||||
.option(SelectOptionPB::new(GOOGLE))
|
||||
.option(SelectOptionPB::new(FACEBOOK))
|
||||
.option(SelectOptionPB::new(TWITTER));
|
||||
.add_option(SelectOptionPB::new(GOOGLE))
|
||||
.add_option(SelectOptionPB::new(FACEBOOK))
|
||||
.add_option(SelectOptionPB::new(TWITTER));
|
||||
let multi_select_field = FieldBuilder::new(multi_select)
|
||||
.name("Platform")
|
||||
.visibility(true)
|
||||
|
Loading…
Reference in New Issue
Block a user