diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
index db77bb4..52a2ddc 100644
Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
index 17987b7..6059953 100644
Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
index 09d4391..585ce95 100644
Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
index d5f1c8d..e1a8742 100644
Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
index 4d6372e..ce2d49c 100644
Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/assets/launcher.jpeg b/assets/launcher.jpeg
new file mode 100644
index 0000000..3fecd11
Binary files /dev/null and b/assets/launcher.jpeg differ
diff --git a/assets/launcher.png b/assets/launcher.png
new file mode 100644
index 0000000..baf0ce4
Binary files /dev/null and b/assets/launcher.png differ
diff --git a/assets/svg/ru.svg b/assets/svg/ru.svg
new file mode 100644
index 0000000..b7c6623
--- /dev/null
+++ b/assets/svg/ru.svg
@@ -0,0 +1,10 @@
+
diff --git a/assets/svg/uk.svg b/assets/svg/uk.svg
new file mode 100644
index 0000000..781b415
--- /dev/null
+++ b/assets/svg/uk.svg
@@ -0,0 +1,16 @@
+
diff --git a/devtools_options.yaml b/devtools_options.yaml
new file mode 100644
index 0000000..fa0b357
--- /dev/null
+++ b/devtools_options.yaml
@@ -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:
diff --git a/lib/components/resources.g.dart b/lib/components/resources.g.dart
new file mode 100644
index 0000000..0627a52
--- /dev/null
+++ b/lib/components/resources.g.dart
@@ -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._();
+
+ /// 
+ static const String ASSETS_SVG_RU_SVG = 'assets/svg/ru.svg';
+
+ /// 
+ static const String ASSETS_SVG_UK_SVG = 'assets/svg/uk.svg';
+}
diff --git a/lib/components/resoures.g.dart b/lib/components/resoures.g.dart
new file mode 100644
index 0000000..276bfa0
--- /dev/null
+++ b/lib/components/resoures.g.dart
@@ -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._();
+
+ /// 
+ static const String ASSETS_SVG_RU_SVG = 'assets/svg/ru.svg';
+
+ /// 
+ static const String ASSETS_SVG_UK_SVG = 'assets/svg/uk.svg';
+}
diff --git a/lib/data/mappers/user_mapper.dart b/lib/data/mappers/user_mapper.dart
index ee80ae2..19fc802 100644
--- a/lib/data/mappers/user_mapper.dart
+++ b/lib/data/mappers/user_mapper.dart
@@ -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);
}
diff --git a/lib/data/repositories/mock_repository.dart b/lib/data/repositories/mock_repository.dart
index 69d4654..5d8287c 100644
--- a/lib/data/repositories/mock_repository.dart
+++ b/lib/data/repositories/mock_repository.dart
@@ -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);
}
}
diff --git a/lib/domain/models/card.dart b/lib/domain/models/card.dart
index e347b3c..cb185e7 100644
--- a/lib/domain/models/card.dart
+++ b/lib/domain/models/card.dart
@@ -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});
}
diff --git a/lib/main.dart b/lib/main.dart
index 8e907d4..9872901 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -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(
- lazy: true,
- create: (_) => ApiRepository(),
- child: BlocProvider(
- lazy: false,
- create: (context) => HomeBloc(context.read()),
- child: const HomePage())),
+ return BlocProvider(
+ lazy: false,
+ create: (context) => LocaleBloc(Locale(Platform.localeName)),
+ child: BlocBuilder(
+ builder: (context, state) {
+ return MaterialApp(
+ title: 'LoveSearch',
+ locale: state.currentLocale,
+ localizationsDelegates: AppLocale.localizationsDelegates,
+ supportedLocales: AppLocale.supportedLocales,
+ debugShowCheckedModeBanner: false,
+ home: RepositoryProvider(
+ lazy: true,
+ create: (_) => ApiRepository(),
+ child: BlocProvider(
+ lazy: true,
+ create: (context) => LikeBloc(),
+ child: BlocProvider(
+ lazy: false,
+ create: (context) =>
+ HomeBloc(context.read()),
+ child: const HomePage()),
+ ),
+ ),
+ );
+ },
+ ),
);
}
}
diff --git a/lib/presentation/common/svg_objects.dart b/lib/presentation/common/svg_objects.dart
index 3dd9e03..13b85f2 100644
--- a/lib/presentation/common/svg_objects.dart
+++ b/lib/presentation/common/svg_objects.dart
@@ -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 = [
diff --git a/lib/presentation/home_page/card.dart b/lib/presentation/home_page/card.dart
index 8eeec16..462c28b 100644
--- a/lib/presentation/home_page/card.dart
+++ b/lib/presentation/home_page/card.dart
@@ -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(0),
+ )
+ : const Icon(
+ Icons.favorite,
+ color: Colors.red,
+ key: ValueKey(1),
+ ),
+ ),
),
],
),
- )
+ ),
],
),
));
diff --git a/lib/presentation/home_page/home_page.dart b/lib/presentation/home_page/home_page.dart
index afba72d..1679d2e 100644
--- a/lib/presentation/home_page/home_page.dart
+++ b/lib/presentation/home_page/home_page.dart
@@ -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().add(const HomeLoadDataEvent());
+ context.read().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()
- .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()
+ .add(HomeLoadDataEvent(search: search)));
+ },
+ ),
+ ),
+ ),
+ GestureDetector(
+ onTap: () =>
+ context.read().add(const ChangeLocaleEvent()),
+ child: SizedBox.square(
+ dimension: 50,
+ child: Padding(
+ padding: const EdgeInsets.all(12),
+ child: BlocBuilder(
+ builder: (context, state) {
+ return state.currentLocale.languageCode == 'ru'
+ ? const SvgRu()
+ : const SvgUk();
+ },
+ ),
+ ),
+ ),
+ )
+ ],
),
BlocBuilder(
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(
+ 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(
@@ -126,6 +168,13 @@ class _BodyState extends State<_Body> {
);
}
+ void _onLike(String? id, String title, bool isLiked) {
+ if (id != null) {
+ context.read().add(ChangeLikeEvent(id));
+ _showSnackBar(context, title, !isLiked);
+ }
+ }
+
Future _onRefresh() {
context
.read()
diff --git a/lib/presentation/like/bloc.dart b/lib/presentation/like/bloc.dart
index 7d918d6..76e46c8 100644
--- a/lib/presentation/like/bloc.dart
+++ b/lib/presentation/like/bloc.dart
@@ -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';
diff --git a/pubspec.yaml b/pubspec.yaml
index 7795da2..ffddfdd 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -57,7 +57,6 @@ dependencies:
flutter_icons:
android: "ic_launcher"
- ios: true
image_path: "assets/launcher.jpeg"
min_sdk_android: 21