CourseWork - isDone

This commit is contained in:
DyCTaTOR 2024-12-12 16:36:29 +04:00
parent 66fcb3fa3a
commit a251967084
48 changed files with 1225 additions and 876 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -427,7 +427,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; 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_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
@ -484,7 +484,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; 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_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 B

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 B

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 B

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 B

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 B

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -1,9 +1,13 @@
{ {
"@@locale": "en", "@@locale": "en",
"search": "Search", "search": "Search",
"liked": "liked!", "liked": "liked",
"disliked": "disliked :(", "disliked": "disliked",
"studentWord": "Student", "activityWord": "Activity",
"startTime": "Start:",
"endTime": "End:",
"description": "Description:",
"duration": "Duration:",
"arbEnding": "Чтобы не забыть про отсутствие запятой :)" "arbEnding": "Чтобы не забыть про отсутствие запятой :)"
} }

View File

@ -1,9 +1,13 @@
{ {
"@@locale": "ru", "@@locale": "ru",
"search": "Поиск", "search": "Поиск",
"liked": "Понравился!", "liked": "понравился",
"disliked": "разонравился :(", "disliked": "разонравился",
"studentWord": "Студент", "activityWord": "Активность",
"startTime": "Начало:",
"endTime": "Окончание:",
"description": "Описание:",
"duration": "Длительность:",
"arbEnding": "Чтобы не забыть про отсутствие запятой :)" "arbEnding": "Чтобы не забыть про отсутствие запятой :)"
} }

View File

@ -82,7 +82,8 @@ abstract class AppLocale {
/// Additional delegates can be added by appending to this list in /// 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 /// MaterialApp. This list does not have to be used at all if a custom list
/// of delegates is preferred or required. /// of delegates is preferred or required.
static const List<LocalizationsDelegate<dynamic>> localizationsDelegates = <LocalizationsDelegate<dynamic>>[ static const List<LocalizationsDelegate<dynamic>> localizationsDelegates =
<LocalizationsDelegate<dynamic>>[
delegate, delegate,
GlobalMaterialLocalizations.delegate, GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate, GlobalCupertinoLocalizations.delegate,
@ -90,10 +91,7 @@ abstract class AppLocale {
]; ];
/// A list of this localizations delegate's supported locales. /// A list of this localizations delegate's supported locales.
static const List<Locale> supportedLocales = <Locale>[ static const List<Locale> supportedLocales = <Locale>[Locale('en'), Locale('ru')];
Locale('en'),
Locale('ru')
];
/// No description provided for @search. /// No description provided for @search.
/// ///
@ -104,20 +102,44 @@ abstract class AppLocale {
/// No description provided for @liked. /// No description provided for @liked.
/// ///
/// In ru, this message translates to: /// In ru, this message translates to:
/// **'Понравился!'** /// **'понравился'**
String get liked; String get liked;
/// No description provided for @disliked. /// No description provided for @disliked.
/// ///
/// In ru, this message translates to: /// In ru, this message translates to:
/// **'разонравился :('** /// **'разонравился'**
String get disliked; String get disliked;
/// No description provided for @studentWord. /// No description provided for @activityWord.
/// ///
/// In ru, this message translates to: /// 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. /// No description provided for @arbEnding.
/// ///
@ -142,18 +164,17 @@ class _AppLocaleDelegate extends LocalizationsDelegate<AppLocale> {
} }
AppLocale lookupAppLocale(Locale locale) { AppLocale lookupAppLocale(Locale locale) {
// Lookup logic when only language code is specified. // Lookup logic when only language code is specified.
switch (locale.languageCode) { switch (locale.languageCode) {
case 'en': return AppLocaleEn(); case 'en':
case 'ru': return AppLocaleRu(); return AppLocaleEn();
case 'ru':
return AppLocaleRu();
} }
throw FlutterError( throw FlutterError(
'AppLocale.delegate failed to load unsupported locale "$locale". This is likely ' 'AppLocale.delegate failed to load unsupported locale "$locale". This is likely '
'an issue with the localizations generation tool. Please file an issue ' 'an issue with the localizations generation tool. Please file an issue '
'on GitHub with a reproducible sample app and the gen-l10n configuration ' 'on GitHub with a reproducible sample app and the gen-l10n configuration '
'that was used.' 'that was used.');
);
} }

View File

@ -10,13 +10,25 @@ class AppLocaleEn extends AppLocale {
String get search => 'Search'; String get search => 'Search';
@override @override
String get liked => 'liked!'; String get liked => 'liked';
@override @override
String get disliked => 'disliked :('; String get disliked => 'disliked';
@override @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 @override
String get arbEnding => 'Чтобы не забыть про отсутствие запятой :)'; String get arbEnding => 'Чтобы не забыть про отсутствие запятой :)';

View File

@ -10,13 +10,25 @@ class AppLocaleRu extends AppLocale {
String get search => 'Поиск'; String get search => 'Поиск';
@override @override
String get liked => 'Понравился!'; String get liked => 'понравился';
@override @override
String get disliked => 'разонравился :('; String get disliked => 'разонравился';
@override @override
String get studentWord => 'Студент'; String get activityWord => 'Активность';
@override
String get startTime => 'Начало:';
@override
String get endTime => 'Окончание:';
@override
String get description => 'Описание:';
@override
String get duration => 'Длительность:';
@override @override
String get arbEnding => 'Чтобы не забыть про отсутствие запятой :)'; String get arbEnding => 'Чтобы не забыть про отсутствие запятой :)';

View 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);
}

View 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?,
);

View 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,
);
}

