Quick actions

cmd+k|ctrl+k

Navigation

Languages

SliverProblem

Snippet info

Language

Dart

Visibility

public

Author

aidarmukanbet

Created

2025-04-16T21:37:00.219378Z

Updated

2025-04-16T21:37:56.693214Z

return Scaffold(
        body: NestedScrollView(
          headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
            return <Widget>[
              // Используем метод для построения заголовка
              _buildHomeHeader(context, username, unViewedCounts, opportunitiesState),

              // Добавляем SliverOverlapAbsorber для корректной работы с вложенными списками
              SliverOverlapAbsorber(
                handle:
                NestedScrollView.sliverOverlapAbsorberHandleFor(context),
                sliver: SliverAppBar(
                  pinned: true,
                  toolbarHeight: 36,
                  backgroundColor: Colors.white,
                  forceElevated: innerBoxIsScrolled,
                  flexibleSpace: FlexibleSpaceBar(
                    background: Padding(
                      padding: const EdgeInsets.symmetric(
                          horizontal: 16, vertical: 8),
                      child: Container(
                        color: Colors.white,
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            const Text(
                              'Возможности',
                              style: TextStyle(
                                fontSize: 18,
                                fontWeight: FontWeight.bold,
                              ),
                            ),
                            const SizedBox(height: 8),
                          ],
                        ),
                      ),
                    ),
                  ),
                  bottom: TabBar(
                    controller: _tabController,
                    labelColor: Colors.blue,
                    unselectedLabelColor: Colors.grey,
                    indicatorColor: Colors.blue,
                    isScrollable: true,
                    tabs: [
                      // Используем кастомные табы с индикаторами непросмотренных элементов
                      BadgedTab(
                        text: 'Стипендии',
                        unviewedCount: unViewedCounts.isNotEmpty ? unViewedCounts[0] : 0,
                      ),
                      BadgedTab(
                        text: 'Стажировки',
                        unviewedCount: unViewedCounts.length > 1 ? unViewedCounts[1] : 0,
                      ),
                      BadgedTab(
                        text: 'Олимпиады',
                        unviewedCount: unViewedCounts.length > 2 ? unViewedCounts[2] : 0,
                      ),
                      // Tab(text: 'Избранные'),  // Для избранных индикатор не нужен
                    ],
                  ),
                ),
              ),
            ];
          },
          body: TabBarView(
            controller: _tabController,
            children: tabs.keys.map((String name) {
              // Map tab name to opportunity type for API calls
              String? opportunityType;
              switch (name) {
                case 'Стипендии':
                  opportunityType = 'grant';
                  break;
                case 'Стажировки':
                  opportunityType = 'internship';
                  break;
                case 'Олимпиады':
                  opportunityType = 'olympiad';
                  break;

              }
              return SafeArea(
                top: false,
                bottom: false,
                child: Builder(
                  builder: (BuildContext context) {
                    return EasyRefresh(
                      header: const MaterialHeader(
                        position: IndicatorPosition.locator,
                      ),
                      footer: const ClassicFooter(
                        position: IndicatorPosition.locator,
                        processedDuration: Duration(milliseconds: 400),
                      ),
                      onRefresh: () async {
                        // Обновляем данные в зависимости от вкладки

                          context.read<OpportunitiesBloc>().add(RefreshOpportunities());
                      },
                      onLoad: () async {
                        // Calculate current offset based on list length
                        final currentList = tabs[name]!;
                        final offset = currentList.length;

                          // Для других вкладок используем OpportunitiesBloc
                          context.read<OpportunitiesBloc>().add(FetchMoreOpportunities(
                            type: opportunityType,
                            offset: offset,
                          ));

                      },
                      child: CustomScrollView(
                        key: PageStorageKey<String>(name),
                        slivers: <Widget>[
                          // Overlap injector for nested scrolling
                          SliverOverlapInjector(
                            handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
                          ),
                          // Header locator for pull-to-refresh
                          const HeaderLocator.sliver(),

                          tabs[name]!.isNotEmpty
                              ? SliverList(
                            delegate: SliverChildBuilderDelegate(
                                  (BuildContext context, int index) {
                                final opportunity = tabs[name]![index];
                                return OpportunityListItem(
                                  data: opportunity,
                                  onFavoritePressed: (data) {
                                    BlocProvider.of<FavoritesBloc>(context).add(
                                        ToggleFavoriteStatus(opportunity: data)
                                    );
                                    BlocProvider.of<OpportunitiesBloc>(context).add(ToggleFavorite(opportunity: data));
                                    print('Favorite toggled for ${data.title}');
                                  },
                                  onReturnRefresh: () {
                                    // Обновляем оба блока при возвращении на экран
                                    BlocProvider.of<OpportunitiesBloc>(context).add(RefreshWOAnimation());
                                    BlocProvider.of<FavoritesBloc>(context).add(LoadFavorites());
                                  },
                                  onMarkViewed: (data){
                                    BlocProvider.of<OpportunitiesBloc>(context).add(MarkOpportunityAsViewed(
                                        eventId: data.eventId,
                                        category: data.typesEvent
                                    ));
                                  },
                                );
                              },
                              // Use correct length based on the current tab
                              childCount: tabs[name]!.length,
                            ),
                          )
                              : const SliverFillRemaining(
                            child: Center(
                              child: Column(
                                mainAxisAlignment: MainAxisAlignment.center,
                                children: [
                                  Icon(Icons.inbox, color: Colors.grey, size: 60),
                                  SizedBox(height: 16),
                                  Text('Список пуст'),
                                ],
                              ),
                            ),
                          ),

                          // Footer locator for infinite loading
                          const FooterLocator.sliver(),
                        ],
                      ),
                    );
                  },
                ),
              );
            }).toList(),
          ),
        ),
      );
  }
