chore: clean up db sort blocs (#4725)

This commit is contained in:
Richard Shiue 2024-02-24 22:55:22 +08:00 committed by GitHub
parent cea1c17b76
commit f5cc6521fb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 284 additions and 623 deletions

View File

@ -1116,7 +1116,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
/// Must call [tapSortMenuInSettingBar] first.
Future<void> tapAllSortButton() async {
await tapButton(find.byType(DatabaseDeleteSortButton));
await tapButton(find.byType(DeleteAllSortsButton));
}
Future<void> scrollOptionFilterListByOffset(Offset offset) async {

View File

@ -121,8 +121,8 @@ class _Header extends StatelessWidget {
if (state.newSortFieldId != null && state.newSortCondition != null) {
context.read<SortEditorBloc>().add(
SortEditorEvent.createSort(
state.newSortFieldId!,
state.newSortCondition!,
fieldId: state.newSortFieldId!,
condition: state.newSortCondition!,
),
);
}
@ -531,9 +531,8 @@ class _SortDetailContent extends StatelessWidget {
} else {
context.read<SortEditorBloc>().add(
SortEditorEvent.editSort(
sortInfo!.sortId,
null,
newCondition,
sortId: sortInfo!.sortId,
condition: newCondition,
),
);
}
@ -545,9 +544,8 @@ class _SortDetailContent extends StatelessWidget {
} else {
context.read<SortEditorBloc>().add(
SortEditorEvent.editSort(
sortInfo!.sortId,
newFieldId,
null,
sortId: sortInfo!.sortId,
fieldId: newFieldId,
),
);
}

View File

@ -1,141 +0,0 @@
import 'dart:async';
import 'package:appflowy/plugins/database/application/field/field_info.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/sort_entities.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/sort_entities.pbserver.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pbserver.dart';
import 'package:appflowy_result/appflowy_result.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import '../../../application/field/field_controller.dart';
import '../../../application/sort/sort_service.dart';
import 'util.dart';
part 'sort_create_bloc.freezed.dart';
class CreateSortBloc extends Bloc<CreateSortEvent, CreateSortState> {
CreateSortBloc({required this.viewId, required this.fieldController})
: _sortBackendSvc = SortBackendService(viewId: viewId),
super(CreateSortState.initial(fieldController.fieldInfos)) {
_dispatch();
}
final String viewId;
final SortBackendService _sortBackendSvc;
final FieldController fieldController;
void Function(List<FieldInfo>)? _onFieldFn;
void _dispatch() {
on<CreateSortEvent>(
(event, emit) async {
event.when(
initial: () {
_startListening();
},
didReceiveFields: (List<FieldInfo> fields) {
emit(
state.copyWith(
allFields: fields,
creatableFields: _filterFields(fields, state.filterText),
),
);
},
didReceiveFilterText: (String text) {
emit(
state.copyWith(
filterText: text,
creatableFields: _filterFields(state.allFields, text),
),
);
},
createDefaultSort: (FieldInfo field) {
emit(state.copyWith(didCreateSort: true));
_createDefaultSort(field);
},
);
},
);
}
List<FieldInfo> _filterFields(
List<FieldInfo> fields,
String filterText,
) {
final List<FieldInfo> allFields = List.from(fields);
final keyword = filterText.toLowerCase();
allFields.retainWhere((field) {
if (!field.canCreateSort) {
return false;
}
if (filterText.isNotEmpty) {
return field.name.toLowerCase().contains(keyword);
}
return true;
});
return allFields;
}
void _startListening() {
_onFieldFn = (fields) {
fields.retainWhere((field) => field.canCreateSort);
add(CreateSortEvent.didReceiveFields(fields));
};
fieldController.addListener(onReceiveFields: _onFieldFn);
}
Future<FlowyResult<void, FlowyError>> _createDefaultSort(
FieldInfo field,
) async {
final result = await _sortBackendSvc.insertSort(
fieldId: field.id,
condition: SortConditionPB.Ascending,
);
return result;
}
@override
Future<void> close() async {
if (_onFieldFn != null) {
fieldController.removeListener(onFieldsListener: _onFieldFn);
_onFieldFn = null;
}
return super.close();
}
}
@freezed
class CreateSortEvent with _$CreateSortEvent {
const factory CreateSortEvent.initial() = _Initial;
const factory CreateSortEvent.didReceiveFields(List<FieldInfo> fields) =
_DidReceiveFields;
const factory CreateSortEvent.createDefaultSort(FieldInfo field) =
_CreateDefaultSort;
const factory CreateSortEvent.didReceiveFilterText(String text) =
_DidReceiveFilterText;
}
@freezed
class CreateSortState with _$CreateSortState {
const factory CreateSortState({
required String filterText,
required List<FieldInfo> creatableFields,
required List<FieldInfo> allFields,
required bool didCreateSort,
}) = _CreateSortState;
factory CreateSortState.initial(List<FieldInfo> fields) {
return CreateSortState(
filterText: "",
creatableFields: getCreatableSorts(fields),
allFields: fields,
didCreateSort: false,
);
}
}

View File

@ -5,29 +5,26 @@ import 'package:appflowy/plugins/database/application/field/field_info.dart';
import 'package:appflowy/plugins/database/application/sort/sort_service.dart';
import 'package:appflowy/plugins/database/grid/presentation/widgets/sort/sort_info.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/sort_entities.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/sort_entities.pbserver.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:collection/collection.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'util.dart';
part 'sort_editor_bloc.freezed.dart';
class SortEditorBloc extends Bloc<SortEditorEvent, SortEditorState> {
SortEditorBloc({
required this.viewId,
required this.fieldController,
required List<SortInfo> sortInfos,
}) : _sortBackendSvc = SortBackendService(viewId: viewId),
super(
SortEditorState.initial(
sortInfos,
fieldController.sortInfos,
fieldController.fieldInfos,
),
) {
_dispatch();
_startListening();
}
final String viewId;
@ -41,9 +38,6 @@ class SortEditorBloc extends Bloc<SortEditorEvent, SortEditorState> {
on<SortEditorEvent>(
(event, emit) async {
await event.when(
initial: () {
_startListening();
},
didReceiveFields: (List<FieldInfo> fields) {
emit(
state.copyWith(
@ -52,10 +46,16 @@ class SortEditorBloc extends Bloc<SortEditorEvent, SortEditorState> {
),
);
},
createSort: (String fieldId, SortConditionPB condition) async {
updateCreateSortFilter: (text) {
emit(state.copyWith(filter: text));
},
createSort: (
String fieldId,
SortConditionPB? condition,
) async {
final result = await _sortBackendSvc.insertSort(
fieldId: fieldId,
condition: condition,
condition: condition ?? SortConditionPB.Ascending,
);
result.fold((l) => {}, (err) => Log.error(err));
},
@ -142,24 +142,25 @@ class SortEditorBloc extends Bloc<SortEditorEvent, SortEditorState> {
@freezed
class SortEditorEvent with _$SortEditorEvent {
const factory SortEditorEvent.initial() = _Initial;
const factory SortEditorEvent.didReceiveFields(List<FieldInfo> fieldInfos) =
_DidReceiveFields;
const factory SortEditorEvent.didReceiveSorts(List<SortInfo> sortInfos) =
_DidReceiveSorts;
const factory SortEditorEvent.createSort(
String fieldId,
SortConditionPB condition,
) = _CreateSort;
const factory SortEditorEvent.editSort(
String sortId,
const factory SortEditorEvent.updateCreateSortFilter(String text) =
_UpdateCreateSortFilter;
const factory SortEditorEvent.createSort({
required String fieldId,
SortConditionPB? condition,
}) = _CreateSort;
const factory SortEditorEvent.editSort({
required String sortId,
String? fieldId,
SortConditionPB? condition,
) = _EditSort;
const factory SortEditorEvent.deleteSort(SortInfo sortInfo) = _DeleteSort;
const factory SortEditorEvent.deleteAllSorts() = _DeleteAllSorts;
}) = _EditSort;
const factory SortEditorEvent.reorderSort(int oldIndex, int newIndex) =
_ReorderSort;
const factory SortEditorEvent.deleteSort(SortInfo sortInfo) = _DeleteSort;
const factory SortEditorEvent.deleteAllSorts() = _DeleteAllSorts;
}
@freezed
@ -168,6 +169,7 @@ class SortEditorState with _$SortEditorState {
required List<SortInfo> sortInfos,
required List<FieldInfo> creatableFields,
required List<FieldInfo> allFields,
required String filter,
}) = _SortEditorState;
factory SortEditorState.initial(
@ -178,6 +180,13 @@ class SortEditorState with _$SortEditorState {
creatableFields: getCreatableSorts(fields),
allFields: fields,
sortInfos: sortInfos,
filter: "",
);
}
}
List<FieldInfo> getCreatableSorts(List<FieldInfo> fieldInfos) {
final List<FieldInfo> creatableFields = List.from(fieldInfos);
creatableFields.retainWhere((element) => element.canCreateSort);
return creatableFields;
}

View File

@ -1,123 +0,0 @@
import 'dart:async';
import 'package:appflowy/plugins/database/application/field/field_info.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import '../../../application/field/field_controller.dart';
import '../../presentation/widgets/sort/sort_info.dart';
import 'util.dart';
part 'sort_menu_bloc.freezed.dart';
class SortMenuBloc extends Bloc<SortMenuEvent, SortMenuState> {
SortMenuBloc({required this.viewId, required this.fieldController})
: super(
SortMenuState.initial(
viewId,
fieldController.sortInfos,
fieldController.fieldInfos,
),
) {
_dispatch();
}
final String viewId;
final FieldController fieldController;
void Function(List<SortInfo>)? _onSortChangeFn;
void Function(List<FieldInfo>)? _onFieldFn;
void _dispatch() {
on<SortMenuEvent>(
(event, emit) async {
event.when(
initial: () {
_startListening();
},
didReceiveSortInfos: (sortInfos) {
emit(state.copyWith(sortInfos: sortInfos));
},
toggleMenu: () {
final isVisible = !state.isVisible;
emit(state.copyWith(isVisible: isVisible));
},
didReceiveFields: (List<FieldInfo> fields) {
emit(
state.copyWith(
fields: fields,
creatableFields: getCreatableSorts(fields),
),
);
},
);
},
);
}
void _startListening() {
_onSortChangeFn = (sortInfos) {
add(SortMenuEvent.didReceiveSortInfos(sortInfos));
};
_onFieldFn = (fields) {
add(SortMenuEvent.didReceiveFields(fields));
};
fieldController.addListener(
onSorts: (sortInfos) {
_onSortChangeFn?.call(sortInfos);
},
onReceiveFields: (fields) {
_onFieldFn?.call(fields);
},
);
}
@override
Future<void> close() {
if (_onSortChangeFn != null) {
fieldController.removeListener(onSortsListener: _onSortChangeFn!);
_onSortChangeFn = null;
}
if (_onFieldFn != null) {
fieldController.removeListener(onFieldsListener: _onFieldFn!);
_onFieldFn = null;
}
return super.close();
}
}
@freezed
class SortMenuEvent with _$SortMenuEvent {
const factory SortMenuEvent.initial() = _Initial;
const factory SortMenuEvent.didReceiveSortInfos(List<SortInfo> sortInfos) =
_DidReceiveSortInfos;
const factory SortMenuEvent.didReceiveFields(List<FieldInfo> fields) =
_DidReceiveFields;
const factory SortMenuEvent.toggleMenu() = _SetMenuVisibility;
}
@freezed
class SortMenuState with _$SortMenuState {
const factory SortMenuState({
required String viewId,
required List<SortInfo> sortInfos,
required List<FieldInfo> fields,
required List<FieldInfo> creatableFields,
required bool isVisible,
}) = _SortMenuState;
factory SortMenuState.initial(
String viewId,
List<SortInfo> sortInfos,
List<FieldInfo> fields,
) =>
SortMenuState(
viewId: viewId,
sortInfos: sortInfos,
fields: fields,
creatableFields: getCreatableSorts(fields),
isVisible: false,
);
}

View File

@ -1,7 +0,0 @@
import 'package:appflowy/plugins/database/application/field/field_info.dart';
List<FieldInfo> getCreatableSorts(List<FieldInfo> fieldInfos) {
final List<FieldInfo> creatableFields = List.from(fieldInfos);
creatableFields.retainWhere((element) => element.canCreateSort);
return creatableFields;
}

View File

@ -1,8 +1,7 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database/application/field/field_controller.dart';
import 'package:appflowy/plugins/database/application/field/field_info.dart';
import 'package:appflowy/plugins/database/grid/application/sort/sort_create_bloc.dart';
import 'package:appflowy/plugins/database/grid/application/sort/sort_editor_bloc.dart';
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
import 'package:appflowy/util/field_type_extension.dart';
import 'package:easy_localization/easy_localization.dart';
@ -15,97 +14,56 @@ import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class GridCreateSortList extends StatefulWidget {
const GridCreateSortList({
class CreateDatabaseViewSortList extends StatelessWidget {
const CreateDatabaseViewSortList({
super.key,
required this.viewId,
required this.fieldController,
required this.onClosed,
this.onCreateSort,
required this.onTap,
});
final String viewId;
final FieldController fieldController;
final VoidCallback onClosed;
final VoidCallback? onCreateSort;
@override
State<StatefulWidget> createState() => _GridCreateSortListState();
}
class _GridCreateSortListState extends State<GridCreateSortList> {
late CreateSortBloc editBloc;
@override
void initState() {
editBloc = CreateSortBloc(
viewId: widget.viewId,
fieldController: widget.fieldController,
)..add(const CreateSortEvent.initial());
super.initState();
}
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return BlocProvider.value(
value: editBloc,
child: BlocListener<CreateSortBloc, CreateSortState>(
listener: (context, state) {
if (state.didCreateSort) {
widget.onClosed();
}
},
child: BlocBuilder<CreateSortBloc, CreateSortState>(
builder: (context, state) {
final cells = state.creatableFields.map((fieldInfo) {
return SizedBox(
height: GridSize.popoverItemHeight,
child: GridSortPropertyCell(
fieldInfo: fieldInfo,
onTap: (fieldInfo) => createSort(fieldInfo),
),
);
}).toList();
return BlocBuilder<SortEditorBloc, SortEditorState>(
builder: (context, state) {
final filter = state.filter.toLowerCase();
final cells = state.creatableFields
.where((field) => field.field.name.toLowerCase().contains(filter))
.map((fieldInfo) {
return GridSortPropertyCell(
fieldInfo: fieldInfo,
onTap: () {
context
.read<SortEditorBloc>()
.add(SortEditorEvent.createSort(fieldId: fieldInfo.id));
onTap.call();
},
);
}).toList();
final List<Widget> slivers = [
SliverPersistentHeader(
pinned: true,
delegate: _SortTextFieldDelegate(),
),
SliverToBoxAdapter(
child: ListView.separated(
shrinkWrap: true,
itemCount: cells.length,
itemBuilder: (BuildContext context, int index) {
return cells[index];
},
separatorBuilder: (BuildContext context, int index) {
return VSpace(GridSize.typeOptionSeparatorHeight);
},
),
),
];
return CustomScrollView(
final List<Widget> slivers = [
SliverPersistentHeader(
pinned: true,
delegate: _SortTextFieldDelegate(),
),
SliverToBoxAdapter(
child: ListView.separated(
shrinkWrap: true,
slivers: slivers,
physics: StyledScrollPhysics(),
);
},
),
),
itemCount: cells.length,
itemBuilder: (_, index) => cells[index],
separatorBuilder: (_, __) =>
VSpace(GridSize.typeOptionSeparatorHeight),
),
),
];
return CustomScrollView(
shrinkWrap: true,
slivers: slivers,
physics: StyledScrollPhysics(),
);
},
);
}
@override
void dispose() {
editBloc.close();
super.dispose();
}
void createSort(FieldInfo field) {
editBloc.add(CreateSortEvent.createDefaultSort(field));
widget.onCreateSort?.call();
}
}
class _SortTextFieldDelegate extends SliverPersistentHeaderDelegate {
@ -127,8 +85,8 @@ class _SortTextFieldDelegate extends SliverPersistentHeaderDelegate {
hintText: LocaleKeys.grid_settings_sortBy.tr(),
onChanged: (text) {
context
.read<CreateSortBloc>()
.add(CreateSortEvent.didReceiveFilterText(text));
.read<SortEditorBloc>()
.add(SortEditorEvent.updateCreateSortFilter(text));
},
),
);
@ -141,9 +99,7 @@ class _SortTextFieldDelegate extends SliverPersistentHeaderDelegate {
double get minExtent => fixHeight;
@override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
return false;
}
bool shouldRebuild(covariant oldDelegate) => false;
}
class GridSortPropertyCell extends StatelessWidget {
@ -154,20 +110,23 @@ class GridSortPropertyCell extends StatelessWidget {
});
final FieldInfo fieldInfo;
final Function(FieldInfo) onTap;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return FlowyButton(
hoverColor: AFThemeExtension.of(context).lightGreyHover,
text: FlowyText.medium(
fieldInfo.name,
color: AFThemeExtension.of(context).textColor,
),
onTap: () => onTap(fieldInfo),
leftIcon: FlowySvg(
fieldInfo.fieldType.svgData,
color: Theme.of(context).iconTheme.color,
return SizedBox(
height: GridSize.popoverItemHeight,
child: FlowyButton(
hoverColor: AFThemeExtension.of(context).lightGreyHover,
text: FlowyText.medium(
fieldInfo.name,
color: AFThemeExtension.of(context).textColor,
),
onTap: onTap,
leftIcon: FlowySvg(
fieldInfo.fieldType.svgData,
color: Theme.of(context).iconTheme.color,
),
),
);
}

View File

@ -27,16 +27,15 @@ class SortChoiceButton extends StatelessWidget {
decoration: BoxDecoration(
color: Colors.transparent,
border: Border.fromBorderSide(
BorderSide(
color: AFThemeExtension.of(context).toggleOffFill,
),
BorderSide(color: Theme.of(context).dividerColor),
),
borderRadius: const BorderRadius.all(Radius.circular(14)),
borderRadius: BorderRadius.all(radius),
),
useIntrinsicWidth: true,
text: FlowyText(
text,
color: AFThemeExtension.of(context).textColor,
overflow: TextOverflow.ellipsis,
),
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
radius: BorderRadius.all(radius),

View File

@ -2,9 +2,7 @@ import 'dart:io';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database/application/field/field_controller.dart';
import 'package:appflowy/plugins/database/grid/application/sort/sort_editor_bloc.dart';
import 'package:appflowy/plugins/database/grid/application/sort/util.dart';
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/sort_entities.pbenum.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
@ -14,7 +12,6 @@ import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'dart:math' as math;
import 'create_sort_list.dart';
import 'order_panel.dart';
@ -22,16 +19,7 @@ import 'sort_choice_button.dart';
import 'sort_info.dart';
class SortEditor extends StatefulWidget {
const SortEditor({
super.key,
required this.viewId,
required this.fieldController,
required this.sortInfos,
});
final String viewId;
final FieldController fieldController;
final List<SortInfo> sortInfos;
const SortEditor({super.key});
@override
State<SortEditor> createState() => _SortEditorState();
@ -42,69 +30,57 @@ class _SortEditorState extends State<SortEditor> {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => SortEditorBloc(
viewId: widget.viewId,
fieldController: widget.fieldController,
sortInfos: widget.sortInfos,
)..add(const SortEditorEvent.initial()),
child: BlocBuilder<SortEditorBloc, SortEditorState>(
builder: (context, state) {
final sortInfos = state.sortInfos;
return ReorderableListView.builder(
onReorder: (oldIndex, newIndex) => context
.read<SortEditorBloc>()
.add(SortEditorEvent.reorderSort(oldIndex, newIndex)),
itemCount: state.sortInfos.length,
itemBuilder: (context, index) => Padding(
key: ValueKey(sortInfos[index].sortId),
padding: const EdgeInsets.symmetric(vertical: 6),
child: DatabaseSortItem(
index: index,
sortInfo: sortInfos[index],
popoverMutex: popoverMutex,
),
),
proxyDecorator: (child, index, animation) => Material(
color: Colors.transparent,
child: Stack(
children: [
BlocProvider.value(
value: context.read<SortEditorBloc>(),
child: child,
),
MouseRegion(
cursor: Platform.isWindows
? SystemMouseCursors.click
: SystemMouseCursors.grabbing,
child: const SizedBox.expand(),
),
],
),
),
shrinkWrap: true,
buildDefaultDragHandles: false,
footer: Row(
return BlocBuilder<SortEditorBloc, SortEditorState>(
builder: (context, state) {
final sortInfos = state.sortInfos;
return ReorderableListView.builder(
onReorder: (oldIndex, newIndex) => context
.read<SortEditorBloc>()
.add(SortEditorEvent.reorderSort(oldIndex, newIndex)),
itemCount: state.sortInfos.length,
itemBuilder: (context, index) => DatabaseSortItem(
key: ValueKey(sortInfos[index].sortId),
index: index,
sortInfo: sortInfos[index],
popoverMutex: popoverMutex,
),
proxyDecorator: (child, index, animation) => Material(
color: Colors.transparent,
child: Stack(
children: [
Flexible(
child: DatabaseAddSortButton(
viewId: widget.viewId,
fieldController: widget.fieldController,
popoverMutex: popoverMutex,
),
BlocProvider.value(
value: context.read<SortEditorBloc>(),
child: child,
),
const HSpace(6),
Flexible(
child: DatabaseDeleteSortButton(
popoverMutex: popoverMutex,
),
MouseRegion(
cursor: Platform.isWindows
? SystemMouseCursors.click
: SystemMouseCursors.grabbing,
child: const SizedBox.expand(),
),
],
),
);
},
),
),
shrinkWrap: true,
buildDefaultDragHandles: false,
footer: Row(
children: [
Flexible(
child: DatabaseAddSortButton(
disable: state.creatableFields.isEmpty,
popoverMutex: popoverMutex,
),
),
const HSpace(6),
Flexible(
child: DeleteAllSortsButton(
popoverMutex: popoverMutex,
),
),
],
),
);
},
);
}
}
@ -123,80 +99,89 @@ class DatabaseSortItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
final deleteButton = FlowyIconButton(
width: 26,
onPressed: () => context
.read<SortEditorBloc>()
.add(SortEditorEvent.deleteSort(sortInfo)),
iconPadding: const EdgeInsets.all(5),
hoverColor: AFThemeExtension.of(context).lightGreyHover,
icon:
FlowySvg(FlowySvgs.close_s, color: Theme.of(context).iconTheme.color),
);
return Row(
children: [
ReorderableDragStartListener(
index: index,
child: MouseRegion(
cursor: Platform.isWindows
? SystemMouseCursors.click
: SystemMouseCursors.grab,
child: SizedBox(
width: 14,
height: 14,
child: FlowySvg(
FlowySvgs.drag_element_s,
color: Theme.of(context).iconTheme.color,
return Container(
padding: const EdgeInsets.symmetric(vertical: 6),
color: Theme.of(context).cardColor,
child: Row(
children: [
ReorderableDragStartListener(
index: index,
child: MouseRegion(
cursor: Platform.isWindows
? SystemMouseCursors.click
: SystemMouseCursors.grab,
child: SizedBox(
width: 14 + 12,
height: 14,
child: FlowySvg(
FlowySvgs.drag_element_s,
size: const Size.square(14),
color: Theme.of(context).iconTheme.color,
),
),
),
),
),
const HSpace(6),
SizedBox(
height: 26,
child: SortChoiceButton(
text: sortInfo.fieldInfo.name,
editable: false,
Flexible(
fit: FlexFit.tight,
child: SizedBox(
height: 26,
child: SortChoiceButton(
text: sortInfo.fieldInfo.name,
editable: false,
),
),
),
),
const HSpace(6),
SizedBox(
height: 26,
child: DatabaseSortItemOrderButton(
sortInfo: sortInfo,
popoverMutex: popoverMutex,
const HSpace(6),
Flexible(
fit: FlexFit.tight,
child: SizedBox(
height: 26,
child: SortConditionButton(
sortInfo: sortInfo,
popoverMutex: popoverMutex,
),
),
),
),
const Spacer(),
const HSpace(6),
deleteButton,
],
const HSpace(6),
FlowyIconButton(
width: 26,
onPressed: () {
context
.read<SortEditorBloc>()
.add(SortEditorEvent.deleteSort(sortInfo));
PopoverContainer.of(context).close();
},
hoverColor: AFThemeExtension.of(context).lightGreyHover,
icon: FlowySvg(
FlowySvgs.trash_m,
color: Theme.of(context).iconTheme.color,
size: const Size.square(16),
),
),
],
),
);
}
}
extension SortConditionExtension on SortConditionPB {
String get title {
switch (this) {
case SortConditionPB.Descending:
return LocaleKeys.grid_sort_descending.tr();
default:
return LocaleKeys.grid_sort_ascending.tr();
}
return switch (this) {
SortConditionPB.Ascending => LocaleKeys.grid_sort_ascending.tr(),
SortConditionPB.Descending => LocaleKeys.grid_sort_descending.tr(),
_ => throw UnimplementedError(),
};
}
}
class DatabaseAddSortButton extends StatefulWidget {
const DatabaseAddSortButton({
super.key,
required this.viewId,
required this.fieldController,
required this.disable,
required this.popoverMutex,
});
final String viewId;
final FieldController fieldController;
final bool disable;
final PopoverMutex popoverMutex;
@override
@ -213,32 +198,36 @@ class _DatabaseAddSortButtonState extends State<DatabaseAddSortButton> {
mutex: widget.popoverMutex,
direction: PopoverDirection.bottomWithLeftAligned,
constraints: BoxConstraints.loose(const Size(200, 300)),
offset: const Offset(0, 8),
offset: const Offset(-6, 8),
triggerActions: PopoverTriggerFlags.none,
asBarrier: true,
popupBuilder: (popoverContext) {
return BlocProvider.value(
value: context.read<SortEditorBloc>(),
child: CreateDatabaseViewSortList(
onTap: () => _popoverController.close(),
),
);
},
onClose: () => context
.read<SortEditorBloc>()
.add(const SortEditorEvent.updateCreateSortFilter("")),
child: SizedBox(
height: GridSize.popoverItemHeight,
child: FlowyButton(
hoverColor: AFThemeExtension.of(context).greyHover,
disable: getCreatableSorts(widget.fieldController.fieldInfos).isEmpty,
disable: widget.disable,
text: FlowyText.medium(LocaleKeys.grid_sort_addSort.tr()),
onTap: () => _popoverController.show(),
leftIcon: const FlowySvg(FlowySvgs.add_s),
),
),
popupBuilder: (BuildContext context) {
return GridCreateSortList(
viewId: widget.viewId,
fieldController: widget.fieldController,
onClosed: () => _popoverController.close(),
);
},
);
}
}
class DatabaseDeleteSortButton extends StatelessWidget {
const DatabaseDeleteSortButton({super.key, required this.popoverMutex});
class DeleteAllSortsButton extends StatelessWidget {
const DeleteAllSortsButton({super.key, required this.popoverMutex});
final PopoverMutex popoverMutex;
@ -264,8 +253,8 @@ class DatabaseDeleteSortButton extends StatelessWidget {
}
}
class DatabaseSortItemOrderButton extends StatefulWidget {
const DatabaseSortItemOrderButton({
class SortConditionButton extends StatefulWidget {
const SortConditionButton({
super.key,
required this.popoverMutex,
required this.sortInfo,
@ -275,21 +264,14 @@ class DatabaseSortItemOrderButton extends StatefulWidget {
final SortInfo sortInfo;
@override
State<DatabaseSortItemOrderButton> createState() =>
_DatabaseSortItemOrderButtonState();
State<SortConditionButton> createState() => _SortConditionButtonState();
}
class _DatabaseSortItemOrderButtonState
extends State<DatabaseSortItemOrderButton> {
class _SortConditionButtonState extends State<SortConditionButton> {
final PopoverController popoverController = PopoverController();
@override
Widget build(BuildContext context) {
final arrow = Transform.rotate(
angle: -math.pi / 2,
child: const FlowySvg(FlowySvgs.arrow_left_s),
);
return AppFlowyPopover(
controller: popoverController,
mutex: widget.popoverMutex,
@ -301,9 +283,8 @@ class _DatabaseSortItemOrderButtonState
onCondition: (condition) {
context.read<SortEditorBloc>().add(
SortEditorEvent.editSort(
widget.sortInfo.sortId,
null,
condition,
sortId: widget.sortInfo.sortId,
condition: condition,
),
);
popoverController.close();
@ -312,7 +293,10 @@ class _DatabaseSortItemOrderButtonState
},
child: SortChoiceButton(
text: widget.sortInfo.sortPB.condition.title,
rightIcon: arrow,
rightIcon: FlowySvg(
FlowySvgs.arrow_down_s,
color: Theme.of(context).iconTheme.color,
),
onTap: () => popoverController.show(),
),
);

View File

@ -1,6 +1,8 @@
import 'dart:math' as math;
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/plugins/database/application/field/field_controller.dart';
import 'package:appflowy/plugins/database/grid/application/sort/sort_menu_bloc.dart';
import 'package:appflowy/plugins/database/grid/application/sort/sort_editor_bloc.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
@ -8,8 +10,6 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:easy_localization/easy_localization.dart';
import 'dart:math' as math;
import 'sort_choice_button.dart';
import 'sort_editor.dart';
import 'sort_info.dart';
@ -24,12 +24,12 @@ class SortMenu extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider<SortMenuBloc>(
create: (context) => SortMenuBloc(
return BlocProvider(
create: (context) => SortEditorBloc(
viewId: fieldController.viewId,
fieldController: fieldController,
)..add(const SortMenuEvent.initial()),
child: BlocBuilder<SortMenuBloc, SortMenuState>(
),
child: BlocBuilder<SortEditorBloc, SortEditorState>(
builder: (context, state) {
if (state.sortInfos.isEmpty) {
return const SizedBox.shrink();
@ -42,10 +42,9 @@ class SortMenu extends StatelessWidget {
offset: const Offset(0, 5),
margin: const EdgeInsets.fromLTRB(6.0, 0.0, 6.0, 6.0),
popupBuilder: (BuildContext popoverContext) {
return SortEditor(
viewId: state.viewId,
fieldController: context.read<SortMenuBloc>().fieldController,
sortInfos: state.sortInfos,
return BlocProvider.value(
value: context.read<SortEditorBloc>(),
child: const SortEditor(),
);
},
child: SortChoiceChip(sortInfos: state.sortInfos),

View File

@ -1,6 +1,6 @@
import 'package:appflowy/plugins/database/application/database_controller.dart';
import 'package:appflowy/plugins/database/grid/application/filter/filter_menu_bloc.dart';
import 'package:appflowy/plugins/database/grid/application/sort/sort_menu_bloc.dart';
import 'package:appflowy/plugins/database/grid/application/sort/sort_editor_bloc.dart';
import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart';
import 'package:appflowy/plugins/database/widgets/setting/setting_button.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -30,24 +30,16 @@ class GridSettingBar extends StatelessWidget {
fieldController: controller.fieldController,
)..add(const GridFilterMenuEvent.initial()),
),
BlocProvider<SortMenuBloc>(
create: (context) => SortMenuBloc(
BlocProvider<SortEditorBloc>(
create: (context) => SortEditorBloc(
viewId: controller.viewId,
fieldController: controller.fieldController,
)..add(const SortMenuEvent.initial()),
),
),
],
child: MultiBlocListener(
listeners: [
BlocListener<GridFilterMenuBloc, GridFilterMenuState>(
listenWhen: (p, c) => p.isVisible != c.isVisible,
listener: (context, state) => toggleExtension.toggle(),
),
BlocListener<SortMenuBloc, SortMenuState>(
listenWhen: (p, c) => p.isVisible != c.isVisible,
listener: (context, state) => toggleExtension.toggle(),
),
],
child: BlocListener<GridFilterMenuBloc, GridFilterMenuState>(
listenWhen: (p, c) => p.isVisible != c.isVisible,
listener: (context, state) => toggleExtension.toggle(),
child: ValueListenableBuilder<bool>(
valueListenable: controller.isLoading,
builder: (context, value, child) {
@ -61,7 +53,7 @@ class GridSettingBar extends StatelessWidget {
children: [
const FilterButton(),
const HSpace(2),
const SortButton(),
SortButton(toggleExtension: toggleExtension),
const HSpace(2),
SettingButton(
databaseController: controller,

View File

@ -1,5 +1,6 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database/grid/application/sort/sort_menu_bloc.dart';
import 'package:appflowy/plugins/database/grid/application/sort/sort_editor_bloc.dart';
import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/size.dart';
@ -12,7 +13,9 @@ import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
import '../sort/create_sort_list.dart';
class SortButton extends StatefulWidget {
const SortButton({super.key});
const SortButton({super.key, required this.toggleExtension});
final ToggleExtensionNotifier toggleExtension;
@override
State<SortButton> createState() => _SortButtonState();
@ -23,7 +26,7 @@ class _SortButtonState extends State<SortButton> {
@override
Widget build(BuildContext context) {
return BlocBuilder<SortMenuBloc, SortMenuState>(
return BlocBuilder<SortEditorBloc, SortEditorState>(
builder: (context, state) {
final textColor = state.sortInfos.isEmpty
? AFThemeExtension.of(context).textColor
@ -41,11 +44,10 @@ class _SortButtonState extends State<SortButton> {
padding: GridSize.toolbarSettingButtonInsets,
radius: Corners.s4Border,
onPressed: () {
final bloc = context.read<SortMenuBloc>();
if (bloc.state.sortInfos.isEmpty) {
if (state.sortInfos.isEmpty) {
_popoverController.show();
} else {
bloc.add(const SortMenuEvent.toggleMenu());
widget.toggleExtension.toggle();
}
},
),
@ -54,27 +56,30 @@ class _SortButtonState extends State<SortButton> {
);
}
Widget wrapPopover(BuildContext buildContext, Widget child) {
Widget wrapPopover(BuildContext context, Widget child) {
return AppFlowyPopover(
controller: _popoverController,
direction: PopoverDirection.bottomWithLeftAligned,
constraints: BoxConstraints.loose(const Size(200, 300)),
offset: const Offset(0, 8),
triggerActions: PopoverTriggerFlags.none,
child: child,
popupBuilder: (BuildContext context) {
final bloc = buildContext.read<SortMenuBloc>();
return GridCreateSortList(
viewId: bloc.viewId,
fieldController: bloc.fieldController,
onClosed: () => _popoverController.close(),
onCreateSort: () {
if (!bloc.state.isVisible) {
bloc.add(const SortMenuEvent.toggleMenu());
}
},
popupBuilder: (popoverContext) {
return BlocProvider.value(
value: context.read<SortEditorBloc>(),
child: CreateDatabaseViewSortList(
onTap: () {
if (!widget.toggleExtension.isToggled) {
widget.toggleExtension.toggle();
}
_popoverController.close();
},
),
);
},
onClose: () => context
.read<SortEditorBloc>()
.add(const SortEditorEvent.updateCreateSortFilter("")),
child: child,
);
}
}

View File

@ -6,7 +6,6 @@ import 'package:appflowy/mobile/presentation/database/view/database_sort_bottom_
import 'package:appflowy/plugins/database/application/database_controller.dart';
import 'package:appflowy/plugins/database/grid/application/filter/filter_menu_bloc.dart';
import 'package:appflowy/plugins/database/grid/application/sort/sort_editor_bloc.dart';
import 'package:appflowy/plugins/database/grid/application/sort/sort_menu_bloc.dart';
import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart';
import 'package:appflowy/workspace/application/view/view_bloc.dart';
import 'package:easy_localization/easy_localization.dart';
@ -34,24 +33,16 @@ class MobileDatabaseControls extends StatelessWidget {
fieldController: controller.fieldController,
)..add(const GridFilterMenuEvent.initial()),
),
BlocProvider<SortMenuBloc>(
create: (context) => SortMenuBloc(
BlocProvider<SortEditorBloc>(
create: (context) => SortEditorBloc(
viewId: controller.viewId,
fieldController: controller.fieldController,
)..add(const SortMenuEvent.initial()),
),
),
],
child: MultiBlocListener(
listeners: [
BlocListener<GridFilterMenuBloc, GridFilterMenuState>(
listenWhen: (p, c) => p.isVisible != c.isVisible,
listener: (context, state) => toggleExtension.toggle(),
),
BlocListener<SortMenuBloc, SortMenuState>(
listenWhen: (p, c) => p.isVisible != c.isVisible,
listener: (context, state) => toggleExtension.toggle(),
),
],
child: BlocListener<GridFilterMenuBloc, GridFilterMenuState>(
listenWhen: (p, c) => p.isVisible != c.isVisible,
listener: (context, state) => toggleExtension.toggle(),
child: ValueListenableBuilder<bool>(
valueListenable: controller.isLoading,
builder: (context, isLoading, child) {
@ -64,7 +55,7 @@ class MobileDatabaseControls extends StatelessWidget {
children: [
_DatabaseControlButton(
icon: FlowySvgs.sort_ascending_s,
count: context.watch<SortMenuBloc>().state.sortInfos.length,
count: context.watch<SortEditorBloc>().state.sortInfos.length,
onTap: () => _showEditSortPanelFromToolbar(
context,
controller,
@ -161,12 +152,8 @@ void _showEditSortPanelFromToolbar(
showDivider: false,
useSafeArea: false,
builder: (_) {
return BlocProvider(
create: (_) => SortEditorBloc(
viewId: databaseController.viewId,
fieldController: databaseController.fieldController,
sortInfos: databaseController.fieldController.sortInfos,
)..add(const SortEditorEvent.initial()),
return BlocProvider.value(
value: context.read<SortEditorBloc>(),
child: const MobileSortEditor(),
);
},