View File

@ -10,9 +10,9 @@ extension CharacterDataDtoToModel on CharacterDataDto {
'https://gryazoveckij-r19.gosweb.gosuslugi.ru/netcat_files/460/2008/net_foto_muzh.jpg'); '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( HomeData toDomain() => HomeData(
data: data?.map((e) => e.toDomain()).toList(), data: data?.map((e) => e.toDomain()).toList(),
nextPage: meta?.pagination?.next, nextPage: meta?.pagination?.next,
); );
} }*/

View 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,
});
}

View File

@ -1,7 +1,9 @@
import 'package:labs/domain/models/Activity.dart';
import 'Student.dart'; import 'Student.dart';
class HomeData { class HomeData {
final List<Student>? data; final List<Activity>? data;
final int? nextPage; final int? nextPage;
HomeData({this.data, this.nextPage}); HomeData({this.data, this.nextPage});

View File

@ -1,14 +1,12 @@
import 'dart:io'; import 'dart:io';
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:labs/presentation/home_page/bloc/bloc.dart'; import 'package:labs/presentation/home_page/bloc/bloc.dart';
import 'package:labs/presentation/home_page/home_page.dart'; import 'package:labs/presentation/home_page/home_page.dart';
import 'package:labs/presentation/like_bloc/like_bloc.dart'; import 'package:labs/presentation/like_bloc/like_bloc.dart';
import 'package:labs/repo/potter_repo.dart'; import 'package:labs/repo/activity_repo.dart';
import 'University.dart';
import 'components/locale/l10n/app_locale.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_bloc.dart';
import 'presentation/locale_bloc/locale_state.dart'; import 'presentation/locale_bloc/locale_state.dart';
@ -20,46 +18,24 @@ class MyApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
title: 'University App', title: 'Time Management App',
theme: ThemeData( theme: ThemeData(
primarySwatch: Colors.blue, primarySwatch: Colors.blue,
scaffoldBackgroundColor: Colors.white, // Устанавливаем белый фон scaffoldBackgroundColor: Colors.white,
), ),
home: ScaffoldMessenger( home: ScaffoldMessenger(
child: UniversityScreen(), child: ActivityScreen(),
), ),
); );
} }
} }
class UniversityScreen extends StatefulWidget { class ActivityScreen extends StatefulWidget {
@override @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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider<LocaleBloc>( return BlocProvider<LocaleBloc>(
@ -68,7 +44,7 @@ class _UniversityScreenState extends State<UniversityScreen> {
child: BlocBuilder<LocaleBloc, LocaleState>( child: BlocBuilder<LocaleBloc, LocaleState>(
builder: (context, state) { builder: (context, state) {
return MaterialApp( return MaterialApp(
title: 'Строев Владимир, ПИбд-32', title: 'Time Management',
locale: state.currentLocale, locale: state.currentLocale,
localizationsDelegates: AppLocale.localizationsDelegates, localizationsDelegates: AppLocale.localizationsDelegates,
supportedLocales: AppLocale.supportedLocales, supportedLocales: AppLocale.supportedLocales,
@ -76,17 +52,17 @@ class _UniversityScreenState extends State<UniversityScreen> {
theme: ThemeData( theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.orangeAccent), colorScheme: ColorScheme.fromSeed(seedColor: Colors.orangeAccent),
useMaterial3: true, useMaterial3: true,
scaffoldBackgroundColor: Colors.white12, // Устанавливаем белый фон scaffoldBackgroundColor: Colors.white12,
), ),
home: RepositoryProvider<PotterRepo>( home: RepositoryProvider<ActivityRepository>(
lazy: true, lazy: true,
create: (_) => PotterRepo(), create: (_) => ActivityRepository(),
child: BlocProvider<LikeBloc>( child: BlocProvider<LikeBloc>(
lazy: false, lazy: false,
create: (context) => LikeBloc(), create: (context) => LikeBloc(),
child: BlocProvider<HomeBloc>( child: BlocProvider<HomeBloc>(
lazy: false, lazy: false,
create: (context) => HomeBloc(context.read<PotterRepo>()), create: (context) => HomeBloc(context.read<ActivityRepository>()),
child: const MyHomePage(), child: const MyHomePage(),
), ),
), ),

View File

@ -1,42 +1,135 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:labs/components/extensions/context_x.dart';
import '../../domain/models/Student.dart'; import 'package:labs/domain/models/Activity.dart';
class DetailsPage extends StatelessWidget { class DetailsPage extends StatelessWidget {
final Student data; final Activity data;
const DetailsPage(this.data, {super.key}); 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(), appBar: AppBar(
body: Column( 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, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Padding( const SizedBox(height: 16),
padding: const EdgeInsets.only(bottom: 16.0), Text(
child: Image.network( context.locale.description,
data.image ?? '', style: TextStyle(
), fontSize: 16,
), color: Colors.white,
Padding( fontWeight: FontWeight.bold,
padding: const EdgeInsets.only(bottom: 4.0),
child: Text(
data.name,
style: Theme.of(context).textTheme.headlineLarge,
), ),
), ),
Text( Text(
Student.getYearWord(data.age), data.description,
style: Theme.of(context).textTheme.bodyLarge, 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( Text(
'Courses: ${data.courses.join(", ")}', data.startTime,
style: Theme.of(context).textTheme.bodyMedium, 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,
),
), ),
], ],
), ),
),
),
),
); );
} }
} }

View File

@ -1,10 +1,10 @@
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:labs/presentation/home_page/bloc/events.dart'; import 'package:labs/presentation/home_page/bloc/events.dart';
import 'package:labs/presentation/home_page/bloc/state.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> { class HomeBloc extends Bloc<HomeEvent, HomeState> {
final PotterRepo repo; final ActivityRepository repo;
HomeBloc(this.repo) : super(const HomeState()) { HomeBloc(this.repo) : super(const HomeState()) {
on<HomeLoadDataEvent>(_onLoadData); on<HomeLoadDataEvent>(_onLoadData);

View File

@ -4,9 +4,9 @@ typedef OnLikeCallBack = void Function(String? id, String title, bool isLiked)?;
class _Card extends StatelessWidget { class _Card extends StatelessWidget {
final String name; final String name;
final int age; final String description;
final List<String> courses; final String startTime;
final String? imageUrl; final String endTime;
final OnLikeCallBack onLike; final OnLikeCallBack onLike;
final VoidCallback? onTap; final VoidCallback? onTap;
final String? id; final String? id;
@ -14,22 +14,22 @@ class _Card extends StatelessWidget {
const _Card( const _Card(
this.name, { this.name, {
required this.age, required this.description,
required this.courses, required this.startTime,
this.imageUrl, required this.endTime,
this.onLike, this.onLike,
this.onTap, this.onTap,
this.id, this.id,
this.isLiked = false, this.isLiked = false,
}); });
factory _Card.fromData(Student data, factory _Card.fromData(Activity data,
{OnLikeCallBack onLike, VoidCallback? onTap, bool isLiked = false}) => {OnLikeCallBack onLike, VoidCallback? onTap, bool isLiked = false}) =>
_Card( _Card(
data.name, data.name,
age: data.age, description: data.description,
courses: data.courses, startTime: data.startTime,
imageUrl: data.image, endTime: data.endTime,
onLike: onLike, onLike: onLike,
onTap: onTap, onTap: onTap,
isLiked: isLiked, isLiked: isLiked,
@ -40,85 +40,66 @@ class _Card extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return GestureDetector(
onTap: onTap, onTap: onTap,
child: Container( child: Card(
margin: const EdgeInsets.only(top: 16, left: 16, right: 16, bottom: 16), margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
constraints: const BoxConstraints(minHeight: 140), elevation: 4,
decoration: BoxDecoration( shape: RoundedRectangleBorder(
color: Colors.blueAccent, borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
), ),
], color: Colors.white,
),
child: IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Align(
alignment: Alignment.bottomLeft,
child: Padding( child: Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.all(16.0),
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(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text(name, style: Theme.of(context).textTheme.bodyLarge), Text(
SizedBox(height: 12), name,
Text('Age: ${Student.getYearWord(age)}', style: const TextStyle(
style: Theme.of(context).textTheme.bodyLarge), fontSize: 18,
SizedBox(height: 12), fontWeight: FontWeight.bold,
Text('Courses: ${courses.join(", ")}', color: Colors.blueAccent,
style: Theme.of(context).textTheme.bodyMedium),
],
), ),
), ),
Expanded( const SizedBox(height: 8),
child: ClipRRect( Text(
borderRadius: const BorderRadius.only( description,
bottomRight: Radius.circular(20), style: const TextStyle(
topRight: Radius.circular(20), 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: [ children: [
Positioned.fill( GestureDetector(
child: Image.network( onTap: () => onLike?.call(id, name, isLiked),
imageUrl ?? '', child: Icon(
fit: BoxFit.cover, isLiked ? Icons.favorite : Icons.favorite_border,
errorBuilder: (_, __, ___) => const Placeholder(), color: isLiked ? Colors.red : Colors.grey,
), ),
), ),
], ],
), ),
],
),
), ),
), ),
], );
),
)));
} }
} }

