Надо починить лайки
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 67 KiB |
BIN
assets/launcher.jpeg
Normal file
After Width: | Height: | Size: 104 KiB |
BIN
assets/launcher.png
Normal file
After Width: | Height: | Size: 81 KiB |
10
assets/svg/ru.svg
Normal 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
@ -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
@ -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:
|
12
lib/components/resources.g.dart
Normal 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';
|
||||
}
|
12
lib/components/resoures.g.dart
Normal 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';
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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});
|
||||
}
|
||||
|
@ -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()),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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>[
|
||||
|
@ -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),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
|
@ -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>()
|
||||
|
@ -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';
|
||||
|
||||
|
@ -57,7 +57,6 @@ dependencies:
|
||||
|
||||
flutter_icons:
|
||||
android: "ic_launcher"
|
||||
ios: true
|
||||
image_path: "assets/launcher.jpeg"
|
||||
min_sdk_android: 21
|
||||
|
||||
|