Compare commits
7 Commits
365f330629
...
e2ecb573a2
Author | SHA1 | Date | |
---|---|---|---|
e2ecb573a2 | |||
8cb5dff498 | |||
a5629975ed | |||
972b304969 | |||
e8ded55055 | |||
04875eb164 | |||
54a59aa470 |
@ -333,6 +333,7 @@
|
|||||||
"addAttachmentFromRandomId": "Link via RID",
|
"addAttachmentFromRandomId": "Link via RID",
|
||||||
"attachmentDetailInfo": "Attachment details",
|
"attachmentDetailInfo": "Attachment details",
|
||||||
"attachmentPastedImage": "Pasted Image",
|
"attachmentPastedImage": "Pasted Image",
|
||||||
|
"attachmentInsertedImage": "Inserted Image",
|
||||||
"attachmentInsertLink": "Insert Link",
|
"attachmentInsertLink": "Insert Link",
|
||||||
"attachmentSetAsPostThumbnail": "Set as post thumbnail",
|
"attachmentSetAsPostThumbnail": "Set as post thumbnail",
|
||||||
"attachmentUnsetAsPostThumbnail": "Unset as post thumbnail",
|
"attachmentUnsetAsPostThumbnail": "Unset as post thumbnail",
|
||||||
@ -650,5 +651,19 @@
|
|||||||
"realmIsCommunity": "Community Realm",
|
"realmIsCommunity": "Community Realm",
|
||||||
"realmIsCommunityDescription": "Community realm will be displayed on the discover page.",
|
"realmIsCommunityDescription": "Community realm will be displayed on the discover page.",
|
||||||
"realmLeave": "Leave Realm",
|
"realmLeave": "Leave Realm",
|
||||||
"realmLeaveDescription": "Leave the current realm and delete the realm's identity."
|
"realmLeaveDescription": "Leave the current realm and delete the realm's identity.",
|
||||||
|
"checkInResultTier1": "Worst",
|
||||||
|
"checkInResultTier2": "Worse",
|
||||||
|
"checkInResultTier3": "Normal",
|
||||||
|
"checkInResultTier4": "Better",
|
||||||
|
"checkInResultTier5": "Best",
|
||||||
|
"flagPostAction": "Flag the Post",
|
||||||
|
"flagPost": "Flag objectionable content",
|
||||||
|
"flagPostDescription": "If flagged users takes 50% or more of the views, the post will be collapsed. You cannot revoke the action.",
|
||||||
|
"flaggedPost": "Post has been flagged.",
|
||||||
|
"postViews": {
|
||||||
|
"zero": "No views",
|
||||||
|
"one": "{} view",
|
||||||
|
"other": "{} views"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -331,6 +331,7 @@
|
|||||||
"addAttachmentFromRandomId": "通过访问 ID 链接",
|
"addAttachmentFromRandomId": "通过访问 ID 链接",
|
||||||
"attachmentDetailInfo": "附件详细信息",
|
"attachmentDetailInfo": "附件详细信息",
|
||||||
"attachmentPastedImage": "粘贴的图片",
|
"attachmentPastedImage": "粘贴的图片",
|
||||||
|
"attachmentInsertedImage": "插入的图片",
|
||||||
"attachmentInsertLink": "插入连接",
|
"attachmentInsertLink": "插入连接",
|
||||||
"attachmentSetAsPostThumbnail": "设置为帖子缩略图",
|
"attachmentSetAsPostThumbnail": "设置为帖子缩略图",
|
||||||
"attachmentUnsetAsPostThumbnail": "取消设置为帖子缩略图",
|
"attachmentUnsetAsPostThumbnail": "取消设置为帖子缩略图",
|
||||||
@ -649,5 +650,19 @@
|
|||||||
"realmIsCommunity": "社区领域",
|
"realmIsCommunity": "社区领域",
|
||||||
"realmIsCommunityDescription": "社区领域会显示在发现页面上。",
|
"realmIsCommunityDescription": "社区领域会显示在发现页面上。",
|
||||||
"realmLeave": "离开领域",
|
"realmLeave": "离开领域",
|
||||||
"realmLeaveDescription": "离开当前领域,并且删除领域中的身份。"
|
"realmLeaveDescription": "离开当前领域,并且删除领域中的身份。",
|
||||||
|
"checkInResultTier1": "大凶",
|
||||||
|
"checkInResultTier2": "凶",
|
||||||
|
"checkInResultTier3": "中平",
|
||||||
|
"checkInResultTier4": "吉",
|
||||||
|
"checkInResultTier5": "大吉",
|
||||||
|
"flagPostAction": "吹哨",
|
||||||
|
"flagPost": "吹哨不良内容",
|
||||||
|
"flagPostDescription": "吹哨不良内容,如果吹哨用户占浏览量的 50% 或以上,则帖子会被折叠。吹哨后不可撤销。",
|
||||||
|
"flaggedPost": "哨子已经吹响。",
|
||||||
|
"postViews": {
|
||||||
|
"zero": "{} 次浏览",
|
||||||
|
"one": "{} 次浏览",
|
||||||
|
"other": "{} 次浏览"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -331,6 +331,7 @@
|
|||||||
"addAttachmentFromRandomId": "通過訪問 ID 鏈接",
|
"addAttachmentFromRandomId": "通過訪問 ID 鏈接",
|
||||||
"attachmentDetailInfo": "附件詳細信息",
|
"attachmentDetailInfo": "附件詳細信息",
|
||||||
"attachmentPastedImage": "粘貼的圖片",
|
"attachmentPastedImage": "粘貼的圖片",
|
||||||
|
"attachmentInsertedImage": "插入的圖片",
|
||||||
"attachmentInsertLink": "插入連接",
|
"attachmentInsertLink": "插入連接",
|
||||||
"attachmentSetAsPostThumbnail": "設置為帖子縮略圖",
|
"attachmentSetAsPostThumbnail": "設置為帖子縮略圖",
|
||||||
"attachmentUnsetAsPostThumbnail": "取消設置為帖子縮略圖",
|
"attachmentUnsetAsPostThumbnail": "取消設置為帖子縮略圖",
|
||||||
@ -624,5 +625,44 @@
|
|||||||
"realmCommunityHint": "該領域是一個社區領域,你可以自由加入。",
|
"realmCommunityHint": "該領域是一個社區領域,你可以自由加入。",
|
||||||
"realmCommunityPublicChannelsHint": "該領域包含的公共頻道",
|
"realmCommunityPublicChannelsHint": "該領域包含的公共頻道",
|
||||||
"realmJoined": "已加入領域 {}。",
|
"realmJoined": "已加入領域 {}。",
|
||||||
"join": "加入"
|
"join": "加入",
|
||||||
|
"pollEditorNew": "新投票",
|
||||||
|
"pollEditorEdit": "編輯投票",
|
||||||
|
"pollEditorDelete": "刪除投票",
|
||||||
|
"pollEditorDeleteDescription": "你確定要刪除這個投票嗎?該操作不可撤銷。",
|
||||||
|
"pollEditorUnlink": "解除鏈接",
|
||||||
|
"pollOptionAdd": "添加選項",
|
||||||
|
"pollOptionName": "選項名稱",
|
||||||
|
"pollLinkExisting": "鏈接現有投票",
|
||||||
|
"pollAnswered": "答案已經反饋。",
|
||||||
|
"pollVotes": {
|
||||||
|
"one": "{} 票",
|
||||||
|
"other": "{} 票"
|
||||||
|
},
|
||||||
|
"publisherDelete": "刪除發佈者 {}",
|
||||||
|
"publisherDeleteDescription": "你確定要刪除這個發佈者嗎?該操作不可撤銷。",
|
||||||
|
"channelIsPublic": "公開頻道",
|
||||||
|
"channelIsPublicDescription": "該頻道是公開的,任何人都可以加入。",
|
||||||
|
"channelIsCommunity": "社區頻道",
|
||||||
|
"channelIsCommunityDescription": "目前來説,社區頻道還沒有什麼特別之處。",
|
||||||
|
"realmIsPublic": "公開領域",
|
||||||
|
"realmIsPublicDescription": "該領域是公開的,任何人都可以加入。",
|
||||||
|
"realmIsCommunity": "社區領域",
|
||||||
|
"realmIsCommunityDescription": "社區領域會顯示在發現頁面上。",
|
||||||
|
"realmLeave": "離開領域",
|
||||||
|
"realmLeaveDescription": "離開當前領域,並且刪除領域中的身份。",
|
||||||
|
"checkInResultTier1": "大凶",
|
||||||
|
"checkInResultTier2": "兇",
|
||||||
|
"checkInResultTier3": "中平",
|
||||||
|
"checkInResultTier4": "吉",
|
||||||
|
"checkInResultTier5": "大吉",
|
||||||
|
"flagPostAction": "吹哨",
|
||||||
|
"flagPost": "吹哨不良內容",
|
||||||
|
"flagPostDescription": "吹哨不良內容,如果吹哨用户佔瀏覽量的 50% 或以上,則帖子會被摺疊。吹哨後不可撤銷。",
|
||||||
|
"flaggedPost": "哨子已經吹響。",
|
||||||
|
"postViews": {
|
||||||
|
"zero": "{} 次瀏覽",
|
||||||
|
"one": "{} 次瀏覽",
|
||||||
|
"other": "{} 次瀏覽"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -331,6 +331,7 @@
|
|||||||
"addAttachmentFromRandomId": "通過訪問 ID 鏈接",
|
"addAttachmentFromRandomId": "通過訪問 ID 鏈接",
|
||||||
"attachmentDetailInfo": "附件詳細信息",
|
"attachmentDetailInfo": "附件詳細信息",
|
||||||
"attachmentPastedImage": "粘貼的圖片",
|
"attachmentPastedImage": "粘貼的圖片",
|
||||||
|
"attachmentInsertedImage": "插入的圖片",
|
||||||
"attachmentInsertLink": "插入連接",
|
"attachmentInsertLink": "插入連接",
|
||||||
"attachmentSetAsPostThumbnail": "設置為帖子縮略圖",
|
"attachmentSetAsPostThumbnail": "設置為帖子縮略圖",
|
||||||
"attachmentUnsetAsPostThumbnail": "取消設置為帖子縮略圖",
|
"attachmentUnsetAsPostThumbnail": "取消設置為帖子縮略圖",
|
||||||
@ -624,5 +625,44 @@
|
|||||||
"realmCommunityHint": "該領域是一個社區領域,你可以自由加入。",
|
"realmCommunityHint": "該領域是一個社區領域,你可以自由加入。",
|
||||||
"realmCommunityPublicChannelsHint": "該領域包含的公共頻道",
|
"realmCommunityPublicChannelsHint": "該領域包含的公共頻道",
|
||||||
"realmJoined": "已加入領域 {}。",
|
"realmJoined": "已加入領域 {}。",
|
||||||
"join": "加入"
|
"join": "加入",
|
||||||
|
"pollEditorNew": "新投票",
|
||||||
|
"pollEditorEdit": "編輯投票",
|
||||||
|
"pollEditorDelete": "刪除投票",
|
||||||
|
"pollEditorDeleteDescription": "你確定要刪除這個投票嗎?該操作不可撤銷。",
|
||||||
|
"pollEditorUnlink": "解除鏈接",
|
||||||
|
"pollOptionAdd": "添加選項",
|
||||||
|
"pollOptionName": "選項名稱",
|
||||||
|
"pollLinkExisting": "鏈接現有投票",
|
||||||
|
"pollAnswered": "答案已經反饋。",
|
||||||
|
"pollVotes": {
|
||||||
|
"one": "{} 票",
|
||||||
|
"other": "{} 票"
|
||||||
|
},
|
||||||
|
"publisherDelete": "刪除發佈者 {}",
|
||||||
|
"publisherDeleteDescription": "你確定要刪除這個發佈者嗎?該操作不可撤銷。",
|
||||||
|
"channelIsPublic": "公開頻道",
|
||||||
|
"channelIsPublicDescription": "該頻道是公開的,任何人都可以加入。",
|
||||||
|
"channelIsCommunity": "社區頻道",
|
||||||
|
"channelIsCommunityDescription": "目前來說,社區頻道還沒有什麼特別之處。",
|
||||||
|
"realmIsPublic": "公開領域",
|
||||||
|
"realmIsPublicDescription": "該領域是公開的,任何人都可以加入。",
|
||||||
|
"realmIsCommunity": "社區領域",
|
||||||
|
"realmIsCommunityDescription": "社區領域會顯示在發現頁面上。",
|
||||||
|
"realmLeave": "離開領域",
|
||||||
|
"realmLeaveDescription": "離開當前領域,並且刪除領域中的身份。",
|
||||||
|
"checkInResultTier1": "大凶",
|
||||||
|
"checkInResultTier2": "兇",
|
||||||
|
"checkInResultTier3": "中平",
|
||||||
|
"checkInResultTier4": "吉",
|
||||||
|
"checkInResultTier5": "大吉",
|
||||||
|
"flagPostAction": "吹哨",
|
||||||
|
"flagPost": "吹哨不良內容",
|
||||||
|
"flagPostDescription": "吹哨不良內容,如果吹哨用戶佔瀏覽量的 50% 或以上,則帖子會被摺疊。吹哨後不可撤銷。",
|
||||||
|
"flaggedPost": "哨子已經吹響。",
|
||||||
|
"postViews": {
|
||||||
|
"zero": "{} 次瀏覽",
|
||||||
|
"one": "{} 次瀏覽",
|
||||||
|
"other": "{} 次瀏覽"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,48 +123,59 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let imageIdentifier = metadata["image"] as? String {
|
if let imageIdentifier = metadata["image"] as? String {
|
||||||
attachMedia(to: content, withIdentifier: imageIdentifier, fileType: UTType.jpeg, doScaleDown: true)
|
attachMedia(to: content, withIdentifier: [imageIdentifier], fileType: UTType.jpeg, doScaleDown: true)
|
||||||
} else if let avatarIdentifier = metadata["avatar"] as? String {
|
} else if let avatarIdentifier = metadata["avatar"] as? String {
|
||||||
attachMedia(to: content, withIdentifier: avatarIdentifier, fileType: UTType.jpeg, doScaleDown: true)
|
attachMedia(to: content, withIdentifier: [avatarIdentifier], fileType: UTType.jpeg, doScaleDown: true)
|
||||||
|
} else if let imagesIdentifier = metadata["images"] as? Array<String> {
|
||||||
|
attachMedia(to: content, withIdentifier: imagesIdentifier, fileType: UTType.jpeg, doScaleDown: true)
|
||||||
} else {
|
} else {
|
||||||
contentHandler?(content)
|
contentHandler?(content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func attachMedia(to content: UNMutableNotificationContent, withIdentifier identifier: String, fileType type: UTType?, doScaleDown scaleDown: Bool = false) {
|
private func attachMedia(to content: UNMutableNotificationContent, withIdentifier identifier: Array<String>, fileType type: UTType?, doScaleDown scaleDown: Bool = false) {
|
||||||
let attachmentUrl = getAttachmentUrl(for: identifier)
|
let attachmentUrls = identifier.compactMap { element in
|
||||||
|
return getAttachmentUrl(for: element)
|
||||||
guard let remoteUrl = URL(string: attachmentUrl) else {
|
}
|
||||||
print("Invalid URL for attachment: \(attachmentUrl)")
|
|
||||||
|
guard !attachmentUrls.isEmpty else {
|
||||||
|
print("Invalid URLs for attachments: \(attachmentUrls)")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let targetSize = 800
|
let targetSize = 800
|
||||||
let scaleProcessor = ResizingImageProcessor(referenceSize: CGSize(width: targetSize, height: targetSize), mode: .aspectFit)
|
let scaleProcessor = ResizingImageProcessor(referenceSize: CGSize(width: targetSize, height: targetSize), mode: .aspectFit)
|
||||||
|
|
||||||
KingfisherManager.shared.retrieveImage(with: remoteUrl, options: scaleDown ? [
|
for attachmentUrl in attachmentUrls {
|
||||||
.processor(scaleProcessor)
|
guard let remoteUrl = URL(string: attachmentUrl) else {
|
||||||
] : nil) { [weak self] result in
|
print("Invalid URL for attachment: \(attachmentUrl)")
|
||||||
guard let self = self else { return }
|
continue // Skip this URL and move to the next one
|
||||||
|
}
|
||||||
switch result {
|
|
||||||
case .success(let retrievalResult):
|
KingfisherManager.shared.retrieveImage(with: remoteUrl, options: scaleDown ? [
|
||||||
// The image is either retrieved from cache or downloaded
|
.processor(scaleProcessor)
|
||||||
let tempDirectory = FileManager.default.temporaryDirectory
|
] : nil) { [weak self] result in
|
||||||
let cachedFileUrl = tempDirectory.appendingPathComponent(identifier)
|
guard let self = self else { return }
|
||||||
|
|
||||||
do {
|
switch result {
|
||||||
// Write the image data to a temporary file for UNNotificationAttachment
|
case .success(let retrievalResult):
|
||||||
try retrievalResult.image.pngData()?.write(to: cachedFileUrl)
|
// The image is either retrieved from cache or downloaded
|
||||||
self.attachLocalMedia(to: content, fileType: type?.identifier, from: cachedFileUrl, withIdentifier: identifier)
|
let tempDirectory = FileManager.default.temporaryDirectory
|
||||||
} catch {
|
let cachedFileUrl = tempDirectory.appendingPathComponent(UUID().uuidString) // Unique identifier for each file
|
||||||
print("Failed to write media to temporary file: \(error.localizedDescription)")
|
|
||||||
|
do {
|
||||||
|
// Write the image data to a temporary file for UNNotificationAttachment
|
||||||
|
try retrievalResult.image.pngData()?.write(to: cachedFileUrl)
|
||||||
|
self.attachLocalMedia(to: content, fileType: type?.identifier, from: cachedFileUrl, withIdentifier: attachmentUrl)
|
||||||
|
} catch {
|
||||||
|
print("Failed to write media to temporary file: \(error.localizedDescription)")
|
||||||
|
self.contentHandler?(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
case .failure(let error):
|
||||||
|
print("Failed to retrieve image: \(error.localizedDescription)")
|
||||||
self.contentHandler?(content)
|
self.contentHandler?(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
case .failure(let error):
|
|
||||||
print("Failed to retrieve image: \(error.localizedDescription)")
|
|
||||||
self.contentHandler?(content)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -158,6 +158,14 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
final TextEditingController aliasController = TextEditingController();
|
final TextEditingController aliasController = TextEditingController();
|
||||||
final TextEditingController rewardController = TextEditingController();
|
final TextEditingController rewardController = TextEditingController();
|
||||||
|
|
||||||
|
ContentInsertionConfiguration get contentInsertionConfiguration => ContentInsertionConfiguration(
|
||||||
|
onContentInserted: (KeyboardInsertedContent content) {
|
||||||
|
if (content.hasData) {
|
||||||
|
addAttachments([PostWriteMedia.fromBytes(content.data!, 'attachmentInsertedImage'.tr(), SnMediaType.image)]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
bool _temporarySaveActive = false;
|
bool _temporarySaveActive = false;
|
||||||
|
|
||||||
PostWriteController({bool doLoadFromTemporary = true}) {
|
PostWriteController({bool doLoadFromTemporary = true}) {
|
||||||
|
@ -45,8 +45,8 @@ class ConfigProvider extends ChangeNotifier {
|
|||||||
bool newDrawerIsCollapsed = false;
|
bool newDrawerIsCollapsed = false;
|
||||||
bool newDrawerIsExpanded = false;
|
bool newDrawerIsExpanded = false;
|
||||||
if (withMediaQuery) {
|
if (withMediaQuery) {
|
||||||
newDrawerIsCollapsed = MediaQuery.of(context).size.width < 450;
|
newDrawerIsCollapsed = MediaQuery.of(context).size.width < 600;
|
||||||
newDrawerIsExpanded = MediaQuery.of(context).size.width >= 451;
|
newDrawerIsExpanded = MediaQuery.of(context).size.width >= 601;
|
||||||
} else {
|
} else {
|
||||||
final rpb = ResponsiveBreakpoints.of(context);
|
final rpb = ResponsiveBreakpoints.of(context);
|
||||||
newDrawerIsCollapsed = rpb.smallerOrEqualTo(MOBILE);
|
newDrawerIsCollapsed = rpb.smallerOrEqualTo(MOBILE);
|
||||||
|
@ -14,9 +14,32 @@ class UserDirectoryProvider {
|
|||||||
final Map<int, SnAccount> _cache = {};
|
final Map<int, SnAccount> _cache = {};
|
||||||
|
|
||||||
Future<List<SnAccount?>> listAccount(Iterable<dynamic> id) async {
|
Future<List<SnAccount?>> listAccount(Iterable<dynamic> id) async {
|
||||||
final out = await Future.wait(
|
final out = List<SnAccount?>.generate(id.length, (e) => null);
|
||||||
id.map((e) => getAccount(e)),
|
final plannedQuery = <int>{};
|
||||||
);
|
for (var idx = 0; idx < out.length; idx++) {
|
||||||
|
var item = id.elementAt(idx);
|
||||||
|
if (item is String && _idCache.containsKey(item)) {
|
||||||
|
item = _idCache[item];
|
||||||
|
}
|
||||||
|
if (_cache.containsKey(item)) {
|
||||||
|
out[idx] = _cache[item];
|
||||||
|
} else {
|
||||||
|
plannedQuery.add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final resp = await _sn.client.get('/cgi/id/users', queryParameters: {'id': plannedQuery.join(',')});
|
||||||
|
final respDecoded = resp.data.map((e) => SnAccount.fromJson(e)).cast<SnAccount>().toList();
|
||||||
|
var sideIdx = 0;
|
||||||
|
for (var idx = 0; idx < out.length; idx++) {
|
||||||
|
if (out[idx] != null) continue;
|
||||||
|
if (respDecoded.length <= sideIdx) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
out[idx] = respDecoded[sideIdx];
|
||||||
|
_cache[respDecoded[sideIdx].id] = out[idx]!;
|
||||||
|
_idCache[respDecoded[sideIdx].name] = respDecoded[sideIdx].id;
|
||||||
|
sideIdx++;
|
||||||
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -474,7 +474,7 @@ class _ChannelMemberListWidgetState extends State<_ChannelMemberListWidget> {
|
|||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
final resp = await sn.client.get('/cgi/im/channels/${widget.channel.keyPath}/members', queryParameters: {
|
final resp = await sn.client.get('/cgi/im/channels/${widget.channel.keyPath}/members', queryParameters: {
|
||||||
'take': 10,
|
'take': 10,
|
||||||
'offset': 0,
|
'offset': _members.length,
|
||||||
});
|
});
|
||||||
final out = List<SnChannelMember>.from(
|
final out = List<SnChannelMember>.from(
|
||||||
resp.data['data']?.map((e) => SnChannelMember.fromJson(e)) ?? [],
|
resp.data['data']?.map((e) => SnChannelMember.fromJson(e)) ?? [],
|
||||||
|
@ -663,10 +663,24 @@ class _HomeDashRecommendationPostWidgetState extends State<_HomeDashRecommendati
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int _currentPage = 0;
|
||||||
|
final PageController _pageController = PageController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_fetchRecommendationPosts();
|
_fetchRecommendationPosts();
|
||||||
|
_pageController.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_currentPage = _pageController.page?.round() ?? 0;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_pageController.dispose();
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -684,17 +698,24 @@ class _HomeDashRecommendationPostWidgetState extends State<_HomeDashRecommendati
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.star),
|
Row(
|
||||||
const Gap(8),
|
children: [
|
||||||
Text(
|
const Icon(Symbols.star),
|
||||||
'postRecommendation',
|
const Gap(8),
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
Text(
|
||||||
).tr()
|
'postRecommendation',
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
).tr(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Text('${_currentPage + 1}/${_posts?.length ?? 0}', style: GoogleFonts.robotoMono())
|
||||||
],
|
],
|
||||||
).padding(horizontal: 18, top: 12, bottom: 8),
|
).padding(horizontal: 18, top: 12, bottom: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: PageView.builder(
|
child: PageView.builder(
|
||||||
|
controller: _pageController,
|
||||||
scrollBehavior: ScrollConfiguration.of(context).copyWith(dragDevices: {
|
scrollBehavior: ScrollConfiguration.of(context).copyWith(dragDevices: {
|
||||||
PointerDeviceKind.mouse,
|
PointerDeviceKind.mouse,
|
||||||
PointerDeviceKind.touch,
|
PointerDeviceKind.touch,
|
||||||
|
@ -3,6 +3,7 @@ import 'dart:math' as math;
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:relative_time/relative_time.dart';
|
import 'package:relative_time/relative_time.dart';
|
||||||
@ -59,10 +60,7 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
final resp = await sn.client.get('/cgi/id/notifications?take=10');
|
final resp = await sn.client.get('/cgi/id/notifications?take=10');
|
||||||
_totalCount = resp.data['count'];
|
_totalCount = resp.data['count'];
|
||||||
_notifications.addAll(
|
_notifications.addAll(
|
||||||
resp.data['data']
|
resp.data['data']?.map((e) => SnNotification.fromJson(e)).cast<SnNotification>() ?? [],
|
||||||
?.map((e) => SnNotification.fromJson(e))
|
|
||||||
.cast<SnNotification>() ??
|
|
||||||
[],
|
|
||||||
);
|
);
|
||||||
nty.updateTray();
|
nty.updateTray();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -188,8 +186,7 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
_fetchNotifications();
|
_fetchNotifications();
|
||||||
},
|
},
|
||||||
isLoading: _isBusy,
|
isLoading: _isBusy,
|
||||||
hasReachedMax: _totalCount != null &&
|
hasReachedMax: _totalCount != null && _notifications.length >= _totalCount!,
|
||||||
_notifications.length >= _totalCount!,
|
|
||||||
itemBuilder: (context, idx) {
|
itemBuilder: (context, idx) {
|
||||||
final nty = _notifications[idx];
|
final nty = _notifications[idx];
|
||||||
return Row(
|
return Row(
|
||||||
@ -221,29 +218,36 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
isAutoWarp: true,
|
isAutoWarp: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if ([
|
if (['interactive.reply', 'interactive.feedback', 'interactive.subscription']
|
||||||
'interactive.feedback',
|
.contains(nty.topic) &&
|
||||||
'interactive.subscription'
|
|
||||||
].contains(nty.topic) &&
|
|
||||||
nty.metadata['related_post'] != null)
|
nty.metadata['related_post'] != null)
|
||||||
StyledWidget(Container(
|
GestureDetector(
|
||||||
decoration: BoxDecoration(
|
child: Container(
|
||||||
borderRadius: const BorderRadius.all(
|
decoration: BoxDecoration(
|
||||||
Radius.circular(8)),
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: Theme.of(context).dividerColor,
|
color: Theme.of(context).dividerColor,
|
||||||
width: 1,
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: PostItem(
|
||||||
|
data: SnPost.fromJson(
|
||||||
|
nty.metadata['related_post']!,
|
||||||
|
),
|
||||||
|
showComments: false,
|
||||||
|
showReactions: false,
|
||||||
|
showMenu: false,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: PostItem(
|
onTap: () {
|
||||||
data: SnPost.fromJson(
|
GoRouter.of(context).pushNamed(
|
||||||
nty.metadata['related_post']!,
|
'postDetail',
|
||||||
),
|
pathParameters: {
|
||||||
showComments: false,
|
'slug': nty.metadata['related_post']!['id'].toString(),
|
||||||
showReactions: false,
|
},
|
||||||
showMenu: false,
|
);
|
||||||
),
|
},
|
||||||
)).padding(top: 8),
|
).padding(top: 8),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
@ -268,10 +272,8 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Symbols.check),
|
icon: const Icon(Symbols.check),
|
||||||
padding: EdgeInsets.all(0),
|
padding: EdgeInsets.all(0),
|
||||||
visualDensity:
|
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
|
||||||
const VisualDensity(horizontal: -4, vertical: -4),
|
onPressed: _isSubmitting ? null : () => _markOneAsRead(nty),
|
||||||
onPressed:
|
|
||||||
_isSubmitting ? null : () => _markOneAsRead(nty),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
).padding(horizontal: 16);
|
).padding(horizontal: 16);
|
||||||
|
@ -602,6 +602,7 @@ class _PostStoryEditor extends StatelessWidget {
|
|||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
),
|
),
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
contentInsertionConfiguration: controller.contentInsertionConfiguration,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -665,6 +666,7 @@ class _PostArticleEditor extends StatelessWidget {
|
|||||||
keyboardType: TextInputType.multiline,
|
keyboardType: TextInputType.multiline,
|
||||||
style: Theme.of(context).textTheme.bodyLarge,
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
contentInsertionConfiguration: controller.contentInsertionConfiguration,
|
||||||
).padding(horizontal: 16),
|
).padding(horizontal: 16),
|
||||||
const Gap(4),
|
const Gap(4),
|
||||||
];
|
];
|
||||||
@ -692,6 +694,7 @@ class _PostArticleEditor extends StatelessWidget {
|
|||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
),
|
),
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
contentInsertionConfiguration: controller.contentInsertionConfiguration,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
@ -726,6 +729,7 @@ class _PostArticleEditor extends StatelessWidget {
|
|||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
),
|
),
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
contentInsertionConfiguration: controller.contentInsertionConfiguration,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -797,6 +801,7 @@ class _PostQuestionEditor extends StatelessWidget {
|
|||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
),
|
),
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
contentInsertionConfiguration: controller.contentInsertionConfiguration,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -189,7 +189,7 @@ class _RealmMemberListWidgetState extends State<_RealmMemberListWidget> {
|
|||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
final resp = await sn.client.get('/cgi/id/realms/${widget.realm!.alias}/members', queryParameters: {
|
final resp = await sn.client.get('/cgi/id/realms/${widget.realm!.alias}/members', queryParameters: {
|
||||||
'take': 10,
|
'take': 10,
|
||||||
'offset': 0,
|
'offset': _members.length,
|
||||||
});
|
});
|
||||||
|
|
||||||
final out = List<SnRealmMember>.from(
|
final out = List<SnRealmMember>.from(
|
||||||
|
@ -1,9 +1,17 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
part 'check_in.freezed.dart';
|
part 'check_in.freezed.dart';
|
||||||
|
|
||||||
part 'check_in.g.dart';
|
part 'check_in.g.dart';
|
||||||
|
|
||||||
const List<String> kCheckInResultTierSymbols = ['Bad', 'Poor', 'Medium', 'Good', 'Great'];
|
final List<String> kCheckInResultTierSymbols = [
|
||||||
|
'checkInResultTier1',
|
||||||
|
'checkInResultTier2',
|
||||||
|
'checkInResultTier3',
|
||||||
|
'checkInResultTier4',
|
||||||
|
'checkInResultTier5'
|
||||||
|
].map((e) => e.tr()).toList();
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class SnCheckInRecord with _$SnCheckInRecord {
|
class SnCheckInRecord with _$SnCheckInRecord {
|
||||||
@ -21,8 +29,7 @@ class SnCheckInRecord with _$SnCheckInRecord {
|
|||||||
required int accountId,
|
required int accountId,
|
||||||
}) = _SnCheckInRecord;
|
}) = _SnCheckInRecord;
|
||||||
|
|
||||||
factory SnCheckInRecord.fromJson(Map<String, dynamic> json) =>
|
factory SnCheckInRecord.fromJson(Map<String, dynamic> json) => _$SnCheckInRecordFromJson(json);
|
||||||
_$SnCheckInRecordFromJson(json);
|
|
||||||
|
|
||||||
String get symbol => kCheckInResultTierSymbols[resultTier];
|
String get symbol => kCheckInResultTierSymbols[resultTier];
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,8 @@ class SnPost with _$SnPost {
|
|||||||
required DateTime? publishedUntil,
|
required DateTime? publishedUntil,
|
||||||
required int totalUpvote,
|
required int totalUpvote,
|
||||||
required int totalDownvote,
|
required int totalDownvote,
|
||||||
|
@Default(0) int totalViews,
|
||||||
|
@Default(0) int totalAggregatedViews,
|
||||||
required int publisherId,
|
required int publisherId,
|
||||||
required int? pollId,
|
required int? pollId,
|
||||||
required SnPublisher publisher,
|
required SnPublisher publisher,
|
||||||
|
@ -47,6 +47,8 @@ mixin _$SnPost {
|
|||||||
DateTime? get publishedUntil => throw _privateConstructorUsedError;
|
DateTime? get publishedUntil => throw _privateConstructorUsedError;
|
||||||
int get totalUpvote => throw _privateConstructorUsedError;
|
int get totalUpvote => throw _privateConstructorUsedError;
|
||||||
int get totalDownvote => throw _privateConstructorUsedError;
|
int get totalDownvote => throw _privateConstructorUsedError;
|
||||||
|
int get totalViews => throw _privateConstructorUsedError;
|
||||||
|
int get totalAggregatedViews => throw _privateConstructorUsedError;
|
||||||
int get publisherId => throw _privateConstructorUsedError;
|
int get publisherId => throw _privateConstructorUsedError;
|
||||||
int? get pollId => throw _privateConstructorUsedError;
|
int? get pollId => throw _privateConstructorUsedError;
|
||||||
SnPublisher get publisher => throw _privateConstructorUsedError;
|
SnPublisher get publisher => throw _privateConstructorUsedError;
|
||||||
@ -95,6 +97,8 @@ abstract class $SnPostCopyWith<$Res> {
|
|||||||
DateTime? publishedUntil,
|
DateTime? publishedUntil,
|
||||||
int totalUpvote,
|
int totalUpvote,
|
||||||
int totalDownvote,
|
int totalDownvote,
|
||||||
|
int totalViews,
|
||||||
|
int totalAggregatedViews,
|
||||||
int publisherId,
|
int publisherId,
|
||||||
int? pollId,
|
int? pollId,
|
||||||
SnPublisher publisher,
|
SnPublisher publisher,
|
||||||
@ -150,6 +154,8 @@ class _$SnPostCopyWithImpl<$Res, $Val extends SnPost>
|
|||||||
Object? publishedUntil = freezed,
|
Object? publishedUntil = freezed,
|
||||||
Object? totalUpvote = null,
|
Object? totalUpvote = null,
|
||||||
Object? totalDownvote = null,
|
Object? totalDownvote = null,
|
||||||
|
Object? totalViews = null,
|
||||||
|
Object? totalAggregatedViews = null,
|
||||||
Object? publisherId = null,
|
Object? publisherId = null,
|
||||||
Object? pollId = freezed,
|
Object? pollId = freezed,
|
||||||
Object? publisher = null,
|
Object? publisher = null,
|
||||||
@ -265,6 +271,14 @@ class _$SnPostCopyWithImpl<$Res, $Val extends SnPost>
|
|||||||
? _value.totalDownvote
|
? _value.totalDownvote
|
||||||
: totalDownvote // ignore: cast_nullable_to_non_nullable
|
: totalDownvote // ignore: cast_nullable_to_non_nullable
|
||||||
as int,
|
as int,
|
||||||
|
totalViews: null == totalViews
|
||||||
|
? _value.totalViews
|
||||||
|
: totalViews // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
totalAggregatedViews: null == totalAggregatedViews
|
||||||
|
? _value.totalAggregatedViews
|
||||||
|
: totalAggregatedViews // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
publisherId: null == publisherId
|
publisherId: null == publisherId
|
||||||
? _value.publisherId
|
? _value.publisherId
|
||||||
: publisherId // ignore: cast_nullable_to_non_nullable
|
: publisherId // ignore: cast_nullable_to_non_nullable
|
||||||
@ -386,6 +400,8 @@ abstract class _$$SnPostImplCopyWith<$Res> implements $SnPostCopyWith<$Res> {
|
|||||||
DateTime? publishedUntil,
|
DateTime? publishedUntil,
|
||||||
int totalUpvote,
|
int totalUpvote,
|
||||||
int totalDownvote,
|
int totalDownvote,
|
||||||
|
int totalViews,
|
||||||
|
int totalAggregatedViews,
|
||||||
int publisherId,
|
int publisherId,
|
||||||
int? pollId,
|
int? pollId,
|
||||||
SnPublisher publisher,
|
SnPublisher publisher,
|
||||||
@ -444,6 +460,8 @@ class __$$SnPostImplCopyWithImpl<$Res>
|
|||||||
Object? publishedUntil = freezed,
|
Object? publishedUntil = freezed,
|
||||||
Object? totalUpvote = null,
|
Object? totalUpvote = null,
|
||||||
Object? totalDownvote = null,
|
Object? totalDownvote = null,
|
||||||
|
Object? totalViews = null,
|
||||||
|
Object? totalAggregatedViews = null,
|
||||||
Object? publisherId = null,
|
Object? publisherId = null,
|
||||||
Object? pollId = freezed,
|
Object? pollId = freezed,
|
||||||
Object? publisher = null,
|
Object? publisher = null,
|
||||||
@ -559,6 +577,14 @@ class __$$SnPostImplCopyWithImpl<$Res>
|
|||||||
? _value.totalDownvote
|
? _value.totalDownvote
|
||||||
: totalDownvote // ignore: cast_nullable_to_non_nullable
|
: totalDownvote // ignore: cast_nullable_to_non_nullable
|
||||||
as int,
|
as int,
|
||||||
|
totalViews: null == totalViews
|
||||||
|
? _value.totalViews
|
||||||
|
: totalViews // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
totalAggregatedViews: null == totalAggregatedViews
|
||||||
|
? _value.totalAggregatedViews
|
||||||
|
: totalAggregatedViews // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
publisherId: null == publisherId
|
publisherId: null == publisherId
|
||||||
? _value.publisherId
|
? _value.publisherId
|
||||||
: publisherId // ignore: cast_nullable_to_non_nullable
|
: publisherId // ignore: cast_nullable_to_non_nullable
|
||||||
@ -614,6 +640,8 @@ class _$SnPostImpl extends _SnPost {
|
|||||||
required this.publishedUntil,
|
required this.publishedUntil,
|
||||||
required this.totalUpvote,
|
required this.totalUpvote,
|
||||||
required this.totalDownvote,
|
required this.totalDownvote,
|
||||||
|
this.totalViews = 0,
|
||||||
|
this.totalAggregatedViews = 0,
|
||||||
required this.publisherId,
|
required this.publisherId,
|
||||||
required this.pollId,
|
required this.pollId,
|
||||||
required this.publisher,
|
required this.publisher,
|
||||||
@ -731,6 +759,12 @@ class _$SnPostImpl extends _SnPost {
|
|||||||
@override
|
@override
|
||||||
final int totalDownvote;
|
final int totalDownvote;
|
||||||
@override
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final int totalViews;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final int totalAggregatedViews;
|
||||||
|
@override
|
||||||
final int publisherId;
|
final int publisherId;
|
||||||
@override
|
@override
|
||||||
final int? pollId;
|
final int? pollId;
|
||||||
@ -743,7 +777,7 @@ class _$SnPostImpl extends _SnPost {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SnPost(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, type: $type, body: $body, language: $language, alias: $alias, aliasPrefix: $aliasPrefix, tags: $tags, categories: $categories, replies: $replies, replyId: $replyId, repostId: $repostId, replyTo: $replyTo, repostTo: $repostTo, visibleUsersList: $visibleUsersList, invisibleUsersList: $invisibleUsersList, visibility: $visibility, editedAt: $editedAt, pinnedAt: $pinnedAt, lockedAt: $lockedAt, isDraft: $isDraft, publishedAt: $publishedAt, publishedUntil: $publishedUntil, totalUpvote: $totalUpvote, totalDownvote: $totalDownvote, publisherId: $publisherId, pollId: $pollId, publisher: $publisher, metric: $metric, preload: $preload)';
|
return 'SnPost(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, type: $type, body: $body, language: $language, alias: $alias, aliasPrefix: $aliasPrefix, tags: $tags, categories: $categories, replies: $replies, replyId: $replyId, repostId: $repostId, replyTo: $replyTo, repostTo: $repostTo, visibleUsersList: $visibleUsersList, invisibleUsersList: $invisibleUsersList, visibility: $visibility, editedAt: $editedAt, pinnedAt: $pinnedAt, lockedAt: $lockedAt, isDraft: $isDraft, publishedAt: $publishedAt, publishedUntil: $publishedUntil, totalUpvote: $totalUpvote, totalDownvote: $totalDownvote, totalViews: $totalViews, totalAggregatedViews: $totalAggregatedViews, publisherId: $publisherId, pollId: $pollId, publisher: $publisher, metric: $metric, preload: $preload)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -796,6 +830,10 @@ class _$SnPostImpl extends _SnPost {
|
|||||||
other.totalUpvote == totalUpvote) &&
|
other.totalUpvote == totalUpvote) &&
|
||||||
(identical(other.totalDownvote, totalDownvote) ||
|
(identical(other.totalDownvote, totalDownvote) ||
|
||||||
other.totalDownvote == totalDownvote) &&
|
other.totalDownvote == totalDownvote) &&
|
||||||
|
(identical(other.totalViews, totalViews) ||
|
||||||
|
other.totalViews == totalViews) &&
|
||||||
|
(identical(other.totalAggregatedViews, totalAggregatedViews) ||
|
||||||
|
other.totalAggregatedViews == totalAggregatedViews) &&
|
||||||
(identical(other.publisherId, publisherId) ||
|
(identical(other.publisherId, publisherId) ||
|
||||||
other.publisherId == publisherId) &&
|
other.publisherId == publisherId) &&
|
||||||
(identical(other.pollId, pollId) || other.pollId == pollId) &&
|
(identical(other.pollId, pollId) || other.pollId == pollId) &&
|
||||||
@ -836,6 +874,8 @@ class _$SnPostImpl extends _SnPost {
|
|||||||
publishedUntil,
|
publishedUntil,
|
||||||
totalUpvote,
|
totalUpvote,
|
||||||
totalDownvote,
|
totalDownvote,
|
||||||
|
totalViews,
|
||||||
|
totalAggregatedViews,
|
||||||
publisherId,
|
publisherId,
|
||||||
pollId,
|
pollId,
|
||||||
publisher,
|
publisher,
|
||||||
@ -888,6 +928,8 @@ abstract class _SnPost extends SnPost {
|
|||||||
required final DateTime? publishedUntil,
|
required final DateTime? publishedUntil,
|
||||||
required final int totalUpvote,
|
required final int totalUpvote,
|
||||||
required final int totalDownvote,
|
required final int totalDownvote,
|
||||||
|
final int totalViews,
|
||||||
|
final int totalAggregatedViews,
|
||||||
required final int publisherId,
|
required final int publisherId,
|
||||||
required final int? pollId,
|
required final int? pollId,
|
||||||
required final SnPublisher publisher,
|
required final SnPublisher publisher,
|
||||||
@ -952,6 +994,10 @@ abstract class _SnPost extends SnPost {
|
|||||||
@override
|
@override
|
||||||
int get totalDownvote;
|
int get totalDownvote;
|
||||||
@override
|
@override
|
||||||
|
int get totalViews;
|
||||||
|
@override
|
||||||
|
int get totalAggregatedViews;
|
||||||
|
@override
|
||||||
int get publisherId;
|
int get publisherId;
|
||||||
@override
|
@override
|
||||||
int? get pollId;
|
int? get pollId;
|
||||||
|
@ -62,6 +62,9 @@ _$SnPostImpl _$$SnPostImplFromJson(Map<String, dynamic> json) => _$SnPostImpl(
|
|||||||
: DateTime.parse(json['published_until'] as String),
|
: DateTime.parse(json['published_until'] as String),
|
||||||
totalUpvote: (json['total_upvote'] as num).toInt(),
|
totalUpvote: (json['total_upvote'] as num).toInt(),
|
||||||
totalDownvote: (json['total_downvote'] as num).toInt(),
|
totalDownvote: (json['total_downvote'] as num).toInt(),
|
||||||
|
totalViews: (json['total_views'] as num?)?.toInt() ?? 0,
|
||||||
|
totalAggregatedViews:
|
||||||
|
(json['total_aggregated_views'] as num?)?.toInt() ?? 0,
|
||||||
publisherId: (json['publisher_id'] as num).toInt(),
|
publisherId: (json['publisher_id'] as num).toInt(),
|
||||||
pollId: (json['poll_id'] as num?)?.toInt(),
|
pollId: (json['poll_id'] as num?)?.toInt(),
|
||||||
publisher:
|
publisher:
|
||||||
@ -101,6 +104,8 @@ Map<String, dynamic> _$$SnPostImplToJson(_$SnPostImpl instance) =>
|
|||||||
'published_until': instance.publishedUntil?.toIso8601String(),
|
'published_until': instance.publishedUntil?.toIso8601String(),
|
||||||
'total_upvote': instance.totalUpvote,
|
'total_upvote': instance.totalUpvote,
|
||||||
'total_downvote': instance.totalDownvote,
|
'total_downvote': instance.totalDownvote,
|
||||||
|
'total_views': instance.totalViews,
|
||||||
|
'total_aggregated_views': instance.totalAggregatedViews,
|
||||||
'publisher_id': instance.publisherId,
|
'publisher_id': instance.publisherId,
|
||||||
'poll_id': instance.pollId,
|
'poll_id': instance.pollId,
|
||||||
'publisher': instance.publisher.toJson(),
|
'publisher': instance.publisher.toJson(),
|
||||||
|
@ -684,6 +684,15 @@ class _PostBottomAction extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
InkWell(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Symbols.play_circle, size: 20, color: iconColor),
|
||||||
|
const Gap(8),
|
||||||
|
Text('postViews').plural(data.totalViews),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
InkWell(
|
InkWell(
|
||||||
@ -829,7 +838,6 @@ class _PostContentHeader extends StatelessWidget {
|
|||||||
await sn.client.delete('/cgi/co/posts/${data.id}', queryParameters: {
|
await sn.client.delete('/cgi/co/posts/${data.id}', queryParameters: {
|
||||||
'publisherId': data.publisherId,
|
'publisherId': data.publisherId,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
context.showSnackbar('postDeleted'.tr(args: ['#${data.id}']));
|
context.showSnackbar('postDeleted'.tr(args: ['#${data.id}']));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -838,6 +846,25 @@ class _PostContentHeader extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _flagPost(BuildContext context) async {
|
||||||
|
final confirm = await context.showConfirmDialog(
|
||||||
|
'flagPost'.tr(),
|
||||||
|
'flagPostDescription'.tr(),
|
||||||
|
);
|
||||||
|
if (!confirm) return;
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
await sn.client.post('/cgi/co/posts/${data.id}/flag');
|
||||||
|
if (!context.mounted) return;
|
||||||
|
context.showSnackbar('postFlagged'.tr());
|
||||||
|
} catch (err) {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Row(
|
return Row(
|
||||||
@ -1029,6 +1056,18 @@ class _PostContentHeader extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.flag),
|
const Icon(Symbols.flag),
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
|
Text('flagPostAction').tr(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
_flagPost(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.report),
|
||||||
|
const Gap(16),
|
||||||
Text('report').tr(),
|
Text('report').tr(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
|||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 2.3.2+69
|
version: 2.3.2+70
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.5.4
|
sdk: ^3.5.4
|
||||||
|
Loading…
x
Reference in New Issue
Block a user