forked from AppFlowy-IO/AppFlowy
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add basic relation field (AppFlowy-IO#4397)
* feat: add basic relation field * fix: clippy * fix: tauri build 🤞 * chore: merge changes * fix: merge main * chore: initial code review pass * fix: rust-lib test * chore: code cleanup * fix: unwrap or default
- Loading branch information
1 parent
f826d05
commit f4ca3ef
Showing
54 changed files
with
1,804 additions
and
34 deletions.
There are no files selected for viewing
139 changes: 139 additions & 0 deletions
139
frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/relation_cell_bloc.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
import 'dart:async'; | ||
|
||
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart'; | ||
import 'package:appflowy/plugins/database/application/field/type_option/type_option_data_parser.dart'; | ||
import 'package:appflowy_backend/dispatch/dispatch.dart'; | ||
import 'package:appflowy_backend/log.dart'; | ||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; | ||
import 'package:flutter_bloc/flutter_bloc.dart'; | ||
import 'package:freezed_annotation/freezed_annotation.dart'; | ||
|
||
part 'relation_cell_bloc.freezed.dart'; | ||
|
||
class RelationCellBloc extends Bloc<RelationCellEvent, RelationCellState> { | ||
RelationCellBloc({required this.cellController}) | ||
: super(RelationCellState.initial()) { | ||
_dispatch(); | ||
_startListening(); | ||
_init(); | ||
} | ||
|
||
final RelationCellController cellController; | ||
void Function()? _onCellChangedFn; | ||
|
||
@override | ||
Future<void> close() async { | ||
if (_onCellChangedFn != null) { | ||
cellController.removeListener(_onCellChangedFn!); | ||
_onCellChangedFn = null; | ||
} | ||
return super.close(); | ||
} | ||
|
||
void _dispatch() { | ||
on<RelationCellEvent>( | ||
(event, emit) async { | ||
await event.when( | ||
didUpdateCell: (RelationCellDataPB? cellData) async { | ||
if (cellData == null || cellData.rowIds.isEmpty) { | ||
emit(state.copyWith(rows: const [])); | ||
return; | ||
} | ||
final payload = RepeatedRowIdPB( | ||
databaseId: state.relatedDatabaseId, | ||
rowIds: cellData.rowIds, | ||
); | ||
final result = | ||
await DatabaseEventGetRelatedRowDatas(payload).send(); | ||
final rows = result.fold( | ||
(data) => data.rows, | ||
(err) { | ||
Log.error(err); | ||
return const <RelatedRowDataPB>[]; | ||
}, | ||
); | ||
emit(state.copyWith(rows: rows)); | ||
}, | ||
didUpdateRelationDatabaseId: (databaseId) { | ||
emit(state.copyWith(relatedDatabaseId: databaseId)); | ||
}, | ||
selectRow: (rowId) async { | ||
await _handleSelectRow(rowId); | ||
}, | ||
); | ||
}, | ||
); | ||
} | ||
|
||
void _startListening() { | ||
_onCellChangedFn = cellController.addListener( | ||
onCellChanged: (data) { | ||
if (!isClosed) { | ||
add(RelationCellEvent.didUpdateCell(data)); | ||
} | ||
}, | ||
onCellFieldChanged: (field) { | ||
if (!isClosed) { | ||
// hack: SingleFieldListener receives notification before | ||
// FieldController's copy is updated. | ||
Future.delayed(const Duration(milliseconds: 50), () { | ||
final RelationTypeOptionPB typeOption = | ||
cellController.getTypeOption(RelationTypeOptionDataParser()); | ||
add( | ||
RelationCellEvent.didUpdateRelationDatabaseId( | ||
typeOption.databaseId, | ||
), | ||
); | ||
}); | ||
} | ||
}, | ||
); | ||
} | ||
|
||
void _init() { | ||
final RelationTypeOptionPB typeOption = | ||
cellController.getTypeOption(RelationTypeOptionDataParser()); | ||
add(RelationCellEvent.didUpdateRelationDatabaseId(typeOption.databaseId)); | ||
final cellData = cellController.getCellData(); | ||
add(RelationCellEvent.didUpdateCell(cellData)); | ||
} | ||
|
||
Future<void> _handleSelectRow(String rowId) async { | ||
final payload = RelationCellChangesetPB( | ||
viewId: cellController.viewId, | ||
cellId: CellIdPB( | ||
viewId: cellController.viewId, | ||
fieldId: cellController.fieldId, | ||
rowId: cellController.rowId, | ||
), | ||
); | ||
if (state.rows.any((row) => row.rowId == rowId)) { | ||
payload.removedRowIds.add(rowId); | ||
} else { | ||
payload.insertedRowIds.add(rowId); | ||
} | ||
final result = await DatabaseEventUpdateRelationCell(payload).send(); | ||
result.fold((l) => null, (err) => Log.error(err)); | ||
} | ||
} | ||
|
||
@freezed | ||
class RelationCellEvent with _$RelationCellEvent { | ||
const factory RelationCellEvent.didUpdateRelationDatabaseId( | ||
String databaseId, | ||
) = _DidUpdateRelationDatabaseId; | ||
const factory RelationCellEvent.didUpdateCell(RelationCellDataPB? data) = | ||
_DidUpdateCell; | ||
const factory RelationCellEvent.selectRow(String rowId) = _SelectRowId; | ||
} | ||
|
||
@freezed | ||
class RelationCellState with _$RelationCellState { | ||
const factory RelationCellState({ | ||
required String relatedDatabaseId, | ||
required List<RelatedRowDataPB> rows, | ||
}) = _RelationCellState; | ||
|
||
factory RelationCellState.initial() => | ||
const RelationCellState(relatedDatabaseId: "", rows: []); | ||
} |
77 changes: 77 additions & 0 deletions
77
...appflowy_flutter/lib/plugins/database/application/cell/bloc/relation_row_search_bloc.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import 'package:appflowy_backend/dispatch/dispatch.dart'; | ||
import 'package:appflowy_backend/log.dart'; | ||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; | ||
import 'package:bloc/bloc.dart'; | ||
import 'package:flutter/foundation.dart'; | ||
import 'package:freezed_annotation/freezed_annotation.dart'; | ||
|
||
part 'relation_row_search_bloc.freezed.dart'; | ||
|
||
class RelationRowSearchBloc | ||
extends Bloc<RelationRowSearchEvent, RelationRowSearchState> { | ||
RelationRowSearchBloc({ | ||
required this.databaseId, | ||
}) : super(RelationRowSearchState.initial()) { | ||
_dispatch(); | ||
_init(); | ||
} | ||
|
||
final String databaseId; | ||
final List<RelatedRowDataPB> allRows = []; | ||
|
||
void _dispatch() { | ||
on<RelationRowSearchEvent>( | ||
(event, emit) { | ||
event.when( | ||
didUpdateRowList: (List<RelatedRowDataPB> rowList) { | ||
allRows.clear(); | ||
allRows.addAll(rowList); | ||
emit(state.copyWith(filteredRows: allRows)); | ||
}, | ||
updateFilter: (String filter) => _updateFilter(filter, emit), | ||
); | ||
}, | ||
); | ||
} | ||
|
||
Future<void> _init() async { | ||
final payload = DatabaseIdPB(value: databaseId); | ||
final result = await DatabaseEventGetRelatedDatabaseRows(payload).send(); | ||
result.fold( | ||
(data) => add(RelationRowSearchEvent.didUpdateRowList(data.rows)), | ||
(err) => Log.error(err), | ||
); | ||
} | ||
|
||
void _updateFilter(String filter, Emitter<RelationRowSearchState> emit) { | ||
final rows = [...allRows]; | ||
if (filter.isNotEmpty) { | ||
rows.retainWhere( | ||
(row) => row.name.toLowerCase().contains(filter.toLowerCase()), | ||
); | ||
} | ||
emit(state.copyWith(filter: filter, filteredRows: rows)); | ||
} | ||
} | ||
|
||
@freezed | ||
class RelationRowSearchEvent with _$RelationRowSearchEvent { | ||
const factory RelationRowSearchEvent.didUpdateRowList( | ||
List<RelatedRowDataPB> rowList, | ||
) = _DidUpdateRowList; | ||
const factory RelationRowSearchEvent.updateFilter(String filter) = | ||
_UpdateFilter; | ||
} | ||
|
||
@freezed | ||
class RelationRowSearchState with _$RelationRowSearchState { | ||
const factory RelationRowSearchState({ | ||
required String filter, | ||
required List<RelatedRowDataPB> filteredRows, | ||
}) = _RelationRowSearchState; | ||
|
||
factory RelationRowSearchState.initial() => const RelationRowSearchState( | ||
filter: "", | ||
filteredRows: [], | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.