Skip to content

Commit 73ca5df

Browse files
authored
Add links for local and remote urls to context menu (#320)
* Add links for local and remote urls to context menu * Make community and user apId fields nullable * Show a home and fediverse icon in links
1 parent d662073 commit 73ca5df

File tree

17 files changed

+238
-91
lines changed

17 files changed

+238
-91
lines changed

assets/icons/fediverse.png

14.9 KB
Loading

lib/src/models/comment.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ abstract class CommentModel with _$CommentModel {
133133
required bool? canAuthUserModerate,
134134
required NotificationControlStatus? notificationControlStatus,
135135
required List<String>? bookmarks,
136+
required String? apId,
136137
}) = _CommentModel;
137138

138139
factory CommentModel.fromMbin(JsonMap json) => CommentModel(
@@ -163,6 +164,7 @@ abstract class CommentModel with _$CommentModel {
163164
canAuthUserModerate: json['canAuthUserModerate'] as bool?,
164165
notificationControlStatus: null,
165166
bookmarks: optionalStringList(json['bookmarks']),
167+
apId: json['apId'] as String?,
166168
);
167169

168170
factory CommentModel.fromLemmy(
@@ -230,6 +232,7 @@ abstract class CommentModel with _$CommentModel {
230232
// Empty string indicates comment is saved. No string indicates comment is not saved.
231233
if (json['saved'] as bool) '',
232234
],
235+
apId: lemmyComment['ap_id'] as String,
233236
);
234237
}
235238

@@ -324,6 +327,7 @@ abstract class CommentModel with _$CommentModel {
324327
// Empty string indicates comment is saved. No string indicates comment is not saved.
325328
if (json['saved'] as bool) '',
326329
],
330+
apId: piefedComment['ap_id'] as String,
327331
);
328332
}
329333
}

lib/src/models/community.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ abstract class DetailedCommunityModel with _$DetailedCommunityModel {
6363
required bool? isBlockedByUser,
6464
required bool isPostingRestrictedToMods,
6565
required NotificationControlStatus? notificationControlStatus,
66+
required String? apId,
6667
}) = _DetailedCommunityModel;
6768

6869
factory DetailedCommunityModel.fromMbin(JsonMap json) {
@@ -93,6 +94,7 @@ abstract class DetailedCommunityModel with _$DetailedCommunityModel {
9394
: NotificationControlStatus.fromJson(
9495
json['notificationStatus'] as String,
9596
),
97+
apId: json['apProfileId'] as String?,
9698
);
9799

98100
communityMentionCache[community.name] = community;
@@ -123,6 +125,7 @@ abstract class DetailedCommunityModel with _$DetailedCommunityModel {
123125
isPostingRestrictedToMods:
124126
(lemmyCommunity['posting_restricted_to_mods']) as bool,
125127
notificationControlStatus: null,
128+
apId: lemmyCommunity['actor_id'] as String,
126129
);
127130

128131
communityMentionCache[community.name] = community;
@@ -170,6 +173,7 @@ abstract class DetailedCommunityModel with _$DetailedCommunityModel {
170173
: communityView['activity_alert'] as bool
171174
? NotificationControlStatus.loud
172175
: NotificationControlStatus.default_,
176+
apId: piefedCommunity['actor_id'] as String,
173177
);
174178

175179
communityMentionCache[community.name] = community;
@@ -184,24 +188,37 @@ abstract class CommunityModel with _$CommunityModel {
184188
required int id,
185189
required String name,
186190
required ImageModel? icon,
191+
required String? apId,
187192
}) = _CommunityModel;
188193

189194
factory CommunityModel.fromMbin(JsonMap json) => CommunityModel(
190195
id: json['magazineId'] as int,
191196
name: json['name'] as String,
192197
icon: mbinGetOptionalImage(json['icon'] as JsonMap?),
198+
apId: json['apProfileId'] as String?,
193199
);
194200

195201
factory CommunityModel.fromLemmy(JsonMap json) => CommunityModel(
196202
id: json['id'] as int,
197203
name: getLemmyPiefedActorName(json),
198204
icon: lemmyGetOptionalImage(json['icon'] as String?),
205+
apId: json['actor_id'] as String,
199206
);
200207

