Причесал код

This commit is contained in:
Вячеслав Иванов 2024-09-25 12:55:28 +04:00
parent 16f0f53745
commit decf3d296d
29 changed files with 178 additions and 151 deletions

View File

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

View File

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

View File

@ -104,13 +104,13 @@ abstract class AppLocale {
/// No description provided for @liked. /// No description provided for @liked.
/// ///
/// In ru, this message translates to: /// In ru, this message translates to:
/// **'понравился!'** /// **'поставлен лайк'**
String get liked; String get liked;
/// No description provided for @disliked. /// No description provided for @disliked.
/// ///
/// In ru, this message translates to: /// In ru, this message translates to:
/// **'разонравился :('** /// **'убран лайк'**
String get disliked; String get disliked;
/// No description provided for @arbEnding. /// No description provided for @arbEnding.

View File

@ -10,10 +10,10 @@ class AppLocaleEn extends AppLocale {
String get search => 'Search'; String get search => 'Search';
@override @override
String get liked => 'liked!'; String get liked => 'liked';
@override @override
String get disliked => 'disliked :('; String get disliked => 'disliked';
@override @override
String get arbEnding => 'Чтобы не забыть про отсутствие запятой :)'; String get arbEnding => 'Чтобы не забыть про отсутствие запятой :)';

View File

@ -10,10 +10,10 @@ class AppLocaleRu extends AppLocale {
String get search => 'Поиск'; String get search => 'Поиск';
@override @override
String get liked => 'понравился!'; String get liked => 'поставлен лайк';
@override @override
String get disliked => 'разонравился :('; String get disliked => 'убран лайк';
@override @override
String get arbEnding => 'Чтобы не забыть про отсутствие запятой :)'; String get arbEnding => 'Чтобы не забыть про отсутствие запятой :)';

View File

@ -12,7 +12,8 @@ class CharactersDto {
this.meta, this.meta,
}); });
factory CharactersDto.fromJson(Map<String, dynamic> json) => _$CharactersDtoFromJson(json); factory CharactersDto.fromJson(Map<String, dynamic> json) =>
_$CharactersDtoFromJson(json);
} }
@JsonSerializable(createToJson: false) @JsonSerializable(createToJson: false)
@ -23,7 +24,8 @@ class CharacterDataDto {
const CharacterDataDto({this.id, this.type, this.attributes}); const CharacterDataDto({this.id, this.type, this.attributes});
factory CharacterDataDto.fromJson(Map<String, dynamic> json) => _$CharacterDataDtoFromJson(json); factory CharacterDataDto.fromJson(Map<String, dynamic> json) =>
_$CharacterDataDtoFromJson(json);
} }
@JsonSerializable(createToJson: false) @JsonSerializable(createToJson: false)
@ -33,7 +35,8 @@ class CharacterAttributesDataDto {
final String? species; final String? species;
final String? image; final String? image;
const CharacterAttributesDataDto({this.name, this.nationality, this.species, this.image}); const CharacterAttributesDataDto(
{this.name, this.nationality, this.species, this.image});
factory CharacterAttributesDataDto.fromJson(Map<String, dynamic> json) => factory CharacterAttributesDataDto.fromJson(Map<String, dynamic> json) =>
_$CharacterAttributesDataDtoFromJson(json); _$CharacterAttributesDataDtoFromJson(json);
@ -45,7 +48,8 @@ class MetaDto {
const MetaDto({this.pagination}); const MetaDto({this.pagination});
factory MetaDto.fromJson(Map<String, dynamic> json) => _$MetaDtoFromJson(json); factory MetaDto.fromJson(Map<String, dynamic> json) =>
_$MetaDtoFromJson(json);
} }
@JsonSerializable(createToJson: false) @JsonSerializable(createToJson: false)
@ -56,5 +60,6 @@ class PaginationDto {
const PaginationDto({this.current, this.next, this.last}); const PaginationDto({this.current, this.next, this.last});
factory PaginationDto.fromJson(Map<String, dynamic> json) => _$PaginationDtoFromJson(json); factory PaginationDto.fromJson(Map<String, dynamic> json) =>
_$PaginationDtoFromJson(json);
} }

