Надо починить лайки

This commit is contained in:
Никита Потапов 2024-12-19 18:44:53 +04:00 committed by Никита
parent 60554238e3
commit 569a2aff06
21 changed files with 216 additions and 94 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 544 B

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 B

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 B

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 67 KiB

BIN
assets/launcher.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

BIN
assets/launcher.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

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

@ -0,0 +1,10 @@
<svg width="210" height="210" viewBox="0 0 210 210" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_2_16" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="210" height="210">
<circle cx="105" cy="105" r="105" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_2_16)">
<rect width="210" height="70" fill="white"/>
<rect y="70" width="210" height="70" fill="#92BCEC"/>
<rect y="140" width="210" height="70" fill="#FF6A59"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 464 B

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

@ -0,0 +1,16 @@
<svg width="210" height="210" viewBox="0 0 210 210" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_2_33" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="210" height="210">
<circle cx="105" cy="105" r="105" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_2_33)">
<rect x="-45.178" y="-2.80414" width="291.632" height="210" fill="#92BCEC"/>
<path d="M-53.9021 235.549L302.849 -25.8606" stroke="white" stroke-width="31.1573"/>
<path d="M252.507 234.39L-93.4718 -29.2878" stroke="white" stroke-width="31.1573"/>
<line x1="103.131" y1="-35.8309" x2="103.131" y2="240.846" stroke="white" stroke-width="43.6202"/>
<path d="M-97.8338 118.086L246.454 118.086" stroke="white" stroke-width="43.6202"/>
<line x1="309.671" y1="-33.314" x2="-59.2315" y2="238.377" stroke="#FF6A59" stroke-width="9.34718"/>
<path d="M-65.6983 -7.13381L301.914 270.757" stroke="#FF6A59" stroke-width="9.34718"/>
<line x1="103.131" y1="-45.8012" x2="103.131" y2="260.786" stroke="#FF6A59" stroke-width="24.9258"/>
<path d="M-62.6261 118.086L308.145 118.086" stroke="#FF6A59" stroke-width="24.9258"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

3
devtools_options.yaml Normal file
View File

@ -0,0 +1,3 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:

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://D:\code\pmu_nikita\PMU-PIbd-31-Potapov-N-S\assets\svg\ru.svg)
static const String ASSETS_SVG_RU_SVG = 'assets/svg/ru.svg';
/// ![preview](file://D:\code\pmu_nikita\PMU-PIbd-31-Potapov-N-S\assets\svg\uk.svg)
static const String ASSETS_SVG_UK_SVG = 'assets/svg/uk.svg';
}

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://D:\code\pmu_labs\pmu_labs_3_course\assets\svg\ru.svg)
static const String ASSETS_SVG_RU_SVG = 'assets/svg/ru.svg';
/// ![preview](file://D:\code\pmu_labs\pmu_labs_3_course\assets\svg\uk.svg)
static const String ASSETS_SVG_UK_SVG = 'assets/svg/uk.svg';
}

View File

@ -21,7 +21,6 @@ extension UserDtoToModel on UserDto {
? "https://avatar.iran.liara.run/public/boy" ? "https://avatar.iran.liara.run/public/boy"
: "https://avatar.iran.liara.run/public/girl") : "https://avatar.iran.liara.run/public/girl")
.toString(), .toString(),
isLiked: false,
age: age, age: age,
distance: distance); distance: distance);
} }

View File

@ -13,7 +13,7 @@ class MockRepository extends ApiInterface {
imageUrl: "https://placehold.co/600x400/png", imageUrl: "https://placehold.co/600x400/png",
age: 21, age: 21,
distance: 24.5, distance: 24.5,
isLiked: false), ),
const CardData( const CardData(
name: "Константин", name: "Константин",
surname: "Злобин", surname: "Злобин",
@ -21,7 +21,7 @@ class MockRepository extends ApiInterface {
imageUrl: "https://placehold.co/600x400/png", imageUrl: "https://placehold.co/600x400/png",
age: 24, age: 24,
distance: 478.3, distance: 478.3,
isLiked: false) )
], pageNumber: 0); ], pageNumber: 0);
} }
} }

