Skip to content

Commit 55faf37

Browse files
authored
Merge pull request #92 from flutter-news-app-full-source-code/refactor/app-shell
Refactor/app shell
2 parents c2111ae + 016306c commit 55faf37

34 files changed

+442
-292
lines changed

lib/app/view/app_shell.dart

Lines changed: 103 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -25,42 +25,12 @@ class AppShell extends StatelessWidget {
2525
@override
2626
Widget build(BuildContext context) {
2727
final l10n = AppLocalizationsX(context).l10n;
28+
final theme = Theme.of(context);
29+
30+
// Use the same text style as the NavigationRail labels for consistency.
31+
final navRailLabelStyle = theme.textTheme.labelMedium;
32+
2833
return Scaffold(
29-
appBar: AppBar(
30-
title: Text(l10n.dashboardTitle),
31-
actions: [
32-
PopupMenuButton<String>(
33-
onSelected: (value) {
34-
if (value == 'settings') {
35-
context.goNamed(Routes.settingsName);
36-
} else if (value == 'signOut') {
37-
context.read<AppBloc>().add(const AppLogoutRequested());
38-
}
39-
},
40-
itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
41-
PopupMenuItem<String>(
42-
value: 'settings',
43-
child: Text(l10n.settings),
44-
),
45-
PopupMenuItem<String>(
46-
value: 'signOut',
47-
child: Text(l10n.signOut),
48-
),
49-
],
50-
child: Padding(
51-
padding: const EdgeInsets.all(AppSpacing.sm),
52-
child: CircleAvatar(
53-
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
54-
child: Icon(
55-
Icons.person,
56-
color: Theme.of(context).colorScheme.onPrimaryContainer,
57-
),
58-
),
59-
),
60-
),
61-
const SizedBox(width: AppSpacing.sm),
62-
],
63-
),
6434
body: AdaptiveScaffold(
6535
selectedIndex: navigationShell.currentIndex,
6636
onSelectedIndexChange: (index) {
@@ -86,6 +56,104 @@ class AppShell extends StatelessWidget {
8656
label: l10n.appConfiguration,
8757
),
8858
],
59+
leadingUnextendedNavRail: const Padding(
60+
padding: EdgeInsets.symmetric(vertical: AppSpacing.lg),
61+
child: Icon(Icons.newspaper_outlined),
62+
),
63+
leadingExtendedNavRail: Padding(
64+
padding: const EdgeInsets.all(AppSpacing.lg),
65+
child: Row(
66+
children: [
67+
const Icon(Icons.newspaper_outlined),
68+
const SizedBox(width: AppSpacing.md),
69+
Text(
70+
l10n.dashboardTitle,
71+
style: theme.textTheme.titleLarge?.copyWith(
72+
color: theme.colorScheme.primary,
73+
),
74+
),
75+
],
76+
),
77+
),
78+
trailingNavRail: Builder(
79+
builder: (context) {
80+
final isExtended =
81+
Breakpoints.mediumLarge.isActive(context) ||
82+
Breakpoints.small.isActive(context);
83+
return Expanded(
84+
child: Padding(
85+
padding: const EdgeInsets.only(bottom: AppSpacing.lg),
86+
child: Column(
87+
mainAxisAlignment: MainAxisAlignment.end,
88+
children: [
89+
// Settings Tile
90+
InkWell(
91+
onTap: () => context.goNamed(Routes.settingsName),
92+
child: Padding(
93+
padding: EdgeInsets.symmetric(
94+
vertical: AppSpacing.md,
95+
horizontal: isExtended ? 24 : 16,
96+
),
97+
child: Row(
98+
mainAxisAlignment: isExtended
99+
? MainAxisAlignment.start
100+
: MainAxisAlignment.center,
101+
children: [
102+
Icon(
103+
Icons.settings_outlined,
104+
color: theme.colorScheme.onSurfaceVariant,
105+
size: 24,
106+
),
107+
if (isExtended) ...[
108+
const SizedBox(width: AppSpacing.lg),
109+
Text(
110+
l10n.settings,
111+
style: navRailLabelStyle,
112+
),
113+
],
114+
],
115+
),
116+
),
117+
),
118+
// Sign Out Tile
119+
InkWell(
120+
onTap: () => context.read<AppBloc>().add(
121+
const AppLogoutRequested(),
122+
),
123+
child: Padding(
124+
padding: EdgeInsets.symmetric(
125+
vertical: AppSpacing.md,
126+
horizontal: isExtended ? 24 : 16,
127+
),
128+
child: Row(
129+
mainAxisAlignment: isExtended
130+
? MainAxisAlignment.start
131+
: MainAxisAlignment.center,
132+
children: [
133+
Icon(
134+
Icons.logout,
135+
color: theme.colorScheme.error,
136+
size: 24,
137+
),
138+
if (isExtended) ...[
139+
const SizedBox(width: AppSpacing.lg),
140+
Text(
141+
l10n.signOut,
142+
style: navRailLabelStyle?.copyWith(
143+
color: theme.colorScheme.error,
144+
),
145+
),
146+
],
147+
],
148+
),
149+
),
150+
),
151+
],
152+
),
153+
),
154+
);
155+
},
156+
),
89157
body: (_) => Padding(
90158
padding: const EdgeInsets.fromLTRB(
91159
0,

lib/app_configuration/view/app_configuration_page.dart

Lines changed: 24 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuratio
55
import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/view/tabs/feed_configuration_tab.dart';
66
import 'package:flutter_news_app_web_dashboard_full_source_code/app_configuration/view/tabs/general_configuration_tab.dart';
77
import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart';
8+
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/widgets/about_icon.dart';
89
import 'package:ui_kit/ui_kit.dart';
910

1011
/// {@template app_configuration_page}
@@ -44,42 +45,30 @@ class _AppConfigurationPageState extends State<AppConfigurationPage>
4445
final l10n = AppLocalizationsX(context).l10n;
4546
return Scaffold(
4647
appBar: AppBar(
47-
title: Text(
48-
l10n.appConfigurationPageTitle,
49-
style: Theme.of(context).textTheme.headlineSmall,
48+
title: Row(
49+
mainAxisSize: MainAxisSize.min,
50+
children: [
51+
Text(
52+
l10n.appConfigurationPageTitle,
53+
),
54+
const SizedBox(
55+
width: AppSpacing.xs,
56+
),
57+
AboutIcon(
58+
dialogTitle: l10n.appConfigurationPageTitle,
59+
dialogDescription: l10n.appConfigurationPageDescription,
60+
),
61+
],
5062
),
51-
bottom: PreferredSize(
52-
preferredSize: const Size.fromHeight(
53-
kTextTabBarHeight + AppSpacing.lg,
54-
),
55-
child: Column(
56-
crossAxisAlignment: CrossAxisAlignment.start,
57-
children: [
58-
Padding(
59-
padding: const EdgeInsets.only(
60-
left: AppSpacing.lg,
61-
right: AppSpacing.lg,
62-
bottom: AppSpacing.lg,
63-
),
64-
child: Text(
65-
l10n.appConfigurationPageDescription,
66-
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
67-
color: Theme.of(context).colorScheme.onSurfaceVariant,
68-
),
69-
),
70-
),
71-
TabBar(
72-
controller: _tabController,
73-
tabAlignment: TabAlignment.start,
74-
isScrollable: true,
75-
tabs: [
76-
Tab(text: l10n.generalTab),
77-
Tab(text: l10n.feedTab),
78-
Tab(text: l10n.advertisementsTab),
79-
],
80-
),
81-
],
82-
),
63+
bottom: TabBar(
64+
controller: _tabController,
65+
tabAlignment: TabAlignment.start,
66+
isScrollable: true,
67+
tabs: [
68+
Tab(text: l10n.generalTab),
69+
Tab(text: l10n.feedTab),
70+
Tab(text: l10n.advertisementsTab),
71+
],
8372
),
8473
),
8574
body: BlocConsumer<AppConfigurationBloc, AppConfigurationState>(

lib/app_configuration/widgets/feed_ad_settings_form.dart

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,6 @@ class _FeedAdSettingsFormState extends State<FeedAdSettingsForm>
4949
vsync: this,
5050
);
5151
_initializeControllers();
52-
// Removed _tabController.addListener(_onTabChanged); as automatic disabling
53-
// for premium users is no longer required.
5452
}
5553

5654
/// Initializes text editing controllers for each user role based on current
@@ -272,7 +270,6 @@ class _FeedAdSettingsFormState extends State<FeedAdSettingsForm>
272270
FeedAdConfiguration config,
273271
) {
274272
final roleConfig = config.visibleTo[role];
275-
// Removed isEnabled check as premium users can now be manually configured.
276273

277274
return Column(
278275
children: [

lib/app_configuration/widgets/interstitial_ad_settings_form.dart

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,6 @@ class _InterstitialAdSettingsFormState extends State<InterstitialAdSettingsForm>
4545
vsync: this,
4646
);
4747
_initializeControllers();
48-
// Removed _tabController.addListener(_onTabChanged); as automatic disabling
49-
// for premium users is no longer required.
5048
}
5149

5250
/// Initializes text editing controllers for each user role based on current
@@ -204,7 +202,6 @@ class _InterstitialAdSettingsFormState extends State<InterstitialAdSettingsForm>
204202
InterstitialAdConfiguration config,
205203
) {
206204
final roleConfig = config.visibleTo[role];
207-
// Removed isEnabled check as premium users can now be manually configured.
208205

209206
return Column(
210207
children: [
@@ -265,7 +262,6 @@ class _InterstitialAdSettingsFormState extends State<InterstitialAdSettingsForm>
265262
},
266263
controller:
267264
_transitionsBeforeShowingInterstitialAdsControllers[role],
268-
// Removed enabled: isEnabled
269265
),
270266
),
271267
],

lib/content_management/bloc/content_management_bloc.dart

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,10 @@ class ContentManagementBloc
104104
);
105105
});
106106

107-
_deletionEventsSubscription = _pendingDeletionsService.deletionEvents.listen(
108-
(event) => add(DeletionEventReceived(event)),
109-
);
107+
_deletionEventsSubscription = _pendingDeletionsService.deletionEvents
108+
.listen(
109+
(event) => add(DeletionEventReceived(event)),
110+
);
110111
}
111112