201208
factory CommunityModel.fromPiefed(JsonMap json) => CommunityModel(
202209
id: json['id'] as int,
203210
name: getLemmyPiefedActorName(json),
204211
icon: lemmyGetOptionalImage(json['icon'] as String?),
212+
apId: json['actor_id'] as String,
213+
);
214+
215+
factory CommunityModel.fromDetailedCommunity(
216+
DetailedCommunityModel community,
217+
) => CommunityModel(
218+
id: community.id,
219+
name: community.name,
220+
icon: community.icon,
221+
apId: community.apId,
205222
);
206223
}
207224

lib/src/models/user.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ abstract class DetailedUserModel with _$DetailedUserModel {
5454
required bool? isFollowerOfUser,
5555
required bool? isBlockedByUser,
5656
required NotificationControlStatus? notificationControlStatus,
57+
required String? apId,
5758
}) = _DetailedUserModel;
5859

5960
factory DetailedUserModel.fromMbin(JsonMap json) {
@@ -75,6 +76,7 @@ abstract class DetailedUserModel with _$DetailedUserModel {
7576
: NotificationControlStatus.fromJson(
7677
json['notificationStatus'] as String,
7778
),
79+
apId: json['apProfileId'] as String?,
7880
);
7981

8082
userMentionCache[user.name] = user;
@@ -99,6 +101,7 @@ abstract class DetailedUserModel with _$DetailedUserModel {
99101
isFollowerOfUser: null,
100102
isBlockedByUser: (json['blocked'] as bool?) ?? false,
101103
notificationControlStatus: null,
104+
apId: lemmyPerson['actor_id'] as String,
102105
);
103106
}
104107

@@ -123,6 +126,7 @@ abstract class DetailedUserModel with _$DetailedUserModel {
123126
: json['activity_alert'] as bool
124127
? NotificationControlStatus.loud
125128
: NotificationControlStatus.default_,
129+
apId: piefedPerson['actor_id'] as String,
126130
);
127131
}
128132
}
@@ -135,6 +139,7 @@ abstract class UserModel with _$UserModel {
135139
required ImageModel? avatar,
136140
required DateTime? createdAt,
137141
required bool isBot,
142+
required String? apId,
138143
}) = _UserModel;
139144