View File

@ -1,18 +1,21 @@
class CardData { class CardData {
final String? name; final String? name;
final String? surname; final String? surname;
final String? description; final String? description;
final String? imageUrl; final String? imageUrl;
final int? age; final int? age;
final double? distance; final double? distance;
final bool? isLiked;
final String? id;
const CardData( const CardData(
{this.name, {this.name,
this.surname, this.surname,
this.description, this.description,
this.imageUrl, this.imageUrl,
this.isLiked,
this.age, this.age,
this.distance}); this.distance,
this.id});
} }

View File

@ -1,8 +1,15 @@
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pmu/data/repositories/api_repository.dart'; import 'package:pmu/data/repositories/api_repository.dart';
import 'package:pmu/presentation/home_page/bloc/bloc.dart'; import 'package:pmu/presentation/home_page/bloc/bloc.dart';
import 'package:pmu/presentation/home_page/home_page.dart'; import 'package:pmu/presentation/home_page/home_page.dart';
import 'package:pmu/presentation/like/bloc.dart';
import 'package:pmu/presentation/locale/bloc.dart';
import 'package:pmu/presentation/locale/state.dart';
import 'components/locale/l10n/app_locale.dart';
void main() { void main() {
runApp(const MyApp()); runApp(const MyApp());
@ -13,15 +20,33 @@ class MyApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider<LocaleBloc>(
lazy: false,
create: (context) => LocaleBloc(Locale(Platform.localeName)),
child: BlocBuilder<LocaleBloc, LocaleState>(
builder: (context, state) {
return MaterialApp( return MaterialApp(
title: 'LoveSearch', title: 'LoveSearch',
locale: state.currentLocale,
localizationsDelegates: AppLocale.localizationsDelegates,
supportedLocales: AppLocale.supportedLocales,
debugShowCheckedModeBanner: false,
home: RepositoryProvider<ApiRepository>( home: RepositoryProvider<ApiRepository>(
lazy: true, lazy: true,
create: (_) => ApiRepository(), create: (_) => ApiRepository(),
child: BlocProvider<LikeBloc>(
lazy: true,
create: (context) => LikeBloc(),
child: BlocProvider<HomeBloc>( child: BlocProvider<HomeBloc>(
lazy: false, lazy: false,
create: (context) => HomeBloc(context.read<ApiRepository>()), create: (context) =>
child: const HomePage())), HomeBloc(context.read<ApiRepository>()),
child: const HomePage()),
),
),
);
},
),
); );
} }
} }

View File