// Метод для построения заголовка главного экрана с приветствием, уведомлениями, поиском и событиями
  Widget _buildHomeHeader(BuildContext context, String username, List<int> unViewedCounts, OpportunitiesLoaded opportunitiesState) {
    return SliverToBoxAdapter(
      child: Padding(
        padding: const EdgeInsets.only(bottom: 8.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // Секция приветствия и кнопка уведомлений
            Padding(
              padding: const EdgeInsets.only(left: 16, right: 16, top: 16),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        'Привет, $username!',
                        style: const TextStyle(
                          fontSize: 22,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      const Text(
                        'Найди своё будущее вместе с нами!',
                        style: TextStyle(
                          color: Colors.grey,
                          fontSize: 14,
                        ),
                      ),
                    ],
                  ),
                  Material(
                    color: Colors.white,
                    borderRadius: BorderRadius.circular(12),
                    elevation: 2,
                    child: InkWell(
                      onTap: () {
                        Navigator.of(context).push(
                          MaterialPageRoute(
                            builder: (context) => UnviewedScreen(),
                          ),
                        );
                      },
                      borderRadius: BorderRadius.circular(12),
                      child: Container(
                        padding: const EdgeInsets.all(10),
                        child: Badge(
                          // Показываем только маркер без цифры внутри
                          smallSize: 8,
                          backgroundColor: Colors.red,
                          // Показываем бейдж только если есть непросмотренные элементы
                          isLabelVisible: unViewedCounts.fold(0, (total, count) => total + count) > 0,
                          child: const Icon(
                            Icons.notifications,
                            color: Colors.grey,
                            size: 24,
                          ),
                        ),
                      ),
                    ),
                  ),
                ],
              ),
            ),

            // Поисковая строка с кнопкой фильтрации
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
              child: Row(
                children: [
                  Expanded(
                    child: GestureDetector(
                      onTap: () {
                        Navigator.of(context).push(
                          MaterialPageRoute(
                            builder: (context) => const SearchScreen(),
                          ),
                        );
                      },
                      child: Container(
                        padding: const EdgeInsets.symmetric(horizontal: 16),
                        height: 48, // Фиксированная высота для поисковой строки
                        decoration: BoxDecoration(
                          color: Colors.white, // Фон контейнера
                          borderRadius: BorderRadius.circular(12),
                          boxShadow: [
                            BoxShadow(
                              color: Colors.black.withOpacity(0.1),
                              // Цвет тени
                              blurRadius: 12,
                              // Размытие
                              spreadRadius: 2,
                              // Радиус распространения
                              offset: const Offset(0, 4), // Смещение вниз
                            ),
                          ],
                        ),
                        child: const TextField(
                          enabled: false,
                          // Отключаем редактирование, чтобы работал только onTap
                          decoration: InputDecoration(
                            icon: Icon(Icons.search, color: Colors.grey),
                            hintText: 'Искать',
                            border: InputBorder.none,
                          ),
                        ),
                      ),
                    ),
                  ),
                  const SizedBox(width: 8),
                  Container(
                    decoration: BoxDecoration(
                      color: Colors.blue,
                      borderRadius: BorderRadius.circular(12),
                      boxShadow: [
                        BoxShadow(
                          color: Colors.black.withOpacity(0.1),
                          // Цвет тени
                          blurRadius: 12,
                          // Размытие
                          spreadRadius: 2,
                          // Радиус распространения
                          offset: const Offset(0, 4), // Смещение вниз
                        ),
                      ],
                    ),
                    child: IconButton(
                      icon: const Icon(Icons.tune),
                      onPressed: () {
                        // Показываем диалог фильтрации или переходим на экран поиска
                        Navigator.of(context).push(
                          MaterialPageRoute(
                            builder: (context) => const SearchScreen(),
                          ),
                        );
                      },
                      color: Colors.white,
                    ),
                  ),
                ],
              ),
            ),

            // Секция "События" с горизонтальным списком
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: [
                      const Text(
                        'Мероприятия',
                        style: TextStyle(
                          fontSize: 18,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      TextButton(
                        onPressed: () {
                          // Переход в EventsScreen
                          Navigator.of(context).push(
                            MaterialPageRoute(
                              builder: (context) => EventsScreen(),
                            ),
                          );
                        },
                        child: const Text(
                          'Смотреть все',
                          style: TextStyle(
                            color: Colors.blue,
                          ),
                        ),
                      ),
                    ],
                  ),
                  const SizedBox(height: 8),
                  SizedBox(
                    height: 180,
                    child: ListView.builder(
                      scrollDirection: Axis.horizontal,
                      itemCount: opportunitiesState.events.length,
                      itemBuilder: (context, index) {
                        final event = opportunitiesState.events[index];
                        return OpportunityCard(
                          event: event,
                          onReturnRefresh: () {
                            BlocProvider.of<EventBloc>(context).add(RefreshWOAnimationEvent());
                          },
                          isHorizontal: true,
                        );
                      },
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
INFO