lab 7 in process

This commit is contained in:
revengel66 2024-12-17 14:33:28 +04:00
parent 903878a412
commit 7afab39d5a
32 changed files with 577 additions and 336 deletions

6
l10n.yaml Normal file
View File

@ -0,0 +1,6 @@
arb-dir: l10n
template-arb-file: app_ru.arb
output-localization-file: app_locale.dart
output-dir: lib/components/locale/l10n
output-class: AppLocale
synthetic-package: false

27
Makefile Normal file
View File

@ -0,0 +1,27 @@
gen:
flutter pub run build_runner build --delete-conflicting-outputs
icon:
flutter pub run flutter_launcher_icons:main
hello:
echo "Hi!"; \
echo "I`m makefile"; \
echo "^_^"
init_res:
dart pub global activate flutter_asset_generator
format:
dart format . --line-length 100
activate_fgen:
flutter pub global activate fgen
res:
fgen --output lib/components/resources.g.dart --no-watch --no-preview; \
make format
loc:
flutter gen-l10n && \
make format

Binary file not shown.

Before

Width:  |  Height:  |  Size: 544 B

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 B

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 B

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 20 KiB

18
assets/svg/ru.svg Normal file
View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 -4 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_503_2726)">
<rect x="0.25" y="0.25" width="27.5" height="19.5" rx="1.75" fill="white" stroke="#F5F5F5" stroke-width="0.5"/>
<mask id="mask0_503_2726" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="28" height="20">
<rect x="0.25" y="0.25" width="27.5" height="19.5" rx="1.75" fill="white" stroke="white" stroke-width="0.5"/>
</mask>
<g mask="url(#mask0_503_2726)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 13.3333H28V6.66667H0V13.3333Z" fill="#0C47B7"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 20H28V13.3333H0V20Z" fill="#E53B35"/>
</g>
</g>
<defs>
<clipPath id="clip0_503_2726">
<rect width="28" height="20" rx="2" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 949 B

2
assets/svg/uk.svg Normal file
View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--twemoji" preserveAspectRatio="xMidYMid meet"><path fill="#00247D" d="M0 9.059V13h5.628zM4.664 31H13v-5.837zM23 25.164V31h8.335zM0 23v3.941L5.63 23zM31.337 5H23v5.837zM36 26.942V23h-5.631zM36 13V9.059L30.371 13zM13 5H4.664L13 10.837z"></path><path fill="#CF1B2B" d="M25.14 23l9.712 6.801a3.977 3.977 0 0 0 .99-1.749L28.627 23H25.14zM13 23h-2.141l-9.711 6.8c.521.53 1.189.909 1.938 1.085L13 23.943V23zm10-10h2.141l9.711-6.8a3.988 3.988 0 0 0-1.937-1.085L23 12.057V13zm-12.141 0L1.148 6.2a3.994 3.994 0 0 0-.991 1.749L7.372 13h3.487z"></path><path fill="#EEE" d="M36 21H21v10h2v-5.836L31.335 31H32a3.99 3.99 0 0 0 2.852-1.199L25.14 23h3.487l7.215 5.052c.093-.337.158-.686.158-1.052v-.058L30.369 23H36v-2zM0 21v2h5.63L0 26.941V27c0 1.091.439 2.078 1.148 2.8l9.711-6.8H13v.943l-9.914 6.941c.294.07.598.116.914.116h.664L13 25.163V31h2V21H0zM36 9a3.983 3.983 0 0 0-1.148-2.8L25.141 13H23v-.943l9.915-6.942A4.001 4.001 0 0 0 32 5h-.663L23 10.837V5h-2v10h15v-2h-5.629L36 9.059V9zM13 5v5.837L4.664 5H4a3.985 3.985 0 0 0-2.852 1.2l9.711 6.8H7.372L.157 7.949A3.968 3.968 0 0 0 0 9v.059L5.628 13H0v2h15V5h-2z"></path><path fill="#CF1B2B" d="M21 15V5h-6v10H0v6h15v10h6V21h15v-6z"></path></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
assets/wp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 KiB

7
l10n/app_en.arb Normal file
View File

