diff --git a/lib/models/projects.dart b/lib/models/projects.dart index 13e7f9a8..0868e0f5 100644 --- a/lib/models/projects.dart +++ b/lib/models/projects.dart @@ -46,6 +46,22 @@ class Project { ProjectRelationships relationships; List? collaborators; + Project copyWith({ + String? id, + String? type, + ProjectAttributes? attributes, + ProjectRelationships? relationships, + List? collaborators, + }) { + return Project( + id: id ?? this.id, + type: type ?? this.type, + attributes: attributes ?? this.attributes, + relationships: relationships ?? this.relationships, + collaborators: collaborators ?? this.collaborators, + ); + } + bool get hasAuthorAccess { var currentUser = locator().currentUser; @@ -81,7 +97,7 @@ class ProjectAttributes { this.description, required this.view, required this.tags, - this.isStarred, + required this.isStarred, required this.authorName, required this.starsCount, }); @@ -94,9 +110,37 @@ class ProjectAttributes { String? description; int view; List tags; - bool? isStarred; + bool isStarred; String authorName; int starsCount; + + ProjectAttributes copyWith({ + String? name, + String? projectAccessType, + DateTime? createdAt, + DateTime? updatedAt, + ImagePreview? imagePreview, + String? description, + int? view, + List? tags, + bool? isStarred, + String? authorName, + int? starsCount, + }) { + return ProjectAttributes( + name: name ?? this.name, + projectAccessType: projectAccessType ?? this.projectAccessType, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + imagePreview: imagePreview ?? this.imagePreview, + view: view ?? this.view, + tags: tags ?? this.tags, + authorName: authorName ?? this.authorName, + starsCount: starsCount ?? this.starsCount, + description: description ?? this.description, + isStarred: isStarred ?? this.isStarred, + ); + } } class ImagePreview { diff --git a/lib/ui/views/profile/user_favourites_view.dart b/lib/ui/views/profile/user_favourites_view.dart index 993041ab..5653a592 100644 --- a/lib/ui/views/profile/user_favourites_view.dart +++ b/lib/ui/views/profile/user_favourites_view.dart @@ -44,6 +44,7 @@ class _UserFavouritesViewState extends State var _result = await Get.toNamed(ProjectDetailsView.id, arguments: project); if (_result is bool) model.onProjectDeleted(project.id); + if (_result is Project) model.onProjectUnstarred(project.id); }, ), ); diff --git a/lib/ui/views/projects/featured_projects_view.dart b/lib/ui/views/projects/featured_projects_view.dart index 68950d6c..93b82073 100644 --- a/lib/ui/views/projects/featured_projects_view.dart +++ b/lib/ui/views/projects/featured_projects_view.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:mobile_app/models/projects.dart'; import 'package:mobile_app/ui/components/cv_header.dart'; import 'package:mobile_app/ui/components/cv_primary_button.dart'; import 'package:mobile_app/ui/views/base_view.dart'; @@ -38,7 +39,14 @@ class _FeaturedProjectsViewState extends State { FeaturedProjectCard( project: project, onViewPressed: () async { - await Get.toNamed(ProjectDetailsView.id, arguments: project); + final _result = await Get.toNamed( + ProjectDetailsView.id, + arguments: project, + ); + + if (_result is Project) { + model.updateFeaturedProject(_result); + } }, ), ); diff --git a/lib/ui/views/projects/project_details_view.dart b/lib/ui/views/projects/project_details_view.dart index 135b62bc..06c4b683 100644 --- a/lib/ui/views/projects/project_details_view.dart +++ b/lib/ui/views/projects/project_details_view.dart @@ -8,7 +8,6 @@ import 'package:mobile_app/locator.dart'; import 'package:mobile_app/models/collaborators.dart'; import 'package:mobile_app/models/projects.dart'; import 'package:mobile_app/services/dialog_service.dart'; -import 'package:mobile_app/services/local_storage_service.dart'; import 'package:mobile_app/ui/components/cv_flat_button.dart'; import 'package:mobile_app/ui/views/base_view.dart'; import 'package:mobile_app/ui/views/profile/profile_view.dart'; @@ -32,8 +31,6 @@ class ProjectDetailsView extends StatefulWidget { } class _ProjectDetailsViewState extends State { - final LocalStorageService _localStorageService = - locator(); final DialogService _dialogService = locator(); late ProjectDetailsViewModel _model; final _formKey = GlobalKey(); @@ -566,92 +563,105 @@ class _ProjectDetailsViewState extends State { return BaseView( onModelReady: (model) { _model = model; + _model.receivedProject = _recievedProject; // initialize collaborators & isStarred for the project.. _model.collaborators = _recievedProject.collaborators; - _model.isProjectStarred = - _recievedProject.attributes.isStarred ?? false; + _model.isProjectStarred = _recievedProject.attributes.isStarred; _model.starCount = _recievedProject.attributes.starsCount; _model.fetchProjectDetails(_recievedProject.id); }, - builder: (context, model, child) => Scaffold( - appBar: AppBar( - title: const Text('Project Details'), - actions: [ - _buildShareActionButton(), - ], - ), - body: Builder(builder: (context) { - var _projectAttrs = _recievedProject.attributes; - var _items = []; - - // Adds project preview.. - _items.add(_buildProjectPreview()); - - _items.add(const SizedBox(height: 16)); - - // Adds Project Name, Star & View count.. - _items.add(_buildProjectNameHeader()); - - // Adds Project attributes.. - _items.add( - Container( - padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 8), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildProjectAuthor(), - _buildProjectDetailComponent( - 'Project Access Type', - _projectAttrs.projectAccessType, - ), - _buildProjectDescription(), - const Divider(height: 32), - Wrap( - spacing: 8, - crossAxisAlignment: WrapCrossAlignment.center, + builder: (context, model, child) => WillPopScope( + onWillPop: () async { + // Check whether the state (i.e starred or not) is changed + final bool isChanged = model.receivedProject.attributes.isStarred ^ + _recievedProject.attributes.isStarred; + Get.back( + result: isChanged ? model.receivedProject : null, + ); + return false; + }, + child: Scaffold( + appBar: AppBar( + title: const Text('Project Details'), + actions: [ + _buildShareActionButton(), + ], + ), + body: Builder( + builder: (context) { + var _projectAttrs = _recievedProject.attributes; + var _items = []; + + // Adds project preview.. + _items.add(_buildProjectPreview()); + + _items.add(const SizedBox(height: 16)); + + // Adds Project Name, Star & View count.. + _items.add(_buildProjectNameHeader()); + + // Adds Project attributes.. + _items.add( + Container( + padding: + const EdgeInsets.symmetric(vertical: 16, horizontal: 8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (!_recievedProject.hasAuthorAccess && - _localStorageService.isLoggedIn) - _buildForkProjectButton(), - if (_localStorageService.isLoggedIn) - _buildStarProjectButton(), + _buildProjectAuthor(), + _buildProjectDetailComponent( + 'Project Access Type', + _projectAttrs.projectAccessType, + ), + _buildProjectDescription(), + const Divider(height: 32), + Wrap( + spacing: 8, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + if (!_recievedProject.hasAuthorAccess && + model.isLoggedIn) + _buildForkProjectButton(), + if (model.isLoggedIn) _buildStarProjectButton(), + if (_recievedProject.hasAuthorAccess) + _buildAddCollaboratorsButton(), + ], + ), + const Divider(height: 32), if (_recievedProject.hasAuthorAccess) - _buildAddCollaboratorsButton(), + Wrap( + spacing: 8, + children: [ + _buildEditButton(), + _buildDeleteButton(), + ], + ), ], ), - const Divider(height: 32), - if (_recievedProject.hasAuthorAccess) - Wrap( - spacing: 8, - children: [ - _buildEditButton(), - _buildDeleteButton(), - ], - ), - ], - ), - ), - ); + ), + ); - if (_model.isSuccess(_model.FETCH_PROJECT_DETAILS) && - _model.collaborators.isNotEmpty) { - _items.add(const Divider()); + if (_model.isSuccess(_model.FETCH_PROJECT_DETAILS) && + _model.collaborators.isNotEmpty) { + _items.add(const Divider()); - _items.add(_buildProjectHeader('Collaborators')); + _items.add(_buildProjectHeader('Collaborators')); - for (var collaborator in _model.collaborators) { - _items.add( - _buildCollaborator(collaborator), + for (var collaborator in _model.collaborators) { + _items.add( + _buildCollaborator(collaborator), + ); + } + } + return ListView( + shrinkWrap: true, + padding: const EdgeInsets.all(16), + children: _items, ); - } - } - return ListView( - shrinkWrap: true, - padding: const EdgeInsets.all(16), - children: _items, - ); - }), + }, + ), + ), ), ); } diff --git a/lib/viewmodels/profile/user_favourites_viewmodel.dart b/lib/viewmodels/profile/user_favourites_viewmodel.dart index 04b6c850..537c2c96 100644 --- a/lib/viewmodels/profile/user_favourites_viewmodel.dart +++ b/lib/viewmodels/profile/user_favourites_viewmodel.dart @@ -27,11 +27,19 @@ class UserFavouritesViewModel extends BaseModel { notifyListeners(); } - void onProjectDeleted(String projectId) { + void _removeFromUserFavorites(String projectId) { _userFavourites.removeWhere((_project) => _project.id == projectId); notifyListeners(); } + void onProjectDeleted(String projectId) { + _removeFromUserFavorites(projectId); + } + + void onProjectUnstarred(String projectId) { + _removeFromUserFavorites(projectId); + } + Future? fetchUserFavourites({String? userId}) async { try { if (previousUserFavouritesBatch?.links.next != null) { diff --git a/lib/viewmodels/projects/featured_projects_viewmodel.dart b/lib/viewmodels/projects/featured_projects_viewmodel.dart index 42dd1cbb..e03bd07f 100644 --- a/lib/viewmodels/projects/featured_projects_viewmodel.dart +++ b/lib/viewmodels/projects/featured_projects_viewmodel.dart @@ -24,6 +24,14 @@ class FeaturedProjectsViewModel extends BaseModel { notifyListeners(); } + void updateFeaturedProject(Project project) { + final int index = _featuredProjects.indexWhere( + (element) => element.id == project.id, + ); + _featuredProjects[index] = project; + notifyListeners(); + } + Future? fetchFeaturedProjects({int size = 5}) async { try { if (previousFeaturedProjectsBatch?.links.next != null) { diff --git a/lib/viewmodels/projects/project_details_viewmodel.dart b/lib/viewmodels/projects/project_details_viewmodel.dart index 9455b584..0dfa6276 100644 --- a/lib/viewmodels/projects/project_details_viewmodel.dart +++ b/lib/viewmodels/projects/project_details_viewmodel.dart @@ -5,6 +5,7 @@ import 'package:mobile_app/models/failure_model.dart'; import 'package:mobile_app/models/projects.dart'; import 'package:mobile_app/services/API/collaborators_api.dart'; import 'package:mobile_app/services/API/projects_api.dart'; +import 'package:mobile_app/services/local_storage_service.dart'; import 'package:mobile_app/viewmodels/base_viewmodel.dart'; class ProjectDetailsViewModel extends BaseModel { @@ -18,6 +19,12 @@ class ProjectDetailsViewModel extends BaseModel { final ProjectsApi _projectsApi = locator(); final CollaboratorsApi _collaboratorsApi = locator(); + final LocalStorageService _localStorageService = + locator(); + + bool get isLoggedIn => _localStorageService.isLoggedIn; + + late Project receivedProject; Project? _project; @@ -164,6 +171,13 @@ class ProjectDetailsViewModel extends BaseModel { isProjectStarred = _toggleMessage!.contains('Starred') ? true : false; isProjectStarred ? starCount++ : starCount--; + receivedProject = receivedProject.copyWith( + attributes: receivedProject.attributes.copyWith( + isStarred: isProjectStarred, + starsCount: starCount, + ), + ); + setStateFor(TOGGLE_STAR, ViewState.Success); } on Failure catch (f) { setStateFor(TOGGLE_STAR, ViewState.Error);