140145
factory UserModel.fromMbin(JsonMap json) => UserModel(
@@ -143,6 +148,7 @@ abstract class UserModel with _$UserModel {
143148
avatar: mbinGetOptionalImage(json['avatar'] as JsonMap?),
144149
createdAt: optionalDateTime(json['createdAt'] as String?),
145150
isBot: (json['isBot'] ?? false) as bool,
151+
apId: json['apProfileId'] as String?,
146152
);
147153

148154
factory UserModel.fromLemmy(JsonMap json) => UserModel(
@@ -151,6 +157,7 @@ abstract class UserModel with _$UserModel {
151157
avatar: lemmyGetOptionalImage(json['avatar'] as String?),
152158
createdAt: DateTime.parse(json['published'] as String),
153159
isBot: json['bot_account'] as bool,
160+
apId: json['actor_id'] as String,
154161
);
155162

156163
factory UserModel.fromPiefed(JsonMap json) => UserModel(
@@ -159,6 +166,7 @@ abstract class UserModel with _$UserModel {
159166
avatar: lemmyGetOptionalImage(json['avatar'] as String?),
160167
createdAt: DateTime.parse(json['published'] as String),
161168
isBot: json['bot'] as bool,
169+
apId: json['actor_id'] as String,
162170
);
163171

164172
factory UserModel.fromDetailedUser(DetailedUserModel user) => UserModel(
@@ -167,6 +175,7 @@ abstract class UserModel with _$UserModel {
167175
avatar: user.avatar,
168176
createdAt: user.createdAt,
169177
isBot: user.isBot,
178+
apId: user.apId,
170179
);
171180
}
172181

lib/src/screens/feed/create_screen.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'package:interstellar/src/models/community.dart';
77
import 'package:interstellar/src/models/post.dart';
88
import 'package:interstellar/src/screens/explore/community_owner_panel.dart';
99
import 'package:interstellar/src/screens/explore/community_screen.dart';
10+
import 'package:interstellar/src/utils/ap_urls.dart';
1011
import 'package:interstellar/src/utils/language.dart';
1112
import 'package:interstellar/src/utils/utils.dart';
1213
import 'package:interstellar/src/widgets/image_selector.dart';
@@ -74,7 +75,7 @@ class _CreateScreenState extends State<CreateScreen> {
7475
}
7576

7677
String body = 'Cross posted from ';
77-
body += post.apId ?? genPostUrl(context, post).toString();
78+
body += genPostUrls(context, post).last.toString();
7879
if (post.body != null && post.body!.trim().isNotEmpty) {
7980
body += '\n\n';
8081
// Wrap original body with markdown quote

lib/src/screens/feed/post_comment.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import 'package:interstellar/src/api/notifications.dart';
66
import 'package:interstellar/src/controller/controller.dart';
77
import 'package:interstellar/src/controller/server.dart';
88
import 'package:interstellar/src/models/comment.dart';
9-
import 'package:interstellar/src/models/post.dart';
109
import 'package:interstellar/src/screens/feed/post_comment_screen.dart';
10+
import 'package:interstellar/src/utils/ap_urls.dart';
1111
import 'package:interstellar/src/utils/utils.dart';
1212
import 'package:interstellar/src/widgets/ban_dialog.dart';
1313
import 'package:interstellar/src/widgets/content_item/content_item.dart';
@@ -283,7 +283,6 @@ class _PostCommentState extends State<PostComment> {
283283
community: widget.comment.community,
284284
);
285285
},
286-
openLinkUri: genCommentUrl(context, widget.comment),
287286
editDraftResourceId:
288287
'edit:${widget.comment.postType.name}:comment:${context.watch<AppController>().instanceHost}:${widget.comment.id}',
289288
replyDraftResourceId:
@@ -354,6 +353,7 @@ class _PostCommentState extends State<PostComment> {
354353
);
355354
},
356355
onClick: widget.onClick ?? collapse,
356+
shareLinks: genCommentUrls(context, widget.comment),
357357
);
358358

359359
final menuWidget = IconButton(

lib/src/screens/feed/post_item.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:interstellar/src/api/notifications.dart';
55
import 'package:interstellar/src/controller/controller.dart';
66
import 'package:interstellar/src/controller/server.dart';
77
import 'package:interstellar/src/models/post.dart';
8+
import 'package:interstellar/src/utils/ap_urls.dart';
89
import 'package:interstellar/src/utils/utils.dart';
910
import 'package:interstellar/src/widgets/ban_dialog.dart';
1011
import 'package:interstellar/src/widgets/content_item/content_item.dart';
@@ -241,7 +242,6 @@ class _PostItemState extends State<PostItem> {
241242
);
242243
},
243244
numComments: widget.item.numComments,
244-
openLinkUri: genPostUrl(context, widget.item),
245245
editDraftResourceId:
246246
'edit:${widget.item.type.name}:${ac.instanceHost}:${widget.item.id}',
247247
replyDraftResourceId:
@@ -322,6 +322,7 @@ class _PostItemState extends State<PostItem> {
322322
isCompact: widget.isCompact,
323323
onClick: widget.isTopLevel ? widget.onTap : null,
324324
crossPost: widget.item,
325+
shareLinks: genPostUrls(context, widget.item),
325326
),
326327
),
327328
);

lib/src/screens/feed/post_page.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:interstellar/src/models/comment.dart';
66
import 'package:interstellar/src/models/post.dart';
77
import 'package:interstellar/src/screens/feed/post_comment.dart';
88
import 'package:interstellar/src/screens/feed/post_item.dart';
9+
import 'package:interstellar/src/utils/ap_urls.dart';
910
import 'package:interstellar/src/utils/utils.dart';
1011
import 'package:interstellar/src/widgets/menus/content_menu.dart';
1112
import 'package:interstellar/src/widgets/context_menu.dart';
@@ -217,7 +218,6 @@ class _PostPageState extends State<PostPage> {
217218
);
218219
},
219220
numComments: crossPost.numComments,
220-
openLinkUri: genPostUrl(context, crossPost),
221221
editDraftResourceId:
222222
'edit:${crossPost.type.name}:${ac.instanceHost}:${crossPost.id}',
223223
replyDraftResourceId:
@@ -300,6 +300,7 @@ class _PostPageState extends State<PostPage> {
300300
);
301301
},
302302
crossPost: crossPost,
303+
shareLinks: genPostUrls(context, crossPost),
303304
);
304305
showContentMenu(context, contentItem);
305306
}

