Compare commits

...

13 Commits

Author SHA1 Message Date
e2ecb573a2 🚀 Launch 2.3.2+70 2025-02-18 00:52:07 +08:00
8cb5dff498 🌐 Update Traditional Chinese localization files 2025-02-18 00:43:57 +08:00
a5629975ed Content insert support 2025-02-18 00:43:12 +08:00
972b304969 Flag posts 2025-02-18 00:38:32 +08:00
e8ded55055 🐛 Fix some listing non offset bugs
 Optimize user listing speed
2025-02-18 00:07:48 +08:00
04875eb164 Support notifications with multiple attachments 2025-02-18 00:07:20 +08:00
54a59aa470 💄 Recommendation post indicator 2025-02-17 18:53:46 +08:00
365f330629 🐛 Realm related bug fixes 2025-02-16 19:50:34 +08:00
a7829d15b2 🐛 Remove android predictive back 2025-02-16 13:29:41 +08:00
a3868a4281 📝 Update README.md 2025-02-16 01:09:45 +08:00
LittleSheep
1d1d61d60c Merge pull request #1 from I21b/master 2025-02-15 23:40:05 +08:00
92
9cd1cad695 Merge branch 'Solsynth:master' into master 2025-02-15 15:00:07 +09:00
92
dde280833b idk what to say 2025-02-15 14:59:45 +09:00
29 changed files with 474 additions and 163 deletions

27
README.md Normal file
View File

