курсовая готова

This commit is contained in:
antoc0der 2024-12-20 11:40:46 +04:00
parent d3dc6956f1
commit f8afb81def
22 changed files with 299 additions and 179 deletions

View File

@ -3,5 +3,6 @@
"search": "Search",
"liked": "liked!",
"disliked": "disliked :(",
"arbEnding": "Чтобы не забыть про отсутствие запятой :)"
"arbEnding": "Чтобы не забыть про отсутствие запятой :)",
"goToLink": "Go to website"
}

View File

@ -3,5 +3,6 @@
"search": "Поиск",
"liked": "понравился!",
"disliked": "разонравился :(",
"arbEnding": "Чтобы не забыть про отсутствие запятой :)"
"arbEnding": "Чтобы не забыть про отсутствие запятой :)",
"goToLink": "Перейти на сайт"
}

View File

@ -1,19 +1,20 @@
import 'package:json_annotation/json_annotation.dart';
part 'news_dto.g.dart';
//сборник классов, необходимых для десериализации с методами fromJson созданными автоматически в g файле. классы paginationdto, metadto - нахрен не упали насколько я понимаю
@JsonSerializable(createToJson: false)
class NewsDto {
@JsonKey(name: 'articles')
final List<NewAttributesDataDto>? data;
final MetaDto? meta;
// final MetaDto? meta;
const NewsDto({
this.data,
this.meta,
// this.meta,
});
factory NewsDto.fromJson(Map<String, dynamic> json) => _$NewsDtoFromJson(json);
factory NewsDto.fromJson(Map<String, dynamic> json) =>
_$NewsDtoFromJson(json);
}
@JsonSerializable(createToJson: false)
@ -24,29 +25,39 @@ class NewAttributesDataDto {
final String? imagelink;
@JsonKey(name: 'url')
final String? id;
@JsonKey(name: 'publishedAt')
final String date;
const NewAttributesDataDto({this.id, this.title, this.description, this.imagelink});
const NewAttributesDataDto({
this.id,
this.title,
this.description,
this.imagelink,
required this.date
});
factory NewAttributesDataDto.fromJson(Map<String, dynamic> json) =>
_$NewAttributesDataDtoFromJson(json);
}
@JsonSerializable(createToJson: false)
class MetaDto {
final PaginationDto? pagination;
const MetaDto({this.pagination});
factory MetaDto.fromJson(Map<String, dynamic> json) => _$MetaDtoFromJson(json);
}
@JsonSerializable(createToJson: false)
class PaginationDto {
final int? current;
final int? next;
final int? last;
const PaginationDto({this.current, this.next, this.last});
factory PaginationDto.fromJson(Map<String, dynamic> json) => _$PaginationDtoFromJson(json);
}
//
// @JsonSerializable(createToJson: false)
// class MetaDto {
// final PaginationDto? pagination;
//
// const MetaDto({this.pagination});
//
// factory MetaDto.fromJson(Map<String, dynamic> json) =>
// _$MetaDtoFromJson(json);
// }
//
// @JsonSerializable(createToJson: false)
// class PaginationDto {
// final int? current;
// final int? next;
// final int? last;
//
// const PaginationDto({this.current, this.next, this.last});
//
// factory PaginationDto.fromJson(Map<String, dynamic> json) =>
// _$PaginationDtoFromJson(json);
// }

View File

