fixed localizations issues

changed application name
updated svg icons
This commit is contained in:
ShabOl 2024-12-16 22:59:15 +04:00
parent a676d454ca
commit 04120c4847
26 changed files with 212 additions and 414 deletions

View File

@ -1,6 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="flutter_android_app"
android:label="Crypto Exchange"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity

View File

@ -1,5 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-ru" viewBox="0 0 640 480">
<path fill="#fff" d="M0 0h640v160H0z"/>
<path fill="#0039a6" d="M0 160h640v160H0z"/>
<path fill="#d52b1e" d="M0 320h640v160H0z"/>
<?xml version="1.0" encoding="utf-8"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 -4 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_503_2726)">
<rect x="0.25" y="0.25" width="27.5" height="19.5" rx="1.75" fill="white" stroke="#F5F5F5" stroke-width="0.5"/>
<mask id="mask0_503_2726" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="28" height="20">
<rect x="0.25" y="0.25" width="27.5" height="19.5" rx="1.75" fill="white" stroke="white" stroke-width="0.5"/>
</mask>
<g mask="url(#mask0_503_2726)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 13.3333H28V6.66667H0V13.3333Z" fill="#0C47B7"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 20H28V13.3333H0V20Z" fill="#E53B35"/>
</g>
</g>
<defs>
<clipPath id="clip0_503_2726">
<rect width="28" height="20" rx="2" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 225 B

After

Width:  |  Height:  |  Size: 968 B

View File

