LabWork7 icon is done

This commit is contained in:
2025-12-10 17:17:57 +04:00
parent 437f43fd59
commit b990453479
30 changed files with 164 additions and 110 deletions

View File

@@ -2,7 +2,7 @@
<application
android:label="flutter_project"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
android:icon="@mipmap/ic_cosmetics">
<activity
android:name=".MainActivity"
android:exported="true"

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

BIN
assets/cosmetics.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

1
assets/svg/ru.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 9 6" width="900" height="600"><path fill="#fff" d="M0 0h9v3H0z"/><path fill="#d52b1e" d="M0 3h9v3H0z"/><path fill="#0039a6" d="M0 2h9v2H0z"/></svg>

After

Width:  |  Height:  |  Size: 200 B

9
assets/svg/uk.svg Normal file
View File

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 30" width="1000" height="600">
<clipPath id="t">
<path d="M25,15h25v15zv15h-25zh-25v-15zv-15h25z"/>
</clipPath>
<path d="M0,0v30h50v-30z" fill="#012169"/>
<path d="M0,0 50,30M50,0 0,30" stroke="#fff" stroke-width="6"/>
<path d="M0,0 50,30M50,0 0,30" clip-path="url(#t)" stroke="#C8102E" stroke-width="4"/>
<path d="M-1 11h22v-12h8v12h22v8h-22v12h-8v-12h-22z" fill="#C8102E" stroke="#FFF" stroke-width="2"/>
</svg>

After

Width:  |  Height:  |  Size: 477 B

View File

@@ -6,11 +6,8 @@ class Debounce {
Debounce._();
static final Debounce _instance = Debounce._();
static Timer? _timer;
static void run(
VoidCallback action, {
Duration delay = const Duration(milliseconds: 500),
}) {
static void run(VoidCallback action, {Duration delay = const Duration(milliseconds: 500)}) {
_timer?.cancel();
_timer = Timer(delay, action);
}
}
}

View File

@@ -23,4 +23,4 @@ class CosmecticDataDto {
const CosmecticDataDto({this.id, this.productType, this.name, this.imageLink});
factory CosmecticDataDto.fromJson(Map<String, dynamic> json) => _$CosmecticDataDtoFromJson(json);
}
}

View File

@@ -12,10 +12,9 @@ CosmeticsDto _$CosmeticsDtoFromJson(Map<String, dynamic> json) => CosmeticsDto(
.toList(),
);
CosmecticDataDto _$CosmecticDataDtoFromJson(Map<String, dynamic> json) =>
CosmecticDataDto(
id: (json['id'] as num?)?.toInt(),
productType: json['product_type'] as String?,
name: json['name'] as String?,
imageLink: json['image_link'] as String?,
);
CosmecticDataDto _$CosmecticDataDtoFromJson(Map<String, dynamic> json) => CosmecticDataDto(
id: (json['id'] as num?)?.toInt(),
productType: json['product_type'] as String?,
name: json['name'] as String?,
imageLink: json['image_link'] as String?,
);

View File

@@ -15,9 +15,7 @@ class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
theme: ThemeData(colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple)),
home: RepositoryProvider<CosmeticsRepository>(
lazy: true,
create: (_) => CosmeticsRepository(),

View File

@@ -19,10 +19,7 @@ class DetailsPage extends StatelessWidget {
),
Padding(
padding: const EdgeInsets.only(bottom: 4.0),
child: Text(
data.name,
style: Theme.of(context).textTheme.headlineLarge,
),
child: Text(data.name, style: Theme.of(context).textTheme.headlineLarge),
),
Text(data.description, style: Theme.of(context).textTheme.bodyLarge),
],

View File

@@ -3,7 +3,6 @@ import 'package:flutter_project/presentation/home_page/bloc/events.dart';
import 'package:flutter_project/presentation/home_page/bloc/state.dart';
import 'package:flutter_project/repositories/cosmetic_repository.dart';
class HomeBloc extends Bloc<HomeEvent, HomeState> {
final CosmeticsRepository repo;
@@ -11,13 +10,13 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
on<HomeLoadDataEvent>(_onLoadData);
}
void _onLoadData(HomeLoadDataEvent event, Emitter<HomeState> emit) async {
emit(state.copyWith(isLoading: true));
void _onLoadData(HomeLoadDataEvent event, Emitter<HomeState> emit) async {
emit(state.copyWith(isLoading: true));
String? error;
String? error;
final data = await repo.loadData(q: event.search, onError: (e) => error = e?.toString(),);
final data = await repo.loadData(q: event.search, onError: (e) => error = e?.toString());
emit(state.copyWith(isLoading: false, data: data, error: error));
}
}
}

View File

@@ -3,8 +3,6 @@ abstract class HomeEvent {
}
class HomeLoadDataEvent extends HomeEvent {
final String? search;
const HomeLoadDataEvent({this.search}); // событие для загрузки данных
}
}

View File