@ -0,0 +1,7 @@
{
"@@locale": "en",
"search": "Search",
"liked": "liked!",
"disliked": "disliked :(",
"arbEnding": "Чтобы не забыть про отсутствие запятой :)"
}

7
l10n/app_ru.arb Normal file
View File

@ -0,0 +1,7 @@
{
"@@locale": "ru",
"search": "Поиск",
"liked": "понравился!",
"disliked": "разонравился :(",
"arbEnding": "Чтобы не забыть про отсутствие запятой :)"
}

View File

@ -0,0 +1,12 @@
/// Generate by [asset_generator](https://github.com/fluttercandies/flutter_asset_generator) library.
/// PLEASE DO NOT EDIT MANUALLY.
// ignore_for_file: constant_identifier_names
class R {
const R._();
/// ![preview](file://C:\Users\Natalia\Desktop\5semestr\PMD\pmd_lab\assets\svg\ru.svg)
static const String ASSETS_SVG_RU_SVG = 'assets/svg/ru.svg';
/// ![preview](file://C:\Users\Natalia\Desktop\5semestr\PMD\pmd_lab\assets\svg\uk.svg)
static const String ASSETS_SVG_UK_SVG = 'assets/svg/uk.svg';
}

View File

@ -9,9 +9,9 @@ class Debounce {
static Timer? _timer;
static void run(
VoidCallback action, {
Duration delay = const Duration(milliseconds: 500),
}) {
VoidCallback action, {
Duration delay = const Duration(milliseconds: 500),
}) {
_timer?.cancel();
_timer = Timer(delay, action);
}

View File

@ -2,9 +2,8 @@ import 'package:json_annotation/json_annotation.dart';
part 'characters_dto.g.dart';
@JsonSerializable(createToJson: false)
class CharactersDto{
class CharactersDto {
final List<CharactersDataDto>? data;
final MetaDto? meta;
const CharactersDto({this.data, this.meta});
@ -12,23 +11,25 @@ class CharactersDto{
}
@JsonSerializable(createToJson: false)
class CharactersDataDto{
class CharactersDataDto {
final String? id;
final String? type;
final CharasterAttributesDataDto? attributes;
const CharactersDataDto({this.id, this.type, this.attributes});
factory CharactersDataDto.fromJson(Map<String, dynamic> json) => _$CharactersDataDtoFromJson(json);
factory CharactersDataDto.fromJson(Map<String, dynamic> json) =>
_$CharactersDataDtoFromJson(json);
}
@JsonSerializable(createToJson: false)
class CharasterAttributesDataDto{
class CharasterAttributesDataDto {
final String? name;
final String? born;
final String? died;
final String? image;
const CharasterAttributesDataDto({this.name, this.born, this.died, this.image});
factory CharasterAttributesDataDto.fromJson(Map<String, dynamic> json) => _$CharasterAttributesDataDtoFromJson(json);
factory CharasterAttributesDataDto.fromJson(Map<String, dynamic> json) =>
_$CharasterAttributesDataDtoFromJson(json);
}
@JsonSerializable(createToJson: false)
@ -49,4 +50,4 @@ class PaginationDto {
const PaginationDto({this.current, this.next, this.last});
factory PaginationDto.fromJson(Map<String, dynamic> json) => _$PaginationDtoFromJson(json);
}
}

View File

@ -6,28 +6,22 @@ part of 'characters_dto.dart';
// JsonSerializableGenerator
// **************************************************************************
CharactersDto _$CharactersDtoFromJson(Map<String, dynamic> json) =>
CharactersDto(
CharactersDto _$CharactersDtoFromJson(Map<String, dynamic> json) => CharactersDto(
data: (json['data'] as List<dynamic>?)
?.map((e) => CharactersDataDto.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>),
);
CharactersDataDto _$CharactersDataDtoFromJson(Map<String, dynamic> json) =>
CharactersDataDto(
CharactersDataDto _$CharactersDataDtoFromJson(Map<String, dynamic> json) => CharactersDataDto(
id: json['id'] as String?,
type: json['type'] as String?,
attributes: json['attributes'] == null
? null
: CharasterAttributesDataDto.fromJson(
json['attributes'] as Map<String, dynamic>),
: CharasterAttributesDataDto.fromJson(json['attributes'] as Map<String, dynamic>),
);
CharasterAttributesDataDto _$CharasterAttributesDataDtoFromJson(
Map<String, dynamic> json) =>
CharasterAttributesDataDto _$CharasterAttributesDataDtoFromJson(Map<String, dynamic> json) =>
CharasterAttributesDataDto(
name: json['name'] as String?,
born: json['born'] as String?,
@ -41,8 +35,7 @@ MetaDto _$MetaDtoFromJson(Map<String, dynamic> json) => MetaDto(
: PaginationDto.fromJson(json['pagination'] as Map<String, dynamic>),
);
PaginationDto _$PaginationDtoFromJson(Map<String, dynamic> json) =>
PaginationDto(
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

@ -2,30 +2,28 @@ import 'package:pmd_lab/data/dtos/characters_dto.dart';
import 'package:pmd_lab/domain/models/card.dart';
import 'package:pmd_lab/domain/models/home.dart';
const _imagePlaceholder = 'https://cdn-icons-png.flaticon.com/512/4054/4054617.png';
extension CharacterDataDtoToModel on CharactersDataDto {
CardData toDomain() => CardData(
attributes?.name ?? 'UNKNOWN',
imgUrl: attributes?.image ?? _imagePlaceholder,
description: _makeDescription(attributes?.born, attributes?.died),
);
String _makeDescription(String? born, String? died){
attributes?.name ?? 'UNKNOWN',
imgUrl: attributes?.image ?? _imagePlaceholder,
description: _makeDescription(attributes?.born, attributes?.died),
);
String _makeDescription(String? born, String? died) {
return born != null && died != null
? '$born - $died'
: born != null
? 'born: $born'
: died != null
? 'died: $died'
: '';
? 'born: $born'
: died != null
? 'died: $died'
: '';
}
}
extension CharactersDtoToModel on CharactersDto {
HomeData toDomain() => HomeData(
data: data?.map((e) => e.toDomain()).toList(),
nextPage: meta?.pagination?.next,
);
data: data?.map((e) => e.toDomain()).toList(),
nextPage: meta?.pagination?.next,
);
}

View File

@ -4,8 +4,8 @@ class CardData {
final String? imgUrl;
CardData(
this.text, {
required this.description,
this.imgUrl,
});
}
this.text, {
required this.description,
this.imgUrl,
});
}

View File

@ -14,20 +14,19 @@ class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// colorScheme: ColorScheme.fromSeed(seedColor: Colors.black), colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: RepositoryProvider<PotterRepository>(
lazy: true,
create: (_) => PotterRepository(),
child: BlocProvider<HomeBloc>(
title: 'Potter App',
theme: ThemeData(
primarySwatch: Colors.orange,
scaffoldBackgroundColor: Colors.white,
useMaterial3: true,
),
home: RepositoryProvider<PotterRepository>(
lazy: true,
create: (_) => PotterRepository(),
child: BlocProvider<HomeBloc>(
lazy: true,
create: (context) => HomeBloc(context.read<PotterRepository>()),
child: const MyHomePage(title: 'Potter API'),
),
)
);
),
));
}
}

