From 72aaa173f5ceabc952ab5c28e024451ac1309920 Mon Sep 17 00:00:00 2001 From: GT610 Date: Sun, 11 Jan 2026 22:35:47 +0800 Subject: [PATCH] refactor(container): Change single error handling to multiple error lists Support the simultaneous display of multiple container operation errors, enhancing error handling capabilities --- lib/data/provider/container.dart | 26 ++++++------ lib/data/provider/container.freezed.dart | 52 +++++++++++++----------- lib/data/provider/container.g.dart | 2 +- lib/data/provider/server/all.g.dart | 2 +- lib/view/page/container/container.dart | 10 ++--- 5 files changed, 50 insertions(+), 42 deletions(-) diff --git a/lib/data/provider/container.dart b/lib/data/provider/container.dart index 99473b5a..a69fa5d8 100644 --- a/lib/data/provider/container.dart +++ b/lib/data/provider/container.dart @@ -25,7 +25,7 @@ abstract class ContainerState with _$ContainerState { @Default(null) List? items, @Default(null) List? images, @Default(null) String? version, - @Default(null) ContainerErr? error, + @Default([]) List errors, @Default(null) String? runLog, @Default(ContainerType.docker) ContainerType type, @Default(false) bool isBusy, @@ -48,7 +48,7 @@ class ContainerNotifier extends _$ContainerNotifier { } Future 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(); 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); } diff --git a/lib/data/provider/container.freezed.dart b/lib/data/provider/container.freezed.dart index 614e4d74..5e36e66f 100644 --- a/lib/data/provider/container.freezed.dart +++ b/lib/data/provider/container.freezed.dart @@ -14,7 +14,7 @@ T _$identity(T value) => value; /// @nodoc mixin _$ContainerState { - List? get items; List? get images; String? get version; ContainerErr? get error; String? get runLog; ContainerType get type; bool get isBusy; + List? get items; List? get images; String? get version; List 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 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? items, List? images, String? version, ContainerErr? error, String? runLog, ContainerType type, bool isBusy + List? items, List? images, String? version, List 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?,images: freezed == images ? _self.images : images // ignore: cast_nullable_to_non_nullable as List?,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,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 Function( List? items, List? images, String? version, ContainerErr? error, String? runLog, ContainerType type, bool isBusy)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( List? items, List? images, String? version, List 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 Function( List? items, List? images, String? version, ContainerErr? error, String? runLog, ContainerType type, bool isBusy) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( List? items, List? images, String? version, List 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? Function( List? items, List? images, String? version, ContainerErr? error, String? runLog, ContainerType type, bool isBusy)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( List? items, List? images, String? version, List 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? items = null, final List? 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? items = null, final List? images = null, this.version = null, final List errors = const [], this.runLog = null, this.type = ContainerType.docker, this.isBusy = false}): _items = items,_images = images,_errors = errors; final List? _items; @@ -234,7 +234,13 @@ class _ContainerState implements ContainerState { } @override@JsonKey() final String? version; -@override@JsonKey() final ContainerErr? error; + final List _errors; +@override@JsonKey() List 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? items, List? images, String? version, ContainerErr? error, String? runLog, ContainerType type, bool isBusy + List? items, List? images, String? version, List 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?,images: freezed == images ? _self._images : images // ignore: cast_nullable_to_non_nullable as List?,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,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, diff --git a/lib/data/provider/container.g.dart b/lib/data/provider/container.g.dart index 21c7e085..bf658b0f 100644 --- a/lib/data/provider/container.g.dart +++ b/lib/data/provider/container.g.dart @@ -58,7 +58,7 @@ final class ContainerNotifierProvider } } -String _$containerNotifierHash() => r'fea65e66499234b0a59bffff8d69c4ab8c93b2fd'; +String _$containerNotifierHash() => r'ab5522342c8d0743d1b886280f81165b08c0b4fb'; final class ContainerNotifierFamily extends $Family with diff --git a/lib/data/provider/server/all.g.dart b/lib/data/provider/server/all.g.dart index 9c2c7df5..1128a62c 100644 --- a/lib/data/provider/server/all.g.dart +++ b/lib/data/provider/server/all.g.dart @@ -41,7 +41,7 @@ final class ServersNotifierProvider } } -String _$serversNotifierHash() => r'3292bdce7d602ff64687b05ff81d120e71761ec2'; +String _$serversNotifierHash() => r'c969ea21a82bb432a633a1c8f5d58eaf1a5c44c4'; abstract class _$ServersNotifier extends $Notifier { ServersState build(); diff --git a/lib/view/page/container/container.dart b/lib/view/page/container/container.dart index dab4b0a6..039a1026 100644 --- a/lib/view/page/container/container.dart +++ b/lib/view/page/container/container.dart @@ -55,12 +55,12 @@ class _ContainerPageState extends ConsumerState { @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 { 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 { 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 { 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; }