diff --git a/lib/content_management/bloc/create_headline/create_headline_bloc.dart b/lib/content_management/bloc/create_headline/create_headline_bloc.dart index 91520e6..aec1ad5 100644 --- a/lib/content_management/bloc/create_headline/create_headline_bloc.dart +++ b/lib/content_management/bloc/create_headline/create_headline_bloc.dart @@ -31,6 +31,7 @@ class CreateHeadlineBloc on(_onCountryChanged); on(_onStatusChanged); on(_onSubmitted); + on(_onDataUpdated); } final DataRepository _headlinesRepository; @@ -180,4 +181,11 @@ class CreateHeadlineBloc ); } } + + void _onDataUpdated( + CreateHeadlineDataUpdated event, + Emitter emit, + ) { + emit(state.copyWith(countries: event.countries)); + } } diff --git a/lib/content_management/bloc/create_headline/create_headline_event.dart b/lib/content_management/bloc/create_headline/create_headline_event.dart index 655eae9..840d679 100644 --- a/lib/content_management/bloc/create_headline/create_headline_event.dart +++ b/lib/content_management/bloc/create_headline/create_headline_event.dart @@ -83,3 +83,13 @@ final class CreateHeadlineStatusChanged extends CreateHeadlineEvent { final class CreateHeadlineSubmitted extends CreateHeadlineEvent { const CreateHeadlineSubmitted(); } + +/// Event to update the BLoC with the latest shared data. +final class CreateHeadlineDataUpdated extends CreateHeadlineEvent { + const CreateHeadlineDataUpdated({required this.countries}); + + final List countries; + + @override + List get props => [countries]; +} diff --git a/lib/content_management/bloc/create_source/create_source_bloc.dart b/lib/content_management/bloc/create_source/create_source_bloc.dart index 0f99f7f..1af9167 100644 --- a/lib/content_management/bloc/create_source/create_source_bloc.dart +++ b/lib/content_management/bloc/create_source/create_source_bloc.dart @@ -30,6 +30,7 @@ class CreateSourceBloc extends Bloc { on(_onHeadquartersChanged); on(_onStatusChanged); on(_onSubmitted); + on(_onDataUpdated); } final DataRepository _sourcesRepository; @@ -130,4 +131,11 @@ class CreateSourceBloc extends Bloc { ); } } + + void _onDataUpdated( + CreateSourceDataUpdated event, + Emitter emit, + ) { + emit(state.copyWith(countries: event.countries, languages: event.languages)); + } } diff --git a/lib/content_management/bloc/create_source/create_source_event.dart b/lib/content_management/bloc/create_source/create_source_event.dart index c2351b0..be839c9 100644 --- a/lib/content_management/bloc/create_source/create_source_event.dart +++ b/lib/content_management/bloc/create_source/create_source_event.dart @@ -70,3 +70,17 @@ final class CreateSourceStatusChanged extends CreateSourceEvent { final class CreateSourceSubmitted extends CreateSourceEvent { const CreateSourceSubmitted(); } + +/// Event to update the BLoC with the latest shared data. +final class CreateSourceDataUpdated extends CreateSourceEvent { + const CreateSourceDataUpdated({ + required this.countries, + required this.languages, + }); + + final List countries; + final List languages; + + @override + List get props => [countries, languages]; +} diff --git a/lib/content_management/bloc/edit_headline/edit_headline_bloc.dart b/lib/content_management/bloc/edit_headline/edit_headline_bloc.dart index 6fc464f..3d6a887 100644 --- a/lib/content_management/bloc/edit_headline/edit_headline_bloc.dart +++ b/lib/content_management/bloc/edit_headline/edit_headline_bloc.dart @@ -31,6 +31,7 @@ class EditHeadlineBloc extends Bloc { on(_onCountryChanged); on(_onStatusChanged); on(_onSubmitted); + on(_onDataUpdated); } final DataRepository _headlinesRepository; @@ -228,4 +229,11 @@ class EditHeadlineBloc extends Bloc { ); } } + + void _onDataUpdated( + EditHeadlineDataUpdated event, + Emitter emit, + ) { + emit(state.copyWith(countries: event.countries)); + } } diff --git a/lib/content_management/bloc/edit_headline/edit_headline_event.dart b/lib/content_management/bloc/edit_headline/edit_headline_event.dart index 6bad7bd..50b9399 100644 --- a/lib/content_management/bloc/edit_headline/edit_headline_event.dart +++ b/lib/content_management/bloc/edit_headline/edit_headline_event.dart @@ -83,3 +83,13 @@ final class EditHeadlineStatusChanged extends EditHeadlineEvent { final class EditHeadlineSubmitted extends EditHeadlineEvent { const EditHeadlineSubmitted(); } + +/// Event to update the BLoC with the latest shared data. +final class EditHeadlineDataUpdated extends EditHeadlineEvent { + const EditHeadlineDataUpdated({required this.countries}); + + final List countries; + + @override + List get props => [countries]; +} diff --git a/lib/content_management/bloc/edit_source/edit_source_bloc.dart b/lib/content_management/bloc/edit_source/edit_source_bloc.dart index 3c5f78b..961e095 100644 --- a/lib/content_management/bloc/edit_source/edit_source_bloc.dart +++ b/lib/content_management/bloc/edit_source/edit_source_bloc.dart @@ -32,6 +32,7 @@ class EditSourceBloc extends Bloc { on(_onHeadquartersChanged); on(_onStatusChanged); on(_onSubmitted); + on(_onDataUpdated); } final DataRepository _sourcesRepository; @@ -195,4 +196,11 @@ class EditSourceBloc extends Bloc { ); } } + + void _onDataUpdated( + EditSourceDataUpdated event, + Emitter emit, + ) { + emit(state.copyWith(countries: event.countries, languages: event.languages)); + } } diff --git a/lib/content_management/bloc/edit_source/edit_source_event.dart b/lib/content_management/bloc/edit_source/edit_source_event.dart index 8620d9a..b9128c3 100644 --- a/lib/content_management/bloc/edit_source/edit_source_event.dart +++ b/lib/content_management/bloc/edit_source/edit_source_event.dart @@ -86,3 +86,17 @@ final class EditSourceStatusChanged extends EditSourceEvent { final class EditSourceSubmitted extends EditSourceEvent { const EditSourceSubmitted(); } + +/// Event to update the BLoC with the latest shared data. +final class EditSourceDataUpdated extends EditSourceEvent { + const EditSourceDataUpdated({ + required this.countries, + required this.languages, + }); + + final List countries; + final List languages; + + @override + List get props => [countries, languages]; +} diff --git a/lib/content_management/view/create_headline_page.dart b/lib/content_management/view/create_headline_page.dart index 1dcbcd6..e87e17c 100644 --- a/lib/content_management/view/create_headline_page.dart +++ b/lib/content_management/view/create_headline_page.dart @@ -19,18 +19,12 @@ class CreateHeadlinePage extends StatelessWidget { @override Widget build(BuildContext context) { - // The list of all countries is fetched once and cached in the - // ContentManagementBloc. We read it here and provide it to the - // CreateHeadlineBloc. - final contentManagementState = context.watch().state; - final allCountries = contentManagementState.allCountries; - return BlocProvider( create: (context) => CreateHeadlineBloc( headlinesRepository: context.read>(), sourcesRepository: context.read>(), topicsRepository: context.read>(), - countries: allCountries, + countries: context.read().state.allCountries, )..add(const CreateHeadlineDataLoaded()), child: const _CreateHeadlineView(), ); @@ -79,221 +73,236 @@ class _CreateHeadlineViewState extends State<_CreateHeadlineView> { ), ], ), - body: BlocConsumer( - listenWhen: (previous, current) => previous.status != current.status, - listener: (context, state) { - if (state.status == CreateHeadlineStatus.success && - state.createdHeadline != null && - ModalRoute.of(context)!.isCurrent) { - ScaffoldMessenger.of(context) - ..hideCurrentSnackBar() - ..showSnackBar( - SnackBar(content: Text(l10n.headlineCreatedSuccessfully)), + body: BlocListener( + listenWhen: (previous, current) => + previous.allCountriesStatus != current.allCountriesStatus && + current.allCountriesStatus == ContentManagementStatus.success, + listener: (context, contentState) { + context.read().add( + CreateHeadlineDataUpdated(countries: contentState.allCountries), + ); + }, + child: BlocConsumer( + listenWhen: (previous, current) => previous.status != current.status, + listener: (context, state) { + if (state.status == CreateHeadlineStatus.success && + state.createdHeadline != null && + ModalRoute.of(context)!.isCurrent) { + ScaffoldMessenger.of(context) + ..hideCurrentSnackBar() + ..showSnackBar( + SnackBar(content: Text(l10n.headlineCreatedSuccessfully)), + ); + context.read().add( + // Refresh the list to show the new headline + const LoadHeadlinesRequested(limit: kDefaultRowsPerPage), ); - context.read().add( - // Refresh the list to show the new headline - const LoadHeadlinesRequested(limit: kDefaultRowsPerPage), - ); - context.pop(); - } - if (state.status == CreateHeadlineStatus.failure) { - ScaffoldMessenger.of(context) - ..hideCurrentSnackBar() - ..showSnackBar( - SnackBar( - content: Text(state.exception!.toFriendlyMessage(context)), - backgroundColor: Theme.of(context).colorScheme.error, - ), + context.pop(); + } + if (state.status == CreateHeadlineStatus.failure) { + ScaffoldMessenger.of(context) + ..hideCurrentSnackBar() + ..showSnackBar( + SnackBar( + content: Text(state.exception!.toFriendlyMessage(context)), + backgroundColor: Theme.of(context).colorScheme.error, + ), + ); + } + }, + builder: (context, state) { + if (state.status == CreateHeadlineStatus.loading) { + return LoadingStateWidget( + icon: Icons.newspaper, + headline: l10n.loadingData, + subheadline: l10n.pleaseWait, ); - } - }, - builder: (context, state) { - if (state.status == CreateHeadlineStatus.loading) { - return LoadingStateWidget( - icon: Icons.newspaper, - headline: l10n.loadingData, - subheadline: l10n.pleaseWait, - ); - } + } - if (state.status == CreateHeadlineStatus.failure && - state.sources.isEmpty && - state.topics.isEmpty) { - return FailureStateWidget( - exception: state.exception!, - onRetry: () => context.read().add( - const CreateHeadlineDataLoaded(), - ), - ); - } + if (state.status == CreateHeadlineStatus.failure && + state.sources.isEmpty && + state.topics.isEmpty) { + return FailureStateWidget( + exception: state.exception!, + onRetry: () => context.read().add( + const CreateHeadlineDataLoaded(), + ), + ); + } - return SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(AppSpacing.lg), - child: Form( - key: _formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - TextFormField( - initialValue: state.title, - decoration: InputDecoration( - labelText: l10n.headlineTitle, - border: const OutlineInputBorder(), - ), - onChanged: (value) => context - .read() - .add(CreateHeadlineTitleChanged(value)), - ), - const SizedBox(height: AppSpacing.lg), - TextFormField( - initialValue: state.excerpt, - decoration: InputDecoration( - labelText: l10n.excerpt, - border: const OutlineInputBorder(), - ), - maxLines: 3, - onChanged: (value) => context - .read() - .add(CreateHeadlineExcerptChanged(value)), - ), - const SizedBox(height: AppSpacing.lg), - TextFormField( - initialValue: state.url, - decoration: InputDecoration( - labelText: l10n.sourceUrl, - border: const OutlineInputBorder(), + return SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(AppSpacing.lg), + child: Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextFormField( + initialValue: state.title, + decoration: InputDecoration( + labelText: l10n.headlineTitle, + border: const OutlineInputBorder(), + ), + onChanged: (value) => context + .read() + .add(CreateHeadlineTitleChanged(value)), ), - onChanged: (value) => context - .read() - .add(CreateHeadlineUrlChanged(value)), - ), - const SizedBox(height: AppSpacing.lg), - TextFormField( - initialValue: state.imageUrl, - decoration: InputDecoration( - labelText: l10n.imageUrl, - border: const OutlineInputBorder(), + const SizedBox(height: AppSpacing.lg), + TextFormField( + initialValue: state.excerpt, + decoration: InputDecoration( + labelText: l10n.excerpt, + border: const OutlineInputBorder(), + ), + maxLines: 3, + onChanged: (value) => context + .read() + .add(CreateHeadlineExcerptChanged(value)), ), - onChanged: (value) => context - .read() - .add(CreateHeadlineImageUrlChanged(value)), - ), - const SizedBox(height: AppSpacing.lg), - DropdownButtonFormField( - value: state.source, - decoration: InputDecoration( - labelText: l10n.sourceName, - border: const OutlineInputBorder(), + const SizedBox(height: AppSpacing.lg), + TextFormField( + initialValue: state.url, + decoration: InputDecoration( + labelText: l10n.sourceUrl, + border: const OutlineInputBorder(), + ), + onChanged: (value) => context + .read() + .add(CreateHeadlineUrlChanged(value)), ), - items: [ - DropdownMenuItem(value: null, child: Text(l10n.none)), - ...state.sources.map( - (source) => DropdownMenuItem( - value: source, - child: Text(source.name), - ), + const SizedBox(height: AppSpacing.lg), + TextFormField( + initialValue: state.imageUrl, + decoration: InputDecoration( + labelText: l10n.imageUrl, + border: const OutlineInputBorder(), ), - ], - onChanged: (value) => context - .read() - .add(CreateHeadlineSourceChanged(value)), - ), - const SizedBox(height: AppSpacing.lg), - DropdownButtonFormField( - value: state.topic, - decoration: InputDecoration( - labelText: l10n.topicName, - border: const OutlineInputBorder(), + onChanged: (value) => context + .read() + .add(CreateHeadlineImageUrlChanged(value)), ), - items: [ - DropdownMenuItem(value: null, child: Text(l10n.none)), - ...state.topics.map( - (topic) => DropdownMenuItem( - value: topic, - child: Text(topic.name), + const SizedBox(height: AppSpacing.lg), + DropdownButtonFormField( + value: state.source, + decoration: InputDecoration( + labelText: l10n.sourceName, + border: const OutlineInputBorder(), + ), + items: [ + DropdownMenuItem(value: null, child: Text(l10n.none)), + ...state.sources.map( + (source) => DropdownMenuItem( + value: source, + child: Text(source.name), + ), ), + ], + onChanged: (value) => context + .read() + .add(CreateHeadlineSourceChanged(value)), + ), + const SizedBox(height: AppSpacing.lg), + DropdownButtonFormField( + value: state.topic, + decoration: InputDecoration( + labelText: l10n.topicName, + border: const OutlineInputBorder(), ), - ], - onChanged: (value) => context - .read() - .add(CreateHeadlineTopicChanged(value)), - ), - const SizedBox(height: AppSpacing.lg), - BlocBuilder( - builder: (context, contentState) { - final isLoading = contentState.allCountriesStatus == - ContentManagementStatus.loading; - return DropdownButtonFormField( - value: state.eventCountry, - decoration: InputDecoration( - labelText: l10n.countryName, - border: const OutlineInputBorder(), - helperText: - isLoading ? l10n.loadingFullList : null, + items: [ + DropdownMenuItem(value: null, child: Text(l10n.none)), + ...state.topics.map( + (topic) => DropdownMenuItem( + value: topic, + child: Text(topic.name), + ), ), - items: [ - DropdownMenuItem( - value: null, - child: Text(l10n.none), + ], + onChanged: (value) => context + .read() + .add(CreateHeadlineTopicChanged(value)), + ), + const SizedBox(height: AppSpacing.lg), + BlocBuilder< + ContentManagementBloc, + ContentManagementState + >( + builder: (context, contentState) { + final isLoading = + contentState.allCountriesStatus == + ContentManagementStatus.loading; + return DropdownButtonFormField( + value: state.eventCountry, + decoration: InputDecoration( + labelText: l10n.countryName, + border: const OutlineInputBorder(), + helperText: isLoading + ? l10n.loadingFullList + : null, ), - ...state.countries.map( - (country) => DropdownMenuItem( - value: country, - child: Row( - children: [ - SizedBox( - width: 32, - height: 20, - child: Image.network( - country.flagUrl, - fit: BoxFit.cover, - errorBuilder: - (context, error, stackTrace) => - const Icon(Icons.flag), + items: [ + DropdownMenuItem( + value: null, + child: Text(l10n.none), + ), + ...state.countries.map( + (country) => DropdownMenuItem( + value: country, + child: Row( + children: [ + SizedBox( + width: 32, + height: 20, + child: Image.network( + country.flagUrl, + fit: BoxFit.cover, + errorBuilder: + (context, error, stackTrace) => + const Icon(Icons.flag), + ), ), - ), - const SizedBox(width: AppSpacing.md), - Text(country.name), - ], + const SizedBox(width: AppSpacing.md), + Text(country.name), + ], + ), ), ), - ), - ], - onChanged: isLoading - ? null - : (value) => context - .read() - .add(CreateHeadlineCountryChanged(value)), - ); - }, - ), - const SizedBox(height: AppSpacing.lg), - DropdownButtonFormField( - value: state.contentStatus, - decoration: InputDecoration( - labelText: l10n.status, - border: const OutlineInputBorder(), + ], + onChanged: isLoading + ? null + : (value) => context + .read() + .add(CreateHeadlineCountryChanged(value)), + ); + }, + ), + const SizedBox(height: AppSpacing.lg), + DropdownButtonFormField( + value: state.contentStatus, + decoration: InputDecoration( + labelText: l10n.status, + border: const OutlineInputBorder(), + ), + items: ContentStatus.values.map((status) { + return DropdownMenuItem( + value: status, + child: Text(status.l10n(context)), + ); + }).toList(), + onChanged: (value) { + if (value == null) return; + context.read().add( + CreateHeadlineStatusChanged(value), + ); + }, ), - items: ContentStatus.values.map((status) { - return DropdownMenuItem( - value: status, - child: Text(status.l10n(context)), - ); - }).toList(), - onChanged: (value) { - if (value == null) return; - context.read().add( - CreateHeadlineStatusChanged(value), - ); - }, - ), - ], + ], + ), ), ), - ), - ); - }, + ); + }, + ), ), ); } diff --git a/lib/content_management/view/create_source_page.dart b/lib/content_management/view/create_source_page.dart index 2544750..4cfab52 100644 --- a/lib/content_management/view/create_source_page.dart +++ b/lib/content_management/view/create_source_page.dart @@ -5,7 +5,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/content_management/bloc/content_management_bloc.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/content_management/bloc/create_source/create_source_bloc.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart'; -import 'package:flutter_news_app_web_dashboard_full_source_code/shared/extensions/source_type_l10n.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/shared/shared.dart'; import 'package:go_router/go_router.dart'; import 'package:ui_kit/ui_kit.dart'; @@ -20,18 +19,11 @@ class CreateSourcePage extends StatelessWidget { @override Widget build(BuildContext context) { - // The lists of all countries and languages are fetched once and cached in - // the ContentManagementBloc. We read them here and provide them to the - // CreateSourceBloc. - final contentManagementState = context.read().state; - final allCountries = contentManagementState.allCountries; - final allLanguages = contentManagementState.allLanguages; - return BlocProvider( create: (context) => CreateSourceBloc( sourcesRepository: context.read>(), - countries: allCountries, - languages: allLanguages, + countries: context.read().state.allCountries, + languages: context.read().state.allLanguages, ), child: const _CreateSourceView(), ); @@ -80,216 +72,248 @@ class _CreateSourceViewState extends State<_CreateSourceView> { ), ], ), - body: BlocConsumer( - listenWhen: (previous, current) => previous.status != current.status, - listener: (context, state) { - if (state.status == CreateSourceStatus.success && - state.createdSource != null && - ModalRoute.of(context)!.isCurrent) { - ScaffoldMessenger.of(context) - ..hideCurrentSnackBar() - ..showSnackBar( - SnackBar(content: Text(l10n.sourceCreatedSuccessfully)), + body: BlocListener( + listenWhen: (previous, current) => + (previous.allCountriesStatus != current.allCountriesStatus && + current.allCountriesStatus == + ContentManagementStatus.success) || + (previous.allLanguagesStatus != current.allLanguagesStatus && + current.allLanguagesStatus == ContentManagementStatus.success), + listener: (context, contentState) { + context.read().add( + CreateSourceDataUpdated( + countries: contentState.allCountries, + languages: contentState.allLanguages, + ), + ); + }, + child: BlocConsumer( + listenWhen: (previous, current) => previous.status != current.status, + listener: (context, state) { + if (state.status == CreateSourceStatus.success && + state.createdSource != null && + ModalRoute.of(context)!.isCurrent) { + ScaffoldMessenger.of(context) + ..hideCurrentSnackBar() + ..showSnackBar( + SnackBar(content: Text(l10n.sourceCreatedSuccessfully)), + ); + context.read().add( + // Refresh the list to show the new source + const LoadSourcesRequested(limit: kDefaultRowsPerPage), ); - context.read().add( - // Refresh the list to show the new source - const LoadSourcesRequested(limit: kDefaultRowsPerPage), - ); - context.pop(); - } - if (state.status == CreateSourceStatus.failure) { - ScaffoldMessenger.of(context) - ..hideCurrentSnackBar() - ..showSnackBar( - SnackBar( - content: Text(state.exception!.toFriendlyMessage(context)), - backgroundColor: Theme.of(context).colorScheme.error, - ), + context.pop(); + } + if (state.status == CreateSourceStatus.failure) { + ScaffoldMessenger.of(context) + ..hideCurrentSnackBar() + ..showSnackBar( + SnackBar( + content: Text(state.exception!.toFriendlyMessage(context)), + backgroundColor: Theme.of(context).colorScheme.error, + ), + ); + } + }, + builder: (context, state) { + if (state.status == CreateSourceStatus.loading) { + return LoadingStateWidget( + icon: Icons.source, + headline: l10n.loadingData, + subheadline: l10n.pleaseWait, ); - } - }, - builder: (context, state) { - if (state.status == CreateSourceStatus.loading) { - return LoadingStateWidget( - icon: Icons.source, - headline: l10n.loadingData, - subheadline: l10n.pleaseWait, - ); - } + } - if (state.status == CreateSourceStatus.failure) { - return FailureStateWidget( - exception: state.exception!, - onRetry: () => context.read().add( - const SharedDataRequested(), - ), - ); - } + if (state.status == CreateSourceStatus.failure) { + return FailureStateWidget( + exception: state.exception!, + onRetry: () => context.read().add( + const SharedDataRequested(), + ), + ); + } - return SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(AppSpacing.lg), - child: Form( - key: _formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - TextFormField( - initialValue: state.name, - decoration: InputDecoration( - labelText: l10n.sourceName, - border: const OutlineInputBorder(), + return SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(AppSpacing.lg), + child: Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextFormField( + initialValue: state.name, + decoration: InputDecoration( + labelText: l10n.sourceName, + border: const OutlineInputBorder(), + ), + onChanged: (value) => context + .read() + .add(CreateSourceNameChanged(value)), ), - onChanged: (value) => context - .read() - .add(CreateSourceNameChanged(value)), - ), - const SizedBox(height: AppSpacing.lg), - TextFormField( - initialValue: state.description, - decoration: InputDecoration( - labelText: l10n.description, - border: const OutlineInputBorder(), + const SizedBox(height: AppSpacing.lg), + TextFormField( + initialValue: state.description, + decoration: InputDecoration( + labelText: l10n.description, + border: const OutlineInputBorder(), + ), + maxLines: 3, + onChanged: (value) => context + .read() + .add(CreateSourceDescriptionChanged(value)), ), - maxLines: 3, - onChanged: (value) => context - .read() - .add(CreateSourceDescriptionChanged(value)), - ), - const SizedBox(height: AppSpacing.lg), - TextFormField( - initialValue: state.url, - decoration: InputDecoration( - labelText: l10n.sourceUrl, - border: const OutlineInputBorder(), + const SizedBox(height: AppSpacing.lg), + TextFormField( + initialValue: state.url, + decoration: InputDecoration( + labelText: l10n.sourceUrl, + border: const OutlineInputBorder(), + ), + onChanged: (value) => context + .read() + .add(CreateSourceUrlChanged(value)), ), - onChanged: (value) => context - .read() - .add(CreateSourceUrlChanged(value)), - ), - const SizedBox(height: AppSpacing.lg), - BlocBuilder( - builder: (context, contentState) { - final isLoading = contentState.allLanguagesStatus == - ContentManagementStatus.loading; - return DropdownButtonFormField( - value: state.language, - decoration: InputDecoration( - labelText: l10n.language, - border: const OutlineInputBorder(), - helperText: - isLoading ? l10n.loadingFullList : null, - ), - items: [ - DropdownMenuItem( - value: null, child: Text(l10n.none)), - ...state.languages.map( - (language) => DropdownMenuItem( - value: language, - child: Text(language.name), - ), + const SizedBox(height: AppSpacing.lg), + BlocBuilder< + ContentManagementBloc, + ContentManagementState + >( + builder: (context, contentState) { + final isLoading = + contentState.allLanguagesStatus == + ContentManagementStatus.loading; + return DropdownButtonFormField( + value: state.language, + decoration: InputDecoration( + labelText: l10n.language, + border: const OutlineInputBorder(), + helperText: isLoading + ? l10n.loadingFullList + : null, ), - ], - onChanged: isLoading - ? null - : (value) => context - .read() - .add(CreateSourceLanguageChanged(value)), - ); - }, - ), - const SizedBox(height: AppSpacing.lg), - DropdownButtonFormField( - value: state.sourceType, - decoration: InputDecoration( - labelText: l10n.sourceType, - border: const OutlineInputBorder(), + items: [ + DropdownMenuItem( + value: null, + child: Text(l10n.none), + ), + ...state.languages.map( + (language) => DropdownMenuItem( + value: language, + child: Text(language.name), + ), + ), + ], + onChanged: isLoading + ? null + : (value) => context + .read() + .add(CreateSourceLanguageChanged(value)), + ); + }, ), - items: [ - DropdownMenuItem(value: null, child: Text(l10n.none)), - ...SourceType.values.map( - (type) => DropdownMenuItem( - value: type, - child: Text(type.localizedName(l10n)), - ), + const SizedBox(height: AppSpacing.lg), + DropdownButtonFormField( + value: state.sourceType, + decoration: InputDecoration( + labelText: l10n.sourceType, + border: const OutlineInputBorder(), ), - ], - onChanged: (value) => context - .read() - .add(CreateSourceTypeChanged(value)), - ), - const SizedBox(height: AppSpacing.lg), - BlocBuilder( - builder: (context, contentState) { - final isLoading = contentState.allCountriesStatus == - ContentManagementStatus.loading; - return DropdownButtonFormField( - value: state.headquarters, - decoration: InputDecoration( - labelText: l10n.headquarters, - border: const OutlineInputBorder(), - helperText: - isLoading ? l10n.loadingFullList : null, + items: [ + DropdownMenuItem(value: null, child: Text(l10n.none)), + ...SourceType.values.map( + (type) => DropdownMenuItem( + value: type, + child: Text(type.localizedName(l10n)), + ), ), - items: [ - DropdownMenuItem( - value: null, child: Text(l10n.none)), - ...state.countries.map( - (country) => DropdownMenuItem( - value: country, - child: Row( - children: [ - SizedBox( - width: 32, - height: 20, - child: Image.network( - country.flagUrl, - fit: BoxFit.cover, - errorBuilder: - (context, error, stackTrace) => - const Icon(Icons.flag), + ], + onChanged: (value) => context + .read() + .add(CreateSourceTypeChanged(value)), + ), + const SizedBox(height: AppSpacing.lg), + BlocBuilder< + ContentManagementBloc, + ContentManagementState + >( + builder: (context, contentState) { + final isLoading = + contentState.allCountriesStatus == + ContentManagementStatus.loading; + return DropdownButtonFormField( + value: state.headquarters, + decoration: InputDecoration( + labelText: l10n.headquarters, + border: const OutlineInputBorder(), + helperText: isLoading + ? l10n.loadingFullList + : null, + ), + items: [ + DropdownMenuItem( + value: null, + child: Text(l10n.none), + ), + ...state.countries.map( + (country) => DropdownMenuItem( + value: country, + child: Row( + children: [ + SizedBox( + width: 32, + height: 20, + child: Image.network( + country.flagUrl, + fit: BoxFit.cover, + errorBuilder: + (context, error, stackTrace) => + const Icon(Icons.flag), + ), ), - ), - const SizedBox(width: AppSpacing.md), - Text(country.name), - ], + const SizedBox(width: AppSpacing.md), + Text(country.name), + ], + ), ), ), - ), - ], - onChanged: isLoading - ? null - : (value) => context.read().add( - CreateSourceHeadquartersChanged(value)), - ); - }, - ), - const SizedBox(height: AppSpacing.lg), - DropdownButtonFormField( - value: state.contentStatus, - decoration: InputDecoration( - labelText: l10n.status, - border: const OutlineInputBorder(), + ], + onChanged: isLoading + ? null + : (value) => + context.read().add( + CreateSourceHeadquartersChanged(value), + ), + ); + }, ), - items: ContentStatus.values.map((status) { - return DropdownMenuItem( - value: status, - child: Text(status.l10n(context)), - ); - }).toList(), - onChanged: (value) { - if (value == null) return; - context.read().add( - CreateSourceStatusChanged(value), - ); - }, - ), - ], + const SizedBox(height: AppSpacing.lg), + DropdownButtonFormField( + value: state.contentStatus, + decoration: InputDecoration( + labelText: l10n.status, + border: const OutlineInputBorder(), + ), + items: ContentStatus.values.map((status) { + return DropdownMenuItem( + value: status, + child: Text(status.l10n(context)), + ); + }).toList(), + onChanged: (value) { + if (value == null) return; + context.read().add( + CreateSourceStatusChanged(value), + ); + }, + ), + ], + ), ), ), - ), - ); - }, + ); + }, + ), ), ); } diff --git a/lib/content_management/view/edit_headline_page.dart b/lib/content_management/view/edit_headline_page.dart index 5c9d10f..558bc0d 100644 --- a/lib/content_management/view/edit_headline_page.dart +++ b/lib/content_management/view/edit_headline_page.dart @@ -22,18 +22,12 @@ class EditHeadlinePage extends StatelessWidget { @override Widget build(BuildContext context) { - // The list of all countries is fetched once and cached in the - // ContentManagementBloc. We read it here and provide it to the - // EditHeadlineBloc. - final contentManagementState = context.watch().state; - final allCountries = contentManagementState.allCountries; - return BlocProvider( create: (context) => EditHeadlineBloc( headlinesRepository: context.read>(), sourcesRepository: context.read>(), topicsRepository: context.read>(), - countries: allCountries, + countries: context.read().state.allCountries, headlineId: headlineId, )..add(const EditHeadlineLoaded()), child: const _EditHeadlineView(), @@ -106,262 +100,277 @@ class _EditHeadlineViewState extends State<_EditHeadlineView> { ), ], ), - body: BlocConsumer( + body: BlocListener( listenWhen: (previous, current) => - previous.status != current.status || - previous.initialHeadline != current.initialHeadline, - listener: (context, state) { - if (state.status == EditHeadlineStatus.success && - state.updatedHeadline != null && - ModalRoute.of(context)!.isCurrent) { - ScaffoldMessenger.of(context) - ..hideCurrentSnackBar() - ..showSnackBar( - SnackBar(content: Text(l10n.headlineUpdatedSuccessfully)), + previous.allCountriesStatus != current.allCountriesStatus && + current.allCountriesStatus == ContentManagementStatus.success, + listener: (context, contentState) { + context.read().add( + EditHeadlineDataUpdated(countries: contentState.allCountries), + ); + }, + child: BlocConsumer( + listenWhen: (previous, current) => + previous.status != current.status || + previous.initialHeadline != current.initialHeadline, + listener: (context, state) { + if (state.status == EditHeadlineStatus.success && + state.updatedHeadline != null && + ModalRoute.of(context)!.isCurrent) { + ScaffoldMessenger.of(context) + ..hideCurrentSnackBar() + ..showSnackBar( + SnackBar(content: Text(l10n.headlineUpdatedSuccessfully)), + ); + context.read().add( + const LoadHeadlinesRequested(limit: kDefaultRowsPerPage), ); - context.read().add( - const LoadHeadlinesRequested(limit: kDefaultRowsPerPage), - ); - context.pop(); - } - if (state.status == EditHeadlineStatus.failure) { - ScaffoldMessenger.of(context) - ..hideCurrentSnackBar() - ..showSnackBar( - SnackBar( - content: Text(state.exception!.toFriendlyMessage(context)), - backgroundColor: Theme.of(context).colorScheme.error, - ), + context.pop(); + } + if (state.status == EditHeadlineStatus.failure) { + ScaffoldMessenger.of(context) + ..hideCurrentSnackBar() + ..showSnackBar( + SnackBar( + content: Text(state.exception!.toFriendlyMessage(context)), + backgroundColor: Theme.of(context).colorScheme.error, + ), + ); + } + if (state.initialHeadline != null) { + _titleController.text = state.title; + _excerptController.text = state.excerpt; + _urlController.text = state.url; + _imageUrlController.text = state.imageUrl; + } + }, + builder: (context, state) { + if (state.status == EditHeadlineStatus.loading) { + return LoadingStateWidget( + icon: Icons.newspaper, + headline: l10n.loadingHeadline, + subheadline: l10n.pleaseWait, ); - } - if (state.initialHeadline != null) { - _titleController.text = state.title; - _excerptController.text = state.excerpt; - _urlController.text = state.url; - _imageUrlController.text = state.imageUrl; - } - }, - builder: (context, state) { - if (state.status == EditHeadlineStatus.loading) { - return LoadingStateWidget( - icon: Icons.newspaper, - headline: l10n.loadingHeadline, - subheadline: l10n.pleaseWait, - ); - } - - if (state.status == EditHeadlineStatus.failure && - state.initialHeadline == null) { - return FailureStateWidget( - exception: state.exception!, - onRetry: () => context.read().add( - const EditHeadlineLoaded(), - ), - ); - } + } - // Find the correct instances from the lists to ensure - // the Dropdowns can display the selections correctly. - Source? selectedSource; - if (state.source != null) { - try { - selectedSource = state.sources.firstWhere( - (s) => s.id == state.source!.id, + if (state.status == EditHeadlineStatus.failure && + state.initialHeadline == null) { + return FailureStateWidget( + exception: state.exception!, + onRetry: () => context.read().add( + const EditHeadlineLoaded(), + ), ); - } catch (_) { - selectedSource = null; } - } - Topic? selectedTopic; - if (state.topic != null) { - try { - selectedTopic = state.topics.firstWhere( - (t) => t.id == state.topic!.id, - ); - } catch (_) { - selectedTopic = null; + // Find the correct instances from the lists to ensure + // the Dropdowns can display the selections correctly. + Source? selectedSource; + if (state.source != null) { + try { + selectedSource = state.sources.firstWhere( + (s) => s.id == state.source!.id, + ); + } catch (_) { + selectedSource = null; + } } - } - Country? selectedCountry; - if (state.eventCountry != null) { - try { - selectedCountry = state.countries.firstWhere( - (c) => c.id == state.eventCountry!.id, - ); - } catch (_) { - selectedCountry = null; + Topic? selectedTopic; + if (state.topic != null) { + try { + selectedTopic = state.topics.firstWhere( + (t) => t.id == state.topic!.id, + ); + } catch (_) { + selectedTopic = null; + } } - } - return SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(AppSpacing.lg), - child: Form( - key: _formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - TextFormField( - controller: _titleController, - decoration: InputDecoration( - labelText: l10n.headlineTitle, - border: const OutlineInputBorder(), - ), - onChanged: (value) => context - .read() - .add(EditHeadlineTitleChanged(value)), - ), - const SizedBox(height: AppSpacing.lg), - TextFormField( - controller: _excerptController, - decoration: InputDecoration( - labelText: l10n.excerpt, - border: const OutlineInputBorder(), - ), - maxLines: 3, - onChanged: (value) => context - .read() - .add(EditHeadlineExcerptChanged(value)), - ), - const SizedBox(height: AppSpacing.lg), - TextFormField( - controller: _urlController, - decoration: InputDecoration( - labelText: l10n.sourceUrl, - border: const OutlineInputBorder(), + Country? selectedCountry; + if (state.eventCountry != null) { + try { + selectedCountry = state.countries.firstWhere( + (c) => c.id == state.eventCountry!.id, + ); + } catch (_) { + selectedCountry = null; + } + } + + return SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(AppSpacing.lg), + child: Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextFormField( + controller: _titleController, + decoration: InputDecoration( + labelText: l10n.headlineTitle, + border: const OutlineInputBorder(), + ), + onChanged: (value) => context + .read() + .add(EditHeadlineTitleChanged(value)), ), - onChanged: (value) => context - .read() - .add(EditHeadlineUrlChanged(value)), - ), - const SizedBox(height: AppSpacing.lg), - TextFormField( - controller: _imageUrlController, - decoration: InputDecoration( - labelText: l10n.imageUrl, - border: const OutlineInputBorder(), + const SizedBox(height: AppSpacing.lg), + TextFormField( + controller: _excerptController, + decoration: InputDecoration( + labelText: l10n.excerpt, + border: const OutlineInputBorder(), + ), + maxLines: 3, + onChanged: (value) => context + .read() + .add(EditHeadlineExcerptChanged(value)), ), - onChanged: (value) => context - .read() - .add(EditHeadlineImageUrlChanged(value)), - ), - const SizedBox(height: AppSpacing.lg), - DropdownButtonFormField( - value: selectedSource, - decoration: InputDecoration( - labelText: l10n.sourceName, - border: const OutlineInputBorder(), + const SizedBox(height: AppSpacing.lg), + TextFormField( + controller: _urlController, + decoration: InputDecoration( + labelText: l10n.sourceUrl, + border: const OutlineInputBorder(), + ), + onChanged: (value) => context + .read() + .add(EditHeadlineUrlChanged(value)), ), - items: [ - DropdownMenuItem(value: null, child: Text(l10n.none)), - ...state.sources.map( - (source) => DropdownMenuItem( - value: source, - child: Text(source.name), - ), + const SizedBox(height: AppSpacing.lg), + TextFormField( + controller: _imageUrlController, + decoration: InputDecoration( + labelText: l10n.imageUrl, + border: const OutlineInputBorder(), ), - ], - onChanged: (value) => context - .read() - .add(EditHeadlineSourceChanged(value)), - ), - const SizedBox(height: AppSpacing.lg), - DropdownButtonFormField( - value: selectedTopic, - decoration: InputDecoration( - labelText: l10n.topicName, - border: const OutlineInputBorder(), + onChanged: (value) => context + .read() + .add(EditHeadlineImageUrlChanged(value)), ), - items: [ - DropdownMenuItem(value: null, child: Text(l10n.none)), - ...state.topics.map( - (topic) => DropdownMenuItem( - value: topic, - child: Text(topic.name), + const SizedBox(height: AppSpacing.lg), + DropdownButtonFormField( + value: selectedSource, + decoration: InputDecoration( + labelText: l10n.sourceName, + border: const OutlineInputBorder(), + ), + items: [ + DropdownMenuItem(value: null, child: Text(l10n.none)), + ...state.sources.map( + (source) => DropdownMenuItem( + value: source, + child: Text(source.name), + ), ), + ], + onChanged: (value) => context + .read() + .add(EditHeadlineSourceChanged(value)), + ), + const SizedBox(height: AppSpacing.lg), + DropdownButtonFormField( + value: selectedTopic, + decoration: InputDecoration( + labelText: l10n.topicName, + border: const OutlineInputBorder(), ), - ], - onChanged: (value) => context - .read() - .add(EditHeadlineTopicChanged(value)), - ), - const SizedBox(height: AppSpacing.lg), - BlocBuilder( - builder: (context, contentState) { - final isLoading = contentState.allCountriesStatus == - ContentManagementStatus.loading; - return DropdownButtonFormField( - value: selectedCountry, - decoration: InputDecoration( - labelText: l10n.countryName, - border: const OutlineInputBorder(), - helperText: - isLoading ? l10n.loadingFullList : null, + items: [ + DropdownMenuItem(value: null, child: Text(l10n.none)), + ...state.topics.map( + (topic) => DropdownMenuItem( + value: topic, + child: Text(topic.name), + ), ), - items: [ - DropdownMenuItem( - value: null, - child: Text(l10n.none), + ], + onChanged: (value) => context + .read() + .add(EditHeadlineTopicChanged(value)), + ), + const SizedBox(height: AppSpacing.lg), + BlocBuilder< + ContentManagementBloc, + ContentManagementState + >( + builder: (context, contentState) { + final isLoading = + contentState.allCountriesStatus == + ContentManagementStatus.loading; + return DropdownButtonFormField( + value: selectedCountry, + decoration: InputDecoration( + labelText: l10n.countryName, + border: const OutlineInputBorder(), + helperText: isLoading + ? l10n.loadingFullList + : null, ), - ...state.countries.map( - (country) => DropdownMenuItem( - value: country, - child: Row( - children: [ - SizedBox( - width: 32, - height: 20, - child: Image.network( - country.flagUrl, - fit: BoxFit.cover, - errorBuilder: - (context, error, stackTrace) => - const Icon(Icons.flag), + items: [ + DropdownMenuItem( + value: null, + child: Text(l10n.none), + ), + ...state.countries.map( + (country) => DropdownMenuItem( + value: country, + child: Row( + children: [ + SizedBox( + width: 32, + height: 20, + child: Image.network( + country.flagUrl, + fit: BoxFit.cover, + errorBuilder: + (context, error, stackTrace) => + const Icon(Icons.flag), + ), ), - ), - const SizedBox(width: AppSpacing.md), - Text(country.name), - ], + const SizedBox(width: AppSpacing.md), + Text(country.name), + ], + ), ), ), - ), - ], - onChanged: isLoading - ? null - : (value) => context - .read() - .add(EditHeadlineCountryChanged(value)), - ); - }, - ), - const SizedBox(height: AppSpacing.lg), - DropdownButtonFormField( - value: state.contentStatus, - decoration: InputDecoration( - labelText: l10n.status, - border: const OutlineInputBorder(), + ], + onChanged: isLoading + ? null + : (value) => context + .read() + .add(EditHeadlineCountryChanged(value)), + ); + }, ), - items: ContentStatus.values.map((status) { - return DropdownMenuItem( - value: status, - child: Text(status.l10n(context)), - ); - }).toList(), - onChanged: (value) { - if (value == null) return; - context.read().add( - EditHeadlineStatusChanged(value), - ); - }, - ), - ], + const SizedBox(height: AppSpacing.lg), + DropdownButtonFormField( + value: state.contentStatus, + decoration: InputDecoration( + labelText: l10n.status, + border: const OutlineInputBorder(), + ), + items: ContentStatus.values.map((status) { + return DropdownMenuItem( + value: status, + child: Text(status.l10n(context)), + ); + }).toList(), + onChanged: (value) { + if (value == null) return; + context.read().add( + EditHeadlineStatusChanged(value), + ); + }, + ), + ], + ), ), ), - ), - ); - }, + ); + }, + ), ), ); } diff --git a/lib/content_management/view/edit_source_page.dart b/lib/content_management/view/edit_source_page.dart index feca72a..c834fa8 100644 --- a/lib/content_management/view/edit_source_page.dart +++ b/lib/content_management/view/edit_source_page.dart @@ -5,7 +5,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/content_management/bloc/content_management_bloc.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/content_management/bloc/edit_source/edit_source_bloc.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart'; -import 'package:flutter_news_app_web_dashboard_full_source_code/shared/extensions/source_type_l10n.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/shared/shared.dart'; import 'package:go_router/go_router.dart'; import 'package:ui_kit/ui_kit.dart'; @@ -23,18 +22,11 @@ class EditSourcePage extends StatelessWidget { @override Widget build(BuildContext context) { - // The lists of all countries and languages are fetched once and cached in - // the ContentManagementBloc. We read them here and provide them to the - // EditSourceBloc. - final contentManagementState = context.read().state; - final allCountries = contentManagementState.allCountries; - final allLanguages = contentManagementState.allLanguages; - return BlocProvider( create: (context) => EditSourceBloc( sourcesRepository: context.read>(), - countries: allCountries, - languages: allLanguages, + countries: context.read().state.allCountries, + languages: context.read().state.allLanguages, sourceId: sourceId, )..add(const EditSourceLoaded()), child: const _EditSourceView(), @@ -104,225 +96,258 @@ class _EditSourceViewState extends State<_EditSourceView> { ), ], ), - body: BlocConsumer( + body: BlocListener( listenWhen: (previous, current) => - previous.status != current.status || - previous.initialSource != current.initialSource, - listener: (context, state) { - if (state.status == EditSourceStatus.success && - state.updatedSource != null && - ModalRoute.of(context)!.isCurrent) { - ScaffoldMessenger.of(context) - ..hideCurrentSnackBar() - ..showSnackBar( - SnackBar(content: Text(l10n.sourceUpdatedSuccessfully)), + (previous.allCountriesStatus != current.allCountriesStatus && + current.allCountriesStatus == + ContentManagementStatus.success) || + (previous.allLanguagesStatus != current.allLanguagesStatus && + current.allLanguagesStatus == ContentManagementStatus.success), + listener: (context, contentState) { + context.read().add( + EditSourceDataUpdated( + countries: contentState.allCountries, + languages: contentState.allLanguages, + ), + ); + }, + child: BlocConsumer( + listenWhen: (previous, current) => + previous.status != current.status || + previous.initialSource != current.initialSource, + listener: (context, state) { + if (state.status == EditSourceStatus.success && + state.updatedSource != null && + ModalRoute.of(context)!.isCurrent) { + ScaffoldMessenger.of(context) + ..hideCurrentSnackBar() + ..showSnackBar( + SnackBar(content: Text(l10n.sourceUpdatedSuccessfully)), + ); + context.read().add( + const LoadSourcesRequested(limit: kDefaultRowsPerPage), ); - context.read().add( - const LoadSourcesRequested(limit: kDefaultRowsPerPage), - ); - context.pop(); - } - if (state.status == EditSourceStatus.failure) { - ScaffoldMessenger.of(context) - ..hideCurrentSnackBar() - ..showSnackBar( - SnackBar( - content: Text(state.exception!.toFriendlyMessage(context)), - backgroundColor: Theme.of(context).colorScheme.error, - ), + context.pop(); + } + if (state.status == EditSourceStatus.failure) { + ScaffoldMessenger.of(context) + ..hideCurrentSnackBar() + ..showSnackBar( + SnackBar( + content: Text(state.exception!.toFriendlyMessage(context)), + backgroundColor: Theme.of(context).colorScheme.error, + ), + ); + } + if (state.initialSource != null) { + _nameController.text = state.name; + _descriptionController.text = state.description; + _urlController.text = state.url; + } + }, + builder: (context, state) { + if (state.status == EditSourceStatus.loading) { + return LoadingStateWidget( + icon: Icons.source, + headline: l10n.loadingSource, + subheadline: l10n.pleaseWait, ); - } - if (state.initialSource != null) { - _nameController.text = state.name; - _descriptionController.text = state.description; - _urlController.text = state.url; - } - }, - builder: (context, state) { - if (state.status == EditSourceStatus.loading) { - return LoadingStateWidget( - icon: Icons.source, - headline: l10n.loadingSource, - subheadline: l10n.pleaseWait, - ); - } + } - if (state.status == EditSourceStatus.failure && - state.initialSource == null) { - return FailureStateWidget( - exception: state.exception!, - onRetry: () => - context.read().add(const EditSourceLoaded()), - ); - } + if (state.status == EditSourceStatus.failure && + state.initialSource == null) { + return FailureStateWidget( + exception: state.exception!, + onRetry: () => context.read().add( + const EditSourceLoaded(), + ), + ); + } - return SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(AppSpacing.lg), - child: Form( - key: _formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - TextFormField( - controller: _nameController, - decoration: InputDecoration( - labelText: l10n.sourceName, - border: const OutlineInputBorder(), - ), - onChanged: (value) => context.read().add( - EditSourceNameChanged(value), - ), - ), - const SizedBox(height: AppSpacing.lg), - TextFormField( - controller: _descriptionController, - decoration: InputDecoration( - labelText: l10n.description, - border: const OutlineInputBorder(), - ), - maxLines: 3, - onChanged: (value) => context.read().add( - EditSourceDescriptionChanged(value), + return SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(AppSpacing.lg), + child: Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextFormField( + controller: _nameController, + decoration: InputDecoration( + labelText: l10n.sourceName, + border: const OutlineInputBorder(), + ), + onChanged: (value) => + context.read().add( + EditSourceNameChanged(value), + ), ), - ), - const SizedBox(height: AppSpacing.lg), - TextFormField( - controller: _urlController, - decoration: InputDecoration( - labelText: l10n.sourceUrl, - border: const OutlineInputBorder(), + const SizedBox(height: AppSpacing.lg), + TextFormField( + controller: _descriptionController, + decoration: InputDecoration( + labelText: l10n.description, + border: const OutlineInputBorder(), + ), + maxLines: 3, + onChanged: (value) => + context.read().add( + EditSourceDescriptionChanged(value), + ), ), - onChanged: (value) => context.read().add( - EditSourceUrlChanged(value), + const SizedBox(height: AppSpacing.lg), + TextFormField( + controller: _urlController, + decoration: InputDecoration( + labelText: l10n.sourceUrl, + border: const OutlineInputBorder(), + ), + onChanged: (value) => + context.read().add( + EditSourceUrlChanged(value), + ), ), - ), - const SizedBox(height: AppSpacing.lg), - BlocBuilder( - builder: (context, contentState) { - final isLoading = contentState.allLanguagesStatus == - ContentManagementStatus.loading; - return DropdownButtonFormField( - value: state.language, - decoration: InputDecoration( - labelText: l10n.language, - border: const OutlineInputBorder(), - helperText: - isLoading ? l10n.loadingFullList : null, - ), - items: [ - DropdownMenuItem( - value: null, child: Text(l10n.none)), - ...state.languages.map( - (language) => DropdownMenuItem( - value: language, - child: Text(language.name), - ), + const SizedBox(height: AppSpacing.lg), + BlocBuilder< + ContentManagementBloc, + ContentManagementState + >( + builder: (context, contentState) { + final isLoading = + contentState.allLanguagesStatus == + ContentManagementStatus.loading; + return DropdownButtonFormField( + value: state.language, + decoration: InputDecoration( + labelText: l10n.language, + border: const OutlineInputBorder(), + helperText: isLoading + ? l10n.loadingFullList + : null, ), - ], - onChanged: isLoading - ? null - : (value) => - context.read().add( + items: [ + DropdownMenuItem( + value: null, + child: Text(l10n.none), + ), + ...state.languages.map( + (language) => DropdownMenuItem( + value: language, + child: Text(language.name), + ), + ), + ], + onChanged: isLoading + ? null + : (value) => context.read().add( EditSourceLanguageChanged(value), ), - ); - }, - ), - const SizedBox(height: AppSpacing.lg), - DropdownButtonFormField( - value: state.sourceType, - decoration: InputDecoration( - labelText: l10n.sourceType, - border: const OutlineInputBorder(), + ); + }, ), - items: [ - DropdownMenuItem(value: null, child: Text(l10n.none)), - ...SourceType.values.map( - (type) => DropdownMenuItem( - value: type, - child: Text(type.localizedName(l10n)), - ), + const SizedBox(height: AppSpacing.lg), + DropdownButtonFormField( + value: state.sourceType, + decoration: InputDecoration( + labelText: l10n.sourceType, + border: const OutlineInputBorder(), ), - ], - onChanged: (value) => context.read().add( - EditSourceTypeChanged(value), - ), - ), - const SizedBox(height: AppSpacing.lg), - BlocBuilder( - builder: (context, contentState) { - final isLoading = contentState.allCountriesStatus == - ContentManagementStatus.loading; - return DropdownButtonFormField( - value: state.headquarters, - decoration: InputDecoration( - labelText: l10n.headquarters, - border: const OutlineInputBorder(), - helperText: - isLoading ? l10n.loadingFullList : null, + items: [ + DropdownMenuItem(value: null, child: Text(l10n.none)), + ...SourceType.values.map( + (type) => DropdownMenuItem( + value: type, + child: Text(type.localizedName(l10n)), + ), ), - items: [ - DropdownMenuItem( - value: null, child: Text(l10n.none)), - ...state.countries.map( - (country) => DropdownMenuItem( - value: country, - child: Row( - children: [ - SizedBox( - width: 32, - height: 20, - child: Image.network( - country.flagUrl, - fit: BoxFit.cover, - errorBuilder: - (context, error, stackTrace) => - const Icon(Icons.flag), + ], + onChanged: (value) => + context.read().add( + EditSourceTypeChanged(value), + ), + ), + const SizedBox(height: AppSpacing.lg), + BlocBuilder< + ContentManagementBloc, + ContentManagementState + >( + builder: (context, contentState) { + final isLoading = + contentState.allCountriesStatus == + ContentManagementStatus.loading; + return DropdownButtonFormField( + value: state.headquarters, + decoration: InputDecoration( + labelText: l10n.headquarters, + border: const OutlineInputBorder(), + helperText: isLoading + ? l10n.loadingFullList + : null, + ), + items: [ + DropdownMenuItem( + value: null, + child: Text(l10n.none), + ), + ...state.countries.map( + (country) => DropdownMenuItem( + value: country, + child: Row( + children: [ + SizedBox( + width: 32, + height: 20, + child: Image.network( + country.flagUrl, + fit: BoxFit.cover, + errorBuilder: + (context, error, stackTrace) => + const Icon(Icons.flag), + ), ), - ), - const SizedBox(width: AppSpacing.md), - Text(country.name), - ], + const SizedBox(width: AppSpacing.md), + Text(country.name), + ], + ), ), ), - ), - ], - onChanged: isLoading - ? null - : (value) => - context.read().add( + ], + onChanged: isLoading + ? null + : (value) => context.read().add( EditSourceHeadquartersChanged(value), ), - ); - }, - ), - const SizedBox(height: AppSpacing.lg), - DropdownButtonFormField( - value: state.contentStatus, - decoration: InputDecoration( - labelText: l10n.status, - border: const OutlineInputBorder(), + ); + }, + ), + const SizedBox(height: AppSpacing.lg), + DropdownButtonFormField( + value: state.contentStatus, + decoration: InputDecoration( + labelText: l10n.status, + border: const OutlineInputBorder(), + ), + items: ContentStatus.values.map((status) { + return DropdownMenuItem( + value: status, + child: Text(status.l10n(context)), + ); + }).toList(), + onChanged: (value) { + if (value == null) return; + context.read().add( + EditSourceStatusChanged(value), + ); + }, ), - items: ContentStatus.values.map((status) { - return DropdownMenuItem( - value: status, - child: Text(status.l10n(context)), - ); - }).toList(), - onChanged: (value) { - if (value == null) return; - context.read().add( - EditSourceStatusChanged(value), - ); - }, - ), - ], + ], + ), ), ), - ), - ); - }, + ); + }, + ), ), ); }