@@ -0,0 +1,27 @@
# Solar Network
![](https://solsynth.dev/_next/static/media/alpha.e779a584.webp)
Hello there! Welcome to the main repository of the HyperNet (also known as the Solar Network). The code here is mainly about the frontend app (also known as Solian). But you can still post issues here to get help and request new features!
## Sub Projects
HyperNet, the Solar Network is a microservices project in which the backends are stored in separate repositories. Here is a simple index for it.
- The Core, Gateway: [Nexus](https://github.com/Solsynth/HyperNet.Nexus)
- The Auth Service: [Passport](https://github.com/Solsynth/HyperNet.Passport)
- The Posting Service: [Interactive](https://github.com/Solsynth/HyperNet.Interactive)
- The Messaging Service: [Messaging](https://github.com/Solsynth/HyperNet.Messaging)
- The Wallet Service: [Wallet](https://github.com/Solsynth/HyperNet.Wallet)
- The Crawler: [Reader](https://github.com/Solsynth/HyperNet.Reader)
- Some others may not be listed, you can search in the organization with `HyperNet.` the prefix of all HyperNet projects.
## Tech Stack
For those people who want to know the tech stack of this project, the frontend was built by Flutter, which provides the cross-platform ability.
The backend was built in Go and PostgreSQL with our very own microservice framework included in the nexus.
-----
The readme will be updated in the future, to be determined. For now, you can check out the link of this repository to learn more on our official website.

View File

@@ -17,7 +17,6 @@
android:label="Solian"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:enableOnBackInvokedCallback="true"
android:requestLegacyExternalStorage="true">
<meta-data
android:name="flutterEmbedding"

View File

@@ -54,7 +54,7 @@ class CheckInWidget : GlanceAppWidget() {
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.registerTypeAdapter(Instant::class.java, InstantAdapter())
.create()
val resultTierSymbols = listOf("大凶", "", "中平", "", "大吉")
val resultTierSymbols = listOf("Bad", "Poor", "Medium", "Good", "Great")
val prefs = currentState.preferences
val checkInRaw: String? = prefs.getString("pas_check_in_record", null)
@@ -120,7 +120,7 @@ class CheckInWidget : GlanceAppWidget() {
}
Text(
text = "You haven't checked in today",
text = "You haven't divined today",
style = TextStyle(fontSize = 15.sp, color = GlanceTheme.colors.onSurface)
)
}

View File

@@ -333,6 +333,7 @@
"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",
@@ -419,7 +420,7 @@
"callMessageEnded": "Call lasted {}",
"callMessageStarted": "Call started",
"dailyCheckIn": "Check In",
"dailyCheckInNone": "You haven't checked in today",
"dailyCheckInNone": "You haven't divined today",
"dailyCheckAction": "Check in right now!",
"dailyCheckDetail": "Can't understand the symbol? Master, help me understand it!",
"dailyCheckDetailTitle": "{}'s fortune details",
@@ -650,5 +651,19 @@
"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."
"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"
}
}

View File

@@ -331,6 +331,7 @@
"addAttachmentFromRandomId": "通过访问 ID 链接",
"attachmentDetailInfo": "附件详细信息",
"attachmentPastedImage": "粘贴的图片",
"attachmentInsertedImage": "插入的图片",
"attachmentInsertLink": "插入连接",
"attachmentSetAsPostThumbnail": "设置为帖子缩略图",
"attachmentUnsetAsPostThumbnail": "取消设置为帖子缩略图",
@@ -649,5 +650,19 @@
"realmIsCommunity": "社区领域",
"realmIsCommunityDescription": "社区领域会显示在发现页面上。",
"realmLeave": "离开领域",
"realmLeaveDescription": "离开当前领域,并且删除领域中的身份。"
"realmLeaveDescription": "离开当前领域,并且删除领域中的身份。",
"checkInResultTier1": "大凶",
"checkInResultTier2": "凶",
"checkInResultTier3": "中平",
"checkInResultTier4": "吉",
"checkInResultTier5": "大吉",
"flagPostAction": "吹哨",
"flagPost": "吹哨不良内容",
"flagPostDescription": "吹哨不良内容,如果吹哨用户占浏览量的 50% 或以上,则帖子会被折叠。吹哨后不可撤销。",
"flaggedPost": "哨子已经吹响。",
"postViews": {
"zero": "{} 次浏览",
"one": "{} 次浏览",
"other": "{} 次浏览"
}
}

View File

@@ -331,6 +331,7 @@
"addAttachmentFromRandomId": "通過訪問 ID 鏈接",
"attachmentDetailInfo": "附件詳細信息",
"attachmentPastedImage": "粘貼的圖片",
"attachmentInsertedImage": "插入的圖片",
"attachmentInsertLink": "插入連接",
"attachmentSetAsPostThumbnail": "設置為帖子縮略圖",
"attachmentUnsetAsPostThumbnail": "取消設置為帖子縮略圖",
@@ -624,5 +625,44 @@
"realmCommunityHint": "該領域是一個社區領域,你可以自由加入。",
"realmCommunityPublicChannelsHint": "該領域包含的公共頻道",
"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": "{} 次瀏覽"
}
}

View File

@@ -331,6 +331,7 @@
"addAttachmentFromRandomId": "通過訪問 ID 鏈接",
"attachmentDetailInfo": "附件詳細信息",
"attachmentPastedImage": "粘貼的圖片",
"attachmentInsertedImage": "插入的圖片",
"attachmentInsertLink": "插入連接",
"attachmentSetAsPostThumbnail": "設置為帖子縮略圖",
"attachmentUnsetAsPostThumbnail": "取消設置為帖子縮略圖",
@@ -624,5 +625,44 @@
"realmCommunityHint": "該領域是一個社區領域,你可以自由加入。",
"realmCommunityPublicChannelsHint": "該領域包含的公共頻道",
"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": "{} 次瀏覽"
}
}

View File

@@ -123,48 +123,59 @@ 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)
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 {
contentHandler?(content)
}
}
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)")
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)")
return
}
let targetSize = 800
let scaleProcessor = ResizingImageProcessor(referenceSize: CGSize(width: targetSize, height: targetSize), mode: .aspectFit)
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)")
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)")
self.contentHandler?(content)
}
case .failure(let error):
print("Failed to retrieve image: \(error.localizedDescription)")
self.contentHandler?(content)
}
}
}

View File

