[pve-devel] [PATCH pve_flutter_frontend 1/2] fix: ui: missing guests in resource tab when status is `paused`

Dominik Csapak d.csapak at proxmox.com
Wed May 21 14:13:35 CEST 2025


high level comment:

please separate a refactoring commit with the actual change

having the sheet factored out is probably a good thing,
but it's much easier to review if it's a separate patch, because one
does not have to track multiple changes at once

also some comments inline:

On 5/20/25 10:05, Shan Shaji wrote:
> When the guest status is set to `paused` the guest was not showing in
> the resources tab. Also there were no option in the resources filter to
> select the `paused` status under the status section.
> 
> This commit fixes the issue by adding the `paused` status under the
> status section in the resources filter sheet. Also the
> `PveMobileResourceFilterSheet` has been splitted into muliple widgets
> and moved to it's on file under the widgets folder.
> 
> Signed-off-by: Shan Shaji <s.shaji at proxmox.com>
> ---
>   lib/pages/main_layout_slim.dart               | 179 +--------------
>   .../pve_mobile_resource_filter_sheet.dart     | 215 ++++++++++++++++++
>   2 files changed, 221 insertions(+), 173 deletions(-)
>   create mode 100644 lib/widgets/pve_mobile_resource_filter_sheet.dart
> 
> diff --git a/lib/pages/main_layout_slim.dart b/lib/pages/main_layout_slim.dart
> index ac5a6f9..5f4d34e 100644
> --- a/lib/pages/main_layout_slim.dart
> +++ b/lib/pages/main_layout_slim.dart
> @@ -26,6 +26,7 @@ import 'package:pve_flutter_frontend/widgets/proxmox_stream_builder_widget.dart'
>   import 'package:pve_flutter_frontend/widgets/pve_file_selector_widget.dart';
>   import 'package:pve_flutter_frontend/widgets/pve_guest_icon_widget.dart';
>   import 'package:pve_flutter_frontend/widgets/pve_help_icon_button_widget.dart';
> +import 'package:pve_flutter_frontend/widgets/pve_mobile_resource_filter_sheet.dart';
>   import 'package:pve_flutter_frontend/widgets/pve_resource_data_card_widget.dart';
>   import 'package:pve_flutter_frontend/widgets/pve_resource_status_chip_widget.dart';
>   import 'package:pve_flutter_frontend/widgets/pve_subscription_alert_dialog.dart';
> @@ -41,6 +42,7 @@ class MainLayoutSlim extends StatefulWidget {
>   
>   class _MainLayoutSlimState extends State<MainLayoutSlim> {
>     BehaviorSubject<int> pageSelector = BehaviorSubject.seeded(0);
> +

as with the other patch, this commits adds some unrelated new lines...

>     @override
>     Widget build(BuildContext context) {
>       final apiClient = Provider.of<ProxmoxApiClient>(context);
> @@ -562,6 +564,7 @@ class PveNodeListTile extends StatelessWidget {
>     final String type;
>     final String? level;
>     final String? ip;
> +

also here

>     const PveNodeListTile(
>         {super.key,
>         required this.name,
> @@ -569,6 +572,7 @@ class PveNodeListTile extends StatelessWidget {
>         required this.type,
>         this.level,
>         this.ip = ''});
> +

and here

>     @override
>     Widget build(BuildContext context) {
>       return ListTile(
> @@ -606,7 +610,7 @@ class MobileResourceOverview extends StatelessWidget {
>             },
>             child: ColoredSafeArea(
>                 child: Scaffold(
> -            endDrawer: _MobileResourceFilterSheet(),
> +            endDrawer: PveMobileResourceFilterSheet(),
>               appBar: AppBar(
>                 automaticallyImplyLeading: false,
>                 elevation: 0,
> @@ -782,6 +786,7 @@ class AppbarSearchTextField extends StatefulWidget {
>     final ValueChanged<String>? onChanged;
>   
>     const AppbarSearchTextField({super.key, this.onChanged});
> +

and here

>     @override
>     _AppbarSearchTextFieldState createState() => _AppbarSearchTextFieldState();
>   }
> @@ -844,178 +849,6 @@ class _AppbarSearchTextFieldState extends State<AppbarSearchTextField> {
>     }
>   }
>   
> -class _MobileResourceFilterSheet extends StatelessWidget {
> -  @override
> -  Widget build(BuildContext context) {
> -    final rBloc = Provider.of<PveResourceBloc>(context);
> -
> -    return ProxmoxStreamBuilder<PveResourceBloc, PveResourceState>(
> -      bloc: rBloc,
> -      builder: (context, state) => Drawer(
> -        child: SingleChildScrollView(
> -          child: Column(
> -            crossAxisAlignment: CrossAxisAlignment.start,
> -            children: <Widget>[
> -              Padding(
> -                padding: const EdgeInsets.fromLTRB(8.0, 20.0, 8.0, 0),
> -                child: ListTile(
> -                  title: const Text(
> -                    'Filter Results',
> -                  ),
> -                  trailing: rBloc.isFiltered
> -                      ? TextButton(
> -                          onPressed: () => rBloc.events.add(ResetFilter()),
> -                          child: Text(
> -                            'Reset',
> -                            style: TextStyle(
> -                              color: Theme.of(context).colorScheme.secondary,
> -                            ),
> -                          ),
> -                        )
> -                      : null,
> -                ),
> -              ),
> -              const Divider(
> -                indent: 0,
> -                endIndent: 0,
> -              ),
> -              Padding(
> -                padding: const EdgeInsets.fromLTRB(8.0, 0, 8.0, 0),
> -                child: Column(
> -                  children: [
> -                    const ListTile(
> -                      title: Text(
> -                        'Type',
> -                        style: TextStyle(fontWeight: FontWeight.bold),
> -                      ),
> -                    ),
> -                    CheckboxListTile(
> -                      dense: true,
> -                      title: Text(
> -                        'Nodes',
> -                        style: TextStyle(
> -                            color: Theme.of(context)
> -                                .colorScheme
> -                                .onSurface
> -                                .withValues(alpha: 0.75)),
> -                      ),
> -                      value: state.typeFilter.contains('node'),
> -                      onChanged: (v) => rBloc.events.add(FilterResources(
> -                        typeFilter: addOrRemove(v!, 'node', state.typeFilter),
> -                      )),
> -                    ),
> -                    CheckboxListTile(
> -                      dense: true,
> -                      title: Text(
> -                        'Qemu',
> -                        style: TextStyle(
> -                            color: Theme.of(context)
> -                                .colorScheme
> -                                .onSurface
> -                                .withValues(alpha: 0.75)),
> -                      ),
> -                      value: state.typeFilter.contains('qemu'),
> -                      onChanged: (v) => rBloc.events.add(FilterResources(
> -                        typeFilter: addOrRemove(v!, 'qemu', state.typeFilter),
> -                      )),
> -                    ),
> -                    CheckboxListTile(
> -                      dense: true,
> -                      title: Text(
> -                        'LXC',
> -                        style: TextStyle(
> -                            color: Theme.of(context)
> -                                .colorScheme
> -                                .onSurface
> -                                .withValues(alpha: 0.75)),
> -                      ),
> -                      value: state.typeFilter.contains('lxc'),
> -                      onChanged: (v) => rBloc.events.add(FilterResources(
> -                        typeFilter: addOrRemove(v!, 'lxc', state.typeFilter),
> -                      )),
> -                    ),
> -                    CheckboxListTile(
> -                      dense: true,
> -                      title: Text(
> -                        'Storage',
> -                        style: TextStyle(
> -                            color: Theme.of(context)
> -                                .colorScheme
> -                                .onSurface
> -                                .withValues(alpha: 0.75)),
> -                      ),
> -                      value: state.typeFilter.contains('storage'),
> -                      onChanged: (v) => rBloc.events.add(FilterResources(
> -                        typeFilter:
> -                            addOrRemove(v!, 'storage', state.typeFilter),
> -                      )),
> -                    ),
> -                  ],
> -                ),
> -              ),
> -              Padding(
> -                padding: const EdgeInsets.fromLTRB(8.0, 0, 8.0, 0),
> -                child: Column(
> -                  children: [
> -                    const ListTile(
> -                      title: Text(
> -                        'Status',
> -                        style: TextStyle(fontWeight: FontWeight.bold),
> -                      ),
> -                    ),
> -                    CheckboxListTile(
> -                      dense: true,
> -                      title: Text(
> -                        'Online',
> -                        style: TextStyle(
> -                            color: Theme.of(context)
> -                                .colorScheme
> -                                .onSurface
> -                                .withValues(alpha: 0.75)),
> -                      ),
> -                      value: state.statusFilter
> -                          .contains(PveResourceStatusType.running),
> -                      onChanged: (v) => rBloc.events.add(FilterResources(
> -                        statusFilter: addOrRemove(v!,
> -                            PveResourceStatusType.running, state.statusFilter),
> -                      )),
> -                    ),
> -                    CheckboxListTile(
> -                      dense: true,
> -                      title: Text(
> -                        'Offline',
> -                        style: TextStyle(
> -                            color: Theme.of(context)
> -                                .colorScheme
> -                                .onSurface
> -                                .withValues(alpha: 0.75)),
> -                      ),
> -                      value: state.statusFilter
> -                          .contains(PveResourceStatusType.stopped),
> -                      onChanged: (v) => rBloc.events.add(FilterResources(
> -                        statusFilter: addOrRemove(v!,
> -                            PveResourceStatusType.stopped, state.statusFilter),
> -                      )),
> -                    ),
> -                  ],
> -                ),
> -              )
> -            ],
> -          ),
> -        ),
> -      ),
> -    );
> -  }
> -
> -  BuiltSet<S> addOrRemove<S>(bool value, S element, BuiltSet<S> filter) {
> -    if (value) {
> -      return filter.rebuild((b) => b..add(element));
> -    } else {
> -      return filter.rebuild((b) => b..remove(element));
> -    }
> -  }
> -}
> -
>   class AppBarFilterIconButton extends StatelessWidget {
>     const AppBarFilterIconButton({super.key});
>   
> diff --git a/lib/widgets/pve_mobile_resource_filter_sheet.dart b/lib/widgets/pve_mobile_resource_filter_sheet.dart
> new file mode 100644
> index 0000000..15b70f1
> --- /dev/null
> +++ b/lib/widgets/pve_mobile_resource_filter_sheet.dart
> @@ -0,0 +1,215 @@
> +import 'package:built_collection/built_collection.dart';
> +import 'package:flutter/material.dart';
> +import 'package:provider/provider.dart';
> +import 'package:proxmox_dart_api_client/proxmox_dart_api_client.dart';
> +import 'package:pve_flutter_frontend/bloc/pve_resource_bloc.dart';
> +import 'package:pve_flutter_frontend/states/pve_resource_state.dart';
> +import 'package:pve_flutter_frontend/widgets/proxmox_stream_builder_widget.dart';
> +
> +class PveMobileResourceFilterSheet extends StatelessWidget {
> +  const PveMobileResourceFilterSheet({super.key});
> +
> +  @override
> +  Widget build(BuildContext context) {
> +    final rBloc = Provider.of<PveResourceBloc>(context);
> +    return ProxmoxStreamBuilder<PveResourceBloc, PveResourceState>(
> +      bloc: rBloc,
> +      builder: (context, state) => Drawer(
> +        child: SingleChildScrollView(
> +          child: Column(
> +            crossAxisAlignment: CrossAxisAlignment.start,
> +            children: [
> +              Padding(
> +                padding: const EdgeInsets.fromLTRB(8.0, 20.0, 8.0, 0),
> +                child: ListTile(
> +                  title: const Text(
> +                    'Filter Results',
> +                  ),
> +                  trailing: rBloc.isFiltered
> +                      ? TextButton(
> +                          onPressed: () => rBloc.events.add(ResetFilter()),
> +                          child: Text(
> +                            'Reset',
> +                            style: TextStyle(
> +                              color: Theme.of(context).colorScheme.secondary,
> +                            ),
> +                          ),
> +                        )
> +                      : null,
> +                ),
> +              ),
> +              const Divider(indent: 0, endIndent: 0),
> +              _PveFilterSheetSection(
> +                sectionTitle: 'Type',
> +                items: [
> +                  _ProxmoxResourceFilterListTile(
> +                    title: 'Nodes',
> +                    value: state.typeFilter.contains('node'),
> +                    onChanged: (v) => rBloc.events.add(
> +                      FilterResources(
> +                        typeFilter: _addOrRemove(v!, 'node', state.typeFilter),
> +                      ),
> +                    ),
> +                  ),
> +                  _ProxmoxResourceFilterListTile(
> +                    title: 'Qemu',
> +                    value: state.typeFilter.contains('qemu'),
> +                    onChanged: (v) => rBloc.events.add(
> +                      FilterResources(
> +                        typeFilter: _addOrRemove(v!, 'qemu', state.typeFilter),
> +                      ),
> +                    ),
> +                  ),
> +                  _ProxmoxResourceFilterListTile(
> +                    title: 'LXC',
> +                    value: state.typeFilter.contains('lxc'),
> +                    onChanged: (v) => rBloc.events.add(
> +                      FilterResources(
> +                        typeFilter: _addOrRemove(v!, 'lxc', state.typeFilter),
> +                      ),
> +                    ),
> +                  ),
> +                  _ProxmoxResourceFilterListTile(
> +                    title: 'Storage',
> +                    value: state.typeFilter.contains('storage'),
> +                    onChanged: (v) => rBloc.events.add(
> +                      FilterResources(
> +                        typeFilter:
> +                            _addOrRemove(v!, 'storage', state.typeFilter),
> +                      ),
> +                    ),
> +                  ),
> +                ],
> +              ),
> +              _PveFilterSheetSection(
> +                sectionTitle: 'Status',
> +                items: [
> +                  _ProxmoxResourceFilterListTile(
> +                    title: 'Online',
> +                    value: state.statusFilter
> +                        .contains(PveResourceStatusType.running),
> +                    onChanged: (v) => rBloc.events.add(
> +                      FilterResources(
> +                        statusFilter: _addOrRemove(
> +                          v!,
> +                          PveResourceStatusType.running,
> +                          state.statusFilter,
> +                        ),
> +                      ),
> +                    ),
> +                  ),
> +                  _ProxmoxResourceFilterListTile(
> +                    title: 'Offline',
> +                    value: state.statusFilter
> +                        .contains(PveResourceStatusType.stopped),
> +                    onChanged: (v) => rBloc.events.add(
> +                      FilterResources(
> +                        statusFilter: _addOrRemove(
> +                          v!,
> +                          PveResourceStatusType.stopped,
> +                          state.statusFilter,
> +                        ),
> +                      ),
> +                    ),
> +                  ),
> +                  _ProxmoxResourceFilterListTile(
> +                    title: 'Paused',
> +                    value: state.statusFilter
> +                        .contains(PveResourceStatusType.paused),
> +                    onChanged: (v) => rBloc.events.add(
> +                      FilterResources(
> +                        statusFilter: _addOrRemove(
> +                          v!,
> +                          PveResourceStatusType.paused,
> +                          state.statusFilter,
> +                        ),
> +                      ),
> +                    ),
> +                  ),
> +                ],
> +              )
> +            ],
> +          ),
> +        ),
> +      ),
> +    );
> +  }
> +
> +  BuiltSet<S> _addOrRemove<S>(bool value, S element, BuiltSet<S> filter) {
> +    if (value) {
> +      return filter.rebuild((b) => b..add(element));
> +    } else {
> +      return filter.rebuild((b) => b..remove(element));
> +    }
> +  }
> +}
> +
> +class _PveFilterSheetSection extends StatelessWidget {
> +  const _PveFilterSheetSection({
> +    required this.items,
> +    required this.sectionTitle,
> +  });
> +
> +  final List<Widget> items;
> +  final String sectionTitle;
> +
> +  @override
> +  Widget build(BuildContext context) {
> +    return Padding(
> +      padding: const EdgeInsets.symmetric(horizontal: 8),
> +      child: Column(
> +        children: [
> +          _ProxmoxFilterSheetHeader(title: sectionTitle),
> +          ...items,
> +        ],
> +      ),
> +    );
> +  }
> +}
> +
> +class _ProxmoxFilterSheetHeader extends StatelessWidget {
> +  const _ProxmoxFilterSheetHeader({
> +    required this.title,
> +  });
> +
> +  final String title;
> +
> +  @override
> +  Widget build(BuildContext context) {
> +    return ListTile(
> +      title: Text(
> +        title,
> +        style: TextStyle(fontWeight: FontWeight.bold),
> +      ),
> +    );
> +  }
> +}

since you only use the _ProxmoxFilterSheetHeader once, you could also
inline this in the _PveFilterSheetSection. That would save more
code than having this as a separate widget.

> +
> +class _ProxmoxResourceFilterListTile extends StatelessWidget {
> +  const _ProxmoxResourceFilterListTile({
> +    required this.title,
> +    this.onChanged,
> +    this.value,
> +  });
> +
> +  final String title;
> +  final ValueChanged<bool?>? onChanged;
> +  final bool? value;
> +
> +  @override
> +  Widget build(BuildContext context) {
> +    return CheckboxListTile(
> +      dense: true,
> +      title: Text(
> +        title,
> +        style: TextStyle(
> +          color: Theme.of(context).colorScheme.onSurface.withValues(
> +                alpha: 0.75,
> +              ),
> +        ),
> +      ),
> +      value: value,
> +      onChanged: onChanged,
> +    );
> +  }
> +}





More information about the pve-devel mailing list