Что-то произошло, но что не понятно)
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 6.5 KiB |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 16 KiB |
BIN
assets/cover.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
assets/ic_launcher.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
assets/play_store_512.png
Normal file
After Width: | Height: | Size: 23 KiB |
1
assets/svg/ru.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 9 6" width="900" height="600"><path fill="#fff" d="M0 0h9v3H0z"/><path fill="#d52b1e" d="M0 3h9v3H0z"/><path fill="#0039a6" d="M0 2h9v2H0z"/></svg>
|
After Width: | Height: | Size: 200 B |
2
assets/svg/uk.svg
Normal 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 |
@ -1,7 +1,8 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ui';
|
||||
|
||||
class Debounce { //помощник
|
||||
class Debounce {
|
||||
//помощник
|
||||
factory Debounce() => _instance; //обращение через статический конструктор
|
||||
|
||||
Debounce._();
|
||||
@ -10,10 +11,11 @@ class Debounce { //помощник
|
||||
|
||||
static Timer? _timer;
|
||||
|
||||
static void run(//создаём задержку для ввода пользователя в поисковую строку
|
||||
VoidCallback action, {
|
||||
Duration delay = const Duration(milliseconds: 500),
|
||||
}) {
|
||||
static void run(
|
||||
//создаём задержку для ввода пользователя в поисковую строку
|
||||
VoidCallback action, {
|
||||
Duration delay = const Duration(milliseconds: 500),
|
||||
}) {
|
||||
_timer?.cancel();
|
||||
_timer = Timer(delay, action);
|
||||
}
|
||||
|
@ -3,9 +3,9 @@ import '../dtos/characters_dto.dart';
|
||||
|
||||
extension CharacterDtoToModel on CharacterDto {
|
||||
CardData toDomain() => CardData(
|
||||
displayName ?? 'UNKNOWN',
|
||||
descriptionText: developerName ?? 'Описание отсутствует',
|
||||
gameDesc: description,
|
||||
imageUrl: displayIcon,
|
||||
);
|
||||
displayName ?? 'UNKNOWN',
|
||||
descriptionText: developerName ?? 'Описание отсутствует',
|
||||
gameDesc: description,
|
||||
imageUrl: displayIcon,
|
||||
);
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
|
||||
import '../../domain/models/card.dart';
|
||||
|
||||
typedef OnErrorCallback = void Function(String? error);
|
||||
|
||||
abstract class ApiInterface{
|
||||
abstract class ApiInterface {
|
||||
Future<List<CardData>?> loadData();
|
||||
}
|
@ -16,7 +16,10 @@ class AgentsRepository extends ApiInterface {
|
||||
static const String _baseUrl = 'https://valorant-api.com/v1/ agents';
|
||||
|
||||
@override
|
||||
Future<List<CardData>?> loadData({OnErrorCallback? onError,String? q,}) async {
|
||||
Future<List<CardData>?> loadData({
|
||||
OnErrorCallback? onError,
|
||||
String? q,
|
||||
}) async {
|
||||
try {
|
||||
// Формирование URL для запроса
|
||||
final Response<dynamic> response = await _dio.get(_baseUrl);
|
||||
@ -30,8 +33,9 @@ class AgentsRepository extends ApiInterface {
|
||||
|
||||
// Фильтрация данных по displayName
|
||||
if (q != null && q.isNotEmpty) {
|
||||
data = data?.where((agent) =>
|
||||
agent.text?.toLowerCase().contains(q.toLowerCase()) ?? false).toList();
|
||||
data = data
|
||||
?.where((agent) => agent.text?.toLowerCase().contains(q.toLowerCase()) ?? false)
|
||||
.toList();
|
||||
}
|
||||
|
||||
return data;
|
||||
|
@ -1,40 +1,37 @@
|
||||
|
||||
import '../../domain/models/card.dart';
|
||||
import 'api_interface.dart';
|
||||
|
||||
class MockRepository extends ApiInterface {
|
||||
@override
|
||||
Future<List<CardData>?> loadData() async{
|
||||
Future<List<CardData>?> loadData() async {
|
||||
return [
|
||||
CardData(
|
||||
'Far Cry 1',
|
||||
CardData('Far Cry 1',
|
||||
descriptionText: 'Так себе',
|
||||
imageUrl: 'https://images.stopgame.ru/games/logos/8351/c1536x856/VdQJNv5ECykaiteg212k5g/far_cry_2-square_1.jpg',
|
||||
imageUrl:
|
||||
'https://images.stopgame.ru/games/logos/8351/c1536x856/VdQJNv5ECykaiteg212k5g/far_cry_2-square_1.jpg',
|
||||
gameDesc: 'Обретите сверхчеловеческую мощь космодесантника. Пустите в ход смертоносные навыки и разрушительное оружие, чтобы ' +
|
||||
' истребить безжалостных тиранидов. Защитите Империум в ярких одиночных боях или многопользовательских режимах с ' +
|
||||
'видом от третьего лица. В продолжении экшена 2011 года Space Marine вам предстоит сразиться с врагами человечества и' +
|
||||
' вновь доказать ему свою преданность в роли лейтенанта Деметрия Тита, который вернулся в ряды Ультрамаринов. Дайте отпор ' +
|
||||
'ужасам Галактики в эпических боях сразу на нескольких планетах. Раскройте мрачные секреты и отбросьте тень вечной ночи.'), // создаётся объект ранее созданного контейнера
|
||||
CardData(
|
||||
'Far Cry 2',
|
||||
CardData('Far Cry 2',
|
||||
descriptionText: 'Лучше',
|
||||
imageUrl: 'https://images.stopgame.ru/games/logos/8351/c1536x856/VdQJNv5ECykaiteg212k5g/far_cry_2-square_1.jpg',
|
||||
imageUrl:
|
||||
'https://images.stopgame.ru/games/logos/8351/c1536x856/VdQJNv5ECykaiteg212k5g/far_cry_2-square_1.jpg',
|
||||
gameDesc: 'Обретите сверхчеловеческую мощь космодесантника. Пустите в ход смертоносные навыки и разрушительное оружие, чтобы ' +
|
||||
' истребить безжалостных тиранидов. Защитите Империум в ярких одиночных боях или многопользовательских режимах с ' +
|
||||
'видом от третьего лица. В продолжении экшена 2011 года Space Marine вам предстоит сразиться с врагами человечества и' +
|
||||
' вновь доказать ему свою преданность в роли лейтенанта Деметрия Тита, который вернулся в ряды Ультрамаринов. Дайте отпор ' +
|
||||
'ужасам Галактики в эпических боях сразу на нескольких планетах. Раскройте мрачные секреты и отбросьте тень вечной ночи.'),
|
||||
CardData(
|
||||
'Far Cry 3',
|
||||
CardData('Far Cry 3',
|
||||
descriptionText: 'Красавцы',
|
||||
imageUrl: 'https://images.stopgame.ru/games/logos/8351/c1536x856/VdQJNv5ECykaiteg212k5g/far_cry_2-square_1.jpg',
|
||||
imageUrl:
|
||||
'https://images.stopgame.ru/games/logos/8351/c1536x856/VdQJNv5ECykaiteg212k5g/far_cry_2-square_1.jpg',
|
||||
gameDesc: 'Обретите сверхчеловеческую мощь космодесантника. Пустите в ход смертоносные навыки и разрушительное оружие, чтобы ' +
|
||||
' истребить безжалостных тиранидов. Защитите Империум в ярких одиночных боях или многопользовательских режимах с ' +
|
||||
'видом от третьего лица. В продолжении экшена 2011 года Space Marine вам предстоит сразиться с врагами человечества и' +
|
||||
' вновь доказать ему свою преданность в роли лейтенанта Деметрия Тита, который вернулся в ряды Ультрамаринов. Дайте отпор ' +
|
||||
'ужасам Галактики в эпических боях сразу на нескольких планетах. Раскройте мрачные секреты и отбросьте тень вечной ночи.'
|
||||
|
||||
),
|
||||
'ужасам Галактики в эпических боях сразу на нескольких планетах. Раскройте мрачные секреты и отбросьте тень вечной ночи.'),
|
||||
];
|
||||
}
|
||||
}
|
@ -8,10 +8,10 @@ class CardData {
|
||||
final String? gameDesc;
|
||||
|
||||
CardData(
|
||||
this.text, {
|
||||
required this.descriptionText,
|
||||
//this.icon = Icons.ac_unit_outlined,
|
||||
this.imageUrl,
|
||||
required this.gameDesc,
|
||||
});
|
||||
this.text, {
|
||||
required this.descriptionText,
|
||||
//this.icon = Icons.ac_unit_outlined,
|
||||
this.imageUrl,
|
||||
required this.gameDesc,
|
||||
});
|
||||
}
|
@ -20,21 +20,18 @@ class MyApp extends StatelessWidget {
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
|
||||
useMaterial3: true,
|
||||
),
|
||||
home: RepositoryProvider<AgentsRepository>( //даём нашему репозиторию доступ
|
||||
home: RepositoryProvider<AgentsRepository>(
|
||||
//даём нашему репозиторию доступ
|
||||
lazy: true, //ленивое создание объекта
|
||||
create: (_) => AgentsRepository(),
|
||||
child: BlocProvider<HomeBloc>( //даём доступ Bloc для нашей страницы
|
||||
child: BlocProvider<HomeBloc>(
|
||||
//даём доступ Bloc для нашей страницы
|
||||
lazy: false,
|
||||
create: (context) => HomeBloc(context.read<AgentsRepository>()), //в конструктор нашего Блока передаём репозиторий с нашей апи, то есть у нас появляется доступ по дереву к апи
|
||||
create: (context) => HomeBloc(context.read<
|
||||
AgentsRepository>()), //в конструктор нашего Блока передаём репозиторий с нашей апи, то есть у нас появляется доступ по дереву к апи
|
||||
child: const MyHomePage(title: 'Агенты Valorant'),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -9,12 +9,15 @@ class DetailsPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(backgroundColor: Colors.deepPurple,
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.deepPurple,
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Container(
|
||||
constraints: BoxConstraints( minHeight: MediaQuery.sizeOf(context).height - 105),
|
||||
decoration: const BoxDecoration(color: Colors.deepPurpleAccent,),
|
||||
constraints: BoxConstraints(minHeight: MediaQuery.sizeOf(context).height - 105),
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.deepPurpleAccent,
|
||||
),
|
||||
padding: const EdgeInsets.all(0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
@ -3,25 +3,27 @@ import 'package:flutter_test_app/presentation/home_page/bloc/events.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../../data/repositories/characters_repository.dart';
|
||||
|
||||
class HomeBloc extends Bloc<HomeEvent, HomeState>{//наследуем от Bloc и передаём состояния
|
||||
class HomeBloc extends Bloc<HomeEvent, HomeState> {
|
||||
//наследуем от Bloc и передаём состояния
|
||||
final AgentsRepository rep;
|
||||
|
||||
HomeBloc(this.rep) : super(const HomeState()) {
|
||||
on<HomeLoadDataEvent>(_onLoadData);
|
||||
}
|
||||
|
||||
Future<void> _onLoadData(
|
||||
HomeLoadDataEvent event, Emitter<HomeState> emit) async {
|
||||
Future<void> _onLoadData(HomeLoadDataEvent event, Emitter<HomeState> emit) async {
|
||||
emit(state.copyWith(isLoading: true)); //метода emit изменяет наши состояния
|
||||
|
||||
String? error;
|
||||
|
||||
final data = await rep.loadData( //загрузка данных
|
||||
final data = await rep.loadData(
|
||||
//загрузка данных
|
||||
q: event.search,
|
||||
onError: (e) => error = e,
|
||||
);
|
||||
|
||||
emit(state.copyWith( //метода emit изменяет наши состояния
|
||||
emit(state.copyWith(
|
||||
//метода emit изменяет наши состояния
|
||||
isLoading: false, //флаг загрузки меняем на false
|
||||
data: data,
|
||||
error: error,
|
||||
|
@ -1,8 +1,9 @@
|
||||
abstract class HomeEvent{ //наследуем все события
|
||||
abstract class HomeEvent {
|
||||
//наследуем все события
|
||||
const HomeEvent();
|
||||
}
|
||||
|
||||
class HomeLoadDataEvent extends HomeEvent{
|
||||
class HomeLoadDataEvent extends HomeEvent {
|
||||
final String? search; //событие поиска
|
||||
|
||||
const HomeLoadDataEvent({this.search}); //событие на загрузку данных
|
||||
|
@ -3,7 +3,8 @@ import 'package:equatable/equatable.dart';
|
||||
import '../../../domain/models/card.dart';
|
||||
|
||||
@CopyWith()
|
||||
class HomeState extends Equatable{ //сравнение двух состояний через Equatable
|
||||
class HomeState extends Equatable {
|
||||
//сравнение двух состояний через Equatable
|
||||
final List<CardData>? data;
|
||||
final bool isLoading; //отображение загрузки
|
||||
final String? error; //отображение ошибок
|
||||
@ -14,7 +15,8 @@ class HomeState extends Equatable{ //сравнение двух состоян
|
||||
this.error,
|
||||
});
|
||||
|
||||
HomeState copyWith({ //copyWith создаёт копию объекта с изменением некоторых данных
|
||||
HomeState copyWith({
|
||||
//copyWith создаёт копию объекта с изменением некоторых данных
|
||||
List<CardData>? data,
|
||||
bool? isLoading,
|
||||
String? error,
|
||||
@ -27,8 +29,8 @@ class HomeState extends Equatable{ //сравнение двух состоян
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
data,
|
||||
isLoading,
|
||||
error,
|
||||
];
|
||||
data,
|
||||
isLoading,
|
||||
error,
|
||||
];
|
||||
}
|
@ -2,7 +2,6 @@ part of 'home_page.dart';
|
||||
|
||||
typedef OnLikeCallback = void Function(String title, bool isLiked); //сделано от дублирования кода
|
||||
|
||||
|
||||
class _Card extends StatefulWidget {
|
||||
final String text;
|
||||
final String descriptionText;
|
||||
@ -12,20 +11,19 @@ class _Card extends StatefulWidget {
|
||||
final VoidCallback? onTap;
|
||||
|
||||
const _Card(
|
||||
this.text, {
|
||||
//this.icon = Icons.ac_unit_outlined,
|
||||
required this.descriptionText,
|
||||
this.imageUrl,
|
||||
this.onLike,
|
||||
this.onTap,
|
||||
}
|
||||
);
|
||||
this.text, {
|
||||
//this.icon = Icons.ac_unit_outlined,
|
||||
required this.descriptionText,
|
||||
this.imageUrl,
|
||||
this.onLike,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
factory _Card.fromData(
|
||||
CardData data, {
|
||||
OnLikeCallback? onLike,
|
||||
VoidCallback? onTap,
|
||||
}) =>
|
||||
CardData data, {
|
||||
OnLikeCallback? onLike,
|
||||
VoidCallback? onTap,
|
||||
}) =>
|
||||
_Card(
|
||||
data.text!,
|
||||
descriptionText: data.descriptionText!,
|
||||
@ -38,7 +36,6 @@ class _Card extends StatefulWidget {
|
||||
State<_Card> createState() => _CardState();
|
||||
}
|
||||
|
||||
|
||||
class _CardState extends State<_Card> {
|
||||
bool isLiked = false;
|
||||
@override
|
||||
@ -57,8 +54,7 @@ class _CardState extends State<_Card> {
|
||||
border: Border.all(
|
||||
color: Colors.grey,
|
||||
width: 2,
|
||||
)
|
||||
),
|
||||
)),
|
||||
child: IntrinsicHeight(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@ -71,7 +67,8 @@ class _CardState extends State<_Card> {
|
||||
child: SizedBox(
|
||||
height: 160,
|
||||
width: 160,
|
||||
child: Stack( // делаем поверх картинки надпись New
|
||||
child: Stack(
|
||||
// делаем поверх картинки надпись New
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Image.network(
|
||||
@ -111,11 +108,11 @@ class _CardState extends State<_Card> {
|
||||
children: [
|
||||
Text(
|
||||
widget.text,
|
||||
style:Theme.of(context).textTheme.headlineLarge,
|
||||
style: Theme.of(context).textTheme.headlineLarge,
|
||||
),
|
||||
Text(
|
||||
widget.descriptionText,
|
||||
style:Theme.of(context).textTheme.bodyLarge,
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -130,24 +127,25 @@ class _CardState extends State<_Card> {
|
||||
bottom: 16,
|
||||
),
|
||||
child: GestureDetector(
|
||||
onTap: (){
|
||||
setState((){
|
||||
onTap: () {
|
||||
setState(() {
|
||||
isLiked = !isLiked; // установка лайков
|
||||
});
|
||||
widget.onLike?.call(widget.text, isLiked);
|
||||
},
|
||||
child: AnimatedSwitcher( // Анимация для наших лайков
|
||||
child: AnimatedSwitcher(
|
||||
// Анимация для наших лайков
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: isLiked
|
||||
? const Icon (
|
||||
Icons.favorite,
|
||||
color: Colors.redAccent,
|
||||
key: ValueKey<int>(0), // ключи для перерисовки
|
||||
)
|
||||
? const Icon(
|
||||
Icons.favorite,
|
||||
color: Colors.redAccent,
|
||||
key: ValueKey<int>(0), // ключи для перерисовки
|
||||
)
|
||||
: const Icon(
|
||||
Icons.favorite_border,
|
||||
key: ValueKey<int>(1), // ключи для перерисовки
|
||||
),
|
||||
Icons.favorite_border,
|
||||
key: ValueKey<int>(1), // ключи для перерисовки
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -49,7 +49,8 @@ class _BodyState extends State<Body> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) { // привязываем логику к кадру
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
// привязываем логику к кадру
|
||||
context.read<HomeBloc>().add(const HomeLoadDataEvent()); // добавляем данные после считывания
|
||||
});
|
||||
super.initState();
|
||||
@ -61,10 +62,9 @@ class _BodyState extends State<Body> {
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _onRefresh() {
|
||||
context
|
||||
.read<HomeBloc>()
|
||||
.add(HomeLoadDataEvent(search: searchController.text));
|
||||
context.read<HomeBloc>().add(HomeLoadDataEvent(search: searchController.text));
|
||||
return Future.value(null); //прекращение отображения загрузки
|
||||
}
|
||||
|
||||
@ -72,8 +72,8 @@ class _BodyState extends State<Body> {
|
||||
void _scrollToCard(String searchQuery, List<CardData>? dataList) {
|
||||
if (dataList == null || dataList.isEmpty) return;
|
||||
|
||||
final index = dataList.indexWhere((data) =>
|
||||
data.text?.toLowerCase() == searchQuery.toLowerCase());
|
||||
final index =
|
||||
dataList.indexWhere((data) => data.text?.toLowerCase() == searchQuery.toLowerCase());
|
||||
|
||||
if (index != -1) {
|
||||
_scrollController.animateTo(
|
||||
@ -92,44 +92,41 @@ class _BodyState extends State<Body> {
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: CupertinoSearchTextField(
|
||||
controller: searchController,
|
||||
onChanged: (search ) { //вызов задержки пока пользователь не перестанет печатать в поисковой строке
|
||||
Debounce.run(() => context
|
||||
.read<HomeBloc>()
|
||||
.add(HomeLoadDataEvent(search: search)));
|
||||
onChanged: (search) {
|
||||
//вызов задержки пока пользователь не перестанет печатать в поисковой строке
|
||||
Debounce.run(() => context.read<HomeBloc>().add(HomeLoadDataEvent(search: search)));
|
||||
},
|
||||
),
|
||||
),
|
||||
BlocBuilder<HomeBloc, HomeState>( //ждёт изменения состояния
|
||||
BlocBuilder<HomeBloc, HomeState>(
|
||||
//ждёт изменения состояния
|
||||
builder: (context, state) => state.error != null
|
||||
? Text(
|
||||
state.error ?? '',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headlineSmall
|
||||
?.copyWith(color: Colors.red),
|
||||
)
|
||||
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?.length ?? 0,
|
||||
itemBuilder: (context, index) {
|
||||
final data = state.data?[index];
|
||||
return data != null
|
||||
? _Card.fromData(
|
||||
data,
|
||||
onLike: (String title, bool isLiked) =>
|
||||
_showSnackBar(context, title, isLiked),
|
||||
onTap: () => _navToDetails(context, data),
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
? const CircularProgressIndicator()
|
||||
: Expanded(
|
||||
child: RefreshIndicator(
|
||||
onRefresh: _onRefresh,
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
itemCount: state.data?.length ?? 0,
|
||||
itemBuilder: (context, index) {
|
||||
final data = state.data?[index];
|
||||
return data != null
|
||||
? _Card.fromData(
|
||||
data,
|
||||
onLike: (String title, bool isLiked) =>
|
||||
_showSnackBar(context, title, isLiked),
|
||||
onTap: () => _navToDetails(context, data),
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
@ -155,4 +152,3 @@ class _BodyState extends State<Body> {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
11
makefile
Normal file
@ -0,0 +1,11 @@
|
||||
gen:
|
||||
flutter pub run build_runner build --delete-conflicting-outputs
|
||||
icon:
|
||||
flutter pub run flutter_launcher_icons:main
|
||||
init_res:
|
||||
dart pub global activate flutter_assets_generator
|
||||
format:
|
||||
dart format . --line-length 100
|
||||
res:
|
||||
fgen --output lib/components/resources.g.dart --no-watch --no-preview; \
|
||||
mingw32-make format
|
250
pubspec.lock
@ -22,6 +22,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.7.0"
|
||||
archive:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: archive
|
||||
sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.6.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:
|
||||
@ -167,7 +183,7 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
copy_with_extension:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: copy_with_extension
|
||||
sha256: fbcf890b0c34aedf0894f91a11a579994b61b4e04080204656b582708b5b1125
|
||||
@ -246,6 +262,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:
|
||||
@ -275,6 +299,14 @@ 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:
|
||||
@ -283,11 +315,29 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
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:
|
||||
@ -320,6 +370,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.15.5"
|
||||
http:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http
|
||||
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
http_multi_server:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -336,14 +394,30 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
image:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image
|
||||
sha256: f31d52537dc417fdcde36088fdf11d191026fd5e4fae742491ebd40e5a8bea7d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.3.0"
|
||||
intl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: intl
|
||||
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.19.0"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: io
|
||||
sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e"
|
||||
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
version: "1.0.5"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -364,10 +438,10 @@ packages:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: json_serializable
|
||||
sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b
|
||||
sha256: c2fcb3920cf2b6ae6845954186420fca40bc0a8abcc84903b7801f17d7050d7c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.8.0"
|
||||
version: "6.9.0"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -460,10 +534,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_config
|
||||
sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd"
|
||||
sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
version: "2.1.1"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -472,6 +546,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:
|
||||
@ -500,10 +630,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pub_semver
|
||||
sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
|
||||
sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
version: "2.1.5"
|
||||
pubspec_parse:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -512,6 +642,62 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: shared_preferences
|
||||
sha256: "95f9997ca1fb9799d494d0cb2a780fd7be075818d59f00c43832ed112b158a82"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.3"
|
||||
shared_preferences_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: "7f172d1b06de5da47b6264c2692ee2ead20bbbc246690427cdb4fc301cd0c549"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.4"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: "07e050c7cd39bad516f8d64c455f04508d09df104be326d8c02551590a0d513d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.3"
|
||||
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:
|
||||
@ -524,10 +710,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_web_socket
|
||||
sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611"
|
||||
sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
version: "2.0.1"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
@ -621,6 +807,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:
|
||||
@ -669,6 +879,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:
|
||||
@ -679,4 +905,4 @@ packages:
|
||||
version: "3.1.2"
|
||||
sdks:
|
||||
dart: ">=3.5.2 <4.0.0"
|
||||
flutter: ">=3.18.0-18.0.pre.54"
|
||||
flutter: ">=3.24.0"
|
||||
|
19
pubspec.yaml
@ -38,11 +38,17 @@ dependencies:
|
||||
cupertino_icons: ^1.0.8
|
||||
dio: ^5.4.2+1
|
||||
pretty_dio_logger: ^1.3.1
|
||||
json_annotation: ^4.8.1
|
||||
json_annotation: ^4.9.0
|
||||
html: ^0.15.0
|
||||
equatable: ^2.0.5
|
||||
flutter_bloc: ^8.1.5
|
||||
copy_with_extension_gen: ^5.0.4
|
||||
flutter_svg: 2.0.7
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
intl: ^0.19.0
|
||||
copy_with_extension: ^5.0.4
|
||||
shared_preferences: ^2.2.3
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
@ -53,21 +59,26 @@ dev_dependencies:
|
||||
# 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_launcher_icons: 0.13.1
|
||||
build_runner: ^2.4.9
|
||||
json_serializable: ^6.7.1
|
||||
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_launcher_icons:
|
||||
android: "ic_launcher"
|
||||
image_path: "assets/ic_launcher.png"
|
||||
min_sdk_android: 21
|
||||
# The following section is specific to Flutter packages.
|
||||
flutter:
|
||||
|
||||
generate: true
|
||||
# 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.
|
||||
uses-material-design: true
|
||||
|
||||
assets:
|
||||
- assets/svg/
|
||||
# To add assets to your application, add an assets section, like this:
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
||||
|