lib/src/utils/ap_urls.dart

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:interstellar/src/controller/controller.dart';
3+
import 'package:interstellar/src/controller/server.dart';
4+
import 'package:interstellar/src/models/comment.dart';
5+
import 'package:interstellar/src/models/community.dart';
6+
import 'package:interstellar/src/models/post.dart';
7+
import 'package:interstellar/src/models/user.dart';
8+
import 'package:interstellar/src/utils/utils.dart';
9+
import 'package:provider/provider.dart';
10+
11+
/// Local url first, remote url last.
12+
/// Only one link if local is the source.
13+
List<Uri> genPostUrls(BuildContext context, PostModel post) {
14+
final ac = context.read<AppController>();
15+
16+
final apUrl = post.apId == null ? null : Uri.tryParse(post.apId!);
17+
18+
return [
19+
if (apUrl == null || apUrl.host != ac.instanceHost)
20+
Uri.https(
21+
ac.instanceHost,
22+
ac.serverSoftware == ServerSoftware.mbin
23+
? '/m/${post.community.name}/${switch (post.type) {
24+
PostType.thread => 't',
25+
PostType.microblog => 'p',
26+
}}/${post.id}'
27+
: '/post/${post.id}',
28+
),
29+
?apUrl,
30+
];
31+
}
32+
33+
/// Local url first, remote url last.
34+
/// Only one link if local is the source.
35+
List<Uri> genCommentUrls(BuildContext context, CommentModel comment) {
36+
final ac = context.read<AppController>();
37+
38+
final apUrl = comment.apId == null ? null : Uri.tryParse(comment.apId!);
39+
40+
return [
41+
if (apUrl == null || apUrl.host != ac.instanceHost)
42+
Uri.https(
43+
ac.instanceHost,
44+
ac.serverSoftware == ServerSoftware.mbin
45+
? '/m/${comment.community.name}/${switch (comment.postType) {
46+
PostType.thread => 't',
47+
PostType.microblog => 'p',
48+
}}/${comment.postId}/-/${switch (comment.postType) {
49+
PostType.thread => 'comment',
50+
PostType.microblog => 'reply',
51+
}}/${comment.id}'
52+
: '/comment/${comment.id}',
53+
),
54+
?apUrl,
55+
];
56+
}
57+
58+
/// Local url first, remote url last.
59+
/// Only one link if local is the source.
60+
List<Uri> genCommunityUrls(BuildContext context, CommunityModel community) {
61+
final ac = context.read<AppController>();
62+
63+
final apUrl = community.apId == null ? null : Uri.tryParse(community.apId!);
64+
65+
return [
66+
if (apUrl == null || apUrl.host != ac.instanceHost)
67+
Uri.https(
68+
ac.instanceHost,
69+
ac.serverSoftware == ServerSoftware.mbin
70+
? '/m/${community.name}'
71+
: '/c/${community.name}',
72+
),
73+
?apUrl,
74+
];
75+
}
76+
77+
/// Local url first, remote url last.
78+
/// Only one link if local is the source.
79+
List<Uri> genUserUrls(BuildContext context, UserModel user) {
80+
final ac = context.read<AppController>();
81+
82+
final apUrl = user.apId == null ? null : Uri.tryParse(user.apId!);
83+
84+
return [
85+
if (apUrl == null || apUrl.host != ac.instanceHost)
86+
Uri.https(
87+
ac.instanceHost,
88+
'/u/${ac.serverSoftware == ServerSoftware.mbin && getNameHost(context, user.name) != ac.instanceHost ? '@' : ''}${user.name}',
89+
),
90+
?apUrl,
91+
];
92+
}

0 commit comments

Comments
 (0)