Compare commits
No commits in common. "e2ecb573a2b5528ee8bd176d18885fa1e0812a10" and "365f3306297c80565658e25534992d6751a04c74" have entirely different histories.
e2ecb573a2
...
365f330629
@ -333,7 +333,6 @@
|
||||
"addAttachmentFromRandomId": "Link via RID",
|
||||
"attachmentDetailInfo": "Attachment details",
|
||||
"attachmentPastedImage": "Pasted Image",
|
||||
"attachmentInsertedImage": "Inserted Image",
|
||||
"attachmentInsertLink": "Insert Link",
|
||||
"attachmentSetAsPostThumbnail": "Set as post thumbnail",
|
||||
"attachmentUnsetAsPostThumbnail": "Unset as post thumbnail",
|
||||
@ -651,19 +650,5 @@
|
||||
"realmIsCommunity": "Community Realm",
|
||||
"realmIsCommunityDescription": "Community realm will be displayed on the discover page.",
|
||||
"realmLeave": "Leave Realm",
|
||||
"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"
|
||||
}
|
||||
"realmLeaveDescription": "Leave the current realm and delete the realm's identity."
|
||||
}
|
||||
|
@ -331,7 +331,6 @@
|
||||
"addAttachmentFromRandomId": "通过访问 ID 链接",
|
||||
"attachmentDetailInfo": "附件详细信息",
|
||||
"attachmentPastedImage": "粘贴的图片",
|
||||
"attachmentInsertedImage": "插入的图片",
|
||||
"attachmentInsertLink": "插入连接",
|
||||
"attachmentSetAsPostThumbnail": "设置为帖子缩略图",
|
||||
"attachmentUnsetAsPostThumbnail": "取消设置为帖子缩略图",
|
||||
@ -650,19 +649,5 @@
|
||||
"realmIsCommunity": "社区领域",
|
||||
"realmIsCommunityDescription": "社区领域会显示在发现页面上。",
|
||||
"realmLeave": "离开领域",
|
||||
"realmLeaveDescription": "离开当前领域,并且删除领域中的身份。",
|
||||
"checkInResultTier1": "大凶",
|
||||
"checkInResultTier2": "凶",
|
||||
"checkInResultTier3": "中平",
|
||||
"checkInResultTier4": "吉",
|
||||
"checkInResultTier5": "大吉",
|
||||
"flagPostAction": "吹哨",
|
||||
"flagPost": "吹哨不良内容",
|
||||
"flagPostDescription": "吹哨不良内容,如果吹哨用户占浏览量的 50% 或以上,则帖子会被折叠。吹哨后不可撤销。",
|
||||
"flaggedPost": "哨子已经吹响。",
|
||||
"postViews": {
|
||||
"zero": "{} 次浏览",
|
||||
"one": "{} 次浏览",
|
||||
"other": "{} 次浏览"
|
||||
}
|
||||
"realmLeaveDescription": "离开当前领域,并且删除领域中的身份。"
|
||||
}
|
||||
|
@ -331,7 +331,6 @@
|
||||
"addAttachmentFromRandomId": "通過訪問 ID 鏈接",
|
||||
"attachmentDetailInfo": "附件詳細信息",
|
||||
"attachmentPastedImage": "粘貼的圖片",
|
||||
"attachmentInsertedImage": "插入的圖片",
|
||||
"attachmentInsertLink": "插入連接",
|
||||
"attachmentSetAsPostThumbnail": "設置為帖子縮略圖",
|
||||
"attachmentUnsetAsPostThumbnail": "取消設置為帖子縮略圖",
|
||||
@ -625,44 +624,5 @@
|
||||
"realmCommunityHint": "該領域是一個社區領域,你可以自由加入。",
|
||||
"realmCommunityPublicChannelsHint": "該領域包含的公共頻道",
|
||||
"realmJoined": "已加入領域 {}。",
|
||||
"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": "{} 次瀏覽"
|
||||
}
|
||||
"join": "加入"
|
||||
}
|
||||
|
@ -331,7 +331,6 @@
|
||||
"addAttachmentFromRandomId": "通過訪問 ID 鏈接",
|
||||
"attachmentDetailInfo": "附件詳細信息",
|
||||
"attachmentPastedImage": "粘貼的圖片",
|
||||
"attachmentInsertedImage": "插入的圖片",
|
||||
"attachmentInsertLink": "插入連接",
|
||||
"attachmentSetAsPostThumbnail": "設置為帖子縮略圖",
|
||||
"attachmentUnsetAsPostThumbnail": "取消設置為帖子縮略圖",
|
||||
@ -625,44 +624,5 @@
|
||||
"realmCommunityHint": "該領域是一個社區領域,你可以自由加入。",
|
||||
"realmCommunityPublicChannelsHint": "該領域包含的公共頻道",
|
||||
"realmJoined": "已加入領域 {}。",
|
||||
"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": "{} 次瀏覽"
|
||||
}
|
||||
"join": "加入"
|
||||
}
|
||||
|
@ -123,59 +123,48 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
}
|
||||
|
||||
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 {
|
||||
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)
|
||||
attachMedia(to: content, withIdentifier: avatarIdentifier, fileType: UTType.jpeg, doScaleDown: true)
|
||||
} else {
|
||||
contentHandler?(content)
|
||||
}
|
||||
}
|
||||
|
||||
private func attachMedia(to content: UNMutableNotificationContent, withIdentifier identifier: Array<String>, fileType type: UTType?, doScaleDown scaleDown: Bool = false) {
|
||||
let attachmentUrls = identifier.compactMap { element in
|
||||
return getAttachmentUrl(for: element)
|
||||
}
|
||||
|
||||
guard !attachmentUrls.isEmpty else {
|
||||
print("Invalid URLs for attachments: \(attachmentUrls)")
|
||||
private func attachMedia(to content: UNMutableNotificationContent, withIdentifier identifier: String, fileType type: UTType?, doScaleDown scaleDown: Bool = false) {
|
||||
let attachmentUrl = getAttachmentUrl(for: identifier)
|
||||
|
||||
guard let remoteUrl = URL(string: attachmentUrl) else {
|
||||
print("Invalid URL for attachment: \(attachmentUrl)")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
let targetSize = 800
|
||||
let scaleProcessor = ResizingImageProcessor(referenceSize: CGSize(width: targetSize, height: targetSize), mode: .aspectFit)
|
||||
|
||||
for attachmentUrl in attachmentUrls {
|
||||
guard let remoteUrl = URL(string: attachmentUrl) else {
|
||||
print("Invalid URL for attachment: \(attachmentUrl)")
|
||||
continue // Skip this URL and move to the next one
|
||||
}
|
||||
|
||||
KingfisherManager.shared.retrieveImage(with: remoteUrl, options: scaleDown ? [
|
||||
.processor(scaleProcessor)
|
||||
] : nil) { [weak self] result in
|
||||
guard let self = self else { return }
|
||||
|
||||
switch result {
|
||||
case .success(let retrievalResult):
|
||||
// The image is either retrieved from cache or downloaded
|
||||
let tempDirectory = FileManager.default.temporaryDirectory
|
||||
let cachedFileUrl = tempDirectory.appendingPathComponent(UUID().uuidString) // Unique identifier for each file
|
||||
|
||||
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)")
|
||||
|
||||
KingfisherManager.shared.retrieveImage(with: remoteUrl, options: scaleDown ? [
|
||||
.processor(scaleProcessor)
|
||||
] : nil) { [weak self] result in
|
||||
guard let self = self else { return }
|
||||
|
||||
switch result {
|
||||
case .success(let retrievalResult):
|
||||
// The image is either retrieved from cache or downloaded
|
||||
let tempDirectory = FileManager.default.temporaryDirectory
|
||||
let cachedFileUrl = tempDirectory.appendingPathComponent(identifier)
|
||||
|
||||
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: identifier)
|
||||
} 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -158,14 +158,6 @@ class PostWriteController extends ChangeNotifier {
|
||||
final TextEditingController aliasController = 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;
|
||||
|
||||
PostWriteController({bool doLoadFromTemporary = true}) {
|
||||
|
@ -45,8 +45,8 @@ class ConfigProvider extends ChangeNotifier {
|
||||
bool newDrawerIsCollapsed = false;
|
||||
bool newDrawerIsExpanded = false;
|
||||
if (withMediaQuery) {
|
||||
newDrawerIsCollapsed = MediaQuery.of(context).size.width < 600;
|
||||
newDrawerIsExpanded = MediaQuery.of(context).size.width >= 601;
|
||||
newDrawerIsCollapsed = MediaQuery.of(context).size.width < 450;
|
||||
newDrawerIsExpanded = MediaQuery.of(context).size.width >= 451;
|
||||
} else {
|
||||
final rpb = ResponsiveBreakpoints.of(context);
|
||||
newDrawerIsCollapsed = rpb.smallerOrEqualTo(MOBILE);
|
||||
|
@ -14,32 +14,9 @@ class UserDirectoryProvider {
|
||||
final Map<int, SnAccount> _cache = {};
|
||||
|
||||
Future<List<SnAccount?>> listAccount(Iterable<dynamic> id) async {
|
||||
final out = List<SnAccount?>.generate(id.length, (e) => null);
|
||||
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++;
|
||||
}
|
||||
final out = await Future.wait(
|
||||
id.map((e) => getAccount(e)),
|
||||
);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
@ -474,7 +474,7 @@ class _ChannelMemberListWidgetState extends State<_ChannelMemberListWidget> {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final resp = await sn.client.get('/cgi/im/channels/${widget.channel.keyPath}/members', queryParameters: {
|
||||
'take': 10,
|
||||
'offset': _members.length,
|
||||
'offset': 0,
|
||||
});
|
||||
final out = List<SnChannelMember>.from(
|
||||
resp.data['data']?.map((e) => SnChannelMember.fromJson(e)) ?? [],
|
||||
|
@ -663,24 +663,10 @@ class _HomeDashRecommendationPostWidgetState extends State<_HomeDashRecommendati
|
||||
}
|
||||
}
|
||||
|
||||
int _currentPage = 0;
|
||||
final PageController _pageController = PageController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_fetchRecommendationPosts();
|
||||
_pageController.addListener(() {
|
||||
setState(() {
|
||||
_currentPage = _pageController.page?.round() ?? 0;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_pageController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -698,24 +684,17 @@ class _HomeDashRecommendationPostWidgetState extends State<_HomeDashRecommendati
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Symbols.star),
|
||||
const Gap(8),
|
||||
Text(
|
||||
'postRecommendation',
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
).tr(),
|
||||
],
|
||||
),
|
||||
Text('${_currentPage + 1}/${_posts?.length ?? 0}', style: GoogleFonts.robotoMono())
|
||||
const Icon(Symbols.star),
|
||||
const Gap(8),
|
||||
Text(
|
||||
'postRecommendation',
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
).tr()
|
||||
],
|
||||
).padding(horizontal: 18, top: 12, bottom: 8),
|
||||
Expanded(
|
||||
child: PageView.builder(
|
||||
controller: _pageController,
|
||||
scrollBehavior: ScrollConfiguration.of(context).copyWith(dragDevices: {
|
||||
PointerDeviceKind.mouse,
|
||||
PointerDeviceKind.touch,
|
||||
|
@ -3,7 +3,6 @@ import 'dart:math' as math;
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:relative_time/relative_time.dart';
|
||||
@ -60,7 +59,10 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
||||
final resp = await sn.client.get('/cgi/id/notifications?take=10');
|
||||
_totalCount = resp.data['count'];
|
||||
_notifications.addAll(
|
||||
resp.data['data']?.map((e) => SnNotification.fromJson(e)).cast<SnNotification>() ?? [],
|
||||
resp.data['data']
|
||||
?.map((e) => SnNotification.fromJson(e))
|
||||
.cast<SnNotification>() ??
|
||||
[],
|
||||
);
|
||||
nty.updateTray();
|
||||
} catch (err) {
|
||||
@ -186,7 +188,8 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
||||
_fetchNotifications();
|
||||
},
|
||||
isLoading: _isBusy,
|
||||
hasReachedMax: _totalCount != null && _notifications.length >= _totalCount!,
|
||||
hasReachedMax: _totalCount != null &&
|
||||
_notifications.length >= _totalCount!,
|
||||
itemBuilder: (context, idx) {
|
||||
final nty = _notifications[idx];
|
||||
return Row(
|
||||
@ -218,36 +221,29 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
||||
isAutoWarp: true,
|
||||
),
|
||||
),
|
||||
if (['interactive.reply', 'interactive.feedback', 'interactive.subscription']
|
||||
.contains(nty.topic) &&
|
||||
if ([
|
||||
'interactive.feedback',
|
||||
'interactive.subscription'
|
||||
].contains(nty.topic) &&
|
||||
nty.metadata['related_post'] != null)
|
||||
GestureDetector(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: PostItem(
|
||||
data: SnPost.fromJson(
|
||||
nty.metadata['related_post']!,
|
||||
),
|
||||
showComments: false,
|
||||
showReactions: false,
|
||||
showMenu: false,
|
||||
StyledWidget(Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(8)),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
GoRouter.of(context).pushNamed(
|
||||
'postDetail',
|
||||
pathParameters: {
|
||||
'slug': nty.metadata['related_post']!['id'].toString(),
|
||||
},
|
||||
);
|
||||
},
|
||||
).padding(top: 8),
|
||||
child: PostItem(
|
||||
data: SnPost.fromJson(
|
||||
nty.metadata['related_post']!,
|
||||
),
|
||||
showComments: false,
|
||||
showReactions: false,
|
||||
showMenu: false,
|
||||
),
|
||||
)).padding(top: 8),
|
||||
const Gap(8),
|
||||
Row(
|
||||
children: [
|
||||
@ -272,8 +268,10 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.check),
|
||||
padding: EdgeInsets.all(0),
|
||||
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
|
||||
onPressed: _isSubmitting ? null : () => _markOneAsRead(nty),
|
||||
visualDensity:
|
||||
const VisualDensity(horizontal: -4, vertical: -4),
|
||||
onPressed:
|
||||
_isSubmitting ? null : () => _markOneAsRead(nty),
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 16);
|
||||
|
@ -602,7 +602,6 @@ class _PostStoryEditor extends StatelessWidget {
|
||||
border: InputBorder.none,
|
||||
),
|
||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
contentInsertionConfiguration: controller.contentInsertionConfiguration,
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -666,7 +665,6 @@ class _PostArticleEditor extends StatelessWidget {
|
||||
keyboardType: TextInputType.multiline,
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
contentInsertionConfiguration: controller.contentInsertionConfiguration,
|
||||
).padding(horizontal: 16),
|
||||
const Gap(4),
|
||||
];
|
||||
@ -694,7 +692,6 @@ class _PostArticleEditor extends StatelessWidget {
|
||||
border: InputBorder.none,
|
||||
),
|
||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
contentInsertionConfiguration: controller.contentInsertionConfiguration,
|
||||
),
|
||||
),
|
||||
const Gap(8),
|
||||
@ -729,7 +726,6 @@ class _PostArticleEditor extends StatelessWidget {
|
||||
border: InputBorder.none,
|
||||
),
|
||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
contentInsertionConfiguration: controller.contentInsertionConfiguration,
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -801,7 +797,6 @@ class _PostQuestionEditor extends StatelessWidget {
|
||||
border: InputBorder.none,
|
||||
),
|
||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
contentInsertionConfiguration: controller.contentInsertionConfiguration,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -189,7 +189,7 @@ class _RealmMemberListWidgetState extends State<_RealmMemberListWidget> {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final resp = await sn.client.get('/cgi/id/realms/${widget.realm!.alias}/members', queryParameters: {
|
||||
'take': 10,
|
||||
'offset': _members.length,
|
||||
'offset': 0,
|
||||
});
|
||||
|
||||
final out = List<SnRealmMember>.from(
|
||||
|
@ -1,17 +1,9 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'check_in.freezed.dart';
|
||||
|
||||
part 'check_in.g.dart';
|
||||
|
||||
final List<String> kCheckInResultTierSymbols = [
|
||||
'checkInResultTier1',
|
||||
'checkInResultTier2',
|
||||
'checkInResultTier3',
|
||||
'checkInResultTier4',
|
||||
'checkInResultTier5'
|
||||
].map((e) => e.tr()).toList();
|
||||
const List<String> kCheckInResultTierSymbols = ['Bad', 'Poor', 'Medium', 'Good', 'Great'];
|
||||
|
||||
@freezed
|
||||
class SnCheckInRecord with _$SnCheckInRecord {
|
||||
@ -29,7 +21,8 @@ class SnCheckInRecord with _$SnCheckInRecord {
|
||||
required int accountId,
|
||||
}) = _SnCheckInRecord;
|
||||
|
||||
factory SnCheckInRecord.fromJson(Map<String, dynamic> json) => _$SnCheckInRecordFromJson(json);
|
||||
factory SnCheckInRecord.fromJson(Map<String, dynamic> json) =>
|
||||
_$SnCheckInRecordFromJson(json);
|
||||
|
||||
String get symbol => kCheckInResultTierSymbols[resultTier];
|
||||
}
|
||||
|
@ -37,8 +37,6 @@ class SnPost with _$SnPost {
|
||||
required DateTime? publishedUntil,
|
||||
required int totalUpvote,
|
||||
required int totalDownvote,
|
||||
@Default(0) int totalViews,
|
||||
@Default(0) int totalAggregatedViews,
|
||||
required int publisherId,
|
||||
required int? pollId,
|
||||
required SnPublisher publisher,
|
||||
|
@ -47,8 +47,6 @@ mixin _$SnPost {
|
||||
DateTime? get publishedUntil => throw _privateConstructorUsedError;
|
||||
int get totalUpvote => throw _privateConstructorUsedError;
|
||||
int get totalDownvote => throw _privateConstructorUsedError;
|
||||
int get totalViews => throw _privateConstructorUsedError;
|
||||
int get totalAggregatedViews => throw _privateConstructorUsedError;
|
||||
int get publisherId => throw _privateConstructorUsedError;
|
||||
int? get pollId => throw _privateConstructorUsedError;
|
||||
SnPublisher get publisher => throw _privateConstructorUsedError;
|
||||
@ -97,8 +95,6 @@ abstract class $SnPostCopyWith<$Res> {
|
||||
DateTime? publishedUntil,
|
||||
int totalUpvote,
|
||||
int totalDownvote,
|
||||
int totalViews,
|
||||
int totalAggregatedViews,
|
||||
int publisherId,
|
||||
int? pollId,
|
||||
SnPublisher publisher,
|
||||
@ -154,8 +150,6 @@ class _$SnPostCopyWithImpl<$Res, $Val extends SnPost>
|
||||
Object? publishedUntil = freezed,
|
||||
Object? totalUpvote = null,
|
||||
Object? totalDownvote = null,
|
||||
Object? totalViews = null,
|
||||
Object? totalAggregatedViews = null,
|
||||
Object? publisherId = null,
|
||||
Object? pollId = freezed,
|
||||
Object? publisher = null,
|
||||
@ -271,14 +265,6 @@ class _$SnPostCopyWithImpl<$Res, $Val extends SnPost>
|
||||
? _value.totalDownvote
|
||||
: totalDownvote // ignore: cast_nullable_to_non_nullable
|
||||
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
|
||||
? _value.publisherId
|
||||
: publisherId // ignore: cast_nullable_to_non_nullable
|
||||
@ -400,8 +386,6 @@ abstract class _$$SnPostImplCopyWith<$Res> implements $SnPostCopyWith<$Res> {
|
||||
DateTime? publishedUntil,
|
||||
int totalUpvote,
|
||||
int totalDownvote,
|
||||
int totalViews,
|
||||
int totalAggregatedViews,
|
||||
int publisherId,
|
||||
int? pollId,
|
||||
SnPublisher publisher,
|
||||
@ -460,8 +444,6 @@ class __$$SnPostImplCopyWithImpl<$Res>
|
||||
Object? publishedUntil = freezed,
|
||||
Object? totalUpvote = null,
|
||||
Object? totalDownvote = null,
|
||||
Object? totalViews = null,
|
||||
Object? totalAggregatedViews = null,
|
||||
Object? publisherId = null,
|
||||
Object? pollId = freezed,
|
||||
Object? publisher = null,
|
||||
@ -577,14 +559,6 @@ class __$$SnPostImplCopyWithImpl<$Res>
|
||||
? _value.totalDownvote
|
||||
: totalDownvote // ignore: cast_nullable_to_non_nullable
|
||||
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
|
||||
? _value.publisherId
|
||||
: publisherId // ignore: cast_nullable_to_non_nullable
|
||||
@ -640,8 +614,6 @@ class _$SnPostImpl extends _SnPost {
|
||||
required this.publishedUntil,
|
||||
required this.totalUpvote,
|
||||
required this.totalDownvote,
|
||||
this.totalViews = 0,
|
||||
this.totalAggregatedViews = 0,
|
||||
required this.publisherId,
|
||||
required this.pollId,
|
||||
required this.publisher,
|
||||
@ -759,12 +731,6 @@ class _$SnPostImpl extends _SnPost {
|
||||
@override
|
||||
final int totalDownvote;
|
||||
@override
|
||||
@JsonKey()
|
||||
final int totalViews;
|
||||
@override
|
||||
@JsonKey()
|
||||
final int totalAggregatedViews;
|
||||
@override
|
||||
final int publisherId;
|
||||
@override
|
||||
final int? pollId;
|
||||
@ -777,7 +743,7 @@ class _$SnPostImpl extends _SnPost {
|
||||
|
||||
@override
|
||||
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, totalViews: $totalViews, totalAggregatedViews: $totalAggregatedViews, 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, publisherId: $publisherId, pollId: $pollId, publisher: $publisher, metric: $metric, preload: $preload)';
|
||||
}
|
||||
|
||||
@override
|
||||
@ -830,10 +796,6 @@ class _$SnPostImpl extends _SnPost {
|
||||
other.totalUpvote == totalUpvote) &&
|
||||
(identical(other.totalDownvote, totalDownvote) ||
|
||||
other.totalDownvote == totalDownvote) &&
|
||||
(identical(other.totalViews, totalViews) ||
|
||||
other.totalViews == totalViews) &&
|
||||
(identical(other.totalAggregatedViews, totalAggregatedViews) ||
|
||||
other.totalAggregatedViews == totalAggregatedViews) &&
|
||||
(identical(other.publisherId, publisherId) ||
|
||||
other.publisherId == publisherId) &&
|
||||
(identical(other.pollId, pollId) || other.pollId == pollId) &&
|
||||
@ -874,8 +836,6 @@ class _$SnPostImpl extends _SnPost {
|
||||
publishedUntil,
|
||||
totalUpvote,
|
||||
totalDownvote,
|
||||
totalViews,
|
||||
totalAggregatedViews,
|
||||
publisherId,
|
||||
pollId,
|
||||
publisher,
|
||||
@ -928,8 +888,6 @@ abstract class _SnPost extends SnPost {
|
||||
required final DateTime? publishedUntil,
|
||||
required final int totalUpvote,
|
||||
required final int totalDownvote,
|
||||
final int totalViews,
|
||||
final int totalAggregatedViews,
|
||||
required final int publisherId,
|
||||
required final int? pollId,
|
||||
required final SnPublisher publisher,
|
||||
@ -994,10 +952,6 @@ abstract class _SnPost extends SnPost {
|
||||
@override
|
||||
int get totalDownvote;
|
||||
@override
|
||||
int get totalViews;
|
||||
@override
|
||||
int get totalAggregatedViews;
|
||||
@override
|
||||
int get publisherId;
|
||||
@override
|
||||
int? get pollId;
|
||||
|
@ -62,9 +62,6 @@ _$SnPostImpl _$$SnPostImplFromJson(Map<String, dynamic> json) => _$SnPostImpl(
|
||||
: DateTime.parse(json['published_until'] as String),
|
||||
totalUpvote: (json['total_upvote'] 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(),
|
||||
pollId: (json['poll_id'] as num?)?.toInt(),
|
||||
publisher:
|
||||
@ -104,8 +101,6 @@ Map<String, dynamic> _$$SnPostImplToJson(_$SnPostImpl instance) =>
|
||||
'published_until': instance.publishedUntil?.toIso8601String(),
|
||||
'total_upvote': instance.totalUpvote,
|
||||
'total_downvote': instance.totalDownvote,
|
||||
'total_views': instance.totalViews,
|
||||
'total_aggregated_views': instance.totalAggregatedViews,
|
||||
'publisher_id': instance.publisherId,
|
||||
'poll_id': instance.pollId,
|
||||
'publisher': instance.publisher.toJson(),
|
||||
|
@ -684,15 +684,6 @@ 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(
|
||||
@ -838,6 +829,7 @@ class _PostContentHeader extends StatelessWidget {
|
||||
await sn.client.delete('/cgi/co/posts/${data.id}', queryParameters: {
|
||||
'publisherId': data.publisherId,
|
||||
});
|
||||
|
||||
if (!context.mounted) return;
|
||||
context.showSnackbar('postDeleted'.tr(args: ['#${data.id}']));
|
||||
} catch (err) {
|
||||
@ -846,25 +838,6 @@ 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
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
@ -1056,18 +1029,6 @@ class _PostContentHeader extends StatelessWidget {
|
||||
children: [
|
||||
const Icon(Symbols.flag),
|
||||
const Gap(16),
|
||||
Text('flagPostAction').tr(),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
_flagPost(context);
|
||||
},
|
||||
),
|
||||
PopupMenuItem(
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Symbols.report),
|
||||
const Gap(16),
|
||||
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
|
||||
# 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.
|
||||
version: 2.3.2+70
|
||||
version: 2.3.2+69
|
||||
|
||||
environment:
|
||||
sdk: ^3.5.4
|
||||
|
Loading…
x
Reference in New Issue
Block a user