From c1e174f2833e2ffeaba12d630e853272ba9b0987 Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 30 Jul 2025 17:06:49 +0100 Subject: [PATCH 01/10] feat(dependencies): add language_picker and update pubspec.lock - Add language_picker: ^0.4.5 to pubspec.yaml - Update pubspec.lock with new dependencies: - language_picker: version 0.4.5 - recase: version 4.1.0 (transitive dependency) --- pubspec.lock | 16 ++++++++++++++++ pubspec.yaml | 1 + 2 files changed, 17 insertions(+) diff --git a/pubspec.lock b/pubspec.lock index ac2eca8..835bcd4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -348,6 +348,14 @@ packages: url: "https://github.com/flutter-news-app-full-source-code/kv-storage-shared-preferences.git" source: git version: "0.0.0" + language_picker: + dependency: "direct main" + description: + name: language_picker + sha256: cace0eab53b712e26f5d2cd834a050b6dd6ab56b2ba31b3000dbe5f89f33f5fd + url: "https://pub.dev" + source: hosted + version: "0.4.5" logging: dependency: "direct main" description: @@ -468,6 +476,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.5" + recase: + dependency: transitive + description: + name: recase + sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213 + url: "https://pub.dev" + source: hosted + version: "4.1.0" shared_preferences: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 3ebff7c..2b893c8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -59,6 +59,7 @@ dependencies: kv_storage_shared_preferences: git: url: https://github.com/flutter-news-app-full-source-code/kv-storage-shared-preferences.git + language_picker: ^0.4.5 logging: ^1.3.0 pinput: ^5.0.1 timeago: ^3.7.1 From 71c334fdc0d3fbb851e521734cc17d5a6cb0706a Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 30 Jul 2025 17:09:05 +0100 Subject: [PATCH 02/10] feat(shared): add language adapter for picker integration --- lib/shared/utils/language_adapter.dart | 28 ++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 lib/shared/utils/language_adapter.dart diff --git a/lib/shared/utils/language_adapter.dart b/lib/shared/utils/language_adapter.dart new file mode 100644 index 0000000..7a237ef --- /dev/null +++ b/lib/shared/utils/language_adapter.dart @@ -0,0 +1,28 @@ +import 'package:language_picker/languages.dart'; + +/// Adapts a [Language] object from the `language_picker` package to a +/// language code string (e.g., 'en', 'ar'). +/// +/// This is used to convert the selected language from the UI picker into the +/// format expected by the `Source` model in the core package. +String adaptPackageLanguageToLanguageCode(Language language) { + return language.isoCode; +} + +/// Adapts a language code string (e.g., 'en', 'ar') to a [Language] object +/// from the `language_picker` package. +/// +/// This is used to convert the language code from a `Source` model into an +/// object that can be used to set the initial value of the language picker. +/// +/// Returns `null` if the language code is not found. +Language? adaptLanguageCodeToPackageLanguage(String languageCode) { + try { + return Languages.defaultLanguages.firstWhere( + (lang) => lang.isoCode.toLowerCase() == languageCode.toLowerCase(), + ); + } catch (_) { + // Return null if no matching language is found + return null; + } +} From 84b229b458c3bc6037ace1450e97fb563f1a087b Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 30 Jul 2025 17:12:03 +0100 Subject: [PATCH 03/10] feat(shared): create language picker and export shared components --- lib/shared/utils/utils.dart | 1 + .../widgets/language_picker_form_field.dart | 57 +++++++++++++++++++ lib/shared/widgets/widgets.dart | 1 + 3 files changed, 59 insertions(+) create mode 100644 lib/shared/widgets/language_picker_form_field.dart diff --git a/lib/shared/utils/utils.dart b/lib/shared/utils/utils.dart index d79c0e8..e071a4b 100644 --- a/lib/shared/utils/utils.dart +++ b/lib/shared/utils/utils.dart @@ -1 +1,2 @@ export 'country_adapter.dart'; +export 'language_adapter.dart'; diff --git a/lib/shared/widgets/language_picker_form_field.dart b/lib/shared/widgets/language_picker_form_field.dart new file mode 100644 index 0000000..e0194f6 --- /dev/null +++ b/lib/shared/widgets/language_picker_form_field.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:language_picker/language_picker.dart'; +import 'package:language_picker/languages.dart'; + +/// A form field for selecting a language using the `language_picker` package. +/// +/// This widget wraps the language picker functionality in a standard +/// [FormField], making it easy to integrate into forms for validation +/// and state management. It presents as a read-only [TextFormField] that, +/// when tapped, opens a language selection dialog. +class LanguagePickerFormField extends FormField { + /// Creates a [LanguagePickerFormField]. + /// + /// The [onSaved], [validator], [initialValue], and [autovalidateMode] are + /// standard [FormField] properties. + /// + /// The [labelText] is displayed as the input field's label. + LanguagePickerFormField({ + super.key, + super.onSaved, + super.validator, + super.initialValue, + super.autovalidateMode, + String? labelText, + }) : super( + builder: (FormFieldState state) { + // This controller is just for displaying the text. The actual + // value is managed by the FormField's state. + final controller = TextEditingController( + text: state.value?.name, + ); + + return TextFormField( + controller: controller, + readOnly: true, + decoration: InputDecoration( + labelText: labelText ?? 'Language', + border: const OutlineInputBorder(), + errorText: state.errorText, + suffixIcon: const Icon(Icons.arrow_drop_down), + ), + onTap: () { + showLanguagePicker( + context: state.context, + // Provide a default if no language is selected yet. + selectedLanguage: state.value ?? Languages.english, + onValuePicked: (Language language) { + state.didChange(language); + // Update the text in the read-only text field. + controller.text = language.name; + }, + ); + }, + ); + }, + ); +} \ No newline at end of file diff --git a/lib/shared/widgets/widgets.dart b/lib/shared/widgets/widgets.dart index a8dd41f..f2917f7 100644 --- a/lib/shared/widgets/widgets.dart +++ b/lib/shared/widgets/widgets.dart @@ -1 +1,2 @@ export 'country_picker_form_field.dart'; +export 'language_picker_form_field.dart'; From 3a67f4931dbe72786c897266d1db8e8290328d90 Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 30 Jul 2025 17:21:19 +0100 Subject: [PATCH 04/10] feat(content): adapt create source bloc for language picker --- .../bloc/create_source/create_source_bloc.dart | 5 +++-- .../bloc/create_source/create_source_event.dart | 2 +- .../bloc/create_source/create_source_state.dart | 11 ++++++----- lib/shared/widgets/language_picker_form_field.dart | 14 +++++++------- 4 files changed, 17 insertions(+), 15 deletions(-) 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 a6c16cd..6a5a596 100644 --- a/lib/content_management/bloc/create_source/create_source_bloc.dart +++ b/lib/content_management/bloc/create_source/create_source_bloc.dart @@ -2,6 +2,7 @@ import 'package:bloc/bloc.dart'; import 'package:core/core.dart'; import 'package:country_picker/country_picker.dart' as picker; import 'package:data_repository/data_repository.dart'; +import 'package:language_picker/language_picker.dart' as language_picker; import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/shared/shared.dart'; @@ -72,7 +73,7 @@ class CreateSourceBloc extends Bloc { CreateSourceLanguageChanged event, Emitter emit, ) { - emit(state.copyWith(language: event.language)); + emit(state.copyWith(language: () => event.language)); } void _onHeadquartersChanged( @@ -115,7 +116,7 @@ class CreateSourceBloc extends Bloc { description: state.description, url: state.url, sourceType: state.sourceType!, - language: state.language, + language: adaptPackageLanguageToLanguageCode(state.language!), createdAt: now, updatedAt: now, headquarters: state.headquarters!, 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 2750166..f364302 100644 --- a/lib/content_management/bloc/create_source/create_source_event.dart +++ b/lib/content_management/bloc/create_source/create_source_event.dart @@ -48,7 +48,7 @@ final class CreateSourceTypeChanged extends CreateSourceEvent { /// Event for when the source's language is changed. final class CreateSourceLanguageChanged extends CreateSourceEvent { const CreateSourceLanguageChanged(this.language); - final String language; + final language_picker.Language? language; @override List get props => [language]; } diff --git a/lib/content_management/bloc/create_source/create_source_state.dart b/lib/content_management/bloc/create_source/create_source_state.dart index 2d38ce4..f37e187 100644 --- a/lib/content_management/bloc/create_source/create_source_state.dart +++ b/lib/content_management/bloc/create_source/create_source_state.dart @@ -1,5 +1,6 @@ part of 'create_source_bloc.dart'; +import 'package:language_picker/language_picker.dart' as language_picker; /// Represents the status of the create source operation. enum CreateSourceStatus { /// Initial state, before any data is loaded. @@ -27,7 +28,7 @@ final class CreateSourceState extends Equatable { this.description = '', this.url = '', this.sourceType, - this.language = '', + this.language, this.headquarters, this.contentStatus = ContentStatus.active, this.exception, @@ -39,7 +40,7 @@ final class CreateSourceState extends Equatable { final String description; final String url; final SourceType? sourceType; - final String language; + final language_picker.Language? language; final Country? headquarters; final ContentStatus contentStatus; final HttpException? exception; @@ -51,7 +52,7 @@ final class CreateSourceState extends Equatable { description.isNotEmpty && url.isNotEmpty && sourceType != null && - language.isNotEmpty && + language != null && headquarters != null; CreateSourceState copyWith({ @@ -60,7 +61,7 @@ final class CreateSourceState extends Equatable { String? description, String? url, ValueGetter? sourceType, - String? language, + ValueGetter? language, ValueGetter? headquarters, ContentStatus? contentStatus, HttpException? exception, @@ -72,7 +73,7 @@ final class CreateSourceState extends Equatable { description: description ?? this.description, url: url ?? this.url, sourceType: sourceType != null ? sourceType() : this.sourceType, - language: language ?? this.language, + language: language != null ? language() : this.language, headquarters: headquarters != null ? headquarters() : this.headquarters, contentStatus: contentStatus ?? this.contentStatus, exception: exception, diff --git a/lib/shared/widgets/language_picker_form_field.dart b/lib/shared/widgets/language_picker_form_field.dart index e0194f6..10d3585 100644 --- a/lib/shared/widgets/language_picker_form_field.dart +++ b/lib/shared/widgets/language_picker_form_field.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:language_picker/language_picker.dart'; -import 'package:language_picker/languages.dart'; +import 'package:language_picker/language_picker.dart' as language_picker; /// A form field for selecting a language using the `language_picker` package. /// @@ -8,7 +7,7 @@ import 'package:language_picker/languages.dart'; /// [FormField], making it easy to integrate into forms for validation /// and state management. It presents as a read-only [TextFormField] that, /// when tapped, opens a language selection dialog. -class LanguagePickerFormField extends FormField { +class LanguagePickerFormField extends FormField { /// Creates a [LanguagePickerFormField]. /// /// The [onSaved], [validator], [initialValue], and [autovalidateMode] are @@ -23,7 +22,7 @@ class LanguagePickerFormField extends FormField { super.autovalidateMode, String? labelText, }) : super( - builder: (FormFieldState state) { + builder: (FormFieldState state) { // This controller is just for displaying the text. The actual // value is managed by the FormField's state. final controller = TextEditingController( @@ -40,11 +39,12 @@ class LanguagePickerFormField extends FormField { suffixIcon: const Icon(Icons.arrow_drop_down), ), onTap: () { - showLanguagePicker( + language_picker.showLanguagePicker( context: state.context, // Provide a default if no language is selected yet. - selectedLanguage: state.value ?? Languages.english, - onValuePicked: (Language language) { + selectedLanguage: + state.value ?? language_picker.Languages.english, + onValuePicked: (language_picker.Language language) { state.didChange(language); // Update the text in the read-only text field. controller.text = language.name; From a84b87813e55e54fc9eeb5102b20d725cbc7c978 Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 30 Jul 2025 17:22:53 +0100 Subject: [PATCH 05/10] feat(content): integrate language picker into create source page --- .../view/create_source_page.dart | 15 +++++++-------- .../widgets/language_picker_form_field.dart | 5 +++++ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/content_management/view/create_source_page.dart b/lib/content_management/view/create_source_page.dart index 728b97a..9e26cd3 100644 --- a/lib/content_management/view/create_source_page.dart +++ b/lib/content_management/view/create_source_page.dart @@ -7,6 +7,7 @@ import 'package:flutter_news_app_web_dashboard_full_source_code/content_manageme 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/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/l10n/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'; @@ -160,15 +161,13 @@ class _CreateSourceViewState extends State<_CreateSourceView> { .add(CreateSourceUrlChanged(value)), ), const SizedBox(height: AppSpacing.lg), - TextFormField( + LanguagePickerFormField( + labelText: l10n.language, initialValue: state.language, - decoration: InputDecoration( - labelText: l10n.language, - border: const OutlineInputBorder(), - ), - onChanged: (value) => context - .read() - .add(CreateSourceLanguageChanged(value)), + onChanged: (language) => + context.read().add( + CreateSourceLanguageChanged(language), + ), ), const SizedBox(height: AppSpacing.lg), DropdownButtonFormField( diff --git a/lib/shared/widgets/language_picker_form_field.dart b/lib/shared/widgets/language_picker_form_field.dart index 10d3585..9e4d59a 100644 --- a/lib/shared/widgets/language_picker_form_field.dart +++ b/lib/shared/widgets/language_picker_form_field.dart @@ -14,6 +14,7 @@ class LanguagePickerFormField extends FormField { /// standard [FormField] properties. /// /// The [labelText] is displayed as the input field's label. + /// The [onChanged] callback is invoked when a new language is selected. LanguagePickerFormField({ super.key, super.onSaved, @@ -21,6 +22,7 @@ class LanguagePickerFormField extends FormField { super.initialValue, super.autovalidateMode, String? labelText, + void Function(language_picker.Language)? onChanged, }) : super( builder: (FormFieldState state) { // This controller is just for displaying the text. The actual @@ -46,6 +48,9 @@ class LanguagePickerFormField extends FormField { state.value ?? language_picker.Languages.english, onValuePicked: (language_picker.Language language) { state.didChange(language); + if (onChanged != null) { + onChanged(language); + } // Update the text in the read-only text field. controller.text = language.name; }, From 832bf5c2a24f02c63c15a61de0886ffd519819f1 Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 30 Jul 2025 17:24:37 +0100 Subject: [PATCH 06/10] feat(content): adapt edit source bloc for language picke --- .../bloc/edit_source/edit_source_bloc.dart | 9 ++++++--- .../bloc/edit_source/edit_source_event.dart | 4 ++-- .../bloc/edit_source/edit_source_state.dart | 11 ++++++----- .../view/create_source_page.dart | 15 ++++++++------- .../widgets/language_picker_form_field.dart | 5 ----- 5 files changed, 22 insertions(+), 22 deletions(-) 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 1a5d629..eb587ac 100644 --- a/lib/content_management/bloc/edit_source/edit_source_bloc.dart +++ b/lib/content_management/bloc/edit_source/edit_source_bloc.dart @@ -2,6 +2,7 @@ import 'package:bloc/bloc.dart'; import 'package:core/core.dart'; import 'package:country_picker/country_picker.dart' as picker; import 'package:data_repository/data_repository.dart'; +import 'package:language_picker/language_picker.dart' as language_picker; import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/app_localizations.dart'; @@ -48,7 +49,9 @@ class EditSourceBloc extends Bloc { description: source.description, url: source.url, sourceType: () => source.sourceType, - language: source.language, + language: () => adaptLanguageCodeToPackageLanguage( + source.language, + ), headquarters: () => source.headquarters, contentStatus: source.status, ), @@ -109,7 +112,7 @@ class EditSourceBloc extends Bloc { ) { emit( state.copyWith( - language: event.language, + language: () => event.language, status: EditSourceStatus.initial, ), ); @@ -169,7 +172,7 @@ class EditSourceBloc extends Bloc { description: state.description, url: state.url, sourceType: state.sourceType, - language: state.language, + language: adaptPackageLanguageToLanguageCode(state.language!), headquarters: state.headquarters, status: state.contentStatus, updatedAt: DateTime.now(), 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 21aa933..831e74d 100644 --- a/lib/content_management/bloc/edit_source/edit_source_event.dart +++ b/lib/content_management/bloc/edit_source/edit_source_event.dart @@ -1,5 +1,6 @@ part of 'edit_source_bloc.dart'; +import 'package:language_picker/language_picker.dart' as language_picker; /// Base class for all events related to the [EditSourceBloc]. sealed class EditSourceEvent extends Equatable { const EditSourceEvent(); @@ -56,8 +57,7 @@ final class EditSourceTypeChanged extends EditSourceEvent { /// Event triggered when the source language input changes. final class EditSourceLanguageChanged extends EditSourceEvent { const EditSourceLanguageChanged(this.language); - - final String language; + final language_picker.Language? language; @override List get props => [language]; diff --git a/lib/content_management/bloc/edit_source/edit_source_state.dart b/lib/content_management/bloc/edit_source/edit_source_state.dart index d04d311..28f452f 100644 --- a/lib/content_management/bloc/edit_source/edit_source_state.dart +++ b/lib/content_management/bloc/edit_source/edit_source_state.dart @@ -1,5 +1,6 @@ part of 'edit_source_bloc.dart'; +import 'package:language_picker/language_picker.dart' as language_picker; /// Represents the status of the edit source operation. enum EditSourceStatus { /// Initial state, before any data is loaded. @@ -27,7 +28,7 @@ final class EditSourceState extends Equatable { this.description = '', this.url = '', this.sourceType, - this.language = '', + this.language, this.headquarters, this.contentStatus = ContentStatus.active, this.exception, @@ -40,7 +41,7 @@ final class EditSourceState extends Equatable { final String description; final String url; final SourceType? sourceType; - final String language; + final language_picker.Language? language; final Country? headquarters; final ContentStatus contentStatus; final HttpException? exception; @@ -52,7 +53,7 @@ final class EditSourceState extends Equatable { description.isNotEmpty && url.isNotEmpty && sourceType != null && - language.isNotEmpty && + language != null && headquarters != null; EditSourceState copyWith({ @@ -62,7 +63,7 @@ final class EditSourceState extends Equatable { String? description, String? url, ValueGetter? sourceType, - String? language, + ValueGetter? language, ValueGetter? headquarters, ContentStatus? contentStatus, HttpException? exception, @@ -75,7 +76,7 @@ final class EditSourceState extends Equatable { description: description ?? this.description, url: url ?? this.url, sourceType: sourceType != null ? sourceType() : this.sourceType, - language: language ?? this.language, + language: language != null ? language() : this.language, headquarters: headquarters != null ? headquarters() : this.headquarters, contentStatus: contentStatus ?? this.contentStatus, exception: exception, diff --git a/lib/content_management/view/create_source_page.dart b/lib/content_management/view/create_source_page.dart index 9e26cd3..728b97a 100644 --- a/lib/content_management/view/create_source_page.dart +++ b/lib/content_management/view/create_source_page.dart @@ -7,7 +7,6 @@ import 'package:flutter_news_app_web_dashboard_full_source_code/content_manageme 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/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/l10n/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'; @@ -161,13 +160,15 @@ class _CreateSourceViewState extends State<_CreateSourceView> { .add(CreateSourceUrlChanged(value)), ), const SizedBox(height: AppSpacing.lg), - LanguagePickerFormField( - labelText: l10n.language, + TextFormField( initialValue: state.language, - onChanged: (language) => - context.read().add( - CreateSourceLanguageChanged(language), - ), + decoration: InputDecoration( + labelText: l10n.language, + border: const OutlineInputBorder(), + ), + onChanged: (value) => context + .read() + .add(CreateSourceLanguageChanged(value)), ), const SizedBox(height: AppSpacing.lg), DropdownButtonFormField( diff --git a/lib/shared/widgets/language_picker_form_field.dart b/lib/shared/widgets/language_picker_form_field.dart index 9e4d59a..10d3585 100644 --- a/lib/shared/widgets/language_picker_form_field.dart +++ b/lib/shared/widgets/language_picker_form_field.dart @@ -14,7 +14,6 @@ class LanguagePickerFormField extends FormField { /// standard [FormField] properties. /// /// The [labelText] is displayed as the input field's label. - /// The [onChanged] callback is invoked when a new language is selected. LanguagePickerFormField({ super.key, super.onSaved, @@ -22,7 +21,6 @@ class LanguagePickerFormField extends FormField { super.initialValue, super.autovalidateMode, String? labelText, - void Function(language_picker.Language)? onChanged, }) : super( builder: (FormFieldState state) { // This controller is just for displaying the text. The actual @@ -48,9 +46,6 @@ class LanguagePickerFormField extends FormField { state.value ?? language_picker.Languages.english, onValuePicked: (language_picker.Language language) { state.didChange(language); - if (onChanged != null) { - onChanged(language); - } // Update the text in the read-only text field. controller.text = language.name; }, From 2548e747602a0b672c152903c5d5a7e4d8932171 Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 30 Jul 2025 17:26:36 +0100 Subject: [PATCH 07/10] feat(content): integrate language picker into edit source page --- .../create_source/create_source_state.dart | 1 - .../bloc/edit_source/edit_source_event.dart | 1 - .../view/edit_source_page.dart | 20 +++++++------------ 3 files changed, 7 insertions(+), 15 deletions(-) diff --git a/lib/content_management/bloc/create_source/create_source_state.dart b/lib/content_management/bloc/create_source/create_source_state.dart index f37e187..48e2cf9 100644 --- a/lib/content_management/bloc/create_source/create_source_state.dart +++ b/lib/content_management/bloc/create_source/create_source_state.dart @@ -1,6 +1,5 @@ part of 'create_source_bloc.dart'; -import 'package:language_picker/language_picker.dart' as language_picker; /// Represents the status of the create source operation. enum CreateSourceStatus { /// Initial state, before any data is loaded. 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 831e74d..ed97d08 100644 --- a/lib/content_management/bloc/edit_source/edit_source_event.dart +++ b/lib/content_management/bloc/edit_source/edit_source_event.dart @@ -1,6 +1,5 @@ part of 'edit_source_bloc.dart'; -import 'package:language_picker/language_picker.dart' as language_picker; /// Base class for all events related to the [EditSourceBloc]. sealed class EditSourceEvent extends Equatable { const EditSourceEvent(); diff --git a/lib/content_management/view/edit_source_page.dart b/lib/content_management/view/edit_source_page.dart index 9bf0dd8..fb20f02 100644 --- a/lib/content_management/view/edit_source_page.dart +++ b/lib/content_management/view/edit_source_page.dart @@ -45,7 +45,6 @@ class _EditSourceViewState extends State<_EditSourceView> { late final TextEditingController _nameController; late final TextEditingController _descriptionController; late final TextEditingController _urlController; - late final TextEditingController _languageController; @override void initState() { @@ -54,7 +53,6 @@ class _EditSourceViewState extends State<_EditSourceView> { _nameController = TextEditingController(text: state.name); _descriptionController = TextEditingController(text: state.description); _urlController = TextEditingController(text: state.url); - _languageController = TextEditingController(text: state.language); } @override @@ -62,7 +60,6 @@ class _EditSourceViewState extends State<_EditSourceView> { _nameController.dispose(); _descriptionController.dispose(); _urlController.dispose(); - _languageController.dispose(); super.dispose(); } @@ -130,7 +127,6 @@ class _EditSourceViewState extends State<_EditSourceView> { _nameController.text = state.name; _descriptionController.text = state.description; _urlController.text = state.url; - _languageController.text = state.language; } }, builder: (context, state) { @@ -193,15 +189,13 @@ class _EditSourceViewState extends State<_EditSourceView> { ), ), const SizedBox(height: AppSpacing.lg), - TextFormField( - controller: _languageController, - decoration: InputDecoration( - labelText: l10n.language, - border: const OutlineInputBorder(), - ), - onChanged: (value) => context.read().add( - EditSourceLanguageChanged(value), - ), + LanguagePickerFormField( + labelText: l10n.language, + initialValue: state.language, + onChanged: (language) => + context.read().add( + EditSourceLanguageChanged(language), + ), ), const SizedBox(height: AppSpacing.lg), DropdownButtonFormField( From a5afe50a647103af74e0b1f263d78cce57e2638a Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 30 Jul 2025 17:42:25 +0100 Subject: [PATCH 08/10] fix(shared): implement correct LanguagePickerFormField using showDialog --- .../widgets/language_picker_form_field.dart | 48 ++++++++++++------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/lib/shared/widgets/language_picker_form_field.dart b/lib/shared/widgets/language_picker_form_field.dart index 10d3585..1a512d3 100644 --- a/lib/shared/widgets/language_picker_form_field.dart +++ b/lib/shared/widgets/language_picker_form_field.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:language_picker/language_picker.dart' as language_picker; +import 'package:language_picker/language_picker.dart'; +import 'package:language_picker/languages.dart'; /// A form field for selecting a language using the `language_picker` package. /// @@ -7,13 +8,14 @@ import 'package:language_picker/language_picker.dart' as language_picker; /// [FormField], making it easy to integrate into forms for validation /// and state management. It presents as a read-only [TextFormField] that, /// when tapped, opens a language selection dialog. -class LanguagePickerFormField extends FormField { +class LanguagePickerFormField extends FormField { /// Creates a [LanguagePickerFormField]. /// /// The [onSaved], [validator], [initialValue], and [autovalidateMode] are /// standard [FormField] properties. /// /// The [labelText] is displayed as the input field's label. + /// The [onChanged] callback is invoked when a new language is selected. LanguagePickerFormField({ super.key, super.onSaved, @@ -21,14 +23,40 @@ class LanguagePickerFormField extends FormField { super.initialValue, super.autovalidateMode, String? labelText, + void Function(Language)? onChanged, }) : super( - builder: (FormFieldState state) { + builder: (FormFieldState state) { // This controller is just for displaying the text. The actual // value is managed by the FormField's state. final controller = TextEditingController( text: state.value?.name, ); + // Helper to build a simple list item for the dialog. + Widget buildDialogItem(Language language) => Row( + children: [ + Text(language.name), + const SizedBox(width: 8), + Flexible(child: Text('(${language.isoCode})')), + ], + ); + + void openLanguagePickerDialog() { + showDialog( + context: state.context, + builder: (context) => LanguagePickerDialog( + isSearchable: true, + title: const Text('Select your language'), + onValuePicked: (Language language) { + state.didChange(language); + onChanged?.call(language); + controller.text = language.name; + }, + itemBuilder: buildDialogItem, + ), + ); + } + return TextFormField( controller: controller, readOnly: true, @@ -38,19 +66,7 @@ class LanguagePickerFormField extends FormField { errorText: state.errorText, suffixIcon: const Icon(Icons.arrow_drop_down), ), - onTap: () { - language_picker.showLanguagePicker( - context: state.context, - // Provide a default if no language is selected yet. - selectedLanguage: - state.value ?? language_picker.Languages.english, - onValuePicked: (language_picker.Language language) { - state.didChange(language); - // Update the text in the read-only text field. - controller.text = language.name; - }, - ); - }, + onTap: openLanguagePickerDialog, ); }, ); From e1179651ffde25360b8f9274627e466ca76a9911 Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 30 Jul 2025 17:54:13 +0100 Subject: [PATCH 09/10] fix(content): finalize language picker integration --- .../bloc/create_source/create_source_bloc.dart | 3 +-- .../bloc/create_source/create_source_event.dart | 2 +- .../bloc/create_source/create_source_state.dart | 4 ++-- .../bloc/edit_source/edit_source_bloc.dart | 2 +- .../bloc/edit_source/edit_source_event.dart | 2 +- .../bloc/edit_source/edit_source_state.dart | 5 ++--- .../view/create_source_page.dart | 14 ++++++-------- 7 files changed, 14 insertions(+), 18 deletions(-) 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 6a5a596..d67597a 100644 --- a/lib/content_management/bloc/create_source/create_source_bloc.dart +++ b/lib/content_management/bloc/create_source/create_source_bloc.dart @@ -2,10 +2,9 @@ import 'package:bloc/bloc.dart'; import 'package:core/core.dart'; import 'package:country_picker/country_picker.dart' as picker; import 'package:data_repository/data_repository.dart'; -import 'package:language_picker/language_picker.dart' as language_picker; import 'package:equatable/equatable.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/shared/shared.dart'; +import 'package:language_picker/languages.dart'; import 'package:uuid/uuid.dart'; part 'create_source_event.dart'; 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 f364302..8bbf2e7 100644 --- a/lib/content_management/bloc/create_source/create_source_event.dart +++ b/lib/content_management/bloc/create_source/create_source_event.dart @@ -48,7 +48,7 @@ final class CreateSourceTypeChanged extends CreateSourceEvent { /// Event for when the source's language is changed. final class CreateSourceLanguageChanged extends CreateSourceEvent { const CreateSourceLanguageChanged(this.language); - final language_picker.Language? language; + final Language? language; @override List get props => [language]; } diff --git a/lib/content_management/bloc/create_source/create_source_state.dart b/lib/content_management/bloc/create_source/create_source_state.dart index 48e2cf9..66165eb 100644 --- a/lib/content_management/bloc/create_source/create_source_state.dart +++ b/lib/content_management/bloc/create_source/create_source_state.dart @@ -39,7 +39,7 @@ final class CreateSourceState extends Equatable { final String description; final String url; final SourceType? sourceType; - final language_picker.Language? language; + final Language? language; final Country? headquarters; final ContentStatus contentStatus; final HttpException? exception; @@ -60,7 +60,7 @@ final class CreateSourceState extends Equatable { String? description, String? url, ValueGetter? sourceType, - ValueGetter? language, + ValueGetter? language, ValueGetter? headquarters, ContentStatus? contentStatus, HttpException? exception, 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 eb587ac..5fa3322 100644 --- a/lib/content_management/bloc/edit_source/edit_source_bloc.dart +++ b/lib/content_management/bloc/edit_source/edit_source_bloc.dart @@ -2,11 +2,11 @@ import 'package:bloc/bloc.dart'; import 'package:core/core.dart'; import 'package:country_picker/country_picker.dart' as picker; import 'package:data_repository/data_repository.dart'; -import 'package:language_picker/language_picker.dart' as language_picker; import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/app_localizations.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/shared/shared.dart'; +import 'package:language_picker/languages.dart'; part 'edit_source_event.dart'; part 'edit_source_state.dart'; 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 ed97d08..2ba8c53 100644 --- a/lib/content_management/bloc/edit_source/edit_source_event.dart +++ b/lib/content_management/bloc/edit_source/edit_source_event.dart @@ -56,7 +56,7 @@ final class EditSourceTypeChanged extends EditSourceEvent { /// Event triggered when the source language input changes. final class EditSourceLanguageChanged extends EditSourceEvent { const EditSourceLanguageChanged(this.language); - final language_picker.Language? language; + final Language? language; @override List get props => [language]; diff --git a/lib/content_management/bloc/edit_source/edit_source_state.dart b/lib/content_management/bloc/edit_source/edit_source_state.dart index 28f452f..557ef6b 100644 --- a/lib/content_management/bloc/edit_source/edit_source_state.dart +++ b/lib/content_management/bloc/edit_source/edit_source_state.dart @@ -1,6 +1,5 @@ part of 'edit_source_bloc.dart'; -import 'package:language_picker/language_picker.dart' as language_picker; /// Represents the status of the edit source operation. enum EditSourceStatus { /// Initial state, before any data is loaded. @@ -41,7 +40,7 @@ final class EditSourceState extends Equatable { final String description; final String url; final SourceType? sourceType; - final language_picker.Language? language; + final Language? language; final Country? headquarters; final ContentStatus contentStatus; final HttpException? exception; @@ -63,7 +62,7 @@ final class EditSourceState extends Equatable { String? description, String? url, ValueGetter? sourceType, - ValueGetter? language, + ValueGetter? language, ValueGetter? headquarters, ContentStatus? contentStatus, HttpException? exception, diff --git a/lib/content_management/view/create_source_page.dart b/lib/content_management/view/create_source_page.dart index 728b97a..cfee1d7 100644 --- a/lib/content_management/view/create_source_page.dart +++ b/lib/content_management/view/create_source_page.dart @@ -160,15 +160,13 @@ class _CreateSourceViewState extends State<_CreateSourceView> { .add(CreateSourceUrlChanged(value)), ), const SizedBox(height: AppSpacing.lg), - TextFormField( + LanguagePickerFormField( + labelText: l10n.language, initialValue: state.language, - decoration: InputDecoration( - labelText: l10n.language, - border: const OutlineInputBorder(), - ), - onChanged: (value) => context - .read() - .add(CreateSourceLanguageChanged(value)), + onChanged: (language) => + context.read().add( + CreateSourceLanguageChanged(language), + ), ), const SizedBox(height: AppSpacing.lg), DropdownButtonFormField( From 0ffd856930e2a1836e556ae98cd0ad85eb5a56a5 Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 30 Jul 2025 17:54:45 +0100 Subject: [PATCH 10/10] fix(content_management): add missing import for MaterialApp - Added 'package:flutter/material.dart' import to create_source_bloc.dart - This import is necessary for using MaterialApp in the app's UI --- .../bloc/create_source/create_source_bloc.dart | 1 + 1 file changed, 1 insertion(+) 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 d67597a..9325782 100644 --- a/lib/content_management/bloc/create_source/create_source_bloc.dart +++ b/lib/content_management/bloc/create_source/create_source_bloc.dart @@ -3,6 +3,7 @@ import 'package:core/core.dart'; import 'package:country_picker/country_picker.dart' as picker; import 'package:data_repository/data_repository.dart'; import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/shared/shared.dart'; import 'package:language_picker/languages.dart'; import 'package:uuid/uuid.dart';