View File

@ -14,7 +14,8 @@ extension CharacterDataDtoToModel on CharacterDataDto {
attributes?.name ?? 'UNKNOWN', attributes?.name ?? 'UNKNOWN',
image: attributes?.image ?? image: attributes?.image ??
'https://upload.wikimedia.org/wikipedia/en/archive/b/b1/20210811082420%21Portrait_placeholder.png', 'https://upload.wikimedia.org/wikipedia/en/archive/b/b1/20210811082420%21Portrait_placeholder.png',
descriptionText: _makeDescriptionText(attributes?.nationality, attributes?.species), descriptionText:
_makeDescriptionText(attributes?.nationality, attributes?.species),
id: id, id: id,
); );

View File

@ -25,7 +25,8 @@ class MockRepository extends ApiInterface {
'Orange', 'Orange',
descriptionText: 'I like autumn', descriptionText: 'I like autumn',
icon: Icons.warning_amber, icon: Icons.warning_amber,
image: 'https://furmanagers.com/wp-content/uploads/2019/11/dreamstime_l_22075357.jpg', image:
'https://furmanagers.com/wp-content/uploads/2019/11/dreamstime_l_22075357.jpg',
), ),
], ],
); );

View File

@ -33,7 +33,8 @@ class PotterRepository extends ApiInterface {
}, },
); );
final CharactersDto dto = CharactersDto.fromJson(response.data as Map<String, dynamic>); final CharactersDto dto =
CharactersDto.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) {

View File

@ -215,7 +215,8 @@ class MyApp extends StatelessWidget {
create: (context) => LikeBloc(), create: (context) => LikeBloc(),
child: BlocProvider<HomeBloc>( child: BlocProvider<HomeBloc>(
lazy: false, lazy: false,
create: (context) => HomeBloc(context.read<PotterRepository>()), create: (context) =>
HomeBloc(context.read<PotterRepository>()),
child: const HomePage(), child: const HomePage(),
), ),
), ),

View File

@ -10,7 +10,8 @@ abstract class SvgObjects {
]; ];
for (final String p in pics) { for (final String p in pics) {
final loader = SvgAssetLoader(p); final loader = SvgAssetLoader(p);
svg.cache.putIfAbsent(loader.cacheKey(null), () => loader.loadBytes(null)); svg.cache
.putIfAbsent(loader.cacheKey(null), () => loader.loadBytes(null));
} }
} }
} }

View File

@ -10,7 +10,8 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
on<HomeLoadDataEvent>(_onLoadData); on<HomeLoadDataEvent>(_onLoadData);
} }
Future<void> _onLoadData(HomeLoadDataEvent event, Emitter<HomeState> emit) async { Future<void> _onLoadData(
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

@ -48,7 +48,7 @@ class _Card extends StatelessWidget {
margin: const EdgeInsets.all(16), margin: const EdgeInsets.all(16),
constraints: const BoxConstraints(minHeight: 240), constraints: const BoxConstraints(minHeight: 240),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white30, color: Colors.grey,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
@ -61,6 +61,29 @@ class _Card extends StatelessWidget {
), ),
child: Column( child: Column(
children: [ children: [
Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: const EdgeInsets.only(
left: 8, right: 16, bottom: 16, top: 10),
child: GestureDetector(
onTap: () => onLike?.call(id, text, isLiked),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
child: isLiked
? const Icon(
Icons.favorite,
color: Colors.redAccent,
key: ValueKey<int>(0),
)
: const Icon(
Icons.favorite_border,
key: ValueKey<int>(1),
),
),
),
),
),
ClipRRect( ClipRRect(
borderRadius: const BorderRadius.only( borderRadius: const BorderRadius.only(
topLeft: Radius.circular(20), topLeft: Radius.circular(20),
@ -83,7 +106,8 @@ class _Card extends StatelessWidget {
), ),
), ),
Padding( Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), padding:
const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
@ -100,28 +124,6 @@ class _Card extends StatelessWidget {
], ],
), ),
), ),
Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: const EdgeInsets.only(left: 8, right: 16, bottom: 16),
child: GestureDetector(
onTap: () => onLike?.call(id, text, isLiked),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
child: isLiked
? const Icon(
Icons.favorite,
color: Colors.redAccent,
key: ValueKey<int>(0),
)
: const Icon(
Icons.favorite_border,
key: ValueKey<int>(1),
),
),
),
),
),
], ],
), ),
), ),

