Skip to content

Commit 9a93a72

Browse files
committed
feat: add new field type
1 parent 60b8000 commit 9a93a72

File tree

38 files changed

+1016
-62
lines changed

38 files changed

+1016
-62
lines changed
Lines changed: 3 additions & 0 deletions
Loading

frontend/app_flowy/assets/translations/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@
160160
"numberFieldName": "Numbers",
161161
"singleSelectFieldName": "Select",
162162
"multiSelectFieldName": "Multiselect",
163+
"urlFieldName": "URL",
163164
"numberFormat": " Number format",
164165
"dateFormat": " Date format",
165166
"includeTime": " Include time",

frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
1212
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
1313
import 'package:flutter/foundation.dart';
1414
import 'package:freezed_annotation/freezed_annotation.dart';
15-
1615
import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart';
1716
import 'package:app_flowy/workspace/application/grid/cell/select_option_service.dart';
1817
import 'package:app_flowy/workspace/application/grid/field/field_service.dart';

frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ part of 'cell_service.dart';
33
typedef GridCellContext = _GridCellContext<Cell, String>;
44
typedef GridSelectOptionCellContext = _GridCellContext<SelectOptionCellData, String>;
55
typedef GridDateCellContext = _GridCellContext<DateCellData, DateCalData>;
6+
typedef GridURLCellContext = _GridCellContext<Cell, String>;
67

78
class GridCellContextBuilder {
89
final GridCellCache _cellCache;
@@ -58,12 +59,21 @@ class GridCellContextBuilder {
5859
cellDataLoader: SelectOptionCellDataLoader(gridCell: _gridCell),
5960
cellDataPersistence: CellDataPersistence(gridCell: _gridCell),
6061
);
61-
default:
62-
throw UnimplementedError;
62+
63+
case FieldType.URL:
64+
return GridURLCellContext(
65+
gridCell: _gridCell,
66+
cellCache: _cellCache,
67+
cellDataLoader: GridCellDataLoader(gridCell: _gridCell),
68+
cellDataPersistence: CellDataPersistence(gridCell: _gridCell),
69+
);
6370
}
71+
throw UnimplementedError;
6472
}
6573
}
6674

