@@ -30,18 +30,15 @@ class SourceFilterPage extends StatelessWidget {
3030 @override
3131 Widget build (BuildContext context) {
3232 return BlocProvider (
33- create: (context) =>
34- SourcesFilterBloc (
35- sourcesRepository: context.read <DataRepository <Source >>(),
36- countriesRepository: context.read <DataRepository <Country >>(),
37- userContentPreferencesRepository: context
38- .read <DataRepository <UserContentPreferences >>(),
39- appBloc: context.read <AppBloc >(),
40- )..add (
41- LoadSourceFilterData (
42- initialSelectedSources: initialSelectedSources,
43- ),
33+ create: (context) => SourcesFilterBloc (
34+ sourcesRepository: context.read <DataRepository <Source >>(),
35+ countriesRepository: context.read <DataRepository <Country >>(),
36+ appBloc: context.read <AppBloc >(),
37+ )..add (
38+ LoadSourceFilterData (
39+ initialSelectedSources: initialSelectedSources,
4440 ),
41+ ),
4542 child: const _SourceFilterView (),
4643 );
4744 }
@@ -65,16 +62,18 @@ class _SourceFilterView extends StatelessWidget {
6562 ),
6663 actions: [
6764 // Apply My Followed Sources Button
68- BlocBuilder <SourcesFilterBloc , SourcesFilterState >(
69- builder: (context, state) {
70- // Determine if the "Apply My Followed" icon should be filled
71- final isFollowedFilterActive =
72- state.followedSources.isNotEmpty &&
73- state.finallySelectedSourceIds.length ==
74- state.followedSources.length &&
75- state.followedSources.every (
76- (source) =>
77- state.finallySelectedSourceIds.contains (source.id),
65+ BlocBuilder <AppBloc , AppState >(
66+ builder: (context, appState) {
67+ final followedSources =
68+ appState.userContentPreferences? .followedSources ?? [];
69+ final sourcesFilterBloc = context.read <SourcesFilterBloc >();
70+
71+ final isFollowedFilterActive = followedSources.isNotEmpty &&
72+ sourcesFilterBloc.state.finallySelectedSourceIds.length ==
73+ followedSources.length &&
74+ followedSources.every (
75+ (source) => sourcesFilterBloc.state.finallySelectedSourceIds
76+ .contains (source.id),
7877 );
7978
8079 return IconButton (
@@ -85,16 +84,23 @@ class _SourceFilterView extends StatelessWidget {
8584 ? theme.colorScheme.primary
8685 : null ,
8786 tooltip: l10n.headlinesFeedFilterApplyFollowedLabel,
88- onPressed:
89- state.followedSourcesStatus ==
90- SourceFilterDataLoadingStatus .loading
91- ? null // Disable while loading
92- : () {
93- // Dispatch event to BLoC to fetch and apply followed sources
94- context.read <SourcesFilterBloc >().add (
95- SourcesFilterApplyFollowedRequested (),
96- );
97- },
87+ onPressed: () {
88+ sourcesFilterBloc.add (
89+ SourceFilterFollowedApplied (
90+ followedSources: followedSources,
91+ ),
92+ );
93+ if (followedSources.isEmpty) {
94+ ScaffoldMessenger .of (context)
95+ ..hideCurrentSnackBar ()
96+ ..showSnackBar (
97+ SnackBar (
98+ content: Text (l10n.noFollowedItemsForFilterSnackbar),
99+ duration: const Duration (seconds: 3 ),
100+ ),
101+ );
102+ }
103+ },
98104 );
99105 },
100106 ),
@@ -112,121 +118,60 @@ class _SourceFilterView extends StatelessWidget {
112118 ),
113119 ],
114120 ),
115- body: BlocListener <SourcesFilterBloc , SourcesFilterState >(
116- listenWhen: (previous, current) =>
117- previous.followedSourcesStatus != current.followedSourcesStatus ||
118- previous.followedSources != current.followedSources,
119- listener: (context, state) {
120- if (state.followedSourcesStatus ==
121- SourceFilterDataLoadingStatus .success) {
122- if (state.followedSources.isEmpty) {
123- ScaffoldMessenger .of (context)
124- ..hideCurrentSnackBar ()
125- ..showSnackBar (
126- SnackBar (
127- content: Text (l10n.noFollowedItemsForFilterSnackbar),
128- duration: const Duration (seconds: 3 ),
121+ body: BlocBuilder <SourcesFilterBloc , SourcesFilterState >(
122+ builder: (context, state) {
123+ final isLoadingMainList =
124+ state.dataLoadingStatus ==
125+ SourceFilterDataLoadingStatus .loading &&
126+ state.allAvailableSources.isEmpty;
127+
128+ if (isLoadingMainList) {
129+ return LoadingStateWidget (
130+ icon: Icons .source_outlined,
131+ headline: l10n.sourceFilterLoadingHeadline,
132+ subheadline: l10n.sourceFilterLoadingSubheadline,
133+ );
134+ }
135+ if (state.dataLoadingStatus ==
136+ SourceFilterDataLoadingStatus .failure &&
137+ state.allAvailableSources.isEmpty) {
138+ return FailureStateWidget (
139+ exception:
140+ state.error ??
141+ const UnknownException (
142+ 'Failed to load source filter data.' ,
129143 ),
130- );
131- }
132- } else if (state.followedSourcesStatus ==
133- SourceFilterDataLoadingStatus .failure) {
134- ScaffoldMessenger .of (context)
135- ..hideCurrentSnackBar ()
136- ..showSnackBar (
137- SnackBar (
138- content: Text (state.error? .message ?? l10n.unknownError),
139- duration: const Duration (seconds: 3 ),
140- ),
141- );
144+ onRetry: () {
145+ context.read <SourcesFilterBloc >().add (const LoadSourceFilterData ());
146+ },
147+ );
142148 }
143- },
144- child: BlocBuilder <SourcesFilterBloc , SourcesFilterState >(
145- builder: (context, state) {
146- final isLoadingMainList =
147- state.dataLoadingStatus ==
148- SourceFilterDataLoadingStatus .loading &&
149- state.allAvailableSources.isEmpty;
150- final isLoadingFollowedSources =
151- state.followedSourcesStatus ==
152- SourceFilterDataLoadingStatus .loading;
153149
154- if (isLoadingMainList) {
155- return LoadingStateWidget (
156- icon: Icons .source_outlined,
157- headline: l10n.sourceFilterLoadingHeadline,
158- subheadline: l10n.sourceFilterLoadingSubheadline,
159- );
160- }
161- if (state.dataLoadingStatus ==
162- SourceFilterDataLoadingStatus .failure &&
163- state.allAvailableSources.isEmpty) {
164- return FailureStateWidget (
165- exception:
166- state.error ??
167- const UnknownException (
168- 'Failed to load source filter data.' ,
169- ),
170- onRetry: () {
171- context.read <SourcesFilterBloc >().add (
172- const LoadSourceFilterData (),
173- );
174- },
175- );
176- }
177-
178- return Stack (
179- children: [
180- Column (
181- crossAxisAlignment: CrossAxisAlignment .start,
182- children: [
183- _buildCountryCapsules (context, state, l10n, textTheme),
184- const SizedBox (height: AppSpacing .md),
185- _buildSourceTypeCapsules (context, state, l10n, textTheme),
186- const SizedBox (height: AppSpacing .md),
187- Padding (
188- padding: const EdgeInsets .symmetric (
189- horizontal: AppSpacing .paddingMedium,
190- ),
191- child: Text (
192- l10n.headlinesFeedFilterSourceLabel,
193- style: textTheme.titleMedium? .copyWith (
194- fontWeight: FontWeight .bold,
195- ),
196- ),
197- ),
198- const SizedBox (height: AppSpacing .sm),
199- Expanded (
200- child: _buildSourcesList (context, state, l10n, textTheme),
201- ),
202- ],
150+ return Column (
151+ crossAxisAlignment: CrossAxisAlignment .start,
152+ children: [
153+ _buildCountryCapsules (context, state, l10n, textTheme),
154+ const SizedBox (height: AppSpacing .md),
155+ _buildSourceTypeCapsules (context, state, l10n, textTheme),
156+ const SizedBox (height: AppSpacing .md),
157+ Padding (
158+ padding: const EdgeInsets .symmetric (
159+ horizontal: AppSpacing .paddingMedium,
203160 ),
204- // Show loading overlay if followed sources are being fetched
205- if (isLoadingFollowedSources)
206- Positioned .fill (
207- child: ColoredBox (
208- color: Colors .black54, // Semi-transparent overlay
209- child: Center (
210- child: Column (
211- mainAxisAlignment: MainAxisAlignment .center,
212- children: [
213- const CircularProgressIndicator (),
214- const SizedBox (height: AppSpacing .md),
215- Text (
216- l10n.headlinesFeedLoadingHeadline,
217- style: theme.textTheme.titleMedium? .copyWith (
218- color: Colors .white,
219- ),
220- ),
221- ],
222- ),
223- ),
224- ),
161+ child: Text (
162+ l10n.headlinesFeedFilterSourceLabel,
163+ style: textTheme.titleMedium? .copyWith (
164+ fontWeight: FontWeight .bold,
225165 ),
226- ],
227- );
228- },
229- ),
166+ ),
167+ ),
168+ const SizedBox (height: AppSpacing .sm),
169+ Expanded (
170+ child: _buildSourcesList (context, state, l10n, textTheme),
171+ ),
172+ ],
173+ );
174+ },
230175 ),
231176 );
232177 }
0 commit comments