View File

@ -0,0 +1,34 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:pmd_lab/components/resources.g.dart';
abstract class SvgObjects {
static void init() {
final pics = <String>[
R.ASSETS_SVG_RU_SVG,
R.ASSETS_SVG_UK_SVG,
];
for (final String p in pics) {
final loader = SvgAssetLoader(p);
svg.cache.putIfAbsent(loader.cacheKey(null), () => loader.loadBytes(null));
}
}
}
class SvgRu extends StatelessWidget {
const SvgRu({super.key});
@override
Widget build(BuildContext context) {
return SvgPicture.asset(R.ASSETS_SVG_RU_SVG);
}
}
class SvgUk extends StatelessWidget {
const SvgUk({super.key});
@override
Widget build(BuildContext context) {
return SvgPicture.asset(R.ASSETS_SVG_UK_SVG);
}
}

View File

@ -9,27 +9,28 @@ class DetailsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body:Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 16),
child: Image.network(data.imgUrl ?? '',),
),
Padding(
padding: const EdgeInsets.only(bottom: 4),
child: Text(
appBar: AppBar(),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 16),
child: Image.network(
data.imgUrl ?? '',
),
),
Padding(
padding: const EdgeInsets.only(bottom: 4),
child: Text(
data.text,
style: Theme.of(context).textTheme.headlineLarge,
),
),
),
Text(
Text(
data.description,
style: Theme.of(context).textTheme.bodyLarge,
)
],
)
);
)
],
));
}
}

View File

@ -36,6 +36,4 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
error: error,
));
}
}

View File

@ -1,11 +1,9 @@
class HomeEvent {
const HomeEvent();
}
class HomeLoadDataEvent extends HomeEvent{
class HomeLoadDataEvent extends HomeEvent {
final String? search;
final int? nextPage;
const HomeLoadDataEvent({this.search, this.nextPage});
}
}

View File

@ -34,11 +34,9 @@ class HomeState extends Equatable {
@override
List<Object?> get props => [
data,
isLoading,
isPaginationLoading,
error,
];
data,
isLoading,
isPaginationLoading,
error,
];
}

View File

@ -72,8 +72,7 @@ class _$HomeStateCWProxyImpl implements _$HomeStateCWProxy {
// ignore: cast_nullable_to_non_nullable
: isLoading as bool,
isPaginationLoading:
isPaginationLoading == const $CopyWithPlaceholder() ||
isPaginationLoading == null
isPaginationLoading == const $CopyWithPlaceholder() || isPaginationLoading == null
? _value.isPaginationLoading
// ignore: cast_nullable_to_non_nullable
: isPaginationLoading as bool,

View File

@ -10,20 +10,20 @@ class _Card extends StatefulWidget {
final VoidCallback? onTap;
const _Card(
this.text, {
required this.description,
this.imgUrl,
this.onLike,
this.onTap,
});
this.text, {
required this.description,
this.imgUrl,
this.onLike,
this.onTap,
});
factory _Card.fromData(CardData data, {OnLikeCallBack onLike, VoidCallback? onTap}) => _Card(
data.text,
description: data.description,
imgUrl: data.imgUrl,
onLike: onLike,
onTap: onTap,
);
data.text,
description: data.description,
imgUrl: data.imgUrl,
onLike: onLike,
onTap: onTap,
);
@override
State<_Card> createState() => _CardState();
@ -38,81 +38,82 @@ class _CardState extends State<_Card> {
onTap: widget.onTap,
child: Container(
margin: const EdgeInsets.only(bottom: 20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
spreadRadius: 1,
offset: const Offset(0, 0),
blurRadius: 15,
)
]),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Stack(
children: [
ClipRRect(
borderRadius: const BorderRadius.only(topLeft: Radius.circular(20), topRight: Radius.circular(20)),
child: Image.network(
widget.imgUrl ?? '',
errorBuilder: (_, __, ___) => const Placeholder(),
height: 370,
alignment: Alignment.topCenter,
fit: BoxFit.cover,
width: double.infinity,
decoration:
BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20), boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
spreadRadius: 1,
offset: const Offset(0, 0),
blurRadius: 15,
)
]),
child: Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
Stack(children: [
ClipRRect(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(20), topRight: Radius.circular(20)),
child: Image.network(
widget.imgUrl ?? '',
errorBuilder: (_, __, ___) => const Placeholder(),
height: 370,
alignment: Alignment.topCenter,
fit: BoxFit.cover,
width: double.infinity,
),
),
]),
Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Text(
widget.text,
style: const TextStyle(
color: Color(0xff4c4c4c),
fontSize: 30,
fontWeight: FontWeight.bold,
),
),
]
),
Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(bottom:10),
child: Text(
widget.text,
style: const TextStyle(
color: Color(0xff4c4c4c),
fontSize: 30,
fontWeight: FontWeight.bold,
),
),
),
Text(
widget.description,
style: const TextStyle(
color: Color(0xff9c9c9c),
fontSize: 20,
fontWeight: FontWeight.normal,
),
),
Padding(
padding: const EdgeInsets.only(top:10),
child: GestureDetector(
onTap:(){
setState(() {
isLiked = !isLiked;
});
widget.onLike?.call(widget.text, isLiked);
},
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: isLiked
? const Icon(Icons.favorite, color: Colors.redAccent,key: ValueKey<int>(0),)
: const Icon(Icons.favorite_border, key:ValueKey<int>(1),),
),
)
)
],
),
),
]),
Text(
widget.description,
style: const TextStyle(
color: Color(0xff9c9c9c),
fontSize: 20,
fontWeight: FontWeight.normal,
),
),
Padding(
padding: const EdgeInsets.only(top: 10),
child: GestureDetector(
onTap: () {
setState(() {
isLiked = !isLiked;
});
widget.onLike?.call(widget.text, isLiked);
},
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: isLiked
? const Icon(
Icons.favorite,
color: Colors.redAccent,
key: ValueKey<int>(0),
)
: const Icon(
Icons.favorite_border,
key: ValueKey<int>(1),
),
),
))
],
),
),
]),
),
);
}
}
}

