Skip to content

Migrate from country and langauge picker to internal solution #42

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
7e1ab2a
build(): remove unused dependencies
fulleni Jul 31, 2025
d8a7e84
chore(deps): update core dependency
fulleni Jul 31, 2025
163e24f
refactor(shared): remove unused utils and their adapters
fulleni Jul 31, 2025
020ae33
feat(bootstrap): add countries and languages data clients and reposit…
fulleni Jul 31, 2025
632b11e
feat(app): inject countries and languages repositories
fulleni Jul 31, 2025
d8672da
feat(content_management): add country data to create headline state
fulleni Jul 31, 2025
0dd71a8
feat(content_management): add countries list to CreateHeadlineState
fulleni Jul 31, 2025
673d45b
refactor(content_management): simplify country change handling
fulleni Jul 31, 2025
41c3e15
feat(content): refactor CreateSourceBloc to fetch API data
fulleni Jul 31, 2025
4088728
feat(content): update CreateSourceState to hold API data
fulleni Jul 31, 2025
d81f5a5
feat(content): update CreateSourceEvent to use core models
fulleni Jul 31, 2025
1677b0f
feat(content): provide repositories to CreateSourceBloc
fulleni Jul 31, 2025
17a7236
feat(content): refactor EditSourceBloc to fetch API data
fulleni Jul 31, 2025
26ee34e
feat(content): update EditSourceState to hold API data
fulleni Jul 31, 2025
e71c366
feat(content): provide repositories to EditSourceBloc
fulleni Jul 31, 2025
6fe021b
feat(content): refactor EditHeadlineBloc to fetch API data
fulleni Jul 31, 2025
6166a40
feat(content): update EditHeadlineEvent to use core models
fulleni Jul 31, 2025
480af3a
feat(content): provide repositories to EditHeadlineBloc
fulleni Jul 31, 2025
26f37e3
refactor(content_management): update Country reference in CreateHeadl…
fulleni Jul 31, 2025
5026065
refactor: remove unused import
fulleni Jul 31, 2025
8983f72
feat(shared): create reusable CountryDropdownFormField
fulleni Jul 31, 2025
a1eac34
chore: misc
fulleni Jul 31, 2025
12a4a33
feat(ui): add LanguageDropdownFormField widget
fulleni Jul 31, 2025
25aed47
refactor(content_management): update form fields in create source page
fulleni Jul 31, 2025
e11d351
refactor(content_management): replace custom country/language pickers
fulleni Jul 31, 2025
b185811
refactor(widgets): reorganize widgets library and add language dropdo…
fulleni Jul 31, 2025
a3da1ef
fix(content_management): remove implicit call and improve repository …
fulleni Jul 31, 2025
929d752
feat(content_management): replace CountryPicker with CountryDropdown
fulleni Jul 31, 2025
3167aa9
refactor(content_management): replace CountryPickerFormField with Cou…
fulleni Jul 31, 2025
d412df0
lint: misc
fulleni Jul 31, 2025
7a01f8d
feat(l10n): add localization support for SourceType enum
fulleni Jul 31, 2025
34bdf07
refactor: remove unused SourceTypeL10n extension
fulleni Jul 31, 2025
17396d6
feat(content_management): add source_type_l10n import to create_sourc…
fulleni Jul 31, 2025
af30ff2
feat(content_management): add localization support for source types
fulleni Jul 31, 2025
86b8cb5
fix(content_management): ensure source language selection is correctl…
fulleni Jul 31, 2025
d7d00de
fix(content_management): use language code directly in CreateSourceBloc
fulleni Jul 31, 2025
63ab07a
fix(content_management): use language code directly in EditSourceBloc
fulleni Jul 31, 2025
b788018
refactor(content_management): update import statements
fulleni Jul 31, 2025
8c68568
lint: misc
fulleni Jul 31, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 19 additions & 11 deletions lib/app/view/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,29 +32,35 @@ class App extends StatelessWidget {
userContentPreferencesRepository,
required DataRepository<RemoteConfig> remoteConfigRepository,
required DataRepository<DashboardSummary> dashboardSummaryRepository,
required DataRepository<Country> countriesRepository,
required DataRepository<Language> languagesRepository,
required KVStorageService storageService,
required AppEnvironment environment,
super.key,
}) : _authenticationRepository = authenticationRepository,
_headlinesRepository = headlinesRepository,
_topicsRepository = topicsRepository,
_sourcesRepository = sourcesRepository,
_userAppSettingsRepository = userAppSettingsRepository,
_userContentPreferencesRepository = userContentPreferencesRepository,
_remoteConfigRepository = remoteConfigRepository,
_kvStorageService = storageService,
_dashboardSummaryRepository = dashboardSummaryRepository,
_environment = environment;
}) : _authenticationRepository = authenticationRepository,
_headlinesRepository = headlinesRepository,
_topicsRepository = topicsRepository,
_sourcesRepository = sourcesRepository,
_userAppSettingsRepository = userAppSettingsRepository,
_userContentPreferencesRepository = userContentPreferencesRepository,
_remoteConfigRepository = remoteConfigRepository,
_kvStorageService = storageService,
_dashboardSummaryRepository = dashboardSummaryRepository,
_countriesRepository = countriesRepository,
_languagesRepository = languagesRepository,
_environment = environment;

