Skip to content
Merged

Aa #132

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
c177e38
build(deps): update core dependency
fulleni Nov 25, 2025
9736ff4
chore: delete absolete files
fulleni Nov 25, 2025
a6d19f7
feat(config): overhaul AppConfigurationPage with new tab structure
fulleni Nov 25, 2025
e7cfcc2
feat(config): create new model-driven tab views
fulleni Nov 25, 2025
f153ad6
feat(config): create new forms for app and navigation ad settings
fulleni Nov 25, 2025
4b0e775
feat(config): create UserLimitsConfigForm for simple user limits
fulleni Nov 25, 2025
168daf3
refactor(config): align ad and platform forms with new AdConfig model
fulleni Nov 25, 2025
5eeb023
refactor(config): align feed ad and decorator forms with new models
fulleni Nov 25, 2025
061e759
refactor(config): align PushNotificationSettingsForm with new model
fulleni Nov 25, 2025
71347da
refactor(config): align saved filter limit forms with UserConfig model
fulleni Nov 25, 2025
86a2a5b
fix(router): remove unnecessary hide directive
fulleni Nov 25, 2025
9ea8122
feat(l10n): add new settings tab and configuration labels
fulleni Nov 25, 2025
6bc6b6a
build(l10n): generate
fulleni Nov 25, 2025
2272641
chore: delete file
fulleni Nov 25, 2025
c28c29e
fix(app_configuration): update ExpansionTile key to ensure uniqueness
fulleni Nov 25, 2025
1c99175
feat(app_configuration): wrap navigation ad settings form in SingleCh…
fulleni Nov 25, 2025
09c1f59
fix(app_configuration): wrap role limit fields in SingleChildScrollView
fulleni Nov 25, 2025
c7a3f3f
refactor(app_configuration): simplify user preference limits management
fulleni Nov 25, 2025
0fbd114
fix(app_configuration): update notification subscription controller keys
fulleni Nov 25, 2025
35c7569
refactor(extensions): remove unused in_article_ad_slot_type_l10n export
fulleni Nov 25, 2025
45e93da
refactor(app): rename UserAppSettings to AppSettings and adjust relat…
fulleni Nov 25, 2025
e8ab82e
style: format
fulleni Nov 25, 2025
f77f0d4
feat(bootstrap): sync with core package AppSettings and Headline models
fulleni Nov 25, 2025
f112536
feat(app): update AppView for AppSettings and FeedItemClickBehavior
fulleni Nov 25, 2025
ac393ad
refactor(data): update AppSettings references and improve code format…
fulleni Nov 25, 2025
b42d9b9
feat(app_bloc): update AppBloc for AppSettings and FeedSettings models
fulleni Nov 25, 2025
f0d3b14
refactor(app_event): update AppUserAppSettingsChanged event type
fulleni Nov 25, 2025
374cf49
refactor(app_state): update AppState to use AppSettings model
fulleni Nov 25, 2025
440d2f0
refactor(create_headline_state): remove excerpt field
fulleni Nov 25, 2025
0f9aec4
refactor(create_headline_event): remove CreateHeadlineExcerptChanged …
fulleni Nov 25, 2025
ebb856c
refactor(create_headline_bloc): remove excerpt handling logic
fulleni Nov 25, 2025
9e45c31
refactor(create_headline_page): remove excerpt TextFormField and logic
fulleni Nov 25, 2025
db50eb0
refactor(edit_headline_state): remove excerpt field
fulleni Nov 25, 2025
29057c2
refactor(edit_headline_event): remove EditHeadlineExcerptChanged event
fulleni Nov 25, 2025
f8cbb53
refactor(edit_headline_bloc): remove excerpt handling logic
fulleni Nov 25, 2025
c7badff
refactor(edit_headline_page): remove excerpt TextFormField and logic
fulleni Nov 25, 2025
8cf31b5
feat(settings_bloc): update SettingsBloc for AppSettings and FeedSett…
fulleni Nov 25, 2025
0df8dd7
refactor(settings_state): update SettingsState to use AppSettings model
fulleni Nov 25, 2025
bf18995
feat(settings_page): update SettingsPage for AppSettings model
fulleni Nov 25, 2025
c5e4eb6
style: format
fulleni Nov 25, 2025
31c3530
refactor(app_configuration): remove unused features variable
fulleni Nov 25, 2025
053eed9
fix(shared): set fixed pagination limit for initial item fetch
fulleni Nov 25, 2025
5f02ff4
chore(deps): update core package
fulleni Nov 25, 2025
852e94d
refactor(app_configuration): simplify maintenance config and update U…
fulleni Nov 25, 2025
aae7dc1
style: format
fulleni Nov 25, 2025
efe2dda
style: correct typo in comment
fulleni Nov 25, 2025
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
39 changes: 20 additions & 19 deletions lib/app/bloc/app_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,28 @@ part 'app_state.dart';
class AppBloc extends Bloc<AppEvent, AppState> {
AppBloc({
required AuthRepository authenticationRepository,
required DataRepository<UserAppSettings> userAppSettingsRepository,
required DataRepository<AppSettings> appSettingsRepository,
required DataRepository<RemoteConfig> appConfigRepository,
required local_config.AppEnvironment environment,
Logger? logger,
}) : _authenticationRepository = authenticationRepository,
_userAppSettingsRepository = userAppSettingsRepository,
_appSettingsRepository = appSettingsRepository,
_appConfigRepository = appConfigRepository,
_logger = logger ?? Logger('AppBloc'),
super(AppState(environment: environment)) {
on<AppUserChanged>(_onAppUserChanged);
on<AppLogoutRequested>(_onLogoutRequested);
on<AppUserAppSettingsChanged>(_onAppUserAppSettingsChanged);
on<AppUserAppSettingsChanged>(
_onAppUserAppSettingsChanged,
);

_userSubscription = _authenticationRepository.authStateChanges.listen(
(User? user) => add(AppUserChanged(user)),
);
}

final AuthRepository _authenticationRepository;
final DataRepository<UserAppSettings> _userAppSettingsRepository;
final DataRepository<AppSettings> _appSettingsRepository;
final DataRepository<RemoteConfig> _appConfigRepository;
final Logger _logger;
late final StreamSubscription<User?> _userSubscription;
Expand Down Expand Up @@ -68,16 +70,16 @@ class AppBloc extends Bloc<AppEvent, AppState> {
// If user is authenticated, load their app settings
if (status == AppStatus.authenticated && user != null) {
try {
final userAppSettings = await _userAppSettingsRepository.read(
final appSettings = await _appSettingsRepository.read(
id: user.id,
);
emit(state.copyWith(userAppSettings: userAppSettings));
emit(state.copyWith(appSettings: appSettings));
} on NotFoundException {
// If settings not found, create default ones
_logger.info(
'User app settings not found for user ${user.id}. Creating default.',
);
final defaultSettings = UserAppSettings(
final defaultSettings = AppSettings(
id: user.id,
displaySettings: const DisplaySettings(
baseTheme: AppBaseTheme.system,
Expand All @@ -92,49 +94,48 @@ class AppBloc extends Bloc<AppEvent, AppState> {
'Default language "en" not found in language fixtures.',
),
),
feedPreferences: const FeedDisplayPreferences(
headlineDensity: HeadlineDensity.standard,
headlineImageStyle: HeadlineImageStyle.largeThumbnail,
showSourceInHeadlineFeed: true,
showPublishDateInHeadlineFeed: true,
feedSettings: const FeedSettings(
feedItemDensity: FeedItemDensity.standard,
feedItemImageStyle: FeedItemImageStyle.largeThumbnail,
feedItemClickBehavior: FeedItemClickBehavior.defaultBehavior,
),
);
await _userAppSettingsRepository.create(item: defaultSettings);
emit(state.copyWith(userAppSettings: defaultSettings));
await _appSettingsRepository.create(item: defaultSettings);
emit(state.copyWith(appSettings: defaultSettings));
} on HttpException catch (e, s) {
// Handle HTTP exceptions during settings load
_logger.severe(
'Error loading user app settings for user ${user.id}: ${e.message}',
e,
s,
);
emit(state.copyWith(clearUserAppSettings: true));
emit(state.copyWith(clearAppSettings: true));
} catch (e, s) {
// Handle any other unexpected errors
_logger.severe(
'Unexpected error loading user app settings for user ${user.id}: $e',
e,
s,
);
emit(state.copyWith(clearUserAppSettings: true));
emit(state.copyWith(clearAppSettings: true));
}
} else {
// If user is unauthenticated or anonymous, clear app settings
emit(state.copyWith(clearUserAppSettings: true));
emit(state.copyWith(clearAppSettings: true));
}
}

void _onAppUserAppSettingsChanged(
AppUserAppSettingsChanged event,
Emitter<AppState> emit,
) {
emit(state.copyWith(userAppSettings: event.userAppSettings));
emit(state.copyWith(appSettings: event.appSettings));
}

void _onLogoutRequested(AppLogoutRequested event, Emitter<AppState> emit) {
unawaited(_authenticationRepository.signOut());
emit(
state.copyWith(clearUserAppSettings: true),
state.copyWith(clearAppSettings: true),
);
}

Expand Down
23 changes: 8 additions & 15 deletions lib/app/bloc/app_event.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
part of 'app_bloc.dart';

abstract class AppEvent extends Equatable {
sealed class AppEvent extends Equatable {
const AppEvent();

@override
List<Object?> get props => [];
}

class AppUserChanged extends AppEvent {
final class AppUserChanged extends AppEvent {
const AppUserChanged(this.user);

final User? user;
Expand All @@ -16,24 +16,17 @@ class AppUserChanged extends AppEvent {
List<Object?> get props => [user];
}

/// {@template app_logout_requested}
/// Event to request user logout.
/// {@endtemplate}
class AppLogoutRequested extends AppEvent {
/// {@macro app_logout_requested}
final class AppLogoutRequested extends AppEvent {
const AppLogoutRequested();
}

/// {@template app_user_app_settings_changed}
/// Event to notify that user application settings have changed.
/// {@endtemplate}
/// Event for when the user's app settings are changed.
final class AppUserAppSettingsChanged extends AppEvent {
/// {@macro app_user_app_settings_changed}
const AppUserAppSettingsChanged(this.userAppSettings);
const AppUserAppSettingsChanged(this.appSettings);

/// The updated user application settings.
final UserAppSettings userAppSettings;
/// The new user app settings.
final AppSettings appSettings;

@override
List<Object?> get props => [userAppSettings];
List<Object?> get props => [appSettings];
}
52 changes: 15 additions & 37 deletions lib/app/bloc/app_state.dart
Original file line number Diff line number Diff line change
@@ -1,69 +1,47 @@
part of 'app_bloc.dart';

/// Represents the application's authentication status.
enum AppStatus {
/// The application is initializing and the status is unknown.
/// The app is in its initial state, typically before any authentication
/// checks have been performed.
initial,

/// The user is authenticated.
/// The user is authenticated and has a valid session.
authenticated,

/// The user is unauthenticated.
/// The user is unauthenticated, meaning they are not logged in.
unauthenticated,

/// The user is anonymous (signed in using an anonymous provider).
/// The user is authenticated anonymously.
anonymous,
}

/// {@template app_state}
/// Represents the overall state of the application, including authentication
/// status, current user, environment, and user-specific settings.
/// {@endtemplate}
class AppState extends Equatable {
/// {@macro app_state}
final class AppState extends Equatable {
const AppState({
required this.environment,
this.status = AppStatus.initial,
this.user,
this.environment,
this.userAppSettings,
this.appSettings,
});

/// The current authentication status of the application.
final AppStatus status;

/// The current user details. Null if unauthenticated.
final User? user;
final AppSettings? appSettings;
final local_config.AppEnvironment environment;

/// The current application environment (e.g., production, development, demo).
final local_config.AppEnvironment? environment;

/// The current user application settings. Null if not loaded or unauthenticated.
final UserAppSettings? userAppSettings;

/// Creates a copy of the current state with updated values.
AppState copyWith({
AppStatus? status,
User? user,
local_config.AppEnvironment? environment,
UserAppSettings? userAppSettings,
bool clearEnvironment = false,
bool clearUserAppSettings = false,
AppSettings? appSettings,
bool clearAppSettings = false,
}) {
return AppState(
status: status ?? this.status,
user: user ?? this.user,
environment: clearEnvironment ? null : environment ?? this.environment,
userAppSettings: clearUserAppSettings
? null
: userAppSettings ?? this.userAppSettings,
appSettings: clearAppSettings ? null : appSettings ?? this.appSettings,
environment: environment,
);
}

@override
List<Object?> get props => [
status,
user,
environment,
userAppSettings,
];
List<Object?> get props => [status, user, appSettings, environment];
}
2 changes: 1 addition & 1 deletion lib/app/config/app_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class AppConfig {
/// A factory constructor for the demo environment.
factory AppConfig.demo() => AppConfig(
environment: AppEnvironment.demo,
baseUrl: '', // No API access needed for in-memory demo
baseUrl: '',
);

/// A factory constructor for the development environment.
Expand Down
31 changes: 15 additions & 16 deletions lib/app/view/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// ignore_for_file: deprecated_member_use

import 'package:auth_repository/auth_repository.dart';
import 'package:core/core.dart' hide AppStatus;
import 'package:core/core.dart';
import 'package:data_repository/data_repository.dart';
import 'package:flex_color_scheme/flex_color_scheme.dart';
import 'package:flutter/material.dart';
Expand Down Expand Up @@ -33,7 +33,7 @@ class App extends StatelessWidget {
required DataRepository<Headline> headlinesRepository,
required DataRepository<Topic> topicsRepository,
required DataRepository<Source> sourcesRepository,
required DataRepository<UserAppSettings> userAppSettingsRepository,
required DataRepository<AppSettings> appSettingsRepository,
required DataRepository<UserContentPreferences>
userContentPreferencesRepository,
required DataRepository<RemoteConfig> remoteConfigRepository,
Expand All @@ -49,7 +49,7 @@ class App extends StatelessWidget {
_headlinesRepository = headlinesRepository,
_topicsRepository = topicsRepository,
_sourcesRepository = sourcesRepository,
_userAppSettingsRepository = userAppSettingsRepository,
_appSettingsRepository = appSettingsRepository,
_userContentPreferencesRepository = userContentPreferencesRepository,
_remoteConfigRepository = remoteConfigRepository,
_kvStorageService = storageService,
Expand All @@ -64,7 +64,7 @@ class App extends StatelessWidget {
final DataRepository<Headline> _headlinesRepository;
final DataRepository<Topic> _topicsRepository;
final DataRepository<Source> _sourcesRepository;
final DataRepository<UserAppSettings> _userAppSettingsRepository;
final DataRepository<AppSettings> _appSettingsRepository;
final DataRepository<UserContentPreferences>
_userContentPreferencesRepository;
final DataRepository<RemoteConfig> _remoteConfigRepository;
Expand All @@ -86,7 +86,7 @@ class App extends StatelessWidget {
RepositoryProvider.value(value: _headlinesRepository),
RepositoryProvider.value(value: _topicsRepository),
RepositoryProvider.value(value: _sourcesRepository),
RepositoryProvider.value(value: _userAppSettingsRepository),
RepositoryProvider.value(value: _appSettingsRepository),
RepositoryProvider.value(value: _userContentPreferencesRepository),
RepositoryProvider.value(value: _remoteConfigRepository),
RepositoryProvider.value(value: _dashboardSummaryRepository),
Expand All @@ -106,8 +106,8 @@ class App extends StatelessWidget {
BlocProvider(
create: (context) => AppBloc(
authenticationRepository: context.read<AuthRepository>(),
userAppSettingsRepository: context
.read<DataRepository<UserAppSettings>>(),
appSettingsRepository: context
.read<DataRepository<AppSettings>>(),
appConfigRepository: context.read<DataRepository<RemoteConfig>>(),
environment: _environment,
logger: Logger('AppBloc'),
Expand Down Expand Up @@ -214,20 +214,19 @@ class _AppViewState extends State<_AppView> {
return BlocListener<AppBloc, AppState>(
listenWhen: (previous, current) =>
previous.status != current.status ||
previous.userAppSettings != current.userAppSettings,
previous.appSettings != current.appSettings,
listener: (context, state) {
_statusNotifier.value = state.status;
},
child: BlocBuilder<AppBloc, AppState>(
builder: (context, state) {
final userAppSettings = state.userAppSettings;
final baseTheme = userAppSettings?.displaySettings.baseTheme;
final accentTheme = userAppSettings?.displaySettings.accentTheme;
final fontFamily = userAppSettings?.displaySettings.fontFamily;
final textScaleFactor =
userAppSettings?.displaySettings.textScaleFactor;
final fontWeight = userAppSettings?.displaySettings.fontWeight;
final language = userAppSettings?.language;
final appSettings = state.appSettings;
final baseTheme = appSettings?.displaySettings.baseTheme;
final accentTheme = appSettings?.displaySettings.accentTheme;
final fontFamily = appSettings?.displaySettings.fontFamily;
final textScaleFactor = appSettings?.displaySettings.textScaleFactor;
final fontWeight = appSettings?.displaySettings.fontWeight;
final language = appSettings?.language;

final lightThemeData = lightTheme(
scheme: accentTheme?.toFlexScheme ?? FlexScheme.materialHc,
Expand Down
Loading
Loading