Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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)

AltStyle によって変換されたページ (->オリジナル) /