@ -1,9 +1,34 @@
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-us" viewBox="0 0 640 480">
<path fill="#bd3d44" d="M0 0h640v480H0"/>
<path stroke="#fff" stroke-width="37" d="M0 55.3h640M0 129h640M0 203h640M0 277h640M0 351h640M0 425h640"/>
<path fill="#192f5d" d="M0 0h364.8v258.5H0"/>
<marker id="us-a" markerHeight="30" markerWidth="30">
<path fill="#fff" d="m14 0 9 27L0 10h28L5 27z"/>
</marker>
<path fill="none" marker-mid="url(#us-a)" d="m0 0 16 11h61 61 61 61 60L47 37h61 61 60 61L16 63h61 61 61 61 60L47 89h61 61 60 61L16 115h61 61 61 61 60L47 141h61 61 60 61L16 166h61 61 61 61 60L47 192h61 61 60 61L16 218h61 61 61 61 60z"/>
<?xml version="1.0" encoding="utf-8"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 -4 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_503_3486)">
<rect width="28" height="20" rx="2" fill="white"/>
<mask id="mask0_503_3486" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="28" height="20">
<rect width="28" height="20" rx="2" fill="white"/>
</mask>
<g mask="url(#mask0_503_3486)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M28 0H0V1.33333H28V0ZM28 2.66667H0V4H28V2.66667ZM0 5.33333H28V6.66667H0V5.33333ZM28 8H0V9.33333H28V8ZM0 10.6667H28V12H0V10.6667ZM28 13.3333H0V14.6667H28V13.3333ZM0 16H28V17.3333H0V16ZM28 18.6667H0V20H28V18.6667Z" fill="#D02F44"/>
<rect width="12" height="9.33333" fill="#46467F"/>
<g filter="url(#filter0_d_503_3486)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.66665 1.99999C2.66665 2.36818 2.36817 2.66666 1.99998 2.66666C1.63179 2.66666 1.33331 2.36818 1.33331 1.99999C1.33331 1.63181 1.63179 1.33333 1.99998 1.33333C2.36817 1.33333 2.66665 1.63181 2.66665 1.99999ZM5.33331 1.99999C5.33331 2.36818 5.03484 2.66666 4.66665 2.66666C4.29846 2.66666 3.99998 2.36818 3.99998 1.99999C3.99998 1.63181 4.29846 1.33333 4.66665 1.33333C5.03484 1.33333 5.33331 1.63181 5.33331 1.99999ZM7.33331 2.66666C7.7015 2.66666 7.99998 2.36818 7.99998 1.99999C7.99998 1.63181 7.7015 1.33333 7.33331 1.33333C6.96512 1.33333 6.66665 1.63181 6.66665 1.99999C6.66665 2.36818 6.96512 2.66666 7.33331 2.66666ZM10.6666 1.99999C10.6666 2.36818 10.3682 2.66666 9.99998 2.66666C9.63179 2.66666 9.33331 2.36818 9.33331 1.99999C9.33331 1.63181 9.63179 1.33333 9.99998 1.33333C10.3682 1.33333 10.6666 1.63181 10.6666 1.99999ZM3.33331 3.99999C3.7015 3.99999 3.99998 3.70152 3.99998 3.33333C3.99998 2.96514 3.7015 2.66666 3.33331 2.66666C2.96512 2.66666 2.66665 2.96514 2.66665 3.33333C2.66665 3.70152 2.96512 3.99999 3.33331 3.99999ZM6.66665 3.33333C6.66665 3.70152 6.36817 3.99999 5.99998 3.99999C5.63179 3.99999 5.33331 3.70152 5.33331 3.33333C5.33331 2.96514 5.63179 2.66666 5.99998 2.66666C6.36817 2.66666 6.66665 2.96514 6.66665 3.33333ZM8.66665 3.99999C9.03484 3.99999 9.33331 3.70152 9.33331 3.33333C9.33331 2.96514 9.03484 2.66666 8.66665 2.66666C8.29846 2.66666 7.99998 2.96514 7.99998 3.33333C7.99998 3.70152 8.29846 3.99999 8.66665 3.99999ZM10.6666 4.66666C10.6666 5.03485 10.3682 5.33333 9.99998 5.33333C9.63179 5.33333 9.33331 5.03485 9.33331 4.66666C9.33331 4.29847 9.63179 3.99999 9.99998 3.99999C10.3682 3.99999 10.6666 4.29847 10.6666 4.66666ZM7.33331 5.33333C7.7015 5.33333 7.99998 5.03485 7.99998 4.66666C7.99998 4.29847 7.7015 3.99999 7.33331 3.99999C6.96512 3.99999 6.66665 4.29847 6.66665 4.66666C6.66665 5.03485 6.96512 5.33333 7.33331 5.33333ZM5.33331 4.66666C5.33331 5.03485 5.03484 5.33333 4.66665 5.33333C4.29846 5.33333 3.99998 5.03485 3.99998 4.66666C3.99998 4.29847 4.29846 3.99999 4.66665 3.99999C5.03484 3.99999 5.33331 4.29847 5.33331 4.66666ZM1.99998 5.33333C2.36817 5.33333 2.66665 5.03485 2.66665 4.66666C2.66665 4.29847 2.36817 3.99999 1.99998 3.99999C1.63179 3.99999 1.33331 4.29847 1.33331 4.66666C1.33331 5.03485 1.63179 5.33333 1.99998 5.33333ZM3.99998 5.99999C3.99998 6.36819 3.7015 6.66666 3.33331 6.66666C2.96512 6.66666 2.66665 6.36819 2.66665 5.99999C2.66665 5.6318 2.96512 5.33333 3.33331 5.33333C3.7015 5.33333 3.99998 5.6318 3.99998 5.99999ZM5.99998 6.66666C6.36817 6.66666 6.66665 6.36819 6.66665 5.99999C6.66665 5.6318 6.36817 5.33333 5.99998 5.33333C5.63179 5.33333 5.33331 5.6318 5.33331 5.99999C5.33331 6.36819 5.63179 6.66666 5.99998 6.66666ZM9.33331 5.99999C9.33331 6.36819 9.03484 6.66666 8.66665 6.66666C8.29846 6.66666 7.99998 6.36819 7.99998 5.99999C7.99998 5.6318 8.29846 5.33333 8.66665 5.33333C9.03484 5.33333 9.33331 5.6318 9.33331 5.99999ZM9.99998 8C10.3682 8 10.6666 7.70152 10.6666 7.33333C10.6666 6.96514 10.3682 6.66666 9.99998 6.66666C9.63179 6.66666 9.33331 6.96514 9.33331 7.33333C9.33331 7.70152 9.63179 8 9.99998 8ZM7.99998 7.33333C7.99998 7.70152 7.7015 8 7.33331 8C6.96512 8 6.66665 7.70152 6.66665 7.33333C6.66665 6.96514 6.96512 6.66666 7.33331 6.66666C7.7015 6.66666 7.99998 6.96514 7.99998 7.33333ZM4.66665 8C5.03484 8 5.33331 7.70152 5.33331 7.33333C5.33331 6.96514 5.03484 6.66666 4.66665 6.66666C4.29846 6.66666 3.99998 6.96514 3.99998 7.33333C3.99998 7.70152 4.29846 8 4.66665 8ZM2.66665 7.33333C2.66665 7.70152 2.36817 8 1.99998 8C1.63179 8 1.33331 7.70152 1.33331 7.33333C1.33331 6.96514 1.63179 6.66666 1.99998 6.66666C2.36817 6.66666 2.66665 6.96514 2.66665 7.33333Z" fill="url(#paint0_linear_503_3486)"/>
</g>
</g>
</g>
<defs>
<filter id="filter0_d_503_3486" x="1.33331" y="1.33333" width="9.33331" height="7.66667" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.06 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_503_3486"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_503_3486" result="shape"/>
</filter>
<linearGradient id="paint0_linear_503_3486" x1="1.33331" y1="1.33333" x2="1.33331" y2="7.99999" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="#F0F0F0"/>
</linearGradient>
<clipPath id="clip0_503_3486">
<rect width="28" height="20" rx="2" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 648 B

