Чисто для вовы пишу чтоб был комит

This commit is contained in:
Kirill 2024-12-20 12:17:48 +04:00
parent 168a9e287c
commit e674af146a
28 changed files with 474 additions and 268 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

View File

@ -2,8 +2,15 @@
"@@locale": "en",
"search": "Search",
"liked": "liked!",
"disliked": "disliked :(",
"complete": "Complete!",
"uncomplete": "Not complete :(",
"delete": "Delete",
"addtask": "Add Task",
"cancel": "Cancel",
"addbutton": "Add",
"nameplaceholder": "Enter task name",
"descriptionplaceholder": "Enter task description",
"imageplaceholder": "Select image",
"arbEnding": "Чтобы не забыть про отсутствие запятой :)"
}

View File

@ -2,8 +2,15 @@
"@@locale": "ru",
"search": "Поиск",
"liked": "понравился!",
"disliked": "Уже не нравится :(",
"complete": "Выполнено!",
"uncomplete": "Ещё не выполнена :(",
"delete": "Удалено",
"addtask": "Добавить задачу",
"cancel": "Отмена",
"addbutton": "Добавить",
"nameplaceholder": "Введите название задачи",
"descriptionplaceholder": "Введите описание задачи",
"imageplaceholder": "выберите изображение",
"arbEnding": "Чтобы не забыть про отсутствие запятой :)"
}

View File

