mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: relation field improvements (#5070)
* feat: keyboard navigation for relation cell editor * feat: open related rows * feat: separated selected and unselected rows * chore: apply suggestions from code review Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io> * chore: fix launch review issues * chore: add documentation --------- Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
This commit is contained in:
@ -29,9 +29,7 @@ class DesktopGridRelationCellSkin extends IEditableRelationCellSkin {
|
||||
popupBuilder: (context) {
|
||||
return BlocProvider.value(
|
||||
value: bloc,
|
||||
child: RelationCellEditor(
|
||||
selectedRowIds: state.rows.map((row) => row.rowId).toList(),
|
||||
),
|
||||
child: const RelationCellEditor(),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
|
@ -29,9 +29,7 @@ class DesktopRowDetailRelationCellSkin extends IEditableRelationCellSkin {
|
||||
popupBuilder: (context) {
|
||||
return BlocProvider.value(
|
||||
value: bloc,
|
||||
child: RelationCellEditor(
|
||||
selectedRowIds: state.rows.map((row) => row.rowId).toList(),
|
||||
),
|
||||
child: const RelationCellEditor(),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
|
@ -1,11 +1,18 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database/application/field/type_option/relation_type_option_cubit.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/common/type_option_separator.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/relation_row_detail.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../application/cell/bloc/relation_cell_bloc.dart';
|
||||
@ -14,132 +21,451 @@ import '../../application/cell/bloc/relation_row_search_bloc.dart';
|
||||
class RelationCellEditor extends StatelessWidget {
|
||||
const RelationCellEditor({
|
||||
super.key,
|
||||
required this.selectedRowIds,
|
||||
});
|
||||
|
||||
final List<String> selectedRowIds;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<RelationCellBloc, RelationCellState>(
|
||||
builder: (context, cellState) {
|
||||
if (cellState.relatedDatabaseMeta == null) {
|
||||
return const _RelationCellEditorDatabaseList();
|
||||
return cellState.relatedDatabaseMeta == null
|
||||
? const _RelationCellEditorDatabasePicker()
|
||||
: _RelationCellEditorContent(
|
||||
relatedDatabaseMeta: cellState.relatedDatabaseMeta!,
|
||||
selectedRowIds: cellState.rows.map((e) => e.rowId).toList(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RelationCellEditorContent extends StatefulWidget {
|
||||
const _RelationCellEditorContent({
|
||||
required this.relatedDatabaseMeta,
|
||||
required this.selectedRowIds,
|
||||
});
|
||||
|
||||
final DatabaseMeta relatedDatabaseMeta;
|
||||
final List<String> selectedRowIds;
|
||||
|
||||
@override
|
||||
State<_RelationCellEditorContent> createState() =>
|
||||
_RelationCellEditorContentState();
|
||||
}
|
||||
|
||||
class _RelationCellEditorContentState
|
||||
extends State<_RelationCellEditorContent> {
|
||||
final textEditingController = TextEditingController();
|
||||
late final FocusNode focusNode;
|
||||
late final bloc = RelationRowSearchBloc(
|
||||
databaseId: widget.relatedDatabaseMeta.databaseId,
|
||||
);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
focusNode = FocusNode(
|
||||
onKeyEvent: (node, event) {
|
||||
switch (event.logicalKey) {
|
||||
case LogicalKeyboardKey.arrowUp when event is! KeyUpEvent:
|
||||
if (textEditingController.value.composing.isCollapsed) {
|
||||
bloc.add(const RelationRowSearchEvent.focusPreviousOption());
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
break;
|
||||
case LogicalKeyboardKey.arrowDown when event is! KeyUpEvent:
|
||||
if (textEditingController.value.composing.isCollapsed) {
|
||||
bloc.add(const RelationRowSearchEvent.focusNextOption());
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
break;
|
||||
case LogicalKeyboardKey.escape when event is! KeyUpEvent:
|
||||
if (!textEditingController.value.composing.isCollapsed) {
|
||||
final end = textEditingController.value.composing.end;
|
||||
final text = textEditingController.text;
|
||||
|
||||
textEditingController.value = TextEditingValue(
|
||||
text: text,
|
||||
selection: TextSelection.collapsed(offset: end),
|
||||
);
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return KeyEventResult.ignored;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return BlocProvider<RelationRowSearchBloc>(
|
||||
create: (context) => RelationRowSearchBloc(
|
||||
databaseId: cellState.relatedDatabaseMeta!.databaseId,
|
||||
),
|
||||
child: BlocBuilder<RelationRowSearchBloc, RelationRowSearchState>(
|
||||
builder: (context, state) {
|
||||
final children = state.filteredRows
|
||||
.map(
|
||||
(row) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6.0),
|
||||
child: FlowyButton(
|
||||
text: FlowyText.medium(
|
||||
row.name.trim().isEmpty
|
||||
? LocaleKeys.grid_title_placeholder.tr()
|
||||
: row.name,
|
||||
color: row.name.trim().isEmpty
|
||||
? Theme.of(context).hintColor
|
||||
: null,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
rightIcon: cellState.rows
|
||||
.map((e) => e.rowId)
|
||||
.contains(row.rowId)
|
||||
? const FlowySvg(
|
||||
FlowySvgs.check_s,
|
||||
)
|
||||
: null,
|
||||
onTap: () => context
|
||||
.read<RelationCellBloc>()
|
||||
.add(RelationCellEvent.selectRow(row.rowId)),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
@override
|
||||
void dispose() {
|
||||
textEditingController.dispose();
|
||||
focusNode.dispose();
|
||||
bloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6.0) +
|
||||
GridSize.typeOptionContentInsets,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
FlowyText.regular(
|
||||
LocaleKeys.grid_relation_inRelatedDatabase.tr(),
|
||||
fontSize: 11,
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 4,
|
||||
vertical: 2,
|
||||
),
|
||||
child: FlowyText.regular(
|
||||
cellState.relatedDatabaseMeta!.databaseName,
|
||||
fontSize: 11,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6.0),
|
||||
child: FlowyTextField(
|
||||
hintText: LocaleKeys
|
||||
.grid_relation_rowSearchTextFieldPlaceholder
|
||||
.tr(),
|
||||
hintStyle: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall
|
||||
?.copyWith(color: Theme.of(context).hintColor),
|
||||
onChanged: (text) => context
|
||||
.read<RelationRowSearchBloc>()
|
||||
.add(RelationRowSearchEvent.updateFilter(text)),
|
||||
),
|
||||
),
|
||||
const VSpace(6.0),
|
||||
const TypeOptionSeparator(spacing: 0.0),
|
||||
if (state.filteredRows.isEmpty)
|
||||
Padding(
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: bloc,
|
||||
child: BlocBuilder<RelationRowSearchBloc, RelationRowSearchState>(
|
||||
buildWhen: (previous, current) =>
|
||||
!listEquals(previous.filteredRows, current.filteredRows),
|
||||
builder: (context, state) {
|
||||
final selected = <RelatedRowDataPB>[];
|
||||
final unselected = <RelatedRowDataPB>[];
|
||||
for (final row in state.filteredRows) {
|
||||
if (widget.selectedRowIds.contains(row.rowId)) {
|
||||
selected.add(row);
|
||||
} else {
|
||||
unselected.add(row);
|
||||
}
|
||||
}
|
||||
return TextFieldTapRegion(
|
||||
child: CustomScrollView(
|
||||
shrinkWrap: true,
|
||||
slivers: [
|
||||
_CellEditorTitle(
|
||||
databaseName: widget.relatedDatabaseMeta.databaseName,
|
||||
),
|
||||
_SearchField(
|
||||
focusNode: focusNode,
|
||||
textEditingController: textEditingController,
|
||||
),
|
||||
const SliverToBoxAdapter(
|
||||
child: TypeOptionSeparator(spacing: 0.0),
|
||||
),
|
||||
if (state.filteredRows.isEmpty)
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(6.0) +
|
||||
GridSize.typeOptionContentInsets,
|
||||
child: FlowyText.regular(
|
||||
LocaleKeys.grid_relation_emptySearchResult.tr(),
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
)
|
||||
else
|
||||
Flexible(
|
||||
child: ListView.separated(
|
||||
shrinkWrap: true,
|
||||
padding: const EdgeInsets.symmetric(vertical: 6.0),
|
||||
separatorBuilder: (context, index) =>
|
||||
VSpace(GridSize.typeOptionSeparatorHeight),
|
||||
itemCount: children.length,
|
||||
itemBuilder: (context, index) => children[index],
|
||||
),
|
||||
),
|
||||
if (selected.isNotEmpty) ...[
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6.0) +
|
||||
GridSize.typeOptionContentInsets,
|
||||
child: FlowyText.regular(
|
||||
LocaleKeys.grid_relation_linkedRowListLabel.plural(
|
||||
selected.length,
|
||||
namedArgs: {'count': '${selected.length}'},
|
||||
),
|
||||
fontSize: 11,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
_RowList(
|
||||
databaseId: widget.relatedDatabaseMeta.databaseId,
|
||||
rows: selected,
|
||||
isSelected: true,
|
||||
),
|
||||
const SliverToBoxAdapter(
|
||||
child: VSpace(4.0),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
if (unselected.isNotEmpty) ...[
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6.0) +
|
||||
GridSize.typeOptionContentInsets,
|
||||
child: FlowyText.regular(
|
||||
LocaleKeys.grid_relation_unlinkedRowListLabel.tr(),
|
||||
fontSize: 11,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
_RowList(
|
||||
databaseId: widget.relatedDatabaseMeta.databaseId,
|
||||
rows: unselected,
|
||||
isSelected: false,
|
||||
),
|
||||
const SliverToBoxAdapter(
|
||||
child: VSpace(4.0),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RelationCellEditorDatabaseList extends StatelessWidget {
|
||||
const _RelationCellEditorDatabaseList();
|
||||
class _CellEditorTitle extends StatelessWidget {
|
||||
const _CellEditorTitle({
|
||||
required this.databaseName,
|
||||
});
|
||||
|
||||
final String databaseName;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6.0) +
|
||||
GridSize.typeOptionContentInsets,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
FlowyText.regular(
|
||||
LocaleKeys.grid_relation_inRelatedDatabase.tr(),
|
||||
fontSize: 11,
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 4,
|
||||
vertical: 2,
|
||||
),
|
||||
child: FlowyText.regular(
|
||||
databaseName,
|
||||
fontSize: 11,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SearchField extends StatelessWidget {
|
||||
const _SearchField({
|
||||
required this.focusNode,
|
||||
required this.textEditingController,
|
||||
});
|
||||
|
||||
final FocusNode focusNode;
|
||||
final TextEditingController textEditingController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 6.0, bottom: 6.0, right: 6.0),
|
||||
child: FlowyTextField(
|
||||
focusNode: focusNode,
|
||||
controller: textEditingController,
|
||||
hintText: LocaleKeys.grid_relation_rowSearchTextFieldPlaceholder.tr(),
|
||||
hintStyle: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall
|
||||
?.copyWith(color: Theme.of(context).hintColor),
|
||||
onChanged: (text) {
|
||||
if (textEditingController.value.composing.isCollapsed) {
|
||||
context
|
||||
.read<RelationRowSearchBloc>()
|
||||
.add(RelationRowSearchEvent.updateFilter(text));
|
||||
}
|
||||
},
|
||||
onSubmitted: (_) {
|
||||
final focusedRowId =
|
||||
context.read<RelationRowSearchBloc>().state.focusedRowId;
|
||||
if (focusedRowId != null) {
|
||||
final row = context
|
||||
.read<RelationCellBloc>()
|
||||
.state
|
||||
.rows
|
||||
.firstWhereOrNull((e) => e.rowId == focusedRowId);
|
||||
if (row != null) {
|
||||
FlowyOverlay.show(
|
||||
context: context,
|
||||
builder: (BuildContext overlayContext) {
|
||||
return RelatedRowDetailPage(
|
||||
databaseId: context
|
||||
.read<RelationCellBloc>()
|
||||
.state
|
||||
.relatedDatabaseMeta!
|
||||
.databaseId,
|
||||
rowId: row.rowId,
|
||||
);
|
||||
},
|
||||
);
|
||||
PopoverContainer.of(context).close();
|
||||
} else {
|
||||
context
|
||||
.read<RelationCellBloc>()
|
||||
.add(RelationCellEvent.selectRow(focusedRowId));
|
||||
}
|
||||
}
|
||||
focusNode.requestFocus();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RowList extends StatelessWidget {
|
||||
const _RowList({
|
||||
required this.databaseId,
|
||||
required this.rows,
|
||||
required this.isSelected,
|
||||
});
|
||||
|
||||
final String databaseId;
|
||||
final List<RelatedRowDataPB> rows;
|
||||
final bool isSelected;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) => _RowListItem(
|
||||
row: rows[index],
|
||||
databaseId: databaseId,
|
||||
isSelected: isSelected,
|
||||
),
|
||||
childCount: rows.length,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RowListItem extends StatelessWidget {
|
||||
const _RowListItem({
|
||||
required this.row,
|
||||
required this.isSelected,
|
||||
required this.databaseId,
|
||||
});
|
||||
|
||||
final RelatedRowDataPB row;
|
||||
final String databaseId;
|
||||
final bool isSelected;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isHovered =
|
||||
context.watch<RelationRowSearchBloc>().state.focusedRowId == row.rowId;
|
||||
return Container(
|
||||
height: 28,
|
||||
margin: const EdgeInsets.symmetric(horizontal: 6.0, vertical: 2.0),
|
||||
decoration: BoxDecoration(
|
||||
color: isHovered ? AFThemeExtension.of(context).lightGreyHover : null,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(6)),
|
||||
),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (isSelected) {
|
||||
FlowyOverlay.show(
|
||||
context: context,
|
||||
builder: (BuildContext overlayContext) {
|
||||
return RelatedRowDetailPage(
|
||||
databaseId: databaseId,
|
||||
rowId: row.rowId,
|
||||
);
|
||||
},
|
||||
);
|
||||
PopoverContainer.of(context).close();
|
||||
} else {
|
||||
context
|
||||
.read<RelationCellBloc>()
|
||||
.add(RelationCellEvent.selectRow(row.rowId));
|
||||
}
|
||||
},
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
onHover: (_) => context
|
||||
.read<RelationRowSearchBloc>()
|
||||
.add(RelationRowSearchEvent.updateFocusedOption(row.rowId)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6.0, vertical: 4.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: FlowyText.medium(
|
||||
row.name.trim().isEmpty
|
||||
? LocaleKeys.grid_title_placeholder.tr()
|
||||
: row.name,
|
||||
color: row.name.trim().isEmpty
|
||||
? Theme.of(context).hintColor
|
||||
: null,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
if (isSelected && isHovered)
|
||||
_UnselectRowButton(
|
||||
onPressed: () => context
|
||||
.read<RelationCellBloc>()
|
||||
.add(RelationCellEvent.selectRow(row.rowId)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _UnselectRowButton extends StatefulWidget {
|
||||
const _UnselectRowButton({
|
||||
required this.onPressed,
|
||||
});
|
||||
|
||||
final VoidCallback onPressed;
|
||||
|
||||
@override
|
||||
State<_UnselectRowButton> createState() => _UnselectRowButtonState();
|
||||
}
|
||||
|
||||
class _UnselectRowButtonState extends State<_UnselectRowButton> {
|
||||
final _materialStatesController = MaterialStatesController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_materialStatesController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextButton(
|
||||
onPressed: widget.onPressed,
|
||||
onHover: (_) => setState(() {}),
|
||||
onFocusChange: (_) => setState(() {}),
|
||||
style: ButtonStyle(
|
||||
fixedSize: const MaterialStatePropertyAll(Size.square(32)),
|
||||
minimumSize: const MaterialStatePropertyAll(Size.square(32)),
|
||||
maximumSize: const MaterialStatePropertyAll(Size.square(32)),
|
||||
overlayColor: MaterialStateProperty.resolveWith((state) {
|
||||
if (state.contains(MaterialState.focused)) {
|
||||
return AFThemeExtension.of(context).greyHover;
|
||||
}
|
||||
return Colors.transparent;
|
||||
}),
|
||||
shape: const MaterialStatePropertyAll(
|
||||
RoundedRectangleBorder(borderRadius: Corners.s6Border),
|
||||
),
|
||||
),
|
||||
statesController: _materialStatesController,
|
||||
child: Container(
|
||||
color: _materialStatesController.value
|
||||
.contains(MaterialState.hovered) ||
|
||||
_materialStatesController.value.contains(MaterialState.focused)
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.onBackground,
|
||||
width: 12,
|
||||
height: 1,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RelationCellEditorDatabasePicker extends StatelessWidget {
|
||||
const _RelationCellEditorDatabasePicker();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -479,7 +479,7 @@ class SelectOptionTagCell extends StatelessWidget {
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 5.0,
|
||||
horizontal: 6.0,
|
||||
vertical: 4.0,
|
||||
),
|
||||
child: SelectOptionTag(
|
||||
|
@ -0,0 +1,39 @@
|
||||
import 'package:appflowy/plugins/database/application/row/related_row_detail_bloc.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import 'row_detail.dart';
|
||||
|
||||
class RelatedRowDetailPage extends StatelessWidget {
|
||||
const RelatedRowDetailPage({
|
||||
super.key,
|
||||
required this.databaseId,
|
||||
required this.rowId,
|
||||
});
|
||||
|
||||
final String databaseId;
|
||||
final String rowId;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (_) => RelatedRowDetailPageBloc(
|
||||
databaseId: databaseId,
|
||||
initialRowId: rowId,
|
||||
),
|
||||
child: BlocBuilder<RelatedRowDetailPageBloc, RelatedRowDetailPageState>(
|
||||
builder: (context, state) {
|
||||
return state.when(
|
||||
loading: () => const SizedBox.shrink(),
|
||||
ready: (databaseController, rowController) {
|
||||
return RowDetailPage(
|
||||
databaseController: databaseController,
|
||||
rowController: rowController,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user