Skip to content

Commit ec145fd

Browse files
committed
refactor: successfully refactored app navigation. It now utilizes a StatefulShellRoute managed by a new AppShell widget (lib/app/view/app_shell.dart) which employs flutter_adaptive_scaffold for responsive navigation.
1 parent e9a9f35 commit ec145fd

File tree

9 files changed

+266
-99
lines changed

9 files changed

+266
-99
lines changed

lib/account/view/account_page.dart

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class _AccountView extends StatelessWidget {
4545
final l10n = context.l10n;
4646
// Watch AppBloc for user details and authentication status
4747
final user = context.watch<AppBloc>().state.user;
48-
final status = user.authenticationStatus; // Use status from User model
48+
final status = user.authenticationStatus;
4949

5050
// Determine if the user is anonymous
5151
final isAnonymous = status == AuthenticationStatus.anonymous;
@@ -61,8 +61,10 @@ class _AccountView extends StatelessWidget {
6161
const SizedBox(height: 24),
6262

6363
// --- Action Tiles ---
64+
_buildNotificationsTile(context),
65+
const Divider(),
6466
_buildSettingsTile(context),
65-
const Divider(), // Visual separator
67+
const Divider(),
6668
if (isAnonymous)
6769
_buildBackupTile(context) // Show Backup CTA for anonymous
6870
else
@@ -144,7 +146,6 @@ class _AccountView extends StatelessWidget {
144146
style: TextStyle(color: Theme.of(context).colorScheme.error),
145147
),
146148
onTap: () {
147-
// Add the logout event to the AccountBloc
148149
context.read<AccountBloc>().add(const AccountLogoutRequested());
149150
// Global redirect will be handled by AppBloc/GoRouter
150151
},
@@ -155,7 +156,7 @@ class _AccountView extends StatelessWidget {
155156
Widget _buildBackupTile(BuildContext context) {
156157
final l10n = context.l10n;
157158
return ListTile(
158-
leading: const Icon(Icons.link), // Icon suggesting connection/linking
159+
leading: const Icon(Icons.link),
159160
title: Text(l10n.accountConnectPrompt),
160161
subtitle: Text(l10n.accountConnectBenefit),
161162
isThreeLine: true, // Allow more space for subtitle
@@ -170,6 +171,20 @@ class _AccountView extends StatelessWidget {
170171
);
171172
}
172173

174+
/// Builds the ListTile for navigating to Notifications.
175+
Widget _buildNotificationsTile(BuildContext context) {
176+
final l10n = context.l10n;
177+
return ListTile(
178+
leading: const Icon(Icons.notifications_outlined),
179+
title: Text(l10n.accountNotificationsTile),
180+
trailing: const Icon(Icons.chevron_right), // Suggests navigation
181+
onTap: () {
182+
// Navigate to the new notifications route (placeholder)
183+
context.goNamed(Routes.notificationsName);
184+
},
185+
);
186+
}
187+
173188
/// Helper to convert AuthenticationStatus enum to a display string.
174189
String _authenticationStatusToString(
175190
BuildContext context,

lib/app/view/app_shell.dart

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
//
2+
// ignore_for_file: lines_longer_than_80_chars
3+
4+
import 'package:flutter/material.dart';
5+
import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart';
6+
import 'package:go_router/go_router.dart';
7+
import 'package:ht_main/l10n/l10n.dart';
8+
9+
/// A responsive scaffold shell for the main application sections.
10+
///
11+
/// Uses [AdaptiveScaffold] to provide appropriate navigation
12+
/// (bottom bar, rail, or drawer) based on screen size.
13+
class AppShell extends StatelessWidget {
14+
/// Creates an [AppShell].
15+
///
16+
/// Requires a [navigationShell] to manage the nested navigators
17+
/// for each section.
18+
const AppShell({required this.navigationShell, super.key});
19+
20+
/// The [StatefulNavigationShell] provided by [GoRouter] for managing nested
21+
/// navigators in a stateful way.
22+
final StatefulNavigationShell navigationShell;
23+
24+
// Corrected callback signature if needed, though the original looks standard.
25+
// Let's ensure the AdaptiveScaffold call uses the correct signature.
26+
// The primary issue was likely the missing import.
27+
28+
void _goBranch(int index) {
29+
// Navigate to the corresponding branch using the index.
30+
// The `saveState` parameter is crucial for preserving the state
31+
// of each navigation branch (e.g., scroll position).
32+
navigationShell.goBranch(
33+
index,
34+
// Navigate to the initial location of the branch if the user taps
35+
// the same active destination again. Otherwise, defaults to false.
36+
initialLocation: index == navigationShell.currentIndex,
37+
);
38+
}
39+
40+
@override
41+
Widget build(BuildContext context) {
42+
final l10n = context.l10n;
43+
44+
return AdaptiveScaffold(
45+
// Use the index from the navigationShell to sync the selected destination.
46+
selectedIndex: navigationShell.currentIndex,
47+
// Callback when a destination is selected.
48+
// Ensure the parameter type is explicitly int.
49+
onSelectedIndexChange: _goBranch,
50+
// Define the navigation destinations.
51+
destinations: [
52+
const NavigationDestination(
53+
// Make const
54+
icon: Icon(Icons.article_outlined),
55+
selectedIcon: Icon(Icons.article),
56+
label: l10n.bottomNavFeedLabel, // Placeholder until generated
57+
),
58+
const NavigationDestination(
59+
// Make const
60+
icon: Icon(Icons.search_outlined),
61+
selectedIcon: Icon(Icons.search),
62+
label: l10n.bottomNavSearchLabel, // Placeholder until generated
63+
),
64+
const NavigationDestination(
65+
// Make const
66+
icon: Icon(Icons.account_circle_outlined),
67+
selectedIcon: Icon(Icons.account_circle),
68+
label: l10n.bottomNavAccountLabel, // Placeholder until generated
69+
),
70+
],
71+
// The body displays the widget tree for the currently selected branch.
72+
// The [NavigationShell] widget handles building the appropriate page.
73+
body: (_) => navigationShell,
74+
75+
// Optional: Configure small screen navigation type (defaults to bottomNav)
76+
// smallBreakpoint: const WidthPlatformBreakpoint(end: 700),
77+
// smallBuilder: (_, __) => AdaptiveScaffold.standardBottomNavigationBar(
78+
// destinations: destinations,
79+
// ),
80+
81+
// Optional: Configure medium screen navigation type (defaults to navRail)
82+
// mediumBreakpoint: const WidthPlatformBreakpoint(begin: 700, end: 1000),
83+
// mediumBuilder: (_, __, ___) => AdaptiveScaffold.standardNavigationRail(
84+
// destinations: destinations,
85+
// // leading: const Icon(Icons.menu), // Example leading widget
86+
// ),
87+
88+
// Optional: Configure large screen navigation type (defaults to drawer)
89+
// largeBreakpoint: const WidthPlatformBreakpoint(begin: 1000),
90+
// largeBuilder: (_, __, ___) => AdaptiveScaffold.standardNavigationDrawer(
91+
// destinations: destinations,
92+
// ),
93+
);
94+
}
95+
}

lib/headlines-feed/view/headlines_feed_page.dart

Lines changed: 8 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
import 'package:flutter/material.dart';
22
import 'package:flutter_bloc/flutter_bloc.dart';
3-
import 'package:go_router/go_router.dart';
43
import 'package:ht_headlines_repository/ht_headlines_repository.dart';
54
import 'package:ht_main/headlines-feed/bloc/headlines_feed_bloc.dart';
65
import 'package:ht_main/headlines-feed/widgets/headline_item_widget.dart';
76
import 'package:ht_main/l10n/l10n.dart';
8-
import 'package:ht_main/router/routes.dart';
97
import 'package:ht_main/shared/constants/constants.dart';
108
import 'package:ht_main/shared/widgets/failure_state_widget.dart';
119
import 'package:ht_main/shared/widgets/loading_state_widget.dart';
@@ -75,38 +73,14 @@ class _HeadlinesFeedViewState extends State<_HeadlinesFeedView> {
7573

7674
return Scaffold(
7775
appBar: AppBar(
78-
leadingWidth: 100,
79-
leading: Row(
80-
mainAxisSize: MainAxisSize.min, // Keep icons close together
81-
children: [
82-
IconButton(
83-
icon: const Icon(Icons.account_circle_outlined),
84-
selectedIcon: const Icon(Icons.account_circle_rounded),
85-
onPressed: () {
86-
// Navigate to the Account page
87-
context.goNamed(Routes.accountName);
88-
},
89-
),
90-
IconButton(
91-
icon: const Icon(Icons.notifications_none),
92-
selectedIcon: const Icon(Icons.notifications_rounded),
93-
onPressed: () {},
94-
),
95-
],
96-
),
76+
// Removed leadingWidth and leading Row
9777
title: Text(
98-
'HT', // Consider localizing this if needed
78+
'HT', // TODO(fulleni): Localize this title
9979
style: textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold),
10080
),
10181
centerTitle: true,
10282
actions: [
103-
const SizedBox(width: AppSpacing.sm),
104-
IconButton(
105-
icon: const Icon(Icons.search),
106-
onPressed: () {
107-
context.goNamed(Routes.searchName);
108-
},
109-
),
83+
// Removed Search IconButton
11084
BlocBuilder<HeadlinesFeedBloc, HeadlinesFeedState>(
11185
builder: (context, state) {
11286
var isFilterApplied = false;
@@ -138,7 +112,7 @@ class _HeadlinesFeedViewState extends State<_HeadlinesFeedView> {
138112
width: AppSpacing.sm,
139113
height: AppSpacing.sm,
140114
decoration: BoxDecoration(
141-
color: colorScheme.primary, // Use variable
115+
color: colorScheme.primary,
142116
shape: BoxShape.circle,
143117
),
144118
),
@@ -160,9 +134,7 @@ class _HeadlinesFeedViewState extends State<_HeadlinesFeedView> {
160134
headline: l10n.headlinesFeedLoadingHeadline,
161135
subheadline: l10n.headlinesFeedLoadingSubheadline,
162136
);
163-
// this silentcase will never be reached
164-
// it here just to fullfill the Exhaustiveness
165-
// Checking os the sealed state.
137+
166138
case HeadlinesFeedLoadingSilently():
167139
// This case is technically unreachable due to buildWhen,
168140
// but required for exhaustive switch.
@@ -177,7 +149,7 @@ class _HeadlinesFeedViewState extends State<_HeadlinesFeedView> {
177149
// Use ListView.separated for consistent spacing
178150
child: ListView.separated(
179151
controller: _scrollController,
180-
// Add vertical padding within the list
152+
181153
padding: const EdgeInsets.only(
182154
top: AppSpacing.md,
183155
bottom: AppSpacing.xxl,
@@ -259,14 +231,14 @@ class _HeadlinesFilterBottomSheetState
259231
child: SingleChildScrollView(
260232
child: Column(
261233
mainAxisSize: MainAxisSize.min,
262-
crossAxisAlignment: CrossAxisAlignment.stretch, // Stretch buttons
234+
crossAxisAlignment: CrossAxisAlignment.stretch,
263235
children: [
264236
Text(
265237
l10n.headlinesFeedFilterTitle,
266238
style: Theme.of(context).textTheme.titleLarge,
267239
textAlign: TextAlign.center,
268240
),
269-
const SizedBox(height: AppSpacing.xl), // Increased spacing
241+
const SizedBox(height: AppSpacing.xl),
270242
// Category Dropdown
271243
DropdownButtonFormField<String>(
272244
decoration: InputDecoration(

lib/l10n/arb/app_ar.arb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,5 +259,21 @@
259259
"accountConnectBenefit": "لحفظ تفضيلاتك وسجل القراءة عبر الأجهزة.",
260260
"@accountConnectBenefit": {
261261
"description": "عنوان فرعي يشرح فائدة ربط حساب مجهول"
262+
},
263+
"bottomNavFeedLabel": "الموجز",
264+
"@bottomNavFeedLabel": {
265+
"description": "Label for the Feed item in the bottom navigation bar"
266+
},
267+
"bottomNavSearchLabel": "بحث",
268+
"@bottomNavSearchLabel": {
269+
"description": "Label for the Search item in the bottom navigation bar"
270+
},
271+
"bottomNavAccountLabel": "الحساب",
272+
"@bottomNavAccountLabel": {
273+
"description": "Label for the Account item in the bottom navigation bar"
274+
},
275+
"accountNotificationsTile": "الإشعارات",
276+
"@accountNotificationsTile": {
277+
"description": "Title for the notifications navigation tile in the account page"
262278
}
263279
}

lib/l10n/arb/app_en.arb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,5 +259,21 @@
259259
"accountConnectBenefit": "Save your preferences and reading history across devices.",
260260
"@accountConnectBenefit": {
261261
"description": "Subtitle explaining the benefit of connecting an anonymous account"
262+
},
263+
"bottomNavFeedLabel": "Feed",
264+
"@bottomNavFeedLabel": {
265+
"description": "Label for the Feed item in the bottom navigation bar"
266+
},
267+
"bottomNavSearchLabel": "Search",
268+
"@bottomNavSearchLabel": {
269+
"description": "Label for the Search item in the bottom navigation bar"
270+
},
271+
"bottomNavAccountLabel": "Account",
272+
"@bottomNavAccountLabel": {
273+
"description": "Label for the Account item in the bottom navigation bar"
274+
},
275+
"accountNotificationsTile": "Notifications",
276+
"@accountNotificationsTile": {
277+
"description": "Title for the notifications navigation tile in the account page"
262278
}
263279
}

0 commit comments

Comments
 (0)