quire-io/scroll-to-index

SliverAutoScrollTag

Opened this issue · 4 comments

I need another instance of the AutoScrollTag be SliverAutoScrollTag so i can use it inside CustomScrollView.
Please note that my itemBuilder is a SliverGrid and i want to wrap it with a SliverAutoScrollTag.
Note that SliverToBoxAdapter won't work here.

Hi @AlaaEldeenYsr
Any simple reproducible code example is helpful : ) Can you provide the it?

My layout is kind of complicated nested scroll physics which make every item of my list is a SliverGrid and to wrap it with AutoScrollTag it throws an exception as it's a RenderBox and not a sliver, So we need the SliverAutoScrollTag copy of the widget.
The every item of my list annotated in below with // TODO: Scroll To Index @jerrywell

  @override
  Widget build(BuildContext context) {
    final controller = context.read<CategoryCubit>();

    return Scaffold(
      appBar: AppBar(
        forceMaterialTransparency: true,
        title: ReactiveValueListenableBuilder(
          formControl: controller.selectedCategoryId,
          builder: (context, control, child) {
            final selectedCategory = controller.categories.data?.firstWhere(
              (element) => element.id == control.value,
              orElse: () => controller.categories.data!.first,
            );

            return Text(selectedCategory?.title ?? '');
          },
        ),
        actions: [
          IconButton(
            onPressed: () {
              context.replace('/search');
            },
            icon: AppIcon(
              icon: Assets.icons.search.path,
              size: 20.sp,
            ),
          ),
        ],
      ),
      body: EntityBuilder(
        entity: controller.categories,
        pendingBuilder: (context) => const AppInPageLoader(),
        rejectedBuilder: (context, error) => ErrorModelWidget(error: error, onRefresh: controller.refresh),
        fulfilledBuilder: (context, data) => CustomScrollView(
          slivers: [
            SliverFloatingHeader(
              child: Container(
                color: Theme.of(context).scaffoldBackgroundColor,
                child: Observer(
                  builder: (context) => Column(children: [
                    ReactiveFormField<String, int>(
                      formControl: controller.selectedCategoryId,
                      valueAccessor: CategoryValueAccessor(list: data),
                      builder: (field) => DefaultTabController(
                        length: data?.length ?? 0,
                        initialIndex: field.value ?? 0,
                        child: TabBar(
                          tabAlignment: TabAlignment.start,
                          labelPadding: EdgeInsets.symmetric(
                            horizontal: AppSpaces.horizontalPadding8,
                          ),
                          onTap: (value) => field.didChange(value),
                          // dividerHeight: 2.h,
                          dividerColor: AppColors.grey7,
                          labelStyle: Theme.of(context).tabBarTheme.labelStyle?.copyWith(
                                fontSize: AppFontSizes.s12,
                                fontWeight: FontWeight.w700,
                              ),
                          unselectedLabelStyle: Theme.of(context).tabBarTheme.unselectedLabelStyle?.copyWith(
                                color: AppColors.grey6,
                                fontSize: AppFontSizes.s12,
                                fontWeight: FontWeight.w700,
                              ),
                          isScrollable: true,
                          tabs: [
                            ...?data?.map((e) => Tab(text: e.title)),
                          ],
                        ),
                      ),
                    ),
                    if (controller.category.fulfilled)
                      Column(children: [
                        DefaultTabController(
                          length: controller.category.data?.children?.length ?? 0,
                          child: Container(
                            color: Theme.of(context).scaffoldBackgroundColor,
                            alignment: AlignmentDirectional.topStart,
                            child: ButtonsTabBar(
                              buttonMargin: EdgeInsets.all(AppSpaces.borderRadius8),
                              unselectedBackgroundColor: AppColors.grey5,
                              contentPadding: EdgeInsets.symmetric(
                                horizontal: AppSpaces.horizontalPadding20,
                                vertical: 5.5.h,
                              ),
                              radius: 100,
                              labelStyle: Theme.of(context).tabBarTheme.labelStyle?.copyWith(
                                    color: Theme.of(context).scaffoldBackgroundColor,
                                    fontWeight: FontWeight.w500,
                                    fontSize: AppFontSizes.s14,
                                  ),
                              unselectedLabelStyle: Theme.of(context).tabBarTheme.unselectedLabelStyle?.copyWith(
                                    color: AppColors.secondary,
                                    fontWeight: FontWeight.w300,
                                  ),
                              tabs: [
                                ...?controller.category.data?.children?.map((e) => Tab(text: e.title)),
                              ],
                            ),
                          ),
                        ),
                        Divider(
                          color: AppColors.grey7,
                          height: 0,
                          thickness: 2.h,
                        )
                      ]),
                  ]),
                ),
              ),
            ),
            CupertinoSliverRefreshControl(
              onRefresh: () async => controller.refresh(),
            ),
            EntityBuilder(
              entity: controller.category,
              rejectedBuilder: (context, error) => SliverFillRemaining(
                hasScrollBody: false,
                child: ErrorModelWidget(error: error, onRefresh: controller.refresh),
              ),
              pendingBuilder: (context) => const SliverFillRemaining(
                hasScrollBody: false,
                child: AppInPageLoader(),
              ),
              pristineBuilder: (context) => const SliverToBoxAdapter(),
              fulfilledBuilder: (context, data) => MultiSliver(pushPinnedChildren: true, children: [
                ...?data.children?.mapIndexed(
                // TODO: Scroll To Index
                  (i, e) => SliverPadding(
                    padding: EdgeInsets.only(bottom: AppSpaces.verticalPadding16).add(
                      EdgeInsets.symmetric(
                        horizontal: AppSpaces.horizontalPadding16,
                        vertical: AppSpaces.verticalPadding12,
                      ),
                    ),
                    sliver: MultiSliver(children: [
                      SliverToBoxAdapter(
                        child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
                          Text(
                            e.title ?? '',
                            style: TextStyle(
                              fontSize: AppFontSizes.s16,
                              fontWeight: FontWeight.w600,
                            ),
                          ),
                          SizedBox(
                            height: AppSpaces.verticalPadding16,
                          )
                        ]),
                      ),
                      SliverGrid.builder(
                        itemCount: data.children?[i].products?.length ?? 0,
                        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                          mainAxisExtent: 238.h,
                          crossAxisCount: 3,
                          mainAxisSpacing: AppSpaces.horizontalPadding8,
                          crossAxisSpacing: AppSpaces.verticalPadding8,
                        ),
                        itemBuilder: (context, j) {
                          final item = e.products![j];

                          return ModelProductCard(
                            item: item,
                            onCartChange: (value) {
                              final controller = context.read<ProductCardActionsCubit>();

                              controller.handleCartChange(
                                hasVariants: item.hasVariants,
                                cartItemId: item.cartItem?.id,
                                body: PutCartItemRequestModel(
                                  productId: item.id,
                                  price: item.inventory?.price,
                                  quantity: value,
                                  offerId: item.offer?.id,
                                ),
                              );
                            },
                            onHeartClick: () {
                              final controller = context.read<ProductCardActionsCubit>();

                              controller.toggleProductFavourites(
                                productId: item.id,
                              );
                            },
                          );
                        },
                      )
                    ]),
                  ),
                ),
              ]),
            ),
          ],
        ),
      ),
      bottomNavigationBar: FreeDeliveryBottomBar(
        hasProductsCount: true,
        elevatedButton: ElevatedButton(
          onPressed: () => context.go("/cart"),
          child: const Text("فتح السلة"),
        ),
      ),
    );
  }

Any updates on this? I am in the same boat and really want this feature.

Hi @AlaaEldeenYsr, The attached example is unable to build and debug, so i can't check it.

However, I have write the code snippet for CustomScrollView with both SliverList and SliverGrid as following.
You can replace the body of example with follow code snippets:

body: CustomScrollView(
  scrollDirection: scrollDirection,
  controller: controller,
  slivers: [
    SliverGrid(
      delegate: SliverChildListDelegate(
        randomList.sublist(0, 10).map<Widget>((data) {
          return Padding(
            padding: EdgeInsets.all(8),
            child: _getRow(data[0], math.max(data[1].toDouble(), 50.0)),
          );
        }).toList(),
      ),
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        mainAxisExtent: 100,
        crossAxisCount: 3,
        mainAxisSpacing: 10,
        crossAxisSpacing: 10,
      ),
    ),
    SliverList(
      delegate: SliverChildListDelegate(
        randomList.sublist(10, 100).map<Widget>((data) {
          return Padding(
            padding: EdgeInsets.all(8),
            child: _getRow(data[0], math.max(data[1].toDouble(), 50.0)),
          );
        }).toList(),
      ),
    )
  ],
),

@lockieRichter the code snippet should be helpful enough to demo how it works with Slivers.