@@ -15,14 +15,14 @@ struct CheckInProvider: TimelineProvider {
func getSnapshot(in context: Context, completion: @escaping (CheckInEntry) -> ()) {
let prefs = UserDefaults(suiteName: "group.solsynth.solian")
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'"
let jsonDecoder = JSONDecoder()
jsonDecoder.dateDecodingStrategy = .formatted(dateFormatter)
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
let checkInRaw = prefs?.string(forKey: "pas_check_in_record")
var checkIn: SolarCheckInRecord?
if let checkInRaw = checkInRaw {
@@ -31,7 +31,7 @@ struct CheckInProvider: TimelineProvider {
checkIn = nil
}
}
let entry = CheckInEntry(
date: Date(),
checkIn: checkIn
@@ -54,11 +54,11 @@ struct CheckInEntry: TimelineEntry {
struct CheckInWidgetEntryView : View {
var entry: CheckInProvider.Entry
private let resultTierSymbols: [String] = ["大凶", "", "中平", "", "大吉"]
private let resultTierSymbols: [String] = ["Bad", "Poor", "Medium", "Good", "Great"]
func checkIn() -> Void {}
func seeDetail() -> Void {}
var body: some View {
@@ -68,9 +68,9 @@ struct CheckInWidgetEntryView : View {
Text(resultTierSymbols[checkIn.resultTier]).font(.system(size: 27, weight: .bold))
Text("+\(checkIn.resultExperience) EXP").font(.system(size: 15, design: .monospaced))
}.padding(.horizontal, 4)
Spacer()
HStack {
VStack(alignment: .leading) {
Text(
@@ -82,7 +82,7 @@ struct CheckInWidgetEntryView : View {
format: .dateTime.day().month()
).font(.system(size: 13))
}.padding(.leading, 4)
Button("See Detail", systemImage: "arrow.right", action: seeDetail)
.labelStyle(.iconOnly)
.buttonBorderShape(.circle)
@@ -91,11 +91,11 @@ struct CheckInWidgetEntryView : View {
} else {
VStack(alignment: .leading) {
Text("Check In").font(.system(size: 19, weight: .bold))
Text("You haven't check in today").font(.system(size: 15))
Text("You haven't divined today").font(.system(size: 15))
}.padding(.horizontal, 4)
Spacer()
HStack(alignment: .bottom) {
Button("Check In", systemImage: "checkmark", action: checkIn).labelStyle(.iconOnly).buttonBorderShape(.circle).frame(maxWidth: .infinity, alignment: .trailing)
}

View File

@@ -158,6 +158,14 @@ 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}) {

View File

@@ -45,8 +45,8 @@ class ConfigProvider extends ChangeNotifier {
bool newDrawerIsCollapsed = false;
bool newDrawerIsExpanded = false;
if (withMediaQuery) {
newDrawerIsCollapsed = MediaQuery.of(context).size.width < 450;
newDrawerIsExpanded = MediaQuery.of(context).size.width >= 451;
newDrawerIsCollapsed = MediaQuery.of(context).size.width < 600;
newDrawerIsExpanded = MediaQuery.of(context).size.width >= 601;
} else {
final rpb = ResponsiveBreakpoints.of(context);
newDrawerIsCollapsed = rpb.smallerOrEqualTo(MOBILE);

View File

@@ -14,9 +14,32 @@ class UserDirectoryProvider {
final Map<int, SnAccount> _cache = {};
Future<List<SnAccount?>> listAccount(Iterable<dynamic> id) async {
final out = await Future.wait(
id.map((e) => getAccount(e)),
);
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++;
}
return out;
}

View File

@@ -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': 0,
'offset': _members.length,
});
final out = List<SnChannelMember>.from(
resp.data['data']?.map((e) => SnChannelMember.fromJson(e)) ?? [],

View File

@@ -663,10 +663,24 @@ 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
@@ -684,17 +698,24 @@ class _HomeDashRecommendationPostWidgetState extends State<_HomeDashRecommendati
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Icon(Symbols.star),
const Gap(8),
Text(
'postRecommendation',
style: Theme.of(context).textTheme.titleLarge,
).tr()
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())
],
).padding(horizontal: 18, top: 12, bottom: 8),
Expanded(
child: PageView.builder(
controller: _pageController,
scrollBehavior: ScrollConfiguration.of(context).copyWith(dragDevices: {
PointerDeviceKind.mouse,
PointerDeviceKind.touch,

View File

@@ -3,6 +3,7 @@ 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';
@@ -59,10 +60,7 @@ 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) {
@@ -188,8 +186,7 @@ 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(
@@ -221,29 +218,36 @@ class _NotificationScreenState extends State<NotificationScreen> {
isAutoWarp: true,
),
),
if ([
'interactive.feedback',
'interactive.subscription'
].contains(nty.topic) &&
if (['interactive.reply', 'interactive.feedback', 'interactive.subscription']
.contains(nty.topic) &&
nty.metadata['related_post'] != null)
StyledWidget(Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(
Radius.circular(8)),
border: Border.all(
color: Theme.of(context).dividerColor,
width: 1,
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,
),
),
child: PostItem(
data: SnPost.fromJson(
nty.metadata['related_post']!,
),
showComments: false,
showReactions: false,
showMenu: false,
),
)).padding(top: 8),
onTap: () {
GoRouter.of(context).pushNamed(
'postDetail',
pathParameters: {
'slug': nty.metadata['related_post']!['id'].toString(),
},
);
},
).padding(top: 8),
const Gap(8),
Row(
children: [
@@ -268,10 +272,8 @@ 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);

View File

@@ -602,6 +602,7 @@ class _PostStoryEditor extends StatelessWidget {
border: InputBorder.none,
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
contentInsertionConfiguration: controller.contentInsertionConfiguration,
),
],
),
@@ -665,6 +666,7 @@ 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),
];
@@ -692,6 +694,7 @@ class _PostArticleEditor extends StatelessWidget {
border: InputBorder.none,
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
contentInsertionConfiguration: controller.contentInsertionConfiguration,
),
),
const Gap(8),
@@ -726,6 +729,7 @@ class _PostArticleEditor extends StatelessWidget {
border: InputBorder.none,
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
contentInsertionConfiguration: controller.contentInsertionConfiguration,
),
),
],
@@ -797,6 +801,7 @@ class _PostQuestionEditor extends StatelessWidget {
border: InputBorder.none,
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
contentInsertionConfiguration: controller.contentInsertionConfiguration,
),
],
),

