woltapp / wolt_modal_sheet

This package provides a responsive modal with multiple pages, motion animation for page transitions, and scrollable content within each page.
MIT License
511 stars 64 forks source link

How to conditionally control modal closure (using Riverpod)? #342

Closed aleksimatias closed 3 days ago

aleksimatias commented 1 week ago

Question on how to correctly handle enableDrag, barrierDismissable and system pop.

Consider the following example:

Below is an example of how I currently have WoltModalSheet.show setup. I was able to conditionally hide the trailing widget using pageContentDecorator, but have been unsuccessful with enableDrag and barrierDismissable.

Considering my current setup, what's the best way to conditionalize enableDrag and barrierDismissable? What is the best way to guard system pop (by displaying _showDismissConfirmationDialog)?

  Future<void> showBottomSheet() async {
    final isCounterMax = ref.read(modalSheetProvider).isCounterMax;

    WoltModalSheet.show<void>(
        context: context,
        pageListBuilder: (modalSheetContext) {
          final pages = _buildPages(modalSheetContext);
          return pages;
        },
        pageContentDecorator: (child) {
          return ProviderScope(
            overrides: [
              modalSheetProvider.overrideWith(() => ModalSheetStateNotifier()),
            ],
            child: child,
          );
        },
        modalTypeBuilder: (modalContext) => WoltModalType.bottomSheet(),
        onModalDismissedWithBarrierTap: isCounterMax
                ? _showDismissConfirmationDialog
                : closeBottomSheet,
        onModalDismissedWithDrag: closeBottomSheet,
        barrierDismissible: !isCounterMax,
        enableDrag: !isCounterMax
        );
  }
aleksimatias commented 1 week ago

Follow-up question related to Riverpod usage (sorry, out of scope for the original question, can create a new issue if needed)

Example, page 1: Adding items, page 2: Deleting items Child widgets of pages listen to state through ref.watch(provider). If items are deleted on page 2, the deletion is successful, but UI doesn't update.

UI updates are functional if used inside a normal showModalBottomSheet, so their logic should be sound.

ulusoyca commented 1 week ago

Hi There!

ref.watch(provider) and context.watch means that you want to watch the entire modal content. Please create your provider using the modalDecorator (using the latest release with hotfix). See this section in the ReadMe.

WoltModalSheet.show(
  context: context,
  pageContentDecorator: (widget) => Align(
      alignment: Alignment.bottomCenter,
      child: ClipRRect(
        ..., // Your clipRRect properties
        child: BackdropFilter(
          ..., // Your backdrop filter properties
          child: widget,
        ),
    ),
  ),
  modalDecorator: (child) {
    // Wrap the modal with `ChangeNotifierProvider` to manage the state of 
    // the entire pages in the modal.
    return ChangeNotifierProvider<StoreOnlineViewModel>(
      builder: (_, __) => StoreOnlineViewModel(),
      child: child,
    );
  },
  pageListBuilder: (context) {
    final viewModel = context.read<StoreOnlineViewModel>();
    return [
      WoltModalSheetPage(
        child: FirstPageContent(viewModel.data),
        pageTitle: Text('First Page Title'),
        // Other properties...
      ),
      WoltModalSheetPage(
        child: SecondPageContent(viewModel.data),
        pageTitle: Text('Second Page Title'),
        // Other properties...
      ),
    ];
  },
);

When it comes to conditional displaying the content; please refer to this example:

the source code

https://github.com/user-attachments/assets/9c5ef31c-00f6-47a6-b2d7-1da68ff5183f

aleksimatias commented 1 week ago

Thanks for the response.

You mentioned the use of modalContentProvider - was this a typo or should this be available in the latest version? Using version 0.9.4 - no sign of it

ulusoyca commented 1 week ago

Ups, I edited my answer. modalDecorator

ulusoyca commented 5 days ago

@aleksimatias does my answer help?

aleksimatias commented 3 days ago

Sorry for the late response.

When using Riverpod, using modalDecorator to wrap the child in a Consumer seems to update the UI correctly:

modalDecorator: (child) {
  return Consumer(
    builder: (context, ref, _) {
      return child;
    }
  );
}

I've yet to attempt fixing the close button visibility/guarding if the modal is dismissible, but I think that a similar approach for pageContentDecorator might do the trick.

ulusoyca commented 3 days ago

If you have a modalDecorator, you can to wrap your close button with Builder widget, and use context.watch.

This is the source code.

Do you mind closing the issue if this solves?

aleksimatias commented 3 days ago

I still needed to wrap the close button with a Consumer, but I'd say this is now solved. Thank you, will leave my working example below as future reference.

trailingNavBarWidget: Consumer(
            builder: (context, ref, _) {
              final isCounterMax = ref.watch(modalSheetProvider).isCounterMax;

              return isCounterMax
                  ? const SizedBox.shrink()
                  : IconButton(
                      icon: const Icon(Icons.close),
                      onPressed: onClose,
                    );
            },
),