mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Merge pull request #467 from AppFlowy-IO/fix_reorder_animation
Fix: reorder animation
This commit is contained in:
commit
f2cd3846f1
@ -161,11 +161,12 @@ class AppViewDataContext extends ChangeNotifier {
|
||||
final String appId;
|
||||
final ValueNotifier<List<View>> _viewsNotifier = ValueNotifier([]);
|
||||
final ValueNotifier<View?> _selectedViewNotifier = ValueNotifier(null);
|
||||
VoidCallback? _menuSharedStateListener;
|
||||
ExpandableController expandController = ExpandableController(initialExpanded: false);
|
||||
|
||||
AppViewDataContext({required this.appId}) {
|
||||
_setLatestView(getIt<MenuSharedState>().latestOpenView);
|
||||
getIt<MenuSharedState>().addLatestViewListener((view) {
|
||||
_menuSharedStateListener = getIt<MenuSharedState>().addLatestViewListener((view) {
|
||||
_setLatestView(view);
|
||||
});
|
||||
}
|
||||
@ -234,4 +235,12 @@ class AppViewDataContext extends ChangeNotifier {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (_menuSharedStateListener != null) {
|
||||
getIt<MenuSharedState>().removeLatestViewListener(_menuSharedStateListener!);
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:app_flowy/workspace/application/grid/cell/select_option_service.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
@ -10,13 +12,52 @@ import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
part 'cell_service.freezed.dart';
|
||||
|
||||
typedef GridDefaultCellContext = GridCellContext<Cell>;
|
||||
typedef GridSelectOptionCellContext = GridCellContext<SelectOptionContext>;
|
||||
|
||||
class GridCellContextBuilder {
|
||||
final GridCellCache _cellCache;
|
||||
final GridCell _gridCell;
|
||||
GridCellContextBuilder({
|
||||
required GridCellCache cellCache,
|
||||
required GridCell gridCell,
|
||||
}) : _cellCache = cellCache,
|
||||
_gridCell = gridCell;
|
||||
|
||||
GridCellContext build() {
|
||||
switch (_gridCell.field.fieldType) {
|
||||
case FieldType.Checkbox:
|
||||
case FieldType.DateTime:
|
||||
case FieldType.Number:
|
||||
return GridDefaultCellContext(
|
||||
gridCell: _gridCell,
|
||||
cellCache: _cellCache,
|
||||
cellDataLoader: DefaultCellDataLoader(gridCell: _gridCell, reloadOnCellChanged: true),
|
||||
);
|
||||
case FieldType.RichText:
|
||||
return GridDefaultCellContext(
|
||||
gridCell: _gridCell,
|
||||
cellCache: _cellCache,
|
||||
cellDataLoader: DefaultCellDataLoader(gridCell: _gridCell),
|
||||
);
|
||||
case FieldType.MultiSelect:
|
||||
case FieldType.SingleSelect:
|
||||
return GridSelectOptionCellContext(
|
||||
gridCell: _gridCell,
|
||||
cellCache: _cellCache,
|
||||
cellDataLoader: SelectOptionCellDataLoader(gridCell: _gridCell),
|
||||
);
|
||||
default:
|
||||
throw UnimplementedError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class GridCellContext<T> extends Equatable {
|
||||
final GridCell gridCell;
|
||||
|
@ -27,14 +27,26 @@ class GridHeaderBloc extends Bloc<GridHeaderEvent, GridHeaderState> {
|
||||
emit(state.copyWith(fields: value.fields));
|
||||
},
|
||||
moveField: (_MoveField value) async {
|
||||
final result = await _fieldService.moveField(value.field.id, value.fromIndex, value.toIndex);
|
||||
result.fold((l) {}, (err) => Log.error(err));
|
||||
await _moveField(value, emit);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _moveField(_MoveField value, Emitter<GridHeaderState> emit) async {
|
||||
final fields = List<Field>.from(state.fields);
|
||||
fields.insert(value.toIndex, fields.removeAt(value.fromIndex));
|
||||
emit(state.copyWith(fields: fields));
|
||||
|
||||
final result = await _fieldService.moveField(
|
||||
value.field.id,
|
||||
value.fromIndex,
|
||||
value.toIndex,
|
||||
);
|
||||
result.fold((l) {}, (err) => Log.error(err));
|
||||
}
|
||||
|
||||
Future<void> _startListening() async {
|
||||
fieldCache.addListener(
|
||||
onChanged: (fields) => add(GridHeaderEvent.didReceiveFieldUpdate(fields)),
|
||||
|
@ -45,6 +45,9 @@ class MenuBloc extends Bloc<MenuEvent, MenuState> {
|
||||
if (state.apps.length > value.fromIndex) {
|
||||
final app = state.apps[value.fromIndex];
|
||||
_workspaceService.moveApp(appId: app.id, fromIndex: value.fromIndex, toIndex: value.toIndex);
|
||||
final apps = List<App>.from(state.apps);
|
||||
apps.insert(value.toIndex, apps.removeAt(value.fromIndex));
|
||||
emit(state.copyWith(apps: apps));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -32,7 +32,7 @@ class ViewSectionBloc extends Bloc<ViewSectionEvent, ViewSectionState> {
|
||||
emit(state.copyWith(views: value.views));
|
||||
},
|
||||
moveView: (_MoveView value) async {
|
||||
await _moveView(value);
|
||||
_moveView(value, emit);
|
||||
},
|
||||
);
|
||||
});
|
||||
@ -59,9 +59,13 @@ class ViewSectionBloc extends Bloc<ViewSectionEvent, ViewSectionState> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _moveView(_MoveView value) async {
|
||||
Future<void> _moveView(_MoveView value, Emitter<ViewSectionState> emit) async {
|
||||
if (value.fromIndex < state.views.length) {
|
||||
final viewId = state.views[value.fromIndex].id;
|
||||
final views = List<View>.from(state.views);
|
||||
views.insert(value.toIndex, views.removeAt(value.fromIndex));
|
||||
emit(state.copyWith(views: views));
|
||||
|
||||
final result = await _appService.moveView(
|
||||
viewId: viewId,
|
||||
fromIndex: value.fromIndex,
|
||||
|
@ -11,7 +11,7 @@ import 'section/section.dart';
|
||||
|
||||
class MenuApp extends StatefulWidget {
|
||||
final App app;
|
||||
MenuApp(this.app, {Key? key}) : super(key: ValueKey(app.hashCode));
|
||||
const MenuApp(this.app, {Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<MenuApp> createState() => _MenuAppState();
|
||||
@ -95,6 +95,11 @@ class _MenuAppState extends State<MenuApp> {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant MenuApp oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
viewDataContext.dispose();
|
||||
|
@ -107,7 +107,7 @@ class HomeMenu extends StatelessWidget {
|
||||
child: ScrollConfiguration(
|
||||
behavior: const ScrollBehavior().copyWith(scrollbars: false),
|
||||
child: BlocSelector<MenuBloc, MenuState, List<Widget>>(
|
||||
selector: (state) => state.apps.map((app) => MenuApp(app)).toList(),
|
||||
selector: (state) => state.apps.map((app) => MenuApp(app, key: ValueKey(app.id))).toList(),
|
||||
builder: (context, menuItems) {
|
||||
return ReorderableListView.builder(
|
||||
itemCount: menuItems.length,
|
||||
@ -116,11 +116,18 @@ class HomeMenu extends StatelessWidget {
|
||||
padding: EdgeInsets.only(bottom: 20.0 - MenuAppSizes.appVPadding),
|
||||
child: MenuUser(user),
|
||||
),
|
||||
onReorder: (oldIndex, newIndex) => context.read<MenuBloc>().add(MenuEvent.moveApp(oldIndex, newIndex)),
|
||||
onReorder: (oldIndex, newIndex) {
|
||||
// Moving item1 from index 0 to index 1
|
||||
// expect: oldIndex: 0, newIndex: 1
|
||||
// receive: oldIndex: 0, newIndex: 2
|
||||
// Workaround: if newIndex > oldIndex, we just minus one
|
||||
int index = newIndex > oldIndex ? newIndex - 1 : newIndex;
|
||||
context.read<MenuBloc>().add(MenuEvent.moveApp(oldIndex, index));
|
||||
},
|
||||
physics: StyledScrollPhysics(),
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return ReorderableDragStartListener(
|
||||
key: ValueKey(menuItems[index].hashCode),
|
||||
key: ValueKey(menuItems[index].key),
|
||||
index: index,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: MenuAppSizes.appVPadding / 2),
|
||||
|
@ -1,5 +1,4 @@
|
||||
import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/cell/select_option_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';
|
||||
@ -12,49 +11,22 @@ import 'text_cell.dart';
|
||||
GridCellWidget buildGridCellWidget(GridCell gridCell, GridCellCache cellCache, {GridCellStyle? style}) {
|
||||
final key = ValueKey(gridCell.rowId + gridCell.field.id);
|
||||
|
||||
final cellContext = makeCellContext(gridCell, cellCache);
|
||||
final cellContextBuilder = GridCellContextBuilder(gridCell: gridCell, cellCache: cellCache);
|
||||
|
||||
switch (gridCell.field.fieldType) {
|
||||
case FieldType.Checkbox:
|
||||
return CheckboxCell(cellContext: cellContext, key: key);
|
||||
return CheckboxCell(cellContextBuilder: cellContextBuilder, key: key);
|
||||
case FieldType.DateTime:
|
||||
return DateCell(cellContext: cellContext, key: key);
|
||||
case FieldType.MultiSelect:
|
||||
return MultiSelectCell(cellContext: cellContext as GridSelectOptionCellContext, style: style, key: key);
|
||||
case FieldType.Number:
|
||||
return NumberCell(cellContext: cellContext, key: key);
|
||||
case FieldType.RichText:
|
||||
return GridTextCell(cellContext: cellContext, style: style, key: key);
|
||||
return DateCell(cellContextBuilder: cellContextBuilder, key: key);
|
||||
case FieldType.SingleSelect:
|
||||
return SingleSelectCell(cellContext: cellContext as GridSelectOptionCellContext, style: style, key: key);
|
||||
default:
|
||||
throw UnimplementedError;
|
||||
}
|
||||
}
|
||||
return SingleSelectCell(cellContextBuilder: cellContextBuilder, style: style, key: key);
|
||||
case FieldType.MultiSelect:
|
||||
return MultiSelectCell(cellContextBuilder: cellContextBuilder, style: style, key: key);
|
||||
case FieldType.Number:
|
||||
return NumberCell(cellContextBuilder: cellContextBuilder, key: key);
|
||||
case FieldType.RichText:
|
||||
return GridTextCell(cellContextBuilder: cellContextBuilder, style: style, key: key);
|
||||
|
||||
GridCellContext makeCellContext(GridCell gridCell, GridCellCache cellCache) {
|
||||
switch (gridCell.field.fieldType) {
|
||||
case FieldType.Checkbox:
|
||||
case FieldType.DateTime:
|
||||
case FieldType.Number:
|
||||
return GridDefaultCellContext(
|
||||
gridCell: gridCell,
|
||||
cellCache: cellCache,
|
||||
cellDataLoader: DefaultCellDataLoader(gridCell: gridCell, reloadOnCellChanged: true),
|
||||
);
|
||||
case FieldType.RichText:
|
||||
return GridDefaultCellContext(
|
||||
gridCell: gridCell,
|
||||
cellCache: cellCache,
|
||||
cellDataLoader: DefaultCellDataLoader(gridCell: gridCell),
|
||||
);
|
||||
case FieldType.MultiSelect:
|
||||
case FieldType.SingleSelect:
|
||||
return GridSelectOptionCellContext(
|
||||
gridCell: gridCell,
|
||||
cellCache: cellCache,
|
||||
cellDataLoader: SelectOptionCellDataLoader(gridCell: gridCell),
|
||||
);
|
||||
default:
|
||||
throw UnimplementedError;
|
||||
}
|
||||
@ -72,7 +44,16 @@ class BlankCell extends StatelessWidget {
|
||||
abstract class GridCellWidget extends HoverWidget {
|
||||
@override
|
||||
final ValueNotifier<bool> onFocus = ValueNotifier<bool>(false);
|
||||
|
||||
final GridCellRequestFocusNotifier requestFocus = GridCellRequestFocusNotifier();
|
||||
|
||||
GridCellWidget({Key? key}) : super(key: key);
|
||||
}
|
||||
|
||||
class GridCellRequestFocusNotifier extends ChangeNotifier {
|
||||
void notify() {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class GridCellStyle {}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@ -31,17 +32,20 @@ class CellContainer extends StatelessWidget {
|
||||
final GridCellWidget child;
|
||||
final Widget? expander;
|
||||
final double width;
|
||||
final RegionStateNotifier rowStateNotifier;
|
||||
const CellContainer({
|
||||
Key? key,
|
||||
required this.child,
|
||||
required this.width,
|
||||
required this.rowStateNotifier,
|
||||
this.expander,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProvider(
|
||||
return ChangeNotifierProxyProvider<RegionStateNotifier, CellStateNotifier>(
|
||||
create: (_) => CellStateNotifier(),
|
||||
update: (_, row, cell) => cell!..onEnter = row.onEnter,
|
||||
child: Selector<CellStateNotifier, bool>(
|
||||
selector: (context, notifier) => notifier.isFocus,
|
||||
builder: (context, isFocus, _) {
|
||||
@ -54,11 +58,15 @@ class CellContainer extends StatelessWidget {
|
||||
container = _CellEnterRegion(child: container, expander: expander!);
|
||||
}
|
||||
|
||||
return Container(
|
||||
constraints: BoxConstraints(maxWidth: width),
|
||||
decoration: _makeBoxDecoration(context, isFocus),
|
||||
padding: GridSize.cellContentInsets,
|
||||
child: container,
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () => child.requestFocus.notify(),
|
||||
child: Container(
|
||||
constraints: BoxConstraints(maxWidth: width),
|
||||
decoration: _makeBoxDecoration(context, isFocus),
|
||||
padding: GridSize.cellContentInsets,
|
||||
child: container,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
@ -7,10 +7,9 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'cell_builder.dart';
|
||||
|
||||
class CheckboxCell extends GridCellWidget {
|
||||
final GridCellContext cellContext;
|
||||
|
||||
final GridCellContextBuilder cellContextBuilder;
|
||||
CheckboxCell({
|
||||
required this.cellContext,
|
||||
required this.cellContextBuilder,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@ -23,7 +22,9 @@ class _CheckboxCellState extends State<CheckboxCell> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_cellBloc = getIt<CheckboxCellBloc>(param1: widget.cellContext)..add(const CheckboxCellEvent.initial());
|
||||
final cellContext = widget.cellContextBuilder.build();
|
||||
_cellBloc = getIt<CheckboxCellBloc>(param1: cellContext)..add(const CheckboxCellEvent.initial());
|
||||
_listenCellRequestFocus();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@ -48,9 +49,21 @@ class _CheckboxCellState extends State<CheckboxCell> {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant CheckboxCell oldWidget) {
|
||||
_listenCellRequestFocus();
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
_cellBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _listenCellRequestFocus() {
|
||||
widget.requestFocus.addListener(() {
|
||||
_cellBloc.add(const CheckboxCellEvent.select());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -14,10 +14,10 @@ abstract class GridCellDelegate {
|
||||
}
|
||||
|
||||
class DateCell extends GridCellWidget {
|
||||
final GridCellContext cellContext;
|
||||
final GridCellContextBuilder cellContextBuilder;
|
||||
|
||||
DateCell({
|
||||
required this.cellContext,
|
||||
required this.cellContextBuilder,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@ -30,7 +30,8 @@ class _DateCellState extends State<DateCell> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_cellBloc = getIt<DateCellBloc>(param1: widget.cellContext)..add(const DateCellEvent.initial());
|
||||
final cellContext = widget.cellContextBuilder.build();
|
||||
_cellBloc = getIt<DateCellBloc>(param1: cellContext)..add(const DateCellEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
|
@ -8,10 +8,10 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'cell_builder.dart';
|
||||
|
||||
class NumberCell extends GridCellWidget {
|
||||
final GridCellContext cellContext;
|
||||
final GridCellContextBuilder cellContextBuilder;
|
||||
|
||||
NumberCell({
|
||||
required this.cellContext,
|
||||
required this.cellContextBuilder,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@ -23,11 +23,13 @@ class _NumberCellState extends State<NumberCell> {
|
||||
late NumberCellBloc _cellBloc;
|
||||
late TextEditingController _controller;
|
||||
late FocusNode _focusNode;
|
||||
VoidCallback? _focusListener;
|
||||
Timer? _delayOperation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_cellBloc = getIt<NumberCellBloc>(param1: widget.cellContext)..add(const NumberCellEvent.initial());
|
||||
final cellContext = widget.cellContextBuilder.build();
|
||||
_cellBloc = getIt<NumberCellBloc>(param1: cellContext)..add(const NumberCellEvent.initial());
|
||||
_controller = TextEditingController(text: _cellBloc.state.content);
|
||||
_focusNode = FocusNode();
|
||||
_focusNode.addListener(() {
|
||||
@ -39,6 +41,7 @@ class _NumberCellState extends State<NumberCell> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_listenCellRequestFocus(context);
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocConsumer<NumberCellBloc, NumberCellState>(
|
||||
@ -67,6 +70,9 @@ class _NumberCellState extends State<NumberCell> {
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
if (_focusListener != null) {
|
||||
widget.requestFocus.removeListener(_focusListener!);
|
||||
}
|
||||
_delayOperation?.cancel();
|
||||
_cellBloc.close();
|
||||
_focusNode.dispose();
|
||||
@ -88,4 +94,19 @@ class _NumberCellState extends State<NumberCell> {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _listenCellRequestFocus(BuildContext context) {
|
||||
if (_focusListener != null) {
|
||||
widget.requestFocus.removeListener(_focusListener!);
|
||||
}
|
||||
|
||||
focusListener() {
|
||||
if (_focusNode.hasFocus == false && _focusNode.canRequestFocus) {
|
||||
FocusScope.of(context).requestFocus(_focusNode);
|
||||
}
|
||||
}
|
||||
|
||||
_focusListener = focusListener;
|
||||
widget.requestFocus.addListener(focusListener);
|
||||
}
|
||||
}
|
||||
|
@ -20,11 +20,11 @@ class SelectOptionCellStyle extends GridCellStyle {
|
||||
}
|
||||
|
||||
class SingleSelectCell extends GridCellWidget {
|
||||
final GridSelectOptionCellContext cellContext;
|
||||
final GridCellContextBuilder cellContextBuilder;
|
||||
late final SelectOptionCellStyle? cellStyle;
|
||||
|
||||
SingleSelectCell({
|
||||
required this.cellContext,
|
||||
required this.cellContextBuilder,
|
||||
GridCellStyle? style,
|
||||
Key? key,
|
||||
}) : super(key: key) {
|
||||
@ -45,7 +45,8 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
|
||||
@override
|
||||
void initState() {
|
||||
// Log.trace("init widget $hashCode");
|
||||
_cellBloc = getIt<SelectionCellBloc>(param1: widget.cellContext)..add(const SelectionCellEvent.initial());
|
||||
final cellContext = _buildCellContext();
|
||||
_cellBloc = getIt<SelectionCellBloc>(param1: cellContext)..add(const SelectionCellEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@ -69,7 +70,7 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
|
||||
widget.onFocus.value = true;
|
||||
SelectOptionCellEditor.show(
|
||||
context,
|
||||
widget.cellContext.clone(),
|
||||
_buildCellContext(),
|
||||
() => widget.onFocus.value = false,
|
||||
);
|
||||
},
|
||||
@ -81,12 +82,8 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant SingleSelectCell oldWidget) {
|
||||
if (oldWidget.cellContext != widget.cellContext) {
|
||||
// Log.trace("did update widget $hashCode");
|
||||
}
|
||||
super.didUpdateWidget(oldWidget);
|
||||
GridSelectOptionCellContext _buildCellContext() {
|
||||
return widget.cellContextBuilder.build() as GridSelectOptionCellContext;
|
||||
}
|
||||
|
||||
@override
|
||||
@ -99,11 +96,11 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
|
||||
|
||||
//----------------------------------------------------------------
|
||||
class MultiSelectCell extends GridCellWidget {
|
||||
final GridSelectOptionCellContext cellContext;
|
||||
final GridCellContextBuilder cellContextBuilder;
|
||||
late final SelectOptionCellStyle? cellStyle;
|
||||
|
||||
MultiSelectCell({
|
||||
required this.cellContext,
|
||||
required this.cellContextBuilder,
|
||||
GridCellStyle? style,
|
||||
Key? key,
|
||||
}) : super(key: key) {
|
||||
@ -123,7 +120,8 @@ class _MultiSelectCellState extends State<MultiSelectCell> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_cellBloc = getIt<SelectionCellBloc>(param1: widget.cellContext)..add(const SelectionCellEvent.initial());
|
||||
final cellContext = _buildCellContext();
|
||||
_cellBloc = getIt<SelectionCellBloc>(param1: cellContext)..add(const SelectionCellEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@ -145,7 +143,7 @@ class _MultiSelectCellState extends State<MultiSelectCell> {
|
||||
widget.onFocus.value = true;
|
||||
SelectOptionCellEditor.show(
|
||||
context,
|
||||
widget.cellContext,
|
||||
_buildCellContext(),
|
||||
() => widget.onFocus.value = false,
|
||||
);
|
||||
},
|
||||
@ -162,4 +160,8 @@ class _MultiSelectCellState extends State<MultiSelectCell> {
|
||||
_cellBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
GridSelectOptionCellContext _buildCellContext() {
|
||||
return widget.cellContextBuilder.build() as GridSelectOptionCellContext;
|
||||
}
|
||||
}
|
||||
|
@ -14,10 +14,10 @@ class GridTextCellStyle extends GridCellStyle {
|
||||
}
|
||||
|
||||
class GridTextCell extends GridCellWidget {
|
||||
final GridCellContext cellContext;
|
||||
final GridCellContextBuilder cellContextBuilder;
|
||||
late final GridTextCellStyle? cellStyle;
|
||||
GridTextCell({
|
||||
required this.cellContext,
|
||||
required this.cellContextBuilder,
|
||||
GridCellStyle? style,
|
||||
Key? key,
|
||||
}) : super(key: key) {
|
||||
@ -36,12 +36,13 @@ class _GridTextCellState extends State<GridTextCell> {
|
||||
late TextCellBloc _cellBloc;
|
||||
late TextEditingController _controller;
|
||||
late FocusNode _focusNode;
|
||||
|
||||
VoidCallback? _focusListener;
|
||||
Timer? _delayOperation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_cellBloc = getIt<TextCellBloc>(param1: widget.cellContext);
|
||||
final cellContext = widget.cellContextBuilder.build();
|
||||
_cellBloc = getIt<TextCellBloc>(param1: cellContext);
|
||||
_cellBloc.add(const TextCellEvent.initial());
|
||||
_controller = TextEditingController(text: _cellBloc.state.content);
|
||||
_focusNode = FocusNode();
|
||||
@ -49,11 +50,14 @@ class _GridTextCellState extends State<GridTextCell> {
|
||||
widget.onFocus.value = _focusNode.hasFocus;
|
||||
focusChanged();
|
||||
});
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_listenCellRequestFocus(context);
|
||||
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocConsumer<TextCellBloc, TextCellState>(
|
||||
@ -83,8 +87,26 @@ class _GridTextCellState extends State<GridTextCell> {
|
||||
);
|
||||
}
|
||||
|
||||
void _listenCellRequestFocus(BuildContext context) {
|
||||
if (_focusListener != null) {
|
||||
widget.requestFocus.removeListener(_focusListener!);
|
||||
}
|
||||
|
||||
focusListener() {
|
||||
if (_focusNode.hasFocus == false && _focusNode.canRequestFocus) {
|
||||
FocusScope.of(context).requestFocus(_focusNode);
|
||||
}
|
||||
}
|
||||
|
||||
_focusListener = focusListener;
|
||||
widget.requestFocus.addListener(focusListener);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
if (_focusListener != null) {
|
||||
widget.requestFocus.removeListener(_focusListener!);
|
||||
}
|
||||
_delayOperation?.cancel();
|
||||
_cellBloc.close();
|
||||
_focusNode.dispose();
|
||||
|
@ -88,7 +88,7 @@ class _RowLeading extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<_RegionStateNotifier>(
|
||||
return Consumer<RegionStateNotifier>(
|
||||
builder: (context, state, _) {
|
||||
return SizedBox(width: GridSize.leadingHeaderPadding, child: state.onEnter ? _activeWidget() : null);
|
||||
},
|
||||
@ -164,13 +164,13 @@ class _RowCells extends StatelessWidget {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: _makeCells(state.cellDataMap),
|
||||
children: _makeCells(context, state.cellDataMap),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _makeCells(GridCellMap gridCellMap) {
|
||||
List<Widget> _makeCells(BuildContext context, GridCellMap gridCellMap) {
|
||||
return gridCellMap.values.map(
|
||||
(gridCell) {
|
||||
Widget? expander;
|
||||
@ -181,6 +181,7 @@ class _RowCells extends StatelessWidget {
|
||||
return CellContainer(
|
||||
width: gridCell.field.width.toDouble(),
|
||||
child: buildGridCellWidget(gridCell, cellCache),
|
||||
rowStateNotifier: Provider.of<RegionStateNotifier>(context, listen: false),
|
||||
expander: expander,
|
||||
);
|
||||
},
|
||||
@ -188,7 +189,7 @@ class _RowCells extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _RegionStateNotifier extends ChangeNotifier {
|
||||
class RegionStateNotifier extends ChangeNotifier {
|
||||
bool _onEnter = false;
|
||||
|
||||
set onEnter(bool value) {
|
||||
@ -226,11 +227,11 @@ class _RowEnterRegion extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _RowEnterRegionState extends State<_RowEnterRegion> {
|
||||
late _RegionStateNotifier _rowStateNotifier;
|
||||
late RegionStateNotifier _rowStateNotifier;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_rowStateNotifier = _RegionStateNotifier();
|
||||
_rowStateNotifier = RegionStateNotifier();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@ use flowy_database::{
|
||||
schema::{view_table, view_table::dsl},
|
||||
SqliteConnection,
|
||||
};
|
||||
use lib_infra::timestamp;
|
||||
use lib_infra::util::timestamp;
|
||||
|
||||
pub struct ViewTableSql();
|
||||
impl ViewTableSql {
|
||||
|
@ -265,7 +265,7 @@ use flowy_user::event_map::UserCloudService;
|
||||
use flowy_user_data_model::entities::{
|
||||
SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserParams, UserProfile,
|
||||
};
|
||||
use lib_infra::{future::FutureResult, timestamp};
|
||||
use lib_infra::{future::FutureResult, util::timestamp};
|
||||
|
||||
impl FolderCouldServiceV1 for LocalServer {
|
||||
fn init(&self) {}
|
||||
|
@ -10,6 +10,8 @@ use crate::{
|
||||
use flowy_folder_data_model::entities::{app::App, trash::Trash, view::View, workspace::Workspace};
|
||||
use lib_ot::core::*;
|
||||
|
||||
use crate::errors::internal_error;
|
||||
use lib_infra::util::move_vec_element;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
|
||||
@ -170,16 +172,12 @@ impl FolderPad {
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(self), err)]
|
||||
pub fn move_app(&mut self, app_id: &str, _from: usize, to: usize) -> CollaborateResult<Option<FolderChange>> {
|
||||
pub fn move_app(&mut self, app_id: &str, from: usize, to: usize) -> CollaborateResult<Option<FolderChange>> {
|
||||
let app = self.read_app(app_id)?;
|
||||
self.with_workspace(&app.workspace_id, |workspace| {
|
||||
match workspace.apps.iter().position(|app| app.id == app_id) {
|
||||
None => Ok(None),
|
||||
Some(index) => {
|
||||
let app = workspace.apps.remove(index);
|
||||
workspace.apps.insert(to, app);
|
||||
Ok(Some(()))
|
||||
}
|
||||
match move_vec_element(&mut workspace.apps, |app| app.id == app_id, from, to).map_err(internal_error)? {
|
||||
true => Ok(Some(())),
|
||||
false => Ok(None),
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -251,16 +249,12 @@ impl FolderPad {
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(self), err)]
|
||||
pub fn move_view(&mut self, view_id: &str, _from: usize, to: usize) -> CollaborateResult<Option<FolderChange>> {
|
||||
pub fn move_view(&mut self, view_id: &str, from: usize, to: usize) -> CollaborateResult<Option<FolderChange>> {
|
||||
let view = self.read_view(view_id)?;
|
||||
self.with_app(&view.belong_to_id, |app| {
|
||||
match app.belongings.iter().position(|view| view.id == view_id) {
|
||||
None => Ok(None),
|
||||
Some(index) => {
|
||||
let view = app.belongings.remove(index);
|
||||
app.belongings.insert(to, view);
|
||||
Ok(Some(()))
|
||||
}
|
||||
match move_vec_element(&mut app.belongings, |view| view.id == view_id, from, to).map_err(internal_error)? {
|
||||
true => Ok(Some(())),
|
||||
false => Ok(None),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ use flowy_grid_data_model::entities::{
|
||||
gen_grid_id, FieldChangesetParams, FieldMeta, FieldOrder, FieldType, GridBlockMeta, GridBlockMetaChangeset,
|
||||
GridMeta,
|
||||
};
|
||||
use lib_infra::util::move_vec_element;
|
||||
use lib_ot::core::{OperationTransformable, PlainTextAttributes, PlainTextDelta, PlainTextDeltaBuilder};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
@ -208,20 +209,22 @@ impl GridMetaPad {
|
||||
pub fn move_field(
|
||||
&mut self,
|
||||
field_id: &str,
|
||||
_from_index: usize,
|
||||
from_index: usize,
|
||||
to_index: usize,
|
||||
) -> CollaborateResult<Option<GridChangeset>> {
|
||||
self.modify_grid(
|
||||
|grid_meta| match grid_meta.fields.iter().position(|field| field.id == field_id) {
|
||||
None => Ok(None),
|
||||
Some(index) => {
|
||||
// debug_assert_eq!(index, from_index);
|
||||
let field_meta = grid_meta.fields.remove(index);
|
||||
grid_meta.fields.insert(to_index, field_meta);
|
||||
Ok(Some(()))
|
||||
}
|
||||
},
|
||||
)
|
||||
self.modify_grid(|grid_meta| {
|
||||
match move_vec_element(
|
||||
&mut grid_meta.fields,
|
||||
|field| field.id == field_id,
|
||||
from_index,
|
||||
to_index,
|
||||
)
|
||||
.map_err(internal_error)?
|
||||
{
|
||||
true => Ok(Some(())),
|
||||
false => Ok(None),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn contain_field(&self, field_id: &str) -> bool {
|
||||
|
@ -1,8 +1,4 @@
|
||||
pub mod code_gen;
|
||||
pub mod future;
|
||||
pub mod retry;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn timestamp() -> i64 {
|
||||
chrono::Utc::now().timestamp()
|
||||
}
|
||||
pub mod util;
|
||||
|
32
shared-lib/lib-infra/src/util.rs
Normal file
32
shared-lib/lib-infra/src/util.rs
Normal file
@ -0,0 +1,32 @@
|
||||
pub fn move_vec_element<T, F>(
|
||||
vec: &mut Vec<T>,
|
||||
filter: F,
|
||||
_from_index: usize,
|
||||
mut to_index: usize,
|
||||
) -> Result<bool, String>
|
||||
where
|
||||
F: FnMut(&T) -> bool,
|
||||
{
|
||||
match vec.iter().position(filter) {
|
||||
None => Ok(false),
|
||||
Some(index) => {
|
||||
if vec.len() > to_index {
|
||||
let removed_element = vec.remove(index);
|
||||
vec.insert(to_index, removed_element);
|
||||
Ok(true)
|
||||
} else {
|
||||
let msg = format!(
|
||||
"Move element to invalid index: {}, current len: {}",
|
||||
to_index,
|
||||
vec.len()
|
||||
);
|
||||
Err(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn timestamp() -> i64 {
|
||||
chrono::Utc::now().timestamp()
|
||||
}
|
Loading…
Reference in New Issue
Block a user