CourseWork - isDone
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 12 KiB |
@ -427,7 +427,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
@ -484,7 +484,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 267 KiB |
Before Width: | Height: | Size: 295 B After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 406 B After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 450 B After Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 282 B After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 462 B After Width: | Height: | Size: 5.6 KiB |
Before Width: | Height: | Size: 704 B After Width: | Height: | Size: 8.7 KiB |
Before Width: | Height: | Size: 406 B After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 586 B After Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 862 B After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 5.3 KiB |
After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 862 B After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 7.2 KiB |
After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 762 B After Width: | Height: | Size: 7.8 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 18 KiB |
@ -1,9 +1,13 @@
|
||||
{
|
||||
"@@locale": "en",
|
||||
"search": "Search",
|
||||
"liked": "liked!",
|
||||
"disliked": "disliked :(",
|
||||
"studentWord": "Student",
|
||||
"liked": "liked",
|
||||
"disliked": "disliked",
|
||||
"activityWord": "Activity",
|
||||
"startTime": "Start:",
|
||||
"endTime": "End:",
|
||||
"description": "Description:",
|
||||
"duration": "Duration:",
|
||||
|
||||
"arbEnding": "Чтобы не забыть про отсутствие запятой :)"
|
||||
}
|
||||
|
@ -1,9 +1,13 @@
|
||||
{
|
||||
"@@locale": "ru",
|
||||
"search": "Поиск",
|
||||
"liked": "Понравился!",
|
||||
"disliked": "разонравился :(",
|
||||
"studentWord": "Студент",
|
||||
"liked": "понравился",
|
||||
"disliked": "разонравился",
|
||||
"activityWord": "Активность",
|
||||
"startTime": "Начало:",
|
||||
"endTime": "Окончание:",
|
||||
"description": "Описание:",
|
||||
"duration": "Длительность:",
|
||||
|
||||
"arbEnding": "Чтобы не забыть про отсутствие запятой :)"
|
||||
}
|
||||
|
@ -82,7 +82,8 @@ abstract class AppLocale {
|
||||
/// Additional delegates can be added by appending to this list in
|
||||
/// MaterialApp. This list does not have to be used at all if a custom list
|
||||
/// of delegates is preferred or required.
|
||||
static const List<LocalizationsDelegate<dynamic>> localizationsDelegates = <LocalizationsDelegate<dynamic>>[
|
||||
static const List<LocalizationsDelegate<dynamic>> localizationsDelegates =
|
||||
<LocalizationsDelegate<dynamic>>[
|
||||
delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
@ -90,10 +91,7 @@ abstract class AppLocale {
|
||||
];
|
||||
|
||||
/// A list of this localizations delegate's supported locales.
|
||||
static const List<Locale> supportedLocales = <Locale>[
|
||||
Locale('en'),
|
||||
Locale('ru')
|
||||
];
|
||||
static const List<Locale> supportedLocales = <Locale>[Locale('en'), Locale('ru')];
|
||||
|
||||
/// No description provided for @search.
|
||||
///
|
||||
@ -104,20 +102,44 @@ abstract class AppLocale {
|
||||
/// No description provided for @liked.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'Понравился!'**
|
||||
/// **'понравился'**
|
||||
String get liked;
|
||||
|
||||
/// No description provided for @disliked.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'разонравился :('**
|
||||
/// **'разонравился'**
|
||||
String get disliked;
|
||||
|
||||
/// No description provided for @studentWord.
|
||||
/// No description provided for @activityWord.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'Студент'**
|
||||
String get studentWord;
|
||||
/// **'Активность'**
|
||||
String get activityWord;
|
||||
|
||||
/// No description provided for @startTime.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'Начало:'**
|
||||
String get startTime;
|
||||
|
||||
/// No description provided for @endTime.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'Окончание:'**
|
||||
String get endTime;
|
||||
|
||||
/// No description provided for @description.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'Описание:'**
|
||||
String get description;
|
||||
|
||||
/// No description provided for @duration.
|
||||
///
|
||||
/// In ru, this message translates to:
|
||||
/// **'Длительность:'**
|
||||
String get duration;
|
||||
|
||||
/// No description provided for @arbEnding.
|
||||
///
|
||||
@ -142,18 +164,17 @@ class _AppLocaleDelegate extends LocalizationsDelegate<AppLocale> {
|
||||
}
|
||||
|
||||
AppLocale lookupAppLocale(Locale locale) {
|
||||
|
||||
|
||||
// Lookup logic when only language code is specified.
|
||||
switch (locale.languageCode) {
|
||||
case 'en': return AppLocaleEn();
|
||||
case 'ru': return AppLocaleRu();
|
||||
case 'en':
|
||||
return AppLocaleEn();
|
||||
case 'ru':
|
||||
return AppLocaleRu();
|
||||
}
|
||||
|
||||
throw FlutterError(
|
||||
'AppLocale.delegate failed to load unsupported locale "$locale". This is likely '
|
||||
'an issue with the localizations generation tool. Please file an issue '
|
||||
'on GitHub with a reproducible sample app and the gen-l10n configuration '
|
||||
'that was used.'
|
||||
);
|
||||
'that was used.');
|
||||
}
|
||||
|
@ -10,13 +10,25 @@ class AppLocaleEn extends AppLocale {
|
||||
String get search => 'Search';
|
||||
|
||||
@override
|
||||
String get liked => 'liked!';
|
||||
String get liked => 'liked';
|
||||
|
||||
@override
|
||||
String get disliked => 'disliked :(';
|
||||
String get disliked => 'disliked';
|
||||
|
||||
@override
|
||||
String get studentWord => 'Student';
|
||||
String get activityWord => 'Activity';
|
||||
|
||||
@override
|
||||
String get startTime => 'Start:';
|
||||
|
||||
@override
|
||||
String get endTime => 'End:';
|
||||
|
||||
@override
|
||||
String get description => 'Description:';
|
||||
|
||||
@override
|
||||
String get duration => 'Duration:';
|
||||
|
||||
@override
|
||||
String get arbEnding => 'Чтобы не забыть про отсутствие запятой :)';
|
||||
|
@ -10,13 +10,25 @@ class AppLocaleRu extends AppLocale {
|
||||
String get search => 'Поиск';
|
||||
|
||||
@override
|
||||
String get liked => 'Понравился!';
|
||||
String get liked => 'понравился';
|
||||
|
||||
@override
|
||||
String get disliked => 'разонравился :(';
|
||||
String get disliked => 'разонравился';
|
||||
|
||||
@override
|
||||
String get studentWord => 'Студент';
|
||||
String get activityWord => 'Активность';
|
||||
|
||||
@override
|
||||
String get startTime => 'Начало:';
|
||||
|
||||
@override
|
||||
String get endTime => 'Окончание:';
|
||||
|
||||
@override
|
||||
String get description => 'Описание:';
|
||||
|
||||
@override
|
||||
String get duration => 'Длительность:';
|
||||
|
||||
@override
|
||||
String get arbEnding => 'Чтобы не забыть про отсутствие запятой :)';
|
||||
|
68
lib/data/dtos/activity_dto.dart
Normal file
@ -0,0 +1,68 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'activity_dto.g.dart'; // Этот файл будет сгенерирован автоматически
|
||||
|
||||
@JsonSerializable(createToJson: false)
|
||||
class ActivitiesDto {
|
||||
final List<ActivityDataDto>? data;
|
||||
final MetaDto? meta;
|
||||
|
||||
const ActivitiesDto({this.data, this.meta});
|
||||
|
||||
factory ActivitiesDto.fromJson(Map<String, dynamic> json) => _$ActivitiesDtoFromJson(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);
|
||||
}
|
||||
|
||||
@JsonSerializable(createToJson: false)
|
||||
class ActivityDataDto {
|
||||
final String? id;
|
||||
final String? type;
|
||||
final ActivityAttributesDataDto? attributes;
|
||||
|
||||
const ActivityDataDto({this.id, this.type, this.attributes});
|
||||
|
||||
factory ActivityDataDto.fromJson(Map<String, dynamic> json) => _$ActivityDataDtoFromJson(json);
|
||||
}
|
||||
|
||||
@JsonSerializable(createToJson: false)
|
||||
class ActivityAttributesDataDto {
|
||||
final String? name;
|
||||
final String? description;
|
||||
final String? startTime;
|
||||
final String? endTime;
|
||||
final String? category;
|
||||
|
||||
const ActivityAttributesDataDto({
|
||||
this.name,
|
||||
this.description,
|
||||
this.startTime,
|
||||
this.endTime,
|
||||
this.category,
|
||||
});
|
||||
|
||||
factory ActivityAttributesDataDto.fromJson(Map<String, dynamic> json) =>
|
||||
_$ActivityAttributesDataDtoFromJson(json);
|
||||
}
|
43
lib/data/dtos/activity_dto.g.dart
Normal file
@ -0,0 +1,43 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'activity_dto.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
ActivitiesDto _$ActivitiesDtoFromJson(Map<String, dynamic> json) => ActivitiesDto(
|
||||
data: (json['data'] as List<dynamic>?)
|
||||
?.map((e) => ActivityDataDto.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
meta: json['meta'] == null ? null : MetaDto.fromJson(json['meta'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
MetaDto _$MetaDtoFromJson(Map<String, dynamic> json) => MetaDto(
|
||||
pagination: json['pagination'] == null
|
||||
? null
|
||||
: PaginationDto.fromJson(json['pagination'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
PaginationDto _$PaginationDtoFromJson(Map<String, dynamic> json) => PaginationDto(
|
||||
current: (json['current'] as num?)?.toInt(),
|
||||
next: (json['next'] as num?)?.toInt(),
|
||||
last: (json['last'] as num?)?.toInt(),
|
||||
);
|
||||
|
||||
ActivityDataDto _$ActivityDataDtoFromJson(Map<String, dynamic> json) => ActivityDataDto(
|
||||
id: json['id'] as String?,
|
||||
type: json['type'] as String?,
|
||||
attributes: json['attributes'] == null
|
||||
? null
|
||||
: ActivityAttributesDataDto.fromJson(json['attributes'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
ActivityAttributesDataDto _$ActivityAttributesDataDtoFromJson(Map<String, dynamic> json) =>
|
||||
ActivityAttributesDataDto(
|
||||
name: json['name'] as String?,
|
||||
description: json['description'] as String?,
|
||||
startTime: json['startTime'] as String?,
|
||||
endTime: json['endTime'] as String?,
|
||||
category: json['category'] as String?,
|
||||
);
|
21
lib/data/mapper/ActivityDataDToModel.dart
Normal file
@ -0,0 +1,21 @@
|
||||
import 'package:labs/domain/models/Activity.dart';
|
||||
import 'package:labs/domain/models/home.dart';
|
||||
|
||||
import '../dtos/activity_dto.dart';
|
||||
|
||||
extension ActivityDataDtoToModel on ActivityDataDto {
|
||||
Activity toDomain() => Activity(
|
||||
id: id ?? '0',
|
||||
name: attributes?.name ?? 'Без названия',
|
||||
description: attributes?.description ?? 'Нет описания',
|
||||
startTime: attributes?.startTime ?? '00:00',
|
||||
endTime: attributes?.endTime ?? '00:00',
|
||||
);
|
||||
}
|
||||
|
||||
extension ActivitiesDtoToModel on ActivitiesDto {
|
||||
HomeData toDomain() => HomeData(
|
||||
data: data?.map((e) => e.toDomain()).toList(),
|
||||
nextPage: meta?.pagination?.next,
|
||||
);
|
||||
}
|
@ -10,9 +10,9 @@ extension CharacterDataDtoToModel on CharacterDataDto {
|
||||
'https://gryazoveckij-r19.gosweb.gosuslugi.ru/netcat_files/460/2008/net_foto_muzh.jpg');
|
||||
}
|
||||
|
||||
extension CharactersDtoToModel on CharactersDto {
|
||||
/*extension CharactersDtoToModel on CharactersDto {
|
||||
HomeData toDomain() => HomeData(
|
||||
data: data?.map((e) => e.toDomain()).toList(),
|
||||
nextPage: meta?.pagination?.next,
|
||||
);
|
||||
}
|
||||
}*/
|
||||
|
17
lib/domain/models/Activity.dart
Normal file
@ -0,0 +1,17 @@
|
||||
class Activity {
|
||||
final String name;
|
||||
final String description;
|
||||
final String startTime;
|
||||
final String endTime;
|
||||
final String? id;
|
||||
final String? image;
|
||||
|
||||
Activity({
|
||||
required this.name,
|
||||
required this.description,
|
||||
required this.startTime,
|
||||
required this.endTime,
|
||||
this.id,
|
||||
this.image,
|
||||
});
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
import 'package:labs/domain/models/Activity.dart';
|
||||
|
||||
import 'Student.dart';
|
||||
|
||||
class HomeData {
|
||||
final List<Student>? data;
|
||||
final List<Activity>? data;
|
||||
final int? nextPage;
|
||||
|
||||
HomeData({this.data, this.nextPage});
|
||||
|
@ -1,14 +1,12 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:labs/presentation/home_page/bloc/bloc.dart';
|
||||
import 'package:labs/presentation/home_page/home_page.dart';
|
||||
import 'package:labs/presentation/like_bloc/like_bloc.dart';
|
||||
import 'package:labs/repo/potter_repo.dart';
|
||||
import 'University.dart';
|
||||
import 'package:labs/repo/activity_repo.dart';
|
||||
import 'components/locale/l10n/app_locale.dart';
|
||||
import 'domain/models/Student.dart';
|
||||
import 'domain/models/Activity.dart';
|
||||
import 'presentation/locale_bloc/locale_bloc.dart';
|
||||
import 'presentation/locale_bloc/locale_state.dart';
|
||||
|
||||
@ -20,46 +18,24 @@ class MyApp extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'University App',
|
||||
title: 'Time Management App',
|
||||
theme: ThemeData(
|
||||
primarySwatch: Colors.blue,
|
||||
scaffoldBackgroundColor: Colors.white, // Устанавливаем белый фон
|
||||
scaffoldBackgroundColor: Colors.white,
|
||||
),
|
||||
home: ScaffoldMessenger(
|
||||
child: UniversityScreen(),
|
||||
child: ActivityScreen(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class UniversityScreen extends StatefulWidget {
|
||||
class ActivityScreen extends StatefulWidget {
|
||||
@override
|
||||
_UniversityScreenState createState() => _UniversityScreenState();
|
||||
_ActivityScreenState createState() => _ActivityScreenState();
|
||||
}
|
||||
|
||||
class _UniversityScreenState extends State<UniversityScreen> {
|
||||
final University university = University();
|
||||
final TextEditingController nameController = TextEditingController();
|
||||
final TextEditingController ageController = TextEditingController();
|
||||
List<String> selectedCourses = [];
|
||||
|
||||
void _addStudent() async {
|
||||
await Future.delayed(Duration(seconds: 1));
|
||||
String name = nameController.text;
|
||||
int age = int.tryParse(ageController.text) ?? 0;
|
||||
|
||||
if (name.isNotEmpty && age > 0 && selectedCourses.isNotEmpty) {
|
||||
setState(() {
|
||||
// Создаем новый список курсов для каждого студента
|
||||
List<String> studentCourses = List.from(selectedCourses);
|
||||
university.addStudent(Student(name, age, studentCourses));
|
||||
nameController.clear();
|
||||
ageController.clear();
|
||||
selectedCourses.clear();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class _ActivityScreenState extends State<ActivityScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<LocaleBloc>(
|
||||
@ -68,7 +44,7 @@ class _UniversityScreenState extends State<UniversityScreen> {
|
||||
child: BlocBuilder<LocaleBloc, LocaleState>(
|
||||
builder: (context, state) {
|
||||
return MaterialApp(
|
||||
title: 'Строев Владимир, ПИбд-32',
|
||||
title: 'Time Management',
|
||||
locale: state.currentLocale,
|
||||
localizationsDelegates: AppLocale.localizationsDelegates,
|
||||
supportedLocales: AppLocale.supportedLocales,
|
||||
@ -76,17 +52,17 @@ class _UniversityScreenState extends State<UniversityScreen> {
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.orangeAccent),
|
||||
useMaterial3: true,
|
||||
scaffoldBackgroundColor: Colors.white12, // Устанавливаем белый фон
|
||||
scaffoldBackgroundColor: Colors.white12,
|
||||
),
|
||||
home: RepositoryProvider<PotterRepo>(
|
||||
home: RepositoryProvider<ActivityRepository>(
|
||||
lazy: true,
|
||||
create: (_) => PotterRepo(),
|
||||
create: (_) => ActivityRepository(),
|
||||
child: BlocProvider<LikeBloc>(
|
||||
lazy: false,
|
||||
create: (context) => LikeBloc(),
|
||||
child: BlocProvider<HomeBloc>(
|
||||
lazy: false,
|
||||
create: (context) => HomeBloc(context.read<PotterRepo>()),
|
||||
create: (context) => HomeBloc(context.read<ActivityRepository>()),
|
||||
child: const MyHomePage(),
|
||||
),
|
||||
),
|
||||
|
@ -1,42 +1,135 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../domain/models/Student.dart';
|
||||
import 'package:labs/components/extensions/context_x.dart';
|
||||
import 'package:labs/domain/models/Activity.dart';
|
||||
|
||||
class DetailsPage extends StatelessWidget {
|
||||
final Student data;
|
||||
final Activity data;
|
||||
|
||||
const DetailsPage(this.data, {super.key});
|
||||
|
||||
// Метод для преобразования времени в минуты
|
||||
int _timeToMinutes(String time) {
|
||||
final parts = time.split(':');
|
||||
final hours = int.parse(parts[0]);
|
||||
final minutes = int.parse(parts[1]);
|
||||
return hours * 60 + minutes;
|
||||
}
|
||||
|
||||
// Метод для вычисления разницы между временем
|
||||
String _calculateDuration(String startTime, String endTime) {
|
||||
final startMinutes = _timeToMinutes(startTime);
|
||||
final endMinutes = _timeToMinutes(endTime);
|
||||
final duration = endMinutes - startMinutes;
|
||||
|
||||
if (duration < 0) {
|
||||
return 'Ошибка: время окончания меньше времени начала';
|
||||
}
|
||||
|
||||
final hours = duration ~/ 60;
|
||||
final minutes = duration % 60;
|
||||
return 'Длительность: ${hours}ч ${minutes}м';
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(),
|
||||
body: Column(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
data.name,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
backgroundColor: Colors.blueAccent,
|
||||
),
|
||||
body: Container(
|
||||
width: double.infinity,
|
||||
height: double.infinity, // Растягиваем контейнер на всю ширину
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Colors.blueAccent, Colors.lightBlueAccent],
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16.0),
|
||||
child: Image.network(
|
||||
data.image ?? '',
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4.0),
|
||||
child: Text(
|
||||
data.name,
|
||||
style: Theme.of(context).textTheme.headlineLarge,
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
context.locale.description,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
Student.getYearWord(data.age),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
data.description,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
context.locale.startTime,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Courses: ${data.courses.join(", ")}',
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
data.startTime,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
color: Colors.white70,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
context.locale.endTime,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
data.endTime,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
color: Colors.white70,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
context.locale.duration,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
_calculateDuration(data.startTime, data.endTime),
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:labs/presentation/home_page/bloc/events.dart';
|
||||
import 'package:labs/presentation/home_page/bloc/state.dart';
|
||||
import 'package:labs/repo/potter_repo.dart';
|
||||
import 'package:labs/repo/activity_repo.dart';
|
||||
|
||||
class HomeBloc extends Bloc<HomeEvent, HomeState> {
|
||||
final PotterRepo repo;
|
||||
final ActivityRepository repo;
|
||||
|
||||
HomeBloc(this.repo) : super(const HomeState()) {
|
||||
on<HomeLoadDataEvent>(_onLoadData);
|
||||
|
@ -4,9 +4,9 @@ typedef OnLikeCallBack = void Function(String? id, String title, bool isLiked)?;
|
||||
|
||||
class _Card extends StatelessWidget {
|
||||
final String name;
|
||||
final int age;
|
||||
final List<String> courses;
|
||||
final String? imageUrl;
|
||||
final String description;
|
||||
final String startTime;
|
||||
final String endTime;
|
||||
final OnLikeCallBack onLike;
|
||||
final VoidCallback? onTap;
|
||||
final String? id;
|
||||
@ -14,22 +14,22 @@ class _Card extends StatelessWidget {
|
||||
|
||||
const _Card(
|
||||
this.name, {
|
||||
required this.age,
|
||||
required this.courses,
|
||||
this.imageUrl,
|
||||
required this.description,
|
||||
required this.startTime,
|
||||
required this.endTime,
|
||||
this.onLike,
|
||||
this.onTap,
|
||||
this.id,
|
||||
this.isLiked = false,
|
||||
});
|
||||
|
||||
factory _Card.fromData(Student data,
|
||||
factory _Card.fromData(Activity data,
|
||||
{OnLikeCallBack onLike, VoidCallback? onTap, bool isLiked = false}) =>
|
||||
_Card(
|
||||
data.name,
|
||||
age: data.age,
|
||||
courses: data.courses,
|
||||
imageUrl: data.image,
|
||||
description: data.description,
|
||||
startTime: data.startTime,
|
||||
endTime: data.endTime,
|
||||
onLike: onLike,
|
||||
onTap: onTap,
|
||||
isLiked: isLiked,
|
||||
@ -40,85 +40,66 @@ class _Card extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(top: 16, left: 16, right: 16, bottom: 16),
|
||||
constraints: const BoxConstraints(minHeight: 140),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blueAccent,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.2),
|
||||
spreadRadius: 2,
|
||||
blurRadius: 5,
|
||||
offset: Offset(0, 3),
|
||||
child: Card(
|
||||
margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
||||
elevation: 4,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: IntrinsicHeight(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.bottomLeft,
|
||||
color: Colors.white,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 8.0,
|
||||
right: 16,
|
||||
bottom: 16,
|
||||
),
|
||||
child: GestureDetector(
|
||||
onTap: () => onLike?.call(id, name, isLiked),
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: isLiked
|
||||
? const Icon(
|
||||
Icons.favorite,
|
||||
color: Colors.lightBlueAccent,
|
||||
key: ValueKey<int>(0),
|
||||
)
|
||||
: const Icon(Icons.favorite_border),
|
||||
key: ValueKey<int>(0),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 16),
|
||||
Flexible(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(name, style: Theme.of(context).textTheme.bodyLarge),
|
||||
SizedBox(height: 12),
|
||||
Text('Age: ${Student.getYearWord(age)}',
|
||||
style: Theme.of(context).textTheme.bodyLarge),
|
||||
SizedBox(height: 12),
|
||||
Text('Courses: ${courses.join(", ")}',
|
||||
style: Theme.of(context).textTheme.bodyMedium),
|
||||
],
|
||||
Text(
|
||||
name,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.blueAccent,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomRight: Radius.circular(20),
|
||||
topRight: Radius.circular(20),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
description,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.grey,
|
||||
),
|
||||
child: Stack(
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'${context.locale.startTime} $startTime',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.black54,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'${context.locale.endTime} $endTime',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.black54,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Image.network(
|
||||
imageUrl ?? '',
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (_, __, ___) => const Placeholder(),
|
||||
GestureDetector(
|
||||
onTap: () => onLike?.call(id, name, isLiked),
|
||||
child: Icon(
|
||||
isLiked ? Icons.favorite : Icons.favorite_border,
|
||||
color: isLiked ? Colors.red : Colors.grey,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)));
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -6,9 +6,9 @@ import 'package:labs/presentation/details_page/details_page.dart';
|
||||
import 'package:labs/presentation/home_page/bloc/events.dart';
|
||||
import 'package:labs/presentation/like_bloc/like_event.dart';
|
||||
import 'package:labs/presentation/like_bloc/like_state.dart';
|
||||
import 'package:labs/repo/potter_repo.dart';
|
||||
import 'package:labs/repo/activity_repo.dart';
|
||||
import '../../components/utils/debounce.dart';
|
||||
import '../../domain/models/Student.dart';
|
||||
import '../../domain/models/Activity.dart';
|
||||
import '../common/svg_objects.dart';
|
||||
import '../like_bloc/like_bloc.dart';
|
||||
import '../locale_bloc/locale_bloc.dart';
|
||||
@ -16,6 +16,7 @@ import '../locale_bloc/locale_events.dart';
|
||||
import '../locale_bloc/locale_state.dart';
|
||||
import 'bloc/bloc.dart';
|
||||
import 'bloc/state.dart';
|
||||
|
||||
part 'card.dart';
|
||||
|
||||
class MyHomePage extends StatefulWidget {
|
||||
@ -42,8 +43,8 @@ class _Body extends StatefulWidget {
|
||||
class _BodyState extends State<_Body> {
|
||||
final searchController = TextEditingController();
|
||||
final scrollController = ScrollController();
|
||||
final PotterRepo repo = PotterRepo();
|
||||
late Future<List<Student>?> data;
|
||||
final ActivityRepository repo = ActivityRepository();
|
||||
late Future<List<Activity>?> data;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -88,7 +89,15 @@ class _BodyState extends State<_Body> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Padding(
|
||||
body: Container(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Colors.blueAccent, Colors.lightBlueAccent],
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
|
||||
child: Column(
|
||||
children: [
|
||||
@ -123,7 +132,8 @@ class _BodyState extends State<_Body> {
|
||||
builder: (context, state) => state.error != null
|
||||
? Text(
|
||||
state.error ?? '',
|
||||
style: Theme.of(context).textTheme.headlineSmall?.copyWith(color: Colors.red),
|
||||
style:
|
||||
Theme.of(context).textTheme.headlineSmall?.copyWith(color: Colors.red),
|
||||
)
|
||||
: state.isLoading
|
||||
? const CircularProgressIndicator()
|
||||
@ -142,7 +152,8 @@ class _BodyState extends State<_Body> {
|
||||
? _Card.fromData(
|
||||
data,
|
||||
onLike: _onLike,
|
||||
isLiked: likeState.likedIds?.contains(data.id) == true,
|
||||
isLiked:
|
||||
likeState.likedIds?.contains(data.id) == true,
|
||||
onTap: () => _navToDetails(context, data),
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
@ -161,10 +172,11 @@ class _BodyState extends State<_Body> {
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _navToDetails(BuildContext context, Student data) {
|
||||
void _navToDetails(BuildContext context, Activity data) {
|
||||
Navigator.push(
|
||||
context,
|
||||
CupertinoPageRoute(builder: (context) => DetailsPage(data)),
|
||||
@ -180,7 +192,7 @@ class _BodyState extends State<_Body> {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text(
|
||||
'${context.locale.studentWord} $title ${isLiked ? context.locale.liked : context.locale.disliked}',
|
||||
'${context.locale.activityWord} $title ${isLiked ? context.locale.liked : context.locale.disliked}',
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
backgroundColor: Colors.lightBlueAccent,
|
||||
|
66
lib/repo/activity_repo.dart
Normal file
@ -0,0 +1,66 @@
|
||||
import 'package:labs/domain/models/Activity.dart';
|
||||
import 'package:labs/domain/models/home.dart';
|
||||
import 'api_interface.dart';
|
||||
|
||||
class ActivityRepository extends ApiInterface {
|
||||
@override
|
||||
Future<HomeData?> loadData(
|
||||
{OnErrorCallback? onError, String? q, int page = 1, int pageSize = 25}) async {
|
||||
try {
|
||||
// Мок-данные для активностей
|
||||
final List<Activity> mockActivities = [
|
||||
Activity(
|
||||
id: '1',
|
||||
name: 'Утренняя пробежка',
|
||||
description: 'Пробежка в парке',
|
||||
startTime: '07:00',
|
||||
endTime: '07:30',
|
||||
),
|
||||
Activity(
|
||||
id: '2',
|
||||
name: 'Работа над проектом',
|
||||
description: 'Завершение курсовой работы',
|
||||
startTime: '10:00',
|
||||
endTime: '12:00',
|
||||
),
|
||||
Activity(
|
||||
id: '3',
|
||||
name: 'Обед',
|
||||
description: 'Обед в кафе',
|
||||
startTime: '13:00',
|
||||
endTime: '14:00',
|
||||
),
|
||||
Activity(
|
||||
id: '4',
|
||||
name: 'Чтение книги',
|
||||
description: 'Чтение главы из книги "Тайм-менеджмент"',
|
||||
startTime: '15:00',
|
||||
endTime: '16:00',
|
||||
),
|
||||
];
|
||||
|
||||
// Фильтрация данных по поисковой строке
|
||||
final filteredActivities = mockActivities.where((activity) {
|
||||
return q == null ||
|
||||
activity.name.toLowerCase().contains(q.toLowerCase()) ||
|
||||
activity.description.toLowerCase().contains(q.toLowerCase());
|
||||
}).toList();
|
||||
|
||||
// Пагинация
|
||||
final startIndex = (page - 1) * pageSize;
|
||||
final endIndex = startIndex + pageSize;
|
||||
final paginatedActivities =
|
||||
filteredActivities.sublist(startIndex, endIndex.clamp(0, filteredActivities.length));
|
||||
|
||||
// Возвращаем данные в формате HomeData
|
||||
return HomeData(
|
||||
data: paginatedActivities,
|
||||
nextPage: endIndex < filteredActivities.length ? page + 1 : null,
|
||||
);
|
||||
} catch (e) {
|
||||
// Обработка ошибок
|
||||
onError?.call(e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -32,8 +32,8 @@ class PotterRepo extends ApiInterface {
|
||||
);
|
||||
|
||||
final CharactersDto dto = CharactersDto.fromJson(response.data as Map<String, dynamic>);
|
||||
final HomeData data = dto.toDomain();
|
||||
return data;
|
||||
/*final HomeData data = dto.toDomain();
|
||||
return data;*/
|
||||
} on DioException catch (e) {
|
||||
onError?.call(e.error?.toString());
|
||||
return null;
|
||||
|
16
pubspec.lock
@ -1,6 +1,14 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
_discoveryapis_commons:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _discoveryapis_commons
|
||||
sha256: "113c4100b90a5b70a983541782431b82168b3cae166ab130649c36eb3559d498"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.7"
|
||||
_fe_analyzer_shared:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -346,6 +354,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
googleapis:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: googleapis
|
||||
sha256: "864f222aed3f2ff00b816c675edf00a39e2aaf373d728d8abec30b37bee1a81c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "13.2.0"
|
||||
graphs:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -14,12 +14,13 @@ dependencies:
|
||||
cupertino_icons: ^1.0.8
|
||||
|
||||
json_annotation: ^4.9.0
|
||||
dio: ^5.4.2+1
|
||||
dio: ^5.7.0
|
||||
pretty_dio_logger: ^1.3.1
|
||||
equatable: ^2.0.5
|
||||
flutter_bloc: ^8.1.6
|
||||
copy_with_extension_gen: ^5.0.4
|
||||
flutter_svg: 2.0.7
|
||||
googleapis: ^13.2.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|