mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Merge pull request #521 from AppFlowy-IO/fix/grid_ui_adjust
Fix: grid UI adjust
This commit is contained in:
@ -180,7 +180,8 @@
|
|||||||
"row": {
|
"row": {
|
||||||
"duplicate": "Duplicate",
|
"duplicate": "Duplicate",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"textPlaceholder": "Empty"
|
"textPlaceholder": "Empty",
|
||||||
|
"copyProperty": "Copied property to clipboard"
|
||||||
},
|
},
|
||||||
"selectOption": {
|
"selectOption": {
|
||||||
"create": "Create",
|
"create": "Create",
|
||||||
|
@ -16,6 +16,7 @@ import 'package:app_flowy/workspace/presentation/home/menu/menu.dart';
|
|||||||
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/app.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/app.pb.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_profile.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_profile.pb.dart';
|
||||||
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
|
|
||||||
class DependencyResolver {
|
class DependencyResolver {
|
||||||
@ -46,6 +47,8 @@ void _resolveUserDeps(GetIt getIt) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _resolveHomeDeps(GetIt getIt) {
|
void _resolveHomeDeps(GetIt getIt) {
|
||||||
|
getIt.registerSingleton(FToast());
|
||||||
|
|
||||||
getIt.registerSingleton(MenuSharedState());
|
getIt.registerSingleton(MenuSharedState());
|
||||||
|
|
||||||
getIt.registerFactoryParam<UserListener, UserProfile, void>(
|
getIt.registerFactoryParam<UserListener, UserProfile, void>(
|
||||||
|
@ -67,7 +67,8 @@ class ApplicationWidget extends StatelessWidget {
|
|||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => ChangeNotifierProvider.value(
|
Widget build(BuildContext context) {
|
||||||
|
return ChangeNotifierProvider.value(
|
||||||
value: settingModel,
|
value: settingModel,
|
||||||
builder: (context, _) {
|
builder: (context, _) {
|
||||||
const ratio = 1.73;
|
const ratio = 1.73;
|
||||||
@ -101,6 +102,7 @@ class ApplicationWidget extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AppGlobals {
|
class AppGlobals {
|
||||||
|
@ -105,7 +105,7 @@ class _GridCellContext<T, D> extends Equatable {
|
|||||||
final FieldService _fieldService;
|
final FieldService _fieldService;
|
||||||
|
|
||||||
late final CellListener _cellListener;
|
late final CellListener _cellListener;
|
||||||
late final ValueNotifier<T?> _cellDataNotifier;
|
late final ValueNotifier<T?>? _cellDataNotifier;
|
||||||
bool isListening = false;
|
bool isListening = false;
|
||||||
VoidCallback? _onFieldChangedFn;
|
VoidCallback? _onFieldChangedFn;
|
||||||
Timer? _loadDataOperation;
|
Timer? _loadDataOperation;
|
||||||
@ -163,19 +163,19 @@ class _GridCellContext<T, D> extends Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onCellChangedFn() {
|
onCellChangedFn() {
|
||||||
onCellChanged(_cellDataNotifier.value);
|
onCellChanged(_cellDataNotifier?.value);
|
||||||
|
|
||||||
if (cellDataLoader.config.reloadOnCellChanged) {
|
if (cellDataLoader.config.reloadOnCellChanged) {
|
||||||
_loadData();
|
_loadData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_cellDataNotifier.addListener(onCellChangedFn);
|
_cellDataNotifier?.addListener(onCellChangedFn);
|
||||||
return onCellChangedFn;
|
return onCellChangedFn;
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeListener(VoidCallback fn) {
|
void removeListener(VoidCallback fn) {
|
||||||
_cellDataNotifier.removeListener(fn);
|
_cellDataNotifier?.removeListener(fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
T? getCellData({bool loadIfNoCache = true}) {
|
T? getCellData({bool loadIfNoCache = true}) {
|
||||||
@ -211,7 +211,7 @@ class _GridCellContext<T, D> extends Equatable {
|
|||||||
_loadDataOperation?.cancel();
|
_loadDataOperation?.cancel();
|
||||||
_loadDataOperation = Timer(const Duration(milliseconds: 10), () {
|
_loadDataOperation = Timer(const Duration(milliseconds: 10), () {
|
||||||
cellDataLoader.loadData().then((data) {
|
cellDataLoader.loadData().then((data) {
|
||||||
_cellDataNotifier.value = data;
|
_cellDataNotifier?.value = data;
|
||||||
cellCache.insert(GridCellCacheData(key: _cacheKey, object: data));
|
cellCache.insert(GridCellCacheData(key: _cacheKey, object: data));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -2,15 +2,13 @@ import 'dart:io' show Platform;
|
|||||||
|
|
||||||
import 'package:app_flowy/startup/startup.dart';
|
import 'package:app_flowy/startup/startup.dart';
|
||||||
import 'package:app_flowy/workspace/application/home/home_bloc.dart';
|
import 'package:app_flowy/workspace/application/home/home_bloc.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/home/home_screen.dart';
|
import 'package:app_flowy/workspace/presentation/home/toast.dart';
|
||||||
import 'package:flowy_infra/theme.dart';
|
import 'package:flowy_infra/theme.dart';
|
||||||
import 'package:flowy_sdk/log.dart';
|
import 'package:flowy_sdk/log.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:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:time/time.dart';
|
import 'package:time/time.dart';
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
|
||||||
|
|
||||||
import 'package:app_flowy/plugin/plugin.dart';
|
import 'package:app_flowy/plugin/plugin.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/plugins/blank/blank.dart';
|
import 'package:app_flowy/workspace/presentation/plugins/blank/blank.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/home/home_sizes.dart';
|
import 'package:app_flowy/workspace/presentation/home/home_sizes.dart';
|
||||||
@ -22,8 +20,6 @@ import 'package:flowy_infra/notifier.dart';
|
|||||||
|
|
||||||
typedef NavigationCallback = void Function(String id);
|
typedef NavigationCallback = void Function(String id);
|
||||||
|
|
||||||
late FToast fToast;
|
|
||||||
|
|
||||||
class HomeStack extends StatelessWidget {
|
class HomeStack extends StatelessWidget {
|
||||||
static GlobalKey<ScaffoldState> scaffoldKey = GlobalKey();
|
static GlobalKey<ScaffoldState> scaffoldKey = GlobalKey();
|
||||||
// final Size size;
|
// final Size size;
|
||||||
@ -74,8 +70,7 @@ class _FadingIndexedStackState extends State<FadingIndexedStack> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
fToast = FToast();
|
initToastWithContext(context);
|
||||||
fToast.init(HomeScreen.scaffoldKey.currentState!.context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
import 'package:app_flowy/startup/startup.dart';
|
||||||
|
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
|
|
||||||
|
class FlowyMessageToast extends StatelessWidget {
|
||||||
|
final String message;
|
||||||
|
const FlowyMessageToast({required this.message, Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||||
|
child: FlowyText.medium(message, color: Colors.white),
|
||||||
|
),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(4)),
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void initToastWithContext(BuildContext context) {
|
||||||
|
getIt<FToast>().init(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void showMessageToast(String message) {
|
||||||
|
final child = FlowyMessageToast(message: message);
|
||||||
|
|
||||||
|
getIt<FToast>().showToast(
|
||||||
|
child: child,
|
||||||
|
gravity: ToastGravity.BOTTOM,
|
||||||
|
toastDuration: const Duration(seconds: 3),
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,171 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
class GridCellAccessoryBuildContext {
|
||||||
|
final BuildContext anchorContext;
|
||||||
|
|
||||||
|
GridCellAccessoryBuildContext({required this.anchorContext});
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class GridCellAccessory implements Widget {
|
||||||
|
void onTap();
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef AccessoryBuilder = List<GridCellAccessory> Function(GridCellAccessoryBuildContext buildContext);
|
||||||
|
|
||||||
|
abstract class AccessoryWidget extends Widget {
|
||||||
|
const AccessoryWidget({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
// The hover will show if the onFocus's value is true
|
||||||
|
ValueNotifier<bool>? get isFocus;
|
||||||
|
|
||||||
|
AccessoryBuilder? get accessoryBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AccessoryHover extends StatefulWidget {
|
||||||
|
final AccessoryWidget 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 accessoryBuilder = widget.child.accessoryBuilder;
|
||||||
|
if (accessoryBuilder != null) {
|
||||||
|
final accessories = accessoryBuilder((GridCellAccessoryBuildContext(anchorContext: context)));
|
||||||
|
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(),
|
||||||
|
);
|
||||||
|
}).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: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:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show FieldType;
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/row/grid_row.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: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 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'cell_accessory.dart';
|
||||||
import 'checkbox_cell.dart';
|
import 'checkbox_cell.dart';
|
||||||
import 'date_cell/date_cell.dart';
|
import 'date_cell/date_cell.dart';
|
||||||
import 'number_cell.dart';
|
import 'number_cell.dart';
|
||||||
@ -48,24 +48,27 @@ class BlankCell extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class GridCellWidget implements FlowyHoverWidget {
|
abstract class GridCellWidget implements AccessoryWidget, CellContainerFocustable {
|
||||||
@override
|
@override
|
||||||
final ValueNotifier<bool> onFocus = ValueNotifier<bool>(false);
|
final ValueNotifier<bool> isFocus = ValueNotifier<bool>(false);
|
||||||
|
|
||||||
final GridCellRequestFocusNotifier requestFocus = GridCellRequestFocusNotifier();
|
@override
|
||||||
|
List<GridCellAccessory> Function(GridCellAccessoryBuildContext buildContext)? get accessoryBuilder => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final GridCellRequestBeginFocus requestBeginFocus = GridCellRequestBeginFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
class GridCellRequestFocusNotifier extends ChangeNotifier {
|
class GridCellRequestBeginFocus extends ChangeNotifier {
|
||||||
VoidCallback? _listener;
|
VoidCallback? _listener;
|
||||||
|
|
||||||
@override
|
void setListener(VoidCallback listener) {
|
||||||
void addListener(VoidCallback listener) {
|
|
||||||
if (_listener != null) {
|
if (_listener != null) {
|
||||||
removeListener(_listener!);
|
removeListener(_listener!);
|
||||||
}
|
}
|
||||||
|
|
||||||
_listener = listener;
|
_listener = listener;
|
||||||
super.addListener(listener);
|
addListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeAllListener() {
|
void removeAllListener() {
|
||||||
@ -81,10 +84,10 @@ class GridCellRequestFocusNotifier extends ChangeNotifier {
|
|||||||
|
|
||||||
abstract class GridCellStyle {}
|
abstract class GridCellStyle {}
|
||||||
|
|
||||||
class CellSingleFocusNode extends FocusNode {
|
class SingleListenrFocusNode extends FocusNode {
|
||||||
VoidCallback? _listener;
|
VoidCallback? _listener;
|
||||||
|
|
||||||
void setSingleListener(VoidCallback listener) {
|
void setListener(VoidCallback listener) {
|
||||||
if (_listener != null) {
|
if (_listener != null) {
|
||||||
removeListener(_listener!);
|
removeListener(_listener!);
|
||||||
}
|
}
|
||||||
@ -93,7 +96,7 @@ class CellSingleFocusNode extends FocusNode {
|
|||||||
super.addListener(listener);
|
super.addListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeSingleListener() {
|
void removeAllListener() {
|
||||||
if (_listener != null) {
|
if (_listener != null) {
|
||||||
removeListener(_listener!);
|
removeListener(_listener!);
|
||||||
}
|
}
|
||||||
@ -123,9 +126,14 @@ class CellStateNotifier extends ChangeNotifier {
|
|||||||
bool get onEnter => _onEnter;
|
bool get onEnter => _onEnter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract class CellContainerFocustable {
|
||||||
|
// Listen on the requestBeginFocus if the
|
||||||
|
GridCellRequestBeginFocus get requestBeginFocus;
|
||||||
|
}
|
||||||
|
|
||||||
class CellContainer extends StatelessWidget {
|
class CellContainer extends StatelessWidget {
|
||||||
final GridCellWidget child;
|
final GridCellWidget child;
|
||||||
final Widget? expander;
|
final AccessoryBuilder? accessoryBuilder;
|
||||||
final double width;
|
final double width;
|
||||||
final RegionStateNotifier rowStateNotifier;
|
final RegionStateNotifier rowStateNotifier;
|
||||||
const CellContainer({
|
const CellContainer({
|
||||||
@ -133,7 +141,7 @@ class CellContainer extends StatelessWidget {
|
|||||||
required this.child,
|
required this.child,
|
||||||
required this.width,
|
required this.width,
|
||||||
required this.rowStateNotifier,
|
required this.rowStateNotifier,
|
||||||
this.expander,
|
this.accessoryBuilder,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -145,17 +153,21 @@ 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(() {
|
child.isFocus.addListener(() {
|
||||||
Provider.of<CellStateNotifier>(context, listen: false).isFocus = child.onFocus.value;
|
Provider.of<CellStateNotifier>(context, listen: false).isFocus = child.isFocus.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (expander != null) {
|
if (accessoryBuilder != null) {
|
||||||
container = CellEnterRegion(child: container, expander: expander!);
|
final buildContext = GridCellAccessoryBuildContext(anchorContext: context);
|
||||||
|
final accessories = accessoryBuilder!(buildContext);
|
||||||
|
if (accessories.isNotEmpty) {
|
||||||
|
container = CellEnterRegion(child: container, accessories: accessories);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
behavior: HitTestBehavior.translucent,
|
behavior: HitTestBehavior.translucent,
|
||||||
onTap: () => child.requestFocus.notify(),
|
onTap: () => child.requestBeginFocus.notify(),
|
||||||
child: Container(
|
child: Container(
|
||||||
constraints: BoxConstraints(maxWidth: width, minHeight: 46),
|
constraints: BoxConstraints(maxWidth: width, minHeight: 46),
|
||||||
decoration: _makeBoxDecoration(context, isFocus),
|
decoration: _makeBoxDecoration(context, isFocus),
|
||||||
@ -182,8 +194,8 @@ class CellContainer extends StatelessWidget {
|
|||||||
|
|
||||||
class CellEnterRegion extends StatelessWidget {
|
class CellEnterRegion extends StatelessWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
final Widget expander;
|
final List<GridCellAccessory> accessories;
|
||||||
const CellEnterRegion({required this.child, required this.expander, Key? key}) : super(key: key);
|
const CellEnterRegion({required this.child, required this.accessories, Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -192,7 +204,7 @@ class CellEnterRegion extends StatelessWidget {
|
|||||||
builder: (context, onEnter, _) {
|
builder: (context, onEnter, _) {
|
||||||
List<Widget> children = [child];
|
List<Widget> children = [child];
|
||||||
if (onEnter) {
|
if (onEnter) {
|
||||||
children.add(expander.positioned(right: 0));
|
children.add(AccessoryContainer(accessories: accessories).positioned(right: 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
return MouseRegion(
|
return MouseRegion(
|
||||||
@ -202,7 +214,6 @@ class CellEnterRegion extends StatelessWidget {
|
|||||||
child: Stack(
|
child: Stack(
|
||||||
alignment: AlignmentDirectional.center,
|
alignment: AlignmentDirectional.center,
|
||||||
fit: StackFit.expand,
|
fit: StackFit.expand,
|
||||||
// alignment: AlignmentDirectional.centerEnd,
|
|
||||||
children: children,
|
children: children,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -24,7 +24,7 @@ class _CheckboxCellState extends State<CheckboxCell> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
final cellContext = widget.cellContextBuilder.build();
|
final cellContext = widget.cellContextBuilder.build();
|
||||||
_cellBloc = getIt<CheckboxCellBloc>(param1: cellContext)..add(const CheckboxCellEvent.initial());
|
_cellBloc = getIt<CheckboxCellBloc>(param1: cellContext)..add(const CheckboxCellEvent.initial());
|
||||||
_listenCellRequestFocus();
|
_handleRequestFocus();
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,19 +51,19 @@ class _CheckboxCellState extends State<CheckboxCell> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(covariant CheckboxCell oldWidget) {
|
void didUpdateWidget(covariant CheckboxCell oldWidget) {
|
||||||
_listenCellRequestFocus();
|
_handleRequestFocus();
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
widget.requestFocus.removeAllListener();
|
widget.requestBeginFocus.removeAllListener();
|
||||||
_cellBloc.close();
|
_cellBloc.close();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _listenCellRequestFocus() {
|
void _handleRequestFocus() {
|
||||||
widget.requestFocus.addListener(() {
|
widget.requestBeginFocus.setListener(() {
|
||||||
_cellBloc.add(const CheckboxCellEvent.select());
|
_cellBloc.add(const CheckboxCellEvent.select());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -76,8 +76,8 @@ class _DateCellState extends State<DateCell> {
|
|||||||
|
|
||||||
void _showCalendar(BuildContext context) {
|
void _showCalendar(BuildContext context) {
|
||||||
final bloc = context.read<DateCellBloc>();
|
final bloc = context.read<DateCellBloc>();
|
||||||
widget.onFocus.value = true;
|
widget.isFocus.value = true;
|
||||||
final calendar = DateCellEditor(onDismissed: () => widget.onFocus.value = false);
|
final calendar = DateCellEditor(onDismissed: () => widget.isFocus.value = false);
|
||||||
calendar.show(
|
calendar.show(
|
||||||
context,
|
context,
|
||||||
cellContext: bloc.cellContext.clone(),
|
cellContext: bloc.cellContext.clone(),
|
||||||
|
@ -22,7 +22,7 @@ class NumberCell extends StatefulWidget with GridCellWidget {
|
|||||||
class _NumberCellState extends State<NumberCell> {
|
class _NumberCellState extends State<NumberCell> {
|
||||||
late NumberCellBloc _cellBloc;
|
late NumberCellBloc _cellBloc;
|
||||||
late TextEditingController _controller;
|
late TextEditingController _controller;
|
||||||
late CellSingleFocusNode _focusNode;
|
late SingleListenrFocusNode _focusNode;
|
||||||
Timer? _delayOperation;
|
Timer? _delayOperation;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -30,14 +30,14 @@ class _NumberCellState extends State<NumberCell> {
|
|||||||
final cellContext = widget.cellContextBuilder.build();
|
final cellContext = widget.cellContextBuilder.build();
|
||||||
_cellBloc = getIt<NumberCellBloc>(param1: cellContext)..add(const NumberCellEvent.initial());
|
_cellBloc = getIt<NumberCellBloc>(param1: cellContext)..add(const NumberCellEvent.initial());
|
||||||
_controller = TextEditingController(text: contentFromState(_cellBloc.state));
|
_controller = TextEditingController(text: contentFromState(_cellBloc.state));
|
||||||
_focusNode = CellSingleFocusNode();
|
_focusNode = SingleListenrFocusNode();
|
||||||
_listenFocusNode();
|
_listenOnFocusNodeChanged();
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
_listenCellRequestFocus(context);
|
_handleCellRequestFocus(context);
|
||||||
return BlocProvider.value(
|
return BlocProvider.value(
|
||||||
value: _cellBloc,
|
value: _cellBloc,
|
||||||
child: MultiBlocListener(
|
child: MultiBlocListener(
|
||||||
@ -65,19 +65,17 @@ class _NumberCellState extends State<NumberCell> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
widget.requestFocus.removeAllListener();
|
widget.requestBeginFocus.removeAllListener();
|
||||||
_delayOperation?.cancel();
|
_delayOperation?.cancel();
|
||||||
_cellBloc.close();
|
_cellBloc.close();
|
||||||
_focusNode.removeSingleListener();
|
_focusNode.removeAllListener();
|
||||||
_focusNode.dispose();
|
_focusNode.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(covariant NumberCell oldWidget) {
|
void didUpdateWidget(covariant NumberCell oldWidget) {
|
||||||
if (oldWidget != widget) {
|
_listenOnFocusNodeChanged();
|
||||||
_listenFocusNode();
|
|
||||||
}
|
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,16 +90,16 @@ class _NumberCellState extends State<NumberCell> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _listenFocusNode() {
|
void _listenOnFocusNodeChanged() {
|
||||||
widget.onFocus.value = _focusNode.hasFocus;
|
widget.isFocus.value = _focusNode.hasFocus;
|
||||||
_focusNode.setSingleListener(() {
|
_focusNode.setListener(() {
|
||||||
widget.onFocus.value = _focusNode.hasFocus;
|
widget.isFocus.value = _focusNode.hasFocus;
|
||||||
focusChanged();
|
focusChanged();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _listenCellRequestFocus(BuildContext context) {
|
void _handleCellRequestFocus(BuildContext context) {
|
||||||
widget.requestFocus.addListener(() {
|
widget.requestBeginFocus.setListener(() {
|
||||||
if (_focusNode.hasFocus == false && _focusNode.canRequestFocus) {
|
if (_focusNode.hasFocus == false && _focusNode.canRequestFocus) {
|
||||||
FocusScope.of(context).requestFocus(_focusNode);
|
FocusScope.of(context).requestFocus(_focusNode);
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
|
|||||||
return _SelectOptionCell(
|
return _SelectOptionCell(
|
||||||
selectOptions: state.selectedOptions,
|
selectOptions: state.selectedOptions,
|
||||||
cellStyle: widget.cellStyle,
|
cellStyle: widget.cellStyle,
|
||||||
onFocus: (value) => widget.onFocus.value = value,
|
onFocus: (value) => widget.isFocus.value = value,
|
||||||
cellContextBuilder: widget.cellContextBuilder);
|
cellContextBuilder: widget.cellContextBuilder);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -113,7 +113,7 @@ class _MultiSelectCellState extends State<MultiSelectCell> {
|
|||||||
return _SelectOptionCell(
|
return _SelectOptionCell(
|
||||||
selectOptions: state.selectedOptions,
|
selectOptions: state.selectedOptions,
|
||||||
cellStyle: widget.cellStyle,
|
cellStyle: widget.cellStyle,
|
||||||
onFocus: (value) => widget.onFocus.value = value,
|
onFocus: (value) => widget.isFocus.value = value,
|
||||||
cellContextBuilder: widget.cellContextBuilder);
|
cellContextBuilder: widget.cellContextBuilder);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -35,7 +35,7 @@ class GridTextCell extends StatefulWidget with GridCellWidget {
|
|||||||
class _GridTextCellState extends State<GridTextCell> {
|
class _GridTextCellState extends State<GridTextCell> {
|
||||||
late TextCellBloc _cellBloc;
|
late TextCellBloc _cellBloc;
|
||||||
late TextEditingController _controller;
|
late TextEditingController _controller;
|
||||||
late CellSingleFocusNode _focusNode;
|
late SingleListenrFocusNode _focusNode;
|
||||||
Timer? _delayOperation;
|
Timer? _delayOperation;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -44,9 +44,9 @@ class _GridTextCellState extends State<GridTextCell> {
|
|||||||
_cellBloc = getIt<TextCellBloc>(param1: cellContext);
|
_cellBloc = getIt<TextCellBloc>(param1: cellContext);
|
||||||
_cellBloc.add(const TextCellEvent.initial());
|
_cellBloc.add(const TextCellEvent.initial());
|
||||||
_controller = TextEditingController(text: _cellBloc.state.content);
|
_controller = TextEditingController(text: _cellBloc.state.content);
|
||||||
_focusNode = CellSingleFocusNode();
|
_focusNode = SingleListenrFocusNode();
|
||||||
|
|
||||||
_listenFocusNode();
|
_listenOnFocusNodeChanged();
|
||||||
_listenRequestFocus(context);
|
_listenRequestFocus(context);
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
@ -81,10 +81,10 @@ class _GridTextCellState extends State<GridTextCell> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
widget.requestFocus.removeAllListener();
|
widget.requestBeginFocus.removeAllListener();
|
||||||
_delayOperation?.cancel();
|
_delayOperation?.cancel();
|
||||||
_cellBloc.close();
|
_cellBloc.close();
|
||||||
_focusNode.removeSingleListener();
|
_focusNode.removeAllListener();
|
||||||
_focusNode.dispose();
|
_focusNode.dispose();
|
||||||
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
@ -93,21 +93,21 @@ class _GridTextCellState extends State<GridTextCell> {
|
|||||||
@override
|
@override
|
||||||
void didUpdateWidget(covariant GridTextCell oldWidget) {
|
void didUpdateWidget(covariant GridTextCell oldWidget) {
|
||||||
if (oldWidget != widget) {
|
if (oldWidget != widget) {
|
||||||
_listenFocusNode();
|
_listenOnFocusNodeChanged();
|
||||||
}
|
}
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _listenFocusNode() {
|
void _listenOnFocusNodeChanged() {
|
||||||
widget.onFocus.value = _focusNode.hasFocus;
|
widget.isFocus.value = _focusNode.hasFocus;
|
||||||
_focusNode.setSingleListener(() {
|
_focusNode.setListener(() {
|
||||||
widget.onFocus.value = _focusNode.hasFocus;
|
widget.isFocus.value = _focusNode.hasFocus;
|
||||||
focusChanged();
|
focusChanged();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _listenRequestFocus(BuildContext context) {
|
void _listenRequestFocus(BuildContext context) {
|
||||||
widget.requestFocus.addListener(() {
|
widget.requestBeginFocus.setListener(() {
|
||||||
if (_focusNode.hasFocus == false && _focusNode.canRequestFocus) {
|
if (_focusNode.hasFocus == false && _focusNode.canRequestFocus) {
|
||||||
FocusScope.of(context).requestFocus(_focusNode);
|
FocusScope.of(context).requestFocus(_focusNode);
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class URLCellEditor extends StatefulWidget {
|
class URLCellEditor extends StatefulWidget with FlowyOverlayDelegate {
|
||||||
final GridURLCellContext cellContext;
|
final GridURLCellContext cellContext;
|
||||||
const URLCellEditor({required this.cellContext, Key? key}) : super(key: key);
|
const URLCellEditor({required this.cellContext, Key? key}) : super(key: key);
|
||||||
|
|
||||||
@ -25,18 +25,27 @@ class URLCellEditor extends StatefulWidget {
|
|||||||
//
|
//
|
||||||
FlowyOverlay.of(context).insertWithAnchor(
|
FlowyOverlay.of(context).insertWithAnchor(
|
||||||
widget: OverlayContainer(
|
widget: OverlayContainer(
|
||||||
child: SizedBox(width: 200, child: editor),
|
child: SizedBox(
|
||||||
|
width: 200,
|
||||||
|
child: Padding(padding: const EdgeInsets.all(6), child: editor),
|
||||||
|
),
|
||||||
constraints: BoxConstraints.loose(const Size(300, 160)),
|
constraints: BoxConstraints.loose(const Size(300, 160)),
|
||||||
),
|
),
|
||||||
identifier: URLCellEditor.identifier(),
|
identifier: URLCellEditor.identifier(),
|
||||||
anchorContext: context,
|
anchorContext: context,
|
||||||
anchorDirection: AnchorDirection.bottomWithCenterAligned,
|
anchorDirection: AnchorDirection.bottomWithCenterAligned,
|
||||||
|
delegate: editor,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static String identifier() {
|
static String identifier() {
|
||||||
return (URLCellEditor).toString();
|
return (URLCellEditor).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool asBarrier() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _URLCellEditorState extends State<URLCellEditor> {
|
class _URLCellEditorState extends State<URLCellEditor> {
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||||
import 'package:app_flowy/workspace/application/grid/cell/url_cell_bloc.dart';
|
import 'package:app_flowy/workspace/application/grid/cell/url_cell_bloc.dart';
|
||||||
|
import 'package:app_flowy/workspace/presentation/home/toast.dart';
|
||||||
|
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/cell_accessory.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra/image.dart';
|
import 'package:flowy_infra/image.dart';
|
||||||
import 'package:flowy_infra/theme.dart';
|
import 'package:flowy_infra/theme.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
|
||||||
import 'package:flutter/gestures.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:app_flowy/workspace/application/grid/prelude.dart';
|
import 'package:app_flowy/workspace/application/grid/prelude.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
@ -14,11 +17,19 @@ import 'cell_editor.dart';
|
|||||||
class GridURLCellStyle extends GridCellStyle {
|
class GridURLCellStyle extends GridCellStyle {
|
||||||
String? placeholder;
|
String? placeholder;
|
||||||
|
|
||||||
|
List<GridURLCellAccessoryType> accessoryTypes;
|
||||||
|
|
||||||
GridURLCellStyle({
|
GridURLCellStyle({
|
||||||
this.placeholder,
|
this.placeholder,
|
||||||
|
this.accessoryTypes = const [],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum GridURLCellAccessoryType {
|
||||||
|
edit,
|
||||||
|
copyURL,
|
||||||
|
}
|
||||||
|
|
||||||
class GridURLCell extends StatefulWidget with GridCellWidget {
|
class GridURLCell extends StatefulWidget with GridCellWidget {
|
||||||
final GridCellContextBuilder cellContextBuilder;
|
final GridCellContextBuilder cellContextBuilder;
|
||||||
late final GridURLCellStyle? cellStyle;
|
late final GridURLCellStyle? cellStyle;
|
||||||
@ -36,6 +47,35 @@ class GridURLCell extends StatefulWidget with GridCellWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
State<GridURLCell> createState() => _GridURLCellState();
|
State<GridURLCell> createState() => _GridURLCellState();
|
||||||
|
|
||||||
|
GridCellAccessory accessoryFromType(GridURLCellAccessoryType ty, GridCellAccessoryBuildContext buildContext) {
|
||||||
|
switch (ty) {
|
||||||
|
case GridURLCellAccessoryType.edit:
|
||||||
|
final cellContext = cellContextBuilder.build() as GridURLCellContext;
|
||||||
|
return _EditURLAccessory(cellContext: cellContext, anchorContext: buildContext.anchorContext);
|
||||||
|
|
||||||
|
case GridURLCellAccessoryType.copyURL:
|
||||||
|
final cellContext = cellContextBuilder.build() as GridURLCellContext;
|
||||||
|
return _CopyURLAccessory(cellContext: cellContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<GridCellAccessory> Function(GridCellAccessoryBuildContext buildContext) get accessoryBuilder => (buildContext) {
|
||||||
|
final List<GridCellAccessory> accessories = [];
|
||||||
|
if (cellStyle != null) {
|
||||||
|
accessories.addAll(cellStyle!.accessoryTypes.map((ty) {
|
||||||
|
return accessoryFromType(ty, buildContext);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the accessories is empty then the default accessory will be GridURLCellAccessoryType.edit
|
||||||
|
if (accessories.isEmpty) {
|
||||||
|
accessories.add(accessoryFromType(GridURLCellAccessoryType.edit, buildContext));
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessories;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
class _GridURLCellState extends State<GridURLCell> {
|
class _GridURLCellState extends State<GridURLCell> {
|
||||||
@ -46,7 +86,7 @@ class _GridURLCellState extends State<GridURLCell> {
|
|||||||
final cellContext = widget.cellContextBuilder.build() as GridURLCellContext;
|
final cellContext = widget.cellContextBuilder.build() as GridURLCellContext;
|
||||||
_cellBloc = URLCellBloc(cellContext: cellContext);
|
_cellBloc = URLCellBloc(cellContext: cellContext);
|
||||||
_cellBloc.add(const URLCellEvent.initial());
|
_cellBloc.add(const URLCellEvent.initial());
|
||||||
_listenRequestFocus(context);
|
_handleRequestFocus();
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,17 +106,18 @@ class _GridURLCellState extends State<GridURLCell> {
|
|||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
decoration: TextDecoration.underline,
|
decoration: TextDecoration.underline,
|
||||||
),
|
),
|
||||||
recognizer: _tapGesture(context),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return CellEnterRegion(
|
return SizedBox.expand(
|
||||||
|
child: GestureDetector(
|
||||||
child: Align(alignment: Alignment.centerLeft, child: richText),
|
child: Align(alignment: Alignment.centerLeft, child: richText),
|
||||||
expander: _EditCellIndicator(onTap: () {
|
onTap: () async {
|
||||||
final cellContext = widget.cellContextBuilder.build() as GridURLCellContext;
|
widget.isFocus.value = true;
|
||||||
URLCellEditor.show(context, cellContext);
|
final url = context.read<URLCellBloc>().state.url;
|
||||||
}),
|
await _openUrlOrEdit(url);
|
||||||
);
|
},
|
||||||
|
));
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -84,18 +125,15 @@ class _GridURLCellState extends State<GridURLCell> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
widget.requestFocus.removeAllListener();
|
widget.requestBeginFocus.removeAllListener();
|
||||||
_cellBloc.close();
|
_cellBloc.close();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
TapGestureRecognizer _tapGesture(BuildContext context) {
|
@override
|
||||||
final gesture = TapGestureRecognizer();
|
void didUpdateWidget(covariant GridURLCell oldWidget) {
|
||||||
gesture.onTap = () async {
|
_handleRequestFocus();
|
||||||
final url = context.read<URLCellBloc>().state.url;
|
super.didUpdateWidget(oldWidget);
|
||||||
await _openUrlOrEdit(url);
|
|
||||||
};
|
|
||||||
return gesture;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _openUrlOrEdit(String url) async {
|
Future<void> _openUrlOrEdit(String url) async {
|
||||||
@ -108,27 +146,48 @@ class _GridURLCellState extends State<GridURLCell> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _listenRequestFocus(BuildContext context) {
|
void _handleRequestFocus() {
|
||||||
widget.requestFocus.addListener(() {
|
widget.requestBeginFocus.setListener(() {
|
||||||
_openUrlOrEdit(_cellBloc.state.url);
|
_openUrlOrEdit(_cellBloc.state.url);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _EditCellIndicator extends StatelessWidget {
|
class _EditURLAccessory extends StatelessWidget with GridCellAccessory {
|
||||||
final VoidCallback onTap;
|
final GridURLCellContext cellContext;
|
||||||
const _EditCellIndicator({required this.onTap, Key? key}) : super(key: key);
|
final BuildContext anchorContext;
|
||||||
|
const _EditURLAccessory({
|
||||||
|
required this.cellContext,
|
||||||
|
required this.anchorContext,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = context.watch<AppTheme>();
|
final theme = context.watch<AppTheme>();
|
||||||
return FlowyIconButton(
|
return svgWidget("editor/edit", color: theme.iconColor);
|
||||||
width: 26,
|
}
|
||||||
onPressed: onTap,
|
|
||||||
hoverColor: theme.hover,
|
@override
|
||||||
radius: BorderRadius.circular(4),
|
void onTap() {
|
||||||
iconPadding: const EdgeInsets.all(5),
|
URLCellEditor.show(anchorContext, cellContext);
|
||||||
icon: svgWidget("editor/edit", color: theme.iconColor),
|
}
|
||||||
);
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
final content = cellContext.getCellData(loadIfNoCache: false)?.content ?? "";
|
||||||
|
Clipboard.setData(ClipboardData(text: content));
|
||||||
|
showMessageToast(LocaleKeys.grid_row_copyProperty.tr());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,7 +86,6 @@ class _GridHeaderCellContainer extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = context.watch<AppTheme>();
|
final theme = context.watch<AppTheme>();
|
||||||
final borderSide = BorderSide(color: theme.shader5, width: 1.0);
|
final borderSide = BorderSide(color: theme.shader5, width: 1.0);
|
||||||
|
|
||||||
final decoration = BoxDecoration(
|
final decoration = BoxDecoration(
|
||||||
border: Border(
|
border: Border(
|
||||||
top: borderSide,
|
top: borderSide,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
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/layout/sizes.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/prelude.dart';
|
||||||
import 'package:flowy_infra/image.dart';
|
import 'package:flowy_infra/image.dart';
|
||||||
import 'package:flowy_infra/theme.dart';
|
import 'package:flowy_infra/theme.dart';
|
||||||
@ -170,16 +171,30 @@ class _RowCells extends StatelessWidget {
|
|||||||
List<Widget> _makeCells(BuildContext context, GridCellMap gridCellMap) {
|
List<Widget> _makeCells(BuildContext context, GridCellMap gridCellMap) {
|
||||||
return gridCellMap.values.map(
|
return gridCellMap.values.map(
|
||||||
(gridCell) {
|
(gridCell) {
|
||||||
Widget? expander;
|
final GridCellWidget child = buildGridCellWidget(gridCell, cellCache);
|
||||||
|
List<GridCellAccessory> accessories = [];
|
||||||
if (gridCell.field.isPrimary) {
|
if (gridCell.field.isPrimary) {
|
||||||
expander = _CellExpander(onExpand: onExpand);
|
accessories.add(_PrimaryCellAccessory(onTapCallback: onExpand));
|
||||||
|
}
|
||||||
|
|
||||||
|
accessoryBuilder(buildContext) {
|
||||||
|
final builder = child.accessoryBuilder;
|
||||||
|
List<GridCellAccessory> accessories = [];
|
||||||
|
if (gridCell.field.isPrimary) {
|
||||||
|
accessories.add(_PrimaryCellAccessory(onTapCallback: onExpand));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (builder != null) {
|
||||||
|
accessories.addAll(builder(buildContext));
|
||||||
|
}
|
||||||
|
return accessories;
|
||||||
}
|
}
|
||||||
|
|
||||||
return CellContainer(
|
return CellContainer(
|
||||||
width: gridCell.field.width.toDouble(),
|
width: gridCell.field.width.toDouble(),
|
||||||
child: buildGridCellWidget(gridCell, cellCache),
|
child: child,
|
||||||
rowStateNotifier: Provider.of<RegionStateNotifier>(context, listen: false),
|
rowStateNotifier: Provider.of<RegionStateNotifier>(context, listen: false),
|
||||||
expander: expander,
|
accessoryBuilder: accessoryBuilder,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
).toList();
|
).toList();
|
||||||
@ -199,23 +214,19 @@ class RegionStateNotifier extends ChangeNotifier {
|
|||||||
bool get onEnter => _onEnter;
|
bool get onEnter => _onEnter;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CellExpander extends StatelessWidget {
|
class _PrimaryCellAccessory extends StatelessWidget with GridCellAccessory {
|
||||||
final VoidCallback onExpand;
|
final VoidCallback onTapCallback;
|
||||||
const _CellExpander({required this.onExpand, Key? key}) : super(key: key);
|
const _PrimaryCellAccessory({required this.onTapCallback, Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = context.watch<AppTheme>();
|
final theme = context.watch<AppTheme>();
|
||||||
return FittedBox(
|
return svgWidget("grid/expander", color: theme.main1);
|
||||||
fit: BoxFit.contain,
|
}
|
||||||
child: FlowyIconButton(
|
|
||||||
width: 26,
|
@override
|
||||||
onPressed: onExpand,
|
void onTap() {
|
||||||
iconPadding: const EdgeInsets.all(5),
|
onTapCallback();
|
||||||
radius: BorderRadius.circular(4),
|
|
||||||
icon: svgWidget("grid/expander", color: theme.main1),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
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';
|
|
||||||
|
|
||||||
class NumberCell extends StatefulWidget {
|
|
||||||
final GridCell cellData;
|
|
||||||
|
|
||||||
const NumberCell({
|
|
||||||
required this.cellData,
|
|
||||||
Key? key,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<NumberCell> createState() => _NumberCellState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _NumberCellState extends State<NumberCell> {
|
|
||||||
late NumberCellBloc _cellBloc;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
_cellBloc = getIt<NumberCellBloc>(param1: widget.cellData);
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocProvider.value(
|
|
||||||
value: _cellBloc,
|
|
||||||
child: BlocBuilder<NumberCellBloc, NumberCellState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
return Container();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> dispose() async {
|
|
||||||
_cellBloc.close();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
@ -24,6 +24,7 @@ class GridRowActionSheet extends StatelessWidget {
|
|||||||
child: BlocBuilder<RowActionSheetBloc, RowActionSheetState>(
|
child: BlocBuilder<RowActionSheetBloc, RowActionSheetState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final cells = _RowAction.values
|
final cells = _RowAction.values
|
||||||
|
.where((value) => value.enable())
|
||||||
.map(
|
.map(
|
||||||
(action) => _RowActionCell(
|
(action) => _RowActionCell(
|
||||||
action: action,
|
action: action,
|
||||||
|
@ -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_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/layout/sizes.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/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/cell/url_cell/url_cell.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart';
|
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart';
|
||||||
@ -10,7 +11,6 @@ import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header
|
|||||||
import 'package:flowy_infra/image.dart';
|
import 'package:flowy_infra/image.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/hover.dart';
|
|
||||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
import 'package:flowy_infra_ui/style_widget/icon_button.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';
|
||||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||||
@ -149,12 +149,18 @@ class _RowDetailCell extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = context.watch<AppTheme>();
|
final theme = context.watch<AppTheme>();
|
||||||
|
final style = _customCellStyle(theme, gridCell.field.fieldType);
|
||||||
|
final cell = buildGridCellWidget(gridCell, cellCache, style: style);
|
||||||
|
|
||||||
final cell = buildGridCellWidget(
|
final gesture = GestureDetector(
|
||||||
gridCell,
|
behavior: HitTestBehavior.translucent,
|
||||||
cellCache,
|
onTap: () => cell.requestBeginFocus.notify(),
|
||||||
style: _buildCellStyle(theme, gridCell.field.fieldType),
|
child: AccessoryHover(
|
||||||
|
child: cell,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 12),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return ConstrainedBox(
|
return ConstrainedBox(
|
||||||
constraints: const BoxConstraints(minHeight: 40),
|
constraints: const BoxConstraints(minHeight: 40),
|
||||||
child: IntrinsicHeight(
|
child: IntrinsicHeight(
|
||||||
@ -167,12 +173,7 @@ class _RowDetailCell extends StatelessWidget {
|
|||||||
child: FieldCellButton(field: gridCell.field, onTap: () => _showFieldEditor(context)),
|
child: FieldCellButton(field: gridCell.field, onTap: () => _showFieldEditor(context)),
|
||||||
),
|
),
|
||||||
const HSpace(10),
|
const HSpace(10),
|
||||||
Expanded(
|
Expanded(child: gesture),
|
||||||
child: FlowyHover2(
|
|
||||||
child: cell,
|
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -191,7 +192,7 @@ class _RowDetailCell extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GridCellStyle? _buildCellStyle(AppTheme theme, FieldType fieldType) {
|
GridCellStyle? _customCellStyle(AppTheme theme, FieldType fieldType) {
|
||||||
switch (fieldType) {
|
switch (fieldType) {
|
||||||
case FieldType.Checkbox:
|
case FieldType.Checkbox:
|
||||||
return null;
|
return null;
|
||||||
@ -217,7 +218,11 @@ GridCellStyle? _buildCellStyle(AppTheme theme, FieldType fieldType) {
|
|||||||
case FieldType.URL:
|
case FieldType.URL:
|
||||||
return GridURLCellStyle(
|
return GridURLCellStyle(
|
||||||
placeholder: LocaleKeys.grid_row_textPlaceholder.tr(),
|
placeholder: LocaleKeys.grid_row_textPlaceholder.tr(),
|
||||||
|
accessoryTypes: [
|
||||||
|
GridURLCellAccessoryType.edit,
|
||||||
|
GridURLCellAccessoryType.copyURL,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
throw UnimplementedError;
|
||||||
}
|
}
|
||||||
|
@ -85,7 +85,7 @@ class GridSettingList extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _renderList() {
|
Widget _renderList() {
|
||||||
final cells = GridSettingAction.values.map((action) {
|
final cells = GridSettingAction.values.where((value) => value.enable()).map((action) {
|
||||||
return _SettingItem(action: action);
|
return _SettingItem(action: action);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import 'package:app_flowy/startup/tasks/rust_sdk.dart';
|
import 'package:app_flowy/startup/tasks/rust_sdk.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
|
import 'package:app_flowy/workspace/presentation/home/toast.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
|
import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra/theme.dart';
|
import 'package:flowy_infra/theme.dart';
|
||||||
@ -16,7 +16,6 @@ import 'package:package_info_plus/package_info_plus.dart';
|
|||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
|
||||||
|
|
||||||
class QuestionBubble extends StatelessWidget {
|
class QuestionBubble extends StatelessWidget {
|
||||||
const QuestionBubble({Key? key}) : super(key: key);
|
const QuestionBubble({Key? key}) : super(key: key);
|
||||||
@ -46,7 +45,7 @@ class QuestionBubble extends StatelessWidget {
|
|||||||
_launchURL("https://discord.gg/9Q2xaN37tV");
|
_launchURL("https://discord.gg/9Q2xaN37tV");
|
||||||
break;
|
break;
|
||||||
case BubbleAction.debug:
|
case BubbleAction.debug:
|
||||||
const _DebugToast().show();
|
_DebugToast().show();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -71,55 +70,14 @@ class QuestionBubble extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DebugToast extends StatelessWidget {
|
class _DebugToast {
|
||||||
const _DebugToast({Key? key}) : super(key: key);
|
void show() async {
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return FutureBuilder(
|
|
||||||
future: Future(() async {
|
|
||||||
var debugInfo = "";
|
var debugInfo = "";
|
||||||
debugInfo += await _getDeviceInfo();
|
debugInfo += await _getDeviceInfo();
|
||||||
debugInfo += await _getDocumentPath();
|
debugInfo += await _getDocumentPath();
|
||||||
|
|
||||||
Clipboard.setData(ClipboardData(text: debugInfo));
|
Clipboard.setData(ClipboardData(text: debugInfo));
|
||||||
}),
|
|
||||||
builder: (BuildContext context, AsyncSnapshot<void> snapshot) {
|
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
|
||||||
if (snapshot.hasError) {
|
|
||||||
return _done(context, Text("Error: ${snapshot.error}"));
|
|
||||||
} else {
|
|
||||||
return _done(context, null);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return const CircularProgressIndicator();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _done(BuildContext context, Widget? error) {
|
showMessageToast(LocaleKeys.questionBubble_debug_success.tr());
|
||||||
final theme = context.watch<AppTheme>();
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 12.0),
|
|
||||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(25.0), color: theme.main1),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.check),
|
|
||||||
const SizedBox(width: 12.0),
|
|
||||||
(error == null) ? Text(LocaleKeys.questionBubble_debug_success.tr()) : error
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void show() {
|
|
||||||
fToast.showToast(
|
|
||||||
child: this,
|
|
||||||
gravity: ToastGravity.BOTTOM,
|
|
||||||
toastDuration: const Duration(seconds: 3),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> _getDeviceInfo() async {
|
Future<String> _getDeviceInfo() async {
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
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);
|
||||||
|
|
||||||
@ -52,7 +49,7 @@ class _FlowyHoverState extends State<FlowyHover> {
|
|||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return child;
|
return Container(child: child, color: widget.style.backgroundColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -63,12 +60,14 @@ class HoverStyle {
|
|||||||
final Color hoverColor;
|
final Color hoverColor;
|
||||||
final BorderRadius borderRadius;
|
final BorderRadius borderRadius;
|
||||||
final EdgeInsets contentMargin;
|
final EdgeInsets contentMargin;
|
||||||
|
final Color backgroundColor;
|
||||||
|
|
||||||
const HoverStyle(
|
const HoverStyle(
|
||||||
{this.borderColor = Colors.transparent,
|
{this.borderColor = Colors.transparent,
|
||||||
this.borderWidth = 0,
|
this.borderWidth = 0,
|
||||||
this.borderRadius = const BorderRadius.all(Radius.circular(6)),
|
this.borderRadius = const BorderRadius.all(Radius.circular(6)),
|
||||||
this.contentMargin = EdgeInsets.zero,
|
this.contentMargin = EdgeInsets.zero,
|
||||||
|
this.backgroundColor = Colors.transparent,
|
||||||
required this.hoverColor});
|
required this.hoverColor});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,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;
|
|
||||||
}
|
|
||||||
|
@ -72,7 +72,7 @@ dependencies:
|
|||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
cupertino_icons: ^1.0.2
|
cupertino_icons: ^1.0.2
|
||||||
device_info_plus: ^3.2.1
|
device_info_plus: ^3.2.1
|
||||||
fluttertoast: ^8.0.8
|
fluttertoast: ^8.0.9
|
||||||
table_calendar: ^3.0.5
|
table_calendar: ^3.0.5
|
||||||
reorderables: ^0.5.0
|
reorderables: ^0.5.0
|
||||||
linked_scroll_controller: ^0.2.0
|
linked_scroll_controller: ^0.2.0
|
||||||
|
Reference in New Issue
Block a user