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": {
"duplicate": "Duplicate",
"delete": "Delete"
"delete": "Delete",
"textPlaceholder": "Empty"
},
"selectOption": {
"purpleColor": "Purple",

View File

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

View File

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

View File

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

View File

@ -1,22 +1,29 @@
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:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
import 'cell_listener.dart';
import 'cell_service.dart';
part 'text_cell_bloc.freezed.dart';
class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
final CellService service;
final CellService _service;
final CellListener _cellListener;
TextCellBloc({
required this.service,
required GridCell cellData,
}) : super(TextCellState.initial(cellData)) {
}) : _service = CellService(),
_cellListener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
super(TextCellState.initial(cellData)) {
on<TextCellEvent>(
(event, emit) async {
await event.map(
initial: (_InitialCell value) async {},
initial: (_InitialCell value) async {
_startListening();
},
updateText: (_UpdateText value) {
updateCellContent(value.text);
emit(state.copyWith(content: value.text));
@ -27,16 +34,28 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
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) {
final fieldId = state.cellData.field.id;
final gridId = state.cellData.gridId;
final rowId = state.cellData.rowId;
service.updateCell(
_service.updateCell(
data: content,
fieldId: fieldId,
gridId: gridId,
@ -44,9 +63,29 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
);
}
@override
Future<void> close() async {
return super.close();
void _startListening() {
_cellListener.updateCellNotifier?.addPublishListener((result) {
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 {
const factory TextCellEvent.initial() = _InitialCell;
const factory TextCellEvent.didReceiveCellData(GridCell cellData) = _DidReceiveCellData;
const factory TextCellEvent.didReceiveCellUpdate(Cell cell) = _DidReceiveCellUpdate;
const factory TextCellEvent.updateText(String text) = _UpdateText;
}

View File

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

View File

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

View File

@ -95,9 +95,11 @@ class _MenuAppState extends State<MenuApp> {
Widget _renderViewSection(AppDataNotifier notifier) {
return MultiProvider(
providers: [ChangeNotifierProvider.value(value: notifier)],
child: Consumer(builder: (context, AppDataNotifier notifier, child) {
return ViewSection(appData: notifier);
}),
child: Consumer(
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/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:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart';
@ -214,8 +213,7 @@ class _GridRowsState extends State<_GridRows> {
key: _key,
initialItemCount: context.read<GridBloc>().state.rows.length,
itemBuilder: (BuildContext context, int index, Animation<double> animation) {
final rowData = context.read<GridBloc>().state.rows[index];
return _renderRow(context, rowData, animation);
return _renderRow(context, state.rows[index], animation);
},
);
},

View File

@ -1,13 +1,17 @@
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:flutter/widgets.dart';
import 'package:provider/provider.dart';
import 'checkbox_cell.dart';
import 'date_cell.dart';
import 'number_cell.dart';
import 'selection_cell/selection_cell.dart';
import 'text_cell.dart';
Widget buildGridCell(GridCell cellData) {
GridCellWidget buildGridCell(GridCell cellData, {GridCellStyle? style}) {
final key = ValueKey(cellData.field.id + cellData.rowId);
switch (cellData.field.fieldType) {
case FieldType.Checkbox:
@ -19,7 +23,7 @@ Widget buildGridCell(GridCell cellData) {
case FieldType.Number:
return NumberCell(cellData: cellData, key: key);
case FieldType.RichText:
return GridTextCell(cellData: cellData, key: key);
return GridTextCell(cellData: cellData, key: key, style: style);
case FieldType.SingleSelect:
return SingleSelectCell(cellData: cellData, key: key);
default:
@ -35,3 +39,116 @@ class BlankCell extends StatelessWidget {
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:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
import 'cell_builder.dart';
class CellStateNotifier extends ChangeNotifier {
bool _isFocus = false;
@ -28,7 +28,7 @@ class CellStateNotifier extends ChangeNotifier {
}
class CellContainer extends StatelessWidget {
final Widget child;
final GridCellWidget child;
final Widget? expander;
final double width;
const CellContainer({
@ -46,6 +46,9 @@ class CellContainer extends StatelessWidget {
selector: (context, notifier) => notifier.isFocus,
builder: (context, isFocus, _) {
Widget container = Center(child: child);
child.onFocus.addListener(() {
Provider.of<CellStateNotifier>(context, listen: false).isFocus = child.onFocus.value;
});
if (expander != null) {
container = _CellEnterRegion(child: container, expander: expander!);
@ -75,16 +78,16 @@ class CellContainer extends StatelessWidget {
}
class _CellEnterRegion extends StatelessWidget {
final Widget expander;
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
Widget build(BuildContext context) {
return Selector<CellStateNotifier, bool>(
selector: (context, notifier) => notifier.onEnter,
builder: (context, onEnter, _) {
List<Widget> children = [child];
List<Widget> children = [Expanded(child: child)];
if (onEnter) {
children.add(expander);
}
@ -93,8 +96,8 @@ class _CellEnterRegion extends StatelessWidget {
cursor: SystemMouseCursors.click,
onEnter: (p) => Provider.of<CellStateNotifier>(context, listen: false).onEnter = true,
onExit: (p) => Provider.of<CellStateNotifier>(context, listen: false).onEnter = false,
child: Stack(
alignment: AlignmentDirectional.centerEnd,
child: Row(
// alignment: AlignmentDirectional.centerEnd,
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:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'cell_builder.dart';
class CheckboxCell extends StatefulWidget {
class CheckboxCell extends GridCellWidget {
final GridCell cellData;
const CheckboxCell({
CheckboxCell({
required this.cellData,
Key? key,
}) : super(key: key);

View File

@ -1,17 +1,22 @@
import 'package:app_flowy/startup/startup.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_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.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 {
final GridCell cellData;
const DateCell({
DateCell({
required this.cellData,
Key? key,
}) : super(key: key);
@ -39,13 +44,13 @@ class _DateCellState extends State<DateCell> {
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
widget.setFocus(context, true);
widget.onFocus.value = true;
_CellCalendar.show(
context,
onSelected: (day) {
context.read<DateCellBloc>().add(DateCellEvent.selectDay(day));
},
onDismissed: () => widget.setFocus(context, false),
onDismissed: () => widget.onFocus.value = false,
);
},
child: MouseRegion(

View File

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

View File

@ -1,6 +1,6 @@
import 'package:app_flowy/startup/startup.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_bloc/flutter_bloc.dart';
@ -10,7 +10,7 @@ import 'selection_editor.dart';
class SingleSelectCell extends GridCellWidget {
final GridCell cellData;
const SingleSelectCell({
SingleSelectCell({
required this.cellData,
Key? key,
}) : super(key: key);
@ -38,13 +38,13 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
return SizedBox.expand(
child: InkWell(
onTap: () {
widget.setFocus(context, true);
widget.onFocus.value = true;
SelectOptionCellEditor.show(
context,
state.cellData,
state.options,
state.selectedOptions,
() => widget.setFocus(context, false),
() => widget.onFocus.value = false,
);
},
child: ClipRRect(child: Row(children: children)),
@ -66,7 +66,7 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
class MultiSelectCell extends GridCellWidget {
final GridCell cellData;
const MultiSelectCell({
MultiSelectCell({
required this.cellData,
Key? key,
}) : super(key: key);
@ -94,13 +94,13 @@ class _MultiSelectCellState extends State<MultiSelectCell> {
return SizedBox.expand(
child: InkWell(
onTap: () {
widget.setFocus(context, true);
widget.onFocus.value = true;
SelectOptionCellEditor.show(
context,
state.cellData,
state.options,
state.selectedOptions,
() => widget.setFocus(context, false),
() => widget.onFocus.value = false,
);
},
child: ClipRRect(child: Row(children: children)),

View File

@ -1,16 +1,39 @@
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_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 {
final GridCell cellData;
const GridTextCell({
late final GridTextCellStyle? cellStyle;
GridTextCell({
required this.cellData,
GridCellStyle? style,
Key? key,
}) : super(key: key);
}) : super(key: key) {
if (style != null) {
cellStyle = (style as GridTextCellStyle);
} else {
cellStyle = null;
}
}
@override
State<GridTextCell> createState() => _GridTextCellState();
@ -19,21 +42,25 @@ class GridTextCell extends GridCellWidget {
class _GridTextCellState extends State<GridTextCell> {
late TextCellBloc _cellBloc;
late TextEditingController _controller;
late CellFocusNode _focusNode;
late FocusNode _focusNode;
Timer? _delayOperation;
@override
void initState() {
_cellBloc = getIt<TextCellBloc>(param1: widget.cellData);
_cellBloc.add(const TextCellEvent.initial());
_controller = TextEditingController(text: _cellBloc.state.content);
_focusNode = CellFocusNode();
_focusNode = FocusNode();
_focusNode.addListener(() {
widget.onFocus.value = _focusNode.hasFocus;
focusChanged();
});
super.initState();
}
@override
Widget build(BuildContext context) {
_focusNode.addCallback(context, focusChanged);
return BlocProvider.value(
value: _cellBloc,
child: BlocConsumer<TextCellBloc, TextCellState>(
@ -42,6 +69,7 @@ class _GridTextCellState extends State<GridTextCell> {
_controller.text = state.content;
}
},
buildWhen: (previous, current) => previous.content != current.content,
builder: (context, state) {
return TextField(
controller: _controller,
@ -50,9 +78,13 @@ class _GridTextCellState extends State<GridTextCell> {
onEditingComplete: () => _focusNode.unfocus(),
maxLines: 1,
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
decoration: const InputDecoration(
contentPadding: EdgeInsets.zero,
border: InputBorder.none,
decoration: InputDecoration(
contentPadding: widget.cellStyle?.contentPadding ?? EdgeInsets.zero,
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,
),
);

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/text.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_bloc/flutter_bloc.dart';
import 'field_type_extension.dart';
@ -20,21 +21,21 @@ class GridFieldCell extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return BlocProvider(
create: (context) => FieldCellBloc(cellContext: cellContext)..add(const FieldCellEvent.initial()),
child: BlocBuilder<FieldCellBloc, FieldCellState>(
builder: (context, state) {
final button = FlowyButton(
hoverColor: theme.shader6,
final button = FieldCellButton(
field: state.field,
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(
width: state.field.width.toDouble(),
@ -125,9 +126,31 @@ class _DragToExpandLine extends StatelessWidget {
borderRadius: BorderRadius.zero,
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_service.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/style_widget/text.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:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'field_name_input.dart';
import 'field_switcher.dart';
@ -70,7 +72,7 @@ class _FieldEditorWidget extends StatelessWidget {
(field) => ListView(
shrinkWrap: true,
children: [
const FlowyText.medium("Edit property", fontSize: 12),
FlowyText.medium(LocaleKeys.grid_field_editProperty.tr(), fontSize: 12),
const VSpace(10),
const _FieldNameTextField(),
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_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/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_bloc/flutter_bloc.dart';
import 'package:window_size/window_size.dart';
class RowDetailPage extends StatelessWidget with FlowyOverlayDelegate {
class RowDetailPage extends StatefulWidget with FlowyOverlayDelegate {
final GridRow rowData;
final GridRowCache rowCache;
@ -16,24 +24,17 @@ class RowDetailPage extends StatelessWidget with FlowyOverlayDelegate {
}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => RowDetailBloc(rowData: rowData, rowCache: rowCache),
child: Container(),
);
}
State<RowDetailPage> createState() => _RowDetailPageState();
void show(BuildContext context) async {
FlowyOverlay.of(context).remove(identifier());
const size = Size(460, 400);
final window = await getWindowInfo();
final size = Size(window.frame.size.width * 0.7, window.frame.size.height * 0.7);
FlowyOverlay.of(context).insertWithRect(
widget: OverlayContainer(
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),
anchorSize: window.frame.size,
anchorDirection: AnchorDirection.center,
@ -46,3 +47,97 @@ class RowDetailPage extends StatelessWidget with FlowyOverlayDelegate {
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(
style: HoverStyle(hoverColor: theme.hover),
builder: (context, onHover) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => onSelected(action),
child: SizedBox(
height: itemHeight,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (action.icon != null) action.icon!,
HSpace(ActionListSizes.itemHPadding),
FlowyText.medium(
action.name,
fontSize: 12,
),
],
),
).padding(
horizontal: ActionListSizes.padding,
vertical: ActionListSizes.padding,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => onSelected(action),
child: SizedBox(
height: itemHeight,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (action.icon != null) action.icon!,
HSpace(ActionListSizes.itemHPadding),
FlowyText.medium(
action.name,
fontSize: 12,
),
],
),
);
},
).padding(
horizontal: ActionListSizes.padding,
vertical: ActionListSizes.padding,
),
),
);
}
}

View File

@ -1,17 +1,22 @@
import 'package:flutter/material.dart';
// ignore: unused_import
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);
class FlowyHover extends StatefulWidget {
final HoverStyle style;
final HoverBuilder builder;
final HoverBuilder? builder;
final Widget? child;
final bool Function()? setSelected;
const FlowyHover({
Key? key,
required this.builder,
this.builder,
this.child,
required this.style,
this.setSelected,
}) : super(key: key);
@ -27,25 +32,27 @@ class _FlowyHoverState extends State<FlowyHover> {
Widget build(BuildContext context) {
return MouseRegion(
cursor: SystemMouseCursors.click,
opaque: false,
onEnter: (p) => setState(() => _onHover = true),
onExit: (p) => setState(() => _onHover = false),
child: render(),
child: renderWidget(),
);
}
Widget render() {
Widget renderWidget() {
var showHover = _onHover;
if (!showHover && widget.setSelected != null) {
showHover = widget.setSelected!();
}
final child = widget.child ?? widget.builder!(context, _onHover);
if (showHover) {
return FlowyHoverContainer(
style: widget.style,
child: widget.builder(context, _onHover),
child: child,
);
} else {
return widget.builder(context, _onHover);
return child;
}
}
}
@ -67,11 +74,11 @@ class HoverStyle {
class FlowyHoverContainer extends StatelessWidget {
final HoverStyle style;
final Widget child;
final Widget? child;
const FlowyHoverContainer({
Key? key,
required this.child,
this.child,
required this.style,
}) : 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;
}