mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: config hover
This commit is contained in:
parent
eeba6884ce
commit
f66adb53c9
@ -177,7 +177,8 @@
|
|||||||
},
|
},
|
||||||
"row": {
|
"row": {
|
||||||
"duplicate": "Duplicate",
|
"duplicate": "Duplicate",
|
||||||
"delete": "Delete"
|
"delete": "Delete",
|
||||||
|
"textPlaceholder": "Empty"
|
||||||
},
|
},
|
||||||
"selectOption": {
|
"selectOption": {
|
||||||
"purpleColor": "Purple",
|
"purpleColor": "Purple",
|
||||||
|
@ -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,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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(
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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),
|
),
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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);
|
||||||
|
@ -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(
|
||||||
|
@ -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>(
|
||||||
|
@ -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)),
|
||||||
|
@ -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,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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),
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user