After

Width:  |  Height:  |  Size: 5.5 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

@ -1,5 +1,5 @@
arb-dir: l10n
template-arb-file: app_ru.arb
template-arb-file: app_en.arb
output-localization: app_locale.dart
output-dir: lib/components/locale/l10n
output-class: AppLocale

View File

@ -1,21 +1,15 @@
{
"@@locale": "en",
"appBarTitle": "Harry Potter characters",
"mainAppBarTitle": "Cryptocurrency Exchange",
"detailsPageAppBarTitle": "Cryptocurrency info",
"settingsPageAppBarTitle": "Settings",
"search": "Search",
"liked": "You liked",
"unliked": "Like removed from",
"errorOccurred": "Error occurred",
"noErrorMsg": "No message provided",
"retry": "Retry",
"unknown": "Unknown",
"searchHint": "Search",
"addedToFavourite": "is added to favourites",
"removedFromFavourite": "is removed from favourites",
"apiYear": "Year",
"apiType": "Type",
"apiRating": "Rating",
"apiDesc": "",
"apiNoDesc": "No description provided",
"coinDataPriceChange": "for the last 24 hours",
"arbEnding": "t"
"settingsLanguage": "Language"
}

View File

@ -1,21 +1,15 @@
{
"@@locale": "ru",
"appBarTitle": "Персонажи из Гарри Поттера",
"mainAppBarTitle": "Криптобиржа",
"detailsPageAppBarTitle": "Сведения о валюте",
"settingsPageAppBarTitle": "Настройки",
"search": "Поиск",
"liked": "Вы добавили в избранное",
"unliked": "Вы удалили из избранного",
"errorOccured": "Произошла ошибка",
"noErrorMsg": "Нет сообщения",
"retry": "Повторить",
"unknown": "Неизвестно",
"searchHint": "Поиск",
"addedToFavourite": "добавлен в избранное",
"removedFromFavourite": "удален из избранного",
"apiYear": "Год",
"apiType": "Тип",
"apiRating": "Рейтинг",
"apiDesc": "(Описание доступно только на английском языке)",
"apiNoDesc": "Нет описания",
"coinDataPriceChange": "за последние 24 часа",
"arbEnding": "t"
"settingsLanguage": "Язык"
}

View File

