From abf1cc3f7d03f679c5d4e8a4a8617d6e9c29efeb Mon Sep 17 00:00:00 2001 From: amirrezaghabeli Date: Tue, 11 Nov 2025 12:58:05 +0330 Subject: [PATCH] Add board service and repository, update routes and assets - Introduced BoardService and BoardRepository for managing board-related data. - Updated app routes to reflect the new board screen structure. - Added new board-related assets to the asset manager. - Refactored BoardScreen navigation to utilize the new provider structure. --- lib/core/config/dependencies.dart | 10 + lib/core/enums/task_status.dart | 29 + lib/core/extensions/board_data_extension.dart | 12 + lib/core/providers/board_provider.dart | 16 + lib/core/routes/app_routes.dart | 5 +- lib/data/repositories/board_repositories.dart | 13 + lib/data/services/board_service.dart | 80 +++ .../model/board_response/board_response.dart | 34 + .../board_response.freezed.dart | 596 ++++++++++++++++++ .../board_response/board_response.g.dart | 51 ++ lib/gen/assets.gen.dart | 12 + lib/view_models/board_view_model.dart | 77 +++ lib/views/board/{ => pages}/board_screen.dart | 309 +++++---- lib/views/board/strings/board_strings.dart | 14 + 14 files changed, 1092 insertions(+), 166 deletions(-) create mode 100644 lib/core/enums/task_status.dart create mode 100644 lib/core/extensions/board_data_extension.dart create mode 100644 lib/core/providers/board_provider.dart create mode 100644 lib/data/repositories/board_repositories.dart create mode 100644 lib/data/services/board_service.dart create mode 100644 lib/data/services/model/board_response/board_response.dart create mode 100644 lib/data/services/model/board_response/board_response.freezed.dart create mode 100644 lib/data/services/model/board_response/board_response.g.dart create mode 100644 lib/view_models/board_view_model.dart rename lib/views/board/{ => pages}/board_screen.dart (53%) create mode 100644 lib/views/board/strings/board_strings.dart diff --git a/lib/core/config/dependencies.dart b/lib/core/config/dependencies.dart index 3b9a416..d53bd99 100644 --- a/lib/core/config/dependencies.dart +++ b/lib/core/config/dependencies.dart @@ -19,8 +19,10 @@ import 'package:tm_app/data/services/your_tenders_service.dart'; import 'package:tm_app/view_models/final_completion_of_documents_view_model.dart'; import '../../data/repositories/auth_repository.dart'; +import '../../data/repositories/board_repositories.dart'; import '../../data/repositories/profile_repository.dart'; import '../../data/services/auth_service.dart'; +import '../../data/services/board_service.dart'; import '../../data/services/profile_service.dart'; import '../../data/services/tenders_service.dart'; import '../../view_models/auth_view_model.dart'; @@ -86,6 +88,10 @@ List get apiClients { create: (context) => NotificationsService(networkManager: context.read()), lazy: true, ), + Provider( + create: (context) => BoardService(networkManager: context.read()), + lazy: true, + ), ]; } @@ -137,6 +143,10 @@ List get repositories { NotificationsRepository(notificationsService: context.read()), lazy: true, ), + Provider( + create: (context) => BoardRepository(boardService: context.read()), + lazy: true, + ), ]; } diff --git a/lib/core/enums/task_status.dart b/lib/core/enums/task_status.dart new file mode 100644 index 0000000..5298c23 --- /dev/null +++ b/lib/core/enums/task_status.dart @@ -0,0 +1,29 @@ +enum TaskStatus { toDo, inProgress, done } + +extension TaskStatusExtension on TaskStatus { + // تبدیل enum به string + String get value => name; + + // تبدیل string به enum + static TaskStatus fromString(String? status) { + if (status == null) { + return TaskStatus.toDo; + } + + return TaskStatus.values.firstWhere( + (e) => e.name == status, + orElse: () => TaskStatus.toDo, + ); + } + + String get displayNameEn { + switch (this) { + case TaskStatus.toDo: + return 'To Do'; + case TaskStatus.inProgress: + return 'In Progress'; + case TaskStatus.done: + return 'Done'; + } + } +} diff --git a/lib/core/extensions/board_data_extension.dart b/lib/core/extensions/board_data_extension.dart new file mode 100644 index 0000000..31039de --- /dev/null +++ b/lib/core/extensions/board_data_extension.dart @@ -0,0 +1,12 @@ +import '../../data/services/model/board_response/board_response.dart'; +import '../enums/task_status.dart'; + +extension BoardDataExtension on BoardData { + // تبدیل status string به TaskStatus enum + TaskStatus get statusEnum => TaskStatusExtension.fromString(status); + + // کپی BoardData با status جدید + BoardData copyWithStatus(TaskStatus newStatus) { + return copyWith(status: newStatus.value); + } +} diff --git a/lib/core/providers/board_provider.dart b/lib/core/providers/board_provider.dart new file mode 100644 index 0000000..be91b0a --- /dev/null +++ b/lib/core/providers/board_provider.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import '../../data/repositories/board_repositories.dart'; +import '../../view_models/board_view_model.dart'; + +/// Lazy BoardViewModel provider +/// Wraps screens that need BoardViewModel +Widget boardProvider({required Widget child}) { + return ChangeNotifierProvider( + create: + (context) => + BoardViewModel(boardRepository: context.read()), + child: child, + ); +} diff --git a/lib/core/routes/app_routes.dart b/lib/core/routes/app_routes.dart index 248e957..cb0c8d2 100644 --- a/lib/core/routes/app_routes.dart +++ b/lib/core/routes/app_routes.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'package:tm_app/views/board/board_screen.dart'; +import 'package:tm_app/views/board/pages/board_screen.dart'; import 'package:tm_app/views/completion_of_documents/pages/complectopn_of_documents_screen.dart'; import 'package:tm_app/views/detail/pages/detail_screen.dart'; import 'package:tm_app/views/forget_password_create/pages/forgot_password_create_screen.dart'; @@ -18,6 +18,7 @@ import '../../views/profile/pages/profile_screen.dart'; import '../../views/splash/pages/splash_screen.dart'; import '../../views/tenders/pages/tenders_screen.dart'; import '../../views/your_tenders/pages/your_tenders_screen.dart'; +import '../providers/board_provider.dart'; import '../providers/final_completion_provider.dart'; import '../providers/forgot_password_provider.dart'; import '../providers/home_provider.dart'; @@ -272,7 +273,7 @@ class BoardRouteData extends GoRouteData with _$BoardRouteData { @override Widget build(BuildContext context, GoRouterState state) { - return const BoardScreen(); + return boardProvider(child: const BoardScreen()); } } diff --git a/lib/data/repositories/board_repositories.dart b/lib/data/repositories/board_repositories.dart new file mode 100644 index 0000000..703ef5b --- /dev/null +++ b/lib/data/repositories/board_repositories.dart @@ -0,0 +1,13 @@ +import '../../core/utils/result.dart'; +import '../services/board_service.dart'; +import '../services/model/board_response/board_response.dart'; + +class BoardRepository { + final BoardService _boardService; + BoardRepository({required BoardService boardService}) + : _boardService = boardService; + + Future> getBoard() async { + return _boardService.getBoard(); + } +} diff --git a/lib/data/services/board_service.dart b/lib/data/services/board_service.dart new file mode 100644 index 0000000..7d2f579 --- /dev/null +++ b/lib/data/services/board_service.dart @@ -0,0 +1,80 @@ +import '../../../core/network/network_manager.dart'; +import '../../core/config/app_config.dart'; +import '../../core/utils/result.dart'; +import 'model/board_response/board_response.dart'; + +class BoardService { + BoardService({required NetworkManager networkManager}) + : _networkManager = networkManager; + + final NetworkManager _networkManager; + + Future> getBoard() async { + final uri = '${AppConfig.apiBaseUrl}/api/v1/board'; + await Future.delayed(const Duration(seconds: 2)); + + // final result = await _networkManager.makeRequest( + // uri, + // (json) => BoardResponse.fromJson(json), + // method: 'GET', + // ); + // return result; + + return Result.ok( + BoardResponse( + success: true, + message: 'Board data fetched successfully', + data: tasks, + error: null, + ), + ); + } +} + +List tasks = [ + BoardData( + id: '1', + description: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua', + deadline: DateTime(2025, 6, 15), + assignedTo: 'Pratyush Tewari', + isReady: false, + status: 'toDo', + ), + BoardData( + id: '2', + description: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua', + deadline: DateTime(2025, 6, 15), + assignedTo: 'Pratyush Tewari', + isReady: true, + status: 'toDo', + ), + BoardData( + id: '3', + description: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua', + deadline: DateTime(2025, 6, 15), + assignedTo: 'Pratyush Tewari', + isReady: false, + status: 'inProgress', + ), + BoardData( + id: '4', + description: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua', + deadline: DateTime(2025, 6, 15), + assignedTo: 'Pratyush Tewari', + isReady: true, + status: 'inProgress', + ), + BoardData( + id: '5', + description: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua', + deadline: DateTime(2025, 6, 15), + assignedTo: 'Pratyush Tewari', + isReady: false, + status: 'done', + ), +]; diff --git a/lib/data/services/model/board_response/board_response.dart b/lib/data/services/model/board_response/board_response.dart new file mode 100644 index 0000000..8bf6c6b --- /dev/null +++ b/lib/data/services/model/board_response/board_response.dart @@ -0,0 +1,34 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +import '../error/error_model.dart'; + +part 'board_response.freezed.dart'; +part 'board_response.g.dart'; + +@freezed +abstract class BoardResponse with _$BoardResponse { + const factory BoardResponse({ + required bool? success, + required String? message, + required List? data, + required ErrorModel? error, + }) = _BoardResponse; + + factory BoardResponse.fromJson(Map json) => + _$BoardResponseFromJson(json); +} + +@freezed +abstract class BoardData with _$BoardData { + const factory BoardData({ + required String? id, + required String? description, + required DateTime? deadline, + required String? assignedTo, + required bool? isReady, + required String? status, + }) = _BoardData; + + factory BoardData.fromJson(Map json) => + _$BoardDataFromJson(json); +} diff --git a/lib/data/services/model/board_response/board_response.freezed.dart b/lib/data/services/model/board_response/board_response.freezed.dart new file mode 100644 index 0000000..bc12e33 --- /dev/null +++ b/lib/data/services/model/board_response/board_response.freezed.dart @@ -0,0 +1,596 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'board_response.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; + +/// @nodoc +mixin _$BoardResponse { + + bool? get success; String? get message; List? get data; ErrorModel? get error; +/// Create a copy of BoardResponse +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$BoardResponseCopyWith get copyWith => _$BoardResponseCopyWithImpl(this as BoardResponse, _$identity); + + /// Serializes this BoardResponse to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is BoardResponse&&(identical(other.success, success) || other.success == success)&&(identical(other.message, message) || other.message == message)&&const DeepCollectionEquality().equals(other.data, data)&&(identical(other.error, error) || other.error == error)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,success,message,const DeepCollectionEquality().hash(data),error); + +@override +String toString() { + return 'BoardResponse(success: $success, message: $message, data: $data, error: $error)'; +} + + +} + +/// @nodoc +abstract mixin class $BoardResponseCopyWith<$Res> { + factory $BoardResponseCopyWith(BoardResponse value, $Res Function(BoardResponse) _then) = _$BoardResponseCopyWithImpl; +@useResult +$Res call({ + bool? success, String? message, List? data, ErrorModel? error +}); + + +$ErrorModelCopyWith<$Res>? get error; + +} +/// @nodoc +class _$BoardResponseCopyWithImpl<$Res> + implements $BoardResponseCopyWith<$Res> { + _$BoardResponseCopyWithImpl(this._self, this._then); + + final BoardResponse _self; + final $Res Function(BoardResponse) _then; + +/// Create a copy of BoardResponse +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? success = freezed,Object? message = freezed,Object? data = freezed,Object? error = freezed,}) { + return _then(_self.copyWith( +success: freezed == success ? _self.success : success // ignore: cast_nullable_to_non_nullable +as bool?,message: freezed == message ? _self.message : message // ignore: cast_nullable_to_non_nullable +as String?,data: freezed == data ? _self.data : data // ignore: cast_nullable_to_non_nullable +as List?,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable +as ErrorModel?, + )); +} +/// Create a copy of BoardResponse +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$ErrorModelCopyWith<$Res>? get error { + if (_self.error == null) { + return null; + } + + return $ErrorModelCopyWith<$Res>(_self.error!, (value) { + return _then(_self.copyWith(error: value)); + }); +} +} + + +/// Adds pattern-matching-related methods to [BoardResponse]. +extension BoardResponsePatterns on BoardResponse { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _BoardResponse value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _BoardResponse() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _BoardResponse value) $default,){ +final _that = this; +switch (_that) { +case _BoardResponse(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _BoardResponse value)? $default,){ +final _that = this; +switch (_that) { +case _BoardResponse() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( bool? success, String? message, List? data, ErrorModel? error)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _BoardResponse() when $default != null: +return $default(_that.success,_that.message,_that.data,_that.error);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( bool? success, String? message, List? data, ErrorModel? error) $default,) {final _that = this; +switch (_that) { +case _BoardResponse(): +return $default(_that.success,_that.message,_that.data,_that.error);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( bool? success, String? message, List? data, ErrorModel? error)? $default,) {final _that = this; +switch (_that) { +case _BoardResponse() when $default != null: +return $default(_that.success,_that.message,_that.data,_that.error);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _BoardResponse implements BoardResponse { + const _BoardResponse({required this.success, required this.message, required final List? data, required this.error}): _data = data; + factory _BoardResponse.fromJson(Map json) => _$BoardResponseFromJson(json); + +@override final bool? success; +@override final String? message; + final List? _data; +@override List? get data { + final value = _data; + if (value == null) return null; + if (_data is EqualUnmodifiableListView) return _data; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); +} + +@override final ErrorModel? error; + +/// Create a copy of BoardResponse +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$BoardResponseCopyWith<_BoardResponse> get copyWith => __$BoardResponseCopyWithImpl<_BoardResponse>(this, _$identity); + +@override +Map toJson() { + return _$BoardResponseToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _BoardResponse&&(identical(other.success, success) || other.success == success)&&(identical(other.message, message) || other.message == message)&&const DeepCollectionEquality().equals(other._data, _data)&&(identical(other.error, error) || other.error == error)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,success,message,const DeepCollectionEquality().hash(_data),error); + +@override +String toString() { + return 'BoardResponse(success: $success, message: $message, data: $data, error: $error)'; +} + + +} + +/// @nodoc +abstract mixin class _$BoardResponseCopyWith<$Res> implements $BoardResponseCopyWith<$Res> { + factory _$BoardResponseCopyWith(_BoardResponse value, $Res Function(_BoardResponse) _then) = __$BoardResponseCopyWithImpl; +@override @useResult +$Res call({ + bool? success, String? message, List? data, ErrorModel? error +}); + + +@override $ErrorModelCopyWith<$Res>? get error; + +} +/// @nodoc +class __$BoardResponseCopyWithImpl<$Res> + implements _$BoardResponseCopyWith<$Res> { + __$BoardResponseCopyWithImpl(this._self, this._then); + + final _BoardResponse _self; + final $Res Function(_BoardResponse) _then; + +/// Create a copy of BoardResponse +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? success = freezed,Object? message = freezed,Object? data = freezed,Object? error = freezed,}) { + return _then(_BoardResponse( +success: freezed == success ? _self.success : success // ignore: cast_nullable_to_non_nullable +as bool?,message: freezed == message ? _self.message : message // ignore: cast_nullable_to_non_nullable +as String?,data: freezed == data ? _self._data : data // ignore: cast_nullable_to_non_nullable +as List?,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable +as ErrorModel?, + )); +} + +/// Create a copy of BoardResponse +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$ErrorModelCopyWith<$Res>? get error { + if (_self.error == null) { + return null; + } + + return $ErrorModelCopyWith<$Res>(_self.error!, (value) { + return _then(_self.copyWith(error: value)); + }); +} +} + + +/// @nodoc +mixin _$BoardData { + + String? get id; String? get description; DateTime? get deadline; String? get assignedTo; bool? get isReady; String? get status; +/// Create a copy of BoardData +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$BoardDataCopyWith get copyWith => _$BoardDataCopyWithImpl(this as BoardData, _$identity); + + /// Serializes this BoardData to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is BoardData&&(identical(other.id, id) || other.id == id)&&(identical(other.description, description) || other.description == description)&&(identical(other.deadline, deadline) || other.deadline == deadline)&&(identical(other.assignedTo, assignedTo) || other.assignedTo == assignedTo)&&(identical(other.isReady, isReady) || other.isReady == isReady)&&(identical(other.status, status) || other.status == status)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,description,deadline,assignedTo,isReady,status); + +@override +String toString() { + return 'BoardData(id: $id, description: $description, deadline: $deadline, assignedTo: $assignedTo, isReady: $isReady, status: $status)'; +} + + +} + +/// @nodoc +abstract mixin class $BoardDataCopyWith<$Res> { + factory $BoardDataCopyWith(BoardData value, $Res Function(BoardData) _then) = _$BoardDataCopyWithImpl; +@useResult +$Res call({ + String? id, String? description, DateTime? deadline, String? assignedTo, bool? isReady, String? status +}); + + + + +} +/// @nodoc +class _$BoardDataCopyWithImpl<$Res> + implements $BoardDataCopyWith<$Res> { + _$BoardDataCopyWithImpl(this._self, this._then); + + final BoardData _self; + final $Res Function(BoardData) _then; + +/// Create a copy of BoardData +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? id = freezed,Object? description = freezed,Object? deadline = freezed,Object? assignedTo = freezed,Object? isReady = freezed,Object? status = freezed,}) { + return _then(_self.copyWith( +id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable +as String?,deadline: freezed == deadline ? _self.deadline : deadline // ignore: cast_nullable_to_non_nullable +as DateTime?,assignedTo: freezed == assignedTo ? _self.assignedTo : assignedTo // ignore: cast_nullable_to_non_nullable +as String?,isReady: freezed == isReady ? _self.isReady : isReady // ignore: cast_nullable_to_non_nullable +as bool?,status: freezed == status ? _self.status : status // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + +} + + +/// Adds pattern-matching-related methods to [BoardData]. +extension BoardDataPatterns on BoardData { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _BoardData value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _BoardData() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _BoardData value) $default,){ +final _that = this; +switch (_that) { +case _BoardData(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _BoardData value)? $default,){ +final _that = this; +switch (_that) { +case _BoardData() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String? id, String? description, DateTime? deadline, String? assignedTo, bool? isReady, String? status)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _BoardData() when $default != null: +return $default(_that.id,_that.description,_that.deadline,_that.assignedTo,_that.isReady,_that.status);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String? id, String? description, DateTime? deadline, String? assignedTo, bool? isReady, String? status) $default,) {final _that = this; +switch (_that) { +case _BoardData(): +return $default(_that.id,_that.description,_that.deadline,_that.assignedTo,_that.isReady,_that.status);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String? id, String? description, DateTime? deadline, String? assignedTo, bool? isReady, String? status)? $default,) {final _that = this; +switch (_that) { +case _BoardData() when $default != null: +return $default(_that.id,_that.description,_that.deadline,_that.assignedTo,_that.isReady,_that.status);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _BoardData implements BoardData { + const _BoardData({required this.id, required this.description, required this.deadline, required this.assignedTo, required this.isReady, required this.status}); + factory _BoardData.fromJson(Map json) => _$BoardDataFromJson(json); + +@override final String? id; +@override final String? description; +@override final DateTime? deadline; +@override final String? assignedTo; +@override final bool? isReady; +@override final String? status; + +/// Create a copy of BoardData +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$BoardDataCopyWith<_BoardData> get copyWith => __$BoardDataCopyWithImpl<_BoardData>(this, _$identity); + +@override +Map toJson() { + return _$BoardDataToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _BoardData&&(identical(other.id, id) || other.id == id)&&(identical(other.description, description) || other.description == description)&&(identical(other.deadline, deadline) || other.deadline == deadline)&&(identical(other.assignedTo, assignedTo) || other.assignedTo == assignedTo)&&(identical(other.isReady, isReady) || other.isReady == isReady)&&(identical(other.status, status) || other.status == status)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,description,deadline,assignedTo,isReady,status); + +@override +String toString() { + return 'BoardData(id: $id, description: $description, deadline: $deadline, assignedTo: $assignedTo, isReady: $isReady, status: $status)'; +} + + +} + +/// @nodoc +abstract mixin class _$BoardDataCopyWith<$Res> implements $BoardDataCopyWith<$Res> { + factory _$BoardDataCopyWith(_BoardData value, $Res Function(_BoardData) _then) = __$BoardDataCopyWithImpl; +@override @useResult +$Res call({ + String? id, String? description, DateTime? deadline, String? assignedTo, bool? isReady, String? status +}); + + + + +} +/// @nodoc +class __$BoardDataCopyWithImpl<$Res> + implements _$BoardDataCopyWith<$Res> { + __$BoardDataCopyWithImpl(this._self, this._then); + + final _BoardData _self; + final $Res Function(_BoardData) _then; + +/// Create a copy of BoardData +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? id = freezed,Object? description = freezed,Object? deadline = freezed,Object? assignedTo = freezed,Object? isReady = freezed,Object? status = freezed,}) { + return _then(_BoardData( +id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable +as String?,deadline: freezed == deadline ? _self.deadline : deadline // ignore: cast_nullable_to_non_nullable +as DateTime?,assignedTo: freezed == assignedTo ? _self.assignedTo : assignedTo // ignore: cast_nullable_to_non_nullable +as String?,isReady: freezed == isReady ? _self.isReady : isReady // ignore: cast_nullable_to_non_nullable +as bool?,status: freezed == status ? _self.status : status // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + + +} + +// dart format on diff --git a/lib/data/services/model/board_response/board_response.g.dart b/lib/data/services/model/board_response/board_response.g.dart new file mode 100644 index 0000000..38983f4 --- /dev/null +++ b/lib/data/services/model/board_response/board_response.g.dart @@ -0,0 +1,51 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'board_response.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_BoardResponse _$BoardResponseFromJson(Map json) => + _BoardResponse( + success: json['success'] as bool?, + message: json['message'] as String?, + data: + (json['data'] as List?) + ?.map((e) => BoardData.fromJson(e as Map)) + .toList(), + error: + json['error'] == null + ? null + : ErrorModel.fromJson(json['error'] as Map), + ); + +Map _$BoardResponseToJson(_BoardResponse instance) => + { + 'success': instance.success, + 'message': instance.message, + 'data': instance.data, + 'error': instance.error, + }; + +_BoardData _$BoardDataFromJson(Map json) => _BoardData( + id: json['id'] as String?, + description: json['description'] as String?, + deadline: + json['deadline'] == null + ? null + : DateTime.parse(json['deadline'] as String), + assignedTo: json['assignedTo'] as String?, + isReady: json['isReady'] as bool?, + status: json['status'] as String?, +); + +Map _$BoardDataToJson(_BoardData instance) => + { + 'id': instance.id, + 'description': instance.description, + 'deadline': instance.deadline?.toIso8601String(), + 'assignedTo': instance.assignedTo, + 'isReady': instance.isReady, + 'status': instance.status, + }; diff --git a/lib/gen/assets.gen.dart b/lib/gen/assets.gen.dart index b5e1b53..8eb463b 100644 --- a/lib/gen/assets.gen.dart +++ b/lib/gen/assets.gen.dart @@ -47,12 +47,21 @@ class $AssetsIconsGen { /// File path: assets/icons/arrows.svg String get arrows => 'assets/icons/arrows.svg'; + /// File path: assets/icons/board.svg + String get board => 'assets/icons/board.svg'; + + /// File path: assets/icons/board_active.svg + String get boardActive => 'assets/icons/board_active.svg'; + /// File path: assets/icons/calendar.svg String get calendar => 'assets/icons/calendar.svg'; /// File path: assets/icons/clipboard-text.svg String get clipboardText => 'assets/icons/clipboard-text.svg'; + /// File path: assets/icons/clock.svg + String get clock => 'assets/icons/clock.svg'; + /// File path: assets/icons/close-circle.svg String get closeCircle => 'assets/icons/close-circle.svg'; @@ -178,8 +187,11 @@ class $AssetsIconsGen { arrowUp, arrowDownSmall, arrows, + board, + boardActive, calendar, clipboardText, + clock, closeCircle, dangerFill, danger, diff --git a/lib/view_models/board_view_model.dart b/lib/view_models/board_view_model.dart new file mode 100644 index 0000000..6b3f407 --- /dev/null +++ b/lib/view_models/board_view_model.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.dart'; + +import '../core/enums/task_status.dart'; +import '../core/utils/result.dart'; +import '../data/repositories/board_repositories.dart'; +import '../data/services/model/board_response/board_response.dart'; + +class BoardViewModel with ChangeNotifier { + final BoardRepository _boardRepository; + BoardViewModel({required BoardRepository boardRepository}) + : _boardRepository = boardRepository; + + bool _isLoading = false; + String? _errorMessage; + List _boardData = []; + + bool get isLoading => _isLoading; + String? get errorMessage => _errorMessage; + List get boardData => _boardData; + bool get hasData => _boardData.isNotEmpty; + + Future getBoard() async { + _isLoading = true; + notifyListeners(); + + final result = await _boardRepository.getBoard(); + + switch (result) { + case Ok(): + // تبدیل به لیست قابل تغییر + _boardData = List.from(result.value.data ?? []); + break; + case Error(): + _errorMessage = result.error.toString(); + break; + } + _isLoading = false; + notifyListeners(); + } + + // update task status + void updateTaskStatus(String taskId, String newStatus) { + final taskIndex = _boardData.indexWhere((task) => task.id == taskId); + if (taskIndex != -1) { + _boardData[taskIndex] = _boardData[taskIndex].copyWith(status: newStatus); + notifyListeners(); + } + } + + bool isValidMove(TaskStatus currentStatus, TaskStatus targetStatus) { + if (currentStatus == TaskStatus.toDo) { + return targetStatus == TaskStatus.inProgress; + } else if (currentStatus == TaskStatus.inProgress) { + return targetStatus == TaskStatus.toDo || targetStatus == TaskStatus.done; + } else if (currentStatus == TaskStatus.done) { + return targetStatus == TaskStatus.inProgress; + } + return false; + } + + List getTasksByStatus(TaskStatus status) { + return boardData.where((task) => task.status == status.value).toList(); + } + + void _updateTaskStatus(BoardData task, TaskStatus newStatus) { + if (task.status == newStatus.value) { + return; + } + + final oldStatus = task.status; + + // update task status in ViewModel + updateTaskStatus(task.id!, newStatus.value); + + // _showStatusChangeMessage(context, task, oldStatus, newStatus); + } +} diff --git a/lib/views/board/board_screen.dart b/lib/views/board/pages/board_screen.dart similarity index 53% rename from lib/views/board/board_screen.dart rename to lib/views/board/pages/board_screen.dart index 425ee8f..32cbdfe 100644 --- a/lib/views/board/board_screen.dart +++ b/lib/views/board/pages/board_screen.dart @@ -1,30 +1,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:provider/provider.dart'; +import 'package:tm_app/core/extensions/board_data_extension.dart'; import 'package:tm_app/core/theme/colors.dart'; import 'package:tm_app/core/utils/size_config.dart'; +import 'package:tm_app/data/services/model/board_response/board_response.dart'; import 'package:tm_app/views/shared/desktop_navigation_widget.dart'; -import '../../core/constants/assets.dart'; - -class TaskItem { - final String id; - final String description; - final DateTime deadline; - final String assignedTo; - final bool isReady; - TaskStatus status; - - TaskItem({ - required this.id, - required this.description, - required this.deadline, - required this.assignedTo, - required this.isReady, - required this.status, - }); -} - -enum TaskStatus { toDo, inProgress, done } +import '../../../core/constants/assets.dart'; +import '../../../core/enums/task_status.dart'; +import '../../../core/utils/app_toast.dart'; +import '../../../view_models/board_view_model.dart'; +import '../strings/board_strings.dart'; class BoardScreen extends StatefulWidget { const BoardScreen({super.key}); @@ -34,175 +21,165 @@ class BoardScreen extends StatefulWidget { } class _BoardScreenState extends State { - List tasks = [ - TaskItem( - id: '1', - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua', - deadline: DateTime(2025, 6, 15), - assignedTo: 'Pratyush Tewari', - isReady: false, - status: TaskStatus.toDo, - ), - TaskItem( - id: '2', - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua', - deadline: DateTime(2025, 6, 15), - assignedTo: 'Pratyush Tewari', - isReady: true, - status: TaskStatus.toDo, - ), - TaskItem( - id: '3', - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua', - deadline: DateTime(2025, 6, 15), - assignedTo: 'Pratyush Tewari', - isReady: false, - status: TaskStatus.inProgress, - ), - TaskItem( - id: '4', - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua', - deadline: DateTime(2025, 6, 15), - assignedTo: 'Pratyush Tewari', - isReady: true, - status: TaskStatus.inProgress, - ), - TaskItem( - id: '5', - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua', - deadline: DateTime(2025, 6, 15), - assignedTo: 'Pratyush Tewari', - isReady: false, - status: TaskStatus.done, - ), - ]; + late BoardViewModel _boardViewModel; - void _updateTaskStatus(TaskItem task, TaskStatus newStatus) { - if (task.status == newStatus) { + @override + void initState() { + super.initState(); + _boardViewModel = context.read(); + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + _boardViewModel.getBoard(); + }); + } + + void _updateTaskStatus(BoardData task, TaskStatus newStatus) { + if (task.statusEnum == newStatus) { return; } - final oldStatus = task.status; - setState(() { - task.status = newStatus; - }); + final oldStatus = task.statusEnum; + + // update task status in ViewModel + _boardViewModel.updateTaskStatus(task.id!, newStatus.value); _showStatusChangeMessage(context, task, oldStatus, newStatus); } void _showStatusChangeMessage( BuildContext context, - TaskItem task, + BoardData task, TaskStatus oldStatus, TaskStatus newStatus, ) { final statusNames = { - TaskStatus.toDo: 'To Do', - TaskStatus.inProgress: 'In Progress', - TaskStatus.done: 'Done', + TaskStatus.toDo: BoardStrings.toDo, + TaskStatus.inProgress: BoardStrings.inProgress, + TaskStatus.done: BoardStrings.done, }; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - 'Task "${task.description}" moved from ${statusNames[oldStatus]} to ${statusNames[newStatus]}', - style: const TextStyle(fontFamily: 'Peyda'), - ), - duration: const Duration(seconds: 2), - backgroundColor: AppColors.successColor, - behavior: SnackBarBehavior.floating, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + AppToast.success( + context, + BoardStrings.taskMovedMessage( + statusNames[oldStatus]!, + statusNames[newStatus]!, ), ); } - List _getTasksByStatus(TaskStatus status) { - return tasks.where((task) => task.status == status).toList(); - } - @override Widget build(BuildContext context) { SizeConfig.init(context); return Scaffold( backgroundColor: AppColors.backgroundColor, - body: Column( children: [ const DesktopNavigationWidget( currentIndex: 2, // Tenders index ), - SizedBox( - width: 740, - height: 735, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 64), - Text( - 'My board', - style: TextStyle( - fontSize: 20.0.sp(), - fontWeight: FontWeight.w600, - color: AppColors.titleColor, - ), - ), - const SizedBox(height: 20), - Expanded( - child: Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - color: AppColors.primary20, + Consumer( + builder: (context, viewModel, child) { + if (viewModel.isLoading) { + return const Expanded( + child: Center( + child: CircularProgressIndicator( + color: AppColors.secondary50, ), - child: Row( + ), + ); + } + if (viewModel.errorMessage != null) { + return Expanded( + child: Center(child: Text(viewModel.errorMessage!)), + ); + } + if (viewModel.hasData == false) { + return const Expanded( + child: Center(child: Text(BoardStrings.emptyListText)), + ); + } + + return Expanded( + child: SingleChildScrollView( + child: SizedBox( + width: 740, + height: 735, + child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // To Do - Expanded( - child: _BoardColumn( - title: 'To do', - color: AppColors.primary20, - textColor: const Color(0xFF1976D2), - tasks: _getTasksByStatus(TaskStatus.toDo), - status: TaskStatus.toDo, - onTaskDropped: _updateTaskStatus, + const SizedBox(height: 32), + Text( + BoardStrings.boardTitle, + style: TextStyle( + fontSize: 20.0.sp(), + fontWeight: FontWeight.w600, + color: AppColors.titleColor, ), ), - const SizedBox(width: 12), - // In Progress + const SizedBox(height: 20), Expanded( - child: _BoardColumn( - title: 'In progress', - color: AppColors.orange20, - textColor: AppColors.orange, - tasks: _getTasksByStatus(TaskStatus.inProgress), - status: TaskStatus.inProgress, - onTaskDropped: _updateTaskStatus, - ), - ), - const SizedBox(width: 12), - // Done - Expanded( - child: _BoardColumn( - title: 'Done', - color: AppColors.secondary20, - textColor: AppColors.secondary70, - tasks: _getTasksByStatus(TaskStatus.done), - status: TaskStatus.done, - onTaskDropped: _updateTaskStatus, + child: Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: AppColors.primary20, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // To Do + Expanded( + child: _BoardColumn( + title: BoardStrings.toDo, + color: AppColors.primary20, + textColor: const Color(0xFF1976D2), + tasks: viewModel.getTasksByStatus( + TaskStatus.toDo, + ), + status: TaskStatus.toDo, + onTaskDropped: _updateTaskStatus, + isValidMove: viewModel.isValidMove, + ), + ), + const SizedBox(width: 12), + // In Progress + Expanded( + child: _BoardColumn( + title: BoardStrings.inProgress, + color: AppColors.orange20, + textColor: AppColors.orange, + tasks: viewModel.getTasksByStatus( + TaskStatus.inProgress, + ), + status: TaskStatus.inProgress, + onTaskDropped: _updateTaskStatus, + isValidMove: viewModel.isValidMove, + ), + ), + const SizedBox(width: 12), + // Done + Expanded( + child: _BoardColumn( + title: BoardStrings.done, + color: AppColors.secondary20, + textColor: AppColors.secondary70, + tasks: viewModel.getTasksByStatus( + TaskStatus.done, + ), + status: TaskStatus.done, + onTaskDropped: _updateTaskStatus, + isValidMove: viewModel.isValidMove, + ), + ), + ], + ), ), ), ], ), ), ), - ], - ), + ); + }, ), ], ), @@ -214,9 +191,10 @@ class _BoardColumn extends StatelessWidget { final String title; final Color color; final Color textColor; - final List tasks; + final List tasks; final TaskStatus status; - final Function(TaskItem, TaskStatus) onTaskDropped; + final Function(BoardData, TaskStatus) onTaskDropped; + final bool Function(TaskStatus, TaskStatus) isValidMove; const _BoardColumn({ required this.title, @@ -225,13 +203,14 @@ class _BoardColumn extends StatelessWidget { required this.tasks, required this.status, required this.onTaskDropped, + required this.isValidMove, }); @override Widget build(BuildContext context) { - return DragTarget( + return DragTarget( onWillAcceptWithDetails: (details) { - return details.data.status != status; + return isValidMove(details.data.statusEnum, status); }, onAcceptWithDetails: (details) { onTaskDropped(details.data, status); @@ -288,7 +267,7 @@ class _BoardColumn extends StatelessWidget { } class _TaskCard extends StatelessWidget { - final TaskItem task; + final BoardData task; const _TaskCard({required this.task}); @@ -298,7 +277,7 @@ class _TaskCard extends StatelessWidget { @override Widget build(BuildContext context) { - return Draggable( + return Draggable( data: task, feedback: Material( elevation: 8, @@ -348,9 +327,9 @@ class _TaskCard extends StatelessWidget { padding: EdgeInsets.symmetric(horizontal: 10.0.w()), decoration: BoxDecoration( color: - task.status == TaskStatus.toDo + task.statusEnum == TaskStatus.toDo ? AppColors.primary20 - : task.status == TaskStatus.inProgress + : task.statusEnum == TaskStatus.inProgress ? AppColors.orange10 : AppColors.secondary10, borderRadius: BorderRadius.circular(4), @@ -359,7 +338,7 @@ class _TaskCard extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - 'Deadline :', + '${BoardStrings.deadlineLabel} :', style: TextStyle( fontSize: 14.0, fontWeight: FontWeight.w500, @@ -367,7 +346,7 @@ class _TaskCard extends StatelessWidget { ), ), Text( - _formatDate(task.deadline), + _formatDate(task.deadline ?? DateTime.now()), style: TextStyle( fontSize: 14.0, fontWeight: FontWeight.w500, @@ -379,7 +358,7 @@ class _TaskCard extends StatelessWidget { ), const SizedBox(height: 12), Text( - task.description, + task.description!, style: TextStyle(fontSize: 14, color: AppColors.grey70), maxLines: 3, overflow: TextOverflow.ellipsis, @@ -389,7 +368,7 @@ class _TaskCard extends StatelessWidget { Container( padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), decoration: BoxDecoration( - color: task.isReady ? AppColors.primary20 : AppColors.red0, + color: task.isReady ?? false ? AppColors.primary20 : AppColors.red0, borderRadius: BorderRadius.circular(8), ), child: Row( @@ -399,16 +378,18 @@ class _TaskCard extends StatelessWidget { width: 8, height: 8, decoration: BoxDecoration( - color: task.isReady ? AppColors.textBlue : AppColors.error, + color: task.isReady! ? AppColors.textBlue : AppColors.error, shape: BoxShape.circle, ), ), const SizedBox(width: 6), Text( - task.isReady ? 'Ready' : 'Incomplete', + task.isReady! + ? BoardStrings.readyLabel + : BoardStrings.incompleteLabel, style: TextStyle( fontSize: 12, - color: task.isReady ? AppColors.textBlue : AppColors.red20, + color: task.isReady! ? AppColors.textBlue : AppColors.red20, fontWeight: FontWeight.w400, ), ), @@ -421,7 +402,7 @@ class _TaskCard extends StatelessWidget { SvgPicture.asset(AssetsManager.profile), const SizedBox(width: 4), Text( - task.assignedTo, + task.assignedTo!, style: TextStyle(fontSize: 12, color: AppColors.grey60), ), ], diff --git a/lib/views/board/strings/board_strings.dart b/lib/views/board/strings/board_strings.dart new file mode 100644 index 0000000..f53a263 --- /dev/null +++ b/lib/views/board/strings/board_strings.dart @@ -0,0 +1,14 @@ +class BoardStrings { + BoardStrings._(); + + static const String boardTitle = 'My Board'; + static const String toDo = 'To Do'; + static const String inProgress = 'In Progress'; + static const String done = 'Done'; + static const String emptyListText = 'No tasks found'; + static const String deadlineLabel = 'Deadline'; + static const String readyLabel = 'Ready'; + static const String incompleteLabel = 'Incomplete'; + static String taskMovedMessage(String oldStatus, String newStatus) => + 'Task moved from $oldStatus to $newStatus'; +}