Design fix

This commit is contained in:
Serxiolog 2024-11-24 21:19:36 +04:00
parent ff4530dae3
commit cc63344d86
24 changed files with 94 additions and 130 deletions

View File

@ -1,7 +1,6 @@
import 'package:first_project/Components/locale/l10n/app_locale.dart'; import 'package:first_project/Components/locale/l10n/app_locale.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
extension LocalContextX on BuildContext { extension LocalContextX on BuildContext {
AppLocale get locale => AppLocale.of(this)!; AppLocale get locale => AppLocale.of(this)!;
} }

View File

@ -82,7 +82,8 @@ abstract class AppLocale {
/// Additional delegates can be added by appending to this list in /// Additional delegates can be added by appending to this list in
/// MaterialApp. This list does not have to be used at all if a custom list /// MaterialApp. This list does not have to be used at all if a custom list
/// of delegates is preferred or required. /// of delegates is preferred or required.
static const List<LocalizationsDelegate<dynamic>> localizationsDelegates = <LocalizationsDelegate<dynamic>>[ static const List<LocalizationsDelegate<dynamic>> localizationsDelegates =
<LocalizationsDelegate<dynamic>>[
delegate, delegate,
GlobalMaterialLocalizations.delegate, GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate, GlobalCupertinoLocalizations.delegate,
@ -90,10 +91,7 @@ abstract class AppLocale {
]; ];
/// A list of this localizations delegate's supported locales. /// A list of this localizations delegate's supported locales.
static const List<Locale> supportedLocales = <Locale>[ static const List<Locale> supportedLocales = <Locale>[Locale('en'), Locale('ru')];
Locale('en'),
Locale('ru')
];
/// No description provided for @search. /// No description provided for @search.
/// ///
@ -136,18 +134,17 @@ class _AppLocaleDelegate extends LocalizationsDelegate<AppLocale> {
} }
AppLocale lookupAppLocale(Locale locale) { AppLocale lookupAppLocale(Locale locale) {
// Lookup logic when only language code is specified. // Lookup logic when only language code is specified.
switch (locale.languageCode) { switch (locale.languageCode) {
case 'en': return AppLocaleEn(); case 'en':
case 'ru': return AppLocaleRu(); return AppLocaleEn();
case 'ru':
return AppLocaleRu();
} }
throw FlutterError( throw FlutterError(
'AppLocale.delegate failed to load unsupported locale "$locale". This is likely ' 'AppLocale.delegate failed to load unsupported locale "$locale". This is likely '
'an issue with the localizations generation tool. Please file an issue ' 'an issue with the localizations generation tool. Please file an issue '
'on GitHub with a reproducible sample app and the gen-l10n configuration ' 'on GitHub with a reproducible sample app and the gen-l10n configuration '
'that was used.' 'that was used.');
);
} }

View File

@ -10,8 +10,11 @@ class Debouce {
static Timer? _timer; 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?.cancel();
_timer = Timer(delay, action); _timer = Timer(delay, action);
} }
} }

View File

@ -44,10 +44,10 @@ class AnimeImagesJpgDto {
final String? largeImage; final String? largeImage;
const AnimeImagesJpgDto(this.image, this.smallImage, this.largeImage); const AnimeImagesJpgDto(this.image, this.smallImage, this.largeImage);
factory AnimeImagesJpgDto.fromJson(Map<String, dynamic> json) => _$AnimeImagesJpgDtoFromJson(json); factory AnimeImagesJpgDto.fromJson(Map<String, dynamic> json) =>
_$AnimeImagesJpgDtoFromJson(json);
} }
@JsonSerializable(createToJson: false) @JsonSerializable(createToJson: false)
class PaginationDto { class PaginationDto {
@JsonKey(name: "last_visible_page") @JsonKey(name: "last_visible_page")
@ -59,4 +59,4 @@ class PaginationDto {
const PaginationDto({this.current, this.last, this.next}); const PaginationDto({this.current, this.last, this.next});
factory PaginationDto.fromJson(Map<String, dynamic> json) => _$PaginationDtoFromJson(json); factory PaginationDto.fromJson(Map<String, dynamic> json) => _$PaginationDtoFromJson(json);
} }

View File

@ -25,22 +25,19 @@ AnimeDataDto _$AnimeDataDtoFromJson(Map<String, dynamic> json) => AnimeDataDto(
(json['score'] as num?)?.toDouble(), (json['score'] as num?)?.toDouble(),
); );
AnimeImagesDto _$AnimeImagesDtoFromJson(Map<String, dynamic> json) => AnimeImagesDto _$AnimeImagesDtoFromJson(Map<String, dynamic> json) => AnimeImagesDto(
AnimeImagesDto(
jpg: json['jpg'] == null jpg: json['jpg'] == null
? null ? null
: AnimeImagesJpgDto.fromJson(json['jpg'] as Map<String, dynamic>), : AnimeImagesJpgDto.fromJson(json['jpg'] as Map<String, dynamic>),
); );
AnimeImagesJpgDto _$AnimeImagesJpgDtoFromJson(Map<String, dynamic> json) => AnimeImagesJpgDto _$AnimeImagesJpgDtoFromJson(Map<String, dynamic> json) => AnimeImagesJpgDto(
AnimeImagesJpgDto(
json['image_url'] as String?, json['image_url'] as String?,
json['small_image_url'] as String?, json['small_image_url'] as String?,
json['large_image_url'] as String?, json['large_image_url'] as String?,
); );
PaginationDto _$PaginationDtoFromJson(Map<String, dynamic> json) => PaginationDto _$PaginationDtoFromJson(Map<String, dynamic> json) => PaginationDto(
PaginationDto(
current: (json['current_page'] as num?)?.toInt(), current: (json['current_page'] as num?)?.toInt(),
last: (json['last_visible_page'] as num?)?.toInt(), last: (json['last_visible_page'] as num?)?.toInt(),
next: json['has_next_page'] as bool?, next: json['has_next_page'] as bool?,

View File

@ -4,17 +4,19 @@ import 'package:first_project/presentation/home_page/home_page.dart';
extension AnimeDataDtoToModel on AnimeDataDto { extension AnimeDataDtoToModel on AnimeDataDto {
CardData toDomain() => CardData( CardData toDomain() => CardData(
title ?? 'NOT', title ?? 'NOT',
imageUrl: images?.jpg?.image ?? "NONE", imageUrl: images?.jpg?.image ?? "NONE",
score: score ?? 0, score: score ?? 0,
description: synopsis == null ? "NONE" : synopsis!.split('\n').sublist(0, synopsis!.split('\n').length - 1).join('\n'), description: synopsis == null
id: id.toString(), ? "NONE"
); : synopsis!.split('\n').sublist(0, synopsis!.split('\n').length - 1).join('\n'),
id: id.toString(),
);
} }
extension AnimesDataDtoToModel on AnimesDto { extension AnimesDataDtoToModel on AnimesDto {
HomeData toDomain() => HomeData( HomeData toDomain() => HomeData(
data: data?.map((e) => e.toDomain()).toList(), data: data?.map((e) => e.toDomain()).toList(),
nextPage: pagination!.next! ? pagination!.current! + 1 : 0, nextPage: pagination!.next! ? pagination!.current! + 1 : 0,
); );
} }

View File

@ -8,31 +8,29 @@ import 'package:pretty_dio_logger/pretty_dio_logger.dart';
class AnimeRepository extends ApiInterface { class AnimeRepository extends ApiInterface {
static final Dio _dio = Dio() static final Dio _dio = Dio()
..interceptors.add(PrettyDioLogger( ..interceptors.add(PrettyDioLogger(
requestHeader: true, requestHeader: true,
requestBody: true, requestBody: true,
)); ));
static const String _baseUrl = 'https://api.jikan.moe'; static const String _baseUrl = 'https://api.jikan.moe';
@override @override
Future<HomeData?> loadData({OnErrorCallback? onError,String? q, int page = 1, int pageSize = 25}) async{ Future<HomeData?> loadData(
try {OnErrorCallback? onError, String? q, int page = 1, int pageSize = 25}) async {
{ try {
const String url = '$_baseUrl/v4/anime'; const String url = '$_baseUrl/v4/anime';
Map<String, dynamic> query = {'q' : q, 'page' : page, 'limit' : pageSize}; Map<String, dynamic> query = {'q': q, 'page': page, 'limit': pageSize};
final Response<dynamic> response = final Response<dynamic> response = await _dio.get<Map<dynamic, dynamic>>(
await _dio.get<Map<dynamic, dynamic>>( url,
url, queryParameters: query,
queryParameters: query, );
); final AnimesDto dto = AnimesDto.fromJson(response.data as Map<String, dynamic>);
final AnimesDto dto = AnimesDto.fromJson(response.data as Map<String, dynamic>); final HomeData data = dto.toDomain();
final HomeData data = dto.toDomain(); return data;
return data; } on DioException catch (e) {
} on DioException catch (e)
{
onError?.call(e.error?.toString()); onError?.call(e.error?.toString());
return null; return null;
} }
} }
} }

View File

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

View File

@ -6,7 +6,7 @@ import 'package:flutter/material.dart';
class MockRepository extends ApiInterface { class MockRepository extends ApiInterface {
@override @override
Future<HomeData?> loadData({OnErrorCallback? onError}) async { Future<HomeData?> loadData({OnErrorCallback? onError}) async {
return HomeData(data:[ return HomeData(data: [
const CardData( const CardData(
"First", "First",
score: 5, score: 5,
@ -17,8 +17,7 @@ class MockRepository extends ApiInterface {
score: 8, score: 8,
icon: Icons.gamepad, icon: Icons.gamepad,
description: "ManyText", description: "ManyText",
imageUrl: imageUrl: "https://i.pinimg.com/originals/21/73/24/217324138d1bbc91663d4943ebe5de60.jpg",
"https://i.pinimg.com/originals/21/73/24/217324138d1bbc91663d4943ebe5de60.jpg",
), ),
const CardData( const CardData(
"Third", "Third",
@ -28,4 +27,4 @@ class MockRepository extends ApiInterface {
), ),
]); ]);
} }
} }

View File

@ -23,8 +23,8 @@ class MyApp extends StatelessWidget {
return BlocProvider<LocaleBloc>( return BlocProvider<LocaleBloc>(
lazy: false, lazy: false,
create: (context) => LocaleBloc(Locale(Platform.localeName)), create: (context) => LocaleBloc(Locale(Platform.localeName)),
child: BlocBuilder<LocaleBloc, LocaleState>( child: BlocBuilder<LocaleBloc, LocaleState>(builder: (context, state) {
builder: (context, state) { return MaterialApp( return MaterialApp(
title: 'Flutter Demo', title: 'Flutter Demo',
locale: state.currentLocale, locale: state.currentLocale,
localizationsDelegates: AppLocale.localizationsDelegates, localizationsDelegates: AppLocale.localizationsDelegates,
@ -48,8 +48,7 @@ class MyApp extends StatelessWidget {
), ),
), ),
); );
} }),
),
); );
} }
} }

View File

@ -32,4 +32,3 @@ class SvgGb extends StatelessWidget {
return SvgPicture.asset(R.ASSETS_SVG_GB_SVG); return SvgPicture.asset(R.ASSETS_SVG_GB_SVG);
} }
} }

View File

@ -10,8 +10,7 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
on<HomeLoadDataEvent>(_onLoadData); on<HomeLoadDataEvent>(_onLoadData);
} }
Future<void> _onLoadData( Future<void> _onLoadData(HomeLoadDataEvent event, Emitter<HomeState> emit) async {
HomeLoadDataEvent event, Emitter<HomeState> emit) async {
if (event.nextPage == null) { if (event.nextPage == null) {
emit(state.copyWith(isLoading: true)); emit(state.copyWith(isLoading: true));
} else { } else {

View File

@ -1,4 +1,3 @@
abstract class HomeEvent { abstract class HomeEvent {
const HomeEvent(); const HomeEvent();
} }
@ -8,4 +7,4 @@ class HomeLoadDataEvent extends HomeEvent {
final int? nextPage; final int? nextPage;
const HomeLoadDataEvent({this.search, this.nextPage}); const HomeLoadDataEvent({this.search, this.nextPage});
} }

View File

@ -20,11 +20,7 @@ class HomeState extends Equatable {
this.error, this.error,
}); });
HomeState copyWith( HomeState copyWith({HomeData? data, bool? isLoading, bool? isPaginationLoading, String? error}) =>
{HomeData? data,
bool? isLoading,
bool? isPaginationLoading,
String? error}) =>
HomeState( HomeState(
data: data ?? this.data, data: data ?? this.data,
isLoading: isLoading ?? this.isLoading, isLoading: isLoading ?? this.isLoading,

View File

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

View File

@ -15,8 +15,7 @@ class CardData {
required this.description, required this.description,
this.icon = Icons.add_call, this.icon = Icons.add_call,
this.score = 0, this.score = 0,
this.imageUrl = this.imageUrl = "https://i.pinimg.com/736x/5f/14/b3/5f14b3f14fcd157bc4dffa39085396cc.jpg",
"https://i.pinimg.com/736x/5f/14/b3/5f14b3f14fcd157bc4dffa39085396cc.jpg",
this.id, this.id,
}); });
} }
@ -106,14 +105,11 @@ class _Card extends StatelessWidget {
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: Colors.purple, color: Colors.purple,
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
topRight: Radius.circular(20), topRight: Radius.circular(20), bottomLeft: Radius.circular(18))),
bottomLeft: Radius.circular(18))),
padding: const EdgeInsets.fromLTRB(8, 2, 8, 2), padding: const EdgeInsets.fromLTRB(8, 2, 8, 2),
child: Text(generateStars(score), child: Text(generateStars(score),
style: Theme.of(context) style:
.textTheme Theme.of(context).textTheme.bodyMedium?.copyWith(color: Colors.white)),
.bodyMedium
?.copyWith(color: Colors.white)),
), ),
), ),
]), ]),

