chore: config hover

This commit is contained in:
appflowy 2022-04-21 15:59:56 +08:00
parent eeba6884ce
commit f66adb53c9
21 changed files with 564 additions and 168 deletions

View File

@ -177,7 +177,8 @@
}, },
"row": { "row": {
"duplicate": "Duplicate", "duplicate": "Duplicate",
"delete": "Delete" "delete": "Delete",
"textPlaceholder": "Empty"
}, },
"selectOption": { "selectOption": {
"purpleColor": "Purple", "purpleColor": "Purple",

View File

@ -170,7 +170,6 @@ void _resolveGridDeps(GetIt getIt) {
getIt.registerFactoryParam<TextCellBloc, GridCell, void>( getIt.registerFactoryParam<TextCellBloc, GridCell, void>(
(cellData, _) => TextCellBloc( (cellData, _) => TextCellBloc(
service: CellService(),
cellData: cellData, cellData: cellData,
), ),
); );
@ -189,7 +188,7 @@ void _resolveGridDeps(GetIt getIt) {
getIt.registerFactoryParam<DateCellBloc, GridCell, void>( getIt.registerFactoryParam<DateCellBloc, GridCell, void>(
(cellData, _) => DateCellBloc( (cellData, _) => DateCellBloc(
cellIdentifier: cellData, cellData: cellData,
), ),
); );

View File

@ -11,13 +11,13 @@ part 'checkbox_cell_bloc.freezed.dart';
class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> { class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
final CellService _service; final CellService _service;
final CellListener _listener; final CellListener _cellListener;
CheckboxCellBloc({ CheckboxCellBloc({
required CellService service, required CellService service,
required GridCell cellData, required GridCell cellData,
}) : _service = service, }) : _service = service,
_listener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id), _cellListener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
super(CheckboxCellState.initial(cellData)) { super(CheckboxCellState.initial(cellData)) {
on<CheckboxCellEvent>( on<CheckboxCellEvent>(
(event, emit) async { (event, emit) async {
@ -38,18 +38,18 @@ class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
@override @override
Future<void> close() async { Future<void> close() async {
await _listener.stop(); await _cellListener.stop();
return super.close(); return super.close();
} }
void _startListening() { void _startListening() {
_listener.updateCellNotifier?.addPublishListener((result) { _cellListener.updateCellNotifier?.addPublishListener((result) {
result.fold( result.fold(
(notificationData) async => await _loadCellData(), (notificationData) async => await _loadCellData(),
(err) => Log.error(err), (err) => Log.error(err),
); );
}); });
_listener.start(); _cellListener.start();
} }
Future<void> _loadCellData() async { Future<void> _loadCellData() async {

View File

@ -15,11 +15,11 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
final CellListener _cellListener; final CellListener _cellListener;
final SingleFieldListener _fieldListener; final SingleFieldListener _fieldListener;
DateCellBloc({required GridCell cellIdentifier}) DateCellBloc({required GridCell cellData})
: _service = CellService(), : _service = CellService(),
_cellListener = CellListener(rowId: cellIdentifier.rowId, fieldId: cellIdentifier.field.id), _cellListener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
_fieldListener = SingleFieldListener(fieldId: cellIdentifier.field.id), _fieldListener = SingleFieldListener(fieldId: cellData.field.id),
super(DateCellState.initial(cellIdentifier)) { super(DateCellState.initial(cellData)) {
on<DateCellEvent>( on<DateCellEvent>(
(event, emit) async { (event, emit) async {
event.map( event.map(

View File

@ -1,22 +1,29 @@
import 'package:app_flowy/workspace/application/grid/row/row_service.dart'; import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell;
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async'; import 'dart:async';
import 'cell_listener.dart';
import 'cell_service.dart'; import 'cell_service.dart';
part 'text_cell_bloc.freezed.dart'; part 'text_cell_bloc.freezed.dart';
class TextCellBloc extends Bloc<TextCellEvent, TextCellState> { class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
final CellService service; final CellService _service;
final CellListener _cellListener;
TextCellBloc({ TextCellBloc({
required this.service,
required GridCell cellData, required GridCell cellData,
}) : super(TextCellState.initial(cellData)) { }) : _service = CellService(),
_cellListener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
super(TextCellState.initial(cellData)) {
on<TextCellEvent>( on<TextCellEvent>(
(event, emit) async { (event, emit) async {
await event.map( await event.map(
initial: (_InitialCell value) async {}, initial: (_InitialCell value) async {
_startListening();
},
updateText: (_UpdateText value) { updateText: (_UpdateText value) {
updateCellContent(value.text); updateCellContent(value.text);
emit(state.copyWith(content: value.text)); emit(state.copyWith(content: value.text));
@ -27,16 +34,28 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
content: value.cellData.cell?.content ?? "", content: value.cellData.cell?.content ?? "",
)); ));
}, },
didReceiveCellUpdate: (_DidReceiveCellUpdate value) {
emit(state.copyWith(
cellData: state.cellData.copyWith(cell: value.cell),
content: value.cell.content,
));
},
); );
}, },
); );
} }
@override
Future<void> close() async {
await _cellListener.stop();
return super.close();
}
void updateCellContent(String content) { void updateCellContent(String content) {
final fieldId = state.cellData.field.id; final fieldId = state.cellData.field.id;
final gridId = state.cellData.gridId; final gridId = state.cellData.gridId;
final rowId = state.cellData.rowId; final rowId = state.cellData.rowId;
service.updateCell( _service.updateCell(
data: content, data: content,
fieldId: fieldId, fieldId: fieldId,
gridId: gridId, gridId: gridId,
@ -44,9 +63,29 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
); );
} }
@override void _startListening() {
Future<void> close() async { _cellListener.updateCellNotifier?.addPublishListener((result) {
return super.close(); result.fold(
(notificationData) async => await _loadCellData(),
(err) => Log.error(err),
);
});
_cellListener.start();
}
Future<void> _loadCellData() async {
final result = await _service.getCell(
gridId: state.cellData.gridId,
fieldId: state.cellData.field.id,
rowId: state.cellData.rowId,
);
if (isClosed) {
return;
}
result.fold(
(cell) => add(TextCellEvent.didReceiveCellUpdate(cell)),
(err) => Log.error(err),
);
} }
} }
@ -54,6 +93,7 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
class TextCellEvent with _$TextCellEvent { class TextCellEvent with _$TextCellEvent {
const factory TextCellEvent.initial() = _InitialCell; const factory TextCellEvent.initial() = _InitialCell;
const factory TextCellEvent.didReceiveCellData(GridCell cellData) = _DidReceiveCellData; const factory TextCellEvent.didReceiveCellData(GridCell cellData) = _DidReceiveCellData;
const factory TextCellEvent.didReceiveCellUpdate(Cell cell) = _DidReceiveCellUpdate;
const factory TextCellEvent.updateText(String text) = _UpdateText; const factory TextCellEvent.updateText(String text) = _UpdateText;
} }

View File

@ -22,8 +22,11 @@ class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
await event.map( await event.map(
initial: (_Initial value) async { initial: (_Initial value) async {
await _startListening(); await _startListening();
_loadCellData();
},
didReceiveCellDatas: (_DidReceiveCellDatas value) {
emit(state.copyWith(cellDatas: value.cellDatas));
}, },
didReceiveCellDatas: (_DidReceiveCellDatas value) {},
); );
}, },
); );
@ -40,16 +43,16 @@ class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
Future<void> _startListening() async { Future<void> _startListening() async {
_rowListenFn = _rowCache.addRowListener( _rowListenFn = _rowCache.addRowListener(
rowId: rowData.rowId, rowId: rowData.rowId,
onUpdated: (cellDatas) => add(RowDetailEvent.didReceiveCellDatas(cellDatas)), onUpdated: (cellDatas) => add(RowDetailEvent.didReceiveCellDatas(cellDatas.values.toList())),
listenWhen: () => !isClosed, listenWhen: () => !isClosed,
); );
} }
Future<void> _loadRow(Emitter<RowDetailState> emit) async { Future<void> _loadCellData() async {
final data = _rowCache.loadCellData(rowData.rowId); final data = _rowCache.loadCellData(rowData.rowId);
data.foldRight(null, (cellDatas, _) { data.foldRight(null, (cellDataMap, _) {
if (!isClosed) { if (!isClosed) {
add(RowDetailEvent.didReceiveCellDatas(cellDatas)); add(RowDetailEvent.didReceiveCellDatas(cellDataMap.values.toList()));
} }
}); });
} }
@ -58,16 +61,16 @@ class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
@freezed @freezed
class RowDetailEvent with _$RowDetailEvent { class RowDetailEvent with _$RowDetailEvent {
const factory RowDetailEvent.initial() = _Initial; const factory RowDetailEvent.initial() = _Initial;
const factory RowDetailEvent.didReceiveCellDatas(CellDataMap cellData) = _DidReceiveCellDatas; const factory RowDetailEvent.didReceiveCellDatas(List<GridCell> cellDatas) = _DidReceiveCellDatas;
} }
@freezed @freezed
class RowDetailState with _$RowDetailState { class RowDetailState with _$RowDetailState {
const factory RowDetailState({ const factory RowDetailState({
required Option<CellDataMap> cellDataMap, required List<GridCell> cellDatas,
}) = _RowDetailState; }) = _RowDetailState;
factory RowDetailState.initial() => RowDetailState( factory RowDetailState.initial() => RowDetailState(
cellDataMap: none(), cellDatas: List.empty(),
); );
} }

View File

@ -82,16 +82,14 @@ class CreateItem extends StatelessWidget {
return FlowyHover( return FlowyHover(
style: config, style: config,
builder: (context, onHover) { child: GestureDetector(
return GestureDetector( onTap: () => onSelected(pluginBuilder),
onTap: () => onSelected(pluginBuilder), child: FlowyText.medium(
child: FlowyText.medium( pluginBuilder.menuName,
pluginBuilder.menuName, color: theme.textColor,
color: theme.textColor, fontSize: 12,
fontSize: 12, ).padding(horizontal: 10, vertical: 6),
).padding(horizontal: 10, vertical: 6), ),
);
},
); );
} }
} }

View File

@ -95,9 +95,11 @@ class _MenuAppState extends State<MenuApp> {
Widget _renderViewSection(AppDataNotifier notifier) { Widget _renderViewSection(AppDataNotifier notifier) {
return MultiProvider( return MultiProvider(
providers: [ChangeNotifierProvider.value(value: notifier)], providers: [ChangeNotifierProvider.value(value: notifier)],
child: Consumer(builder: (context, AppDataNotifier notifier, child) { child: Consumer(
return ViewSection(appData: notifier); builder: (context, AppDataNotifier notifier, child) {
}), return ViewSection(appData: notifier);
},
),
); );
} }

View File

@ -1,6 +1,5 @@
import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/grid/grid_bloc.dart'; import 'package:app_flowy/workspace/application/grid/grid_bloc.dart';
import 'package:app_flowy/workspace/application/grid/row/row_bloc.dart';
import 'package:app_flowy/workspace/application/grid/row/row_service.dart'; import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart'; import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart'; import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart';
@ -214,8 +213,7 @@ class _GridRowsState extends State<_GridRows> {
key: _key, key: _key,
initialItemCount: context.read<GridBloc>().state.rows.length, initialItemCount: context.read<GridBloc>().state.rows.length,
itemBuilder: (BuildContext context, int index, Animation<double> animation) { itemBuilder: (BuildContext context, int index, Animation<double> animation) {
final rowData = context.read<GridBloc>().state.rows[index]; return _renderRow(context, state.rows[index], animation);
return _renderRow(context, rowData, animation);
}, },
); );
}, },

View File

@ -1,13 +1,17 @@
import 'package:app_flowy/workspace/application/grid/row/row_service.dart'; import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show FieldType; import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show FieldType;
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart';
import 'checkbox_cell.dart'; import 'checkbox_cell.dart';
import 'date_cell.dart'; import 'date_cell.dart';
import 'number_cell.dart'; import 'number_cell.dart';
import 'selection_cell/selection_cell.dart'; import 'selection_cell/selection_cell.dart';
import 'text_cell.dart'; import 'text_cell.dart';
Widget buildGridCell(GridCell cellData) { GridCellWidget buildGridCell(GridCell cellData, {GridCellStyle? style}) {
final key = ValueKey(cellData.field.id + cellData.rowId); final key = ValueKey(cellData.field.id + cellData.rowId);
switch (cellData.field.fieldType) { switch (cellData.field.fieldType) {
case FieldType.Checkbox: case FieldType.Checkbox:
@ -19,7 +23,7 @@ Widget buildGridCell(GridCell cellData) {
case FieldType.Number: case FieldType.Number:
return NumberCell(cellData: cellData, key: key); return NumberCell(cellData: cellData, key: key);
case FieldType.RichText: case FieldType.RichText:
return GridTextCell(cellData: cellData, key: key); return GridTextCell(cellData: cellData, key: key, style: style);
case FieldType.SingleSelect: case FieldType.SingleSelect:
return SingleSelectCell(cellData: cellData, key: key); return SingleSelectCell(cellData: cellData, key: key);
default: default:
@ -35,3 +39,116 @@ class BlankCell extends StatelessWidget {
return Container(); return Container();
} }
} }
abstract class GridCellWidget extends HoverWidget {
@override
final ValueNotifier<bool> onFocus = ValueNotifier<bool>(false);
GridCellWidget({Key? key}) : super(key: key);
}
abstract class B {
ValueNotifier<bool> get onFocus;
}
abstract class GridCellStyle {}
//
abstract class HoverWidget extends StatefulWidget {
const HoverWidget({Key? key}) : super(key: key);
ValueNotifier<bool> get onFocus;
}
class FlowyHover2 extends StatefulWidget {
final HoverWidget child;
const FlowyHover2({required this.child, Key? key}) : super(key: key);
@override
State<FlowyHover2> createState() => _FlowyHover2State();
}
class _FlowyHover2State extends State<FlowyHover2> {
late FlowyHoverState _hoverState;
@override
void initState() {
_hoverState = FlowyHoverState();
widget.child.onFocus.addListener(() {
_hoverState.onFocus = widget.child.onFocus.value;
});
super.initState();
}
@override
void dispose() {
_hoverState.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider.value(
value: _hoverState,
child: MouseRegion(
cursor: SystemMouseCursors.click,
opaque: false,
onEnter: (p) => setState(() => _hoverState.onHover = true),
onExit: (p) => setState(() => _hoverState.onHover = false),
child: Stack(
fit: StackFit.expand,
alignment: AlignmentDirectional.center,
children: [
const _HoverBackground(),
widget.child,
],
),
),
);
}
}
class _HoverBackground extends StatelessWidget {
const _HoverBackground({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return Consumer<FlowyHoverState>(
builder: (context, state, child) {
if (state.onHover || state.onFocus) {
return FlowyHoverContainer(
style: HoverStyle(
borderRadius: Corners.s6Border,
hoverColor: theme.shader6,
),
);
} else {
return const SizedBox();
}
},
);
}
}
class FlowyHoverState extends ChangeNotifier {
bool _onHover = false;
bool _onFocus = false;
set onHover(bool value) {
if (_onHover != value) {
_onHover = value;
notifyListeners();
}
}
bool get onHover => _onHover;
set onFocus(bool value) {
if (_onFocus != value) {
_onFocus = value;
notifyListeners();
}
}
bool get onFocus => _onFocus;
}

View File

@ -1,8 +1,8 @@
import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra/theme.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
import 'cell_builder.dart';
class CellStateNotifier extends ChangeNotifier { class CellStateNotifier extends ChangeNotifier {
bool _isFocus = false; bool _isFocus = false;
@ -28,7 +28,7 @@ class CellStateNotifier extends ChangeNotifier {
} }
class CellContainer extends StatelessWidget { class CellContainer extends StatelessWidget {
final Widget child; final GridCellWidget child;
final Widget? expander; final Widget? expander;
final double width; final double width;
const CellContainer({ const CellContainer({
@ -46,6 +46,9 @@ class CellContainer extends StatelessWidget {
selector: (context, notifier) => notifier.isFocus, selector: (context, notifier) => notifier.isFocus,
builder: (context, isFocus, _) { builder: (context, isFocus, _) {
Widget container = Center(child: child); Widget container = Center(child: child);
child.onFocus.addListener(() {
Provider.of<CellStateNotifier>(context, listen: false).isFocus = child.onFocus.value;
});
if (expander != null) { if (expander != null) {
container = _CellEnterRegion(child: container, expander: expander!); container = _CellEnterRegion(child: container, expander: expander!);
@ -75,16 +78,16 @@ class CellContainer extends StatelessWidget {
} }
class _CellEnterRegion extends StatelessWidget { class _CellEnterRegion extends StatelessWidget {
final Widget expander;
final Widget child; final Widget child;
const _CellEnterRegion({required this.expander, required this.child, Key? key}) : super(key: key); final Widget expander;
const _CellEnterRegion({required this.child, required this.expander, Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Selector<CellStateNotifier, bool>( return Selector<CellStateNotifier, bool>(
selector: (context, notifier) => notifier.onEnter, selector: (context, notifier) => notifier.onEnter,
builder: (context, onEnter, _) { builder: (context, onEnter, _) {
List<Widget> children = [child]; List<Widget> children = [Expanded(child: child)];
if (onEnter) { if (onEnter) {
children.add(expander); children.add(expander);
} }
@ -93,8 +96,8 @@ class _CellEnterRegion extends StatelessWidget {
cursor: SystemMouseCursors.click, cursor: SystemMouseCursors.click,
onEnter: (p) => Provider.of<CellStateNotifier>(context, listen: false).onEnter = true, onEnter: (p) => Provider.of<CellStateNotifier>(context, listen: false).onEnter = true,
onExit: (p) => Provider.of<CellStateNotifier>(context, listen: false).onEnter = false, onExit: (p) => Provider.of<CellStateNotifier>(context, listen: false).onEnter = false,
child: Stack( child: Row(
alignment: AlignmentDirectional.centerEnd, // alignment: AlignmentDirectional.centerEnd,
children: children, children: children,
), ),
); );
@ -102,35 +105,3 @@ class _CellEnterRegion extends StatelessWidget {
); );
} }
} }
abstract class GridCellWidget extends StatefulWidget {
const GridCellWidget({Key? key}) : super(key: key);
void setFocus(BuildContext context, bool value) {
Provider.of<CellStateNotifier>(context, listen: false).isFocus = value;
}
}
class CellFocusNode extends FocusNode {
VoidCallback? focusCallback;
void addCallback(BuildContext context, VoidCallback callback) {
if (focusCallback != null) {
removeListener(focusCallback!);
}
focusCallback = () {
Provider.of<CellStateNotifier>(context, listen: false).isFocus = hasFocus;
callback();
};
addListener(focusCallback!);
}
@override
void dispose() {
if (focusCallback != null) {
removeListener(focusCallback!);
}
super.dispose();
}
}

View File

@ -4,11 +4,12 @@ import 'package:flowy_infra/image.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'cell_builder.dart';
class CheckboxCell extends StatefulWidget { class CheckboxCell extends GridCellWidget {
final GridCell cellData; final GridCell cellData;
const CheckboxCell({ CheckboxCell({
required this.cellData, required this.cellData,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);

View File

@ -1,17 +1,22 @@
import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/grid/prelude.dart'; import 'package:app_flowy/workspace/application/grid/prelude.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/cell_container.dart';
import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:table_calendar/table_calendar.dart'; import 'package:table_calendar/table_calendar.dart';
import 'cell_builder.dart';
abstract class GridCellDelegate {
void onFocus(bool isFocus);
GridCellDelegate get delegate;
}
class DateCell extends GridCellWidget { class DateCell extends GridCellWidget {
final GridCell cellData; final GridCell cellData;
const DateCell({ DateCell({
required this.cellData, required this.cellData,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@ -39,13 +44,13 @@ class _DateCellState extends State<DateCell> {
child: GestureDetector( child: GestureDetector(
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
onTap: () { onTap: () {
widget.setFocus(context, true); widget.onFocus.value = true;
_CellCalendar.show( _CellCalendar.show(
context, context,
onSelected: (day) { onSelected: (day) {
context.read<DateCellBloc>().add(DateCellEvent.selectDay(day)); context.read<DateCellBloc>().add(DateCellEvent.selectDay(day));
}, },
onDismissed: () => widget.setFocus(context, false), onDismissed: () => widget.onFocus.value = false,
); );
}, },
child: MouseRegion( child: MouseRegion(

View File

@ -2,14 +2,15 @@ import 'dart:async';
import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/grid/prelude.dart'; import 'package:app_flowy/workspace/application/grid/prelude.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/cell_container.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'cell_builder.dart';
class NumberCell extends GridCellWidget { class NumberCell extends GridCellWidget {
final GridCell cellData; final GridCell cellData;
const NumberCell({ NumberCell({
required this.cellData, required this.cellData,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@ -21,21 +22,23 @@ class NumberCell extends GridCellWidget {
class _NumberCellState extends State<NumberCell> { class _NumberCellState extends State<NumberCell> {
late NumberCellBloc _cellBloc; late NumberCellBloc _cellBloc;
late TextEditingController _controller; late TextEditingController _controller;
late CellFocusNode _focusNode; late FocusNode _focusNode;
Timer? _delayOperation; Timer? _delayOperation;
@override @override
void initState() { void initState() {
_cellBloc = getIt<NumberCellBloc>(param1: widget.cellData)..add(const NumberCellEvent.initial()); _cellBloc = getIt<NumberCellBloc>(param1: widget.cellData)..add(const NumberCellEvent.initial());
_controller = TextEditingController(text: _cellBloc.state.content); _controller = TextEditingController(text: _cellBloc.state.content);
_focusNode = CellFocusNode(); _focusNode = FocusNode();
_focusNode.addListener(() {
widget.onFocus.value = _focusNode.hasFocus;
focusChanged();
});
super.initState(); super.initState();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
_focusNode.addCallback(context, focusChanged);
return BlocProvider.value( return BlocProvider.value(
value: _cellBloc, value: _cellBloc,
child: BlocConsumer<NumberCellBloc, NumberCellState>( child: BlocConsumer<NumberCellBloc, NumberCellState>(

View File

@ -1,6 +1,6 @@
import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/grid/prelude.dart'; import 'package:app_flowy/workspace/application/grid/prelude.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/cell_container.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@ -10,7 +10,7 @@ import 'selection_editor.dart';
class SingleSelectCell extends GridCellWidget { class SingleSelectCell extends GridCellWidget {
final GridCell cellData; final GridCell cellData;
const SingleSelectCell({ SingleSelectCell({
required this.cellData, required this.cellData,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@ -38,13 +38,13 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
return SizedBox.expand( return SizedBox.expand(
child: InkWell( child: InkWell(
onTap: () { onTap: () {
widget.setFocus(context, true); widget.onFocus.value = true;
SelectOptionCellEditor.show( SelectOptionCellEditor.show(
context, context,
state.cellData, state.cellData,
state.options, state.options,
state.selectedOptions, state.selectedOptions,
() => widget.setFocus(context, false), () => widget.onFocus.value = false,
); );
}, },
child: ClipRRect(child: Row(children: children)), child: ClipRRect(child: Row(children: children)),
@ -66,7 +66,7 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
class MultiSelectCell extends GridCellWidget { class MultiSelectCell extends GridCellWidget {
final GridCell cellData; final GridCell cellData;
const MultiSelectCell({ MultiSelectCell({
required this.cellData, required this.cellData,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@ -94,13 +94,13 @@ class _MultiSelectCellState extends State<MultiSelectCell> {
return SizedBox.expand( return SizedBox.expand(
child: InkWell( child: InkWell(
onTap: () { onTap: () {
widget.setFocus(context, true); widget.onFocus.value = true;
SelectOptionCellEditor.show( SelectOptionCellEditor.show(
context, context,
state.cellData, state.cellData,
state.options, state.options,
state.selectedOptions, state.selectedOptions,
() => widget.setFocus(context, false), () => widget.onFocus.value = false,
); );
}, },
child: ClipRRect(child: Row(children: children)), child: ClipRRect(child: Row(children: children)),

View File

@ -1,16 +1,39 @@
import 'dart:async'; import 'dart:async';
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/grid/prelude.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'cell_container.dart'; import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/grid/prelude.dart';
import 'cell_builder.dart';
class GridTextCellStyle extends GridCellStyle {
String? placeholder;
Color? hoverColor;
bool filled;
InputBorder? inputBorder;
EdgeInsets? contentPadding;
GridTextCellStyle({
this.placeholder,
this.hoverColor,
this.filled = false,
this.inputBorder,
this.contentPadding,
});
}
class GridTextCell extends GridCellWidget { class GridTextCell extends GridCellWidget {
final GridCell cellData; final GridCell cellData;
const GridTextCell({ late final GridTextCellStyle? cellStyle;
GridTextCell({
required this.cellData, required this.cellData,
GridCellStyle? style,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key) {
if (style != null) {
cellStyle = (style as GridTextCellStyle);
} else {
cellStyle = null;
}
}
@override @override
State<GridTextCell> createState() => _GridTextCellState(); State<GridTextCell> createState() => _GridTextCellState();
@ -19,21 +42,25 @@ class GridTextCell extends GridCellWidget {
class _GridTextCellState extends State<GridTextCell> { class _GridTextCellState extends State<GridTextCell> {
late TextCellBloc _cellBloc; late TextCellBloc _cellBloc;
late TextEditingController _controller; late TextEditingController _controller;
late CellFocusNode _focusNode; late FocusNode _focusNode;
Timer? _delayOperation; Timer? _delayOperation;
@override @override
void initState() { void initState() {
_cellBloc = getIt<TextCellBloc>(param1: widget.cellData); _cellBloc = getIt<TextCellBloc>(param1: widget.cellData);
_cellBloc.add(const TextCellEvent.initial());
_controller = TextEditingController(text: _cellBloc.state.content); _controller = TextEditingController(text: _cellBloc.state.content);
_focusNode = CellFocusNode(); _focusNode = FocusNode();
_focusNode.addListener(() {
widget.onFocus.value = _focusNode.hasFocus;
focusChanged();
});
super.initState(); super.initState();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
_focusNode.addCallback(context, focusChanged);
return BlocProvider.value( return BlocProvider.value(
value: _cellBloc, value: _cellBloc,
child: BlocConsumer<TextCellBloc, TextCellState>( child: BlocConsumer<TextCellBloc, TextCellState>(
@ -42,6 +69,7 @@ class _GridTextCellState extends State<GridTextCell> {
_controller.text = state.content; _controller.text = state.content;
} }
}, },
buildWhen: (previous, current) => previous.content != current.content,
builder: (context, state) { builder: (context, state) {
return TextField( return TextField(
controller: _controller, controller: _controller,
@ -50,9 +78,13 @@ class _GridTextCellState extends State<GridTextCell> {
onEditingComplete: () => _focusNode.unfocus(), onEditingComplete: () => _focusNode.unfocus(),
maxLines: 1, maxLines: 1,
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
decoration: const InputDecoration( decoration: InputDecoration(
contentPadding: EdgeInsets.zero, contentPadding: widget.cellStyle?.contentPadding ?? EdgeInsets.zero,
border: InputBorder.none, border: widget.cellStyle?.inputBorder ?? InputBorder.none,
hintText: widget.cellStyle?.placeholder,
hoverColor: widget.cellStyle?.hoverColor ?? Colors.transparent,
filled: widget.cellStyle?.filled ?? false,
fillColor: Colors.transparent,
isDense: true, isDense: true,
), ),
); );

View File

@ -7,6 +7,7 @@ import 'package:flowy_infra_ui/style_widget/button.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Field;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'field_type_extension.dart'; import 'field_type_extension.dart';
@ -20,21 +21,21 @@ class GridFieldCell extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return BlocProvider( return BlocProvider(
create: (context) => FieldCellBloc(cellContext: cellContext)..add(const FieldCellEvent.initial()), create: (context) => FieldCellBloc(cellContext: cellContext)..add(const FieldCellEvent.initial()),
child: BlocBuilder<FieldCellBloc, FieldCellState>( child: BlocBuilder<FieldCellBloc, FieldCellState>(
builder: (context, state) { builder: (context, state) {
final button = FlowyButton( final button = FieldCellButton(
hoverColor: theme.shader6, field: state.field,
onTap: () => _showActionSheet(context), onTap: () => _showActionSheet(context),
leftIcon: svgWidget(state.field.fieldType.iconName(), color: theme.iconColor),
text: FlowyText.medium(state.field.name, fontSize: 12),
padding: GridSize.cellContentInsets,
); );
const line = Positioned(top: 0, bottom: 0, right: 0, child: _DragToExpandLine()); const line = Positioned(
top: 0,
bottom: 0,
right: 0,
child: _DragToExpandLine(),
);
return _CellContainer( return _CellContainer(
width: state.field.width.toDouble(), width: state.field.width.toDouble(),
@ -125,9 +126,31 @@ class _DragToExpandLine extends StatelessWidget {
borderRadius: BorderRadius.zero, borderRadius: BorderRadius.zero,
contentMargin: const EdgeInsets.only(left: 5), contentMargin: const EdgeInsets.only(left: 5),
), ),
builder: (_, onHover) => const SizedBox(width: 2), child: const SizedBox(width: 2),
), ),
), ),
); );
} }
} }
class FieldCellButton extends StatelessWidget {
final VoidCallback onTap;
final Field field;
const FieldCellButton({
required this.field,
required this.onTap,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return FlowyButton(
hoverColor: theme.shader6,
onTap: onTap,
leftIcon: svgWidget(field.fieldType.iconName(), color: theme.iconColor),
text: FlowyText.medium(field.name, fontSize: 12),
padding: GridSize.cellContentInsets,
);
}
}

View File

@ -2,12 +2,14 @@ import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/grid/field/field_editor_bloc.dart'; import 'package:app_flowy/workspace/application/grid/field/field_editor_bloc.dart';
import 'package:app_flowy/workspace/application/grid/field/field_service.dart'; import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
import 'package:app_flowy/workspace/application/grid/field/field_switch_bloc.dart'; import 'package:app_flowy/workspace/application/grid/field/field_switch_bloc.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Field; import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Field;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'field_name_input.dart'; import 'field_name_input.dart';
import 'field_switcher.dart'; import 'field_switcher.dart';
@ -70,7 +72,7 @@ class _FieldEditorWidget extends StatelessWidget {
(field) => ListView( (field) => ListView(
shrinkWrap: true, shrinkWrap: true,
children: [ children: [
const FlowyText.medium("Edit property", fontSize: 12), FlowyText.medium(LocaleKeys.grid_field_editProperty.tr(), fontSize: 12),
const VSpace(10), const VSpace(10),
const _FieldNameTextField(), const _FieldNameTextField(),
const VSpace(10), const VSpace(10),

View File

@ -1,11 +1,19 @@
import 'package:app_flowy/workspace/application/grid/row/row_detail_bloc.dart'; import 'package:app_flowy/workspace/application/grid/row/row_detail_bloc.dart';
import 'package:app_flowy/workspace/application/grid/row/row_service.dart'; import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/prelude.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show FieldType;
import 'package:easy_localization/easy_localization.dart';
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:window_size/window_size.dart'; import 'package:window_size/window_size.dart';
class RowDetailPage extends StatelessWidget with FlowyOverlayDelegate { class RowDetailPage extends StatefulWidget with FlowyOverlayDelegate {
final GridRow rowData; final GridRow rowData;
final GridRowCache rowCache; final GridRowCache rowCache;
@ -16,24 +24,17 @@ class RowDetailPage extends StatelessWidget with FlowyOverlayDelegate {
}) : super(key: key); }) : super(key: key);
@override @override
Widget build(BuildContext context) { State<RowDetailPage> createState() => _RowDetailPageState();
return BlocProvider(
create: (context) => RowDetailBloc(rowData: rowData, rowCache: rowCache),
child: Container(),
);
}
void show(BuildContext context) async { void show(BuildContext context) async {
FlowyOverlay.of(context).remove(identifier());
const size = Size(460, 400);
final window = await getWindowInfo(); final window = await getWindowInfo();
final size = Size(window.frame.size.width * 0.7, window.frame.size.height * 0.7);
FlowyOverlay.of(context).insertWithRect( FlowyOverlay.of(context).insertWithRect(
widget: OverlayContainer( widget: OverlayContainer(
child: this, child: this,
constraints: BoxConstraints.tight(const Size(460, 400)), constraints: BoxConstraints.tight(size),
), ),
identifier: identifier(), identifier: RowDetailPage.identifier(),
anchorPosition: Offset(-size.width / 2.0, -size.height / 2.0), anchorPosition: Offset(-size.width / 2.0, -size.height / 2.0),
anchorSize: window.frame.size, anchorSize: window.frame.size,
anchorDirection: AnchorDirection.center, anchorDirection: AnchorDirection.center,
@ -46,3 +47,97 @@ class RowDetailPage extends StatelessWidget with FlowyOverlayDelegate {
return (RowDetailPage).toString(); return (RowDetailPage).toString();
} }
} }
class _RowDetailPageState extends State<RowDetailPage> {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) {
final bloc = RowDetailBloc(rowData: widget.rowData, rowCache: widget.rowCache);
bloc.add(const RowDetailEvent.initial());
return bloc;
},
child: const Padding(
padding: EdgeInsets.symmetric(horizontal: 80, vertical: 40),
child: _PropertyList(),
),
);
}
}
class _PropertyList extends StatelessWidget {
const _PropertyList({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocBuilder<RowDetailBloc, RowDetailState>(
buildWhen: (previous, current) => previous.cellDatas != current.cellDatas,
builder: (context, state) {
return ListView.separated(
itemCount: state.cellDatas.length,
itemBuilder: (BuildContext context, int index) {
return _RowDetailCell(cellData: state.cellDatas[index]);
},
separatorBuilder: (BuildContext context, int index) {
return const VSpace(2);
},
);
},
);
}
}
class _RowDetailCell extends StatelessWidget {
final GridCell cellData;
const _RowDetailCell({required this.cellData, Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
final cell = buildGridCell(
cellData,
style: _buildCellStyle(theme, cellData.field.fieldType),
);
return SizedBox(
height: 36,
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 150,
child: FieldCellButton(field: cellData.field, onTap: () {}),
),
const HSpace(10),
Expanded(child: FlowyHover2(child: cell)),
],
),
);
}
}
GridCellStyle? _buildCellStyle(AppTheme theme, FieldType fieldType) {
switch (fieldType) {
case FieldType.Checkbox:
return null;
case FieldType.DateTime:
return null;
case FieldType.MultiSelect:
return null;
case FieldType.Number:
return null;
case FieldType.RichText:
return GridTextCellStyle(
hoverColor: theme.shader6,
filled: true,
placeholder: LocaleKeys.grid_row_textPlaceholder.tr(),
contentPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 10),
);
case FieldType.SingleSelect:
return null;
default:
return null;
}
}

View File

@ -86,29 +86,27 @@ class ActionCell<T extends ActionItem> extends StatelessWidget {
return FlowyHover( return FlowyHover(
style: HoverStyle(hoverColor: theme.hover), style: HoverStyle(hoverColor: theme.hover),
builder: (context, onHover) { child: GestureDetector(
return GestureDetector( behavior: HitTestBehavior.opaque,
behavior: HitTestBehavior.opaque, onTap: () => onSelected(action),
onTap: () => onSelected(action), child: SizedBox(
child: SizedBox( height: itemHeight,
height: itemHeight, child: Row(
child: Row( crossAxisAlignment: CrossAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, children: [
children: [ if (action.icon != null) action.icon!,
if (action.icon != null) action.icon!, HSpace(ActionListSizes.itemHPadding),
HSpace(ActionListSizes.itemHPadding), FlowyText.medium(
FlowyText.medium( action.name,
action.name, fontSize: 12,
fontSize: 12, ),
), ],
],
),
).padding(
horizontal: ActionListSizes.padding,
vertical: ActionListSizes.padding,
), ),
); ).padding(
}, horizontal: ActionListSizes.padding,
vertical: ActionListSizes.padding,
),
),
); );
} }
} }

View File

@ -1,17 +1,22 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
// ignore: unused_import // ignore: unused_import
import 'package:flowy_infra/time/duration.dart'; import 'package:flowy_infra/time/duration.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/theme.dart';
import 'package:provider/provider.dart';
typedef HoverBuilder = Widget Function(BuildContext context, bool onHover); typedef HoverBuilder = Widget Function(BuildContext context, bool onHover);
class FlowyHover extends StatefulWidget { class FlowyHover extends StatefulWidget {
final HoverStyle style; final HoverStyle style;
final HoverBuilder builder; final HoverBuilder? builder;
final Widget? child;
final bool Function()? setSelected; final bool Function()? setSelected;
const FlowyHover({ const FlowyHover({
Key? key, Key? key,
required this.builder, this.builder,
this.child,
required this.style, required this.style,
this.setSelected, this.setSelected,
}) : super(key: key); }) : super(key: key);
@ -27,25 +32,27 @@ class _FlowyHoverState extends State<FlowyHover> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MouseRegion( return MouseRegion(
cursor: SystemMouseCursors.click, cursor: SystemMouseCursors.click,
opaque: false,
onEnter: (p) => setState(() => _onHover = true), onEnter: (p) => setState(() => _onHover = true),
onExit: (p) => setState(() => _onHover = false), onExit: (p) => setState(() => _onHover = false),
child: render(), child: renderWidget(),
); );
} }
Widget render() { Widget renderWidget() {
var showHover = _onHover; var showHover = _onHover;
if (!showHover && widget.setSelected != null) { if (!showHover && widget.setSelected != null) {
showHover = widget.setSelected!(); showHover = widget.setSelected!();
} }
final child = widget.child ?? widget.builder!(context, _onHover);
if (showHover) { if (showHover) {
return FlowyHoverContainer( return FlowyHoverContainer(
style: widget.style, style: widget.style,
child: widget.builder(context, _onHover), child: child,
); );
} else { } else {
return widget.builder(context, _onHover); return child;
} }
} }
} }
@ -67,11 +74,11 @@ class HoverStyle {
class FlowyHoverContainer extends StatelessWidget { class FlowyHoverContainer extends StatelessWidget {
final HoverStyle style; final HoverStyle style;
final Widget child; final Widget? child;
const FlowyHoverContainer({ const FlowyHoverContainer({
Key? key, Key? key,
required this.child, this.child,
required this.style, required this.style,
}) : super(key: key); }) : super(key: key);
@ -93,3 +100,104 @@ class FlowyHoverContainer extends StatelessWidget {
); );
} }
} }
//
abstract class HoverWidget extends StatefulWidget {
const HoverWidget({Key? key}) : super(key: key);
ValueNotifier<bool> get onFocus;
}
class FlowyHover2 extends StatefulWidget {
final HoverWidget child;
const FlowyHover2({required this.child, Key? key}) : super(key: key);
@override
State<FlowyHover2> createState() => _FlowyHover2State();
}
class _FlowyHover2State extends State<FlowyHover2> {
late FlowyHoverState _hoverState;
@override
void initState() {
_hoverState = FlowyHoverState();
widget.child.onFocus.addListener(() {
_hoverState.onFocus = widget.child.onFocus.value;
});
super.initState();
}
@override
void dispose() {
_hoverState.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider.value(
value: _hoverState,
child: MouseRegion(
cursor: SystemMouseCursors.click,
opaque: false,
onEnter: (p) => setState(() => _hoverState.onHover = true),
onExit: (p) => setState(() => _hoverState.onHover = false),
child: Stack(
fit: StackFit.expand,
alignment: AlignmentDirectional.center,
children: [
const _HoverBackground(),
widget.child,
],
),
),
);
}
}
class _HoverBackground extends StatelessWidget {
const _HoverBackground({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return Consumer<FlowyHoverState>(
builder: (context, state, child) {
if (state.onHover || state.onFocus) {
return FlowyHoverContainer(
style: HoverStyle(
borderRadius: Corners.s6Border,
hoverColor: theme.shader6,
),
);
} else {
return const SizedBox();
}
},
);
}
}
class FlowyHoverState extends ChangeNotifier {
bool _onHover = false;
bool _onFocus = false;
set onHover(bool value) {
if (_onHover != value) {
_onHover = value;
notifyListeners();
}
}
bool get onHover => _onHover;
set onFocus(bool value) {
if (_onFocus != value) {
_onFocus = value;
notifyListeners();
}
}
bool get onFocus => _onFocus;
}