View File

@ -59,7 +59,7 @@ class _BodyState extends State<_Body> {
void _onNextPageListener() { void _onNextPageListener() {
if (scrollController.offset > scrollController.position.maxScrollExtent) { if (scrollController.offset > scrollController.position.maxScrollExtent) {
// preventing multiple pagination request on multiple swipes // preventing multiple pagination request on multiple swipes
final bloc = context.read<HomeBloc>(); final bloc = context.read<HomeBloc>();
if (!bloc.state.isPaginationLoading) { if (!bloc.state.isPaginationLoading) {
bloc.add(HomeLoadDataEvent( bloc.add(HomeLoadDataEvent(
@ -93,14 +93,16 @@ class _BodyState extends State<_Body> {
controller: searchController, controller: searchController,
placeholder: context.locale.search, placeholder: context.locale.search,
onChanged: (search) { onChanged: (search) {
Debounce.run( Debounce.run(() => context
() => context.read<HomeBloc>().add(HomeLoadDataEvent(search: search))); .read<HomeBloc>()
.add(HomeLoadDataEvent(search: search)));
}, },
), ),
), ),
), ),
GestureDetector( GestureDetector(
onTap: () => context.read<LocaleBloc>().add(const ChangeLocaleEvent()), onTap: () =>
context.read<LocaleBloc>().add(const ChangeLocaleEvent()),
child: SizedBox.square( child: SizedBox.square(
dimension: 50, dimension: 50,
child: Padding( child: Padding(
@ -121,7 +123,10 @@ 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).textTheme.headlineSmall?.copyWith(color: Colors.red), style: Theme.of(context)
.textTheme
.headlineSmall
?.copyWith(color: Colors.red),
) )
: state.isLoading : state.isLoading
? const CircularProgressIndicator() ? const CircularProgressIndicator()
@ -140,8 +145,11 @@ class _BodyState extends State<_Body> {
? _Card.fromData( ? _Card.fromData(
data, data,
onLike: _onLike, onLike: _onLike,
isLiked: likeState.likedIds?.contains(data.id) == true, isLiked: likeState.likedIds
onTap: () => _navToDetails(context, data), ?.contains(data.id) ==
true,
onTap: () =>
_navToDetails(context, data),
) )
: const SizedBox.shrink(); : const SizedBox.shrink();
}, },
@ -162,7 +170,9 @@ class _BodyState extends State<_Body> {
} }
Future<void> _onRefresh() { Future<void> _onRefresh() {
context.read<HomeBloc>().add(HomeLoadDataEvent(search: searchController.text)); context
.read<HomeBloc>()
.add(HomeLoadDataEvent(search: searchController.text));
return Future.value(null); return Future.value(null);
} }

View File

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

View File

@ -5,11 +5,13 @@ import 'package:flutter_app/presentation/locale_bloc/locale_state.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
class LocaleBloc extends Bloc<LocaleEvent, LocaleState> { class LocaleBloc extends Bloc<LocaleEvent, LocaleState> {
LocaleBloc(Locale defaultLocale) : super(LocaleState(currentLocale: defaultLocale)) { LocaleBloc(Locale defaultLocale)
: super(LocaleState(currentLocale: defaultLocale)) {
on<ChangeLocaleEvent>(_onChangeLocale); on<ChangeLocaleEvent>(_onChangeLocale);
} }
Future<void> _onChangeLocale(ChangeLocaleEvent event, Emitter<LocaleState> emit) async { Future<void> _onChangeLocale(
ChangeLocaleEvent event, Emitter<LocaleState> emit) async {
final toChange = AppLocale.supportedLocales final toChange = AppLocale.supportedLocales
.firstWhere((e) => e.languageCode != state.currentLocale.languageCode); .firstWhere((e) => e.languageCode != state.currentLocale.languageCode);
emit(state.copyWith(currentLocale: toChange)); emit(state.copyWith(currentLocale: toChange));