View File

@ -84,8 +84,7 @@ class _BodyState extends State<_Body> {
} }
void _onNextPageListener() { void _onNextPageListener() {
if (scrollController.offset > if (scrollController.offset > scrollController.position.maxScrollExtent - 50) {
scrollController.position.maxScrollExtent - 50) {
final bloc = context.read<HomeBloc>(); final bloc = context.read<HomeBloc>();
if (!bloc.state.isPaginationLoading) { if (!bloc.state.isPaginationLoading) {
bloc.add(HomeLoadDataEvent( bloc.add(HomeLoadDataEvent(
@ -109,10 +108,7 @@ class _BodyState extends State<_Body> {
builder: (context, state) => state.error != null builder: (context, state) => state.error != null
? Text( ? Text(
state.error ?? '', state.error ?? '',
style: Theme.of(context) style: Theme.of(context).textTheme.headlineSmall?.copyWith(color: Colors.red),
.textTheme
.headlineSmall
?.copyWith(color: Colors.red),
) )
: state.isLoading : state.isLoading
? const CircularProgressIndicator() ? const CircularProgressIndicator()
@ -126,25 +122,21 @@ class _BodyState extends State<_Body> {
controller: searchController, controller: searchController,
placeholder: context.locale.search, placeholder: context.locale.search,
onChanged: (search) { onChanged: (search) {
Debouce.run(() => context Debouce.run(() =>
.read<HomeBloc>() context.read<HomeBloc>().add(HomeLoadDataEvent(search: search)));
.add(HomeLoadDataEvent(search: search)));
}, },
), ),
), ),
), ),
GestureDetector( GestureDetector(
onTap: () => context onTap: () => context.read<LocaleBloc>().add(const ChangeLocaleEvent()),
.read<LocaleBloc>()
.add(const ChangeLocaleEvent()),
child: SizedBox.square( child: SizedBox.square(
dimension: 50, dimension: 50,
child: Padding( child: Padding(
padding: const EdgeInsets.only(right: 15), padding: const EdgeInsets.only(right: 15),
child: BlocBuilder<LocaleBloc, LocaleState>( child: BlocBuilder<LocaleBloc, LocaleState>(
builder: (context, state) { builder: (context, state) {
return state.currentLocale.languageCode == return state.currentLocale.languageCode == 'ru'
'ru'
? const SvgRu() ? const SvgRu()
: const SvgGb(); : const SvgGb();
}, },
@ -152,8 +144,7 @@ class _BodyState extends State<_Body> {
), ),
)), )),
]), ]),
BlocBuilder<LikeBloc, LikeState>( BlocBuilder<LikeBloc, LikeState>(builder: (context, likeState) {
builder: (context, likeState) {
return Expanded( return Expanded(
child: RefreshIndicator( child: RefreshIndicator(
onRefresh: _onRefresh, onRefresh: _onRefresh,
@ -168,8 +159,7 @@ class _BodyState extends State<_Body> {
data, data,
isLiked: likeState.likedIds?.contains(data.id) == true, isLiked: likeState.likedIds?.contains(data.id) == true,
onLike: _onLike, onLike: _onLike,
onTap: () => onTap: () => _navToDetails(context, data),
_navToDetails(context, data),
) )
: const SizedBox.shrink(); : const SizedBox.shrink();
}), }),
@ -192,11 +182,10 @@ class _BodyState extends State<_Body> {
} }
void _onLike(String? id, String title, bool isLiked) { void _onLike(String? id, String title, bool isLiked) {
if (id != null) if (id != null) {
{ context.read<LikeBloc>().add(ChangeLikeEvent(id));
context.read<LikeBloc>().add(ChangeLikeEvent(id)); _showLiked(context, title, !isLiked);
_showLiked(context, title, !isLiked); }
}
} }
void _showLiked(BuildContext context, String title, bool isLiked) { void _showLiked(BuildContext context, String title, bool isLiked) {
@ -213,9 +202,7 @@ class _BodyState extends State<_Body> {
} }
Future<void> _onRefresh() { Future<void> _onRefresh() {
context context.read<HomeBloc>().add(HomeLoadDataEvent(search: searchController.text));
.read<HomeBloc>()
.add(HomeLoadDataEvent(search: searchController.text));
return Future.value(null); return Future.value(null);
} }
} }

View File

@ -1,4 +1,3 @@
import 'package:first_project/presentation/like_bloc/like_event.dart'; import 'package:first_project/presentation/like_bloc/like_event.dart';
import 'package:first_project/presentation/like_bloc/like_state.dart'; import 'package:first_project/presentation/like_bloc/like_state.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@ -24,7 +23,7 @@ class LikeBloc extends Bloc<LikeEvent, LikeState> {
if (updatedList.contains(event.id)) { if (updatedList.contains(event.id)) {
updatedList.remove(event.id); updatedList.remove(event.id);
}else { } else {
updatedList.add(event.id); updatedList.add(event.id);
} }
@ -33,5 +32,4 @@ class LikeBloc extends Bloc<LikeEvent, LikeState> {
emit(state.copyWith(likedIds: updatedList)); emit(state.copyWith(likedIds: updatedList));
} }
} }

View File

@ -10,4 +10,4 @@ class ChangeLikeEvent extends LikeEvent {
final String id; final String id;
const ChangeLikeEvent(this.id); const ChangeLikeEvent(this.id);
} }

View File

@ -11,4 +11,4 @@ class LikeState extends Equatable {
@override @override
List<Object?> get props => [likedIds]; List<Object?> get props => [likedIds];
} }

View File

@ -11,7 +11,8 @@ class LocaleBloc extends Bloc<LocaleEvent, LocaleState> {
} }
Future<void> _onChangeLocale(ChangeLocaleEvent event, Emitter<LocaleState> emit) async { Future<void> _onChangeLocale(ChangeLocaleEvent event, Emitter<LocaleState> emit) async {
final toChange = AppLocale.supportedLocales.firstWhere((e) => e.languageCode != state.currentLocale.languageCode); final toChange = AppLocale.supportedLocales
.firstWhere((e) => e.languageCode != state.currentLocale.languageCode);
emit(state.copyWith(currentLocale: toChange)); emit(state.copyWith(currentLocale: toChange));
} }
} }