@ -101,17 +101,59 @@ abstract class AppLocale {
/// **'Поиск'**
String get search;
/// No description provided for @liked.
/// No description provided for @complete.
///
/// In ru, this message translates to:
/// **'понравился!'**
String get liked;
/// **'Выполнено!'**
String get complete;
/// No description provided for @disliked.
/// No description provided for @uncomplete.
///
/// In ru, this message translates to:
/// **'Уже не нравится :('**
String get disliked;
/// **'Ещё не выполнена :('**
String get uncomplete;
/// No description provided for @delete.
///
/// In ru, this message translates to:
/// **'Удалено'**
String get delete;
/// No description provided for @addtask.
///
/// In ru, this message translates to:
/// **'Добавить задачу'**
String get addtask;
/// No description provided for @cancel.
///
/// In ru, this message translates to:
/// **'Отмена'**
String get cancel;
/// No description provided for @addbutton.
///
/// In ru, this message translates to:
/// **'Добавить'**
String get addbutton;
/// No description provided for @nameplaceholder.
///
/// In ru, this message translates to:
/// **'Введите название задачи'**
String get nameplaceholder;
/// No description provided for @descriptionplaceholder.
///
/// In ru, this message translates to:
/// **'Введите описание задачи'**
String get descriptionplaceholder;
/// No description provided for @imageplaceholder.
///
/// In ru, this message translates to:
/// **'выберите изображение'**
String get imageplaceholder;
/// No description provided for @arbEnding.
///

View File

@ -10,10 +10,31 @@ class AppLocaleEn extends AppLocale {
String get search => 'Search';
@override
String get liked => 'liked!';
String get complete => 'Complete!';
@override
String get disliked => 'disliked :(';
String get uncomplete => 'Not complete :(';
@override
String get delete => 'Delete';
@override
String get addtask => 'Add Task';
@override
String get cancel => 'Cancel';
@override
String get addbutton => 'Add';
@override
String get nameplaceholder => 'Enter task name';
@override
String get descriptionplaceholder => 'Enter task description';
@override
String get imageplaceholder => 'Select image';
@override
String get arbEnding => 'Чтобы не забыть про отсутствие запятой :)';

View File

@ -10,10 +10,31 @@ class AppLocaleRu extends AppLocale {
String get search => 'Поиск';
@override
String get liked => 'понравился!';
String get complete => 'Выполнено!';
@override
String get disliked => 'Уже не нравится :(';
String get uncomplete => 'Ещё не выполнена :(';
@override
String get delete => 'Удалено';
@override
String get addtask => 'Добавить задачу';
@override
String get cancel => 'Отмена';
@override
String get addbutton => 'Добавить';
@override
String get nameplaceholder => 'Введите название задачи';
@override
String get descriptionplaceholder => 'Введите описание задачи';
@override
String get imageplaceholder => 'выберите изображение';
@override
String get arbEnding => 'Чтобы не забыть про отсутствие запятой :)';

33
lib/data/dtos/Task.dart Normal file
View File

@ -0,0 +1,33 @@
class Task {
final int id;
final String name;
final String description;
final String date;
final String imageUrl;
Task({
required this.id,
required this.name,
required this.description,
required this.date,
required this.imageUrl
});
Map<String, dynamic> toJson() {
return {
'name': name,
'description': description,
'date': date,
'imageUrl': imageUrl
};
}
factory Task.fromJson(Map<String, dynamic> json) {
return Task(
id: json['id'],
name: json['name'],
description: json['description'],
date: json['date'],
imageUrl: json['imageUrl']
);
}
}

View File

@ -1,60 +0,0 @@
import 'package:json_annotation/json_annotation.dart';
part 'films_dto.g.dart';
@JsonSerializable(createToJson: false)
class FilmsDto {
final List<FilmDataDto>? data;
final MetaDto? meta;
const FilmsDto({
this.data,
this.meta,
});
factory FilmsDto.fromJson(Map<String, dynamic> json) => _$FilmsDtoFromJson(json);
}
@JsonSerializable(createToJson: false)
class FilmDataDto {
final String? id;
final String? type;
final FilmAttributesDataDto? attributes;
const FilmDataDto({this.id, this.type, this.attributes});
factory FilmDataDto.fromJson(Map<String, dynamic> json) => _$FilmDataDtoFromJson(json);
}
@JsonSerializable(createToJson: false)
class FilmAttributesDataDto {
final String? title;
final DateTime? release_date;
final String? budget;
final String? poster;
const FilmAttributesDataDto({this.title, this.release_date, this.budget, this.poster});
factory FilmAttributesDataDto.fromJson(Map<String, dynamic> json) =>
_$FilmAttributesDataDtoFromJson(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,38 +0,0 @@
import 'package:testlab/data/dtos/films_dto.dart';
import 'package:testlab/domain/models/card.dart';
import 'package:testlab/domain/models/home.dart';
import 'package:intl/intl.dart';
const _imagePlaceholder =
'https://ralfvanveen.com/wp-content/uploads/2021/06/Placeholder-_-Glossary.svg';
extension FilmsDtoToModel on FilmsDto {
HomeData toDomain() => HomeData(
data: data?.map((e) => e.toDomain()).toList(),
nextPage: meta?.pagination?.next,
);
}
extension FilmDataDtoToModel on FilmDataDto {
CardData toDomain() => CardData(
attributes?.title ?? 'UNKNOWN',
imageUrl: attributes?.poster ?? _imagePlaceholder,
descriptionText:
_makeDescriptionText(formatDateToString(attributes?.release_date), attributes?.budget),
id: id,
);
String formatDateToString(DateTime? date) {
return date != null ? DateFormat('yyyy-MM-dd').format(date) : 'UNKNOWN DATE';
}
String _makeDescriptionText(String? release_date, String? budget) {
return release_date != null && budget != null
? '$release_date \n $budget'
: release_date != null
? 'release: $release_date'
: budget != null
? 'budget: $budget'
: '';
}
}

View File

@ -3,5 +3,5 @@ import 'package:testlab/domain/models/home.dart';
typedef OnErrorCallback = void Function(String? error);
abstract class ApiInterface {
Future<HomeData?> loadData({OnErrorCallback? onError});
Future<HomeData?> loadData(String name);
}

View File

@ -5,7 +5,7 @@ import 'package:testlab/domain/models/home.dart';
class MockRepository extends ApiInterface {
@override
Future<HomeData?> loadData({OnErrorCallback? onError}) async {
Future<HomeData?> loadData(String name) async {
return HomeData(data: [
CardData(
'Mobile dev',

View File

@ -1,44 +0,0 @@
import 'package:dio/dio.dart';
import 'package:testlab/data/dtos/films_dto.dart';
import 'package:testlab/data/mapper/films_mapper.dart';
import 'package:testlab/data/repositories/api_interface.dart';
import 'package:pretty_dio_logger/pretty_dio_logger.dart';
import 'package:testlab/domain/models/home.dart';
class PotterFilmsRepository 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 = 5,
}) async {
try {
const String url = '$_baseUrl/v1/movies';
final Response<dynamic> response = await _dio.get<Map<dynamic, dynamic>>(
url,
queryParameters: {
'filter[title_cont]': q,
'page[number]': page,
'page[size]': pageSize,
},
);
final FilmsDto dto = FilmsDto.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;
}
}
}

View File

@ -0,0 +1,82 @@
import 'dart:convert';
import 'dart:async';
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import '../../domain/models/card.dart';
import '../../domain/models/home.dart';
import '../dtos/Task.dart';
import 'api_interface.dart';
class repository extends ApiInterface {
Future<HomeData?> loadData(String name) async {
final response = await http.get(Uri.parse('http://192.168.1.67:8080/api/1.0/task/name/$name'));
//final response = await http.get(Uri.parse('http://192.168.112.64:8080/api/1.0/task/name/$name'));
if (response.statusCode == 200) {
List jsonResponse = json.decode(utf8.decode(response.bodyBytes));
List<CardData> cardDataList = jsonResponse.map<CardData>((taskJson) {
Task task = Task.fromJson(taskJson);
return CardData(
task.name,
descriptionText: '${task.description}\nДата: ${task.date}',
imageUrl: task.imageUrl,
icon: Icons.close,
id: task.id.toString(),
);
}).toList();
return HomeData(data: cardDataList);
} else {
throw Exception('Не удалось загрузить задачи');
}
}
Future<void> addTask(String taskName, String taskDescription, String? taskImageUrl) async {
DateTime now = DateTime.now();
String formattedDate = DateFormat('dd.MM.yyyy').format(now);
String defaultImageUrl = "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTOwRConBYl2t6L8QMOAQqa5FDmPB_bg7EnGA&s";
String finalImageUrl = taskImageUrl ?? defaultImageUrl;
final response = await http.post(
Uri.parse('http://192.168.1.67:8080/api/1.0/task'),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(Task(
id: 0,
name: taskName,
description: taskDescription,
date: formattedDate,
imageUrl: finalImageUrl,
).toJson()),
);
if (response.statusCode == 200) {
} else {
throw Exception('Ошибка при добавлении задачи');
}
}
Future<void> deleteTask(int taskId) async {
final response = await http.delete(
Uri.parse('http://192.168.1.67:8080/api/1.0/task/$taskId'),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
);
if (response.statusCode == 200) {
// Задача успешно удалена
} else {
throw Exception('Ошибка при удалении задачи');
}
}
}

View File

@ -2,7 +2,6 @@ import 'card.dart';
class HomeData {
final List<CardData>? data;
final int? nextPage;
HomeData({this.data, this.nextPage});
HomeData({this.data});
}

View File

@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
import 'package:testlab/presentaition/home_page/home_page.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:testlab/presentaition/home_page/bloc/bloc.dart';
import 'data/repositories/potter_films_repository.dart';
import 'data/repositories/repository.dart';
import 'package:testlab/presentaition/like_bloc/like_bloc.dart';
import 'package:testlab/presentaition/locale_bloc/locale_bloc.dart';
import 'package:testlab/presentaition/locale_bloc/locale_state.dart';
@ -34,15 +34,15 @@ class MyApp extends StatelessWidget {
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: RepositoryProvider<PotterFilmsRepository>(
home: RepositoryProvider<repository>(
lazy: true,
create: (_) => PotterFilmsRepository(),
create: (_) => repository(),
child: BlocProvider<LikeBloc>(
lazy: false,
create: (context) => LikeBloc(),
child: BlocProvider<HomeBloc>(
lazy: false,
create: (context) => HomeBloc(context.read<PotterFilmsRepository>()),
create: (context) => HomeBloc(context.read<repository>()),
child: const MyHomePage(title: 'Фирсов Кирилл Алексеевич'),
),
),

View File

@ -1,39 +1,39 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:testlab/data/repositories/potter_films_repository.dart';
import 'package:testlab/presentaition/home_page/bloc/events.dart';
import 'package:testlab/presentaition/home_page/bloc/state.dart';
import '../../../data/repositories/repository.dart';
class HomeBloc extends Bloc<HomeEvent, HomeState> {
final PotterFilmsRepository repo;
final repository repo;
HomeBloc(this.repo) : super(const HomeState()) {
on<HomeLoadDataEvent>(_onLoadData);
on<HomeAddTaskEvent>(_onAddTask);
}
Future<void> _onLoadData(HomeLoadDataEvent event, Emitter<HomeState> emit) async {
if (event.nextPage == null) {
emit(state.copyWith(isLoading: true));
} else {
emit(state.copyWith(isPaginationLoading: true));
}
emit(state.copyWith(isLoading: true));
String? error;
final data = await repo.loadData(
q: event.search,
page: event.nextPage ?? 1,
onError: (e) => error = e,
);
final data = await repo.loadData(event.search.toString());
if (event.nextPage != null) {
data?.data?.insertAll(0, state.data?.data ?? []);
}
//data?.data?.insertAll(0, state.data?.data ?? []);
emit(state.copyWith(
isLoading: false,
isPaginationLoading: false,
data: data,
error: error,
));
}
Future<void> _onAddTask(HomeAddTaskEvent eventTask, Emitter<HomeState> emit) async {
emit(state.copyWith(isLoading: true));
String? error;
await repo.addTask(eventTask.name.toString(), eventTask.name.toString(), eventTask.name.toString(),);
}
}

View File

@ -4,7 +4,12 @@ abstract class HomeEvent {
class HomeLoadDataEvent extends HomeEvent {
final String? search;
final int? nextPage;
const HomeLoadDataEvent({this.search, this.nextPage});
const HomeLoadDataEvent({this.search});
}
class HomeAddTaskEvent extends HomeEvent {
final String? name;
const HomeAddTaskEvent({this.name});
}

View File

@ -9,13 +9,11 @@ part 'state.g.dart';
class HomeState extends Equatable {
final HomeData? data;
final bool isLoading;
final bool isPaginationLoading;
final String? error;
const HomeState({
this.data,
this.isLoading = false,
this.isPaginationLoading = false,
this.error,
});
@ -23,7 +21,6 @@ class HomeState extends Equatable {
List<Object?> get props => [
data,
isLoading,
isPaginationLoading,
error,
];
}

View File

@ -1,6 +1,7 @@
part of 'home_page.dart';
typedef OnLikeCallback = void Function(String? id, String title, bool isLiked)?;
typedef OnDeleteCallback = void Function(String? id, String title)?;
class _Card extends StatelessWidget {
final String text;
@ -8,6 +9,7 @@ class _Card extends StatelessWidget {
final IconData icon;
final String? imageUrl;
final OnLikeCallback onLike;
final OnDeleteCallback onDelete;
final VoidCallback? onTap;
final String? id;
final bool isLiked;
@ -18,6 +20,7 @@ class _Card extends StatelessWidget {
required this.descriptionText,
this.imageUrl,
this.onLike,
this.onDelete,
this.onTap,
this.id,
this.isLiked = false,
@ -26,6 +29,7 @@ class _Card extends StatelessWidget {
factory _Card.fromData(
CardData data, {
OnLikeCallback onLike,
OnDeleteCallback onDelete,
VoidCallback? onTap,
bool isLiked = false,
}) =>
@ -35,18 +39,12 @@ class _Card extends StatelessWidget {
icon: data.icon,
imageUrl: data.imageUrl,
onLike: onLike,
onDelete: onDelete,
onTap: onTap,
isLiked: isLiked,
id: data.id,
);
/*@override
State<_Card> createState() => _CardState();
}
class _CardState extends State<_Card> {
bool isLiked = false;*/
@override
Widget build(BuildContext context) {
return GestureDetector(
@ -89,19 +87,6 @@ class _CardState extends State<_Card> {
Expanded(
child: Stack(
children: [
Align(
alignment: Alignment.bottomLeft,
child: Padding(
padding: const EdgeInsets.only(
left: 8.0,
right: 8.0,
bottom: 16.0,
),
child: Icon(
icon,
),
),
),
Padding(
padding: const EdgeInsets.only(left: 3.0, top: 3.0),
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
@ -124,30 +109,38 @@ class _CardState extends State<_Card> {
),
],
)),
Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: const EdgeInsets.only(
right: 16.0,
bottom: 16.0,
),
child: GestureDetector(
onTap: () => onLike?.call(id, text, isLiked),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: isLiked
? const Icon(
Icons.thumb_up,
color: Colors.redAccent,
key: ValueKey<int>(0),
)
: const Icon(
Icons.thumb_up_off_alt,
key: ValueKey<int>(1),
),
Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
onTap: () => onDelete?.call(id, text),
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Icon(
icon,
),
),
),
),
Padding(
padding: const EdgeInsets.all(10.0),
child: GestureDetector(
onTap: () => onLike?.call(id, text, isLiked),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: isLiked
? const Icon(
Icons.check_box_rounded,
color: Colors.redAccent,
key: ValueKey<int>(0),
)
: const Icon(
Icons.check_box,
key: ValueKey<int>(1),
),
),
),
),
],
),
],
),

View File

@ -8,6 +8,7 @@ import 'package:testlab/presentaition/home_page/bloc/bloc.dart';
import 'package:testlab/presentaition/home_page/bloc/events.dart';
import 'package:testlab/presentaition/home_page/bloc/state.dart';
import 'package:testlab/domain/models/card.dart';
import '../../data/repositories/repository.dart';
import '../common/svg_objects.dart';
import '../like_bloc/like_bloc.dart';
import '../like_bloc/like_event.dart';
@ -58,7 +59,6 @@ class Body extends StatefulWidget {
class BodyState extends State<Body> {
final searchController = TextEditingController();
final scrollController = ScrollController();
@override
void initState() {
@ -67,27 +67,12 @@ class BodyState extends State<Body> {
context.read<HomeBloc>().add(const HomeLoadDataEvent());
context.read<LikeBloc>().add(const LoadLikesEvent());
});
scrollController.addListener(_onNextPageListener);
super.initState();
}
void _onNextPageListener() {
if (scrollController.offset > scrollController.position.maxScrollExtent) {
final bloc = context.read<HomeBloc>();
if (!bloc.state.isPaginationLoading) {
bloc.add(HomeLoadDataEvent(
search: searchController.text,
nextPage: bloc.state.data?.nextPage,
));
}
}
}
@override
void dispose() {
searchController.dispose();
scrollController.dispose();
super.dispose();
}
@ -129,7 +114,7 @@ class BodyState extends State<Body> {
CupertinoIcons.clear,
color: Colors.white,
),
placeholder: 'Search...',
placeholder: context.locale.search,
placeholderStyle: const TextStyle(
color: Colors.white54,
),
@ -167,37 +152,51 @@ class BodyState extends State<Body> {
return Expanded(
child: RefreshIndicator(
onRefresh: _onRefresh,
child: ListView.builder(
controller: scrollController,
padding: EdgeInsets.zero,
itemCount: state.data?.data?.length ?? 0,
itemBuilder: (context, index) {
final data = state.data?.data?[index];
return data != null
? _Card.fromData(
data,
onLike: _onLike,
isLiked: likeState.likedIds?.contains(data.id) == true,
onTap: () => _navToDetails(context, data),
)
: const SizedBox.shrink();
},
),
child: Stack(
children: [
ListView.builder(
padding: EdgeInsets.zero,
itemCount: state.data?.data?.length ?? 0,
itemBuilder: (context, index) {
final data = state.data?.data?[index];
return data != null
? _Card.fromData(
data,
onDelete: _onDelete,
onLike: _onLike,
isLiked: likeState.likedIds?.contains(data.id) == true,
onTap: () => _navToDetails(context, data),
)
: const SizedBox.shrink();
},
),
Positioned(
bottom: 16,
right: 16,
child: FloatingActionButton(
onPressed: () {
_showAddTaskDialog(context);
},
tooltip: 'Добавить задачу',
child: Icon(Icons.add),
),
),
]
)
),
);
},
),
),
BlocBuilder<HomeBloc, HomeState>(
builder: (context, state) => state.isPaginationLoading
? const CircularProgressIndicator()
: const SizedBox.shrink(),
),
],
),
);
}
Future<void> _onRefresh() {
context.read<HomeBloc>().add(HomeLoadDataEvent(search: searchController.text));
return Future.value(null);
@ -214,7 +213,7 @@ class BodyState extends State<Body> {
WidgetsBinding.instance.addPostFrameCallback((_) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(
'$title ${isLiked ? context.locale.liked : context.locale.disliked}',
'$title ${isLiked ? context.locale.complete : context.locale.uncomplete}',
style: const TextStyle(
color: Colors.white,
fontSize: 18,
@ -227,10 +226,142 @@ class BodyState extends State<Body> {
});
}
void _showDeleteBar(BuildContext context, String title) {
WidgetsBinding.instance.addPostFrameCallback((_) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(
'$title ${context.locale.delete}',
style: const TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
backgroundColor: Colors.black,
duration: const Duration(seconds: 1),
));
});
}
final repository repo = new repository();
Future<void> _showAddTaskDialog(BuildContext context) async {
final TextEditingController _taskNameController = TextEditingController();
final TextEditingController _taskDescriptionController = TextEditingController();
String? selectedImageUrl;
List<String> imageUrls = [
'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQtsv2O-lLpxro9HH9CUejcymOOnGdQJwjasg&s',
'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcT--ZMDGsa_8y9r9u0RjAn576ajsVVfsOIcDA&s',
'https://img-webcalypt.ru/storage/memes/YhGTdxW7HnbmhqAd37XjsMCTj1fHUW7AADagYMDbmyU9VBEhDBbq4d3XNGDWo34FibwtjyeVsyxudpzeViSmHdJ7e6C50tLbZnkZZAP81AV7aM0R8VSi4YEnqgcZ1EtE-md.jpeg',
];
return showDialog<void>(
context: context,
barrierDismissible: false, // Разрешить закрытие диалога по нажатию вне его
builder: (BuildContext context) {
return AlertDialog(
title: Text(context.locale.addtask),
content: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextField(
controller: _taskNameController,
decoration: InputDecoration(hintText: context.locale.nameplaceholder),
),
SizedBox(height: 16.0),
TextField(
controller: _taskDescriptionController,
decoration: InputDecoration(hintText: context.locale.descriptionplaceholder),
maxLines: 5,
minLines: 1,
),
SizedBox(height: 16.0),
if (selectedImageUrl != null)
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Image.network(
selectedImageUrl!,
width: 70,
height: 70,
fit: BoxFit.cover,
),
),
SizedBox(height: 16.0),
DropdownButton<String>(
hint: Text(context.locale.imageplaceholder),
value: selectedImageUrl,
isExpanded: true,
items: imageUrls.map((String url) {
return DropdownMenuItem<String>(
value: url,
child: Row(
children: [
Image.network(url, width: 50, height: 50),
SizedBox(width: 10),
Expanded(child: Text(url, overflow: TextOverflow.ellipsis)),
],
),
);
}).toList(),
onChanged: (String? value) {
setState(() {
selectedImageUrl = value;
});
},
),
],
),
actions: <Widget>[
TextButton(
child: Text(context.locale.cancel),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: Text(context.locale.addbutton),
onPressed: () async {
await repo.addTask(
_taskNameController.text,
_taskDescriptionController.text,
selectedImageUrl
);
Navigator.of(context).pop();
_onRefresh();
},
),
],
);
},
);
}
void _onLike(String? id, String title, bool isLiked) {
if (id != null) {
context.read<LikeBloc>().add(ChangeLikeEvent(id));
_showSnackBar(context, title, !isLiked);
}
}
Future<void> _onDelete(String? id, String title) async {
if (id != null) {
try {
int taskId = int.parse(id);
await repo.deleteTask(taskId);
_onRefresh();
_showDeleteBar(context, title);
} catch (e) {
print("Ошибка преобразования id в int: $e");
}
}
}
}

8
local.properties Normal file
View File

@ -0,0 +1,8 @@
## This file must *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
#Mon Dec 09 15:56:48 GMT+04:00 2024
sdk.dir=C\:\\Users\\Admin\\AppData\\Local\\Android\\Sdk

View File

@ -12,4 +12,7 @@ format:
res:
fgen --output lib/components/resources.g.dart --no-watch --no-preview; \
make format
make format
loc:
flutter gen l10n

View File

@ -71,7 +71,6 @@ dev_dependencies:
flutter_icons:
android: "ic_launcher"
ios: true
image_path: "assets/launcher.jpg"
min_sdk_android: 21