mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: per-view field visibility UI (#3296)
* chore: default field settings if not exist * chore: field settings listeners and services * chore: don't need to updateFieldInfos * feat: per-view field visibilty UI * fix: remove unresolved imports
This commit is contained in:
parent
9b7ff375b2
commit
f3aaff77b9
@ -0,0 +1,55 @@
|
|||||||
|
import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/tar_bar/tar_bar_add_button.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pbenum.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
|
||||||
|
import 'util/database_test_op.dart';
|
||||||
|
import 'util/util.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
group('database field settings', () {
|
||||||
|
testWidgets('field visibility', (tester) async {
|
||||||
|
await tester.initializeAppFlowy();
|
||||||
|
await tester.tapGoButton();
|
||||||
|
|
||||||
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
|
await tester.tapCreateLinkedDatabaseViewButton(AddButtonAction.grid);
|
||||||
|
|
||||||
|
// create a field
|
||||||
|
await tester.scrollToRight(find.byType(GridPage));
|
||||||
|
await tester.tapNewPropertyButton();
|
||||||
|
await tester.renameField('New field 1');
|
||||||
|
await tester.dismissFieldEditor();
|
||||||
|
|
||||||
|
// hide the field
|
||||||
|
await tester.tapGridFieldWithName('New field 1');
|
||||||
|
await tester.tapHidePropertyButton();
|
||||||
|
await tester.noFieldWithName('New field 1');
|
||||||
|
|
||||||
|
// go back to inline database view, expect field to be shown
|
||||||
|
await tester.tapTabBarLinkedViewByViewName('Untitled');
|
||||||
|
await tester.findFieldWithName('New field 1');
|
||||||
|
|
||||||
|
// go back to linked database view, expect field to be hidden
|
||||||
|
await tester.tapTabBarLinkedViewByViewName('grid');
|
||||||
|
await tester.noFieldWithName('New field 1');
|
||||||
|
|
||||||
|
// use the settings button to show the field
|
||||||
|
await tester.tapDatabaseSettingButton();
|
||||||
|
await tester.tapViewPropertiesButton();
|
||||||
|
await tester.tapViewTogglePropertyVisibilityButtonByName('New field 1');
|
||||||
|
await tester.dismissFieldEditor();
|
||||||
|
await tester.findFieldWithName('New field 1');
|
||||||
|
|
||||||
|
// open first row in popup then hide the field
|
||||||
|
await tester.openFirstRowDetailPage();
|
||||||
|
await tester.tapGridFieldWithNameInRowDetailPage('New field 1');
|
||||||
|
await tester.tapHidePropertyButtonInFieldEditor();
|
||||||
|
await tester.dismissRowDetailPage();
|
||||||
|
await tester.noFieldWithName('New field 1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -106,26 +106,6 @@ void main() {
|
|||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('hide field', (tester) async {
|
|
||||||
await tester.initializeAppFlowy();
|
|
||||||
await tester.tapGoButton();
|
|
||||||
|
|
||||||
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
|
||||||
|
|
||||||
// create a field
|
|
||||||
await tester.scrollToRight(find.byType(GridPage));
|
|
||||||
await tester.tapNewPropertyButton();
|
|
||||||
await tester.renameField('New field 1');
|
|
||||||
await tester.dismissFieldEditor();
|
|
||||||
|
|
||||||
// Delete the field
|
|
||||||
await tester.tapGridFieldWithName('New field 1');
|
|
||||||
await tester.tapHidePropertyButton();
|
|
||||||
|
|
||||||
await tester.noFieldWithName('New field 1');
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('create checklist field ', (tester) async {
|
testWidgets('create checklist field ', (tester) async {
|
||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
@ -4,6 +4,7 @@ import 'package:integration_test/integration_test.dart';
|
|||||||
import 'database_calendar_test.dart' as database_calendar_test;
|
import 'database_calendar_test.dart' as database_calendar_test;
|
||||||
import 'database_cell_test.dart' as database_cell_test;
|
import 'database_cell_test.dart' as database_cell_test;
|
||||||
import 'database_field_test.dart' as database_field_test;
|
import 'database_field_test.dart' as database_field_test;
|
||||||
|
import 'database_field_settings_test.dart' as database_field_settings_test;
|
||||||
import 'database_filter_test.dart' as database_filter_test;
|
import 'database_filter_test.dart' as database_filter_test;
|
||||||
import 'database_row_page_test.dart' as database_row_page_test;
|
import 'database_row_page_test.dart' as database_row_page_test;
|
||||||
import 'database_row_test.dart' as database_row_test;
|
import 'database_row_test.dart' as database_row_test;
|
||||||
@ -47,6 +48,7 @@ void main() {
|
|||||||
// Database integration tests
|
// Database integration tests
|
||||||
database_cell_test.main();
|
database_cell_test.main();
|
||||||
database_field_test.main();
|
database_field_test.main();
|
||||||
|
database_field_settings_test.main();
|
||||||
database_share_test.main();
|
database_share_test.main();
|
||||||
database_row_page_test.main();
|
database_row_page_test.main();
|
||||||
database_row_test.main();
|
database_row_test.main();
|
||||||
|
@ -35,6 +35,7 @@ import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar
|
|||||||
import 'package:appflowy/plugins/database_view/tar_bar/tab_bar_header.dart';
|
import 'package:appflowy/plugins/database_view/tar_bar/tab_bar_header.dart';
|
||||||
import 'package:appflowy/plugins/database_view/tar_bar/tar_bar_add_button.dart';
|
import 'package:appflowy/plugins/database_view/tar_bar/tar_bar_add_button.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/database_layout_ext.dart';
|
import 'package:appflowy/plugins/database_view/widgets/database_layout_ext.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/widgets/field/grid_property.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/row/accessory/cell_accessory.dart';
|
import 'package:appflowy/plugins/database_view/widgets/row/accessory/cell_accessory.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/row/cells/cells.dart';
|
import 'package:appflowy/plugins/database_view/widgets/row/cells/cells.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/row/cells/checklist_cell/checklist_progress_bar.dart';
|
import 'package:appflowy/plugins/database_view/widgets/row/cells/checklist_cell/checklist_progress_bar.dart';
|
||||||
@ -503,6 +504,18 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
|||||||
await tapButton(findDateCell);
|
await tapButton(findDateCell);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> tapGridFieldWithNameInRowDetailPage(String name) async {
|
||||||
|
final fields = find.byWidgetPredicate(
|
||||||
|
(widget) => widget is FieldCellButton && widget.field.name == name,
|
||||||
|
);
|
||||||
|
final field = find.descendant(
|
||||||
|
of: find.byType(RowDetailPage),
|
||||||
|
matching: fields,
|
||||||
|
);
|
||||||
|
await tapButton(field);
|
||||||
|
await pumpAndSettle();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> duplicateRowInRowDetailPage() async {
|
Future<void> duplicateRowInRowDetailPage() async {
|
||||||
final duplicateButton = find.byType(RowDetailPageDuplicateButton);
|
final duplicateButton = find.byType(RowDetailPageDuplicateButton);
|
||||||
await tapButton(duplicateButton);
|
await tapButton(duplicateButton);
|
||||||
@ -585,6 +598,11 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
|||||||
await tapButton(field);
|
await tapButton(field);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> tapHidePropertyButtonInFieldEditor() async {
|
||||||
|
final button = find.byType(HideFieldButton);
|
||||||
|
await tapButton(button);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> tapRowDetailPageCreatePropertyButton() async {
|
Future<void> tapRowDetailPageCreatePropertyButton() async {
|
||||||
await tapButton(find.byType(CreateRowFieldButton));
|
await tapButton(find.byType(CreateRowFieldButton));
|
||||||
}
|
}
|
||||||
@ -925,6 +943,23 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
|||||||
await tapButton(button);
|
await tapButton(button);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Should call [tapDatabaseSettingButton] first.
|
||||||
|
Future<void> tapViewPropertiesButton() async {
|
||||||
|
final findSettingItem = find.byType(DatabaseSettingItem);
|
||||||
|
final findLayoutButton = find.byWidgetPredicate(
|
||||||
|
(widget) =>
|
||||||
|
widget is FlowyText &&
|
||||||
|
widget.text == DatabaseSettingAction.showProperties.title(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final button = find.descendant(
|
||||||
|
of: findSettingItem,
|
||||||
|
matching: findLayoutButton,
|
||||||
|
);
|
||||||
|
|
||||||
|
await tapButton(button);
|
||||||
|
}
|
||||||
|
|
||||||
/// Should call [tapDatabaseSettingButton] first.
|
/// Should call [tapDatabaseSettingButton] first.
|
||||||
Future<void> tapDatabaseLayoutButton() async {
|
Future<void> tapDatabaseLayoutButton() async {
|
||||||
final findSettingItem = find.byType(DatabaseSettingItem);
|
final findSettingItem = find.byType(DatabaseSettingItem);
|
||||||
@ -1111,6 +1146,11 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
|||||||
await tapButton(findCreateButton);
|
await tapButton(findCreateButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> tapTabBarLinkedViewByViewName(String name) async {
|
||||||
|
final viewButton = findTabBarLinkViewByViewName(name);
|
||||||
|
await tapButton(viewButton);
|
||||||
|
}
|
||||||
|
|
||||||
Finder findTabBarLinkViewByViewLayout(ViewLayoutPB layout) {
|
Finder findTabBarLinkViewByViewLayout(ViewLayoutPB layout) {
|
||||||
return find.byWidgetPredicate(
|
return find.byWidgetPredicate(
|
||||||
(widget) => widget is TabBarItemButton && widget.view.layout == layout,
|
(widget) => widget is TabBarItemButton && widget.view.layout == layout,
|
||||||
@ -1212,6 +1252,18 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
|||||||
Future<void> tapAddSelectOptionButton() async {
|
Future<void> tapAddSelectOptionButton() async {
|
||||||
await tapButtonWithName(LocaleKeys.grid_field_addSelectOption.tr());
|
await tapButtonWithName(LocaleKeys.grid_field_addSelectOption.tr());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> tapViewTogglePropertyVisibilityButtonByName(
|
||||||
|
String fieldName,
|
||||||
|
) async {
|
||||||
|
final field = find.byWidgetPredicate(
|
||||||
|
(widget) =>
|
||||||
|
widget is GridPropertyCell && widget.fieldInfo.name == fieldName,
|
||||||
|
);
|
||||||
|
final toggleVisibilityButton =
|
||||||
|
find.descendant(of: field, matching: find.byType(FlowyIconButton));
|
||||||
|
await tapButton(toggleVisibilityButton);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Finder finderForDatabaseLayoutType(DatabaseLayoutPB layout) {
|
Finder finderForDatabaseLayoutType(DatabaseLayoutPB layout) {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
import 'package:appflowy/plugins/database_view/application/field_settings/field_settings_service.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pb.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'field_service.dart';
|
import 'field_service.dart';
|
||||||
@ -8,13 +10,18 @@ part 'field_action_sheet_bloc.freezed.dart';
|
|||||||
|
|
||||||
class FieldActionSheetBloc
|
class FieldActionSheetBloc
|
||||||
extends Bloc<FieldActionSheetEvent, FieldActionSheetState> {
|
extends Bloc<FieldActionSheetEvent, FieldActionSheetState> {
|
||||||
|
final String fieldId;
|
||||||
final FieldBackendService fieldService;
|
final FieldBackendService fieldService;
|
||||||
|
final FieldSettingsBackendService fieldSettingsService;
|
||||||
|
|
||||||
FieldActionSheetBloc({required FieldContext fieldCellContext})
|
FieldActionSheetBloc({required FieldContext fieldCellContext})
|
||||||
: fieldService = FieldBackendService(
|
: fieldId = fieldCellContext.field.id,
|
||||||
|
fieldService = FieldBackendService(
|
||||||
viewId: fieldCellContext.viewId,
|
viewId: fieldCellContext.viewId,
|
||||||
fieldId: fieldCellContext.field.id,
|
fieldId: fieldCellContext.field.id,
|
||||||
),
|
),
|
||||||
|
fieldSettingsService =
|
||||||
|
FieldSettingsBackendService(viewId: fieldCellContext.viewId),
|
||||||
super(
|
super(
|
||||||
FieldActionSheetState.initial(
|
FieldActionSheetState.initial(
|
||||||
TypeOptionPB.create()..field_2 = fieldCellContext.field,
|
TypeOptionPB.create()..field_2 = fieldCellContext.field,
|
||||||
@ -31,14 +38,20 @@ class FieldActionSheetBloc
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
hideField: (_HideField value) async {
|
hideField: (_HideField value) async {
|
||||||
final result = await fieldService.updateField(visibility: false);
|
final result = await fieldSettingsService.updateFieldSettings(
|
||||||
|
fieldId: fieldId,
|
||||||
|
fieldVisibility: FieldVisibility.AlwaysHidden,
|
||||||
|
);
|
||||||
result.fold(
|
result.fold(
|
||||||
(l) => null,
|
(l) => null,
|
||||||
(err) => Log.error(err),
|
(err) => Log.error(err),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
showField: (_ShowField value) async {
|
showField: (_ShowField value) async {
|
||||||
final result = await fieldService.updateField(visibility: true);
|
final result = await fieldSettingsService.updateFieldSettings(
|
||||||
|
fieldId: fieldId,
|
||||||
|
fieldVisibility: FieldVisibility.AlwaysShown,
|
||||||
|
);
|
||||||
result.fold(
|
result.fold(
|
||||||
(l) => null,
|
(l) => null,
|
||||||
(err) => Log.error(err),
|
(err) => Log.error(err),
|
||||||
|
@ -417,7 +417,7 @@ class FieldController {
|
|||||||
_fieldNotifier.fieldInfos = newFieldInfos;
|
_fieldNotifier.fieldInfos = newFieldInfos;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<FieldInfo> updateFields(List<FieldPB> updatedFieldPBs) {
|
Future<List<FieldInfo>> updateFields(List<FieldPB> updatedFieldPBs) async {
|
||||||
if (updatedFieldPBs.isEmpty) {
|
if (updatedFieldPBs.isEmpty) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@ -429,7 +429,8 @@ class FieldController {
|
|||||||
newFields.indexWhere((field) => field.id == updatedFieldPB.id);
|
newFields.indexWhere((field) => field.id == updatedFieldPB.id);
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
newFields.removeAt(index);
|
newFields.removeAt(index);
|
||||||
final fieldInfo = FieldInfo.initial(updatedFieldPB);
|
final initial = FieldInfo.initial(updatedFieldPB);
|
||||||
|
final fieldInfo = await attachFieldSettings(initial);
|
||||||
newFields.insert(index, fieldInfo);
|
newFields.insert(index, fieldInfo);
|
||||||
updatedFields.add(fieldInfo);
|
updatedFields.add(fieldInfo);
|
||||||
}
|
}
|
||||||
@ -443,16 +444,16 @@ class FieldController {
|
|||||||
|
|
||||||
// Listen on field's changes
|
// Listen on field's changes
|
||||||
_fieldListener.start(
|
_fieldListener.start(
|
||||||
onFieldsChanged: (result) {
|
onFieldsChanged: (result) async {
|
||||||
result.fold(
|
result.fold(
|
||||||
(changeset) {
|
(changeset) async {
|
||||||
if (_isDisposed) {
|
if (_isDisposed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
deleteFields(changeset.deletedFields);
|
deleteFields(changeset.deletedFields);
|
||||||
insertFields(changeset.insertedFields);
|
insertFields(changeset.insertedFields);
|
||||||
|
|
||||||
final updatedFields = updateFields(changeset.updatedFields);
|
final updatedFields = await updateFields(changeset.updatedFields);
|
||||||
for (final listener in _updatedFieldCallbacks.values) {
|
for (final listener in _updatedFieldCallbacks.values) {
|
||||||
listener(updatedFields);
|
listener(updatedFields);
|
||||||
}
|
}
|
||||||
@ -548,7 +549,7 @@ class FieldController {
|
|||||||
|
|
||||||
_fieldNotifier.fieldInfos =
|
_fieldNotifier.fieldInfos =
|
||||||
newFields.map((field) => FieldInfo.initial(field)).toList();
|
newFields.map((field) => FieldInfo.initial(field)).toList();
|
||||||
Future.wait([
|
await Future.wait([
|
||||||
_loadFilters(),
|
_loadFilters(),
|
||||||
_loadSorts(),
|
_loadSorts(),
|
||||||
_loadAllFieldSettings(),
|
_loadAllFieldSettings(),
|
||||||
|
@ -30,7 +30,6 @@ class FieldBackendService {
|
|||||||
Future<Either<Unit, FlowyError>> updateField({
|
Future<Either<Unit, FlowyError>> updateField({
|
||||||
String? name,
|
String? name,
|
||||||
bool? frozen,
|
bool? frozen,
|
||||||
bool? visibility,
|
|
||||||
double? width,
|
double? width,
|
||||||
}) {
|
}) {
|
||||||
final payload = FieldChangesetPB.create()
|
final payload = FieldChangesetPB.create()
|
||||||
@ -45,10 +44,6 @@ class FieldBackendService {
|
|||||||
payload.frozen = frozen;
|
payload.frozen = frozen;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (visibility != null) {
|
|
||||||
payload.visibility = visibility;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (width != null) {
|
if (width != null) {
|
||||||
payload.width = width.toInt();
|
payload.width = width.toInt();
|
||||||
}
|
}
|
||||||
|
@ -246,7 +246,8 @@ class RowCache {
|
|||||||
// ignore: prefer_collection_literals
|
// ignore: prefer_collection_literals
|
||||||
final cellContextMap = CellContextByFieldId();
|
final cellContextMap = CellContextByFieldId();
|
||||||
for (final fieldInfo in _fieldDelegate.fieldInfos) {
|
for (final fieldInfo in _fieldDelegate.fieldInfos) {
|
||||||
if (fieldInfo.field.visibility) {
|
if (fieldInfo.visibility != null &&
|
||||||
|
fieldInfo.visibility != FieldVisibility.AlwaysHidden) {
|
||||||
cellContextMap[fieldInfo.id] = DatabaseCellContext(
|
cellContextMap[fieldInfo.id] = DatabaseCellContext(
|
||||||
rowMeta: rowMeta,
|
rowMeta: rowMeta,
|
||||||
viewId: viewId,
|
viewId: viewId,
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/application/field_settings/field_settings_service.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pb.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
@ -28,13 +30,13 @@ class DatabasePropertyBloc
|
|||||||
_startListening();
|
_startListening();
|
||||||
},
|
},
|
||||||
setFieldVisibility: (_SetFieldVisibility value) async {
|
setFieldVisibility: (_SetFieldVisibility value) async {
|
||||||
final fieldBackendSvc = FieldBackendService(
|
final fieldSettingsSvc = FieldSettingsBackendService(
|
||||||
viewId: viewId,
|
viewId: viewId,
|
||||||
fieldId: value.fieldId,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final result = await fieldBackendSvc.updateField(
|
final result = await fieldSettingsSvc.updateFieldSettings(
|
||||||
visibility: value.visibility,
|
fieldId: value.fieldId,
|
||||||
|
fieldVisibility: value.visibility,
|
||||||
);
|
);
|
||||||
|
|
||||||
result.fold((l) => null, (err) => Log.error(err));
|
result.fold((l) => null, (err) => Log.error(err));
|
||||||
@ -84,7 +86,7 @@ class DatabasePropertyEvent with _$DatabasePropertyEvent {
|
|||||||
const factory DatabasePropertyEvent.initial() = _Initial;
|
const factory DatabasePropertyEvent.initial() = _Initial;
|
||||||
const factory DatabasePropertyEvent.setFieldVisibility(
|
const factory DatabasePropertyEvent.setFieldVisibility(
|
||||||
String fieldId,
|
String fieldId,
|
||||||
bool visibility,
|
FieldVisibility visibility,
|
||||||
) = _SetFieldVisibility;
|
) = _SetFieldVisibility;
|
||||||
const factory DatabasePropertyEvent.didReceiveFieldUpdate(
|
const factory DatabasePropertyEvent.didReceiveFieldUpdate(
|
||||||
List<FieldInfo> fields,
|
List<FieldInfo> fields,
|
||||||
|
@ -2,6 +2,7 @@ import 'package:appflowy/plugins/database_view/application/field/field_controlle
|
|||||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pb.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
@ -16,18 +17,25 @@ class GridHeaderBloc extends Bloc<GridHeaderEvent, GridHeaderState> {
|
|||||||
GridHeaderBloc({
|
GridHeaderBloc({
|
||||||
required this.viewId,
|
required this.viewId,
|
||||||
required this.fieldController,
|
required this.fieldController,
|
||||||
}) : super(GridHeaderState.initial(fieldController.fieldInfos)) {
|
}) : super(GridHeaderState.initial()) {
|
||||||
on<GridHeaderEvent>(
|
on<GridHeaderEvent>(
|
||||||
(event, emit) async {
|
(event, emit) async {
|
||||||
await event.map(
|
await event.map(
|
||||||
initial: (_InitialHeader value) async {
|
initial: (_InitialHeader value) async {
|
||||||
_startListening();
|
_startListening();
|
||||||
|
add(
|
||||||
|
GridHeaderEvent.didReceiveFieldUpdate(fieldController.fieldInfos),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
|
didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
fields: value.fields
|
fields: value.fields
|
||||||
.where((element) => element.field.visibility)
|
.where(
|
||||||
|
(element) =>
|
||||||
|
element.visibility != null &&
|
||||||
|
element.visibility != FieldVisibility.AlwaysHidden,
|
||||||
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -83,9 +91,5 @@ class GridHeaderState with _$GridHeaderState {
|
|||||||
const factory GridHeaderState({required List<FieldInfo> fields}) =
|
const factory GridHeaderState({required List<FieldInfo> fields}) =
|
||||||
_GridHeaderState;
|
_GridHeaderState;
|
||||||
|
|
||||||
factory GridHeaderState.initial(List<FieldInfo> fields) {
|
factory GridHeaderState.initial() => const GridHeaderState(fields: []);
|
||||||
// final List<FieldPB> newFields = List.from(fields);
|
|
||||||
// newFields.retainWhere((field) => field.visibility);
|
|
||||||
return GridHeaderState(fields: fields);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
import 'package:appflowy/plugins/database_view/application/field_settings/field_settings_service.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/row/row_service.dart';
|
import 'package:appflowy/plugins/database_view/application/row/row_service.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pb.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
@ -10,18 +12,18 @@ part 'row_detail_bloc.freezed.dart';
|
|||||||
|
|
||||||
class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
|
class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
|
||||||
final RowBackendService rowService;
|
final RowBackendService rowService;
|
||||||
final RowController dataController;
|
final RowController rowController;
|
||||||
|
|
||||||
RowDetailBloc({
|
RowDetailBloc({
|
||||||
required this.dataController,
|
required this.rowController,
|
||||||
}) : rowService = RowBackendService(viewId: dataController.viewId),
|
}) : rowService = RowBackendService(viewId: rowController.viewId),
|
||||||
super(RowDetailState.initial()) {
|
super(RowDetailState.initial()) {
|
||||||
on<RowDetailEvent>(
|
on<RowDetailEvent>(
|
||||||
(event, emit) async {
|
(event, emit) async {
|
||||||
await event.when(
|
await event.when(
|
||||||
initial: () async {
|
initial: () async {
|
||||||
await _startListening();
|
await _startListening();
|
||||||
final cells = dataController.loadData();
|
final cells = rowController.loadData();
|
||||||
if (!isClosed) {
|
if (!isClosed) {
|
||||||
add(RowDetailEvent.didReceiveCellDatas(cells.values.toList()));
|
add(RowDetailEvent.didReceiveCellDatas(cells.values.toList()));
|
||||||
}
|
}
|
||||||
@ -32,9 +34,24 @@ class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
|
|||||||
deleteField: (fieldId) {
|
deleteField: (fieldId) {
|
||||||
_fieldBackendService(fieldId).deleteField();
|
_fieldBackendService(fieldId).deleteField();
|
||||||
},
|
},
|
||||||
|
showField: (fieldId) async {
|
||||||
|
final result =
|
||||||
|
await FieldSettingsBackendService(viewId: rowController.viewId)
|
||||||
|
.updateFieldSettings(
|
||||||
|
fieldId: fieldId,
|
||||||
|
fieldVisibility: FieldVisibility.AlwaysShown,
|
||||||
|
);
|
||||||
|
result.fold(
|
||||||
|
(l) {},
|
||||||
|
(err) => Log.error(err),
|
||||||
|
);
|
||||||
|
},
|
||||||
hideField: (fieldId) async {
|
hideField: (fieldId) async {
|
||||||
final result = await _fieldBackendService(fieldId).updateField(
|
final result =
|
||||||
visibility: false,
|
await FieldSettingsBackendService(viewId: rowController.viewId)
|
||||||
|
.updateFieldSettings(
|
||||||
|
fieldId: fieldId,
|
||||||
|
fieldVisibility: FieldVisibility.AlwaysHidden,
|
||||||
);
|
);
|
||||||
result.fold(
|
result.fold(
|
||||||
(l) {},
|
(l) {},
|
||||||
@ -57,12 +74,12 @@ class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
dataController.dispose();
|
rowController.dispose();
|
||||||
return super.close();
|
return super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _startListening() async {
|
Future<void> _startListening() async {
|
||||||
dataController.addListener(
|
rowController.addListener(
|
||||||
onRowChanged: (cells, reason) {
|
onRowChanged: (cells, reason) {
|
||||||
if (!isClosed) {
|
if (!isClosed) {
|
||||||
add(RowDetailEvent.didReceiveCellDatas(cells.values.toList()));
|
add(RowDetailEvent.didReceiveCellDatas(cells.values.toList()));
|
||||||
@ -73,7 +90,7 @@ class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
|
|||||||
|
|
||||||
FieldBackendService _fieldBackendService(String fieldId) {
|
FieldBackendService _fieldBackendService(String fieldId) {
|
||||||
return FieldBackendService(
|
return FieldBackendService(
|
||||||
viewId: dataController.viewId,
|
viewId: rowController.viewId,
|
||||||
fieldId: fieldId,
|
fieldId: fieldId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -83,6 +100,7 @@ class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
|
|||||||
class RowDetailEvent with _$RowDetailEvent {
|
class RowDetailEvent with _$RowDetailEvent {
|
||||||
const factory RowDetailEvent.initial() = _Initial;
|
const factory RowDetailEvent.initial() = _Initial;
|
||||||
const factory RowDetailEvent.deleteField(String fieldId) = _DeleteField;
|
const factory RowDetailEvent.deleteField(String fieldId) = _DeleteField;
|
||||||
|
const factory RowDetailEvent.showField(String fieldId) = _ShowField;
|
||||||
const factory RowDetailEvent.hideField(String fieldId) = _HideField;
|
const factory RowDetailEvent.hideField(String fieldId) = _HideField;
|
||||||
const factory RowDetailEvent.deleteRow(String rowId) = _DeleteRow;
|
const factory RowDetailEvent.deleteRow(String rowId) = _DeleteRow;
|
||||||
const factory RowDetailEvent.duplicateRow(String rowId, String? groupId) =
|
const factory RowDetailEvent.duplicateRow(String rowId, String? groupId) =
|
||||||
|
@ -164,11 +164,6 @@ class FieldCellButton extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// Using this technique to have proper text ellipsis
|
|
||||||
// https://github.com/flutter/flutter/issues/18761#issuecomment-812390920
|
|
||||||
final text = Characters(field.name)
|
|
||||||
.replaceAll(Characters(''), Characters('\u{200B}'))
|
|
||||||
.toString();
|
|
||||||
return FlowyButton(
|
return FlowyButton(
|
||||||
hoverColor: AFThemeExtension.of(context).greyHover,
|
hoverColor: AFThemeExtension.of(context).greyHover,
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
@ -177,7 +172,7 @@ class FieldCellButton extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
radius: radius,
|
radius: radius,
|
||||||
text: FlowyText.medium(
|
text: FlowyText.medium(
|
||||||
text,
|
field.name,
|
||||||
maxLines: maxLines,
|
maxLines: maxLines,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
|
@ -102,7 +102,7 @@ class _FieldEditorState extends State<FieldEditor> {
|
|||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||||
child: _HideFieldButton(
|
child: HideFieldButton(
|
||||||
popoverMutex: popoverMutex,
|
popoverMutex: popoverMutex,
|
||||||
onHidden: () {
|
onHidden: () {
|
||||||
state.field.fold(
|
state.field.fold(
|
||||||
@ -236,11 +236,12 @@ class _DeleteFieldButton extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _HideFieldButton extends StatelessWidget {
|
@visibleForTesting
|
||||||
|
class HideFieldButton extends StatelessWidget {
|
||||||
final PopoverMutex popoverMutex;
|
final PopoverMutex popoverMutex;
|
||||||
final VoidCallback? onHidden;
|
final VoidCallback? onHidden;
|
||||||
|
|
||||||
const _HideFieldButton({
|
const HideFieldButton({
|
||||||
required this.popoverMutex,
|
required this.popoverMutex,
|
||||||
required this.onHidden,
|
required this.onHidden,
|
||||||
Key? key,
|
Key? key,
|
||||||
|
@ -42,12 +42,10 @@ class _GridHeaderSliverAdaptorState extends State<GridHeaderSliverAdaptor> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) {
|
create: (context) {
|
||||||
final bloc = getIt<GridHeaderBloc>(
|
return getIt<GridHeaderBloc>(
|
||||||
param1: widget.viewId,
|
param1: widget.viewId,
|
||||||
param2: widget.fieldController,
|
param2: widget.fieldController,
|
||||||
);
|
)..add(const GridHeaderEvent.initial());
|
||||||
bloc.add(const GridHeaderEvent.initial());
|
|
||||||
return bloc;
|
|
||||||
},
|
},
|
||||||
child: BlocBuilder<GridHeaderBloc, GridHeaderState>(
|
child: BlocBuilder<GridHeaderBloc, GridHeaderState>(
|
||||||
buildWhen: (previous, current) =>
|
buildWhen: (previous, current) =>
|
||||||
@ -97,7 +95,6 @@ class _GridHeaderState extends State<_GridHeader> {
|
|||||||
buildWhen: (previous, current) => previous.fields != current.fields,
|
buildWhen: (previous, current) => previous.fields != current.fields,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final cells = state.fields
|
final cells = state.fields
|
||||||
.where((fieldInfo) => fieldInfo.field.visibility)
|
|
||||||
.map(
|
.map(
|
||||||
(field) => FieldContext(
|
(field) => FieldContext(
|
||||||
viewId: widget.viewId,
|
viewId: widget.viewId,
|
||||||
|
@ -5,6 +5,7 @@ import 'package:appflowy/plugins/database_view/application/field/type_option/typ
|
|||||||
import 'package:appflowy/plugins/database_view/application/setting/property_bloc.dart';
|
import 'package:appflowy/plugins/database_view/application/setting/property_bloc.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_extension.dart';
|
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_extension.dart';
|
||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
|
|
||||||
import 'package:flowy_infra/theme_extension.dart';
|
import 'package:flowy_infra/theme_extension.dart';
|
||||||
@ -44,7 +45,7 @@ class _DatabasePropertyListState extends State<DatabasePropertyList> {
|
|||||||
child: BlocBuilder<DatabasePropertyBloc, DatabasePropertyState>(
|
child: BlocBuilder<DatabasePropertyBloc, DatabasePropertyState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final cells = state.fieldContexts.map((field) {
|
final cells = state.fieldContexts.map((field) {
|
||||||
return _GridPropertyCell(
|
return GridPropertyCell(
|
||||||
key: ValueKey(field.id),
|
key: ValueKey(field.id),
|
||||||
viewId: widget.viewId,
|
viewId: widget.viewId,
|
||||||
fieldInfo: field,
|
fieldInfo: field,
|
||||||
@ -75,12 +76,13 @@ class _DatabasePropertyListState extends State<DatabasePropertyList> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _GridPropertyCell extends StatefulWidget {
|
@visibleForTesting
|
||||||
|
class GridPropertyCell extends StatefulWidget {
|
||||||
final FieldInfo fieldInfo;
|
final FieldInfo fieldInfo;
|
||||||
final String viewId;
|
final String viewId;
|
||||||
final PopoverMutex popoverMutex;
|
final PopoverMutex popoverMutex;
|
||||||
|
|
||||||
const _GridPropertyCell({
|
const GridPropertyCell({
|
||||||
super.key,
|
super.key,
|
||||||
required this.fieldInfo,
|
required this.fieldInfo,
|
||||||
required this.viewId,
|
required this.viewId,
|
||||||
@ -88,26 +90,22 @@ class _GridPropertyCell extends StatefulWidget {
|
|||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_GridPropertyCell> createState() => _GridPropertyCellState();
|
State<GridPropertyCell> createState() => _GridPropertyCellState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _GridPropertyCellState extends State<_GridPropertyCell> {
|
class _GridPropertyCellState extends State<GridPropertyCell> {
|
||||||
final PopoverController _popoverController = PopoverController();
|
final PopoverController _popoverController = PopoverController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final checkmark = FlowySvg(
|
final visiblity = widget.fieldInfo.visibility;
|
||||||
widget.fieldInfo.field.visibility ? FlowySvgs.show_m : FlowySvgs.hide_m,
|
final visibleIcon = FlowySvg(
|
||||||
|
visiblity != null && visiblity != FieldVisibility.AlwaysHidden
|
||||||
|
? FlowySvgs.show_m
|
||||||
|
: FlowySvgs.hide_m,
|
||||||
color: Theme.of(context).iconTheme.color,
|
color: Theme.of(context).iconTheme.color,
|
||||||
);
|
);
|
||||||
|
|
||||||
return SizedBox(
|
|
||||||
height: GridSize.popoverItemHeight,
|
|
||||||
child: _editFieldButton(context, checkmark),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _editFieldButton(BuildContext context, Widget checkmark) {
|
|
||||||
return AppFlowyPopover(
|
return AppFlowyPopover(
|
||||||
mutex: widget.popoverMutex,
|
mutex: widget.popoverMutex,
|
||||||
controller: _popoverController,
|
controller: _popoverController,
|
||||||
@ -116,6 +114,8 @@ class _GridPropertyCellState extends State<_GridPropertyCell> {
|
|||||||
constraints: BoxConstraints.loose(const Size(240, 400)),
|
constraints: BoxConstraints.loose(const Size(240, 400)),
|
||||||
triggerActions: PopoverTriggerFlags.none,
|
triggerActions: PopoverTriggerFlags.none,
|
||||||
margin: EdgeInsets.zero,
|
margin: EdgeInsets.zero,
|
||||||
|
child: SizedBox(
|
||||||
|
height: GridSize.popoverItemHeight,
|
||||||
child: FlowyButton(
|
child: FlowyButton(
|
||||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||||
text: FlowyText.medium(
|
text: FlowyText.medium(
|
||||||
@ -129,17 +129,25 @@ class _GridPropertyCellState extends State<_GridPropertyCell> {
|
|||||||
rightIcon: FlowyIconButton(
|
rightIcon: FlowyIconButton(
|
||||||
hoverColor: Colors.transparent,
|
hoverColor: Colors.transparent,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
if (widget.fieldInfo.fieldSettings == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final newVisiblity = _newFieldVisibility(
|
||||||
|
widget.fieldInfo.fieldSettings!.visibility,
|
||||||
|
);
|
||||||
context.read<DatabasePropertyBloc>().add(
|
context.read<DatabasePropertyBloc>().add(
|
||||||
DatabasePropertyEvent.setFieldVisibility(
|
DatabasePropertyEvent.setFieldVisibility(
|
||||||
widget.fieldInfo.id,
|
widget.fieldInfo.id,
|
||||||
!widget.fieldInfo.field.visibility,
|
newVisiblity,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
icon: checkmark.padding(all: 6.0),
|
icon: visibleIcon.padding(all: 6.0),
|
||||||
),
|
),
|
||||||
onTap: () => _popoverController.show(),
|
onTap: () => _popoverController.show(),
|
||||||
).padding(horizontal: 6.0),
|
).padding(horizontal: 6.0),
|
||||||
|
),
|
||||||
popupBuilder: (BuildContext context) {
|
popupBuilder: (BuildContext context) {
|
||||||
return FieldEditor(
|
return FieldEditor(
|
||||||
viewId: widget.viewId,
|
viewId: widget.viewId,
|
||||||
@ -151,4 +159,12 @@ class _GridPropertyCellState extends State<_GridPropertyCell> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FieldVisibility _newFieldVisibility(FieldVisibility current) {
|
||||||
|
return switch (current) {
|
||||||
|
FieldVisibility.AlwaysShown => FieldVisibility.AlwaysHidden,
|
||||||
|
FieldVisibility.AlwaysHidden => FieldVisibility.AlwaysShown,
|
||||||
|
_ => FieldVisibility.AlwaysHidden,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ class _RowDetailPageState extends State<RowDetailPage> {
|
|||||||
return FlowyDialog(
|
return FlowyDialog(
|
||||||
child: BlocProvider(
|
child: BlocProvider(
|
||||||
create: (context) {
|
create: (context) {
|
||||||
return RowDetailBloc(dataController: widget.rowController)
|
return RowDetailBloc(rowController: widget.rowController)
|
||||||
..add(const RowDetailEvent.initial());
|
..add(const RowDetailEvent.initial());
|
||||||
},
|
},
|
||||||
child: ListView(
|
child: ListView(
|
||||||
|
@ -17,7 +17,6 @@ use crate::database::mock_data::{COMPLETED, FACEBOOK, GOOGLE, PAUSED, PLANNED, T
|
|||||||
pub fn make_test_board() -> DatabaseData {
|
pub fn make_test_board() -> DatabaseData {
|
||||||
let mut fields = vec![];
|
let mut fields = vec![];
|
||||||
let mut rows = vec![];
|
let mut rows = vec![];
|
||||||
|
|
||||||
// Iterate through the FieldType to create the corresponding Field.
|
// Iterate through the FieldType to create the corresponding Field.
|
||||||
for field_type in FieldType::iter() {
|
for field_type in FieldType::iter() {
|
||||||
match field_type {
|
match field_type {
|
||||||
|
Loading…
Reference in New Issue
Block a user