Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 15 additions & 5 deletions lib/account/view/account_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:flutter_news_app_mobile_client_full_source_code/authentication/b
import 'package:flutter_news_app_mobile_client_full_source_code/l10n/l10n.dart';
import 'package:flutter_news_app_mobile_client_full_source_code/router/routes.dart';
import 'package:go_router/go_router.dart';
import 'package:logging/logging.dart'; // Import for Logger
import 'package:ui_kit/ui_kit.dart';

/// {@template account_view}
Expand All @@ -16,6 +17,9 @@ class AccountPage extends StatelessWidget {
/// {@macro account_view}
const AccountPage({super.key});

// Logger instance for AccountPage
static final _logger = Logger('AccountPage');

@override
Widget build(BuildContext context) {
final l10n = AppLocalizationsX(context).l10n;
Expand All @@ -27,6 +31,7 @@ class AccountPage extends StatelessWidget {
final theme = Theme.of(context);
final textTheme = theme.textTheme;

// Removed BlocListener as per user instruction.
return Scaffold(
appBar: AppBar(
title: Text(l10n.accountPageTitle, style: textTheme.titleLarge),
Expand Down Expand Up @@ -102,7 +107,6 @@ class AccountPage extends StatelessWidget {
statusWidget = Padding(
padding: const EdgeInsets.only(top: AppSpacing.md),
child: ElevatedButton.icon(
// Changed to ElevatedButton
icon: const Icon(Icons.link_outlined),
label: Text(l10n.accountSignInPromptButton),
style: ElevatedButton.styleFrom(
Expand All @@ -113,10 +117,16 @@ class AccountPage extends StatelessWidget {
textStyle: textTheme.labelLarge,
),
onPressed: () {
context.goNamed(
Routes.authenticationName,
queryParameters: {'context': 'linking'},
_logger.info(
'AccountPage: "Link Account" button pressed. '
'Dispatching AuthenticationLinkingInitiated event and navigating to authentication page with AuthFlow.linkAccount extra.',
);
// Dispatch the event to set the AuthFlow in AuthenticationBloc
context.read<AuthenticationBloc>().add(
const AuthenticationLinkingInitiated(),
);
// Navigate to the authentication page
context.pushNamed(Routes.authenticationName);
},
),
);
Expand All @@ -127,7 +137,6 @@ class AccountPage extends StatelessWidget {
children: [
const SizedBox(height: AppSpacing.md),
OutlinedButton.icon(
// Changed to OutlinedButton.icon
icon: Icon(Icons.logout, color: colorScheme.error),
label: Text(l10n.accountSignOutTile),
style: OutlinedButton.styleFrom(
Expand All @@ -140,6 +149,7 @@ class AccountPage extends StatelessWidget {
textStyle: textTheme.labelLarge,
),
onPressed: () {
_logger.info('AccountPage: "Sign Out" button pressed.');
context.read<AuthenticationBloc>().add(
const AuthenticationSignOutRequested(),
);
Expand Down
25 changes: 25 additions & 0 deletions lib/app/bloc/app_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'package:flutter_news_app_mobile_client_full_source_code/app/config/confi
import 'package:flutter_news_app_mobile_client_full_source_code/app/services/demo_data_initializer_service.dart';
import 'package:flutter_news_app_mobile_client_full_source_code/app/services/demo_data_migration_service.dart';
import 'package:flutter_news_app_mobile_client_full_source_code/app/services/package_info_service.dart';
import 'package:go_router/go_router.dart';
import 'package:logging/logging.dart';
import 'package:pub_semver/pub_semver.dart';

Expand Down Expand Up @@ -79,6 +80,7 @@ class AppBloc extends Bloc<AppEvent, AppState> {
on<AppUserFeedDecoratorShown>(_onAppUserFeedDecoratorShown);
on<AppUserContentPreferencesChanged>(_onAppUserContentPreferencesChanged);
on<AppLogoutRequested>(_onLogoutRequested);
on<PostAuthRedirectIntentCaptured>(_onPostAuthRedirectIntentCaptured);

// Subscribe to the authentication repository's authStateChanges stream.
// This stream is the single source of truth for the user's auth state
Expand Down Expand Up @@ -408,6 +410,19 @@ class AppBloc extends Bloc<AppEvent, AppState> {
// After potential initialization and migration,
// ensure user-specific data (settings and preferences) are loaded.
await _fetchAndSetUserData(newUser, emit);

// After user data is loaded, check for a pending redirect intent.
final redirectIntent = state.postAuthRedirectIntent;
if (redirectIntent != null) {
_logger.info(
'[AppBloc] Post-authentication redirect intent found: '
'${redirectIntent.matchedLocation}. Navigating...',
);
// Use the navigatorKey's context to navigate to the intended route.
_navigatorKey.currentState?.context.go(redirectIntent.matchedLocation);
// Clear the intent after navigation to prevent re-triggering.
emit(state.copyWith(clearPostAuthRedirectIntent: true));
}
} else {
// If user logs out, clear user-specific data from state.
emit(state.copyWith(settings: null, userContentPreferences: null));
Expand Down Expand Up @@ -802,4 +817,14 @@ class AppBloc extends Bloc<AppEvent, AppState> {
);
}
}

/// Handles [PostAuthRedirectIntentCaptured] events.
///
/// Stores the intended navigation path in the state.
void _onPostAuthRedirectIntentCaptured(
PostAuthRedirectIntentCaptured event,
Emitter<AppState> emit,
) {
emit(state.copyWith(postAuthRedirectIntent: event.intent));
}
}
15 changes: 15 additions & 0 deletions lib/app/bloc/app_event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,18 @@ class AppUserFeedDecoratorShown extends AppEvent {
@override
List<Object> get props => [userId, feedDecoratorType, isCompleted];
}

/// {@template post_auth_redirect_intent_captured}
/// Event triggered when a navigation intent is captured before an authentication
/// flow, indicating where the user should be redirected after successful auth.
/// {@endtemplate}
final class PostAuthRedirectIntentCaptured extends AppEvent {
/// {@macro post_auth_redirect_intent_captured}
const PostAuthRedirectIntentCaptured({required this.intent});

/// The [GoRouterState] representing the intended destination.
final GoRouterState intent;

@override
List<Object> get props => [intent];
}
25 changes: 22 additions & 3 deletions lib/app/bloc/app_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,18 @@ enum AppLifeCycleStatus {
/// The application is currently loading user-specific data (settings, preferences).
loadingUserData,

/// The user is not authenticated.
/// The user is not authenticated. This state indicates that there is no
/// active user session at all, and the user needs to either sign in or
/// sign up.
unauthenticated,

/// The user is authenticated (e.g., standard user).
/// The user is authenticated (e.g., standard user). This state indicates
/// a full, permanent user session is active.
authenticated,

/// The user is anonymous (e.g., guest user).
/// The user is anonymous (e.g., guest user). This state indicates a temporary
/// user session is active, allowing limited functionality before a full
/// account is created or linked.
anonymous,

/// A critical error occurred during application startup,
Expand Down Expand Up @@ -49,6 +54,8 @@ class AppState extends Equatable {
this.settings,
this.selectedBottomNavigationIndex = 0,
this.currentAppVersion,
// New property to store the intended navigation path after authentication.
this.postAuthRedirectIntent,
});

/// The current status of the application, indicating its lifecycle stage.
Expand Down Expand Up @@ -89,6 +96,12 @@ class AppState extends Equatable {
/// This is used for version enforcement.
final String? currentAppVersion;

/// Stores the intended navigation path (GoRouterState) that the user was
/// trying to access before being redirected for authentication.
/// This is used to redirect the user back to their original destination
/// after successful login or account linking.
final GoRouterState? postAuthRedirectIntent;

/// The latest required app version from the remote configuration.
/// Returns `null` if remote config is not available.
String? get latestAppVersion => remoteConfig?.appStatus.latestAppVersion;
Expand Down Expand Up @@ -162,6 +175,7 @@ class AppState extends Equatable {
selectedBottomNavigationIndex,
environment,
currentAppVersion,
postAuthRedirectIntent,
];

/// Creates a copy of this [AppState] with the given fields replaced with
Expand All @@ -178,6 +192,8 @@ class AppState extends Equatable {
int? selectedBottomNavigationIndex,
local_config.AppEnvironment? environment,
String? currentAppVersion,
GoRouterState? postAuthRedirectIntent,
bool clearPostAuthRedirectIntent = false,
}) {
return AppState(
status: status ?? this.status,
Expand All @@ -194,6 +210,9 @@ class AppState extends Equatable {
selectedBottomNavigationIndex ?? this.selectedBottomNavigationIndex,
environment: environment ?? this.environment,
currentAppVersion: currentAppVersion ?? this.currentAppVersion,
postAuthRedirectIntent: clearPostAuthRedirectIntent
? null
: postAuthRedirectIntent ?? this.postAuthRedirectIntent,
);
}
}
Loading
Loading