112113
final DataRepository<Headline> _headlinesRepository;
@@ -120,7 +121,8 @@ class ContentManagementBloc
120121
late final StreamSubscription<Type> _headlineUpdateSubscription;
121122
late final StreamSubscription<Type> _topicUpdateSubscription;
122123
late final StreamSubscription<Type> _sourceUpdateSubscription;
123-
late final StreamSubscription<DeletionEvent<dynamic>> _deletionEventsSubscription;
124+
late final StreamSubscription<DeletionEvent<dynamic>>
125+
_deletionEventsSubscription;
124126

125127
@override
126128
Future<void> close() {
@@ -221,7 +223,8 @@ class ContentManagementBloc
221223
final previousHeadlines = isPaginating ? state.headlines : <Headline>[];
222224

223225
final paginatedHeadlines = await _headlinesRepository.readAll(
224-
filter: event.filter ?? buildHeadlinesFilterMap(_headlinesFilterBloc.state),
226+
filter:
227+
event.filter ?? buildHeadlinesFilterMap(_headlinesFilterBloc.state),
225228
sort: [const SortOption('updatedAt', SortOrder.desc)],
226229
pagination: PaginationOptions(
227230
cursor: event.startAfterId,
@@ -609,8 +612,7 @@ class ContentManagementBloc
609612
final previousSources = isPaginating ? state.sources : <Source>[];
610613

611614
final paginatedSources = await _sourcesRepository.readAll(
612-
filter:
613-
event.filter ?? buildSourcesFilterMap(_sourcesFilterBloc.state),
615+
filter: event.filter ?? buildSourcesFilterMap(_sourcesFilterBloc.state),
614616
sort: [const SortOption('updatedAt', SortOrder.desc)],
615617
pagination: PaginationOptions(
616618
cursor: event.startAfterId,

lib/content_management/bloc/content_management_state.dart

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,9 @@ class ContentManagementState extends Equatable {
122122
sources: sources ?? this.sources,
123123
sourcesCursor: sourcesCursor ?? this.sourcesCursor,
124124
sourcesHasMore: sourcesHasMore ?? this.sourcesHasMore,
125-
exception: exception, // Explicitly set to null if not provided
126-
lastPendingDeletionId:
127-
lastPendingDeletionId, // Explicitly set to null if not provided
128-
snackbarMessage:
129-
snackbarMessage, // Explicitly set to null if not provided
125+
exception: exception,
126+
lastPendingDeletionId: lastPendingDeletionId,
127+
snackbarMessage: snackbarMessage,
130128
);
131129
}
132130

0 commit comments

Comments
 (0)