что-не очень
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 8.7 KiB |
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 42 KiB |
BIN
assets/launcher.png
Normal file
After Width: | Height: | Size: 202 KiB |
6
l10n.yaml
Normal file
@ -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
|
9
l10n/app_en.arb
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"@@locale": "en",
|
||||||
|
|
||||||
|
"search": "Search",
|
||||||
|
"liked": "Like :)",
|
||||||
|
"disliked": "Dislike :(",
|
||||||
|
|
||||||
|
"arbEnding": ""
|
||||||
|
}
|
9
l10n/app_ru.arb
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"@@locale": "ru",
|
||||||
|
|
||||||
|
"search": "Поиск",
|
||||||
|
"liked": "Круто :)",
|
||||||
|
"disliked": "Не круто :с",
|
||||||
|
|
||||||
|
"arbEnding": ""
|
||||||
|
}
|
6
lib/components/extensions/context_x.dart
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import '../locale/l10n/app_locale.dart';
|
||||||
|
|
||||||
|
extension LocalContextX on BuildContext {
|
||||||
|
AppLocale get locale => AppLocale.of(this)!;
|
||||||
|
}
|
@ -11,10 +11,10 @@ class Debounce {
|
|||||||
static Timer? _timer;
|
static Timer? _timer;
|
||||||
|
|
||||||
static void run(
|
static void run(
|
||||||
VoidCallback action, {
|
VoidCallback action, {
|
||||||
Duration delay = const Duration(milliseconds: 1000),
|
Duration delay = const Duration(milliseconds: 1000),
|
||||||
}) {
|
}) {
|
||||||
_timer?.cancel();
|
_timer?.cancel();
|
||||||
_timer = Timer(delay, action);
|
_timer = Timer(delay, action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,9 @@ class QuoteDataDto {
|
|||||||
final String? body;
|
final String? body;
|
||||||
final String? author;
|
final String? author;
|
||||||
final String? imageUrl;
|
final String? imageUrl;
|
||||||
|
final String? id;
|
||||||
|
|
||||||
const QuoteDataDto({this.body, this.author, this.imageUrl});
|
const QuoteDataDto({this.body, this.author, this.imageUrl, this.id});
|
||||||
|
|
||||||
factory QuoteDataDto.fromJson(Map<String, dynamic> json) => _$QuoteDataDtoFromJson(json);
|
factory QuoteDataDto.fromJson(Map<String, dynamic> json) => _$QuoteDataDtoFromJson(json);
|
||||||
}
|
}
|
||||||
|
@ -2,13 +2,13 @@ import '../../domain/quote.dart';
|
|||||||
import '/data/dtos/quotes_dto.dart';
|
import '/data/dtos/quotes_dto.dart';
|
||||||
import '/presentation/home_page/home_page.dart';
|
import '/presentation/home_page/home_page.dart';
|
||||||
|
|
||||||
const _imagePlaceholder =
|
const _imagePlaceholder = 'https://cdn-icons-png.flaticon.com/128/17818/17818874.png';
|
||||||
'https://cdn-icons-png.flaticon.com/128/17818/17818874.png';
|
|
||||||
|
|
||||||
extension QuoteDtoToModel on QuoteDataDto {
|
extension QuoteDtoToModel on QuoteDataDto {
|
||||||
Quote toDomain() => Quote(
|
Quote toDomain() => Quote(
|
||||||
body ?? 'Без текста',
|
body ?? 'Без текста',
|
||||||
author ?? 'Неизвестный автор',
|
author ?? 'Неизвестный автор',
|
||||||
imageUrl ?? _imagePlaceholder,
|
imageUrl ?? _imagePlaceholder,
|
||||||
);
|
id ?? "",
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
@ -24,12 +24,10 @@ class QuotesRepository extends ApiInterface {
|
|||||||
try {
|
try {
|
||||||
final Map<String, dynamic> queryParams = {};
|
final Map<String, dynamic> queryParams = {};
|
||||||
|
|
||||||
|
|
||||||
if (q != null && q.isNotEmpty) {
|
if (q != null && q.isNotEmpty) {
|
||||||
queryParams['filter'] = q;
|
queryParams['filter'] = q;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (author != null && author.isNotEmpty) {
|
if (author != null && author.isNotEmpty) {
|
||||||
queryParams['type'] = 'author';
|
queryParams['type'] = 'author';
|
||||||
queryParams['filter'] = author;
|
queryParams['filter'] = author;
|
||||||
@ -51,4 +49,3 @@ class QuotesRepository extends ApiInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,11 +2,13 @@ class Quote {
|
|||||||
final String text;
|
final String text;
|
||||||
final String author;
|
final String author;
|
||||||
final String imagePath;
|
final String imagePath;
|
||||||
|
final String id;
|
||||||
bool isFavorite;
|
bool isFavorite;
|
||||||
|
|
||||||
Quote(this.text, this.author, this.imagePath, [this.isFavorite = false]);
|
Quote(this.text, this.author, this.imagePath, this.id,[this.isFavorite = false]);
|
||||||
|
|
||||||
void toggleFavorite() {
|
bool toggleFavorite() {
|
||||||
isFavorite = !isFavorite;
|
isFavorite = !isFavorite;
|
||||||
|
return isFavorite;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:pmu_flutter_labs/presentation/home_page/bloc/events.dart';
|
||||||
|
import 'package:pmu_flutter_labs/presentation/like_bloc/like_bloc.dart';
|
||||||
|
import 'components/locale/l10n/app_locale.dart';
|
||||||
import 'data/repositories/quotes_repository.dart';
|
import 'data/repositories/quotes_repository.dart';
|
||||||
import '/presentation/home_page/bloc/bloc.dart';
|
import '/presentation/home_page/bloc/bloc.dart';
|
||||||
import '/presentation/home_page/home_page.dart';
|
import '/presentation/home_page/home_page.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
}
|
}
|
||||||
@ -15,6 +17,8 @@ class MyApp extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
title: 'Цитаты',
|
title: 'Цитаты',
|
||||||
|
localizationsDelegates: AppLocale.localizationsDelegates,
|
||||||
|
supportedLocales: AppLocale.supportedLocales,
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
|
colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
@ -22,12 +26,16 @@ class MyApp extends StatelessWidget {
|
|||||||
home: RepositoryProvider<QuotesRepository>(
|
home: RepositoryProvider<QuotesRepository>(
|
||||||
lazy: true,
|
lazy: true,
|
||||||
create: (_) => QuotesRepository(),
|
create: (_) => QuotesRepository(),
|
||||||
child: BlocProvider<HomeBloc>(
|
child: BlocProvider(
|
||||||
lazy: false,
|
create: (context) => LikeBloc(), // Add LikeBloc here
|
||||||
create: (context) => HomeBloc(context.read<QuotesRepository>()),
|
child: BlocProvider(
|
||||||
child: const MyHomePage(title: "Цитаты",),
|
create: (context) => HomeBloc(context.read<QuotesRepository>())
|
||||||
|
..add(const HomeLoadDataEvent()), // Ensure initial load
|
||||||
|
child: const MyHomePage(title: "Цитаты"),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
34
lib/presentation/common/svg_objects.dart
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import '/components/resources.g.dart';
|
||||||
|
|
||||||
|
abstract class SvgObjects {
|
||||||
|
static void init() {
|
||||||
|
final pics = <String>[
|
||||||
|
R.ASSETS_SVG_RU_SVG,
|
||||||
|
R.ASSETS_SVG_US_SVG,
|
||||||
|
];
|
||||||
|
for (final String p in pics) {
|
||||||
|
final loader = SvgAssetLoader(p);
|
||||||
|
svg.cache.putIfAbsent(loader.cacheKey(null), () => loader.loadBytes(null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SvgRu extends StatelessWidget {
|
||||||
|
const SvgRu({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SvgPicture.asset(R.ASSETS_SVG_RU_SVG);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SvgUk extends StatelessWidget {
|
||||||
|
const SvgUk({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SvgPicture.asset(R.ASSETS_SVG_US_SVG);
|
||||||
|
}
|
||||||
|
}
|
@ -30,4 +30,4 @@ class ErrorDialog extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,11 @@ import 'package:flutter/material.dart';
|
|||||||
import 'error_dialog.dart';
|
import 'error_dialog.dart';
|
||||||
|
|
||||||
void showErrorDialog(
|
void showErrorDialog(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
required String error,
|
required String error,
|
||||||
}) {
|
}) {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) => ErrorDialog(error),
|
builder: (_) => ErrorDialog(error),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -24,12 +24,14 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
|
|||||||
} else {
|
} else {
|
||||||
// Если автор указан, фильтруем данные по тексту цитаты
|
// Если автор указан, фильтруем данные по тексту цитаты
|
||||||
try {
|
try {
|
||||||
final currentData = await repo.loadData(q:search, author: author,
|
final currentData = await repo.loadData(
|
||||||
|
q: search,
|
||||||
|
author: author,
|
||||||
);
|
);
|
||||||
final filteredData = currentData?.where((quote) {
|
final filteredData = currentData?.where((quote) {
|
||||||
final matchesAuthor = quote.author.toLowerCase().contains(author.toLowerCase());
|
final matchesAuthor = quote.author.toLowerCase().contains(author.toLowerCase());
|
||||||
final matchesSearch = search.isEmpty ||
|
final matchesSearch =
|
||||||
quote.text.toLowerCase().contains(search.toLowerCase());
|
search.isEmpty || quote.text.toLowerCase().contains(search.toLowerCase());
|
||||||
return matchesAuthor && matchesSearch;
|
return matchesAuthor && matchesSearch;
|
||||||
}).toList();
|
}).toList();
|
||||||
emit(state.copyWith(data: Future.value(filteredData)));
|
emit(state.copyWith(data: Future.value(filteredData)));
|
||||||
@ -39,13 +41,8 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Future<void> _onRefreshData(HomeRefreshEvent event, Emitter<HomeState> emit) async {
|
Future<void> _onRefreshData(HomeRefreshEvent event, Emitter<HomeState> emit) async {
|
||||||
add(HomeLoadDataEvent(search: event.search, author: event.author)); // Просто перезапускаем загрузку
|
add(HomeLoadDataEvent(
|
||||||
|
search: event.search, author: event.author)); // Просто перезапускаем загрузку
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ import 'package:equatable/equatable.dart';
|
|||||||
import '../../../domain/quote.dart';
|
import '../../../domain/quote.dart';
|
||||||
import '../home_page.dart';
|
import '../home_page.dart';
|
||||||
|
|
||||||
|
|
||||||
class HomeState extends Equatable {
|
class HomeState extends Equatable {
|
||||||
final Future<List<Quote>>? data; // Изменено
|
final Future<List<Quote>>? data; // Изменено
|
||||||
|
|
||||||
|
@ -1,55 +1,60 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import '../../domain/quote.dart';
|
import '../../domain/quote.dart';
|
||||||
|
import '../like_bloc/like_bloc.dart';
|
||||||
|
import '../like_bloc/like_state.dart';
|
||||||
import 'home_page.dart';
|
import 'home_page.dart';
|
||||||
|
|
||||||
class QuoteCard extends StatelessWidget {
|
class QuoteCard extends StatelessWidget {
|
||||||
final Quote quote;
|
final Quote quote;
|
||||||
final VoidCallback onFavoriteToggle;
|
final VoidCallback onFavoriteToggle;
|
||||||
|
|
||||||
const QuoteCard({
|
const QuoteCard({Key? key, required this.quote, required this.onFavoriteToggle}) : super(key: key);
|
||||||
super.key,
|
|
||||||
required this.quote,
|
|
||||||
required this.onFavoriteToggle,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Card(
|
return Card(
|
||||||
margin: const EdgeInsets.symmetric(
|
margin: const EdgeInsets.all(8.0),
|
||||||
vertical: 8.0,
|
child: Padding(
|
||||||
horizontal: 10.0,
|
padding: const EdgeInsets.all(16.0),
|
||||||
),
|
child: Column(
|
||||||
child: ListTile(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
contentPadding: const EdgeInsets.all(8.0),
|
children: [
|
||||||
leading: SizedBox(
|
Text(
|
||||||
width: 50.0,
|
quote.text,
|
||||||
child: Image.network(
|
style: const TextStyle(fontSize: 18.0),
|
||||||
quote.imagePath,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
errorBuilder: (_, __, ___) =>
|
|
||||||
const Icon(Icons.error, color: Colors.red),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
quote.text,
|
|
||||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w500),
|
|
||||||
),
|
|
||||||
subtitle: Text('- ${quote.author}'),
|
|
||||||
trailing: IconButton(
|
|
||||||
icon: Icon(
|
|
||||||
quote.isFavorite ? Icons.favorite : Icons.favorite_border,
|
|
||||||
color: quote.isFavorite ? Colors.red : null,
|
|
||||||
),
|
|
||||||
onPressed: onFavoriteToggle,
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => QuoteDetailScreen(quote: quote),
|
|
||||||
),
|
),
|
||||||
);
|
const SizedBox(height: 8.0),
|
||||||
},
|
Text(
|
||||||
|
'- ${quote.author}',
|
||||||
|
style: const TextStyle(fontStyle: FontStyle.italic, fontSize: 16.0),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8.0),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
ElevatedButton.icon(
|
||||||
|
onPressed: onFavoriteToggle,
|
||||||
|
icon: BlocBuilder<LikeBloc, LikeState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
final isLiked = state.likedIds?.contains(quote.id) ?? false;
|
||||||
|
return Icon(
|
||||||
|
isLiked ? Icons.favorite : Icons.favorite_border,
|
||||||
|
color: isLiked ? Colors.red : Colors.grey,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
label: BlocBuilder<LikeBloc, LikeState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
final isLiked = state.likedIds?.contains(quote.id) ?? false;
|
||||||
|
return Text(isLiked ? 'Убрать из избранного' : 'Добавить в избранное');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -75,8 +80,7 @@ class QuoteDetailScreen extends StatelessWidget {
|
|||||||
Image.network(
|
Image.network(
|
||||||
quote.imagePath,
|
quote.imagePath,
|
||||||
height: 150,
|
height: 150,
|
||||||
errorBuilder: (_, __, ___) =>
|
errorBuilder: (_, __, ___) => const Icon(Icons.error, color: Colors.red),
|
||||||
const Icon(Icons.error, color: Colors.red),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
Text(
|
Text(
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:pmu_flutter_labs/components/extensions/context_x.dart';
|
||||||
import '../../components/utils/debounce.dart';
|
import '../../components/utils/debounce.dart';
|
||||||
import '../../domain/quote.dart';
|
import '../../domain/quote.dart';
|
||||||
|
import '../common/svg_objects.dart';
|
||||||
|
import '../like_bloc/like_bloc.dart';
|
||||||
|
import '../like_bloc/like_event.dart';
|
||||||
import '/data/repositories/quotes_repository.dart';
|
import '/data/repositories/quotes_repository.dart';
|
||||||
import '/presentation/home_page/bloc/bloc.dart';
|
import '/presentation/home_page/bloc/bloc.dart';
|
||||||
import '/presentation/home_page/bloc/events.dart';
|
import '/presentation/home_page/bloc/events.dart';
|
||||||
@ -40,11 +44,15 @@ class _HomePageBodyState extends State<_HomePageBody> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
SvgObjects.init();
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
searchController.addListener(() {
|
searchController.addListener(() {
|
||||||
Debounce.run(() {
|
Debounce.run(() {
|
||||||
context.read<HomeBloc>().add(HomeLoadDataEvent(search: searchController.text));
|
context.read<HomeBloc>().add(HomeLoadDataEvent(
|
||||||
|
search: searchController.text,
|
||||||
|
author: authorController.text,
|
||||||
|
));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -56,6 +64,18 @@ class _HomePageBodyState extends State<_HomePageBody> {
|
|||||||
));
|
));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
context.read<LikeBloc>().add(const LoadLikesEvent());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showSnackbar(BuildContext context, bool isLiked) {
|
||||||
|
final message = isLiked ? context.locale.liked : context.locale.disliked;
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(message),
|
||||||
|
duration: const Duration(seconds: 2),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -119,9 +139,7 @@ class _HomePageBodyState extends State<_HomePageBody> {
|
|||||||
return QuoteCard(
|
return QuoteCard(
|
||||||
quote: quote,
|
quote: quote,
|
||||||
onFavoriteToggle: () {
|
onFavoriteToggle: () {
|
||||||
setState(() {
|
context.read<LikeBloc>().add(ChangeLikeEvent(quote.id));
|
||||||
quote.toggleFavorite();
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -136,3 +154,4 @@ class _HomePageBodyState extends State<_HomePageBody> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
36
lib/presentation/like_bloc/like_bloc.dart
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
//import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
import 'like_event.dart';
|
||||||
|
import 'like_state.dart';
|
||||||
|
|
||||||
|
const String _likedPrefsKey = 'liked';
|
||||||
|
|
||||||
|
class LikeBloc extends Bloc<LikeEvent, LikeState> {
|
||||||
|
LikeBloc() : super(const LikeState(likedIds: [])) {
|
||||||
|
on<ChangeLikeEvent>(_onChangeLike);
|
||||||
|
on<LoadLikesEvent>(_onLoadLikes);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onLoadLikes(LoadLikesEvent event, Emitter<LikeState> emit) async {
|
||||||
|
//final prefs = await SharedPreferences.getInstance();
|
||||||
|
//final data = prefs.getStringList(_likedPrefsKey);
|
||||||
|
|
||||||
|
//emit(state.copyWith(likedIds: data));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onChangeLike(ChangeLikeEvent event, Emitter<LikeState> emit) async {
|
||||||
|
final updatedList = List<String>.from(state.likedIds ?? []);
|
||||||
|
|
||||||
|
if (updatedList.contains(event.id)) {
|
||||||
|
updatedList.remove(event.id);
|
||||||
|
} else {
|
||||||
|
updatedList.add(event.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
//final prefs = await SharedPreferences.getInstance();
|
||||||
|
//prefs.setStringList(_likedPrefsKey, updatedList);
|
||||||
|
|
||||||
|
emit(state.copyWith(likedIds: updatedList));
|
||||||
|
}
|
||||||
|
}
|
13
lib/presentation/like_bloc/like_event.dart
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
abstract class LikeEvent {
|
||||||
|
const LikeEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
class LoadLikesEvent extends LikeEvent {
|
||||||
|
const LoadLikesEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChangeLikeEvent extends LikeEvent {
|
||||||
|
final String id;
|
||||||
|
|
||||||
|
const ChangeLikeEvent(this.id);
|
||||||
|
}
|
14
lib/presentation/like_bloc/like_state.dart
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import 'package:copy_with_extension/copy_with_extension.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
part 'like_state.g.dart';
|
||||||
|
|
||||||
|
@CopyWith()
|
||||||
|
class LikeState extends Equatable {
|
||||||
|
final List<String>? likedIds;
|
||||||
|
|
||||||
|
const LikeState({required this.likedIds});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [likedIds];
|
||||||
|
}
|
14
makefile
Normal 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;\
|
||||||
|
dart format . --line-length 100
|
||||||
|
loc:
|
||||||
|
flutter gen-l10n
|
||||||
|
dart format . --line-length 100
|
52
pubspec.lock
@ -26,10 +26,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: archive
|
name: archive
|
||||||
sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d
|
sha256: "08064924cbf0ab88280a0c3f60db9dd24fec693927e725ecb176f16c629d1cb8"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.6.1"
|
version: "4.0.1"
|
||||||
args:
|
args:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -254,6 +254,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.1"
|
version: "1.3.1"
|
||||||
|
ffi:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: ffi
|
||||||
|
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.3"
|
||||||
file:
|
file:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -353,10 +361,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: http_multi_server
|
name: http_multi_server
|
||||||
sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b"
|
sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.2.1"
|
version: "3.2.2"
|
||||||
http_parser:
|
http_parser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -369,10 +377,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image
|
name: image
|
||||||
sha256: f31d52537dc417fdcde36088fdf11d191026fd5e4fae742491ebd40e5a8bea7d
|
sha256: "20842a5ad1555be624c314b0c0cc0566e8ece412f61e859a42efeb6d4101a26c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.3.0"
|
version: "4.5.0"
|
||||||
intl:
|
intl:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -505,10 +513,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: package_config
|
name: package_config
|
||||||
sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd"
|
sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "2.1.1"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -541,6 +549,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.1"
|
version: "1.5.1"
|
||||||
|
posix:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: posix
|
||||||
|
sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.1"
|
||||||
pretty_dio_logger:
|
pretty_dio_logger:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -561,10 +577,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: pub_semver
|
name: pub_semver
|
||||||
sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
|
sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
version: "2.1.5"
|
||||||
pubspec_parse:
|
pubspec_parse:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -606,10 +622,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: source_helper
|
name: source_helper
|
||||||
sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd"
|
sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.4"
|
version: "1.3.5"
|
||||||
source_span:
|
source_span:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -638,10 +654,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: stream_transform
|
name: stream_transform
|
||||||
sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f"
|
sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "2.1.1"
|
||||||
string_scanner:
|
string_scanner:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -670,10 +686,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: timing
|
name: timing
|
||||||
sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32"
|
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.1"
|
version: "1.0.2"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -726,10 +742,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: watcher
|
name: watcher
|
||||||
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
|
sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.1"
|
||||||
web:
|
web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
10
pubspec.yaml
@ -7,6 +7,7 @@ environment:
|
|||||||
sdk: ^3.5.4
|
sdk: ^3.5.4
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
# shared_preferences: ^2.3.3
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
@ -29,9 +30,10 @@ dependencies:
|
|||||||
sdk: flutter
|
sdk: flutter
|
||||||
intl: ^0.19.0
|
intl: ^0.19.0
|
||||||
|
|
||||||
#shared_preferences: ^2.0.0
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_lints: ^5.0.0
|
flutter_lints: ^5.0.0
|
||||||
@ -41,9 +43,15 @@ dev_dependencies:
|
|||||||
build_runner: ^2.4.9
|
build_runner: ^2.4.9
|
||||||
json_serializable: ^6.7.1
|
json_serializable: ^6.7.1
|
||||||
|
|
||||||
|
flutter_icons:
|
||||||
|
android: "ic_launcher"
|
||||||
|
image_path: "assets/launcher.png"
|
||||||
|
min_sdk_android: 21
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
generate: true
|
generate: true
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
|
||||||
|
assets:
|
||||||
|
- assets/svg/
|
||||||
|
|
||||||
|