@ -1,6 +1,8 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import '../../components/resoures.g.dart';
abstract class SvgObjects { abstract class SvgObjects {
static void init() { static void init() {
final pics = <String>[ final pics = <String>[

View File

@ -1,14 +1,15 @@
part of 'home_page.dart'; part of 'home_page.dart';
typedef OnLikeCallback = void Function(String title, bool isLiked)?; typedef OnLikeCallback = void Function(String? id, String title, bool isLiked)?;
const double NORMAL_ICON_SCALE = 2.0; const double NORMAL_ICON_SCALE = 2.0;
const double SCALED_ICON_SCALE = 2.5; const double SCALED_ICON_SCALE = 2.5;
class _Card extends StatefulWidget { class _Card extends StatelessWidget {
final String? description; final String? description;
final String? imageUrl; final String? imageUrl;
final bool? isLiked; final bool isLiked;
final String? id;
final String? name; final String? name;
final String? surname; final String? surname;
@ -20,34 +21,28 @@ class _Card extends StatefulWidget {
this.surname, this.surname,
this.description, this.description,
this.imageUrl, this.imageUrl,
this.isLiked, this.isLiked = false,
this.onLike, this.onLike,
this.onTap}); this.onTap,
this.id});
factory _Card.fromData(CardData data, factory _Card.fromData(CardData data,
{OnLikeCallback onLike, VoidCallback? onTap}) => {OnLikeCallback onLike, VoidCallback? onTap, bool isLiked = false}) =>
_Card( _Card(
name: data.name, name: data.name,
surname: data.surname, surname: data.surname,
description: data.description, description: data.description,
imageUrl: data.imageUrl, imageUrl: data.imageUrl,
isLiked: data.isLiked, isLiked: isLiked,
onLike: onLike, onLike: onLike,
onTap: onTap); onTap: onTap,
id: data.id,
@override );
State<_Card> createState() => _CardState();
}
class _CardState extends State<_Card> {
bool isLiked = false;
double iconScale = NORMAL_ICON_SCALE;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return GestureDetector(
onTap: widget.onTap, onTap: onTap,
child: Container( child: Container(
margin: const EdgeInsets.all(10), margin: const EdgeInsets.all(10),
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),
@ -77,7 +72,7 @@ class _CardState extends State<_Card> {
children: [ children: [
Flexible( Flexible(
child: Text( child: Text(
(widget.name ?? "") + (" ") + (widget.surname ?? ""), (name ?? "") + (" ") + (surname ?? ""),
style: const TextStyle( style: const TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 30, fontSize: 30,
@ -93,7 +88,7 @@ class _CardState extends State<_Card> {
children: [ children: [
Flexible( Flexible(
child: Text( child: Text(
widget.description ?? "", description ?? "",
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle( style: const TextStyle(
color: Colors.white, color: Colors.white,
@ -104,43 +99,40 @@ class _CardState extends State<_Card> {
], ],
), ),
), ),
if (widget.imageUrl != null) if (imageUrl != null)
Padding( Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Image.network(widget.imageUrl!, child: Image.network(imageUrl!,
height: 500, height: 500,
width: double.infinity, width: double.infinity,
fit: BoxFit.fitWidth), fit: BoxFit.fitWidth),
), ),
Padding( Padding(
padding: const EdgeInsets.all(4.0), padding: const EdgeInsets.all(4.0),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
GestureDetector( GestureDetector(
onTap: () => { onTap: () => onLike?.call(id, description!, isLiked),
setState(() {
isLiked = !isLiked;
widget.onLike?.call(widget.name ?? "", isLiked);
})
},
child: AnimatedScale(
scale: iconScale,
duration: const Duration(milliseconds: 250),
child: AnimatedSwitcher( child: AnimatedSwitcher(
duration: const Duration(milliseconds: 250), duration: const Duration(milliseconds: 250),
child: isLiked child: !isLiked
? const Icon( ? const Icon(
Icons.favorite_border,
color: Colors.black,
key: ValueKey<int>(0),
)
: const Icon(
Icons.favorite, Icons.favorite,
color: Colors.red, color: Colors.red,
key: ValueKey(1), key: ValueKey<int>(1),
) ),
: const Icon(Icons.favorite_border, ),
color: Colors.black, key: ValueKey(1)))),
), ),
], ],
), ),
) ),
], ],
), ),
)); ));

View File

@ -2,11 +2,20 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pmu/components/debounce.dart'; import 'package:pmu/components/debounce.dart';
import 'package:pmu/components/extensions/content_x.dart';
import 'package:pmu/domain/models/card.dart'; import 'package:pmu/domain/models/card.dart';
import 'package:pmu/presentation/common/svg_objects.dart';
import 'package:pmu/presentation/detail_pages/card_detail_page.dart'; import 'package:pmu/presentation/detail_pages/card_detail_page.dart';
import 'package:pmu/presentation/home_page/bloc/bloc.dart'; import 'package:pmu/presentation/home_page/bloc/bloc.dart';
import 'package:pmu/presentation/home_page/bloc/events.dart'; import 'package:pmu/presentation/home_page/bloc/events.dart';
import 'package:pmu/presentation/home_page/bloc/state.dart'; import 'package:pmu/presentation/home_page/bloc/state.dart';
import 'package:pmu/presentation/locale/bloc.dart';
import 'package:pmu/presentation/locale/events.dart';
import 'package:pmu/presentation/locale/state.dart';
import '../like/bloc.dart';
import '../like/events.dart';
import '../like/state.dart';
part 'card.dart'; part 'card.dart';
@ -39,6 +48,7 @@ class _BodyState extends State<_Body> {
void initState() { void initState() {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
context.read<HomeBloc>().add(const HomeLoadDataEvent()); context.read<HomeBloc>().add(const HomeLoadDataEvent());
context.read<LikeBloc>().add(const LoadLikesEvent());
}); });
scrollController.addListener(_onNextPageListener); scrollController.addListener(_onNextPageListener);
@ -72,10 +82,15 @@ class _BodyState extends State<_Body> {
padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top), padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
child: Column( child: Column(
children: [ children: [
Padding( Row(
padding: const EdgeInsets.all(12), children: [
Expanded(
flex: 4,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: CupertinoSearchTextField( child: CupertinoSearchTextField(
controller: searchController, controller: searchController,
placeholder: context.locale.search,
onChanged: (search) { onChanged: (search) {
Debounce.run(() => context Debounce.run(() => context
.read<HomeBloc>() .read<HomeBloc>()
@ -83,6 +98,26 @@ class _BodyState extends State<_Body> {
}, },
), ),
), ),
),
GestureDetector(
onTap: () =>
context.read<LocaleBloc>().add(const ChangeLocaleEvent()),
child: SizedBox.square(
dimension: 50,
child: Padding(
padding: const EdgeInsets.all(12),
child: BlocBuilder<LocaleBloc, LocaleState>(
builder: (context, state) {
return state.currentLocale.languageCode == 'ru'
? const SvgRu()
: const SvgUk();
},
),
),
),
)
],
),
BlocBuilder<HomeBloc, HomeState>( BlocBuilder<HomeBloc, HomeState>(
builder: (context, state) => state.error != null builder: (context, state) => state.error != null
? Text( ? Text(
@ -94,7 +129,9 @@ class _BodyState extends State<_Body> {
) )
: state.isLoading : state.isLoading
? const CircularProgressIndicator() ? const CircularProgressIndicator()
: Expanded( : BlocBuilder<LikeBloc, LikeState>(
builder: (context, likeState) {
return Expanded(
child: RefreshIndicator( child: RefreshIndicator(
onRefresh: _onRefresh, onRefresh: _onRefresh,
child: ListView.builder( child: ListView.builder(
@ -106,14 +143,19 @@ class _BodyState extends State<_Body> {
return data != null return data != null
? _Card.fromData( ? _Card.fromData(
data, data,
onLike: (title, isLiked) => _showSnackBar( onLike: _onLike,
context, title, isLiked), isLiked: likeState.likedIds
onTap: () => _navToDetails(context, data), ?.contains(data.id) ==
true,
onTap: () =>
_navToDetails(context, data),
) )
: const SizedBox.shrink(); : const SizedBox.shrink();
}, },
), ),
), ),
);
},
), ),
), ),
BlocBuilder<HomeBloc, HomeState>( BlocBuilder<HomeBloc, HomeState>(
@ -126,6 +168,13 @@ class _BodyState extends State<_Body> {
); );
} }
void _onLike(String? id, String title, bool isLiked) {
if (id != null) {
context.read<LikeBloc>().add(ChangeLikeEvent(id));
_showSnackBar(context, title, !isLiked);
}
}
Future<void> _onRefresh() { Future<void> _onRefresh() {
context context
.read<HomeBloc>() .read<HomeBloc>()

View File

@ -1,8 +1,8 @@
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pmu/presentation/like/events.dart';
import 'package:pmu/presentation/like/state.dart'; import 'package:pmu/presentation/like/state.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'events.dart';
const String _likedPrefsKey = 'liked'; const String _likedPrefsKey = 'liked';

View File

@ -57,7 +57,6 @@ dependencies:
flutter_icons: flutter_icons:
android: "ic_launcher" android: "ic_launcher"
ios: true
image_path: "assets/launcher.jpeg" image_path: "assets/launcher.jpeg"
min_sdk_android: 21 min_sdk_android: 21