Распознавание виджетов на экране приложения Flutter

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Hola, Amigos! На связи Саша Чаплыгин, Flutter-dev агентства продуктовой разработки Amiga и соавтор телеграм-канала Flutter. Много. Сегодня мы вновь займемся практикой! Расскажу об интересной теме — определение положения объекта на экране. Это может быть полезно, когда мы хотим понять, виден тот или иной виджет на экране в данный момент или нет.

Давайте рассмотрим конкретный пример: в приложении есть блок с уведомлениями, и согласно техническому заданию, необходимо отправить запрос на сервер, когда плитка в списке уведомлений становится видимой на экране, чтобы пометить уведомление как прочитанное.

Как же это сделать?

Для начала создадим кастомный виджет уведомления. Для определения объекта на экране используем пакет visibility_detector. При применении данного пакета  обязательно используйте UniqueKey, так как данный пакет включает в себя RenderObject.

import 'package:flutter/material.dart';
import 'package:hmelbakery/core/style/colors.dart';
import 'package:visibility_detector/visibility_detector.dart';


class NotificationChip extends StatefulWidget {
 NotificationChip({
   required this.index,
   required this.visibleIndex,
   super.key,
   this.title = '',
   this.text = '',
   this.subtitle = '',
   this.status = true,
 });


 final String title;
 final int index;
 final ValueChanged<int> visibleIndex;
 final String text;
 final String subtitle;
 final bool status;


 @override
 State<NotificationChip> createState() => _NotificationChipState();
}


class _NotificationChipState extends State<NotificationChip> {
 final UniqueKey key = UniqueKey();
 late bool _visible;


 @override
 void initState() {
   _visible = widget.status;
   super.initState();
 }


 @override
 Widget build(BuildContext context) {
   return VisibilityDetector(
     onVisibilityChanged: !_visible
         ? (visibilityInfo) {
             // Здесь определяется на сколько виден объект в процентах
             var visiblePercentage = visibilityInfo.visibleFraction * 100;
             if (visiblePercentage == 100.0) {
               setState(() {
                 _visible = true;
               });
               widget.visibleIndex.call(widget.index); // прочитали
             }
           }
         : null,
     key: key,
     child: Container(
       decoration: BoxDecoration(color: AppColors.grey, borderRadius: BorderRadius.circular(16)),
       child: Padding(
         padding: const EdgeInsets.all(16),
         child: Column(
           mainAxisAlignment: MainAxisAlignment.start,
           crossAxisAlignment: CrossAxisAlignment.start,
           children: [
             Row(
               mainAxisAlignment: MainAxisAlignment.spaceBetween,
               children: [
                 Text(
                   widget.title,
                   style: Theme.of(context).textTheme.titleLarge,
                 ),
                 if (!_visible)
                   Container(
                     width: 16,
                     height: 16,
                     decoration: const BoxDecoration(
                       color: AppColors.yellow,
                       shape: BoxShape.circle,
                     ),
                   ),
               ],
             ),
             const SizedBox(height: 8),
             Text(widget.text, style: Theme.of(context).textTheme.bodyMedium),
             const SizedBox(height: 16),
             Text(
               widget.subtitle,
               style: Theme.of(context).textTheme.bodyMedium?.copyWith(color: AppColors.black400),
             ),
           ],
         ),
       ),
     ),
   );
 }
}

Отображаем список уведомлений на экране.

ListView.builder(
   padding: const EdgeInsets.symmetric(horizontal: 16),
   itemBuilder: (BuildContext context, int index) {
     if (index == state.pageState.data.length - 1 && !state.pageState.loadNewPage) {
       context.read<ProfileNotificationsBloc>().add(ProfileNotificationsFetchDataEvent());
     }
     return Column(
       children: [
         NotificationChip(
           title: state.pageState.data[index].title,
           text: state.pageState.data[index].text,
           subtitle: DateConverter.formattingDateWTime(state.pageState.data[index].datetime),
           status: !(state.pageState.data[index].statusCode == 'unread'),
           index: index,
           visibleIndex: (int value) {
             context
                 .read<ProfileNotificationsBloc>()
                 .add(ProfileNotificationsMarkReadEvent(index: value)); // передаем в блок индекс прочитаного 
           },
         ),
         const SizedBox(height: 8),
         if (index == state.pageState.data.length - 1 && state.pageState.loadNewPage) ...[
           const Center(child: CircularProgressIndicator(color: AppColors.black)),
           const SizedBox(height: 20),
         ],
       ],
     );
   },
   itemCount: state.pageState.data.length,
 ),

В блоке создаем список прочитанных индексов и тут же меняем состояние тех, что уже просмотрели, для снятия значка «прочитанности». 

markRead(ProfileNotificationsMarkReadEvent event, emit) async {
 if (!state.pageState.readIndexes.contains(event.index)) {
   emit(ProfileNotificationsUp(state.pageState.copyWith(
     readIndexes: [...state.pageState.readIndexes, event.index],
     data: state.pageState.data..[event.index] = state.pageState.data[event.index].copyWith(statusCode: 'read'),
   )));
 }
}

Далее нужно решить задачу, как правильно отправить на сервер информацию? Не будем же мы каждый прочитанный индекс отправлять, верно?

Timer? _timer;
List<int> _tempList = [];


_timerFunc() { // старт функции в блоке
 _timer = Timer.periodic(
   const Duration(seconds: 1),
   (timer) {
     if (_tempList.length < state.pageState.readIndexes.length) {
       _tempList = state.pageState.readIndexes;
       notificationsRepository.markRead(
         request: MarkReadNotificationRequest(
           notifications: state.pageState.data
               .sublist(_tempList.first, _tempList.length > 1 ? _tempList.last : _tempList.first + 1)
               .map((e) => e.id)
               .toList(),
         ),
       );
     }
   },
 );
}

Создаем таймер, который каждую секунду проверяет изменился ли список прочитанных уведомлений, и тогда отправляем на сервер. Конечно же мы не будем заставлять пользователя ждать ответа и блокировать экран загрузкой, поэтому не используем async/await. 

Всё готово! Надеюсь, вам будет полезно. Делитесь в нашем чате мобильных разработчиков о своем опыте применения пакета visibility_detector. 

А также всегда ждем вас в нашем телеграм-канале Flutter. Много, который мы ведем командой мобильных разработчиков. Рассказываем про свой личный опыт и делимся советами от софт-скиллов до технических знаний. Присоединяйтесь!

Источник: https://habr.com/ru/articles/804729/


Интересные статьи

Интересные статьи

В данной статье рассмотрим функции, переменные, типы данных, а также некоторые другие базовые элементы синтаксиса языка Rust на примере написания простого приложения для хранения заметок. Лю...
Производительность важна для формирования положительного пользовательского опыта использования приложения, поэтому разработчики стремятся ускорить работу своих программ. Для приложений в области б...
А что, если я скажу, что подобное #application.properties spring.datasource.url=${SPRING_DATASOURCE_URL}?someProperty=${PROPERTY} содержит ошибку. Не согласны? Разбор под катом.
Когда скорость имеет решающее значение, когда необходимо иметь возможность обмениваться информацией мгновенно, есть несколько способы добиться этого, отправка множества сетевых запросов на сервер не в...
Всем привет! Меня зовут Денис, я разрабатываю Apphud – сервис по аналитике авто-возобновляемых подписок в iOS-приложениях. В данной статье я расскажу как настроить, реализовать и валидировать ...