Browse Source

RFC: errorBloc

pull/390/head
Nikolas Rimikis 2 years ago
parent
commit
6a6898de75
No known key found for this signature in database
GPG Key ID: 85ED1DE9786A4FF2
  1. 42
      packages/neon/neon/lib/src/blocs/apps.dart
  2. 2
      packages/neon/neon/lib/src/blocs/blocs.dart
  3. 88
      packages/neon/neon/lib/src/blocs/error.dart
  4. 49
      packages/neon/neon/lib/src/pages/home.dart

42
packages/neon/neon/lib/src/blocs/apps.dart

@ -50,6 +50,8 @@ class AppsBloc extends InteractiveBloc implements AppsBlocEvents, AppsBlocStates
}
}),
);
unawaited(_checkCompatibility());
}
});
@ -59,11 +61,51 @@ class AppsBloc extends InteractiveBloc implements AppsBlocEvents, AppsBlocStates
(final data) => data.capabilities.notifications != null ? _findAppImplementation('notifications') : null,
),
);
unawaited(_checkCompatibility());
});
unawaited(refresh());
}
Future<void> _checkCompatibility() async {
final apps = appImplementations.valueOrNull;
final capabilities = _capabilitiesBloc.capabilities.valueOrNull;
if (capabilities == null || apps == null) {
return;
}
final appIds = {
'core',
...apps.data!.map((final a) => a.id),
};
final notSupported = <(String, Object?)>{};
for (final id in appIds) {
try {
final (supported, minVersion) = switch (id) {
'core' => await _account.client.core.isSupported(capabilities.data),
'news' => await _account.client.news.isSupported(),
'notes' => await _account.client.notes.isSupported(capabilities.data),
_ => (true, null),
};
if (!supported) {
notSupported.add((id, minVersion));
}
} catch (e, s) {
debugPrint(e.toString());
debugPrint(s.toString());
}
}
if (notSupported.isNotEmpty) {
ErrorBloc().addVersionErrors(notSupported);
}
}
T? _findAppImplementation<T extends AppImplementation>(final String id) {
final matches = _filteredAppImplementations([id]);
if (matches.isNotEmpty) {

2
packages/neon/neon/lib/src/blocs/blocs.dart

@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:neon/l10n/localizations.dart';
import 'package:neon/neon.dart';
import 'package:nextcloud/nextcloud.dart';
import 'package:package_info_plus/package_info_plus.dart';
@ -14,6 +15,7 @@ import 'package:window_manager/window_manager.dart';
part 'accounts.dart';
part 'apps.dart';
part 'capabilities.dart';
part 'error.dart';
part 'first_launch.dart';
part 'login.dart';
part 'next_push.dart';

88
packages/neon/neon/lib/src/blocs/error.dart

@ -0,0 +1,88 @@
part of 'blocs.dart';
typedef TranslationCallback = String Function(AppLocalizations l10n);
abstract class ErrorBlocEvents {
/// Adds an error to the [ErrorBlocStates.globalErrors].
///
/// Used to signal non app specific errors.
void addGlobalError(final String message);
/// Adds an error to the [ErrorBlocStates.appErrors].
///
/// Used to signal errors specific to an app identified by [appId].
void addAppError(final String appId, final String message);
}
abstract class ErrorBlocStates {
/// Errors for the global neon framework.
BehaviorSubject<String> get globalErrors;
/// Errors for a specific app.
Map<String, BehaviorSubject<String>> get appErrors;
}
/// Holds error messages to be displayed by the UI
///
/// It will cache the last emmited error.
/// The [ErrorBloc] is a singleton.
class ErrorBloc extends Bloc implements ErrorBlocEvents, ErrorBlocStates {
factory ErrorBloc() => instance ??= ErrorBloc._();
@visibleForTesting
factory ErrorBloc.mocked(final ErrorBloc mock) => instance ??= mock;
ErrorBloc._();
@visibleForTesting
static ErrorBloc? instance;
AppLocalizations? l10n;
@override
final BehaviorSubject<String> globalErrors = BehaviorSubject();
@override
final Map<String, BehaviorSubject<String>> appErrors = {};
@override
void dispose() {
ErrorBloc.instance = null;
}
@override
void addGlobalError(final String message) {
globalErrors.add(message);
}
@override
void addAppError(final String appId, final String message) {
if (appErrors[appId] == null) {
appErrors[appId] = BehaviorSubject();
}
appErrors[appId]!.add(message);
}
void addVersionErrors(final Iterable<(String, Object?)> errors) {
assert(l10n != null, 'Localization must be register to process version Errors.');
final buffer = StringBuffer();
for (final error in errors) {
// TODO: reword errorUnsupportedVersion to support multiple errors
// TODO: add version info
final (appId, minVersion) = error;
final appName = l10n!.appImplementationName(appId);
final message = l10n!.errorUnsupportedVersion(appName);
buffer.write(message);
}
if (buffer.isNotEmpty) {
addGlobalError(buffer.toString());
}
}
String translateError(final TranslationCallback callback) => callback(l10n!);
}

49
packages/neon/neon/lib/src/pages/home.dart

@ -11,7 +11,6 @@ class HomePage extends StatefulWidget {
State<HomePage> createState() => _HomePageState();
}
// ignore: prefer_mixin
class _HomePageState extends State<HomePage> {
final _scaffoldKey = GlobalKey<ScaffoldState>();
final drawerScrollController = ScrollController();
@ -20,7 +19,6 @@ class _HomePageState extends State<HomePage> {
late GlobalOptions _globalOptions;
late AccountsBloc _accountsBloc;
late AppsBloc _appsBloc;
late CapabilitiesBloc _capabilitiesBloc;
@override
void initState() {
@ -29,7 +27,6 @@ class _HomePageState extends State<HomePage> {
_accountsBloc = Provider.of<AccountsBloc>(context, listen: false);
_account = _accountsBloc.activeAccount.value!;
_appsBloc = _accountsBloc.activeAppsBloc;
_capabilitiesBloc = _accountsBloc.activeCapabilitiesBloc;
_appsBloc.openNotifications.listen((final _) async {
final notificationsAppImplementation = _appsBloc.notificationsAppImplementation.valueOrNull;
@ -42,47 +39,6 @@ class _HomePageState extends State<HomePage> {
}
});
_capabilitiesBloc.capabilities.listen((final result) async {
if (result.data != null) {
// ignore cached version and prevent duplicate dialogs
if (result.cached) {
return;
}
_appsBloc.appImplementations.listen((final appsResult) async {
// ignore cached version and prevent duplicate dialogs
if (appsResult.data == null || appsResult.cached) {
return;
}
for (final id in [
'core',
...appsResult.data!.map((final a) => a.id),
]) {
try {
final (supported, _) = switch (id) {
'core' => await _account.client.core.isSupported(result.data),
'news' => await _account.client.news.isSupported(),
'notes' => await _account.client.notes.isSupported(result.data),
_ => (true, null),
};
if (supported || !mounted) {
return;
}
var name = AppLocalizations.of(context).appImplementationName(id);
if (name == '') {
name = id;
}
await _showProblem(
AppLocalizations.of(context).errorUnsupportedVersion(name),
);
} catch (e, s) {
debugPrint(e.toString());
debugPrint(s.toString());
}
}
});
}
});
GlobalPopups().register(context);
unawaited(_checkMaintenanceMode());
@ -168,9 +124,7 @@ class _HomePageState extends State<HomePage> {
}
@override
Widget build(final BuildContext context) => ResultBuilder<Capabilities>(
stream: _capabilitiesBloc.capabilities,
builder: (final context, final capabilities) => ResultBuilder<Iterable<AppImplementation>>(
Widget build(final BuildContext context) => ResultBuilder<Iterable<AppImplementation>>(
stream: _appsBloc.appImplementations,
builder: (final context, final appImplementations) => ResultBuilder<NotificationsAppInterface?>(
stream: _appsBloc.notificationsAppImplementation,
@ -329,6 +283,5 @@ class _HomePageState extends State<HomePage> {
),
),
),
),
);
}

Loading…
Cancel
Save