Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improving UX: Intuitive User Posts and User Events Display and Dynamic Feed Functionality #2321

Merged
merged 16 commits into from
Jan 21, 2024
99 changes: 84 additions & 15 deletions lib/services/post_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,28 +40,39 @@ class PostService {
// ignore: prefer_final_fields
List<Post> _posts = [];

//Getters
/// Getter for Stream of posts.
Stream<List<Post>> get postStream => _postStream;

/// Getter for Stream of update in any post.
Stream<Post> get updatedPostStream => _updatedPostStream;

//Setters
///This method sets up a stream that constantly listens to change in current org.
///
/// **params**:
/// None
///
/// **returns**:
/// None
void setOrgStreamSubscription() {
_userConfig.currentOrgInfoStream.listen((updatedOrganization) {
if (updatedOrganization != _currentOrg) {
print("org changes from post service");
_renderedPostID.clear();
_currentOrg = updatedOrganization;
getPosts();
}
});
}

/// Retrieves all posts of the organization.
/// Method used to fetch all posts of the current organisation.
///
/// **params**:
/// None
///
/// This method queries the organization ID from `_currentOrg` and fetches
/// posts using a GraphQL query. The retrieved posts are added to the internal
/// post stream
/// **returns**:
/// * `Future<void>`: define_the_return
Future<void> getPosts() async {
await _dbFunctions.refreshAccessToken(userConfig.currentUser.refreshToken!);
_dbFunctions.init();
// variables
final String currentOrgID = _currentOrg.id!;
final String query = PostQueries().getPostsById(currentOrgID);
Expand All @@ -76,7 +87,6 @@ class PostService {

// ignore:avoid_dynamic_calls
final List postsJson = result.data!['postsByOrganization'] as List;

postsJson.forEach((postJson) {
final Post post = Post.fromJson(postJson as Map<String, dynamic>);
if (!_renderedPostID.contains(post.sId)) {
Expand All @@ -87,10 +97,43 @@ class PostService {
_postStreamController.add(_posts);
}

/// This function is used to add Like to the Post.
/// Method to refresh feed of current selected organisation.
///
/// **params**:
/// None
///
/// params:
/// * [postId] : id of the post where like need to be added.
/// **returns**:
/// * `Future<void>`: define_the_return
Future<void> refreshFeed() async {
_posts.clear();
_renderedPostID.clear();
await getPosts();
}

///Method to add newly created post at the very top of the feed.
///
/// **params**:
/// * `newPost`: define_the_param
Dante291 marked this conversation as resolved.
Show resolved Hide resolved
///
/// **returns**:
/// None
void addNewpost(Post newPost) {
if (!_posts.contains(newPost)) {
_posts.insert(0, newPost);
}
_postStreamController.add(_posts);
}

///Method to add like on a Post.
///
/// This method basically update likedBy list of a Post
/// in database.
///
/// **params**:
/// * `postID`: define_the_param
///
/// **returns**:
/// * `Future<void>`: define_the_return
Future<void> addLike(String postID) async {
_localAddLike(postID);
final String mutation = PostQueries().addLike();
Expand All @@ -102,6 +145,13 @@ class PostService {
return result;
}

/// Locally add like on a Post and updates it using updated Post Stream.
///
/// **params**:
/// * `postID`: define_the_param
///
/// **returns**:
/// None
void _localAddLike(String postID) {
_posts.forEach((post) {
if (post.sId == postID) {
Expand All @@ -111,10 +161,16 @@ class PostService {
});
}

/// This function is used to remove like from the Post.
/// Method to remove like in a Post.
///
/// params:
/// * [postId] : id of the post where like need to be removed.
/// This method basically update likedBy list of a Post
/// and removes the like of a user in database.
///
/// **params**:
/// * `postID`: define_the_param
///
/// **returns**:
/// * `Future<void>`: define_the_return
Future<void> removeLike(String postID) async {
_removeLocal(postID);
final String mutation = PostQueries().removeLike();
Expand All @@ -124,6 +180,13 @@ class PostService {
return result;
}

/// Locally removes the like of a user and update the Post UI.
///
/// **params**:
/// * `postID`: define_the_param
///
/// **returns**:
/// None
void _removeLocal(String postID) {
_posts.forEach((post) {
if (post.sId == postID) {
Expand All @@ -135,7 +198,13 @@ class PostService {
});
}

// Functions related to comments
///Method to add comment of a user and update comments using updated Post Stream.
///
/// **params**:
/// * `postID`: define_the_param
///
/// **returns**:
/// None
void addCommentLocally(String postID) {
for (int i = 0; i < _posts.length; i++) {
if (_posts[i].sId == postID) {
Expand Down
22 changes: 22 additions & 0 deletions lib/utils/post_queries.dart
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,28 @@ class PostQueries {
file: \$file
) {
_id
text
createdAt
imageUrl
videoUrl
title
commentCount
likeCount
creator{
_id
firstName
lastName
image
}
organization{
_id
}
likedBy{
_id
}
comments{
_id
}
}
}
''';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ import 'dart:async';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:talawa/enums/enums.dart';
import 'package:talawa/locator.dart';
import 'package:talawa/models/organization/org_info.dart';
import 'package:talawa/models/post/post_model.dart';
import 'package:talawa/services/database_mutation_functions.dart';
import 'package:talawa/services/image_service.dart';
import 'package:talawa/services/navigation_service.dart';
import 'package:talawa/services/post_service.dart';
import 'package:talawa/services/third_party_service/multi_media_pick_service.dart';
import 'package:talawa/services/user_config.dart';
import 'package:talawa/utils/post_queries.dart';
Expand All @@ -31,6 +34,8 @@ class AddPostViewModel extends BaseModel {
final TextEditingController _controller = TextEditingController();
final TextEditingController _textHashTagController = TextEditingController();
final TextEditingController _titleController = TextEditingController();

/// to check if in.
late bool demoMode;

/// The image file that is to be uploaded.
Expand Down Expand Up @@ -171,14 +176,18 @@ class AddPostViewModel extends BaseModel {
// {TODO: Image not getting uploaded}
if (_imageFile == null) {
try {
await _dbFunctions.gqlAuthMutation(
final result = await _dbFunctions.gqlAuthMutation(
PostQueries().uploadPost(),
variables: {
"text": "${_controller.text} #${_textHashTagController.text}",
"organizationId": _selectedOrg.id,
"title": _titleController.text,
},
);
final Post newPost = Post.fromJson(
(result as QueryResult).data!['createPost'] as Map<String, dynamic>,
);
locator<PostService>().addNewpost(newPost);
_navigationService.showTalawaErrorSnackBar(
"Post is uploaded",
MessageType.info,
Expand All @@ -192,7 +201,7 @@ class AddPostViewModel extends BaseModel {
}
} else {
try {
await _dbFunctions.gqlAuthMutation(
final result = await _dbFunctions.gqlAuthMutation(
PostQueries().uploadPost(),
variables: {
"text": _controller.text,
Expand All @@ -201,6 +210,10 @@ class AddPostViewModel extends BaseModel {
"file": 'data:image/png;base64,${_imageInBase64!}',
},
);
final Post newPost = Post.fromJson(
(result as QueryResult).data!['createPost'] as Map<String, dynamic>,
);
locator<PostService>().addNewpost(newPost);
_navigationService.showTalawaErrorSnackBar(
"Post is uploaded",
MessageType.info,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,17 @@
String _chosenValue = 'All Events';
String _emptyListMessage = "Looks like there aren't any events.";
List<Event> _events = [];
final List<Event> _userEvents = [];
final Set<String> _uniqueEventIds = {};
late StreamSubscription _currentOrganizationStreamSubscription;
late final List<Event> _bufferEvents;

/// Getter method to retrieve the list of events.
List<Event> get events => _events;

/// Getter method to retrieve the list of User events.
List<Event> get userEvents => _userEvents;

Check warning on line 40 in lib/view_model/after_auth_view_models/event_view_models/explore_events_view_model.dart

View check run for this annotation

Codecov / codecov/patch

lib/view_model/after_auth_view_models/event_view_models/explore_events_view_model.dart#L40

Added line #L40 was not covered by tests

/// Getter method to retrieve the EventService instance.
EventService get eventService => _eventService;

Expand Down Expand Up @@ -72,6 +76,7 @@
Future<void> refreshEvents() async {
setState(ViewState.busy);
_events.clear();
_userEvents.clear();
_uniqueEventIds.clear();
await _eventService.getEvents();
setState(ViewState.idle);
Expand Down Expand Up @@ -108,13 +113,17 @@
/// **returns**:
/// * `Future<void>`: return future void.
Future<void> checkIfExistsAndAddNewEvent(Event newEvent) async {
// checking if the `newEvent.id` is unique and not exist already.
if ((!_uniqueEventIds.contains(newEvent.id)) &&
(newEvent.organization!.id == userConfig.currentOrg.id)) {
// Check if the event is unique and belongs to the current organization
if (!_uniqueEventIds.contains(newEvent.id) &&
newEvent.organization!.id == userConfig.currentOrg.id) {
_uniqueEventIds.add(newEvent.id!);
_parseEventDateTime(newEvent);
notifyListeners();
}
if (!_userEvents.any((event) => event.id == newEvent.id) &&
newEvent.creator!.id == userConfig.currentUser.id) {
_userEvents.insert(0, newEvent);
}
notifyListeners();
}

/// The helper function that used to parse the date and time.
Expand All @@ -136,18 +145,20 @@
newEvent.endTime = DateFormat('HH:mm:ss').format(DateTime.now());
}

final startMoment = DateTime.parse(
'${newEvent.startDate!} ${newEvent.startTime!}',
).toLocal();
try {
final startMoment =
DateTime.parse('${newEvent.startDate!} ${newEvent.startTime!}')
.toLocal();
final endMoment =
DateTime.parse('${newEvent.endDate!} ${newEvent.endTime!}').toLocal();

final endMoment = DateTime.parse(
'${newEvent.endDate!} ${newEvent.endTime!}',
).toLocal();

newEvent.startDate = DateFormat('yMd').format(startMoment);
newEvent.endDate = DateFormat('yMd').format(endMoment);
newEvent.startTime = DateFormat.jm().format(startMoment);
newEvent.endTime = DateFormat.jm().format(endMoment);
newEvent.startDate = DateFormat('yMd').format(startMoment);
newEvent.endDate = DateFormat('yMd').format(endMoment);
newEvent.startTime = DateFormat.jm().format(startMoment);
newEvent.endTime = DateFormat.jm().format(endMoment);
} catch (e) {
print('Error parsing event date/time: $e');

Check warning on line 160 in lib/view_model/after_auth_view_models/event_view_models/explore_events_view_model.dart

View check run for this annotation

Codecov / codecov/patch

lib/view_model/after_auth_view_models/event_view_models/explore_events_view_model.dart#L160

Added line #L160 was not covered by tests
}

_events.insert(0, newEvent);
}
Expand Down Expand Up @@ -176,6 +187,7 @@
print(result);
_uniqueEventIds.remove(eventId);
_events.removeWhere((element) => element.id == eventId);
_userEvents.removeWhere((element) => element.id == eventId);
await Future.delayed(const Duration(milliseconds: 500));
setState(ViewState.idle);
}
Expand Down Expand Up @@ -211,7 +223,7 @@
}
break;
// if `_chosenValue` is "created event".
case 'Created Events':
case 'My Events':
{
// loop through the `_events` list and check
// for the creator id matched the current user id.
Expand Down
Loading
Loading