refactor(container): Change single error handling to multiple error lists

Support the simultaneous display of multiple container operation errors, enhancing error handling capabilities
This commit is contained in:
GT610
2026-01-11 22:35:47 +08:00
parent 0659e7f9d0
commit 72aaa173f5
5 changed files with 50 additions and 42 deletions

View File

@@ -25,7 +25,7 @@ abstract class ContainerState with _$ContainerState {
@Default(null) List<ContainerPs>? items,
@Default(null) List<ContainerImg>? images,
@Default(null) String? version,
@Default(null) ContainerErr? error,
@Default(<ContainerErr>[]) List<ContainerErr> errors,
@Default(null) String? runLog,
@Default(ContainerType.docker) ContainerType type,
@Default(false) bool isBusy,
@@ -48,7 +48,7 @@ class ContainerNotifier extends _$ContainerNotifier {
}
Future<void> setType(ContainerType type) async {
state = state.copyWith(type: type, error: null, runLog: null, items: null, images: null, version: null);
state = state.copyWith(type: type, errors: [], runLog: null, items: null, images: null, version: null);
Stores.container.setType(type, hostId);
sudoCompleter = Completer<bool>();
await refresh();
@@ -98,7 +98,7 @@ class ContainerNotifier extends _$ContainerNotifier {
/// Code 127 means command not found
if (code == 127 || raw.contains(_dockerNotFound)) {
state = state.copyWith(error: ContainerErr(type: ContainerErrType.notInstalled));
state = state.copyWith(errors: [ContainerErr(type: ContainerErrType.notInstalled)]);
return;
}
@@ -106,10 +106,12 @@ class ContainerNotifier extends _$ContainerNotifier {
final segments = raw.split(ScriptConstants.separator);
if (segments.length != ContainerCmdType.values.length) {
state = state.copyWith(
error: ContainerErr(
type: ContainerErrType.segmentsNotMatch,
message: 'Container segments: ${segments.length}',
),
errors: [
ContainerErr(
type: ContainerErrType.segmentsNotMatch,
message: 'Container segments: ${segments.length}',
),
],
);
Loggers.app.warning('Container segments: ${segments.length}\n$raw');
return;
@@ -119,10 +121,10 @@ class ContainerNotifier extends _$ContainerNotifier {
final verRaw = ContainerCmdType.version.find(segments);
try {
final version = json.decode(verRaw)['Client']['Version'];
state = state.copyWith(version: version, error: null);
state = state.copyWith(version: version);
} catch (e, trace) {
state = state.copyWith(
error: ContainerErr(type: ContainerErrType.invalidVersion, message: '$e'),
errors: [...state.errors, ContainerErr(type: ContainerErrType.invalidVersion, message: '$e')],
);
Loggers.app.warning('Container version failed', e, trace);
}
@@ -140,7 +142,7 @@ class ContainerNotifier extends _$ContainerNotifier {
state = state.copyWith(items: items);
} catch (e, trace) {
state = state.copyWith(
error: ContainerErr(type: ContainerErrType.parsePs, message: '$e'),
errors: [...state.errors, ContainerErr(type: ContainerErrType.parsePs, message: '$e')],
);
Loggers.app.warning('Container ps failed', e, trace);
}
@@ -162,7 +164,7 @@ class ContainerNotifier extends _$ContainerNotifier {
state = state.copyWith(images: images);
} catch (e, trace) {
state = state.copyWith(
error: ContainerErr(type: ContainerErrType.parseImages, message: '$e'),
errors: [...state.errors, ContainerErr(type: ContainerErrType.parseImages, message: '$e')],
);
Loggers.app.warning('Container images failed', e, trace);
}
@@ -189,7 +191,7 @@ class ContainerNotifier extends _$ContainerNotifier {
}
} catch (e, trace) {
state = state.copyWith(
error: ContainerErr(type: ContainerErrType.parseStats, message: '$e'),
errors: [...state.errors, ContainerErr(type: ContainerErrType.parseStats, message: '$e')],
);
Loggers.app.warning('Parse docker stats: $statsRaw', e, trace);
}

View File

@@ -14,7 +14,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$ContainerState {
List<ContainerPs>? get items; List<ContainerImg>? get images; String? get version; ContainerErr? get error; String? get runLog; ContainerType get type; bool get isBusy;
List<ContainerPs>? get items; List<ContainerImg>? get images; String? get version; List<ContainerErr> get errors; String? get runLog; ContainerType get type; bool get isBusy;
/// Create a copy of ContainerState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -25,16 +25,16 @@ $ContainerStateCopyWith<ContainerState> get copyWith => _$ContainerStateCopyWith
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is ContainerState&&const DeepCollectionEquality().equals(other.items, items)&&const DeepCollectionEquality().equals(other.images, images)&&(identical(other.version, version) || other.version == version)&&(identical(other.error, error) || other.error == error)&&(identical(other.runLog, runLog) || other.runLog == runLog)&&(identical(other.type, type) || other.type == type)&&(identical(other.isBusy, isBusy) || other.isBusy == isBusy));
return identical(this, other) || (other.runtimeType == runtimeType&&other is ContainerState&&const DeepCollectionEquality().equals(other.items, items)&&const DeepCollectionEquality().equals(other.images, images)&&(identical(other.version, version) || other.version == version)&&const DeepCollectionEquality().equals(other.errors, errors)&&(identical(other.runLog, runLog) || other.runLog == runLog)&&(identical(other.type, type) || other.type == type)&&(identical(other.isBusy, isBusy) || other.isBusy == isBusy));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(items),const DeepCollectionEquality().hash(images),version,error,runLog,type,isBusy);
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(items),const DeepCollectionEquality().hash(images),version,const DeepCollectionEquality().hash(errors),runLog,type,isBusy);
@override
String toString() {
return 'ContainerState(items: $items, images: $images, version: $version, error: $error, runLog: $runLog, type: $type, isBusy: $isBusy)';
return 'ContainerState(items: $items, images: $images, version: $version, errors: $errors, runLog: $runLog, type: $type, isBusy: $isBusy)';
}
@@ -45,7 +45,7 @@ abstract mixin class $ContainerStateCopyWith<$Res> {
factory $ContainerStateCopyWith(ContainerState value, $Res Function(ContainerState) _then) = _$ContainerStateCopyWithImpl;
@useResult
$Res call({
List<ContainerPs>? items, List<ContainerImg>? images, String? version, ContainerErr? error, String? runLog, ContainerType type, bool isBusy
List<ContainerPs>? items, List<ContainerImg>? images, String? version, List<ContainerErr> errors, String? runLog, ContainerType type, bool isBusy
});
@@ -62,13 +62,13 @@ class _$ContainerStateCopyWithImpl<$Res>
/// Create a copy of ContainerState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? items = freezed,Object? images = freezed,Object? version = freezed,Object? error = freezed,Object? runLog = freezed,Object? type = null,Object? isBusy = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? items = freezed,Object? images = freezed,Object? version = freezed,Object? errors = null,Object? runLog = freezed,Object? type = null,Object? isBusy = null,}) {
return _then(_self.copyWith(
items: freezed == items ? _self.items : items // ignore: cast_nullable_to_non_nullable
as List<ContainerPs>?,images: freezed == images ? _self.images : images // ignore: cast_nullable_to_non_nullable
as List<ContainerImg>?,version: freezed == version ? _self.version : version // ignore: cast_nullable_to_non_nullable
as String?,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable
as ContainerErr?,runLog: freezed == runLog ? _self.runLog : runLog // ignore: cast_nullable_to_non_nullable
as String?,errors: null == errors ? _self.errors : errors // ignore: cast_nullable_to_non_nullable
as List<ContainerErr>,runLog: freezed == runLog ? _self.runLog : runLog // ignore: cast_nullable_to_non_nullable
as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as ContainerType,isBusy: null == isBusy ? _self.isBusy : isBusy // ignore: cast_nullable_to_non_nullable
as bool,
@@ -156,10 +156,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<ContainerPs>? items, List<ContainerImg>? images, String? version, ContainerErr? error, String? runLog, ContainerType type, bool isBusy)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<ContainerPs>? items, List<ContainerImg>? images, String? version, List<ContainerErr> errors, String? runLog, ContainerType type, bool isBusy)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _ContainerState() when $default != null:
return $default(_that.items,_that.images,_that.version,_that.error,_that.runLog,_that.type,_that.isBusy);case _:
return $default(_that.items,_that.images,_that.version,_that.errors,_that.runLog,_that.type,_that.isBusy);case _:
return orElse();
}
@@ -177,10 +177,10 @@ return $default(_that.items,_that.images,_that.version,_that.error,_that.runLog,
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<ContainerPs>? items, List<ContainerImg>? images, String? version, ContainerErr? error, String? runLog, ContainerType type, bool isBusy) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<ContainerPs>? items, List<ContainerImg>? images, String? version, List<ContainerErr> errors, String? runLog, ContainerType type, bool isBusy) $default,) {final _that = this;
switch (_that) {
case _ContainerState():
return $default(_that.items,_that.images,_that.version,_that.error,_that.runLog,_that.type,_that.isBusy);case _:
return $default(_that.items,_that.images,_that.version,_that.errors,_that.runLog,_that.type,_that.isBusy);case _:
throw StateError('Unexpected subclass');
}
@@ -197,10 +197,10 @@ return $default(_that.items,_that.images,_that.version,_that.error,_that.runLog,
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<ContainerPs>? items, List<ContainerImg>? images, String? version, ContainerErr? error, String? runLog, ContainerType type, bool isBusy)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<ContainerPs>? items, List<ContainerImg>? images, String? version, List<ContainerErr> errors, String? runLog, ContainerType type, bool isBusy)? $default,) {final _that = this;
switch (_that) {
case _ContainerState() when $default != null:
return $default(_that.items,_that.images,_that.version,_that.error,_that.runLog,_that.type,_that.isBusy);case _:
return $default(_that.items,_that.images,_that.version,_that.errors,_that.runLog,_that.type,_that.isBusy);case _:
return null;
}
@@ -212,7 +212,7 @@ return $default(_that.items,_that.images,_that.version,_that.error,_that.runLog,
class _ContainerState implements ContainerState {
const _ContainerState({final List<ContainerPs>? items = null, final List<ContainerImg>? images = null, this.version = null, this.error = null, this.runLog = null, this.type = ContainerType.docker, this.isBusy = false}): _items = items,_images = images;
const _ContainerState({final List<ContainerPs>? items = null, final List<ContainerImg>? images = null, this.version = null, final List<ContainerErr> errors = const <ContainerErr>[], this.runLog = null, this.type = ContainerType.docker, this.isBusy = false}): _items = items,_images = images,_errors = errors;
final List<ContainerPs>? _items;
@@ -234,7 +234,13 @@ class _ContainerState implements ContainerState {
}
@override@JsonKey() final String? version;
@override@JsonKey() final ContainerErr? error;
final List<ContainerErr> _errors;
@override@JsonKey() List<ContainerErr> get errors {
if (_errors is EqualUnmodifiableListView) return _errors;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_errors);
}
@override@JsonKey() final String? runLog;
@override@JsonKey() final ContainerType type;
@override@JsonKey() final bool isBusy;
@@ -249,16 +255,16 @@ _$ContainerStateCopyWith<_ContainerState> get copyWith => __$ContainerStateCopyW
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ContainerState&&const DeepCollectionEquality().equals(other._items, _items)&&const DeepCollectionEquality().equals(other._images, _images)&&(identical(other.version, version) || other.version == version)&&(identical(other.error, error) || other.error == error)&&(identical(other.runLog, runLog) || other.runLog == runLog)&&(identical(other.type, type) || other.type == type)&&(identical(other.isBusy, isBusy) || other.isBusy == isBusy));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ContainerState&&const DeepCollectionEquality().equals(other._items, _items)&&const DeepCollectionEquality().equals(other._images, _images)&&(identical(other.version, version) || other.version == version)&&const DeepCollectionEquality().equals(other._errors, _errors)&&(identical(other.runLog, runLog) || other.runLog == runLog)&&(identical(other.type, type) || other.type == type)&&(identical(other.isBusy, isBusy) || other.isBusy == isBusy));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_items),const DeepCollectionEquality().hash(_images),version,error,runLog,type,isBusy);
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_items),const DeepCollectionEquality().hash(_images),version,const DeepCollectionEquality().hash(_errors),runLog,type,isBusy);
@override
String toString() {
return 'ContainerState(items: $items, images: $images, version: $version, error: $error, runLog: $runLog, type: $type, isBusy: $isBusy)';
return 'ContainerState(items: $items, images: $images, version: $version, errors: $errors, runLog: $runLog, type: $type, isBusy: $isBusy)';
}
@@ -269,7 +275,7 @@ abstract mixin class _$ContainerStateCopyWith<$Res> implements $ContainerStateCo
factory _$ContainerStateCopyWith(_ContainerState value, $Res Function(_ContainerState) _then) = __$ContainerStateCopyWithImpl;
@override @useResult
$Res call({
List<ContainerPs>? items, List<ContainerImg>? images, String? version, ContainerErr? error, String? runLog, ContainerType type, bool isBusy
List<ContainerPs>? items, List<ContainerImg>? images, String? version, List<ContainerErr> errors, String? runLog, ContainerType type, bool isBusy
});
@@ -286,13 +292,13 @@ class __$ContainerStateCopyWithImpl<$Res>
/// Create a copy of ContainerState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? items = freezed,Object? images = freezed,Object? version = freezed,Object? error = freezed,Object? runLog = freezed,Object? type = null,Object? isBusy = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? items = freezed,Object? images = freezed,Object? version = freezed,Object? errors = null,Object? runLog = freezed,Object? type = null,Object? isBusy = null,}) {
return _then(_ContainerState(
items: freezed == items ? _self._items : items // ignore: cast_nullable_to_non_nullable
as List<ContainerPs>?,images: freezed == images ? _self._images : images // ignore: cast_nullable_to_non_nullable
as List<ContainerImg>?,version: freezed == version ? _self.version : version // ignore: cast_nullable_to_non_nullable
as String?,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable
as ContainerErr?,runLog: freezed == runLog ? _self.runLog : runLog // ignore: cast_nullable_to_non_nullable
as String?,errors: null == errors ? _self._errors : errors // ignore: cast_nullable_to_non_nullable
as List<ContainerErr>,runLog: freezed == runLog ? _self.runLog : runLog // ignore: cast_nullable_to_non_nullable
as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as ContainerType,isBusy: null == isBusy ? _self.isBusy : isBusy // ignore: cast_nullable_to_non_nullable
as bool,

View File

@@ -58,7 +58,7 @@ final class ContainerNotifierProvider
}
}
String _$containerNotifierHash() => r'fea65e66499234b0a59bffff8d69c4ab8c93b2fd';
String _$containerNotifierHash() => r'ab5522342c8d0743d1b886280f81165b08c0b4fb';
final class ContainerNotifierFamily extends $Family
with

