Merge pull request #116 from krawieck/api-v2

This commit is contained in:
Marcin Wojnarowski 2021-01-26 23:59:16 +01:00 committed by GitHub
commit 67e9ea9a3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 619 additions and 567 deletions

View File

@ -1,4 +1,4 @@
import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:lemmy_api_client/v2.dart';
import 'util/hot_rank.dart';
@ -25,13 +25,16 @@ extension on CommentSortType {
a.comment.computedHotRank.compareTo(b.comment.computedHotRank);
case CommentSortType.new_:
return (b, a) => a.comment.published.compareTo(b.comment.published);
return (b, a) =>
a.comment.comment.published.compareTo(b.comment.comment.published);
case CommentSortType.old:
return (b, a) => b.comment.published.compareTo(a.comment.published);
return (b, a) =>
b.comment.comment.published.compareTo(a.comment.comment.published);
case CommentSortType.top:
return (b, a) => a.comment.score.compareTo(b.comment.score);
return (b, a) =>
a.comment.counts.score.compareTo(b.comment.counts.score);
}
throw Exception('unreachable');
@ -50,15 +53,16 @@ class CommentTree {
static List<CommentTree> fromList(List<CommentView> comments) {
CommentTree gatherChildren(CommentTree parent) {
for (final el in comments) {
if (el.parentId == parent.comment.id) {
if (el.comment.parentId == parent.comment.comment.id) {
parent.children.add(gatherChildren(CommentTree(el)));
}
}
return parent;
}
final topLevelParents =
comments.where((e) => e.parentId == null).map((e) => CommentTree(e));
final topLevelParents = comments
.where((e) => e.comment.parentId == null)
.map((e) => CommentTree(e));
final result = topLevelParents.map(gatherChildren).toList();
return result;

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:lemmy_api_client/v2.dart';
import '../pages/settings.dart';
import '../util/goto.dart';

View File

@ -2,7 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:lemmy_api_client/v2.dart';
import 'package:url_launcher/url_launcher.dart' as ul;
import '../hooks/delayed_loading.dart';
@ -32,10 +32,9 @@ class AddAccountPage extends HookWidget {
final selectedInstance = useState(instanceHost);
final icon = useState<String>(null);
useEffect(() {
LemmyApi(selectedInstance.value)
.v1
.getSite()
.then((site) => icon.value = site.site.icon);
LemmyApiV2(selectedInstance.value)
.run(GetSite())
.then((site) => icon.value = site.siteView.site.icon);
return null;
}, [selectedInstance.value]);

View File

@ -1,7 +1,7 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:lemmy_api_client/v2.dart';
import '../hooks/debounce.dart';
import '../hooks/stores.dart';
@ -33,7 +33,7 @@ class AddInstancePage extends HookWidget {
return;
}
try {
icon.value = (await LemmyApi(inst).v1.getSite()).site.icon;
icon.value = (await LemmyApiV2(inst).run(GetSite())).siteView.site.icon;
isSite.value = true;
// ignore: avoid_catches_without_on_clauses
} catch (e) {
@ -75,7 +75,7 @@ class AddInstancePage extends HookWidget {
),
body: ListView(
children: [
if (isSite.value == true)
if (isSite.value == true && icon.value != null)
SizedBox(
height: 150,
child: FullscreenableImage(

View File

@ -1,6 +1,6 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:lemmy_api_client/v2.dart';
import '../util/goto.dart';
import '../widgets/markdown_text.dart';
@ -55,23 +55,23 @@ class CommunitiesListItem extends StatelessWidget {
@override
Widget build(BuildContext context) => ListTile(
title: Text(community.name),
subtitle: community.description != null
title: Text(community.community.name),
subtitle: community.community.description != null
? Opacity(
opacity: 0.7,
child: MarkdownText(
community.description,
community.community.description,
instanceHost: community.instanceHost,
),
)
: null,
onTap: () =>
goToCommunity.byId(context, community.instanceHost, community.id),
leading: community.icon != null
onTap: () => goToCommunity.byId(
context, community.instanceHost, community.community.id),
leading: community.community.icon != null
? CachedNetworkImage(
height: 50,
width: 50,
imageUrl: community.icon,
imageUrl: community.community.icon,
imageBuilder: (context, imageProvider) => Container(
decoration: BoxDecoration(
shape: BoxShape.circle,

View File

@ -5,7 +5,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:fuzzy/fuzzy.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:lemmy_api_client/v2.dart';
import '../hooks/delayed_loading.dart';
import '../hooks/refreshable.dart';
@ -33,8 +33,9 @@ class CommunitiesTab extends HookWidget {
getInstances() {
final futures = accountsStore.loggedInInstances
.map(
(instanceHost) =>
LemmyApi(instanceHost).v1.getSite().then((e) => e.site),
(instanceHost) => LemmyApiV2(instanceHost)
.run(GetSite())
.then((e) => e.siteView.site),
)
.toList();
@ -44,14 +45,13 @@ class CommunitiesTab extends HookWidget {
getCommunities() {
final futures = accountsStore.loggedInInstances
.map(
(instanceHost) => LemmyApi(instanceHost)
.v1
.getUserDetails(
(instanceHost) => LemmyApiV2(instanceHost)
.run(GetUserDetails(
sort: SortType.active,
savedOnly: false,
userId:
accountsStore.defaultTokenFor(instanceHost).payload.id,
)
))
.then((e) => e.follows),
)
.toList();
@ -108,8 +108,8 @@ class CommunitiesTab extends HookWidget {
final instances = instancesRefreshable.snapshot.data;
final communities = communitiesRefreshable.snapshot.data
..forEach(
(e) => e.sort((a, b) => a.communityName.compareTo(b.communityName)));
..forEach((e) =>
e.sort((a, b) => a.community.name.compareTo(b.community.name)));
final filterIcon = () {
if (filterController.text.isEmpty) {
@ -127,12 +127,12 @@ class CommunitiesTab extends HookWidget {
filterCommunities(List<CommunityFollowerView> comm) {
final matches = Fuzzy(
comm.map((e) => e.communityName).toList(),
comm.map((e) => e.community.name).toList(),
options: FuzzyOptions(threshold: 0.5),
).search(filterController.text).map((e) => e.item);
return matches
.map((match) => comm.firstWhere((e) => e.communityName == match));
.map((match) => comm.firstWhere((e) => e.community.name == match));
}
toggleCollapse(int i) => isCollapsed.value =
@ -203,18 +203,18 @@ class CommunitiesTab extends HookWidget {
onTap: () => goToCommunity.byId(
context,
accountsStore.loggedInInstances.elementAt(i),
comm.communityId),
comm.community.id),
dense: true,
leading: VerticalDivider(
color: theme.hintColor,
),
title: Row(
children: [
if (comm.communityIcon != null)
if (comm.community.icon != null)
CachedNetworkImage(
height: 30,
width: 30,
imageUrl: comm.communityIcon,
imageUrl: comm.community.icon,
imageBuilder: (context, imageProvider) =>
Container(
decoration: BoxDecoration(
@ -231,14 +231,14 @@ class CommunitiesTab extends HookWidget {
const SizedBox(width: 30),
const SizedBox(width: 10),
Text(
'''!${comm.communityName}${comm.isLocal ? '' : '@${comm.originInstanceHost}'}''',
'''!${comm.community.name}${comm.community.local ? '' : '@${comm.community.originInstanceHost}'}''',
),
],
),
trailing: _CommunitySubscribeToggle(
key: ValueKey(comm.communityId),
key: ValueKey(comm.community.id),
instanceHost: comm.instanceHost,
communityId: comm.communityId,
communityId: comm.community.id,
),
),
)
@ -272,11 +272,11 @@ class _CommunitySubscribeToggle extends HookWidget {
delayed.start();
try {
await LemmyApi(instanceHost).v1.followCommunity(
communityId: communityId,
follow: !subbed.value,
auth: accountsStore.defaultTokenFor(instanceHost).raw,
);
await LemmyApiV2(instanceHost).run(FollowCommunity(
communityId: communityId,
follow: !subbed.value,
auth: accountsStore.defaultTokenFor(instanceHost).raw,
));
subbed.value = !subbed.value;
} on Exception catch (err) {
Scaffold.of(context).showSnackBar(SnackBar(

View File

@ -3,7 +3,7 @@ import 'package:esys_flutter_share/esys_flutter_share.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:lemmy_api_client/v2.dart';
import 'package:url_launcher/url_launcher.dart' as ul;
import '../hooks/delayed_loading.dart';
@ -45,8 +45,8 @@ class CommunityPage extends HookWidget {
_community = null;
CommunityPage.fromCommunityView(this._community)
: instanceHost = _community.instanceHost,
communityId = _community.id,
communityName = _community.name;
communityId = _community.community.id,
communityName = _community.community.name;
@override
Widget build(BuildContext context) {
@ -57,15 +57,15 @@ class CommunityPage extends HookWidget {
final token = accountsStore.defaultTokenFor(instanceHost);
if (communityId != null) {
return LemmyApi(instanceHost).v1.getCommunity(
id: communityId,
auth: token?.raw,
);
return LemmyApiV2(instanceHost).run(GetCommunity(
id: communityId,
auth: token?.raw,
));
} else {
return LemmyApi(instanceHost).v1.getCommunity(
name: communityName,
auth: token?.raw,
);
return LemmyApiV2(instanceHost).run(GetCommunity(
name: communityName,
auth: token?.raw,
));
}
});
@ -73,7 +73,7 @@ class CommunityPage extends HookWidget {
final community = () {
if (fullCommunitySnap.hasData) {
return fullCommunitySnap.data.community;
return fullCommunitySnap.data.communityView;
} else if (_community != null) {
return _community;
} else {
@ -111,7 +111,7 @@ class CommunityPage extends HookWidget {
// FUNCTIONS
void _share() =>
Share.text('Share instance', community.actorId, 'text/plain');
Share.text('Share instance', community.community.actorId, 'text/plain');
void _openMoreMenu() {
showModalBottomSheet(
@ -123,8 +123,9 @@ class CommunityPage extends HookWidget {
ListTile(
leading: const Icon(Icons.open_in_browser),
title: const Text('Open in browser'),
onTap: () async => await ul.canLaunch(community.actorId)
? ul.launch(community.actorId)
onTap: () async => await ul
.canLaunch(community.community.actorId)
? ul.launch(community.community.actorId)
: Scaffold.of(context).showSnackBar(
const SnackBar(content: Text("can't open in browser"))),
),
@ -133,11 +134,10 @@ class CommunityPage extends HookWidget {
title: const Text('Nerd stuff'),
onTap: () {
showInfoTablePopup(context, {
'id': community.id,
'actorId': community.actorId,
'created by': '@${community.creatorName}',
'hot rank': community.hotRank,
'published': community.published,
'id': community.community.id,
'actorId': community.community.actorId,
'created by': '@${community.creator.name}',
'published': community.community.published,
});
},
),
@ -160,7 +160,7 @@ class CommunityPage extends HookWidget {
backgroundColor: theme.cardColor,
brightness: theme.brightness,
iconTheme: theme.iconTheme,
title: Text('!${community.name}',
title: Text('!${community.community.name}',
style: TextStyle(color: colorOnCard)),
actions: [
IconButton(icon: const Icon(Icons.share), onPressed: _share),
@ -190,26 +190,26 @@ class CommunityPage extends HookWidget {
children: [
InfinitePostList(
fetcher: (page, batchSize, sort) =>
LemmyApi(community.instanceHost).v1.getPosts(
type: PostListingType.community,
sort: sort,
communityId: community.id,
page: page,
limit: batchSize,
),
LemmyApiV2(community.instanceHost).run(GetPosts(
type: PostListingType.community,
sort: sort,
communityId: community.community.id,
page: page,
limit: batchSize,
)),
),
InfiniteCommentList(
fetcher: (page, batchSize, sortType) =>
LemmyApi(community.instanceHost).v1.getComments(
communityId: community.id,
auth: accountsStore
.defaultTokenFor(community.instanceHost)
?.raw,
type: CommentListingType.community,
sort: sortType,
limit: batchSize,
page: page,
)),
LemmyApiV2(community.instanceHost).run(GetComments(
communityId: community.community.id,
auth: accountsStore
.defaultTokenFor(community.instanceHost)
?.raw,
type: CommentListingType.community,
sort: sortType,
limit: batchSize,
page: page,
))),
_AboutTab(
community: community,
moderators: fullCommunitySnap.data?.moderators,
@ -237,7 +237,7 @@ class _CommunityOverview extends StatelessWidget {
final theme = Theme.of(context);
final shadow = BoxShadow(color: theme.canvasColor, blurRadius: 5);
final icon = community.icon != null
final icon = community.community.icon != null
? Stack(
alignment: Alignment.center,
children: [
@ -256,9 +256,9 @@ class _CommunityOverview extends StatelessWidget {
width: 83,
height: 83,
child: FullscreenableImage(
url: community.icon,
url: community.community.icon,
child: CachedNetworkImage(
imageUrl: community.icon,
imageUrl: community.community.icon,
imageBuilder: (context, imageProvider) => Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
@ -277,11 +277,11 @@ class _CommunityOverview extends StatelessWidget {
: null;
return Stack(children: [
if (community.banner != null)
if (community.community.banner != null)
FullscreenableImage(
url: community.banner,
url: community.community.banner,
child: CachedNetworkImage(
imageUrl: community.banner,
imageUrl: community.community.banner,
errorWidget: (_, __, ___) => const SizedBox.shrink(),
),
),
@ -289,7 +289,7 @@ class _CommunityOverview extends StatelessWidget {
child: Padding(
padding: const EdgeInsets.only(top: 45),
child: Column(children: [
if (community.icon != null) icon,
if (community.community.icon != null) icon,
// NAME
Center(
child: Padding(
@ -304,17 +304,17 @@ class _CommunityOverview extends StatelessWidget {
text: '!',
style: TextStyle(fontWeight: FontWeight.w200)),
TextSpan(
text: community.name,
text: community.community.name,
style: const TextStyle(fontWeight: FontWeight.w600)),
const TextSpan(
text: '@',
style: TextStyle(fontWeight: FontWeight.w200)),
TextSpan(
text: community.originInstanceHost,
text: community.community.originInstanceHost,
style: const TextStyle(fontWeight: FontWeight.w600),
recognizer: TapGestureRecognizer()
..onTap = () => goToInstance(
context, community.originInstanceHost),
context, community.community.originInstanceHost),
),
],
),
@ -326,7 +326,7 @@ class _CommunityOverview extends StatelessWidget {
child: Padding(
padding: const EdgeInsets.only(top: 8, left: 20, right: 20),
child: Text(
community.title,
community.community.title,
textAlign: TextAlign.center,
style:
TextStyle(fontWeight: FontWeight.w300, shadows: [shadow]),
@ -346,7 +346,7 @@ class _CommunityOverview extends StatelessWidget {
padding: EdgeInsets.only(right: 3),
child: Icon(Icons.people, size: 20),
),
Text(compactNumber(community.numberOfSubscribers)),
Text(compactNumber(community.counts.subscribers)),
const Spacer(
flex: 4,
),
@ -416,10 +416,10 @@ class _AboutTab extends StatelessWidget {
return ListView(
padding: const EdgeInsets.only(top: 20),
children: [
if (community.description != null) ...[
if (community.community.description != null) ...[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: MarkdownText(community.description,
child: MarkdownText(community.community.description,
instanceHost: community.instanceHost),
),
const _Divider(),
@ -435,13 +435,13 @@ class _AboutTab extends StatelessWidget {
child: _Badge('X users online'),
),
_Badge(
'''${community.numberOfSubscribers} subscriber${pluralS(community.numberOfSubscribers)}'''),
'''${community.counts.subscribers} subscriber${pluralS(community.counts.subscribers)}'''),
_Badge(
'''${community.numberOfPosts} post${pluralS(community.numberOfPosts)}'''),
'''${community.counts.posts} post${pluralS(community.counts.posts)}'''),
Padding(
padding: const EdgeInsets.only(right: 15),
child: _Badge(
'''${community.numberOfComments} comment${pluralS(community.numberOfComments)}'''),
'''${community.counts.comments} comment${pluralS(community.counts.comments)}'''),
),
],
),
@ -454,7 +454,7 @@ class _AboutTab extends StatelessWidget {
borderRadius: BorderRadius.circular(10),
),
onPressed: goToCategories,
child: Text(community.categoryName),
child: Text(community.category.name),
),
),
const _Divider(),
@ -475,9 +475,12 @@ class _AboutTab extends StatelessWidget {
child: Text('Mods:', style: theme.textTheme.subtitle2),
),
for (final mod in moderators)
// TODO: add user picture, maybe make it into reusable component
ListTile(
title: Text(mod.userPreferredUsername ?? '@${mod.userName}'),
onTap: () => goToUser.byId(context, mod.instanceHost, mod.userId),
title: Text(
mod.moderator.preferredUsername ?? '@${mod.moderator.name}'),
onTap: () =>
goToUser.byId(context, mod.instanceHost, mod.moderator.id),
),
]
],
@ -536,10 +539,10 @@ class _FollowButton extends HookWidget {
subscribe(Jwt token) async {
delayed.start();
try {
await LemmyApi(community.instanceHost).v1.followCommunity(
communityId: community.id,
await LemmyApiV2(community.instanceHost).run(FollowCommunity(
communityId: community.community.id,
follow: !isSubbed.value,
auth: token.raw);
auth: token.raw));
isSubbed.value = !isSubbed.value;
// ignore: avoid_catches_without_on_clauses
} catch (e) {

View File

@ -2,7 +2,8 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:image_picker/image_picker.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:lemmy_api_client/pictrs.dart';
import 'package:lemmy_api_client/v2.dart';
import '../hooks/delayed_loading.dart';
import '../hooks/image_picker.dart';
@ -26,20 +27,20 @@ class CreatePostFab extends HookWidget {
return FloatingActionButton(
onPressed: loggedInAction((_) => showCupertinoModalPopup(
context: context, builder: (_) => CreatePost())),
context: context, builder: (_) => CreatePostPage())),
child: const Icon(Icons.add),
);
}
}
/// Modal for creating a post to some community in some instance
class CreatePost extends HookWidget {
class CreatePostPage extends HookWidget {
final CommunityView community;
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey();
CreatePost() : community = null;
CreatePost.toCommunity(this.community);
CreatePostPage() : community = null;
CreatePostPage.toCommunity(this.community);
@override
Widget build(BuildContext context) {
@ -58,16 +59,15 @@ class CreatePost extends HookWidget {
final pictrsDeleteToken = useState<PictrsUploadFile>(null);
final allCommunitiesSnap = useMemoFuture(
() => LemmyApi(selectedInstance.value)
.v1
.listCommunities(
sort: SortType.hot,
limit: 9999,
auth: accStore.defaultTokenFor(selectedInstance.value).raw,
)
() => LemmyApiV2(selectedInstance.value)
.run(ListCommunities(
sort: SortType.hot,
limit: 9999,
auth: accStore.defaultTokenFor(selectedInstance.value).raw,
))
.then(
(value) {
value.sort((a, b) => a.name.compareTo(b.name));
value.sort((a, b) => a.community.name.compareTo(b.community.name));
return value;
},
),
@ -82,7 +82,7 @@ class CreatePost extends HookWidget {
imageUploadLoading.value = true;
final token = accStore.defaultTokenFor(selectedInstance.value);
final pictrs = LemmyApi(selectedInstance.value).pictrs;
final pictrs = PictrsApi(selectedInstance.value);
final upload =
await pictrs.upload(filePath: pic.path, auth: token.raw);
pictrsDeleteToken.value = upload.files[0];
@ -100,8 +100,7 @@ class CreatePost extends HookWidget {
}
removePicture() {
LemmyApi(selectedInstance.value)
.pictrs
PictrsApi(selectedInstance.value)
.delete(pictrsDeleteToken.value)
.catchError((_) {});
@ -135,15 +134,16 @@ class CreatePost extends HookWidget {
border: OutlineInputBorder()),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
value: selectedCommunity.value?.name,
value: selectedCommunity.value?.community?.name,
hint: const Text('Community'),
onChanged: (val) => selectedCommunity.value =
allCommunitiesSnap.data.firstWhere((e) => e.name == val),
onChanged: (val) => selectedCommunity.value = allCommunitiesSnap.data
.firstWhere((e) => e.community.name == val),
items: allCommunitiesSnap.hasData
? allCommunitiesSnap.data
// FIXME: use id instead of name cuz it breaks with federation
.map((e) => DropdownMenuItem(
value: e.name,
child: Text(e.name),
value: e.community.name,
child: Text(e.community.name),
))
.toList()
: const [
@ -219,19 +219,20 @@ class CreatePost extends HookWidget {
return;
}
final api = LemmyApi(selectedInstance.value).v1;
final api = LemmyApiV2(selectedInstance.value);
final token = accStore.defaultTokenFor(selectedInstance.value);
delayed.start();
try {
final res = await api.createPost(
url: urlController.text.isEmpty ? null : urlController.text,
body: bodyController.text.isEmpty ? null : bodyController.text,
nsfw: nsfw.value,
name: titleController.text,
communityId: selectedCommunity.value.id,
auth: token.raw);
final res = await api.run(CreatePost(
url: urlController.text.isEmpty ? null : urlController.text,
body: bodyController.text.isEmpty ? null : bodyController.text,
nsfw: nsfw.value,
name: titleController.text,
communityId: selectedCommunity.value.community.id,
auth: token.raw,
));
unawaited(goToReplace(context, (_) => FullPostPage.fromPostView(res)));
return;
// ignore: avoid_catches_without_on_clauses

View File

@ -3,7 +3,7 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:lemmy_api_client/v2.dart';
import '../hooks/logged_in_action.dart';
import '../hooks/refreshable.dart';
@ -25,15 +25,17 @@ class FullPostPage extends HookWidget {
assert(instanceHost != null),
post = null;
FullPostPage.fromPostView(this.post)
: id = post.id,
: id = post.post.id,
instanceHost = post.instanceHost;
@override
Widget build(BuildContext context) {
final accStore = useAccountsStore();
final fullPostRefreshable = useRefreshable(() => LemmyApi(instanceHost)
.v1
.getPost(id: id, auth: accStore.defaultTokenFor(instanceHost)?.raw));
final fullPostRefreshable =
useRefreshable(() => LemmyApiV2(instanceHost).run(GetPost(
id: id,
auth: accStore.defaultTokenFor(instanceHost)?.raw,
)));
final loggedInAction = useLoggedInAction(instanceHost);
final newComments = useState(const <CommentView>[]);
@ -58,7 +60,7 @@ class FullPostPage extends HookWidget {
// VARIABLES
final post = fullPostRefreshable.snapshot.hasData
? fullPostRefreshable.snapshot.data.post
? fullPostRefreshable.snapshot.data.postView
: this.post;
final fullPost = fullPostRefreshable.snapshot.data;
@ -78,7 +80,7 @@ class FullPostPage extends HookWidget {
}
}
sharePost() => Share.text('Share post', post.apId, 'text/plain');
sharePost() => Share.text('Share post', post.post.apId, 'text/plain');
comment() async {
final newComment = await showCupertinoModalPopup<CommentView>(
@ -98,7 +100,7 @@ class FullPostPage extends HookWidget {
SavePostButton(post),
IconButton(
icon: Icon(moreIcon),
onPressed: () => Post.showMoreMenu(context, post)),
onPressed: () => PostWidget.showMoreMenu(context, post)),
],
),
floatingActionButton: FloatingActionButton(
@ -109,11 +111,11 @@ class FullPostPage extends HookWidget {
child: ListView(
physics: const AlwaysScrollableScrollPhysics(),
children: [
Post(post, fullPost: true),
PostWidget(post, fullPost: true),
if (fullPostRefreshable.snapshot.hasData)
CommentSection(
newComments.value.followedBy(fullPost.comments).toList(),
postCreatorId: fullPost.post.creatorId)
postCreatorId: fullPost.postView.creator.id)
else if (fullPostRefreshable.snapshot.hasError)
Padding(
padding:

View File

@ -4,7 +4,7 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:lemmy_api_client/v2.dart';
import '../hooks/infinite_scroll.dart';
import '../hooks/memo_future.dart';
@ -35,10 +35,11 @@ class HomeTab extends HookWidget {
final instancesIcons = useMemoFuture(() async {
final instances = accStore.instances.toList(growable: false);
final sites = await Future.wait(instances
.map((e) => LemmyApi(e).v1.getSite().catchError((e) => null)));
.map((e) => LemmyApiV2(e).run(GetSite()).catchError((e) => null)));
return {
for (var i = 0; i < sites.length; i++) instances[i]: sites[i].site.icon
for (var i = 0; i < sites.length; i++)
instances[i]: sites[i].siteView.site.icon
};
});
@ -262,13 +263,13 @@ class InfiniteHomeList extends HookWidget {
}();
final futures =
instances.map((instanceHost) => LemmyApi(instanceHost).v1.getPosts(
instances.map((instanceHost) => LemmyApiV2(instanceHost).run(GetPosts(
type: listingType,
sort: sort,
page: page,
limit: limit,
auth: accStore.defaultTokenFor(instanceHost)?.raw,
));
)));
final posts = await Future.wait(futures);
final newPosts = <PostView>[];
final longest = posts.map((e) => e.length).reduce(max);
@ -286,13 +287,13 @@ class InfiniteHomeList extends HookWidget {
Future<List<PostView>> Function(int, int) fetcherFromInstance(
String instanceHost, PostListingType listingType, SortType sort) =>
(page, batchSize) => LemmyApi(instanceHost).v1.getPosts(
(page, batchSize) => LemmyApiV2(instanceHost).run(GetPosts(
type: listingType,
sort: sort,
page: page,
limit: batchSize,
auth: accStore.defaultTokenFor(instanceHost)?.raw,
);
));
return InfiniteScroll<PostView>(
prepend: Column(
@ -305,7 +306,7 @@ class InfiniteHomeList extends HookWidget {
),
builder: (post) => Column(
children: [
Post(post),
PostWidget(post),
const SizedBox(height: 20),
],
),

View File

@ -3,7 +3,7 @@ import 'package:esys_flutter_share/esys_flutter_share.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:lemmy_api_client/v2.dart';
import 'package:url_launcher/url_launcher.dart' as ul;
import '../hooks/stores.dart';
@ -30,9 +30,9 @@ class InstancePage extends HookWidget {
InstancePage({@required this.instanceHost})
: assert(instanceHost != null),
siteFuture = LemmyApi(instanceHost).v1.getSite(),
siteFuture = LemmyApiV2(instanceHost).run(GetSite()),
communitiesFuture =
LemmyApi(instanceHost).v1.listCommunities(sort: SortType.hot);
LemmyApiV2(instanceHost).run(ListCommunities(sort: SortType.hot));
@override
Widget build(BuildContext context) {
@ -80,8 +80,8 @@ class InstancePage extends HookWidget {
leading: const Icon(Icons.open_in_browser),
title: const Text('Open in browser'),
onTap: () async => await ul
.canLaunch('https://${site.site.instanceHost}')
? ul.launch('https://${site.site.instanceHost}')
.canLaunch('https://${site.instanceHost}')
? ul.launch('https://${site.instanceHost}')
: Scaffold.of(context).showSnackBar(
const SnackBar(content: Text("can't open in browser"))),
),
@ -91,12 +91,12 @@ class InstancePage extends HookWidget {
onTap: () {
showInfoTablePopup(context, {
'url': instanceHost,
'creator': '@${site.site.creatorName}',
'creator': '@${site.siteView.creator.name}',
'version': site.version,
'enableDownvotes': site.site.enableDownvotes,
'enableNsfw': site.site.enableNsfw,
'published': site.site.published,
'updated': site.site.updated,
'enableDownvotes': site.siteView.site.enableDownvotes,
'enableNsfw': site.siteView.site.enableNsfw,
'published': site.siteView.site.published,
'updated': site.siteView.site.updated,
});
},
),
@ -119,7 +119,7 @@ class InstancePage extends HookWidget {
backgroundColor: theme.cardColor,
iconTheme: theme.iconTheme,
title: Text(
site.site.name,
site.siteView.site.name,
style: TextStyle(color: colorOnCard),
),
actions: [
@ -130,11 +130,11 @@ class InstancePage extends HookWidget {
],
flexibleSpace: FlexibleSpaceBar(
background: Stack(children: [
if (site.site.banner != null)
if (site.siteView.site.banner != null)
FullscreenableImage(
url: site.site.banner,
url: site.siteView.site.banner,
child: CachedNetworkImage(
imageUrl: site.site.banner,
imageUrl: site.siteView.site.banner,
errorWidget: (_, __, ___) => const SizedBox.shrink(),
),
),
@ -144,20 +144,20 @@ class InstancePage extends HookWidget {
children: [
Padding(
padding: const EdgeInsets.only(top: 40),
child: site.site.icon == null
child: site.siteView.site.icon == null
? const SizedBox(height: 100, width: 100)
: FullscreenableImage(
url: site.site.icon,
url: site.siteView.site.icon,
child: CachedNetworkImage(
width: 100,
height: 100,
imageUrl: site.site.icon,
imageUrl: site.siteView.site.icon,
errorWidget: (_, __, ___) =>
const Icon(Icons.warning),
),
),
),
Text(site.site.name,
Text(site.siteView.site.name,
style: theme.textTheme.headline6),
Text(instanceHost, style: theme.textTheme.caption)
],
@ -186,23 +186,23 @@ class InstancePage extends HookWidget {
children: [
InfinitePostList(
fetcher: (page, batchSize, sort) =>
LemmyApi(instanceHost).v1.getPosts(
// TODO: switch between all and subscribed
type: PostListingType.all,
sort: sort,
limit: batchSize,
page: page,
auth: accStore.defaultTokenFor(instanceHost)?.raw,
)),
LemmyApiV2(instanceHost).run(GetPosts(
// TODO: switch between all and subscribed
type: PostListingType.all,
sort: sort,
limit: batchSize,
page: page,
auth: accStore.defaultTokenFor(instanceHost)?.raw,
))),
InfiniteCommentList(
fetcher: (page, batchSize, sort) =>
LemmyApi(instanceHost).v1.getComments(
type: CommentListingType.all,
sort: sort,
limit: batchSize,
page: page,
auth: accStore.defaultTokenFor(instanceHost)?.raw,
)),
LemmyApiV2(instanceHost).run(GetComments(
type: CommentListingType.all,
sort: sort,
limit: batchSize,
page: page,
auth: accStore.defaultTokenFor(instanceHost)?.raw,
))),
_AboutTab(site,
communitiesFuture: communitiesFuture,
instanceHost: instanceHost),
@ -268,13 +268,13 @@ class _AboutTab extends HookWidget {
context,
(_) => CommunitiesListPage(
fetcher: (page, batchSize, sortType) =>
LemmyApi(instanceHost).v1.listCommunities(
sort: sortType,
limit: batchSize,
page: page,
auth: accStore.defaultTokenFor(instanceHost)?.raw,
),
title: 'Communities of ${site.site.name}',
LemmyApiV2(instanceHost).run(ListCommunities(
sort: sortType,
limit: batchSize,
page: page,
auth: accStore.defaultTokenFor(instanceHost)?.raw,
)),
title: 'Communities of ${site.siteView.site.name}',
),
);
}
@ -287,7 +287,7 @@ class _AboutTab extends HookWidget {
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 15),
child: MarkdownText(
site.site.description,
site.siteView.site.description,
instanceHost: instanceHost,
),
),
@ -299,10 +299,10 @@ class _AboutTab extends HookWidget {
children: [
const SizedBox(width: 7),
const _Badge('X users online'),
_Badge('${site.site.numberOfUsers} users'),
_Badge('${site.site.numberOfCommunities} communities'),
_Badge('${site.site.numberOfPosts} posts'),
_Badge('${site.site.numberOfComments} comments'),
_Badge('${site.siteView.counts.users} users'),
_Badge('${site.siteView.counts.communities} communities'),
_Badge('${site.siteView.counts.posts} posts'),
_Badge('${site.siteView.counts.comments} comments'),
const SizedBox(width: 15),
],
),
@ -317,15 +317,15 @@ class _AboutTab extends HookWidget {
),
),
if (commSnap.hasData)
...commSnap.data.take(6).map((e) => ListTile(
onTap: () =>
goToCommunity.byId(context, e.instanceHost, e.id),
title: Text(e.name),
leading: e.icon != null
...commSnap.data.take(6).map((c) => ListTile(
onTap: () => goToCommunity.byId(
context, c.instanceHost, c.community.id),
title: Text(c.community.name),
leading: c.community.icon != null
? CachedNetworkImage(
height: 50,
width: 50,
imageUrl: e.icon,
imageUrl: c.community.icon,
errorWidget: (_, __, ___) =>
const SizedBox(width: 50, height: 50),
imageBuilder: (context, imageProvider) => Container(
@ -362,20 +362,20 @@ class _AboutTab extends HookWidget {
),
),
),
...site.admins.map((e) => ListTile(
title: Text((e.preferredUsername == null ||
e.preferredUsername.isEmpty)
? '@${e.name}'
: e.preferredUsername),
subtitle: e.bio != null
? MarkdownText(e.bio, instanceHost: instanceHost)
...site.admins.map((u) => ListTile(
title: Text((u.user.preferredUsername == null ||
u.user.preferredUsername.isEmpty)
? '@${u.user.name}'
: u.user.preferredUsername),
subtitle: u.user.bio != null
? MarkdownText(u.user.bio, instanceHost: instanceHost)
: null,
onTap: () => goToUser.byId(context, instanceHost, e.id),
leading: e.avatar != null
onTap: () => goToUser.byId(context, instanceHost, u.user.id),
leading: u.user.avatar != null
? CachedNetworkImage(
height: 50,
width: 50,
imageUrl: e.avatar,
imageUrl: u.user.avatar,
errorWidget: (_, __, ___) =>
const SizedBox(width: 50, height: 50),
imageBuilder: (context, imageProvider) => Container(
@ -396,7 +396,6 @@ class _AboutTab extends HookWidget {
title: const Center(child: Text('Modlog')),
onTap: goToModLog,
),
const SizedBox(height: 20),
],
),
),

View File

@ -2,13 +2,15 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:image_picker/image_picker.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:lemmy_api_client/pictrs.dart';
import 'package:lemmy_api_client/v2.dart';
import '../hooks/delayed_loading.dart';
import '../hooks/image_picker.dart';
import '../hooks/ref.dart';
import '../hooks/stores.dart';
import '../util/pictrs.dart';
import '../widgets/bottom_safe.dart';
/// Page for managing things like username, email, avatar etc
/// This page will assume the manage account is logged in and
@ -28,9 +30,8 @@ class ManageAccountPage extends HookWidget {
final theme = Theme.of(context);
final userFuture = useMemoized(() async {
final site = await LemmyApi(instanceHost)
.v1
.getSite(auth: accountStore.tokenFor(instanceHost, username).raw);
final site = await LemmyApiV2(instanceHost).run(
GetSite(auth: accountStore.tokenFor(instanceHost, username).raw));
return site.myUser;
});
@ -45,7 +46,7 @@ class ManageAccountPage extends HookWidget {
Text('@$instanceHost@$username', style: theme.textTheme.headline6),
centerTitle: true,
),
body: FutureBuilder<User>(
body: FutureBuilder<UserSafeSettings>(
future: userFuture,
builder: (_, userSnap) {
if (userSnap.hasError) {
@ -67,7 +68,7 @@ class _ManageAccount extends HookWidget {
: assert(user != null),
super(key: key);
final User user;
final UserSafeSettings user;
@override
Widget build(BuildContext context) {
@ -104,35 +105,35 @@ class _ManageAccount extends HookWidget {
saveDelayedLoading.start();
try {
await LemmyApi(user.instanceHost).v1.saveUserSettings(
showNsfw: showNsfw.value,
theme: user.theme,
defaultSortType: defaultSortType.value,
defaultListingType: defaultListingType.value,
lang: user.lang,
showAvatars: showAvatars.value,
sendNotificationsToEmail: sendNotificationsToEmail.value,
auth: token.raw,
avatar: avatar.current,
banner: banner.current,
newPassword: newPasswordController.text.isEmpty
? null
: newPasswordController.text,
newPasswordVerify: newPasswordVerifyController.text.isEmpty
? null
: newPasswordVerifyController.text,
oldPassword: oldPasswordController.text.isEmpty
? null
: oldPasswordController.text,
matrixUserId: matrixUserController.text.isEmpty
? null
: matrixUserController.text,
preferredUsername: displayNameController.text.isEmpty
? null
: displayNameController.text,
bio: bioController.text.isEmpty ? null : bioController.text,
email: emailController.text.isEmpty ? null : emailController.text,
);
await LemmyApiV2(user.instanceHost).run(SaveUserSettings(
showNsfw: showNsfw.value,
theme: user.theme,
defaultSortType: defaultSortType.value,
defaultListingType: defaultListingType.value,
lang: user.lang,
showAvatars: showAvatars.value,
sendNotificationsToEmail: sendNotificationsToEmail.value,
auth: token.raw,
avatar: avatar.current,
banner: banner.current,
newPassword: newPasswordController.text.isEmpty
? null
: newPasswordController.text,
newPasswordVerify: newPasswordVerifyController.text.isEmpty
? null
: newPasswordVerifyController.text,
oldPassword: oldPasswordController.text.isEmpty
? null
: oldPasswordController.text,
matrixUserId: matrixUserController.text.isEmpty
? null
: matrixUserController.text,
preferredUsername: displayNameController.text.isEmpty
? null
: displayNameController.text,
bio: bioController.text.isEmpty ? null : bioController.text,
email: emailController.text.isEmpty ? null : emailController.text,
));
informAcceptedAvatarRef.current();
informAcceptedBannerRef.current();
@ -186,10 +187,10 @@ class _ManageAccount extends HookWidget {
deleteDelayedLoading.start();
try {
await LemmyApi(user.instanceHost).v1.deleteAccount(
password: deleteAccountPasswordController.text,
auth: token.raw,
);
await LemmyApiV2(user.instanceHost).run(DeleteAccount(
password: deleteAccountPasswordController.text,
auth: token.raw,
));
accountsStore.removeAccount(user.instanceHost, user.name);
Navigator.of(context).pop();
@ -430,6 +431,7 @@ class _ManageAccount extends HookWidget {
),
child: const Text('DELETE ACCOUNT'),
),
const BottomSafe(),
],
);
}
@ -439,7 +441,7 @@ class _ManageAccount extends HookWidget {
class _ImagePicker extends HookWidget {
final String name;
final String initialUrl;
final User user;
final UserSafeSettings user;
final ValueChanged<String> onChange;
/// _ImagePicker will set the ref to a callback that can inform _ImagePicker
@ -478,10 +480,10 @@ class _ImagePicker extends HookWidget {
if (pic != null) {
delayedLoading.start();
final upload = await LemmyApi(user.instanceHost).pictrs.upload(
filePath: pic.path,
auth: accountsStore.tokenFor(user.instanceHost, user.name).raw,
);
final upload = await PictrsApi(user.instanceHost).upload(
filePath: pic.path,
auth: accountsStore.tokenFor(user.instanceHost, user.name).raw,
);
pictrsDeleteToken.value = upload.files[0];
url.value =
pathToPictrs(user.instanceHost, pictrsDeleteToken.value.file);
@ -497,8 +499,7 @@ class _ImagePicker extends HookWidget {
}
removePicture({bool updateState = true}) {
LemmyApi(user.instanceHost)
.pictrs
PictrsApi(user.instanceHost)
.delete(pictrsDeleteToken.value)
.catchError((_) {});

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:lemmy_api_client/v2.dart';
import '../comment_tree.dart';
import '../hooks/stores.dart';
@ -82,14 +82,14 @@ class _SearchResultsList extends HookWidget {
return SortableInfiniteList(
fetcher: (page, batchSize, sort) async {
final s = await LemmyApi(instanceHost).v1.search(
q: query,
sort: sort,
type: type,
auth: accStore.defaultTokenFor(instanceHost)?.raw,
page: page,
limit: batchSize,
);
final s = await LemmyApiV2(instanceHost).run(Search(
q: query,
sort: sort,
type: type,
auth: accStore.defaultTokenFor(instanceHost)?.raw,
page: page,
limit: batchSize,
));
switch (s.type) {
case SearchType.comments:
@ -107,7 +107,7 @@ class _SearchResultsList extends HookWidget {
builder: (data) {
switch (type) {
case SearchType.comments:
return Comment(
return CommentWidget(
CommentTree(data as CommentView),
postCreatorId: null,
);
@ -116,10 +116,10 @@ class _SearchResultsList extends HookWidget {
case SearchType.posts:
return Padding(
padding: const EdgeInsets.only(bottom: 20),
child: Post(data as PostView),
child: PostWidget(data as PostView),
);
case SearchType.users:
return UsersListItem(user: data as UserView);
return UsersListItem(user: data as UserViewSafe);
default:
throw UnimplementedError();
}

View File

@ -1,7 +1,7 @@
import 'package:esys_flutter_share/esys_flutter_share.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:lemmy_api_client/v2.dart';
import '../widgets/user_profile.dart';
@ -9,20 +9,20 @@ import '../widgets/user_profile.dart';
class UserPage extends HookWidget {
final int userId;
final String instanceHost;
final Future<UserDetails> _userDetails;
final Future<FullUserView> _userDetails;
UserPage({@required this.userId, @required this.instanceHost})
: assert(userId != null),
assert(instanceHost != null),
_userDetails = LemmyApi(instanceHost).v1.getUserDetails(
userId: userId, savedOnly: true, sort: SortType.active);
_userDetails = LemmyApiV2(instanceHost).run(GetUserDetails(
userId: userId, savedOnly: true, sort: SortType.active));
UserPage.fromName({@required this.instanceHost, @required String username})
: assert(instanceHost != null),
assert(username != null),
userId = null,
_userDetails = LemmyApi(instanceHost).v1.getUserDetails(
username: username, savedOnly: true, sort: SortType.active);
_userDetails = LemmyApiV2(instanceHost).run(GetUserDetails(
username: username, savedOnly: true, sort: SortType.active));
@override
Widget build(BuildContext context) {
@ -30,7 +30,7 @@ class UserPage extends HookWidget {
final body = () {
if (userDetailsSnap.hasData) {
return UserProfile.fromUserDetails(userDetailsSnap.data);
return UserProfile.fromFullUserView(userDetailsSnap.data);
} else if (userDetailsSnap.hasError) {
return const Center(child: Text('Could not find that user.'));
} else {
@ -52,7 +52,7 @@ class UserPage extends HookWidget {
IconButton(
icon: const Icon(Icons.share),
onPressed: () => Share.text('Share user',
userDetailsSnap.data.user.actorId, 'text/plain'),
userDetailsSnap.data.userView.user.actorId, 'text/plain'),
)
]
],

View File

@ -1,6 +1,6 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:lemmy_api_client/v2.dart';
import '../util/goto.dart';
import '../widgets/markdown_text.dart';
@ -8,7 +8,7 @@ import '../widgets/markdown_text.dart';
/// Infinite list of Users fetched by the given fetcher
class UsersListPage extends StatelessWidget {
final String title;
final List<UserView> users;
final List<UserViewSafe> users;
const UsersListPage({Key key, @required this.users, this.title})
: assert(users != null),
@ -34,7 +34,7 @@ class UsersListPage extends StatelessWidget {
}
class UsersListItem extends StatelessWidget {
final UserView user;
final UserViewSafe user;
const UsersListItem({Key key, @required this.user})
: assert(user != null),
@ -42,25 +42,25 @@ class UsersListItem extends StatelessWidget {
@override
Widget build(BuildContext context) => ListTile(
title: Text(
(user.preferredUsername == null || user.preferredUsername.isEmpty)
? '@${user.name}'
: user.preferredUsername),
subtitle: user.bio != null
title: Text((user.user.preferredUsername == null ||
user.user.preferredUsername.isEmpty)
? '@${user.user.name}'
: user.user.preferredUsername),
subtitle: user.user.bio != null
? Opacity(
opacity: 0.5,
child: MarkdownText(
user.bio,
user.user.bio,
instanceHost: user.instanceHost,
),
)
: null,
onTap: () => goToUser.byId(context, user.instanceHost, user.id),
leading: user.avatar != null
onTap: () => goToUser.byId(context, user.instanceHost, user.user.id),
leading: user.user.avatar != null
? CachedNetworkImage(
height: 50,
width: 50,
imageUrl: user.avatar,
imageUrl: user.user.avatar,
errorWidget: (_, __, ___) =>
const SizedBox(height: 50, width: 50),
imageBuilder: (context, imageProvider) => Container(

View File

@ -2,7 +2,7 @@ import 'dart:collection';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:lemmy_api_client/v2.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../util/unawaited.dart';
@ -215,14 +215,13 @@ class AccountsStore extends ChangeNotifier {
throw Exception('No such instance was added');
}
final lemmy = LemmyApi(instanceHost).v1;
final token = await lemmy.login(
final lemmy = LemmyApiV2(instanceHost);
final token = await lemmy.run(Login(
usernameOrEmail: usernameOrEmail,
password: password,
);
));
final userData =
await lemmy.getSite(auth: token.raw).then((value) => value.myUser);
await lemmy.run(GetSite(auth: token.raw)).then((value) => value.myUser);
_tokens[instanceHost][userData.name] = token;
@ -244,7 +243,7 @@ class AccountsStore extends ChangeNotifier {
if (!assumeValid) {
try {
await LemmyApi(instanceHost).v1.getSite();
await LemmyApiV2(instanceHost).run(GetSite());
// ignore: avoid_catches_without_on_clauses
} catch (_) {
throw Exception('This instance seems to not exist');

View File

@ -1,4 +1,4 @@
import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:lemmy_api_client/v2.dart';
import '../cleanup_url.dart';
@ -10,30 +10,41 @@ import '../cleanup_url.dart';
// [.isLocal] is true iff `.originInstanceHost == .instanceHost`
extension GetInstanceCommunityView on CommunityView {
extension GetInstanceCommunitySafe on CommunitySafe {
String get originInstanceHost => _extract(actorId);
bool get isLocal => originInstanceHost == instanceHost;
// bool get isLocal => originInstanceHost == instanceHost;
}
extension GetInstanceUserView on UserView {
extension GetInstanceUserSafe on UserSafe {
String get originInstanceHost => _extract(actorId);
bool get isLocal => originInstanceHost == instanceHost;
// bool get isLocal => originInstanceHost == instanceHost;
}
extension GetInstanceCommunityFollowerView on CommunityFollowerView {
String get originInstanceHost => _extract(communityActorId);
bool get isLocal => originInstanceHost == instanceHost;
}
extension GetInstancePostView on PostView {
extension GetInstancePostView on Post {
String get originInstanceHost => _extract(apId);
bool get isLocal => originInstanceHost == instanceHost;
// bool get isLocal => originInstanceHost == instanceHost;
}
extension GetInstanceCommentView on CommentView {
extension GetInstanceCommentView on Comment {
String get originInstanceHost => _extract(apId);
bool get isLocal => originInstanceHost == instanceHost;
// bool get isLocal => originInstanceHost == instanceHost;
}
// TODO: change it to something more robust? regex?
String _extract(String s) => cleanUpUrl(s.split('/')[2]);
extension DisplayName on UserSafe {
String get displayName {
final name = () {
if (preferredUsername != null && preferredUsername != '') {
return preferredUsername;
} else {
return '@${this.name}';
}
}();
if (!local) return '$name@$originInstanceHost';
return name;
}
}

View File

@ -1,6 +1,6 @@
import 'dart:math' show log, max, pow, ln10;
import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:lemmy_api_client/v2.dart';
/// Calculates hot rank
/// because API always claims it's `0`
@ -17,5 +17,6 @@ double _calculateHotRank(int score, DateTime time) {
}
extension CommentHotRank on CommentView {
double get computedHotRank => _calculateHotRank(score, published);
double get computedHotRank =>
_calculateHotRank(counts.score, comment.published);
}

View File

@ -0,0 +1,10 @@
import 'package:flutter/material.dart';
class BottomSafe extends StatelessWidget {
final double additionalPadding;
const BottomSafe([this.additionalPadding = 0]);
@override
Widget build(BuildContext context) => SizedBox(
height: MediaQuery.of(context).padding.bottom + additionalPadding);
}

View File

@ -4,13 +4,14 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:lemmy_api_client/v2.dart';
import 'package:timeago/timeago.dart' as timeago;
import 'package:url_launcher/url_launcher.dart' as ul;
import '../comment_tree.dart';
import '../hooks/delayed_loading.dart';
import '../hooks/logged_in_action.dart';
import '../hooks/stores.dart';
import '../util/extensions/api.dart';
import '../util/goto.dart';
import '../util/intl.dart';
@ -21,7 +22,7 @@ import 'markdown_text.dart';
import 'write_comment.dart';
/// A single comment that renders its replies
class Comment extends HookWidget {
class CommentWidget extends HookWidget {
final int indent;
final int postCreatorId;
final CommentTree commentTree;
@ -37,7 +38,7 @@ class Comment extends HookWidget {
Colors.indigo,
];
Comment(
CommentWidget(
this.commentTree, {
this.indent = 0,
@required this.postCreatorId,
@ -48,48 +49,52 @@ class Comment extends HookWidget {
_showCommentInfo(BuildContext context) {
final com = commentTree.comment;
showInfoTablePopup(context, {
'id': com.id,
'creatorId': com.creatorId,
'postId': com.postId,
'postName': com.postName,
'parentId': com.parentId,
'removed': com.removed,
'read': com.read,
'published': com.published,
'updated': com.updated,
'deleted': com.deleted,
'apId': com.apId,
'local': com.local,
'communityId': com.communityId,
'communityActorId': com.communityActorId,
'communityLocal': com.communityLocal,
'communityName': com.communityName,
'communityIcon': com.communityIcon,
'banned': com.banned,
'bannedFromCommunity': com.bannedFromCommunity,
'creatorActirId': com.creatorActorId,
'userId': com.userId,
'upvotes': com.upvotes,
'downvotes': com.downvotes,
'score': com.score,
'% of upvotes': '${100 * (com.upvotes / (com.upvotes + com.downvotes))}%',
'hotRank': com.hotRank,
'hotRankActive': com.hotRankActive,
'id': com.comment.id,
'creatorId': com.comment.creatorId,
'postId': com.comment.postId,
'postName': com.post.name,
'parentId': com.comment.parentId,
'removed': com.comment.removed,
'read': com.comment.read,
'published': com.comment.published,
'updated': com.comment.updated,
'deleted': com.comment.deleted,
'apId': com.comment.apId,
'local': com.comment.local,
'communityId': com.community.id,
'communityActorId': com.community.actorId,
'communityLocal': com.community.local,
'communityName': com.community.name,
'communityIcon': com.community.icon,
'banned': com.creator.banned,
'bannedFromCommunity': com.creatorBannedFromCommunity,
'creatorActirId': com.creator.actorId,
'userId': com.creator.id,
'upvotes': com.counts.upvotes,
'downvotes': com.counts.downvotes,
'score': com.counts.score,
'% of upvotes':
'''${100 * (com.counts.upvotes / (com.counts.upvotes + com.counts.downvotes))}%''',
});
}
bool get isOP => commentTree.comment.creatorId == postCreatorId;
bool get isMine =>
commentTree.comment.creatorId == commentTree.comment.userId;
bool get isOP =>
commentTree.comment.comment.creatorId ==
commentTree.comment.post.creatorId;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final accStore = useAccountsStore();
final isMine = commentTree.comment.comment.creatorId ==
accStore.defaultTokenFor(commentTree.comment.instanceHost).payload.id;
final selectable = useState(false);
final showRaw = useState(false);
final collapsed = useState(false);
final myVote = useState(commentTree.comment.myVote ?? VoteType.none);
final isDeleted = useState(commentTree.comment.deleted);
final isDeleted = useState(commentTree.comment.comment.deleted);
final delayedVoting = useDelayedLoading();
final delayedDeletion = useDelayedLoading();
final loggedInAction = useLoggedInAction(commentTree.comment.instanceHost);
@ -98,14 +103,17 @@ class Comment extends HookWidget {
final comment = commentTree.comment;
handleDelete(Jwt token) async {
final api = LemmyApi(token.payload.iss).v1;
final api = LemmyApiV2(token.payload.iss);
delayedDeletion.start();
Navigator.of(context).pop();
try {
final res = await api.deleteComment(
editId: comment.id, deleted: !isDeleted.value, auth: token.raw);
isDeleted.value = res.deleted;
final res = await api.run(DeleteComment(
commentId: comment.comment.id,
deleted: !isDeleted.value,
auth: token.raw,
));
isDeleted.value = res.commentView.comment.deleted;
// ignore: avoid_catches_without_on_clauses
} catch (e) {
Scaffold.of(context).showSnackBar(
@ -128,22 +136,22 @@ class Comment extends HookWidget {
ListTile(
leading: const Icon(Icons.open_in_browser),
title: const Text('Open in browser'),
onTap: () async => await ul.canLaunch(com.apId)
? ul.launch(com.apId)
onTap: () async => await ul.canLaunch(com.comment.apId)
? ul.launch(com.comment.apId)
: Scaffold.of(context).showSnackBar(
const SnackBar(content: Text("can't open in browser"))),
),
ListTile(
leading: const Icon(Icons.share),
title: const Text('Share url'),
onTap: () =>
Share.text('Share comment url', com.apId, 'text/plain'),
onTap: () => Share.text(
'Share comment url', com.comment.apId, 'text/plain'),
),
ListTile(
leading: const Icon(Icons.share),
title: const Text('Share text'),
onTap: () =>
Share.text('Share comment text', com.content, 'text/plain'),
onTap: () => Share.text(
'Share comment text', com.comment.content, 'text/plain'),
),
ListTile(
leading: Icon(
@ -191,13 +199,13 @@ class Comment extends HookWidget {
}
vote(VoteType vote, Jwt token) async {
final api = LemmyApi(token.payload.iss).v1;
final api = LemmyApiV2(token.payload.iss);
delayedVoting.start();
try {
final res = await api.createCommentLike(
commentId: comment.id, score: vote, auth: token.raw);
myVote.value = res.myVote;
final res = await api.run(CreateCommentLike(
commentId: comment.comment.id, score: vote, auth: token.raw));
myVote.value = res.commentView.myVote;
// ignore: avoid_catches_without_on_clauses
} catch (e) {
Scaffold.of(context)
@ -208,20 +216,7 @@ class Comment extends HookWidget {
}
// decide which username to use
final username = () {
final name = () {
if (comment.creatorPreferredUsername != null &&
comment.creatorPreferredUsername != '') {
return comment.creatorPreferredUsername;
} else {
return '@${comment.creatorName}';
}
}();
if (!comment.isLocal) return '$name@${comment.originInstanceHost}';
return name;
}();
final username = comment.creator.displayName;
final body = () {
if (isDeleted.value) {
@ -231,7 +226,7 @@ class Comment extends HookWidget {
style: TextStyle(fontStyle: FontStyle.italic),
),
);
} else if (comment.removed) {
} else if (comment.comment.removed) {
return const Flexible(
child: Text(
'comment deleted by moderator',
@ -243,7 +238,7 @@ class Comment extends HookWidget {
child: Opacity(
opacity: 0.3,
child: Text(
commentTree.comment.content,
commentTree.comment.comment.content,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
@ -256,10 +251,10 @@ class Comment extends HookWidget {
return Flexible(
child: showRaw.value
? selectable.value
? SelectableText(commentTree.comment.content)
: Text(commentTree.comment.content)
? SelectableText(commentTree.comment.comment.content)
: Text(commentTree.comment.comment.content)
: MarkdownText(
commentTree.comment.content,
commentTree.comment.comment.content,
instanceHost: commentTree.comment.instanceHost,
selectable: selectable.value,
));
@ -269,13 +264,15 @@ class Comment extends HookWidget {
final actions = collapsed.value
? const SizedBox.shrink()
: Row(children: [
if (selectable.value && !isDeleted.value && !comment.removed)
if (selectable.value &&
!isDeleted.value &&
!comment.comment.removed)
_CommentAction(
icon: Icons.content_copy,
tooltip: 'copy',
onPressed: () {
Clipboard.setData(
ClipboardData(text: commentTree.comment.content))
Clipboard.setData(ClipboardData(
text: commentTree.comment.comment.content))
.then((_) => Scaffold.of(context).showSnackBar(
const SnackBar(
content: Text('comment copied to clipboard'))));
@ -285,7 +282,7 @@ class Comment extends HookWidget {
_CommentAction(
icon: Icons.link,
onPressed: () =>
goToPost(context, comment.instanceHost, comment.postId),
goToPost(context, comment.instanceHost, comment.post.id),
tooltip: 'go to post',
),
_CommentAction(
@ -295,7 +292,7 @@ class Comment extends HookWidget {
tooltip: 'more',
),
_SaveComment(commentTree.comment),
if (!isDeleted.value && !comment.removed)
if (!isDeleted.value && !comment.comment.removed)
_CommentAction(
icon: Icons.reply,
onPressed: loggedInAction((_) => reply()),
@ -340,14 +337,14 @@ class Comment extends HookWidget {
child: Column(
children: [
Row(children: [
if (comment.creatorAvatar != null)
if (comment.creator.avatar != null)
Padding(
padding: const EdgeInsets.only(right: 5),
child: InkWell(
onTap: () => goToUser.byId(
context, comment.instanceHost, comment.creatorId),
context, comment.instanceHost, comment.creator.id),
child: CachedNetworkImage(
imageUrl: comment.creatorAvatar,
imageUrl: comment.creator.avatar,
height: 20,
width: 20,
imageBuilder: (context, imageProvider) => Container(
@ -365,18 +362,19 @@ class Comment extends HookWidget {
),
InkWell(
onTap: () => goToUser.byId(
context, comment.instanceHost, comment.creatorId),
context, comment.instanceHost, comment.creator.id),
child: Text(username,
style: TextStyle(
color: Theme.of(context).accentColor,
)),
),
if (isOP) _CommentTag('OP', Theme.of(context).accentColor),
if (comment.banned) const _CommentTag('BANNED', Colors.red),
if (comment.bannedFromCommunity)
if (comment.creator.banned)
const _CommentTag('BANNED', Colors.red),
if (comment.creatorBannedFromCommunity)
const _CommentTag('BANNED FROM COMMUNITY', Colors.red),
const Spacer(),
if (collapsed.value && commentTree.children.length > 0) ...[
if (collapsed.value && commentTree.children.isNotEmpty) ...[
_CommentTag('+${commentTree.children.length}',
Theme.of(context).accentColor),
const SizedBox(width: 7),
@ -390,10 +388,10 @@ class Comment extends HookWidget {
size: const Size.square(16),
child: const CircularProgressIndicator())
else
Text(compactNumber(comment.score +
Text(compactNumber(comment.counts.score +
(wasVoted ? 0 : myVote.value.value))),
const Text(' · '),
Text(timeago.format(comment.published)),
Text(timeago.format(comment.comment.published)),
],
),
)
@ -407,7 +405,7 @@ class Comment extends HookWidget {
),
if (!collapsed.value)
for (final c in newReplies.value.followedBy(commentTree.children))
Comment(
CommentWidget(
c,
indent: indent + 1,
postCreatorId: postCreatorId,
@ -430,13 +428,16 @@ class _SaveComment extends HookWidget {
final delayed = useDelayedLoading();
handleSave(Jwt token) async {
final api = LemmyApi(comment.instanceHost).v1;
final api = LemmyApiV2(comment.instanceHost);
delayed.start();
try {
final res = await api.saveComment(
commentId: comment.id, save: !isSaved.value, auth: token.raw);
isSaved.value = res.saved;
final res = await api.run(SaveComment(
commentId: comment.comment.id,
save: !isSaved.value,
auth: token.raw,
));
isSaved.value = res.commentView.saved;
// ignore: avoid_catches_without_on_clauses
} catch (e) {
Scaffold.of(context)

View File

@ -1,10 +1,11 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:lemmy_api_client/v2.dart';
import '../comment_tree.dart';
import 'bottom_modal.dart';
import 'bottom_safe.dart';
import 'comment.dart';
/// Manages comments section, sorts them
@ -29,7 +30,7 @@ class CommentSection extends HookWidget {
}) : comments =
CommentTree.sortList(sortType, CommentTree.fromList(rawComments)),
rawComments = rawComments
..sort((b, a) => a.published.compareTo(b.published)),
..sort((b, a) => a.comment.published.compareTo(b.comment.published)),
assert(postCreatorId != null);
@override
@ -99,13 +100,14 @@ class CommentSection extends HookWidget {
)
else if (sorting.value == CommentSortType.chat)
for (final com in rawComments)
Comment(
CommentWidget(
CommentTree(com),
postCreatorId: postCreatorId,
)
else
for (final com in comments) Comment(com, postCreatorId: postCreatorId),
const SizedBox(height: 50),
for (final com in comments)
CommentWidget(com, postCreatorId: postCreatorId),
const BottomSafe(50),
]);
}
}

View File

@ -3,6 +3,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import '../hooks/ref.dart';
import 'bottom_safe.dart';
class InfiniteScrollController {
VoidCallback clear;
@ -82,7 +83,7 @@ class InfiniteScroll<T> extends HookWidget {
if (i == data.value.length) {
// if there are no more, skip
if (!hasMore.current) {
return const SizedBox.shrink();
return const BottomSafe();
}
// if it's already fetching more, skip
@ -98,7 +99,10 @@ class InfiniteScroll<T> extends HookWidget {
}).whenComplete(() => isFetching.current = false);
}
return loadingWidget;
return SafeArea(
top: false,
child: loadingWidget,
);
}
// not last element, render list item

View File

@ -5,7 +5,7 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:intl/intl.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:lemmy_api_client/v2.dart';
import 'package:timeago/timeago.dart' as timeago;
import 'package:url_launcher/url_launcher.dart' as ul;
@ -47,12 +47,13 @@ MediaType whatType(String url) {
}
/// A post overview card
class Post extends HookWidget {
class PostWidget extends HookWidget {
final PostView post;
final String instanceHost;
final bool fullPost;
Post(this.post, {this.fullPost = false}) : instanceHost = post.instanceHost;
PostWidget(this.post, {this.fullPost = false})
: instanceHost = post.instanceHost;
// == ACTIONS ==
@ -66,8 +67,8 @@ class Post extends HookWidget {
ListTile(
leading: const Icon(Icons.open_in_browser),
title: const Text('Open in browser'),
onTap: () async => await ul.canLaunch(post.apId)
? ul.launch(post.apId)
onTap: () async => await ul.canLaunch(post.post.apId)
? ul.launch(post.post.apId)
: Scaffold.of(context).showSnackBar(
const SnackBar(content: Text("can't open in browser"))),
),
@ -76,19 +77,16 @@ class Post extends HookWidget {
title: const Text('Nerd stuff'),
onTap: () {
showInfoTablePopup(context, {
'id': post.id,
'apId': post.apId,
'upvotes': post.upvotes,
'downvotes': post.downvotes,
'score': post.score,
'id': post.post.id,
'apId': post.post.apId,
'upvotes': post.counts.upvotes,
'downvotes': post.counts.downvotes,
'score': post.counts.score,
'% of upvotes':
'''${(100 * (post.upvotes / (post.upvotes + post.downvotes))).toInt()}%''',
'hotRank': post.hotRank,
'hotRank active': post.hotRankActive,
'local': post.local,
'published': post.published,
'updated': post.updated ?? 'never',
'newestActivityTime': post.newestActivityTime,
'''${(100 * (post.counts.upvotes / (post.counts.upvotes + post.counts.downvotes))).toInt()}%''',
'local': post.post.local,
'published': post.post.published,
'updated': post.post.updated ?? 'never',
});
},
),
@ -104,12 +102,12 @@ class Post extends HookWidget {
Widget build(BuildContext context) {
final theme = Theme.of(context);
void _openLink() => linkLauncher(
context: context, url: post.url, instanceHost: instanceHost);
context: context, url: post.post.url, instanceHost: instanceHost);
final urlDomain = () {
if (whatType(post.url) == MediaType.none) return null;
if (whatType(post.post.url) == MediaType.none) return null;
final url = post.url.split('/')[2];
final url = post.post.url.split('/')[2]; // TODO: change to Url(str).host
if (url.startsWith('www.')) return url.substring(4);
return url;
}();
@ -122,12 +120,12 @@ class Post extends HookWidget {
Column(
mainAxisSize: MainAxisSize.min,
children: [
if (post.communityIcon != null)
if (post.community.icon != null)
Padding(
padding: const EdgeInsets.only(right: 10),
child: InkWell(
onTap: () => goToCommunity.byId(
context, instanceHost, post.communityId),
context, instanceHost, post.community.id),
child: SizedBox(
height: 40,
width: 40,
@ -141,7 +139,7 @@ class Post extends HookWidget {
),
),
),
imageUrl: post.communityIcon,
imageUrl: post.community.icon,
errorWidget: (context, url, error) =>
Text(error.toString()),
),
@ -165,22 +163,22 @@ class Post extends HookWidget {
text: '!',
style: TextStyle(fontWeight: FontWeight.w300)),
TextSpan(
text: post.communityName,
text: post.community.name,
style:
const TextStyle(fontWeight: FontWeight.w600),
recognizer: TapGestureRecognizer()
..onTap = () => goToCommunity.byId(
context, instanceHost, post.communityId)),
context, instanceHost, post.community.id)),
const TextSpan(
text: '@',
style: TextStyle(fontWeight: FontWeight.w300)),
TextSpan(
text: post.originInstanceHost,
text: post.post.originInstanceHost,
style:
const TextStyle(fontWeight: FontWeight.w600),
recognizer: TapGestureRecognizer()
..onTap = () => goToInstance(
context, post.originInstanceHost)),
context, post.post.originInstanceHost)),
],
),
)
@ -197,29 +195,32 @@ class Post extends HookWidget {
text: 'by',
style: TextStyle(fontWeight: FontWeight.w300)),
TextSpan(
text:
''' ${post.creatorPreferredUsername ?? post.creatorName}''',
text: ' ${post.creator.displayName}',
style:
const TextStyle(fontWeight: FontWeight.w600),
recognizer: TapGestureRecognizer()
..onTap = () => goToUser.byId(
context, post.instanceHost, post.creatorId),
context,
post.instanceHost,
post.creator.id,
),
),
TextSpan(
text:
''' · ${timeago.format(post.published, locale: 'en_short')}'''),
if (post.locked) const TextSpan(text: ' · 🔒'),
if (post.stickied) const TextSpan(text: ' · 📌'),
if (post.nsfw) const TextSpan(text: ' · '),
if (post.nsfw)
''' · ${timeago.format(post.post.published, locale: 'en_short')}'''),
if (post.post.locked) const TextSpan(text: ' · 🔒'),
if (post.post.stickied)
const TextSpan(text: ' · 📌'),
if (post.post.nsfw) const TextSpan(text: ' · '),
if (post.post.nsfw)
const TextSpan(
text: 'NSFW',
style: TextStyle(color: Colors.red)),
if (urlDomain != null)
TextSpan(text: ' · $urlDomain'),
if (post.removed)
if (post.post.removed)
const TextSpan(text: ' · REMOVED'),
if (post.deleted)
if (post.post.deleted)
const TextSpan(text: ' · DELETED'),
],
))
@ -250,15 +251,15 @@ class Post extends HookWidget {
Expanded(
flex: 100,
child: Text(
post.name,
post.post.name,
textAlign: TextAlign.left,
softWrap: true,
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.w600),
),
),
if (whatType(post.url) == MediaType.other &&
post.thumbnailUrl != null) ...[
if (whatType(post.post.url) == MediaType.other &&
post.post.thumbnailUrl != null) ...[
const Spacer(),
InkWell(
onTap: _openLink,
@ -266,7 +267,7 @@ class Post extends HookWidget {
ClipRRect(
borderRadius: BorderRadius.circular(20),
child: CachedNetworkImage(
imageUrl: post.thumbnailUrl,
imageUrl: post.post.thumbnailUrl,
width: 70,
height: 70,
fit: BoxFit.cover,
@ -291,7 +292,7 @@ class Post extends HookWidget {
/// assemble link preview
Widget linkPreview() {
assert(post.url != null);
assert(post.post.url != null);
return Padding(
padding: const EdgeInsets.all(10),
@ -315,14 +316,14 @@ class Post extends HookWidget {
]),
Row(children: [
Flexible(
child: Text(post.embedTitle ?? '',
child: Text(post.post.embedTitle ?? '',
style: theme.textTheme.subtitle1
.apply(fontWeightDelta: 2)))
]),
if (post.embedDescription != null &&
post.embedDescription.isNotEmpty)
if (post.post.embedDescription != null &&
post.post.embedDescription.isNotEmpty)
Row(children: [
Flexible(child: Text(post.embedDescription))
Flexible(child: Text(post.post.embedDescription))
]),
],
),
@ -334,12 +335,12 @@ class Post extends HookWidget {
/// assemble image
Widget postImage() {
assert(post.url != null);
assert(post.post.url != null);
return FullscreenableImage(
url: post.url,
url: post.post.url,
child: CachedNetworkImage(
imageUrl: post.url,
imageUrl: post.post.url,
errorWidget: (_, __, ___) => const Icon(Icons.warning),
progressIndicatorBuilder: (context, url, progress) =>
CircularProgressIndicator(value: progress.progress),
@ -356,7 +357,8 @@ class Post extends HookWidget {
Expanded(
flex: 999,
child: Text(
''' ${NumberFormat.compact().format(post.numberOfComments)} comment${post.numberOfComments == 1 ? '' : 's'}''',
' ${NumberFormat.compact().format(post.counts.comments)}'
' comment${post.counts.comments == 1 ? '' : 's'}',
overflow: TextOverflow.fade,
softWrap: false,
),
@ -365,7 +367,9 @@ class Post extends HookWidget {
if (!fullPost)
IconButton(
icon: const Icon(Icons.share),
onPressed: () => Share.text('Share post url', post.apId,
onPressed: () => Share.text(
'Share post url',
post.post.apId,
'text/plain')), // TODO: find a way to mark it as url
if (!fullPost) SavePostButton(post),
_Voting(post),
@ -387,16 +391,17 @@ class Post extends HookWidget {
children: [
info(),
title(),
if (whatType(post.url) != MediaType.other &&
whatType(post.url) != MediaType.none)
if (whatType(post.post.url) != MediaType.other &&
whatType(post.post.url) != MediaType.none)
postImage()
else if (post.url != null && post.url.isNotEmpty)
else if (post.post.url != null && post.post.url.isNotEmpty)
linkPreview(),
if (post.body != null)
if (post.post.body != null)
// TODO: trim content
Padding(
padding: const EdgeInsets.all(10),
child: MarkdownText(post.body, instanceHost: instanceHost)),
child:
MarkdownText(post.post.body, instanceHost: instanceHost)),
actions(),
],
),
@ -421,12 +426,12 @@ class _Voting extends HookWidget {
final loggedInAction = useLoggedInAction(post.instanceHost);
vote(VoteType vote, Jwt token) async {
final api = LemmyApi(post.instanceHost).v1;
final api = LemmyApiV2(post.instanceHost);
loading.start();
try {
final res = await api.createPostLike(
postId: post.id, score: vote, auth: token.raw);
final res = await api.run(
CreatePostLike(postId: post.post.id, score: vote, auth: token.raw));
myVote.value = res.myVote;
// ignore: avoid_catches_without_on_clauses
} catch (e) {
@ -455,7 +460,7 @@ class _Voting extends HookWidget {
width: 20, height: 20, child: CircularProgressIndicator())
else
Text(NumberFormat.compact()
.format(post.score + (wasVoted ? 0 : myVote.value.value))),
.format(post.counts.score + (wasVoted ? 0 : myVote.value.value))),
IconButton(
icon: Icon(
Icons.arrow_downward,

View File

@ -1,7 +1,7 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:lemmy_api_client/v2.dart';
import 'bottom_modal.dart';

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:lemmy_api_client/v2.dart';
import '../hooks/delayed_loading.dart';
import '../hooks/logged_in_action.dart';
@ -20,12 +20,12 @@ class SavePostButton extends HookWidget {
final loggedInAction = useLoggedInAction(post.instanceHost);
savePost(Jwt token) async {
final api = LemmyApi(post.instanceHost).v1;
final api = LemmyApiV2(post.instanceHost);
loading.start();
try {
final res = await api.savePost(
postId: post.id, save: !isSaved.value, auth: token.raw);
final res = await api.run(SavePost(
postId: post.post.id, save: !isSaved.value, auth: token.raw));
isSaved.value = res.saved;
// ignore: avoid_catches_without_on_clauses
} catch (e) {

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:lemmy_api_client/v2.dart';
import '../comment_tree.dart';
import '../hooks/infinite_scroll.dart';
@ -57,7 +57,7 @@ class InfinitePostList extends StatelessWidget {
onStyleChange: () {},
builder: (post) => Column(
children: [
Post(post),
PostWidget(post),
const SizedBox(height: 20),
],
),
@ -72,7 +72,7 @@ class InfiniteCommentList extends StatelessWidget {
const InfiniteCommentList({@required this.fetcher}) : assert(fetcher != null);
Widget build(BuildContext context) => SortableInfiniteList<CommentView>(
builder: (comment) => Comment(
builder: (comment) => CommentWidget(
CommentTree(comment),
postCreatorId: null,
detached: true,

View File

@ -2,7 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:intl/intl.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:lemmy_api_client/v2.dart';
import 'package:timeago/timeago.dart' as timeago;
import '../hooks/stores.dart';
@ -17,16 +17,16 @@ import 'sortable_infinite_list.dart';
/// Shared widget of UserPage and ProfileTab
class UserProfile extends HookWidget {
final Future<UserDetails> _userDetails;
final Future<FullUserView> _userDetails;
final String instanceHost;
UserProfile({@required int userId, @required this.instanceHost})
: _userDetails = LemmyApi(instanceHost).v1.getUserDetails(
userId: userId, savedOnly: false, sort: SortType.active);
: _userDetails = LemmyApiV2(instanceHost).run(GetUserDetails(
userId: userId, savedOnly: false, sort: SortType.active));
UserProfile.fromUserDetails(UserDetails userDetails)
: _userDetails = Future.value(userDetails),
instanceHost = userDetails.user.instanceHost;
UserProfile.fromFullUserView(FullUserView fullUserView)
: _userDetails = Future.value(fullUserView),
instanceHost = fullUserView.instanceHost;
@override
Widget build(BuildContext context) {
@ -47,7 +47,7 @@ class UserProfile extends HookWidget {
);
}
final userView = userDetailsSnap.data.user;
final userView = userDetailsSnap.data.userView;
return DefaultTabController(
length: 3,
@ -78,27 +78,25 @@ class UserProfile extends HookWidget {
// TODO: first batch is already fetched on render
// TODO: comment and post come from the same endpoint, could be shared
InfinitePostList(
fetcher: (page, batchSize, sort) => LemmyApi(instanceHost)
.v1
.getUserDetails(
userId: userView.id,
fetcher: (page, batchSize, sort) => LemmyApiV2(instanceHost)
.run(GetUserDetails(
userId: userView.user.id,
savedOnly: false,
sort: SortType.active,
page: page,
limit: batchSize,
)
))
.then((val) => val.posts),
),
InfiniteCommentList(
fetcher: (page, batchSize, sort) => LemmyApi(instanceHost)
.v1
.getUserDetails(
userId: userView.id,
fetcher: (page, batchSize, sort) => LemmyApiV2(instanceHost)
.run(GetUserDetails(
userId: userView.user.id,
savedOnly: false,
sort: SortType.active,
page: page,
limit: batchSize,
)
))
.then((val) => val.comments),
),
_AboutTab(userDetailsSnap.data),
@ -113,7 +111,7 @@ class UserProfile extends HookWidget {
/// Such as his nickname, no. of posts, no. of posts,
/// banner, avatar etc.
class _UserOverview extends HookWidget {
final UserView userView;
final UserViewSafe userView;
const _UserOverview(this.userView);
@ -125,12 +123,12 @@ class _UserOverview extends HookWidget {
return Stack(
children: [
if (userView.banner != null)
if (userView.user.banner != null)
// TODO: for some reason doesnt react to presses
FullscreenableImage(
url: userView.banner,
url: userView.user.banner,
child: CachedNetworkImage(
imageUrl: userView.banner,
imageUrl: userView.user.banner,
errorWidget: (_, __, ___) => const SizedBox.shrink(),
),
)
@ -174,7 +172,7 @@ class _UserOverview extends HookWidget {
SafeArea(
child: Column(
children: [
if (userView.avatar != null)
if (userView.user.avatar != null)
SizedBox(
width: 80,
height: 80,
@ -190,9 +188,9 @@ class _UserOverview extends HookWidget {
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(12)),
child: FullscreenableImage(
url: userView.avatar,
url: userView.user.avatar,
child: CachedNetworkImage(
imageUrl: userView.avatar,
imageUrl: userView.user.avatar,
errorWidget: (_, __, ___) => const SizedBox.shrink(),
),
),
@ -200,14 +198,14 @@ class _UserOverview extends HookWidget {
),
),
Padding(
padding: userView.avatar != null
padding: userView.user.avatar != null
? const EdgeInsets.only(top: 8)
: const EdgeInsets.only(top: 70),
child: Padding(
padding:
EdgeInsets.only(top: userView.avatar == null ? 10 : 0),
padding: EdgeInsets.only(
top: userView.user.avatar == null ? 10 : 0),
child: Text(
userView.preferredUsername ?? userView.name,
userView.user.preferredUsername ?? userView.user.name,
style: theme.textTheme.headline6,
),
),
@ -218,14 +216,14 @@ class _UserOverview extends HookWidget {
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'@${userView.name}@',
'@${userView.user.name}@',
style: theme.textTheme.caption,
),
InkWell(
onTap: () =>
goToInstance(context, userView.originInstanceHost),
onTap: () => goToInstance(
context, userView.user.originInstanceHost),
child: Text(
userView.originInstanceHost,
userView.user.originInstanceHost,
style: theme.textTheme.caption,
),
)
@ -248,8 +246,8 @@ class _UserOverview extends HookWidget {
Padding(
padding: const EdgeInsets.only(left: 4),
child: Text(
'${compactNumber(userView.numberOfPosts)}'
' Post${pluralS(userView.numberOfPosts)}',
'${compactNumber(userView.counts.postCount)}'
' Post${pluralS(userView.counts.postCount)}',
style: TextStyle(color: colorOnTopOfAccentColor),
),
),
@ -269,8 +267,8 @@ class _UserOverview extends HookWidget {
Padding(
padding: const EdgeInsets.only(left: 4),
child: Text(
'${compactNumber(userView.numberOfComments)}'
' Comment${pluralS(userView.numberOfComments)}',
'${compactNumber(userView.counts.commentCount)}'
''' Comment${pluralS(userView.counts.commentCount)}''',
style:
TextStyle(color: colorOnTopOfAccentColor),
),
@ -285,7 +283,7 @@ class _UserOverview extends HookWidget {
Padding(
padding: const EdgeInsets.only(top: 15),
child: Text(
'Joined ${timeago.format(userView.published)}',
'Joined ${timeago.format(userView.user.published)}',
style: theme.textTheme.bodyText1,
),
),
@ -301,7 +299,8 @@ class _UserOverview extends HookWidget {
Padding(
padding: const EdgeInsets.only(left: 4),
child: Text(
DateFormat('MMM dd, yyyy').format(userView.published),
DateFormat('MMM dd, yyyy')
.format(userView.user.published),
style: theme.textTheme.bodyText1,
),
),
@ -318,19 +317,21 @@ class _UserOverview extends HookWidget {
}
class _AboutTab extends HookWidget {
final UserDetails userDetails;
final FullUserView userDetails;
const _AboutTab(this.userDetails);
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final instanceHost = userDetails.user.instanceHost;
final instanceHost = userDetails.userView.user.instanceHost;
final accStore = useAccountsStore();
final isOwnedAccount = accStore.loggedInInstances.contains(instanceHost) &&
accStore.usernamesFor(instanceHost).contains(userDetails.user.name);
accStore
.usernamesFor(instanceHost)
.contains(userDetails.userView.user.name);
const wallPadding = EdgeInsets.symmetric(horizontal: 15);
@ -378,10 +379,10 @@ class _AboutTab extends HookWidget {
),
onTap: () {}, // TODO: go to account editing
),
if (userDetails.user.bio != null) ...[
if (userDetails.userView.user.bio != null) ...[
Padding(
padding: wallPadding,
child: MarkdownText(userDetails.user.bio,
child: MarkdownText(userDetails.userView.user.bio,
instanceHost: instanceHost)),
divider,
],
@ -396,9 +397,9 @@ class _AboutTab extends HookWidget {
),
for (final comm
in userDetails.moderates
..sort((a, b) => a.communityName.compareTo(b.communityName)))
..sort((a, b) => a.community.name.compareTo(b.community.name)))
communityTile(
comm.communityName, comm.communityIcon, comm.communityId),
comm.community.name, comm.community.icon, comm.community.id),
divider
],
ListTile(
@ -412,9 +413,9 @@ class _AboutTab extends HookWidget {
if (userDetails.follows.isNotEmpty)
for (final comm
in userDetails.follows
..sort((a, b) => a.communityName.compareTo(b.communityName)))
..sort((a, b) => a.community.name.compareTo(b.community.name)))
communityTile(
comm.communityName, comm.communityIcon, comm.communityId)
comm.community.name, comm.community.icon, comm.community.id)
else
const Padding(
padding: EdgeInsets.only(top: 8),

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:lemmy_api_client/v2.dart';
import '../hooks/delayed_loading.dart';
import '../hooks/stores.dart';
@ -32,7 +32,7 @@ class WriteComment extends HookWidget {
final preview = () {
final body = MarkdownText(
comment?.content ?? post.body ?? '',
comment?.comment?.content ?? post.post.body ?? '',
instanceHost: instanceHost,
);
@ -40,7 +40,7 @@ class WriteComment extends HookWidget {
return Column(
children: [
Text(
post.name,
post.post.name,
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600),
),
const SizedBox(height: 4),
@ -53,18 +53,19 @@ class WriteComment extends HookWidget {
}();
handleSubmit() async {
final api = LemmyApi(instanceHost).v1;
final api = LemmyApiV2(instanceHost);
final token = accStore.defaultTokenFor(instanceHost);
delayed.start();
try {
final res = await api.createComment(
content: controller.text,
postId: post?.id ?? comment.postId,
parentId: comment?.id,
auth: token.raw);
Navigator.of(context).pop(res);
final res = await api.run(CreateComment(
content: controller.text,
postId: post?.post?.id ?? comment.post.id,
parentId: comment?.recipient?.id,
auth: token.raw,
));
Navigator.of(context).pop(res.commentView);
// ignore: avoid_catches_without_on_clauses
} catch (e) {
print(e);

View File

@ -35,7 +35,7 @@ packages:
name: cached_network_image
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.3"
version: "2.5.0"
characters:
dependency: transitive
description:
@ -91,7 +91,7 @@ packages:
name: effective_dart
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.4"
version: "1.3.0"
esys_flutter_share:
dependency: "direct main"
description:
@ -138,7 +138,7 @@ packages:
name: flutter_cache_manager
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
version: "2.1.1"
flutter_hooks:
dependency: "direct main"
description:
@ -191,13 +191,20 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
freezed_annotation:
dependency: transitive
description:
name: freezed_annotation
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.0"
fuzzy:
dependency: "direct main"
description:
name: fuzzy
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.3"
version: "0.3.0"
http:
dependency: transitive
description:
@ -225,7 +232,7 @@ packages:
name: image_picker
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.7+12"
version: "0.6.7+21"
image_picker_platform_interface:
dependency: transitive
description:
@ -246,7 +253,7 @@ packages:
name: json_annotation
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.0"
version: "3.1.1"
latinize:
dependency: transitive
description:
@ -260,7 +267,7 @@ packages:
name: lemmy_api_client
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.0"
version: "0.10.0"
markdown:
dependency: "direct main"
description:
@ -302,7 +309,7 @@ packages:
name: package_info
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.3"
version: "0.4.3+2"
path:
dependency: transitive
description:
@ -316,7 +323,7 @@ packages:
name: path_provider
url: "https://pub.dartlang.org"
source: hosted
version: "1.6.22"
version: "1.6.27"
path_provider_linux:
dependency: transitive
description:
@ -330,21 +337,21 @@ packages:
name: path_provider_macos
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.4+4"
version: "0.0.4+8"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.3"
version: "1.0.4"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.4+1"
version: "0.0.4+3"
pedantic:
dependency: transitive
description:
@ -365,7 +372,7 @@ packages:
name: photo_view
url: "https://pub.dartlang.org"
source: hosted
version: "0.10.2"
version: "0.10.3"
platform:
dependency: transitive
description:
@ -393,35 +400,35 @@ packages:
name: provider
url: "https://pub.dartlang.org"
source: hosted
version: "4.3.2+2"
version: "4.3.3"
rxdart:
dependency: transitive
description:
name: rxdart
url: "https://pub.dartlang.org"
source: hosted
version: "0.24.1"
version: "0.25.0"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
url: "https://pub.dartlang.org"
source: hosted
version: "0.5.12+2"
version: "0.5.12+4"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.2+2"
version: "0.0.2+4"
shared_preferences_macos:
dependency: transitive
description:
name: shared_preferences_macos
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.1+10"
version: "0.0.1+11"
shared_preferences_platform_interface:
dependency: transitive
description:
@ -442,7 +449,7 @@ packages:
name: shared_preferences_windows
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.1+1"
version: "0.0.2+2"
sky_engine:
dependency: transitive
description: flutter
@ -461,14 +468,14 @@ packages:
name: sqflite
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.1+2"
version: "1.3.2+2"
sqflite_common:
dependency: transitive
description:
name: sqflite_common
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2+1"
version: "1.0.3+1"
stack_trace:
dependency: transitive
description:
@ -517,7 +524,7 @@ packages:
name: timeago
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.28"
version: "2.0.29"
typed_data:
dependency: transitive
description:
@ -531,21 +538,21 @@ packages:
name: url_launcher
url: "https://pub.dartlang.org"
source: hosted
version: "5.7.8"
version: "5.7.10"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.1+3"
version: "0.0.1+4"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.1+8"
version: "0.0.1+9"
url_launcher_platform_interface:
dependency: transitive
description:
@ -559,14 +566,14 @@ packages:
name: url_launcher_web
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.5"
version: "0.1.5+1"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.1+1"
version: "0.0.1+3"
uuid:
dependency: transitive
description:
@ -587,7 +594,7 @@ packages:
name: win32
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.3"
version: "1.7.4"
xdg_directories:
dependency: transitive
description:
@ -610,5 +617,5 @@ packages:
source: hosted
version: "2.2.1"
sdks:
dart: ">=2.10.0 <2.11.0"
flutter: ">=1.22.0 <2.0.0"
dart: ">=2.10.2 <2.11.0"
flutter: ">=1.22.2 <2.0.0"

View File

@ -43,7 +43,7 @@ dependencies:
# utils
timeago: ^2.0.27
fuzzy: <1.0.0
lemmy_api_client: ^0.9.0
lemmy_api_client: ^0.10.0
flutter:
sdk: flutter