lab5 done
This commit is contained in:
parent
fa4fcb5acb
commit
d1c005db18
32
lib/data/dtos/potions_dto.g.dart
Normal file
32
lib/data/dtos/potions_dto.g.dart
Normal file
@ -0,0 +1,32 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'potions_dto.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
PotionsDto _$PotionsDtoFromJson(Map<String, dynamic> json) => PotionsDto(
|
||||
data: (json['data'] as List<dynamic>?)
|
||||
?.map((e) => PotionDataDto.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
|
||||
PotionDataDto _$PotionDataDtoFromJson(Map<String, dynamic> json) =>
|
||||
PotionDataDto(
|
||||
id: json['id'] as String?,
|
||||
type: json['type'] as String?,
|
||||
attributes: json['attributes'] == null
|
||||
? null
|
||||
: PotionAttributesDataDto.fromJson(
|
||||
json['attributes'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
PotionAttributesDataDto _$PotionAttributesDataDtoFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
PotionAttributesDataDto(
|
||||
name: json['name'] as String?,
|
||||
characteristics: json['characteristics'] as String?,
|
||||
effect: json['effect'] as String?,
|
||||
image: json['image'] as String?,
|
||||
);
|
@ -1,10 +1,23 @@
|
||||
import 'package:flutter_labs/data/dtos/potions_dto.dart';
|
||||
import 'package:flutter_labs/domain/models/card.dart';
|
||||
|
||||
const _imagePlaceholder =
|
||||
'https://cdn-icons-png.flaticon.com/512/4036/4036418.png';
|
||||
|
||||
extension PotionDataDtoToModel on PotionDataDto {
|
||||
CardData toDomain() => CardData(
|
||||
attributes?.name ?? 'UNKNOWN',
|
||||
imageUrl: attributes?.image,
|
||||
descriptionText: '${attributes?.characteristics} - ${attributes?.effect}',
|
||||
imageUrl: attributes?.image ?? _imagePlaceholder,
|
||||
descriptionText: _makeDescriptionText(attributes?.characteristics, attributes?.effect),
|
||||
);
|
||||
|
||||
String _makeDescriptionText(String? characteristics, String? effect) {
|
||||
return characteristics != null && effect != null
|
||||
? '$characteristics - $effect'
|
||||
: characteristics != null
|
||||
? 'characteristics: $characteristics'
|
||||
: effect != null
|
||||
? 'effect: $effect'
|
||||
: '';
|
||||
}
|
||||
}
|
7
lib/data/repositories/api_interface.dart
Normal file
7
lib/data/repositories/api_interface.dart
Normal file
@ -0,0 +1,7 @@
|
||||
import 'package:flutter_labs/domain/models/card.dart';
|
||||
|
||||
typedef OnErrorCallback = void Function(String? error);
|
||||
|
||||
abstract class ApiInterface {
|
||||
Future<List<CardData>?> loadData({OnErrorCallback? onError});
|
||||
}
|
30
lib/data/repositories/mock_repository.dart
Normal file
30
lib/data/repositories/mock_repository.dart
Normal file
@ -0,0 +1,30 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_labs/data/repositories/api_interface.dart';
|
||||
import 'package:flutter_labs/domain/models/card.dart';
|
||||
|
||||
class MockRepository extends ApiInterface {
|
||||
@override
|
||||
Future<List<CardData>?> loadData({OnErrorCallback? onError}) async {
|
||||
return [
|
||||
CardData(
|
||||
'Mouse',
|
||||
descriptionText: 'hehehe',
|
||||
imageUrl:
|
||||
'https://sun9-26.userapi.com/impg/sB_tLPWPCIqxQWmlbcmlRYiw1ibFb70_QMtNwg/56qpyc_C8Go.jpg?size=736x711&quality=95&sign=8f7163b54538a2e7bad5f36a857485d4&type=album',
|
||||
),
|
||||
CardData(
|
||||
'Mouse2',
|
||||
descriptionText: 'pretty face',
|
||||
icon: Icons.hail,
|
||||
imageUrl:
|
||||
'https://sun165-1.userapi.com/impg/EVLbaLilqr8xw5tsqZLQQb6DSYrdKo7Q9sYSsw/H4FRwyMR6Ec.jpg?size=1280x960&quality=96&sign=f606e4ae3d1ccd27917cd1ffa6d91e58&type=album',
|
||||
),
|
||||
CardData(
|
||||
'Mouse3',
|
||||
descriptionText: 'I like hamsters',
|
||||
icon: Icons.warning_amber,
|
||||
imageUrl: 'https://sun34-1.userapi.com/impg/_DLT-op0LbBdgh5h-ILvC7IMDY5kbLR349v7vA/tX7vtk6mNlA.jpg?size=736x736&quality=96&sign=47f2b0f63bf249c62f4498fb637695d5&type=album',
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
35
lib/data/repositories/potter_repository.dart
Normal file
35
lib/data/repositories/potter_repository.dart
Normal file
@ -0,0 +1,35 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter_labs/data/dtos/potions_dto.dart';
|
||||
import 'package:flutter_labs/data/mappers/potions_mapper.dart';
|
||||
import 'package:flutter_labs/data/repositories/api_interface.dart';
|
||||
import 'package:flutter_labs/domain/models/card.dart';
|
||||
import 'package:pretty_dio_logger/pretty_dio_logger.dart';
|
||||
|
||||
class PotterRepository extends ApiInterface {
|
||||
static final Dio _dio = Dio()
|
||||
..interceptors.add(PrettyDioLogger(
|
||||
requestHeader: true,
|
||||
requestBody: true,
|
||||
));
|
||||
|
||||
static const String _baseUrl = 'https://api.potterdb.com';
|
||||
|
||||
@override
|
||||
Future<List<CardData>?> loadData({String? q, OnErrorCallback? onError}) async {
|
||||
try {
|
||||
const String url = '$_baseUrl/v1/potions';
|
||||
|
||||
final Response<dynamic> response = await _dio.get<Map<dynamic, dynamic>>(
|
||||
url,
|
||||
queryParameters: q != null ? {'filter[name_cont]': q} : null,
|
||||
);
|
||||
|
||||
final PotionsDto dto = PotionsDto.fromJson(response.data as Map<String, dynamic>);
|
||||
final List<CardData>? data = dto.data?.map((e) => e.toDomain()).toList();
|
||||
return data;
|
||||
} on DioException catch (e) {
|
||||
onError?.call(e.response?.statusMessage);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@ class MyApp extends StatelessWidget {
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
||||
useMaterial3: true,
|
||||
),
|
||||
home: const HomePage(title: 'The coolest hamsters on earth ^^'),
|
||||
home: const HomePage(title: 'The most unusual liquids O_o'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
33
lib/presentation/dialogs/error_dialog.dart
Normal file
33
lib/presentation/dialogs/error_dialog.dart
Normal file
@ -0,0 +1,33 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ErrorDialog extends StatelessWidget {
|
||||
final String? error;
|
||||
const ErrorDialog(this.error, {super.key});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.all(36),
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.grey,
|
||||
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.error, color: Colors.white),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
error ?? 'UNKNOWN',
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
12
lib/presentation/dialogs/show_dialog.dart
Normal file
12
lib/presentation/dialogs/show_dialog.dart
Normal file
@ -0,0 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_labs/presentation/dialogs/error_dialog.dart';
|
||||
|
||||
void showErrorDialog(
|
||||
BuildContext context, {
|
||||
required String? error,
|
||||
}) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (_) => ErrorDialog(error),
|
||||
);
|
||||
}
|
@ -80,25 +80,26 @@ class _CardState extends State<_Card> {
|
||||
errorBuilder: (_, __, ___) => const Placeholder(),
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.orangeAccent,
|
||||
borderRadius: BorderRadius.only(
|
||||
topRight: Radius.circular(20),
|
||||
//
|
||||
)),
|
||||
padding: const EdgeInsets.fromLTRB(8, 2, 8, 2),
|
||||
child: Text(
|
||||
'скидок нет',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium
|
||||
?.copyWith(color: Colors.black),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Пусть будет для скидки
|
||||
//Align(
|
||||
// alignment: Alignment.bottomLeft,
|
||||
// child: Container(
|
||||
// decoration: const BoxDecoration(
|
||||
// color: Colors.orangeAccent,
|
||||
// borderRadius: BorderRadius.only(
|
||||
// topRight: Radius.circular(20),
|
||||
// //
|
||||
// )),
|
||||
// padding: const EdgeInsets.fromLTRB(8, 2, 8, 2),
|
||||
// child: Text(
|
||||
// 'скидок нет',
|
||||
// style: Theme.of(context)
|
||||
// .textTheme
|
||||
// .bodyMedium
|
||||
// ?.copyWith(color: Colors.black),
|
||||
// ),
|
||||
// ),
|
||||
//),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -111,7 +112,7 @@ class _CardState extends State<_Card> {
|
||||
children: [
|
||||
Text(
|
||||
widget.text,
|
||||
style: Theme.of(context).textTheme.headlineLarge,
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
Text(
|
||||
widget.descriptionText,
|
||||
|
@ -1,7 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_labs/data/repositories/potter_repository.dart';
|
||||
import 'package:flutter_labs/presentation/details_page/details_page.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_labs/domain/models/card.dart';
|
||||
import 'package:flutter_labs/presentation/dialogs/show_dialog.dart';
|
||||
|
||||
part 'card.dart';
|
||||
|
||||
@ -15,74 +17,89 @@ class HomePage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _HomePageState extends State<HomePage> {
|
||||
final Color _color = Colors.orangeAccent;
|
||||
|
||||
@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 StatelessWidget {
|
||||
class Body extends StatefulWidget {
|
||||
const Body({super.key}); // ключи
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final data = [
|
||||
CardData('mouse',
|
||||
descriptionText: 'hahaha',
|
||||
icon: Icons.abc,
|
||||
imageUrl:
|
||||
'https://sun9-26.userapi.com/impg/sB_tLPWPCIqxQWmlbcmlRYiw1ibFb70_QMtNwg/56qpyc_C8Go.jpg?size=736x711&quality=95&sign=8f7163b54538a2e7bad5f36a857485d4&type=album'),
|
||||
CardData('mouse2',
|
||||
descriptionText: 'ahahaha',
|
||||
icon: Icons.access_alarm_outlined,
|
||||
imageUrl:
|
||||
'https://sun165-1.userapi.com/impg/EVLbaLilqr8xw5tsqZLQQb6DSYrdKo7Q9sYSsw/H4FRwyMR6Ec.jpg?size=1280x960&quality=96&sign=f606e4ae3d1ccd27917cd1ffa6d91e58&type=album'),
|
||||
CardData('mouse3',
|
||||
descriptionText: 'eeee',
|
||||
icon: Icons.access_alarm_rounded,
|
||||
imageUrl:
|
||||
'https://sun34-1.userapi.com/impg/_DLT-op0LbBdgh5h-ILvC7IMDY5kbLR349v7vA/tX7vtk6mNlA.jpg?size=736x736&quality=96&sign=47f2b0f63bf249c62f4498fb637695d5&type=album'),
|
||||
];
|
||||
State<Body> createState() => _BodyState();
|
||||
}
|
||||
|
||||
return Center(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: data.map((data) {
|
||||
return _Card.fromData(
|
||||
data,
|
||||
onLike: (String title, bool isLiked) =>
|
||||
_showSnackBar(context, title, isLiked),
|
||||
onTap: () => _navToDetails(context, data),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
class _BodyState extends State<Body> {
|
||||
final searchController = TextEditingController();
|
||||
late Future<List<CardData>?> data;
|
||||
|
||||
final repo = PotterRepository();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
data = repo.loadData(onError: (e) => showErrorDialog(context, error: e));
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: CupertinoSearchTextField(
|
||||
controller: searchController,
|
||||
onChanged: (search) {
|
||||
setState(() {
|
||||
data = repo.loadData(q: search);
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: FutureBuilder<List<CardData>?>(
|
||||
future: data,
|
||||
builder: (context, snapshot) => SingleChildScrollView(
|
||||
child: snapshot.hasData
|
||||
? Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: snapshot.data?.map((data) {
|
||||
return _Card.fromData(
|
||||
data,
|
||||
onLike: (String title, bool isLiked) =>
|
||||
_showSnackBar(context, title, isLiked),
|
||||
onTap: () => _navToDetails(context, data),
|
||||
);
|
||||
}).toList() ??
|
||||
[],
|
||||
)
|
||||
: const CircularProgressIndicator(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _navToDetails(BuildContext context, CardData data) {
|
||||
Navigator.push(
|
||||
context,
|
||||
CupertinoPageRoute(builder: (context) => DetailsPage(data)),
|
||||
);
|
||||
}
|
||||
|
||||
void _showSnackBar(BuildContext context, String title, bool isLiked) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text(
|
||||
'Cute hamster $title ${isLiked ? 'liked!' : 'disliked :('}',
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
content: Text(
|
||||
'$title ${isLiked ? 'liked!' : 'disliked :('}',
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
backgroundColor: Colors.orangeAccent,
|
||||
duration: const Duration(seconds: 1),
|
||||
));
|
||||
|
24
pubspec.lock
24
pubspec.lock
@ -182,6 +182,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.7"
|
||||
dio:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dio
|
||||
sha256: "5598aa796bbf4699afd5c67c0f5f6e2ed542afc956884b9cd58c306966efc260"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.7.0"
|
||||
dio_web_adapter:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dio_web_adapter
|
||||
sha256: "33259a9276d6cea88774a0000cfae0d861003497755969c92faa223108620dc8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -400,6 +416,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.1"
|
||||
pretty_dio_logger:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: pretty_dio_logger
|
||||
sha256: "36f2101299786d567869493e2f5731de61ce130faa14679473b26905a92b6407"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -11,8 +11,9 @@ dependencies:
|
||||
sdk: flutter
|
||||
|
||||
cupertino_icons: ^1.0.8
|
||||
|
||||
json_annotation: ^4.8.1
|
||||
dio: ^5.4.2+1
|
||||
pretty_dio_logger: ^1.3.1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
Loading…
x
Reference in New Issue
Block a user