75+
// T: the type of the CellData
76+
// D: the type of the data that will be save to disk
6777
// ignore: must_be_immutable
6878
class _GridCellContext<T, D> extends Equatable {
6979
final GridCell gridCell;
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell;
2+
import 'package:flutter_bloc/flutter_bloc.dart';
3+
import 'package:freezed_annotation/freezed_annotation.dart';
4+
import 'dart:async';
5+
import 'cell_service/cell_service.dart';
6+
7+
part 'url_cell_bloc.freezed.dart';
8+
9+
class URLCellBloc extends Bloc<URLCellEvent, URLCellState> {
10+
final GridURLCellContext cellContext;
11+
void Function()? _onCellChangedFn;
12+
URLCellBloc({
13+
required this.cellContext,
14+
}) : super(URLCellState.initial(cellContext)) {
15+
on<URLCellEvent>(
16+
(event, emit) async {
17+
event.when(
18+
initial: () {
19+
_startListening();
20+
},
21+
updateText: (text) {
22+
cellContext.saveCellData(text);
23+
emit(state.copyWith(content: text));
24+
},
25+
didReceiveCellUpdate: (cellData) {
26+
emit(state.copyWith(content: cellData.content));
27+
},
28+
);
29+
},
30+
);
31+
}
32+
33+
@override
34+
Future<void> close() async {
35+
if (_onCellChangedFn != null) {
36+
cellContext.removeListener(_onCellChangedFn!);
37+
_onCellChangedFn = null;
38+
}
39+
cellContext.dispose();
40+
return super.close();
41+
}
42+
43+
void _startListening() {
44+
_onCellChangedFn = cellContext.startListening(
45+
onCellChanged: ((cellData) {
46+
if (!isClosed) {
47+
add(URLCellEvent.didReceiveCellUpdate(cellData));
48+
}
49+
}),
50+
);
51+
}
52+
}
53+
54+
@freezed
55+
class URLCellEvent with _$URLCellEvent {
56+
const factory URLCellEvent.initial() = _InitialCell;
57+
const factory URLCellEvent.didReceiveCellUpdate(Cell cell) = _DidReceiveCellUpdate;
58+
const factory URLCellEvent.updateText(String text) = _UpdateText;
59+
}
60+
61+
@freezed
62+
class URLCellState with _$URLCellState {
63+
const factory URLCellState({
64+
required String content,
65+
required String url,
66+
}) = _URLCellState;
67+
68+
factory URLCellState.initial(GridURLCellContext context) {
69+
final cellData = context.getCellData();
70+
return URLCellState(content: cellData?.content ?? "", url: "");
71+
}
72+
}

frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import 'date_cell/date_cell.dart';
1313
import 'number_cell.dart';
1414
import 'select_option_cell/select_option_cell.dart';
1515
import 'text_cell.dart';
16+
import 'url_cell.dart';
1617

1718
GridCellWidget buildGridCellWidget(GridCell gridCell, GridCellCache cellCache, {GridCellStyle? style}) {
1819
final key = ValueKey(gridCell.cellId());
@@ -32,10 +33,11 @@ GridCellWidget buildGridCellWidget(GridCell gridCell, GridCellCache cellCache, {
3233
return NumberCell(cellContextBuilder: cellContextBuilder, key: key);
3334
case FieldType.RichText:
3435
return GridTextCell(cellContextBuilder: cellContextBuilder, style: style, key: key);
35-
36-
default:
37-
throw UnimplementedError;
36+
case FieldType.URL:
37+
return GridURLCell(cellContextBuilder: cellContextBuilder, style: style, key: key);
38+
3839
}
40+
throw UnimplementedError;
3941
}
4042

4143
class BlankCell extends StatelessWidget {
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import 'dart:async';
2+
import 'package:app_flowy/workspace/application/grid/cell/url_cell_bloc.dart';
3+
import 'package:flutter/material.dart';
4+
import 'package:flutter_bloc/flutter_bloc.dart';
5+
import 'package:app_flowy/workspace/application/grid/prelude.dart';
6+
import 'cell_builder.dart';
7+
8+
class GridURLCellStyle extends GridCellStyle {
9+
String? placeholder;
10+
11+
GridURLCellStyle({
12+
this.placeholder,
13+
});
14+
}
15+
16+
class GridURLCell extends StatefulWidget with GridCellWidget {
17+
final GridCellContextBuilder cellContextBuilder;
18+
late final GridURLCellStyle? cellStyle;
19+
GridURLCell({
20+
required this.cellContextBuilder,
21+
GridCellStyle? style,
22+
Key? key,
23+
}) : super(key: key) {
24+
if (style != null) {
25+
cellStyle = (style as GridURLCellStyle);
26+
} else {
27+
cellStyle = null;
28+
}
29+
}
30+
31+
@override
32+
State<GridURLCell> createState() => _GridURLCellState();
33+
}
34+
35+
class _GridURLCellState extends State<GridURLCell> {
36+
late URLCellBloc _cellBloc;
37+
late TextEditingController _controller;
38+
late CellSingleFocusNode _focusNode;
39+
Timer? _delayOperation;
40+
41+
@override
42+
void initState() {
43+
final cellContext = widget.cellContextBuilder.build() as GridURLCellContext;
44+
_cellBloc = URLCellBloc(cellContext: cellContext);
45+
_cellBloc.add(const URLCellEvent.initial());
46+
_controller = TextEditingController(text: _cellBloc.state.content);
47+
_focusNode = CellSingleFocusNode();
48+
49+
_listenFocusNode();
50+
_listenRequestFocus(context);
51+
super.initState();
52+
}
53+
54+
@override
55+
Widget build(BuildContext context) {
56+
return BlocProvider.value(
57+
value: _cellBloc,
58+
child: BlocListener<URLCellBloc, URLCellState>(
59+
listener: (context, state) {
60+
if (_controller.text != state.content) {
61+
_controller.text = state.content;
62+
}
63+
},
64+
child: TextField(
65+
controller: _controller,
66+
focusNode: _focusNode,
67+
onChanged: (value) => focusChanged(),
68+
onEditingComplete: () => _focusNode.unfocus(),
69+
maxLines: null,
70+
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
71+
decoration: InputDecoration(
72+
contentPadding: EdgeInsets.zero,
73+
border: InputBorder.none,
74+
hintText: widget.cellStyle?.placeholder,
75+
isDense: true,
76+
),
77+
),
78+
),
79+
);
80+
}
81+
82+
@override
83+
Future<void> dispose() async {
84+
widget.requestFocus.removeAllListener();
85+
_delayOperation?.cancel();
86+
_cellBloc.close();
87+
_focusNode.removeSingleListener();
88+
_focusNode.dispose();
89+
90+
super.dispose();
91+
}
92+
93+
@override
94+
void didUpdateWidget(covariant GridURLCell oldWidget) {
95+
if (oldWidget != widget) {
96+
_listenFocusNode();
97+
}
98+
super.didUpdateWidget(oldWidget);
99+
}
100+
101+
void _listenFocusNode() {
102+
widget.onFocus.value = _focusNode.hasFocus;
103+
_focusNode.setSingleListener(() {
104+
widget.onFocus.value = _focusNode.hasFocus;
105+
focusChanged();
106+
});
107+
}
108+
109+
void _listenRequestFocus(BuildContext context) {
110+
widget.requestFocus.addListener(() {
111+
if (_focusNode.hasFocus == false && _focusNode.canRequestFocus) {
112+
FocusScope.of(context).requestFocus(_focusNode);
113+
}
114+
});
115+
}
116+
117+
Future<void> focusChanged() async {
118+
if (mounted) {
119+
_delayOperation?.cancel();
120+
_delayOperation = Timer(const Duration(milliseconds: 300), () {
121+
if (_cellBloc.isClosed == false && _controller.text != _cellBloc.state.content) {
122+
_cellBloc.add(URLCellEvent.updateText(_controller.text));
123+
}
124+
});
125+
}
126+
}
127+
}

frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import 'type_option/multi_select.dart';
2222
import 'type_option/number.dart';
2323
import 'type_option/rich_text.dart';
2424
import 'type_option/single_select.dart';
25+
import 'type_option/url.dart';
2526

2627
typedef UpdateFieldCallback = void Function(Field, Uint8List);
2728
typedef SwitchToFieldCallback = Future<Either<FieldTypeOptionData, FlowyError>> Function(
@@ -168,9 +169,12 @@ TypeOptionBuilder _makeTypeOptionBuild({
168169
typeOptionContext as RichTextTypeOptionContext,
169170
);
170171

171-
default:
172-
throw UnimplementedError;
172+
case FieldType.URL:
173+
return URLTypeOptionBuilder(
174+
typeOptionContext as URLTypeOptionContext,
175+
);
173176
}
177+
throw UnimplementedError;
174178
}
175179

176180
TypeOptionContext _makeTypeOptionContext(GridFieldContext fieldContext) {
@@ -205,9 +209,15 @@ TypeOptionContext _makeTypeOptionContext(GridFieldContext fieldContext) {
205209
fieldContext: fieldContext,
206210
dataBuilder: SingleSelectTypeOptionDataBuilder(),
207211
);
208-
default:
209-
throw UnimplementedError();
212+
213+
case FieldType.URL:
214+
return URLTypeOptionContext(
215+
fieldContext: fieldContext,
216+
dataBuilder: URLTypeOptionDataBuilder(),
217+
);
210218
}
219+
220+
throw UnimplementedError();
211221
}
212222

213223
abstract class TypeOptionWidget extends StatelessWidget {

frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_type_extension.dart

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ extension FieldTypeListExtension on FieldType {
1717
return "grid/field/text";
1818
case FieldType.SingleSelect:
1919
return "grid/field/single_select";
20-
default:
21-
throw UnimplementedError;
20+
case FieldType.URL:
21+
return "grid/field/url";
2222
}
23+
throw UnimplementedError;
2324
}
2425

2526
String title() {
@@ -36,8 +37,9 @@ extension FieldTypeListExtension on FieldType {
3637
return LocaleKeys.grid_field_textFieldName.tr();
3738
case FieldType.SingleSelect:
3839
return LocaleKeys.grid_field_singleSelectFieldName.tr();
39-
default:
40-
throw UnimplementedError;
40+
case FieldType.URL:
41+
return LocaleKeys.grid_field_urlFieldName.tr();
4142
}
43+
throw UnimplementedError;
4244
}
4345
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
2+
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart';
3+
import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart';
4+
import 'package:flutter/material.dart';
5+
6+
typedef URLTypeOptionContext = TypeOptionContext<URLTypeOption>;
7+
8+
class URLTypeOptionDataBuilder extends TypeOptionDataBuilder<URLTypeOption> {
9+
@override
10+
URLTypeOption fromBuffer(List<int> buffer) {
11+
return URLTypeOption.fromBuffer(buffer);
12+
}
13+
}
14+
15+
class URLTypeOptionBuilder extends TypeOptionBuilder {
16+
URLTypeOptionBuilder(URLTypeOptionContext typeOptionContext);
17+
18+
@override
19+
Widget? get customWidget => null;
20+
}

0 commit comments

Comments
 (0)