From 51c9ec6e6766a86a4b0a25b686f8ccb5035dac0f Mon Sep 17 00:00:00 2001 From: theEvilReaper Date: Thu, 18 Sep 2025 22:35:15 +0200 Subject: [PATCH 01/12] Switch list type to paginated result for the ItemModel --- lib/api/model/item_model.dart | 5 + lib/api/model/item_model.freezed.dart | 133 +++++++++++++++++++++++++- lib/api/state/app_state.dart | 33 +++++-- lib/api/state/app_state.freezed.dart | 67 +++++++------ lib/api/state/app_state.g.dart | 19 ++-- 5 files changed, 206 insertions(+), 51 deletions(-) diff --git a/lib/api/model/item_model.dart b/lib/api/model/item_model.dart index 90abdfb1..c532b74a 100644 --- a/lib/api/model/item_model.dart +++ b/lib/api/model/item_model.dart @@ -5,6 +5,11 @@ import 'data_model.dart'; part 'item_model.g.dart'; part 'item_model.freezed.dart'; +ItemModel itemModelFromJson(Object? json) => + ItemModel.fromJson(json as Map); + +Map itemModelToJson(ItemModel item) => item.toJson(); + @freezed abstract class ItemModel with _$ItemModel, DataModel { const ItemModel._(); // Add this private constructor diff --git a/lib/api/model/item_model.freezed.dart b/lib/api/model/item_model.freezed.dart index 466e6a9a..236db3e9 100644 --- a/lib/api/model/item_model.freezed.dart +++ b/lib/api/model/item_model.freezed.dart @@ -1,6 +1,5 @@ -// dart format width=80 -// coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file // ignore_for_file: type=lint // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark @@ -87,6 +86,136 @@ as List?, } +/// Adds pattern-matching-related methods to [ItemModel]. +extension ItemModelPatterns on ItemModel { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ItemModel value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ItemModel() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ItemModel value) $default,){ +final _that = this; +switch (_that) { +case _ItemModel(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ItemModel value)? $default,){ +final _that = this; +switch (_that) { +case _ItemModel() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String uiName, String? id, String? variableName, String? comment, String? displayName, ItemGroup group, String? material, int? customModelId, int? amount, Map? enchantments, Set? flags, List? lore)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ItemModel() when $default != null: +return $default(_that.uiName,_that.id,_that.variableName,_that.comment,_that.displayName,_that.group,_that.material,_that.customModelId,_that.amount,_that.enchantments,_that.flags,_that.lore);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String uiName, String? id, String? variableName, String? comment, String? displayName, ItemGroup group, String? material, int? customModelId, int? amount, Map? enchantments, Set? flags, List? lore) $default,) {final _that = this; +switch (_that) { +case _ItemModel(): +return $default(_that.uiName,_that.id,_that.variableName,_that.comment,_that.displayName,_that.group,_that.material,_that.customModelId,_that.amount,_that.enchantments,_that.flags,_that.lore);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String uiName, String? id, String? variableName, String? comment, String? displayName, ItemGroup group, String? material, int? customModelId, int? amount, Map? enchantments, Set? flags, List? lore)? $default,) {final _that = this; +switch (_that) { +case _ItemModel() when $default != null: +return $default(_that.uiName,_that.id,_that.variableName,_that.comment,_that.displayName,_that.group,_that.material,_that.customModelId,_that.amount,_that.enchantments,_that.flags,_that.lore);case _: + return null; + +} +} + +} + /// @nodoc @JsonSerializable() diff --git a/lib/api/state/app_state.dart b/lib/api/state/app_state.dart index 2d1c01ef..e2b7fe60 100644 --- a/lib/api/state/app_state.dart +++ b/lib/api/state/app_state.dart @@ -7,6 +7,7 @@ import 'package:stelaris/api/model/font_model.dart'; import 'package:stelaris/api/model/item_model.dart'; import 'package:stelaris/api/model/notification_model.dart'; import 'package:stelaris/api/model/theme/theme_settings.dart'; +import 'package:stelaris/api/paginated_result.dart'; part 'app_state.g.dart'; part 'app_state.freezed.dart'; @@ -14,7 +15,20 @@ part 'app_state.freezed.dart'; @freezed abstract class AppState with _$AppState { const factory AppState({ - @Default([]) List items, + @GenericPaginatedResultConverter( + fromJsonT: itemModelFromJson, + toJsonT: itemModelToJson, + ) + @Default( + PaginatedResult( + items: [], + totalItems: 0, + totalPages: 0, + currentPage: 1, + pageSize: 0, + ), + ) + PaginatedResult items, @Default([]) List notifications, @Default([]) List fonts, @GenericPaginatedResultConverter( @@ -35,13 +49,16 @@ abstract class AppState with _$AppState { @Default(false) bool isLoadingAttributesMore, @Default(true) bool openNavigation, - @Default(ThemeSettings( - isDarkMode: false, - primaryColor: Colors.blue, - accentColor: Colors.blueAccent, - fontScale: 1, - useSystemTheme: true, - )) ThemeSettings themeSettings, + @Default( + ThemeSettings( + isDarkMode: false, + primaryColor: Colors.blue, + accentColor: Colors.blueAccent, + fontScale: 1, + useSystemTheme: true, + ), + ) + ThemeSettings themeSettings, @JsonKey(includeToJson: false) ItemModel? selectedItem, @JsonKey(includeToJson: false) NotificationModel? selectedNotification, @JsonKey(includeToJson: false) FontModel? selectedFont, diff --git a/lib/api/state/app_state.freezed.dart b/lib/api/state/app_state.freezed.dart index 76688f3f..9ccdfb4b 100644 --- a/lib/api/state/app_state.freezed.dart +++ b/lib/api/state/app_state.freezed.dart @@ -15,7 +15,7 @@ T _$identity(T value) => value; /// @nodoc mixin _$AppState { - List get items; List get notifications; List get fonts;@GenericPaginatedResultConverter(fromJsonT: attributeFromJson, toJsonT: attributeToJson) PaginatedResult get attributes;@JsonKey(includeToJson: false, includeFromJson: false) bool get isLoadingAttributesMore; bool get openNavigation; ThemeSettings get themeSettings;@JsonKey(includeToJson: false) ItemModel? get selectedItem;@JsonKey(includeToJson: false) NotificationModel? get selectedNotification;@JsonKey(includeToJson: false) FontModel? get selectedFont;@JsonKey(includeToJson: false) AttributeModel? get selectedAttribute; +@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) PaginatedResult get items; List get notifications; List get fonts; List get attributes; bool get openNavigation; ThemeSettings get themeSettings;@JsonKey(includeToJson: false) ItemModel? get selectedItem;@JsonKey(includeToJson: false) NotificationModel? get selectedNotification;@JsonKey(includeToJson: false) FontModel? get selectedFont;@JsonKey(includeToJson: false) AttributeModel? get selectedAttribute; /// Create a copy of AppState /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -28,16 +28,16 @@ $AppStateCopyWith get copyWith => _$AppStateCopyWithImpl(thi @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is AppState&&const DeepCollectionEquality().equals(other.items, items)&&const DeepCollectionEquality().equals(other.notifications, notifications)&&const DeepCollectionEquality().equals(other.fonts, fonts)&&(identical(other.attributes, attributes) || other.attributes == attributes)&&(identical(other.isLoadingAttributesMore, isLoadingAttributesMore) || other.isLoadingAttributesMore == isLoadingAttributesMore)&&(identical(other.openNavigation, openNavigation) || other.openNavigation == openNavigation)&&(identical(other.themeSettings, themeSettings) || other.themeSettings == themeSettings)&&(identical(other.selectedItem, selectedItem) || other.selectedItem == selectedItem)&&(identical(other.selectedNotification, selectedNotification) || other.selectedNotification == selectedNotification)&&(identical(other.selectedFont, selectedFont) || other.selectedFont == selectedFont)&&(identical(other.selectedAttribute, selectedAttribute) || other.selectedAttribute == selectedAttribute)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is AppState&&(identical(other.items, items) || other.items == items)&&const DeepCollectionEquality().equals(other.notifications, notifications)&&const DeepCollectionEquality().equals(other.fonts, fonts)&&const DeepCollectionEquality().equals(other.attributes, attributes)&&(identical(other.openNavigation, openNavigation) || other.openNavigation == openNavigation)&&(identical(other.themeSettings, themeSettings) || other.themeSettings == themeSettings)&&(identical(other.selectedItem, selectedItem) || other.selectedItem == selectedItem)&&(identical(other.selectedNotification, selectedNotification) || other.selectedNotification == selectedNotification)&&(identical(other.selectedFont, selectedFont) || other.selectedFont == selectedFont)&&(identical(other.selectedAttribute, selectedAttribute) || other.selectedAttribute == selectedAttribute)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(items),const DeepCollectionEquality().hash(notifications),const DeepCollectionEquality().hash(fonts),attributes,isLoadingAttributesMore,openNavigation,themeSettings,selectedItem,selectedNotification,selectedFont,selectedAttribute); +int get hashCode => Object.hash(runtimeType,items,const DeepCollectionEquality().hash(notifications),const DeepCollectionEquality().hash(fonts),const DeepCollectionEquality().hash(attributes),openNavigation,themeSettings,selectedItem,selectedNotification,selectedFont,selectedAttribute); @override String toString() { - return 'AppState(items: $items, notifications: $notifications, fonts: $fonts, attributes: $attributes, isLoadingAttributesMore: $isLoadingAttributesMore, openNavigation: $openNavigation, themeSettings: $themeSettings, selectedItem: $selectedItem, selectedNotification: $selectedNotification, selectedFont: $selectedFont, selectedAttribute: $selectedAttribute)'; + return 'AppState(items: $items, notifications: $notifications, fonts: $fonts, attributes: $attributes, openNavigation: $openNavigation, themeSettings: $themeSettings, selectedItem: $selectedItem, selectedNotification: $selectedNotification, selectedFont: $selectedFont, selectedAttribute: $selectedAttribute)'; } @@ -48,7 +48,7 @@ abstract mixin class $AppStateCopyWith<$Res> { factory $AppStateCopyWith(AppState value, $Res Function(AppState) _then) = _$AppStateCopyWithImpl; @useResult $Res call({ - List items, List notifications, List fonts,@GenericPaginatedResultConverter(fromJsonT: attributeFromJson, toJsonT: attributeToJson) PaginatedResult attributes,@JsonKey(includeToJson: false, includeFromJson: false) bool isLoadingAttributesMore, bool openNavigation, ThemeSettings themeSettings,@JsonKey(includeToJson: false) ItemModel? selectedItem,@JsonKey(includeToJson: false) NotificationModel? selectedNotification,@JsonKey(includeToJson: false) FontModel? selectedFont,@JsonKey(includeToJson: false) AttributeModel? selectedAttribute +@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) PaginatedResult items, List notifications, List fonts, List attributes, bool openNavigation, ThemeSettings themeSettings,@JsonKey(includeToJson: false) ItemModel? selectedItem,@JsonKey(includeToJson: false) NotificationModel? selectedNotification,@JsonKey(includeToJson: false) FontModel? selectedFont,@JsonKey(includeToJson: false) AttributeModel? selectedAttribute }); @@ -65,14 +65,13 @@ class _$AppStateCopyWithImpl<$Res> /// Create a copy of AppState /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? items = null,Object? notifications = null,Object? fonts = null,Object? attributes = null,Object? isLoadingAttributesMore = null,Object? openNavigation = null,Object? themeSettings = null,Object? selectedItem = freezed,Object? selectedNotification = freezed,Object? selectedFont = freezed,Object? selectedAttribute = freezed,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? items = null,Object? notifications = null,Object? fonts = null,Object? attributes = null,Object? openNavigation = null,Object? themeSettings = null,Object? selectedItem = freezed,Object? selectedNotification = freezed,Object? selectedFont = freezed,Object? selectedAttribute = freezed,}) { return _then(_self.copyWith( items: null == items ? _self.items : items // ignore: cast_nullable_to_non_nullable -as List,notifications: null == notifications ? _self.notifications : notifications // ignore: cast_nullable_to_non_nullable +as PaginatedResult,notifications: null == notifications ? _self.notifications : notifications // ignore: cast_nullable_to_non_nullable as List,fonts: null == fonts ? _self.fonts : fonts // ignore: cast_nullable_to_non_nullable as List,attributes: null == attributes ? _self.attributes : attributes // ignore: cast_nullable_to_non_nullable -as PaginatedResult,isLoadingAttributesMore: null == isLoadingAttributesMore ? _self.isLoadingAttributesMore : isLoadingAttributesMore // ignore: cast_nullable_to_non_nullable -as bool,openNavigation: null == openNavigation ? _self.openNavigation : openNavigation // ignore: cast_nullable_to_non_nullable +as List,openNavigation: null == openNavigation ? _self.openNavigation : openNavigation // ignore: cast_nullable_to_non_nullable as bool,themeSettings: null == themeSettings ? _self.themeSettings : themeSettings // ignore: cast_nullable_to_non_nullable as ThemeSettings,selectedItem: freezed == selectedItem ? _self.selectedItem : selectedItem // ignore: cast_nullable_to_non_nullable as ItemModel?,selectedNotification: freezed == selectedNotification ? _self.selectedNotification : selectedNotification // ignore: cast_nullable_to_non_nullable @@ -211,10 +210,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( List items, List notifications, List fonts, @GenericPaginatedResultConverter(fromJsonT: attributeFromJson, toJsonT: attributeToJson) PaginatedResult attributes, @JsonKey(includeToJson: false, includeFromJson: false) bool isLoadingAttributesMore, bool openNavigation, ThemeSettings themeSettings, @JsonKey(includeToJson: false) ItemModel? selectedItem, @JsonKey(includeToJson: false) NotificationModel? selectedNotification, @JsonKey(includeToJson: false) FontModel? selectedFont, @JsonKey(includeToJson: false) AttributeModel? selectedAttribute)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function(@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) PaginatedResult items, List notifications, List fonts, List attributes, bool openNavigation, ThemeSettings themeSettings, @JsonKey(includeToJson: false) ItemModel? selectedItem, @JsonKey(includeToJson: false) NotificationModel? selectedNotification, @JsonKey(includeToJson: false) FontModel? selectedFont, @JsonKey(includeToJson: false) AttributeModel? selectedAttribute)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _AppState() when $default != null: -return $default(_that.items,_that.notifications,_that.fonts,_that.attributes,_that.isLoadingAttributesMore,_that.openNavigation,_that.themeSettings,_that.selectedItem,_that.selectedNotification,_that.selectedFont,_that.selectedAttribute);case _: +return $default(_that.items,_that.notifications,_that.fonts,_that.attributes,_that.openNavigation,_that.themeSettings,_that.selectedItem,_that.selectedNotification,_that.selectedFont,_that.selectedAttribute);case _: return orElse(); } @@ -232,10 +231,10 @@ return $default(_that.items,_that.notifications,_that.fonts,_that.attributes,_th /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( List items, List notifications, List fonts, @GenericPaginatedResultConverter(fromJsonT: attributeFromJson, toJsonT: attributeToJson) PaginatedResult attributes, @JsonKey(includeToJson: false, includeFromJson: false) bool isLoadingAttributesMore, bool openNavigation, ThemeSettings themeSettings, @JsonKey(includeToJson: false) ItemModel? selectedItem, @JsonKey(includeToJson: false) NotificationModel? selectedNotification, @JsonKey(includeToJson: false) FontModel? selectedFont, @JsonKey(includeToJson: false) AttributeModel? selectedAttribute) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function(@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) PaginatedResult items, List notifications, List fonts, List attributes, bool openNavigation, ThemeSettings themeSettings, @JsonKey(includeToJson: false) ItemModel? selectedItem, @JsonKey(includeToJson: false) NotificationModel? selectedNotification, @JsonKey(includeToJson: false) FontModel? selectedFont, @JsonKey(includeToJson: false) AttributeModel? selectedAttribute) $default,) {final _that = this; switch (_that) { case _AppState(): -return $default(_that.items,_that.notifications,_that.fonts,_that.attributes,_that.isLoadingAttributesMore,_that.openNavigation,_that.themeSettings,_that.selectedItem,_that.selectedNotification,_that.selectedFont,_that.selectedAttribute);case _: +return $default(_that.items,_that.notifications,_that.fonts,_that.attributes,_that.openNavigation,_that.themeSettings,_that.selectedItem,_that.selectedNotification,_that.selectedFont,_that.selectedAttribute);case _: throw StateError('Unexpected subclass'); } @@ -252,10 +251,10 @@ return $default(_that.items,_that.notifications,_that.fonts,_that.attributes,_th /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( List items, List notifications, List fonts, @GenericPaginatedResultConverter(fromJsonT: attributeFromJson, toJsonT: attributeToJson) PaginatedResult attributes, @JsonKey(includeToJson: false, includeFromJson: false) bool isLoadingAttributesMore, bool openNavigation, ThemeSettings themeSettings, @JsonKey(includeToJson: false) ItemModel? selectedItem, @JsonKey(includeToJson: false) NotificationModel? selectedNotification, @JsonKey(includeToJson: false) FontModel? selectedFont, @JsonKey(includeToJson: false) AttributeModel? selectedAttribute)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function(@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) PaginatedResult items, List notifications, List fonts, List attributes, bool openNavigation, ThemeSettings themeSettings, @JsonKey(includeToJson: false) ItemModel? selectedItem, @JsonKey(includeToJson: false) NotificationModel? selectedNotification, @JsonKey(includeToJson: false) FontModel? selectedFont, @JsonKey(includeToJson: false) AttributeModel? selectedAttribute)? $default,) {final _that = this; switch (_that) { case _AppState() when $default != null: -return $default(_that.items,_that.notifications,_that.fonts,_that.attributes,_that.isLoadingAttributesMore,_that.openNavigation,_that.themeSettings,_that.selectedItem,_that.selectedNotification,_that.selectedFont,_that.selectedAttribute);case _: +return $default(_that.items,_that.notifications,_that.fonts,_that.attributes,_that.openNavigation,_that.themeSettings,_that.selectedItem,_that.selectedNotification,_that.selectedFont,_that.selectedAttribute);case _: return null; } @@ -267,16 +266,10 @@ return $default(_that.items,_that.notifications,_that.fonts,_that.attributes,_th @JsonSerializable() class _AppState implements AppState { - const _AppState({final List items = const [], final List notifications = const [], final List fonts = const [], @GenericPaginatedResultConverter(fromJsonT: attributeFromJson, toJsonT: attributeToJson) this.attributes = const PaginatedResult(items: [], totalItems: 0, totalPages: 0, currentPage: 1, pageSize: 0), @JsonKey(includeToJson: false, includeFromJson: false) this.isLoadingAttributesMore = false, this.openNavigation = true, this.themeSettings = const ThemeSettings(isDarkMode: false, primaryColor: Colors.blue, accentColor: Colors.blueAccent, fontScale: 1, useSystemTheme: true), @JsonKey(includeToJson: false) this.selectedItem, @JsonKey(includeToJson: false) this.selectedNotification, @JsonKey(includeToJson: false) this.selectedFont, @JsonKey(includeToJson: false) this.selectedAttribute}): _items = items,_notifications = notifications,_fonts = fonts; + const _AppState({@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) this.items = const PaginatedResult(items: [], totalItems: 0, totalPages: 0, currentPage: 1, pageSize: 0), final List notifications = const [], final List fonts = const [], final List attributes = const [], this.openNavigation = true, this.themeSettings = const ThemeSettings(isDarkMode: false, primaryColor: Colors.blue, accentColor: Colors.blueAccent, fontScale: 1, useSystemTheme: true), @JsonKey(includeToJson: false) this.selectedItem, @JsonKey(includeToJson: false) this.selectedNotification, @JsonKey(includeToJson: false) this.selectedFont, @JsonKey(includeToJson: false) this.selectedAttribute}): _notifications = notifications,_fonts = fonts,_attributes = attributes; factory _AppState.fromJson(Map json) => _$AppStateFromJson(json); - final List _items; -@override@JsonKey() List get items { - if (_items is EqualUnmodifiableListView) return _items; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_items); -} - +@override@JsonKey()@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) final PaginatedResult items; final List _notifications; @override@JsonKey() List get notifications { if (_notifications is EqualUnmodifiableListView) return _notifications; @@ -291,8 +284,13 @@ class _AppState implements AppState { return EqualUnmodifiableListView(_fonts); } -@override@JsonKey()@GenericPaginatedResultConverter(fromJsonT: attributeFromJson, toJsonT: attributeToJson) final PaginatedResult attributes; -@override@JsonKey(includeToJson: false, includeFromJson: false) final bool isLoadingAttributesMore; + final List _attributes; +@override@JsonKey() List get attributes { + if (_attributes is EqualUnmodifiableListView) return _attributes; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_attributes); +} + @override@JsonKey() final bool openNavigation; @override@JsonKey() final ThemeSettings themeSettings; @override@JsonKey(includeToJson: false) final ItemModel? selectedItem; @@ -313,16 +311,16 @@ Map toJson() { @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppState&&const DeepCollectionEquality().equals(other._items, _items)&&const DeepCollectionEquality().equals(other._notifications, _notifications)&&const DeepCollectionEquality().equals(other._fonts, _fonts)&&(identical(other.attributes, attributes) || other.attributes == attributes)&&(identical(other.isLoadingAttributesMore, isLoadingAttributesMore) || other.isLoadingAttributesMore == isLoadingAttributesMore)&&(identical(other.openNavigation, openNavigation) || other.openNavigation == openNavigation)&&(identical(other.themeSettings, themeSettings) || other.themeSettings == themeSettings)&&(identical(other.selectedItem, selectedItem) || other.selectedItem == selectedItem)&&(identical(other.selectedNotification, selectedNotification) || other.selectedNotification == selectedNotification)&&(identical(other.selectedFont, selectedFont) || other.selectedFont == selectedFont)&&(identical(other.selectedAttribute, selectedAttribute) || other.selectedAttribute == selectedAttribute)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppState&&(identical(other.items, items) || other.items == items)&&const DeepCollectionEquality().equals(other._notifications, _notifications)&&const DeepCollectionEquality().equals(other._fonts, _fonts)&&const DeepCollectionEquality().equals(other._attributes, _attributes)&&(identical(other.openNavigation, openNavigation) || other.openNavigation == openNavigation)&&(identical(other.themeSettings, themeSettings) || other.themeSettings == themeSettings)&&(identical(other.selectedItem, selectedItem) || other.selectedItem == selectedItem)&&(identical(other.selectedNotification, selectedNotification) || other.selectedNotification == selectedNotification)&&(identical(other.selectedFont, selectedFont) || other.selectedFont == selectedFont)&&(identical(other.selectedAttribute, selectedAttribute) || other.selectedAttribute == selectedAttribute)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_items),const DeepCollectionEquality().hash(_notifications),const DeepCollectionEquality().hash(_fonts),attributes,isLoadingAttributesMore,openNavigation,themeSettings,selectedItem,selectedNotification,selectedFont,selectedAttribute); +int get hashCode => Object.hash(runtimeType,items,const DeepCollectionEquality().hash(_notifications),const DeepCollectionEquality().hash(_fonts),const DeepCollectionEquality().hash(_attributes),openNavigation,themeSettings,selectedItem,selectedNotification,selectedFont,selectedAttribute); @override String toString() { - return 'AppState(items: $items, notifications: $notifications, fonts: $fonts, attributes: $attributes, isLoadingAttributesMore: $isLoadingAttributesMore, openNavigation: $openNavigation, themeSettings: $themeSettings, selectedItem: $selectedItem, selectedNotification: $selectedNotification, selectedFont: $selectedFont, selectedAttribute: $selectedAttribute)'; + return 'AppState(items: $items, notifications: $notifications, fonts: $fonts, attributes: $attributes, openNavigation: $openNavigation, themeSettings: $themeSettings, selectedItem: $selectedItem, selectedNotification: $selectedNotification, selectedFont: $selectedFont, selectedAttribute: $selectedAttribute)'; } @@ -333,7 +331,7 @@ abstract mixin class _$AppStateCopyWith<$Res> implements $AppStateCopyWith<$Res> factory _$AppStateCopyWith(_AppState value, $Res Function(_AppState) _then) = __$AppStateCopyWithImpl; @override @useResult $Res call({ - List items, List notifications, List fonts,@GenericPaginatedResultConverter(fromJsonT: attributeFromJson, toJsonT: attributeToJson) PaginatedResult attributes,@JsonKey(includeToJson: false, includeFromJson: false) bool isLoadingAttributesMore, bool openNavigation, ThemeSettings themeSettings,@JsonKey(includeToJson: false) ItemModel? selectedItem,@JsonKey(includeToJson: false) NotificationModel? selectedNotification,@JsonKey(includeToJson: false) FontModel? selectedFont,@JsonKey(includeToJson: false) AttributeModel? selectedAttribute +@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) PaginatedResult items, List notifications, List fonts, List attributes, bool openNavigation, ThemeSettings themeSettings,@JsonKey(includeToJson: false) ItemModel? selectedItem,@JsonKey(includeToJson: false) NotificationModel? selectedNotification,@JsonKey(includeToJson: false) FontModel? selectedFont,@JsonKey(includeToJson: false) AttributeModel? selectedAttribute }); @@ -350,14 +348,13 @@ class __$AppStateCopyWithImpl<$Res> /// Create a copy of AppState /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? items = null,Object? notifications = null,Object? fonts = null,Object? attributes = null,Object? isLoadingAttributesMore = null,Object? openNavigation = null,Object? themeSettings = null,Object? selectedItem = freezed,Object? selectedNotification = freezed,Object? selectedFont = freezed,Object? selectedAttribute = freezed,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? items = null,Object? notifications = null,Object? fonts = null,Object? attributes = null,Object? openNavigation = null,Object? themeSettings = null,Object? selectedItem = freezed,Object? selectedNotification = freezed,Object? selectedFont = freezed,Object? selectedAttribute = freezed,}) { return _then(_AppState( -items: null == items ? _self._items : items // ignore: cast_nullable_to_non_nullable -as List,notifications: null == notifications ? _self._notifications : notifications // ignore: cast_nullable_to_non_nullable +items: null == items ? _self.items : items // ignore: cast_nullable_to_non_nullable +as PaginatedResult,notifications: null == notifications ? _self._notifications : notifications // ignore: cast_nullable_to_non_nullable as List,fonts: null == fonts ? _self._fonts : fonts // ignore: cast_nullable_to_non_nullable -as List,attributes: null == attributes ? _self.attributes : attributes // ignore: cast_nullable_to_non_nullable -as PaginatedResult,isLoadingAttributesMore: null == isLoadingAttributesMore ? _self.isLoadingAttributesMore : isLoadingAttributesMore // ignore: cast_nullable_to_non_nullable -as bool,openNavigation: null == openNavigation ? _self.openNavigation : openNavigation // ignore: cast_nullable_to_non_nullable +as List,attributes: null == attributes ? _self._attributes : attributes // ignore: cast_nullable_to_non_nullable +as List,openNavigation: null == openNavigation ? _self.openNavigation : openNavigation // ignore: cast_nullable_to_non_nullable as bool,themeSettings: null == themeSettings ? _self.themeSettings : themeSettings // ignore: cast_nullable_to_non_nullable as ThemeSettings,selectedItem: freezed == selectedItem ? _self.selectedItem : selectedItem // ignore: cast_nullable_to_non_nullable as ItemModel?,selectedNotification: freezed == selectedNotification ? _self.selectedNotification : selectedNotification // ignore: cast_nullable_to_non_nullable diff --git a/lib/api/state/app_state.g.dart b/lib/api/state/app_state.g.dart index d3f66f78..69042323 100644 --- a/lib/api/state/app_state.g.dart +++ b/lib/api/state/app_state.g.dart @@ -7,11 +7,18 @@ part of 'app_state.dart'; // ************************************************************************** _AppState _$AppStateFromJson(Map json) => _AppState( - items: - (json['items'] as List?) - ?.map((e) => ItemModel.fromJson(e as Map)) - .toList() ?? - const [], + items: json['items'] == null + ? const PaginatedResult( + items: [], + totalItems: 0, + totalPages: 0, + currentPage: 1, + pageSize: 0, + ) + : PaginatedResult.fromJson( + json['items'] as Map, + (value) => ItemModel.fromJson(value as Map), + ), notifications: (json['notifications'] as List?) ?.map((e) => NotificationModel.fromJson(e as Map)) @@ -63,7 +70,7 @@ _AppState _$AppStateFromJson(Map json) => _AppState( ); Map _$AppStateToJson(_AppState instance) => { - 'items': instance.items, + 'items': instance.items.toJson((value) => value), 'notifications': instance.notifications, 'fonts': instance.fonts, 'attributes': instance.attributes.toJson((value) => value), From aa0c508783596123fce1b6df6a378d10d7432ffd Mon Sep 17 00:00:00 2001 From: theEvilReaper Date: Fri, 19 Sep 2025 10:45:08 +0200 Subject: [PATCH 02/12] Update item actions to support paginated results --- lib/api/state/actions/item_actions.dart | 84 ++++++++++++++++--------- 1 file changed, 56 insertions(+), 28 deletions(-) diff --git a/lib/api/state/actions/item_actions.dart b/lib/api/state/actions/item_actions.dart index 4ad18e9e..9dd6b250 100644 --- a/lib/api/state/actions/item_actions.dart +++ b/lib/api/state/actions/item_actions.dart @@ -6,6 +6,7 @@ import 'package:stelaris/api/model/item/item_enchantment_model.dart'; import 'package:stelaris/api/model/item/item_flag_model.dart'; import 'package:stelaris/api/model/item/item_lore_model.dart'; import 'package:stelaris/api/model/item_model.dart'; +import 'package:stelaris/api/paginated_result.dart'; import 'package:stelaris/api/state/app_state.dart'; import 'package:stelaris/api/util/minecraft/enchantment.dart'; @@ -54,38 +55,39 @@ class ItemFlagResetAction extends ReduxAction { class InitItemAction extends ReduxAction { @override Future reduce() async { - final List items = await ApiService().itemApi.getAll(); + final PaginatedResult items = await ApiService().itemApi + .getPage(); return state.copyWith(items: items); } InitItemAction(); } -class AddItemAction extends ReduxAction { +class ItemAddAction extends ReduxAction { final ItemModel _model; - AddItemAction(this._model); + ItemAddAction(this._model); @override Future reduce() async { final ItemModel added = await ApiService().itemApi.add(_model); - final List items = List.of(state.items, growable: true); - items.add(added); - return state.copyWith(items: items, selectedItem: added); + final List items = List.of(state.items.items, growable: true) + ..add(added); + return _updateItemInState(state, items, added); } } -class RemoveItemAction extends ReduxAction { +class ItemRemoveAction extends ReduxAction { final ItemModel model; - RemoveItemAction(this.model); + ItemRemoveAction(this.model); @override Future reduce() async { final ItemModel removedEntry = await ApiService().itemApi.remove(model); - final List items = List.of(state.items, growable: true); - items.removeWhere((element) => element.id == removedEntry.id); - return state.copyWith(items: items, selectedItem: null); + final List items = List.of(state.items.items, growable: true) + ..remove(removedEntry); + return _updateItemInState(state, items, null); } } @@ -97,11 +99,20 @@ class ItemDatabaseUpdate extends ReduxAction { if (state.selectedItem == null) return null; final ItemModel selected = state.selectedItem!; final ItemModel dbModel = await ApiService().itemApi.update(selected); - final List models = List.of(state.items, growable: true); - final int index = models.indexWhere((element) => element.id == selected.id); - models.removeAt(index); - models.insert(index, dbModel); - return state.copyWith(items: models, selectedItem: dbModel); + + final List updatedList = List.of( + state.items.items, + growable: true, + ); + final int index = updatedList.indexWhere( + (element) => element.id == selected.id, + ); + + if (index != -1) { + updatedList[index] = dbModel; + } + + return _updateItemInState(state, updatedList, dbModel); } } @@ -139,17 +150,18 @@ class SaveEnchantmentsAction extends ReduxAction { final updatedItem = await ApiService().itemApi.update(selectedItem); // Update the items list with the updated item from the server - final updatedState = state.copyWith( - selectedItem: updatedItem, - items: state.items.map((item) { - if (item.id == updatedItem.id) { - return updatedItem; - } - return item; - }).toList(), + final List updatedList = List.of( + state.items.items, + growable: true, + ); + final int index = updatedList.indexWhere( + (element) => element.id == selectedItem.id, ); - return updatedState; + if (index != -1) { + updatedList[index] = updatedItem; + } + return _updateItemInState(state, updatedList, updatedItem); } catch (e) { // Call the error callback if provided if (onError != null) { @@ -270,10 +282,26 @@ class ItemFlagFetchAction extends ReduxAction { Future reduce() async { if (state.selectedItem == null) return null; final ItemModel selected = state.selectedItem!; - final ItemFlagModel dbModel = await ApiService().itemApi.getFlags(selected.id!); - final ItemModel updatedItem = selected.copyWith( - flags: dbModel.flags, + final ItemFlagModel dbModel = await ApiService().itemApi.getFlags( + selected.id!, ); + final ItemModel updatedItem = selected.copyWith(flags: dbModel.flags); return state.copyWith(selectedItem: updatedItem); } } + +AppState _updateItemInState( + AppState state, + List newItems, + ItemModel? selectedItem, { + int? totalItems, +}) { + final updated = state.items.copyWith( + items: newItems, + totalItems: totalItems ?? state.items.totalItems, + totalPages: state.items.totalPages, + currentPage: state.items.currentPage, + pageSize: state.items.pageSize, + ); + return state.copyWith(items: updated, selectedItem: selectedItem); +} From 8eb11b08c2c8838210052003879e1aa21ac61dde Mon Sep 17 00:00:00 2001 From: theEvilReaper Date: Fri, 19 Sep 2025 10:45:21 +0200 Subject: [PATCH 03/12] Update items call --- lib/api/state/factory/item/item_vm_state.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/api/state/factory/item/item_vm_state.dart b/lib/api/state/factory/item/item_vm_state.dart index 1cddeed0..b32cbf13 100644 --- a/lib/api/state/factory/item/item_vm_state.dart +++ b/lib/api/state/factory/item/item_vm_state.dart @@ -9,7 +9,7 @@ class ItemVmFactory extends VmFactory { @override ItemViewModel fromStore() => - ItemViewModel(itemModels: state.items, selected: state.selectedItem); + ItemViewModel(itemModels: state.items.items, selected: state.selectedItem); } class ItemViewModel extends Vm { From 365cc870cc2e06454059b3e1a4ca567fe18a4cf0 Mon Sep 17 00:00:00 2001 From: theEvilReaper Date: Fri, 19 Sep 2025 10:45:38 +0200 Subject: [PATCH 04/12] Update action names --- lib/feature/item/item_page.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/feature/item/item_page.dart b/lib/feature/item/item_page.dart index e127888a..771b0094 100644 --- a/lib/feature/item/item_page.dart +++ b/lib/feature/item/item_page.dart @@ -34,7 +34,7 @@ class ItemPage extends StatelessWidget { mapToDeleteDialog: (value) => createDeleteText(value.uiName, context), mapToDeleteSuccessfully: (value) { - context.dispatch(RemoveItemAction(value)); + context.dispatch(ItemRemoveAction(value)); return true; }, callFunction: (model) => context.dispatch(SelectedItemAction(model)), @@ -57,7 +57,7 @@ class ItemPage extends StatelessWidget { title: 'Create new item', valueUpdate: (value) { final model = ItemModel(uiName: value); - context.dispatch(AddItemAction(model)); + context.dispatch(ItemAddAction(model)); Navigator.pop(context, true); }, formKey: GlobalKey(), From a3d014c344826e3663d1e7cd308f5d880a4bb019 Mon Sep 17 00:00:00 2001 From: theEvilReaper Date: Sun, 21 Sep 2025 18:18:52 +0200 Subject: [PATCH 05/12] Replace remove with removeWhere call --- lib/api/state/actions/item_actions.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/api/state/actions/item_actions.dart b/lib/api/state/actions/item_actions.dart index 9dd6b250..dc5571c2 100644 --- a/lib/api/state/actions/item_actions.dart +++ b/lib/api/state/actions/item_actions.dart @@ -86,7 +86,7 @@ class ItemRemoveAction extends ReduxAction { Future reduce() async { final ItemModel removedEntry = await ApiService().itemApi.remove(model); final List items = List.of(state.items.items, growable: true) - ..remove(removedEntry); + ..removeWhere((element) => element.id == removedEntry.id); return _updateItemInState(state, items, null); } } From a616b1dffabc88ba99c9016375ddfbb218a63ff6 Mon Sep 17 00:00:00 2001 From: theEvilReaper Date: Mon, 22 Sep 2025 14:32:47 +0200 Subject: [PATCH 06/12] Update generated app state file --- lib/api/state/app_state.freezed.dart | 53 +++++++++++++--------------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/lib/api/state/app_state.freezed.dart b/lib/api/state/app_state.freezed.dart index 9ccdfb4b..2516d199 100644 --- a/lib/api/state/app_state.freezed.dart +++ b/lib/api/state/app_state.freezed.dart @@ -15,7 +15,7 @@ T _$identity(T value) => value; /// @nodoc mixin _$AppState { -@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) PaginatedResult get items; List get notifications; List get fonts; List get attributes; bool get openNavigation; ThemeSettings get themeSettings;@JsonKey(includeToJson: false) ItemModel? get selectedItem;@JsonKey(includeToJson: false) NotificationModel? get selectedNotification;@JsonKey(includeToJson: false) FontModel? get selectedFont;@JsonKey(includeToJson: false) AttributeModel? get selectedAttribute; +@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) PaginatedResult get items; List get notifications; List get fonts;@GenericPaginatedResultConverter(fromJsonT: attributeFromJson, toJsonT: attributeToJson) PaginatedResult get attributes;@JsonKey(includeToJson: false, includeFromJson: false) bool get isLoadingAttributesMore; bool get openNavigation; ThemeSettings get themeSettings;@JsonKey(includeToJson: false) ItemModel? get selectedItem;@JsonKey(includeToJson: false) NotificationModel? get selectedNotification;@JsonKey(includeToJson: false) FontModel? get selectedFont;@JsonKey(includeToJson: false) AttributeModel? get selectedAttribute; /// Create a copy of AppState /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -28,16 +28,16 @@ $AppStateCopyWith get copyWith => _$AppStateCopyWithImpl(thi @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is AppState&&(identical(other.items, items) || other.items == items)&&const DeepCollectionEquality().equals(other.notifications, notifications)&&const DeepCollectionEquality().equals(other.fonts, fonts)&&const DeepCollectionEquality().equals(other.attributes, attributes)&&(identical(other.openNavigation, openNavigation) || other.openNavigation == openNavigation)&&(identical(other.themeSettings, themeSettings) || other.themeSettings == themeSettings)&&(identical(other.selectedItem, selectedItem) || other.selectedItem == selectedItem)&&(identical(other.selectedNotification, selectedNotification) || other.selectedNotification == selectedNotification)&&(identical(other.selectedFont, selectedFont) || other.selectedFont == selectedFont)&&(identical(other.selectedAttribute, selectedAttribute) || other.selectedAttribute == selectedAttribute)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is AppState&&(identical(other.items, items) || other.items == items)&&const DeepCollectionEquality().equals(other.notifications, notifications)&&const DeepCollectionEquality().equals(other.fonts, fonts)&&(identical(other.attributes, attributes) || other.attributes == attributes)&&(identical(other.isLoadingAttributesMore, isLoadingAttributesMore) || other.isLoadingAttributesMore == isLoadingAttributesMore)&&(identical(other.openNavigation, openNavigation) || other.openNavigation == openNavigation)&&(identical(other.themeSettings, themeSettings) || other.themeSettings == themeSettings)&&(identical(other.selectedItem, selectedItem) || other.selectedItem == selectedItem)&&(identical(other.selectedNotification, selectedNotification) || other.selectedNotification == selectedNotification)&&(identical(other.selectedFont, selectedFont) || other.selectedFont == selectedFont)&&(identical(other.selectedAttribute, selectedAttribute) || other.selectedAttribute == selectedAttribute)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,items,const DeepCollectionEquality().hash(notifications),const DeepCollectionEquality().hash(fonts),const DeepCollectionEquality().hash(attributes),openNavigation,themeSettings,selectedItem,selectedNotification,selectedFont,selectedAttribute); +int get hashCode => Object.hash(runtimeType,items,const DeepCollectionEquality().hash(notifications),const DeepCollectionEquality().hash(fonts),attributes,isLoadingAttributesMore,openNavigation,themeSettings,selectedItem,selectedNotification,selectedFont,selectedAttribute); @override String toString() { - return 'AppState(items: $items, notifications: $notifications, fonts: $fonts, attributes: $attributes, openNavigation: $openNavigation, themeSettings: $themeSettings, selectedItem: $selectedItem, selectedNotification: $selectedNotification, selectedFont: $selectedFont, selectedAttribute: $selectedAttribute)'; + return 'AppState(items: $items, notifications: $notifications, fonts: $fonts, attributes: $attributes, isLoadingAttributesMore: $isLoadingAttributesMore, openNavigation: $openNavigation, themeSettings: $themeSettings, selectedItem: $selectedItem, selectedNotification: $selectedNotification, selectedFont: $selectedFont, selectedAttribute: $selectedAttribute)'; } @@ -48,7 +48,7 @@ abstract mixin class $AppStateCopyWith<$Res> { factory $AppStateCopyWith(AppState value, $Res Function(AppState) _then) = _$AppStateCopyWithImpl; @useResult $Res call({ -@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) PaginatedResult items, List notifications, List fonts, List attributes, bool openNavigation, ThemeSettings themeSettings,@JsonKey(includeToJson: false) ItemModel? selectedItem,@JsonKey(includeToJson: false) NotificationModel? selectedNotification,@JsonKey(includeToJson: false) FontModel? selectedFont,@JsonKey(includeToJson: false) AttributeModel? selectedAttribute +@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) PaginatedResult items, List notifications, List fonts,@GenericPaginatedResultConverter(fromJsonT: attributeFromJson, toJsonT: attributeToJson) PaginatedResult attributes,@JsonKey(includeToJson: false, includeFromJson: false) bool isLoadingAttributesMore, bool openNavigation, ThemeSettings themeSettings,@JsonKey(includeToJson: false) ItemModel? selectedItem,@JsonKey(includeToJson: false) NotificationModel? selectedNotification,@JsonKey(includeToJson: false) FontModel? selectedFont,@JsonKey(includeToJson: false) AttributeModel? selectedAttribute }); @@ -65,13 +65,14 @@ class _$AppStateCopyWithImpl<$Res> /// Create a copy of AppState /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? items = null,Object? notifications = null,Object? fonts = null,Object? attributes = null,Object? openNavigation = null,Object? themeSettings = null,Object? selectedItem = freezed,Object? selectedNotification = freezed,Object? selectedFont = freezed,Object? selectedAttribute = freezed,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? items = null,Object? notifications = null,Object? fonts = null,Object? attributes = null,Object? isLoadingAttributesMore = null,Object? openNavigation = null,Object? themeSettings = null,Object? selectedItem = freezed,Object? selectedNotification = freezed,Object? selectedFont = freezed,Object? selectedAttribute = freezed,}) { return _then(_self.copyWith( items: null == items ? _self.items : items // ignore: cast_nullable_to_non_nullable as PaginatedResult,notifications: null == notifications ? _self.notifications : notifications // ignore: cast_nullable_to_non_nullable as List,fonts: null == fonts ? _self.fonts : fonts // ignore: cast_nullable_to_non_nullable as List,attributes: null == attributes ? _self.attributes : attributes // ignore: cast_nullable_to_non_nullable -as List,openNavigation: null == openNavigation ? _self.openNavigation : openNavigation // ignore: cast_nullable_to_non_nullable +as PaginatedResult,isLoadingAttributesMore: null == isLoadingAttributesMore ? _self.isLoadingAttributesMore : isLoadingAttributesMore // ignore: cast_nullable_to_non_nullable +as bool,openNavigation: null == openNavigation ? _self.openNavigation : openNavigation // ignore: cast_nullable_to_non_nullable as bool,themeSettings: null == themeSettings ? _self.themeSettings : themeSettings // ignore: cast_nullable_to_non_nullable as ThemeSettings,selectedItem: freezed == selectedItem ? _self.selectedItem : selectedItem // ignore: cast_nullable_to_non_nullable as ItemModel?,selectedNotification: freezed == selectedNotification ? _self.selectedNotification : selectedNotification // ignore: cast_nullable_to_non_nullable @@ -210,10 +211,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function(@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) PaginatedResult items, List notifications, List fonts, List attributes, bool openNavigation, ThemeSettings themeSettings, @JsonKey(includeToJson: false) ItemModel? selectedItem, @JsonKey(includeToJson: false) NotificationModel? selectedNotification, @JsonKey(includeToJson: false) FontModel? selectedFont, @JsonKey(includeToJson: false) AttributeModel? selectedAttribute)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function(@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) PaginatedResult items, List notifications, List fonts, @GenericPaginatedResultConverter(fromJsonT: attributeFromJson, toJsonT: attributeToJson) PaginatedResult attributes, @JsonKey(includeToJson: false, includeFromJson: false) bool isLoadingAttributesMore, bool openNavigation, ThemeSettings themeSettings, @JsonKey(includeToJson: false) ItemModel? selectedItem, @JsonKey(includeToJson: false) NotificationModel? selectedNotification, @JsonKey(includeToJson: false) FontModel? selectedFont, @JsonKey(includeToJson: false) AttributeModel? selectedAttribute)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _AppState() when $default != null: -return $default(_that.items,_that.notifications,_that.fonts,_that.attributes,_that.openNavigation,_that.themeSettings,_that.selectedItem,_that.selectedNotification,_that.selectedFont,_that.selectedAttribute);case _: +return $default(_that.items,_that.notifications,_that.fonts,_that.attributes,_that.isLoadingAttributesMore,_that.openNavigation,_that.themeSettings,_that.selectedItem,_that.selectedNotification,_that.selectedFont,_that.selectedAttribute);case _: return orElse(); } @@ -231,10 +232,10 @@ return $default(_that.items,_that.notifications,_that.fonts,_that.attributes,_th /// } /// ``` -@optionalTypeArgs TResult when(TResult Function(@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) PaginatedResult items, List notifications, List fonts, List attributes, bool openNavigation, ThemeSettings themeSettings, @JsonKey(includeToJson: false) ItemModel? selectedItem, @JsonKey(includeToJson: false) NotificationModel? selectedNotification, @JsonKey(includeToJson: false) FontModel? selectedFont, @JsonKey(includeToJson: false) AttributeModel? selectedAttribute) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function(@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) PaginatedResult items, List notifications, List fonts, @GenericPaginatedResultConverter(fromJsonT: attributeFromJson, toJsonT: attributeToJson) PaginatedResult attributes, @JsonKey(includeToJson: false, includeFromJson: false) bool isLoadingAttributesMore, bool openNavigation, ThemeSettings themeSettings, @JsonKey(includeToJson: false) ItemModel? selectedItem, @JsonKey(includeToJson: false) NotificationModel? selectedNotification, @JsonKey(includeToJson: false) FontModel? selectedFont, @JsonKey(includeToJson: false) AttributeModel? selectedAttribute) $default,) {final _that = this; switch (_that) { case _AppState(): -return $default(_that.items,_that.notifications,_that.fonts,_that.attributes,_that.openNavigation,_that.themeSettings,_that.selectedItem,_that.selectedNotification,_that.selectedFont,_that.selectedAttribute);case _: +return $default(_that.items,_that.notifications,_that.fonts,_that.attributes,_that.isLoadingAttributesMore,_that.openNavigation,_that.themeSettings,_that.selectedItem,_that.selectedNotification,_that.selectedFont,_that.selectedAttribute);case _: throw StateError('Unexpected subclass'); } @@ -251,10 +252,10 @@ return $default(_that.items,_that.notifications,_that.fonts,_that.attributes,_th /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function(@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) PaginatedResult items, List notifications, List fonts, List attributes, bool openNavigation, ThemeSettings themeSettings, @JsonKey(includeToJson: false) ItemModel? selectedItem, @JsonKey(includeToJson: false) NotificationModel? selectedNotification, @JsonKey(includeToJson: false) FontModel? selectedFont, @JsonKey(includeToJson: false) AttributeModel? selectedAttribute)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function(@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) PaginatedResult items, List notifications, List fonts, @GenericPaginatedResultConverter(fromJsonT: attributeFromJson, toJsonT: attributeToJson) PaginatedResult attributes, @JsonKey(includeToJson: false, includeFromJson: false) bool isLoadingAttributesMore, bool openNavigation, ThemeSettings themeSettings, @JsonKey(includeToJson: false) ItemModel? selectedItem, @JsonKey(includeToJson: false) NotificationModel? selectedNotification, @JsonKey(includeToJson: false) FontModel? selectedFont, @JsonKey(includeToJson: false) AttributeModel? selectedAttribute)? $default,) {final _that = this; switch (_that) { case _AppState() when $default != null: -return $default(_that.items,_that.notifications,_that.fonts,_that.attributes,_that.openNavigation,_that.themeSettings,_that.selectedItem,_that.selectedNotification,_that.selectedFont,_that.selectedAttribute);case _: +return $default(_that.items,_that.notifications,_that.fonts,_that.attributes,_that.isLoadingAttributesMore,_that.openNavigation,_that.themeSettings,_that.selectedItem,_that.selectedNotification,_that.selectedFont,_that.selectedAttribute);case _: return null; } @@ -266,7 +267,7 @@ return $default(_that.items,_that.notifications,_that.fonts,_that.attributes,_th @JsonSerializable() class _AppState implements AppState { - const _AppState({@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) this.items = const PaginatedResult(items: [], totalItems: 0, totalPages: 0, currentPage: 1, pageSize: 0), final List notifications = const [], final List fonts = const [], final List attributes = const [], this.openNavigation = true, this.themeSettings = const ThemeSettings(isDarkMode: false, primaryColor: Colors.blue, accentColor: Colors.blueAccent, fontScale: 1, useSystemTheme: true), @JsonKey(includeToJson: false) this.selectedItem, @JsonKey(includeToJson: false) this.selectedNotification, @JsonKey(includeToJson: false) this.selectedFont, @JsonKey(includeToJson: false) this.selectedAttribute}): _notifications = notifications,_fonts = fonts,_attributes = attributes; + const _AppState({@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) this.items = const PaginatedResult(items: [], totalItems: 0, totalPages: 0, currentPage: 1, pageSize: 0), final List notifications = const [], final List fonts = const [], @GenericPaginatedResultConverter(fromJsonT: attributeFromJson, toJsonT: attributeToJson) this.attributes = const PaginatedResult(items: [], totalItems: 0, totalPages: 0, currentPage: 1, pageSize: 0), @JsonKey(includeToJson: false, includeFromJson: false) this.isLoadingAttributesMore = false, this.openNavigation = true, this.themeSettings = const ThemeSettings(isDarkMode: false, primaryColor: Colors.blue, accentColor: Colors.blueAccent, fontScale: 1, useSystemTheme: true), @JsonKey(includeToJson: false) this.selectedItem, @JsonKey(includeToJson: false) this.selectedNotification, @JsonKey(includeToJson: false) this.selectedFont, @JsonKey(includeToJson: false) this.selectedAttribute}): _notifications = notifications,_fonts = fonts; factory _AppState.fromJson(Map json) => _$AppStateFromJson(json); @override@JsonKey()@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) final PaginatedResult items; @@ -284,13 +285,8 @@ class _AppState implements AppState { return EqualUnmodifiableListView(_fonts); } - final List _attributes; -@override@JsonKey() List get attributes { - if (_attributes is EqualUnmodifiableListView) return _attributes; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_attributes); -} - +@override@JsonKey()@GenericPaginatedResultConverter(fromJsonT: attributeFromJson, toJsonT: attributeToJson) final PaginatedResult attributes; +@override@JsonKey(includeToJson: false, includeFromJson: false) final bool isLoadingAttributesMore; @override@JsonKey() final bool openNavigation; @override@JsonKey() final ThemeSettings themeSettings; @override@JsonKey(includeToJson: false) final ItemModel? selectedItem; @@ -311,16 +307,16 @@ Map toJson() { @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppState&&(identical(other.items, items) || other.items == items)&&const DeepCollectionEquality().equals(other._notifications, _notifications)&&const DeepCollectionEquality().equals(other._fonts, _fonts)&&const DeepCollectionEquality().equals(other._attributes, _attributes)&&(identical(other.openNavigation, openNavigation) || other.openNavigation == openNavigation)&&(identical(other.themeSettings, themeSettings) || other.themeSettings == themeSettings)&&(identical(other.selectedItem, selectedItem) || other.selectedItem == selectedItem)&&(identical(other.selectedNotification, selectedNotification) || other.selectedNotification == selectedNotification)&&(identical(other.selectedFont, selectedFont) || other.selectedFont == selectedFont)&&(identical(other.selectedAttribute, selectedAttribute) || other.selectedAttribute == selectedAttribute)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppState&&(identical(other.items, items) || other.items == items)&&const DeepCollectionEquality().equals(other._notifications, _notifications)&&const DeepCollectionEquality().equals(other._fonts, _fonts)&&(identical(other.attributes, attributes) || other.attributes == attributes)&&(identical(other.isLoadingAttributesMore, isLoadingAttributesMore) || other.isLoadingAttributesMore == isLoadingAttributesMore)&&(identical(other.openNavigation, openNavigation) || other.openNavigation == openNavigation)&&(identical(other.themeSettings, themeSettings) || other.themeSettings == themeSettings)&&(identical(other.selectedItem, selectedItem) || other.selectedItem == selectedItem)&&(identical(other.selectedNotification, selectedNotification) || other.selectedNotification == selectedNotification)&&(identical(other.selectedFont, selectedFont) || other.selectedFont == selectedFont)&&(identical(other.selectedAttribute, selectedAttribute) || other.selectedAttribute == selectedAttribute)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,items,const DeepCollectionEquality().hash(_notifications),const DeepCollectionEquality().hash(_fonts),const DeepCollectionEquality().hash(_attributes),openNavigation,themeSettings,selectedItem,selectedNotification,selectedFont,selectedAttribute); +int get hashCode => Object.hash(runtimeType,items,const DeepCollectionEquality().hash(_notifications),const DeepCollectionEquality().hash(_fonts),attributes,isLoadingAttributesMore,openNavigation,themeSettings,selectedItem,selectedNotification,selectedFont,selectedAttribute); @override String toString() { - return 'AppState(items: $items, notifications: $notifications, fonts: $fonts, attributes: $attributes, openNavigation: $openNavigation, themeSettings: $themeSettings, selectedItem: $selectedItem, selectedNotification: $selectedNotification, selectedFont: $selectedFont, selectedAttribute: $selectedAttribute)'; + return 'AppState(items: $items, notifications: $notifications, fonts: $fonts, attributes: $attributes, isLoadingAttributesMore: $isLoadingAttributesMore, openNavigation: $openNavigation, themeSettings: $themeSettings, selectedItem: $selectedItem, selectedNotification: $selectedNotification, selectedFont: $selectedFont, selectedAttribute: $selectedAttribute)'; } @@ -331,7 +327,7 @@ abstract mixin class _$AppStateCopyWith<$Res> implements $AppStateCopyWith<$Res> factory _$AppStateCopyWith(_AppState value, $Res Function(_AppState) _then) = __$AppStateCopyWithImpl; @override @useResult $Res call({ -@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) PaginatedResult items, List notifications, List fonts, List attributes, bool openNavigation, ThemeSettings themeSettings,@JsonKey(includeToJson: false) ItemModel? selectedItem,@JsonKey(includeToJson: false) NotificationModel? selectedNotification,@JsonKey(includeToJson: false) FontModel? selectedFont,@JsonKey(includeToJson: false) AttributeModel? selectedAttribute +@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) PaginatedResult items, List notifications, List fonts,@GenericPaginatedResultConverter(fromJsonT: attributeFromJson, toJsonT: attributeToJson) PaginatedResult attributes,@JsonKey(includeToJson: false, includeFromJson: false) bool isLoadingAttributesMore, bool openNavigation, ThemeSettings themeSettings,@JsonKey(includeToJson: false) ItemModel? selectedItem,@JsonKey(includeToJson: false) NotificationModel? selectedNotification,@JsonKey(includeToJson: false) FontModel? selectedFont,@JsonKey(includeToJson: false) AttributeModel? selectedAttribute }); @@ -348,13 +344,14 @@ class __$AppStateCopyWithImpl<$Res> /// Create a copy of AppState /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? items = null,Object? notifications = null,Object? fonts = null,Object? attributes = null,Object? openNavigation = null,Object? themeSettings = null,Object? selectedItem = freezed,Object? selectedNotification = freezed,Object? selectedFont = freezed,Object? selectedAttribute = freezed,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? items = null,Object? notifications = null,Object? fonts = null,Object? attributes = null,Object? isLoadingAttributesMore = null,Object? openNavigation = null,Object? themeSettings = null,Object? selectedItem = freezed,Object? selectedNotification = freezed,Object? selectedFont = freezed,Object? selectedAttribute = freezed,}) { return _then(_AppState( items: null == items ? _self.items : items // ignore: cast_nullable_to_non_nullable as PaginatedResult,notifications: null == notifications ? _self._notifications : notifications // ignore: cast_nullable_to_non_nullable as List,fonts: null == fonts ? _self._fonts : fonts // ignore: cast_nullable_to_non_nullable -as List,attributes: null == attributes ? _self._attributes : attributes // ignore: cast_nullable_to_non_nullable -as List,openNavigation: null == openNavigation ? _self.openNavigation : openNavigation // ignore: cast_nullable_to_non_nullable +as List,attributes: null == attributes ? _self.attributes : attributes // ignore: cast_nullable_to_non_nullable +as PaginatedResult,isLoadingAttributesMore: null == isLoadingAttributesMore ? _self.isLoadingAttributesMore : isLoadingAttributesMore // ignore: cast_nullable_to_non_nullable +as bool,openNavigation: null == openNavigation ? _self.openNavigation : openNavigation // ignore: cast_nullable_to_non_nullable as bool,themeSettings: null == themeSettings ? _self.themeSettings : themeSettings // ignore: cast_nullable_to_non_nullable as ThemeSettings,selectedItem: freezed == selectedItem ? _self.selectedItem : selectedItem // ignore: cast_nullable_to_non_nullable as ItemModel?,selectedNotification: freezed == selectedNotification ? _self.selectedNotification : selectedNotification // ignore: cast_nullable_to_non_nullable From 5d9d2285b7d0c6e2f9f93833c61f948ff1b1e97b Mon Sep 17 00:00:00 2001 From: theEvilReaper Date: Mon, 22 Sep 2025 16:02:58 +0200 Subject: [PATCH 07/12] Improve count update when adding a new item --- lib/api/state/actions/item_actions.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/api/state/actions/item_actions.dart b/lib/api/state/actions/item_actions.dart index dc5571c2..925ca38c 100644 --- a/lib/api/state/actions/item_actions.dart +++ b/lib/api/state/actions/item_actions.dart @@ -73,7 +73,7 @@ class ItemAddAction extends ReduxAction { final ItemModel added = await ApiService().itemApi.add(_model); final List items = List.of(state.items.items, growable: true) ..add(added); - return _updateItemInState(state, items, added); + return _updateItemInState(state, items, added, totalItems: items.length); } } From f75233c27a885a3c8efaf1df9aa07bc75d0c42c9 Mon Sep 17 00:00:00 2001 From: theEvilReaper Date: Mon, 22 Sep 2025 16:57:30 +0200 Subject: [PATCH 08/12] Add new variables to store for pagination requests --- lib/api/state/app_state.dart | 3 ++ lib/api/state/app_state.freezed.dart | 39 ++++++++++--------- lib/api/state/factory/item/item_vm_state.dart | 22 +++++++---- 3 files changed, 39 insertions(+), 25 deletions(-) diff --git a/lib/api/state/app_state.dart b/lib/api/state/app_state.dart index e2b7fe60..669ff920 100644 --- a/lib/api/state/app_state.dart +++ b/lib/api/state/app_state.dart @@ -48,6 +48,9 @@ abstract class AppState with _$AppState { @JsonKey(includeToJson: false, includeFromJson: false) @Default(false) bool isLoadingAttributesMore, + @JsonKey(includeToJson: false, includeFromJson: false) + @Default(false) + bool isLoadingMoreItems, @Default(true) bool openNavigation, @Default( ThemeSettings( diff --git a/lib/api/state/app_state.freezed.dart b/lib/api/state/app_state.freezed.dart index 2516d199..81701d20 100644 --- a/lib/api/state/app_state.freezed.dart +++ b/lib/api/state/app_state.freezed.dart @@ -15,7 +15,7 @@ T _$identity(T value) => value; /// @nodoc mixin _$AppState { -@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) PaginatedResult get items; List get notifications; List get fonts;@GenericPaginatedResultConverter(fromJsonT: attributeFromJson, toJsonT: attributeToJson) PaginatedResult get attributes;@JsonKey(includeToJson: false, includeFromJson: false) bool get isLoadingAttributesMore; bool get openNavigation; ThemeSettings get themeSettings;@JsonKey(includeToJson: false) ItemModel? get selectedItem;@JsonKey(includeToJson: false) NotificationModel? get selectedNotification;@JsonKey(includeToJson: false) FontModel? get selectedFont;@JsonKey(includeToJson: false) AttributeModel? get selectedAttribute; +@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) PaginatedResult get items; List get notifications; List get fonts;@GenericPaginatedResultConverter(fromJsonT: attributeFromJson, toJsonT: attributeToJson) PaginatedResult get attributes;@JsonKey(includeToJson: false, includeFromJson: false) bool get isLoadingAttributesMore;@JsonKey(includeToJson: false, includeFromJson: false) bool get isLoadingMoreItems; bool get openNavigation; ThemeSettings get themeSettings;@JsonKey(includeToJson: false) ItemModel? get selectedItem;@JsonKey(includeToJson: false) NotificationModel? get selectedNotification;@JsonKey(includeToJson: false) FontModel? get selectedFont;@JsonKey(includeToJson: false) AttributeModel? get selectedAttribute; /// Create a copy of AppState /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -28,16 +28,16 @@ $AppStateCopyWith get copyWith => _$AppStateCopyWithImpl(thi @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is AppState&&(identical(other.items, items) || other.items == items)&&const DeepCollectionEquality().equals(other.notifications, notifications)&&const DeepCollectionEquality().equals(other.fonts, fonts)&&(identical(other.attributes, attributes) || other.attributes == attributes)&&(identical(other.isLoadingAttributesMore, isLoadingAttributesMore) || other.isLoadingAttributesMore == isLoadingAttributesMore)&&(identical(other.openNavigation, openNavigation) || other.openNavigation == openNavigation)&&(identical(other.themeSettings, themeSettings) || other.themeSettings == themeSettings)&&(identical(other.selectedItem, selectedItem) || other.selectedItem == selectedItem)&&(identical(other.selectedNotification, selectedNotification) || other.selectedNotification == selectedNotification)&&(identical(other.selectedFont, selectedFont) || other.selectedFont == selectedFont)&&(identical(other.selectedAttribute, selectedAttribute) || other.selectedAttribute == selectedAttribute)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is AppState&&(identical(other.items, items) || other.items == items)&&const DeepCollectionEquality().equals(other.notifications, notifications)&&const DeepCollectionEquality().equals(other.fonts, fonts)&&(identical(other.attributes, attributes) || other.attributes == attributes)&&(identical(other.isLoadingAttributesMore, isLoadingAttributesMore) || other.isLoadingAttributesMore == isLoadingAttributesMore)&&(identical(other.isLoadingMoreItems, isLoadingMoreItems) || other.isLoadingMoreItems == isLoadingMoreItems)&&(identical(other.openNavigation, openNavigation) || other.openNavigation == openNavigation)&&(identical(other.themeSettings, themeSettings) || other.themeSettings == themeSettings)&&(identical(other.selectedItem, selectedItem) || other.selectedItem == selectedItem)&&(identical(other.selectedNotification, selectedNotification) || other.selectedNotification == selectedNotification)&&(identical(other.selectedFont, selectedFont) || other.selectedFont == selectedFont)&&(identical(other.selectedAttribute, selectedAttribute) || other.selectedAttribute == selectedAttribute)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,items,const DeepCollectionEquality().hash(notifications),const DeepCollectionEquality().hash(fonts),attributes,isLoadingAttributesMore,openNavigation,themeSettings,selectedItem,selectedNotification,selectedFont,selectedAttribute); +int get hashCode => Object.hash(runtimeType,items,const DeepCollectionEquality().hash(notifications),const DeepCollectionEquality().hash(fonts),attributes,isLoadingAttributesMore,isLoadingMoreItems,openNavigation,themeSettings,selectedItem,selectedNotification,selectedFont,selectedAttribute); @override String toString() { - return 'AppState(items: $items, notifications: $notifications, fonts: $fonts, attributes: $attributes, isLoadingAttributesMore: $isLoadingAttributesMore, openNavigation: $openNavigation, themeSettings: $themeSettings, selectedItem: $selectedItem, selectedNotification: $selectedNotification, selectedFont: $selectedFont, selectedAttribute: $selectedAttribute)'; + return 'AppState(items: $items, notifications: $notifications, fonts: $fonts, attributes: $attributes, isLoadingAttributesMore: $isLoadingAttributesMore, isLoadingMoreItems: $isLoadingMoreItems, openNavigation: $openNavigation, themeSettings: $themeSettings, selectedItem: $selectedItem, selectedNotification: $selectedNotification, selectedFont: $selectedFont, selectedAttribute: $selectedAttribute)'; } @@ -48,7 +48,7 @@ abstract mixin class $AppStateCopyWith<$Res> { factory $AppStateCopyWith(AppState value, $Res Function(AppState) _then) = _$AppStateCopyWithImpl; @useResult $Res call({ -@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) PaginatedResult items, List notifications, List fonts,@GenericPaginatedResultConverter(fromJsonT: attributeFromJson, toJsonT: attributeToJson) PaginatedResult attributes,@JsonKey(includeToJson: false, includeFromJson: false) bool isLoadingAttributesMore, bool openNavigation, ThemeSettings themeSettings,@JsonKey(includeToJson: false) ItemModel? selectedItem,@JsonKey(includeToJson: false) NotificationModel? selectedNotification,@JsonKey(includeToJson: false) FontModel? selectedFont,@JsonKey(includeToJson: false) AttributeModel? selectedAttribute +@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) PaginatedResult items, List notifications, List fonts,@GenericPaginatedResultConverter(fromJsonT: attributeFromJson, toJsonT: attributeToJson) PaginatedResult attributes,@JsonKey(includeToJson: false, includeFromJson: false) bool isLoadingAttributesMore,@JsonKey(includeToJson: false, includeFromJson: false) bool isLoadingMoreItems, bool openNavigation, ThemeSettings themeSettings,@JsonKey(includeToJson: false) ItemModel? selectedItem,@JsonKey(includeToJson: false) NotificationModel? selectedNotification,@JsonKey(includeToJson: false) FontModel? selectedFont,@JsonKey(includeToJson: false) AttributeModel? selectedAttribute }); @@ -65,13 +65,14 @@ class _$AppStateCopyWithImpl<$Res> /// Create a copy of AppState /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? items = null,Object? notifications = null,Object? fonts = null,Object? attributes = null,Object? isLoadingAttributesMore = null,Object? openNavigation = null,Object? themeSettings = null,Object? selectedItem = freezed,Object? selectedNotification = freezed,Object? selectedFont = freezed,Object? selectedAttribute = freezed,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? items = null,Object? notifications = null,Object? fonts = null,Object? attributes = null,Object? isLoadingAttributesMore = null,Object? isLoadingMoreItems = null,Object? openNavigation = null,Object? themeSettings = null,Object? selectedItem = freezed,Object? selectedNotification = freezed,Object? selectedFont = freezed,Object? selectedAttribute = freezed,}) { return _then(_self.copyWith( items: null == items ? _self.items : items // ignore: cast_nullable_to_non_nullable as PaginatedResult,notifications: null == notifications ? _self.notifications : notifications // ignore: cast_nullable_to_non_nullable as List,fonts: null == fonts ? _self.fonts : fonts // ignore: cast_nullable_to_non_nullable as List,attributes: null == attributes ? _self.attributes : attributes // ignore: cast_nullable_to_non_nullable as PaginatedResult,isLoadingAttributesMore: null == isLoadingAttributesMore ? _self.isLoadingAttributesMore : isLoadingAttributesMore // ignore: cast_nullable_to_non_nullable +as bool,isLoadingMoreItems: null == isLoadingMoreItems ? _self.isLoadingMoreItems : isLoadingMoreItems // ignore: cast_nullable_to_non_nullable as bool,openNavigation: null == openNavigation ? _self.openNavigation : openNavigation // ignore: cast_nullable_to_non_nullable as bool,themeSettings: null == themeSettings ? _self.themeSettings : themeSettings // ignore: cast_nullable_to_non_nullable as ThemeSettings,selectedItem: freezed == selectedItem ? _self.selectedItem : selectedItem // ignore: cast_nullable_to_non_nullable @@ -211,10 +212,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function(@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) PaginatedResult items, List notifications, List fonts, @GenericPaginatedResultConverter(fromJsonT: attributeFromJson, toJsonT: attributeToJson) PaginatedResult attributes, @JsonKey(includeToJson: false, includeFromJson: false) bool isLoadingAttributesMore, bool openNavigation, ThemeSettings themeSettings, @JsonKey(includeToJson: false) ItemModel? selectedItem, @JsonKey(includeToJson: false) NotificationModel? selectedNotification, @JsonKey(includeToJson: false) FontModel? selectedFont, @JsonKey(includeToJson: false) AttributeModel? selectedAttribute)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function(@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) PaginatedResult items, List notifications, List fonts, @GenericPaginatedResultConverter(fromJsonT: attributeFromJson, toJsonT: attributeToJson) PaginatedResult attributes, @JsonKey(includeToJson: false, includeFromJson: false) bool isLoadingAttributesMore, @JsonKey(includeToJson: false, includeFromJson: false) bool isLoadingMoreItems, bool openNavigation, ThemeSettings themeSettings, @JsonKey(includeToJson: false) ItemModel? selectedItem, @JsonKey(includeToJson: false) NotificationModel? selectedNotification, @JsonKey(includeToJson: false) FontModel? selectedFont, @JsonKey(includeToJson: false) AttributeModel? selectedAttribute)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _AppState() when $default != null: -return $default(_that.items,_that.notifications,_that.fonts,_that.attributes,_that.isLoadingAttributesMore,_that.openNavigation,_that.themeSettings,_that.selectedItem,_that.selectedNotification,_that.selectedFont,_that.selectedAttribute);case _: +return $default(_that.items,_that.notifications,_that.fonts,_that.attributes,_that.isLoadingAttributesMore,_that.isLoadingMoreItems,_that.openNavigation,_that.themeSettings,_that.selectedItem,_that.selectedNotification,_that.selectedFont,_that.selectedAttribute);case _: return orElse(); } @@ -232,10 +233,10 @@ return $default(_that.items,_that.notifications,_that.fonts,_that.attributes,_th /// } /// ``` -@optionalTypeArgs TResult when(TResult Function(@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) PaginatedResult items, List notifications, List fonts, @GenericPaginatedResultConverter(fromJsonT: attributeFromJson, toJsonT: attributeToJson) PaginatedResult attributes, @JsonKey(includeToJson: false, includeFromJson: false) bool isLoadingAttributesMore, bool openNavigation, ThemeSettings themeSettings, @JsonKey(includeToJson: false) ItemModel? selectedItem, @JsonKey(includeToJson: false) NotificationModel? selectedNotification, @JsonKey(includeToJson: false) FontModel? selectedFont, @JsonKey(includeToJson: false) AttributeModel? selectedAttribute) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function(@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) PaginatedResult items, List notifications, List fonts, @GenericPaginatedResultConverter(fromJsonT: attributeFromJson, toJsonT: attributeToJson) PaginatedResult attributes, @JsonKey(includeToJson: false, includeFromJson: false) bool isLoadingAttributesMore, @JsonKey(includeToJson: false, includeFromJson: false) bool isLoadingMoreItems, bool openNavigation, ThemeSettings themeSettings, @JsonKey(includeToJson: false) ItemModel? selectedItem, @JsonKey(includeToJson: false) NotificationModel? selectedNotification, @JsonKey(includeToJson: false) FontModel? selectedFont, @JsonKey(includeToJson: false) AttributeModel? selectedAttribute) $default,) {final _that = this; switch (_that) { case _AppState(): -return $default(_that.items,_that.notifications,_that.fonts,_that.attributes,_that.isLoadingAttributesMore,_that.openNavigation,_that.themeSettings,_that.selectedItem,_that.selectedNotification,_that.selectedFont,_that.selectedAttribute);case _: +return $default(_that.items,_that.notifications,_that.fonts,_that.attributes,_that.isLoadingAttributesMore,_that.isLoadingMoreItems,_that.openNavigation,_that.themeSettings,_that.selectedItem,_that.selectedNotification,_that.selectedFont,_that.selectedAttribute);case _: throw StateError('Unexpected subclass'); } @@ -252,10 +253,10 @@ return $default(_that.items,_that.notifications,_that.fonts,_that.attributes,_th /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function(@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) PaginatedResult items, List notifications, List fonts, @GenericPaginatedResultConverter(fromJsonT: attributeFromJson, toJsonT: attributeToJson) PaginatedResult attributes, @JsonKey(includeToJson: false, includeFromJson: false) bool isLoadingAttributesMore, bool openNavigation, ThemeSettings themeSettings, @JsonKey(includeToJson: false) ItemModel? selectedItem, @JsonKey(includeToJson: false) NotificationModel? selectedNotification, @JsonKey(includeToJson: false) FontModel? selectedFont, @JsonKey(includeToJson: false) AttributeModel? selectedAttribute)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function(@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) PaginatedResult items, List notifications, List fonts, @GenericPaginatedResultConverter(fromJsonT: attributeFromJson, toJsonT: attributeToJson) PaginatedResult attributes, @JsonKey(includeToJson: false, includeFromJson: false) bool isLoadingAttributesMore, @JsonKey(includeToJson: false, includeFromJson: false) bool isLoadingMoreItems, bool openNavigation, ThemeSettings themeSettings, @JsonKey(includeToJson: false) ItemModel? selectedItem, @JsonKey(includeToJson: false) NotificationModel? selectedNotification, @JsonKey(includeToJson: false) FontModel? selectedFont, @JsonKey(includeToJson: false) AttributeModel? selectedAttribute)? $default,) {final _that = this; switch (_that) { case _AppState() when $default != null: -return $default(_that.items,_that.notifications,_that.fonts,_that.attributes,_that.isLoadingAttributesMore,_that.openNavigation,_that.themeSettings,_that.selectedItem,_that.selectedNotification,_that.selectedFont,_that.selectedAttribute);case _: +return $default(_that.items,_that.notifications,_that.fonts,_that.attributes,_that.isLoadingAttributesMore,_that.isLoadingMoreItems,_that.openNavigation,_that.themeSettings,_that.selectedItem,_that.selectedNotification,_that.selectedFont,_that.selectedAttribute);case _: return null; } @@ -267,7 +268,7 @@ return $default(_that.items,_that.notifications,_that.fonts,_that.attributes,_th @JsonSerializable() class _AppState implements AppState { - const _AppState({@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) this.items = const PaginatedResult(items: [], totalItems: 0, totalPages: 0, currentPage: 1, pageSize: 0), final List notifications = const [], final List fonts = const [], @GenericPaginatedResultConverter(fromJsonT: attributeFromJson, toJsonT: attributeToJson) this.attributes = const PaginatedResult(items: [], totalItems: 0, totalPages: 0, currentPage: 1, pageSize: 0), @JsonKey(includeToJson: false, includeFromJson: false) this.isLoadingAttributesMore = false, this.openNavigation = true, this.themeSettings = const ThemeSettings(isDarkMode: false, primaryColor: Colors.blue, accentColor: Colors.blueAccent, fontScale: 1, useSystemTheme: true), @JsonKey(includeToJson: false) this.selectedItem, @JsonKey(includeToJson: false) this.selectedNotification, @JsonKey(includeToJson: false) this.selectedFont, @JsonKey(includeToJson: false) this.selectedAttribute}): _notifications = notifications,_fonts = fonts; + const _AppState({@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) this.items = const PaginatedResult(items: [], totalItems: 0, totalPages: 0, currentPage: 1, pageSize: 0), final List notifications = const [], final List fonts = const [], @GenericPaginatedResultConverter(fromJsonT: attributeFromJson, toJsonT: attributeToJson) this.attributes = const PaginatedResult(items: [], totalItems: 0, totalPages: 0, currentPage: 1, pageSize: 0), @JsonKey(includeToJson: false, includeFromJson: false) this.isLoadingAttributesMore = false, @JsonKey(includeToJson: false, includeFromJson: false) this.isLoadingMoreItems = false, this.openNavigation = true, this.themeSettings = const ThemeSettings(isDarkMode: false, primaryColor: Colors.blue, accentColor: Colors.blueAccent, fontScale: 1, useSystemTheme: true), @JsonKey(includeToJson: false) this.selectedItem, @JsonKey(includeToJson: false) this.selectedNotification, @JsonKey(includeToJson: false) this.selectedFont, @JsonKey(includeToJson: false) this.selectedAttribute}): _notifications = notifications,_fonts = fonts; factory _AppState.fromJson(Map json) => _$AppStateFromJson(json); @override@JsonKey()@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) final PaginatedResult items; @@ -287,6 +288,7 @@ class _AppState implements AppState { @override@JsonKey()@GenericPaginatedResultConverter(fromJsonT: attributeFromJson, toJsonT: attributeToJson) final PaginatedResult attributes; @override@JsonKey(includeToJson: false, includeFromJson: false) final bool isLoadingAttributesMore; +@override@JsonKey(includeToJson: false, includeFromJson: false) final bool isLoadingMoreItems; @override@JsonKey() final bool openNavigation; @override@JsonKey() final ThemeSettings themeSettings; @override@JsonKey(includeToJson: false) final ItemModel? selectedItem; @@ -307,16 +309,16 @@ Map toJson() { @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppState&&(identical(other.items, items) || other.items == items)&&const DeepCollectionEquality().equals(other._notifications, _notifications)&&const DeepCollectionEquality().equals(other._fonts, _fonts)&&(identical(other.attributes, attributes) || other.attributes == attributes)&&(identical(other.isLoadingAttributesMore, isLoadingAttributesMore) || other.isLoadingAttributesMore == isLoadingAttributesMore)&&(identical(other.openNavigation, openNavigation) || other.openNavigation == openNavigation)&&(identical(other.themeSettings, themeSettings) || other.themeSettings == themeSettings)&&(identical(other.selectedItem, selectedItem) || other.selectedItem == selectedItem)&&(identical(other.selectedNotification, selectedNotification) || other.selectedNotification == selectedNotification)&&(identical(other.selectedFont, selectedFont) || other.selectedFont == selectedFont)&&(identical(other.selectedAttribute, selectedAttribute) || other.selectedAttribute == selectedAttribute)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppState&&(identical(other.items, items) || other.items == items)&&const DeepCollectionEquality().equals(other._notifications, _notifications)&&const DeepCollectionEquality().equals(other._fonts, _fonts)&&(identical(other.attributes, attributes) || other.attributes == attributes)&&(identical(other.isLoadingAttributesMore, isLoadingAttributesMore) || other.isLoadingAttributesMore == isLoadingAttributesMore)&&(identical(other.isLoadingMoreItems, isLoadingMoreItems) || other.isLoadingMoreItems == isLoadingMoreItems)&&(identical(other.openNavigation, openNavigation) || other.openNavigation == openNavigation)&&(identical(other.themeSettings, themeSettings) || other.themeSettings == themeSettings)&&(identical(other.selectedItem, selectedItem) || other.selectedItem == selectedItem)&&(identical(other.selectedNotification, selectedNotification) || other.selectedNotification == selectedNotification)&&(identical(other.selectedFont, selectedFont) || other.selectedFont == selectedFont)&&(identical(other.selectedAttribute, selectedAttribute) || other.selectedAttribute == selectedAttribute)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,items,const DeepCollectionEquality().hash(_notifications),const DeepCollectionEquality().hash(_fonts),attributes,isLoadingAttributesMore,openNavigation,themeSettings,selectedItem,selectedNotification,selectedFont,selectedAttribute); +int get hashCode => Object.hash(runtimeType,items,const DeepCollectionEquality().hash(_notifications),const DeepCollectionEquality().hash(_fonts),attributes,isLoadingAttributesMore,isLoadingMoreItems,openNavigation,themeSettings,selectedItem,selectedNotification,selectedFont,selectedAttribute); @override String toString() { - return 'AppState(items: $items, notifications: $notifications, fonts: $fonts, attributes: $attributes, isLoadingAttributesMore: $isLoadingAttributesMore, openNavigation: $openNavigation, themeSettings: $themeSettings, selectedItem: $selectedItem, selectedNotification: $selectedNotification, selectedFont: $selectedFont, selectedAttribute: $selectedAttribute)'; + return 'AppState(items: $items, notifications: $notifications, fonts: $fonts, attributes: $attributes, isLoadingAttributesMore: $isLoadingAttributesMore, isLoadingMoreItems: $isLoadingMoreItems, openNavigation: $openNavigation, themeSettings: $themeSettings, selectedItem: $selectedItem, selectedNotification: $selectedNotification, selectedFont: $selectedFont, selectedAttribute: $selectedAttribute)'; } @@ -327,7 +329,7 @@ abstract mixin class _$AppStateCopyWith<$Res> implements $AppStateCopyWith<$Res> factory _$AppStateCopyWith(_AppState value, $Res Function(_AppState) _then) = __$AppStateCopyWithImpl; @override @useResult $Res call({ -@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) PaginatedResult items, List notifications, List fonts,@GenericPaginatedResultConverter(fromJsonT: attributeFromJson, toJsonT: attributeToJson) PaginatedResult attributes,@JsonKey(includeToJson: false, includeFromJson: false) bool isLoadingAttributesMore, bool openNavigation, ThemeSettings themeSettings,@JsonKey(includeToJson: false) ItemModel? selectedItem,@JsonKey(includeToJson: false) NotificationModel? selectedNotification,@JsonKey(includeToJson: false) FontModel? selectedFont,@JsonKey(includeToJson: false) AttributeModel? selectedAttribute +@GenericPaginatedResultConverter(fromJsonT: itemModelFromJson, toJsonT: itemModelToJson) PaginatedResult items, List notifications, List fonts,@GenericPaginatedResultConverter(fromJsonT: attributeFromJson, toJsonT: attributeToJson) PaginatedResult attributes,@JsonKey(includeToJson: false, includeFromJson: false) bool isLoadingAttributesMore,@JsonKey(includeToJson: false, includeFromJson: false) bool isLoadingMoreItems, bool openNavigation, ThemeSettings themeSettings,@JsonKey(includeToJson: false) ItemModel? selectedItem,@JsonKey(includeToJson: false) NotificationModel? selectedNotification,@JsonKey(includeToJson: false) FontModel? selectedFont,@JsonKey(includeToJson: false) AttributeModel? selectedAttribute }); @@ -344,13 +346,14 @@ class __$AppStateCopyWithImpl<$Res> /// Create a copy of AppState /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? items = null,Object? notifications = null,Object? fonts = null,Object? attributes = null,Object? isLoadingAttributesMore = null,Object? openNavigation = null,Object? themeSettings = null,Object? selectedItem = freezed,Object? selectedNotification = freezed,Object? selectedFont = freezed,Object? selectedAttribute = freezed,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? items = null,Object? notifications = null,Object? fonts = null,Object? attributes = null,Object? isLoadingAttributesMore = null,Object? isLoadingMoreItems = null,Object? openNavigation = null,Object? themeSettings = null,Object? selectedItem = freezed,Object? selectedNotification = freezed,Object? selectedFont = freezed,Object? selectedAttribute = freezed,}) { return _then(_AppState( items: null == items ? _self.items : items // ignore: cast_nullable_to_non_nullable as PaginatedResult,notifications: null == notifications ? _self._notifications : notifications // ignore: cast_nullable_to_non_nullable as List,fonts: null == fonts ? _self._fonts : fonts // ignore: cast_nullable_to_non_nullable as List,attributes: null == attributes ? _self.attributes : attributes // ignore: cast_nullable_to_non_nullable as PaginatedResult,isLoadingAttributesMore: null == isLoadingAttributesMore ? _self.isLoadingAttributesMore : isLoadingAttributesMore // ignore: cast_nullable_to_non_nullable +as bool,isLoadingMoreItems: null == isLoadingMoreItems ? _self.isLoadingMoreItems : isLoadingMoreItems // ignore: cast_nullable_to_non_nullable as bool,openNavigation: null == openNavigation ? _self.openNavigation : openNavigation // ignore: cast_nullable_to_non_nullable as bool,themeSettings: null == themeSettings ? _self.themeSettings : themeSettings // ignore: cast_nullable_to_non_nullable as ThemeSettings,selectedItem: freezed == selectedItem ? _self.selectedItem : selectedItem // ignore: cast_nullable_to_non_nullable diff --git a/lib/api/state/factory/item/item_vm_state.dart b/lib/api/state/factory/item/item_vm_state.dart index b32cbf13..36020c67 100644 --- a/lib/api/state/factory/item/item_vm_state.dart +++ b/lib/api/state/factory/item/item_vm_state.dart @@ -4,21 +4,29 @@ import 'package:stelaris/api/state/app_state.dart'; import 'package:stelaris/feature/item/item_page.dart'; class ItemVmFactory extends VmFactory { - ItemVmFactory(); @override - ItemViewModel fromStore() => - ItemViewModel(itemModels: state.items.items, selected: state.selectedItem); + ItemViewModel fromStore() => ItemViewModel( + itemModels: state.items.items, + selected: state.selectedItem, + hasNextPage: state.attributes.hasNextPage, + isLoadingMore: state.isLoadingMoreItems, + ); } class ItemViewModel extends Vm { - final List itemModels; final ItemModel? selected; + final bool hasNextPage; + final bool isLoadingMore; - ItemViewModel({required this.itemModels, required this.selected,}) - : super(equals: [itemModels, selected]); + ItemViewModel({ + required this.itemModels, + required this.selected, + required this.hasNextPage, + required this.isLoadingMore, + }) : super(equals: [itemModels, selected, hasNextPage, isLoadingMore]); bool isSelectedItem(ItemModel model) { if (selected == null) return false; @@ -30,4 +38,4 @@ class ItemViewModel extends Vm { } return selectedModel.hashCode == model.hashCode; } -} \ No newline at end of file +} From 9e7e6f5cfb8bd55661bd57240ce447479269b734 Mon Sep 17 00:00:00 2001 From: theEvilReaper Date: Mon, 22 Sep 2025 16:57:52 +0200 Subject: [PATCH 09/12] Add check to load more items --- lib/api/state/actions/item_actions.dart | 47 ++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/lib/api/state/actions/item_actions.dart b/lib/api/state/actions/item_actions.dart index 925ca38c..faf95ec9 100644 --- a/lib/api/state/actions/item_actions.dart +++ b/lib/api/state/actions/item_actions.dart @@ -53,14 +53,53 @@ class ItemFlagResetAction extends ReduxAction { } class InitItemAction extends ReduxAction { + @override Future reduce() async { - final PaginatedResult items = await ApiService().itemApi - .getPage(); - return state.copyWith(items: items); + // If we already have items and more pages, treat this as load-more. + final hasExisting = state.items.items.isNotEmpty; + final canLoadMore = state.items.hasNextPage; + + if (hasExisting && canLoadMore) { + if (state.isLoadingMoreItems) return null; + dispatchSync(_SetLoadMoreItemModels(true)); + try { + final current = state.items; + final nextPage = current.currentPage + 1; + final size = 10; + final next = await ApiService().itemApi.getPage(page: nextPage, size: size); + + final merged = List.of(current.items)..addAll(next.items); + final updated = current.copyWith( + items: merged, + totalItems: next.totalItems != 0 ? next.totalItems : current.totalItems, + totalPages: next.totalPages != 0 ? next.totalPages : current.totalPages, + currentPage: next.currentPage != 0 ? next.currentPage : nextPage, + pageSize: next.pageSize != 0 ? next.pageSize : size, + ); + return state.copyWith(items: updated); + } finally { + dispatchSync(_SetLoadMoreItemModels(false)); + } + } else { + // Initial load (or refresh) + final PaginatedResult result = + await ApiService().itemApi.getPage(page: 1, size: state.items.pageSize == 0 ? 10 : state.items.pageSize); + return state.copyWith(items: result); + } } +} - InitItemAction(); +/// Internal action to manage the loading state for attribute pagination. +/// +/// This private action controls the `isLoadingMoreItems` flag in the state, +/// preventing multiple simultaneous load-more requests. It's used internally +/// by InitAttributeAction during pagination operations. +class _SetLoadMoreItemModels extends ReduxAction { + final bool value; + _SetLoadMoreItemModels(this.value); + @override + AppState reduce() => state.copyWith(isLoadingMoreItems: value); } class ItemAddAction extends ReduxAction { From 2c747eb6d7da7693f3b1fd84da3bcedd454071eb Mon Sep 17 00:00:00 2001 From: theEvilReaper Date: Mon, 22 Sep 2025 16:58:28 +0200 Subject: [PATCH 10/12] Add usage of the PaginatedBaseModelViewTabs widget --- lib/feature/item/item_page.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/feature/item/item_page.dart b/lib/feature/item/item_page.dart index 771b0094..04ddbb33 100644 --- a/lib/feature/item/item_page.dart +++ b/lib/feature/item/item_page.dart @@ -6,8 +6,8 @@ import 'package:stelaris/api/model/item_model.dart'; import 'package:stelaris/api/state/actions/item_actions.dart'; import 'package:stelaris/api/state/app_state.dart'; import 'package:stelaris/api/state/factory/item/item_vm_state.dart'; -import 'package:stelaris/feature/base/base_model_view_tabs.dart'; import 'package:stelaris/feature/base/model_text.dart'; +import 'package:stelaris/feature/base/paginated_model_view_tab.dart'; import 'package:stelaris/feature/dialogs/entry_update_dialog.dart'; import 'package:stelaris/feature/item/enchantment/enchantment_page.dart'; import 'package:stelaris/feature/item/general/item_general_page.dart'; @@ -26,7 +26,7 @@ class ItemPage extends StatelessWidget { onDispose: (store) => store.dispatch(RemoveSelectItemAction(), notify: false), builder: (context, vm) { - return BaseModelViewTabs( + return PaginatedBaseModelViewTabs( mapToDataModelItem: (value) => TextWidget(displayName: value.uiName), openFunction: () => _openCreationDialog(context), @@ -43,6 +43,9 @@ class ItemPage extends StatelessWidget { tabPages: (pages) => pages, compareFunction: (model) => vm.isSelectedItem(model), tabs: _getTabs(), + onLoadMore: vm.hasNextPage && !vm.isLoadingMore + ? () => context.dispatch(InitItemAction()) + : null, ); }, ); From 703810cf2cbfabda8c6d62d2c00895b5b9904cb6 Mon Sep 17 00:00:00 2001 From: theEvilReaper Date: Mon, 22 Sep 2025 17:41:13 +0200 Subject: [PATCH 11/12] Fix wrong value access --- lib/api/state/factory/item/item_vm_state.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/api/state/factory/item/item_vm_state.dart b/lib/api/state/factory/item/item_vm_state.dart index 36020c67..189952e3 100644 --- a/lib/api/state/factory/item/item_vm_state.dart +++ b/lib/api/state/factory/item/item_vm_state.dart @@ -10,7 +10,7 @@ class ItemVmFactory extends VmFactory { ItemViewModel fromStore() => ItemViewModel( itemModels: state.items.items, selected: state.selectedItem, - hasNextPage: state.attributes.hasNextPage, + hasNextPage: state.items.hasNextPage, isLoadingMore: state.isLoadingMoreItems, ); } From c3037e16e9e8908b8b5acd264bbeb5803134aa28 Mon Sep 17 00:00:00 2001 From: Steffen Wonning <6805083+theEvilReaper@users.noreply.github.com> Date: Mon, 22 Sep 2025 17:47:41 +0200 Subject: [PATCH 12/12] Improve documentation Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lib/api/state/actions/item_actions.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/api/state/actions/item_actions.dart b/lib/api/state/actions/item_actions.dart index faf95ec9..f727bd5f 100644 --- a/lib/api/state/actions/item_actions.dart +++ b/lib/api/state/actions/item_actions.dart @@ -90,11 +90,11 @@ class InitItemAction extends ReduxAction { } } -/// Internal action to manage the loading state for attribute pagination. +/// Internal action to manage the loading state for item pagination. /// /// This private action controls the `isLoadingMoreItems` flag in the state, /// preventing multiple simultaneous load-more requests. It's used internally -/// by InitAttributeAction during pagination operations. +/// by InitItemAction during pagination operations. class _SetLoadMoreItemModels extends ReduxAction { final bool value; _SetLoadMoreItemModels(this.value);