diff --git a/lib/application/bloc.dart b/lib/application/bloc.dart index 8bb91e598..765738293 100644 --- a/lib/application/bloc.dart +++ b/lib/application/bloc.dart @@ -18,6 +18,7 @@ export 'main_tab/main_tab_bloc.dart'; export 'material/material_bloc.dart'; export 'materials/materials_bloc.dart'; export 'settings/settings_bloc.dart'; +export 'tier_list_form/tier_list_form_bloc.dart'; export 'tierlist/tier_list_bloc.dart'; export 'today_materials/today_materials_bloc.dart'; export 'url_page/url_page_bloc.dart'; diff --git a/lib/application/tier_list_form/tier_list_form_bloc.dart b/lib/application/tier_list_form/tier_list_form_bloc.dart new file mode 100644 index 000000000..53e09f15c --- /dev/null +++ b/lib/application/tier_list_form/tier_list_form_bloc.dart @@ -0,0 +1,33 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:genshindb/domain/extensions/string_extensions.dart'; +import 'package:meta/meta.dart'; + +part 'tier_list_form_bloc.freezed.dart'; +part 'tier_list_form_event.dart'; +part 'tier_list_form_state.dart'; + +const _initialState = TierListFormState.loaded(name: '', isNameDirty: false, isNameValid: false); + +class TierListFormBloc extends Bloc { + TierListFormBloc() : super(_initialState); + + static int nameMaxLength = 25; + + @override + Stream mapEventToState(TierListFormEvent event) async* { + final s = event.map( + nameChanged: (e) { + final isValid = e.name.isNotNullEmptyOrWhitespace && e.name.length <= nameMaxLength; + final isDirty = e.name != state.name; + + return state.copyWith.call(name: e.name, isNameDirty: isDirty, isNameValid: isValid); + }, + close: (_) => _initialState, + ); + + yield s; + } +} diff --git a/lib/application/tier_list_form/tier_list_form_event.dart b/lib/application/tier_list_form/tier_list_form_event.dart new file mode 100644 index 000000000..30bd22ef4 --- /dev/null +++ b/lib/application/tier_list_form/tier_list_form_event.dart @@ -0,0 +1,10 @@ +part of 'tier_list_form_bloc.dart'; + +@freezed +abstract class TierListFormEvent implements _$TierListFormEvent { + const factory TierListFormEvent.nameChanged({ + @required String name, + }) = _NameChanged; + + const factory TierListFormEvent.close() = _Close; +} diff --git a/lib/application/tier_list_form/tier_list_form_state.dart b/lib/application/tier_list_form/tier_list_form_state.dart new file mode 100644 index 000000000..b6ca94109 --- /dev/null +++ b/lib/application/tier_list_form/tier_list_form_state.dart @@ -0,0 +1,10 @@ +part of 'tier_list_form_bloc.dart'; + +@freezed +abstract class TierListFormState implements _$TierListFormState { + const factory TierListFormState.loaded({ + @required String name, + @required bool isNameDirty, + @required bool isNameValid, + }) = _LoadedState; +} diff --git a/lib/main.dart b/lib/main.dart index 47f81ca50..80f992dac 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -200,6 +200,7 @@ class MyApp extends StatelessWidget { return CalculatorAscMaterialsOrderBloc(dataService, ctx.read()); }, ), + BlocProvider(create: (_) => TierListFormBloc()), ], child: BlocBuilder( builder: (ctx, state) => AppWidget(), diff --git a/lib/presentation/tierlist/widgets/rename_tierlist_dialog.dart b/lib/presentation/tierlist/widgets/rename_tierlist_dialog.dart index fee86ed1c..bd829b6ac 100644 --- a/lib/presentation/tierlist/widgets/rename_tierlist_dialog.dart +++ b/lib/presentation/tierlist/widgets/rename_tierlist_dialog.dart @@ -19,11 +19,13 @@ class RenameTierListRowDialog extends StatefulWidget { class _RenameTierListRowDialogState extends State { TextEditingController _textController; + String _currentValue; @override void initState() { - final text = widget.title ?? ''; - _textController = TextEditingController(text: text); + _currentValue = widget.title; + _textController = TextEditingController(text: _currentValue); + _textController.addListener(_textChanged); super.initState(); } @@ -33,31 +35,31 @@ class _RenameTierListRowDialogState extends State { return AlertDialog( title: Text(s.rename), actions: [ - FlatButton( - onPressed: () { - Navigator.pop(context); - }, + OutlinedButton( + onPressed: () => Navigator.pop(context), child: Text(s.cancel), ), - FlatButton( - onPressed: () { - context.read().add(TierListEvent.rowTextChanged(index: widget.index, newValue: _textController.text)); - Navigator.pop(context); - }, - child: Text(s.ok), - ) + BlocBuilder( + builder: (ctx, state) => ElevatedButton( + onPressed: state.isNameValid ? _saveName : null, + child: Text(s.save), + ), + ), ], - content: TextFormField( - controller: _textController, - keyboardType: TextInputType.text, - minLines: 1, - maxLength: 40, - autofocus: true, - textInputAction: TextInputAction.next, - decoration: InputDecoration( - alignLabelWithHint: true, - labelText: s.name, - hintText: s.tierListBuilder, + content: BlocBuilder( + builder: (ctx, state) => TextFormField( + controller: _textController, + keyboardType: TextInputType.text, + minLines: 1, + maxLength: TierListFormBloc.nameMaxLength, + autofocus: true, + textInputAction: TextInputAction.next, + decoration: InputDecoration( + alignLabelWithHint: true, + labelText: s.name, + hintText: s.tierListBuilder, + errorText: !state.isNameValid && state.isNameDirty ? s.invalidValue : null, + ), ), ), ); @@ -65,7 +67,22 @@ class _RenameTierListRowDialogState extends State { @override void dispose() { + _textController.removeListener(_textChanged); _textController.dispose(); super.dispose(); } + + void _saveName() { + context.read().add(TierListEvent.rowTextChanged(index: widget.index, newValue: _textController.text)); + Navigator.pop(context); + } + + void _textChanged() { + //Focusing the text field triggers text changed, that why we used it like this + if (_currentValue == _textController.text) { + return; + } + _currentValue = _textController.text; + context.read().add(TierListFormEvent.nameChanged(name: _currentValue)); + } } diff --git a/lib/presentation/tierlist/widgets/tierlist_row.dart b/lib/presentation/tierlist/widgets/tierlist_row.dart index 03ddbcb05..b13b6517d 100644 --- a/lib/presentation/tierlist/widgets/tierlist_row.dart +++ b/lib/presentation/tierlist/widgets/tierlist_row.dart @@ -186,6 +186,7 @@ class TierListRow extends StatelessWidget { index: index, ), ); + context.read().add(const TierListFormEvent.close()); } Future _showColorPicker(BuildContext context) async {