@@ -5,17 +5,14 @@ import '../../../domain/models/card.dart';
part 'state.g.dart';
@CopyWith()
class HomeState extends Equatable { // для сравнения состояний
class HomeState extends Equatable {
// для сравнения состояний
final List<CardData>? data;
final bool isLoading;
final String? error;
const HomeState({
this.data,
this.isLoading = false,
this.error,
});
const HomeState({this.data, this.isLoading = false, this.error});
@override
List<Object?> get props => [data, isLoading, error];
}
}

View File

@@ -21,11 +21,7 @@ class _Card extends StatefulWidget {
this.onTap,
});
factory _Card.fromData(
CardData data, {
OnLikeCallBack onLike,
VoidCallback? onTap,
}) => _Card(
factory _Card.fromData(CardData data, {OnLikeCallBack onLike, VoidCallback? onTap}) => _Card(
data.name,
description: data.description,
imageUrl: data.imageUrl,
@@ -80,15 +76,14 @@ class _CardState extends State<_Card> {
child: Container(
decoration: const BoxDecoration(
color: Colors.deepPurple,
borderRadius: BorderRadius.only(
topRight: Radius.circular(20),
),
borderRadius: BorderRadius.only(topRight: Radius.circular(20)),
),
padding: const EdgeInsets.fromLTRB(8, 2, 8, 2),
child: Text(
'Cкидка 5%',
style: Theme.of(context).textTheme.bodyMedium
?.copyWith(color: Colors.white),
style: Theme.of(
context,
).textTheme.bodyMedium?.copyWith(color: Colors.white),
),
),
),
@@ -107,10 +102,7 @@ class _CardState extends State<_Card> {
style: Theme.of(context).textTheme.headlineLarge,
maxLines: 3,
),
Text(
widget.description,
style: Theme.of(context).textTheme.bodyLarge,
),
Text(widget.description, style: Theme.of(context).textTheme.bodyLarge),
],
),
),
@@ -125,11 +117,7 @@ class _CardState extends State<_Card> {
Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: const EdgeInsets.only(
left: 8,
right: 16,
bottom: 16,
),
padding: const EdgeInsets.only(left: 8, right: 16, bottom: 16),
child: GestureDetector(
onTap: () {
setState(() {
@@ -145,10 +133,7 @@ class _CardState extends State<_Card> {
color: Colors.redAccent,
key: ValueKey<int>(0),
)
: const Icon(
Icons.favorite_border,
key: ValueKey<int>(1),
),
: const Icon(Icons.favorite_border, key: ValueKey<int>(1)),
),
),
),

View File

@@ -5,7 +5,6 @@ import 'package:flutter_project/domain/models/card.dart';
import 'package:flutter_project/presentation/details_page/details_page.dart';
import 'package:flutter_project/presentation/home_page/bloc/state.dart';
import '../../components/utils/debounce.dart';
import 'bloc/bloc.dart';
import 'bloc/events.dart';
@@ -28,10 +27,7 @@ class _MyHomePageState extends State<MyHomePage> {
WidgetsBinding.instance.addPostFrameCallback((_) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Приветствуем!',
style: Theme.of(context).textTheme.bodyLarge,
),
content: Text('Приветствуем!', style: Theme.of(context).textTheme.bodyLarge),
backgroundColor: Colors.purple,
duration: const Duration(seconds: 1),
),
@@ -86,38 +82,38 @@ class _BodyState extends State<Body> {
child: CupertinoSearchTextField(
controller: searchController,
onChanged: (search) {
Debounce.run(() => context.read<HomeBloc>().add(HomeLoadDataEvent(search: search)));
Debounce.run(() => context.read<HomeBloc>().add(HomeLoadDataEvent(search: search)));
},
),
),
BlocBuilder<HomeBloc, HomeState>(
builder: (context, state) => state.error != null
? Text(
state.error ?? '',
style: Theme.of(context).textTheme.headlineSmall?.copyWith(color: Colors.red),
)
:state.isLoading
? const CircularProgressIndicator()
? Text(
state.error ?? '',
style: Theme.of(context).textTheme.headlineSmall?.copyWith(color: Colors.red),
)
: state.isLoading
? const CircularProgressIndicator()
: Expanded(
child: RefreshIndicator(
onRefresh: _onRefresh,
child: ListView.builder(
padding: EdgeInsets.zero,
itemCount: state.data?.length ?? 0,
itemBuilder: (context, index) {
final data = state.data?[index];
return data != null
? _Card.fromData(
data,
onLike: (title, isLiked) =>
_showSnackBar(context, title, isLiked),
onTap: () => _navToDetails(context, data),
)
: const SizedBox.shrink();
},
),
)
),
child: RefreshIndicator(
onRefresh: _onRefresh,
child: ListView.builder(
padding: EdgeInsets.zero,
itemCount: state.data?.length ?? 0,
itemBuilder: (context, index) {
final data = state.data?[index];
return data != null
? _Card.fromData(
data,
onLike: (title, isLiked) =>
_showSnackBar(context, title, isLiked),
onTap: () => _navToDetails(context, data),
)
: const SizedBox.shrink();
},
),
),
),
),
],
),
@@ -125,10 +121,7 @@ class _BodyState extends State<Body> {
}
void _navToDetails(BuildContext context, CardData data) {
Navigator.push(
context,
CupertinoPageRoute(builder: (context) => DetailsPage(data)),
);
Navigator.push(context, CupertinoPageRoute(builder: (context) => DetailsPage(data)));
}
void _showSnackBar(BuildContext context, String title, bool isLiked) {
@@ -145,12 +138,9 @@ class _BodyState extends State<Body> {
);
});
}
Future<void> _onRefresh() {
context.read<HomeBloc>().add(
HomeLoadDataEvent(
search: searchController.text,
),
);
context.read<HomeBloc>().add(HomeLoadDataEvent(search: searchController.text));
return Future.value(null);
}
}
}