View File

@ -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/home_page/bloc/events.dart';
import 'package:labs/presentation/like_bloc/like_event.dart'; import 'package:labs/presentation/like_bloc/like_event.dart';
import 'package:labs/presentation/like_bloc/like_state.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 '../../components/utils/debounce.dart';
import '../../domain/models/Student.dart'; import '../../domain/models/Activity.dart';
import '../common/svg_objects.dart'; import '../common/svg_objects.dart';
import '../like_bloc/like_bloc.dart'; import '../like_bloc/like_bloc.dart';
import '../locale_bloc/locale_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 '../locale_bloc/locale_state.dart';
import 'bloc/bloc.dart'; import 'bloc/bloc.dart';
import 'bloc/state.dart'; import 'bloc/state.dart';
part 'card.dart'; part 'card.dart';
class MyHomePage extends StatefulWidget { class MyHomePage extends StatefulWidget {
@ -42,8 +43,8 @@ class _Body extends StatefulWidget {
class _BodyState extends State<_Body> { class _BodyState extends State<_Body> {
final searchController = TextEditingController(); final searchController = TextEditingController();
final scrollController = ScrollController(); final scrollController = ScrollController();
final PotterRepo repo = PotterRepo(); final ActivityRepository repo = ActivityRepository();
late Future<List<Student>?> data; late Future<List<Activity>?> data;
@override @override
void initState() { void initState() {
@ -88,7 +89,15 @@ class _BodyState extends State<_Body> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( 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), padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
child: Column( child: Column(
children: [ children: [
@ -123,7 +132,8 @@ class _BodyState extends State<_Body> {
builder: (context, state) => state.error != null builder: (context, state) => state.error != null
? Text( ? Text(
state.error ?? '', state.error ?? '',
style: Theme.of(context).textTheme.headlineSmall?.copyWith(color: Colors.red), style:
Theme.of(context).textTheme.headlineSmall?.copyWith(color: Colors.red),
) )
: state.isLoading : state.isLoading
? const CircularProgressIndicator() ? const CircularProgressIndicator()
@ -142,7 +152,8 @@ class _BodyState extends State<_Body> {
? _Card.fromData( ? _Card.fromData(
data, data,
onLike: _onLike, onLike: _onLike,
isLiked: likeState.likedIds?.contains(data.id) == true, isLiked:
likeState.likedIds?.contains(data.id) == true,
onTap: () => _navToDetails(context, data), onTap: () => _navToDetails(context, data),
) )
: const SizedBox.shrink(); : 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( Navigator.push(
context, context,
CupertinoPageRoute(builder: (context) => DetailsPage(data)), CupertinoPageRoute(builder: (context) => DetailsPage(data)),
@ -180,7 +192,7 @@ class _BodyState extends State<_Body> {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar( ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text( 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, style: Theme.of(context).textTheme.bodyLarge,
), ),
backgroundColor: Colors.lightBlueAccent, backgroundColor: Colors.lightBlueAccent,

View 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;
}
}
}

View File

@ -32,8 +32,8 @@ class PotterRepo extends ApiInterface {
); );
final CharactersDto dto = CharactersDto.fromJson(response.data as Map<String, dynamic>); final CharactersDto dto = CharactersDto.fromJson(response.data as Map<String, dynamic>);
final HomeData data = dto.toDomain(); /*final HomeData data = dto.toDomain();
return data; return data;*/
} on DioException catch (e) { } on DioException catch (e) {
onError?.call(e.error?.toString()); onError?.call(e.error?.toString());
return null; return null;

View File

@ -1,6 +1,14 @@
# Generated by pub # Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile # See https://dart.dev/tools/pub/glossary#lockfile
packages: packages:
_discoveryapis_commons:
dependency: transitive
description:
name: _discoveryapis_commons
sha256: "113c4100b90a5b70a983541782431b82168b3cae166ab130649c36eb3559d498"
url: "https://pub.dev"
source: hosted
version: "1.0.7"
_fe_analyzer_shared: _fe_analyzer_shared:
dependency: transitive dependency: transitive
description: description:
@ -346,6 +354,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.2" version: "2.1.2"
googleapis:
dependency: "direct main"
description:
name: googleapis
sha256: "864f222aed3f2ff00b816c675edf00a39e2aaf373d728d8abec30b37bee1a81c"
url: "https://pub.dev"
source: hosted
version: "13.2.0"
graphs: graphs:
dependency: transitive dependency: transitive
description: description:

View File

@ -14,12 +14,13 @@ dependencies:
cupertino_icons: ^1.0.8 cupertino_icons: ^1.0.8
json_annotation: ^4.9.0 json_annotation: ^4.9.0
dio: ^5.4.2+1 dio: ^5.7.0
pretty_dio_logger: ^1.3.1 pretty_dio_logger: ^1.3.1
equatable: ^2.0.5 equatable: ^2.0.5
flutter_bloc: ^8.1.6 flutter_bloc: ^8.1.6
copy_with_extension_gen: ^5.0.4 copy_with_extension_gen: ^5.0.4
flutter_svg: 2.0.7 flutter_svg: 2.0.7
googleapis: ^13.2.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: