diff --git a/android/settings.gradle b/android/settings.gradle
index b9e43bd..e07c1cb 100644
--- a/android/settings.gradle
+++ b/android/settings.gradle
@@ -18,7 +18,7 @@ pluginManagement {
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
- id "com.android.application" version "8.1.0" apply false
+ id "com.android.application" version "8.2.2" apply false
id "org.jetbrains.kotlin.android" version "1.8.22" apply false
}
diff --git a/assets/svg/ru.svg b/assets/svg/ru.svg
new file mode 100644
index 0000000..ae12982
--- /dev/null
+++ b/assets/svg/ru.svg
@@ -0,0 +1,19 @@
+
+
+
\ No newline at end of file
diff --git a/assets/svg/uk.svg b/assets/svg/uk.svg
new file mode 100644
index 0000000..88e2211
--- /dev/null
+++ b/assets/svg/uk.svg
@@ -0,0 +1,23 @@
+
+
+
\ No newline at end of file
diff --git a/l10n.yaml b/l10n.yaml
new file mode 100644
index 0000000..d26d702
--- /dev/null
+++ b/l10n.yaml
@@ -0,0 +1,6 @@
+arb-dir: l10n
+template-arb-file: app_ru.arb
+output-localization-file: app_locale.dart
+output-dir: lib/components/locale/l10n
+output-class: AppLocale
+synthetic-package: false
\ No newline at end of file
diff --git a/l10n/app_en.arb b/l10n/app_en.arb
new file mode 100644
index 0000000..e092062
--- /dev/null
+++ b/l10n/app_en.arb
@@ -0,0 +1,9 @@
+{
+ "@@locale": "en",
+
+ "search": "Search",
+ "liked": "liked!",
+ "disliked": "disliked :(",
+
+ "arbEnding": "Чтобы не забыть про отсутствие запятой :)"
+}
diff --git a/l10n/app_ru.arb b/l10n/app_ru.arb
new file mode 100644
index 0000000..f0788a7
--- /dev/null
+++ b/l10n/app_ru.arb
@@ -0,0 +1,9 @@
+{
+ "@@locale": "ru",
+
+ "search": "Поиск",
+ "liked": "понравился!",
+ "disliked": "разонравился :(",
+
+ "arbEnding": "Чтобы не забыть про отсутствие запятой :)"
+}
diff --git a/lib/components/extensions/context_x.dart b/lib/components/extensions/context_x.dart
new file mode 100644
index 0000000..cc81ee9
--- /dev/null
+++ b/lib/components/extensions/context_x.dart
@@ -0,0 +1,6 @@
+import 'package:flutter/widgets.dart';
+import 'package:flutter_test_app/components/locale/l10n/app_locale.dart';
+
+extension LocalContextX on BuildContext {
+ AppLocale get locale => AppLocale.of(this)!;
+}
diff --git a/lib/components/locale/l10n/app_locale.dart b/lib/components/locale/l10n/app_locale.dart
new file mode 100644
index 0000000..0922eb1
--- /dev/null
+++ b/lib/components/locale/l10n/app_locale.dart
@@ -0,0 +1,153 @@
+import 'dart:async';
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter_localizations/flutter_localizations.dart';
+import 'package:intl/intl.dart' as intl;
+
+import 'app_locale_en.dart';
+import 'app_locale_ru.dart';
+
+// ignore_for_file: type=lint
+
+/// Callers can lookup localized strings with an instance of AppLocale
+/// returned by `AppLocale.of(context)`.
+///
+/// Applications need to include `AppLocale.delegate()` in their app's
+/// `localizationDelegates` list, and the locales they support in the app's
+/// `supportedLocales` list. For example:
+///
+/// ```dart
+/// import 'l10n/app_locale.dart';
+///
+/// return MaterialApp(
+/// localizationsDelegates: AppLocale.localizationsDelegates,
+/// supportedLocales: AppLocale.supportedLocales,
+/// home: MyApplicationHome(),
+/// );
+/// ```
+///
+/// ## Update pubspec.yaml
+///
+/// Please make sure to update your pubspec.yaml to include the following
+/// packages:
+///
+/// ```yaml
+/// dependencies:
+/// # Internationalization support.
+/// flutter_localizations:
+/// sdk: flutter
+/// intl: any # Use the pinned version from flutter_localizations
+///
+/// # Rest of dependencies
+/// ```
+///
+/// ## iOS Applications
+///
+/// iOS applications define key application metadata, including supported
+/// locales, in an Info.plist file that is built into the application bundle.
+/// To configure the locales supported by your app, you’ll need to edit this
+/// file.
+///
+/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file.
+/// Then, in the Project Navigator, open the Info.plist file under the Runner
+/// project’s Runner folder.
+///
+/// Next, select the Information Property List item, select Add Item from the
+/// Editor menu, then select Localizations from the pop-up menu.
+///
+/// Select and expand the newly-created Localizations item then, for each
+/// locale your application supports, add a new item and select the locale
+/// you wish to add from the pop-up menu in the Value field. This list should
+/// be consistent with the languages listed in the AppLocale.supportedLocales
+/// property.
+abstract class AppLocale {
+ AppLocale(String locale) : localeName = intl.Intl.canonicalizedLocale(locale.toString());
+
+ final String localeName;
+
+ static AppLocale? of(BuildContext context) {
+ return Localizations.of(context, AppLocale);
+ }
+
+ static const LocalizationsDelegate delegate = _AppLocaleDelegate();
+
+ /// A list of this localizations delegate along with the default localizations
+ /// delegates.
+ ///
+ /// Returns a list of localizations delegates containing this delegate along with
+ /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate,
+ /// and GlobalWidgetsLocalizations.delegate.
+ ///
+ /// Additional delegates can be added by appending to this list in
+ /// MaterialApp. This list does not have to be used at all if a custom list
+ /// of delegates is preferred or required.
+ static const List> localizationsDelegates = >[
+ delegate,
+ GlobalMaterialLocalizations.delegate,
+ GlobalCupertinoLocalizations.delegate,
+ GlobalWidgetsLocalizations.delegate,
+ ];
+
+ /// A list of this localizations delegate's supported locales.
+ static const List supportedLocales = [
+ Locale('en'),
+ Locale('ru')
+ ];
+
+ /// No description provided for @search.
+ ///
+ /// In ru, this message translates to:
+ /// **'Поиск'**
+ String get search;
+
+ /// No description provided for @liked.
+ ///
+ /// In ru, this message translates to:
+ /// **'понравился!'**
+ String get liked;
+
+ /// No description provided for @disliked.
+ ///
+ /// In ru, this message translates to:
+ /// **'разонравился :('**
+ String get disliked;
+
+ /// No description provided for @arbEnding.
+ ///
+ /// In ru, this message translates to:
+ /// **'Чтобы не забыть про отсутствие запятой :)'**
+ String get arbEnding;
+}
+
+class _AppLocaleDelegate extends LocalizationsDelegate {
+ const _AppLocaleDelegate();
+
+ @override
+ Future load(Locale locale) {
+ return SynchronousFuture(lookupAppLocale(locale));
+ }
+
+ @override
+ bool isSupported(Locale locale) => ['en', 'ru'].contains(locale.languageCode);
+
+ @override
+ bool shouldReload(_AppLocaleDelegate old) => false;
+}
+
+AppLocale lookupAppLocale(Locale locale) {
+
+
+ // Lookup logic when only language code is specified.
+ switch (locale.languageCode) {
+ case 'en': return AppLocaleEn();
+ case 'ru': return AppLocaleRu();
+ }
+
+ throw FlutterError(
+ 'AppLocale.delegate failed to load unsupported locale "$locale". This is likely '
+ 'an issue with the localizations generation tool. Please file an issue '
+ 'on GitHub with a reproducible sample app and the gen-l10n configuration '
+ 'that was used.'
+ );
+}
diff --git a/lib/components/locale/l10n/app_locale_en.dart b/lib/components/locale/l10n/app_locale_en.dart
new file mode 100644
index 0000000..599548c
--- /dev/null
+++ b/lib/components/locale/l10n/app_locale_en.dart
@@ -0,0 +1,20 @@
+import 'app_locale.dart';
+
+// ignore_for_file: type=lint
+
+/// The translations for English (`en`).
+class AppLocaleEn extends AppLocale {
+ AppLocaleEn([String locale = 'en']) : super(locale);
+
+ @override
+ String get search => 'Search';
+
+ @override
+ String get liked => 'liked!';
+
+ @override
+ String get disliked => 'disliked :(';
+
+ @override
+ String get arbEnding => 'Чтобы не забыть про отсутствие запятой :)';
+}
diff --git a/lib/components/locale/l10n/app_locale_ru.dart b/lib/components/locale/l10n/app_locale_ru.dart
new file mode 100644
index 0000000..b8d5444
--- /dev/null
+++ b/lib/components/locale/l10n/app_locale_ru.dart
@@ -0,0 +1,20 @@
+import 'app_locale.dart';
+
+// ignore_for_file: type=lint
+
+/// The translations for Russian (`ru`).
+class AppLocaleRu extends AppLocale {
+ AppLocaleRu([String locale = 'ru']) : super(locale);
+
+ @override
+ String get search => 'Поиск';
+
+ @override
+ String get liked => 'понравился!';
+
+ @override
+ String get disliked => 'разонравился :(';
+
+ @override
+ String get arbEnding => 'Чтобы не забыть про отсутствие запятой :)';
+}
diff --git a/lib/components/resources.g.dart b/lib/components/resources.g.dart
new file mode 100644
index 0000000..9645894
--- /dev/null
+++ b/lib/components/resources.g.dart
@@ -0,0 +1,10 @@
+/// 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/utils/debounce.dart b/lib/components/utils/debounce.dart
index 44231fe..767a187 100644
--- a/lib/components/utils/debounce.dart
+++ b/lib/components/utils/debounce.dart
@@ -17,4 +17,4 @@ class Debounce {
_timer?.cancel();
_timer = Timer(delay, action);
}
-}
\ No newline at end of file
+}
diff --git a/lib/data/dtos/potions_dto.dart b/lib/data/dtos/characters_dto.dart
similarity index 52%
rename from lib/data/dtos/potions_dto.dart
rename to lib/data/dtos/characters_dto.dart
index 1cb18aa..2bccf76 100644
--- a/lib/data/dtos/potions_dto.dart
+++ b/lib/data/dtos/characters_dto.dart
@@ -1,42 +1,42 @@
import 'package:json_annotation/json_annotation.dart';
-part 'potions_dto.g.dart';
+part 'characters_dto.g.dart';
@JsonSerializable(createToJson: false)
-class PotionsDto {
- final List? data;
+class CharactersDto {
+ final List? data;
final MetaDto? meta;
- const PotionsDto({
+ const CharactersDto({
this.data,
this.meta,
});
- factory PotionsDto.fromJson(Map json) => _$PotionsDtoFromJson(json);
+ factory CharactersDto.fromJson(Map json) => _$CharactersDtoFromJson(json);
}
@JsonSerializable(createToJson: false)
-class PotionDataDto {
+class CharacterDataDto {
final String? id;
final String? type;
- final PotionAttributesDataDto? attributes;
+ final CharacterAttributesDataDto? attributes;
- const PotionDataDto({this.id, this.type, this.attributes});
+ const CharacterDataDto({this.id, this.type, this.attributes});
- factory PotionDataDto.fromJson(Map json) => _$PotionDataDtoFromJson(json);
+ factory CharacterDataDto.fromJson(Map json) => _$CharacterDataDtoFromJson(json);
}
@JsonSerializable(createToJson: false)
-class PotionAttributesDataDto {
+class CharacterAttributesDataDto {
final String? name;
- final String? characteristics;
- final String? effect;
+ final String? born;
+ final String? died;
final String? image;
- const PotionAttributesDataDto({this.name, this.characteristics, this.effect, this.image});
+ const CharacterAttributesDataDto({this.name, this.born, this.died, this.image});
- factory PotionAttributesDataDto.fromJson(Map json) =>
- _$PotionAttributesDataDtoFromJson(json);
+ factory CharacterAttributesDataDto.fromJson(Map json) =>
+ _$CharacterAttributesDataDtoFromJson(json);
}
@JsonSerializable(createToJson: false)
@@ -57,4 +57,4 @@ class PaginationDto {
const PaginationDto({this.current, this.next, this.last});
factory PaginationDto.fromJson(Map json) => _$PaginationDtoFromJson(json);
-}
\ No newline at end of file
+}
diff --git a/lib/data/dtos/characters_dto.g.dart b/lib/data/dtos/characters_dto.g.dart
new file mode 100644
index 0000000..ca7a4b4
--- /dev/null
+++ b/lib/data/dtos/characters_dto.g.dart
@@ -0,0 +1,42 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'characters_dto.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+CharactersDto _$CharactersDtoFromJson(Map json) => CharactersDto(
+ data: (json['data'] as List?)
+ ?.map((e) => CharacterDataDto.fromJson(e as Map))
+ .toList(),
+ meta: json['meta'] == null ? null : MetaDto.fromJson(json['meta'] as Map),
+);
+
+CharacterDataDto _$CharacterDataDtoFromJson(Map json) => CharacterDataDto(
+ id: json['id'] as String?,
+ type: json['type'] as String?,
+ attributes: json['attributes'] == null
+ ? null
+ : CharacterAttributesDataDto.fromJson(json['attributes'] as Map),
+);
+
+CharacterAttributesDataDto _$CharacterAttributesDataDtoFromJson(Map json) =>
+ CharacterAttributesDataDto(
+ name: json['name'] as String?,
+ born: json['born'] as String?,
+ died: json['died'] as String?,
+ image: json['image'] as String?,
+ );
+
+MetaDto _$MetaDtoFromJson(Map json) => MetaDto(
+ pagination: json['pagination'] == null
+ ? null
+ : PaginationDto.fromJson(json['pagination'] as Map),
+);
+
+PaginationDto _$PaginationDtoFromJson(Map json) => PaginationDto(
+ current: json['current'] as int?,
+ next: json['next'] as int?,
+ last: json['last'] as int?,
+);
diff --git a/lib/data/dtos/potions_dto.g.dart b/lib/data/dtos/potions_dto.g.dart
deleted file mode 100644
index b9ce093..0000000
--- a/lib/data/dtos/potions_dto.g.dart
+++ /dev/null
@@ -1,48 +0,0 @@
-// GENERATED CODE - DO NOT MODIFY BY HAND
-
-part of 'potions_dto.dart';
-
-// **************************************************************************
-// JsonSerializableGenerator
-// **************************************************************************
-
-PotionsDto _$PotionsDtoFromJson(Map json) => PotionsDto(
- data: (json['data'] as List?)
- ?.map((e) => PotionDataDto.fromJson(e as Map))
- .toList(),
- meta: json['meta'] == null
- ? null
- : MetaDto.fromJson(json['meta'] as Map),
-);
-
-PotionDataDto _$PotionDataDtoFromJson(Map json) =>
- PotionDataDto(
- id: json['id'] as String?,
- type: json['type'] as String?,
- attributes: json['attributes'] == null
- ? null
- : PotionAttributesDataDto.fromJson(
- json['attributes'] as Map),
- );
-
-PotionAttributesDataDto _$PotionAttributesDataDtoFromJson(
- Map json) =>
- PotionAttributesDataDto(
- name: json['name'] as String?,
- characteristics: json['characteristics'] as String?,
- effect: json['effect'] as String?,
- image: json['image'] as String?,
- );
-
-MetaDto _$MetaDtoFromJson(Map json) => MetaDto(
- pagination: json['pagination'] == null
- ? null
- : PaginationDto.fromJson(json['pagination'] as Map),
-);
-
-PaginationDto _$PaginationDtoFromJson(Map json) =>
- PaginationDto(
- current: (json['current'] as num?)?.toInt(),
- next: (json['next'] as num?)?.toInt(),
- last: (json['last'] as num?)?.toInt(),
- );
diff --git a/lib/data/mappers/characters_mapper.dart b/lib/data/mappers/characters_mapper.dart
new file mode 100644
index 0000000..3438478
--- /dev/null
+++ b/lib/data/mappers/characters_mapper.dart
@@ -0,0 +1,32 @@
+import 'package:flutter_test_app/data/dtos/characters_dto.dart';
+import 'package:flutter_test_app/domain/models/card.dart';
+import 'package:flutter_test_app/domain/models/home.dart';
+
+const _imagePlaceholder =
+ 'https://upload.wikimedia.org/wikipedia/en/archive/b/b1/20210811082420%21Portrait_placeholder.png';
+
+extension CharactersDtoToModel on CharactersDto {
+ HomeData toDomain() => HomeData(
+ data: data?.map((e) => e.toDomain()).toList(),
+ nextPage: meta?.pagination?.next,
+ );
+}
+
+extension CharacterDataDtoToModel on CharacterDataDto {
+ CardData toDomain() => CardData(
+ attributes?.name ?? 'UNKNOWN',
+ imageUrl: attributes?.image ?? _imagePlaceholder,
+ descriptionText: _makeDescriptionText(attributes?.born, attributes?.died),
+ id: id,
+ );
+
+ String _makeDescriptionText(String? born, String? died) {
+ return born != null && died != null
+ ? '$born - $died'
+ : born != null
+ ? 'born: $born'
+ : died != null
+ ? 'died: $died'
+ : '';
+ }
+}
diff --git a/lib/data/mappers/potions_mapper.dart b/lib/data/mappers/potions_mapper.dart
deleted file mode 100644
index d529670..0000000
--- a/lib/data/mappers/potions_mapper.dart
+++ /dev/null
@@ -1,31 +0,0 @@
-import 'package:flutter_test_app/data/dtos/potions_dto.dart';
-import 'package:flutter_test_app/domain/models/card.dart';
-import 'package:flutter_test_app/domain/models/home.dart';
-
-const String imagePlaceholder =
- 'https://cdn-icons-png.flaticon.com/512/4036/4036418.png';
-
-extension PotionsDataDtoToModel on PotionsDto {
- HomeData toDomain() => HomeData(
- data: data?.map((e) => e.toDomain()).toList(),
- nextPage: meta?.pagination?.next,
- );
-}
-
-extension PotionDataDtoToModel on PotionDataDto {
- CardData toDomain() => CardData(
- attributes?.name ?? 'UNKNOWN',
- imageUrl: attributes?.image ?? imagePlaceholder,
- descriptionText: _makeDescriptionText(attributes?.characteristics, attributes?.effect),
- );
-
- String _makeDescriptionText(String? characteristics, String? effect) {
- return characteristics != null && effect != null
- ? '$characteristics - $effect'
- : characteristics != null
- ? 'characteristics: $characteristics'
- : effect != null
- ? 'effect: $effect'
- : '';
- }
-}
\ No newline at end of file
diff --git a/lib/data/repositories/api_interface.dart b/lib/data/repositories/api_interface.dart
index 4dfd80b..cc35215 100644
--- a/lib/data/repositories/api_interface.dart
+++ b/lib/data/repositories/api_interface.dart
@@ -4,4 +4,4 @@ typedef OnErrorCallback = void Function(String? error);
abstract class ApiInterface {
Future loadData({OnErrorCallback? onError});
-}
\ No newline at end of file
+}
diff --git a/lib/data/repositories/mock_repository.dart b/lib/data/repositories/mock_repository.dart
index 343eb40..d81ba4a 100644
--- a/lib/data/repositories/mock_repository.dart
+++ b/lib/data/repositories/mock_repository.dart
@@ -6,28 +6,28 @@ import 'package:flutter_test_app/domain/models/home.dart';
class MockRepository extends ApiInterface {
@override
Future loadData({OnErrorCallback? onError}) async {
- return HomeData (
- data: [
- CardData(
- 'Mouse',
- descriptionText: 'hehehe',
- imageUrl:
- 'https://sun9-26.userapi.com/impg/sB_tLPWPCIqxQWmlbcmlRYiw1ibFb70_QMtNwg/56qpyc_C8Go.jpg?size=736x711&quality=95&sign=8f7163b54538a2e7bad5f36a857485d4&type=album',
- ),
- CardData(
- 'Mouse2',
- descriptionText: 'pretty face',
- icon: Icons.hail,
- imageUrl:
- 'https://sun165-1.userapi.com/impg/EVLbaLilqr8xw5tsqZLQQb6DSYrdKo7Q9sYSsw/H4FRwyMR6Ec.jpg?size=1280x960&quality=96&sign=f606e4ae3d1ccd27917cd1ffa6d91e58&type=album',
- ),
- CardData(
- 'Mouse3',
- descriptionText: 'I like hamsters',
- icon: Icons.warning_amber,
- imageUrl: 'https://sun34-1.userapi.com/impg/_DLT-op0LbBdgh5h-ILvC7IMDY5kbLR349v7vA/tX7vtk6mNlA.jpg?size=736x736&quality=96&sign=47f2b0f63bf249c62f4498fb637695d5&type=album',
- ),
- ]
+ return HomeData(
+ data: [
+ CardData(
+ 'Freeze',
+ descriptionText: 'so cold..',
+ imageUrl:
+ 'https://www.skedaddlewildlife.com/wp-content/uploads/2018/09/depositphotos_22425309-stock-photo-a-lonely-raccoon-in-winter.jpg',
+ ),
+ CardData(
+ 'Hi',
+ descriptionText: 'pretty face',
+ icon: Icons.hail,
+ imageUrl:
+ 'https://www.thesprucepets.com/thmb/nKNaS4I586B_H7sEUw9QAXvWM_0=/2121x0/filters:no_upscale():strip_icc()/GettyImages-135630198-5ba7d225c9e77c0050cff91b.jpg',
+ ),
+ CardData(
+ 'Orange',
+ descriptionText: 'I like autumn',
+ icon: Icons.warning_amber,
+ imageUrl: 'https://furmanagers.com/wp-content/uploads/2019/11/dreamstime_l_22075357.jpg',
+ ),
+ ],
);
}
-}
\ No newline at end of file
+}
diff --git a/lib/data/repositories/potter_repository.dart b/lib/data/repositories/potter_repository.dart
index 2a08ce5..b3ada5f 100644
--- a/lib/data/repositories/potter_repository.dart
+++ b/lib/data/repositories/potter_repository.dart
@@ -1,6 +1,6 @@
import 'package:dio/dio.dart';
-import 'package:flutter_test_app/data/dtos/potions_dto.dart';
-import 'package:flutter_test_app/data/mappers/potions_mapper.dart';
+import 'package:flutter_test_app/data/dtos/characters_dto.dart';
+import 'package:flutter_test_app/data/mappers/characters_mapper.dart';
import 'package:flutter_test_app/data/repositories/api_interface.dart';
import 'package:flutter_test_app/domain/models/home.dart';
import 'package:pretty_dio_logger/pretty_dio_logger.dart';
@@ -22,7 +22,7 @@ class PotterRepository extends ApiInterface {
int pageSize = 25,
}) async {
try {
- const String url = '$_baseUrl/v1/potions';
+ const String url = '$_baseUrl/v1/characters';
final Response response = await _dio.get