Add lemmy support for comment viewing and voting
This commit is contained in:
parent
847c84431c
commit
069eb3700b
|
@ -28,6 +28,6 @@ void main() async {
|
|||
// Load user settings
|
||||
final settingsController = SettingsController();
|
||||
await settingsController.loadSettings();
|
||||
print(settingsController.api);
|
||||
|
||||
runApp(MyApp(settingsController: settingsController));
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ class API {
|
|||
final http.Client httpClient;
|
||||
final String server;
|
||||
|
||||
final KbinAPIComments comments;
|
||||
final APIComments comments;
|
||||
final KbinAPIDomains domains;
|
||||
final APIThreads entries;
|
||||
final APIMagazines magazines;
|
||||
|
@ -32,7 +32,7 @@ class API {
|
|||
this.software,
|
||||
this.httpClient,
|
||||
this.server,
|
||||
) : comments = KbinAPIComments(software, httpClient, server),
|
||||
) : comments = APIComments(software, httpClient, server),
|
||||
domains = KbinAPIDomains(software, httpClient, server),
|
||||
entries = APIThreads(software, httpClient, server),
|
||||
magazines = APIMagazines(software, httpClient, server),
|
||||
|
|
|
@ -10,6 +10,14 @@ import 'package:interstellar/src/widgets/selection_menu.dart';
|
|||
|
||||
enum CommentSort { newest, top, hot, active, oldest }
|
||||
|
||||
const Map<CommentSort, String> lemmyCommentSortMap = {
|
||||
CommentSort.active: 'Controversial',
|
||||
CommentSort.hot: 'Hot',
|
||||
CommentSort.newest: 'New',
|
||||
CommentSort.oldest: 'Old',
|
||||
CommentSort.top: 'Top',
|
||||
};
|
||||
|
||||
const SelectionMenu<CommentSort> commentSortSelect = SelectionMenu(
|
||||
'Sort Comments',
|
||||
[
|
||||
|
@ -50,12 +58,12 @@ const _postTypeKbinComment = {
|
|||
PostType.microblog: 'post-comments',
|
||||
};
|
||||
|
||||
class KbinAPIComments {
|
||||
class APIComments {
|
||||
final ServerSoftware software;
|
||||
final http.Client httpClient;
|
||||
final String server;
|
||||
|
||||
KbinAPIComments(
|
||||
APIComments(
|
||||
this.software,
|
||||
this.httpClient,
|
||||
this.server,
|
||||
|
@ -69,6 +77,9 @@ class KbinAPIComments {
|
|||
List<String>? langs,
|
||||
bool? usePreferredLangs,
|
||||
}) async {
|
||||
switch (software) {
|
||||
case ServerSoftware.kbin:
|
||||
case ServerSoftware.mbin:
|
||||
final path = '/api/${_postTypeKbin[postType]}/$postId/comments';
|
||||
final query = queryParams({
|
||||
'p': page?.toString(),
|
||||
|
@ -83,6 +94,23 @@ class KbinAPIComments {
|
|||
|
||||
return CommentListModel.fromKbin(
|
||||
jsonDecode(response.body) as Map<String, Object?>);
|
||||
|
||||
case ServerSoftware.lemmy:
|
||||
const path = '/api/v3/comment/list';
|
||||
final query = queryParams({
|
||||
'post_id': postId.toString(),
|
||||
'page': page?.toString(),
|
||||
'sort': lemmyCommentSortMap[sort],
|
||||
'max_depth': '8',
|
||||
});
|
||||
|
||||
final response = await httpClient.get(Uri.https(server, path, query));
|
||||
|
||||
httpErrorHandler(response, message: 'Failed to load comments');
|
||||
|
||||
return CommentListModel.fromLemmy(
|
||||
jsonDecode(response.body) as Map<String, Object?>);
|
||||
}
|
||||
}
|
||||
|
||||
Future<CommentListModel> listFromUser(
|
||||
|
@ -110,6 +138,9 @@ class KbinAPIComments {
|
|||
}
|
||||
|
||||
Future<CommentModel> get(PostType postType, int commentId) async {
|
||||
switch (software) {
|
||||
case ServerSoftware.kbin:
|
||||
case ServerSoftware.mbin:
|
||||
final path = '/api/${_postTypeKbinComment[postType]}/$commentId';
|
||||
|
||||
final response = await httpClient.get(Uri.https(server, path));
|
||||
|
@ -118,15 +149,37 @@ class KbinAPIComments {
|
|||
|
||||
return CommentModel.fromKbin(
|
||||
jsonDecode(response.body) as Map<String, Object?>);
|
||||
|
||||
case ServerSoftware.lemmy:
|
||||
const path = '/api/v3/comment/list';
|
||||
final query = queryParams({
|
||||
'parent_id': commentId.toString(),
|
||||
});
|
||||
|
||||
final response = await httpClient.get(Uri.https(server, path, query));
|
||||
|
||||
httpErrorHandler(response, message: 'Failed to load comment');
|
||||
|
||||
return CommentModel.fromLemmy(
|
||||
(jsonDecode(response.body)['comments'] as List<dynamic>).first,
|
||||
possibleChildren:
|
||||
jsonDecode(response.body)['comments'] as List<dynamic>,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<CommentModel> putVote(
|
||||
Future<CommentModel> vote(
|
||||
PostType postType,
|
||||
int commentId,
|
||||
int choice,
|
||||
int newScore,
|
||||
) async {
|
||||
final path =
|
||||
'/api/${_postTypeKbinComment[postType]}/$commentId/vote/$choice';
|
||||
switch (software) {
|
||||
case ServerSoftware.kbin:
|
||||
case ServerSoftware.mbin:
|
||||
final path = choice == 1
|
||||
? '/api/${_postTypeKbinComment[postType]}/$commentId/favourite'
|
||||
: '/api/${_postTypeKbinComment[postType]}/$commentId/vote/$choice';
|
||||
|
||||
final response = await httpClient.put(Uri.https(server, path));
|
||||
|
||||
|
@ -134,17 +187,41 @@ class KbinAPIComments {
|
|||
|
||||
return CommentModel.fromKbin(
|
||||
jsonDecode(response.body) as Map<String, Object?>);
|
||||
case ServerSoftware.lemmy:
|
||||
const path = '/api/v3/comment/like';
|
||||
|
||||
final response = await httpClient.post(
|
||||
Uri.https(server, path),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: jsonEncode({
|
||||
'comment_id': commentId,
|
||||
'score': newScore,
|
||||
}),
|
||||
);
|
||||
|
||||
httpErrorHandler(response, message: 'Failed to send vote');
|
||||
|
||||
return CommentModel.fromLemmy(
|
||||
jsonDecode(response.body)['comment_view'] as Map<String, Object?>);
|
||||
}
|
||||
}
|
||||
|
||||
Future<CommentModel> putFavorite(PostType postType, int commentId) async {
|
||||
final path = '/api/${_postTypeKbinComment[postType]}/$commentId/favourite';
|
||||
Future<CommentModel> boost(PostType postType, int commentId) async {
|
||||
switch (software) {
|
||||
case ServerSoftware.kbin:
|
||||
case ServerSoftware.mbin:
|
||||
final path = '/api/${_postTypeKbinComment[postType]}/$commentId/vote/1';
|
||||
|
||||
final response = await httpClient.put(Uri.https(server, path));
|
||||
|
||||
httpErrorHandler(response, message: 'Failed to send vote');
|
||||
httpErrorHandler(response, message: 'Failed to send boost');
|
||||
|
||||
return CommentModel.fromKbin(
|
||||
jsonDecode(response.body) as Map<String, Object?>);
|
||||
|
||||
case ServerSoftware.lemmy:
|
||||
throw Exception('Tried to boost on lemmy');
|
||||
}
|
||||
}
|
||||
|
||||
Future<CommentModel> create(
|
||||
|
|
|
@ -21,6 +21,19 @@ class CommentListModel with _$CommentListModel {
|
|||
nextPage: kbinCalcNextPaginationPage(
|
||||
json['pagination'] as Map<String, Object?>),
|
||||
);
|
||||
|
||||
factory CommentListModel.fromLemmy(Map<String, Object?> json) =>
|
||||
CommentListModel(
|
||||
items: (json['comments'] as List<dynamic>)
|
||||
.where(
|
||||
(c) => (c['comment']['path'] as String).split('.').length == 2)
|
||||
.map((c) => CommentModel.fromLemmy(
|
||||
c as Map<String, Object?>,
|
||||
possibleChildren: json['comments'] as List<dynamic>,
|
||||
))
|
||||
.toList(),
|
||||
nextPage: null,
|
||||
);
|
||||
}
|
||||
|
||||
@freezed
|
||||
|
@ -44,7 +57,6 @@ class CommentModel with _$CommentModel {
|
|||
required bool? isAdult,
|
||||
required DateTime createdAt,
|
||||
required DateTime? editedAt,
|
||||
required DateTime lastActive,
|
||||
required List<CommentModel>? children,
|
||||
required int childCount,
|
||||
required String visibility,
|
||||
|
@ -73,7 +85,6 @@ class CommentModel with _$CommentModel {
|
|||
isAdult: json['isAdult'] as bool,
|
||||
createdAt: DateTime.parse(json['createdAt'] as String),
|
||||
editedAt: optionalDateTime(json['editedAt'] as String?),
|
||||
lastActive: DateTime.parse(json['lastActive'] as String),
|
||||
children: (json['children'] as List<dynamic>)
|
||||
.map((c) => CommentModel.fromKbin(c as Map<String, Object?>))
|
||||
.toList(),
|
||||
|
@ -81,43 +92,53 @@ class CommentModel with _$CommentModel {
|
|||
visibility: json['visibility'] as String,
|
||||
);
|
||||
|
||||
// factory CommentModel.fromLemmy(
|
||||
// Map<String, Object?> json, List<dynamic> allCommentsJson) {
|
||||
// final lemmyComment = json['comment'] as Map<String, Object?>;
|
||||
// final lemmyCounts = json['counts'] as Map<String, Object?>;
|
||||
factory CommentModel.fromLemmy(
|
||||
Map<String, Object?> json, {
|
||||
List<dynamic> possibleChildren = const [],
|
||||
}) {
|
||||
final lemmyComment = json['comment'] as Map<String, Object?>;
|
||||
final lemmyCounts = json['counts'] as Map<String, Object?>;
|
||||
|
||||
// final lemmyPath = lemmyComment['path'] as String;
|
||||
// final lemmyPathSegments =
|
||||
// lemmyPath.split('.').map((e) => int.parse(e)).toList();
|
||||
final lemmyPath = lemmyComment['path'] as String;
|
||||
final lemmyPathSegments =
|
||||
lemmyPath.split('.').map((e) => int.parse(e)).toList();
|
||||
|
||||
// return CommentModel(
|
||||
// id: json['commentId'] as int,
|
||||
// user: UserModel.fromLemmy(json['creator'] as Map<String, Object?>),
|
||||
// magazine:
|
||||
// MagazineModel.fromLemmy(json['community'] as Map<String, Object?>),
|
||||
// postType: PostType.thread,
|
||||
// postId: (json['post'] as Map<String, Object?>)['id'] as int,
|
||||
// rootId: lemmyPathSegments.length > 2 ? lemmyPathSegments[1] : null,
|
||||
// parentId: lemmyPathSegments.length > 2
|
||||
// ? lemmyPathSegments[lemmyPathSegments.length - 2]
|
||||
// : null,
|
||||
// image: null,
|
||||
// body: json['content'] as String,
|
||||
// lang: null,
|
||||
// upvotes: lemmyCounts['upvotes'] as int,
|
||||
// downvotes: lemmyCounts['downvotes'] as int,
|
||||
// boosts: null,
|
||||
// myVote: json['my_vote'] as int?,
|
||||
// myBoost: null,
|
||||
// isAdult: json['isAdult'] as bool,
|
||||
// createdAt: DateTime.parse(json['createdAt'] as String),
|
||||
// editedAt: optionalDateTime(json['editedAt'] as String?),
|
||||
// lastActive: DateTime.parse(json['lastActive'] as String),
|
||||
// children: (json['children'] as List<dynamic>)
|
||||
// .map((c) => CommentModel.fromKbin(c as Map<String, Object?>))
|
||||
// .toList(),
|
||||
// childCount: json['childCount'] as int,
|
||||
// visibility: json['visibility'] as String,
|
||||
// );
|
||||
// }
|
||||
final children = possibleChildren
|
||||
.where((c) {
|
||||
String childPath = c['comment']['path'];
|
||||
|
||||
return childPath.startsWith('$lemmyPath.') &&
|
||||
(childPath.split('.').length == lemmyPathSegments.length + 1);
|
||||
})
|
||||
.map((c) =>
|
||||
CommentModel.fromLemmy(c, possibleChildren: possibleChildren))
|
||||
.toList();
|
||||
|
||||
return CommentModel(
|
||||
id: lemmyComment['id'] as int,
|
||||
user: UserModel.fromLemmy(json['creator'] as Map<String, Object?>),
|
||||
magazine:
|
||||
MagazineModel.fromLemmy(json['community'] as Map<String, Object?>),
|
||||
postType: PostType.thread,
|
||||
postId: (json['post'] as Map<String, Object?>)['id'] as int,
|
||||
rootId: lemmyPathSegments.length > 2 ? lemmyPathSegments[1] : null,
|
||||
parentId: lemmyPathSegments.length > 2
|
||||
? lemmyPathSegments[lemmyPathSegments.length - 2]
|
||||
: null,
|
||||
image: null,
|
||||
body: lemmyComment['content'] as String,
|
||||
lang: null,
|
||||
upvotes: lemmyCounts['upvotes'] as int,
|
||||
downvotes: lemmyCounts['downvotes'] as int,
|
||||
boosts: null,
|
||||
myVote: json['my_vote'] as int?,
|
||||
myBoost: null,
|
||||
isAdult: null,
|
||||
createdAt: DateTime.parse(lemmyComment['published'] as String),
|
||||
editedAt: optionalDateTime(json['updated'] as String?),
|
||||
children: children,
|
||||
childCount: lemmyCounts['child_count'] as int,
|
||||
visibility: 'visible',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ class _EntryCommentState extends State<PostComment> {
|
|||
.read<SettingsController>()
|
||||
.api
|
||||
.comments
|
||||
.putVote(widget.comment.postType, widget.comment.id, 1);
|
||||
.boost(widget.comment.postType, widget.comment.id);
|
||||
widget.onUpdate(newValue.copyWith(
|
||||
childCount: widget.comment.childCount,
|
||||
children: widget.comment.children,
|
||||
|
@ -66,7 +66,8 @@ class _EntryCommentState extends State<PostComment> {
|
|||
.read<SettingsController>()
|
||||
.api
|
||||
.comments
|
||||
.putFavorite(widget.comment.postType, widget.comment.id);
|
||||
.vote(widget.comment.postType, widget.comment.id, 1,
|
||||
widget.comment.myVote == 1 ? 0 : 1);
|
||||
widget.onUpdate(newValue.copyWith(
|
||||
childCount: widget.comment.childCount,
|
||||
children: widget.comment.children,
|
||||
|
@ -80,7 +81,8 @@ class _EntryCommentState extends State<PostComment> {
|
|||
.read<SettingsController>()
|
||||
.api
|
||||
.comments
|
||||
.putVote(widget.comment.postType, widget.comment.id, -1);
|
||||
.vote(widget.comment.postType, widget.comment.id, -1,
|
||||
widget.comment.myVote == -1 ? 0 : -1);
|
||||
widget.onUpdate(newValue.copyWith(
|
||||
childCount: widget.comment.childCount,
|
||||
children: widget.comment.children,
|
||||
|
|
|
@ -28,8 +28,8 @@ class _PostPageState extends State<PostPage> {
|
|||
|
||||
CommentSort commentSort = CommentSort.hot;
|
||||
|
||||
final PagingController<String, CommentModel> _pagingController =
|
||||
PagingController(firstPageKey: '1');
|
||||
final PagingController<int, CommentModel> _pagingController =
|
||||
PagingController(firstPageKey: 1);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -48,13 +48,13 @@ class _PostPageState extends State<PostPage> {
|
|||
widget.onUpdate(newValue);
|
||||
}
|
||||
|
||||
Future<void> _fetchPage(String pageKey) async {
|
||||
Future<void> _fetchPage(int pageKey) async {
|
||||
try {
|
||||
final newPage =
|
||||
await context.read<SettingsController>().api.comments.list(
|
||||
_data.type,
|
||||
_data.id,
|
||||
page: int.parse(pageKey),
|
||||
page: pageKey,
|
||||
sort: commentSort,
|
||||
usePreferredLangs: whenLoggedIn(context,
|
||||
context.read<SettingsController>().useAccountLangFilter),
|
||||
|
@ -69,7 +69,14 @@ class _PostPageState extends State<PostPage> {
|
|||
final newItems =
|
||||
newPage.items.where((e) => !currentItemIds.contains(e.id)).toList();
|
||||
|
||||
_pagingController.appendPage(newItems, newPage.nextPage);
|
||||
_pagingController.appendPage(
|
||||
newItems,
|
||||
context.read<SettingsController>().serverSoftware ==
|
||||
ServerSoftware.lemmy
|
||||
? (newPage.items.isEmpty ? null : pageKey + 1)
|
||||
: (newPage.nextPage == null
|
||||
? null
|
||||
: int.parse(newPage.nextPage!)));
|
||||
} catch (error) {
|
||||
_pagingController.error = error;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue