mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: config cell accessory
This commit is contained in:
parent
9c5081bc07
commit
9518e164b5
@ -0,0 +1,161 @@
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
abstract class GridCellAccessory implements Widget {
|
||||
void onTap(BuildContext context);
|
||||
}
|
||||
|
||||
abstract class AccessoryHoverChild extends Widget {
|
||||
const AccessoryHoverChild({Key? key}) : super(key: key);
|
||||
|
||||
// The hover will show if the onFocus's value is true
|
||||
ValueNotifier<bool>? get isFocus;
|
||||
|
||||
List<GridCellAccessory> accessories();
|
||||
}
|
||||
|
||||
class AccessoryHover extends StatefulWidget {
|
||||
final AccessoryHoverChild child;
|
||||
final EdgeInsets contentPadding;
|
||||
const AccessoryHover({
|
||||
required this.child,
|
||||
this.contentPadding = EdgeInsets.zero,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<AccessoryHover> createState() => _AccessoryHoverState();
|
||||
}
|
||||
|
||||
class _AccessoryHoverState extends State<AccessoryHover> {
|
||||
late AccessoryHoverState _hoverState;
|
||||
VoidCallback? _listenerFn;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_hoverState = AccessoryHoverState();
|
||||
_listenerFn = () => _hoverState.isFocus = widget.child.isFocus?.value ?? false;
|
||||
widget.child.isFocus?.addListener(_listenerFn!);
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_hoverState.dispose();
|
||||
|
||||
if (_listenerFn != null) {
|
||||
widget.child.isFocus?.removeListener(_listenerFn!);
|
||||
_listenerFn = null;
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List<Widget> children = [
|
||||
const _Background(),
|
||||
Padding(padding: widget.contentPadding, child: widget.child),
|
||||
];
|
||||
final accessories = widget.child.accessories();
|
||||
if (accessories.isNotEmpty) {
|
||||
children.add(
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 6),
|
||||
child: AccessoryContainer(accessories: accessories),
|
||||
).positioned(right: 0),
|
||||
);
|
||||
}
|
||||
|
||||
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.loose,
|
||||
alignment: AlignmentDirectional.center,
|
||||
children: children,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AccessoryHoverState extends ChangeNotifier {
|
||||
bool _onHover = false;
|
||||
bool _isFocus = false;
|
||||
|
||||
set onHover(bool value) {
|
||||
if (_onHover != value) {
|
||||
_onHover = value;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
bool get onHover => _onHover;
|
||||
|
||||
set isFocus(bool value) {
|
||||
if (_isFocus != value) {
|
||||
_isFocus = value;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
bool get isFocus => _isFocus;
|
||||
}
|
||||
|
||||
class _Background extends StatelessWidget {
|
||||
const _Background({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.watch<AppTheme>();
|
||||
return Consumer<AccessoryHoverState>(
|
||||
builder: (context, state, child) {
|
||||
if (state.onHover || state.isFocus) {
|
||||
return FlowyHoverContainer(
|
||||
style: HoverStyle(borderRadius: Corners.s6Border, hoverColor: theme.shader6),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AccessoryContainer extends StatelessWidget {
|
||||
final List<GridCellAccessory> accessories;
|
||||
const AccessoryContainer({required this.accessories, Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.watch<AppTheme>();
|
||||
final children = accessories.map((accessory) {
|
||||
final hover = FlowyHover(
|
||||
style: HoverStyle(hoverColor: theme.bg3, backgroundColor: theme.surface),
|
||||
builder: (_, onHover) => Container(
|
||||
width: 26,
|
||||
height: 26,
|
||||
padding: const EdgeInsets.all(3),
|
||||
child: accessory,
|
||||
),
|
||||
);
|
||||
return GestureDetector(
|
||||
child: hover,
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => accessory.onTap(context),
|
||||
);
|
||||
}).toList();
|
||||
|
||||
return Wrap(children: children, spacing: 6);
|
||||
}
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.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:app_flowy/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart';
|
||||
@ -8,6 +7,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'cell_accessory.dart';
|
||||
import 'checkbox_cell.dart';
|
||||
import 'date_cell/date_cell.dart';
|
||||
import 'number_cell.dart';
|
||||
@ -48,22 +48,20 @@ class BlankCell extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
abstract class GridCellExpander implements Widget {
|
||||
void onExpand(BuildContext context);
|
||||
}
|
||||
|
||||
abstract class GridCellWidget implements FlowyHoverWidget {
|
||||
abstract class GridCellWidget implements AccessoryHoverChild, CellContainerFocustable {
|
||||
@override
|
||||
final ValueNotifier<bool> onFocus = ValueNotifier<bool>(false);
|
||||
final ValueNotifier<bool> isFocus = ValueNotifier<bool>(false);
|
||||
|
||||
final GridCellBeginFocusFocus beginFocus = GridCellBeginFocusFocus();
|
||||
|
||||
GridCellExpander? buildExpander() {
|
||||
return null;
|
||||
@override
|
||||
List<GridCellAccessory> accessories() {
|
||||
return List.empty();
|
||||
}
|
||||
|
||||
@override
|
||||
final GridCellRequestBeginFocus requestBeginFocus = GridCellRequestBeginFocus();
|
||||
}
|
||||
|
||||
class GridCellBeginFocusFocus extends ChangeNotifier {
|
||||
class GridCellRequestBeginFocus extends ChangeNotifier {
|
||||
VoidCallback? _listener;
|
||||
|
||||
void setListener(VoidCallback listener) {
|
||||
@ -130,9 +128,14 @@ class CellStateNotifier extends ChangeNotifier {
|
||||
bool get onEnter => _onEnter;
|
||||
}
|
||||
|
||||
abstract class CellContainerFocustable {
|
||||
// Listen on the requestBeginFocus if the
|
||||
GridCellRequestBeginFocus get requestBeginFocus;
|
||||
}
|
||||
|
||||
class CellContainer extends StatelessWidget {
|
||||
final GridCellWidget child;
|
||||
final GridCellExpander? expander;
|
||||
final List<GridCellAccessory> accessories;
|
||||
final double width;
|
||||
final RegionStateNotifier rowStateNotifier;
|
||||
const CellContainer({
|
||||
@ -140,7 +143,7 @@ class CellContainer extends StatelessWidget {
|
||||
required this.child,
|
||||
required this.width,
|
||||
required this.rowStateNotifier,
|
||||
this.expander,
|
||||
this.accessories = const [],
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@ -152,17 +155,17 @@ 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;
|
||||
child.isFocus.addListener(() {
|
||||
Provider.of<CellStateNotifier>(context, listen: false).isFocus = child.isFocus.value;
|
||||
});
|
||||
|
||||
if (expander != null) {
|
||||
container = CellEnterRegion(child: container, expander: expander!);
|
||||
if (accessories.isNotEmpty) {
|
||||
container = CellEnterRegion(child: container, accessories: accessories);
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () => child.beginFocus.notify(),
|
||||
onTap: () => child.requestBeginFocus.notify(),
|
||||
child: Container(
|
||||
constraints: BoxConstraints(maxWidth: width, minHeight: 46),
|
||||
decoration: _makeBoxDecoration(context, isFocus),
|
||||
@ -189,33 +192,17 @@ class CellContainer extends StatelessWidget {
|
||||
|
||||
class CellEnterRegion extends StatelessWidget {
|
||||
final Widget child;
|
||||
final GridCellExpander expander;
|
||||
const CellEnterRegion({required this.child, required this.expander, Key? key}) : super(key: key);
|
||||
final List<GridCellAccessory> accessories;
|
||||
const CellEnterRegion({required this.child, required this.accessories, Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.watch<AppTheme>();
|
||||
|
||||
return Selector<CellStateNotifier, bool>(
|
||||
selector: (context, notifier) => notifier.onEnter,
|
||||
builder: (context, onEnter, _) {
|
||||
List<Widget> children = [child];
|
||||
if (onEnter) {
|
||||
final hover = FlowyHover(
|
||||
style: HoverStyle(hoverColor: theme.bg3, backgroundColor: theme.surface),
|
||||
builder: (_, onHover) => Container(
|
||||
width: 26,
|
||||
height: 26,
|
||||
padding: const EdgeInsets.all(3),
|
||||
child: expander,
|
||||
),
|
||||
);
|
||||
|
||||
children.add(GestureDetector(
|
||||
child: hover,
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => expander.onExpand(context),
|
||||
).positioned(right: 0));
|
||||
children.add(AccessoryContainer(accessories: accessories).positioned(right: 0));
|
||||
}
|
||||
|
||||
return MouseRegion(
|
||||
@ -225,7 +212,6 @@ class CellEnterRegion extends StatelessWidget {
|
||||
child: Stack(
|
||||
alignment: AlignmentDirectional.center,
|
||||
fit: StackFit.expand,
|
||||
// alignment: AlignmentDirectional.centerEnd,
|
||||
children: children,
|
||||
),
|
||||
);
|
||||
|
@ -57,13 +57,13 @@ class _CheckboxCellState extends State<CheckboxCell> {
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
widget.beginFocus.removeAllListener();
|
||||
widget.requestBeginFocus.removeAllListener();
|
||||
_cellBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _handleRequestFocus() {
|
||||
widget.beginFocus.setListener(() {
|
||||
widget.requestBeginFocus.setListener(() {
|
||||
_cellBloc.add(const CheckboxCellEvent.select());
|
||||
});
|
||||
}
|
||||
|
@ -76,8 +76,8 @@ class _DateCellState extends State<DateCell> {
|
||||
|
||||
void _showCalendar(BuildContext context) {
|
||||
final bloc = context.read<DateCellBloc>();
|
||||
widget.onFocus.value = true;
|
||||
final calendar = DateCellEditor(onDismissed: () => widget.onFocus.value = false);
|
||||
widget.isFocus.value = true;
|
||||
final calendar = DateCellEditor(onDismissed: () => widget.isFocus.value = false);
|
||||
calendar.show(
|
||||
context,
|
||||
cellContext: bloc.cellContext.clone(),
|
||||
|
@ -65,7 +65,7 @@ class _NumberCellState extends State<NumberCell> {
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
widget.beginFocus.removeAllListener();
|
||||
widget.requestBeginFocus.removeAllListener();
|
||||
_delayOperation?.cancel();
|
||||
_cellBloc.close();
|
||||
_focusNode.removeAllListener();
|
||||
@ -91,15 +91,15 @@ class _NumberCellState extends State<NumberCell> {
|
||||
}
|
||||
|
||||
void _listenOnFocusNodeChanged() {
|
||||
widget.onFocus.value = _focusNode.hasFocus;
|
||||
widget.isFocus.value = _focusNode.hasFocus;
|
||||
_focusNode.setListener(() {
|
||||
widget.onFocus.value = _focusNode.hasFocus;
|
||||
widget.isFocus.value = _focusNode.hasFocus;
|
||||
focusChanged();
|
||||
});
|
||||
}
|
||||
|
||||
void _handleCellRequestFocus(BuildContext context) {
|
||||
widget.beginFocus.setListener(() {
|
||||
widget.requestBeginFocus.setListener(() {
|
||||
if (_focusNode.hasFocus == false && _focusNode.canRequestFocus) {
|
||||
FocusScope.of(context).requestFocus(_focusNode);
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
|
||||
return _SelectOptionCell(
|
||||
selectOptions: state.selectedOptions,
|
||||
cellStyle: widget.cellStyle,
|
||||
onFocus: (value) => widget.onFocus.value = value,
|
||||
onFocus: (value) => widget.isFocus.value = value,
|
||||
cellContextBuilder: widget.cellContextBuilder);
|
||||
},
|
||||
),
|
||||
@ -113,7 +113,7 @@ class _MultiSelectCellState extends State<MultiSelectCell> {
|
||||
return _SelectOptionCell(
|
||||
selectOptions: state.selectedOptions,
|
||||
cellStyle: widget.cellStyle,
|
||||
onFocus: (value) => widget.onFocus.value = value,
|
||||
onFocus: (value) => widget.isFocus.value = value,
|
||||
cellContextBuilder: widget.cellContextBuilder);
|
||||
},
|
||||
),
|
||||
|
@ -81,7 +81,7 @@ class _GridTextCellState extends State<GridTextCell> {
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
widget.beginFocus.removeAllListener();
|
||||
widget.requestBeginFocus.removeAllListener();
|
||||
_delayOperation?.cancel();
|
||||
_cellBloc.close();
|
||||
_focusNode.removeAllListener();
|
||||
@ -99,15 +99,15 @@ class _GridTextCellState extends State<GridTextCell> {
|
||||
}
|
||||
|
||||
void _listenOnFocusNodeChanged() {
|
||||
widget.onFocus.value = _focusNode.hasFocus;
|
||||
widget.isFocus.value = _focusNode.hasFocus;
|
||||
_focusNode.setListener(() {
|
||||
widget.onFocus.value = _focusNode.hasFocus;
|
||||
widget.isFocus.value = _focusNode.hasFocus;
|
||||
focusChanged();
|
||||
});
|
||||
}
|
||||
|
||||
void _listenRequestFocus(BuildContext context) {
|
||||
widget.beginFocus.setListener(() {
|
||||
widget.requestBeginFocus.setListener(() {
|
||||
if (_focusNode.hasFocus == false && _focusNode.canRequestFocus) {
|
||||
FocusScope.of(context).requestFocus(_focusNode);
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class URLCellEditor extends StatefulWidget {
|
||||
class URLCellEditor extends StatefulWidget with FlowyOverlayDelegate {
|
||||
final GridURLCellContext cellContext;
|
||||
const URLCellEditor({required this.cellContext, Key? key}) : super(key: key);
|
||||
|
||||
@ -34,12 +34,18 @@ class URLCellEditor extends StatefulWidget {
|
||||
identifier: URLCellEditor.identifier(),
|
||||
anchorContext: context,
|
||||
anchorDirection: AnchorDirection.bottomWithCenterAligned,
|
||||
delegate: editor,
|
||||
);
|
||||
}
|
||||
|
||||
static String identifier() {
|
||||
return (URLCellEditor).toString();
|
||||
}
|
||||
|
||||
@override
|
||||
bool asBarrier() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class _URLCellEditorState extends State<URLCellEditor> {
|
||||
|
@ -1,9 +1,10 @@
|
||||
import 'dart:async';
|
||||
import 'package:app_flowy/workspace/application/grid/cell/url_cell_bloc.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/cell_accessory.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/prelude.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
@ -13,11 +14,19 @@ import 'cell_editor.dart';
|
||||
class GridURLCellStyle extends GridCellStyle {
|
||||
String? placeholder;
|
||||
|
||||
List<GridURLCellAccessoryType> accessoryTypes;
|
||||
|
||||
GridURLCellStyle({
|
||||
this.placeholder,
|
||||
this.accessoryTypes = const [],
|
||||
});
|
||||
}
|
||||
|
||||
enum GridURLCellAccessoryType {
|
||||
edit,
|
||||
copyURL,
|
||||
}
|
||||
|
||||
class GridURLCell extends StatefulWidget with GridCellWidget {
|
||||
final GridCellContextBuilder cellContextBuilder;
|
||||
late final GridURLCellStyle? cellStyle;
|
||||
@ -37,9 +46,30 @@ class GridURLCell extends StatefulWidget with GridCellWidget {
|
||||
State<GridURLCell> createState() => _GridURLCellState();
|
||||
|
||||
@override
|
||||
GridCellExpander? buildExpander() {
|
||||
final cellContext = cellContextBuilder.build() as GridURLCellContext;
|
||||
return _EditURLCellIndicator(cellContext: cellContext);
|
||||
List<GridCellAccessory> accessories() {
|
||||
final List<GridCellAccessory> accessories = [];
|
||||
if (cellStyle != null) {
|
||||
accessories.addAll(cellStyle!.accessoryTypes.map(accessoryFromType));
|
||||
}
|
||||
|
||||
// If the accessories is empty then the default accessory will be GridURLCellAccessoryType.edit
|
||||
if (accessories.isEmpty) {
|
||||
accessories.add(accessoryFromType(GridURLCellAccessoryType.edit));
|
||||
}
|
||||
|
||||
return accessories;
|
||||
}
|
||||
|
||||
GridCellAccessory accessoryFromType(GridURLCellAccessoryType ty) {
|
||||
switch (ty) {
|
||||
case GridURLCellAccessoryType.edit:
|
||||
final cellContext = cellContextBuilder.build() as GridURLCellContext;
|
||||
return _EditURLAccessory(cellContext: cellContext);
|
||||
|
||||
case GridURLCellAccessoryType.copyURL:
|
||||
final cellContext = cellContextBuilder.build() as GridURLCellContext;
|
||||
return _CopyURLAccessory(cellContext: cellContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,7 +108,7 @@ class _GridURLCellState extends State<GridURLCell> {
|
||||
child: GestureDetector(
|
||||
child: Align(alignment: Alignment.centerLeft, child: richText),
|
||||
onTap: () async {
|
||||
widget.onFocus.value = true;
|
||||
widget.isFocus.value = true;
|
||||
final url = context.read<URLCellBloc>().state.url;
|
||||
await _openUrlOrEdit(url);
|
||||
},
|
||||
@ -90,7 +120,7 @@ class _GridURLCellState extends State<GridURLCell> {
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
widget.beginFocus.removeAllListener();
|
||||
widget.requestBeginFocus.removeAllListener();
|
||||
_cellBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
@ -112,15 +142,15 @@ class _GridURLCellState extends State<GridURLCell> {
|
||||
}
|
||||
|
||||
void _handleRequestFocus() {
|
||||
widget.beginFocus.setListener(() {
|
||||
widget.requestBeginFocus.setListener(() {
|
||||
_openUrlOrEdit(_cellBloc.state.url);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class _EditURLCellIndicator extends StatelessWidget with GridCellExpander {
|
||||
class _EditURLAccessory extends StatelessWidget with GridCellAccessory {
|
||||
final GridURLCellContext cellContext;
|
||||
const _EditURLCellIndicator({required this.cellContext, Key? key}) : super(key: key);
|
||||
const _EditURLAccessory({required this.cellContext, Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -129,7 +159,24 @@ class _EditURLCellIndicator extends StatelessWidget with GridCellExpander {
|
||||
}
|
||||
|
||||
@override
|
||||
void onExpand(BuildContext context) {
|
||||
void onTap(BuildContext context) {
|
||||
URLCellEditor.show(context, cellContext);
|
||||
}
|
||||
}
|
||||
|
||||
class _CopyURLAccessory extends StatelessWidget with GridCellAccessory {
|
||||
final GridURLCellContext cellContext;
|
||||
const _CopyURLAccessory({required this.cellContext, Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.watch<AppTheme>();
|
||||
return svgWidget("editor/copy", color: theme.iconColor);
|
||||
}
|
||||
|
||||
@override
|
||||
void onTap(BuildContext context) {
|
||||
final content = cellContext.getCellData()?.content ?? "";
|
||||
Clipboard.setData(ClipboardData(text: content));
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:app_flowy/workspace/application/grid/prelude.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/cell_accessory.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/prelude.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
@ -171,16 +172,17 @@ class _RowCells extends StatelessWidget {
|
||||
return gridCellMap.values.map(
|
||||
(gridCell) {
|
||||
final GridCellWidget child = buildGridCellWidget(gridCell, cellCache);
|
||||
GridCellExpander? expander = child.buildExpander();
|
||||
List<GridCellAccessory> accessories = [];
|
||||
if (gridCell.field.isPrimary) {
|
||||
expander = _PrimaryCellExpander(onTap: onExpand);
|
||||
accessories.add(_PrimaryCellAccessory(onTapCallback: onExpand));
|
||||
}
|
||||
accessories.addAll(child.accessories());
|
||||
|
||||
return CellContainer(
|
||||
width: gridCell.field.width.toDouble(),
|
||||
child: child,
|
||||
rowStateNotifier: Provider.of<RegionStateNotifier>(context, listen: false),
|
||||
expander: expander,
|
||||
accessories: accessories,
|
||||
);
|
||||
},
|
||||
).toList();
|
||||
@ -200,9 +202,9 @@ class RegionStateNotifier extends ChangeNotifier {
|
||||
bool get onEnter => _onEnter;
|
||||
}
|
||||
|
||||
class _PrimaryCellExpander extends StatelessWidget with GridCellExpander {
|
||||
final VoidCallback onTap;
|
||||
const _PrimaryCellExpander({required this.onTap, Key? key}) : super(key: key);
|
||||
class _PrimaryCellAccessory extends StatelessWidget with GridCellAccessory {
|
||||
final VoidCallback onTapCallback;
|
||||
const _PrimaryCellAccessory({required this.onTapCallback, Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -211,8 +213,8 @@ class _PrimaryCellExpander extends StatelessWidget with GridCellExpander {
|
||||
}
|
||||
|
||||
@override
|
||||
void onExpand(BuildContext context) {
|
||||
onTap();
|
||||
void onTap(BuildContext context) {
|
||||
onTapCallback();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ import 'package:app_flowy/workspace/application/grid/field/field_service.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/presentation/plugins/grid/src/layout/sizes.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/cell_accessory.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/prelude.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/url_cell.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart';
|
||||
@ -149,12 +150,9 @@ class _RowDetailCell extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.watch<AppTheme>();
|
||||
final style = _customCellStyle(theme, gridCell.field.fieldType);
|
||||
final cell = buildGridCellWidget(gridCell, cellCache, style: style);
|
||||
|
||||
final cell = buildGridCellWidget(
|
||||
gridCell,
|
||||
cellCache,
|
||||
style: _buildCellStyle(theme, gridCell.field.fieldType),
|
||||
);
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(minHeight: 40),
|
||||
child: IntrinsicHeight(
|
||||
@ -168,7 +166,7 @@ class _RowDetailCell extends StatelessWidget {
|
||||
),
|
||||
const HSpace(10),
|
||||
Expanded(
|
||||
child: FlowyHover2(
|
||||
child: AccessoryHover(
|
||||
child: cell,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 12),
|
||||
),
|
||||
@ -191,7 +189,7 @@ class _RowDetailCell extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
GridCellStyle? _buildCellStyle(AppTheme theme, FieldType fieldType) {
|
||||
GridCellStyle? _customCellStyle(AppTheme theme, FieldType fieldType) {
|
||||
switch (fieldType) {
|
||||
case FieldType.Checkbox:
|
||||
return null;
|
||||
@ -217,7 +215,11 @@ GridCellStyle? _buildCellStyle(AppTheme theme, FieldType fieldType) {
|
||||
case FieldType.URL:
|
||||
return GridURLCellStyle(
|
||||
placeholder: LocaleKeys.grid_row_textPlaceholder.tr(),
|
||||
accessoryTypes: [
|
||||
GridURLCellAccessoryType.edit,
|
||||
GridURLCellAccessoryType.copyURL,
|
||||
],
|
||||
);
|
||||
}
|
||||
return null;
|
||||
throw UnimplementedError;
|
||||
}
|
||||
|
@ -1,9 +1,6 @@
|
||||
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);
|
||||
|
||||
@ -102,120 +99,3 @@ class FlowyHoverContainer extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
abstract class FlowyHoverWidget extends Widget {
|
||||
const FlowyHoverWidget({Key? key}) : super(key: key);
|
||||
|
||||
ValueNotifier<bool>? get onFocus;
|
||||
}
|
||||
|
||||
class FlowyHover2 extends StatefulWidget {
|
||||
final FlowyHoverWidget child;
|
||||
final EdgeInsets contentPadding;
|
||||
const FlowyHover2({
|
||||
required this.child,
|
||||
this.contentPadding = EdgeInsets.zero,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<FlowyHover2> createState() => _FlowyHover2State();
|
||||
}
|
||||
|
||||
class _FlowyHover2State extends State<FlowyHover2> {
|
||||
late FlowyHoverState _hoverState;
|
||||
VoidCallback? _listenerFn;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_hoverState = FlowyHoverState();
|
||||
|
||||
listener() {
|
||||
_hoverState.onFocus = widget.child.onFocus?.value ?? false;
|
||||
}
|
||||
|
||||
_listenerFn = listener;
|
||||
widget.child.onFocus?.addListener(listener);
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_hoverState.dispose();
|
||||
|
||||
if (_listenerFn != null) {
|
||||
widget.child.onFocus?.removeListener(_listenerFn!);
|
||||
_listenerFn = null;
|
||||
}
|
||||
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.loose,
|
||||
alignment: AlignmentDirectional.center,
|
||||
children: [
|
||||
const _HoverBackground(),
|
||||
Padding(
|
||||
padding: widget.contentPadding,
|
||||
child: 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…
Reference in New Issue
Block a user