@ -10,9 +10,9 @@ NewsDto _$NewsDtoFromJson(Map<String, dynamic> json) => NewsDto(
data: (json['articles'] as List<dynamic>?)
?.map((e) => NewAttributesDataDto.fromJson(e as Map<String, dynamic>))
.toList(),
meta: json['meta'] == null
? null
: MetaDto.fromJson(json['meta'] as Map<String, dynamic>),
// meta: json['meta'] == null
// ? null
// : MetaDto.fromJson(json['meta'] as Map<String, dynamic>),
);
NewAttributesDataDto _$NewAttributesDataDtoFromJson(
@ -22,17 +22,18 @@ NewAttributesDataDto _$NewAttributesDataDtoFromJson(
title: json['title'] as String?,
description: json['description'] as String?,
imagelink: json['urlToImage'] as String?,
date: json['publishedAt'] as String,
);
MetaDto _$MetaDtoFromJson(Map<String, dynamic> json) => MetaDto(
pagination: json['pagination'] == null
? null
: PaginationDto.fromJson(json['pagination'] as Map<String, dynamic>),
);
PaginationDto _$PaginationDtoFromJson(Map<String, dynamic> json) =>
PaginationDto(
current: (json['current'] as num?)?.toInt(),
next: (json['next'] as num?)?.toInt(),
last: (json['last'] as num?)?.toInt(),
);
// MetaDto _$MetaDtoFromJson(Map<String, dynamic> json) => MetaDto(
// pagination: json['pagination'] == null
// ? null
// : PaginationDto.fromJson(json['pagination'] as Map<String, dynamic>),
// );
//
// PaginationDto _$PaginationDtoFromJson(Map<String, dynamic> json) =>
// PaginationDto(
// current: (json['current'] as num?)?.toInt(),
// next: (json['next'] as num?)?.toInt(),
// last: (json['last'] as num?)?.toInt(),
// );

View File

@ -1,25 +1,39 @@
import 'package:pmu/data/dtos/news_dto.dart';
import 'package:pmu/domain/models/card.dart';
import 'package:pmu/domain/models/home.dart';
import 'package:intl/intl.dart';
import 'package:http/http.dart' as http;
import 'package:uri/uri.dart';
int _id = 0;
extension NewDataDtoToModel on NewAttributesDataDto {
CardData toDomain() => CardData(
text: title ?? 'UNKNOWN',
imageUrl: imagelink,
imageUrl: imagelink ?? 'https://smart.mag-river.ru/uploads/goods/img/445-360/fit/no-image.png',
descText: description ?? 'NOTHING',
id: id,
date: toNormalDate(date) ?? 'UNKNOWN',
);
}
extension NewsDtoToModel on NewsDto {
HomeData toDomain() => HomeData(
data: data?.map((e) => e.toDomain()).toList(),
nextPage: meta?.pagination?.next,
// nextPage: meta?.pagination?.next,
);
}
int _getId(){
_id+=1;
return _id;
}
}
String toNormalDate(String date){
// Парсинг строки в объект DateTime
DateTime dateTime = DateTime.parse(date);
// Форматирование даты в нужный формат
String formattedDate = DateFormat('dd.MM.yyyy').format(dateTime);
return formattedDate;
}

View File