@ -95,89 +95,53 @@ abstract class AppLocale {
Locale('ru')
];
/// No description provided for @appBarTitle.
/// No description provided for @mainAppBarTitle.
///
/// In ru, this message translates to:
/// **'Персонажи из Гарри Поттера'**
String get appBarTitle;
/// In en, this message translates to:
/// **'Cryptocurrency Exchange'**
String get mainAppBarTitle;
/// No description provided for @search.
/// No description provided for @detailsPageAppBarTitle.
///
/// In ru, this message translates to:
/// **'Поиск'**
String get search;
/// In en, this message translates to:
/// **'Cryptocurrency info'**
String get detailsPageAppBarTitle;
/// No description provided for @liked.
/// No description provided for @settingsPageAppBarTitle.
///
/// In ru, this message translates to:
/// **'Вы добавили в избранное'**
String get liked;
/// In en, this message translates to:
/// **'Settings'**
String get settingsPageAppBarTitle;
/// No description provided for @unliked.
/// No description provided for @searchHint.
///
/// In ru, this message translates to:
/// **'Вы удалили из избранного'**
String get unliked;
/// In en, this message translates to:
/// **'Search'**
String get searchHint;
/// No description provided for @errorOccured.
/// No description provided for @addedToFavourite.
///
/// In ru, this message translates to:
/// **'Произошла ошибка'**
String get errorOccured;
/// In en, this message translates to:
/// **'is added to favourites'**
String get addedToFavourite;
/// No description provided for @noErrorMsg.
/// No description provided for @removedFromFavourite.
///
/// In ru, this message translates to:
/// **'Нет сообщения'**
String get noErrorMsg;
/// In en, this message translates to:
/// **'is removed from favourites'**
String get removedFromFavourite;
/// No description provided for @retry.
/// No description provided for @coinDataPriceChange.
///
/// In ru, this message translates to:
/// **'Повторить'**
String get retry;
/// In en, this message translates to:
/// **'for the last 24 hours'**
String get coinDataPriceChange;
/// No description provided for @unknown.
/// No description provided for @settingsLanguage.
///
/// In ru, this message translates to:
/// **'Неизвестно'**
String get unknown;
/// No description provided for @apiYear.
///
/// In ru, this message translates to:
/// **'Год'**
String get apiYear;
/// No description provided for @apiType.
///
/// In ru, this message translates to:
/// **'Тип'**
String get apiType;
/// No description provided for @apiRating.
///
/// In ru, this message translates to:
/// **'Рейтинг'**
String get apiRating;
/// No description provided for @apiDesc.
///
/// In ru, this message translates to:
/// **'(Описание доступно только на английском языке)'**
String get apiDesc;
/// No description provided for @apiNoDesc.
///
/// In ru, this message translates to:
/// **'Нет описания'**
String get apiNoDesc;
/// No description provided for @arbEnding.
///
/// In ru, this message translates to:
/// **'t'**
String get arbEnding;
/// In en, this message translates to:
/// **'Language'**
String get settingsLanguage;
}
class _AppLocaleDelegate extends LocalizationsDelegate<AppLocale> {

View File

@ -7,44 +7,26 @@ class AppLocaleEn extends AppLocale {
AppLocaleEn([String locale = 'en']) : super(locale);
@override
String get appBarTitle => 'Harry Potter characters';
String get mainAppBarTitle => 'Cryptocurrency Exchange';
@override
String get search => 'Search';
String get detailsPageAppBarTitle => 'Cryptocurrency info';
@override
String get liked => 'You liked';
String get settingsPageAppBarTitle => 'Settings';
@override
String get unliked => 'Like removed from';
String get searchHint => 'Search';
@override
String get errorOccured => 'Произошла ошибка';
String get addedToFavourite => 'is added to favourites';
@override
String get noErrorMsg => 'No message provided';
String get removedFromFavourite => 'is removed from favourites';
@override
String get retry => 'Retry';
String get coinDataPriceChange => 'for the last 24 hours';
@override
String get unknown => 'Unknown';
@override
String get apiYear => 'Year';
@override
String get apiType => 'Type';
@override
String get apiRating => 'Rating';
@override
String get apiDesc => '';
@override
String get apiNoDesc => 'No description provided';
@override
String get arbEnding => 't';
String get settingsLanguage => 'Language';
}

View File

@ -7,44 +7,26 @@ class AppLocaleRu extends AppLocale {
AppLocaleRu([String locale = 'ru']) : super(locale);
@override
String get appBarTitle => 'Персонажи из Гарри Поттера';
String get mainAppBarTitle => 'Криптобиржа';
@override
String get search => 'Поиск';
String get detailsPageAppBarTitle => 'Сведения о валюте';
@override
String get liked => 'Вы добавили в избранное';
String get settingsPageAppBarTitle => 'Настройки';
@override
String get unliked => 'Вы удалили из избранного';
String get searchHint => 'Поиск';
@override
String get errorOccured => 'Произошла ошибка';
String get addedToFavourite => 'добавлен в избранное';
@override
String get noErrorMsg => 'Нет сообщения';
String get removedFromFavourite => 'удален из избранного';
@override
String get retry => 'Повторить';
String get coinDataPriceChange => 'за последние 24 часа';
@override
String get unknown => 'Неизвестно';
@override
String get apiYear => 'Год';
@override
String get apiType => 'Тип';
@override
String get apiRating => 'Рейтинг';
@override
String get apiDesc => '(Описание доступно только на английском языке)';
@override
String get apiNoDesc => 'Нет описания';
@override
String get arbEnding => 't';
String get settingsLanguage => 'Язык';
}

View File

@ -1,56 +0,0 @@
import 'package:json_annotation/json_annotation.dart';
part 'characters_dto.g.dart';
@JsonSerializable(createToJson: false)
class CharactersDto {
final List<CharacterDataDto>? data;
final MetaDto? meta;
const CharactersDto({this.data, this.meta});
factory CharactersDto.fromJson(Map<String, dynamic> json) => _$CharactersDtoFromJson(json);
}
@JsonSerializable(createToJson: false)
class CharacterDataDto {
final String? id;
final String? type;
final CharacterAttributesDataDto? attributes;
const CharacterDataDto({this.id, this.type, this.attributes});
factory CharacterDataDto.fromJson(Map<String, dynamic> json) => _$CharacterDataDtoFromJson(json);
}
@JsonSerializable(createToJson: false)
class CharacterAttributesDataDto {
final String? name;
final String? born;
final String? died;
final String? image;
const CharacterAttributesDataDto({this.name, this.born, this.died, this.image});
factory CharacterAttributesDataDto.fromJson(Map<String, dynamic> json) => _$CharacterAttributesDataDtoFromJson(json);
}
@JsonSerializable(createToJson: false)
class MetaDto {
final PaginationDto? pagination;
const MetaDto({this.pagination});
factory MetaDto.fromJson(Map<String, dynamic> json) => _$MetaDtoFromJson(json);
}
@JsonSerializable(createToJson: false)
class PaginationDto {
final int? current;
final int? next;
final int? last;
const PaginationDto({this.current, this.next, this.last});
factory PaginationDto.fromJson(Map<String, dynamic> json) => _$PaginationDtoFromJson(json);
}

View File

@ -1,49 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'characters_dto.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
CharactersDto _$CharactersDtoFromJson(Map<String, dynamic> json) =>
CharactersDto(
data: (json['data'] as List<dynamic>?)
?.map((e) => CharacterDataDto.fromJson(e as Map<String, dynamic>))
.toList(),
meta: json['meta'] == null
? null
: MetaDto.fromJson(json['meta'] as Map<String, dynamic>),
);
CharacterDataDto _$CharacterDataDtoFromJson(Map<String, dynamic> json) =>
CharacterDataDto(
id: json['id'] as String?,
type: json['type'] as String?,
attributes: json['attributes'] == null
? null
: CharacterAttributesDataDto.fromJson(
json['attributes'] as Map<String, dynamic>),
);
CharacterAttributesDataDto _$CharacterAttributesDataDtoFromJson(
Map<String, dynamic> 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<String, dynamic> json) => MetaDto(
pagination: json['pagination'] == null
? null
: PaginationDto.fromJson(json['pagination'] as Map<String, dynamic>),
);
PaginationDto _$PaginationDtoFromJson(Map<String, dynamic> json) =>
PaginationDto(
current: (json['current'] as num?)?.toInt(),
next: (json['next'] as num?)?.toInt(),
last: (json['last'] as num?)?.toInt(),
);

View File

@ -2,13 +2,14 @@ import 'package:json_annotation/json_annotation.dart';
part 'coins_dto.g.dart';
@JsonSerializable(createToJson: false)
class CoinsDto {
final List<CoinDataDto>? coins;
CoinsDto({this.coins});
factory CoinsDto.fromJson(Map<String, dynamic> json) => _$CoinsDtoFromJson(json);
factory CoinsDto.fromJson(List<dynamic> json) => CoinsDto(
coins: json.map((e) => CoinDataDto.fromJson(e as Map<String, dynamic>)).toList(),
);
}
@JsonSerializable(createToJson: false)
@ -16,7 +17,9 @@ class CoinDataDto {
final String? id;
final String? name;
final String? image;
@JsonKey(name: 'current_price')
final double? currentPrice;
@JsonKey(name: 'price_change_24h')
final double? priceChange24h;
CoinDataDto({

View File

@ -6,16 +6,10 @@ part of 'coins_dto.dart';
// JsonSerializableGenerator
// **************************************************************************
CoinsDto _$CoinsDtoFromJson(Map<String, dynamic> json) => CoinsDto(
coins: (json['coins'] as List<dynamic>?)
?.map((e) => CoinDataDto.fromJson(e as Map<String, dynamic>))
.toList(),
);
CoinDataDto _$CoinDataDtoFromJson(Map<String, dynamic> json) => CoinDataDto(
id: json['id'] as String?,
name: json['name'] as String?,
image: json['image'] as String?,
currentPrice: (json['currentPrice'] as num?)?.toDouble(),
priceChange24h: (json['priceChange24h'] as num?)?.toDouble(),
currentPrice: (json['current_price'] as num?)?.toDouble(),
priceChange24h: (json['price_change_24h'] as num?)?.toDouble(),
);

View File

@ -1,34 +0,0 @@
import 'package:flutter_android_app/data/dtos/characters_dto.dart';
import 'package:flutter_android_app/domain/models/card.dart';
import '../../domain/models/home.dart';
const _imagePlaceholder = 'https://gryazoveckij-r19.gosweb.gosuslugi.ru/netcat_files/460/2008/net_foto_muzh.jpg';
/*
extension CharactersDataDto on CharactersDto {
HomeData toDomain() => HomeData(
data: data?.map((e) => e.toDomain()).toList(),
nextPage: meta?.pagination?.next,
);
}
extension CharacterDataDtoToModel on CharacterDataDto {
CardData toDomain() => CardData(
title: attributes?.name ?? 'UNKNOWN',
imageUrl: attributes?.image ?? _imagePlaceholder,
description: _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'
: '';
}
*/

View File

@ -35,13 +35,13 @@ extension CoinDataDtoToModel on CoinDataDto {
retVal += _getLocalizedPrice(priceChange, locale?.localeName);
return '$retVal за последние 24 часа';
return '$retVal ${locale?.coinDataPriceChange}';
}
}
extension CoinsDtoToModel on CoinsDto {
HomeData toDomain(AppLocale? locale) => HomeData(
HomeData toDomain(AppLocale? locale, int currentPage) => HomeData(
data: coins?.map((e) => e.toDomain(locale)).toList(),
nextPage: 1,
nextPage: currentPage + 1,
);
}

View File

@ -1,5 +1,3 @@
import 'package:flutter/material.dart';
class CardData {
final String id;
final String title;

View File

@ -1,12 +1,13 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_android_app/components/extensions/context_x.dart';
import 'package:flutter_android_app/presentation/home_page/bloc/bloc.dart';
import 'package:flutter_android_app/presentation/home_page/home_page.dart';
import 'package:flutter_android_app/presentation/like_bloc/like_bloc.dart';
import 'package:flutter_android_app/presentation/locale_bloc/locale_bloc.dart';
import 'package:flutter_android_app/presentation/locale_bloc/locale_state.dart';
import 'package:flutter_android_app/repositories/mock_repository.dart';
import 'package:flutter_android_app/repositories/crypto_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'components/locale/l10n/app_localizations.dart';
@ -25,7 +26,7 @@ class MyApp extends StatelessWidget {
create: (context) => LikeBloc(),
child: BlocProvider<LocaleBloc>(
lazy: false,
create: (context) => LocaleBloc(Locale(Platform.localeName)),
create: (context) => LocaleBloc(Locale(_getLangCode(Platform.localeName))),
child: BlocBuilder<LocaleBloc, LocaleState>(
builder: (context, state) => MaterialApp(
title: 'Cryptocurrency Exchange App',
@ -37,13 +38,13 @@ class MyApp extends StatelessWidget {
useMaterial3: true,
),
debugShowCheckedModeBanner: false,
home: RepositoryProvider<MockRepository>(
home: RepositoryProvider<CryptoRepository>(
lazy: true,
create: (_) => MockRepository(),
create: (_) => CryptoRepository(),
child: BlocProvider<HomeBloc>(
lazy: false,
create: (context) => HomeBloc(context.read<MockRepository>()),
child: const MyHomePage(title: 'Cryptocurrency Exchange'),
create: (context) => HomeBloc(context.read<CryptoRepository>()),
child: const MyHomePage(),
),
),
),
@ -51,4 +52,9 @@ class MyApp extends StatelessWidget {
),
);
}
String _getLangCode(String fullLocaleName) {
int index = fullLocaleName.indexOf('_');
return index != -1 ? fullLocaleName.substring(0, index) : fullLocaleName;
}
}

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_android_app/components/extensions/context_x.dart';
import 'package:flutter_android_app/domain/models/card.dart';
class DetailsPage extends StatelessWidget {
@ -9,7 +10,10 @@ class DetailsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
appBar: AppBar(
title: Text(context.locale.detailsPageAppBarTitle),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [

View File

@ -23,6 +23,7 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
search: event.search,
page: event.nextPage ?? 1,
onError: (e) => error = e,
locale: event.locale,
);
if (event.nextPage != null) {

View File

@ -1,3 +1,5 @@
import 'package:flutter_android_app/components/locale/l10n/app_localizations.dart';
abstract class HomeEvent {
const HomeEvent();
}
@ -5,6 +7,7 @@ abstract class HomeEvent {
class HomeLoadDataEvent extends HomeEvent {
final String? search;
final int? nextPage;
final AppLocale? locale;
const HomeLoadDataEvent({this.search, this.nextPage});
const HomeLoadDataEvent({this.search, this.nextPage, this.locale});
}

View File

@ -8,7 +8,6 @@ class _Card extends StatelessWidget {
final String? imageUrl;
final String currentPrice;
final String priceChange;
final AppLocale locale;
final OnLikeCallback onLike;
final VoidCallback? onTap;
final bool isLiked;
@ -20,14 +19,12 @@ class _Card extends StatelessWidget {
this.imageUrl,
required this.currentPrice,
required this.priceChange,
required this.locale,
this.onLike,
this.onTap,
this.isLiked = false,
});
factory _Card.fromData(
AppLocale locale,
CardData data, {
OnLikeCallback onLike,
VoidCallback? onTap,
@ -38,7 +35,6 @@ class _Card extends StatelessWidget {
imageUrl: data.imageUrl,
currentPrice: data.currentPrice,
priceChange: data.priceChange,
locale: locale,
onLike: onLike,
onTap: onTap,
isLiked: isLiked,

View File

@ -1,7 +1,5 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_android_app/components/extensions/context_x.dart';
import 'package:flutter_android_app/components/locale/l10n/app_localizations.dart';
import 'package:flutter_android_app/components/utils/debounce.dart';
import 'package:flutter_android_app/domain/models/card.dart';
import 'package:flutter_android_app/presentation/details_page/details_page.dart';
@ -14,24 +12,19 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import '../common/svg_objects.dart';
import '../like_bloc/like_events.dart';
import '../locale_bloc/locale_bloc.dart';
import '../locale_bloc/locale_events.dart';
import '../locale_bloc/locale_state.dart';
import '../settings_page/settings_page.dart';
part 'card.dart';
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key, required this.title});
final String title;
const MyHomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(title),
title: Text(context.locale.mainAppBarTitle),
actions: [
Padding(
padding: const EdgeInsets.only(right: 12.0),
@ -43,7 +36,6 @@ class MyHomePage extends StatelessWidget {
),
),
],
),
body: const Body(),
);
@ -66,7 +58,7 @@ class _BodyState extends State<Body> {
SvgObjects.init();
WidgetsBinding.instance.addPostFrameCallback((_) {
context.read<HomeBloc>().add(const HomeLoadDataEvent());
context.read<HomeBloc>().add(HomeLoadDataEvent(locale: context.locale));
context.read<LikeBloc>().add(const LoadLikesEvent());
});
@ -90,23 +82,25 @@ class _BodyState extends State<Body> {
child: SearchBar(
controller: searchController,
onChanged: (search) {
Debounce.run(() => context.read<HomeBloc>().add(HomeLoadDataEvent(search: search)));
Debounce.run(() => context.read<HomeBloc>().add(HomeLoadDataEvent(search: search, locale: context.locale)));
},
leading: const Icon(Icons.search),
trailing: [
IconButton(
icon: const Icon(Icons.close),
onPressed: () { searchController.clear(); },
onPressed: () {
searchController.clear();
context.read<HomeBloc>().add(HomeLoadDataEvent(locale: context.locale));
},
),
],
hintText: context.locale.search,
hintText: context.locale.searchHint,
elevation: const WidgetStatePropertyAll(0.0),
padding: const WidgetStatePropertyAll(EdgeInsets.only(left: 18, right: 10)),
backgroundColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.secondaryContainer),
),
),
),
],
),
BlocBuilder<HomeBloc, HomeState>(
@ -129,7 +123,6 @@ class _BodyState extends State<Body> {
final data = state.data?.data?[index];
return data != null
? _Card.fromData(
context.locale,
data,
isLiked: likeState.likedIds?.contains(data.id) == true,
onLike: _onLike,
@ -164,7 +157,7 @@ class _BodyState extends State<Body> {
WidgetsBinding.instance.addPostFrameCallback((_) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(
'$title ${isLiked ? context.locale.liked : context.locale.unliked}',
'$title ${isLiked ? context.locale.addedToFavourite : context.locale.removedFromFavourite}',
style: Theme.of(context).textTheme.bodyLarge
),
backgroundColor: Colors.deepPurple.shade200,
@ -174,11 +167,13 @@ class _BodyState extends State<Body> {
}
void _navToDetails(BuildContext context, CardData data) {
Navigator.push(context, CupertinoPageRoute(builder: (context) => DetailsPage(data)));
Navigator.push(context, MaterialPageRoute<void>(
builder: (context) => DetailsPage(data)),
);
}
Future<void> _onRefresh() {
context.read<HomeBloc>().add(HomeLoadDataEvent(search: searchController.text));
context.read<HomeBloc>().add(HomeLoadDataEvent(search: searchController.text, locale: context.locale));
return Future.value(null);
}
@ -189,6 +184,7 @@ class _BodyState extends State<Body> {
bloc.add(HomeLoadDataEvent(
search: searchController.text,
nextPage: bloc.state.data?.nextPage,
locale: context.locale,
));
}
}

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_android_app/components/extensions/context_x.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../common/svg_objects.dart';
@ -12,14 +13,27 @@ class SettingsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Settings')),
body: Center(
child: GestureDetector(
appBar: AppBar(
title: Text(context.locale.settingsPageAppBarTitle),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: Padding(
padding: const EdgeInsets.only(left: 20, right: 16, top: 8),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${context.locale.settingsLanguage}:',
style: Theme.of(context).textTheme.titleMedium,
),
GestureDetector(
onTap: () => context.read<LocaleBloc>().add(const ChangeLocaleEvent()),
child: SizedBox.square(
dimension: 50,
child: Padding(
padding: const EdgeInsets.only(right: 12),
padding: const EdgeInsets.only(right: 0),
child: BlocBuilder<LocaleBloc, LocaleState>(
builder: (context, state) {
return state.currentLocale.languageCode == 'ru'
@ -29,6 +43,10 @@ class SettingsPage extends StatelessWidget {
),
),
),
],
),
],
),
),
);
}

View File

@ -32,12 +32,13 @@ class CryptoRepository extends ApiInterface {
}) async {
try {
Map<String, dynamic> queryParams = {
'x_cg_demo_api_key': _apiKey,
'vs_currency': _getCurrencyName(locale?.localeName),
'per_page': pageSize,
'page': page,
};
if (search != null) {
if (search != null && search.isNotEmpty) {
final Response<dynamic> searchResponse = await _dio.get<Map<dynamic, dynamic>>(
'$_baseUrl$_searchUrl',
queryParameters: {
@ -52,17 +53,22 @@ class CryptoRepository extends ApiInterface {
for (var coinData in searchCoinsDto.coins!) {
ids += coinData.id != null ? '${coinData.id},' : '';
}
if (ids.isEmpty) {
return HomeData();
}
queryParams['ids'] = ids;
}
}
final Response<dynamic> response = await _dio.get<Map<dynamic, dynamic>>(
final response = await _dio.get(
'$_baseUrl$_coinsDataUrl',
queryParameters: queryParams,
);
final CoinsDto dto = CoinsDto.fromJson(response.data as Map<String, dynamic>);
final HomeData data = dto.toDomain(locale);
final CoinsDto dto = CoinsDto.fromJson(response.data as List<dynamic>);
final HomeData data = dto.toDomain(locale, page);
return data;
} on DioException catch (e) {

View File

@ -1,46 +0,0 @@
import 'package:dio/dio.dart';
import 'package:flutter_android_app/data/dtos/characters_dto.dart';
import 'package:flutter_android_app/data/mappers/characters_mapper.dart';
import 'package:flutter_android_app/domain/models/home.dart';
import 'package:flutter_android_app/repositories/api_interface.dart';
import 'package:pretty_dio_logger/pretty_dio_logger.dart';
import '../components/utils/error_callback.dart';
/*
class PotterRepository extends ApiInterface {
static final Dio _dio = Dio()
..interceptors.add(PrettyDioLogger(
requestHeader: true,
requestBody: true,
));
static const String _baseUrl = 'https://api.potterdb.com';
@override
Future<HomeData?> loadData({
OnErrorCallback? onError,
String? q, int page = 1,
int pageSize = 20,
}) async {
try {
const String url = '$_baseUrl/v1/characters';
final Response<dynamic> response = await _dio.get<Map<dynamic, dynamic>>(
url,
queryParameters: {
'filter[name_cont]': q,
'page[number]': page,
'page[size]': pageSize,
},
);
final CharactersDto dto = CharactersDto.fromJson(response.data as Map<String, dynamic>);
final HomeData data = dto.toDomain();
return data;
} on DioException catch (e) {
onError?.call(e.error?.toString());
return null;
}
}
}
*/