mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: add card container
This commit is contained in:
parent
2282aa948e
commit
28e77ae68c
@ -108,8 +108,8 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
);
|
||||
}
|
||||
|
||||
List<BoardColumnItem> _buildRows(List<RowPB> rows) {
|
||||
return rows.map((row) {
|
||||
List<AFColumnItem> _buildRows(List<RowPB> rows) {
|
||||
final items = rows.map((row) {
|
||||
// final rowInfo = RowInfo(
|
||||
// gridId: _dataController.gridId,
|
||||
// blockId: row.blockId,
|
||||
@ -120,6 +120,8 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
// );
|
||||
return BoardColumnItem(row: row);
|
||||
}).toList();
|
||||
|
||||
return <AFColumnItem>[...items];
|
||||
}
|
||||
|
||||
Future<void> _loadGrid(Emitter<BoardState> emit) async {
|
||||
|
@ -0,0 +1,71 @@
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
|
||||
part 'board_checkbox_cell_bloc.freezed.dart';
|
||||
|
||||
class BoardCheckboxCellBloc
|
||||
extends Bloc<BoardCheckboxCellEvent, BoardCheckboxCellState> {
|
||||
final GridCheckboxCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
BoardCheckboxCellBloc({
|
||||
required this.cellController,
|
||||
}) : super(BoardCheckboxCellState.initial(cellController)) {
|
||||
on<BoardCheckboxCellEvent>(
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
initial: () async {
|
||||
_startListening();
|
||||
},
|
||||
didReceiveCellUpdate: (cellData) {
|
||||
emit(state.copyWith(isSelected: _isSelected(cellData)));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_onCellChangedFn != null) {
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onCellChangedFn = cellController.startListening(
|
||||
onCellChanged: ((cellContent) {
|
||||
if (!isClosed) {
|
||||
add(BoardCheckboxCellEvent.didReceiveCellUpdate(cellContent ?? ""));
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardCheckboxCellEvent with _$BoardCheckboxCellEvent {
|
||||
const factory BoardCheckboxCellEvent.initial() = _InitialCell;
|
||||
const factory BoardCheckboxCellEvent.didReceiveCellUpdate(
|
||||
String cellContent) = _DidReceiveCellUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardCheckboxCellState with _$BoardCheckboxCellState {
|
||||
const factory BoardCheckboxCellState({
|
||||
required bool isSelected,
|
||||
}) = _CheckboxCellState;
|
||||
|
||||
factory BoardCheckboxCellState.initial(GridCellController context) {
|
||||
return BoardCheckboxCellState(
|
||||
isSelected: _isSelected(context.getCellData()));
|
||||
}
|
||||
}
|
||||
|
||||
bool _isSelected(String? cellData) {
|
||||
return cellData == "Yes";
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option_entities.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
|
||||
part 'board_url_cell_bloc.freezed.dart';
|
||||
|
||||
class BoardURLCellBloc extends Bloc<BoardURLCellEvent, BoardURLCellState> {
|
||||
final GridURLCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
BoardURLCellBloc({
|
||||
required this.cellController,
|
||||
}) : super(BoardURLCellState.initial(cellController)) {
|
||||
on<BoardURLCellEvent>(
|
||||
(event, emit) async {
|
||||
event.when(
|
||||
initial: () {
|
||||
_startListening();
|
||||
},
|
||||
didReceiveCellUpdate: (cellData) {
|
||||
emit(state.copyWith(
|
||||
content: cellData?.content ?? "",
|
||||
url: cellData?.url ?? "",
|
||||
));
|
||||
},
|
||||
updateURL: (String url) {
|
||||
cellController.saveCellData(url, deduplicate: true);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_onCellChangedFn != null) {
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onCellChangedFn = cellController.startListening(
|
||||
onCellChanged: ((cellData) {
|
||||
if (!isClosed) {
|
||||
add(BoardURLCellEvent.didReceiveCellUpdate(cellData));
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardURLCellEvent with _$BoardURLCellEvent {
|
||||
const factory BoardURLCellEvent.initial() = _InitialCell;
|
||||
const factory BoardURLCellEvent.updateURL(String url) = _UpdateURL;
|
||||
const factory BoardURLCellEvent.didReceiveCellUpdate(URLCellDataPB? cell) =
|
||||
_DidReceiveCellUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardURLCellState with _$BoardURLCellState {
|
||||
const factory BoardURLCellState({
|
||||
required String content,
|
||||
required String url,
|
||||
}) = _BoardURLCellState;
|
||||
|
||||
factory BoardURLCellState.initial(GridURLCellController context) {
|
||||
final cellData = context.getCellData();
|
||||
return BoardURLCellState(
|
||||
content: cellData?.content ?? "",
|
||||
url: cellData?.url ?? "",
|
||||
);
|
||||
}
|
||||
}
|
@ -1,10 +1,12 @@
|
||||
import 'package:app_flowy/plugins/board/application/card/card_bloc.dart';
|
||||
import 'package:app_flowy/plugins/board/application/card/card_data_controller.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import 'card_cell_builder.dart';
|
||||
import 'card_container.dart';
|
||||
|
||||
class BoardCard extends StatefulWidget {
|
||||
final String gridId;
|
||||
@ -40,8 +42,10 @@ class _BoardCardState extends State<BoardCard> {
|
||||
value: _cardBloc,
|
||||
child: BlocBuilder<BoardCardBloc, BoardCardState>(
|
||||
builder: (context, state) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
return BoardCardContainer(
|
||||
accessoryBuilder: (context) {
|
||||
return [const _CardMoreOption()];
|
||||
},
|
||||
child: Column(
|
||||
children: _makeCells(context, state.gridCellMap),
|
||||
),
|
||||
@ -64,3 +68,17 @@ class _BoardCardState extends State<BoardCard> {
|
||||
).toList();
|
||||
}
|
||||
}
|
||||
|
||||
class _CardMoreOption extends StatelessWidget with CardAccessory {
|
||||
const _CardMoreOption({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return svgWidget('home/details', color: context.read<AppTheme>().iconColor);
|
||||
}
|
||||
|
||||
@override
|
||||
void onTap(BuildContext context) {
|
||||
print('show options');
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,133 @@
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
class BoardCardContainer extends StatelessWidget {
|
||||
final Widget child;
|
||||
final CardAccessoryBuilder? accessoryBuilder;
|
||||
const BoardCardContainer({
|
||||
required this.child,
|
||||
this.accessoryBuilder,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProvider(
|
||||
create: (_) => _CardContainerNotifier(),
|
||||
child: Consumer<_CardContainerNotifier>(
|
||||
builder: (context, notifier, _) {
|
||||
Widget container = Center(child: child);
|
||||
if (accessoryBuilder != null) {
|
||||
final accessories = accessoryBuilder!(context);
|
||||
if (accessories.isNotEmpty) {
|
||||
container = _CardEnterRegion(
|
||||
child: container,
|
||||
accessories: accessories,
|
||||
);
|
||||
}
|
||||
}
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: container,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class CardAccessory implements Widget {
|
||||
void onTap(BuildContext context);
|
||||
}
|
||||
|
||||
typedef CardAccessoryBuilder = List<CardAccessory> Function(
|
||||
BuildContext buildContext,
|
||||
);
|
||||
|
||||
class CardAccessoryContainer extends StatelessWidget {
|
||||
final List<CardAccessory> accessories;
|
||||
const CardAccessoryContainer({required this.accessories, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.read<AppTheme>();
|
||||
final children = accessories.map((accessory) {
|
||||
final hover = FlowyHover(
|
||||
style: HoverStyle(
|
||||
hoverColor: theme.hover,
|
||||
backgroundColor: theme.surface,
|
||||
),
|
||||
builder: (_, onHover) => Container(
|
||||
width: 26,
|
||||
height: 26,
|
||||
padding: const EdgeInsets.all(3),
|
||||
child: accessory,
|
||||
),
|
||||
);
|
||||
return GestureDetector(
|
||||
child: hover,
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => accessory.onTap(context),
|
||||
);
|
||||
}).toList();
|
||||
|
||||
return Wrap(children: children, spacing: 6);
|
||||
}
|
||||
}
|
||||
|
||||
class _CardEnterRegion extends StatelessWidget {
|
||||
final Widget child;
|
||||
final List<CardAccessory> accessories;
|
||||
const _CardEnterRegion(
|
||||
{required this.child, required this.accessories, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<_CardContainerNotifier, bool>(
|
||||
selector: (context, notifier) => notifier.onEnter,
|
||||
builder: (context, onEnter, _) {
|
||||
List<Widget> children = [child];
|
||||
if (onEnter) {
|
||||
children.add(CardAccessoryContainer(accessories: accessories)
|
||||
.positioned(right: 0));
|
||||
}
|
||||
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
onEnter: (p) =>
|
||||
Provider.of<_CardContainerNotifier>(context, listen: false)
|
||||
.onEnter = true,
|
||||
onExit: (p) =>
|
||||
Provider.of<_CardContainerNotifier>(context, listen: false)
|
||||
.onEnter = false,
|
||||
child: IntrinsicHeight(
|
||||
child: Stack(
|
||||
alignment: AlignmentDirectional.center,
|
||||
fit: StackFit.expand,
|
||||
children: children,
|
||||
)),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CardContainerNotifier extends ChangeNotifier {
|
||||
bool _onEnter = false;
|
||||
|
||||
_CardContainerNotifier();
|
||||
|
||||
set onEnter(bool value) {
|
||||
if (_onEnter != value) {
|
||||
_onEnter = value;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
bool get onEnter => _onEnter;
|
||||
}
|
@ -8,6 +8,8 @@ import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
||||
import 'cell_builder.dart';
|
||||
|
||||
class GridCellAccessoryBuildContext {
|
||||
final BuildContext anchorContext;
|
||||
final bool isCellEditing;
|
||||
@ -57,18 +59,6 @@ class PrimaryCellAccessory extends StatelessWidget with GridCellAccessory {
|
||||
bool enable() => !isCellEditing;
|
||||
}
|
||||
|
||||
typedef AccessoryBuilder = List<GridCellAccessory> Function(
|
||||
GridCellAccessoryBuildContext buildContext);
|
||||
|
||||
abstract class CellAccessory extends Widget {
|
||||
const CellAccessory({Key? key}) : super(key: key);
|
||||
|
||||
// The hover will show if the isHover's value is true
|
||||
ValueNotifier<bool>? get onAccessoryHover;
|
||||
|
||||
AccessoryBuilder? get accessoryBuilder;
|
||||
}
|
||||
|
||||
class AccessoryHover extends StatefulWidget {
|
||||
final CellAccessory child;
|
||||
final EdgeInsets contentPadding;
|
||||
|
@ -94,6 +94,18 @@ abstract class CellEditable {
|
||||
ValueNotifier<bool> get onCellEditing;
|
||||
}
|
||||
|
||||
typedef AccessoryBuilder = List<GridCellAccessory> Function(
|
||||
GridCellAccessoryBuildContext buildContext);
|
||||
|
||||
abstract class CellAccessory extends Widget {
|
||||
const CellAccessory({Key? key}) : super(key: key);
|
||||
|
||||
// The hover will show if the isHover's value is true
|
||||
ValueNotifier<bool>? get onAccessoryHover;
|
||||
|
||||
AccessoryBuilder? get accessoryBuilder;
|
||||
}
|
||||
|
||||
abstract class GridCellWidget extends StatefulWidget
|
||||
implements CellAccessory, CellEditable, CellShortcuts {
|
||||
GridCellWidget({Key? key}) : super(key: key) {
|
||||
|
@ -25,24 +25,28 @@ class CellContainer extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProxyProvider<RegionStateNotifier,
|
||||
CellContainerNotifier>(
|
||||
create: (_) => CellContainerNotifier(child),
|
||||
_CellContainerNotifier>(
|
||||
create: (_) => _CellContainerNotifier(child),
|
||||
update: (_, rowStateNotifier, cellStateNotifier) =>
|
||||
cellStateNotifier!..onEnter = rowStateNotifier.onEnter,
|
||||
child: Selector<CellContainerNotifier, bool>(
|
||||
child: Selector<_CellContainerNotifier, bool>(
|
||||
selector: (context, notifier) => notifier.isFocus,
|
||||
builder: (context, isFocus, _) {
|
||||
Widget container = Center(child: GridCellShortcuts(child: child));
|
||||
|
||||
if (accessoryBuilder != null) {
|
||||
final accessories = accessoryBuilder!(GridCellAccessoryBuildContext(
|
||||
anchorContext: context,
|
||||
isCellEditing: isFocus,
|
||||
));
|
||||
final accessories = accessoryBuilder!(
|
||||
GridCellAccessoryBuildContext(
|
||||
anchorContext: context,
|
||||
isCellEditing: isFocus,
|
||||
),
|
||||
);
|
||||
|
||||
if (accessories.isNotEmpty) {
|
||||
container =
|
||||
CellEnterRegion(child: container, accessories: accessories);
|
||||
container = _GridCellEnterRegion(
|
||||
child: container,
|
||||
accessories: accessories,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,16 +78,16 @@ class CellContainer extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class CellEnterRegion extends StatelessWidget {
|
||||
class _GridCellEnterRegion extends StatelessWidget {
|
||||
final Widget child;
|
||||
final List<GridCellAccessory> accessories;
|
||||
const CellEnterRegion(
|
||||
const _GridCellEnterRegion(
|
||||
{required this.child, required this.accessories, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<CellContainerNotifier, bool>(
|
||||
return Selector<_CellContainerNotifier, bool>(
|
||||
selector: (context, notifier) => notifier.onEnter,
|
||||
builder: (context, onEnter, _) {
|
||||
List<Widget> children = [child];
|
||||
@ -95,10 +99,10 @@ class CellEnterRegion extends StatelessWidget {
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
onEnter: (p) =>
|
||||
Provider.of<CellContainerNotifier>(context, listen: false)
|
||||
Provider.of<_CellContainerNotifier>(context, listen: false)
|
||||
.onEnter = true,
|
||||
onExit: (p) =>
|
||||
Provider.of<CellContainerNotifier>(context, listen: false)
|
||||
Provider.of<_CellContainerNotifier>(context, listen: false)
|
||||
.onEnter = false,
|
||||
child: Stack(
|
||||
alignment: AlignmentDirectional.center,
|
||||
@ -111,13 +115,13 @@ class CellEnterRegion extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class CellContainerNotifier extends ChangeNotifier {
|
||||
class _CellContainerNotifier extends ChangeNotifier {
|
||||
final CellEditable cellEditable;
|
||||
VoidCallback? _onCellFocusListener;
|
||||
bool _isFocus = false;
|
||||
bool _onEnter = false;
|
||||
|
||||
CellContainerNotifier(this.cellEditable) {
|
||||
_CellContainerNotifier(this.cellEditable) {
|
||||
_onCellFocusListener = () => isFocus = cellEditable.onCellFocus.value;
|
||||
cellEditable.onCellFocus.addListener(_onCellFocusListener!);
|
||||
}
|
||||
|
@ -181,28 +181,27 @@ class RowContent extends StatelessWidget {
|
||||
return gridCellMap.values.map(
|
||||
(cellId) {
|
||||
final GridCellWidget child = builder.build(cellId);
|
||||
accessoryBuilder(GridCellAccessoryBuildContext buildContext) {
|
||||
final builder = child.accessoryBuilder;
|
||||
List<GridCellAccessory> accessories = [];
|
||||
if (cellId.field.isPrimary) {
|
||||
accessories.add(PrimaryCellAccessory(
|
||||
onTapCallback: onExpand,
|
||||
isCellEditing: buildContext.isCellEditing,
|
||||
));
|
||||
}
|
||||
|
||||
if (builder != null) {
|
||||
accessories.addAll(builder(buildContext));
|
||||
}
|
||||
return accessories;
|
||||
}
|
||||
|
||||
return CellContainer(
|
||||
width: cellId.field.width.toDouble(),
|
||||
child: child,
|
||||
rowStateNotifier:
|
||||
Provider.of<RegionStateNotifier>(context, listen: false),
|
||||
accessoryBuilder: accessoryBuilder,
|
||||
accessoryBuilder: (buildContext) {
|
||||
final builder = child.accessoryBuilder;
|
||||
List<GridCellAccessory> accessories = [];
|
||||
if (cellId.field.isPrimary) {
|
||||
accessories.add(PrimaryCellAccessory(
|
||||
onTapCallback: onExpand,
|
||||
isCellEditing: buildContext.isCellEditing,
|
||||
));
|
||||
}
|
||||
|
||||
if (builder != null) {
|
||||
accessories.addAll(builder(buildContext));
|
||||
}
|
||||
return accessories;
|
||||
},
|
||||
);
|
||||
},
|
||||
).toList();
|
||||
|
@ -23,18 +23,19 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final column1 = AFBoardColumnData(id: "To Do", items: [
|
||||
List<AFColumnItem> a = [
|
||||
TextItem("Card 1"),
|
||||
TextItem("Card 2"),
|
||||
RichTextItem(title: "Card 3", subtitle: 'Aug 1, 2020 4:05 PM'),
|
||||
// RichTextItem(title: "Card 3", subtitle: 'Aug 1, 2020 4:05 PM'),
|
||||
TextItem("Card 4"),
|
||||
]);
|
||||
final column2 = AFBoardColumnData(id: "In Progress", items: [
|
||||
RichTextItem(title: "Card 5", subtitle: 'Aug 1, 2020 4:05 PM'),
|
||||
TextItem("Card 6"),
|
||||
];
|
||||
final column1 = AFBoardColumnData(id: "To Do", items: a);
|
||||
final column2 = AFBoardColumnData(id: "In Progress", items: <AFColumnItem>[
|
||||
// RichTextItem(title: "Card 5", subtitle: 'Aug 1, 2020 4:05 PM'),
|
||||
// TextItem("Card 6"),
|
||||
]);
|
||||
|
||||
final column3 = AFBoardColumnData(id: "Done", items: []);
|
||||
final column3 = AFBoardColumnData(id: "Done", items: <AFColumnItem>[]);
|
||||
|
||||
boardDataController.addColumn(column1);
|
||||
boardDataController.addColumn(column2);
|
||||
|
Loading…
Reference in New Issue
Block a user