View File

@@ -4,4 +4,4 @@ typedef OnErrorCallback = void Function(Object? error);
abstract class ApiInterface {
Future<List<CardData>?> loadData();
}
}

View File

@@ -7,10 +7,7 @@ import 'api_interface.dart';
class CosmeticsRepository implements ApiInterface {
static final Dio _dio = Dio()
..interceptors.add(PrettyDioLogger(
requestHeader: true,
requestBody: true,
));
..interceptors.add(PrettyDioLogger(requestHeader: true, requestBody: true));
static const String _baseUrl = 'https://makeup-api.herokuapp.com';
@@ -21,8 +18,8 @@ class CosmeticsRepository implements ApiInterface {
final Response<dynamic> response = await _dio.get<List<dynamic>>(
url,
queryParameters: {
'brand': 'maybelline', // фиксированный бренд
if (q != null && q.isNotEmpty) // тип продукта из поиска
'brand': 'maybelline', // фиксированный бренд
if (q != null && q.isNotEmpty) // тип продукта из поиска
'product_type': q.toLowerCase(),
},
);

View File

@@ -17,7 +17,7 @@ class MockRepository extends ApiInterface {
'Darling',
description: 'Тушь для ресниц',
imageUrl:
'https://avatars.mds.yandex.net/get-vertis-journal/4469561/7_Byuti_nabory.png_1757063983772/845x845',
'https://avatars.mds.yandex.net/get-vertis-journal/4469561/7_Byuti_nabory.png_1757063983772/845x845',
icon: Icons.shopping_cart,
price: 1922,
),
@@ -30,4 +30,4 @@ class MockRepository extends ApiInterface {
),
];
}
}
}

14
makefile Normal file
View File

@@ -0,0 +1,14 @@
gen:
flutter pub run build_runner build --delete-conflicting-outputs
icon:
flutter pub run flutter_launcher_icons:main
init_res:
dart pub global activate flutter_asset_generator
format:
dart format . --line-length 100
res:
fgen --output lib/components/resources.g.dart --no-watch --no-preview; \
make format
loc:
flutter gen-l10n; \
make format

View File

@@ -17,6 +17,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "8.4.1"
archive:
dependency: transitive
description:
name: archive
sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd"
url: "https://pub.dev"
source: hosted
version: "4.0.7"
args:
dependency: transitive
description:
@@ -113,6 +121,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.4"
cli_util:
dependency: transitive
description:
name: cli_util
sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
url: "https://pub.dev"
source: hosted
version: "0.4.2"
clock:
dependency: transitive
description:
@@ -217,6 +233,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.3"
ffi:
dependency: transitive
description:
name: ffi
sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
file:
dependency: transitive
description:
@@ -246,6 +270,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "9.1.1"
flutter_launcher_icons:
dependency: "direct dev"
description:
name: flutter_launcher_icons
sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea"
url: "https://pub.dev"
source: hosted
version: "0.13.1"
flutter_lints:
dependency: "direct dev"
description:
@@ -291,6 +323,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.1.2"
image:
dependency: transitive
description:
name: image
sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928"
url: "https://pub.dev"
source: hosted
version: "4.5.4"
io:
dependency: transitive
description:
@@ -411,6 +451,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.9.1"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1"
url: "https://pub.dev"
source: hosted
version: "7.0.1"
pool:
dependency: transitive
description:
@@ -419,6 +467,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.5.2"
posix:
dependency: transitive
description:
name: posix
sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61"
url: "https://pub.dev"
source: hosted
version: "6.0.3"
pretty_dio_logger:
dependency: "direct main"
description:
@@ -600,6 +656,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.3"
xml:
dependency: transitive
description:
name: xml
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
url: "https://pub.dev"
source: hosted
version: "6.6.1"
yaml:
dependency: transitive
description:

View File

@@ -24,12 +24,21 @@ dev_dependencies:
flutter_test:
sdk: flutter
flutter_launcher_icons: ^0.13.1
build_runner: ^2.4.9
json_serializable: ^6.7.1
copy_with_extension_gen: ^10.0.1
flutter_lints: ^5.0.0
flutter_icons:
android: "ic_cosmetics"
image_path: "assets/cosmetics.png"
min_sdk_android: 21
flutter:
uses-material-design: true
assets:
- assets/cosmetics.png