mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Feat/sort UI (#1642)
* feat: implement sort UI * chore: config sort listener * chore: config sort ui * chore: config sort ui * feat: support ascending & descending * fix: reorder rows bugs * chore: add tests Co-authored-by: nathan <nathan@appflowy.io>
This commit is contained in:
parent
92baa573e1
commit
b7ba189642
@ -193,6 +193,7 @@
|
||||
"grid": {
|
||||
"settings": {
|
||||
"filter": "Filter",
|
||||
"sort": "Sort",
|
||||
"sortBy": "Sort by",
|
||||
"Properties": "Properties",
|
||||
"group": "Group",
|
||||
@ -273,6 +274,12 @@
|
||||
"newColumn": "New column",
|
||||
"deleteFieldPromptMessage": "Are you sure? This property will be deleted"
|
||||
},
|
||||
"sort": {
|
||||
"ascending": "Ascending",
|
||||
"descending": "Descending",
|
||||
"deleteSort": "Delete sort",
|
||||
"addSort": "Add sort"
|
||||
},
|
||||
"row": {
|
||||
"duplicate": "Duplicate",
|
||||
"delete": "Delete",
|
||||
|
@ -132,6 +132,7 @@ class BoardDataController {
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
await _viewCache.dispose();
|
||||
await _gridFFIService.closeGrid();
|
||||
await fieldController.dispose();
|
||||
}
|
||||
|
@ -5,7 +5,12 @@ import 'package:app_flowy/plugins/grid/application/filter/filter_service.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/grid_service.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/setting/setting_listener.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/setting/setting_service.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/sort/sort_listener.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/sort/sort_service.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/filter/filter_info.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/sort/sort_info.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-grid/filter_changeset.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-grid/sort_entities.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
@ -46,21 +51,39 @@ class _GridFilterNotifier extends ChangeNotifier {
|
||||
List<FilterInfo> get filters => _filters;
|
||||
}
|
||||
|
||||
class _GridSortNotifier extends ChangeNotifier {
|
||||
List<SortInfo> _sorts = [];
|
||||
|
||||
set sorts(List<SortInfo> sorts) {
|
||||
_sorts = sorts;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void notify() {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
List<SortInfo> get sorts => _sorts;
|
||||
}
|
||||
|
||||
typedef OnReceiveUpdateFields = void Function(List<FieldInfo>);
|
||||
typedef OnReceiveFields = void Function(List<FieldInfo>);
|
||||
typedef OnReceiveFilters = void Function(List<FilterInfo>);
|
||||
typedef OnReceiveSorts = void Function(List<SortInfo>);
|
||||
|
||||
class GridFieldController {
|
||||
final String gridId;
|
||||
// Listeners
|
||||
final GridFieldsListener _fieldListener;
|
||||
final SettingListener _settingListener;
|
||||
final FiltersListener _filterListener;
|
||||
final FiltersListener _filtersListener;
|
||||
final SortsListener _sortsListener;
|
||||
|
||||
// FFI services
|
||||
final GridFFIService _gridFFIService;
|
||||
final SettingFFIService _settingFFIService;
|
||||
final FilterFFIService _filterFFIService;
|
||||
final SortFFIService _sortFFIService;
|
||||
|
||||
// Field callbacks
|
||||
final Map<OnReceiveFields, VoidCallback> _fieldCallbacks = {};
|
||||
@ -78,9 +101,16 @@ class GridFieldController {
|
||||
_GridFilterNotifier? _filterNotifier = _GridFilterNotifier();
|
||||
final Map<String, FilterPB> _filterPBByFieldId = {};
|
||||
|
||||
// Sort callbacks
|
||||
final Map<OnReceiveSorts, VoidCallback> _sortCallbacks = {};
|
||||
_GridSortNotifier? _sortNotifier = _GridSortNotifier();
|
||||
final Map<String, SortPB> _sortPBByFieldId = {};
|
||||
|
||||
// Getters
|
||||
List<FieldInfo> get fieldInfos => [..._fieldNotifier?.fieldInfos ?? []];
|
||||
List<FilterInfo> get filterInfos => [..._filterNotifier?.filters ?? []];
|
||||
List<SortInfo> get sortInfos => [..._sortNotifier?.sorts ?? []];
|
||||
|
||||
FieldInfo? getField(String fieldId) {
|
||||
final fields = _fieldNotifier?.fieldInfos
|
||||
.where((element) => element.id == fieldId)
|
||||
@ -105,12 +135,26 @@ class GridFieldController {
|
||||
return filters.first;
|
||||
}
|
||||
|
||||
SortInfo? getSort(String sortId) {
|
||||
final sorts = _sortNotifier?.sorts
|
||||
.where((element) => element.sortId == sortId)
|
||||
.toList() ??
|
||||
[];
|
||||
if (sorts.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
assert(sorts.length == 1);
|
||||
return sorts.first;
|
||||
}
|
||||
|
||||
GridFieldController({required this.gridId})
|
||||
: _fieldListener = GridFieldsListener(gridId: gridId),
|
||||
_settingListener = SettingListener(gridId: gridId),
|
||||
_filterListener = FiltersListener(viewId: gridId),
|
||||
_gridFFIService = GridFFIService(gridId: gridId),
|
||||
_filterFFIService = FilterFFIService(viewId: gridId),
|
||||
_filtersListener = FiltersListener(viewId: gridId),
|
||||
_gridFFIService = GridFFIService(gridId: gridId),
|
||||
_sortFFIService = SortFFIService(viewId: gridId),
|
||||
_sortsListener = SortsListener(viewId: gridId),
|
||||
_settingFFIService = SettingFFIService(viewId: gridId) {
|
||||
//Listen on field's changes
|
||||
_listenOnFieldChanges();
|
||||
@ -121,9 +165,12 @@ class GridFieldController {
|
||||
//Listen on the fitler changes
|
||||
_listenOnFilterChanges();
|
||||
|
||||
//Listen on the sort changes
|
||||
_listenOnSortChanged();
|
||||
|
||||
_settingFFIService.getSetting().then((result) {
|
||||
result.fold(
|
||||
(setting) => _updateSettingConfiguration(setting),
|
||||
(setting) => _updateSetting(setting),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
});
|
||||
@ -131,68 +178,95 @@ class GridFieldController {
|
||||
|
||||
void _listenOnFilterChanges() {
|
||||
//Listen on the fitler changes
|
||||
_filterListener.start(onFilterChanged: (result) {
|
||||
|
||||
deleteFilterFromChangeset(
|
||||
List<FilterInfo> filters,
|
||||
FilterChangesetNotificationPB changeset,
|
||||
) {
|
||||
final deleteFilterIds = changeset.deleteFilters.map((e) => e.id).toList();
|
||||
if (deleteFilterIds.isNotEmpty) {
|
||||
filters.retainWhere(
|
||||
(element) => !deleteFilterIds.contains(element.filter.id),
|
||||
);
|
||||
|
||||
_filterPBByFieldId
|
||||
.removeWhere((key, value) => deleteFilterIds.contains(value.id));
|
||||
}
|
||||
}
|
||||
|
||||
insertFilterFromChangeset(
|
||||
List<FilterInfo> filters,
|
||||
FilterChangesetNotificationPB changeset,
|
||||
) {
|
||||
for (final newFilter in changeset.insertFilters) {
|
||||
final filterIndex =
|
||||
filters.indexWhere((element) => element.filter.id == newFilter.id);
|
||||
if (filterIndex == -1) {
|
||||
final fieldInfo = _findFieldInfo(
|
||||
fieldInfos: fieldInfos,
|
||||
fieldId: newFilter.fieldId,
|
||||
fieldType: newFilter.fieldType,
|
||||
);
|
||||
if (fieldInfo != null) {
|
||||
_filterPBByFieldId[fieldInfo.id] = newFilter;
|
||||
filters.add(FilterInfo(gridId, newFilter, fieldInfo));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateFilterFromChangeset(
|
||||
List<FilterInfo> filters,
|
||||
FilterChangesetNotificationPB changeset,
|
||||
) {
|
||||
for (final updatedFilter in changeset.updateFilters) {
|
||||
final filterIndex = filters.indexWhere(
|
||||
(element) => element.filter.id == updatedFilter.filterId,
|
||||
);
|
||||
// Remove the old filter
|
||||
if (filterIndex != -1) {
|
||||
filters.removeAt(filterIndex);
|
||||
_filterPBByFieldId
|
||||
.removeWhere((key, value) => value.id == updatedFilter.filterId);
|
||||
}
|
||||
|
||||
// Insert the filter if there is a fitler and its field info is
|
||||
// not null
|
||||
if (updatedFilter.hasFilter()) {
|
||||
final fieldInfo = _findFieldInfo(
|
||||
fieldInfos: fieldInfos,
|
||||
fieldId: updatedFilter.filter.fieldId,
|
||||
fieldType: updatedFilter.filter.fieldType,
|
||||
);
|
||||
|
||||
if (fieldInfo != null) {
|
||||
// Insert the filter with the position: filterIndex, otherwise,
|
||||
// append it to the end of the list.
|
||||
final filterInfo =
|
||||
FilterInfo(gridId, updatedFilter.filter, fieldInfo);
|
||||
if (filterIndex != -1) {
|
||||
filters.insert(filterIndex, filterInfo);
|
||||
} else {
|
||||
filters.add(filterInfo);
|
||||
}
|
||||
_filterPBByFieldId[fieldInfo.id] = updatedFilter.filter;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_filtersListener.start(onFilterChanged: (result) {
|
||||
result.fold(
|
||||
(changeset) {
|
||||
(FilterChangesetNotificationPB changeset) {
|
||||
final List<FilterInfo> filters = filterInfos;
|
||||
// Deletes the filters
|
||||
final deleteFilterIds =
|
||||
changeset.deleteFilters.map((e) => e.id).toList();
|
||||
if (deleteFilterIds.isNotEmpty) {
|
||||
filters.retainWhere(
|
||||
(element) => !deleteFilterIds.contains(element.filter.id),
|
||||
);
|
||||
|
||||
_filterPBByFieldId.removeWhere(
|
||||
(key, value) => deleteFilterIds.contains(value.id));
|
||||
}
|
||||
deleteFilterFromChangeset(filters, changeset);
|
||||
|
||||
// Inserts the new filter if it's not exist
|
||||
for (final newFilter in changeset.insertFilters) {
|
||||
final filterIndex = filters
|
||||
.indexWhere((element) => element.filter.id == newFilter.id);
|
||||
if (filterIndex == -1) {
|
||||
final fieldInfo = _findFieldInfoForFilter(fieldInfos, newFilter);
|
||||
if (fieldInfo != null) {
|
||||
_filterPBByFieldId[fieldInfo.id] = newFilter;
|
||||
filters.add(FilterInfo(gridId, newFilter, fieldInfo));
|
||||
}
|
||||
}
|
||||
}
|
||||
insertFilterFromChangeset(filters, changeset);
|
||||
|
||||
for (final updatedFilter in changeset.updateFilters) {
|
||||
final filterIndex = filters.indexWhere(
|
||||
(element) => element.filter.id == updatedFilter.filterId,
|
||||
);
|
||||
// Remove the old filter
|
||||
if (filterIndex != -1) {
|
||||
filters.removeAt(filterIndex);
|
||||
_filterPBByFieldId.removeWhere(
|
||||
(key, value) => value.id == updatedFilter.filterId);
|
||||
}
|
||||
updateFilterFromChangeset(filters, changeset);
|
||||
|
||||
// Insert the filter if there is a fitler and its field info is
|
||||
// not null
|
||||
if (updatedFilter.hasFilter()) {
|
||||
final fieldInfo = _findFieldInfoForFilter(
|
||||
fieldInfos,
|
||||
updatedFilter.filter,
|
||||
);
|
||||
|
||||
if (fieldInfo != null) {
|
||||
// Insert the filter with the position: filterIndex, otherwise,
|
||||
// append it to the end of the list.
|
||||
final filterInfo =
|
||||
FilterInfo(gridId, updatedFilter.filter, fieldInfo);
|
||||
if (filterIndex != -1) {
|
||||
filters.insert(filterIndex, filterInfo);
|
||||
} else {
|
||||
filters.add(filterInfo);
|
||||
}
|
||||
_filterPBByFieldId[fieldInfo.id] = updatedFilter.filter;
|
||||
}
|
||||
}
|
||||
}
|
||||
_updateFieldInfos();
|
||||
_filterNotifier?.filters = filters;
|
||||
},
|
||||
@ -201,11 +275,99 @@ class GridFieldController {
|
||||
});
|
||||
}
|
||||
|
||||
void _listenOnSortChanged() {
|
||||
deleteSortFromChangeset(
|
||||
List<SortInfo> newSortInfos,
|
||||
SortChangesetNotificationPB changeset,
|
||||
) {
|
||||
final deleteSortIds = changeset.deleteSorts.map((e) => e.id).toList();
|
||||
if (deleteSortIds.isNotEmpty) {
|
||||
newSortInfos.retainWhere(
|
||||
(element) => !deleteSortIds.contains(element.sortId),
|
||||
);
|
||||
|
||||
_sortPBByFieldId
|
||||
.removeWhere((key, value) => deleteSortIds.contains(value.id));
|
||||
}
|
||||
}
|
||||
|
||||
insertSortFromChangeset(
|
||||
List<SortInfo> newSortInfos,
|
||||
SortChangesetNotificationPB changeset,
|
||||
) {
|
||||
for (final newSortPB in changeset.insertSorts) {
|
||||
final sortIndex = newSortInfos
|
||||
.indexWhere((element) => element.sortId == newSortPB.id);
|
||||
if (sortIndex == -1) {
|
||||
final fieldInfo = _findFieldInfo(
|
||||
fieldInfos: fieldInfos,
|
||||
fieldId: newSortPB.fieldId,
|
||||
fieldType: newSortPB.fieldType,
|
||||
);
|
||||
|
||||
if (fieldInfo != null) {
|
||||
_sortPBByFieldId[newSortPB.fieldId] = newSortPB;
|
||||
newSortInfos.add(SortInfo(sortPB: newSortPB, fieldInfo: fieldInfo));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateSortFromChangeset(
|
||||
List<SortInfo> newSortInfos,
|
||||
SortChangesetNotificationPB changeset,
|
||||
) {
|
||||
for (final updatedSort in changeset.updateSorts) {
|
||||
final sortIndex = newSortInfos.indexWhere(
|
||||
(element) => element.sortId == updatedSort.id,
|
||||
);
|
||||
// Remove the old filter
|
||||
if (sortIndex != -1) {
|
||||
newSortInfos.removeAt(sortIndex);
|
||||
}
|
||||
|
||||
final fieldInfo = _findFieldInfo(
|
||||
fieldInfos: fieldInfos,
|
||||
fieldId: updatedSort.fieldId,
|
||||
fieldType: updatedSort.fieldType,
|
||||
);
|
||||
|
||||
if (fieldInfo != null) {
|
||||
final newSortInfo = SortInfo(
|
||||
sortPB: updatedSort,
|
||||
fieldInfo: fieldInfo,
|
||||
);
|
||||
if (sortIndex != -1) {
|
||||
newSortInfos.insert(sortIndex, newSortInfo);
|
||||
} else {
|
||||
newSortInfos.add(newSortInfo);
|
||||
}
|
||||
_sortPBByFieldId[updatedSort.fieldId] = updatedSort;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_sortsListener.start(onSortChanged: (result) {
|
||||
result.fold(
|
||||
(SortChangesetNotificationPB changeset) {
|
||||
final List<SortInfo> newSortInfos = sortInfos;
|
||||
deleteSortFromChangeset(newSortInfos, changeset);
|
||||
insertSortFromChangeset(newSortInfos, changeset);
|
||||
updateSortFromChangeset(newSortInfos, changeset);
|
||||
|
||||
_updateFieldInfos();
|
||||
_sortNotifier?.sorts = newSortInfos;
|
||||
},
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void _listenOnSettingChanges() {
|
||||
//Listen on setting changes
|
||||
_settingListener.start(onSettingUpdated: (result) {
|
||||
result.fold(
|
||||
(setting) => _updateSettingConfiguration(setting),
|
||||
(setting) => _updateSetting(setting),
|
||||
(r) => Log.error(r),
|
||||
);
|
||||
});
|
||||
@ -229,14 +391,18 @@ class GridFieldController {
|
||||
});
|
||||
}
|
||||
|
||||
void _updateSettingConfiguration(GridSettingPB setting) {
|
||||
void _updateSetting(GridSettingPB setting) {
|
||||
_groupConfigurationByFieldId.clear();
|
||||
for (final configuration in setting.groupConfigurations.items) {
|
||||
_groupConfigurationByFieldId[configuration.fieldId] = configuration;
|
||||
}
|
||||
|
||||
for (final configuration in setting.filters.items) {
|
||||
_filterPBByFieldId[configuration.fieldId] = configuration;
|
||||
for (final filter in setting.filters.items) {
|
||||
_filterPBByFieldId[filter.fieldId] = filter;
|
||||
}
|
||||
|
||||
for (final sort in setting.sorts.items) {
|
||||
_sortPBByFieldId[sort.fieldId] = sort;
|
||||
}
|
||||
|
||||
_updateFieldInfos();
|
||||
@ -247,6 +413,7 @@ class GridFieldController {
|
||||
for (var field in _fieldNotifier!.fieldInfos) {
|
||||
field._isGroupField = _groupConfigurationByFieldId[field.id] != null;
|
||||
field._hasFilter = _filterPBByFieldId[field.id] != null;
|
||||
field._hasSort = _sortPBByFieldId[field.id] != null;
|
||||
}
|
||||
_fieldNotifier?.notify();
|
||||
}
|
||||
@ -254,8 +421,9 @@ class GridFieldController {
|
||||
|
||||
Future<void> dispose() async {
|
||||
await _fieldListener.stop();
|
||||
await _filterListener.stop();
|
||||
await _filtersListener.stop();
|
||||
await _settingListener.stop();
|
||||
await _sortsListener.stop();
|
||||
|
||||
for (final callback in _fieldCallbacks.values) {
|
||||
_fieldNotifier?.removeListener(callback);
|
||||
@ -266,8 +434,15 @@ class GridFieldController {
|
||||
for (final callback in _filterCallbacks.values) {
|
||||
_filterNotifier?.removeListener(callback);
|
||||
}
|
||||
for (final callback in _sortCallbacks.values) {
|
||||
_sortNotifier?.removeListener(callback);
|
||||
}
|
||||
|
||||
_filterNotifier?.dispose();
|
||||
_filterNotifier = null;
|
||||
|
||||
_sortNotifier?.dispose();
|
||||
_sortNotifier = null;
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> loadFields({
|
||||
@ -280,6 +455,7 @@ class GridFieldController {
|
||||
_fieldNotifier?.fieldInfos =
|
||||
newFields.map((field) => FieldInfo(field: field)).toList();
|
||||
_loadFilters();
|
||||
_loadSorts();
|
||||
_updateFieldInfos();
|
||||
return left(unit);
|
||||
},
|
||||
@ -294,14 +470,17 @@ class GridFieldController {
|
||||
(filterPBs) {
|
||||
final List<FilterInfo> filters = [];
|
||||
for (final filterPB in filterPBs) {
|
||||
final fieldInfo = _findFieldInfoForFilter(fieldInfos, filterPB);
|
||||
final fieldInfo = _findFieldInfo(
|
||||
fieldInfos: fieldInfos,
|
||||
fieldId: filterPB.fieldId,
|
||||
fieldType: filterPB.fieldType,
|
||||
);
|
||||
if (fieldInfo != null) {
|
||||
final filterInfo = FilterInfo(gridId, filterPB, fieldInfo);
|
||||
filters.add(filterInfo);
|
||||
}
|
||||
}
|
||||
|
||||
_updateFieldInfos();
|
||||
_filterNotifier?.filters = filters;
|
||||
return left(unit);
|
||||
},
|
||||
@ -310,10 +489,38 @@ class GridFieldController {
|
||||
});
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> _loadSorts() async {
|
||||
return _sortFFIService.getAllSorts().then((result) {
|
||||
return result.fold(
|
||||
(sortPBs) {
|
||||
final List<SortInfo> sortInfos = [];
|
||||
for (final sortPB in sortPBs) {
|
||||
final fieldInfo = _findFieldInfo(
|
||||
fieldInfos: fieldInfos,
|
||||
fieldId: sortPB.fieldId,
|
||||
fieldType: sortPB.fieldType,
|
||||
);
|
||||
|
||||
if (fieldInfo != null) {
|
||||
final sortInfo = SortInfo(sortPB: sortPB, fieldInfo: fieldInfo);
|
||||
sortInfos.add(sortInfo);
|
||||
}
|
||||
}
|
||||
|
||||
_updateFieldInfos();
|
||||
_sortNotifier?.sorts = sortInfos;
|
||||
return left(unit);
|
||||
},
|
||||
(err) => right(err),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void addListener({
|
||||
OnReceiveFields? onFields,
|
||||
OnReceiveUpdateFields? onFieldsUpdated,
|
||||
OnReceiveFilters? onFilters,
|
||||
OnReceiveSorts? onSorts,
|
||||
bool Function()? listenWhen,
|
||||
}) {
|
||||
if (onFieldsUpdated != null) {
|
||||
@ -350,10 +557,23 @@ class GridFieldController {
|
||||
_filterCallbacks[onFilters] = callback;
|
||||
_filterNotifier?.addListener(callback);
|
||||
}
|
||||
|
||||
if (onSorts != null) {
|
||||
callback() {
|
||||
if (listenWhen != null && listenWhen() == false) {
|
||||
return;
|
||||
}
|
||||
onSorts(sortInfos);
|
||||
}
|
||||
|
||||
_sortCallbacks[onSorts] = callback;
|
||||
_sortNotifier?.addListener(callback);
|
||||
}
|
||||
}
|
||||
|
||||
void removeListener({
|
||||
OnReceiveFields? onFieldsListener,
|
||||
OnReceiveSorts? onSortsListener,
|
||||
OnReceiveFilters? onFiltersListener,
|
||||
OnReceiveUpdateFields? onChangesetListener,
|
||||
}) {
|
||||
@ -369,6 +589,13 @@ class GridFieldController {
|
||||
_filterNotifier?.removeListener(callback);
|
||||
}
|
||||
}
|
||||
|
||||
if (onSortsListener != null) {
|
||||
final callback = _sortCallbacks.remove(onSortsListener);
|
||||
if (callback != null) {
|
||||
_sortNotifier?.removeListener(callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _deleteFields(List<FieldIdPB> deletedFields) {
|
||||
@ -466,11 +693,13 @@ class GridRowFieldNotifierImpl extends IGridRowFieldNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
FieldInfo? _findFieldInfoForFilter(
|
||||
List<FieldInfo> fieldInfos, FilterPB filter) {
|
||||
FieldInfo? _findFieldInfo({
|
||||
required List<FieldInfo> fieldInfos,
|
||||
required String fieldId,
|
||||
required FieldType fieldType,
|
||||
}) {
|
||||
final fieldIndex = fieldInfos.indexWhere((element) {
|
||||
return element.id == filter.fieldId &&
|
||||
element.fieldType == filter.fieldType;
|
||||
return element.id == fieldId && element.fieldType == fieldType;
|
||||
});
|
||||
if (fieldIndex != -1) {
|
||||
return fieldInfos[fieldIndex];
|
||||
@ -485,6 +714,8 @@ class FieldInfo {
|
||||
|
||||
bool _hasFilter = false;
|
||||
|
||||
bool _hasSort = false;
|
||||
|
||||
String get id => _field.id;
|
||||
|
||||
FieldType get fieldType => _field.fieldType;
|
||||
@ -529,5 +760,18 @@ class FieldInfo {
|
||||
}
|
||||
}
|
||||
|
||||
bool get canCreateSort {
|
||||
if (_hasSort) return false;
|
||||
|
||||
switch (_field.fieldType) {
|
||||
case FieldType.RichText:
|
||||
case FieldType.Checkbox:
|
||||
case FieldType.Number:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
FieldInfo({required FieldPB field}) : _field = field;
|
||||
}
|
||||
|
@ -197,8 +197,6 @@ class FilterFFIService {
|
||||
required String filterId,
|
||||
required FieldType fieldType,
|
||||
}) {
|
||||
TextFilterConditionPB.DoesNotContain.value;
|
||||
|
||||
final deleteFilterPayload = DeleteFilterPayloadPB.create()
|
||||
..fieldId = fieldId
|
||||
..filterId = filterId
|
||||
|
@ -0,0 +1,47 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'grid_accessory_bloc.freezed.dart';
|
||||
|
||||
class GridAccessoryMenuBloc
|
||||
extends Bloc<GridAccessoryMenuEvent, GridAccessoryMenuState> {
|
||||
final String viewId;
|
||||
|
||||
GridAccessoryMenuBloc({required this.viewId})
|
||||
: super(GridAccessoryMenuState.initial(
|
||||
viewId,
|
||||
)) {
|
||||
on<GridAccessoryMenuEvent>(
|
||||
(event, emit) async {
|
||||
event.when(
|
||||
initial: () {},
|
||||
toggleMenu: () {
|
||||
emit(state.copyWith(isVisible: !state.isVisible));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class GridAccessoryMenuEvent with _$GridAccessoryMenuEvent {
|
||||
const factory GridAccessoryMenuEvent.initial() = _Initial;
|
||||
const factory GridAccessoryMenuEvent.toggleMenu() = _MenuVisibleChange;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class GridAccessoryMenuState with _$GridAccessoryMenuState {
|
||||
const factory GridAccessoryMenuState({
|
||||
required String viewId,
|
||||
required bool isVisible,
|
||||
}) = _GridAccessoryMenuState;
|
||||
|
||||
factory GridAccessoryMenuState.initial(
|
||||
String viewId,
|
||||
) =>
|
||||
GridAccessoryMenuState(
|
||||
viewId: viewId,
|
||||
isVisible: false,
|
||||
);
|
||||
}
|
@ -81,6 +81,23 @@ class GridRowCache {
|
||||
_showRows(changeset.visibleRows);
|
||||
}
|
||||
|
||||
void reorderAllRows(List<String> rowIds) {
|
||||
_rowList.reorderWithRowIds(rowIds);
|
||||
_rowChangeReasonNotifier.receive(const RowsChangedReason.reorderRows());
|
||||
}
|
||||
|
||||
void reorderSingleRow(ReorderSingleRowPB reorderRow) {
|
||||
final rowInfo = _rowList.get(reorderRow.rowId);
|
||||
if (rowInfo != null) {
|
||||
_rowList.moveRow(
|
||||
reorderRow.rowId, reorderRow.oldIndex, reorderRow.newIndex);
|
||||
_rowChangeReasonNotifier.receive(RowsChangedReason.reorderSingleRow(
|
||||
reorderRow,
|
||||
rowInfo,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
void _deleteRows(List<String> deletedRowIds) {
|
||||
for (final rowId in deletedRowIds) {
|
||||
final deletedRow = _rowList.remove(rowId);
|
||||
@ -266,6 +283,8 @@ class _RowChangesetNotifier extends ChangeNotifier {
|
||||
update: (_) => notifyListeners(),
|
||||
fieldDidChange: (_) => notifyListeners(),
|
||||
initial: (_) {},
|
||||
reorderRows: (_) => notifyListeners(),
|
||||
reorderSingleRow: (_) => notifyListeners(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -292,6 +311,9 @@ class RowsChangedReason with _$RowsChangedReason {
|
||||
const factory RowsChangedReason.update(UpdatedIndexMap indexs) = _Update;
|
||||
const factory RowsChangedReason.fieldDidChange() = _FieldDidChange;
|
||||
const factory RowsChangedReason.initial() = InitialListState;
|
||||
const factory RowsChangedReason.reorderRows() = _ReorderRows;
|
||||
const factory RowsChangedReason.reorderSingleRow(
|
||||
ReorderSingleRowPB reorderRow, RowInfo rowInfo) = _ReorderSingleRow;
|
||||
}
|
||||
|
||||
class InsertedIndex {
|
||||
|
@ -5,8 +5,7 @@ import 'package:appflowy_backend/protobuf/flowy-grid/row_entities.pb.dart';
|
||||
import 'row_cache.dart';
|
||||
|
||||
class RowList {
|
||||
/// _rows containers the current block's rows
|
||||
/// Use List to reverse the order of the GridRow.
|
||||
/// Use List to reverse the order of the row.
|
||||
List<RowInfo> _rowInfos = [];
|
||||
|
||||
List<RowInfo> get rows => List.from(_rowInfos);
|
||||
@ -150,6 +149,28 @@ class RowList {
|
||||
return deletedRows;
|
||||
}
|
||||
|
||||
void reorderWithRowIds(List<String> rowIds) {
|
||||
_rowInfos.clear();
|
||||
|
||||
for (final rowId in rowIds) {
|
||||
final rowInfo = _rowInfoByRowId[rowId];
|
||||
if (rowInfo != null) {
|
||||
_rowInfos.add(rowInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void moveRow(String rowId, int oldIndex, int newIndex) {
|
||||
final index = _rowInfos.indexWhere(
|
||||
(rowInfo) => rowInfo.rowPB.id == rowId,
|
||||
);
|
||||
if (index != -1) {
|
||||
assert(index == oldIndex);
|
||||
final rowInfo = remove(rowId)!.rowInfo;
|
||||
insert(newIndex, rowInfo);
|
||||
}
|
||||
}
|
||||
|
||||
bool contains(String rowId) {
|
||||
return _rowInfoByRowId[rowId] != null;
|
||||
}
|
||||
|
@ -0,0 +1,132 @@
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pbserver.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-grid/sort_entities.pbenum.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-grid/sort_entities.pbserver.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import 'sort_service.dart';
|
||||
import 'util.dart';
|
||||
|
||||
part 'sort_create_bloc.freezed.dart';
|
||||
|
||||
class CreateSortBloc extends Bloc<CreateSortEvent, CreateSortState> {
|
||||
final String viewId;
|
||||
final SortFFIService _ffiService;
|
||||
final GridFieldController fieldController;
|
||||
void Function(List<FieldInfo>)? _onFieldFn;
|
||||
CreateSortBloc({required this.viewId, required this.fieldController})
|
||||
: _ffiService = SortFFIService(viewId: viewId),
|
||||
super(CreateSortState.initial(fieldController.fieldInfos)) {
|
||||
on<CreateSortEvent>(
|
||||
(event, emit) async {
|
||||
event.when(
|
||||
initial: () async {
|
||||
_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(onFields: _onFieldFn);
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> _createDefaultSort(FieldInfo field) async {
|
||||
final result = await _ffiService.insertSort(
|
||||
fieldId: field.id,
|
||||
fieldType: field.fieldType,
|
||||
condition: GridSortConditionPB.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,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,128 @@
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/sort/sort_info.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-grid/sort_entities.pbenum.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-grid/sort_entities.pbserver.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import 'sort_service.dart';
|
||||
import 'util.dart';
|
||||
|
||||
part 'sort_editor_bloc.freezed.dart';
|
||||
|
||||
class SortEditorBloc extends Bloc<SortEditorEvent, SortEditorState> {
|
||||
final String viewId;
|
||||
final SortFFIService _ffiService;
|
||||
final GridFieldController fieldController;
|
||||
void Function(List<FieldInfo>)? _onFieldFn;
|
||||
SortEditorBloc({
|
||||
required this.viewId,
|
||||
required this.fieldController,
|
||||
required List<SortInfo> sortInfos,
|
||||
}) : _ffiService = SortFFIService(viewId: viewId),
|
||||
super(SortEditorState.initial(sortInfos, fieldController.fieldInfos)) {
|
||||
on<SortEditorEvent>(
|
||||
(event, emit) async {
|
||||
event.when(
|
||||
initial: () async {
|
||||
_startListening();
|
||||
},
|
||||
didReceiveFields: (List<FieldInfo> fields) {
|
||||
final List<FieldInfo> allFields = List.from(fields);
|
||||
final List<FieldInfo> creatableFields = List.from(fields);
|
||||
creatableFields.retainWhere((field) => field.canCreateSort);
|
||||
emit(
|
||||
state.copyWith(
|
||||
allFields: allFields,
|
||||
creatableFields: creatableFields,
|
||||
),
|
||||
);
|
||||
},
|
||||
setCondition:
|
||||
(SortInfo sortInfo, GridSortConditionPB condition) async {
|
||||
final result = await _ffiService.updateSort(
|
||||
fieldId: sortInfo.fieldInfo.id,
|
||||
sortId: sortInfo.sortId,
|
||||
fieldType: sortInfo.fieldInfo.fieldType,
|
||||
condition: condition,
|
||||
);
|
||||
result.fold((l) => {}, (err) => Log.error(err));
|
||||
},
|
||||
deleteAllSorts: () async {
|
||||
final result = await _ffiService.deleteAllSorts();
|
||||
result.fold((l) => {}, (err) => Log.error(err));
|
||||
},
|
||||
didReceiveSorts: (List<SortInfo> sortInfos) {
|
||||
emit(state.copyWith(sortInfos: sortInfos));
|
||||
},
|
||||
deleteSort: (SortInfo sortInfo) async {
|
||||
final result = await _ffiService.deleteSort(
|
||||
fieldId: sortInfo.fieldInfo.id,
|
||||
sortId: sortInfo.sortId,
|
||||
fieldType: sortInfo.fieldInfo.fieldType,
|
||||
);
|
||||
result.fold((l) => null, (err) => Log.error(err));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onFieldFn = (fields) {
|
||||
add(SortEditorEvent.didReceiveFields(List.from(fields)));
|
||||
};
|
||||
|
||||
fieldController.addListener(
|
||||
listenWhen: () => !isClosed,
|
||||
onFields: _onFieldFn,
|
||||
onSorts: (sorts) {
|
||||
add(SortEditorEvent.didReceiveSorts(sorts));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_onFieldFn != null) {
|
||||
fieldController.removeListener(onFieldsListener: _onFieldFn);
|
||||
_onFieldFn = null;
|
||||
}
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
||||
@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.setCondition(
|
||||
SortInfo sortInfo, GridSortConditionPB condition) = _SetCondition;
|
||||
const factory SortEditorEvent.deleteSort(SortInfo sortInfo) = _DeleteSort;
|
||||
const factory SortEditorEvent.deleteAllSorts() = _DeleteAllSorts;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class SortEditorState with _$SortEditorState {
|
||||
const factory SortEditorState({
|
||||
required List<SortInfo> sortInfos,
|
||||
required List<FieldInfo> creatableFields,
|
||||
required List<FieldInfo> allFields,
|
||||
}) = _SortEditorState;
|
||||
|
||||
factory SortEditorState.initial(
|
||||
List<SortInfo> sortInfos,
|
||||
List<FieldInfo> fields,
|
||||
) {
|
||||
return SortEditorState(
|
||||
creatableFields: getCreatableSorts(fields),
|
||||
allFields: fields,
|
||||
sortInfos: sortInfos,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:app_flowy/core/grid_notification.dart';
|
||||
import 'package:flowy_infra/notifier.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-grid/dart_notification.pbenum.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-grid/sort_entities.pb.dart';
|
||||
|
||||
typedef SortNotifiedValue = Either<SortChangesetNotificationPB, FlowyError>;
|
||||
|
||||
class SortsListener {
|
||||
final String viewId;
|
||||
PublishNotifier<SortNotifiedValue>? _notifier = PublishNotifier();
|
||||
GridNotificationListener? _listener;
|
||||
|
||||
SortsListener({required this.viewId});
|
||||
|
||||
void start({
|
||||
required void Function(SortNotifiedValue) onSortChanged,
|
||||
}) {
|
||||
_notifier?.addPublishListener(onSortChanged);
|
||||
_listener = GridNotificationListener(
|
||||
objectId: viewId,
|
||||
handler: _handler,
|
||||
);
|
||||
}
|
||||
|
||||
void _handler(
|
||||
GridDartNotification ty,
|
||||
Either<Uint8List, FlowyError> result,
|
||||
) {
|
||||
switch (ty) {
|
||||
case GridDartNotification.DidUpdateSort:
|
||||
result.fold(
|
||||
(payload) => _notifier?.value =
|
||||
left(SortChangesetNotificationPB.fromBuffer(payload)),
|
||||
(error) => _notifier?.value = right(error),
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> stop() async {
|
||||
await _listener?.stop();
|
||||
_notifier?.dispose();
|
||||
_notifier = null;
|
||||
}
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/sort/sort_info.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import 'util.dart';
|
||||
|
||||
part 'sort_menu_bloc.freezed.dart';
|
||||
|
||||
class SortMenuBloc extends Bloc<SortMenuEvent, SortMenuState> {
|
||||
final String viewId;
|
||||
final GridFieldController fieldController;
|
||||
void Function(List<SortInfo>)? _onSortChangeFn;
|
||||
void Function(List<FieldInfo>)? _onFieldFn;
|
||||
|
||||
SortMenuBloc({required this.viewId, required this.fieldController})
|
||||
: super(SortMenuState.initial(
|
||||
viewId,
|
||||
fieldController.sortInfos,
|
||||
fieldController.fieldInfos,
|
||||
)) {
|
||||
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);
|
||||
},
|
||||
onFields: (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,
|
||||
);
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-grid/grid_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-grid/setting_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-grid/sort_entities.pb.dart';
|
||||
|
||||
class SortFFIService {
|
||||
final String viewId;
|
||||
|
||||
SortFFIService({required this.viewId});
|
||||
|
||||
Future<Either<List<SortPB>, FlowyError>> getAllSorts() {
|
||||
final payload = GridIdPB()..value = viewId;
|
||||
|
||||
return GridEventGetAllSorts(payload).send().then((result) {
|
||||
return result.fold(
|
||||
(repeated) => left(repeated.items),
|
||||
(r) => right(r),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> updateSort({
|
||||
required String fieldId,
|
||||
required String sortId,
|
||||
required FieldType fieldType,
|
||||
required GridSortConditionPB condition,
|
||||
}) {
|
||||
var insertSortPayload = AlterSortPayloadPB.create()
|
||||
..fieldId = fieldId
|
||||
..fieldType = fieldType
|
||||
..viewId = viewId
|
||||
..condition = condition
|
||||
..sortId = sortId;
|
||||
|
||||
final payload = GridSettingChangesetPB.create()
|
||||
..gridId = viewId
|
||||
..alterSort = insertSortPayload;
|
||||
return GridEventUpdateGridSetting(payload).send().then((result) {
|
||||
return result.fold(
|
||||
(l) => left(l),
|
||||
(err) {
|
||||
Log.error(err);
|
||||
return right(err);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> insertSort({
|
||||
required String fieldId,
|
||||
required FieldType fieldType,
|
||||
required GridSortConditionPB condition,
|
||||
}) {
|
||||
var insertSortPayload = AlterSortPayloadPB.create()
|
||||
..fieldId = fieldId
|
||||
..fieldType = fieldType
|
||||
..viewId = viewId
|
||||
..condition = condition;
|
||||
|
||||
final payload = GridSettingChangesetPB.create()
|
||||
..gridId = viewId
|
||||
..alterSort = insertSortPayload;
|
||||
return GridEventUpdateGridSetting(payload).send().then((result) {
|
||||
return result.fold(
|
||||
(l) => left(l),
|
||||
(err) {
|
||||
Log.error(err);
|
||||
return right(err);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> deleteSort({
|
||||
required String fieldId,
|
||||
required String sortId,
|
||||
required FieldType fieldType,
|
||||
}) {
|
||||
final deleteFilterPayload = DeleteSortPayloadPB.create()
|
||||
..fieldId = fieldId
|
||||
..sortId = sortId
|
||||
..viewId = viewId
|
||||
..fieldType = fieldType;
|
||||
|
||||
final payload = GridSettingChangesetPB.create()
|
||||
..gridId = viewId
|
||||
..deleteSort = deleteFilterPayload;
|
||||
|
||||
return GridEventUpdateGridSetting(payload).send().then((result) {
|
||||
return result.fold(
|
||||
(l) => left(l),
|
||||
(err) {
|
||||
Log.error(err);
|
||||
return right(err);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> deleteAllSorts() {
|
||||
final payload = GridIdPB(value: viewId);
|
||||
return GridEventDeleteAllSorts(payload).send().then((result) {
|
||||
return result.fold(
|
||||
(l) => left(l),
|
||||
(err) {
|
||||
Log.error(err);
|
||||
return right(err);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
|
||||
|
||||
List<FieldInfo> getCreatableSorts(List<FieldInfo> fieldInfos) {
|
||||
final List<FieldInfo> creatableFields = List.from(fieldInfos);
|
||||
creatableFields.retainWhere((element) => element.canCreateSort);
|
||||
return creatableFields;
|
||||
}
|
@ -37,6 +37,18 @@ class GridViewCache {
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
},
|
||||
onReorderAllRows: (result) {
|
||||
result.fold(
|
||||
(rowIds) => _rowCache.reorderAllRows(rowIds),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
},
|
||||
onReorderSingleRow: (result) {
|
||||
result.fold(
|
||||
(reorderRow) => _rowCache.reorderSingleRow(reorderRow),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
import 'package:app_flowy/core/grid_notification.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-grid/sort_entities.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_infra/notifier.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
@ -11,11 +12,17 @@ typedef GridRowsVisibilityNotifierValue
|
||||
= Either<GridRowsVisibilityChangesetPB, FlowyError>;
|
||||
|
||||
typedef GridViewRowsNotifierValue = Either<GridViewRowsChangesetPB, FlowyError>;
|
||||
typedef GridViewReorderAllRowsNotifierValue = Either<List<String>, FlowyError>;
|
||||
typedef GridViewSingleRowNotifierValue = Either<ReorderSingleRowPB, FlowyError>;
|
||||
|
||||
class GridViewListener {
|
||||
final String viewId;
|
||||
PublishNotifier<GridViewRowsNotifierValue>? _rowsNotifier = PublishNotifier();
|
||||
PublishNotifier<GridRowsVisibilityNotifierValue>? _rowsVisibilityNotifier =
|
||||
PublishNotifier<GridViewReorderAllRowsNotifierValue>? _reorderAllRows =
|
||||
PublishNotifier();
|
||||
PublishNotifier<GridViewSingleRowNotifierValue>? _reorderSingleRow =
|
||||
PublishNotifier();
|
||||
PublishNotifier<GridRowsVisibilityNotifierValue>? _rowsVisibility =
|
||||
PublishNotifier();
|
||||
|
||||
GridNotificationListener? _listener;
|
||||
@ -23,6 +30,9 @@ class GridViewListener {
|
||||
|
||||
void start({
|
||||
required void Function(GridViewRowsNotifierValue) onRowsChanged,
|
||||
required void Function(GridViewReorderAllRowsNotifierValue)
|
||||
onReorderAllRows,
|
||||
required void Function(GridViewSingleRowNotifierValue) onReorderSingleRow,
|
||||
required void Function(GridRowsVisibilityNotifierValue)
|
||||
onRowsVisibilityChanged,
|
||||
}) {
|
||||
@ -36,16 +46,18 @@ class GridViewListener {
|
||||
);
|
||||
|
||||
_rowsNotifier?.addPublishListener(onRowsChanged);
|
||||
_rowsVisibilityNotifier?.addPublishListener(onRowsVisibilityChanged);
|
||||
_rowsVisibility?.addPublishListener(onRowsVisibilityChanged);
|
||||
_reorderAllRows?.addPublishListener(onReorderAllRows);
|
||||
_reorderSingleRow?.addPublishListener(onReorderSingleRow);
|
||||
}
|
||||
|
||||
void _handler(GridDartNotification ty, Either<Uint8List, FlowyError> result) {
|
||||
switch (ty) {
|
||||
case GridDartNotification.DidUpdateGridViewRowsVisibility:
|
||||
result.fold(
|
||||
(payload) => _rowsVisibilityNotifier?.value =
|
||||
(payload) => _rowsVisibility?.value =
|
||||
left(GridRowsVisibilityChangesetPB.fromBuffer(payload)),
|
||||
(error) => _rowsVisibilityNotifier?.value = right(error),
|
||||
(error) => _rowsVisibility?.value = right(error),
|
||||
);
|
||||
break;
|
||||
case GridDartNotification.DidUpdateGridViewRows:
|
||||
@ -55,7 +67,20 @@ class GridViewListener {
|
||||
(error) => _rowsNotifier?.value = right(error),
|
||||
);
|
||||
break;
|
||||
|
||||
case GridDartNotification.DidReorderRows:
|
||||
result.fold(
|
||||
(payload) => _reorderAllRows?.value =
|
||||
left(ReorderAllRowsPB.fromBuffer(payload).rowOrders),
|
||||
(error) => _reorderAllRows?.value = right(error),
|
||||
);
|
||||
break;
|
||||
case GridDartNotification.DidReorderSingleRow:
|
||||
result.fold(
|
||||
(payload) => _reorderSingleRow?.value =
|
||||
left(ReorderSingleRowPB.fromBuffer(payload)),
|
||||
(error) => _reorderSingleRow?.value = right(error),
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -63,10 +88,16 @@ class GridViewListener {
|
||||
|
||||
Future<void> stop() async {
|
||||
await _listener?.stop();
|
||||
_rowsVisibilityNotifier?.dispose();
|
||||
_rowsVisibilityNotifier = null;
|
||||
_rowsVisibility?.dispose();
|
||||
_rowsVisibility = null;
|
||||
|
||||
_rowsNotifier?.dispose();
|
||||
_rowsNotifier = null;
|
||||
|
||||
_reorderAllRows?.dispose();
|
||||
_reorderAllRows = null;
|
||||
|
||||
_reorderSingleRow?.dispose();
|
||||
_reorderSingleRow = null;
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import 'package:app_flowy/plugins/grid/application/filter/filter_menu_bloc.dart'
|
||||
import 'package:app_flowy/plugins/grid/application/grid_data_controller.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/grid_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/sort/sort_menu_bloc.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui_web.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
|
||||
@ -20,13 +21,13 @@ import '../application/setting/setting_bloc.dart';
|
||||
import 'controller/grid_scroll.dart';
|
||||
import 'layout/layout.dart';
|
||||
import 'layout/sizes.dart';
|
||||
import 'widgets/accessory_menu.dart';
|
||||
import 'widgets/cell/cell_builder.dart';
|
||||
import 'widgets/row/grid_row.dart';
|
||||
import 'widgets/footer/grid_footer.dart';
|
||||
import 'widgets/header/grid_header.dart';
|
||||
import 'widgets/row/row_detail.dart';
|
||||
import 'widgets/shortcuts.dart';
|
||||
import 'widgets/filter/menu.dart';
|
||||
import 'widgets/toolbar/grid_toolbar.dart';
|
||||
|
||||
class GridPage extends StatefulWidget {
|
||||
@ -62,6 +63,12 @@ class _GridPageState extends State<GridPage> {
|
||||
fieldController: widget.gridController.fieldController,
|
||||
)..add(const GridFilterMenuEvent.initial()),
|
||||
),
|
||||
BlocProvider<SortMenuBloc>(
|
||||
create: (context) => SortMenuBloc(
|
||||
viewId: widget.view.id,
|
||||
fieldController: widget.gridController.fieldController,
|
||||
)..add(const SortMenuEvent.initial()),
|
||||
),
|
||||
BlocProvider<GridSettingBloc>(
|
||||
create: (context) => GridSettingBloc(gridId: widget.view.id),
|
||||
),
|
||||
@ -139,7 +146,7 @@ class _FlowyGridState extends State<FlowyGrid> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const GridToolbar(),
|
||||
const GridFilterMenu(),
|
||||
GridAccessoryMenu(viewId: state.gridId),
|
||||
_gridHeader(context, state.gridId),
|
||||
Flexible(child: child),
|
||||
const RowCountBadge(),
|
||||
@ -218,9 +225,22 @@ class _GridRowsState extends State<_GridRows> {
|
||||
_renderRow(context, item.rowInfo, animation),
|
||||
);
|
||||
},
|
||||
reorderSingleRow: (reorderRow, rowInfo) {
|
||||
// _key.currentState?.removeItem(
|
||||
// reorderRow.oldIndex,
|
||||
// (context, animation) => _renderRow(context, rowInfo, animation),
|
||||
// );
|
||||
// _key.currentState?.insertItem(reorderRow.newIndex);
|
||||
},
|
||||
);
|
||||
},
|
||||
buildWhen: (previous, current) => false,
|
||||
buildWhen: (previous, current) {
|
||||
return current.reason.whenOrNull(
|
||||
reorderRows: () => true,
|
||||
reorderSingleRow: (reorderRow, rowInfo) => true,
|
||||
) ??
|
||||
false;
|
||||
},
|
||||
builder: (context, state) {
|
||||
return SliverAnimatedList(
|
||||
key: _key,
|
||||
|
@ -11,7 +11,7 @@ class GridSize {
|
||||
static double get headerContainerPadding => 0 * scale;
|
||||
static double get cellHPadding => 10 * scale;
|
||||
static double get cellVPadding => 10 * scale;
|
||||
static double get typeOptionItemHeight => 32 * scale;
|
||||
static double get popoverItemHeight => 32 * scale;
|
||||
static double get typeOptionSeparatorHeight => 4 * scale;
|
||||
|
||||
static EdgeInsets get headerContentInsets => EdgeInsets.symmetric(
|
||||
|
@ -0,0 +1,89 @@
|
||||
import 'package:app_flowy/plugins/grid/application/filter/filter_menu_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/grid_accessory_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/sort/sort_menu_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/layout/sizes.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/sort/sort_menu.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import 'filter/filter_menu.dart';
|
||||
|
||||
class GridAccessoryMenu extends StatelessWidget {
|
||||
final String viewId;
|
||||
const GridAccessoryMenu({required this.viewId, Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => GridAccessoryMenuBloc(viewId: viewId),
|
||||
child: MultiBlocListener(
|
||||
listeners: [
|
||||
BlocListener<GridFilterMenuBloc, GridFilterMenuState>(
|
||||
listenWhen: (p, c) => p.isVisible != c.isVisible,
|
||||
listener: (context, state) => context
|
||||
.read<GridAccessoryMenuBloc>()
|
||||
.add(const GridAccessoryMenuEvent.toggleMenu())),
|
||||
BlocListener<SortMenuBloc, SortMenuState>(
|
||||
listenWhen: (p, c) => p.isVisible != c.isVisible,
|
||||
listener: (context, state) => context
|
||||
.read<GridAccessoryMenuBloc>()
|
||||
.add(const GridAccessoryMenuEvent.toggleMenu()),
|
||||
),
|
||||
],
|
||||
child: BlocBuilder<GridAccessoryMenuBloc, GridAccessoryMenuState>(
|
||||
builder: (context, state) {
|
||||
if (state.isVisible) {
|
||||
return const _AccessoryMenu();
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AccessoryMenu extends StatelessWidget {
|
||||
const _AccessoryMenu({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<GridAccessoryMenuBloc, GridAccessoryMenuState>(
|
||||
builder: (context, state) {
|
||||
return _wrapPadding(
|
||||
Column(
|
||||
children: [
|
||||
Divider(
|
||||
height: 1.0,
|
||||
color: AFThemeExtension.of(context).toggleOffFill,
|
||||
),
|
||||
const VSpace(6),
|
||||
IntrinsicHeight(
|
||||
child: Row(
|
||||
children: const [
|
||||
SortMenu(),
|
||||
HSpace(6),
|
||||
FilterMenu(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _wrapPadding(Widget child) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: GridSize.leadingHeaderPadding,
|
||||
vertical: 6,
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
@ -114,7 +114,7 @@ class _ChecklistOptionCellState extends State<_ChecklistOptionCell> {
|
||||
: svgWidget('editor/editor_uncheck');
|
||||
return _wrapPopover(
|
||||
SizedBox(
|
||||
height: GridSize.typeOptionItemHeight,
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
|
@ -163,9 +163,9 @@ class _CellCalendarWidgetState extends State<_CellCalendarWidget> {
|
||||
firstDay: kFirstDay,
|
||||
lastDay: kLastDay,
|
||||
focusedDay: state.focusedDay,
|
||||
rowHeight: GridSize.typeOptionItemHeight,
|
||||
rowHeight: GridSize.popoverItemHeight,
|
||||
calendarFormat: state.format,
|
||||
daysOfWeekHeight: GridSize.typeOptionItemHeight,
|
||||
daysOfWeekHeight: GridSize.popoverItemHeight,
|
||||
headerStyle: HeaderStyle(
|
||||
formatButtonVisible: false,
|
||||
titleCentered: true,
|
||||
@ -243,7 +243,7 @@ class _IncludeTimeButton extends StatelessWidget {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: SizedBox(
|
||||
height: GridSize.typeOptionItemHeight,
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: Padding(
|
||||
padding: GridSize.typeOptionContentInsets,
|
||||
child: Row(
|
||||
@ -327,7 +327,7 @@ class _TimeTextFieldState extends State<_TimeTextField> {
|
||||
child: Padding(
|
||||
padding: GridSize.typeOptionContentInsets,
|
||||
child: RoundedInputField(
|
||||
height: GridSize.typeOptionItemHeight,
|
||||
height: GridSize.popoverItemHeight,
|
||||
focusNode: _focusNode,
|
||||
autoFocus: true,
|
||||
hintText: widget.bloc.state.timeHintText,
|
||||
@ -376,7 +376,7 @@ class _DateTypeOptionButton extends StatelessWidget {
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: SizedBox(
|
||||
height: GridSize.typeOptionItemHeight,
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
text: FlowyText.medium(title),
|
||||
margin: GridSize.typeOptionContentInsets,
|
||||
|
@ -181,7 +181,7 @@ class _Title extends StatelessWidget {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: SizedBox(
|
||||
height: GridSize.typeOptionItemHeight,
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyText.medium(
|
||||
LocaleKeys.grid_selectOption_panelTitle.tr(),
|
||||
color: Theme.of(context).hintColor,
|
||||
@ -200,7 +200,7 @@ class _CreateOptionCell extends StatelessWidget {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: SizedBox(
|
||||
height: GridSize.typeOptionItemHeight,
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: Row(
|
||||
children: [
|
||||
FlowyText.medium(
|
||||
@ -254,7 +254,7 @@ class _SelectOptionCellState extends State<_SelectOptionCell> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final child = SizedBox(
|
||||
height: GridSize.typeOptionItemHeight,
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: SelectOptionTagCell(
|
||||
option: widget.option,
|
||||
onSelected: (option) {
|
||||
|
@ -98,7 +98,7 @@ class _SelectOptionFilterCellState extends State<_SelectOptionFilterCell> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: GridSize.typeOptionItemHeight,
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: SelectOptionTagCell(
|
||||
option: widget.option,
|
||||
onSelected: (option) {
|
||||
|
@ -57,7 +57,7 @@ class _GridCreateFilterListState extends State<GridCreateFilterList> {
|
||||
builder: (context, state) {
|
||||
final cells = state.creatableFields.map((fieldInfo) {
|
||||
return SizedBox(
|
||||
height: GridSize.typeOptionItemHeight,
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: _FilterPropertyCell(
|
||||
fieldInfo: fieldInfo,
|
||||
onTap: (fieldInfo) => createFilter(fieldInfo),
|
||||
|
@ -1,80 +1,49 @@
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/filter/filter_menu_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import 'create_filter_list.dart';
|
||||
import 'menu_item.dart';
|
||||
import 'filter_menu_item.dart';
|
||||
|
||||
class GridFilterMenu extends StatelessWidget {
|
||||
const GridFilterMenu({Key? key}) : super(key: key);
|
||||
class FilterMenu extends StatelessWidget {
|
||||
const FilterMenu({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<GridFilterMenuBloc, GridFilterMenuState>(
|
||||
builder: (context, state) {
|
||||
if (state.isVisible) {
|
||||
return _wrapPadding(Column(
|
||||
children: [
|
||||
buildDivider(context),
|
||||
const VSpace(6),
|
||||
buildFilterItems(state.viewId, state),
|
||||
],
|
||||
));
|
||||
} else {
|
||||
return const SizedBox();
|
||||
final List<Widget> children = [];
|
||||
children.addAll(
|
||||
state.filters
|
||||
.map((filterInfo) => FilterMenuItem(filterInfo: filterInfo))
|
||||
.toList(),
|
||||
);
|
||||
|
||||
if (state.creatableFields.isNotEmpty) {
|
||||
children.add(AddFilterButton(viewId: state.viewId));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _wrapPadding(Widget child) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: GridSize.leadingHeaderPadding,
|
||||
vertical: 6,
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildDivider(BuildContext context) {
|
||||
return Divider(
|
||||
height: 1.0,
|
||||
color: AFThemeExtension.of(context).toggleOffFill,
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildFilterItems(String viewId, GridFilterMenuState state) {
|
||||
final List<Widget> children = [];
|
||||
children.addAll(
|
||||
state.filters
|
||||
.map((filterInfo) => FilterMenuItem(filterInfo: filterInfo))
|
||||
.toList(),
|
||||
);
|
||||
|
||||
if (state.creatableFields.isNotEmpty) {
|
||||
children.add(AddFilterButton(viewId: viewId));
|
||||
}
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Wrap(
|
||||
spacing: 6,
|
||||
runSpacing: 4,
|
||||
children: children,
|
||||
return Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Wrap(
|
||||
spacing: 6,
|
||||
runSpacing: 4,
|
||||
children: children,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -77,7 +77,7 @@ class _EditFieldButton extends StatelessWidget {
|
||||
return BlocBuilder<FieldActionSheetBloc, FieldActionSheetState>(
|
||||
builder: (context, state) {
|
||||
return SizedBox(
|
||||
height: GridSize.typeOptionItemHeight,
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
text: FlowyText.medium(
|
||||
LocaleKeys.grid_field_editProperty.tr(),
|
||||
@ -119,7 +119,7 @@ class _FieldOperationList extends StatelessWidget {
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
height: GridSize.typeOptionItemHeight,
|
||||
height: GridSize.popoverItemHeight,
|
||||
width: cellWidth,
|
||||
child: FieldActionCell(
|
||||
fieldInfo: fieldInfo,
|
||||
|
@ -225,7 +225,7 @@ class _DeleteFieldButton extends StatelessWidget {
|
||||
);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4.0),
|
||||
child: SizedBox(height: GridSize.typeOptionItemHeight, child: button),
|
||||
child: SizedBox(height: GridSize.popoverItemHeight, child: button),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -58,7 +58,7 @@ class FieldTypeCell extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: GridSize.typeOptionItemHeight,
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
text: FlowyText.medium(fieldType.title()),
|
||||
onTap: () => onSelectField(fieldType),
|
||||
|
@ -103,7 +103,7 @@ class _SwitchFieldButton extends StatelessWidget {
|
||||
);
|
||||
|
||||
return SizedBox(
|
||||
height: GridSize.typeOptionItemHeight,
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: widget,
|
||||
);
|
||||
}
|
||||
|
@ -147,7 +147,7 @@ class DateFormatButton extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: GridSize.typeOptionItemHeight,
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
text: FlowyText.medium(LocaleKeys.grid_field_dateFormat.tr()),
|
||||
margin: buttonMargins,
|
||||
@ -178,7 +178,7 @@ class TimeFormatButton extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: GridSize.typeOptionItemHeight,
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
text: FlowyText.medium(LocaleKeys.grid_field_timeFormat.tr()),
|
||||
margin: buttonMargins,
|
||||
@ -204,7 +204,7 @@ class _IncludeTimeButton extends StatelessWidget {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: SizedBox(
|
||||
height: GridSize.typeOptionItemHeight,
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: Padding(
|
||||
padding: GridSize.typeOptionContentInsets,
|
||||
child: Row(
|
||||
@ -286,7 +286,7 @@ class DateFormatCell extends StatelessWidget {
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
height: GridSize.typeOptionItemHeight,
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
text: FlowyText.medium(dateFormat.title()),
|
||||
rightIcon: checkmark,
|
||||
@ -368,7 +368,7 @@ class TimeFormatCell extends StatelessWidget {
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
height: GridSize.typeOptionItemHeight,
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
text: FlowyText.medium(timeFormat.title()),
|
||||
rightIcon: checkmark,
|
||||
|
@ -52,13 +52,13 @@ class NumberTypeOptionWidget extends TypeOptionWidget {
|
||||
create: (context) =>
|
||||
NumberTypeOptionBloc(typeOptionContext: typeOptionContext),
|
||||
child: SizedBox(
|
||||
height: GridSize.typeOptionItemHeight,
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: BlocConsumer<NumberTypeOptionBloc, NumberTypeOptionState>(
|
||||
listener: (context, state) =>
|
||||
typeOptionContext.typeOption = state.typeOption,
|
||||
builder: (context, state) {
|
||||
final button = SizedBox(
|
||||
height: GridSize.typeOptionItemHeight,
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
margin: GridSize.typeOptionContentInsets,
|
||||
rightIcon: svgWidget(
|
||||
@ -177,7 +177,7 @@ class NumberFormatCell extends StatelessWidget {
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
height: GridSize.typeOptionItemHeight,
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
text: FlowyText.medium(format.title()),
|
||||
onTap: () => onSelected(format),
|
||||
|
@ -85,7 +85,7 @@ class OptionTitle extends StatelessWidget {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: SizedBox(
|
||||
height: GridSize.typeOptionItemHeight,
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: Row(children: children),
|
||||
),
|
||||
);
|
||||
@ -185,7 +185,7 @@ class _OptionCellState extends State<_OptionCell> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final child = SizedBox(
|
||||
height: GridSize.typeOptionItemHeight,
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: SelectOptionTagCell(
|
||||
option: widget.option,
|
||||
onSelected: (SelectOptionPB pb) {
|
||||
@ -243,7 +243,7 @@ class _AddOptionButton extends StatelessWidget {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: SizedBox(
|
||||
height: GridSize.typeOptionItemHeight,
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
text: FlowyText.medium(LocaleKeys.grid_field_addSelectOption.tr()),
|
||||
onTap: () {
|
||||
|
@ -101,7 +101,7 @@ class _DeleteTag extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: GridSize.typeOptionItemHeight,
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
text: FlowyText.medium(LocaleKeys.grid_selectOption_deleteTag.tr()),
|
||||
leftIcon: svgWidget(
|
||||
@ -162,7 +162,7 @@ class SelectOptionColorList extends StatelessWidget {
|
||||
Padding(
|
||||
padding: GridSize.typeOptionContentInsets,
|
||||
child: SizedBox(
|
||||
height: GridSize.typeOptionItemHeight,
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyText.medium(
|
||||
LocaleKeys.grid_selectOption_colorPanelTitle.tr(),
|
||||
textAlign: TextAlign.left,
|
||||
@ -212,7 +212,7 @@ class _SelectOptionColorCell extends StatelessWidget {
|
||||
);
|
||||
|
||||
return SizedBox(
|
||||
height: GridSize.typeOptionItemHeight,
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
text: FlowyText.medium(color.optionName()),
|
||||
leftIcon: colorIcon,
|
||||
|
@ -56,7 +56,7 @@ class _RowActionCell extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: GridSize.typeOptionItemHeight,
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
text: FlowyText.medium(
|
||||
action.title(),
|
||||
|
@ -0,0 +1,168 @@
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/sort/sort_create_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/layout/sizes.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/header/field_type_extension.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text_field.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class GridCreateSortList extends StatefulWidget {
|
||||
final String viewId;
|
||||
final GridFieldController fieldController;
|
||||
final VoidCallback onClosed;
|
||||
final VoidCallback? onCreateSort;
|
||||
|
||||
const GridCreateSortList({
|
||||
required this.viewId,
|
||||
required this.fieldController,
|
||||
required this.onClosed,
|
||||
this.onCreateSort,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
@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: _SortPropertyCell(
|
||||
fieldInfo: fieldInfo,
|
||||
onTap: (fieldInfo) => createSort(fieldInfo),
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
|
||||
List<Widget> slivers = [
|
||||
SliverPersistentHeader(
|
||||
pinned: true,
|
||||
delegate: _FilterTextFieldDelegate(),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: ListView.separated(
|
||||
controller: ScrollController(),
|
||||
shrinkWrap: true,
|
||||
itemCount: cells.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return cells[index];
|
||||
},
|
||||
separatorBuilder: (BuildContext context, int index) {
|
||||
return VSpace(GridSize.typeOptionSeparatorHeight);
|
||||
},
|
||||
),
|
||||
),
|
||||
];
|
||||
return CustomScrollView(
|
||||
shrinkWrap: true,
|
||||
slivers: slivers,
|
||||
controller: ScrollController(),
|
||||
physics: StyledScrollPhysics(),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
editBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void createSort(FieldInfo field) {
|
||||
editBloc.add(CreateSortEvent.createDefaultSort(field));
|
||||
widget.onCreateSort?.call();
|
||||
}
|
||||
}
|
||||
|
||||
class _FilterTextFieldDelegate extends SliverPersistentHeaderDelegate {
|
||||
_FilterTextFieldDelegate();
|
||||
|
||||
double fixHeight = 46;
|
||||
|
||||
@override
|
||||
Widget build(
|
||||
BuildContext context, double shrinkOffset, bool overlapsContent) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
height: fixHeight,
|
||||
child: FlowyTextField(
|
||||
hintText: LocaleKeys.grid_settings_filterBy.tr(),
|
||||
onChanged: (text) {
|
||||
context
|
||||
.read<CreateSortBloc>()
|
||||
.add(CreateSortEvent.didReceiveFilterText(text));
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
double get maxExtent => fixHeight;
|
||||
|
||||
@override
|
||||
double get minExtent => fixHeight;
|
||||
|
||||
@override
|
||||
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class _SortPropertyCell extends StatelessWidget {
|
||||
final FieldInfo fieldInfo;
|
||||
final Function(FieldInfo) onTap;
|
||||
const _SortPropertyCell({
|
||||
required this.fieldInfo,
|
||||
required this.onTap,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FlowyButton(
|
||||
text: FlowyText.medium(fieldInfo.name),
|
||||
onTap: () => onTap(fieldInfo),
|
||||
leftIcon: svgWidget(
|
||||
fieldInfo.fieldType.iconName(),
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-grid/sort_entities.pbenum.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class OrderPanel extends StatelessWidget {
|
||||
final Function(GridSortConditionPB) onCondition;
|
||||
const OrderPanel({required this.onCondition, Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final List<Widget> children = GridSortConditionPB.values.map((condition) {
|
||||
return SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
text: FlowyText.medium(textFromCondition(condition)),
|
||||
onTap: () => onCondition(condition),
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(minWidth: 160),
|
||||
child: IntrinsicWidth(
|
||||
child: IntrinsicHeight(
|
||||
child: Column(
|
||||
children: children,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String textFromCondition(GridSortConditionPB condition) {
|
||||
switch (condition) {
|
||||
case GridSortConditionPB.Ascending:
|
||||
return LocaleKeys.grid_sort_ascending.tr();
|
||||
case GridSortConditionPB.Descending:
|
||||
return LocaleKeys.grid_sort_descending.tr();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SortChoiceButton extends StatelessWidget {
|
||||
final String text;
|
||||
final VoidCallback? onTap;
|
||||
final Widget? leftIcon;
|
||||
final Widget? rightIcon;
|
||||
final Radius radius;
|
||||
final bool editable;
|
||||
|
||||
const SortChoiceButton({
|
||||
required this.text,
|
||||
this.onTap,
|
||||
this.radius = const Radius.circular(14),
|
||||
this.leftIcon,
|
||||
this.rightIcon,
|
||||
this.editable = true,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final borderSide = BorderSide(
|
||||
color: AFThemeExtension.of(context).toggleOffFill,
|
||||
width: 1.0,
|
||||
);
|
||||
|
||||
final decoration = BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
border: Border.fromBorderSide(borderSide),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(14)),
|
||||
);
|
||||
|
||||
return FlowyButton(
|
||||
decoration: decoration,
|
||||
useIntrinsicWidth: true,
|
||||
text: FlowyText(text),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||
radius: BorderRadius.all(radius),
|
||||
leftIcon: leftIcon,
|
||||
rightIcon: rightIcon,
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
onTap: onTap,
|
||||
disable: !editable,
|
||||
disableOpacity: 1.0,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,293 @@
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/sort/sort_editor_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/sort/util.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/layout/sizes.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/sort/create_sort_list.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-grid/sort_entities.pbenum.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'order_panel.dart';
|
||||
import 'sort_choice_button.dart';
|
||||
import 'sort_info.dart';
|
||||
|
||||
class SortEditor extends StatefulWidget {
|
||||
final String viewId;
|
||||
final List<SortInfo> sortInfos;
|
||||
final GridFieldController fieldController;
|
||||
const SortEditor({
|
||||
required this.viewId,
|
||||
required this.fieldController,
|
||||
required this.sortInfos,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<SortEditor> createState() => _SortEditorState();
|
||||
}
|
||||
|
||||
class _SortEditorState extends State<SortEditor> {
|
||||
final popoverMutex = PopoverMutex();
|
||||
|
||||
@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) {
|
||||
return IntrinsicWidth(
|
||||
child: IntrinsicHeight(
|
||||
child: Column(
|
||||
children: [
|
||||
_SortList(popoverMutex: popoverMutex),
|
||||
_AddSortButton(
|
||||
viewId: widget.viewId,
|
||||
fieldController: widget.fieldController,
|
||||
popoverMutex: popoverMutex,
|
||||
),
|
||||
_DeleteSortButton(popoverMutex: popoverMutex),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SortList extends StatelessWidget {
|
||||
final PopoverMutex popoverMutex;
|
||||
const _SortList({required this.popoverMutex, Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<SortEditorBloc, SortEditorState>(
|
||||
builder: (context, state) {
|
||||
final List<Widget> children = state.sortInfos
|
||||
.map((info) => Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
child: _SortItem(
|
||||
sortInfo: info,
|
||||
popoverMutex: popoverMutex,
|
||||
),
|
||||
))
|
||||
.toList();
|
||||
|
||||
return Column(
|
||||
children: children,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SortItem extends StatelessWidget {
|
||||
final SortInfo sortInfo;
|
||||
final PopoverMutex popoverMutex;
|
||||
const _SortItem({
|
||||
required this.popoverMutex,
|
||||
required this.sortInfo,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final nameButton = SortChoiceButton(
|
||||
text: sortInfo.fieldInfo.name,
|
||||
editable: false,
|
||||
onTap: () {},
|
||||
);
|
||||
final orderButton = _OrderButton(
|
||||
sortInfo: sortInfo,
|
||||
popoverMutex: popoverMutex,
|
||||
);
|
||||
|
||||
final deleteButton = FlowyIconButton(
|
||||
width: 26,
|
||||
onPressed: () {
|
||||
context
|
||||
.read<SortEditorBloc>()
|
||||
.add(SortEditorEvent.deleteSort(sortInfo));
|
||||
},
|
||||
iconPadding: const EdgeInsets.all(5),
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
icon: svgWidget(
|
||||
"home/close",
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
);
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
SizedBox(height: 26, child: nameButton),
|
||||
const HSpace(6),
|
||||
SizedBox(height: 26, child: orderButton),
|
||||
const HSpace(16),
|
||||
deleteButton
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
String textFromCondition(GridSortConditionPB condition) {
|
||||
switch (condition) {
|
||||
case GridSortConditionPB.Ascending:
|
||||
return LocaleKeys.grid_sort_ascending.tr();
|
||||
case GridSortConditionPB.Descending:
|
||||
return LocaleKeys.grid_sort_descending.tr();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
class _AddSortButton extends StatefulWidget {
|
||||
final String viewId;
|
||||
final GridFieldController fieldController;
|
||||
final PopoverMutex popoverMutex;
|
||||
const _AddSortButton({
|
||||
required this.viewId,
|
||||
required this.fieldController,
|
||||
required this.popoverMutex,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<_AddSortButton> createState() => _AddSortButtonState();
|
||||
}
|
||||
|
||||
class _AddSortButtonState extends State<_AddSortButton> {
|
||||
final _popoverController = PopoverController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppFlowyPopover(
|
||||
controller: _popoverController,
|
||||
mutex: widget.popoverMutex,
|
||||
direction: PopoverDirection.bottomWithLeftAligned,
|
||||
constraints: BoxConstraints.loose(const Size(200, 300)),
|
||||
offset: const Offset(0, 10),
|
||||
triggerActions: PopoverTriggerFlags.none,
|
||||
asBarrier: true,
|
||||
child: SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
disable: getCreatableSorts(widget.fieldController.fieldInfos).isEmpty,
|
||||
text: FlowyText.medium(LocaleKeys.grid_sort_addSort.tr()),
|
||||
onTap: () => _popoverController.show(),
|
||||
leftIcon: svgWidget(
|
||||
"home/add",
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
popupBuilder: (BuildContext context) {
|
||||
return GridCreateSortList(
|
||||
viewId: widget.viewId,
|
||||
fieldController: widget.fieldController,
|
||||
onClosed: () => _popoverController.close(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _DeleteSortButton extends StatelessWidget {
|
||||
final PopoverMutex popoverMutex;
|
||||
const _DeleteSortButton({required this.popoverMutex, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<SortEditorBloc, SortEditorState>(
|
||||
builder: (context, state) {
|
||||
return SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
text: FlowyText.medium(LocaleKeys.grid_sort_deleteSort.tr()),
|
||||
onTap: () {
|
||||
context
|
||||
.read<SortEditorBloc>()
|
||||
.add(const SortEditorEvent.deleteAllSorts());
|
||||
},
|
||||
leftIcon: svgWidget(
|
||||
"editor/delete",
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _OrderButton extends StatefulWidget {
|
||||
final SortInfo sortInfo;
|
||||
final PopoverMutex popoverMutex;
|
||||
const _OrderButton({
|
||||
required this.popoverMutex,
|
||||
required this.sortInfo,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_OrderButtonState createState() => _OrderButtonState();
|
||||
}
|
||||
|
||||
class _OrderButtonState extends State<_OrderButton> {
|
||||
final PopoverController popoverController = PopoverController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final arrow = Transform.rotate(
|
||||
angle: -math.pi / 2,
|
||||
child: svgWidget("home/arrow_left"),
|
||||
);
|
||||
|
||||
return AppFlowyPopover(
|
||||
controller: popoverController,
|
||||
mutex: widget.popoverMutex,
|
||||
constraints: BoxConstraints.loose(const Size(340, 200)),
|
||||
direction: PopoverDirection.bottomWithLeftAligned,
|
||||
triggerActions: PopoverTriggerFlags.none,
|
||||
popupBuilder: (BuildContext popoverContext) {
|
||||
return OrderPanel(
|
||||
onCondition: (condition) {
|
||||
context
|
||||
.read<SortEditorBloc>()
|
||||
.add(SortEditorEvent.setCondition(widget.sortInfo, condition));
|
||||
popoverController.close();
|
||||
},
|
||||
);
|
||||
},
|
||||
child: SortChoiceButton(
|
||||
text: textFromCondition(widget.sortInfo.sortPB.condition),
|
||||
rightIcon: arrow,
|
||||
onTap: () => popoverController.show(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String textFromCondition(GridSortConditionPB condition) {
|
||||
switch (condition) {
|
||||
case GridSortConditionPB.Ascending:
|
||||
return LocaleKeys.grid_sort_ascending.tr();
|
||||
case GridSortConditionPB.Descending:
|
||||
return LocaleKeys.grid_sort_descending.tr();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-grid/sort_entities.pb.dart';
|
||||
|
||||
class SortInfo {
|
||||
final SortPB sortPB;
|
||||
final FieldInfo fieldInfo;
|
||||
|
||||
SortInfo({required this.sortPB, required this.fieldInfo});
|
||||
|
||||
String get sortId => sortPB.id;
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
import 'package:app_flowy/plugins/grid/application/sort/sort_menu_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/sort/sort_info.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'sort_choice_button.dart';
|
||||
import 'sort_editor.dart';
|
||||
|
||||
class SortMenu extends StatelessWidget {
|
||||
const SortMenu({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<SortMenuBloc, SortMenuState>(
|
||||
builder: (context, state) {
|
||||
if (state.sortInfos.isNotEmpty) {
|
||||
return AppFlowyPopover(
|
||||
controller: PopoverController(),
|
||||
constraints: BoxConstraints.loose(const Size(340, 200)),
|
||||
direction: PopoverDirection.bottomWithLeftAligned,
|
||||
popupBuilder: (BuildContext popoverContext) {
|
||||
return SortEditor(
|
||||
viewId: state.viewId,
|
||||
fieldController: context.read<SortMenuBloc>().fieldController,
|
||||
sortInfos: state.sortInfos,
|
||||
);
|
||||
},
|
||||
child: SortChoiceChip(sortInfos: state.sortInfos),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SortChoiceChip extends StatelessWidget {
|
||||
final List<SortInfo> sortInfos;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
const SortChoiceChip({
|
||||
Key? key,
|
||||
required this.sortInfos,
|
||||
this.onTap,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final arrow = Transform.rotate(
|
||||
angle: -math.pi / 2,
|
||||
child: svgWidget("home/arrow_left"),
|
||||
);
|
||||
|
||||
final text = LocaleKeys.grid_settings_sort.tr();
|
||||
final leftIcon = svgWidget(
|
||||
"grid/setting/sort",
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
);
|
||||
|
||||
return SizedBox(
|
||||
height: 28,
|
||||
child: SortChoiceButton(
|
||||
text: text,
|
||||
leftIcon: leftIcon,
|
||||
rightIcon: arrow,
|
||||
onTap: onTap,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -54,7 +54,7 @@ class _FilterButtonState extends State<FilterButton> {
|
||||
Widget _wrapPopover(BuildContext buildContext, Widget child) {
|
||||
return AppFlowyPopover(
|
||||
controller: _popoverController,
|
||||
direction: PopoverDirection.leftWithTopAligned,
|
||||
direction: PopoverDirection.bottomWithLeftAligned,
|
||||
constraints: BoxConstraints.loose(const Size(200, 300)),
|
||||
offset: const Offset(0, 10),
|
||||
triggerActions: PopoverTriggerFlags.none,
|
||||
|
@ -77,7 +77,7 @@ class _GridGroupCell extends StatelessWidget {
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
height: GridSize.typeOptionItemHeight,
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
text: FlowyText.medium(fieldInfo.name),
|
||||
leftIcon: svgWidget(
|
||||
|
@ -103,7 +103,7 @@ class _GridPropertyCellState extends State<_GridPropertyCell> {
|
||||
);
|
||||
|
||||
return SizedBox(
|
||||
height: GridSize.typeOptionItemHeight,
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: _editFieldButton(context, checkmark),
|
||||
);
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ class _SettingItem extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: GridSize.typeOptionItemHeight,
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
text: FlowyText.medium(
|
||||
action.title(),
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/toolbar/sort_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../application/field/field_controller.dart';
|
||||
@ -27,6 +28,7 @@ class GridToolbar extends StatelessWidget {
|
||||
SizedBox(width: GridSize.leadingHeaderPadding),
|
||||
const Spacer(),
|
||||
const FilterButton(),
|
||||
const SortButton(),
|
||||
const SettingButton(),
|
||||
],
|
||||
),
|
||||
|
@ -45,7 +45,7 @@ class _SettingButtonState extends State<SettingButton> {
|
||||
return AppFlowyPopover(
|
||||
controller: _popoverController,
|
||||
constraints: BoxConstraints.loose(const Size(260, 400)),
|
||||
direction: PopoverDirection.leftWithTopAligned,
|
||||
direction: PopoverDirection.bottomWithLeftAligned,
|
||||
offset: const Offset(0, 10),
|
||||
margin: EdgeInsets.zero,
|
||||
triggerActions: PopoverTriggerFlags.none,
|
||||
|
@ -0,0 +1,80 @@
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/sort/sort_menu_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/sort/create_sort_list.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class SortButton extends StatefulWidget {
|
||||
const SortButton({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<SortButton> createState() => _SortButtonState();
|
||||
}
|
||||
|
||||
class _SortButtonState extends State<SortButton> {
|
||||
final _popoverController = PopoverController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<SortMenuBloc, SortMenuState>(
|
||||
builder: (context, state) {
|
||||
final textColor = state.sortInfos.isEmpty
|
||||
? null
|
||||
: Theme.of(context).colorScheme.primary;
|
||||
|
||||
return wrapPopover(
|
||||
context,
|
||||
SizedBox(
|
||||
height: 26,
|
||||
child: FlowyTextButton(
|
||||
LocaleKeys.grid_settings_sort.tr(),
|
||||
fontSize: 13,
|
||||
fontColor: textColor,
|
||||
fillColor: Colors.transparent,
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 2),
|
||||
onPressed: () {
|
||||
final bloc = context.read<SortMenuBloc>();
|
||||
if (bloc.state.sortInfos.isEmpty) {
|
||||
_popoverController.show();
|
||||
} else {
|
||||
bloc.add(const SortMenuEvent.toggleMenu());
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget wrapPopover(BuildContext buildContext, Widget child) {
|
||||
return AppFlowyPopover(
|
||||
controller: _popoverController,
|
||||
direction: PopoverDirection.bottomWithLeftAligned,
|
||||
constraints: BoxConstraints.loose(const Size(200, 300)),
|
||||
offset: const Offset(0, 10),
|
||||
margin: const EdgeInsets.all(6),
|
||||
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());
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ packages:
|
||||
name: async
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.8.2"
|
||||
version: "2.9.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -21,21 +21,14 @@ packages:
|
||||
name: characters
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
charcode:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: charcode
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
version: "1.2.1"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
version: "1.1.1"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -49,14 +42,14 @@ packages:
|
||||
name: crypto
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "3.0.2"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
version: "1.3.1"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@ -75,7 +68,7 @@ packages:
|
||||
name: flutter_svg
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.4"
|
||||
version: "1.1.6"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
@ -87,35 +80,35 @@ packages:
|
||||
name: lints
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
version: "2.0.1"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.12.11"
|
||||
version: "0.12.12"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.4"
|
||||
version: "0.1.5"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.7.0"
|
||||
version: "1.8.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.1"
|
||||
version: "1.8.2"
|
||||
path_drawing:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -136,7 +129,7 @@ packages:
|
||||
name: petitparser
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "5.0.0"
|
||||
version: "5.1.0"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
@ -148,7 +141,7 @@ packages:
|
||||
name: source_span
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.2"
|
||||
version: "1.9.0"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -169,21 +162,21 @@ packages:
|
||||
name: string_scanner
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
version: "1.1.1"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
version: "1.2.1"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.9"
|
||||
version: "0.4.12"
|
||||
textstyle_extensions:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -197,21 +190,21 @@ packages:
|
||||
name: time
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
version: "2.1.3"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
version: "1.3.1"
|
||||
uuid:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: uuid
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.4"
|
||||
version: "3.0.7"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -227,5 +220,5 @@ packages:
|
||||
source: hosted
|
||||
version: "6.1.0"
|
||||
sdks:
|
||||
dart: ">=2.17.0 <3.0.0"
|
||||
dart: ">=2.18.0 <3.0.0"
|
||||
flutter: ">=2.11.0-0.1.pre"
|
||||
|
@ -7,7 +7,7 @@ packages:
|
||||
name: animations
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
version: "2.0.7"
|
||||
appflowy_popover:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -21,7 +21,7 @@ packages:
|
||||
name: async
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.8.2"
|
||||
version: "2.9.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -35,21 +35,14 @@ packages:
|
||||
name: characters
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
charcode:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: charcode
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
version: "1.2.1"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
version: "1.1.1"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -63,35 +56,35 @@ packages:
|
||||
name: crypto
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "3.0.2"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cupertino_icons
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.3"
|
||||
version: "1.0.5"
|
||||
dartz:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dartz
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.10.0-nullsafety.2"
|
||||
version: "0.10.1"
|
||||
equatable:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: equatable
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
version: "2.0.5"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
version: "1.3.1"
|
||||
flowy_infra:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -138,7 +131,7 @@ packages:
|
||||
name: flutter_svg
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.4"
|
||||
version: "1.1.6"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
@ -162,42 +155,42 @@ packages:
|
||||
name: lint
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.5.3"
|
||||
version: "1.10.0"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
version: "2.0.1"
|
||||
loading_indicator:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: loading_indicator
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "3.1.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.12.11"
|
||||
version: "0.12.12"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.4"
|
||||
version: "0.1.5"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.7.0"
|
||||
version: "1.8.0"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -211,7 +204,7 @@ packages:
|
||||
name: path
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.1"
|
||||
version: "1.8.2"
|
||||
path_drawing:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -232,21 +225,21 @@ packages:
|
||||
name: petitparser
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "5.0.0"
|
||||
version: "5.1.0"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
version: "2.1.3"
|
||||
provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: provider
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "6.0.1"
|
||||
version: "6.0.5"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
@ -258,7 +251,7 @@ packages:
|
||||
name: source_span
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.2"
|
||||
version: "1.9.0"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -279,7 +272,7 @@ packages:
|
||||
name: string_scanner
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
version: "1.1.1"
|
||||
styled_widget:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -293,14 +286,14 @@ packages:
|
||||
name: term_glyph
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
version: "1.2.1"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.9"
|
||||
version: "0.4.12"
|
||||
textstyle_extensions:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -314,21 +307,21 @@ packages:
|
||||
name: time
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
version: "2.1.3"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
version: "1.3.1"
|
||||
uuid:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: uuid
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.4"
|
||||
version: "3.0.7"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -344,5 +337,5 @@ packages:
|
||||
source: hosted
|
||||
version: "6.1.0"
|
||||
sdks:
|
||||
dart: ">=2.17.0 <3.0.0"
|
||||
flutter: ">=2.11.0-0.1.pre"
|
||||
dart: ">=2.18.0 <3.0.0"
|
||||
flutter: ">=3.0.0"
|
||||
|
@ -7,7 +7,7 @@ packages:
|
||||
name: async
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.8.2"
|
||||
version: "2.9.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -21,21 +21,14 @@ packages:
|
||||
name: characters
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
charcode:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: charcode
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
version: "1.2.1"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
version: "1.1.1"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -49,7 +42,7 @@ packages:
|
||||
name: fake_async
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
version: "1.3.1"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@ -80,35 +73,35 @@ packages:
|
||||
name: matcher
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.12.11"
|
||||
version: "0.12.12"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.4"
|
||||
version: "0.1.5"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.7.0"
|
||||
version: "1.8.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.1"
|
||||
version: "1.8.2"
|
||||
plugin_platform_interface:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
version: "2.1.3"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
@ -120,7 +113,7 @@ packages:
|
||||
name: source_span
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.2"
|
||||
version: "1.9.0"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -141,21 +134,21 @@ packages:
|
||||
name: string_scanner
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
version: "1.1.1"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
version: "1.2.1"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.9"
|
||||
version: "0.4.12"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -7,7 +7,7 @@ packages:
|
||||
name: async
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.8.2"
|
||||
version: "2.9.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -21,21 +21,14 @@ packages:
|
||||
name: characters
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
charcode:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: charcode
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
version: "1.2.1"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
version: "1.1.1"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -49,7 +42,7 @@ packages:
|
||||
name: fake_async
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
version: "1.3.1"
|
||||
flowy_infra_ui_platform_interface:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -99,35 +92,35 @@ packages:
|
||||
name: matcher
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.12.11"
|
||||
version: "0.12.12"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.4"
|
||||
version: "0.1.5"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.7.0"
|
||||
version: "1.8.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.1"
|
||||
version: "1.8.2"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
version: "2.1.3"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
@ -139,7 +132,7 @@ packages:
|
||||
name: source_span
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.2"
|
||||
version: "1.9.0"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -160,21 +153,21 @@ packages:
|
||||
name: string_scanner
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
version: "1.1.1"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
version: "1.2.1"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.9"
|
||||
version: "0.4.12"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -45,7 +45,7 @@ class AppFlowyPopover extends StatelessWidget {
|
||||
windowPadding: windowPadding,
|
||||
popupBuilder: (context) {
|
||||
final child = popupBuilder(context);
|
||||
debugPrint("show popover: $child");
|
||||
debugPrint("Show popover: $child");
|
||||
return _PopoverContainer(
|
||||
constraints: constraints,
|
||||
margin: margin,
|
||||
|
@ -18,6 +18,8 @@ class FlowyButton extends StatelessWidget {
|
||||
final BorderRadius? radius;
|
||||
final BoxDecoration? decoration;
|
||||
final bool useIntrinsicWidth;
|
||||
final bool disable;
|
||||
final double disableOpacity;
|
||||
|
||||
const FlowyButton({
|
||||
Key? key,
|
||||
@ -32,23 +34,29 @@ class FlowyButton extends StatelessWidget {
|
||||
this.radius,
|
||||
this.decoration,
|
||||
this.useIntrinsicWidth = false,
|
||||
this.disable = false,
|
||||
this.disableOpacity = 0.5,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: onTap,
|
||||
child: FlowyHover(
|
||||
style: HoverStyle(
|
||||
borderRadius: radius ?? Corners.s6Border,
|
||||
hoverColor: hoverColor ?? Theme.of(context).colorScheme.secondary,
|
||||
if (!disable) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: onTap,
|
||||
child: FlowyHover(
|
||||
style: HoverStyle(
|
||||
borderRadius: radius ?? Corners.s6Border,
|
||||
hoverColor: hoverColor ?? Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
onHover: onHover,
|
||||
isSelected: () => isSelected,
|
||||
builder: (context, onHover) => _render(),
|
||||
),
|
||||
onHover: onHover,
|
||||
isSelected: () => isSelected,
|
||||
builder: (context, onHover) => _render(),
|
||||
),
|
||||
);
|
||||
);
|
||||
} else {
|
||||
return Opacity(opacity: disableOpacity, child: _render());
|
||||
}
|
||||
}
|
||||
|
||||
Widget _render() {
|
||||
@ -63,7 +71,7 @@ class FlowyButton extends StatelessWidget {
|
||||
children.add(Expanded(child: text));
|
||||
|
||||
if (rightIcon != null) {
|
||||
children.add(const HSpace(10));
|
||||
children.add(const HSpace(6));
|
||||
// No need to define the size of rightIcon. Just use its intrinsic width
|
||||
children.add(rightIcon!);
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ packages:
|
||||
name: animations
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
version: "2.0.7"
|
||||
appflowy_popover:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -21,7 +21,7 @@ packages:
|
||||
name: async
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.8.2"
|
||||
version: "2.9.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -35,21 +35,14 @@ packages:
|
||||
name: characters
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
charcode:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: charcode
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
version: "1.2.1"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
version: "1.1.1"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -63,28 +56,28 @@ packages:
|
||||
name: crypto
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "3.0.2"
|
||||
dartz:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dartz
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.10.0-nullsafety.2"
|
||||
version: "0.10.1"
|
||||
equatable:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: equatable
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
version: "2.0.5"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
version: "1.3.1"
|
||||
flowy_infra:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -124,7 +117,7 @@ packages:
|
||||
name: flutter_svg
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.4"
|
||||
version: "1.1.6"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
@ -148,42 +141,42 @@ packages:
|
||||
name: lint
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.5.3"
|
||||
version: "1.10.0"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
version: "2.0.1"
|
||||
loading_indicator:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: loading_indicator
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "3.1.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.12.11"
|
||||
version: "0.12.12"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.4"
|
||||
version: "0.1.5"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.7.0"
|
||||
version: "1.8.0"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -197,7 +190,7 @@ packages:
|
||||
name: path
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.1"
|
||||
version: "1.8.2"
|
||||
path_drawing:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -218,21 +211,21 @@ packages:
|
||||
name: petitparser
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "5.0.0"
|
||||
version: "5.1.0"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
version: "2.1.3"
|
||||
provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: provider
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "6.0.1"
|
||||
version: "6.0.5"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
@ -244,7 +237,7 @@ packages:
|
||||
name: source_span
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.2"
|
||||
version: "1.9.0"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -265,7 +258,7 @@ packages:
|
||||
name: string_scanner
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
version: "1.1.1"
|
||||
styled_widget:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -279,14 +272,14 @@ packages:
|
||||
name: term_glyph
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
version: "1.2.1"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.9"
|
||||
version: "0.4.12"
|
||||
textstyle_extensions:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -300,21 +293,21 @@ packages:
|
||||
name: time
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
version: "2.1.3"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
version: "1.3.1"
|
||||
uuid:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: uuid
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.4"
|
||||
version: "3.0.7"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -330,5 +323,5 @@ packages:
|
||||
source: hosted
|
||||
version: "6.1.0"
|
||||
sdks:
|
||||
dart: ">=2.17.0 <3.0.0"
|
||||
flutter: ">=2.11.0-0.1.pre"
|
||||
dart: ">=2.18.0 <3.0.0"
|
||||
flutter: ">=3.0.0"
|
||||
|
490
frontend/app_flowy/packages/flowy_sdk/pubspec.lock
Normal file
490
frontend/app_flowy/packages/flowy_sdk/pubspec.lock
Normal file
@ -0,0 +1,490 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
_fe_analyzer_shared:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "50.0.0"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "5.2.0"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.3.1"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.9.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
build:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.3.1"
|
||||
build_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_config
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
build_daemon:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_daemon
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
build_resolvers:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_resolvers
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
build_runner:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_runner
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.3.3"
|
||||
build_runner_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_runner_core
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "7.2.7"
|
||||
built_collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: built_collection
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "5.1.1"
|
||||
built_value:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: built_value
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "8.4.2"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
checked_yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: checked_yaml
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
code_builder:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: code_builder
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.4.0"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.16.0"
|
||||
convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: convert
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: crypto
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
dart_style:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dart_style
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.2.4"
|
||||
dartz:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dartz
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.10.1"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
ffi:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: ffi
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "6.1.4"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fixnum
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_lints
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
freezed:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: freezed
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
freezed_annotation:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: freezed_annotation
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
frontend_server_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: frontend_server_client
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: glob
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
graphs:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: graphs
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
http_multi_server:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_multi_server
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.2.1"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: io
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.3"
|
||||
isolates:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: isolates
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.3+8"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: js
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.6.5"
|
||||
json_annotation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: json_annotation
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.7.0"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
logger:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: logger
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: logging
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.12.12"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.5"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.3"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_config
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.2"
|
||||
pool:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pool
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.5.1"
|
||||
protobuf:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: protobuf
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pub_semver
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
pubspec_parse:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pubspec_parse
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
shelf_web_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_web_socket
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.3"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.99"
|
||||
source_gen:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_gen
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.6"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.9.0"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.10.0"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
stream_transform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_transform
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.12"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: timing
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: watcher
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_socket_channel
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: yaml
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
sdks:
|
||||
dart: ">=2.18.0 <3.0.0"
|
||||
flutter: ">=1.17.0"
|
@ -157,6 +157,9 @@ pub enum ErrorCode {
|
||||
|
||||
#[error("Out of bounds")]
|
||||
OutOfBounds = 52,
|
||||
|
||||
#[error("Sort id is empty")]
|
||||
SortIdIsEmpty = 53,
|
||||
}
|
||||
|
||||
impl ErrorCode {
|
||||
|
@ -2,7 +2,7 @@ use crate::entities::parser::NotEmptyStr;
|
||||
use crate::entities::{
|
||||
AlterFilterParams, AlterFilterPayloadPB, AlterSortParams, AlterSortPayloadPB, DeleteFilterParams,
|
||||
DeleteFilterPayloadPB, DeleteGroupParams, DeleteGroupPayloadPB, DeleteSortParams, DeleteSortPayloadPB,
|
||||
InsertGroupParams, InsertGroupPayloadPB, RepeatedFilterPB, RepeatedGroupConfigurationPB,
|
||||
InsertGroupParams, InsertGroupPayloadPB, RepeatedFilterPB, RepeatedGroupConfigurationPB, RepeatedSortPB,
|
||||
};
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::ErrorCode;
|
||||
@ -25,6 +25,9 @@ pub struct GridSettingPB {
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub group_configurations: RepeatedGroupConfigurationPB,
|
||||
|
||||
#[pb(index = 5)]
|
||||
pub sorts: RepeatedSortPB,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
|
@ -1,13 +1,14 @@
|
||||
use crate::entities::parser::NotEmptyStr;
|
||||
use crate::entities::FieldType;
|
||||
use crate::services::sort::SortType;
|
||||
use std::sync::Arc;
|
||||
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::ErrorCode;
|
||||
use grid_rev_model::{FieldTypeRevision, SortCondition, SortRevision};
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct GridSortPB {
|
||||
pub struct SortPB {
|
||||
#[pb(index = 1)]
|
||||
pub id: String,
|
||||
|
||||
@ -21,7 +22,7 @@ pub struct GridSortPB {
|
||||
pub condition: GridSortConditionPB,
|
||||
}
|
||||
|
||||
impl std::convert::From<&SortRevision> for GridSortPB {
|
||||
impl std::convert::From<&SortRevision> for SortPB {
|
||||
fn from(sort_rev: &SortRevision) -> Self {
|
||||
Self {
|
||||
id: sort_rev.id.clone(),
|
||||
@ -32,6 +33,26 @@ impl std::convert::From<&SortRevision> for GridSortPB {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct RepeatedSortPB {
|
||||
#[pb(index = 1)]
|
||||
pub items: Vec<SortPB>,
|
||||
}
|
||||
|
||||
impl std::convert::From<Vec<Arc<SortRevision>>> for RepeatedSortPB {
|
||||
fn from(revs: Vec<Arc<SortRevision>>) -> Self {
|
||||
RepeatedSortPB {
|
||||
items: revs.into_iter().map(|rev| rev.as_ref().into()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<Vec<SortPB>> for RepeatedSortPB {
|
||||
fn from(items: Vec<SortPB>) -> Self {
|
||||
Self { items }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
|
||||
#[repr(u8)]
|
||||
pub enum GridSortConditionPB {
|
||||
@ -64,7 +85,7 @@ pub struct AlterSortPayloadPB {
|
||||
#[pb(index = 3)]
|
||||
pub field_type: FieldType,
|
||||
|
||||
/// Create a new filter if the filter_id is None
|
||||
/// Create a new sort if the sort_id is None
|
||||
#[pb(index = 4, one_of)]
|
||||
pub sort_id: Option<String>,
|
||||
|
||||
@ -83,9 +104,10 @@ impl TryInto<AlterSortParams> for AlterSortPayloadPB {
|
||||
let field_id = NotEmptyStr::parse(self.field_id)
|
||||
.map_err(|_| ErrorCode::FieldIdIsEmpty)?
|
||||
.0;
|
||||
|
||||
let sort_id = match self.sort_id {
|
||||
None => None,
|
||||
Some(filter_id) => Some(NotEmptyStr::parse(filter_id).map_err(|_| ErrorCode::FilterIdIsEmpty)?.0),
|
||||
Some(sort_id) => Some(NotEmptyStr::parse(sort_id).map_err(|_| ErrorCode::SortIdIsEmpty)?.0),
|
||||
};
|
||||
|
||||
Ok(AlterSortParams {
|
||||
@ -164,16 +186,25 @@ pub struct SortChangesetNotificationPB {
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub insert_sorts: Vec<GridSortPB>,
|
||||
pub insert_sorts: Vec<SortPB>,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub delete_sorts: Vec<GridSortPB>,
|
||||
pub delete_sorts: Vec<SortPB>,
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub update_sorts: Vec<GridSortPB>,
|
||||
pub update_sorts: Vec<SortPB>,
|
||||
}
|
||||
|
||||
impl SortChangesetNotificationPB {
|
||||
pub fn new(view_id: String) -> Self {
|
||||
Self {
|
||||
view_id,
|
||||
insert_sorts: vec![],
|
||||
delete_sorts: vec![],
|
||||
update_sorts: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extend(&mut self, other: SortChangesetNotificationPB) {
|
||||
self.insert_sorts.extend(other.insert_sorts);
|
||||
self.delete_sorts.extend(other.delete_sorts);
|
||||
@ -194,8 +225,11 @@ pub struct ReorderAllRowsPB {
|
||||
#[derive(Debug, Default, ProtoBuf)]
|
||||
pub struct ReorderSingleRowPB {
|
||||
#[pb(index = 1)]
|
||||
pub old_index: i32,
|
||||
pub row_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub old_index: i32,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub new_index: i32,
|
||||
}
|
||||
|
@ -81,6 +81,30 @@ pub(crate) async fn get_all_filters_handler(
|
||||
data_result(filters)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(data, manager), err)]
|
||||
pub(crate) async fn get_all_sorts_handler(
|
||||
data: AFPluginData<GridIdPB>,
|
||||
manager: AFPluginState<Arc<GridManager>>,
|
||||
) -> DataResult<RepeatedSortPB, FlowyError> {
|
||||
let grid_id: GridIdPB = data.into_inner();
|
||||
let editor = manager.open_grid(grid_id.as_ref()).await?;
|
||||
let sorts = RepeatedSortPB {
|
||||
items: editor.get_all_sorts(grid_id.as_ref()).await?,
|
||||
};
|
||||
data_result(sorts)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(data, manager), err)]
|
||||
pub(crate) async fn delete_all_sorts_handler(
|
||||
data: AFPluginData<GridIdPB>,
|
||||
manager: AFPluginState<Arc<GridManager>>,
|
||||
) -> Result<(), FlowyError> {
|
||||
let grid_id: GridIdPB = data.into_inner();
|
||||
let editor = manager.open_grid(grid_id.as_ref()).await?;
|
||||
let _ = editor.delete_all_sorts(grid_id.as_ref()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(data, manager), err)]
|
||||
pub(crate) async fn get_fields_handler(
|
||||
data: AFPluginData<GetFieldPayloadPB>,
|
||||
|
@ -13,6 +13,8 @@ pub fn init(grid_manager: Arc<GridManager>) -> AFPlugin {
|
||||
.event(GridEvent::GetGridSetting, get_grid_setting_handler)
|
||||
.event(GridEvent::UpdateGridSetting, update_grid_setting_handler)
|
||||
.event(GridEvent::GetAllFilters, get_all_filters_handler)
|
||||
.event(GridEvent::GetAllSorts, get_all_sorts_handler)
|
||||
.event(GridEvent::DeleteAllSorts, delete_all_sorts_handler)
|
||||
// Field
|
||||
.event(GridEvent::GetFields, get_fields_handler)
|
||||
.event(GridEvent::UpdateField, update_field_handler)
|
||||
@ -75,6 +77,12 @@ pub enum GridEvent {
|
||||
#[event(input = "GridIdPB", output = "RepeatedFilterPB")]
|
||||
GetAllFilters = 4,
|
||||
|
||||
#[event(input = "GridIdPB", output = "RepeatedSortPB")]
|
||||
GetAllSorts = 5,
|
||||
|
||||
#[event(input = "GridIdPB")]
|
||||
DeleteAllSorts = 6,
|
||||
|
||||
/// [GetFields] event is used to get the grid's settings.
|
||||
///
|
||||
/// The event handler accepts a [GetFieldPayloadPB] and returns a [RepeatedFieldPB]
|
||||
|
@ -92,7 +92,6 @@ impl GridManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all, err)]
|
||||
pub async fn open_grid<T: AsRef<str>>(&self, grid_id: T) -> FlowyResult<Arc<GridRevisionEditor>> {
|
||||
let grid_id = grid_id.as_ref();
|
||||
let _ = self.migration.run_v1_migration(grid_id).await;
|
||||
@ -103,16 +102,20 @@ impl GridManager {
|
||||
pub async fn close_grid<T: AsRef<str>>(&self, grid_id: T) -> FlowyResult<()> {
|
||||
let grid_id = grid_id.as_ref();
|
||||
tracing::Span::current().record("grid_id", &grid_id);
|
||||
|
||||
self.grid_editors.write().await.remove(grid_id).await;
|
||||
// self.task_scheduler.write().await.unregister_handler(grid_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// #[tracing::instrument(level = "debug", skip(self), err)]
|
||||
pub async fn get_grid_editor(&self, grid_id: &str) -> FlowyResult<Arc<GridRevisionEditor>> {
|
||||
match self.grid_editors.read().await.get(grid_id) {
|
||||
None => Err(FlowyError::internal().context("Should call open_grid function first")),
|
||||
let read_guard = self.grid_editors.read().await;
|
||||
let editor = read_guard.get(grid_id);
|
||||
match editor {
|
||||
None => {
|
||||
// Drop the read_guard ASAP in case of the following read/write lock
|
||||
drop(read_guard);
|
||||
self.open_grid(grid_id).await
|
||||
}
|
||||
Some(editor) => Ok(editor),
|
||||
}
|
||||
}
|
||||
@ -125,6 +128,7 @@ impl GridManager {
|
||||
let mut grid_editors = self.grid_editors.write().await;
|
||||
let db_pool = self.grid_user.db_pool()?;
|
||||
let editor = self.make_grid_rev_editor(grid_id, db_pool).await?;
|
||||
tracing::trace!("Open grid: {}", grid_id);
|
||||
grid_editors.insert(grid_id.to_string(), editor.clone());
|
||||
Ok(editor)
|
||||
}
|
||||
|
@ -4,9 +4,9 @@ use crate::services::cell::{
|
||||
FromCellChangesetString, FromCellString, TypeCellData,
|
||||
};
|
||||
use crate::services::field::{
|
||||
default_order, CheckboxTypeOptionPB, ChecklistTypeOptionPB, DateTypeOptionPB, MultiSelectTypeOptionPB,
|
||||
NumberTypeOptionPB, RichTextTypeOptionPB, SingleSelectTypeOptionPB, TypeOption, TypeOptionCellData,
|
||||
TypeOptionCellDataCompare, TypeOptionCellDataFilter, TypeOptionTransform, URLTypeOptionPB,
|
||||
CheckboxTypeOptionPB, ChecklistTypeOptionPB, DateTypeOptionPB, MultiSelectTypeOptionPB, NumberTypeOptionPB,
|
||||
RichTextTypeOptionPB, SingleSelectTypeOptionPB, TypeOption, TypeOptionCellData, TypeOptionCellDataCompare,
|
||||
TypeOptionCellDataFilter, TypeOptionTransform, URLTypeOptionPB,
|
||||
};
|
||||
use crate::services::filter::FilterType;
|
||||
use flowy_error::FlowyResult;
|
||||
@ -221,15 +221,13 @@ where
|
||||
|
||||
fn handle_cell_compare(&self, left_cell_data: &str, right_cell_data: &str, field_rev: &FieldRevision) -> Ordering {
|
||||
let field_type: FieldType = field_rev.ty.into();
|
||||
let left = self.get_decoded_cell_data(left_cell_data.to_owned(), &field_type, field_rev);
|
||||
let right = self.get_decoded_cell_data(right_cell_data.to_owned(), &field_type, field_rev);
|
||||
|
||||
match (left, right) {
|
||||
(Ok(left), Ok(right)) => self.apply_cmp(&left, &right),
|
||||
(Ok(_), Err(_)) => Ordering::Greater,
|
||||
(Err(_), Ok(_)) => Ordering::Less,
|
||||
(Err(_), Err(_)) => default_order(),
|
||||
}
|
||||
let left = self
|
||||
.get_decoded_cell_data(left_cell_data.to_owned(), &field_type, field_rev)
|
||||
.unwrap_or_default();
|
||||
let right = self
|
||||
.get_decoded_cell_data(right_cell_data.to_owned(), &field_type, field_rev)
|
||||
.unwrap_or_default();
|
||||
self.apply_cmp(&left, &right)
|
||||
}
|
||||
|
||||
fn handle_cell_filter(
|
||||
|
@ -115,7 +115,7 @@ impl FilterController {
|
||||
.collect::<HashMap<String, Arc<FieldRevision>>>()
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "receive_task_result", level = "trace", skip_all, fields(filter_result), err)]
|
||||
#[tracing::instrument(name = "process_filter_task", level = "trace", skip_all, fields(filter_result), err)]
|
||||
pub async fn process(&mut self, predicate: &str) -> FlowyResult<()> {
|
||||
let event_type = FilterEvent::from_str(predicate).unwrap();
|
||||
match event_type {
|
||||
|
@ -629,14 +629,6 @@ impl GridRevisionEditor {
|
||||
self.view_manager.get_filters(&filter_id).await
|
||||
}
|
||||
|
||||
pub async fn insert_group(&self, params: InsertGroupParams) -> FlowyResult<()> {
|
||||
self.view_manager.insert_or_update_group(params).await
|
||||
}
|
||||
|
||||
pub async fn delete_group(&self, params: DeleteGroupParams) -> FlowyResult<()> {
|
||||
self.view_manager.delete_group(params).await
|
||||
}
|
||||
|
||||
pub async fn create_or_update_filter(&self, params: AlterFilterParams) -> FlowyResult<()> {
|
||||
self.view_manager.create_or_update_filter(params).await?;
|
||||
Ok(())
|
||||
@ -647,6 +639,20 @@ impl GridRevisionEditor {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_all_sorts(&self, view_id: &str) -> FlowyResult<Vec<SortPB>> {
|
||||
Ok(self
|
||||
.view_manager
|
||||
.get_all_sorts(view_id)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|sort| SortPB::from(sort.as_ref()))
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub async fn delete_all_sorts(&self, view_id: &str) -> FlowyResult<()> {
|
||||
self.view_manager.delete_all_sorts(view_id).await
|
||||
}
|
||||
|
||||
pub async fn delete_sort(&self, params: DeleteSortParams) -> FlowyResult<()> {
|
||||
self.view_manager.delete_sort(params).await?;
|
||||
Ok(())
|
||||
@ -657,6 +663,14 @@ impl GridRevisionEditor {
|
||||
Ok(sort_rev)
|
||||
}
|
||||
|
||||
pub async fn insert_group(&self, params: InsertGroupParams) -> FlowyResult<()> {
|
||||
self.view_manager.insert_or_update_group(params).await
|
||||
}
|
||||
|
||||
pub async fn delete_group(&self, params: DeleteGroupParams) -> FlowyResult<()> {
|
||||
self.view_manager.delete_group(params).await
|
||||
}
|
||||
|
||||
pub async fn move_row(&self, params: MoveRowParams) -> FlowyResult<()> {
|
||||
let MoveRowParams {
|
||||
view_id: _,
|
||||
|
@ -39,6 +39,7 @@ impl SortController {
|
||||
pub fn new<T>(
|
||||
view_id: &str,
|
||||
handler_id: &str,
|
||||
sorts: Vec<Arc<SortRevision>>,
|
||||
delegate: T,
|
||||
task_scheduler: Arc<RwLock<TaskDispatcher>>,
|
||||
cell_data_cache: AtomicCellDataCache,
|
||||
@ -52,7 +53,7 @@ impl SortController {
|
||||
handler_id: handler_id.to_string(),
|
||||
delegate: Box::new(delegate),
|
||||
task_scheduler,
|
||||
sorts: vec![],
|
||||
sorts,
|
||||
cell_data_cache,
|
||||
row_index_cache: Default::default(),
|
||||
notifier,
|
||||
@ -72,7 +73,7 @@ impl SortController {
|
||||
self.gen_task(task_type, QualityOfService::Background).await;
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "receive_sort_task_result", level = "trace", skip_all, err)]
|
||||
#[tracing::instrument(name = "process_sort_task", level = "trace", skip_all, err)]
|
||||
pub async fn process(&mut self, predicate: &str) -> FlowyResult<()> {
|
||||
let event_type = SortEvent::from_str(predicate).unwrap();
|
||||
let mut row_revs = self.delegate.get_row_revs().await;
|
||||
@ -103,6 +104,7 @@ impl SortController {
|
||||
return Ok(());
|
||||
}
|
||||
let notification = ReorderSingleRowResult {
|
||||
row_id,
|
||||
view_id: self.view_id.clone(),
|
||||
old_index: old_row_index,
|
||||
new_index: new_row_index,
|
||||
@ -120,9 +122,6 @@ impl SortController {
|
||||
|
||||
#[tracing::instrument(name = "schedule_sort_task", level = "trace", skip(self))]
|
||||
async fn gen_task(&self, task_type: SortEvent, qos: QualityOfService) {
|
||||
if self.sorts.is_empty() {
|
||||
return;
|
||||
}
|
||||
let task_id = self.task_scheduler.read().await.next_task_id();
|
||||
let task = Task::new(&self.handler_id, task_id, TaskContent::Text(task_type.to_string()), qos);
|
||||
self.task_scheduler.write().await.add_task(task);
|
||||
@ -142,13 +141,19 @@ impl SortController {
|
||||
});
|
||||
}
|
||||
|
||||
pub async fn delete_all_sorts(&mut self) {
|
||||
self.sorts.clear();
|
||||
self.gen_task(SortEvent::SortDidChanged, QualityOfService::Background)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn did_update_view_field_type_option(&self, _field_rev: &FieldRevision) {
|
||||
//
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(self))]
|
||||
pub async fn did_receive_changes(&mut self, changeset: SortChangeset) -> SortChangesetNotificationPB {
|
||||
let mut notification = SortChangesetNotificationPB::default();
|
||||
let mut notification = SortChangesetNotificationPB::new(self.view_id.clone());
|
||||
if let Some(insert_sort) = changeset.insert_sort {
|
||||
if let Some(sort) = self.delegate.get_sort_rev(insert_sort).await {
|
||||
notification.insert_sorts.push(sort.as_ref().into());
|
||||
@ -172,10 +177,11 @@ impl SortController {
|
||||
}
|
||||
}
|
||||
|
||||
if !notification.insert_sorts.is_empty() || !notification.delete_sorts.is_empty() {
|
||||
self.gen_task(SortEvent::SortDidChanged, QualityOfService::Background)
|
||||
if !notification.is_empty() {
|
||||
self.gen_task(SortEvent::SortDidChanged, QualityOfService::UserInteractive)
|
||||
.await;
|
||||
}
|
||||
tracing::trace!("sort notification: {:?}", notification);
|
||||
notification
|
||||
}
|
||||
}
|
||||
@ -200,6 +206,7 @@ fn cmp_row(
|
||||
_ => default_order(),
|
||||
};
|
||||
|
||||
// The order is calculated by Ascending. So reverse the order if the SortCondition is descending.
|
||||
match sort.condition {
|
||||
SortCondition::Ascending => order,
|
||||
SortCondition::Descending => order.reverse(),
|
||||
@ -216,7 +223,7 @@ fn cmp_cell(
|
||||
match TypeOptionCellExt::new_with_cell_data_cache(field_rev.as_ref(), Some(cell_data_cache.clone()))
|
||||
.get_type_option_cell_data_handler(&field_type)
|
||||
{
|
||||
None => Ordering::Less,
|
||||
None => default_order(),
|
||||
Some(handler) => {
|
||||
let cal_order = || {
|
||||
let left_cell_str = TypeCellData::try_from(left_cell).ok()?.into_inner();
|
||||
|
@ -47,6 +47,7 @@ impl ReorderAllRowsResult {
|
||||
#[derive(Clone)]
|
||||
pub struct ReorderSingleRowResult {
|
||||
pub view_id: String,
|
||||
pub row_id: String,
|
||||
pub old_index: usize,
|
||||
pub new_index: usize,
|
||||
}
|
||||
|
@ -54,6 +54,7 @@ impl GridViewChangedReceiverRunner {
|
||||
}
|
||||
GridViewChanged::ReorderSingleRowNotification(notification) => {
|
||||
let reorder_row = ReorderSingleRowPB {
|
||||
row_id: notification.row_id,
|
||||
old_index: notification.old_index as i32,
|
||||
new_index: notification.new_index as i32,
|
||||
};
|
||||
|
@ -340,24 +340,6 @@ impl GridViewRevisionEditor {
|
||||
self.group_controller.read().await.field_id().to_string()
|
||||
}
|
||||
|
||||
pub async fn get_view_setting(&self) -> GridSettingPB {
|
||||
let field_revs = self.delegate.get_field_revs(None).await;
|
||||
make_grid_setting(&*self.pad.read().await, &field_revs)
|
||||
}
|
||||
|
||||
pub async fn get_all_view_filters(&self) -> Vec<Arc<FilterRevision>> {
|
||||
let field_revs = self.delegate.get_field_revs(None).await;
|
||||
self.pad.read().await.get_all_filters(&field_revs)
|
||||
}
|
||||
|
||||
pub async fn get_view_filters(&self, filter_type: &FilterType) -> Vec<Arc<FilterRevision>> {
|
||||
let field_type_rev: FieldTypeRevision = filter_type.field_type.clone().into();
|
||||
self.pad
|
||||
.read()
|
||||
.await
|
||||
.get_filters(&filter_type.field_id, &field_type_rev)
|
||||
}
|
||||
|
||||
/// Initialize new group when grouping by a new field
|
||||
///
|
||||
pub async fn initialize_new_group(&self, params: InsertGroupParams) -> FlowyResult<()> {
|
||||
@ -385,6 +367,18 @@ impl GridViewRevisionEditor {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_view_setting(&self) -> GridSettingPB {
|
||||
let field_revs = self.delegate.get_field_revs(None).await;
|
||||
let grid_setting = make_grid_setting(&*self.pad.read().await, &field_revs);
|
||||
grid_setting
|
||||
}
|
||||
|
||||
pub async fn get_all_view_sorts(&self) -> Vec<Arc<SortRevision>> {
|
||||
let field_revs = self.delegate.get_field_revs(None).await;
|
||||
self.pad.read().await.get_all_sorts(&field_revs)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(self), err)]
|
||||
pub async fn insert_view_sort(&self, params: AlterSortParams) -> FlowyResult<SortRevision> {
|
||||
let sort_type = SortType::from(¶ms);
|
||||
let is_exist = params.sort_id.is_some();
|
||||
@ -420,14 +414,13 @@ impl GridViewRevisionEditor {
|
||||
.did_receive_changes(SortChangeset::from_insert(sort_type))
|
||||
.await
|
||||
};
|
||||
drop(sort_controller);
|
||||
|
||||
self.notify_did_update_sort(changeset).await;
|
||||
drop(sort_controller);
|
||||
Ok(sort_rev)
|
||||
}
|
||||
|
||||
pub async fn delete_view_sort(&self, params: DeleteSortParams) -> FlowyResult<()> {
|
||||
let changeset = self
|
||||
let notification = self
|
||||
.sort_controller
|
||||
.write()
|
||||
.await
|
||||
@ -441,10 +434,39 @@ impl GridViewRevisionEditor {
|
||||
})
|
||||
.await?;
|
||||
|
||||
self.notify_did_update_sort(changeset).await;
|
||||
self.notify_did_update_sort(notification).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_all_view_sorts(&self) -> FlowyResult<()> {
|
||||
let all_sorts = self.get_all_view_sorts().await;
|
||||
self.sort_controller.write().await.delete_all_sorts().await;
|
||||
let _ = self
|
||||
.modify(|pad| {
|
||||
let changeset = pad.delete_all_sorts()?;
|
||||
Ok(changeset)
|
||||
})
|
||||
.await?;
|
||||
|
||||
let mut notification = SortChangesetNotificationPB::new(self.view_id.clone());
|
||||
notification.delete_sorts = all_sorts.into_iter().map(|sort| SortPB::from(sort.as_ref())).collect();
|
||||
self.notify_did_update_sort(notification).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_all_view_filters(&self) -> Vec<Arc<FilterRevision>> {
|
||||
let field_revs = self.delegate.get_field_revs(None).await;
|
||||
self.pad.read().await.get_all_filters(&field_revs)
|
||||
}
|
||||
|
||||
pub async fn get_view_filters(&self, filter_type: &FilterType) -> Vec<Arc<FilterRevision>> {
|
||||
let field_type_rev: FieldTypeRevision = filter_type.field_type.clone().into();
|
||||
self.pad
|
||||
.read()
|
||||
.await
|
||||
.get_filters(&filter_type.field_id, &field_type_rev)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(self), err)]
|
||||
pub async fn insert_view_filter(&self, params: AlterFilterParams) -> FlowyResult<()> {
|
||||
let filter_type = FilterType::from(¶ms);
|
||||
@ -605,16 +627,16 @@ impl GridViewRevisionEditor {
|
||||
.send();
|
||||
}
|
||||
|
||||
pub async fn notify_did_update_filter(&self, changeset: FilterChangesetNotificationPB) {
|
||||
send_dart_notification(&changeset.view_id, GridDartNotification::DidUpdateFilter)
|
||||
.payload(changeset)
|
||||
pub async fn notify_did_update_filter(&self, notification: FilterChangesetNotificationPB) {
|
||||
send_dart_notification(¬ification.view_id, GridDartNotification::DidUpdateFilter)
|
||||
.payload(notification)
|
||||
.send();
|
||||
}
|
||||
|
||||
pub async fn notify_did_update_sort(&self, changeset: SortChangesetNotificationPB) {
|
||||
if !changeset.is_empty() {
|
||||
send_dart_notification(&changeset.view_id, GridDartNotification::DidUpdateSort)
|
||||
.payload(changeset)
|
||||
pub async fn notify_did_update_sort(&self, notification: SortChangesetNotificationPB) {
|
||||
if !notification.is_empty() {
|
||||
send_dart_notification(¬ification.view_id, GridDartNotification::DidUpdateSort)
|
||||
.payload(notification)
|
||||
.send();
|
||||
}
|
||||
}
|
||||
@ -764,6 +786,8 @@ async fn make_sort_controller(
|
||||
cell_data_cache: AtomicCellDataCache,
|
||||
) -> Arc<RwLock<SortController>> {
|
||||
let handler_id = gen_handler_id();
|
||||
let field_revs = delegate.get_field_revs(None).await;
|
||||
let sorts = pad.read().await.get_all_sorts(&field_revs);
|
||||
let sort_delegate = GridViewSortDelegateImpl {
|
||||
editor_delegate: delegate.clone(),
|
||||
view_revision_pad: pad,
|
||||
@ -773,6 +797,7 @@ async fn make_sort_controller(
|
||||
let sort_controller = Arc::new(RwLock::new(SortController::new(
|
||||
view_id,
|
||||
&handler_id,
|
||||
sorts,
|
||||
sort_delegate,
|
||||
task_scheduler.clone(),
|
||||
cell_data_cache,
|
||||
|
@ -138,11 +138,21 @@ impl GridViewManager {
|
||||
view_editor.delete_view_filter(params).await
|
||||
}
|
||||
|
||||
pub async fn get_all_sorts(&self, view_id: &str) -> FlowyResult<Vec<Arc<SortRevision>>> {
|
||||
let view_editor = self.get_view_editor(view_id).await?;
|
||||
Ok(view_editor.get_all_view_sorts().await)
|
||||
}
|
||||
|
||||
pub async fn create_or_update_sort(&self, params: AlterSortParams) -> FlowyResult<SortRevision> {
|
||||
let view_editor = self.get_view_editor(¶ms.view_id).await?;
|
||||
view_editor.insert_view_sort(params).await
|
||||
}
|
||||
|
||||
pub async fn delete_all_sorts(&self, view_id: &str) -> FlowyResult<()> {
|
||||
let view_editor = self.get_view_editor(view_id).await?;
|
||||
view_editor.delete_all_view_sorts().await
|
||||
}
|
||||
|
||||
pub async fn delete_sort(&self, params: DeleteSortParams) -> FlowyResult<()> {
|
||||
let view_editor = self.get_view_editor(¶ms.view_id).await?;
|
||||
view_editor.delete_view_sort(params).await
|
||||
|
@ -119,12 +119,14 @@ pub(crate) async fn apply_change(
|
||||
|
||||
pub fn make_grid_setting(view_pad: &GridViewRevisionPad, field_revs: &[Arc<FieldRevision>]) -> GridSettingPB {
|
||||
let layout_type: GridLayout = view_pad.layout.clone().into();
|
||||
let filter_configurations = view_pad.get_all_filters(field_revs);
|
||||
let filters = view_pad.get_all_filters(field_revs);
|
||||
let group_configurations = view_pad.get_groups_by_field_revs(field_revs);
|
||||
let sorts = view_pad.get_all_sorts(field_revs);
|
||||
GridSettingPB {
|
||||
layouts: GridLayoutPB::all(),
|
||||
layout_type,
|
||||
filters: filter_configurations.into(),
|
||||
filters: filters.into(),
|
||||
sorts: sorts.into(),
|
||||
group_configurations: group_configurations.into(),
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,10 @@
|
||||
use flowy_grid::entities::FieldType;
|
||||
use std::sync::Arc;
|
||||
|
||||
use flowy_grid::services::field::{
|
||||
ChecklistTypeOptionPB, DateCellChangeset, MultiSelectTypeOptionPB, SelectOptionPB, SingleSelectTypeOptionPB,
|
||||
URLCellChangeset, URLCellData,
|
||||
};
|
||||
use flowy_grid::services::row::RowRevisionBuilder;
|
||||
use grid_rev_model::{FieldRevision, RowRevision};
|
||||
use std::sync::Arc;
|
||||
|
||||
use strum::EnumCount;
|
||||
|
||||
|
@ -0,0 +1,50 @@
|
||||
use crate::grid::sort_test::script::{GridSortTest, SortScript::*};
|
||||
use flowy_grid::entities::FieldType;
|
||||
use grid_rev_model::SortCondition;
|
||||
|
||||
#[tokio::test]
|
||||
async fn sort_checkbox_and_then_text_by_descending_test() {
|
||||
let mut test = GridSortTest::new().await;
|
||||
let checkbox_field = test.get_first_field_rev(FieldType::Checkbox);
|
||||
let text_field = test.get_first_field_rev(FieldType::RichText);
|
||||
let scripts = vec![
|
||||
AssertCellContentOrder {
|
||||
field_id: checkbox_field.id.clone(),
|
||||
orders: vec!["Yes", "Yes", "No", "No", "No", "Yes"],
|
||||
},
|
||||
AssertCellContentOrder {
|
||||
field_id: text_field.id.clone(),
|
||||
orders: vec!["A", "", "C", "DA", "AE", "AE"],
|
||||
},
|
||||
// Insert checkbox sort
|
||||
InsertSort {
|
||||
field_rev: checkbox_field.clone(),
|
||||
condition: SortCondition::Descending,
|
||||
},
|
||||
AssertCellContentOrder {
|
||||
field_id: checkbox_field.id.clone(),
|
||||
orders: vec!["Yes", "Yes", "Yes", "No", "No", "No"],
|
||||
},
|
||||
AssertCellContentOrder {
|
||||
field_id: text_field.id.clone(),
|
||||
orders: vec!["A", "", "AE", "C", "DA", "AE"],
|
||||
},
|
||||
// Insert text sort. After inserting the text sort, the order of the rows
|
||||
// will be changed.
|
||||
// before: ["A", "", "AE", "C", "DA", "AE"]
|
||||
// after: ["", "A", "AE", "AE", "C", "DA"]
|
||||
InsertSort {
|
||||
field_rev: text_field.clone(),
|
||||
condition: SortCondition::Ascending,
|
||||
},
|
||||
AssertCellContentOrder {
|
||||
field_id: checkbox_field.id.clone(),
|
||||
orders: vec!["Yes", "Yes", "Yes", "No", "No", "No"],
|
||||
},
|
||||
AssertCellContentOrder {
|
||||
field_id: text_field.id.clone(),
|
||||
orders: vec!["", "A", "AE", "AE", "C", "DA"],
|
||||
},
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
mod checkbox_and_text_test;
|
||||
mod multi_sort_test;
|
||||
mod script;
|
||||
mod single_sort_test;
|
||||
|
@ -125,7 +125,7 @@ impl<Connection: 'static> RevisionManager<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all, fields(deserializer, object) err)]
|
||||
#[tracing::instrument(level = "trace", skip_all, fields(deserializer, object) err)]
|
||||
pub async fn initialize<B>(&mut self, _cloud: Option<Arc<dyn RevisionCloudService>>) -> FlowyResult<B::Output>
|
||||
where
|
||||
B: RevisionObjectDeserializer,
|
||||
|
@ -127,8 +127,8 @@ impl GridViewRevisionPad {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_all_sorts(&self, field_revs: &[Arc<FieldRevision>]) -> Vec<Arc<SortRevision>> {
|
||||
self.sorts.get_objects_by_field_revs(field_revs)
|
||||
pub fn get_all_sorts(&self, _field_revs: &[Arc<FieldRevision>]) -> Vec<Arc<SortRevision>> {
|
||||
self.sorts.get_all_objects()
|
||||
}
|
||||
|
||||
/// For the moment, a field type only have one filter.
|
||||
@ -148,12 +148,12 @@ impl GridViewRevisionPad {
|
||||
|
||||
pub fn insert_sort(
|
||||
&mut self,
|
||||
sort_id: &str,
|
||||
field_id: &str,
|
||||
sort_rev: SortRevision,
|
||||
) -> CollaborateResult<Option<GridViewRevisionChangeset>> {
|
||||
self.modify(|view| {
|
||||
let field_type = sort_rev.field_type;
|
||||
view.sorts.add_object(sort_id, &field_type, sort_rev);
|
||||
view.sorts.add_object(field_id, &field_type, sort_rev);
|
||||
Ok(Some(()))
|
||||
})
|
||||
}
|
||||
@ -194,6 +194,13 @@ impl GridViewRevisionPad {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn delete_all_sorts(&mut self) -> CollaborateResult<Option<GridViewRevisionChangeset>> {
|
||||
self.modify(|view| {
|
||||
view.sorts.clear();
|
||||
Ok(Some(()))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_all_filters(&self, field_revs: &[Arc<FieldRevision>]) -> Vec<Arc<FilterRevision>> {
|
||||
self.filters.get_objects_by_field_revs(field_revs)
|
||||
}
|
||||
|
@ -67,6 +67,7 @@ impl TaskDispatcher {
|
||||
let content = task.content.take()?;
|
||||
if let Some(handler) = self.handlers.get(&task.handler_id) {
|
||||
task.set_state(TaskState::Processing);
|
||||
tracing::trace!("Run {} task with content: {:?}", handler.handler_name(), content);
|
||||
match tokio::time::timeout(self.timeout, handler.run(content)).await {
|
||||
Ok(result) => match result {
|
||||
Ok(_) => task.set_state(TaskState::Done),
|
||||
@ -91,6 +92,7 @@ impl TaskDispatcher {
|
||||
pub fn add_task(&mut self, task: Task) {
|
||||
debug_assert!(!task.state().is_done());
|
||||
if task.state().is_done() {
|
||||
tracing::warn!("Should not add a task which state is done");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -50,6 +50,7 @@ impl Ord for PendingTask {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TaskContent {
|
||||
Text(String),
|
||||
Blob(Vec<u8>),
|
||||
|
Loading…
Reference in New Issue
Block a user