diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 3f74614..0000000 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 122e71f..0000000 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 4e139fc..0000000 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 8519d99..0000000 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 04c8bcd..0000000 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/assets/launcher.png b/assets/launcher.png deleted file mode 100644 index cc0a5ff..0000000 Binary files a/assets/launcher.png and /dev/null differ diff --git a/l10n/app_en.arb b/l10n/app_en.arb index 12efc72..432e2d2 100644 --- a/l10n/app_en.arb +++ b/l10n/app_en.arb @@ -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": "Чтобы не забыть про отсутствие запятой :)" } \ No newline at end of file diff --git a/l10n/app_ru.arb b/l10n/app_ru.arb index 2c4995d..5f160dd 100644 --- a/l10n/app_ru.arb +++ b/l10n/app_ru.arb @@ -2,8 +2,15 @@ "@@locale": "ru", "search": "Поиск", - "liked": "понравился!", - "disliked": "Уже не нравится :(", + "complete": "Выполнено!", + "uncomplete": "Ещё не выполнена :(", + "delete": "Удалено", + "addtask": "Добавить задачу", + "cancel": "Отмена", + "addbutton": "Добавить", + "nameplaceholder": "Введите название задачи", + "descriptionplaceholder": "Введите описание задачи", + "imageplaceholder": "выберите изображение", "arbEnding": "Чтобы не забыть про отсутствие запятой :)" } \ No newline at end of file diff --git a/lib/components/locale/l10n/app_locale.dart b/lib/components/locale/l10n/app_locale.dart index b67c8f3..6afc3b1 100644 --- a/lib/components/locale/l10n/app_locale.dart +++ b/lib/components/locale/l10n/app_locale.dart @@ -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. /// diff --git a/lib/components/locale/l10n/app_locale_en.dart b/lib/components/locale/l10n/app_locale_en.dart index 599548c..c258c23 100644 --- a/lib/components/locale/l10n/app_locale_en.dart +++ b/lib/components/locale/l10n/app_locale_en.dart @@ -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 => 'Чтобы не забыть про отсутствие запятой :)'; diff --git a/lib/components/locale/l10n/app_locale_ru.dart b/lib/components/locale/l10n/app_locale_ru.dart index 8bf7ec0..8cd721e 100644 --- a/lib/components/locale/l10n/app_locale_ru.dart +++ b/lib/components/locale/l10n/app_locale_ru.dart @@ -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 => 'Чтобы не забыть про отсутствие запятой :)'; diff --git a/lib/data/dtos/Task.dart b/lib/data/dtos/Task.dart new file mode 100644 index 0000000..d66be83 --- /dev/null +++ b/lib/data/dtos/Task.dart @@ -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 toJson() { + return { + 'name': name, + 'description': description, + 'date': date, + 'imageUrl': imageUrl + }; + } + factory Task.fromJson(Map json) { + return Task( + id: json['id'], + name: json['name'], + description: json['description'], + date: json['date'], + imageUrl: json['imageUrl'] + ); + } +} \ No newline at end of file diff --git a/lib/data/dtos/films_dto.dart b/lib/data/dtos/films_dto.dart deleted file mode 100644 index 009399c..0000000 --- a/lib/data/dtos/films_dto.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; - -part 'films_dto.g.dart'; - -@JsonSerializable(createToJson: false) -class FilmsDto { - final List? data; - final MetaDto? meta; - - const FilmsDto({ - this.data, - this.meta, - }); - - factory FilmsDto.fromJson(Map 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 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 json) => - _$FilmAttributesDataDtoFromJson(json); -} - -@JsonSerializable(createToJson: false) -class MetaDto { - final PaginationDto? pagination; - - const MetaDto({this.pagination}); - - factory MetaDto.fromJson(Map 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 json) => _$PaginationDtoFromJson(json); -} diff --git a/lib/data/mapper/films_mapper.dart b/lib/data/mapper/films_mapper.dart deleted file mode 100644 index 22b23e8..0000000 --- a/lib/data/mapper/films_mapper.dart +++ /dev/null @@ -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' - : ''; - } -} diff --git a/lib/data/repositories/api_interface.dart b/lib/data/repositories/api_interface.dart index 6760b2c..e03b911 100644 --- a/lib/data/repositories/api_interface.dart +++ b/lib/data/repositories/api_interface.dart @@ -3,5 +3,5 @@ import 'package:testlab/domain/models/home.dart'; typedef OnErrorCallback = void Function(String? error); abstract class ApiInterface { - Future loadData({OnErrorCallback? onError}); + Future loadData(String name); } diff --git a/lib/data/repositories/mock_repository.dart b/lib/data/repositories/mock_repository.dart index c83abc8..3b62fb8 100644 --- a/lib/data/repositories/mock_repository.dart +++ b/lib/data/repositories/mock_repository.dart @@ -5,7 +5,7 @@ import 'package:testlab/domain/models/home.dart'; class MockRepository extends ApiInterface { @override - Future loadData({OnErrorCallback? onError}) async { + Future loadData(String name) async { return HomeData(data: [ CardData( 'Mobile dev', diff --git a/lib/data/repositories/potter_films_repository.dart b/lib/data/repositories/potter_films_repository.dart deleted file mode 100644 index b4b0d99..0000000 --- a/lib/data/repositories/potter_films_repository.dart +++ /dev/null @@ -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 loadData({ - OnErrorCallback? onError, - String? q, - int page = 1, - int pageSize = 5, - }) async { - try { - const String url = '$_baseUrl/v1/movies'; - - final Response response = await _dio.get>( - url, - queryParameters: { - 'filter[title_cont]': q, - 'page[number]': page, - 'page[size]': pageSize, - }, - ); - - final FilmsDto dto = FilmsDto.fromJson(response.data as Map); - final HomeData data = dto.toDomain(); - return data; - } on DioException catch (e) { - onError?.call(e.error?.toString()); - return null; - } - } -} diff --git a/lib/data/repositories/repository.dart b/lib/data/repositories/repository.dart new file mode 100644 index 0000000..6cb4d17 --- /dev/null +++ b/lib/data/repositories/repository.dart @@ -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 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 cardDataList = jsonResponse.map((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 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: { + '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 deleteTask(int taskId) async { + final response = await http.delete( + Uri.parse('http://192.168.1.67:8080/api/1.0/task/$taskId'), + headers: { + 'Content-Type': 'application/json; charset=UTF-8', + }, + ); + + if (response.statusCode == 200) { + // Задача успешно удалена + } else { + throw Exception('Ошибка при удалении задачи'); + } + } +} \ No newline at end of file diff --git a/lib/domain/models/home.dart b/lib/domain/models/home.dart index 2f0ba57..a1bd389 100644 --- a/lib/domain/models/home.dart +++ b/lib/domain/models/home.dart @@ -2,7 +2,6 @@ import 'card.dart'; class HomeData { final List? data; - final int? nextPage; - HomeData({this.data, this.nextPage}); + HomeData({this.data}); } diff --git a/lib/main.dart b/lib/main.dart index b5bf78a..02b794b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -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( + home: RepositoryProvider( lazy: true, - create: (_) => PotterFilmsRepository(), + create: (_) => repository(), child: BlocProvider( lazy: false, create: (context) => LikeBloc(), child: BlocProvider( lazy: false, - create: (context) => HomeBloc(context.read()), + create: (context) => HomeBloc(context.read()), child: const MyHomePage(title: 'Фирсов Кирилл Алексеевич'), ), ), diff --git a/lib/presentaition/home_page/bloc/bloc.dart b/lib/presentaition/home_page/bloc/bloc.dart index 3d899c4..9c673e4 100644 --- a/lib/presentaition/home_page/bloc/bloc.dart +++ b/lib/presentaition/home_page/bloc/bloc.dart @@ -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 { - final PotterFilmsRepository repo; + final repository repo; HomeBloc(this.repo) : super(const HomeState()) { on(_onLoadData); + on(_onAddTask); } Future _onLoadData(HomeLoadDataEvent event, Emitter 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 _onAddTask(HomeAddTaskEvent eventTask, Emitter emit) async { + emit(state.copyWith(isLoading: true)); + + String? error; + + await repo.addTask(eventTask.name.toString(), eventTask.name.toString(), eventTask.name.toString(),); + } } diff --git a/lib/presentaition/home_page/bloc/events.dart b/lib/presentaition/home_page/bloc/events.dart index cefba70..2f8272e 100644 --- a/lib/presentaition/home_page/bloc/events.dart +++ b/lib/presentaition/home_page/bloc/events.dart @@ -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}); } diff --git a/lib/presentaition/home_page/bloc/state.dart b/lib/presentaition/home_page/bloc/state.dart index 2990318..d415d75 100644 --- a/lib/presentaition/home_page/bloc/state.dart +++ b/lib/presentaition/home_page/bloc/state.dart @@ -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 get props => [ data, isLoading, - isPaginationLoading, error, ]; } diff --git a/lib/presentaition/home_page/card.dart b/lib/presentaition/home_page/card.dart index 8769405..9a10929 100644 --- a/lib/presentaition/home_page/card.dart +++ b/lib/presentaition/home_page/card.dart @@ -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(0), - ) - : const Icon( - Icons.thumb_up_off_alt, - key: ValueKey(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(0), + ) + : const Icon( + Icons.check_box, + key: ValueKey(1), + ), + ), + ), + ), + ], ), ], ), diff --git a/lib/presentaition/home_page/home_page.dart b/lib/presentaition/home_page/home_page.dart index d805c37..0fc0dd5 100644 --- a/lib/presentaition/home_page/home_page.dart +++ b/lib/presentaition/home_page/home_page.dart @@ -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 { final searchController = TextEditingController(); - final scrollController = ScrollController(); @override void initState() { @@ -67,27 +67,12 @@ class BodyState extends State { context.read().add(const HomeLoadDataEvent()); context.read().add(const LoadLikesEvent()); }); - - scrollController.addListener(_onNextPageListener); super.initState(); } - void _onNextPageListener() { - if (scrollController.offset > scrollController.position.maxScrollExtent) { - final bloc = context.read(); - 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 { 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 { 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( - builder: (context, state) => state.isPaginationLoading - ? const CircularProgressIndicator() - : const SizedBox.shrink(), - ), ], ), ); } + + + + Future _onRefresh() { context.read().add(HomeLoadDataEvent(search: searchController.text)); return Future.value(null); @@ -214,7 +213,7 @@ class BodyState extends State { 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 { }); } + 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 _showAddTaskDialog(BuildContext context) async { + + final TextEditingController _taskNameController = TextEditingController(); + final TextEditingController _taskDescriptionController = TextEditingController(); + String? selectedImageUrl; + + List 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( + context: context, + barrierDismissible: false, // Разрешить закрытие диалога по нажатию вне его + builder: (BuildContext context) { + return AlertDialog( + title: Text(context.locale.addtask), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + 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( + hint: Text(context.locale.imageplaceholder), + value: selectedImageUrl, + isExpanded: true, + items: imageUrls.map((String url) { + return DropdownMenuItem( + 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: [ + 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().add(ChangeLikeEvent(id)); _showSnackBar(context, title, !isLiked); } } + + Future _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"); + } + } + } + + } diff --git a/local.properties b/local.properties new file mode 100644 index 0000000..0781896 --- /dev/null +++ b/local.properties @@ -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 diff --git a/makefile b/makefile index 982f849..d1b1b11 100644 --- a/makefile +++ b/makefile @@ -12,4 +12,7 @@ format: res: fgen --output lib/components/resources.g.dart --no-watch --no-preview; \ - make format \ No newline at end of file + make format + +loc: + flutter gen l10n \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 02af619..3baa7de 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -71,7 +71,6 @@ dev_dependencies: flutter_icons: android: "ic_launcher" - ios: true image_path: "assets/launcher.jpg" min_sdk_android: 21