mirror of
https://gitlab.com/comunic/comunicmobile
synced 2024-11-25 14:29:22 +00:00
295 lines
7.7 KiB
Dart
295 lines
7.7 KiB
Dart
import 'dart:math';
|
|
|
|
import 'package:comunic/helpers/events_helper.dart';
|
|
import 'package:comunic/helpers/groups_helper.dart';
|
|
import 'package:comunic/helpers/posts_helper.dart';
|
|
import 'package:comunic/helpers/users_helper.dart';
|
|
import 'package:comunic/lists/groups_list.dart';
|
|
import 'package:comunic/lists/posts_list.dart';
|
|
import 'package:comunic/lists/users_list.dart';
|
|
import 'package:comunic/models/comment.dart';
|
|
import 'package:comunic/models/post.dart';
|
|
import 'package:comunic/ui/screens/conversation_screen.dart';
|
|
import 'package:comunic/ui/tiles/post_tile.dart';
|
|
import 'package:comunic/ui/widgets/safe_state.dart';
|
|
import 'package:comunic/ui/widgets/scroll_watcher.dart';
|
|
import 'package:comunic/utils/intl_utils.dart';
|
|
import 'package:comunic/utils/ui_utils.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
|
/// Posts list widget
|
|
///
|
|
/// Displays a list of posts
|
|
///
|
|
/// @author Pierre HUBERT
|
|
|
|
class PostsListWidget extends StatefulWidget {
|
|
final List<Widget>? topWidgets;
|
|
final Future<PostsList?> Function() getPostsList;
|
|
final Future<PostsList?> Function(int from)? getOlder;
|
|
final bool showPostsTarget;
|
|
final bool buildListView;
|
|
final bool userNamesClickable;
|
|
final bool disablePullToRefresh;
|
|
|
|
const PostsListWidget({
|
|
Key? key,
|
|
required this.getPostsList,
|
|
required this.showPostsTarget,
|
|
this.userNamesClickable = true,
|
|
this.buildListView = true,
|
|
this.getOlder,
|
|
this.topWidgets,
|
|
this.disablePullToRefresh = false,
|
|
}) : super(key: key);
|
|
|
|
@override
|
|
State<StatefulWidget> createState() => PostsListWidgetState();
|
|
}
|
|
|
|
class PostsListWidgetState extends SafeState<PostsListWidget> {
|
|
// Helpers
|
|
final UsersHelper _usersHelper = UsersHelper();
|
|
final GroupsHelper _groupsHelper = GroupsHelper();
|
|
|
|
// Class members
|
|
PostsList? _list;
|
|
late UsersList _users;
|
|
late GroupsList _groups;
|
|
ScrollWatcher? _scrollController;
|
|
ErrorLevel _error = ErrorLevel.NONE;
|
|
bool _loading = false;
|
|
|
|
final _registeredPosts = Set<int>();
|
|
|
|
set error(ErrorLevel err) => setState(() => _error = err);
|
|
|
|
int get _numberTopWidgets =>
|
|
widget.topWidgets == null ? 0 : widget.topWidgets!.length;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
_scrollController = ScrollWatcher(onReachBottom: reachedPostsBottom);
|
|
|
|
// Register to events
|
|
this.listen<NewCommentEvent>((ev) => this._addComment(ev.comment));
|
|
this.listenChangeState<UpdatedCommentEvent>(
|
|
(ev) => this._updateComment(ev.comment));
|
|
this.listenChangeState<DeletedCommentEvent>(
|
|
(ev) => this._removeComment(ev.commentID));
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
super.dispose();
|
|
|
|
_unregisterAllPosts();
|
|
}
|
|
|
|
@override
|
|
void setState(fn) {
|
|
if (!mounted) return;
|
|
super.setState(fn);
|
|
|
|
// Register for posts update
|
|
_registerRequiredPosts();
|
|
}
|
|
|
|
@override
|
|
void didChangeDependencies() {
|
|
super.didChangeDependencies();
|
|
loadPostsList();
|
|
}
|
|
|
|
void _loadError() {
|
|
error = _list == null ? ErrorLevel.MAJOR : ErrorLevel.MINOR;
|
|
_loading = false;
|
|
}
|
|
|
|
/// Load the list of posts
|
|
Future<void> loadPostsList({bool getOlder = false}) async {
|
|
if (_loading) return;
|
|
|
|
_loading = true;
|
|
|
|
try {
|
|
final list = !getOlder
|
|
? await widget.getPostsList()
|
|
: await widget.getOlder!(_list!.oldestID);
|
|
|
|
if (list == null) return _loadError();
|
|
|
|
final users = await _usersHelper.getList(list.usersID);
|
|
|
|
final groups = await _groupsHelper.getList(list.groupsID);
|
|
|
|
if (groups == null) return _loadError();
|
|
|
|
if (!mounted) return;
|
|
|
|
setState(() {
|
|
if (!getOlder) {
|
|
_list = list;
|
|
_users = users;
|
|
_groups = groups;
|
|
} else {
|
|
_list!.addAll(list);
|
|
_users.addAll(users);
|
|
_groups.addAll(groups);
|
|
}
|
|
});
|
|
} catch (e, s) {
|
|
print("Failed to load post information ! $e => $s");
|
|
_loadError();
|
|
}
|
|
|
|
_loading = false;
|
|
}
|
|
|
|
/// Register for potential new posts
|
|
void _registerRequiredPosts() async {
|
|
if (_list == null) return;
|
|
|
|
final missing = _list!
|
|
.where((f) => !_registeredPosts.contains(f.id))
|
|
.map((f) => f.id)
|
|
.toSet();
|
|
|
|
_registeredPosts.addAll(missing);
|
|
|
|
for (final postID in missing) {
|
|
await PostsHelper().registerPostEvents(postID);
|
|
}
|
|
}
|
|
|
|
/// Unregister from all posts
|
|
///
|
|
/// This method should be called only once
|
|
void _unregisterAllPosts() async {
|
|
for (final postID in _registeredPosts) {
|
|
// We put the try - catch here because we must absolutely unregister ALL
|
|
// POSTS event if one post fails to remove
|
|
try {
|
|
await PostsHelper().unregisterPostEvents(postID);
|
|
} catch (e, stack) {
|
|
print("Could not unregister post! $e $stack");
|
|
}
|
|
}
|
|
}
|
|
|
|
Widget _buildErrorCard() {
|
|
return buildErrorCard(tr("Could not get the list of posts !"));
|
|
}
|
|
|
|
Widget _buildNoPostNotice() {
|
|
return Center(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: Text(tr("There is no post to display here yet.")!),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildListViewWithRefreshIndicator() => RefreshIndicator(
|
|
child: _buildListView(),
|
|
onRefresh: () => loadPostsList(),
|
|
);
|
|
|
|
Widget _buildListView() {
|
|
return ListView.builder(
|
|
// We use max function here to display to post notice in case there are not posts to display but there are custom widgets...
|
|
itemCount: max(_list!.length, 1) + _numberTopWidgets,
|
|
|
|
itemBuilder: _buildItem,
|
|
controller: _scrollController,
|
|
);
|
|
}
|
|
|
|
Widget _buildColumn() {
|
|
return Column(
|
|
children: List.generate(
|
|
_list!.length,
|
|
(i) => _buildItem(null, i),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildItem(BuildContext? context, int index) {
|
|
if (index < _numberTopWidgets) return widget.topWidgets![index];
|
|
|
|
// Show no posts notice if required
|
|
if (_list!.length == 0) return _buildNoPostNotice();
|
|
|
|
return PostTile(
|
|
post: _list![index - _numberTopWidgets],
|
|
usersInfo: _users,
|
|
groupsInfo: _groups,
|
|
onDeletedPost: _removePost,
|
|
showPostTarget: widget.showPostsTarget,
|
|
userNamesClickable: widget.userNamesClickable,
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (_error == ErrorLevel.MAJOR) return _buildErrorCard();
|
|
if (_list == null) return buildCenteredProgressBar();
|
|
if (_list!.length == 0 && _numberTopWidgets == 0)
|
|
return _buildNoPostNotice();
|
|
if (!widget.buildListView) return _buildColumn();
|
|
|
|
if (widget.disablePullToRefresh) return _buildListView();
|
|
|
|
return _buildListViewWithRefreshIndicator();
|
|
}
|
|
|
|
void _removePost(Post post) => setState(() => _list!.remove(post));
|
|
|
|
void reachedPostsBottom() {
|
|
if (widget.getOlder != null) loadPostsList(getOlder: true);
|
|
}
|
|
|
|
/// Add new comment
|
|
void _addComment(Comment c) async {
|
|
if (_list == null) return;
|
|
|
|
try {
|
|
// This command will throw if post could not be found
|
|
final Post p = _list!.singleWhere((p) => p.id == c.postID);
|
|
|
|
if (!_users.hasUser(c.userID))
|
|
_users.add(await UsersHelper().getSingleWithThrow(c.userID));
|
|
|
|
setState(() {
|
|
p.comments!.add(c);
|
|
});
|
|
} catch (e, stack) {
|
|
print("$e\n$stack");
|
|
}
|
|
}
|
|
|
|
/// Update a comment
|
|
void _updateComment(Comment c) {
|
|
if (_list == null) return;
|
|
|
|
try {
|
|
final Post p = _list!.singleWhere((p) => p.id == c.postID);
|
|
|
|
final index = p.comments!.indexWhere((d) => d.id == c.id);
|
|
|
|
if (index > -1) p.comments![index] = c;
|
|
} catch (e, stack) {
|
|
print("$e\n$stack");
|
|
}
|
|
}
|
|
|
|
/// Remove a comment from the list
|
|
void _removeComment(int? commentID) {
|
|
if (_list == null) return;
|
|
|
|
_list!.forEach((p) => p.comments!.removeWhere((c) => c.id == commentID));
|
|
}
|
|
}
|