fix: dispose of resources in time (#4625)

* fix: limit length when renaming views

* chore: clean up database listeners

* fix: dispose of controllers properly

* fix: dispose of resources properly

* fix: deleting filters with same name

* chore: extend DatabaseTabBarItemBuilder

* fix: null check on null value
This commit is contained in:
Mathias Mogensen
2024-02-07 18:38:53 +01:00
committed by GitHub
parent 0db21b91a3
commit 92ceb2cd7d
61 changed files with 384 additions and 335 deletions

View File

@ -44,7 +44,6 @@ class _MobileBottomSheetEditLinkWidgetState
void dispose() { void dispose() {
textController.dispose(); textController.dispose();
hrefController.dispose(); hrefController.dispose();
super.dispose(); super.dispose();
} }

View File

@ -27,14 +27,12 @@ class _MobileBottomSheetRenameWidgetState
@override @override
void initState() { void initState() {
super.initState(); super.initState();
controller = TextEditingController(text: widget.name); controller = TextEditingController(text: widget.name);
} }
@override @override
void dispose() { void dispose() {
controller.dispose(); controller.dispose();
super.dispose(); super.dispose();
} }

View File

@ -38,6 +38,12 @@ class _MobileBoardContentState extends State<MobileBoardContent> {
scrollController = ScrollController(); scrollController = ScrollController();
} }
@override
void dispose() {
scrollController.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width; final screenWidth = MediaQuery.of(context).size.width;

View File

@ -19,6 +19,12 @@ class _MobileBoardTrailingState extends State<MobileBoardTrailing> {
bool isEditing = false; bool isEditing = false;
@override
void dispose() {
_textController.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final screenSize = MediaQuery.of(context).size; final screenSize = MediaQuery.of(context).size;

View File

@ -196,7 +196,6 @@ class _MobileFieldEditorState extends State<MobileFieldEditor> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
values = widget.defaultValues; values = widget.defaultValues;
controller.text = values.name; controller.text = values.name;
} }
@ -204,7 +203,6 @@ class _MobileFieldEditorState extends State<MobileFieldEditor> {
@override @override
void dispose() { void dispose() {
controller.dispose(); controller.dispose();
super.dispose(); super.dispose();
} }

View File

@ -83,6 +83,12 @@ class _NameAndIconState extends State<_NameAndIcon> {
textEditingController.text = widget.view.name; textEditingController.text = widget.view.name;
} }
@override
void dispose() {
textEditingController.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return FlowyOptionTile.textField( return FlowyOptionTile.textField(

View File

@ -23,6 +23,7 @@ class _EditUsernameBottomSheetState extends State<EditUsernameBottomSheet> {
late TextEditingController _textFieldController; late TextEditingController _textFieldController;
final _formKey = GlobalKey<FormState>(); final _formKey = GlobalKey<FormState>();
@override @override
void initState() { void initState() {
super.initState(); super.initState();

View File

@ -16,6 +16,16 @@ class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
final CheckboxCellController cellController; final CheckboxCellController cellController;
void Function()? _onCellChangedFn; void Function()? _onCellChangedFn;
@override
Future<void> close() async {
if (_onCellChangedFn != null) {
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
await cellController.dispose();
return super.close();
}
void _dispatch() { void _dispatch() {
on<CheckboxCellEvent>( on<CheckboxCellEvent>(
(event, emit) { (event, emit) {
@ -35,17 +45,6 @@ class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
); );
} }
@override
Future<void> close() async {
if (_onCellChangedFn != null) {
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
await cellController.dispose();
return super.close();
}
void _startListening() { void _startListening() {
_onCellChangedFn = cellController.addListener( _onCellChangedFn = cellController.addListener(
onCellChanged: (cellData) { onCellChanged: (cellData) {

View File

@ -32,6 +32,16 @@ class ChecklistCellBloc extends Bloc<ChecklistCellEvent, ChecklistCellState> {
final ChecklistCellBackendService _checklistCellService; final ChecklistCellBackendService _checklistCellService;
void Function()? _onCellChangedFn; void Function()? _onCellChangedFn;
@override
Future<void> close() async {
if (_onCellChangedFn != null) {
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
await cellController.dispose();
return super.close();
}
void _dispatch() { void _dispatch() {
on<ChecklistCellEvent>( on<ChecklistCellEvent>(
(event, emit) async { (event, emit) async {
@ -79,16 +89,6 @@ class ChecklistCellBloc extends Bloc<ChecklistCellEvent, ChecklistCellState> {
); );
} }
@override
Future<void> close() async {
if (_onCellChangedFn != null) {
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
await cellController.dispose();
return super.close();
}
void _startListening() { void _startListening() {
_onCellChangedFn = cellController.addListener( _onCellChangedFn = cellController.addListener(
onCellChanged: (data) { onCellChanged: (data) {

View File

@ -17,6 +17,16 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
final DateCellController cellController; final DateCellController cellController;
void Function()? _onCellChangedFn; void Function()? _onCellChangedFn;
@override
Future<void> close() async {
if (_onCellChangedFn != null) {
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
await cellController.dispose();
return super.close();
}
void _dispatch() { void _dispatch() {
on<DateCellEvent>( on<DateCellEvent>(
(event, emit) async { (event, emit) async {
@ -35,15 +45,6 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
); );
} }
@override
Future<void> close() async {
if (_onCellChangedFn != null) {
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
return super.close();
}
void _startListening() { void _startListening() {
_onCellChangedFn = cellController.addListener( _onCellChangedFn = cellController.addListener(
onCellChanged: (data) { onCellChanged: (data) {

View File

@ -15,6 +15,16 @@ class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
final NumberCellController cellController; final NumberCellController cellController;
void Function()? _onCellChangedFn; void Function()? _onCellChangedFn;
@override
Future<void> close() async {
if (_onCellChangedFn != null) {
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
await cellController.dispose();
return super.close();
}
void _dispatch() { void _dispatch() {
on<NumberCellEvent>( on<NumberCellEvent>(
(event, emit) async { (event, emit) async {
@ -45,16 +55,6 @@ class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
); );
} }
@override
Future<void> close() async {
if (_onCellChangedFn != null) {
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
await cellController.dispose();
return super.close();
}
void _startListening() { void _startListening() {
_onCellChangedFn = cellController.addListener( _onCellChangedFn = cellController.addListener(
onCellChanged: (cellContent) { onCellChanged: (cellContent) {

View File

@ -17,6 +17,16 @@ class SelectOptionCellBloc
final SelectOptionCellController cellController; final SelectOptionCellController cellController;
void Function()? _onCellChangedFn; void Function()? _onCellChangedFn;
@override
Future<void> close() async {
if (_onCellChangedFn != null) {
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
await cellController.dispose();
return super.close();
}
void _dispatch() { void _dispatch() {
on<SelectOptionCellEvent>( on<SelectOptionCellEvent>(
(event, emit) async { (event, emit) async {
@ -36,16 +46,6 @@ class SelectOptionCellBloc
); );
} }
@override
Future<void> close() async {
if (_onCellChangedFn != null) {
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
await cellController.dispose();
return super.close();
}
void _startListening() { void _startListening() {
_onCellChangedFn = cellController.addListener( _onCellChangedFn = cellController.addListener(
onCellChanged: (selectOptionCellData) { onCellChanged: (selectOptionCellData) {

View File

@ -15,6 +15,16 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
final TextCellController cellController; final TextCellController cellController;
void Function()? _onCellChangedFn; void Function()? _onCellChangedFn;
@override
Future<void> close() async {
if (_onCellChangedFn != null) {
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
await cellController.dispose();
return super.close();
}
void _dispatch() { void _dispatch() {
on<TextCellEvent>( on<TextCellEvent>(
(event, emit) { (event, emit) {
@ -41,16 +51,6 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
); );
} }
@override
Future<void> close() async {
if (_onCellChangedFn != null) {
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
await cellController.dispose();
return super.close();
}
void _startListening() { void _startListening() {
_onCellChangedFn = cellController.addListener( _onCellChangedFn = cellController.addListener(
onCellChanged: (cellContent) { onCellChanged: (cellContent) {

View File

@ -17,6 +17,16 @@ class TimestampCellBloc extends Bloc<TimestampCellEvent, TimestampCellState> {
final TimestampCellController cellController; final TimestampCellController cellController;
void Function()? _onCellChangedFn; void Function()? _onCellChangedFn;
@override
Future<void> close() async {
if (_onCellChangedFn != null) {
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
await cellController.dispose();
return super.close();
}
void _dispatch() { void _dispatch() {
on<TimestampCellEvent>( on<TimestampCellEvent>(
(event, emit) async { (event, emit) async {
@ -35,16 +45,6 @@ class TimestampCellBloc extends Bloc<TimestampCellEvent, TimestampCellState> {
); );
} }
@override
Future<void> close() async {
if (_onCellChangedFn != null) {
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
await cellController.dispose();
return super.close();
}
void _startListening() { void _startListening() {
_onCellChangedFn = cellController.addListener( _onCellChangedFn = cellController.addListener(
onCellChanged: (data) { onCellChanged: (data) {

View File

@ -16,6 +16,16 @@ class URLCellBloc extends Bloc<URLCellEvent, URLCellState> {
final URLCellController cellController; final URLCellController cellController;
void Function()? _onCellChangedFn; void Function()? _onCellChangedFn;
@override
Future<void> close() async {
if (_onCellChangedFn != null) {
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
await cellController.dispose();
return super.close();
}
void _dispatch() { void _dispatch() {
on<URLCellEvent>( on<URLCellEvent>(
(event, emit) async { (event, emit) async {
@ -39,16 +49,6 @@ class URLCellBloc extends Bloc<URLCellEvent, URLCellState> {
); );
} }
@override
Future<void> close() async {
if (_onCellChangedFn != null) {
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
await cellController.dispose();
return super.close();
}
void _startListening() { void _startListening() {
_onCellChangedFn = cellController.addListener( _onCellChangedFn = cellController.addListener(
onCellChanged: (cellData) { onCellChanged: (cellData) {

View File

@ -1,52 +0,0 @@
import 'dart:typed_data';
import 'package:appflowy/core/notification/grid_notification.dart';
import 'package:flowy_infra/notifier.dart';
import 'package:appflowy_backend/protobuf/flowy-error/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:dartz/dartz.dart';
/// Listener for database layout changes.
class DatabaseLayoutListener {
DatabaseLayoutListener(this.viewId);
final String viewId;
PublishNotifier<Either<DatabaseLayoutPB, FlowyError>>? _layoutNotifier =
PublishNotifier();
DatabaseNotificationListener? _listener;
void start({
required void Function(Either<DatabaseLayoutPB, FlowyError>)
onLayoutChanged,
}) {
_layoutNotifier?.addPublishListener(onLayoutChanged);
_listener = DatabaseNotificationListener(
objectId: viewId,
handler: _handler,
);
}
void _handler(
DatabaseNotification ty,
Either<Uint8List, FlowyError> result,
) {
switch (ty) {
case DatabaseNotification.DidUpdateDatabaseLayout:
result.fold(
(payload) => _layoutNotifier?.value =
left(DatabaseLayoutMetaPB.fromBuffer(payload).layout),
(error) => _layoutNotifier?.value = right(error),
);
break;
default:
break;
}
}
Future<void> stop() async {
await _listener?.stop();
_layoutNotifier?.dispose();
_layoutNotifier = null;
}
}

View File

@ -1,62 +0,0 @@
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pb.dart';
import 'setting_listener.dart';
import 'setting_service.dart';
typedef OnError = void Function(FlowyError);
typedef OnSettingUpdated = void Function(DatabaseViewSettingPB);
class SettingController {
SettingController({
required this.viewId,
}) : _settingBackendSvc = SettingBackendService(viewId: viewId),
_listener = DatabaseSettingListener(viewId: viewId) {
// Load setting
_settingBackendSvc.getSetting().then((result) {
result.fold(
(newSetting) => updateSetting(newSetting),
(err) => _onError?.call(err),
);
});
// Listen on the setting changes
_listener.start(
onSettingUpdated: (result) {
result.fold(
(newSetting) => updateSetting(newSetting),
(err) => _onError?.call(err),
);
},
);
}
final String viewId;
final SettingBackendService _settingBackendSvc;
final DatabaseSettingListener _listener;
OnSettingUpdated? _onSettingUpdated;
OnError? _onError;
DatabaseViewSettingPB? _setting;
DatabaseViewSettingPB? get setting => _setting;
void startListening({
required OnSettingUpdated onSettingUpdated,
required OnError onError,
}) {
assert(_onSettingUpdated == null, 'Should call once');
assert(_onError == null, 'Should call once');
_onSettingUpdated = onSettingUpdated;
_onError = onError;
}
void updateSetting(DatabaseViewSettingPB newSetting) {
_setting = newSetting;
_onSettingUpdated?.call(newSetting);
}
void dispose() {
_onSettingUpdated = null;
_onError = null;
_listener.stop();
}
}

View File

@ -131,6 +131,7 @@ class DatabaseTabBarBloc
Future<void> close() async { Future<void> close() async {
for (final tabBar in state.tabBars) { for (final tabBar in state.tabBars) {
await state.tabBarControllerByViewId[tabBar.viewId]?.dispose(); await state.tabBarControllerByViewId[tabBar.viewId]?.dispose();
tabBar.dispose();
} }
return super.close(); return super.close();
} }
@ -261,6 +262,10 @@ class DatabaseTabBar extends Equatable {
@override @override
List<Object?> get props => [view.hashCode]; List<Object?> get props => [view.hashCode];
void dispose() {
_builder.dispose();
}
} }
typedef OnViewUpdated = void Function(ViewPB newView); typedef OnViewUpdated = void Function(ViewPB newView);

View File

@ -33,7 +33,7 @@ import '../application/board_bloc.dart';
import 'toolbar/board_setting_bar.dart'; import 'toolbar/board_setting_bar.dart';
import 'widgets/board_hidden_groups.dart'; import 'widgets/board_hidden_groups.dart';
class BoardPageTabBarBuilderImpl implements DatabaseTabBarItemBuilder { class BoardPageTabBarBuilderImpl extends DatabaseTabBarItemBuilder {
@override @override
Widget content( Widget content(
BuildContext context, BuildContext context,

View File

@ -26,6 +26,12 @@ class CalendarSettingBloc
final DatabaseController _databaseController; final DatabaseController _databaseController;
final DatabaseLayoutSettingListener _listener; final DatabaseLayoutSettingListener _listener;
@override
Future<void> close() async {
await _listener.stop();
return super.close();
}
void _dispatch() { void _dispatch() {
on<CalendarSettingEvent>((event, emit) { on<CalendarSettingEvent>((event, emit) {
event.when( event.when(
@ -108,12 +114,6 @@ class CalendarSettingBloc
}, },
); );
} }
@override
Future<void> close() async {
await _listener.stop();
return super.close();
}
} }
@freezed @freezed

View File

@ -30,7 +30,7 @@ import 'calendar_day.dart';
import 'layout/sizes.dart'; import 'layout/sizes.dart';
import 'toolbar/calendar_setting_bar.dart'; import 'toolbar/calendar_setting_bar.dart';
class CalendarPageTabBarBuilderImpl implements DatabaseTabBarItemBuilder { class CalendarPageTabBarBuilderImpl extends DatabaseTabBarItemBuilder {
@override @override
Widget content( Widget content(
BuildContext context, BuildContext context,

View File

@ -29,6 +29,7 @@ class CalculationsBloc extends Bloc<CalculationsEvent, CalculationsState> {
@override @override
Future<void> close() async { Future<void> close() async {
_fieldController.removeListener(onFieldsListener: _onReceiveFields); _fieldController.removeListener(onFieldsListener: _onReceiveFields);
await _calculationsListener.stop();
await super.close(); await super.close();
} }

View File

@ -73,7 +73,7 @@ class GridFilterMenuBloc
} }
@override @override
Future<void> close() { Future<void> close() async {
if (_onFilterFn != null) { if (_onFilterFn != null) {
fieldController.removeListener(onFiltersListener: _onFilterFn!); fieldController.removeListener(onFiltersListener: _onFilterFn!);
_onFilterFn = null; _onFilterFn = null;

View File

@ -45,7 +45,7 @@ class ToggleExtensionNotifier extends ChangeNotifier {
} }
} }
class DesktopGridTabBarBuilderImpl implements DatabaseTabBarItemBuilder { class DesktopGridTabBarBuilderImpl extends DatabaseTabBarItemBuilder {
final _toggleExtension = ToggleExtensionNotifier(); final _toggleExtension = ToggleExtensionNotifier();
@override @override
@ -86,6 +86,12 @@ class DesktopGridTabBarBuilderImpl implements DatabaseTabBarItemBuilder {
); );
} }
@override
void dispose() {
_toggleExtension.dispose();
super.dispose();
}
ValueKey _makeValueKey(DatabaseController controller) { ValueKey _makeValueKey(DatabaseController controller) {
return ValueKey(controller.viewId); return ValueKey(controller.viewId);
} }

View File

@ -29,7 +29,7 @@ import 'widgets/header/mobile_grid_header.dart';
import 'widgets/mobile_fab.dart'; import 'widgets/mobile_fab.dart';
import 'widgets/row/mobile_row.dart'; import 'widgets/row/mobile_row.dart';
class MobileGridTabBarBuilderImpl implements DatabaseTabBarItemBuilder { class MobileGridTabBarBuilderImpl extends DatabaseTabBarItemBuilder {
@override @override
Widget content( Widget content(
BuildContext context, BuildContext context,

View File

@ -35,7 +35,12 @@ class FilterMenu extends StatelessWidget {
final List<Widget> children = []; final List<Widget> children = [];
children.addAll( children.addAll(
state.filters state.filters
.map((filterInfo) => FilterMenuItem(filterInfo: filterInfo)) .map(
(filterInfo) => FilterMenuItem(
key: ValueKey(filterInfo.filter.id),
filterInfo: filterInfo,
),
)
.toList(), .toList(),
); );

View File

@ -326,6 +326,12 @@ class _RowEnterRegionState extends State<_RowEnterRegion> {
_rowStateNotifier = RegionStateNotifier(); _rowStateNotifier = RegionStateNotifier();
} }
@override
Future<void> dispose() async {
_rowStateNotifier.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ChangeNotifierProvider.value( return ChangeNotifierProvider.value(
@ -338,10 +344,4 @@ class _RowEnterRegionState extends State<_RowEnterRegion> {
), ),
); );
} }
@override
Future<void> dispose() async {
_rowStateNotifier.dispose();
super.dispose();
}
} }

View File

@ -41,6 +41,12 @@ abstract class DatabaseTabBarItemBuilder {
BuildContext context, BuildContext context,
DatabaseController controller, DatabaseController controller,
); );
/// Should be called in case a builder has resources it
/// needs to dispose of.
///
// If we add any logic in this method, add @mustCallSuper !
void dispose() {}
} }
class DatabaseTabBarView extends StatefulWidget { class DatabaseTabBarView extends StatefulWidget {

View File

@ -47,6 +47,16 @@ class CardBloc extends Bloc<CardEvent, CardState> {
VoidCallback? _rowCallback; VoidCallback? _rowCallback;
@override
Future<void> close() async {
if (_rowCallback != null) {
_rowCache.removeRowListener(_rowCallback!);
_rowCallback = null;
}
await _rowListener.stop();
return super.close();
}
void _dispatch() { void _dispatch() {
on<CardEvent>( on<CardEvent>(
(event, emit) async { (event, emit) async {
@ -73,16 +83,6 @@ class CardBloc extends Bloc<CardEvent, CardState> {
); );
} }
@override
Future<void> close() async {
if (_rowCallback != null) {
_rowCache.removeRowListener(_rowCallback!);
_rowCallback = null;
}
await _rowListener.stop();
return super.close();
}
Future<void> _startListening() async { Future<void> _startListening() async {
_rowCallback = _rowCache.addListener( _rowCallback = _rowCache.addListener(
rowId: rowId, rowId: rowId,
@ -113,7 +113,7 @@ List<CellContext> _makeCells(
cellContexts.removeWhere((cellContext) { cellContexts.removeWhere((cellContext) {
final fieldInfo = fieldController.getField(cellContext.fieldId); final fieldInfo = fieldController.getField(cellContext.fieldId);
return fieldInfo == null || return fieldInfo == null ||
!fieldInfo.fieldSettings!.visibility.isVisibleState() || !(fieldInfo.fieldSettings?.visibility.isVisibleState() ?? false) ||
(groupFieldId != null && cellContext.fieldId == groupFieldId); (groupFieldId != null && cellContext.fieldId == groupFieldId);
}); });
return cellContexts.toList(); return cellContexts.toList();

View File

@ -1,10 +1,14 @@
import 'package:appflowy/plugins/database/application/cell/cell_controller.dart';
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
import 'package:appflowy/plugins/database/application/database_controller.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:appflowy/plugins/database/application/cell/cell_controller.dart';
import 'package:appflowy/plugins/database/application/database_controller.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import '../row/accessory/cell_accessory.dart';
import '../row/accessory/cell_shortcuts.dart';
import '../row/cells/cell_container.dart';
import 'editable_cell_skeleton/checkbox.dart'; import 'editable_cell_skeleton/checkbox.dart';
import 'editable_cell_skeleton/checklist.dart'; import 'editable_cell_skeleton/checklist.dart';
import 'editable_cell_skeleton/date.dart'; import 'editable_cell_skeleton/date.dart';
@ -13,9 +17,6 @@ import 'editable_cell_skeleton/select_option.dart';
import 'editable_cell_skeleton/text.dart'; import 'editable_cell_skeleton/text.dart';
import 'editable_cell_skeleton/timestamp.dart'; import 'editable_cell_skeleton/timestamp.dart';
import 'editable_cell_skeleton/url.dart'; import 'editable_cell_skeleton/url.dart';
import '../row/accessory/cell_accessory.dart';
import '../row/accessory/cell_shortcuts.dart';
import '../row/cells/cell_container.dart';
enum EditableCellStyle { enum EditableCellStyle {
desktopGrid, desktopGrid,
@ -113,11 +114,12 @@ class EditableCellBuilder {
CellContext cellContext, { CellContext cellContext, {
required EditableCellSkinMap skinMap, required EditableCellSkinMap skinMap,
}) { }) {
final cellController = makeCellController(databaseController, cellContext); final DatabaseController(:fieldController) = databaseController;
final fieldType = fieldController.getField(cellContext.fieldId)!.fieldType;
final key = ValueKey( final key = ValueKey(
"${databaseController.viewId}${cellContext.fieldId}${cellContext.rowId}", "${databaseController.viewId}${cellContext.fieldId}${cellContext.rowId}",
); );
final fieldType = cellController.fieldType;
assert(skinMap.has(fieldType)); assert(skinMap.has(fieldType));
return switch (fieldType) { return switch (fieldType) {
FieldType.Checkbox => EditableCheckboxCell( FieldType.Checkbox => EditableCheckboxCell(
@ -239,6 +241,7 @@ abstract class GridCellState<T extends EditableCellWidget> extends State<T> {
@override @override
void dispose() { void dispose() {
widget.requestFocus.removeListener(onRequestFocus);
widget.requestFocus.dispose(); widget.requestFocus.dispose();
super.dispose(); super.dispose();
} }

View File

@ -89,6 +89,7 @@ class _GridURLCellState extends GridEditableTextCell<EditableURLCell> {
@override @override
void dispose() { void dispose() {
widget._cellDataNotifier.dispose();
_textEditingController.dispose(); _textEditingController.dispose();
cellBloc.close(); cellBloc.close();
super.dispose(); super.dispose();

View File

@ -78,13 +78,13 @@ class MobileRowDetailURLCellSkin extends IEditableURLCellSkin {
const []; const [];
void _showURLEditor(BuildContext context, URLCellBloc bloc, String content) { void _showURLEditor(BuildContext context, URLCellBloc bloc, String content) {
final controller = TextEditingController(text: content);
showMobileBottomSheet( showMobileBottomSheet(
context, context,
title: LocaleKeys.board_mobile_editURL.tr(), title: LocaleKeys.board_mobile_editURL.tr(),
showHeader: true, showHeader: true,
showCloseButton: true, showCloseButton: true,
builder: (_) { builder: (_) {
final controller = TextEditingController(text: content);
return TextField( return TextField(
controller: controller, controller: controller,
autofocus: true, autofocus: true,
@ -95,6 +95,6 @@ class MobileRowDetailURLCellSkin extends IEditableURLCellSkin {
}, },
); );
}, },
); ).then((_) => controller.dispose());
} }
} }

View File

@ -184,6 +184,14 @@ class _ChecklistItemState extends State<ChecklistItem> {
} }
} }
@override
void dispose() {
_textController.dispose();
_focusNode.dispose();
_debounceOnChanged?.cancel();
super.dispose();
}
@override @override
void didUpdateWidget(ChecklistItem oldWidget) { void didUpdateWidget(ChecklistItem oldWidget) {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
@ -300,6 +308,12 @@ class _NewTaskItemState extends State<NewTaskItem> {
} }
} }
@override
void dispose() {
_textEditingController.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(

View File

@ -37,6 +37,7 @@ class _SelectOptionCellEditorState extends State<SelectOptionCellEditor> {
@override @override
void dispose() { void dispose() {
popoverMutex.dispose(); popoverMutex.dispose();
textEditingController.dispose();
super.dispose(); super.dispose();
} }

View File

@ -162,7 +162,6 @@ class _SelectOptionTextFieldState extends State<SelectOptionTextField> {
}, },
), ),
child: SingleChildScrollView( child: SingleChildScrollView(
controller: ScrollController(),
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
child: Wrap(spacing: 4, children: children), child: Wrap(spacing: 4, children: children),
), ),

View File

@ -108,7 +108,8 @@ class _FindMenuState extends State<FindMenu> {
widget.searchService.currentSelectedIndex.removeListener(_setState); widget.searchService.currentSelectedIndex.removeListener(_setState);
widget.searchService.dispose(); widget.searchService.dispose();
findTextEditingController.removeListener(_searchPattern); findTextEditingController.removeListener(_searchPattern);
findTextEditingController.dispose();
findTextFieldFocusNode.dispose();
super.dispose(); super.dispose();
} }
@ -241,6 +242,12 @@ class _ReplaceMenuState extends State<ReplaceMenu> {
late final FocusNode replaceTextFieldFocusNode; late final FocusNode replaceTextFieldFocusNode;
final replaceTextEditingController = TextEditingController(); final replaceTextEditingController = TextEditingController();
@override
void dispose() {
replaceTextEditingController.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Row( return Row(

View File

@ -351,6 +351,12 @@ class CoverColorPicker extends StatefulWidget {
class _CoverColorPickerState extends State<CoverColorPicker> { class _CoverColorPickerState extends State<CoverColorPicker> {
final scrollController = ScrollController(); final scrollController = ScrollController();
@override
void dispose() {
scrollController.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
@ -375,12 +381,6 @@ class _CoverColorPickerState extends State<CoverColorPicker> {
); );
} }
@override
void dispose() {
super.dispose();
scrollController.dispose();
}
Widget _buildColorItems(List<ColorOption> options, String? selectedColor) { Widget _buildColorItems(List<ColorOption> options, String? selectedColor) {
return Row( return Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,

View File

@ -110,9 +110,13 @@ class _NetworkImageUrlInputState extends State<NetworkImageUrlInput> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
urlController.addListener(() { urlController.addListener(() => setState(() {}));
setState(() {}); }
});
@override
void dispose() {
urlController.dispose();
super.dispose();
} }
@override @override

View File

@ -120,6 +120,12 @@ class _MathInputTextFieldState extends State<MathInputTextField> {
); );
} }
@override
void dispose() {
textEditingController.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SizedBox( return SizedBox(

View File

@ -187,10 +187,10 @@ class MathEquationBlockComponentWidgetState
} }
void showEditingDialog() { void showEditingDialog() {
final controller = TextEditingController(text: formula);
showDialog( showDialog(
context: context, context: context,
builder: (context) { builder: (context) {
final controller = TextEditingController(text: formula);
return AlertDialog( return AlertDialog(
backgroundColor: Theme.of(context).canvasColor, backgroundColor: Theme.of(context).canvasColor,
title: Text( title: Text(
@ -234,7 +234,7 @@ class MathEquationBlockComponentWidgetState
actionsAlignment: MainAxisAlignment.spaceAround, actionsAlignment: MainAxisAlignment.spaceAround,
); );
}, },
); ).then((_) => controller.dispose());
} }
void updateMathEquation(String mathEquation, BuildContext context) { void updateMathEquation(String mathEquation, BuildContext context) {

View File

@ -478,7 +478,6 @@ class _ToolbarItemListViewState extends State<_ToolbarItemListView> {
void dispose() { void dispose() {
widget.editorState.selectionNotifier widget.editorState.selectionNotifier
.removeListener(_debounceUpdatePilotPosition); .removeListener(_debounceUpdatePilotPosition);
super.dispose(); super.dispose();
} }

View File

@ -134,6 +134,13 @@ class _InlineActionsHandlerState extends State<InlineActionsHandler> {
startOffset = widget.editorState.selection?.endIndex ?? 0; startOffset = widget.editorState.selection?.endIndex ?? 0;
} }
@override
void dispose() {
_scrollController.dispose();
_focusNode.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Focus( return Focus(

View File

@ -28,6 +28,13 @@ class TrashPage extends StatefulWidget {
class _TrashPageState extends State<TrashPage> { class _TrashPageState extends State<TrashPage> {
final ScrollController _scrollController = ScrollController(); final ScrollController _scrollController = ScrollController();
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
const horizontalPadding = 80.0; const horizontalPadding = 80.0;

View File

@ -28,6 +28,13 @@ class EncryptSecretScreen extends StatefulWidget {
class _EncryptSecretScreenState extends State<EncryptSecretScreen> { class _EncryptSecretScreenState extends State<EncryptSecretScreen> {
final TextEditingController _textEditingController = TextEditingController(); final TextEditingController _textEditingController = TextEditingController();
@override
void dispose() {
_textEditingController.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(

View File

@ -24,13 +24,19 @@ class MobileWorkspaceStartScreen extends StatefulWidget {
class _MobileWorkspaceStartScreenState class _MobileWorkspaceStartScreenState
extends State<MobileWorkspaceStartScreen> { extends State<MobileWorkspaceStartScreen> {
WorkspacePB? selectedWorkspace; WorkspacePB? selectedWorkspace;
final TextEditingController controller = TextEditingController();
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final style = Theme.of(context); final style = Theme.of(context);
final size = MediaQuery.of(context).size; final size = MediaQuery.of(context).size;
const double spacing = 16.0; const double spacing = 16.0;
final TextEditingController controller = TextEditingController();
final List<DropdownMenuEntry<WorkspacePB>> workspaceEntries = final List<DropdownMenuEntry<WorkspacePB>> workspaceEntries =
<DropdownMenuEntry<WorkspacePB>>[]; <DropdownMenuEntry<WorkspacePB>>[];
for (final WorkspacePB workspace in widget.workspaceState.workspaces) { for (final WorkspacePB workspace in widget.workspaceState.workspaces) {

View File

@ -22,6 +22,12 @@ class TabsBloc extends Bloc<TabsEvent, TabsState> {
late final MenuSharedState menuSharedState; late final MenuSharedState menuSharedState;
@override
Future<void> close() {
state.dispose();
return super.close();
}
void _dispatch() { void _dispatch() {
on<TabsEvent>( on<TabsEvent>(
(event, emit) async { (event, emit) async {

View File

@ -98,4 +98,10 @@ class TabsState {
currentIndex: newIndex ?? currentIndex, currentIndex: newIndex ?? currentIndex,
pageManagers: pageManagers ?? _pageManagers, pageManagers: pageManagers ?? _pageManagers,
); );
void dispose() {
for (final manager in pageManagers) {
manager.dispose();
}
}
} }

View File

@ -253,6 +253,10 @@ class PageManager {
), ),
); );
} }
void dispose() {
_notifier.dispose();
}
} }
class HomeTopBar extends StatelessWidget { class HomeTopBar extends StatelessWidget {

View File

@ -498,6 +498,7 @@ class _SingleInnerViewItemState extends State<SingleInnerViewItem> {
title: LocaleKeys.disclosureAction_rename.tr(), title: LocaleKeys.disclosureAction_rename.tr(),
autoSelectAllText: true, autoSelectAllText: true,
value: widget.view.name, value: widget.view.name,
maxLength: 256,
confirm: (newValue) { confirm: (newValue) {
context.read<ViewBloc>().add(ViewEvent.rename(newValue)); context.read<ViewBloc>().add(ViewEvent.rename(newValue));
}, },

View File

@ -25,6 +25,7 @@ class DefaultEmojiPickerViewState extends State<DefaultEmojiPickerView>
final FocusNode _emojiFocusNode = FocusNode(); final FocusNode _emojiFocusNode = FocusNode();
EmojiCategoryGroup searchEmojiList = EmojiCategoryGroup searchEmojiList =
EmojiCategoryGroup(EmojiCategory.SEARCH, <Emoji>[]); EmojiCategoryGroup(EmojiCategory.SEARCH, <Emoji>[]);
final scrollController = ScrollController();
@override @override
void initState() { void initState() {
@ -70,6 +71,7 @@ class DefaultEmojiPickerViewState extends State<DefaultEmojiPickerView>
_emojiFocusNode.dispose(); _emojiFocusNode.dispose();
_pageController?.dispose(); _pageController?.dispose();
_tabController?.dispose(); _tabController?.dispose();
scrollController.dispose();
super.dispose(); super.dispose();
} }
@ -224,8 +226,6 @@ class DefaultEmojiPickerViewState extends State<DefaultEmojiPickerView>
Widget _buildPage(double emojiSize, EmojiCategoryGroup emojiCategoryGroup) { Widget _buildPage(double emojiSize, EmojiCategoryGroup emojiCategoryGroup) {
// Display notice if recent has no entries yet // Display notice if recent has no entries yet
final scrollController = ScrollController();
if (emojiCategoryGroup.category == EmojiCategory.RECENT && if (emojiCategoryGroup.category == EmojiCategory.RECENT &&
emojiCategoryGroup.emoji.isEmpty) { emojiCategoryGroup.emoji.isEmpty) {
return _buildNoRecent(); return _buildNoRecent();

View File

@ -277,6 +277,12 @@ class CloudURLInputState extends State<CloudURLInput> {
_controller = TextEditingController(text: widget.url); _controller = TextEditingController(text: widget.url);
} }
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return TextField( return TextField(
@ -306,12 +312,6 @@ class CloudURLInputState extends State<CloudURLInput> {
onChanged: widget.onChanged, onChanged: widget.onChanged,
); );
} }
@override
void dispose() {
_controller.dispose();
super.dispose();
}
} }
class AppFlowyCloudEnableSync extends StatelessWidget { class AppFlowyCloudEnableSync extends StatelessWidget {

View File

@ -273,6 +273,12 @@ class SupabaseInputState extends State<SupabaseInput> {
_controller = TextEditingController(text: widget.url); _controller = TextEditingController(text: widget.url);
} }
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return TextField( return TextField(
@ -298,12 +304,6 @@ class SupabaseInputState extends State<SupabaseInput> {
onChanged: widget.onChanged, onChanged: widget.onChanged,
); );
} }
@override
void dispose() {
_controller.dispose();
super.dispose();
}
} }
class SupabaseSelfhostTip extends StatelessWidget { class SupabaseSelfhostTip extends StatelessWidget {

View File

@ -138,10 +138,10 @@ class ShortcutsListTile extends StatelessWidget {
} }
void showKeyListenerDialog(BuildContext widgetContext) { void showKeyListenerDialog(BuildContext widgetContext) {
final controller = TextEditingController(text: shortcutEvent.command);
showDialog( showDialog(
context: widgetContext, context: widgetContext,
builder: (builderContext) { builder: (builderContext) {
final controller = TextEditingController(text: shortcutEvent.command);
final formKey = GlobalKey<FormState>(); final formKey = GlobalKey<FormState>();
return AlertDialog( return AlertDialog(
title: Text(LocaleKeys.settings_shortcuts_updateShortcutStep.tr()), title: Text(LocaleKeys.settings_shortcuts_updateShortcutStep.tr()),
@ -184,7 +184,7 @@ class ShortcutsListTile extends StatelessWidget {
), ),
); );
}, },
); ).then((_) => controller.dispose());
} }
String? _validateForConflicts(BuildContext context, String command) { String? _validateForConflicts(BuildContext context, String command) {

View File

@ -246,6 +246,13 @@ class UserNameInputState extends State<UserNameInput> {
_controller = TextEditingController(text: widget.name); _controller = TextEditingController(text: widget.name);
} }
@override
void dispose() {
_controller.dispose();
_debounce?.cancel();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return TextField( return TextField(
@ -277,13 +284,6 @@ class UserNameInputState extends State<UserNameInput> {
}, },
); );
} }
@override
void dispose() {
_controller.dispose();
_debounce?.cancel();
super.dispose();
}
} }
@visibleForTesting @visibleForTesting

View File

@ -19,6 +19,7 @@ class NavigatorTextFieldDialog extends StatefulWidget {
required this.value, required this.value,
required this.confirm, required this.confirm,
this.cancel, this.cancel,
this.maxLength,
}); });
final String value; final String value;
@ -26,6 +27,7 @@ class NavigatorTextFieldDialog extends StatefulWidget {
final void Function()? cancel; final void Function()? cancel;
final void Function(String) confirm; final void Function(String) confirm;
final bool autoSelectAllText; final bool autoSelectAllText;
final int? maxLength;
@override @override
State<NavigatorTextFieldDialog> createState() => State<NavigatorTextFieldDialog> createState() =>
@ -38,6 +40,7 @@ class _NavigatorTextFieldDialogState extends State<NavigatorTextFieldDialog> {
@override @override
void initState() { void initState() {
super.initState();
newValue = widget.value; newValue = widget.value;
controller.text = newValue; controller.text = newValue;
if (widget.autoSelectAllText) { if (widget.autoSelectAllText) {
@ -46,7 +49,12 @@ class _NavigatorTextFieldDialogState extends State<NavigatorTextFieldDialog> {
extentOffset: newValue.length, extentOffset: newValue.length,
); );
} }
super.initState(); }
@override
void dispose() {
controller.dispose();
super.dispose();
} }
@override @override
@ -63,9 +71,12 @@ class _NavigatorTextFieldDialogState extends State<NavigatorTextFieldDialog> {
FlowyFormTextInput( FlowyFormTextInput(
hintText: LocaleKeys.dialogCreatePageNameHint.tr(), hintText: LocaleKeys.dialogCreatePageNameHint.tr(),
controller: controller, controller: controller,
textStyle: Theme.of(context).textTheme.bodySmall?.copyWith( textStyle: Theme.of(context)
fontSize: FontSizes.s16, .textTheme
), .bodySmall
?.copyWith(fontSize: FontSizes.s16),
maxLength: widget.maxLength,
showCounter: false,
autoFocus: true, autoFocus: true,
onChanged: (text) { onChanged: (text) {
newValue = text; newValue = text;

View File

@ -65,8 +65,10 @@ class _RenameViewPopoverState extends State<RenameViewPopover> {
width: 220, width: 220,
child: FlowyTextField( child: FlowyTextField(
controller: _controller, controller: _controller,
maxLength: 256,
onSubmitted: _updateViewName, onSubmitted: _updateViewName,
onCanceled: () => _updateViewName(_controller.text), onCanceled: () => _updateViewName(_controller.text),
showCounter: false,
), ),
), ),
], ],

View File

@ -48,9 +48,3 @@ class PublishNotifier<T> extends ChangeNotifier {
); );
} }
} }
class Notifier extends ChangeNotifier {
void notify() {
notifyListeners();
}
}

View File

@ -30,6 +30,12 @@ class _KeyboardScreenState extends State<KeyboardScreen> {
final TextEditingController _controller = final TextEditingController _controller =
TextEditingController(text: 'Hello Flowy'); TextEditingController(text: 'Hello Flowy');
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(

View File

@ -45,7 +45,9 @@ class StyledSingleChildScrollViewState
@override @override
void dispose() { void dispose() {
// scrollController.dispose(); if (widget.controller == null) {
scrollController.dispose();
}
super.dispose(); super.dispose();
} }

View File

@ -79,6 +79,7 @@ class FlowyTextFieldState extends State<FlowyTextField> {
focusNode.addListener(notifyDidEndEditing); focusNode.addListener(notifyDidEndEditing);
controller = widget.controller ?? TextEditingController(); controller = widget.controller ?? TextEditingController();
if (widget.text != null) { if (widget.text != null) {
controller.text = widget.text!; controller.text = widget.text!;
} }
@ -95,6 +96,19 @@ class FlowyTextFieldState extends State<FlowyTextField> {
} }
} }
@override
void dispose() {
focusNode.removeListener(notifyDidEndEditing);
if (widget.focusNode == null) {
focusNode.dispose();
}
if (widget.controller == null) {
controller.dispose();
}
_debounceOnChanged?.cancel();
super.dispose();
}
void _debounceOnChangedText(Duration duration, String text) { void _debounceOnChangedText(Duration duration, String text) {
_debounceOnChanged?.cancel(); _debounceOnChanged?.cancel();
_debounceOnChanged = Timer(duration, () async { _debounceOnChanged = Timer(duration, () async {
@ -200,15 +214,6 @@ class FlowyTextFieldState extends State<FlowyTextField> {
); );
} }
@override
void dispose() {
focusNode.removeListener(notifyDidEndEditing);
if (widget.focusNode == null) {
focusNode.dispose();
}
super.dispose();
}
void notifyDidEndEditing() { void notifyDidEndEditing() {
if (!focusNode.hasFocus) { if (!focusNode.hasFocus) {
if (controller.text.isNotEmpty && widget.submitOnLeave) { if (controller.text.isNotEmpty && widget.submitOnLeave) {
@ -222,8 +227,7 @@ class FlowyTextFieldState extends State<FlowyTextField> {
String? _suffixText() { String? _suffixText() {
if (widget.maxLength != null) { if (widget.maxLength != null) {
return ' ${controller.text.length}/${widget.maxLength}'; return ' ${controller.text.length}/${widget.maxLength}';
} else { }
return null; return null;
} }
} }
}

View File

@ -17,6 +17,8 @@ class FlowyFormTextInput extends StatelessWidget {
final TextStyle? textStyle; final TextStyle? textStyle;
final TextAlign textAlign; final TextAlign textAlign;
final int? maxLines; final int? maxLines;
final int? maxLength;
final bool showCounter;
final TextEditingController? controller; final TextEditingController? controller;
final TextCapitalization? capitalization; final TextCapitalization? capitalization;
final Function(String)? onChanged; final Function(String)? onChanged;
@ -24,8 +26,8 @@ class FlowyFormTextInput extends StatelessWidget {
final Function(bool)? onFocusChanged; final Function(bool)? onFocusChanged;
final Function(FocusNode)? onFocusCreated; final Function(FocusNode)? onFocusCreated;
const FlowyFormTextInput( const FlowyFormTextInput({
{Key? key, super.key,
this.label, this.label,
this.autoFocus, this.autoFocus,
this.initialValue, this.initialValue,
@ -39,8 +41,10 @@ class FlowyFormTextInput extends StatelessWidget {
this.capitalization, this.capitalization,
this.textStyle, this.textStyle,
this.textAlign = TextAlign.center, this.textAlign = TextAlign.center,
this.maxLines}) this.maxLines,
: super(key: key); this.maxLength,
this.showCounter = true,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -57,16 +61,17 @@ class FlowyFormTextInput extends StatelessWidget {
onFocusChanged: onFocusChanged, onFocusChanged: onFocusChanged,
controller: controller, controller: controller,
maxLines: maxLines, maxLines: maxLines,
inputDecoration: InputDecoration( maxLength: maxLength,
isDense: true, showCounter: showCounter,
contentPadding: contentPadding ?? kDefaultTextInputPadding, contentPadding: contentPadding ?? kDefaultTextInputPadding,
border: const ThinUnderlineBorder( hintText: hintText,
borderSide: BorderSide(width: 5, color: Colors.red)),
hintStyle: Theme.of(context) hintStyle: Theme.of(context)
.textTheme .textTheme
.bodyMedium! .bodyMedium!
.copyWith(color: Theme.of(context).hintColor.withOpacity(0.7)), .copyWith(color: Theme.of(context).hintColor.withOpacity(0.7)),
hintText: hintText, isDense: true,
inputBorder: const ThinUnderlineBorder(
borderSide: BorderSide(width: 5, color: Colors.red),
), ),
); );
} }
@ -82,6 +87,8 @@ class StyledSearchTextInput extends StatefulWidget {
final IconData? icon; final IconData? icon;
final String? initialValue; final String? initialValue;
final int? maxLines; final int? maxLines;
final int? maxLength;
final bool showCounter;
final TextEditingController? controller; final TextEditingController? controller;
final TextCapitalization? capitalization; final TextCapitalization? capitalization;
final TextInputType? type; final TextInputType? type;
@ -89,11 +96,14 @@ class StyledSearchTextInput extends StatefulWidget {
final bool? autoValidate; final bool? autoValidate;
final bool? enableSuggestions; final bool? enableSuggestions;
final bool? autoCorrect; final bool? autoCorrect;
final bool isDense;
final String? errorText; final String? errorText;
final String? hintText; final String? hintText;
final TextStyle? hintStyle;
final Widget? prefixIcon; final Widget? prefixIcon;
final Widget? suffixIcon; final Widget? suffixIcon;
final InputDecoration? inputDecoration; final InputDecoration? inputDecoration;
final InputBorder? inputBorder;
final Function(String)? onChanged; final Function(String)? onChanged;
final Function()? onEditingComplete; final Function()? onEditingComplete;
@ -105,7 +115,7 @@ class StyledSearchTextInput extends StatefulWidget {
final VoidCallback? onTap; final VoidCallback? onTap;
const StyledSearchTextInput({ const StyledSearchTextInput({
Key? key, super.key,
this.label, this.label,
this.autoFocus = false, this.autoFocus = false,
this.obscureText = false, this.obscureText = false,
@ -118,6 +128,7 @@ class StyledSearchTextInput extends StatefulWidget {
this.autoValidate = false, this.autoValidate = false,
this.enableSuggestions = true, this.enableSuggestions = true,
this.autoCorrect = true, this.autoCorrect = true,
this.isDense = false,
this.errorText, this.errorText,
this.style, this.style,
this.contentPadding, this.contentPadding,
@ -133,9 +144,13 @@ class StyledSearchTextInput extends StatefulWidget {
this.onSaved, this.onSaved,
this.onTap, this.onTap,
this.hintText, this.hintText,
this.hintStyle,
this.capitalization, this.capitalization,
this.maxLines, this.maxLines,
}) : super(key: key); this.maxLength,
this.showCounter = false,
this.inputBorder,
});
@override @override
StyledSearchTextInputState createState() => StyledSearchTextInputState(); StyledSearchTextInputState createState() => StyledSearchTextInputState();
@ -175,7 +190,9 @@ class StyledSearchTextInputState extends State<StyledSearchTextInput> {
@override @override
void dispose() { void dispose() {
if (widget.controller == null) {
_controller.dispose(); _controller.dispose();
}
_focusNode.dispose(); _focusNode.dispose();
super.dispose(); super.dispose();
} }
@ -208,20 +225,25 @@ class StyledSearchTextInputState extends State<StyledSearchTextInput> {
showCursor: true, showCursor: true,
enabled: widget.enabled, enabled: widget.enabled,
maxLines: widget.maxLines, maxLines: widget.maxLines,
maxLength: widget.maxLength,
textCapitalization: widget.capitalization ?? TextCapitalization.none, textCapitalization: widget.capitalization ?? TextCapitalization.none,
textAlign: widget.textAlign, textAlign: widget.textAlign,
decoration: widget.inputDecoration ?? decoration: widget.inputDecoration ??
InputDecoration( InputDecoration(
prefixIcon: widget.prefixIcon, prefixIcon: widget.prefixIcon,
suffixIcon: widget.suffixIcon, suffixIcon: widget.suffixIcon,
counterText: "",
suffixText: widget.showCounter ? _suffixText() : "",
contentPadding: widget.contentPadding ?? EdgeInsets.all(Insets.m), contentPadding: widget.contentPadding ?? EdgeInsets.all(Insets.m),
border: const OutlineInputBorder(borderSide: BorderSide.none), border: widget.inputBorder ??
isDense: true, const OutlineInputBorder(borderSide: BorderSide.none),
isDense: widget.isDense,
icon: widget.icon == null ? null : Icon(widget.icon), icon: widget.icon == null ? null : Icon(widget.icon),
errorText: widget.errorText, errorText: widget.errorText,
errorMaxLines: 2, errorMaxLines: 2,
hintText: widget.hintText, hintText: widget.hintText,
hintStyle: Theme.of(context) hintStyle: widget.hintStyle ??
Theme.of(context)
.textTheme .textTheme
.bodyMedium! .bodyMedium!
.copyWith(color: Theme.of(context).hintColor), .copyWith(color: Theme.of(context).hintColor),
@ -230,6 +252,13 @@ class StyledSearchTextInputState extends State<StyledSearchTextInput> {
), ),
); );
} }
String? _suffixText() {
if (widget.controller != null && widget.maxLength != null) {
return ' ${widget.controller!.text.length}/${widget.maxLength}';
}
return null;
}
} }
class ThinUnderlineBorder extends InputBorder { class ThinUnderlineBorder extends InputBorder {