final AuthRepository _authenticationRepository;
final DataRepository<Headline> _headlinesRepository;
final DataRepository<Topic> _topicsRepository;
final DataRepository<Source> _sourcesRepository;
final DataRepository<UserAppSettings> _userAppSettingsRepository;
final DataRepository<UserContentPreferences>
_userContentPreferencesRepository;
_userContentPreferencesRepository;
final DataRepository<RemoteConfig> _remoteConfigRepository;
final DataRepository<DashboardSummary> _dashboardSummaryRepository;
final DataRepository<Country> _countriesRepository;
final DataRepository<Language> _languagesRepository;
final KVStorageService _kvStorageService;
final AppEnvironment _environment;

Expand All @@ -70,6 +76,8 @@ class App extends StatelessWidget {
RepositoryProvider.value(value: _userContentPreferencesRepository),
RepositoryProvider.value(value: _remoteConfigRepository),
RepositoryProvider.value(value: _dashboardSummaryRepository),
RepositoryProvider.value(value: _countriesRepository),
RepositoryProvider.value(value: _languagesRepository),
RepositoryProvider.value(value: _kvStorageService),
],
child: MultiBlocProvider(
Expand Down
48 changes: 48 additions & 0 deletions lib/bootstrap.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ Future<Widget> bootstrap(
DataClient<UserAppSettings> userAppSettingsClient;
DataClient<RemoteConfig> remoteConfigClient;
DataClient<DashboardSummary> dashboardSummaryClient;
DataClient<Country> countriesClient;
DataClient<Language> languagesClient;

if (appConfig.environment == app_config.AppEnvironment.demo) {
headlinesClient = DataInMemory<Headline>(
Expand Down Expand Up @@ -102,6 +104,18 @@ Future<Widget> bootstrap(
initialData: dashboardSummaryFixturesData,
logger: Logger('DataInMemory<DashboardSummary>'),
);
countriesClient = DataInMemory<Country>(
toJson: (i) => i.toJson(),
getId: (i) => i.id,
initialData: countriesFixturesData,
logger: Logger('DataInMemory<Country>'),
);
languagesClient = DataInMemory<Language>(
toJson: (i) => i.toJson(),
getId: (i) => i.id,
initialData: languagesFixturesData,
logger: Logger('DataInMemory<Language>'),
);
} else if (appConfig.environment == app_config.AppEnvironment.development) {
headlinesClient = DataApi<Headline>(
httpClient: httpClient!,
Expand Down Expand Up @@ -152,6 +166,20 @@ Future<Widget> bootstrap(
toJson: (summary) => summary.toJson(),
logger: Logger('DataApi<DashboardSummary>'),
);
countriesClient = DataApi<Country>(
httpClient: httpClient,
modelName: 'country',
fromJson: Country.fromJson,
toJson: (country) => country.toJson(),
logger: Logger('DataApi<Country>'),
);
languagesClient = DataApi<Language>(
httpClient: httpClient,
modelName: 'language',
fromJson: Language.fromJson,
toJson: (language) => language.toJson(),
logger: Logger('DataApi<Language>'),
);
} else {
headlinesClient = DataApi<Headline>(
httpClient: httpClient!,
Expand Down Expand Up @@ -202,6 +230,20 @@ Future<Widget> bootstrap(
toJson: (summary) => summary.toJson(),
logger: Logger('DataApi<DashboardSummary>'),
);
countriesClient = DataApi<Country>(
httpClient: httpClient,
modelName: 'country',
fromJson: Country.fromJson,
toJson: (country) => country.toJson(),
logger: Logger('DataApi<Country>'),
);
languagesClient = DataApi<Language>(
httpClient: httpClient,
modelName: 'language',
fromJson: Language.fromJson,
toJson: (language) => language.toJson(),
logger: Logger('DataApi<Language>'),
);
}

final headlinesRepository = DataRepository<Headline>(
Expand All @@ -222,6 +264,10 @@ Future<Widget> bootstrap(
final dashboardSummaryRepository = DataRepository<DashboardSummary>(
dataClient: dashboardSummaryClient,
);
final countriesRepository =
DataRepository<Country>(dataClient: countriesClient);
final languagesRepository =
DataRepository<Language>(dataClient: languagesClient);

return App(
authenticationRepository: authenticationRepository,
Expand All @@ -232,6 +278,8 @@ Future<Widget> bootstrap(
userContentPreferencesRepository: userContentPreferencesRepository,
remoteConfigRepository: remoteConfigRepository,
dashboardSummaryRepository: dashboardSummaryRepository,
countriesRepository: countriesRepository,
languagesRepository: languagesRepository,
storageService: kvStorage,
environment: environment,
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
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';
Expand All @@ -18,10 +16,12 @@ class CreateHeadlineBloc
required DataRepository<Headline> headlinesRepository,
required DataRepository<Source> sourcesRepository,
required DataRepository<Topic> topicsRepository,
}) : _headlinesRepository = headlinesRepository,
_sourcesRepository = sourcesRepository,
_topicsRepository = topicsRepository,
super(const CreateHeadlineState()) {
required DataRepository<Country> countriesRepository,
}) : _headlinesRepository = headlinesRepository,
_sourcesRepository = sourcesRepository,
_topicsRepository = topicsRepository,
_countriesRepository = countriesRepository,
super(const CreateHeadlineState()) {
on<CreateHeadlineDataLoaded>(_onDataLoaded);
on<CreateHeadlineTitleChanged>(_onTitleChanged);
on<CreateHeadlineExcerptChanged>(_onExcerptChanged);
Expand All @@ -37,6 +37,7 @@ class CreateHeadlineBloc
final DataRepository<Headline> _headlinesRepository;
final DataRepository<Source> _sourcesRepository;
final DataRepository<Topic> _topicsRepository;
final DataRepository<Country> _countriesRepository;
final _uuid = const Uuid();

Future<void> _onDataLoaded(
Expand All @@ -48,23 +49,30 @@ class CreateHeadlineBloc
final [
sourcesResponse,
topicsResponse,
countriesResponse,
] = await Future.wait([
_sourcesRepository.readAll(
sort: [const SortOption('updatedAt', SortOrder.desc)],
),
_topicsRepository.readAll(
sort: [const SortOption('updatedAt', SortOrder.desc)],
),
_countriesRepository.readAll(
sort: [const SortOption('name', SortOrder.asc)],
),
]);

final sources = (sourcesResponse as PaginatedResponse<Source>).items;
final topics = (topicsResponse as PaginatedResponse<Topic>).items;
final countries =
(countriesResponse as PaginatedResponse<Country>).items;

emit(
state.copyWith(
status: CreateHeadlineStatus.initial,
sources: sources,
topics: topics,
countries: countries,
),
);
} on HttpException catch (e) {
Expand Down Expand Up @@ -125,13 +133,7 @@ class CreateHeadlineBloc
CreateHeadlineCountryChanged event,
Emitter<CreateHeadlineState> emit,
) {
final packageCountry = event.country;
if (packageCountry == null) {
emit(state.copyWith(eventCountry: () => null));
} else {
final coreCountry = adaptPackageCountryToCoreCountry(packageCountry);
emit(state.copyWith(eventCountry: () => coreCountry));
}
emit(state.copyWith(eventCountry: () => event.country));
}

void _onStatusChanged(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 picker.Country? country;
final Country? country;
@override
List<Object?> get props => [country];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ final class CreateHeadlineState extends Equatable {
this.eventCountry,
this.sources = const [],
this.topics = const [],
this.countries = const [],
this.contentStatus = ContentStatus.active,
this.exception,
this.createdHeadline,
Expand All @@ -46,6 +47,7 @@ final class CreateHeadlineState extends Equatable {
final Country? eventCountry;
final List<Source> sources;
final List<Topic> topics;
final List<Country> countries;
final ContentStatus contentStatus;
final HttpException? exception;
final Headline? createdHeadline;
Expand All @@ -71,6 +73,7 @@ final class CreateHeadlineState extends Equatable {
ValueGetter<Country?>? eventCountry,
List<Source>? sources,
List<Topic>? topics,
List<Country>? countries,
ContentStatus? contentStatus,
HttpException? exception,
Headline? createdHeadline,
Expand All @@ -86,6 +89,7 @@ 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,
Expand All @@ -104,6 +108,7 @@ final class CreateHeadlineState extends Equatable {
eventCountry,
sources,
topics,
countries,
contentStatus,
exception,
createdHeadline,
Expand Down
52 changes: 35 additions & 17 deletions lib/content_management/bloc/create_source/create_source_bloc.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
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/material.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/shared.dart';
import 'package:language_picker/languages.dart';
import 'package:flutter/foundation.dart';
import 'package:uuid/uuid.dart';

part 'create_source_event.dart';
Expand All @@ -16,8 +13,12 @@ class CreateSourceBloc extends Bloc<CreateSourceEvent, CreateSourceState> {
/// {@macro create_source_bloc}
CreateSourceBloc({
required DataRepository<Source> sourcesRepository,
}) : _sourcesRepository = sourcesRepository,
super(const CreateSourceState()) {
required DataRepository<Country> countriesRepository,
required DataRepository<Language> languagesRepository,
}) : _sourcesRepository = sourcesRepository,
_countriesRepository = countriesRepository,
_languagesRepository = languagesRepository,
super(const CreateSourceState()) {
on<CreateSourceDataLoaded>(_onDataLoaded);
on<CreateSourceNameChanged>(_onNameChanged);
on<CreateSourceDescriptionChanged>(_onDescriptionChanged);
Expand All @@ -30,15 +31,38 @@ class CreateSourceBloc extends Bloc<CreateSourceEvent, CreateSourceState> {
}

final DataRepository<Source> _sourcesRepository;
final DataRepository<Country> _countriesRepository;
final DataRepository<Language> _languagesRepository;
final _uuid = const Uuid();

Future<void> _onDataLoaded(
CreateSourceDataLoaded event,
Emitter<CreateSourceState> emit,
) async {
// 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));
emit(state.copyWith(status: CreateSourceStatus.loading));
try {
final [countriesResponse, languagesResponse] = await Future.wait([
_countriesRepository.readAll(
sort: [const SortOption('name', SortOrder.asc)],
),
_languagesRepository.readAll(
sort: [const SortOption('name', SortOrder.asc)],
),
]);

final countries = (countriesResponse as PaginatedResponse<Country>).items;
final languages = (languagesResponse as PaginatedResponse<Language>).items;

emit(
state.copyWith(
status: CreateSourceStatus.initial,
countries: countries,
languages: languages,
),
);
} catch (e) {
emit(state.copyWith(status: CreateSourceStatus.failure));
}
}

void _onNameChanged(
Expand Down Expand Up @@ -80,13 +104,7 @@ class CreateSourceBloc extends Bloc<CreateSourceEvent, CreateSourceState> {
CreateSourceHeadquartersChanged event,
Emitter<CreateSourceState> emit,
) {
final packageCountry = event.headquarters;
if (packageCountry == null) {
emit(state.copyWith(headquarters: () => null));
} else {
final coreCountry = adaptPackageCountryToCoreCountry(packageCountry);
emit(state.copyWith(headquarters: () => coreCountry));
}
emit(state.copyWith(headquarters: () => event.headquarters));
}

void _onStatusChanged(
Expand Down Expand Up @@ -116,7 +134,7 @@ class CreateSourceBloc extends Bloc<CreateSourceEvent, CreateSourceState> {
description: state.description,
url: state.url,
sourceType: state.sourceType!,
language: adaptPackageLanguageToLanguageCode(state.language!),
language: state.language!,
createdAt: now,
updatedAt: now,
headquarters: state.headquarters!,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 picker.Country? headquarters;
final Country? headquarters;
@override
List<Object?> get props => [headquarters];
}
Expand Down
Loading
Loading