chore: support link/number/multiselect/checkbox board cell

This commit is contained in:
appflowy 2022-08-13 10:01:40 +08:00
parent 9d72e36c19
commit 2282aa948e
19 changed files with 195 additions and 63 deletions

View File

@ -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();
}
}

View File

@ -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,
),
);
}

View File

@ -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,
),
);
}

View File

@ -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),
);
},
),

View File

@ -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,
),
);
}

View File

@ -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();
}
}

View File

@ -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();
}

View File

@ -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>;

View File

@ -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({

View File

@ -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();

View File

@ -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();
}

View File

@ -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,

View File

@ -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();

View File

@ -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];
}

View File

@ -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);

View File

@ -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")

View File

@ -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

View File

@ -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);
}

View File

@ -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)