diff --git a/lib/app/view/app.dart b/lib/app/view/app.dart index b270aa7..8fa936a 100644 --- a/lib/app/view/app.dart +++ b/lib/app/view/app.dart @@ -26,7 +26,6 @@ class App extends StatelessWidget { required AuthRepository authenticationRepository, required DataRepository headlinesRepository, required DataRepository topicsRepository, - required DataRepository countriesRepository, required DataRepository sourcesRepository, required DataRepository userAppSettingsRepository, required DataRepository @@ -39,7 +38,6 @@ class App extends StatelessWidget { }) : _authenticationRepository = authenticationRepository, _headlinesRepository = headlinesRepository, _topicsRepository = topicsRepository, - _countriesRepository = countriesRepository, _sourcesRepository = sourcesRepository, _userAppSettingsRepository = userAppSettingsRepository, _userContentPreferencesRepository = userContentPreferencesRepository, @@ -51,7 +49,6 @@ class App extends StatelessWidget { final AuthRepository _authenticationRepository; final DataRepository _headlinesRepository; final DataRepository _topicsRepository; - final DataRepository _countriesRepository; final DataRepository _sourcesRepository; final DataRepository _userAppSettingsRepository; final DataRepository @@ -68,7 +65,6 @@ class App extends StatelessWidget { RepositoryProvider.value(value: _authenticationRepository), RepositoryProvider.value(value: _headlinesRepository), RepositoryProvider.value(value: _topicsRepository), - RepositoryProvider.value(value: _countriesRepository), RepositoryProvider.value(value: _sourcesRepository), RepositoryProvider.value(value: _userAppSettingsRepository), RepositoryProvider.value(value: _userContentPreferencesRepository), diff --git a/lib/bootstrap.dart b/lib/bootstrap.dart index efd5dcb..8296499 100644 --- a/lib/bootstrap.dart +++ b/lib/bootstrap.dart @@ -55,7 +55,6 @@ Future bootstrap( DataClient headlinesClient; DataClient topicsClient; - DataClient countriesClient; DataClient sourcesClient; DataClient userContentPreferencesClient; DataClient userAppSettingsClient; @@ -75,12 +74,6 @@ Future bootstrap( initialData: topicsFixturesData, logger: Logger('DataInMemory'), ); - countriesClient = DataInMemory( - toJson: (i) => i.toJson(), - getId: (i) => i.id, - initialData: countriesFixturesData, - logger: Logger('DataInMemory'), - ); sourcesClient = DataInMemory( toJson: (i) => i.toJson(), getId: (i) => i.id, @@ -124,13 +117,6 @@ Future bootstrap( toJson: (topic) => topic.toJson(), logger: Logger('DataApi'), ); - countriesClient = DataApi( - httpClient: httpClient, - modelName: 'country', - fromJson: Country.fromJson, - toJson: (country) => country.toJson(), - logger: Logger('DataApi'), - ); sourcesClient = DataApi( httpClient: httpClient, modelName: 'source', @@ -181,13 +167,6 @@ Future bootstrap( toJson: (topic) => topic.toJson(), logger: Logger('DataApi'), ); - countriesClient = DataApi( - httpClient: httpClient, - modelName: 'country', - fromJson: Country.fromJson, - toJson: (country) => country.toJson(), - logger: Logger('DataApi'), - ); sourcesClient = DataApi( httpClient: httpClient, modelName: 'source', @@ -229,9 +208,6 @@ Future bootstrap( dataClient: headlinesClient, ); final topicsRepository = DataRepository(dataClient: topicsClient); - final countriesRepository = DataRepository( - dataClient: countriesClient, - ); final sourcesRepository = DataRepository(dataClient: sourcesClient); final userContentPreferencesRepository = DataRepository( @@ -251,7 +227,6 @@ Future bootstrap( authenticationRepository: authenticationRepository, headlinesRepository: headlinesRepository, topicsRepository: topicsRepository, - countriesRepository: countriesRepository, sourcesRepository: sourcesRepository, userAppSettingsRepository: userAppSettingsRepository, userContentPreferencesRepository: userContentPreferencesRepository, 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 8af8dea..da10939 100644 --- a/lib/content_management/bloc/create_headline/create_headline_bloc.dart +++ b/lib/content_management/bloc/create_headline/create_headline_bloc.dart @@ -1,8 +1,10 @@ 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:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter_news_app_web_dashboard_full_source_code/shared/shared.dart'; import 'package:uuid/uuid.dart'; part 'create_headline_event.dart'; @@ -16,11 +18,9 @@ class CreateHeadlineBloc required DataRepository headlinesRepository, required DataRepository sourcesRepository, required DataRepository topicsRepository, - required DataRepository countriesRepository, }) : _headlinesRepository = headlinesRepository, _sourcesRepository = sourcesRepository, _topicsRepository = topicsRepository, - _countriesRepository = countriesRepository, super(const CreateHeadlineState()) { on(_onDataLoaded); on(_onTitleChanged); @@ -37,7 +37,6 @@ class CreateHeadlineBloc final DataRepository _headlinesRepository; final DataRepository _sourcesRepository; final DataRepository _topicsRepository; - final DataRepository _countriesRepository; final _uuid = const Uuid(); Future _onDataLoaded( @@ -49,7 +48,6 @@ class CreateHeadlineBloc final [ sourcesResponse, topicsResponse, - countriesResponse, ] = await Future.wait([ _sourcesRepository.readAll( sort: [const SortOption('updatedAt', SortOrder.desc)], @@ -57,21 +55,16 @@ class CreateHeadlineBloc _topicsRepository.readAll( sort: [const SortOption('updatedAt', SortOrder.desc)], ), - _countriesRepository.readAll( - sort: [const SortOption('updatedAt', SortOrder.desc)], - ), ]); final sources = (sourcesResponse as PaginatedResponse).items; final topics = (topicsResponse as PaginatedResponse).items; - final countries = (countriesResponse as PaginatedResponse).items; emit( state.copyWith( status: CreateHeadlineStatus.initial, sources: sources, topics: topics, - countries: countries, ), ); } on HttpException catch (e) { @@ -132,7 +125,13 @@ class CreateHeadlineBloc CreateHeadlineCountryChanged event, Emitter emit, ) { - emit(state.copyWith(eventCountry: () => event.country)); + final packageCountry = event.country; + if (packageCountry == null) { + emit(state.copyWith(eventCountry: () => null)); + } else { + final coreCountry = adaptPackageCountryToCoreCountry(packageCountry); + emit(state.copyWith(eventCountry: () => coreCountry)); + } } void _onStatusChanged( 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..a600672 100644 --- a/lib/content_management/bloc/create_headline/create_headline_event.dart +++ b/lib/content_management/bloc/create_headline/create_headline_event.dart @@ -64,7 +64,7 @@ final class CreateHeadlineTopicChanged extends CreateHeadlineEvent { /// Event for when the headline's country is changed. final class CreateHeadlineCountryChanged extends CreateHeadlineEvent { const CreateHeadlineCountryChanged(this.country); - final Country? country; + final picker.Country? country; @override List get props => [country]; } diff --git a/lib/content_management/bloc/create_headline/create_headline_state.dart b/lib/content_management/bloc/create_headline/create_headline_state.dart index 0739db1..2ff304f 100644 --- a/lib/content_management/bloc/create_headline/create_headline_state.dart +++ b/lib/content_management/bloc/create_headline/create_headline_state.dart @@ -31,7 +31,6 @@ final class CreateHeadlineState extends Equatable { this.eventCountry, this.sources = const [], this.topics = const [], - this.countries = const [], this.contentStatus = ContentStatus.active, this.exception, this.createdHeadline, @@ -47,7 +46,6 @@ final class CreateHeadlineState extends Equatable { final Country? eventCountry; final List sources; final List topics; - final List countries; final ContentStatus contentStatus; final HttpException? exception; final Headline? createdHeadline; @@ -73,7 +71,6 @@ final class CreateHeadlineState extends Equatable { ValueGetter? eventCountry, List? sources, List? topics, - List? countries, ContentStatus? contentStatus, HttpException? exception, Headline? createdHeadline, @@ -89,7 +86,6 @@ final class CreateHeadlineState extends Equatable { eventCountry: eventCountry != null ? eventCountry() : this.eventCountry, sources: sources ?? this.sources, topics: topics ?? this.topics, - countries: countries ?? this.countries, contentStatus: contentStatus ?? this.contentStatus, exception: exception, createdHeadline: createdHeadline ?? this.createdHeadline, @@ -108,7 +104,6 @@ final class CreateHeadlineState extends Equatable { eventCountry, sources, topics, - countries, contentStatus, exception, createdHeadline, 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 695ce5d..a6c16cd 100644 --- a/lib/content_management/bloc/create_source/create_source_bloc.dart +++ b/lib/content_management/bloc/create_source/create_source_bloc.dart @@ -1,8 +1,10 @@ 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:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter_news_app_web_dashboard_full_source_code/shared/shared.dart'; import 'package:uuid/uuid.dart'; part 'create_source_event.dart'; @@ -13,9 +15,7 @@ class CreateSourceBloc extends Bloc { /// {@macro create_source_bloc} CreateSourceBloc({ required DataRepository sourcesRepository, - required DataRepository countriesRepository, }) : _sourcesRepository = sourcesRepository, - _countriesRepository = countriesRepository, super(const CreateSourceState()) { on(_onDataLoaded); on(_onNameChanged); @@ -29,36 +29,15 @@ class CreateSourceBloc extends Bloc { } final DataRepository _sourcesRepository; - final DataRepository _countriesRepository; final _uuid = const Uuid(); Future _onDataLoaded( CreateSourceDataLoaded event, Emitter emit, ) async { - emit(state.copyWith(status: CreateSourceStatus.loading)); - try { - final countriesResponse = await _countriesRepository.readAll( - sort: [const SortOption('updatedAt', SortOrder.desc)], - ); - final countries = countriesResponse.items; - - emit( - state.copyWith( - status: CreateSourceStatus.initial, - countries: countries, - ), - ); - } on HttpException catch (e) { - emit(state.copyWith(status: CreateSourceStatus.failure, exception: e)); - } catch (e) { - emit( - state.copyWith( - status: CreateSourceStatus.failure, - exception: UnknownException('An unexpected error occurred: $e'), - ), - ); - } + // This event is now a no-op since we don't need to load countries. + // We just ensure the BLoC is in the initial state. + emit(state.copyWith(status: CreateSourceStatus.initial)); } void _onNameChanged( @@ -100,7 +79,13 @@ class CreateSourceBloc extends Bloc { CreateSourceHeadquartersChanged event, Emitter emit, ) { - emit(state.copyWith(headquarters: () => event.headquarters)); + final packageCountry = event.headquarters; + if (packageCountry == null) { + emit(state.copyWith(headquarters: () => null)); + } else { + final coreCountry = adaptPackageCountryToCoreCountry(packageCountry); + emit(state.copyWith(headquarters: () => coreCountry)); + } } void _onStatusChanged( 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 8838dd6..2750166 100644 --- a/lib/content_management/bloc/create_source/create_source_event.dart +++ b/lib/content_management/bloc/create_source/create_source_event.dart @@ -56,7 +56,7 @@ final class CreateSourceLanguageChanged extends CreateSourceEvent { /// Event for when the source's headquarters is changed. final class CreateSourceHeadquartersChanged extends CreateSourceEvent { const CreateSourceHeadquartersChanged(this.headquarters); - final Country? headquarters; + final picker.Country? headquarters; @override List get props => [headquarters]; } 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 32f3657..2d38ce4 100644 --- a/lib/content_management/bloc/create_source/create_source_state.dart +++ b/lib/content_management/bloc/create_source/create_source_state.dart @@ -29,7 +29,6 @@ final class CreateSourceState extends Equatable { this.sourceType, this.language = '', this.headquarters, - this.countries = const [], this.contentStatus = ContentStatus.active, this.exception, this.createdSource, @@ -42,7 +41,6 @@ final class CreateSourceState extends Equatable { final SourceType? sourceType; final String language; final Country? headquarters; - final List countries; final ContentStatus contentStatus; final HttpException? exception; final Source? createdSource; @@ -64,7 +62,6 @@ final class CreateSourceState extends Equatable { ValueGetter? sourceType, String? language, ValueGetter? headquarters, - List? countries, ContentStatus? contentStatus, HttpException? exception, Source? createdSource, @@ -77,7 +74,6 @@ final class CreateSourceState extends Equatable { sourceType: sourceType != null ? sourceType() : this.sourceType, language: language ?? this.language, headquarters: headquarters != null ? headquarters() : this.headquarters, - countries: countries ?? this.countries, contentStatus: contentStatus ?? this.contentStatus, exception: exception, createdSource: createdSource ?? this.createdSource, @@ -93,7 +89,6 @@ final class CreateSourceState extends Equatable { sourceType, language, headquarters, - countries, contentStatus, exception, createdSource, 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 30bcbbd..34fd165 100644 --- a/lib/content_management/bloc/edit_headline/edit_headline_bloc.dart +++ b/lib/content_management/bloc/edit_headline/edit_headline_bloc.dart @@ -1,8 +1,10 @@ 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:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter_news_app_web_dashboard_full_source_code/shared/shared.dart'; part 'edit_headline_event.dart'; part 'edit_headline_state.dart'; @@ -14,12 +16,10 @@ class EditHeadlineBloc extends Bloc { required DataRepository headlinesRepository, required DataRepository sourcesRepository, required DataRepository topicsRepository, - required DataRepository countriesRepository, required String headlineId, }) : _headlinesRepository = headlinesRepository, _sourcesRepository = sourcesRepository, _topicsRepository = topicsRepository, - _countriesRepository = countriesRepository, _headlineId = headlineId, super(const EditHeadlineState()) { on(_onLoaded); @@ -37,7 +37,6 @@ class EditHeadlineBloc extends Bloc { final DataRepository _headlinesRepository; final DataRepository _sourcesRepository; final DataRepository _topicsRepository; - final DataRepository _countriesRepository; final String _headlineId; Future _onLoaded( @@ -50,7 +49,6 @@ class EditHeadlineBloc extends Bloc { headlineResponse, sourcesResponse, topicsResponse, - countriesResponse, ] = await Future.wait([ _headlinesRepository.read(id: _headlineId), _sourcesRepository.readAll( @@ -59,15 +57,11 @@ class EditHeadlineBloc extends Bloc { _topicsRepository.readAll( sort: [const SortOption('updatedAt', SortOrder.desc)], ), - _countriesRepository.readAll( - sort: [const SortOption('updatedAt', SortOrder.desc)], - ), ]); final headline = headlineResponse as Headline; final sources = (sourcesResponse as PaginatedResponse).items; final topics = (topicsResponse as PaginatedResponse).items; - final countries = (countriesResponse as PaginatedResponse).items; emit( state.copyWith( @@ -82,7 +76,6 @@ class EditHeadlineBloc extends Bloc { eventCountry: () => headline.eventCountry, sources: sources, topics: topics, - countries: countries, contentStatus: headline.status, ), ); @@ -166,12 +159,16 @@ class EditHeadlineBloc extends Bloc { EditHeadlineCountryChanged event, Emitter emit, ) { - emit( - state.copyWith( - eventCountry: () => event.country, - status: EditHeadlineStatus.initial, - ), - ); + final packageCountry = event.country; + if (packageCountry == null) { + emit(state.copyWith(eventCountry: () => null)); + } else { + final coreCountry = adaptPackageCountryToCoreCountry(packageCountry); + emit( + state.copyWith( + eventCountry: () => coreCountry, status: EditHeadlineStatus.initial), + ); + } } void _onStatusChanged( 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..933ae8f 100644 --- a/lib/content_management/bloc/edit_headline/edit_headline_event.dart +++ b/lib/content_management/bloc/edit_headline/edit_headline_event.dart @@ -64,7 +64,7 @@ final class EditHeadlineTopicChanged extends EditHeadlineEvent { /// Event for when the headline's country is changed. final class EditHeadlineCountryChanged extends EditHeadlineEvent { const EditHeadlineCountryChanged(this.country); - final Country? country; + final picker.Country? country; @override List get props => [country]; } diff --git a/lib/content_management/bloc/edit_headline/edit_headline_state.dart b/lib/content_management/bloc/edit_headline/edit_headline_state.dart index fba983f..024c230 100644 --- a/lib/content_management/bloc/edit_headline/edit_headline_state.dart +++ b/lib/content_management/bloc/edit_headline/edit_headline_state.dart @@ -32,7 +32,6 @@ final class EditHeadlineState extends Equatable { this.eventCountry, this.sources = const [], this.topics = const [], - this.countries = const [], this.contentStatus = ContentStatus.active, this.exception, this.updatedHeadline, @@ -49,7 +48,6 @@ final class EditHeadlineState extends Equatable { final Country? eventCountry; final List sources; final List topics; - final List countries; final ContentStatus contentStatus; final HttpException? exception; final Headline? updatedHeadline; @@ -76,7 +74,6 @@ final class EditHeadlineState extends Equatable { ValueGetter? eventCountry, List? sources, List? topics, - List? countries, ContentStatus? contentStatus, HttpException? exception, Headline? updatedHeadline, @@ -93,7 +90,6 @@ final class EditHeadlineState extends Equatable { eventCountry: eventCountry != null ? eventCountry() : this.eventCountry, sources: sources ?? this.sources, topics: topics ?? this.topics, - countries: countries ?? this.countries, contentStatus: contentStatus ?? this.contentStatus, exception: exception, updatedHeadline: updatedHeadline ?? this.updatedHeadline, @@ -113,7 +109,6 @@ final class EditHeadlineState extends Equatable { eventCountry, sources, topics, - countries, contentStatus, exception, updatedHeadline, 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 6eca88c..1a5d629 100644 --- a/lib/content_management/bloc/edit_source/edit_source_bloc.dart +++ b/lib/content_management/bloc/edit_source/edit_source_bloc.dart @@ -1,9 +1,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: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'; part 'edit_source_event.dart'; part 'edit_source_state.dart'; @@ -13,10 +15,8 @@ class EditSourceBloc extends Bloc { /// {@macro edit_source_bloc} EditSourceBloc({ required DataRepository sourcesRepository, - required DataRepository countriesRepository, required String sourceId, }) : _sourcesRepository = sourcesRepository, - _countriesRepository = countriesRepository, _sourceId = sourceId, super(const EditSourceState()) { on(_onLoaded); @@ -31,7 +31,6 @@ class EditSourceBloc extends Bloc { } final DataRepository _sourcesRepository; - final DataRepository _countriesRepository; final String _sourceId; Future _onLoaded( @@ -40,14 +39,7 @@ class EditSourceBloc extends Bloc { ) async { emit(state.copyWith(status: EditSourceStatus.loading)); try { - final [sourceResponse, countriesResponse] = await Future.wait([ - _sourcesRepository.read(id: _sourceId), - _countriesRepository.readAll( - sort: [const SortOption('updatedAt', SortOrder.desc)], - ), - ]); - final source = sourceResponse as Source; - final countries = (countriesResponse as PaginatedResponse).items; + final source = await _sourcesRepository.read(id: _sourceId); emit( state.copyWith( status: EditSourceStatus.initial, @@ -59,7 +51,6 @@ class EditSourceBloc extends Bloc { language: source.language, headquarters: () => source.headquarters, contentStatus: source.status, - countries: countries, ), ); } on HttpException catch (e) { @@ -128,12 +119,16 @@ class EditSourceBloc extends Bloc { EditSourceHeadquartersChanged event, Emitter emit, ) { - emit( - state.copyWith( - headquarters: () => event.headquarters, - status: EditSourceStatus.initial, - ), - ); + final packageCountry = event.headquarters; + if (packageCountry == null) { + emit(state.copyWith(headquarters: () => null)); + } else { + final coreCountry = adaptPackageCountryToCoreCountry(packageCountry); + emit( + state.copyWith( + headquarters: () => coreCountry, status: EditSourceStatus.initial), + ); + } } void _onStatusChanged( 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 6a7246a..21aa933 100644 --- a/lib/content_management/bloc/edit_source/edit_source_event.dart +++ b/lib/content_management/bloc/edit_source/edit_source_event.dart @@ -67,7 +67,7 @@ final class EditSourceLanguageChanged extends EditSourceEvent { final class EditSourceHeadquartersChanged extends EditSourceEvent { const EditSourceHeadquartersChanged(this.headquarters); - final Country? headquarters; + final picker.Country? headquarters; @override List get props => [headquarters]; 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 8610f46..d04d311 100644 --- a/lib/content_management/bloc/edit_source/edit_source_state.dart +++ b/lib/content_management/bloc/edit_source/edit_source_state.dart @@ -29,7 +29,6 @@ final class EditSourceState extends Equatable { this.sourceType, this.language = '', this.headquarters, - this.countries = const [], this.contentStatus = ContentStatus.active, this.exception, this.updatedSource, @@ -43,7 +42,6 @@ final class EditSourceState extends Equatable { final SourceType? sourceType; final String language; final Country? headquarters; - final List countries; final ContentStatus contentStatus; final HttpException? exception; final Source? updatedSource; @@ -66,7 +64,6 @@ final class EditSourceState extends Equatable { ValueGetter? sourceType, String? language, ValueGetter? headquarters, - List? countries, ContentStatus? contentStatus, HttpException? exception, Source? updatedSource, @@ -80,7 +77,6 @@ final class EditSourceState extends Equatable { sourceType: sourceType != null ? sourceType() : this.sourceType, language: language ?? this.language, headquarters: headquarters != null ? headquarters() : this.headquarters, - countries: countries ?? this.countries, contentStatus: contentStatus ?? this.contentStatus, exception: exception, updatedSource: updatedSource ?? this.updatedSource, @@ -97,7 +93,6 @@ final class EditSourceState extends Equatable { sourceType, language, headquarters, - countries, contentStatus, exception, updatedSource, diff --git a/lib/content_management/view/create_headline_page.dart b/lib/content_management/view/create_headline_page.dart index ec040bc..ae795cb 100644 --- a/lib/content_management/view/create_headline_page.dart +++ b/lib/content_management/view/create_headline_page.dart @@ -1,4 +1,5 @@ import 'package:core/core.dart'; +import 'package:country_picker/country_picker.dart' as picker; import 'package:data_repository/data_repository.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -24,7 +25,6 @@ class CreateHeadlinePage extends StatelessWidget { headlinesRepository: context.read>(), sourcesRepository: context.read>(), topicsRepository: context.read>(), - countriesRepository: context.read>(), )..add(const CreateHeadlineDataLoaded()), child: const _CreateHeadlineView(), ); @@ -112,8 +112,7 @@ class _CreateHeadlineViewState extends State<_CreateHeadlineView> { if (state.status == CreateHeadlineStatus.failure && state.sources.isEmpty && - state.topics.isEmpty && - state.countries.isEmpty) { + state.topics.isEmpty) { return FailureStateWidget( exception: state.exception!, onRetry: () => context.read().add( @@ -215,24 +214,18 @@ class _CreateHeadlineViewState extends State<_CreateHeadlineView> { .add(CreateHeadlineTopicChanged(value)), ), const SizedBox(height: AppSpacing.lg), - DropdownButtonFormField( - value: state.eventCountry, - decoration: InputDecoration( - labelText: l10n.countryName, - border: const OutlineInputBorder(), - ), - items: [ - DropdownMenuItem(value: null, child: Text(l10n.none)), - ...state.countries.map( - (country) => DropdownMenuItem( - value: country, - child: Text(country.name), - ), - ), - ], - onChanged: (value) => context - .read() - .add(CreateHeadlineCountryChanged(value)), + CountryPickerFormField( + labelText: l10n.countryName, + initialValue: state.eventCountry != null + ? adaptCoreCountryToPackageCountry( + state.eventCountry!, + ) + : null, + onChanged: (picker.Country country) { + context.read().add( + CreateHeadlineCountryChanged(country), + ); + }, ), const SizedBox(height: AppSpacing.lg), DropdownButtonFormField( diff --git a/lib/content_management/view/create_source_page.dart b/lib/content_management/view/create_source_page.dart index a9961bf..728b97a 100644 --- a/lib/content_management/view/create_source_page.dart +++ b/lib/content_management/view/create_source_page.dart @@ -1,4 +1,5 @@ import 'package:core/core.dart'; +import 'package:country_picker/country_picker.dart' as picker; import 'package:data_repository/data_repository.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -23,7 +24,6 @@ class CreateSourcePage extends StatelessWidget { return BlocProvider( create: (context) => CreateSourceBloc( sourcesRepository: context.read>(), - countriesRepository: context.read>(), )..add(const CreateSourceDataLoaded()), child: const _CreateSourceView(), ); @@ -109,8 +109,7 @@ class _CreateSourceViewState extends State<_CreateSourceView> { ); } - if (state.status == CreateSourceStatus.failure && - state.countries.isEmpty) { + if (state.status == CreateSourceStatus.failure) { return FailureStateWidget( exception: state.exception!, onRetry: () => context.read().add( @@ -192,24 +191,18 @@ class _CreateSourceViewState extends State<_CreateSourceView> { .add(CreateSourceTypeChanged(value)), ), const SizedBox(height: AppSpacing.lg), - DropdownButtonFormField( - value: state.headquarters, - decoration: InputDecoration( - labelText: l10n.headquarters, - border: const OutlineInputBorder(), - ), - items: [ - DropdownMenuItem(value: null, child: Text(l10n.none)), - ...state.countries.map( - (country) => DropdownMenuItem( - value: country, - child: Text(country.name), - ), - ), - ], - onChanged: (value) => context - .read() - .add(CreateSourceHeadquartersChanged(value)), + CountryPickerFormField( + labelText: l10n.headquarters, + initialValue: state.headquarters != null + ? adaptCoreCountryToPackageCountry( + state.headquarters!, + ) + : null, + onChanged: (picker.Country country) { + context.read().add( + CreateSourceHeadquartersChanged(country), + ); + }, ), const SizedBox(height: AppSpacing.lg), DropdownButtonFormField( diff --git a/lib/content_management/view/edit_headline_page.dart b/lib/content_management/view/edit_headline_page.dart index be64450..b2caf30 100644 --- a/lib/content_management/view/edit_headline_page.dart +++ b/lib/content_management/view/edit_headline_page.dart @@ -1,4 +1,5 @@ import 'package:core/core.dart'; +import 'package:country_picker/country_picker.dart' as picker; import 'package:data_repository/data_repository.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -27,7 +28,6 @@ class EditHeadlinePage extends StatelessWidget { headlinesRepository: context.read>(), sourcesRepository: context.read>(), topicsRepository: context.read>(), - countriesRepository: context.read>(), headlineId: headlineId, )..add(const EditHeadlineLoaded()), child: const _EditHeadlineView(), @@ -178,17 +178,6 @@ class _EditHeadlineViewState extends State<_EditHeadlineView> { } } - 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), @@ -282,24 +271,18 @@ class _EditHeadlineViewState extends State<_EditHeadlineView> { .add(EditHeadlineTopicChanged(value)), ), const SizedBox(height: AppSpacing.lg), - DropdownButtonFormField( - value: selectedCountry, - decoration: InputDecoration( - labelText: l10n.countryName, - border: const OutlineInputBorder(), - ), - items: [ - DropdownMenuItem(value: null, child: Text(l10n.none)), - ...state.countries.map( - (country) => DropdownMenuItem( - value: country, - child: Text(country.name), - ), - ), - ], - onChanged: (value) => context - .read() - .add(EditHeadlineCountryChanged(value)), + CountryPickerFormField( + labelText: l10n.countryName, + initialValue: state.eventCountry != null + ? adaptCoreCountryToPackageCountry( + state.eventCountry!, + ) + : null, + onChanged: (picker.Country country) { + context.read().add( + EditHeadlineCountryChanged(country), + ); + }, ), const SizedBox(height: AppSpacing.lg), DropdownButtonFormField( diff --git a/lib/content_management/view/edit_source_page.dart b/lib/content_management/view/edit_source_page.dart index 2c53a2a..9bf0dd8 100644 --- a/lib/content_management/view/edit_source_page.dart +++ b/lib/content_management/view/edit_source_page.dart @@ -1,4 +1,5 @@ import 'package:core/core.dart'; +import 'package:country_picker/country_picker.dart' as picker; import 'package:data_repository/data_repository.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -25,7 +26,6 @@ class EditSourcePage extends StatelessWidget { return BlocProvider( create: (context) => EditSourceBloc( sourcesRepository: context.read>(), - countriesRepository: context.read>(), sourceId: sourceId, )..add(const EditSourceLoaded()), child: const _EditSourceView(), @@ -151,19 +151,6 @@ class _EditSourceViewState extends State<_EditSourceView> { ); } - // Find the correct Country instance from the list to ensure - // the Dropdown can display the selection correctly. - Country? selectedHeadquarters; - if (state.headquarters != null) { - try { - selectedHeadquarters = state.countries.firstWhere( - (c) => c.id == state.headquarters!.id, - ); - } catch (_) { - selectedHeadquarters = null; - } - } - return SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(AppSpacing.lg), @@ -237,24 +224,18 @@ class _EditSourceViewState extends State<_EditSourceView> { ), ), const SizedBox(height: AppSpacing.lg), - DropdownButtonFormField( - value: selectedHeadquarters, - decoration: InputDecoration( - labelText: l10n.headquarters, - border: const OutlineInputBorder(), - ), - items: [ - DropdownMenuItem(value: null, child: Text(l10n.none)), - ...state.countries.map( - (country) => DropdownMenuItem( - value: country, - child: Text(country.name), - ), - ), - ], - onChanged: (value) => context.read().add( - EditSourceHeadquartersChanged(value), - ), + CountryPickerFormField( + labelText: l10n.headquarters, + initialValue: state.headquarters != null + ? adaptCoreCountryToPackageCountry( + state.headquarters!, + ) + : null, + onChanged: (picker.Country country) { + context.read().add( + EditSourceHeadquartersChanged(country), + ); + }, ), const SizedBox(height: AppSpacing.lg), DropdownButtonFormField( diff --git a/lib/shared/shared.dart b/lib/shared/shared.dart index 9338530..39c64fc 100644 --- a/lib/shared/shared.dart +++ b/lib/shared/shared.dart @@ -1,7 +1,3 @@ -/// Barrel file for the shared library. -/// -/// Exports common constants, theme elements, and widgets used across -/// the application to promote consistency and maintainability. -library; - export 'extensions/extensions.dart'; +export 'utils/utils.dart'; +export 'widgets/widgets.dart'; diff --git a/lib/shared/utils/country_adapter.dart b/lib/shared/utils/country_adapter.dart new file mode 100644 index 0000000..b0db818 --- /dev/null +++ b/lib/shared/utils/country_adapter.dart @@ -0,0 +1,34 @@ +import 'package:core/core.dart' as core; +import 'package:country_picker/country_picker.dart' as picker; + +/// Adapts a [picker.Country] from the `country_picker` package to a +/// [core.Country] model. +/// +/// This is used when a user selects a country from the picker and we need to +/// update our application state with our internal [core.Country] model. +core.Country adaptPackageCountryToCoreCountry(picker.Country packageCountry) { + final now = DateTime.now(); + return core.Country( + // Use the ISO code as a unique, deterministic ID, since we are no longer + // fetching countries as entities from a database. + id: packageCountry.countryCode, + isoCode: packageCountry.countryCode, + name: packageCountry.name, + // Construct a flag URL from a public CDN using the ISO code. + flagUrl: + 'https://flagcdn.com/h40/${packageCountry.countryCode.toLowerCase()}.png', + createdAt: now, + updatedAt: now, + status: core.ContentStatus.active, + ); +} + +/// Adapts a [core.Country] model to a [picker.Country] from the +/// `country_picker` package. +/// +/// This is used when we have an existing [core.Country] (e.g., when editing a +/// headline) and need to display it in the UI using the `country_picker` +/// package's widgets. +picker.Country adaptCoreCountryToPackageCountry(core.Country coreCountry) { + return picker.Country.parse(coreCountry.isoCode); +} diff --git a/lib/shared/utils/utils.dart b/lib/shared/utils/utils.dart new file mode 100644 index 0000000..d79c0e8 --- /dev/null +++ b/lib/shared/utils/utils.dart @@ -0,0 +1 @@ +export 'country_adapter.dart'; diff --git a/lib/shared/widgets/country_picker_form_field.dart b/lib/shared/widgets/country_picker_form_field.dart new file mode 100644 index 0000000..7057473 --- /dev/null +++ b/lib/shared/widgets/country_picker_form_field.dart @@ -0,0 +1,90 @@ +import 'package:country_picker/country_picker.dart' as picker; +import 'package:flutter/material.dart'; +import 'package:ui_kit/ui_kit.dart'; + +/// A form field for selecting a country using the `country_picker` package. +class CountryPickerFormField extends StatelessWidget { + /// Creates a [CountryPickerFormField]. + const CountryPickerFormField({ + required this.onChanged, + this.initialValue, + this.labelText, + super.key, + }); + + /// The currently selected country. Can be null. + final picker.Country? initialValue; + + /// Callback function that is called when a new country is selected. + final ValueChanged onChanged; + + /// The text to display as the label for the form field. + final String? labelText; + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: () { + picker.showCountryPicker( + context: context, + onSelect: onChanged, + countryListTheme: picker.CountryListThemeData( + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + textStyle: Theme.of(context).textTheme.bodyMedium, + bottomSheetHeight: MediaQuery.of(context).size.height * 0.8, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(AppSpacing.md), + topRight: Radius.circular(AppSpacing.md), + ), + inputDecoration: InputDecoration( + labelText: 'Search', // TODO(fulleni): Localize this string + hintText: + 'Start typing to search...', // TODO(fulleni): Localize this + prefixIcon: const Icon(Icons.search), + border: OutlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).colorScheme.primary.withOpacity(0.2), + ), + ), + ), + ), + ); + }, + child: InputDecorator( + decoration: InputDecoration( + labelText: labelText, + border: const OutlineInputBorder(), + contentPadding: const EdgeInsets.symmetric( + horizontal: AppSpacing.md, + vertical: AppSpacing.md, + ), + ), + child: Row( + children: [ + if (initialValue != null) ...[ + Text(initialValue!.flagEmoji, style: const TextStyle(fontSize: 24)), + const SizedBox(width: AppSpacing.md), + Expanded( + child: Text( + initialValue!.name, + style: Theme.of(context).textTheme.bodyLarge, + overflow: TextOverflow.ellipsis, + ), + ), + ] else + Expanded( + child: Text( + 'Select a country', // TODO(you): Localize this string + style: Theme.of(context) + .textTheme + .bodyLarge + ?.copyWith(color: Theme.of(context).hintColor), + ), + ), + const Icon(Icons.arrow_drop_down), + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/shared/widgets/widgets.dart b/lib/shared/widgets/widgets.dart new file mode 100644 index 0000000..a8dd41f --- /dev/null +++ b/lib/shared/widgets/widgets.dart @@ -0,0 +1 @@ +export 'country_picker_form_field.dart'; diff --git a/pubspec.lock b/pubspec.lock index 01f37a5..ac2eca8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -94,6 +94,14 @@ packages: url: "https://github.com/flutter-news-app-full-source-code/core.git" source: git version: "0.0.0" + country_picker: + dependency: "direct main" + description: + name: country_picker + sha256: "9b14c04f9a35e99f6de6bcbc453a556bb98345aecb481c7a0e843c94c2bee1f8" + url: "https://pub.dev" + source: hosted + version: "2.0.27" crypto: dependency: transitive description: @@ -586,6 +594,14 @@ packages: url: "https://github.com/flutter-news-app-full-source-code/ui-kit.git" source: git version: "0.0.0" + universal_io: + dependency: transitive + description: + name: universal_io + sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad" + url: "https://pub.dev" + source: hosted + version: "2.2.2" universal_platform: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 173d062..3ebff7c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -24,6 +24,7 @@ dependencies: core: git: url: https://github.com/flutter-news-app-full-source-code/core.git + country_picker: ^2.0.27 data_api: git: url: https://github.com/flutter-news-app-full-source-code/data-api.git