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

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/girl")
.toString(),
isLiked: false,
age: age,
distance: distance);
}

View File

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

View File

@ -1,18 +1,21 @@
class CardData {
final String? name;
final String? surname;
final String? description;
final String? imageUrl;
final int? age;
final double? distance;
final bool? isLiked;
final String? id;
const CardData(
{this.name,
this.surname,
this.description,
this.imageUrl,
this.isLiked,
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_bloc/flutter_bloc.dart';
import 'package:pmu/data/repositories/api_repository.dart';
import 'package:pmu/presentation/home_page/bloc/bloc.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() {
runApp(const MyApp());
@ -13,15 +20,33 @@ class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'LoveSearch',
home: RepositoryProvider<ApiRepository>(
lazy: true,
create: (_) => ApiRepository(),
child: BlocProvider<HomeBloc>(
lazy: false,
create: (context) => HomeBloc(context.read<ApiRepository>()),
child: const HomePage())),
return BlocProvider<LocaleBloc>(
lazy: false,
create: (context) => LocaleBloc(Locale(Platform.localeName)),
child: BlocBuilder<LocaleBloc, LocaleState>(
builder: (context, state) {
return MaterialApp(
title: 'LoveSearch',
locale: state.currentLocale,
localizationsDelegates: AppLocale.localizationsDelegates,
supportedLocales: AppLocale.supportedLocales,
debugShowCheckedModeBanner: false,
home: RepositoryProvider<ApiRepository>(
lazy: true,
create: (_) => ApiRepository(),
child: BlocProvider<LikeBloc>(
lazy: true,
create: (context) => LikeBloc(),
child: BlocProvider<HomeBloc>(
lazy: false,
create: (context) =>
HomeBloc(context.read<ApiRepository>()),
child: const HomePage()),
),
),
);
},
),
);
}
}

View File

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

View File

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

View File

@ -2,11 +2,20 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.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/presentation/common/svg_objects.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/events.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';
@ -39,6 +48,7 @@ class _BodyState extends State<_Body> {
void initState() {
WidgetsBinding.instance.addPostFrameCallback((_) {
context.read<HomeBloc>().add(const HomeLoadDataEvent());
context.read<LikeBloc>().add(const LoadLikesEvent());
});
scrollController.addListener(_onNextPageListener);
@ -72,16 +82,41 @@ class _BodyState extends State<_Body> {
padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(12),
child: CupertinoSearchTextField(
controller: searchController,
onChanged: (search) {
Debounce.run(() => context
.read<HomeBloc>()
.add(HomeLoadDataEvent(search: search)));
},
),
Row(
children: [
Expanded(
flex: 4,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: CupertinoSearchTextField(
controller: searchController,
placeholder: context.locale.search,
onChanged: (search) {
Debounce.run(() => context
.read<HomeBloc>()
.add(HomeLoadDataEvent(search: search)));
},
),
),
),
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>(
builder: (context, state) => state.error != null
@ -94,26 +129,33 @@ class _BodyState extends State<_Body> {
)
: state.isLoading
? const CircularProgressIndicator()
: Expanded(
child: RefreshIndicator(
onRefresh: _onRefresh,
child: ListView.builder(
controller: scrollController,
padding: EdgeInsets.zero,
itemCount: state.data?.data?.length ?? 0,
itemBuilder: (context, index) {
final data = state.data?.data?[index];
return data != null
? _Card.fromData(
data,
onLike: (title, isLiked) => _showSnackBar(
context, title, isLiked),
onTap: () => _navToDetails(context, data),
)
: const SizedBox.shrink();
},
),
),
: BlocBuilder<LikeBloc, LikeState>(
builder: (context, likeState) {
return Expanded(
child: RefreshIndicator(
onRefresh: _onRefresh,
child: ListView.builder(
controller: scrollController,
padding: EdgeInsets.zero,
itemCount: state.data?.data?.length ?? 0,
itemBuilder: (context, index) {
final data = state.data?.data?[index];
return data != null
? _Card.fromData(
data,
onLike: _onLike,
isLiked: likeState.likedIds
?.contains(data.id) ==
true,
onTap: () =>
_navToDetails(context, data),
)
: const SizedBox.shrink();
},
),
),
);
},
),
),
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() {
context
.read<HomeBloc>()

View File

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

View File

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