Skip to content

Commit 50d5536

Browse files
authored
Merge pull request #2 from headlines-toolkit/feature_remote_config
Feature remote config
2 parents 68ce42b + ddc3969 commit 50d5536

File tree

11 files changed

+1173
-32
lines changed

11 files changed

+1173
-32
lines changed

analysis_options.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
analyzer:
22
errors:
33
avoid_print: ignore
4+
document_ignores: ignore
5+
lines_longer_than_80_chars: ignore
46
include: package:very_good_analysis/analysis_options.9.0.0.yaml
57
linter:
68
rules:

lib/app/bloc/app_bloc.dart

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import 'dart:async';
33
import 'package:bloc/bloc.dart';
44
import 'package:equatable/equatable.dart';
55
import 'package:ht_auth_repository/ht_auth_repository.dart';
6-
import 'package:ht_data_repository/ht_data_repository.dart';
76
import 'package:ht_dashboard/app/config/config.dart' as local_config;
7+
import 'package:ht_data_repository/ht_data_repository.dart';
88
import 'package:ht_shared/ht_shared.dart';
99

1010
part 'app_event.dart';
@@ -21,11 +21,7 @@ class AppBloc extends Bloc<AppEvent, AppState> {
2121
_appConfigRepository = appConfigRepository,
2222
_environment = environment,
2323
super(
24-
const AppState(
25-
user: null,
26-
status: AppStatus.initial,
27-
environment: null,
28-
),
24+
const AppState(),
2925
) {
3026
on<AppUserChanged>(_onAppUserChanged);
3127
on<AppLogoutRequested>(_onLogoutRequested);
@@ -56,7 +52,8 @@ class AppBloc extends Bloc<AppEvent, AppState> {
5652
status = AppStatus.authenticated;
5753
// ignore: no_default_cases
5854
default: // Fallback for any other roles not explicitly handled
59-
status = AppStatus.unauthenticated; // Treat other roles as unauthenticated for dashboard
55+
status = AppStatus
56+
.unauthenticated; // Treat other roles as unauthenticated for dashboard
6057
}
6158

6259
// Emit user and status update

lib/app/bloc/app_state.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ class AppState extends Equatable {
4848

4949
@override
5050
List<Object?> get props => [
51-
status,
52-
user,
53-
environment,
54-
];
51+
status,
52+
user,
53+
environment,
54+
];
5555
}

lib/app/view/app.dart

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,13 @@ import 'package:flutter/material.dart';
55
import 'package:flutter_bloc/flutter_bloc.dart';
66
import 'package:go_router/go_router.dart';
77
import 'package:ht_auth_repository/ht_auth_repository.dart';
8-
import 'package:ht_data_repository/ht_data_repository.dart';
9-
import 'package:ht_kv_storage_service/ht_kv_storage_service.dart';
108
import 'package:ht_dashboard/app/bloc/app_bloc.dart';
119
import 'package:ht_dashboard/app/config/app_environment.dart';
1210
import 'package:ht_dashboard/authentication/bloc/authentication_bloc.dart';
1311
import 'package:ht_dashboard/l10n/app_localizations.dart';
14-
import 'package:ht_dashboard/l10n/l10n.dart';
1512
import 'package:ht_dashboard/router/router.dart';
13+
import 'package:ht_data_repository/ht_data_repository.dart';
14+
import 'package:ht_kv_storage_service/ht_kv_storage_service.dart';
1615
import 'package:ht_shared/ht_shared.dart';
1716

1817
class App extends StatelessWidget {
@@ -24,7 +23,7 @@ class App extends StatelessWidget {
2423
required HtDataRepository<Source> htSourcesRepository,
2524
required HtDataRepository<UserAppSettings> htUserAppSettingsRepository,
2625
required HtDataRepository<UserContentPreferences>
27-
htUserContentPreferencesRepository,
26+
htUserContentPreferencesRepository,
2827
required HtDataRepository<AppConfig> htAppConfigRepository,
2928
required HtKVStorageService kvStorageService,
3029
required AppEnvironment environment,
@@ -47,7 +46,7 @@ class App extends StatelessWidget {
4746
final HtDataRepository<Source> _htSourcesRepository;
4847
final HtDataRepository<UserAppSettings> _htUserAppSettingsRepository;
4948
final HtDataRepository<UserContentPreferences>
50-
_htUserContentPreferencesRepository;
49+
_htUserContentPreferencesRepository;
5150
final HtDataRepository<AppConfig> _htAppConfigRepository;
5251
final HtKVStorageService _kvStorageService;
5352
final AppEnvironment _environment;
@@ -71,8 +70,8 @@ class App extends StatelessWidget {
7170
BlocProvider(
7271
create: (context) => AppBloc(
7372
authenticationRepository: context.read<HtAuthRepository>(),
74-
userAppSettingsRepository:
75-
context.read<HtDataRepository<UserAppSettings>>(),
73+
userAppSettingsRepository: context
74+
.read<HtDataRepository<UserAppSettings>>(),
7675
appConfigRepository: context.read<HtDataRepository<AppConfig>>(),
7776
environment: _environment,
7877
),

lib/app/view/app_shell.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
22
import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart';
33
import 'package:go_router/go_router.dart';
44
import 'package:ht_dashboard/l10n/l10n.dart';
5-
import 'package:ht_dashboard/router/routes.dart';
65

76
/// A responsive scaffold shell for the main application sections.
87
///
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import 'package:bloc/bloc.dart';
2+
import 'package:equatable/equatable.dart';
3+
import 'package:ht_data_repository/ht_data_repository.dart';
4+
import 'package:ht_shared/ht_shared.dart'; // Use AppConfig from ht_shared
5+
6+
part 'app_configuration_event.dart';
7+
part 'app_configuration_state.dart';
8+
9+
class AppConfigurationBloc
10+
extends Bloc<AppConfigurationEvent, AppConfigurationState> {
11+
AppConfigurationBloc({
12+
required HtDataRepository<AppConfig> appConfigRepository,
13+
}) : _appConfigRepository = appConfigRepository,
14+
super(const AppConfigurationState()) {
15+
on<AppConfigurationLoaded>(_onAppConfigurationLoaded);
16+
on<AppConfigurationUpdated>(_onAppConfigurationUpdated);
17+
on<AppConfigurationFieldChanged>(_onAppConfigurationFieldChanged);
18+
}
19+
20+
final HtDataRepository<AppConfig> _appConfigRepository;
21+
22+
Future<void> _onAppConfigurationLoaded(
23+
AppConfigurationLoaded event,
24+
Emitter<AppConfigurationState> emit,
25+
) async {
26+
emit(state.copyWith(status: AppConfigurationStatus.loading));
27+
try {
28+
final appConfig = await _appConfigRepository.read(id: 'app_config');
29+
emit(
30+
state.copyWith(
31+
status: AppConfigurationStatus.success,
32+
appConfig: appConfig,
33+
isDirty: false,
34+
),
35+
);
36+
} on HtHttpException catch (e) {
37+
emit(
38+
state.copyWith(
39+
status: AppConfigurationStatus.failure,
40+
errorMessage: e.message,
41+
),
42+
);
43+
} catch (e) {
44+
emit(
45+
state.copyWith(
46+
status: AppConfigurationStatus.failure,
47+
errorMessage: 'An unknown error occurred: $e',
48+
),
49+
);
50+
}
51+
}
52+
53+
Future<void> _onAppConfigurationUpdated(
54+
AppConfigurationUpdated event,
55+
Emitter<AppConfigurationState> emit,
56+
) async {
57+
emit(state.copyWith(status: AppConfigurationStatus.loading));
58+
try {
59+
final updatedConfig = await _appConfigRepository.update(
60+
id: event.appConfig.id,
61+
item: event.appConfig,
62+
);
63+
emit(
64+
state.copyWith(
65+
status: AppConfigurationStatus.success,
66+
appConfig: updatedConfig,
67+
isDirty: false,
68+
),
69+
);
70+
} on HtHttpException catch (e) {
71+
emit(
72+
state.copyWith(
73+
status: AppConfigurationStatus.failure,
74+
errorMessage: e.message,
75+
),
76+
);
77+
} catch (e) {
78+
emit(
79+
state.copyWith(
80+
status: AppConfigurationStatus.failure,
81+
errorMessage: 'An unknown error occurred: $e',
82+
),
83+
);
84+
}
85+
}
86+
87+
void _onAppConfigurationFieldChanged(
88+
AppConfigurationFieldChanged event,
89+
Emitter<AppConfigurationState> emit,
90+
) {
91+
emit(
92+
state.copyWith(
93+
appConfig: event.appConfig,
94+
isDirty: true,
95+
clearErrorMessage: true, // Clear any previous error messages
96+
),
97+
);
98+
}
99+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
part of 'app_configuration_bloc.dart';
2+
3+
/// Abstract base class for all events in the AppConfigurationBloc.
4+
abstract class AppConfigurationEvent extends Equatable {
5+
/// {@macro app_configuration_event}
6+
const AppConfigurationEvent();
7+
8+
@override
9+
List<Object?> get props => [];
10+
}
11+
12+
/// {@template app_configuration_loaded}
13+
/// Event to request the loading of the application configuration.
14+
/// {@endtemplate}
15+
class AppConfigurationLoaded extends AppConfigurationEvent {
16+
/// {@macro app_configuration_loaded}
17+
const AppConfigurationLoaded();
18+
}
19+
20+
/// {@template app_configuration_updated}
21+
/// Event to request the update of the application configuration.
22+
///
23+
/// Carries the new [appConfig] object to be saved.
24+
/// {@endtemplate}
25+
class AppConfigurationUpdated extends AppConfigurationEvent {
26+
/// {@macro app_configuration_updated}
27+
const AppConfigurationUpdated(this.appConfig);
28+
29+
/// The updated application configuration.
30+
final AppConfig appConfig;
31+
32+
@override
33+
List<Object?> get props => [appConfig];
34+
}
35+
36+
/// {@template app_configuration_field_changed}
37+
/// Event to notify that a field in the application configuration has changed.
38+
///
39+
/// This event is used to update the local state and mark it as dirty,
40+
/// without immediately triggering a backend save.
41+
/// {@endtemplate}
42+
class AppConfigurationFieldChanged extends AppConfigurationEvent {
43+
/// {@macro app_configuration_field_changed}
44+
const AppConfigurationFieldChanged({
45+
this.appConfig,
46+
});
47+
48+
/// The partially or fully updated AppConfig object.
49+
final AppConfig? appConfig;
50+
51+
@override
52+
List<Object?> get props => [appConfig];
53+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
part of 'app_configuration_bloc.dart';
2+
3+
/// Represents the status of the AppConfigurationBloc.
4+
enum AppConfigurationStatus {
5+
/// The configuration is in its initial state.
6+
initial,
7+
8+
/// The configuration is currently being loaded.
9+
loading,
10+
11+
/// The configuration has been successfully loaded or updated.
12+
success,
13+
14+
/// An error occurred during loading or updating the configuration.
15+
failure,
16+
}
17+
18+
/// {@template app_configuration_state}
19+
/// State for the AppConfigurationBloc.
20+
/// {@endtemplate}
21+
class AppConfigurationState extends Equatable {
22+
/// {@macro app_configuration_state}
23+
const AppConfigurationState({
24+
this.status = AppConfigurationStatus.initial,
25+
this.appConfig,
26+
this.errorMessage,
27+
this.isDirty = false,
28+
});
29+
30+
/// The current status of the application configuration.
31+
final AppConfigurationStatus status;
32+
33+
/// The loaded or updated application configuration.
34+
final AppConfig? appConfig;
35+
36+
/// An error message if an operation failed.
37+
final String? errorMessage;
38+
39+
/// Indicates if there are unsaved changes to the configuration.
40+
final bool isDirty;
41+
42+
/// Creates a copy of the current state with updated values.
43+
AppConfigurationState copyWith({
44+
AppConfigurationStatus? status,
45+
AppConfig? appConfig,
46+
String? errorMessage,
47+
bool? isDirty,
48+
bool clearErrorMessage = false,
49+
}) {
50+
return AppConfigurationState(
51+
status: status ?? this.status,
52+
appConfig: appConfig ?? this.appConfig,
53+
errorMessage: clearErrorMessage
54+
? null
55+
: errorMessage ?? this.errorMessage,
56+
isDirty: isDirty ?? this.isDirty,
57+
);
58+
}
59+
60+
@override
61+
List<Object?> get props => [
62+
status,
63+
appConfig,
64+
errorMessage,
65+
isDirty,
66+
];
67+
}

0 commit comments

Comments
 (0)