View File

@ -4,4 +4,4 @@ abstract class LocaleEvent {
class ChangeLocaleEvent extends LocaleEvent { class ChangeLocaleEvent extends LocaleEvent {
const ChangeLocaleEvent(); const ChangeLocaleEvent();
} }

View File

@ -1,4 +1,3 @@
import 'dart:ui'; import 'dart:ui';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:copy_with_extension/copy_with_extension.dart'; import 'package:copy_with_extension/copy_with_extension.dart';
@ -13,4 +12,4 @@ class LocaleState extends Equatable {
@override @override
List<Object?> get props => [currentLocale]; List<Object?> get props => [currentLocale];
} }

View File

@ -27,8 +27,7 @@ class _$LocaleStateCWProxyImpl implements _$LocaleStateCWProxy {
final LocaleState _value; final LocaleState _value;
@override @override
LocaleState currentLocale(Locale currentLocale) => LocaleState currentLocale(Locale currentLocale) => this(currentLocale: currentLocale);
this(currentLocale: currentLocale);
@override @override
@ -42,11 +41,10 @@ class _$LocaleStateCWProxyImpl implements _$LocaleStateCWProxy {
Object? currentLocale = const $CopyWithPlaceholder(), Object? currentLocale = const $CopyWithPlaceholder(),
}) { }) {
return LocaleState( return LocaleState(
currentLocale: currentLocale: currentLocale == const $CopyWithPlaceholder() || currentLocale == null
currentLocale == const $CopyWithPlaceholder() || currentLocale == null ? _value.currentLocale
? _value.currentLocale // ignore: cast_nullable_to_non_nullable
// ignore: cast_nullable_to_non_nullable : currentLocale as Locale,
: currentLocale as Locale,
); );
} }
} }