View File

@@ -41,7 +41,7 @@ final class ServersNotifierProvider
}
}
String _$serversNotifierHash() => r'3292bdce7d602ff64687b05ff81d120e71761ec2';
String _$serversNotifierHash() => r'c969ea21a82bb432a633a1c8f5d58eaf1a5c44c4';
abstract class _$ServersNotifier extends $Notifier<ServersState> {
ServersState build();

View File

@@ -55,12 +55,12 @@ class _ContainerPageState extends ConsumerState<ContainerPage> {
@override
Widget build(BuildContext context) {
final err = ref.watch(_provider.select((p) => p.error));
final errors = ref.watch(_provider.select((p) => p.errors));
return Scaffold(
appBar: _buildAppBar(),
body: SafeArea(child: _buildMain()),
floatingActionButton: err == null ? _buildFAB() : null,
floatingActionButton: errors.isEmpty ? _buildFAB() : null,
);
}
@@ -84,7 +84,7 @@ class _ContainerPageState extends ConsumerState<ContainerPage> {
Widget _buildMain() {
final containerState = _containerState;
if (containerState.error != null && containerState.items == null) {
if (containerState.errors.isNotEmpty && containerState.items == null) {
return SizedBox.expand(
child: Column(
children: [
@@ -93,7 +93,7 @@ class _ContainerPageState extends ConsumerState<ContainerPage> {
UIs.height13,
Padding(
padding: const EdgeInsets.symmetric(horizontal: 23),
child: Text(containerState.error.toString()),
child: Text(containerState.errors.map((e) => e.toString()).join('\n')),
),
const Spacer(),
UIs.height13,
@@ -334,7 +334,7 @@ class _ContainerPageState extends ConsumerState<ContainerPage> {
return ExpandTile(
leading: const Icon(Icons.settings),
title: Text(libL10n.setting),
initiallyExpanded: containerState.error != null,
initiallyExpanded: containerState.errors.isNotEmpty,
children: _SettingsMenuItems.values.map((item) => _buildSettingTile(item, containerState)).toList(),
).cardx;
}