View File

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pmd_lab/components/utils/debounce.dart';
import 'package:pmd_lab/domain/models/card.dart';
import 'package:pmd_lab/presentation/common/svg_objects.dart';
import 'package:pmd_lab/presentation/details_page/details_page.dart';
import 'package:pmd_lab/presentation/home_page/bloc/bloc.dart';
import 'package:pmd_lab/presentation/home_page/bloc/events.dart';
@ -12,68 +13,21 @@ import 'package:pmd_lab/repositories/mock_repository.dart';
import 'package:pmd_lab/repositories/potter_repository.dart';
part 'card.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// colorScheme: ColorScheme.fromSeed(seedColor: Colors.black), colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(title: 'Flutter rocks!'),
);
}
}
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> {
final Color _color = Colors.orangeAccent;
@override
void initState(){
WidgetsBinding.instance.addPostFrameCallback( (_) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('Hello', style: Theme
.of(context)
.textTheme
.bodyLarge,),
backgroundColor: Colors.orangeAccent,
duration: const Duration(seconds: 1),
));
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: _color,
title: Text(widget.title),
),
body: const Body(),
);
return const Scaffold(body: Body());
}
}
class Body extends StatefulWidget {
const Body({super.key});
@override
State<Body> createState() => _BodyState();
}
@ -86,12 +40,15 @@ class _BodyState extends State<Body> {
@override
void initState() {
SvgObjects.init();
WidgetsBinding.instance.addPostFrameCallback((_) {
context.read<HomeBloc>().add(const HomeLoadDataEvent());
});
scrollController.addListener(_onNextPageListener);
super.initState();
}
void _onNextPageListener() {
if (scrollController.offset > scrollController.position.maxScrollExtent) {
final bloc = context.read<HomeBloc>();
@ -103,8 +60,9 @@ class _BodyState extends State<Body> {
}
}
}
@override
void dispose(){
void dispose() {
searchController.dispose();
scrollController.dispose();
@ -113,7 +71,6 @@ class _BodyState extends State<Body> {
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
child: Column(
@ -130,31 +87,32 @@ class _BodyState extends State<Body> {
BlocBuilder<HomeBloc, HomeState>(
builder: (context, state) => state.error != null
? Text(
state.error ?? '',
style: Theme.of(context).textTheme.headlineSmall?.copyWith(color: Colors.red),
)
: state.isLoading
? const CircularProgressIndicator()
: Expanded(
child: RefreshIndicator(
onRefresh: _onRefresh,
child: ListView.builder(
padding: EdgeInsets.zero,
itemCount: state.data?.data?.length ?? 0,
itemBuilder: (context, index) {
final data = state.data?.data?[index];
return data != null
? _Card.fromData(
data,
onLike: (title, isLiked) => _showSnackBar(context, title, isLiked),
onTap: () => _navToDetails(context, data),
)
: const SizedBox.shrink();
},
), // ListView.builder
), // RefreshIndicator
), // Expanded
? Text(
state.error ?? '',
style: Theme.of(context).textTheme.headlineSmall?.copyWith(color: Colors.red),
)
: state.isLoading
? const CircularProgressIndicator()
: Expanded(
child: RefreshIndicator(
onRefresh: _onRefresh,
child: ListView.builder(
padding: EdgeInsets.zero,
itemCount: state.data?.data?.length ?? 0,
itemBuilder: (context, index) {
final data = state.data?.data?[index];
return data != null
? _Card.fromData(
data,
onLike: (title, isLiked) =>
_showSnackBar(context, title, isLiked),
onTap: () => _navToDetails(context, data),
)
: const SizedBox.shrink();
},
), // ListView.builder
), // RefreshIndicator
), // Expanded
), // BlocBuilder
BlocBuilder<HomeBloc, HomeState>(
builder: (context, state) => state.isPaginationLoading
@ -171,10 +129,8 @@ class _BodyState extends State<Body> {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(
'Article $title ${isLiked ? 'liked!' : 'disliked :('}',
style: Theme
.of(context)
.textTheme
.bodyLarge,),
style: Theme.of(context).textTheme.bodyLarge,
),
backgroundColor: Colors.orangeAccent,
duration: const Duration(seconds: 1),
));
@ -192,5 +148,4 @@ class _BodyState extends State<Body> {
context.read<HomeBloc>().add(HomeLoadDataEvent(search: searchController.text));
return Future.value(null);
}
}
}

View File

@ -1,4 +1,3 @@
import 'package:pmd_lab/domain/models/home.dart';
typedef OnErrorCallback = void Function(String? error);
@ -6,4 +5,3 @@ typedef OnErrorCallback = void Function(String? error);
abstract class ApiInterface {
Future<HomeData?> loadData({OnErrorCallback? onError});
}

View File

@ -4,8 +4,8 @@ import 'package:pmd_lab/domain/models/card.dart';
class MockRepository extends ApiInterface {
@override
Future <HomeData?> loadData({OnErrorCallback? onError}) async {
return HomeData();
Future<HomeData?> loadData({OnErrorCallback? onError}) async {
return HomeData();
// return [
// CardData(
// 'Какая-то новость',
@ -25,6 +25,6 @@ class MockRepository extends ApiInterface {
// imgUrl:
// "https://universal.revengel.ru/assets/cache_image/images/services/1_731x487_1cb.png",
// )
//];
//];
}
}
}

View File

@ -8,7 +8,7 @@ import 'package:pmd_lab/domain/models/home.dart';
import 'package:pmd_lab/repositories/api_interface.dart';
import 'package:pretty_dio_logger/pretty_dio_logger.dart';
class PotterRepository extends ApiInterface{
class PotterRepository extends ApiInterface {
static final Dio _dio = Dio()
..interceptors.add(PrettyDioLogger(
requestHeader: true,
@ -39,10 +39,9 @@ class PotterRepository extends ApiInterface{
final CharactersDto dto = CharactersDto.fromJson(response.data as Map<String, dynamic>);
final HomeData data = dto.toDomain();
return data;
} on DioException catch (e) {
onError?.call(e.error?.toString());
return null;
}
}
}
}

View File

@ -22,6 +22,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.7.0"
archive:
dependency: transitive
description:
name: archive
sha256: "08064924cbf0ab88280a0c3f60db9dd24fec693927e725ecb176f16c629d1cb8"
url: "https://pub.dev"
source: hosted
version: "4.0.1"
args:
dependency: transitive
description:
@ -134,6 +142,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.3"
cli_util:
dependency: transitive
description:
name: cli_util
sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
url: "https://pub.dev"
source: hosted
version: "0.4.2"
clock:
dependency: transitive
description:
@ -238,6 +254,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.1"
ffi:
dependency: transitive
description:
name: ffi
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
url: "https://pub.dev"
source: hosted
version: "2.1.3"
file:
dependency: transitive
description:
@ -267,19 +291,45 @@ packages:
url: "https://pub.dev"
source: hosted
version: "8.1.6"
flutter_launcher_icons:
dependency: "direct dev"
description:
name: flutter_launcher_icons
sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea"
url: "https://pub.dev"
source: hosted
version: "0.13.1"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c"
sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04
url: "https://pub.dev"
source: hosted
version: "4.0.0"
version: "2.0.3"
flutter_localizations:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_svg:
dependency: "direct main"
description:
name: flutter_svg
sha256: "8c5d68a82add3ca76d792f058b186a0599414f279f00ece4830b9b231b570338"
url: "https://pub.dev"
source: hosted
version: "2.0.7"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
frontend_server_client:
dependency: transitive
description:
@ -304,6 +354,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.2"
http:
dependency: transitive
description:
name: http
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
url: "https://pub.dev"
source: hosted
version: "1.2.2"
http_multi_server:
dependency: transitive
description:
@ -320,6 +378,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.0.2"
image:
dependency: transitive
description:
name: image
sha256: "20842a5ad1555be624c314b0c0cc0566e8ece412f61e859a42efeb6d4101a26c"
url: "https://pub.dev"
source: hosted
version: "4.5.0"
intl:
dependency: "direct main"
description:
name: intl
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
url: "https://pub.dev"
source: hosted
version: "0.19.0"
io:
dependency: transitive
description:
@ -380,10 +454,10 @@ packages:
dependency: transitive
description:
name: lints
sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452"
url: "https://pub.dev"
source: hosted
version: "4.0.0"
version: "2.1.1"
logging:
dependency: transitive
description:
@ -456,6 +530,62 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.9.0"
path_parsing:
dependency: transitive
description:
name: path_parsing
sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
source: hosted
version: "2.3.0"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
url: "https://pub.dev"
source: hosted
version: "6.0.2"
platform:
dependency: transitive
description:
name: platform
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
url: "https://pub.dev"
source: hosted
version: "3.1.6"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
pool:
dependency: transitive
description:
@ -464,6 +594,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.5.1"
posix:
dependency: transitive
description:
name: posix
sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a
url: "https://pub.dev"
source: hosted
version: "6.0.1"
pretty_dio_logger:
dependency: "direct main"
description:
@ -496,6 +634,62 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.0"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180
url: "https://pub.dev"
source: hosted
version: "2.2.3"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "02a7d8a9ef346c9af715811b01fbd8e27845ad2c41148eefd31321471b41863d"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
url: "https://pub.dev"
source: hosted
version: "2.5.4"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e
url: "https://pub.dev"
source: hosted
version: "2.4.2"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shelf:
dependency: transitive
description:
@ -605,6 +799,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.4.0"
vector_graphics:
dependency: transitive
description:
name: vector_graphics
sha256: "27d5fefe86fb9aace4a9f8375b56b3c292b64d8c04510df230f849850d912cb7"
url: "https://pub.dev"
source: hosted
version: "1.1.15"
vector_graphics_codec:
dependency: transitive
description:
name: vector_graphics_codec
sha256: "2430b973a4ca3c4dbc9999b62b8c719a160100dcbae5c819bae0cacce32c9cdb"
url: "https://pub.dev"
source: hosted
version: "1.1.12"
vector_graphics_compiler:
dependency: transitive
description:
name: vector_graphics_compiler
sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad"
url: "https://pub.dev"
source: hosted
version: "1.1.16"
vector_math:
dependency: transitive
description:
@ -653,6 +871,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.1"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
xml:
dependency: transitive
description:
name: xml
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
url: "https://pub.dev"
source: hosted
version: "6.5.0"
yaml:
dependency: transitive
description:
@ -663,4 +897,4 @@ packages:
version: "3.1.2"
sdks:
dart: ">=3.5.3 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54"
flutter: ">=3.24.0"

View File

@ -1,98 +1,54 @@
name: pmd_lab
description: "A new Flutter project."
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+1
environment:
sdk: ^3.5.3
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
flutter:
sdk: flutter
# Виджеты
cupertino_icons: ^1.0.2
flutter_svg: 2.0.7
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8
# Сетевое взаимодействие
json_annotation: ^4.8.1
dio: ^5.4.2+1
pretty_dio_logger: ^1.3.1
# BLoC
equatable: ^2.0.5
flutter_bloc: ^8.1.5
copy_with_extension_gen: ^5.0.4
# Localization
flutter_localizations:
sdk: flutter
intl: ^0.19.0
shared_preferences: 2.2.3
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
# Иконки
flutter_launcher_icons: 0.13.1
# Сетевое взаимодействие
build_runner: ^2.4.9
json_serializable: ^6.7.1
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^4.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
flutter_icons:
android: "ic_launcher"
image_path: "assets/wp.png"
min_sdk_android: 21
# The following section is specific to Flutter packages.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
generate: true
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/to/asset-from-package
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/to/font-from-package
assets:
- assets/svg/