View File

@@ -186,7 +186,11 @@ class _RealmScreenState extends State<RealmScreen> {
GoRouter.of(context).pushNamed(
'realmDetail',
pathParameters: {'alias': realm.alias},
);
).then((value) {
if (value == true) {
_fetchRealms();
}
});
},
);
}
@@ -244,7 +248,11 @@ class _RealmScreenState extends State<RealmScreen> {
GoRouter.of(context).pushNamed(
'realmDetail',
pathParameters: {'alias': realm.alias},
);
).then((value) {
if (value == true) {
_fetchRealms();
}
});
},
),
),

View File

@@ -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': 0,
'offset': _members.length,
});
final out = List<SnRealmMember>.from(
@@ -343,12 +343,9 @@ class _RealmSettingsWidgetState extends State<_RealmSettingsWidget> {
setState(() => _isBusy = true);
try {
await sn.client.delete('/cgi/id/realms/${widget.realm!.alias}');
await sn.client.delete('/cgi/id/realms/${widget.realm!.id}');
if (!mounted) return;
Navigator.pop(context, true);
context.showSnackbar('realmDeleted'.tr(args: [
'#${widget.realm!.alias}',
]));
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);

View File

@@ -57,7 +57,7 @@ Future<ThemeData> createAppTheme(
),
pageTransitionsTheme: PageTransitionsTheme(
builders: {
TargetPlatform.android: PredictiveBackPageTransitionsBuilder(),
TargetPlatform.android: ZoomPageTransitionsBuilder(),
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
TargetPlatform.macOS: ZoomPageTransitionsBuilder(),
TargetPlatform.fuchsia: ZoomPageTransitionsBuilder(),

View File

@@ -20,7 +20,7 @@ class SnAccount with _$SnAccount {
required String description,
required String name,
required String nick,
required Map<String, dynamic> permNodes,
@Default({}) Map<String, dynamic> permNodes,
required String language,
required SnAccountProfile? profile,
@Default([]) List<SnAccountBadge> badges,

View File

@@ -385,7 +385,7 @@ class _$SnAccountImpl extends _SnAccount {
required this.description,
required this.name,
required this.nick,
required final Map<String, dynamic> permNodes,
final Map<String, dynamic> permNodes = const {},
required this.language,
required this.profile,
final List<SnAccountBadge> badges = const [],
@@ -437,6 +437,7 @@ class _$SnAccountImpl extends _SnAccount {
final String nick;
final Map<String, dynamic> _permNodes;
@override
@JsonKey()
Map<String, dynamic> get permNodes {
if (_permNodes is EqualUnmodifiableMapView) return _permNodes;
// ignore: implicit_dynamic_type
@@ -566,7 +567,7 @@ abstract class _SnAccount extends SnAccount {
required final String description,
required final String name,
required final String nick,
required final Map<String, dynamic> permNodes,
final Map<String, dynamic> permNodes,
required final String language,
required final SnAccountProfile? profile,
final List<SnAccountBadge> badges,

View File

@@ -25,7 +25,7 @@ _$SnAccountImpl _$$SnAccountImplFromJson(Map<String, dynamic> json) =>
description: json['description'] as String,
name: json['name'] as String,
nick: json['nick'] as String,
permNodes: json['perm_nodes'] as Map<String, dynamic>,
permNodes: json['perm_nodes'] as Map<String, dynamic>? ?? const {},
language: json['language'] as String,
profile: json['profile'] == null
? null

View File

@@ -1,9 +1,17 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'check_in.freezed.dart';
part 'check_in.g.dart';
const List<String> kCheckInResultTierSymbols = ['大凶', '', '中平', '', '大吉'];
final List<String> kCheckInResultTierSymbols = [
'checkInResultTier1',
'checkInResultTier2',
'checkInResultTier3',
'checkInResultTier4',
'checkInResultTier5'
].map((e) => e.tr()).toList();
@freezed
class SnCheckInRecord with _$SnCheckInRecord {
@@ -21,8 +29,7 @@ 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];
}

View File

@@ -37,6 +37,8 @@ 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,

View File

@@ -47,6 +47,8 @@ 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;
@@ -95,6 +97,8 @@ abstract class $SnPostCopyWith<$Res> {
DateTime? publishedUntil,
int totalUpvote,
int totalDownvote,
int totalViews,
int totalAggregatedViews,
int publisherId,
int? pollId,
SnPublisher publisher,
@@ -150,6 +154,8 @@ 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,
@@ -265,6 +271,14 @@ 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
@@ -386,6 +400,8 @@ abstract class _$$SnPostImplCopyWith<$Res> implements $SnPostCopyWith<$Res> {
DateTime? publishedUntil,
int totalUpvote,
int totalDownvote,
int totalViews,
int totalAggregatedViews,
int publisherId,
int? pollId,
SnPublisher publisher,
@@ -444,6 +460,8 @@ 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,
@@ -559,6 +577,14 @@ 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
@@ -614,6 +640,8 @@ 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,
@@ -731,6 +759,12 @@ 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;
@@ -743,7 +777,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, 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
@@ -796,6 +830,10 @@ 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) &&
@@ -836,6 +874,8 @@ class _$SnPostImpl extends _SnPost {
publishedUntil,
totalUpvote,
totalDownvote,
totalViews,
totalAggregatedViews,
publisherId,
pollId,
publisher,
@@ -888,6 +928,8 @@ 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,
@@ -952,6 +994,10 @@ 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;

View File

@@ -62,6 +62,9 @@ _$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:
@@ -101,6 +104,8 @@ 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(),

View File

@@ -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(
@@ -829,7 +838,6 @@ 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) {
@@ -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
Widget build(BuildContext context) {
return Row(
@@ -1029,6 +1056,18 @@ 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(),
],
),

View File

@@ -66,10 +66,10 @@ packages:
dependency: transitive
description:
name: async
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
url: "https://pub.dev"
source: hosted
version: "2.12.0"
version: "2.11.0"
bitsdojo_window:
dependency: "direct main"
description:
@@ -114,10 +114,10 @@ packages:
dependency: transitive
description:
name: boolean_selector
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
version: "2.1.1"
build:
dependency: transitive
description:
@@ -226,10 +226,10 @@ packages:
dependency: transitive
description:
name: characters
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
url: "https://pub.dev"
source: hosted
version: "1.4.0"
version: "1.3.0"
checked_yaml:
dependency: transitive
description:
@@ -250,10 +250,10 @@ packages:
dependency: transitive
description:
name: clock
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
url: "https://pub.dev"
source: hosted
version: "1.1.2"
version: "1.1.1"
code_builder:
dependency: transitive
description:
@@ -266,10 +266,10 @@ packages:
dependency: "direct main"
description:
name: collection
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
url: "https://pub.dev"
source: hosted
version: "1.19.1"
version: "1.19.0"
connectivity_plus:
dependency: transitive
description:
@@ -474,10 +474,10 @@ packages:
dependency: transitive
description:
name: fake_async
sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
url: "https://pub.dev"
source: hosted
version: "1.3.2"
version: "1.3.1"
ffi:
dependency: transitive
description:
@@ -1158,18 +1158,18 @@ packages:
dependency: transitive
description:
name: leak_tracker
sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06"
url: "https://pub.dev"
source: hosted
version: "10.0.8"
version: "10.0.7"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379"
url: "https://pub.dev"
source: hosted
version: "3.0.9"
version: "3.0.8"
leak_tracker_testing:
dependency: transitive
description:
@@ -1238,10 +1238,10 @@ packages:
dependency: transitive
description:
name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev"
source: hosted
version: "0.12.17"
version: "0.12.16+1"
material_color_utilities:
dependency: transitive
description:
@@ -1342,10 +1342,10 @@ packages:
dependency: transitive
description:
name: meta
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
url: "https://pub.dev"
source: hosted
version: "1.16.0"
version: "1.15.0"
mime:
dependency: "direct main"
description:
@@ -1414,10 +1414,10 @@ packages:
dependency: "direct main"
description:
name: path
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
version: "1.9.0"
path_parsing:
dependency: transitive
description:
@@ -1534,10 +1534,10 @@ packages:
dependency: transitive
description:
name: petitparser
sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646"
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
url: "https://pub.dev"
source: hosted
version: "6.1.0"
version: "6.0.2"
photo_view:
dependency: "direct main"
description:
@@ -1558,10 +1558,10 @@ packages:
dependency: transitive
description:
name: platform_detect
sha256: "7394dc1d884e652785a37c3ff25c54e503c6d9fa2f35b55d5efc0a133dec122c"
sha256: a62f99417fc4fa2d099ce0ccdbb1bd3977920f2a64292c326271f049d4bc3a4f
url: "https://pub.dev"
source: hosted
version: "2.1.5"
version: "2.1.0"
plugin_platform_interface:
dependency: transitive
description:
@@ -1883,10 +1883,10 @@ packages:
dependency: transitive
description:
name: source_span
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev"
source: hosted
version: "1.10.1"
version: "1.10.0"
sprintf:
dependency: transitive
description:
@@ -1899,34 +1899,34 @@ packages:
dependency: transitive
description:
name: sqflite
sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03
sha256: "2d7299468485dca85efeeadf5d38986909c5eb0cd71fd3db2c2f000e6c9454bb"
url: "https://pub.dev"
source: hosted
version: "2.4.2"
version: "2.4.1"
sqflite_android:
dependency: transitive
description:
name: sqflite_android
sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b"
sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
version: "2.4.0"
sqflite_common:
dependency: transitive
description:
name: sqflite_common
sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b"
sha256: "761b9740ecbd4d3e66b8916d784e581861fd3c3553eda85e167bc49fdb68f709"
url: "https://pub.dev"
source: hosted
version: "2.5.5"
version: "2.5.4+6"
sqflite_darwin:
dependency: transitive
description:
name: sqflite_darwin
sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3"
sha256: "22adfd9a2c7d634041e96d6241e6e1c8138ca6817018afc5d443fef91dcefa9c"
url: "https://pub.dev"
source: hosted
version: "2.4.2"
version: "2.4.1+1"
sqflite_platform_interface:
dependency: transitive
description:
@@ -1939,18 +1939,18 @@ packages:
dependency: transitive
description:
name: stack_trace
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
url: "https://pub.dev"
source: hosted
version: "1.12.1"
version: "1.12.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev"
source: hosted
version: "2.1.4"
version: "2.1.2"
stream_transform:
dependency: transitive
description:
@@ -1963,10 +1963,10 @@ packages:
dependency: transitive
description:
name: string_scanner
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3"
url: "https://pub.dev"
source: hosted
version: "1.4.1"
version: "1.3.0"
styled_widget:
dependency: "direct main"
description:
@@ -1987,26 +1987,26 @@ packages:
dependency: "direct main"
description:
name: synchronized
sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6"
sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225"
url: "https://pub.dev"
source: hosted
version: "3.3.1"
version: "3.3.0+3"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
url: "https://pub.dev"
source: hosted
version: "1.2.2"
version: "1.2.1"
test_api:
dependency: transitive
description:
name: test_api
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
url: "https://pub.dev"
source: hosted
version: "0.7.4"
version: "0.7.3"
timing:
dependency: transitive
description:
@@ -2195,10 +2195,10 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b
url: "https://pub.dev"
source: hosted
version: "14.3.1"
version: "14.3.0"
volume_controller:
dependency: transitive
description:
@@ -2312,5 +2312,5 @@ packages:
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.7.0 <4.0.0"
dart: ">=3.6.0 <4.0.0"
flutter: ">=3.27.0"

View File

@@ -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+69
version: 2.3.2+70
environment:
sdk: ^3.5.4