Add deduplication

This commit is contained in:
shilangyu 2021-04-06 17:52:10 +02:00
parent b35507c3ad
commit e0cb1a0a83
5 changed files with 68 additions and 41 deletions

View File

@ -38,6 +38,7 @@ class CommunitiesListPage extends StatelessWidget {
)
],
),
uniqueProp: (item) => item.community.id,
),
);
}

View File

@ -118,6 +118,7 @@ class InboxPage extends HookWidget {
canBeMarkedAsRead: true,
hideOnRead: unreadOnly.value,
),
uniqueProp: (item) => item.comment.id,
),
SortableInfiniteList<PersonMentionView>(
noItems: const Text('no mentions'),
@ -135,6 +136,7 @@ class InboxPage extends HookWidget {
umv,
hideOnRead: unreadOnly.value,
),
uniqueProp: (item) => item.personMention.id,
),
InfiniteScroll<PrivateMessageView>(
noItems: const Padding(
@ -154,6 +156,7 @@ class InboxPage extends HookWidget {
privateMessageView: mv,
hideOnRead: unreadOnly.value,
),
uniqueProp: (item) => item.privateMessage.id,
),
],
),

View File

@ -120,6 +120,20 @@ class _SearchResultsList extends HookWidget {
throw UnimplementedError();
}
},
uniqueProp: (item) {
switch (type) {
case SearchType.comments:
return (item as CommentView).comment.id;
case SearchType.communities:
return (item as CommunityView).community.id;
case SearchType.posts:
return (item as PostView).post.id;
case SearchType.users:
return (item as PersonViewSafe).person.id;
default:
return item;
}
},
);
}
}

View File

@ -1,3 +1,5 @@
import 'dart:collection';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
@ -47,6 +49,10 @@ class InfiniteScroll<T> extends HookWidget {
/// Widget that will be displayed if there are no items
final Widget noItems;
/// Maps an item to its unique property that will allow to detect possible
/// duplicates thus perfoming deduplication
final Object Function(T item) uniqueProp;
const InfiniteScroll({
this.batchSize = 10,
this.leading = const SizedBox.shrink(),
@ -57,6 +63,7 @@ class InfiniteScroll<T> extends HookWidget {
@required this.fetcher,
this.controller,
this.noItems = const SizedBox.shrink(),
this.uniqueProp,
}) : assert(itemBuilder != null),
assert(fetcher != null),
assert(batchSize > 0);
@ -64,6 +71,8 @@ class InfiniteScroll<T> extends HookWidget {
@override
Widget build(BuildContext context) {
final data = useState<List<T>>([]);
// holds unique props of the data
final dataSet = useRef(HashSet<Object>());
final hasMore = useRef(true);
final isFetching = useRef(false);
@ -111,13 +120,19 @@ class InfiniteScroll<T> extends HookWidget {
// if it's already fetching more, skip
if (!isFetching.current) {
isFetching.current = true;
fetcher(page, batchSize).then((newData) {
fetcher(page, batchSize).then((incoming) {
// if got less than the batchSize, mark the list as done
if (newData.length < batchSize) {
if (incoming.length < batchSize) {
hasMore.current = false;
}
final newData = incoming.where(
(e) => !dataSet.current.contains(uniqueProp?.call(e) ?? e),
);
// append new data
data.value = [...data.value, ...newData];
dataSet.current.addAll(newData.map(uniqueProp ?? (e) => e));
}).whenComplete(() => isFetching.current = false);
}

View File

@ -20,6 +20,7 @@ class SortableInfiniteList<T> extends HookWidget {
final Function onStyleChange;
final Widget noItems;
final SortType defaultSort;
final Object Function(T item) uniqueProp;
const SortableInfiniteList({
@required this.fetcher,
@ -28,6 +29,7 @@ class SortableInfiniteList<T> extends HookWidget {
this.onStyleChange,
this.noItems,
this.defaultSort = SortType.active,
this.uniqueProp,
}) : assert(fetcher != null),
assert(itemBuilder != null),
assert(defaultSort != null);
@ -56,49 +58,41 @@ class SortableInfiniteList<T> extends HookWidget {
controller: isc,
batchSize: 20,
noItems: noItems,
uniqueProp: uniqueProp,
);
}
}
class InfinitePostList extends StatelessWidget {
final FetcherWithSorting<PostView> fetcher;
final InfiniteScrollController controller;
const InfinitePostList({
@required this.fetcher,
this.controller,
}) : assert(fetcher != null);
Widget build(BuildContext context) => SortableInfiniteList<PostView>(
onStyleChange: () {},
itemBuilder: (post) => Column(
children: [
PostWidget(post),
const SizedBox(height: 20),
],
),
fetcher: fetcher,
controller: controller,
noItems: const Text('there are no posts'),
);
class InfinitePostList extends SortableInfiniteList<PostView> {
InfinitePostList({
@required FetcherWithSorting<PostView> fetcher,
InfiniteScrollController controller,
}) : super(
itemBuilder: (post) => Column(
children: [
PostWidget(post),
const SizedBox(height: 20),
],
),
fetcher: fetcher,
controller: controller,
noItems: const Text('there are no posts'),
uniqueProp: (item) => item.post.id,
);
}
class InfiniteCommentList extends StatelessWidget {
final FetcherWithSorting<CommentView> fetcher;
final InfiniteScrollController controller;
const InfiniteCommentList({
@required this.fetcher,
this.controller,
}) : assert(fetcher != null);
Widget build(BuildContext context) => SortableInfiniteList<CommentView>(
itemBuilder: (comment) => CommentWidget(
CommentTree(comment),
detached: true,
),
fetcher: fetcher,
controller: controller,
noItems: const Text('there are no comments'),
);
class InfiniteCommentList extends SortableInfiniteList<CommentView> {
InfiniteCommentList({
@required FetcherWithSorting<CommentView> fetcher,
InfiniteScrollController controller,
}) : super(
itemBuilder: (comment) => CommentWidget(
CommentTree(comment),
detached: true,
),
fetcher: fetcher,
controller: controller,
noItems: const Text('there are no comments'),
uniqueProp: (item) => item.comment.id,
);
}