@ -5,15 +5,17 @@ import 'package:pmu/data/repositories/api_interface.dart';
import 'package:pmu/domain/models/card.dart';
import 'package:pmu/domain/models/home.dart';
import 'package:pretty_dio_logger/pretty_dio_logger.dart';
const int page = 3;
// класс необходим для отправки запросов в апи и формирования конечных объектов homeData из response
class BbcRepository extends ApiInterface {
static const String _baseUrl = 'https://newsapi.org/v2';
static const String _apiKey = '&apiKey=b9848c2aa43e4a0ba12dfe925db8513c';
static final Dio _dio = Dio()
..interceptors.add(PrettyDioLogger(
requestHeader: true,
requestBody: true,
));
static const String _baseUrl = 'https://newsapi.org/v2';
static const String _apiKey = '&apiKey=b9848c2aa43e4a0ba12dfe925db8513c';
@override
Future<HomeData?> loadData({
OnErrorCallback? onError,
@ -25,20 +27,25 @@ class BbcRepository extends ApiInterface {
// final String url = '$_baseUrl/everything?q=$q&page=$page&pageSize=$pageSize$_apiKey';
final String url = '$_baseUrl/everything';
final Response<dynamic> response = await _dio.get<Map<dynamic, dynamic>>(url,
queryParameters: ((q != null && !q.isEmpty)
? {
'q': q,
'page': page,
'pageSize': pageSize,
'apikey': 'b9848c2aa43e4a0ba12dfe925db8513c'
}
: {
'q': 'news',
'page': page,
'pageSize': pageSize,
'apikey': 'b9848c2aa43e4a0ba12dfe925db8513c'
}));
final Response<dynamic> response =
await _dio.get<Map<dynamic, dynamic>>(url,
queryParameters: ((q != null && !q.isEmpty)
? {
'q': q,
'page': page,
'pageSize': pageSize,
'sortBy': 'publishedAt',
// 'sortBy': 'relevancy',
'apikey': 'b9848c2aa43e4a0ba12dfe925db8513c',
}
: {
'q': 'россия',
'page': page,
'pageSize': pageSize,
// 'sortBy': 'popularity',
'sortBy': 'publishedAt',
'apikey': 'b9848c2aa43e4a0ba12dfe925db8513c'
}));
final NewsDto dto = NewsDto.fromJson(response.data as Map<String, dynamic>);
final HomeData data = dto.toDomain();
return data;

View File

@ -1,58 +0,0 @@
import 'package:flutter/material.dart';
import 'package:pmu/data/repositories/api_interface.dart';
import 'package:pmu/domain/models/card.dart';
import 'package:pmu/domain/models/home.dart';
class MockRepository extends ApiInterface {
@override
Future<HomeData?> loadData() async {
return HomeData(
data: [
CardData(
text: 'Что',
descText:
'Как следует из заключения, которое имеется в распоряжении агентства, документ направлен на сохранение и укрепление традиционных семейных ценностей. Он предусматривает установление защиты от распространения в интернете, СМИ, кинофильмах и рекламе информации, пропагандирующей отказ от рождения детей.' *
10,
imageUrl:
'https://sun9-80.userapi.com/impg/6zgTFCriDQjiAQITCZIMb6jCjViUBgo1fzMLwA/4MJOK5aBZt8.jpg?size=1622x2160&quality=95&sign=57b1751fe201d3728998e96ac3a7ed7a&type=album',
),
CardData(
text: 'Вершит',
descText:
'Речь идет о двух нежилых помещениях: одно — площадью почти 650 квадратных метров, второе — свыше 330. Cогласно сервисам проверки недвижимости, оба помещения относятся к особняку в Чистом переулке, одно из них оценивается почти в 73 миллиона рублей (по кадастровой стоимости).',
imageUrl:
'https://sun9-32.userapi.com/impg/0GkPBnqsxmWufKQrO7kA8y6JpnVwVPuezGhwvQ/_h1wPKl1-s0.jpg?size=600x600&quality=96&sign=e1f3eb74cf15263de35e82c83aedb894&type=album'),
CardData(
text: 'Судьбу',
descText:
'На контроле ситуация с эвакуацией курян, проживающих в 15-километровой зоне в Рыльском и Хомутовском районах. Всего за прошедшую неделю из приграничья в безопасные районы убыли самостоятельно и организованными колоннами более шести тысяч человек", — говорится в публикации по итогам заседания регионального правительства.',
imageUrl:
'https://sun9-70.userapi.com/impg/pzUP2M-ekHGD-0hvftyyrAYoMGB91-aCPwcPEA/dEc1PemeQpk.jpg?size=474x600&quality=95&sign=d83d8106137e012bfda2de9e70010f43&type=album',
),
CardData(
text: 'Человечества',
descText:
'"Запущена официальная процедура смены фамилии", — прокомментировали РИА Новости в пресс-службе объединенной компании Wildberries и Russ (РВБ).',
icon: Icons.add_call,
imageUrl: 'https://i.pinimg.com/736x/df/91/dc/df91dc3de2580cffc66d01000c0c6d82.jpg',
),
CardData(
text: 'В этом мире?',
descText:
'"Мы не располагаем никакой информацией на этот счет. Это скорее вопрос, который относится к компетенции наших военных, поэтому я рекомендую туда обращаться", — сказал пресс-секретарь.',
icon: Icons.add_call,
imageUrl:
'https://sun9-80.userapi.com/impg/EzIhe8VsYrt0Eq-LzYf9DMitSISIDNVUATkz0w/qzIsFaog46s.jpg?size=483x604&quality=96&sign=5bb2247498cc216af4518677af87c9d6&type=album',
),
CardData(
text: 'Некая незримая сущность?',
descText:
'"Мы не располагаем никакой информацией на этот счет. Это скорее вопрос, который относится к компетенции наших военных, поэтому я рекомендую туда обращаться", — сказал пресс-секретарь.',
icon: Icons.add_call,
imageUrl:
'https://sun9-30.userapi.com/impg/5CPSfy33XVIKNTZBd5dgONJrPeCMFn6lOE81kg/eL8SLoL530I.jpg?size=1278x1280&quality=95&sign=f809d63142dac3559e7f7c3ddf817614&type=album',
),
],
);
}
}

View File

@ -6,6 +6,7 @@ class CardData {
final String descText;
final String? imageUrl;
final String? id;
final String date;
CardData({
@ -14,5 +15,6 @@ class CardData {
required this.descText,
this.imageUrl,
this.id,
required this.date,
});
}

View File

@ -2,7 +2,9 @@ import 'package:pmu/domain/models/card.dart';
class HomeData {
final List<CardData>? data;
final int? nextPage;
//final int? nextPage;
HomeData({this.data, this.nextPage});
HomeData({this.data,
// this.nextPage
});
}

View File

@ -1,5 +1,8 @@
import 'package:flutter/material.dart';
import 'package:pmu/domain/models/card.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:pmu/components/extensions/context_x.dart';
class DetailsPage extends StatelessWidget {
final CardData data;
@ -9,21 +12,26 @@ class DetailsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(backgroundColor: Colors.lightBlue, foregroundColor: Colors.white),
appBar: AppBar(
backgroundColor: Colors.lightBlue, foregroundColor: Colors.white),
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 4.0, top: 8.0, left: 8.0, right: 8.0),
padding: const EdgeInsets.only(
bottom: 4.0, top: 8.0, left: 8.0, right: 8.0),
child: Text(
data.text,
style:
const TextStyle(color: Colors.black, fontWeight: FontWeight.w500, fontSize: 26),
style: const TextStyle(
color: Colors.black,
fontWeight: FontWeight.w500,
fontSize: 26),
),
),
Padding(
padding: const EdgeInsets.only(bottom: 16.0, top: 8.0, left: 8.0, right: 8.0),
padding: const EdgeInsets.only(
bottom: 16.0, top: 8.0, left: 8.0, right: 8.0),
child: SizedBox(
width: double.infinity,
child: ClipRRect(
@ -34,12 +42,33 @@ class DetailsPage extends StatelessWidget {
)),
)),
Padding(
padding: const EdgeInsets.only(bottom: 4.0, top: 8.0, left: 8.0, right: 8.0),
padding: const EdgeInsets.only(
bottom: 4.0, top: 8.0, left: 8.0, right: 8.0),
child: Text(
data.descText,
style: Theme.of(context).textTheme.bodyMedium,
),
),
Padding(
padding: const EdgeInsets.only(
bottom: 4.0, top: 8.0, left: 8.0, right: 8.0),
child: Text(
data.date,
style: const TextStyle(
color: Colors.black,
fontWeight: FontWeight.w500,
fontSize: 16),
),
),
Padding(
padding: const EdgeInsets.only(
bottom: 4.0, top: 8.0, left: 0.0, right: 8.0),
child: TextButton(
onPressed: () => launchUrl(Uri.parse(data.id ??
'https://newsapi.org/docs/endpoints/everything')),
child: Text('${context.locale.goToLink}'),
),
),
],
),
),

View File

@ -2,8 +2,9 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pmu/data/repositories/bbc_repository.dart';
import 'package:pmu/presentation/home_page/bloc/events.dart';
import 'package:pmu/presentation/home_page/bloc/state.dart';
//класс для управления состоянием главной страницы приложения, взаимодействует с репозиторием, отвечает за подгрузку данных
class HomeBloc extends Bloc<HomeEvent, HomeState> {
// объект для доступа к репозиторию
final BbcRepository repo;
HomeBloc(this.repo) : super(const HomeState()) {
@ -12,20 +13,24 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
// мы должны изменить наше состояние, передаем внутрь новое состояние
Future<void> _onLoadData(HomeLoadDataEvent event, Emitter<HomeState> emit) async {
// изменение состояния загрузки
if (event.nextPage == null) {
emit(state.copyWith(isLoading: true));
} else {
emit(state.copyWith(isPaginationLoading: true));
}
//загрузка данных
String? error;
final data = await repo.loadData(
q: event.search,
page: event.nextPage ?? 1,
page: event.nextPage ?? 5,
onError: (e) => error = e,
);
//объединение новых данных со старыми
if (event.nextPage != null) {
data?.data?.insertAll(0, state.data?.data ?? []);
}
// эмит нового состояния
emit(state.copyWith(
isLoading: false,
isPaginationLoading: false,

View File

@ -19,16 +19,18 @@ class HomeState extends Equatable {
this.error,
});
// HomeState copyWith({
// HomeData? data,
// bool? isLoading,
// bool? isPaginationLoading,
// }) =>
// HomeState(
// data: data ?? this.data,
// isLoading: isLoading ?? this.isLoading,
// isPaginationLoading: isPaginationLoading ?? this.isPaginationLoading,
// );
HomeState copyWith({
HomeData? data,
bool? isLoading,
bool? isPaginationLoading,
String? error,
}) =>
HomeState(
data: data ?? this.data,
isLoading: isLoading ?? this.isLoading,
isPaginationLoading: isPaginationLoading ?? this.isPaginationLoading,
error: error ?? this.error,
);
@override
List<Object?> get props => [

View File

@ -11,6 +11,7 @@ class _Card extends StatelessWidget {
final VoidCallback? onTap;
final String? id;
final bool isLiked;
final String date;
const _Card(
this.text, {
@ -21,6 +22,7 @@ class _Card extends StatelessWidget {
this.onTap,
this.id,
this.isLiked = false,
required this.date,
});
factory _Card.fromData(
@ -38,6 +40,7 @@ class _Card extends StatelessWidget {
onTap: onTap,
isLiked: isLiked,
id: data.id,
date: data.date,
);
@override

View File

@ -4,7 +4,6 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pmu/components/extensions/context_x.dart';
import 'package:pmu/components/utils/debounce.dart';
import 'package:pmu/data/repositories/bbc_repository.dart';
import 'package:pmu/data/repositories/mock_repository.dart';
import 'package:pmu/domain/models/card.dart';
import 'package:pmu/presentation/common/svg_objects.dart';
import 'package:pmu/presentation/details_page/details_page.dart';
@ -20,17 +19,14 @@ import 'package:pmu/presentation/locale_bloc/locale_events.dart';
import 'package:pmu/presentation/locale_bloc/locale_state.dart';
part 'card.dart';
//основной виджет со скаффолд и боди внутри
class MyHomePage extends StatefulWidget {
//const MyHomePage({super.key, required this.title});
//final String title;
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
// состояние его
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
@ -39,45 +35,51 @@ class _MyHomePageState extends State<MyHomePage> {
);
}
}
// виджет с соновной логикой(на самом деле вся логика в состоянии)
class _Body extends StatefulWidget {
const _Body({super.key});
@override
State<_Body> createState() => _BodyState();
}
// состояние боди с основной лог8икой
class _BodyState extends State<_Body> {
// создание объектов контроллеров для скролла и поиска
final searchController = TextEditingController();
final scrollController = ScrollController();
// late Future<List<CardData>?> data;
// final repo = BbcRepository();
int page = 1;
// инициализация
@override
void initState() {
//инициализация иконок флагов вроде
SvgObjects.init();
// после завершения сборки виджетов отправляет события загрузки данных и лайков в соответсвующие блоки, дальнейшая работа происходит там
WidgetsBinding.instance.addPostFrameCallback((_) {
context.read<HomeBloc>().add(const HomeLoadDataEvent());
context.read<LikeBloc>().add(const LoadLikesEvent());
});
//data = repo.loadData(onError: (e) => showErrorDialog(context, error: e));
// вешает слушатель на скролл контроллер(для пагинации)
scrollController.addListener(_onNextPageListener);
super.initState();
}
//слушатель на скролл контроллер
void _onNextPageListener() {
if (scrollController.offset > scrollController.position.maxScrollExtent) {
// проверка на то что список прокручен не до конца
if (scrollController.offset >= scrollController.position.maxScrollExtent) {
// preventing multiple pagination request on multiple swipes
final bloc = context.read<HomeBloc>();
if (!bloc.state.isPaginationLoading) {
bloc.add(HomeLoadDataEvent(
final homebloc = context.read<HomeBloc>();
// проверка на то что подгрузка пагинации уже не активна
if (!homebloc.state.isPaginationLoading) {
page ++;
// отправляем в хомблок событие на подгрузку данных со следующей страницы
homebloc.add(HomeLoadDataEvent(
search: searchController.text,
nextPage: 2,
nextPage: page,
));
}
}
}
// освобождает контроллеры(чистит)
void dispose() {
searchController.dispose();
scrollController.dispose();
@ -96,6 +98,7 @@ class _BodyState extends State<_Body> {
flex: 4,
child: Padding(
padding: const EdgeInsets.all(12),
// поисковая строка и немного ее обработки: на изменение текста в строке происходит ран дебаунса, я пока не знаю что это. и посылается в хомблок событие на загрузку новых данных
child: CupertinoSearchTextField(
controller: searchController,
placeholder: context.locale.search,
@ -108,15 +111,17 @@ class _BodyState extends State<_Body> {
),
),
GestureDetector(
// при нажатии на эту херь отправляется событие на смену локали
onTap: () =>
context.read<LocaleBloc>().add(const ChangeLocaleEvent()),
child: SizedBox.square(
dimension: 50,
child: Padding(
padding: const EdgeInsets.only(right: 12),
// отображение флага в зависимости от состояния локали
child: BlocBuilder<LocaleBloc, LocaleState>(
builder: (context, state) {
return state.currentLocale.languageCode == 'ru'
builder: (context, localeState) {
return localeState.currentLocale.languageCode == 'ru'
? const SvgRu()
: const SvgUk();
},
@ -127,15 +132,15 @@ class _BodyState extends State<_Body> {
],
),
BlocBuilder<HomeBloc, HomeState>(
builder: (context, state) => state.error != null
builder: (context, homeState) => homeState.error != null
? Text(
state.error ?? '',
homeState.error ?? '',
style: Theme.of(context)
.textTheme
.headlineSmall
?.copyWith(color: Colors.red),
)
: state.isLoading
: homeState.isLoading
? const CircularProgressIndicator()
: BlocBuilder<LikeBloc, LikeState>(
builder: (context, likeState) {
@ -145,9 +150,9 @@ class _BodyState extends State<_Body> {
child: ListView.builder(
controller: scrollController,
padding: EdgeInsets.zero,
itemCount: state.data?.data?.length ?? 0,
itemCount: homeState.data?.data?.length ?? 0,
itemBuilder: (context, index) {
final data = state.data?.data?[index];
final data = homeState.data?.data?[index];
return data != null
? _Card.fromData(
data,
@ -167,19 +172,20 @@ class _BodyState extends State<_Body> {
),
),
BlocBuilder<HomeBloc, HomeState>(
builder: (context, state) => state.isPaginationLoading
builder: (context, homeState) => homeState.isPaginationLoading
? const CircularProgressIndicator()
: const SizedBox.shrink(),
),
]),
);
}
// функция показа снэкбара
void _showSnackBar(BuildContext context, String title, bool isLiked) {
WidgetsBinding.instance.addPostFrameCallback((_) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(
'The New ${isLiked ? context.locale.liked : context.locale.disliked}',
// в зависимомти от локализациии и от того понравилась или разонравилась новость отображается итоговый снэкбар
'${isLiked ? context.locale.liked : context.locale.disliked}',
style: Theme.of(context).textTheme.bodyLarge,
),
backgroundColor: Colors.lightBlue,
@ -187,23 +193,23 @@ class _BodyState extends State<_Body> {
));
});
}
// функция для перехода к детальной странице
void _navToDetails(BuildContext context, CardData data) {
Navigator.push(
context,
CupertinoPageRoute(builder: (context) => DetailsPage(data)),
);
}
// функция вызывается при нажатии на кнопку лайка и вызывает функцию показа снэкбара, так же, запоминается айди лайкнутой новости
void _onLike(String? id, String title, bool isLiked) {
if (id != null) {
context.read<LikeBloc>().add(ChangeLikeEvent(id));
_showSnackBar(context, title, !isLiked);
}
}
// функция вызывается при тяге вниз основной части для обновления и вызывает событие загрузки данных для homedata
Future<void> _onRefresh() {
page = 1;
context
.read<HomeBloc>()
.add(HomeLoadDataEvent(search: searchController.text));

View File

@ -9,17 +9,17 @@ class LikeBloc extends Bloc<LikeEvent, LikeState> {
on<ChangeLikeEvent>(_onChangeLike);
on<LoadLikesEvent>(_onLoadLikes);
}
Future<void> _onLoadLikes(LoadLikesEvent event, Emitter<LikeState> emit) async {
Future<void> _onLoadLikes(LoadLikesEvent loadLikesEvent, Emitter<LikeState> emit) async {
final prefs = await SharedPreferences.getInstance();
final data = prefs.getStringList(_likedPrefsKey);
emit(state.copyWith(likedIds: data));
}
Future<void> _onChangeLike(ChangeLikeEvent event, Emitter<LikeState> emit) async {
Future<void> _onChangeLike(ChangeLikeEvent changeLikeEvent, Emitter<LikeState> emit) async {
final updatedList = List<String>.from(state.likedIds ?? []);
if (updatedList.contains(event.id)) {
updatedList.remove(event.id);
if (updatedList.contains(changeLikeEvent.id)) {
updatedList.remove(changeLikeEvent.id);
} else {
updatedList.add(event.id);
updatedList.add(changeLikeEvent.id);
}
final prefs = await SharedPreferences.getInstance();
prefs.setStringList(_likedPrefsKey, updatedList);

View File

@ -6,6 +6,10 @@
#include "generated_plugin_registrant.h"
#include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
}

View File

@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
url_launcher_linux
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST

View File

@ -6,7 +6,9 @@ import FlutterMacOS
import Foundation
import shared_preferences_foundation
import url_launcher_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
}

View File

@ -331,7 +331,7 @@ packages:
source: hosted
version: "2.3.2"
http:
dependency: transitive
dependency: "direct main"
description:
name: http
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
@ -594,6 +594,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.0"
quiver:
dependency: transitive
description:
name: quiver
sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2
url: "https://pub.dev"
source: hosted
version: "3.2.2"
shared_preferences:
dependency: "direct main"
description:
@ -759,6 +767,78 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.2"
uri:
dependency: "direct main"
description:
name: uri
sha256: "889eea21e953187c6099802b7b4cf5219ba8f3518f604a1033064d45b1b8268a"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
url_launcher:
dependency: "direct main"
description:
name: url_launcher
sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603"
url: "https://pub.dev"
source: hosted
version: "6.3.1"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193"
url: "https://pub.dev"
source: hosted
version: "6.3.14"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626"
url: "https://pub.dev"
source: hosted
version: "6.3.2"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935"
url: "https://pub.dev"
source: hosted
version: "3.2.1"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2"
url: "https://pub.dev"
source: hosted
version: "3.2.2"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e"
url: "https://pub.dev"
source: hosted
version: "2.3.3"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
sha256: "44cf3aabcedde30f2dba119a9dea3b0f2672fbe6fa96e85536251d678216b3c4"
url: "https://pub.dev"
source: hosted
version: "3.1.3"
vector_graphics:
dependency: transitive
description:

View File

@ -42,6 +42,9 @@ dependencies:
equatable: ^2.0.5
flutter_bloc: ^8.1.5
copy_with_extension_gen: ^5.0.4
http: ^1.2.2
uri: ^1.0.0
url_launcher: ^6.3.1
flutter_localizations:
sdk: flutter

View File

@ -6,6 +6,9 @@
#include "generated_plugin_registrant.h"
#include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
}

View File

@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
url_launcher_windows
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST