Compare commits

...

35 Commits

Author SHA1 Message Date
72e6a6a1f6 Enhanced profile edit 2025-03-02 20:37:36 +08:00
66aef44281 ⬆️ Upgrade freezed 2025-03-02 15:22:24 +08:00
7bb73c80b0 🐛 Fixes on load new messages 2025-03-01 22:52:22 +08:00
d043ef2410 🐛 Fix websocket uri too long cause disconnect 2025-03-01 18:49:45 +08:00
1d0e2f7591 Provide client id to websocket 2025-03-01 18:34:59 +08:00
e9ef28d764 Optimize loading speed of chat
 Support new subscribe channel
2025-03-01 18:32:31 +08:00
289aa17a7a 🐛 Fix video post editor layout issue 2025-02-28 00:11:54 +08:00
93f41bb523 Chat input auto grow 2025-02-28 00:08:12 +08:00
09ec9d4a0c 🐛 Fix displaying quoted message attachment with weird padding 2025-02-28 00:03:47 +08:00
1153fbdeee Cache management 2025-02-27 23:46:47 +08:00
e933058338 💄 Optimize runtime log screen 2025-02-27 23:33:29 +08:00
ae9743c84f ♻️ Refactor logging module 2025-02-27 23:30:08 +08:00
32bf834108 Logging framework 2025-02-27 22:58:31 +08:00
1b41c847a6 Custom fonts 2025-02-27 22:35:12 +08:00
b1af6c2c97 🐛 Optimize and fix profile page loading issue 2025-02-27 22:11:53 +08:00
8e76ff3f84 Optimize user loading api usage 2025-02-27 20:51:47 +08:00
bd26602299 Code highlighting 2025-02-26 23:29:02 +08:00
52ab1d0d10 🐛 Fix chat last message displaying inconsistences 2025-02-26 00:29:35 +08:00
f746e06f65 ⚗️ Experimental user first badge showing on chat 2025-02-26 00:25:42 +08:00
d11069a2be 🐛 Bug fixes on notification page 2025-02-26 00:00:53 +08:00
d6dc487d9e Latex Rendering, closed #9 2025-02-25 23:49:48 +08:00
a07c7cdede 🐛 Fix infinite loading own sticker 2025-02-25 22:56:30 +08:00
acbc125dec 🚀 Launch 2.3.2+75 2025-02-24 23:21:06 +08:00
ad0ee971c1 Desktop mute notification
🐛 Bug fixes on tray icon
2025-02-24 22:46:02 +08:00
52d6bb083e 🐛 Fix macos titlebar not centered 2025-02-24 22:38:08 +08:00
2027eab49b 💄 Optimize displaying of message 2025-02-24 22:35:14 +08:00
566ebde1dd 🐛 Fix windows tray issue 2025-02-24 21:59:41 +08:00
9e039cc532 🐛 Fix editing message 2025-02-24 21:31:12 +08:00
c4b95d7084 🐛 Fix account settings screen error cause by locale 2025-02-24 21:25:12 +08:00
a66129a9ba 🐛 Bug fixes 2025-02-24 21:18:49 +08:00
44e1a8bf67 🚀 Launch 2.3.2+74 2025-02-23 22:45:01 +08:00
efcfd3f57d 🚀 Launch 2.3.2+73 2025-02-23 21:37:33 +08:00
84759715a4 💄 Not showing notification when in the channel 2025-02-23 21:19:34 +08:00
fda09382dd 💄 Hide unread count auto after entering channel 2025-02-23 21:10:32 +08:00
2c5dd0563a 🐛 Fix checking for update db issue 2025-02-23 21:10:18 +08:00
93 changed files with 12047 additions and 10739 deletions

View File

@@ -0,0 +1,11 @@
meta {
name: Check Status
type: http
seq: 1
}
get {
url: {{endpoint}}/directory/status
body: none
auth: none
}

View File

@@ -0,0 +1,11 @@
meta {
name: List Services
type: http
seq: 2
}
get {
url: {{endpoint}}/directory/services
body: none
auth: none
}

View File

@@ -0,0 +1,18 @@
meta {
name: Deal Abuse Report
type: http
seq: 3
}
put {
url: {{endpoint}}/cgi/id/reports/abuse/3/status
body: json
auth: inherit
}
body:json {
{
"status": "processed",
"message": "相关附件已经进行评级处理,未来会将该项权限下放到帖主以及社区成员。"
}
}

View File

@@ -203,6 +203,11 @@
"other": "{} comments" "other": "{} comments"
}, },
"settingsAppearance": "Appearance", "settingsAppearance": "Appearance",
"settingsCustomFonts": "Custom Fonts",
"settingsCustomFontsDescription": "Set custom fonts for the application.",
"settingsCustomFontFamily": "Custom Font Family",
"settingsCustomFontFamilyHint": "Use comma to separate fonts, higher priority comes first",
"settingsCustomFontApplied": "Custom font has been applied.",
"settingsDisplayLanguage": "Display Language", "settingsDisplayLanguage": "Display Language",
"settingsDisplayLanguageDescription": "Set the application language.", "settingsDisplayLanguageDescription": "Set the application language.",
"settingsDisplayLanguageSystem": "Follow System", "settingsDisplayLanguageSystem": "Follow System",
@@ -719,7 +724,24 @@
"stickersNewDescription": "Create a new sticker belongs to this pack.", "stickersNewDescription": "Create a new sticker belongs to this pack.",
"stickersPackNew": "New Sticker Pack", "stickersPackNew": "New Sticker Pack",
"trayMenuShow": "Show", "trayMenuShow": "Show",
"trayMenuMuteNotification": "Do Not Disturb",
"update": "Update", "update": "Update",
"forceUpdate": "Force Update", "forceUpdate": "Force Update",
"forceUpdateDescription": "Force to show the application update popup, even the new version is not available." "forceUpdateDescription": "Force to show the application update popup, even the new version is not available.",
"debugLogging": "Runtime Logs",
"runtimeLogsOpen": "Open Logs",
"runtimeLogsDescription": "Show the runtime logs to help debugging.",
"signinResetPasswordHint": "Please enter the username / email address to help us to find your account and reset your password.",
"cacheSize": "Cache Size",
"cacheDelete": "Clean Cache",
"cacheDeleteDescription": "Remove the cached images and other resources from your disk, the content will be downloaded from server again.",
"cacheDeleted": "All cache has been cleaned up.",
"userNoDescription": "No description.",
"fieldTimeZone": "Time Zone",
"fieldGender": "Gender",
"fieldPronouns": "Pronouns",
"fieldLocation": "Location",
"fieldLinks": "Links",
"fieldLinkName": "Name",
"fieldLinkUrl": "URL"
} }

View File

@@ -201,6 +201,11 @@
"other": "{} 条评论" "other": "{} 条评论"
}, },
"settingsAppearance": "外观", "settingsAppearance": "外观",
"settingsCustomFonts": "自定义字体",
"settingsCustomFontsDescription": "设置应用程序使用的字体。",
"settingsCustomFontFamily": "应用字体",
"settingsCustomFontFamilyHint": "使用英文逗号分割每一种字体,越前优先级越高",
"settingsCustomFontApplied": "自定义字体已经应用。",
"settingsDisplayLanguage": "显示语言", "settingsDisplayLanguage": "显示语言",
"settingsDisplayLanguageDescription": "设置应用程序使用的语言", "settingsDisplayLanguageDescription": "设置应用程序使用的语言",
"settingsDisplayLanguageSystem": "跟随系统", "settingsDisplayLanguageSystem": "跟随系统",
@@ -717,7 +722,24 @@
"stickersNewDescription": "创建一个新的贴图。", "stickersNewDescription": "创建一个新的贴图。",
"stickersPackNew": "新建贴图包", "stickersPackNew": "新建贴图包",
"trayMenuShow": "显示", "trayMenuShow": "显示",
"trayMenuMuteNotification": "静音通知",
"update": "更新", "update": "更新",
"forceUpdate": "强制更新", "forceUpdate": "强制更新",
"forceUpdateDescription": "强制更新应用程序,即使有更新的版本可能不可用。" "forceUpdateDescription": "强制更新应用程序,即使有更新的版本可能不可用。",
"runtimeLogs": "运行时日志",
"runtimeLogsOpen": "打开日志文件",
"runtimeLogsDescription": "显示运行时的日志记录。",
"signinResetPasswordHint": "请输入用户名/电子邮箱地址以帮助我们找到您的帐户并重置密码。",
"cacheSize": "缓存资源大小",
"cacheDelete": "清除缓存",
"cacheDeleteDescription": "从磁盘中移除缓存的图片和其他资源,内容将从服务器重新下载。",
"cacheDeleted": "所有缓存已被清除。",
"userNoDescription": "这个人很懒,没有留下什么……",
"fieldTimeZone": "时区",
"fieldGender": "性别",
"fieldPronouns": "人称代词",
"fieldLocation": "位置",
"fieldLinks": "链接",
"fieldLinkName": "名称",
"fieldLinkUrl": "链接"
} }

View File

@@ -201,6 +201,11 @@
"other": "{} 條評論" "other": "{} 條評論"
}, },
"settingsAppearance": "外觀", "settingsAppearance": "外觀",
"settingsCustomFonts": "自定義字體",
"settingsCustomFontsDescription": "設置應用程序使用的字體。",
"settingsCustomFontFamily": "應用字體",
"settingsCustomFontFamilyHint": "使用英文逗號分割每一種字體,越前優先級越高",
"settingsCustomFontApplied": "自定義字體已經應用。",
"settingsDisplayLanguage": "顯示語言", "settingsDisplayLanguage": "顯示語言",
"settingsDisplayLanguageDescription": "設置應用程序使用的語言", "settingsDisplayLanguageDescription": "設置應用程序使用的語言",
"settingsDisplayLanguageSystem": "跟隨系統", "settingsDisplayLanguageSystem": "跟隨系統",
@@ -717,7 +722,24 @@
"stickersNewDescription": "創建一個新的貼圖。", "stickersNewDescription": "創建一個新的貼圖。",
"stickersPackNew": "新建貼圖包", "stickersPackNew": "新建貼圖包",
"trayMenuShow": "顯示", "trayMenuShow": "顯示",
"trayMenuMuteNotification": "靜音通知",
"update": "更新", "update": "更新",
"forceUpdate": "強制更新", "forceUpdate": "強制更新",
"forceUpdateDescription": "強制更新應用程序,即使有更新的版本可能不可用。" "forceUpdateDescription": "強制更新應用程序,即使有更新的版本可能不可用。",
"runtimeLogs": "運行時日誌",
"runtimeLogsOpen": "打開日誌文件",
"runtimeLogsDescription": "顯示運行時的日誌記錄。",
"signinResetPasswordHint": "請輸入用户名/電子郵箱地址以幫助我們找到您的帳户並重置密碼。",
"cacheSize": "緩存資源大小",
"cacheDelete": "清除緩存",
"cacheDeleteDescription": "從磁盤中移除緩存的圖片和其他資源,內容將從服務器重新下載。",
"cacheDeleted": "所有緩存已被清除。",
"userNoDescription": "這個人很懶,沒有留下什麼……",
"fieldTimeZone": "時區",
"fieldGender": "性別",
"fieldPronouns": "人稱代詞",
"fieldLocation": "位置",
"fieldLinks": "鏈接",
"fieldLinkName": "名稱",
"fieldLinkUrl": "鏈接"
} }

View File

@@ -201,6 +201,11 @@
"other": "{} 條評論" "other": "{} 條評論"
}, },
"settingsAppearance": "外觀", "settingsAppearance": "外觀",
"settingsCustomFonts": "自定義字體",
"settingsCustomFontsDescription": "設置應用程序使用的字體。",
"settingsCustomFontFamily": "應用字體",
"settingsCustomFontFamilyHint": "使用英文逗號分割每一種字體,越前優先級越高",
"settingsCustomFontApplied": "自定義字體已經應用。",
"settingsDisplayLanguage": "顯示語言", "settingsDisplayLanguage": "顯示語言",
"settingsDisplayLanguageDescription": "設置應用程序使用的語言", "settingsDisplayLanguageDescription": "設置應用程序使用的語言",
"settingsDisplayLanguageSystem": "跟隨系統", "settingsDisplayLanguageSystem": "跟隨系統",
@@ -717,7 +722,24 @@
"stickersNewDescription": "創建一個新的貼圖。", "stickersNewDescription": "創建一個新的貼圖。",
"stickersPackNew": "新建貼圖包", "stickersPackNew": "新建貼圖包",
"trayMenuShow": "顯示", "trayMenuShow": "顯示",
"trayMenuMuteNotification": "靜音通知",
"update": "更新", "update": "更新",
"forceUpdate": "強制更新", "forceUpdate": "強制更新",
"forceUpdateDescription": "強制更新應用程序,即使有更新的版本可能不可用。" "forceUpdateDescription": "強制更新應用程序,即使有更新的版本可能不可用。",
"runtimeLogs": "運行時日誌",
"runtimeLogsOpen": "打開日誌文件",
"runtimeLogsDescription": "顯示運行時的日誌記錄。",
"signinResetPasswordHint": "請輸入用戶名/電子郵箱地址以幫助我們找到您的帳戶並重置密碼。",
"cacheSize": "緩存資源大小",
"cacheDelete": "清除緩存",
"cacheDeleteDescription": "從磁盤中移除緩存的圖片和其他資源,內容將從服務器重新下載。",
"cacheDeleted": "所有緩存已被清除。",
"userNoDescription": "這個人很懶,沒有留下什麼……",
"fieldTimeZone": "時區",
"fieldGender": "性別",
"fieldPronouns": "人稱代詞",
"fieldLocation": "位置",
"fieldLinks": "鏈接",
"fieldLinkName": "名稱",
"fieldLinkUrl": "鏈接"
} }

View File

@@ -52,14 +52,14 @@ PODS:
- Firebase/Messaging (11.8.0): - Firebase/Messaging (11.8.0):
- Firebase/CoreOnly - Firebase/CoreOnly
- FirebaseMessaging (~> 11.8.0) - FirebaseMessaging (~> 11.8.0)
- firebase_analytics (11.4.3): - firebase_analytics (11.4.4):
- Firebase/Analytics (= 11.8.0) - Firebase/Analytics (= 11.8.0)
- firebase_core - firebase_core
- Flutter - Flutter
- firebase_core (3.12.0): - firebase_core (3.12.1):
- Firebase/CoreOnly (= 11.8.0) - Firebase/CoreOnly (= 11.8.0)
- Flutter - Flutter
- firebase_messaging (15.2.3): - firebase_messaging (15.2.4):
- Firebase/Messaging (= 11.8.0) - Firebase/Messaging (= 11.8.0)
- firebase_core - firebase_core
- Flutter - Flutter
@@ -113,6 +113,8 @@ PODS:
- OrderedSet (~> 6.0.3) - OrderedSet (~> 6.0.3)
- flutter_native_splash (2.4.3): - flutter_native_splash (2.4.3):
- Flutter - Flutter
- flutter_timezone (0.0.1):
- Flutter
- flutter_udid (0.0.1): - flutter_udid (0.0.1):
- Flutter - Flutter
- SAMKeychain - SAMKeychain
@@ -122,6 +124,8 @@ PODS:
- gal (1.0.0): - gal (1.0.0):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
- geolocator_apple (1.2.0):
- Flutter
- GoogleAppMeasurement (11.8.0): - GoogleAppMeasurement (11.8.0):
- GoogleAppMeasurement/AdIdSupport (= 11.8.0) - GoogleAppMeasurement/AdIdSupport (= 11.8.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0) - GoogleUtilities/AppDelegateSwizzler (~> 8.0)
@@ -235,7 +239,7 @@ PODS:
- sqlite3_flutter_libs (0.0.1): - sqlite3_flutter_libs (0.0.1):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
- sqlite3 (~> 3.49.0) - sqlite3 (~> 3.49.1)
- sqlite3/dbstatvtab - sqlite3/dbstatvtab
- sqlite3/fts5 - sqlite3/fts5
- sqlite3/perf-threadsafe - sqlite3/perf-threadsafe
@@ -267,9 +271,11 @@ DEPENDENCIES:
- flutter_app_update (from `.symlinks/plugins/flutter_app_update/ios`) - flutter_app_update (from `.symlinks/plugins/flutter_app_update/ios`)
- flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`) - flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
- flutter_timezone (from `.symlinks/plugins/flutter_timezone/ios`)
- flutter_udid (from `.symlinks/plugins/flutter_udid/ios`) - flutter_udid (from `.symlinks/plugins/flutter_udid/ios`)
- flutter_webrtc (from `.symlinks/plugins/flutter_webrtc/ios`) - flutter_webrtc (from `.symlinks/plugins/flutter_webrtc/ios`)
- gal (from `.symlinks/plugins/gal/darwin`) - gal (from `.symlinks/plugins/gal/darwin`)
- geolocator_apple (from `.symlinks/plugins/geolocator_apple/ios`)
- home_widget (from `.symlinks/plugins/home_widget/ios`) - home_widget (from `.symlinks/plugins/home_widget/ios`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- in_app_review (from `.symlinks/plugins/in_app_review/ios`) - in_app_review (from `.symlinks/plugins/in_app_review/ios`)
@@ -343,12 +349,16 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/flutter_inappwebview_ios/ios" :path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
flutter_native_splash: flutter_native_splash:
:path: ".symlinks/plugins/flutter_native_splash/ios" :path: ".symlinks/plugins/flutter_native_splash/ios"
flutter_timezone:
:path: ".symlinks/plugins/flutter_timezone/ios"
flutter_udid: flutter_udid:
:path: ".symlinks/plugins/flutter_udid/ios" :path: ".symlinks/plugins/flutter_udid/ios"
flutter_webrtc: flutter_webrtc:
:path: ".symlinks/plugins/flutter_webrtc/ios" :path: ".symlinks/plugins/flutter_webrtc/ios"
gal: gal:
:path: ".symlinks/plugins/gal/darwin" :path: ".symlinks/plugins/gal/darwin"
geolocator_apple:
:path: ".symlinks/plugins/geolocator_apple/ios"
home_widget: home_widget:
:path: ".symlinks/plugins/home_widget/ios" :path: ".symlinks/plugins/home_widget/ios"
image_picker_ios: image_picker_ios:
@@ -404,9 +414,9 @@ SPEC CHECKSUMS:
file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49 file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808 file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
Firebase: d80354ed7f6df5f9aca55e9eb47cc4b634735eaf Firebase: d80354ed7f6df5f9aca55e9eb47cc4b634735eaf
firebase_analytics: 7ec1166af61987fa968766eb11587c562a5650ee firebase_analytics: e3b6782e70e32b7fa18f7cd233e3201975dd86aa
firebase_core: 6e223dfa350b2edceb729cea505eaaef59330682 firebase_core: ac395f994af4e28f6a38b59e05a88ca57abeb874
firebase_messaging: 07fde77ae28c08616a1d4d870450efc2b38cf40d firebase_messaging: 7e223f4ee7ca053bf4ce43748e84a6d774ec9728
FirebaseAnalytics: 4fd42def128146e24e480e89f310e3d8534ea42b FirebaseAnalytics: 4fd42def128146e24e480e89f310e3d8534ea42b
FirebaseCore: 99fe0c4b44a39f37d99e6404e02009d2db5d718d FirebaseCore: 99fe0c4b44a39f37d99e6404e02009d2db5d718d
FirebaseCoreInternal: df24ce5af28864660ecbd13596fc8dd3a8c34629 FirebaseCoreInternal: df24ce5af28864660ecbd13596fc8dd3a8c34629
@@ -416,9 +426,11 @@ SPEC CHECKSUMS:
flutter_app_update: 65f61da626cb111d1b24674abc4b01728d7723bc flutter_app_update: 65f61da626cb111d1b24674abc4b01728d7723bc
flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4 flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4
flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29 flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29
flutter_timezone: ac3da59ac941ff1c98a2e1f0293420e020120282
flutter_udid: b2417673f287ee62817a1de3d1643f47b9f508ab flutter_udid: b2417673f287ee62817a1de3d1643f47b9f508ab
flutter_webrtc: 90260f83024b1b96d239a575ea4e3708e79344d1 flutter_webrtc: 90260f83024b1b96d239a575ea4e3708e79344d1
gal: 6a522c75909f1244732d4596d11d6a2f86ff37a5 gal: 6a522c75909f1244732d4596d11d6a2f86ff37a5
geolocator_apple: 9bcea1918ff7f0062d98345d238ae12718acfbc1
GoogleAppMeasurement: fc0817122bd4d4189164f85374e06773b9561896 GoogleAppMeasurement: fc0817122bd4d4189164f85374e06773b9561896
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
@@ -445,7 +457,7 @@ SPEC CHECKSUMS:
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983 sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983
sqlite3_flutter_libs: 069c435986dd4b63461aecd68f4b30be4a9e9daa sqlite3_flutter_libs: cc304edcb8e1d8c595d1b08c7aeb46a47691d9db
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
video_compress: fce97e4fb1dfd88175aa07d2ffc8a2f297f87fbe video_compress: fce97e4fb1dfd88175aa07d2ffc8a2f297f87fbe

View File

@@ -1,6 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:developer';
import 'dart:math' as math; import 'dart:math' as math;
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
@@ -8,6 +7,7 @@ import 'package:drift/drift.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:surface/database/database.dart'; import 'package:surface/database/database.dart';
import 'package:surface/logger.dart';
import 'package:surface/providers/database.dart'; import 'package:surface/providers/database.dart';
import 'package:surface/providers/sn_attachment.dart'; import 'package:surface/providers/sn_attachment.dart';
import 'package:surface/providers/sn_network.dart'; import 'package:surface/providers/sn_network.dart';
@@ -38,6 +38,7 @@ class ChatMessageController extends ChangeNotifier {
bool isPending = true; bool isPending = true;
bool isLoading = false; bool isLoading = false;
bool isAggressiveLoading = false;
int? messageTotal; int? messageTotal;
@@ -183,6 +184,7 @@ class ChatMessageController extends ChangeNotifier {
} else { } else {
messages.insert(0, message); messages.insert(0, message);
} }
notifyListeners();
await _applyMessage(message); await _applyMessage(message);
notifyListeners(); notifyListeners();
@@ -194,9 +196,11 @@ class ChatMessageController extends ChangeNotifier {
channelId: channel!.id, channelId: channel!.id,
createdAt: Value(message.createdAt), createdAt: Value(message.createdAt),
), ),
onConflict: DoUpdate((_) => SnLocalChatMessageCompanion.custom( onConflict: DoUpdate(
content: Constant(jsonEncode(message.toJson())), (_) => SnLocalChatMessageCompanion.custom(
)), content: Constant(jsonEncode(message.toJson())),
),
),
); );
} else { } else {
incomeStrandedQueue.add(message); incomeStrandedQueue.add(message);
@@ -212,21 +216,21 @@ class ChatMessageController extends ChangeNotifier {
final idx = final idx =
messages.indexWhere((x) => x.id == message.relatedEventId); messages.indexWhere((x) => x.id == message.relatedEventId);
if (idx != -1) { if (idx != -1) {
final newBody = message.body; final newBody = Map<String, dynamic>.from(message.body);
newBody.remove('related_event'); newBody.remove('related_event');
messages[idx] = messages[idx].copyWith( messages[idx] = messages[idx].copyWith(
body: newBody, body: newBody,
updatedAt: message.updatedAt, updatedAt: message.updatedAt,
); );
if (message.relatedEventId != null) { }
await (_dt.db.snLocalChatMessage.update() if (message.relatedEventId != null) {
..where((e) => e.id.equals(message.relatedEventId!))) await (_dt.db.snLocalChatMessage.update()
.write( ..where((e) => e.id.equals(message.relatedEventId!)))
SnLocalChatMessageCompanion.custom( .write(
content: Constant(jsonEncode(messages[idx].toJson())), SnLocalChatMessageCompanion.custom(
), content: Constant(jsonEncode(messages[idx].toJson())),
); ),
} );
} }
} }
case 'messages.delete': case 'messages.delete':
@@ -318,10 +322,11 @@ class ChatMessageController extends ChangeNotifier {
/// Check the local storage is up to date with the server. /// Check the local storage is up to date with the server.
/// If the local storage is not up to date, it will be updated. /// If the local storage is not up to date, it will be updated.
Future<void> checkUpdate() async { Future<void> checkUpdate() async {
isLoading = true; isAggressiveLoading = true;
notifyListeners(); notifyListeners();
final mostRecentMessage = await (_dt.db.snLocalChatMessage.select() final mostRecentMessage = await (_dt.db.snLocalChatMessage.select()
..where((e) => e.channelId.equals(channel!.id))
..limit(1) ..limit(1)
..orderBy([ ..orderBy([
(e) => (e) =>
@@ -348,13 +353,19 @@ class ChatMessageController extends ChangeNotifier {
final countToFetch = math.min(resp.data['count'] as int, 100); final countToFetch = math.min(resp.data['count'] as int, 100);
for (int idx = 0; idx < countToFetch; idx += kSingleBatchLoadLimit) { for (int idx = 0; idx < countToFetch; idx += kSingleBatchLoadLimit) {
await getMessages(kSingleBatchLoadLimit, idx, forceRemote: true); final out = await getMessages(
kSingleBatchLoadLimit,
idx,
forceRemote: true,
);
messages.insertAll(0, out);
notifyListeners();
} }
} catch (err) { } catch (err) {
rethrow; rethrow;
} finally { } finally {
await loadMessages(); await loadMessages();
isLoading = false; isAggressiveLoading = false;
isCheckedUpdate = true; isCheckedUpdate = true;
_saveMessageToLocal(incomeStrandedQueue).then((_) { _saveMessageToLocal(incomeStrandedQueue).then((_) {
@@ -529,7 +540,7 @@ class ChatMessageController extends ChangeNotifier {
}, },
).toJson(), ).toJson(),
)); ));
log('[Messaging] Send read event request: $_readEventAnchor'); logging.debug('[Messaging] Send read event request: $_readEventAnchor');
} }
@override @override

10
lib/logger.dart Normal file
View File

@@ -0,0 +1,10 @@
import 'package:talker/talker.dart';
final logging = Talker(
settings: TalkerSettings(
enabled: true,
useHistory: true,
maxHistoryItems: 1000,
useConsoleLogs: true,
),
);

View File

@@ -20,6 +20,7 @@ import 'package:relative_time/relative_time.dart';
import 'package:responsive_framework/responsive_framework.dart'; import 'package:responsive_framework/responsive_framework.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:surface/firebase_options.dart'; import 'package:surface/firebase_options.dart';
import 'package:surface/logger.dart';
import 'package:surface/providers/channel.dart'; import 'package:surface/providers/channel.dart';
import 'package:surface/providers/chat_call.dart'; import 'package:surface/providers/chat_call.dart';
import 'package:surface/providers/config.dart'; import 'package:surface/providers/config.dart';
@@ -107,8 +108,7 @@ void main() async {
} }
if (!kIsWeb && Platform.isAndroid) { if (!kIsWeb && Platform.isAndroid) {
final ImagePickerPlatform imagePickerImplementation = final ImagePickerPlatform imagePickerImplementation = ImagePickerPlatform.instance;
ImagePickerPlatform.instance;
if (imagePickerImplementation is ImagePickerAndroid) { if (imagePickerImplementation is ImagePickerAndroid) {
imagePickerImplementation.useAndroidPhotoPicker = true; imagePickerImplementation.useAndroidPhotoPicker = true;
} }
@@ -227,15 +227,14 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
if (prefs.containsKey('first_boot_time')) { if (prefs.containsKey('first_boot_time')) {
final rawTime = prefs.getString('first_boot_time'); final rawTime = prefs.getString('first_boot_time');
final time = DateTime.tryParse(rawTime ?? ''); final time = DateTime.tryParse(rawTime ?? '');
if (time != null && if (time != null && time.isBefore(DateTime.now().subtract(const Duration(days: 3)))) {
time.isBefore(DateTime.now().subtract(const Duration(days: 3)))) {
final inAppReview = InAppReview.instance; final inAppReview = InAppReview.instance;
if (prefs.getBool('rating_requested') == true) return; if (prefs.getBool('rating_requested') == true) return;
if (await inAppReview.isAvailable()) { if (await inAppReview.isAvailable()) {
await inAppReview.requestReview(); await inAppReview.requestReview();
prefs.setBool('rating_requested', true); prefs.setBool('rating_requested', true);
} else { } else {
log('Unable request app review, unavailable'); logging.error('Unable request app review, unavailable');
} }
} }
} else { } else {
@@ -259,21 +258,16 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
final remoteVersionString = resp.data?['tag_name'] ?? '0.0.0+0'; final remoteVersionString = resp.data?['tag_name'] ?? '0.0.0+0';
final remoteVersion = Version.parse(remoteVersionString.split('+').first); final remoteVersion = Version.parse(remoteVersionString.split('+').first);
final localVersion = Version.parse(localVersionString.split('+').first); final localVersion = Version.parse(localVersionString.split('+').first);
final remoteBuildNumber = final remoteBuildNumber = int.tryParse(remoteVersionString.split('+').last) ?? 0;
int.tryParse(remoteVersionString.split('+').last) ?? 0; final localBuildNumber = int.tryParse(localVersionString.split('+').last) ?? 0;
final localBuildNumber = logging.info("[Update] Local: $localVersionString, Remote: $remoteVersionString");
int.tryParse(localVersionString.split('+').last) ?? 0; if ((remoteVersion > localVersion || remoteBuildNumber > localBuildNumber) && mounted) {
log("[Update] Local: $localVersionString, Remote: $remoteVersionString");
if ((remoteVersion > localVersion ||
remoteBuildNumber > localBuildNumber) &&
mounted) {
final config = context.read<ConfigProvider>(); final config = context.read<ConfigProvider>();
config.setUpdate( config.setUpdate(remoteVersionString, resp.data?['body'] ?? 'No changelog');
remoteVersionString, resp.data?['body'] ?? 'No changelog'); logging.info("[Update] Update available: $remoteVersionString");
log("[Update] Update available: $remoteVersionString");
} }
} catch (e) { } catch (e) {
log('[Error] Unable to check update: $e'); logging.error('[Error] Unable to check update...', e);
if (mounted) context.showErrorDialog('Unable to check update: $e'); if (mounted) context.showErrorDialog('Unable to check update: $e');
} }
} }
@@ -306,7 +300,7 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
if (!mounted) return; if (!mounted) return;
final sticker = context.read<SnStickerProvider>(); final sticker = context.read<SnStickerProvider>();
await sticker.listSticker(); await sticker.listSticker();
log('[Bootstrap] Everything initialized!'); logging.info('[Bootstrap] Everything initialized!');
} catch (err) { } catch (err) {
if (!mounted) return; if (!mounted) return;
await context.showErrorDialog(err); await context.showErrorDialog(err);
@@ -333,43 +327,54 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
} }
} }
final Menu _appTrayMenu = Menu(
items: [
MenuItem(
key: 'version_label',
label: 'Solian',
disabled: true,
),
MenuItem.separator(),
MenuItem.checkbox(
checked: false,
key: 'mute_notification',
label: 'trayMenuMuteNotification'.tr(),
),
MenuItem.separator(),
MenuItem(
key: 'window_show',
label: 'trayMenuShow'.tr(),
),
MenuItem(
key: 'exit',
label: 'trayMenuExit'.tr(),
),
],
);
Future<void> _trayInitialization() async { Future<void> _trayInitialization() async {
if (kIsWeb || Platform.isAndroid || Platform.isIOS) return; if (kIsWeb || Platform.isAndroid || Platform.isIOS) return;
final icon = Platform.isWindows final icon = Platform.isWindows ? 'assets/icon/tray-icon.ico' : 'assets/icon/tray-icon.png';
? 'assets/icon/tray-icon.ico'
: 'assets/icon/tray-icon.png';
final appVersion = await PackageInfo.fromPlatform(); final appVersion = await PackageInfo.fromPlatform();
trayManager.addListener(this); trayManager.addListener(this);
await trayManager.setIcon(icon); await trayManager.setIcon(icon);
Menu menu = Menu( _appTrayMenu.items![0] = MenuItem(
items: [ key: 'version_label',
MenuItem( label: 'Solian ${appVersion.version}+${appVersion.buildNumber}',
key: 'version_label', disabled: true,
label: 'Solian ${appVersion.version}+${appVersion.buildNumber}',
disabled: true,
),
MenuItem.separator(),
MenuItem(
key: 'window_show',
label: 'trayMenuShow'.tr(),
),
MenuItem(
key: 'exit',
label: 'trayMenuExit'.tr(),
),
],
); );
await trayManager.setContextMenu(menu);
await trayManager.setContextMenu(_appTrayMenu);
} }
Future<void> _notifyInitialization() async { Future<void> _notifyInitialization() async {
if (kIsWeb || Platform.isAndroid || Platform.isIOS) return; if (kIsWeb || Platform.isAndroid || Platform.isIOS) return;
await localNotifier.setup( await localNotifier.setup(
appName: 'solian', appName: 'Solian',
shortcutPolicy: ShortcutPolicy.requireCreate, shortcutPolicy: ShortcutPolicy.requireCreate,
); );
} }
@@ -424,12 +429,23 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
@override @override
void onTrayMenuItemClick(MenuItem menuItem) { void onTrayMenuItemClick(MenuItem menuItem) {
switch (menuItem.key) { switch (menuItem.key) {
case 'mute_notification':
final nty = context.read<NotificationProvider>();
nty.isMuted = !nty.isMuted;
_appTrayMenu.items![2].checked = nty.isMuted;
trayManager.setContextMenu(_appTrayMenu);
break;
case 'window_show': case 'window_show':
appWindow.show(); // To prevent the window from being hide after just show on macOS
Timer(const Duration(milliseconds: 100), () => appWindow.show());
break; break;
case 'exit': case 'exit':
_appLifecycleListener?.dispose(); _appLifecycleListener?.dispose();
SystemChannels.platform.invokeMethod('SystemNavigator.pop'); if (Platform.isWindows) {
appWindow.close();
} else {
SystemChannels.platform.invokeMethod('SystemNavigator.pop');
}
break; break;
} }
} }

View File

@@ -18,6 +18,7 @@ const kAppNotifyWithHaptic = 'app_notify_with_haptic';
const kAppExpandPostLink = 'app_expand_post_link'; const kAppExpandPostLink = 'app_expand_post_link';
const kAppExpandChatLink = 'app_expand_chat_link'; const kAppExpandChatLink = 'app_expand_chat_link';
const kAppRealmCompactView = 'app_realm_compact_view'; const kAppRealmCompactView = 'app_realm_compact_view';
const kAppCustomFonts = 'app_custom_fonts';
const Map<String, FilterQuality> kImageQualityLevel = { const Map<String, FilterQuality> kImageQualityLevel = {
'settingsImageQualityLowest': FilterQuality.none, 'settingsImageQualityLowest': FilterQuality.none,

View File

@@ -1,8 +1,8 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:developer';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:surface/logger.dart';
import 'package:surface/providers/sn_network.dart'; import 'package:surface/providers/sn_network.dart';
import 'package:surface/types/link.dart'; import 'package:surface/types/link.dart';
@@ -20,7 +20,7 @@ class SnLinkPreviewProvider {
final target = b64.encode(url); final target = b64.encode(url);
if (_cache.containsKey(target)) return _cache[target]; if (_cache.containsKey(target)) return _cache[target];
log('[LinkPreview] Fetching $url ($target)'); logging.debug('[LinkPreview] Fetching $url ($target)');
try { try {
final resp = await _sn.client.get('/cgi/re/link/$target'); final resp = await _sn.client.get('/cgi/re/link/$target');
@@ -28,7 +28,7 @@ class SnLinkPreviewProvider {
_cache[url] = meta; _cache[url] = meta;
return meta; return meta;
} catch (err) { } catch (err) {
log('[LinkPreview] Failed to fetch $url ($target)...'); logging.warning('[LinkPreview] Failed to fetch $url ($target)...', err);
return null; return null;
} }
} }

View File

@@ -1,4 +1,3 @@
import 'dart:developer';
import 'dart:io'; import 'dart:io';
import 'package:bitsdojo_window/bitsdojo_window.dart'; import 'package:bitsdojo_window/bitsdojo_window.dart';
@@ -9,6 +8,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_udid/flutter_udid.dart'; import 'package:flutter_udid/flutter_udid.dart';
import 'package:local_notifier/local_notifier.dart'; import 'package:local_notifier/local_notifier.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:surface/logger.dart';
import 'package:surface/providers/config.dart'; import 'package:surface/providers/config.dart';
import 'package:surface/providers/sn_network.dart'; import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/userinfo.dart'; import 'package:surface/providers/userinfo.dart';
@@ -48,11 +48,13 @@ class NotificationProvider extends ChangeNotifier {
var deviceUuid = await FlutterUdid.consistentUdid; var deviceUuid = await FlutterUdid.consistentUdid;
if (deviceUuid.isEmpty) { if (deviceUuid.isEmpty) {
log("Unable to active push notifications, couldn't get device uuid"); logging.warning(
'[Push Notification] Unable to active push notifications, couldn\'t get device uuid');
return; return;
} else { } else {
log('Device UUID is $deviceUuid'); logging.info('[Push Notification] Device UUID is $deviceUuid');
log('Registering device push notifications...'); logging
.info('[Push Notification] Registering device push notifications...');
} }
if (Platform.isIOS || Platform.isMacOS) { if (Platform.isIOS || Platform.isMacOS) {
@@ -62,7 +64,7 @@ class NotificationProvider extends ChangeNotifier {
provider = 'fcm'; provider = 'fcm';
token = await FirebaseMessaging.instance.getToken(); token = await FirebaseMessaging.instance.getToken();
} }
log('Device Push Token is $token'); logging.info('[Push Notification] Device Push Token is $token');
await _sn.client.post( await _sn.client.post(
'/cgi/id/notifications/subscription', '/cgi/id/notifications/subscription',
@@ -78,10 +80,25 @@ class NotificationProvider extends ChangeNotifier {
int showingTrayCount = 0; int showingTrayCount = 0;
List<SnNotification> notifications = List.empty(growable: true); List<SnNotification> notifications = List.empty(growable: true);
int? skippableNotifyChannel;
bool isMuted = false;
void listen() { void listen() {
_ws.pk.stream.listen((event) { _ws.pk.stream.listen((event) {
if (event.method == 'notifications.new') { if (event.method == 'notifications.new') {
final notification = SnNotification.fromJson(event.payload!); final notification = SnNotification.fromJson(event.payload!);
final doHaptic = _cfg.prefs.getBool(kAppNotifyWithHaptic) ?? true;
if (doHaptic) HapticFeedback.mediumImpact();
if (notification.topic == 'messaging.message' &&
skippableNotifyChannel != null) {
if (notification.metadata['channel_id'] != null &&
notification.metadata['channel_id'] == skippableNotifyChannel) {
return;
}
}
if (showingCount < 0) showingCount = 0; if (showingCount < 0) showingCount = 0;
showingCount++; showingCount++;
showingTrayCount++; showingTrayCount++;
@@ -92,10 +109,8 @@ class NotificationProvider extends ChangeNotifier {
}); });
notifyListeners(); notifyListeners();
updateTray(); updateTray();
final doHaptic = _cfg.prefs.getBool(kAppNotifyWithHaptic) ?? true;
if (doHaptic) HapticFeedback.mediumImpact();
if (!kIsWeb) { if (!kIsWeb && !isMuted) {
if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) { if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
LocalNotification notify = LocalNotification( LocalNotification notify = LocalNotification(
title: notification.title, title: notification.title,

View File

@@ -41,7 +41,8 @@ class SnAttachmentProvider {
return out; return out;
} }
Future<List<SnAttachment?>> getMultiple(List<String> rids, {noCache = false}) async { Future<List<SnAttachment?>> getMultiple(List<String> rids,
{noCache = false}) async {
final result = List<SnAttachment?>.filled(rids.length, null); final result = List<SnAttachment?>.filled(rids.length, null);
final Map<String, int> randomMapping = {}; final Map<String, int> randomMapping = {};
for (int i = 0; i < rids.length; i++) { for (int i = 0; i < rids.length; i++) {
@@ -62,8 +63,10 @@ class SnAttachmentProvider {
'id': pendingFetch.join(','), 'id': pendingFetch.join(','),
}, },
); );
final List<SnAttachment?> out = final List<SnAttachment?> out = resp.data['data']
resp.data['data'].map((e) => e['id'] == 0 ? null : SnAttachment.fromJson(e)).cast<SnAttachment?>().toList(); .map((e) => e['id'] == 0 ? null : SnAttachment.fromJson(e))
.cast<SnAttachment?>()
.toList();
for (final item in out) { for (final item in out) {
if (item == null) continue; if (item == null) continue;
@@ -77,7 +80,13 @@ class SnAttachmentProvider {
return result; return result;
} }
static Map<String, String> mimetypeOverrides = {'mov': 'video/quicktime', 'mp4': 'video/mp4'}; static Map<String, String> mimetypeOverrides = {
'mov': 'video/quicktime',
'mp4': 'video/mp4',
'm4a': 'audio/mp4',
'apng': 'image/apng',
'webp': 'image/webp',
};
Future<SnAttachment> directUploadOne( Future<SnAttachment> directUploadOne(
Uint8List data, Uint8List data,
@@ -89,8 +98,11 @@ class SnAttachmentProvider {
bool analyzeNow = false, bool analyzeNow = false,
}) async { }) async {
final filePayload = MultipartFile.fromBytes(data, filename: filename); final filePayload = MultipartFile.fromBytes(data, filename: filename);
final fileAlt = filename.contains('.') ? filename.substring(0, filename.lastIndexOf('.')) : filename; final fileAlt = filename.contains('.')
final fileExt = filename.substring(filename.lastIndexOf('.') + 1).toLowerCase(); ? filename.substring(0, filename.lastIndexOf('.'))
: filename;
final fileExt =
filename.substring(filename.lastIndexOf('.') + 1).toLowerCase();
String? mimetypeOverride; String? mimetypeOverride;
if (mimetype != null) { if (mimetype != null) {
@@ -127,8 +139,11 @@ class SnAttachmentProvider {
Map<String, dynamic>? metadata, { Map<String, dynamic>? metadata, {
String? mimetype, String? mimetype,
}) async { }) async {
final fileAlt = filename.contains('.') ? filename.substring(0, filename.lastIndexOf('.')) : filename; final fileAlt = filename.contains('.')
final fileExt = filename.substring(filename.lastIndexOf('.') + 1).toLowerCase(); ? filename.substring(0, filename.lastIndexOf('.'))
: filename;
final fileExt =
filename.substring(filename.lastIndexOf('.') + 1).toLowerCase();
String? mimetypeOverride; String? mimetypeOverride;
if (mimetype == null && mimetypeOverrides.keys.contains(fileExt)) { if (mimetype == null && mimetypeOverrides.keys.contains(fileExt)) {
@@ -146,7 +161,10 @@ class SnAttachmentProvider {
if (mimetypeOverride != null) 'mimetype': mimetypeOverride, if (mimetypeOverride != null) 'mimetype': mimetypeOverride,
}); });
return (SnAttachmentFragment.fromJson(resp.data['meta']), resp.data['chunk_size'] as int); return (
SnAttachmentFragment.fromJson(resp.data['meta']),
resp.data['chunk_size'] as int
);
} }
Future<dynamic> _chunkedUploadOnePart( Future<dynamic> _chunkedUploadOnePart(
@@ -197,7 +215,10 @@ class SnAttachmentProvider {
(entry.value + 1) * chunkSize, (entry.value + 1) * chunkSize,
await file.length(), await file.length(),
); );
final data = Uint8List.fromList(await file.openRead(beginCursor, endCursor).expand((chunk) => chunk).toList()); final data = Uint8List.fromList(await file
.openRead(beginCursor, endCursor)
.expand((chunk) => chunk)
.toList());
final result = await _chunkedUploadOnePart( final result = await _chunkedUploadOnePart(
data, data,

View File

@@ -1,6 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:developer';
import 'dart:io'; import 'dart:io';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
@@ -11,9 +10,12 @@ import 'package:package_info_plus/package_info_plus.dart';
import 'package:device_info_plus/device_info_plus.dart'; import 'package:device_info_plus/device_info_plus.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:surface/logger.dart';
import 'package:surface/providers/config.dart'; import 'package:surface/providers/config.dart';
import 'package:surface/providers/widget.dart'; import 'package:surface/providers/widget.dart';
import 'package:synchronized/synchronized.dart'; import 'package:synchronized/synchronized.dart';
import 'package:talker_dio_logger/talker_dio_logger_interceptor.dart';
import 'package:talker_dio_logger/talker_dio_logger_settings.dart';
const kNetworkServerDirectory = [ const kNetworkServerDirectory = [
('Solar Network', 'https://api.sn.solsynth.dev'), ('Solar Network', 'https://api.sn.solsynth.dev'),
@@ -36,6 +38,19 @@ class SnNetworkProvider {
client = Dio(); client = Dio();
client.interceptors.add(
TalkerDioLogger(
talker: logging,
settings: const TalkerDioLoggerSettings(
printRequestHeaders: false,
printResponseHeaders: false,
printResponseMessage: false,
printResponseData: false,
printRequestData: false,
),
),
);
client.interceptors.add(RetryInterceptor( client.interceptors.add(RetryInterceptor(
dio: client, dio: client,
retries: 3, retries: 3,
@@ -69,7 +84,6 @@ class SnNetworkProvider {
_prefs = _config.prefs; _prefs = _config.prefs;
client.options.baseUrl = _config.serverUrl; client.options.baseUrl = _config.serverUrl;
}); });
} }
static Future<Dio> createOffContextClient() async { static Future<Dio> createOffContextClient() async {
@@ -91,7 +105,8 @@ class SnNetworkProvider {
RequestOptions options, RequestOptions options,
RequestInterceptorHandler handler, RequestInterceptorHandler handler,
) async { ) async {
final atk = await _getFreshAtk(client, prefs.getString(kAtkStoreKey), prefs.getString(kRtkStoreKey), (atk, rtk) { final atk = await _getFreshAtk(client, prefs.getString(kAtkStoreKey),
prefs.getString(kRtkStoreKey), (atk, rtk) {
prefs.setString(kAtkStoreKey, atk); prefs.setString(kAtkStoreKey, atk);
prefs.setString(kRtkStoreKey, rtk); prefs.setString(kRtkStoreKey, rtk);
}); });
@@ -103,7 +118,8 @@ class SnNetworkProvider {
}, },
), ),
); );
client.options.baseUrl = prefs.getString(kNetworkServerStoreKey) ?? kNetworkServerDefault; client.options.baseUrl =
prefs.getString(kNetworkServerStoreKey) ?? kNetworkServerDefault;
return client; return client;
} }
@@ -119,7 +135,8 @@ class SnNetworkProvider {
platformInfo = 'Web; ${deviceInfo.vendor}'; platformInfo = 'Web; ${deviceInfo.vendor}';
} else if (Platform.isAndroid) { } else if (Platform.isAndroid) {
final deviceInfo = await DeviceInfoPlugin().androidInfo; final deviceInfo = await DeviceInfoPlugin().androidInfo;
platformInfo = 'Android; ${deviceInfo.brand} ${deviceInfo.model}; ${deviceInfo.id}'; platformInfo =
'Android; ${deviceInfo.brand} ${deviceInfo.model}; ${deviceInfo.id}';
} else if (Platform.isIOS) { } else if (Platform.isIOS) {
final deviceInfo = await DeviceInfoPlugin().iosInfo; final deviceInfo = await DeviceInfoPlugin().iosInfo;
platformInfo = 'iOS; ${deviceInfo.model}; ${deviceInfo.name}'; platformInfo = 'iOS; ${deviceInfo.model}; ${deviceInfo.name}';
@@ -128,7 +145,8 @@ class SnNetworkProvider {
platformInfo = 'MacOS; ${deviceInfo.model}; ${deviceInfo.hostName}'; platformInfo = 'MacOS; ${deviceInfo.model}; ${deviceInfo.hostName}';
} else if (Platform.isWindows) { } else if (Platform.isWindows) {
final deviceInfo = await DeviceInfoPlugin().windowsInfo; final deviceInfo = await DeviceInfoPlugin().windowsInfo;
platformInfo = 'Windows NT; ${deviceInfo.productName}; ${deviceInfo.computerName}'; platformInfo =
'Windows NT; ${deviceInfo.productName}; ${deviceInfo.computerName}';
} else if (Platform.isLinux) { } else if (Platform.isLinux) {
final deviceInfo = await DeviceInfoPlugin().linuxInfo; final deviceInfo = await DeviceInfoPlugin().linuxInfo;
platformInfo = 'Linux; ${deviceInfo.prettyName}'; platformInfo = 'Linux; ${deviceInfo.prettyName}';
@@ -148,12 +166,15 @@ class SnNetworkProvider {
final tkLock = Lock(); final tkLock = Lock();
Future<String?> getFreshAtk() async { Future<String?> getFreshAtk() async {
return await _getFreshAtk(client, _prefs.getString(kAtkStoreKey), _prefs.getString(kRtkStoreKey), (atk, rtk) { return await _getFreshAtk(
client, _prefs.getString(kAtkStoreKey), _prefs.getString(kRtkStoreKey),
(atk, rtk) {
setTokenPair(atk, rtk); setTokenPair(atk, rtk);
}); });
} }
static Future<String?> _getFreshAtk(Dio client, String? atk, String? rtk, Function(String atk, String rtk)? onRefresh) async { static Future<String?> _getFreshAtk(Dio client, String? atk, String? rtk,
Function(String atk, String rtk)? onRefresh) async {
if (_refreshCompleter != null) { if (_refreshCompleter != null) {
return await _refreshCompleter!.future; return await _refreshCompleter!.future;
} else { } else {
@@ -185,7 +206,8 @@ class SnNetworkProvider {
final payload = b64.decode(rawPayload); final payload = b64.decode(rawPayload);
final exp = jsonDecode(payload)['exp']; final exp = jsonDecode(payload)['exp'];
if (exp <= DateTime.now().millisecondsSinceEpoch ~/ 1000) { if (exp <= DateTime.now().millisecondsSinceEpoch ~/ 1000) {
log('Access token need refresh, doing it at ${DateTime.now()}'); logging.debug(
'[Auth] Access token need refresh, doing it at ${DateTime.now()}');
final result = await _refreshToken(client.options.baseUrl, rtk); final result = await _refreshToken(client.options.baseUrl, rtk);
if (result == null) { if (result == null) {
atk = null; atk = null;
@@ -199,12 +221,12 @@ class SnNetworkProvider {
_refreshCompleter!.complete(atk); _refreshCompleter!.complete(atk);
return atk; return atk;
} else { } else {
log('Access token refresh failed...'); logging.error('[Auth] Access token refresh failed...');
_refreshCompleter!.complete(null); _refreshCompleter!.complete(null);
} }
} }
} catch (err) { } catch (err) {
log('Failed to authenticate user: $err'); logging.error('[Auth] Failed to authenticate user...', err);
_refreshCompleter!.completeError(err); _refreshCompleter!.completeError(err);
} finally { } finally {
_refreshCompleter = null; _refreshCompleter = null;
@@ -237,7 +259,8 @@ class SnNetworkProvider {
return result.$1; return result.$1;
} }
static Future<(String, String)?> _refreshToken(String baseUrl, String? rtk) async { static Future<(String, String)?> _refreshToken(
String baseUrl, String? rtk) async {
if (rtk == null) return null; if (rtk == null) return null;
final dio = Dio(); final dio = Dio();

View File

@@ -1,7 +1,6 @@
import 'dart:developer';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:surface/logger.dart';
import 'package:surface/providers/sn_network.dart'; import 'package:surface/providers/sn_network.dart';
import 'package:surface/types/attachment.dart'; import 'package:surface/types/attachment.dart';
@@ -51,7 +50,7 @@ class SnStickerProvider {
return sticker; return sticker;
} catch (err) { } catch (err) {
_cache[alias] = null; _cache[alias] = null;
log('[Sticker] Failed to lookup sticker $alias: $err'); logging.warning('[Sticker] Failed to lookup sticker $alias', err);
} }
return null; return null;
@@ -66,7 +65,7 @@ class SnStickerProvider {
_cacheSticker(sticker); _cacheSticker(sticker);
} }
} catch (err) { } catch (err) {
log('[Sticker] Failed to list stickers: $err'); logging.error('[Sticker] Failed to list stickers...', err);
rethrow; rethrow;
} }
} }

View File

@@ -13,8 +13,16 @@ class ThemeProvider extends ChangeNotifier {
}); });
} }
void reloadTheme({Color? seedColorOverride, bool? useMaterial3}) { void reloadTheme({
createAppThemeSet(seedColorOverride: seedColorOverride, useMaterial3: useMaterial3).then((value) { Color? seedColorOverride,
bool? useMaterial3,
String? customFonts,
}) {
createAppThemeSet(
seedColorOverride: seedColorOverride,
useMaterial3: useMaterial3,
customFonts: customFonts,
).then((value) {
theme = value; theme = value;
notifyListeners(); notifyListeners();
}); });

View File

@@ -27,8 +27,11 @@ class UserDirectoryProvider {
plannedQuery.add(item); plannedQuery.add(item);
} }
} }
final resp = await _sn.client.get('/cgi/id/users', queryParameters: {'id': plannedQuery.join(',')}); if (plannedQuery.isEmpty) return out;
final respDecoded = resp.data.map((e) => SnAccount.fromJson(e)).cast<SnAccount>().toList(); 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; var sideIdx = 0;
for (var idx = 0; idx < out.length; idx++) { for (var idx = 0; idx < out.length; idx++) {
if (out[idx] != null) continue; if (out[idx] != null) continue;

View File

@@ -1,8 +1,7 @@
import 'dart:developer';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:surface/logger.dart';
import 'package:surface/providers/config.dart'; import 'package:surface/providers/config.dart';
import 'package:surface/providers/sn_network.dart'; import 'package:surface/providers/sn_network.dart';
import 'package:surface/types/account.dart'; import 'package:surface/types/account.dart';
@@ -30,8 +29,8 @@ class UserProvider extends ChangeNotifier {
notifyListeners(); notifyListeners();
refreshUser().then((value) async { refreshUser().then((value) async {
if (value != null) { if (value != null) {
log('Logged in as @${value.name}'); logging.info('[Auth] Logged in as @${value.name}');
log('Atk: ${await atk}'); logging.debug('[Auth] Access token: ${await atk}');
} }
}); });
} }

View File

@@ -1,12 +1,15 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_udid/flutter_udid.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:surface/logger.dart';
import 'package:surface/providers/sn_network.dart'; import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/userinfo.dart'; import 'package:surface/providers/userinfo.dart';
import 'package:surface/types/websocket.dart'; import 'package:surface/types/websocket.dart';
import 'package:web_socket_channel/io.dart';
import 'package:web_socket_channel/web_socket_channel.dart'; import 'package:web_socket_channel/web_socket_channel.dart';
class WebSocketProvider extends ChangeNotifier { class WebSocketProvider extends ChangeNotifier {
@@ -30,7 +33,7 @@ class WebSocketProvider extends ChangeNotifier {
if (isConnected) return; if (isConnected) return;
if (!_ua.isAuthorized) return; if (!_ua.isAuthorized) return;
log('[WebSocket] Connecting to the server...'); logging.debug('[WebSocket] Connecting to the server...');
await connect(); await connect();
} }
@@ -39,7 +42,7 @@ class WebSocketProvider extends ChangeNotifier {
Future<void> connect({noRetry = false}) async { Future<void> connect({noRetry = false}) async {
if (_connectCompleter != null) { if (_connectCompleter != null) {
await _connectCompleter!.future; await _connectCompleter!.future;
_connectCompleter = null; return;
} }
if (!_ua.isAuthorized) return; if (!_ua.isAuthorized) return;
@@ -50,29 +53,42 @@ class WebSocketProvider extends ChangeNotifier {
try { try {
_connectCompleter = Completer<void>(); _connectCompleter = Completer<void>();
final clientId = await FlutterUdid.consistentUdid;
final atk = await _sn.getFreshAtk(); final atk = await _sn.getFreshAtk();
final uri = Uri.parse( final uri = Uri.parse(
'${_sn.client.options.baseUrl.replaceFirst('http', 'ws')}/ws?tk=$atk', kIsWeb
? '${_sn.client.options.baseUrl.replaceFirst('http', 'ws')}/ws?tk=$atk'
: '${_sn.client.options.baseUrl.replaceFirst('http', 'ws')}/ws?clientId=${clientId}tk=$atk',
); );
isBusy = true; isBusy = true;
notifyListeners(); notifyListeners();
conn = WebSocketChannel.connect(uri); conn = kIsWeb
? WebSocketChannel.connect(uri)
: IOWebSocketChannel.connect(
uri,
headers: {'Authorization': 'Bearer $atk'},
);
await conn!.ready; await conn!.ready;
_wsStream = conn!.stream.asBroadcastStream(); _wsStream = conn!.stream.asBroadcastStream();
listen(); listen();
log('[WebSocket] Connected to server!'); logging.info('[WebSocket] Connected to server!');
isConnected = true; isConnected = true;
} catch (err) { } catch (err) {
if (err is WebSocketChannelException) { if (err is WebSocketChannelException) {
log('Failed to connect to websocket: ${(err.inner as dynamic).message}'); logging.error(
'[WebSocket] Failed to connect to websocket...',
err.inner,
);
} else { } else {
log('Failed to connect to websocket: $err'); logging.error('[WebSocket] Failed to connect to websocket...', err);
} }
if (!noRetry) { if (!noRetry) {
log('Retry connecting to websocket in 3 seconds...'); logging.warning(
'[WebSocket] Retry connecting to websocket in 3 seconds...',
);
return Future.delayed( return Future.delayed(
const Duration(seconds: 3), const Duration(seconds: 3),
() => connect(noRetry: true), () => connect(noRetry: true),
@@ -100,7 +116,8 @@ class WebSocketProvider extends ChangeNotifier {
_wsStream!.listen( _wsStream!.listen(
(event) { (event) {
final packet = WebSocketPackage.fromJson(jsonDecode(event)); final packet = WebSocketPackage.fromJson(jsonDecode(event));
log('Websocket incoming message: ${packet.method} ${packet.message}'); logging.debug(
'[Websocket] Incoming message: ${packet.method} ${packet.message}');
pk.sink.add(packet); pk.sink.add(packet);
}, },
onDone: () { onDone: () {

View File

@@ -21,6 +21,7 @@ import 'package:surface/screens/chat/room.dart';
import 'package:surface/screens/explore.dart'; import 'package:surface/screens/explore.dart';
import 'package:surface/screens/friend.dart'; import 'package:surface/screens/friend.dart';
import 'package:surface/screens/home.dart'; import 'package:surface/screens/home.dart';
import 'package:surface/screens/logging.dart';
import 'package:surface/screens/news/news_detail.dart'; import 'package:surface/screens/news/news_detail.dart';
import 'package:surface/screens/news/news_list.dart'; import 'package:surface/screens/news/news_list.dart';
import 'package:surface/screens/notification.dart'; import 'package:surface/screens/notification.dart';
@@ -249,6 +250,11 @@ final _appRoutes = [
), ),
], ],
), ),
GoRoute(
path: '/debug/logging',
name: 'debugLogging',
builder: (context, state) => const DebugLoggingScreen(),
),
GoRoute( GoRoute(
path: '/album', path: '/album',
name: 'album', name: 'album',

View File

@@ -45,8 +45,7 @@ class AccountScreen extends StatelessWidget {
? Stack( ? Stack(
fit: StackFit.expand, fit: StackFit.expand,
children: [ children: [
AutoResizeUniversalImage(sn.getAttachmentUrl(ua.user!.banner), AutoResizeUniversalImage(sn.getAttachmentUrl(ua.user!.banner), fit: BoxFit.cover),
fit: BoxFit.cover),
Positioned( Positioned(
top: 0, top: 0,
left: 0, left: 0,
@@ -80,9 +79,7 @@ class AccountScreen extends StatelessWidget {
], ],
), ),
body: SingleChildScrollView( body: SingleChildScrollView(
child: ua.isAuthorized child: ua.isAuthorized ? _AuthorizedAccountScreen() : _UnauthorizedAccountScreen(),
? _AuthorizedAccountScreen()
: _UnauthorizedAccountScreen(),
), ),
); );
} }
@@ -118,15 +115,19 @@ class _AuthorizedAccountScreen extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.baseline, crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic, textBaseline: TextBaseline.alphabetic,
children: [ children: [
Text(ua.user!.nick) Text(ua.user!.nick).textStyle(Theme.of(context).textTheme.titleLarge!),
.textStyle(Theme.of(context).textTheme.titleLarge!),
const Gap(4), const Gap(4),
Text('@${ua.user!.name}') Text('@${ua.user!.name}').textStyle(Theme.of(context).textTheme.bodySmall!),
.textStyle(Theme.of(context).textTheme.bodySmall!),
], ],
), ),
Text(ua.user!.description) Text(
.textStyle(Theme.of(context).textTheme.bodyMedium!), (ua.user!.profile?.description.isNotEmpty ?? false)
? ua.user!.profile!.description
: 'userNoDescription'.tr(),
style: (ua.user!.profile?.description.isEmpty ?? true)
? TextStyle(fontStyle: FontStyle.italic)
: null,
).textStyle(Theme.of(context).textTheme.bodyMedium!),
], ],
), ),
); );
@@ -225,9 +226,7 @@ class _UnauthorizedAccountScreen extends StatelessWidget {
child: Icon(Symbols.waving_hand, size: 28), child: Icon(Symbols.waving_hand, size: 28),
), ),
const Gap(8), const Gap(8),
Text('accountIntroTitle') Text('accountIntroTitle').tr().textStyle(Theme.of(context).textTheme.titleLarge!),
.tr()
.textStyle(Theme.of(context).textTheme.titleLarge!),
Text('accountIntroSubtitle').tr(), Text('accountIntroSubtitle').tr(),
], ],
).padding(all: 20), ).padding(all: 20),

View File

@@ -54,14 +54,20 @@ class AccountSettingsScreen extends StatelessWidget {
child: DropdownButton2<Locale?>( child: DropdownButton2<Locale?>(
isExpanded: true, isExpanded: true,
items: [ items: [
...EasyLocalization.of(context)!.supportedLocales.mapIndexed((idx, ele) { ...EasyLocalization.of(context)!
.supportedLocales
.mapIndexed((idx, ele) {
return DropdownMenuItem<Locale?>( return DropdownMenuItem<Locale?>(
value: Locale.parse(ele.toString()), value: Locale.parse(ele.toString()),
child: Text('${ele.languageCode}-${ele.countryCode}').fontSize(14), child: Text('${ele.languageCode}-${ele.countryCode}')
.fontSize(14),
); );
}), }),
], ],
value: ua.user?.language != null ? Locale.parse(ua.user!.language) : Locale.parse('en-US'), value: ua.user?.language != null
? (Locale.tryParse(ua.user!.language) ??
Locale.parse('en-US'))
: Locale.parse('en-US'),
onChanged: (Locale? value) { onChanged: (Locale? value) {
if (value == null) return; if (value == null) return;
_setAccountLanguage(context, value); _setAccountLanguage(context, value);

View File

@@ -6,6 +6,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_timezone/flutter_timezone.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
@@ -36,11 +37,16 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> {
final _firstNameController = TextEditingController(); final _firstNameController = TextEditingController();
final _lastNameController = TextEditingController(); final _lastNameController = TextEditingController();
final _descriptionController = TextEditingController(); final _descriptionController = TextEditingController();
final _timezoneController = TextEditingController();
final _genderController = TextEditingController();
final _pronounsController = TextEditingController();
final _locationController = TextEditingController();
final _birthdayController = TextEditingController(); final _birthdayController = TextEditingController();
String? _avatar; String? _avatar;
String? _banner; String? _banner;
DateTime? _birthday; DateTime? _birthday;
List<(String, String)>? _links;
bool _isBusy = false; bool _isBusy = false;
@@ -51,15 +57,21 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> {
final prof = ua.user!; final prof = ua.user!;
_usernameController.text = prof.name; _usernameController.text = prof.name;
_nicknameController.text = prof.nick; _nicknameController.text = prof.nick;
_descriptionController.text = prof.description; _descriptionController.text = prof.profile!.description;
_firstNameController.text = prof.profile!.firstName; _firstNameController.text = prof.profile!.firstName;
_lastNameController.text = prof.profile!.lastName; _lastNameController.text = prof.profile!.lastName;
_timezoneController.text = prof.profile!.timeZone;
_genderController.text = prof.profile!.gender;
_pronounsController.text = prof.profile!.pronouns;
_locationController.text = prof.profile!.location;
_avatar = prof.avatar; _avatar = prof.avatar;
_banner = prof.banner; _banner = prof.banner;
if (prof.profile!.birthday != null) { _links = prof.profile!.links.entries.map((ele) => (ele.key, ele.value)).toList();
_birthday = prof.profile!.birthday?.toLocal();
if(_birthday != null) {
_birthdayController.text = DateFormat(_kDateFormat).format( _birthdayController.text = DateFormat(_kDateFormat).format(
prof.profile!.birthday!.toLocal(), prof.profile!.birthday!.toLocal(),
); );
} }
} }
@@ -166,7 +178,14 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> {
'description': _descriptionController.value.text, 'description': _descriptionController.value.text,
'first_name': _firstNameController.value.text, 'first_name': _firstNameController.value.text,
'last_name': _lastNameController.value.text, 'last_name': _lastNameController.value.text,
'time_zone': _timezoneController.value.text,
'gender': _genderController.value.text,
'pronouns': _pronounsController.value.text,
'location': _locationController.value.text,
'birthday': _birthday?.toUtc().toIso8601String(), 'birthday': _birthday?.toUtc().toIso8601String(),
'links': {
for (final link in _links!.where((ele) => ele.$1.isNotEmpty && ele.$2.isNotEmpty)) link.$1: link.$2
},
}, },
); );
@@ -197,6 +216,10 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> {
_firstNameController.dispose(); _firstNameController.dispose();
_lastNameController.dispose(); _lastNameController.dispose();
_descriptionController.dispose(); _descriptionController.dispose();
_timezoneController.dispose();
_genderController.dispose();
_pronounsController.dispose();
_locationController.dispose();
_birthdayController.dispose(); _birthdayController.dispose();
super.dispose(); super.dispose();
} }
@@ -262,6 +285,7 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> {
).padding(horizontal: padding), ).padding(horizontal: padding),
const Gap(8 + 28), const Gap(8 + 28),
Column( Column(
spacing: 4,
children: [ children: [
TextField( TextField(
readOnly: true, readOnly: true,
@@ -271,16 +295,16 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> {
labelText: 'fieldUsername'.tr(), labelText: 'fieldUsername'.tr(),
helperText: 'fieldUsernameCannotEditHint'.tr(), helperText: 'fieldUsernameCannotEditHint'.tr(),
), ),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
), ),
const Gap(4),
TextField( TextField(
controller: _nicknameController, controller: _nicknameController,
decoration: InputDecoration( decoration: InputDecoration(
border: const UnderlineInputBorder(), border: const UnderlineInputBorder(),
labelText: 'fieldNickname'.tr(), labelText: 'fieldNickname'.tr(),
), ),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
), ),
const Gap(4),
Row( Row(
children: [ children: [
Flexible( Flexible(
@@ -291,6 +315,7 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> {
border: const UnderlineInputBorder(), border: const UnderlineInputBorder(),
labelText: 'fieldFirstName'.tr(), labelText: 'fieldFirstName'.tr(),
), ),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
), ),
), ),
const Gap(8), const Gap(8),
@@ -302,11 +327,38 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> {
border: const UnderlineInputBorder(), border: const UnderlineInputBorder(),
labelText: 'fieldLastName'.tr(), labelText: 'fieldLastName'.tr(),
), ),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
),
),
],
),
Row(
children: [
Flexible(
flex: 1,
child: TextField(
controller: _genderController,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
labelText: 'fieldGender'.tr(),
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
),
),
const Gap(4),
Flexible(
flex: 1,
child: TextField(
controller: _pronounsController,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
labelText: 'fieldPronouns'.tr(),
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
), ),
), ),
], ],
), ),
const Gap(4),
TextField( TextField(
controller: _descriptionController, controller: _descriptionController,
keyboardType: TextInputType.multiline, keyboardType: TextInputType.multiline,
@@ -316,8 +368,51 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> {
border: const UnderlineInputBorder(), border: const UnderlineInputBorder(),
labelText: 'fieldDescription'.tr(), labelText: 'fieldDescription'.tr(),
), ),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: TextField(
controller: _timezoneController,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
labelText: 'fieldTimeZone'.tr(),
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
),
),
const Gap(4),
StyledWidget(IconButton(
icon: const Icon(Symbols.calendar_month),
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
onPressed: () async {
_timezoneController.text = await FlutterTimezone.getLocalTimezone();
},
)).padding(top: 6),
const Gap(4),
StyledWidget(IconButton(
icon: const Icon(Symbols.clear),
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
onPressed: () {
_timezoneController.clear();
},
)).padding(top: 6),
],
),
TextField(
controller: _locationController,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
labelText: 'fieldLocation'.tr(),
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
), ),
const Gap(4),
TextField( TextField(
controller: _birthdayController, controller: _birthdayController,
readOnly: true, readOnly: true,
@@ -327,6 +422,75 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> {
), ),
onTap: () => _selectBirthday(), onTap: () => _selectBirthday(),
), ),
if (_links != null)
Card(
margin: const EdgeInsets.only(top: 16, bottom: 4),
child: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
'fieldLinks'.tr(),
style: Theme.of(context).textTheme.titleMedium!.copyWith(fontSize: 17),
),
),
IconButton(
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
icon: const Icon(Symbols.add),
onPressed: () {
setState(() => _links!.add(('', '')));
},
),
],
),
const Gap(8),
for (var idx = 0; idx < _links!.length; idx++)
Row(
children: [
Flexible(
flex: 1,
child: TextFormField(
initialValue: _links![idx].$1,
decoration: InputDecoration(
isDense: true,
border: const OutlineInputBorder(),
labelText: 'fieldLinkName'.tr(),
),
onChanged: (value) {
_links![idx] = (value, _links![idx].$2);
},
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
),
),
const Gap(8),
Flexible(
flex: 1,
child: TextFormField(
initialValue: _links![idx].$2,
decoration: InputDecoration(
isDense: true,
border: const OutlineInputBorder(),
labelText: 'fieldLinkUrl'.tr(),
),
onChanged: (value) {
_links![idx] = (_links![idx].$1, value);
},
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
),
),
],
),
],
),
),
),
], ],
).padding(horizontal: padding + 8), ).padding(horizontal: padding + 8),
const Gap(12), const Gap(12),
@@ -340,6 +504,7 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> {
), ),
], ],
).padding(horizontal: padding), ).padding(horizontal: padding),
Gap(MediaQuery.of(context).padding.bottom),
], ],
), ),
), ),

View File

@@ -43,7 +43,8 @@ class UserScreen extends StatefulWidget {
State<UserScreen> createState() => _UserScreenState(); State<UserScreen> createState() => _UserScreenState();
} }
class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateMixin { class _UserScreenState extends State<UserScreen>
with SingleTickerProviderStateMixin {
late final ScrollController _scrollController = ScrollController(); late final ScrollController _scrollController = ScrollController();
SnAccount? _account; SnAccount? _account;
@@ -64,13 +65,18 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
} }
} }
Future<List<SnCheckInRecord>> _getCheckInRecords() async { List<SnCheckInRecord>? _records;
Future<void> _getCheckInRecords() async {
try { try {
final sn = context.read<SnNetworkProvider>(); final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.get('/cgi/id/users/${widget.name}/check-in?take=14'); final resp =
return List.from( await sn.client.get('/cgi/id/users/${widget.name}/check-in?take=14');
resp.data['data']?.map((x) => SnCheckInRecord.fromJson(x)) ?? [], setState(() {
); _records = List.from(
resp.data['data']?.map((x) => SnCheckInRecord.fromJson(x)) ?? [],
);
});
} catch (err) { } catch (err) {
if (mounted) context.showErrorDialog(err); if (mounted) context.showErrorDialog(err);
rethrow; rethrow;
@@ -98,7 +104,8 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
Future<void> _fetchPublishers() async { Future<void> _fetchPublishers() async {
try { try {
final sn = context.read<SnNetworkProvider>(); final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.get('/cgi/co/publishers?user=${widget.name}'); final resp =
await sn.client.get('/cgi/co/publishers?user=${widget.name}');
_publishers = List<SnPublisher>.from( _publishers = List<SnPublisher>.from(
resp.data?.map((e) => SnPublisher.fromJson(e)) ?? [], resp.data?.map((e) => SnPublisher.fromJson(e)) ?? [],
); );
@@ -144,7 +151,8 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
'related': _account!.name, 'related': _account!.name,
}); });
if (!mounted) return; if (!mounted) return;
context.showSnackbar('userBlocked'.tr(args: ['@${_account?.name ?? 'unknown'}'])); context.showSnackbar(
'userBlocked'.tr(args: ['@${_account?.name ?? 'unknown'}']));
} catch (err) { } catch (err) {
if (!mounted) return; if (!mounted) return;
context.showErrorDialog(err); context.showErrorDialog(err);
@@ -160,9 +168,11 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
try { try {
final rel = context.read<SnRelationshipProvider>(); final rel = context.read<SnRelationshipProvider>();
await rel.updateRelationship(_account!.id, 1, _accountRelationship?.permNodes ?? {}); await rel.updateRelationship(
_account!.id, 1, _accountRelationship?.permNodes ?? {});
if (!mounted) return; if (!mounted) return;
context.showSnackbar('userUnblocked'.tr(args: ['@${_account?.name ?? 'unknown'}'])); context.showSnackbar(
'userUnblocked'.tr(args: ['@${_account?.name ?? 'unknown'}']));
} catch (err) { } catch (err) {
if (!mounted) return; if (!mounted) return;
context.showErrorDialog(err); context.showErrorDialog(err);
@@ -188,12 +198,14 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
double _appBarBlur = 0.0; double _appBarBlur = 0.0;
late final _appBarWidth = MediaQuery.of(context).size.width; late final _appBarWidth = MediaQuery.of(context).size.width;
late final _appBarHeight = (_appBarWidth * kBannerAspectRatio).roundToDouble(); late final _appBarHeight =
(_appBarWidth * kBannerAspectRatio).roundToDouble();
void _updateAppBarBlur() { void _updateAppBarBlur() {
if (_scrollController.offset > _appBarHeight) return; if (_scrollController.offset > _appBarHeight) return;
setState(() { setState(() {
_appBarBlur = (_scrollController.offset / _appBarHeight * 10).clamp(0.0, 10.0); _appBarBlur =
(_scrollController.offset / _appBarHeight * 10).clamp(0.0, 10.0);
}); });
} }
@@ -205,6 +217,7 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
_fetchStatus(); _fetchStatus();
_fetchPublishers(); _fetchPublishers();
_getCheckInRecords();
try { try {
final rel = context.read<SnRelationshipProvider>(); final rel = context.read<SnRelationshipProvider>();
@@ -260,18 +273,20 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
text: TextSpan(children: [ text: TextSpan(children: [
TextSpan( TextSpan(
text: _account!.nick, text: _account!.nick,
style: Theme.of(context).textTheme.titleLarge!.copyWith( style:
color: Colors.white, Theme.of(context).textTheme.titleLarge!.copyWith(
shadows: labelShadows, color: Colors.white,
), shadows: labelShadows,
),
), ),
const TextSpan(text: '\n'), const TextSpan(text: '\n'),
TextSpan( TextSpan(
text: '@${_account!.name}', text: '@${_account!.name}',
style: Theme.of(context).textTheme.bodySmall!.copyWith( style:
color: Colors.white, Theme.of(context).textTheme.bodySmall!.copyWith(
shadows: labelShadows, color: Colors.white,
), shadows: labelShadows,
),
), ),
]), ]),
), ),
@@ -339,7 +354,8 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
PopupMenuButton( PopupMenuButton(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
style: ButtonStyle( style: ButtonStyle(
visualDensity: VisualDensity(horizontal: -4, vertical: -4), visualDensity:
VisualDensity(horizontal: -4, vertical: -4),
), ),
itemBuilder: (context) => [ itemBuilder: (context) => [
PopupMenuItem( PopupMenuItem(
@@ -390,7 +406,7 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
], ],
).padding(right: 8), ).padding(right: 8),
const Gap(12), const Gap(12),
Text(_account!.description).padding(horizontal: 8), Text(_account!.profile!.description).padding(horizontal: 8),
const Gap(4), const Gap(4),
Card( Card(
child: Row( child: Row(
@@ -399,7 +415,9 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
Symbols.circle, Symbols.circle,
fill: 1, fill: 1,
size: 16, size: 16,
color: (_status?.isOnline ?? false) ? Colors.green : Colors.grey, color: (_status?.isOnline ?? false)
? Colors.green
: Colors.grey,
).padding(all: 4), ).padding(all: 4),
const Gap(8), const Gap(8),
Text( Text(
@@ -409,7 +427,9 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
: 'accountStatusOffline'.tr() : 'accountStatusOffline'.tr()
: 'loading'.tr(), : 'loading'.tr(),
), ),
if (_status != null && !_status!.isOnline && _status!.lastSeenAt != null) if (_status != null &&
!_status!.isOnline &&
_status!.lastSeenAt != null)
Text( Text(
'accountStatusLastSeen'.tr(args: [ 'accountStatusLastSeen'.tr(args: [
_status!.lastSeenAt != null _status!.lastSeenAt != null
@@ -429,11 +449,14 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
(ele) => Tooltip( (ele) => Tooltip(
richMessage: TextSpan( richMessage: TextSpan(
children: [ children: [
TextSpan(text: kBadgesMeta[ele.type]?.$1.tr() ?? 'unknown'.tr()), TextSpan(
text: kBadgesMeta[ele.type]?.$1.tr() ??
'unknown'.tr()),
if (ele.metadata['title'] != null) if (ele.metadata['title'] != null)
TextSpan( TextSpan(
text: '\n${ele.metadata['title']}', text: '\n${ele.metadata['title']}',
style: const TextStyle(fontWeight: FontWeight.bold), style: const TextStyle(
fontWeight: FontWeight.bold),
), ),
TextSpan(text: '\n'), TextSpan(text: '\n'),
TextSpan( TextSpan(
@@ -442,7 +465,8 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
], ],
), ),
child: Icon( child: Icon(
kBadgesMeta[ele.type]?.$2 ?? Symbols.question_mark, kBadgesMeta[ele.type]?.$2 ??
Symbols.question_mark,
color: kBadgesMeta[ele.type]?.$3, color: kBadgesMeta[ele.type]?.$3,
fill: 1, fill: 1,
), ),
@@ -458,7 +482,9 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
children: [ children: [
const Icon(Symbols.calendar_add_on), const Icon(Symbols.calendar_add_on),
const Gap(8), const Gap(8),
Text('publisherJoinedAt').tr(args: [DateFormat('y/M/d').format(_account!.createdAt)]), Text('publisherJoinedAt').tr(args: [
DateFormat('y/M/d').format(_account!.createdAt)
]),
], ],
), ),
Row( Row(
@@ -491,17 +517,24 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
children: [ children: [
const Icon(Symbols.star), const Icon(Symbols.star),
const Gap(8), const Gap(8),
Text('Lv${getLevelFromExp(_account?.profile?.experience ?? 0)}'), Text(
'Lv${getLevelFromExp(_account?.profile?.experience ?? 0)}'),
const Gap(8), const Gap(8),
Text(calcLevelUpProgressLevel(_account?.profile?.experience ?? 0)).fontSize(11).opacity(0.5), Text(calcLevelUpProgressLevel(
_account?.profile?.experience ?? 0))
.fontSize(11)
.opacity(0.5),
const Gap(8), const Gap(8),
Container( Container(
width: double.infinity, width: double.infinity,
constraints: const BoxConstraints(maxWidth: 160), constraints: const BoxConstraints(maxWidth: 160),
child: LinearProgressIndicator( child: LinearProgressIndicator(
value: calcLevelUpProgress(_account?.profile?.experience ?? 0), value: calcLevelUpProgress(
_account?.profile?.experience ?? 0),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
backgroundColor: Theme.of(context).colorScheme.surfaceContainer, backgroundColor: Theme.of(context)
.colorScheme
.surfaceContainer,
).alignment(Alignment.centerLeft), ).alignment(Alignment.centerLeft),
), ),
], ],
@@ -514,21 +547,23 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
SliverToBoxAdapter(child: const Divider()), SliverToBoxAdapter(child: const Divider()),
const SliverGap(12), const SliverGap(12),
SliverToBoxAdapter( SliverToBoxAdapter(
child: FutureBuilder<List<SnCheckInRecord>>( child: Builder(
future: _getCheckInRecords(), builder: (context) {
builder: (context, snapshot) { if (_records == null) return const SizedBox.shrink();
if (!snapshot.hasData) return const SizedBox.shrink(); if (_records!.length <= 1) {
if (snapshot.data!.length <= 1) {
return Text( return Text(
'accountCheckInNoRecords', 'accountCheckInNoRecords',
textAlign: TextAlign.center, textAlign: TextAlign.center,
).tr().fontWeight(FontWeight.bold).center().padding(horizontal: 20, vertical: 8); )
.tr()
.fontWeight(FontWeight.bold)
.center()
.padding(horizontal: 20, vertical: 8);
} }
final records = snapshot.data!;
return SizedBox( return SizedBox(
width: double.infinity, width: double.infinity,
height: 240, height: 240,
child: CheckInRecordChart(records: records), child: CheckInRecordChart(records: _records!),
).padding( ).padding(
right: 24, right: 24,
left: 16, left: 16,
@@ -544,7 +579,11 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('accountBadge').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4), Text('accountBadge')
.bold()
.fontSize(17)
.tr()
.padding(horizontal: 20, bottom: 4),
SizedBox( SizedBox(
height: 80, height: 80,
width: double.infinity, width: double.infinity,
@@ -558,7 +597,8 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
child: Card( child: Card(
child: ListTile( child: ListTile(
leading: Icon( leading: Icon(
kBadgesMeta[badge.type]?.$2 ?? Symbols.question_mark, kBadgesMeta[badge.type]?.$2 ??
Symbols.question_mark,
color: kBadgesMeta[badge.type]?.$3, color: kBadgesMeta[badge.type]?.$3,
fill: 1, fill: 1,
), ),
@@ -568,7 +608,8 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
subtitle: badge.metadata['title'] != null subtitle: badge.metadata['title'] != null
? Text(badge.metadata['title']) ? Text(badge.metadata['title'])
: Text( : Text(
DateFormat('y/M/d').format(badge.createdAt), DateFormat('y/M/d')
.format(badge.createdAt),
), ),
), ),
), ),
@@ -664,7 +705,8 @@ class CheckInRecordChart extends StatelessWidget {
), ),
) )
.toList(), .toList(),
getTooltipColor: (_) => Theme.of(context).colorScheme.surfaceContainerHigh, getTooltipColor: (_) =>
Theme.of(context).colorScheme.surfaceContainerHigh,
), ),
), ),
titlesData: FlTitlesData( titlesData: FlTitlesData(

View File

@@ -97,7 +97,7 @@ class _AccountPublisherEditScreenState extends State<AccountPublisherEditScreen>
_banner = ua.user!.banner; _banner = ua.user!.banner;
_nickController.text = ua.user!.nick; _nickController.text = ua.user!.nick;
_nameController.text = ua.user!.name; _nameController.text = ua.user!.name;
_descriptionController.text = ua.user!.description; _descriptionController.text = ua.user!.profile!.description;
setState(() {}); setState(() {});
} }

View File

@@ -109,7 +109,7 @@ class _PublisherNewPersonalState extends State<_PublisherNewPersonal> {
_nameController.text = ua.user!.name; _nameController.text = ua.user!.name;
_nickController.text = ua.user!.nick; _nickController.text = ua.user!.nick;
_descriptionController.text = ua.user!.description; _descriptionController.text = ua.user!.profile!.description;
} }
@override @override

View File

@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_expandable_fab/flutter_expandable_fab.dart'; import 'package:flutter_expandable_fab/flutter_expandable_fab.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.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:responsive_framework/responsive_framework.dart'; import 'package:responsive_framework/responsive_framework.dart';
@@ -41,6 +42,7 @@ class _ChatScreenState extends State<ChatScreen> {
Future<void> _fetchWhatsNew() async { Future<void> _fetchWhatsNew() async {
final sn = context.read<SnNetworkProvider>(); final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.get('/cgi/im/whats-new'); final resp = await sn.client.get('/cgi/im/whats-new');
if (resp.data == null) return;
final List<dynamic> out = resp.data; final List<dynamic> out = resp.data;
setState(() { setState(() {
_unreadCounts = {for (var v in out) v['channel_id']: v['count']}; _unreadCounts = {for (var v in out) v['channel_id']: v['count']};
@@ -72,18 +74,20 @@ class _ChatScreenState extends State<ChatScreen> {
if (!mounted) return; if (!mounted) return;
final ud = context.read<UserDirectoryProvider>(); final ud = context.read<UserDirectoryProvider>();
final idSet = <int>{};
for (final channel in channels) { for (final channel in channels) {
if (channel.type == 1) { if (channel.type == 1) {
await ud.listAccount( idSet.addAll(
channel.members channel.members
?.cast<SnChannelMember?>() ?.cast<SnChannelMember?>()
.map((ele) => ele?.accountId) .map((ele) => ele?.accountId)
.where((ele) => ele != null) .where((ele) => ele != null)
.toSet() ?? .cast<int>() ??
{}, [],
); );
} }
} }
if (idSet.isNotEmpty) await ud.listAccount(idSet);
if (mounted) setState(() => _channels = channels); if (mounted) setState(() => _channels = channels);
}) })
@@ -135,6 +139,28 @@ class _ChatScreenState extends State<ChatScreen> {
_fetchWhatsNew(); _fetchWhatsNew();
} }
void _onTapChannel(SnChannel channel) {
final doExpand = ResponsiveBreakpoints.of(context).largerOrEqualTo(DESKTOP);
if (doExpand) {
setState(() => _focusChannel = channel);
return;
}
GoRouter.of(context).pushNamed(
'chatRoom',
pathParameters: {
'scope': channel.realm?.alias ?? 'global',
'alias': channel.alias,
},
).then((value) {
if (mounted) {
_unreadCounts?[channel.id] = 0;
setState(() => _unreadCounts?[channel.id] = 0);
_refreshChannels(noRemote: true);
}
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ud = context.read<UserDirectoryProvider>(); final ud = context.read<UserDirectoryProvider>();
@@ -258,17 +284,42 @@ class _ChatScreenState extends State<ChatScreen> {
channel.name), channel.name),
), ),
const Gap(8), const Gap(8),
if (_unreadCounts?[channel.id] != null) if (_unreadCounts?[channel.id] != null &&
_unreadCounts![channel.id]! > 0)
Badge( Badge(
label: Text('${_unreadCounts![channel.id]}'), label: Text('${_unreadCounts![channel.id]}'),
), ),
], ],
), ),
subtitle: lastMessage != null subtitle: lastMessage != null
? Text( ? Row(
'${ud.getAccountFromCache(lastMessage.sender.accountId)?.nick}: ${lastMessage.body['text'] ?? 'Unable preview'}', children: [
maxLines: 1, Expanded(
overflow: TextOverflow.ellipsis, child: Text(
lastMessage.body['text'] ??
'Unable preview',
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
const Gap(4),
Text(
DateFormat(
lastMessage.createdAt.toLocal().day ==
DateTime.now().day
? 'HH:mm'
: lastMessage.createdAt
.toLocal()
.year ==
DateTime.now().year
? 'MM/dd'
: 'yy/MM/dd',
).format(lastMessage.createdAt.toLocal()),
style: GoogleFonts.robotoMono(
fontSize: 12,
),
),
],
) )
: Text( : Text(
channel.description, channel.description,
@@ -283,19 +334,7 @@ class _ChatScreenState extends State<ChatScreen> {
?.avatar, ?.avatar,
), ),
onTap: () { onTap: () {
if (doExpand) { _onTapChannel(channel);
setState(() => _focusChannel = channel);
return;
}
GoRouter.of(context).pushNamed(
'chatRoom',
pathParameters: {
'scope': channel.realm?.alias ?? 'global',
'alias': channel.alias,
},
).then((value) {
if (mounted) _refreshChannels(noRemote: true);
});
}, },
); );
} }
@@ -305,17 +344,54 @@ class _ChatScreenState extends State<ChatScreen> {
children: [ children: [
Expanded(child: Text(channel.name)), Expanded(child: Text(channel.name)),
const Gap(8), const Gap(8),
if (_unreadCounts?[channel.id] != null) if (_unreadCounts?[channel.id] != null &&
_unreadCounts![channel.id]! > 0)
Badge( Badge(
label: Text('${_unreadCounts![channel.id]}'), label: Text('${_unreadCounts![channel.id]}'),
), ),
], ],
), ),
subtitle: lastMessage != null subtitle: lastMessage != null
? Text( ? Row(
'${ud.getAccountFromCache(lastMessage.sender.accountId)?.nick}: ${lastMessage.body['text'] ?? 'Unable preview'}', children: [
maxLines: 1, Badge(
overflow: TextOverflow.ellipsis, label: Text(ud
.getAccountFromCache(
lastMessage.sender.accountId)
?.nick ??
'unknown'.tr()),
backgroundColor:
Theme.of(context).colorScheme.primary,
textColor:
Theme.of(context).colorScheme.onPrimary,
),
const Gap(6),
Expanded(
child: Text(
lastMessage.body['text'] ??
'Unable preview',
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
const Gap(4),
Text(
DateFormat(
lastMessage.createdAt.toLocal().day ==
DateTime.now().day
? 'HH:mm'
: lastMessage.createdAt
.toLocal()
.year ==
DateTime.now().year
? 'MM/dd'
: 'yy/MM/dd',
).format(lastMessage.createdAt.toLocal()),
style: GoogleFonts.robotoMono(
fontSize: 12,
),
),
],
) )
: Text( : Text(
channel.description, channel.description,
@@ -325,23 +401,16 @@ class _ChatScreenState extends State<ChatScreen> {
contentPadding: contentPadding:
const EdgeInsets.symmetric(horizontal: 16), const EdgeInsets.symmetric(horizontal: 16),
leading: AccountImage( leading: AccountImage(
content: null, content: channel.realm?.avatar,
fallbackWidget: const Icon(Symbols.chat, size: 20), fallbackWidget: const Icon(Symbols.chat, size: 20),
), ),
onTap: () { onTap: () {
if (doExpand) { if (doExpand) {
_unreadCounts?[channel.id] = 0;
setState(() => _focusChannel = channel); setState(() => _focusChannel = channel);
return; return;
} }
GoRouter.of(context).pushNamed( _onTapChannel(channel);
'chatRoom',
pathParameters: {
'scope': channel.realm?.alias ?? 'global',
'alias': channel.alias,
},
).then((value) {
if (value == true) _refreshChannels(noRemote: true);
});
}, },
); );
}, },

View File

@@ -13,6 +13,7 @@ import 'package:surface/controllers/chat_message_controller.dart';
import 'package:surface/controllers/post_write_controller.dart'; import 'package:surface/controllers/post_write_controller.dart';
import 'package:surface/providers/channel.dart'; import 'package:surface/providers/channel.dart';
import 'package:surface/providers/chat_call.dart'; import 'package:surface/providers/chat_call.dart';
import 'package:surface/providers/notification.dart';
import 'package:surface/providers/sn_network.dart'; import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/user_directory.dart'; import 'package:surface/providers/user_directory.dart';
import 'package:surface/providers/userinfo.dart'; import 'package:surface/providers/userinfo.dart';
@@ -84,6 +85,16 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
orElse: () => null, orElse: () => null,
); );
} }
if (!mounted) return;
final nty = context.read<NotificationProvider>();
nty.skippableNotifyChannel = _channel!.id;
final ws = context.read<WebSocketProvider>();
if (_channel != null) {
ws.conn?.sink.add({
'channel_id': _channel?.id,
});
}
} catch (err) { } catch (err) {
if (!mounted) return; if (!mounted) return;
context.showErrorDialog(err); context.showErrorDialog(err);
@@ -232,6 +243,14 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
void dispose() { void dispose() {
_wsSubscription?.cancel(); _wsSubscription?.cancel();
_messageController.dispose(); _messageController.dispose();
final nty = context.read<NotificationProvider>();
nty.skippableNotifyChannel = null;
final ws = context.read<WebSocketProvider>();
if (_channel != null) {
ws.conn?.sink.add({
'channel_id': _channel?.id,
});
}
super.dispose(); super.dispose();
} }
@@ -282,7 +301,9 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
builder: (context, _) { builder: (context, _) {
return Column( return Column(
children: [ children: [
LoadingIndicator(isActive: _isBusy), LoadingIndicator(
isActive: _isBusy || _messageController.isAggressiveLoading,
),
SingleChildScrollView( SingleChildScrollView(
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
child: MaterialBanner( child: MaterialBanner(
@@ -308,8 +329,8 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
if (_messageController.isPending) if (_messageController.isPending)
Expanded( Expanded(
child: const CircularProgressIndicator().center(), child: const CircularProgressIndicator().center(),
), )
if (!_messageController.isPending) else
Expanded( Expanded(
child: InfiniteList( child: InfiniteList(
reverse: true, reverse: true,

View File

@@ -2,7 +2,6 @@ import 'dart:math' as math;
import 'dart:ui'; import 'dart:ui';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
@@ -94,8 +93,12 @@ class _HomeScreenState extends State<HomeScreen> {
: MainAxisAlignment.start, : MainAxisAlignment.start,
children: [ children: [
_HomeDashUpdateWidget( _HomeDashUpdateWidget(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
bottom: 8, left: 8, right: 8)), bottom: 8,
left: 8,
right: 8,
),
),
_HomeDashSpecialDayWidget().padding(horizontal: 8), _HomeDashSpecialDayWidget().padding(horizontal: 8),
StaggeredGrid.extent( StaggeredGrid.extent(
maxCrossAxisExtent: 280, maxCrossAxisExtent: 280,

167
lib/screens/logging.dart Normal file
View File

@@ -0,0 +1,167 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/logger.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
import 'package:talker_dio_logger/dio_logs.dart';
import 'package:talker_flutter/talker_flutter.dart';
final Map<LogLevel, IconData> kLogLevelIcons = {
LogLevel.error: Symbols.error,
LogLevel.critical: Symbols.error,
LogLevel.warning: Symbols.warning,
LogLevel.info: Symbols.info,
LogLevel.debug: Symbols.info_i,
LogLevel.verbose: Symbols.info_i,
};
final Map<LogLevel, bool> kLogLevelFilled = {
LogLevel.error: false,
LogLevel.critical: true,
LogLevel.warning: true,
LogLevel.info: true,
LogLevel.debug: false,
LogLevel.verbose: false,
};
class DebugLoggingScreen extends StatelessWidget {
const DebugLoggingScreen({super.key});
@override
Widget build(BuildContext context) {
final talkerTheme = TalkerScreenTheme.fromTheme(Theme.of(context));
return AppScaffold(
appBar: AppBar(
leading: const PageBackButton(),
title: Text('debugLogging').tr(),
actions: [
IconButton(
onPressed: () {
logging.cleanHistory();
Navigator.pop(context);
},
icon: const Icon(Symbols.delete),
),
],
),
body: ListView.builder(
reverse: true,
padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom),
itemCount: logging.history.length,
itemBuilder: (context, index) {
final log = logging.history[index];
final color = log.getFlutterColor(talkerTheme);
return ListTile(
minTileHeight: 0,
tileColor: color.withOpacity(0.2),
leading: Icon(
kLogLevelIcons[log.logLevel ?? LogLevel.debug] ?? Symbols.help,
color: color,
fill: (kLogLevelFilled[log.logLevel ?? LogLevel.debug] ?? false)
? 1
: 0,
),
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (log is DioRequestLog)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${log.requestOptions.method} ${log.displayMessage}',
style: GoogleFonts.robotoMono(fontSize: 13),
),
if (log.requestOptions.data != null)
Theme(
data: Theme.of(context).copyWith(
dividerColor: Colors.transparent,
),
child: ExpansionTile(
title: Text('Payload').fontSize(13),
minTileHeight: 0,
tilePadding: EdgeInsets.zero,
expandedCrossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
log.requestOptions.data.toString(),
style: GoogleFonts.robotoMono(fontSize: 13),
),
],
),
),
],
)
else if (log is DioResponseLog)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${log.response.statusCode} ${log.displayMessage}',
style: GoogleFonts.robotoMono(fontSize: 13),
),
if (log.response.data != null)
Theme(
data: Theme.of(context).copyWith(
dividerColor: Colors.transparent,
),
child: ExpansionTile(
title: Text('Payload').fontSize(13),
minTileHeight: 0,
tilePadding: EdgeInsets.zero,
expandedCrossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
log.response.data.toString(),
style: GoogleFonts.robotoMono(fontSize: 13),
),
],
),
),
],
)
else
Text(
log.displayMessage,
style: GoogleFonts.robotoMono(fontSize: 13),
),
if (log.exception != null)
Text(
log.displayException,
style: GoogleFonts.robotoMono(fontSize: 13),
).bold(),
if (log.error != null)
Text(
log.displayException,
style: GoogleFonts.robotoMono(fontSize: 13),
).bold(),
if (log.stackTrace != null)
Text(
log.displayStackTrace,
style: GoogleFonts.robotoMono(fontSize: 12),
).padding(top: 4),
],
),
subtitle: Text(
'${(log.title?.replaceAll('-', ' ') ?? 'default').capitalizeEachWord()} · ${log.displayTime()}',
).fontSize(11),
onTap: () {
Clipboard.setData(
ClipboardData(
text: log.generateTextMessage(),
),
);
},
);
},
),
);
}
}

View File

@@ -29,6 +29,7 @@ const Map<String, IconData> kNotificationTopicIcons = {
'passport.security.otp': Symbols.password, 'passport.security.otp': Symbols.password,
'interactive.subscription': Symbols.subscriptions, 'interactive.subscription': Symbols.subscriptions,
'interactive.feedback': Symbols.add_reaction, 'interactive.feedback': Symbols.add_reaction,
'interactive.reply': Symbols.reply,
'messaging.callStart': Symbols.call_received, 'messaging.callStart': Symbols.call_received,
'wallet.transaction.new': Symbols.receipt, 'wallet.transaction.new': Symbols.receipt,
}; };
@@ -57,10 +58,17 @@ class _NotificationScreenState extends State<NotificationScreen> {
try { try {
final sn = context.read<SnNetworkProvider>(); final sn = context.read<SnNetworkProvider>();
final nty = context.read<NotificationProvider>(); final nty = context.read<NotificationProvider>();
final resp = await sn.client.get('/cgi/id/notifications?take=10'); final resp =
await sn.client.get('/cgi/id/notifications', queryParameters: {
'take': 10,
'offset': _notifications.length,
});
_totalCount = resp.data['count']; _totalCount = resp.data['count'];
_notifications.addAll( _notifications.addAll(
resp.data['data']?.map((e) => SnNotification.fromJson(e)).cast<SnNotification>() ?? [], resp.data['data']
?.map((e) => SnNotification.fromJson(e))
.cast<SnNotification>() ??
[],
); );
nty.updateTray(); nty.updateTray();
} catch (err) { } catch (err) {
@@ -186,7 +194,8 @@ class _NotificationScreenState extends State<NotificationScreen> {
_fetchNotifications(); _fetchNotifications();
}, },
isLoading: _isBusy, isLoading: _isBusy,
hasReachedMax: _totalCount != null && _notifications.length >= _totalCount!, hasReachedMax: _totalCount != null &&
_notifications.length >= _totalCount!,
itemBuilder: (context, idx) { itemBuilder: (context, idx) {
final nty = _notifications[idx]; final nty = _notifications[idx];
return Row( return Row(
@@ -218,13 +227,17 @@ class _NotificationScreenState extends State<NotificationScreen> {
isAutoWarp: true, isAutoWarp: true,
), ),
), ),
if (['interactive.reply', 'interactive.feedback', 'interactive.subscription'] if ([
.contains(nty.topic) && 'interactive.reply',
'interactive.feedback',
'interactive.subscription'
].contains(nty.topic) &&
nty.metadata['related_post'] != null) nty.metadata['related_post'] != null)
GestureDetector( GestureDetector(
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: const BorderRadius.all(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,
@@ -243,7 +256,9 @@ class _NotificationScreenState extends State<NotificationScreen> {
GoRouter.of(context).pushNamed( GoRouter.of(context).pushNamed(
'postDetail', 'postDetail',
pathParameters: { pathParameters: {
'slug': nty.metadata['related_post']!['id'].toString(), 'slug': nty
.metadata['related_post']!['id']
.toString(),
}, },
); );
}, },
@@ -272,8 +287,10 @@ 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: const VisualDensity(horizontal: -4, vertical: -4), visualDensity:
onPressed: _isSubmitting ? null : () => _markOneAsRead(nty), const VisualDensity(horizontal: -4, vertical: -4),
onPressed:
_isSubmitting ? null : () => _markOneAsRead(nty),
), ),
], ],
).padding(horizontal: 16); ).padding(horizontal: 16);

View File

@@ -95,8 +95,9 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
resp.data?.map((e) => SnPublisher.fromJson(e)) ?? [], resp.data?.map((e) => SnPublisher.fromJson(e)) ?? [],
); );
final beforeId = config.prefs.getInt('int_last_publisher_id'); final beforeId = config.prefs.getInt('int_last_publisher_id');
_writeController _writeController.setPublisher(
.setPublisher(_publishers?.where((ele) => ele.id == beforeId).firstOrNull ?? _publishers?.firstOrNull); _publishers?.where((ele) => ele.id == beforeId).firstOrNull ??
_publishers?.firstOrNull);
} catch (err) { } catch (err) {
if (!mounted) return; if (!mounted) return;
context.showErrorDialog(err); context.showErrorDialog(err);
@@ -125,7 +126,11 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
final HotKey _pasteHotKey = HotKey( final HotKey _pasteHotKey = HotKey(
key: PhysicalKeyboardKey.keyV, key: PhysicalKeyboardKey.keyV,
modifiers: [(!kIsWeb && Platform.isMacOS) ? HotKeyModifier.meta : HotKeyModifier.control], modifiers: [
(!kIsWeb && Platform.isMacOS)
? HotKeyModifier.meta
: HotKeyModifier.control
],
scope: HotKeyScope.inapp, scope: HotKeyScope.inapp,
); );
@@ -232,7 +237,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
if (widget.extraProps != null) { if (widget.extraProps != null) {
_writeController.contentController.text = widget.extraProps!.text ?? ''; _writeController.contentController.text = widget.extraProps!.text ?? '';
_writeController.titleController.text = widget.extraProps!.title ?? ''; _writeController.titleController.text = widget.extraProps!.title ?? '';
_writeController.descriptionController.text = widget.extraProps!.description ?? ''; _writeController.descriptionController.text =
widget.extraProps!.description ?? '';
_writeController.addAttachments(widget.extraProps!.attachments ?? []); _writeController.addAttachments(widget.extraProps!.attachments ?? []);
} }
} }
@@ -253,7 +259,9 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
textAlign: TextAlign.center, textAlign: TextAlign.center,
text: TextSpan(children: [ text: TextSpan(children: [
TextSpan( TextSpan(
text: _writeController.title.isNotEmpty ? _writeController.title : 'untitled'.tr(), text: _writeController.title.isNotEmpty
? _writeController.title
: 'untitled'.tr(),
style: Theme.of(context).textTheme.titleLarge!.copyWith( style: Theme.of(context).textTheme.titleLarge!.copyWith(
color: Theme.of(context).appBarTheme.foregroundColor!, color: Theme.of(context).appBarTheme.foregroundColor!,
), ),
@@ -280,7 +288,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
children: [ children: [
if (_writeController.editingPost != null) if (_writeController.editingPost != null)
Container( Container(
padding: const EdgeInsets.only(top: 4, bottom: 4, left: 20, right: 20), padding: const EdgeInsets.only(
top: 4, bottom: 4, left: 20, right: 20),
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border( border: Border(
bottom: BorderSide( bottom: BorderSide(
@@ -294,13 +303,16 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
children: [ children: [
const Icon(Icons.edit, size: 16), const Icon(Icons.edit, size: 16),
const Gap(10), const Gap(10),
Text('postEditingNotice').tr(args: ['@${_writeController.editingPost!.publisher.name}']), Text('postEditingNotice').tr(args: [
'@${_writeController.editingPost!.publisher.name}'
]),
], ],
), ),
), ),
if (_writeController.replyingPost != null) if (_writeController.replyingPost != null)
Container( Container(
padding: const EdgeInsets.only(top: 4, bottom: 4, left: 20, right: 20), padding: const EdgeInsets.only(
top: 4, bottom: 4, left: 20, right: 20),
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border( border: Border(
bottom: BorderSide( bottom: BorderSide(
@@ -314,7 +326,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
children: [ children: [
const Icon(Symbols.reply, size: 16), const Icon(Symbols.reply, size: 16),
const Gap(10), const Gap(10),
Text('@${_writeController.replyingPost!.publisher.name}').bold(), Text('@${_writeController.replyingPost!.publisher.name}')
.bold(),
const Gap(4), const Gap(4),
Expanded( Expanded(
child: Text( child: Text(
@@ -328,7 +341,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
), ),
if (_writeController.repostingPost != null) if (_writeController.repostingPost != null)
Container( Container(
padding: const EdgeInsets.only(top: 4, bottom: 4, left: 20, right: 20), padding: const EdgeInsets.only(
top: 4, bottom: 4, left: 20, right: 20),
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border( border: Border(
bottom: BorderSide( bottom: BorderSide(
@@ -342,7 +356,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
children: [ children: [
const Icon(Symbols.forward, size: 16), const Icon(Symbols.forward, size: 16),
const Gap(10), const Gap(10),
Text('@${_writeController.repostingPost!.publisher.name}').bold(), Text('@${_writeController.repostingPost!.publisher.name}')
.bold(),
const Gap(4), const Gap(4),
Expanded( Expanded(
child: Text( child: Text(
@@ -384,7 +399,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
}) })
.padding(top: 8), .padding(top: 8),
), ),
if (_writeController.attachments.isNotEmpty || _writeController.thumbnail != null) if (_writeController.attachments.isNotEmpty ||
_writeController.thumbnail != null)
Positioned( Positioned(
bottom: 0, bottom: 0,
left: 0, left: 0,
@@ -393,16 +409,19 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
attachments: _writeController.attachments, attachments: _writeController.attachments,
isBusy: _writeController.isBusy, isBusy: _writeController.isBusy,
onUpload: (int idx) async { onUpload: (int idx) async {
await _writeController.uploadSingleAttachment(context, idx); await _writeController.uploadSingleAttachment(
context, idx);
}, },
onInsertLink: (int idx) async { onInsertLink: (int idx) async {
_writeController.contentController.text += _writeController.contentController.text +=
'\n![](solink://attachments/${_writeController.attachments[idx].attachment!.rid})'; '\n![](solink://attachments/${_writeController.attachments[idx].attachment!.rid})';
}, },
onUpdate: (int idx, PostWriteMedia updatedMedia) async { onUpdate:
(int idx, PostWriteMedia updatedMedia) async {
_writeController.setIsBusy(true); _writeController.setIsBusy(true);
try { try {
_writeController.setAttachmentAt(idx, updatedMedia); _writeController.setAttachmentAt(
idx, updatedMedia);
} finally { } finally {
_writeController.setIsBusy(false); _writeController.setIsBusy(false);
} }
@@ -415,7 +434,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
_writeController.setIsBusy(false); _writeController.setIsBusy(false);
} }
}, },
onUpdateBusy: (state) => _writeController.setIsBusy(state), onUpdateBusy: (state) =>
_writeController.setIsBusy(state),
).padding(bottom: 8), ).padding(bottom: 8),
), ),
], ],
@@ -426,11 +446,13 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (_writeController.isBusy && _writeController.progress != null) if (_writeController.isBusy &&
_writeController.progress != null)
TweenAnimationBuilder<double>( TweenAnimationBuilder<double>(
tween: Tween(begin: 0, end: _writeController.progress), tween: Tween(begin: 0, end: _writeController.progress),
duration: Duration(milliseconds: 300), duration: Duration(milliseconds: 300),
builder: (context, value, _) => LinearProgressIndicator(value: value, minHeight: 2), builder: (context, value, _) =>
LinearProgressIndicator(value: value, minHeight: 2),
) )
else if (_writeController.isBusy) else if (_writeController.isBusy)
const LinearProgressIndicator(value: null, minHeight: 2), const LinearProgressIndicator(value: null, minHeight: 2),
@@ -439,12 +461,14 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
Container( Container(
child: _writeController.temporaryRestored child: _writeController.temporaryRestored
? Container( ? Container(
padding: const EdgeInsets.only(top: 4, bottom: 4, left: 28, right: 22), padding: const EdgeInsets.only(
top: 4, bottom: 4, left: 28, right: 22),
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border( border: Border(
bottom: BorderSide( bottom: BorderSide(
color: Theme.of(context).dividerColor, color: Theme.of(context).dividerColor,
width: 1 / MediaQuery.of(context).devicePixelRatio, width: 1 /
MediaQuery.of(context).devicePixelRatio,
), ),
), ),
), ),
@@ -453,7 +477,9 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
children: [ children: [
const Icon(Icons.restore, size: 20), const Icon(Icons.restore, size: 20),
const Gap(8), const Gap(8),
Expanded(child: Text('postLocalDraftRestored').tr()), Expanded(
child:
Text('postLocalDraftRestored').tr()),
InkWell( InkWell(
child: Text('dialogDismiss').tr(), child: Text('dialogDismiss').tr(),
onTap: () { onTap: () {
@@ -464,8 +490,10 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
)) ))
: const SizedBox.shrink(), : const SizedBox.shrink(),
) )
.height(_writeController.temporaryRestored ? 32 : 0, animate: true) .height(_writeController.temporaryRestored ? 32 : 0,
.animate(const Duration(milliseconds: 300), Curves.fastLinearToSlowEaseIn), animate: true)
.animate(const Duration(milliseconds: 300),
Curves.fastLinearToSlowEaseIn),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
@@ -485,11 +513,18 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
), ),
if (_writeController.mode == 'stories') if (_writeController.mode == 'stories')
IconButton( IconButton(
icon: Icon(Symbols.poll, color: Theme.of(context).colorScheme.primary), icon: Icon(Symbols.poll,
color: Theme.of(context)
.colorScheme
.primary),
style: ButtonStyle( style: ButtonStyle(
backgroundColor: _writeController.poll == null backgroundColor:
? null _writeController.poll == null
: WidgetStatePropertyAll(Theme.of(context).colorScheme.surfaceContainer), ? null
: WidgetStatePropertyAll(
Theme.of(context)
.colorScheme
.surfaceContainer),
), ),
onPressed: () { onPressed: () {
_showPollEditorDialog(); _showPollEditorDialog();
@@ -497,14 +532,22 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
), ),
if (_writeController.mode == 'articles') if (_writeController.mode == 'articles')
IconButton( IconButton(
icon: Icon(Symbols.full_coverage, color: Theme.of(context).colorScheme.primary), icon: Icon(Symbols.full_coverage,
color: Theme.of(context)
.colorScheme
.primary),
style: ButtonStyle( style: ButtonStyle(
backgroundColor: _writeController.thumbnail == null backgroundColor:
? null _writeController.thumbnail == null
: WidgetStatePropertyAll(Theme.of(context).colorScheme.surfaceContainer), ? null
: WidgetStatePropertyAll(
Theme.of(context)
.colorScheme
.surfaceContainer),
), ),
onPressed: () { onPressed: () {
if (_writeController.thumbnail != null) { if (_writeController.thumbnail !=
null) {
_writeController.setThumbnail(null); _writeController.setThumbnail(null);
return; return;
} }
@@ -517,7 +560,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
), ),
), ),
TextButton.icon( TextButton.icon(
onPressed: (_writeController.isBusy || _writeController.publisher == null) onPressed: (_writeController.isBusy ||
_writeController.publisher == null)
? null ? null
: () { : () {
_writeController.sendPost(context).then((_) { _writeController.sendPost(context).then((_) {
@@ -556,7 +600,8 @@ class _PostPublisherPopup extends StatelessWidget {
final List<SnPublisher>? publishers; final List<SnPublisher>? publishers;
final Function onUpdate; final Function onUpdate;
const _PostPublisherPopup({required this.controller, this.publishers, required this.onUpdate}); const _PostPublisherPopup(
{required this.controller, this.publishers, required this.onUpdate});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -568,7 +613,9 @@ class _PostPublisherPopup extends StatelessWidget {
children: [ children: [
const Icon(Symbols.face, size: 24), const Icon(Symbols.face, size: 24),
const Gap(16), const Gap(16),
Text('accountPublishers', style: Theme.of(context).textTheme.titleLarge).tr(), Text('accountPublishers',
style: Theme.of(context).textTheme.titleLarge)
.tr(),
], ],
).padding(horizontal: 20, top: 16, bottom: 12), ).padding(horizontal: 20, top: 16, bottom: 12),
ListTile( ListTile(
@@ -612,7 +659,8 @@ class _PostRealmPopup extends StatelessWidget {
final List<SnRealm>? realms; final List<SnRealm>? realms;
final Function onUpdate; final Function onUpdate;
const _PostRealmPopup({required this.controller, this.realms, required this.onUpdate}); const _PostRealmPopup(
{required this.controller, this.realms, required this.onUpdate});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -624,7 +672,8 @@ class _PostRealmPopup extends StatelessWidget {
children: [ children: [
const Icon(Symbols.face, size: 24), const Icon(Symbols.face, size: 24),
const Gap(16), const Gap(16),
Text('accountRealms', style: Theme.of(context).textTheme.titleLarge).tr(), Text('accountRealms', style: Theme.of(context).textTheme.titleLarge)
.tr(),
], ],
).padding(horizontal: 20, top: 16, bottom: 12), ).padding(horizontal: 20, top: 16, bottom: 12),
ListTile( ListTile(
@@ -665,7 +714,8 @@ class _PostStoryEditor extends StatelessWidget {
final Function? onTapPublisher; final Function? onTapPublisher;
final Function? onTapRealm; final Function? onTapRealm;
const _PostStoryEditor({required this.controller, this.onTapPublisher, this.onTapRealm}); const _PostStoryEditor(
{required this.controller, this.onTapPublisher, this.onTapRealm});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -717,7 +767,8 @@ class _PostStoryEditor extends StatelessWidget {
border: InputBorder.none, border: InputBorder.none,
), ),
style: Theme.of(context).textTheme.titleLarge, style: Theme.of(context).textTheme.titleLarge,
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
).padding(horizontal: 16), ).padding(horizontal: 16),
const Gap(8), const Gap(8),
TextField( TextField(
@@ -732,8 +783,10 @@ class _PostStoryEditor extends StatelessWidget {
), ),
border: InputBorder.none, border: InputBorder.none,
), ),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), onTapOutside: (_) =>
contentInsertionConfiguration: controller.contentInsertionConfiguration, FocusManager.instance.primaryFocus?.unfocus(),
contentInsertionConfiguration:
controller.contentInsertionConfiguration,
), ),
], ],
), ),
@@ -749,7 +802,8 @@ class _PostArticleEditor extends StatelessWidget {
final Function? onTapPublisher; final Function? onTapPublisher;
final Function? onTapRealm; final Function? onTapRealm;
const _PostArticleEditor({required this.controller, this.onTapPublisher, this.onTapRealm}); const _PostArticleEditor(
{required this.controller, this.onTapPublisher, this.onTapRealm});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -857,8 +911,10 @@ class _PostArticleEditor extends StatelessWidget {
), ),
border: InputBorder.none, border: InputBorder.none,
), ),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), onTapOutside: (_) =>
contentInsertionConfiguration: controller.contentInsertionConfiguration, FocusManager.instance.primaryFocus?.unfocus(),
contentInsertionConfiguration:
controller.contentInsertionConfiguration,
), ),
), ),
const Gap(8), const Gap(8),
@@ -893,7 +949,8 @@ class _PostArticleEditor extends StatelessWidget {
border: InputBorder.none, border: InputBorder.none,
), ),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
contentInsertionConfiguration: controller.contentInsertionConfiguration, contentInsertionConfiguration:
controller.contentInsertionConfiguration,
), ),
), ),
], ],
@@ -906,7 +963,8 @@ class _PostQuestionEditor extends StatelessWidget {
final Function? onTapPublisher; final Function? onTapPublisher;
final Function? onTapRealm; final Function? onTapRealm;
const _PostQuestionEditor({required this.controller, this.onTapPublisher, this.onTapRealm}); const _PostQuestionEditor(
{required this.controller, this.onTapPublisher, this.onTapRealm});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -958,7 +1016,8 @@ class _PostQuestionEditor extends StatelessWidget {
border: InputBorder.none, border: InputBorder.none,
), ),
style: Theme.of(context).textTheme.titleLarge, style: Theme.of(context).textTheme.titleLarge,
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
).padding(horizontal: 16), ).padding(horizontal: 16),
const Gap(8), const Gap(8),
TextField( TextField(
@@ -969,7 +1028,8 @@ class _PostQuestionEditor extends StatelessWidget {
border: InputBorder.none, border: InputBorder.none,
isCollapsed: true, isCollapsed: true,
), ),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
).padding(horizontal: 16), ).padding(horizontal: 16),
const Gap(8), const Gap(8),
TextField( TextField(
@@ -984,8 +1044,10 @@ class _PostQuestionEditor extends StatelessWidget {
), ),
border: InputBorder.none, border: InputBorder.none,
), ),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), onTapOutside: (_) =>
contentInsertionConfiguration: controller.contentInsertionConfiguration, FocusManager.instance.primaryFocus?.unfocus(),
contentInsertionConfiguration:
controller.contentInsertionConfiguration,
), ),
], ],
), ),
@@ -1001,7 +1063,8 @@ class _PostVideoEditor extends StatelessWidget {
final Function? onTapPublisher; final Function? onTapPublisher;
final Function? onTapRealm; final Function? onTapRealm;
const _PostVideoEditor({required this.controller, this.onTapPublisher, this.onTapRealm}); const _PostVideoEditor(
{required this.controller, this.onTapPublisher, this.onTapRealm});
void _selectVideo(BuildContext context) async { void _selectVideo(BuildContext context) async {
final video = await showDialog<SnAttachment?>( final video = await showDialog<SnAttachment?>(
@@ -1022,7 +1085,8 @@ class _PostVideoEditor extends StatelessWidget {
final result = await showDialog<SnAttachment?>( final result = await showDialog<SnAttachment?>(
context: context, context: context,
builder: (context) => PendingAttachmentAltDialog(media: PostWriteMedia(controller.videoAttachment)), builder: (context) => PendingAttachmentAltDialog(
media: PostWriteMedia(controller.videoAttachment)),
); );
if (result == null) return; if (result == null) return;
@@ -1034,7 +1098,8 @@ class _PostVideoEditor extends StatelessWidget {
final result = await showDialog<SnAttachmentBoost?>( final result = await showDialog<SnAttachmentBoost?>(
context: context, context: context,
builder: (context) => PendingAttachmentBoostDialog(media: PostWriteMedia(controller.videoAttachment)), builder: (context) => PendingAttachmentBoostDialog(
media: PostWriteMedia(controller.videoAttachment)),
); );
if (result == null) return; if (result == null) return;
@@ -1077,7 +1142,8 @@ class _PostVideoEditor extends StatelessWidget {
try { try {
final sn = context.read<SnNetworkProvider>(); final sn = context.read<SnNetworkProvider>();
await sn.client.delete('/cgi/uc/attachments/${controller.videoAttachment!.id}'); await sn.client
.delete('/cgi/uc/attachments/${controller.videoAttachment!.id}');
controller.setVideoAttachment(null); controller.setVideoAttachment(null);
} catch (err) { } catch (err) {
if (!context.mounted) return; if (!context.mounted) return;
@@ -1087,143 +1153,159 @@ class _PostVideoEditor extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Container(
children: [ padding: const EdgeInsets.symmetric(horizontal: 12),
Column( constraints: const BoxConstraints(maxWidth: 640),
children: [ child: Row(
Material( crossAxisAlignment: CrossAxisAlignment.start,
elevation: 2, children: [
borderRadius: const BorderRadius.all(Radius.circular(24)), Column(
child: GestureDetector( children: [
onTap: () { Material(
onTapPublisher?.call(); elevation: 2,
}, borderRadius: const BorderRadius.all(Radius.circular(24)),
child: AccountImage( child: GestureDetector(
content: controller.publisher?.avatar, onTap: () {
onTapPublisher?.call();
},
child: AccountImage(
content: controller.publisher?.avatar,
),
), ),
), ),
), const Gap(11),
const Gap(11), Material(
Material( elevation: 1,
elevation: 1, borderRadius: const BorderRadius.all(Radius.circular(24)),
borderRadius: const BorderRadius.all(Radius.circular(24)), child: GestureDetector(
child: GestureDetector( onTap: () {
onTap: () { onTapRealm?.call();
onTapRealm?.call(); },
}, child: AccountImage(
child: AccountImage( content: controller.realm?.avatar,
content: controller.realm?.avatar, fallbackWidget: const Icon(Symbols.globe, size: 20),
fallbackWidget: const Icon(Symbols.globe, size: 20), radius: 14,
radius: 14, ),
), ),
), ),
), ],
],
),
const Gap(16),
TextField(
controller: controller.titleController,
decoration: InputDecoration.collapsed(
hintText: 'fieldPostTitle'.tr(),
border: InputBorder.none,
), ),
style: Theme.of(context).textTheme.titleLarge, Expanded(
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), child: Column(
).padding(horizontal: 16), children: [
const Gap(8), const Gap(6),
TextField( TextField(
controller: controller.descriptionController, controller: controller.titleController,
decoration: InputDecoration.collapsed( decoration: InputDecoration.collapsed(
hintText: 'fieldPostDescription'.tr(), hintText: 'fieldPostTitle'.tr(),
border: InputBorder.none, border: InputBorder.none,
), ),
maxLines: null, style: Theme.of(context).textTheme.titleLarge,
keyboardType: TextInputType.multiline, onTapOutside: (_) =>
style: Theme.of(context).textTheme.bodyLarge, FocusManager.instance.primaryFocus?.unfocus(),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), ).padding(horizontal: 16),
).padding(horizontal: 16), const Gap(8),
const Gap(12), TextField(
Container( controller: controller.descriptionController,
margin: const EdgeInsets.only(left: 16, right: 16), decoration: InputDecoration.collapsed(
decoration: BoxDecoration( hintText: 'fieldPostDescription'.tr(),
borderRadius: BorderRadius.circular(16), border: InputBorder.none,
border: Border.all(color: Theme.of(context).dividerColor), ),
), maxLines: null,
child: ContextMenuRegion( keyboardType: TextInputType.multiline,
contextMenu: ContextMenu( style: Theme.of(context).textTheme.bodyLarge,
entries: [ onTapOutside: (_) =>
MenuItem( FocusManager.instance.primaryFocus?.unfocus(),
label: 'attachmentSetAlt'.tr(), ).padding(horizontal: 16),
icon: Symbols.description, const Gap(12),
onSelected: () { Container(
_setAlt(context); margin: const EdgeInsets.only(left: 16, right: 16),
}, decoration: BoxDecoration(
), borderRadius: BorderRadius.circular(16),
MenuItem( border: Border.all(color: Theme.of(context).dividerColor),
label: 'attachmentBoost'.tr(), ),
icon: Symbols.bolt, child: ContextMenuRegion(
onSelected: () { contextMenu: ContextMenu(
_createBoost(context); entries: [
}, MenuItem(
), label: 'attachmentSetAlt'.tr(),
MenuItem( icon: Symbols.description,
label: 'attachmentSetThumbnail'.tr(), onSelected: () {
icon: Symbols.image, _setAlt(context);
onSelected: () { },
_setThumbnail(context); ),
}, MenuItem(
), label: 'attachmentBoost'.tr(),
MenuItem( icon: Symbols.bolt,
label: 'attachmentCopyRandomId'.tr(), onSelected: () {
icon: Symbols.content_copy, _createBoost(context);
onSelected: () { },
Clipboard.setData(ClipboardData(text: controller.videoAttachment!.rid)); ),
}, MenuItem(
), label: 'attachmentSetThumbnail'.tr(),
MenuItem( icon: Symbols.image,
label: 'delete'.tr(), onSelected: () {
icon: Symbols.delete, _setThumbnail(context);
onSelected: () => _deleteAttachment(context), },
), ),
MenuItem( MenuItem(
label: 'unlink'.tr(), label: 'attachmentCopyRandomId'.tr(),
icon: Symbols.link_off, icon: Symbols.content_copy,
onSelected: () { onSelected: () {
controller.setVideoAttachment(null); Clipboard.setData(ClipboardData(
}, text: controller.videoAttachment!.rid));
},
),
MenuItem(
label: 'delete'.tr(),
icon: Symbols.delete,
onSelected: () => _deleteAttachment(context),
),
MenuItem(
label: 'unlink'.tr(),
icon: Symbols.link_off,
onSelected: () {
controller.setVideoAttachment(null);
},
),
],
),
child: InkWell(
borderRadius: BorderRadius.circular(16),
onTap: controller.videoAttachment == null
? () => _selectVideo(context)
: null,
child: AspectRatio(
aspectRatio: 16 / 9,
child: controller.videoAttachment == null
? Center(
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.add),
const Gap(4),
Text('postVideoUpload'.tr()),
],
),
)
: ClipRRect(
borderRadius: BorderRadius.circular(16),
child: AttachmentItem(
data: controller.videoAttachment!,
heroTag: const Uuid().v4(),
),
),
),
),
),
), ),
], ],
), ),
child: InkWell(
borderRadius: BorderRadius.circular(16),
onTap: controller.videoAttachment == null ? () => _selectVideo(context) : null,
child: AspectRatio(
aspectRatio: 16 / 9,
child: controller.videoAttachment == null
? Center(
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.add),
const Gap(4),
Text('postVideoUpload'.tr()),
],
),
)
: ClipRRect(
borderRadius: BorderRadius.circular(16),
child: AttachmentItem(
data: controller.videoAttachment!,
heroTag: const Uuid().v4(),
),
),
),
),
), ),
), ],
], ),
); );
} }
} }

View File

@@ -6,6 +6,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.dart'; import 'package:flutter_colorpicker/flutter_colorpicker.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
@@ -48,6 +49,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
late final SharedPreferences _prefs; late final SharedPreferences _prefs;
String _docBasepath = '/'; String _docBasepath = '/';
final TextEditingController _customFontController = TextEditingController();
final TextEditingController _serverUrlController = TextEditingController(); final TextEditingController _serverUrlController = TextEditingController();
@override @override
@@ -62,11 +64,15 @@ class _SettingsScreenState extends State<SettingsScreen> {
final config = context.read<ConfigProvider>(); final config = context.read<ConfigProvider>();
_prefs = config.prefs; _prefs = config.prefs;
_serverUrlController.text = config.serverUrl; _serverUrlController.text = config.serverUrl;
if (_prefs.getString(kAppCustomFonts) != null) {
_customFontController.text = _prefs.getString(kAppCustomFonts) ?? '';
}
} }
@override @override
void dispose() { void dispose() {
_serverUrlController.dispose(); _serverUrlController.dispose();
_customFontController.dispose();
super.dispose(); super.dispose();
} }
@@ -330,6 +336,47 @@ class _SettingsScreenState extends State<SettingsScreen> {
setState(() {}); setState(() {});
}, },
), ),
ListTile(
leading: const Icon(Symbols.font_download),
title: Text('settingsCustomFonts').tr(),
subtitle: Text('settingsCustomFontsDescription').tr(),
contentPadding: const EdgeInsets.only(left: 24, right: 14),
trailing: IconButton(
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
icon: const Icon(Icons.clear),
onPressed: () {
_prefs.remove(kAppCustomFonts);
context.showSnackbar('settingsCustomFontApplied'.tr());
final theme = context.read<ThemeProvider>();
_customFontController.clear();
theme.reloadTheme();
},
),
),
TextField(
controller: _customFontController,
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: 'settingsCustomFontFamily'.tr(),
helperText: 'settingsCustomFontFamilyHint'.tr(),
prefixIcon: const Icon(Symbols.format_paint),
suffixIcon: IconButton(
icon: const Icon(Symbols.save),
onPressed: () {
_prefs.setString(
kAppCustomFonts,
_customFontController.text,
);
context.showSnackbar('settingsCustomFontApplied'.tr());
final theme = context.read<ThemeProvider>();
theme.reloadTheme();
},
),
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
).padding(horizontal: 16, top: 8, bottom: 4),
], ],
), ),
Column( Column(
@@ -534,6 +581,37 @@ class _SettingsScreenState extends State<SettingsScreen> {
.fontSize(17) .fontSize(17)
.tr() .tr()
.padding(horizontal: 20, bottom: 4), .padding(horizontal: 20, bottom: 4),
ListTile(
leading: const Icon(Symbols.home_storage),
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
title: Text('cacheSize').tr(),
subtitle: FutureBuilder(
future: DefaultCacheManager().store.getCacheSize(),
builder: (context, snapshot) {
if (!snapshot.hasData || kIsWeb) {
return Text('unknown').tr();
}
return Text(
snapshot.data!.formatBytes(),
style: GoogleFonts.robotoMono(),
);
},
),
),
ListTile(
leading: const Icon(Symbols.cleaning_services),
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
title: Text('cacheDelete').tr(),
subtitle: Text('cacheDeleteDescription').tr(),
trailing: const Icon(Symbols.chevron_right),
onTap: () async {
await DefaultCacheManager().emptyCache();
if (!context.mounted) return;
HapticFeedback.heavyImpact();
context.showSnackbar('cacheDeleted'.tr());
setState(() {});
},
),
ListTile( ListTile(
leading: const Icon(Symbols.database), leading: const Icon(Symbols.database),
contentPadding: const EdgeInsets.symmetric(horizontal: 24), contentPadding: const EdgeInsets.symmetric(horizontal: 24),
@@ -618,6 +696,16 @@ class _SettingsScreenState extends State<SettingsScreen> {
); );
}, },
), ),
ListTile(
title: Text('runtimeLogsOpen').tr(),
subtitle: Text('runtimeLogsDescription').tr(),
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
leading: const Icon(Symbols.receipt_long),
trailing: const Icon(Symbols.chevron_right),
onTap: () async {
GoRouter.of(context).pushNamed('debugLogging');
},
),
ListTile( ListTile(
title: Text('settingsMiscAbout').tr(), title: Text('settingsMiscAbout').tr(),
subtitle: Text('settingsMiscAboutDescription').tr(), subtitle: Text('settingsMiscAboutDescription').tr(),

View File

@@ -179,7 +179,9 @@ class _StickerScreenState extends State<StickerScreen>
child: InfiniteList( child: InfiniteList(
itemCount: _packs.length, itemCount: _packs.length,
onFetchData: _fetchPacks, onFetchData: _fetchPacks,
hasReachedMax: _totalCount != null && _packs.length >= _totalCount!, hasReachedMax:
(_totalCount != null && _packs.length >= _totalCount!) ||
_tabController.index == 2,
isLoading: _isBusy, isLoading: _isBusy,
itemBuilder: (context, idx) { itemBuilder: (context, idx) {
final pack = _packs[idx]; final pack = _packs[idx];

View File

@@ -11,10 +11,19 @@ class ThemeSet {
ThemeSet({required this.light, required this.dark}); ThemeSet({required this.light, required this.dark});
} }
Future<ThemeSet> createAppThemeSet({Color? seedColorOverride, bool? useMaterial3}) async { Future<ThemeSet> createAppThemeSet(
{Color? seedColorOverride, bool? useMaterial3, String? customFonts}) async {
return ThemeSet( return ThemeSet(
light: await createAppTheme(Brightness.light, useMaterial3: useMaterial3), light: await createAppTheme(
dark: await createAppTheme(Brightness.dark, useMaterial3: useMaterial3), Brightness.light,
useMaterial3: useMaterial3,
customFonts: customFonts,
),
dark: await createAppTheme(
Brightness.dark,
useMaterial3: useMaterial3,
customFonts: customFonts,
),
); );
} }
@@ -22,24 +31,35 @@ Future<ThemeData> createAppTheme(
Brightness brightness, { Brightness brightness, {
Color? seedColorOverride, Color? seedColorOverride,
bool? useMaterial3, bool? useMaterial3,
String? customFonts,
}) async { }) async {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
final seedColorString = prefs.getInt(kAppColorSchemeStoreKey); final seedColorString = prefs.getInt(kAppColorSchemeStoreKey);
final seedColor = seedColorString != null ? Color(seedColorString) : Colors.indigo; final seedColor =
seedColorString != null ? Color(seedColorString) : Colors.indigo;
final colorScheme = ColorScheme.fromSeed( final colorScheme = ColorScheme.fromSeed(
seedColor: seedColorOverride ?? seedColor, seedColor: seedColorOverride ?? seedColor,
brightness: brightness, brightness: brightness,
); );
final hasAppBarTransparent = prefs.getBool(kAppbarTransparentStoreKey) ?? false; final hasAppBarTransparent =
final useM3 = useMaterial3 ?? (prefs.getBool(kMaterialYouToggleStoreKey) ?? true); prefs.getBool(kAppbarTransparentStoreKey) ?? false;
final useM3 =
useMaterial3 ?? (prefs.getBool(kMaterialYouToggleStoreKey) ?? true);
final inUseFonts = (customFonts ?? prefs.getString(kAppCustomFonts))
?.split(',')
.map((ele) => ele.trim())
.toList();
return ThemeData( return ThemeData(
useMaterial3: useM3, useMaterial3: useM3,
colorScheme: colorScheme, colorScheme: colorScheme,
brightness: brightness, brightness: brightness,
fontFamily: inUseFonts?.firstOrNull,
fontFamilyFallback: inUseFonts?.sublist(1),
iconTheme: IconThemeData( iconTheme: IconThemeData(
fill: 0, fill: 0,
weight: 400, weight: 400,
@@ -52,8 +72,10 @@ Future<ThemeData> createAppTheme(
appBarTheme: AppBarTheme( appBarTheme: AppBarTheme(
centerTitle: true, centerTitle: true,
elevation: hasAppBarTransparent ? 0 : null, elevation: hasAppBarTransparent ? 0 : null,
backgroundColor: hasAppBarTransparent ? Colors.transparent : colorScheme.primary, backgroundColor:
foregroundColor: hasAppBarTransparent ? colorScheme.onSurface : colorScheme.onPrimary, hasAppBarTransparent ? Colors.transparent : colorScheme.primary,
foregroundColor:
hasAppBarTransparent ? colorScheme.onSurface : colorScheme.onPrimary,
), ),
pageTransitionsTheme: PageTransitionsTheme( pageTransitionsTheme: PageTransitionsTheme(
builders: { builders: {

View File

@@ -4,7 +4,7 @@ part 'account.freezed.dart';
part 'account.g.dart'; part 'account.g.dart';
@freezed @freezed
class SnAccount with _$SnAccount { abstract class SnAccount with _$SnAccount {
const SnAccount._(); const SnAccount._();
const factory SnAccount({ const factory SnAccount({
@@ -16,7 +16,6 @@ class SnAccount with _$SnAccount {
required List<SnAccountContact>? contacts, required List<SnAccountContact>? contacts,
@Default("") String avatar, @Default("") String avatar,
@Default("") String banner, @Default("") String banner,
required String description,
required String name, required String name,
required String nick, required String nick,
@Default({}) Map<String, dynamic> permNodes, @Default({}) Map<String, dynamic> permNodes,
@@ -35,7 +34,7 @@ class SnAccount with _$SnAccount {
} }
@freezed @freezed
class SnAccountContact with _$SnAccountContact { abstract class SnAccountContact with _$SnAccountContact {
const factory SnAccountContact({ const factory SnAccountContact({
required int accountId, required int accountId,
required String content, required String content,
@@ -54,18 +53,24 @@ class SnAccountContact with _$SnAccountContact {
} }
@freezed @freezed
class SnAccountProfile with _$SnAccountProfile { abstract class SnAccountProfile with _$SnAccountProfile {
const factory SnAccountProfile({ const factory SnAccountProfile({
required int id, required int id,
required int accountId,
required DateTime? birthday,
required DateTime createdAt, required DateTime createdAt,
required DateTime updatedAt,
required DateTime? deletedAt, required DateTime? deletedAt,
required int experience,
required String firstName, required String firstName,
required String lastName, required String lastName,
required String description,
required String timeZone,
required String location,
required String pronouns,
required String gender,
@Default({}) Map<String, String> links,
required int experience,
required DateTime? lastSeenAt, required DateTime? lastSeenAt,
required DateTime updatedAt, required DateTime? birthday,
required int accountId,
}) = _SnAccountProfile; }) = _SnAccountProfile;
factory SnAccountProfile.fromJson(Map<String, Object?> json) => factory SnAccountProfile.fromJson(Map<String, Object?> json) =>
@@ -73,7 +78,7 @@ class SnAccountProfile with _$SnAccountProfile {
} }
@freezed @freezed
class SnRelationship with _$SnRelationship { abstract class SnRelationship with _$SnRelationship {
const factory SnRelationship({ const factory SnRelationship({
required int id, required int id,
required DateTime createdAt, required DateTime createdAt,
@@ -92,7 +97,7 @@ class SnRelationship with _$SnRelationship {
} }
@freezed @freezed
class SnAccountBadge with _$SnAccountBadge { abstract class SnAccountBadge with _$SnAccountBadge {
const factory SnAccountBadge({ const factory SnAccountBadge({
required int id, required int id,
required DateTime createdAt, required DateTime createdAt,
@@ -108,7 +113,7 @@ class SnAccountBadge with _$SnAccountBadge {
} }
@freezed @freezed
class SnAccountStatusInfo with _$SnAccountStatusInfo { abstract class SnAccountStatusInfo with _$SnAccountStatusInfo {
const factory SnAccountStatusInfo({ const factory SnAccountStatusInfo({
required bool isDisturbable, required bool isDisturbable,
required bool isOnline, required bool isOnline,
@@ -121,7 +126,7 @@ class SnAccountStatusInfo with _$SnAccountStatusInfo {
} }
@freezed @freezed
class SnAbuseReport with _$SnAbuseReport { abstract class SnAbuseReport with _$SnAbuseReport {
const factory SnAbuseReport({ const factory SnAbuseReport({
required int id, required int id,
required DateTime createdAt, required DateTime createdAt,

File diff suppressed because it is too large Load Diff

View File

@@ -6,8 +6,7 @@ part of 'account.dart';
// JsonSerializableGenerator // JsonSerializableGenerator
// ************************************************************************** // **************************************************************************
_$SnAccountImpl _$$SnAccountImplFromJson(Map<String, dynamic> json) => _SnAccount _$SnAccountFromJson(Map<String, dynamic> json) => _SnAccount(
_$SnAccountImpl(
id: (json['id'] as num).toInt(), id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String), createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String), updatedAt: DateTime.parse(json['updated_at'] as String),
@@ -22,7 +21,6 @@ _$SnAccountImpl _$$SnAccountImplFromJson(Map<String, dynamic> json) =>
.toList(), .toList(),
avatar: json['avatar'] as String? ?? "", avatar: json['avatar'] as String? ?? "",
banner: json['banner'] as String? ?? "", banner: json['banner'] as String? ?? "",
description: json['description'] as String,
name: json['name'] as String, name: json['name'] as String,
nick: json['nick'] as String, nick: json['nick'] as String,
permNodes: json['perm_nodes'] as Map<String, dynamic>? ?? const {}, permNodes: json['perm_nodes'] as Map<String, dynamic>? ?? const {},
@@ -43,7 +41,7 @@ _$SnAccountImpl _$$SnAccountImplFromJson(Map<String, dynamic> json) =>
automatedId: (json['automated_id'] as num?)?.toInt(), automatedId: (json['automated_id'] as num?)?.toInt(),
); );
Map<String, dynamic> _$$SnAccountImplToJson(_$SnAccountImpl instance) => Map<String, dynamic> _$SnAccountToJson(_SnAccount instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id, 'id': instance.id,
'created_at': instance.createdAt.toIso8601String(), 'created_at': instance.createdAt.toIso8601String(),
@@ -53,7 +51,6 @@ Map<String, dynamic> _$$SnAccountImplToJson(_$SnAccountImpl instance) =>
'contacts': instance.contacts?.map((e) => e.toJson()).toList(), 'contacts': instance.contacts?.map((e) => e.toJson()).toList(),
'avatar': instance.avatar, 'avatar': instance.avatar,
'banner': instance.banner, 'banner': instance.banner,
'description': instance.description,
'name': instance.name, 'name': instance.name,
'nick': instance.nick, 'nick': instance.nick,
'perm_nodes': instance.permNodes, 'perm_nodes': instance.permNodes,
@@ -67,9 +64,8 @@ Map<String, dynamic> _$$SnAccountImplToJson(_$SnAccountImpl instance) =>
'automated_id': instance.automatedId, 'automated_id': instance.automatedId,
}; };
_$SnAccountContactImpl _$$SnAccountContactImplFromJson( _SnAccountContact _$SnAccountContactFromJson(Map<String, dynamic> json) =>
Map<String, dynamic> json) => _SnAccountContact(
_$SnAccountContactImpl(
accountId: (json['account_id'] as num).toInt(), accountId: (json['account_id'] as num).toInt(),
content: json['content'] as String, content: json['content'] as String,
createdAt: DateTime.parse(json['created_at'] as String), createdAt: DateTime.parse(json['created_at'] as String),
@@ -86,8 +82,7 @@ _$SnAccountContactImpl _$$SnAccountContactImplFromJson(
: DateTime.parse(json['verified_at'] as String), : DateTime.parse(json['verified_at'] as String),
); );
Map<String, dynamic> _$$SnAccountContactImplToJson( Map<String, dynamic> _$SnAccountContactToJson(_SnAccountContact instance) =>
_$SnAccountContactImpl instance) =>
<String, dynamic>{ <String, dynamic>{
'account_id': instance.accountId, 'account_id': instance.accountId,
'content': instance.content, 'content': instance.content,
@@ -101,44 +96,57 @@ Map<String, dynamic> _$$SnAccountContactImplToJson(
'verified_at': instance.verifiedAt?.toIso8601String(), 'verified_at': instance.verifiedAt?.toIso8601String(),
}; };
_$SnAccountProfileImpl _$$SnAccountProfileImplFromJson( _SnAccountProfile _$SnAccountProfileFromJson(Map<String, dynamic> json) =>
Map<String, dynamic> json) => _SnAccountProfile(
_$SnAccountProfileImpl(
id: (json['id'] as num).toInt(), id: (json['id'] as num).toInt(),
accountId: (json['account_id'] as num).toInt(),
birthday: json['birthday'] == null
? null
: DateTime.parse(json['birthday'] as String),
createdAt: DateTime.parse(json['created_at'] as String), createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt: json['deleted_at'] == null deletedAt: json['deleted_at'] == null
? null ? null
: DateTime.parse(json['deleted_at'] as String), : DateTime.parse(json['deleted_at'] as String),
experience: (json['experience'] as num).toInt(),
firstName: json['first_name'] as String, firstName: json['first_name'] as String,
lastName: json['last_name'] as String, lastName: json['last_name'] as String,
description: json['description'] as String,
timeZone: json['time_zone'] as String,
location: json['location'] as String,
pronouns: json['pronouns'] as String,
gender: json['gender'] as String,
links: (json['links'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as String),
) ??
const {},
experience: (json['experience'] as num).toInt(),
lastSeenAt: json['last_seen_at'] == null lastSeenAt: json['last_seen_at'] == null
? null ? null
: DateTime.parse(json['last_seen_at'] as String), : DateTime.parse(json['last_seen_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String), birthday: json['birthday'] == null
? null
: DateTime.parse(json['birthday'] as String),
accountId: (json['account_id'] as num).toInt(),
); );
Map<String, dynamic> _$$SnAccountProfileImplToJson( Map<String, dynamic> _$SnAccountProfileToJson(_SnAccountProfile instance) =>
_$SnAccountProfileImpl instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id, 'id': instance.id,
'account_id': instance.accountId,
'birthday': instance.birthday?.toIso8601String(),
'created_at': instance.createdAt.toIso8601String(), 'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(), 'deleted_at': instance.deletedAt?.toIso8601String(),
'experience': instance.experience,
'first_name': instance.firstName, 'first_name': instance.firstName,
'last_name': instance.lastName, 'last_name': instance.lastName,
'description': instance.description,
'time_zone': instance.timeZone,
'location': instance.location,
'pronouns': instance.pronouns,
'gender': instance.gender,
'links': instance.links,
'experience': instance.experience,
'last_seen_at': instance.lastSeenAt?.toIso8601String(), 'last_seen_at': instance.lastSeenAt?.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(), 'birthday': instance.birthday?.toIso8601String(),
'account_id': instance.accountId,
}; };
_$SnRelationshipImpl _$$SnRelationshipImplFromJson(Map<String, dynamic> json) => _SnRelationship _$SnRelationshipFromJson(Map<String, dynamic> json) =>
_$SnRelationshipImpl( _SnRelationship(
id: (json['id'] as num).toInt(), id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String), createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String), updatedAt: DateTime.parse(json['updated_at'] as String),
@@ -157,8 +165,7 @@ _$SnRelationshipImpl _$$SnRelationshipImplFromJson(Map<String, dynamic> json) =>
permNodes: json['perm_nodes'] as Map<String, dynamic>? ?? const {}, permNodes: json['perm_nodes'] as Map<String, dynamic>? ?? const {},
); );
Map<String, dynamic> _$$SnRelationshipImplToJson( Map<String, dynamic> _$SnRelationshipToJson(_SnRelationship instance) =>
_$SnRelationshipImpl instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id, 'id': instance.id,
'created_at': instance.createdAt.toIso8601String(), 'created_at': instance.createdAt.toIso8601String(),
@@ -172,8 +179,8 @@ Map<String, dynamic> _$$SnRelationshipImplToJson(
'perm_nodes': instance.permNodes, 'perm_nodes': instance.permNodes,
}; };
_$SnAccountBadgeImpl _$$SnAccountBadgeImplFromJson(Map<String, dynamic> json) => _SnAccountBadge _$SnAccountBadgeFromJson(Map<String, dynamic> json) =>
_$SnAccountBadgeImpl( _SnAccountBadge(
id: (json['id'] as num).toInt(), id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String), createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String), updatedAt: DateTime.parse(json['updated_at'] as String),
@@ -183,8 +190,7 @@ _$SnAccountBadgeImpl _$$SnAccountBadgeImplFromJson(Map<String, dynamic> json) =>
metadata: json['metadata'] as Map<String, dynamic>? ?? const {}, metadata: json['metadata'] as Map<String, dynamic>? ?? const {},
); );
Map<String, dynamic> _$$SnAccountBadgeImplToJson( Map<String, dynamic> _$SnAccountBadgeToJson(_SnAccountBadge instance) =>
_$SnAccountBadgeImpl instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id, 'id': instance.id,
'created_at': instance.createdAt.toIso8601String(), 'created_at': instance.createdAt.toIso8601String(),
@@ -195,9 +201,8 @@ Map<String, dynamic> _$$SnAccountBadgeImplToJson(
'metadata': instance.metadata, 'metadata': instance.metadata,
}; };
_$SnAccountStatusInfoImpl _$$SnAccountStatusInfoImplFromJson( _SnAccountStatusInfo _$SnAccountStatusInfoFromJson(Map<String, dynamic> json) =>
Map<String, dynamic> json) => _SnAccountStatusInfo(
_$SnAccountStatusInfoImpl(
isDisturbable: json['is_disturbable'] as bool, isDisturbable: json['is_disturbable'] as bool,
isOnline: json['is_online'] as bool, isOnline: json['is_online'] as bool,
lastSeenAt: json['last_seen_at'] == null lastSeenAt: json['last_seen_at'] == null
@@ -206,8 +211,8 @@ _$SnAccountStatusInfoImpl _$$SnAccountStatusInfoImplFromJson(
status: json['status'], status: json['status'],
); );
Map<String, dynamic> _$$SnAccountStatusInfoImplToJson( Map<String, dynamic> _$SnAccountStatusInfoToJson(
_$SnAccountStatusInfoImpl instance) => _SnAccountStatusInfo instance) =>
<String, dynamic>{ <String, dynamic>{
'is_disturbable': instance.isDisturbable, 'is_disturbable': instance.isDisturbable,
'is_online': instance.isOnline, 'is_online': instance.isOnline,
@@ -215,8 +220,8 @@ Map<String, dynamic> _$$SnAccountStatusInfoImplToJson(
'status': instance.status, 'status': instance.status,
}; };
_$SnAbuseReportImpl _$$SnAbuseReportImplFromJson(Map<String, dynamic> json) => _SnAbuseReport _$SnAbuseReportFromJson(Map<String, dynamic> json) =>
_$SnAbuseReportImpl( _SnAbuseReport(
id: (json['id'] as num).toInt(), id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String), createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String), updatedAt: DateTime.parse(json['updated_at'] as String),
@@ -229,7 +234,7 @@ _$SnAbuseReportImpl _$$SnAbuseReportImplFromJson(Map<String, dynamic> json) =>
accountId: (json['account_id'] as num).toInt(), accountId: (json['account_id'] as num).toInt(),
); );
Map<String, dynamic> _$$SnAbuseReportImplToJson(_$SnAbuseReportImpl instance) => Map<String, dynamic> _$SnAbuseReportToJson(_SnAbuseReport instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id, 'id': instance.id,
'created_at': instance.createdAt.toIso8601String(), 'created_at': instance.createdAt.toIso8601String(),

View File

@@ -12,7 +12,7 @@ enum SnMediaType {
} }
@freezed @freezed
class SnAttachment with _$SnAttachment { abstract class SnAttachment with _$SnAttachment {
const SnAttachment._(); const SnAttachment._();
const factory SnAttachment({ const factory SnAttachment({
@@ -65,7 +65,7 @@ class SnAttachment with _$SnAttachment {
} }
@freezed @freezed
class SnAttachmentFragment with _$SnAttachmentFragment { abstract class SnAttachmentFragment with _$SnAttachmentFragment {
const SnAttachmentFragment._(); const SnAttachmentFragment._();
const factory SnAttachmentFragment({ const factory SnAttachmentFragment({
@@ -96,7 +96,7 @@ class SnAttachmentFragment with _$SnAttachmentFragment {
} }
@freezed @freezed
class SnAttachmentPool with _$SnAttachmentPool { abstract class SnAttachmentPool with _$SnAttachmentPool {
const factory SnAttachmentPool({ const factory SnAttachmentPool({
required int id, required int id,
required DateTime createdAt, required DateTime createdAt,
@@ -113,7 +113,7 @@ class SnAttachmentPool with _$SnAttachmentPool {
} }
@freezed @freezed
class SnAttachmentDestination with _$SnAttachmentDestination { abstract class SnAttachmentDestination with _$SnAttachmentDestination {
const factory SnAttachmentDestination({ const factory SnAttachmentDestination({
@Default(0) int id, @Default(0) int id,
required String type, required String type,
@@ -126,7 +126,7 @@ class SnAttachmentDestination with _$SnAttachmentDestination {
} }
@freezed @freezed
class SnAttachmentBoost with _$SnAttachmentBoost { abstract class SnAttachmentBoost with _$SnAttachmentBoost {
const factory SnAttachmentBoost({ const factory SnAttachmentBoost({
required int id, required int id,
required DateTime createdAt, required DateTime createdAt,
@@ -143,7 +143,7 @@ class SnAttachmentBoost with _$SnAttachmentBoost {
} }
@freezed @freezed
class SnSticker with _$SnSticker { abstract class SnSticker with _$SnSticker {
const factory SnSticker({ const factory SnSticker({
required int id, required int id,
required DateTime createdAt, required DateTime createdAt,
@@ -162,7 +162,7 @@ class SnSticker with _$SnSticker {
} }
@freezed @freezed
class SnStickerPack with _$SnStickerPack { abstract class SnStickerPack with _$SnStickerPack {
const factory SnStickerPack({ const factory SnStickerPack({
required int id, required int id,
required DateTime createdAt, required DateTime createdAt,
@@ -179,7 +179,7 @@ class SnStickerPack with _$SnStickerPack {
} }
@freezed @freezed
class SnAttachmentBilling with _$SnAttachmentBilling { abstract class SnAttachmentBilling with _$SnAttachmentBilling {
const factory SnAttachmentBilling({ const factory SnAttachmentBilling({
required int currentBytes, required int currentBytes,
required int discountFileSize, required int discountFileSize,

File diff suppressed because it is too large Load Diff

View File

@@ -6,8 +6,8 @@ part of 'attachment.dart';
// JsonSerializableGenerator // JsonSerializableGenerator
// ************************************************************************** // **************************************************************************
_$SnAttachmentImpl _$$SnAttachmentImplFromJson(Map<String, dynamic> json) => _SnAttachment _$SnAttachmentFromJson(Map<String, dynamic> json) =>
_$SnAttachmentImpl( _SnAttachment(
id: (json['id'] as num).toInt(), id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String), createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String), updatedAt: DateTime.parse(json['updated_at'] as String),
@@ -57,7 +57,7 @@ _$SnAttachmentImpl _$$SnAttachmentImplFromJson(Map<String, dynamic> json) =>
metadata: json['metadata'] as Map<String, dynamic>? ?? const {}, metadata: json['metadata'] as Map<String, dynamic>? ?? const {},
); );
Map<String, dynamic> _$$SnAttachmentImplToJson(_$SnAttachmentImpl instance) => Map<String, dynamic> _$SnAttachmentToJson(_SnAttachment instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id, 'id': instance.id,
'created_at': instance.createdAt.toIso8601String(), 'created_at': instance.createdAt.toIso8601String(),
@@ -92,9 +92,9 @@ Map<String, dynamic> _$$SnAttachmentImplToJson(_$SnAttachmentImpl instance) =>
'metadata': instance.metadata, 'metadata': instance.metadata,
}; };
_$SnAttachmentFragmentImpl _$$SnAttachmentFragmentImplFromJson( _SnAttachmentFragment _$SnAttachmentFragmentFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
_$SnAttachmentFragmentImpl( _SnAttachmentFragment(
id: (json['id'] as num).toInt(), id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String), createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String), updatedAt: DateTime.parse(json['updated_at'] as String),
@@ -119,8 +119,8 @@ _$SnAttachmentFragmentImpl _$$SnAttachmentFragmentImplFromJson(
const [], const [],
); );
Map<String, dynamic> _$$SnAttachmentFragmentImplToJson( Map<String, dynamic> _$SnAttachmentFragmentToJson(
_$SnAttachmentFragmentImpl instance) => _SnAttachmentFragment instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id, 'id': instance.id,
'created_at': instance.createdAt.toIso8601String(), 'created_at': instance.createdAt.toIso8601String(),
@@ -138,9 +138,8 @@ Map<String, dynamic> _$$SnAttachmentFragmentImplToJson(
'file_chunks_missing': instance.fileChunksMissing, 'file_chunks_missing': instance.fileChunksMissing,
}; };
_$SnAttachmentPoolImpl _$$SnAttachmentPoolImplFromJson( _SnAttachmentPool _$SnAttachmentPoolFromJson(Map<String, dynamic> json) =>
Map<String, dynamic> json) => _SnAttachmentPool(
_$SnAttachmentPoolImpl(
id: (json['id'] as num).toInt(), id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String), createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String), updatedAt: DateTime.parse(json['updated_at'] as String),
@@ -154,8 +153,7 @@ _$SnAttachmentPoolImpl _$$SnAttachmentPoolImplFromJson(
accountId: (json['account_id'] as num?)?.toInt(), accountId: (json['account_id'] as num?)?.toInt(),
); );
Map<String, dynamic> _$$SnAttachmentPoolImplToJson( Map<String, dynamic> _$SnAttachmentPoolToJson(_SnAttachmentPool instance) =>
_$SnAttachmentPoolImpl instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id, 'id': instance.id,
'created_at': instance.createdAt.toIso8601String(), 'created_at': instance.createdAt.toIso8601String(),
@@ -168,9 +166,9 @@ Map<String, dynamic> _$$SnAttachmentPoolImplToJson(
'account_id': instance.accountId, 'account_id': instance.accountId,
}; };
_$SnAttachmentDestinationImpl _$$SnAttachmentDestinationImplFromJson( _SnAttachmentDestination _$SnAttachmentDestinationFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
_$SnAttachmentDestinationImpl( _SnAttachmentDestination(
id: (json['id'] as num?)?.toInt() ?? 0, id: (json['id'] as num?)?.toInt() ?? 0,
type: json['type'] as String, type: json['type'] as String,
label: json['label'] as String, label: json['label'] as String,
@@ -178,8 +176,8 @@ _$SnAttachmentDestinationImpl _$$SnAttachmentDestinationImplFromJson(
isBoost: json['is_boost'] as bool, isBoost: json['is_boost'] as bool,
); );
Map<String, dynamic> _$$SnAttachmentDestinationImplToJson( Map<String, dynamic> _$SnAttachmentDestinationToJson(
_$SnAttachmentDestinationImpl instance) => _SnAttachmentDestination instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id, 'id': instance.id,
'type': instance.type, 'type': instance.type,
@@ -188,9 +186,8 @@ Map<String, dynamic> _$$SnAttachmentDestinationImplToJson(
'is_boost': instance.isBoost, 'is_boost': instance.isBoost,
}; };
_$SnAttachmentBoostImpl _$$SnAttachmentBoostImplFromJson( _SnAttachmentBoost _$SnAttachmentBoostFromJson(Map<String, dynamic> json) =>
Map<String, dynamic> json) => _SnAttachmentBoost(
_$SnAttachmentBoostImpl(
id: (json['id'] as num).toInt(), id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String), createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String), updatedAt: DateTime.parse(json['updated_at'] as String),
@@ -205,8 +202,7 @@ _$SnAttachmentBoostImpl _$$SnAttachmentBoostImplFromJson(
account: (json['account'] as num).toInt(), account: (json['account'] as num).toInt(),
); );
Map<String, dynamic> _$$SnAttachmentBoostImplToJson( Map<String, dynamic> _$SnAttachmentBoostToJson(_SnAttachmentBoost instance) =>
_$SnAttachmentBoostImpl instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id, 'id': instance.id,
'created_at': instance.createdAt.toIso8601String(), 'created_at': instance.createdAt.toIso8601String(),
@@ -219,8 +215,7 @@ Map<String, dynamic> _$$SnAttachmentBoostImplToJson(
'account': instance.account, 'account': instance.account,
}; };
_$SnStickerImpl _$$SnStickerImplFromJson(Map<String, dynamic> json) => _SnSticker _$SnStickerFromJson(Map<String, dynamic> json) => _SnSticker(
_$SnStickerImpl(
id: (json['id'] as num).toInt(), id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String), createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String), updatedAt: DateTime.parse(json['updated_at'] as String),
@@ -237,7 +232,7 @@ _$SnStickerImpl _$$SnStickerImplFromJson(Map<String, dynamic> json) =>
accountId: (json['account_id'] as num).toInt(), accountId: (json['account_id'] as num).toInt(),
); );
Map<String, dynamic> _$$SnStickerImplToJson(_$SnStickerImpl instance) => Map<String, dynamic> _$SnStickerToJson(_SnSticker instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id, 'id': instance.id,
'created_at': instance.createdAt.toIso8601String(), 'created_at': instance.createdAt.toIso8601String(),
@@ -252,8 +247,8 @@ Map<String, dynamic> _$$SnStickerImplToJson(_$SnStickerImpl instance) =>
'account_id': instance.accountId, 'account_id': instance.accountId,
}; };
_$SnStickerPackImpl _$$SnStickerPackImplFromJson(Map<String, dynamic> json) => _SnStickerPack _$SnStickerPackFromJson(Map<String, dynamic> json) =>
_$SnStickerPackImpl( _SnStickerPack(
id: (json['id'] as num).toInt(), id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String), createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String), updatedAt: DateTime.parse(json['updated_at'] as String),
@@ -269,7 +264,7 @@ _$SnStickerPackImpl _$$SnStickerPackImplFromJson(Map<String, dynamic> json) =>
accountId: (json['account_id'] as num).toInt(), accountId: (json['account_id'] as num).toInt(),
); );
Map<String, dynamic> _$$SnStickerPackImplToJson(_$SnStickerPackImpl instance) => Map<String, dynamic> _$SnStickerPackToJson(_SnStickerPack instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id, 'id': instance.id,
'created_at': instance.createdAt.toIso8601String(), 'created_at': instance.createdAt.toIso8601String(),
@@ -282,16 +277,15 @@ Map<String, dynamic> _$$SnStickerPackImplToJson(_$SnStickerPackImpl instance) =>
'account_id': instance.accountId, 'account_id': instance.accountId,
}; };
_$SnAttachmentBillingImpl _$$SnAttachmentBillingImplFromJson( _SnAttachmentBilling _$SnAttachmentBillingFromJson(Map<String, dynamic> json) =>
Map<String, dynamic> json) => _SnAttachmentBilling(
_$SnAttachmentBillingImpl(
currentBytes: (json['current_bytes'] as num).toInt(), currentBytes: (json['current_bytes'] as num).toInt(),
discountFileSize: (json['discount_file_size'] as num).toInt(), discountFileSize: (json['discount_file_size'] as num).toInt(),
includedRatio: (json['included_ratio'] as num).toDouble(), includedRatio: (json['included_ratio'] as num).toDouble(),
); );
Map<String, dynamic> _$$SnAttachmentBillingImplToJson( Map<String, dynamic> _$SnAttachmentBillingToJson(
_$SnAttachmentBillingImpl instance) => _SnAttachmentBilling instance) =>
<String, dynamic>{ <String, dynamic>{
'current_bytes': instance.currentBytes, 'current_bytes': instance.currentBytes,
'discount_file_size': instance.discountFileSize, 'discount_file_size': instance.discountFileSize,

View File

@@ -4,7 +4,7 @@ part 'auth.freezed.dart';
part 'auth.g.dart'; part 'auth.g.dart';
@freezed @freezed
class SnAuthResult with _$SnAuthResult { abstract class SnAuthResult with _$SnAuthResult {
const factory SnAuthResult({ const factory SnAuthResult({
required bool isFinished, required bool isFinished,
required SnAuthTicket? ticket, required SnAuthTicket? ticket,
@@ -15,7 +15,7 @@ class SnAuthResult with _$SnAuthResult {
} }
@freezed @freezed
class SnAuthTicket with _$SnAuthTicket { abstract class SnAuthTicket with _$SnAuthTicket {
const factory SnAuthTicket({ const factory SnAuthTicket({
required int id, required int id,
required DateTime createdAt, required DateTime createdAt,
@@ -41,7 +41,7 @@ class SnAuthTicket with _$SnAuthTicket {
} }
@freezed @freezed
class SnAuthFactor with _$SnAuthFactor { abstract class SnAuthFactor with _$SnAuthFactor {
const factory SnAuthFactor({ const factory SnAuthFactor({
required int id, required int id,
required DateTime createdAt, required DateTime createdAt,

File diff suppressed because it is too large Load Diff

View File

@@ -6,22 +6,22 @@ part of 'auth.dart';
// JsonSerializableGenerator // JsonSerializableGenerator
// ************************************************************************** // **************************************************************************
_$SnAuthResultImpl _$$SnAuthResultImplFromJson(Map<String, dynamic> json) => _SnAuthResult _$SnAuthResultFromJson(Map<String, dynamic> json) =>
_$SnAuthResultImpl( _SnAuthResult(
isFinished: json['is_finished'] as bool, isFinished: json['is_finished'] as bool,
ticket: json['ticket'] == null ticket: json['ticket'] == null
? null ? null
: SnAuthTicket.fromJson(json['ticket'] as Map<String, dynamic>), : SnAuthTicket.fromJson(json['ticket'] as Map<String, dynamic>),
); );
Map<String, dynamic> _$$SnAuthResultImplToJson(_$SnAuthResultImpl instance) => Map<String, dynamic> _$SnAuthResultToJson(_SnAuthResult instance) =>
<String, dynamic>{ <String, dynamic>{
'is_finished': instance.isFinished, 'is_finished': instance.isFinished,
'ticket': instance.ticket?.toJson(), 'ticket': instance.ticket?.toJson(),
}; };
_$SnAuthTicketImpl _$$SnAuthTicketImplFromJson(Map<String, dynamic> json) => _SnAuthTicket _$SnAuthTicketFromJson(Map<String, dynamic> json) =>
_$SnAuthTicketImpl( _SnAuthTicket(
id: (json['id'] as num).toInt(), id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String), createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String), updatedAt: DateTime.parse(json['updated_at'] as String),
@@ -52,7 +52,7 @@ _$SnAuthTicketImpl _$$SnAuthTicketImplFromJson(Map<String, dynamic> json) =>
const [], const [],
); );
Map<String, dynamic> _$$SnAuthTicketImplToJson(_$SnAuthTicketImpl instance) => Map<String, dynamic> _$SnAuthTicketToJson(_SnAuthTicket instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id, 'id': instance.id,
'created_at': instance.createdAt.toIso8601String(), 'created_at': instance.createdAt.toIso8601String(),
@@ -73,8 +73,8 @@ Map<String, dynamic> _$$SnAuthTicketImplToJson(_$SnAuthTicketImpl instance) =>
'factor_trail': instance.factorTrail, 'factor_trail': instance.factorTrail,
}; };
_$SnAuthFactorImpl _$$SnAuthFactorImplFromJson(Map<String, dynamic> json) => _SnAuthFactor _$SnAuthFactorFromJson(Map<String, dynamic> json) =>
_$SnAuthFactorImpl( _SnAuthFactor(
id: (json['id'] as num).toInt(), id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String), createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String), updatedAt: DateTime.parse(json['updated_at'] as String),
@@ -86,7 +86,7 @@ _$SnAuthFactorImpl _$$SnAuthFactorImplFromJson(Map<String, dynamic> json) =>
accountId: (json['account_id'] as num?)?.toInt(), accountId: (json['account_id'] as num?)?.toInt(),
); );
Map<String, dynamic> _$$SnAuthFactorImplToJson(_$SnAuthFactorImpl instance) => Map<String, dynamic> _$SnAuthFactorToJson(_SnAuthFactor instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id, 'id': instance.id,
'created_at': instance.createdAt.toIso8601String(), 'created_at': instance.createdAt.toIso8601String(),

View File

@@ -8,7 +8,7 @@ part 'chat.freezed.dart';
part 'chat.g.dart'; part 'chat.g.dart';
@freezed @freezed
class SnChannel with _$SnChannel { abstract class SnChannel with _$SnChannel {
const SnChannel._(); const SnChannel._();
const factory SnChannel({ const factory SnChannel({
@@ -37,7 +37,7 @@ class SnChannel with _$SnChannel {
} }
@freezed @freezed
class SnChannelMember with _$SnChannelMember { abstract class SnChannelMember with _$SnChannelMember {
const SnChannelMember._(); const SnChannelMember._();
const factory SnChannelMember({ const factory SnChannelMember({
@@ -61,7 +61,7 @@ class SnChannelMember with _$SnChannelMember {
} }
@freezed @freezed
class SnChatMessage with _$SnChatMessage { abstract class SnChatMessage with _$SnChatMessage {
const SnChatMessage._(); const SnChatMessage._();
const factory SnChatMessage({ const factory SnChatMessage({
@@ -86,7 +86,7 @@ class SnChatMessage with _$SnChatMessage {
} }
@freezed @freezed
class SnChatMessagePreload with _$SnChatMessagePreload { abstract class SnChatMessagePreload with _$SnChatMessagePreload {
const SnChatMessagePreload._(); const SnChatMessagePreload._();
const factory SnChatMessagePreload({ const factory SnChatMessagePreload({
@@ -99,7 +99,7 @@ class SnChatMessagePreload with _$SnChatMessagePreload {
} }
@freezed @freezed
class SnChatCall with _$SnChatCall { abstract class SnChatCall with _$SnChatCall {
const factory SnChatCall({ const factory SnChatCall({
required int id, required int id,
required DateTime createdAt, required DateTime createdAt,

File diff suppressed because it is too large Load Diff

View File

@@ -6,8 +6,7 @@ part of 'chat.dart';
// JsonSerializableGenerator // JsonSerializableGenerator
// ************************************************************************** // **************************************************************************
_$SnChannelImpl _$$SnChannelImplFromJson(Map<String, dynamic> json) => _SnChannel _$SnChannelFromJson(Map<String, dynamic> json) => _SnChannel(
_$SnChannelImpl(
id: (json['id'] as num).toInt(), id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String), createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String), updatedAt: DateTime.parse(json['updated_at'] as String),
@@ -31,7 +30,7 @@ _$SnChannelImpl _$$SnChannelImplFromJson(Map<String, dynamic> json) =>
isCommunity: json['is_community'] as bool, isCommunity: json['is_community'] as bool,
); );
Map<String, dynamic> _$$SnChannelImplToJson(_$SnChannelImpl instance) => Map<String, dynamic> _$SnChannelToJson(_SnChannel instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id, 'id': instance.id,
'created_at': instance.createdAt.toIso8601String(), 'created_at': instance.createdAt.toIso8601String(),
@@ -50,9 +49,8 @@ Map<String, dynamic> _$$SnChannelImplToJson(_$SnChannelImpl instance) =>
'is_community': instance.isCommunity, 'is_community': instance.isCommunity,
}; };
_$SnChannelMemberImpl _$$SnChannelMemberImplFromJson( _SnChannelMember _$SnChannelMemberFromJson(Map<String, dynamic> json) =>
Map<String, dynamic> json) => _SnChannelMember(
_$SnChannelMemberImpl(
id: (json['id'] as num).toInt(), id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String), createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String), updatedAt: DateTime.parse(json['updated_at'] as String),
@@ -74,8 +72,7 @@ _$SnChannelMemberImpl _$$SnChannelMemberImplFromJson(
events: json['events'], events: json['events'],
); );
Map<String, dynamic> _$$SnChannelMemberImplToJson( Map<String, dynamic> _$SnChannelMemberToJson(_SnChannelMember instance) =>
_$SnChannelMemberImpl instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id, 'id': instance.id,
'created_at': instance.createdAt.toIso8601String(), 'created_at': instance.createdAt.toIso8601String(),
@@ -92,8 +89,8 @@ Map<String, dynamic> _$$SnChannelMemberImplToJson(
'events': instance.events, 'events': instance.events,
}; };
_$SnChatMessageImpl _$$SnChatMessageImplFromJson(Map<String, dynamic> json) => _SnChatMessage _$SnChatMessageFromJson(Map<String, dynamic> json) =>
_$SnChatMessageImpl( _SnChatMessage(
id: (json['id'] as num).toInt(), id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String), createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String), updatedAt: DateTime.parse(json['updated_at'] as String),
@@ -115,7 +112,7 @@ _$SnChatMessageImpl _$$SnChatMessageImplFromJson(Map<String, dynamic> json) =>
json['preload'] as Map<String, dynamic>), json['preload'] as Map<String, dynamic>),
); );
Map<String, dynamic> _$$SnChatMessageImplToJson(_$SnChatMessageImpl instance) => Map<String, dynamic> _$SnChatMessageToJson(_SnChatMessage instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id, 'id': instance.id,
'created_at': instance.createdAt.toIso8601String(), 'created_at': instance.createdAt.toIso8601String(),
@@ -133,9 +130,9 @@ Map<String, dynamic> _$$SnChatMessageImplToJson(_$SnChatMessageImpl instance) =>
'preload': instance.preload?.toJson(), 'preload': instance.preload?.toJson(),
}; };
_$SnChatMessagePreloadImpl _$$SnChatMessagePreloadImplFromJson( _SnChatMessagePreload _$SnChatMessagePreloadFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
_$SnChatMessagePreloadImpl( _SnChatMessagePreload(
attachments: (json['attachments'] as List<dynamic>?) attachments: (json['attachments'] as List<dynamic>?)
?.map((e) => e == null ?.map((e) => e == null
? null ? null
@@ -146,15 +143,14 @@ _$SnChatMessagePreloadImpl _$$SnChatMessagePreloadImplFromJson(
: SnChatMessage.fromJson(json['quote_event'] as Map<String, dynamic>), : SnChatMessage.fromJson(json['quote_event'] as Map<String, dynamic>),
); );
Map<String, dynamic> _$$SnChatMessagePreloadImplToJson( Map<String, dynamic> _$SnChatMessagePreloadToJson(
_$SnChatMessagePreloadImpl instance) => _SnChatMessagePreload instance) =>
<String, dynamic>{ <String, dynamic>{
'attachments': instance.attachments?.map((e) => e?.toJson()).toList(), 'attachments': instance.attachments?.map((e) => e?.toJson()).toList(),
'quote_event': instance.quoteEvent?.toJson(), 'quote_event': instance.quoteEvent?.toJson(),
}; };
_$SnChatCallImpl _$$SnChatCallImplFromJson(Map<String, dynamic> json) => _SnChatCall _$SnChatCallFromJson(Map<String, dynamic> json) => _SnChatCall(
_$SnChatCallImpl(
id: (json['id'] as num).toInt(), id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String), createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String), updatedAt: DateTime.parse(json['updated_at'] as String),
@@ -172,7 +168,7 @@ _$SnChatCallImpl _$$SnChatCallImplFromJson(Map<String, dynamic> json) =>
participants: json['participants'] as List<dynamic>? ?? const [], participants: json['participants'] as List<dynamic>? ?? const [],
); );
Map<String, dynamic> _$$SnChatCallImplToJson(_$SnChatCallImpl instance) => Map<String, dynamic> _$SnChatCallToJson(_SnChatCall instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id, 'id': instance.id,
'created_at': instance.createdAt.toIso8601String(), 'created_at': instance.createdAt.toIso8601String(),

View File

@@ -14,7 +14,7 @@ final List<String> kCheckInResultTierSymbols = [
].map((e) => e.tr()).toList(); ].map((e) => e.tr()).toList();
@freezed @freezed
class SnCheckInRecord with _$SnCheckInRecord { abstract class SnCheckInRecord with _$SnCheckInRecord {
const SnCheckInRecord._(); const SnCheckInRecord._();
const factory SnCheckInRecord({ const factory SnCheckInRecord({

View File

@@ -1,3 +1,4 @@
// dart format width=80
// coverage:ignore-file // coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint // ignore_for_file: type=lint
@@ -9,128 +10,81 @@ part of 'check_in.dart';
// FreezedGenerator // FreezedGenerator
// ************************************************************************** // **************************************************************************
// dart format off
T _$identity<T>(T value) => value; T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
SnCheckInRecord _$SnCheckInRecordFromJson(Map<String, dynamic> json) {
return _SnCheckInRecord.fromJson(json);
}
/// @nodoc /// @nodoc
mixin _$SnCheckInRecord { mixin _$SnCheckInRecord {
int get id => throw _privateConstructorUsedError; int get id;
DateTime get createdAt => throw _privateConstructorUsedError; DateTime get createdAt;
DateTime get updatedAt => throw _privateConstructorUsedError; DateTime get updatedAt;
DateTime? get deletedAt => throw _privateConstructorUsedError; DateTime? get deletedAt;
int get resultTier => throw _privateConstructorUsedError; int get resultTier;
int get resultExperience => throw _privateConstructorUsedError; int get resultExperience;
double get resultCoin => throw _privateConstructorUsedError; double get resultCoin;
List<int> get resultModifiers => throw _privateConstructorUsedError; List<int> get resultModifiers;
int get accountId => throw _privateConstructorUsedError; int get accountId;
/// Serializes this SnCheckInRecord to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of SnCheckInRecord /// Create a copy of SnCheckInRecord
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
$SnCheckInRecordCopyWith<SnCheckInRecord> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $SnCheckInRecordCopyWith<$Res> {
factory $SnCheckInRecordCopyWith(
SnCheckInRecord value, $Res Function(SnCheckInRecord) then) =
_$SnCheckInRecordCopyWithImpl<$Res, SnCheckInRecord>;
@useResult
$Res call(
{int id,
DateTime createdAt,
DateTime updatedAt,
DateTime? deletedAt,
int resultTier,
int resultExperience,
double resultCoin,
List<int> resultModifiers,
int accountId});
}
/// @nodoc
class _$SnCheckInRecordCopyWithImpl<$Res, $Val extends SnCheckInRecord>
implements $SnCheckInRecordCopyWith<$Res> {
_$SnCheckInRecordCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of SnCheckInRecord
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
$SnCheckInRecordCopyWith<SnCheckInRecord> get copyWith =>
_$SnCheckInRecordCopyWithImpl<SnCheckInRecord>(
this as SnCheckInRecord, _$identity);
/// Serializes this SnCheckInRecord to a JSON map.
Map<String, dynamic> toJson();
@override @override
$Res call({ bool operator ==(Object other) {
Object? id = null, return identical(this, other) ||
Object? createdAt = null, (other.runtimeType == runtimeType &&
Object? updatedAt = null, other is SnCheckInRecord &&
Object? deletedAt = freezed, (identical(other.id, id) || other.id == id) &&
Object? resultTier = null, (identical(other.createdAt, createdAt) ||
Object? resultExperience = null, other.createdAt == createdAt) &&
Object? resultCoin = null, (identical(other.updatedAt, updatedAt) ||
Object? resultModifiers = null, other.updatedAt == updatedAt) &&
Object? accountId = null, (identical(other.deletedAt, deletedAt) ||
}) { other.deletedAt == deletedAt) &&
return _then(_value.copyWith( (identical(other.resultTier, resultTier) ||
id: null == id other.resultTier == resultTier) &&
? _value.id (identical(other.resultExperience, resultExperience) ||
: id // ignore: cast_nullable_to_non_nullable other.resultExperience == resultExperience) &&
as int, (identical(other.resultCoin, resultCoin) ||
createdAt: null == createdAt other.resultCoin == resultCoin) &&
? _value.createdAt const DeepCollectionEquality()
: createdAt // ignore: cast_nullable_to_non_nullable .equals(other.resultModifiers, resultModifiers) &&
as DateTime, (identical(other.accountId, accountId) ||
updatedAt: null == updatedAt other.accountId == accountId));
? _value.updatedAt }
: updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime, @JsonKey(includeFromJson: false, includeToJson: false)
deletedAt: freezed == deletedAt @override
? _value.deletedAt int get hashCode => Object.hash(
: deletedAt // ignore: cast_nullable_to_non_nullable runtimeType,
as DateTime?, id,
resultTier: null == resultTier createdAt,
? _value.resultTier updatedAt,
: resultTier // ignore: cast_nullable_to_non_nullable deletedAt,
as int, resultTier,
resultExperience: null == resultExperience resultExperience,
? _value.resultExperience resultCoin,
: resultExperience // ignore: cast_nullable_to_non_nullable const DeepCollectionEquality().hash(resultModifiers),
as int, accountId);
resultCoin: null == resultCoin
? _value.resultCoin @override
: resultCoin // ignore: cast_nullable_to_non_nullable String toString() {
as double, return 'SnCheckInRecord(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, resultTier: $resultTier, resultExperience: $resultExperience, resultCoin: $resultCoin, resultModifiers: $resultModifiers, accountId: $accountId)';
resultModifiers: null == resultModifiers
? _value.resultModifiers
: resultModifiers // ignore: cast_nullable_to_non_nullable
as List<int>,
accountId: null == accountId
? _value.accountId
: accountId // ignore: cast_nullable_to_non_nullable
as int,
) as $Val);
} }
} }
/// @nodoc /// @nodoc
abstract class _$$SnCheckInRecordImplCopyWith<$Res> abstract mixin class $SnCheckInRecordCopyWith<$Res> {
implements $SnCheckInRecordCopyWith<$Res> { factory $SnCheckInRecordCopyWith(
factory _$$SnCheckInRecordImplCopyWith(_$SnCheckInRecordImpl value, SnCheckInRecord value, $Res Function(SnCheckInRecord) _then) =
$Res Function(_$SnCheckInRecordImpl) then) = _$SnCheckInRecordCopyWithImpl;
__$$SnCheckInRecordImplCopyWithImpl<$Res>;
@override
@useResult @useResult
$Res call( $Res call(
{int id, {int id,
@@ -145,12 +99,12 @@ abstract class _$$SnCheckInRecordImplCopyWith<$Res>
} }
/// @nodoc /// @nodoc
class __$$SnCheckInRecordImplCopyWithImpl<$Res> class _$SnCheckInRecordCopyWithImpl<$Res>
extends _$SnCheckInRecordCopyWithImpl<$Res, _$SnCheckInRecordImpl> implements $SnCheckInRecordCopyWith<$Res> {
implements _$$SnCheckInRecordImplCopyWith<$Res> { _$SnCheckInRecordCopyWithImpl(this._self, this._then);
__$$SnCheckInRecordImplCopyWithImpl(
_$SnCheckInRecordImpl _value, $Res Function(_$SnCheckInRecordImpl) _then) final SnCheckInRecord _self;
: super(_value, _then); final $Res Function(SnCheckInRecord) _then;
/// Create a copy of SnCheckInRecord /// Create a copy of SnCheckInRecord
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@@ -167,41 +121,41 @@ class __$$SnCheckInRecordImplCopyWithImpl<$Res>
Object? resultModifiers = null, Object? resultModifiers = null,
Object? accountId = null, Object? accountId = null,
}) { }) {
return _then(_$SnCheckInRecordImpl( return _then(_self.copyWith(
id: null == id id: null == id
? _value.id ? _self.id
: id // ignore: cast_nullable_to_non_nullable : id // ignore: cast_nullable_to_non_nullable
as int, as int,
createdAt: null == createdAt createdAt: null == createdAt
? _value.createdAt ? _self.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime, as DateTime,
updatedAt: null == updatedAt updatedAt: null == updatedAt
? _value.updatedAt ? _self.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable : updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime, as DateTime,
deletedAt: freezed == deletedAt deletedAt: freezed == deletedAt
? _value.deletedAt ? _self.deletedAt
: deletedAt // ignore: cast_nullable_to_non_nullable : deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?, as DateTime?,
resultTier: null == resultTier resultTier: null == resultTier
? _value.resultTier ? _self.resultTier
: resultTier // ignore: cast_nullable_to_non_nullable : resultTier // ignore: cast_nullable_to_non_nullable
as int, as int,
resultExperience: null == resultExperience resultExperience: null == resultExperience
? _value.resultExperience ? _self.resultExperience
: resultExperience // ignore: cast_nullable_to_non_nullable : resultExperience // ignore: cast_nullable_to_non_nullable
as int, as int,
resultCoin: null == resultCoin resultCoin: null == resultCoin
? _value.resultCoin ? _self.resultCoin
: resultCoin // ignore: cast_nullable_to_non_nullable : resultCoin // ignore: cast_nullable_to_non_nullable
as double, as double,
resultModifiers: null == resultModifiers resultModifiers: null == resultModifiers
? _value._resultModifiers ? _self.resultModifiers
: resultModifiers // ignore: cast_nullable_to_non_nullable : resultModifiers // ignore: cast_nullable_to_non_nullable
as List<int>, as List<int>,
accountId: null == accountId accountId: null == accountId
? _value.accountId ? _self.accountId
: accountId // ignore: cast_nullable_to_non_nullable : accountId // ignore: cast_nullable_to_non_nullable
as int, as int,
)); ));
@@ -210,8 +164,8 @@ class __$$SnCheckInRecordImplCopyWithImpl<$Res>
/// @nodoc /// @nodoc
@JsonSerializable() @JsonSerializable()
class _$SnCheckInRecordImpl extends _SnCheckInRecord { class _SnCheckInRecord extends SnCheckInRecord {
const _$SnCheckInRecordImpl( const _SnCheckInRecord(
{required this.id, {required this.id,
required this.createdAt, required this.createdAt,
required this.updatedAt, required this.updatedAt,
@@ -223,9 +177,8 @@ class _$SnCheckInRecordImpl extends _SnCheckInRecord {
required this.accountId}) required this.accountId})
: _resultModifiers = resultModifiers, : _resultModifiers = resultModifiers,
super._(); super._();
factory _SnCheckInRecord.fromJson(Map<String, dynamic> json) =>
factory _$SnCheckInRecordImpl.fromJson(Map<String, dynamic> json) => _$SnCheckInRecordFromJson(json);
_$$SnCheckInRecordImplFromJson(json);
@override @override
final int id; final int id;
@@ -252,16 +205,26 @@ class _$SnCheckInRecordImpl extends _SnCheckInRecord {
@override @override
final int accountId; final int accountId;
/// Create a copy of SnCheckInRecord
/// with the given fields replaced by the non-null parameter values.
@override @override
String toString() { @JsonKey(includeFromJson: false, includeToJson: false)
return 'SnCheckInRecord(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, resultTier: $resultTier, resultExperience: $resultExperience, resultCoin: $resultCoin, resultModifiers: $resultModifiers, accountId: $accountId)'; @pragma('vm:prefer-inline')
_$SnCheckInRecordCopyWith<_SnCheckInRecord> get copyWith =>
__$SnCheckInRecordCopyWithImpl<_SnCheckInRecord>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$SnCheckInRecordToJson(
this,
);
} }
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || return identical(this, other) ||
(other.runtimeType == runtimeType && (other.runtimeType == runtimeType &&
other is _$SnCheckInRecordImpl && other is _SnCheckInRecord &&
(identical(other.id, id) || other.id == id) && (identical(other.id, id) || other.id == id) &&
(identical(other.createdAt, createdAt) || (identical(other.createdAt, createdAt) ||
other.createdAt == createdAt) && other.createdAt == createdAt) &&
@@ -295,62 +258,94 @@ class _$SnCheckInRecordImpl extends _SnCheckInRecord {
const DeepCollectionEquality().hash(_resultModifiers), const DeepCollectionEquality().hash(_resultModifiers),
accountId); accountId);
/// Create a copy of SnCheckInRecord
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override @override
@pragma('vm:prefer-inline') String toString() {
_$$SnCheckInRecordImplCopyWith<_$SnCheckInRecordImpl> get copyWith => return 'SnCheckInRecord(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, resultTier: $resultTier, resultExperience: $resultExperience, resultCoin: $resultCoin, resultModifiers: $resultModifiers, accountId: $accountId)';
__$$SnCheckInRecordImplCopyWithImpl<_$SnCheckInRecordImpl>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$SnCheckInRecordImplToJson(
this,
);
} }
} }
abstract class _SnCheckInRecord extends SnCheckInRecord { /// @nodoc
const factory _SnCheckInRecord( abstract mixin class _$SnCheckInRecordCopyWith<$Res>
{required final int id, implements $SnCheckInRecordCopyWith<$Res> {
required final DateTime createdAt, factory _$SnCheckInRecordCopyWith(
required final DateTime updatedAt, _SnCheckInRecord value, $Res Function(_SnCheckInRecord) _then) =
required final DateTime? deletedAt, __$SnCheckInRecordCopyWithImpl;
required final int resultTier, @override
required final int resultExperience, @useResult
required final double resultCoin, $Res call(
required final List<int> resultModifiers, {int id,
required final int accountId}) = _$SnCheckInRecordImpl; DateTime createdAt,
const _SnCheckInRecord._() : super._(); DateTime updatedAt,
DateTime? deletedAt,
int resultTier,
int resultExperience,
double resultCoin,
List<int> resultModifiers,
int accountId});
}
factory _SnCheckInRecord.fromJson(Map<String, dynamic> json) = /// @nodoc
_$SnCheckInRecordImpl.fromJson; class __$SnCheckInRecordCopyWithImpl<$Res>
implements _$SnCheckInRecordCopyWith<$Res> {
__$SnCheckInRecordCopyWithImpl(this._self, this._then);
@override final _SnCheckInRecord _self;
int get id; final $Res Function(_SnCheckInRecord) _then;
@override
DateTime get createdAt;
@override
DateTime get updatedAt;
@override
DateTime? get deletedAt;
@override
int get resultTier;
@override
int get resultExperience;
@override
double get resultCoin;
@override
List<int> get resultModifiers;
@override
int get accountId;
/// Create a copy of SnCheckInRecord /// Create a copy of SnCheckInRecord
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@override @override
@JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline')
_$$SnCheckInRecordImplCopyWith<_$SnCheckInRecordImpl> get copyWith => $Res call({
throw _privateConstructorUsedError; Object? id = null,
Object? createdAt = null,
Object? updatedAt = null,
Object? deletedAt = freezed,
Object? resultTier = null,
Object? resultExperience = null,
Object? resultCoin = null,
Object? resultModifiers = null,
Object? accountId = null,
}) {
return _then(_SnCheckInRecord(
id: null == id
? _self.id
: id // ignore: cast_nullable_to_non_nullable
as int,
createdAt: null == createdAt
? _self.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,
updatedAt: null == updatedAt
? _self.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,
deletedAt: freezed == deletedAt
? _self.deletedAt
: deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
resultTier: null == resultTier
? _self.resultTier
: resultTier // ignore: cast_nullable_to_non_nullable
as int,
resultExperience: null == resultExperience
? _self.resultExperience
: resultExperience // ignore: cast_nullable_to_non_nullable
as int,
resultCoin: null == resultCoin
? _self.resultCoin
: resultCoin // ignore: cast_nullable_to_non_nullable
as double,
resultModifiers: null == resultModifiers
? _self._resultModifiers
: resultModifiers // ignore: cast_nullable_to_non_nullable
as List<int>,
accountId: null == accountId
? _self.accountId
: accountId // ignore: cast_nullable_to_non_nullable
as int,
));
}
} }
// dart format on

View File

@@ -6,9 +6,8 @@ part of 'check_in.dart';
// JsonSerializableGenerator // JsonSerializableGenerator
// ************************************************************************** // **************************************************************************
_$SnCheckInRecordImpl _$$SnCheckInRecordImplFromJson( _SnCheckInRecord _$SnCheckInRecordFromJson(Map<String, dynamic> json) =>
Map<String, dynamic> json) => _SnCheckInRecord(
_$SnCheckInRecordImpl(
id: (json['id'] as num).toInt(), id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String), createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String), updatedAt: DateTime.parse(json['updated_at'] as String),
@@ -24,8 +23,7 @@ _$SnCheckInRecordImpl _$$SnCheckInRecordImplFromJson(
accountId: (json['account_id'] as num).toInt(), accountId: (json['account_id'] as num).toInt(),
); );
Map<String, dynamic> _$$SnCheckInRecordImplToJson( Map<String, dynamic> _$SnCheckInRecordToJson(_SnCheckInRecord instance) =>
_$SnCheckInRecordImpl instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id, 'id': instance.id,
'created_at': instance.createdAt.toIso8601String(), 'created_at': instance.createdAt.toIso8601String(),

View File

@@ -4,7 +4,7 @@ part 'link.g.dart';
part 'link.freezed.dart'; part 'link.freezed.dart';
@freezed @freezed
class SnLinkMeta with _$SnLinkMeta { abstract class SnLinkMeta with _$SnLinkMeta {
const SnLinkMeta._(); const SnLinkMeta._();
const factory SnLinkMeta({ const factory SnLinkMeta({

View File

@@ -1,3 +1,4 @@
// dart format width=80
// coverage:ignore-file // coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint // ignore_for_file: type=lint
@@ -9,332 +10,41 @@ part of 'link.dart';
// FreezedGenerator // FreezedGenerator
// ************************************************************************** // **************************************************************************
// dart format off
T _$identity<T>(T value) => value; T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
SnLinkMeta _$SnLinkMetaFromJson(Map<String, dynamic> json) {
return _SnLinkMeta.fromJson(json);
}
/// @nodoc /// @nodoc
mixin _$SnLinkMeta { mixin _$SnLinkMeta {
int get id => throw _privateConstructorUsedError; int get id;
DateTime get createdAt => throw _privateConstructorUsedError; DateTime get createdAt;
DateTime get updatedAt => throw _privateConstructorUsedError; DateTime get updatedAt;
DateTime? get deletedAt => throw _privateConstructorUsedError; DateTime? get deletedAt;
String get entryId => throw _privateConstructorUsedError; String get entryId;
String? get icon => throw _privateConstructorUsedError; String? get icon;
String get url => throw _privateConstructorUsedError; String get url;
String? get title => throw _privateConstructorUsedError; String? get title;
String? get image => throw _privateConstructorUsedError; String? get image;
String? get video => throw _privateConstructorUsedError; String? get video;
String? get audio => throw _privateConstructorUsedError; String? get audio;
String? get description => throw _privateConstructorUsedError; String? get description;
String? get siteName => throw _privateConstructorUsedError; String? get siteName;
String? get type => throw _privateConstructorUsedError; String? get type;
/// Serializes this SnLinkMeta to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of SnLinkMeta /// Create a copy of SnLinkMeta
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$SnLinkMetaCopyWith<SnLinkMeta> get copyWith => $SnLinkMetaCopyWith<SnLinkMeta> get copyWith =>
throw _privateConstructorUsedError; _$SnLinkMetaCopyWithImpl<SnLinkMeta>(this as SnLinkMeta, _$identity);
}
/// @nodoc /// Serializes this SnLinkMeta to a JSON map.
abstract class $SnLinkMetaCopyWith<$Res> { Map<String, dynamic> toJson();
factory $SnLinkMetaCopyWith(
SnLinkMeta value, $Res Function(SnLinkMeta) then) =
_$SnLinkMetaCopyWithImpl<$Res, SnLinkMeta>;
@useResult
$Res call(
{int id,
DateTime createdAt,
DateTime updatedAt,
DateTime? deletedAt,
String entryId,
String? icon,
String url,
String? title,
String? image,
String? video,
String? audio,
String? description,
String? siteName,
String? type});
}
/// @nodoc
class _$SnLinkMetaCopyWithImpl<$Res, $Val extends SnLinkMeta>
implements $SnLinkMetaCopyWith<$Res> {
_$SnLinkMetaCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of SnLinkMeta
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? createdAt = null,
Object? updatedAt = null,
Object? deletedAt = freezed,
Object? entryId = null,
Object? icon = freezed,
Object? url = null,
Object? title = freezed,
Object? image = freezed,
Object? video = freezed,
Object? audio = freezed,
Object? description = freezed,
Object? siteName = freezed,
Object? type = freezed,
}) {
return _then(_value.copyWith(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
createdAt: null == createdAt
? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,
updatedAt: null == updatedAt
? _value.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,
deletedAt: freezed == deletedAt
? _value.deletedAt
: deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
entryId: null == entryId
? _value.entryId
: entryId // ignore: cast_nullable_to_non_nullable
as String,
icon: freezed == icon
? _value.icon
: icon // ignore: cast_nullable_to_non_nullable
as String?,
url: null == url
? _value.url
: url // ignore: cast_nullable_to_non_nullable
as String,
title: freezed == title
? _value.title
: title // ignore: cast_nullable_to_non_nullable
as String?,
image: freezed == image
? _value.image
: image // ignore: cast_nullable_to_non_nullable
as String?,
video: freezed == video
? _value.video
: video // ignore: cast_nullable_to_non_nullable
as String?,
audio: freezed == audio
? _value.audio
: audio // ignore: cast_nullable_to_non_nullable
as String?,
description: freezed == description
? _value.description
: description // ignore: cast_nullable_to_non_nullable
as String?,
siteName: freezed == siteName
? _value.siteName
: siteName // ignore: cast_nullable_to_non_nullable
as String?,
type: freezed == type
? _value.type
: type // ignore: cast_nullable_to_non_nullable
as String?,
) as $Val);
}
}
/// @nodoc
abstract class _$$SnLinkMetaImplCopyWith<$Res>
implements $SnLinkMetaCopyWith<$Res> {
factory _$$SnLinkMetaImplCopyWith(
_$SnLinkMetaImpl value, $Res Function(_$SnLinkMetaImpl) then) =
__$$SnLinkMetaImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{int id,
DateTime createdAt,
DateTime updatedAt,
DateTime? deletedAt,
String entryId,
String? icon,
String url,
String? title,
String? image,
String? video,
String? audio,
String? description,
String? siteName,
String? type});
}
/// @nodoc
class __$$SnLinkMetaImplCopyWithImpl<$Res>
extends _$SnLinkMetaCopyWithImpl<$Res, _$SnLinkMetaImpl>
implements _$$SnLinkMetaImplCopyWith<$Res> {
__$$SnLinkMetaImplCopyWithImpl(
_$SnLinkMetaImpl _value, $Res Function(_$SnLinkMetaImpl) _then)
: super(_value, _then);
/// Create a copy of SnLinkMeta
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? createdAt = null,
Object? updatedAt = null,
Object? deletedAt = freezed,
Object? entryId = null,
Object? icon = freezed,
Object? url = null,
Object? title = freezed,
Object? image = freezed,
Object? video = freezed,
Object? audio = freezed,
Object? description = freezed,
Object? siteName = freezed,
Object? type = freezed,
}) {
return _then(_$SnLinkMetaImpl(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
createdAt: null == createdAt
? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,
updatedAt: null == updatedAt
? _value.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,
deletedAt: freezed == deletedAt
? _value.deletedAt
: deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
entryId: null == entryId
? _value.entryId
: entryId // ignore: cast_nullable_to_non_nullable
as String,
icon: freezed == icon
? _value.icon
: icon // ignore: cast_nullable_to_non_nullable
as String?,
url: null == url
? _value.url
: url // ignore: cast_nullable_to_non_nullable
as String,
title: freezed == title
? _value.title
: title // ignore: cast_nullable_to_non_nullable
as String?,
image: freezed == image
? _value.image
: image // ignore: cast_nullable_to_non_nullable
as String?,
video: freezed == video
? _value.video
: video // ignore: cast_nullable_to_non_nullable
as String?,
audio: freezed == audio
? _value.audio
: audio // ignore: cast_nullable_to_non_nullable
as String?,
description: freezed == description
? _value.description
: description // ignore: cast_nullable_to_non_nullable
as String?,
siteName: freezed == siteName
? _value.siteName
: siteName // ignore: cast_nullable_to_non_nullable
as String?,
type: freezed == type
? _value.type
: type // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// @nodoc
@JsonSerializable()
class _$SnLinkMetaImpl extends _SnLinkMeta {
const _$SnLinkMetaImpl(
{required this.id,
required this.createdAt,
required this.updatedAt,
required this.deletedAt,
required this.entryId,
required this.icon,
required this.url,
required this.title,
required this.image,
required this.video,
required this.audio,
required this.description,
required this.siteName,
required this.type})
: super._();
factory _$SnLinkMetaImpl.fromJson(Map<String, dynamic> json) =>
_$$SnLinkMetaImplFromJson(json);
@override
final int id;
@override
final DateTime createdAt;
@override
final DateTime updatedAt;
@override
final DateTime? deletedAt;
@override
final String entryId;
@override
final String? icon;
@override
final String url;
@override
final String? title;
@override
final String? image;
@override
final String? video;
@override
final String? audio;
@override
final String? description;
@override
final String? siteName;
@override
final String? type;
@override
String toString() {
return 'SnLinkMeta(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, entryId: $entryId, icon: $icon, url: $url, title: $title, image: $image, video: $video, audio: $audio, description: $description, siteName: $siteName, type: $type)';
}
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || return identical(this, other) ||
(other.runtimeType == runtimeType && (other.runtimeType == runtimeType &&
other is _$SnLinkMetaImpl && other is SnLinkMeta &&
(identical(other.id, id) || other.id == id) && (identical(other.id, id) || other.id == id) &&
(identical(other.createdAt, createdAt) || (identical(other.createdAt, createdAt) ||
other.createdAt == createdAt) && other.createdAt == createdAt) &&
@@ -375,76 +85,351 @@ class _$SnLinkMetaImpl extends _SnLinkMeta {
siteName, siteName,
type); type);
/// Create a copy of SnLinkMeta
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override @override
@pragma('vm:prefer-inline') String toString() {
_$$SnLinkMetaImplCopyWith<_$SnLinkMetaImpl> get copyWith => return 'SnLinkMeta(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, entryId: $entryId, icon: $icon, url: $url, title: $title, image: $image, video: $video, audio: $audio, description: $description, siteName: $siteName, type: $type)';
__$$SnLinkMetaImplCopyWithImpl<_$SnLinkMetaImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$SnLinkMetaImplToJson(
this,
);
} }
} }
abstract class _SnLinkMeta extends SnLinkMeta { /// @nodoc
const factory _SnLinkMeta( abstract mixin class $SnLinkMetaCopyWith<$Res> {
{required final int id, factory $SnLinkMetaCopyWith(
required final DateTime createdAt, SnLinkMeta value, $Res Function(SnLinkMeta) _then) =
required final DateTime updatedAt, _$SnLinkMetaCopyWithImpl;
required final DateTime? deletedAt, @useResult
required final String entryId, $Res call(
required final String? icon, {int id,
required final String url, DateTime createdAt,
required final String? title, DateTime updatedAt,
required final String? image, DateTime? deletedAt,
required final String? video, String entryId,
required final String? audio, String? icon,
required final String? description, String url,
required final String? siteName, String? title,
required final String? type}) = _$SnLinkMetaImpl; String? image,
const _SnLinkMeta._() : super._(); String? video,
String? audio,
String? description,
String? siteName,
String? type});
}
factory _SnLinkMeta.fromJson(Map<String, dynamic> json) = /// @nodoc
_$SnLinkMetaImpl.fromJson; class _$SnLinkMetaCopyWithImpl<$Res> implements $SnLinkMetaCopyWith<$Res> {
_$SnLinkMetaCopyWithImpl(this._self, this._then);
final SnLinkMeta _self;
final $Res Function(SnLinkMeta) _then;
/// Create a copy of SnLinkMeta
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? createdAt = null,
Object? updatedAt = null,
Object? deletedAt = freezed,
Object? entryId = null,
Object? icon = freezed,
Object? url = null,
Object? title = freezed,
Object? image = freezed,
Object? video = freezed,
Object? audio = freezed,
Object? description = freezed,
Object? siteName = freezed,
Object? type = freezed,
}) {
return _then(_self.copyWith(
id: null == id
? _self.id
: id // ignore: cast_nullable_to_non_nullable
as int,
createdAt: null == createdAt
? _self.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,
updatedAt: null == updatedAt
? _self.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,
deletedAt: freezed == deletedAt
? _self.deletedAt
: deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
entryId: null == entryId
? _self.entryId
: entryId // ignore: cast_nullable_to_non_nullable
as String,
icon: freezed == icon
? _self.icon
: icon // ignore: cast_nullable_to_non_nullable
as String?,
url: null == url
? _self.url
: url // ignore: cast_nullable_to_non_nullable
as String,
title: freezed == title
? _self.title
: title // ignore: cast_nullable_to_non_nullable
as String?,
image: freezed == image
? _self.image
: image // ignore: cast_nullable_to_non_nullable
as String?,
video: freezed == video
? _self.video
: video // ignore: cast_nullable_to_non_nullable
as String?,
audio: freezed == audio
? _self.audio
: audio // ignore: cast_nullable_to_non_nullable
as String?,
description: freezed == description
? _self.description
: description // ignore: cast_nullable_to_non_nullable
as String?,
siteName: freezed == siteName
? _self.siteName
: siteName // ignore: cast_nullable_to_non_nullable
as String?,
type: freezed == type
? _self.type
: type // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// @nodoc
@JsonSerializable()
class _SnLinkMeta extends SnLinkMeta {
const _SnLinkMeta(
{required this.id,
required this.createdAt,
required this.updatedAt,
required this.deletedAt,
required this.entryId,
required this.icon,
required this.url,
required this.title,
required this.image,
required this.video,
required this.audio,
required this.description,
required this.siteName,
required this.type})
: super._();
factory _SnLinkMeta.fromJson(Map<String, dynamic> json) =>
_$SnLinkMetaFromJson(json);
@override @override
int get id; final int id;
@override @override
DateTime get createdAt; final DateTime createdAt;
@override @override
DateTime get updatedAt; final DateTime updatedAt;
@override @override
DateTime? get deletedAt; final DateTime? deletedAt;
@override @override
String get entryId; final String entryId;
@override @override
String? get icon; final String? icon;
@override @override
String get url; final String url;
@override @override
String? get title; final String? title;
@override @override
String? get image; final String? image;
@override @override
String? get video; final String? video;
@override @override
String? get audio; final String? audio;
@override @override
String? get description; final String? description;
@override @override
String? get siteName; final String? siteName;
@override @override
String? get type; final String? type;
/// Create a copy of SnLinkMeta /// Create a copy of SnLinkMeta
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@override @override
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
_$$SnLinkMetaImplCopyWith<_$SnLinkMetaImpl> get copyWith => @pragma('vm:prefer-inline')
throw _privateConstructorUsedError; _$SnLinkMetaCopyWith<_SnLinkMeta> get copyWith =>
__$SnLinkMetaCopyWithImpl<_SnLinkMeta>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$SnLinkMetaToJson(
this,
);
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _SnLinkMeta &&
(identical(other.id, id) || other.id == id) &&
(identical(other.createdAt, createdAt) ||
other.createdAt == createdAt) &&
(identical(other.updatedAt, updatedAt) ||
other.updatedAt == updatedAt) &&
(identical(other.deletedAt, deletedAt) ||
other.deletedAt == deletedAt) &&
(identical(other.entryId, entryId) || other.entryId == entryId) &&
(identical(other.icon, icon) || other.icon == icon) &&
(identical(other.url, url) || other.url == url) &&
(identical(other.title, title) || other.title == title) &&
(identical(other.image, image) || other.image == image) &&
(identical(other.video, video) || other.video == video) &&
(identical(other.audio, audio) || other.audio == audio) &&
(identical(other.description, description) ||
other.description == description) &&
(identical(other.siteName, siteName) ||
other.siteName == siteName) &&
(identical(other.type, type) || other.type == type));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
id,
createdAt,
updatedAt,
deletedAt,
entryId,
icon,
url,
title,
image,
video,
audio,
description,
siteName,
type);
@override
String toString() {
return 'SnLinkMeta(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, entryId: $entryId, icon: $icon, url: $url, title: $title, image: $image, video: $video, audio: $audio, description: $description, siteName: $siteName, type: $type)';
}
} }
/// @nodoc
abstract mixin class _$SnLinkMetaCopyWith<$Res>
implements $SnLinkMetaCopyWith<$Res> {
factory _$SnLinkMetaCopyWith(
_SnLinkMeta value, $Res Function(_SnLinkMeta) _then) =
__$SnLinkMetaCopyWithImpl;
@override
@useResult
$Res call(
{int id,
DateTime createdAt,
DateTime updatedAt,
DateTime? deletedAt,
String entryId,
String? icon,
String url,
String? title,
String? image,
String? video,
String? audio,
String? description,
String? siteName,
String? type});
}
/// @nodoc
class __$SnLinkMetaCopyWithImpl<$Res> implements _$SnLinkMetaCopyWith<$Res> {
__$SnLinkMetaCopyWithImpl(this._self, this._then);
final _SnLinkMeta _self;
final $Res Function(_SnLinkMeta) _then;
/// Create a copy of SnLinkMeta
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$Res call({
Object? id = null,
Object? createdAt = null,
Object? updatedAt = null,
Object? deletedAt = freezed,
Object? entryId = null,
Object? icon = freezed,
Object? url = null,
Object? title = freezed,
Object? image = freezed,
Object? video = freezed,
Object? audio = freezed,
Object? description = freezed,
Object? siteName = freezed,
Object? type = freezed,
}) {
return _then(_SnLinkMeta(
id: null == id
? _self.id
: id // ignore: cast_nullable_to_non_nullable
as int,
createdAt: null == createdAt
? _self.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,
updatedAt: null == updatedAt
? _self.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,
deletedAt: freezed == deletedAt
? _self.deletedAt
: deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
entryId: null == entryId
? _self.entryId
: entryId // ignore: cast_nullable_to_non_nullable
as String,
icon: freezed == icon
? _self.icon
: icon // ignore: cast_nullable_to_non_nullable
as String?,
url: null == url
? _self.url
: url // ignore: cast_nullable_to_non_nullable
as String,
title: freezed == title
? _self.title
: title // ignore: cast_nullable_to_non_nullable
as String?,
image: freezed == image
? _self.image
: image // ignore: cast_nullable_to_non_nullable
as String?,
video: freezed == video
? _self.video
: video // ignore: cast_nullable_to_non_nullable
as String?,
audio: freezed == audio
? _self.audio
: audio // ignore: cast_nullable_to_non_nullable
as String?,
description: freezed == description
? _self.description
: description // ignore: cast_nullable_to_non_nullable
as String?,
siteName: freezed == siteName
? _self.siteName
: siteName // ignore: cast_nullable_to_non_nullable
as String?,
type: freezed == type
? _self.type
: type // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
// dart format on

View File

@@ -6,8 +6,7 @@ part of 'link.dart';
// JsonSerializableGenerator // JsonSerializableGenerator
// ************************************************************************** // **************************************************************************
_$SnLinkMetaImpl _$$SnLinkMetaImplFromJson(Map<String, dynamic> json) => _SnLinkMeta _$SnLinkMetaFromJson(Map<String, dynamic> json) => _SnLinkMeta(
_$SnLinkMetaImpl(
id: (json['id'] as num).toInt(), id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String), createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String), updatedAt: DateTime.parse(json['updated_at'] as String),
@@ -26,7 +25,7 @@ _$SnLinkMetaImpl _$$SnLinkMetaImplFromJson(Map<String, dynamic> json) =>
type: json['type'] as String?, type: json['type'] as String?,
); );
Map<String, dynamic> _$$SnLinkMetaImplToJson(_$SnLinkMetaImpl instance) => Map<String, dynamic> _$SnLinkMetaToJson(_SnLinkMeta instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id, 'id': instance.id,
'created_at': instance.createdAt.toIso8601String(), 'created_at': instance.createdAt.toIso8601String(),

View File

@@ -4,7 +4,7 @@ part 'news.freezed.dart';
part 'news.g.dart'; part 'news.g.dart';
@freezed @freezed
class SnNewsSource with _$SnNewsSource { abstract class SnNewsSource with _$SnNewsSource {
const factory SnNewsSource({ const factory SnNewsSource({
required String id, required String id,
required String label, required String label,
@@ -18,7 +18,7 @@ class SnNewsSource with _$SnNewsSource {
} }
@freezed @freezed
class SnNewsArticle with _$SnNewsArticle { abstract class SnNewsArticle with _$SnNewsArticle {
const factory SnNewsArticle({ const factory SnNewsArticle({
required int id, required int id,
required DateTime createdAt, required DateTime createdAt,

File diff suppressed because it is too large Load Diff

View File

@@ -6,8 +6,8 @@ part of 'news.dart';
// JsonSerializableGenerator // JsonSerializableGenerator
// ************************************************************************** // **************************************************************************
_$SnNewsSourceImpl _$$SnNewsSourceImplFromJson(Map<String, dynamic> json) => _SnNewsSource _$SnNewsSourceFromJson(Map<String, dynamic> json) =>
_$SnNewsSourceImpl( _SnNewsSource(
id: json['id'] as String, id: json['id'] as String,
label: json['label'] as String, label: json['label'] as String,
type: json['type'] as String, type: json['type'] as String,
@@ -16,7 +16,7 @@ _$SnNewsSourceImpl _$$SnNewsSourceImplFromJson(Map<String, dynamic> json) =>
enabled: json['enabled'] as bool, enabled: json['enabled'] as bool,
); );
Map<String, dynamic> _$$SnNewsSourceImplToJson(_$SnNewsSourceImpl instance) => Map<String, dynamic> _$SnNewsSourceToJson(_SnNewsSource instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id, 'id': instance.id,
'label': instance.label, 'label': instance.label,
@@ -26,8 +26,8 @@ Map<String, dynamic> _$$SnNewsSourceImplToJson(_$SnNewsSourceImpl instance) =>
'enabled': instance.enabled, 'enabled': instance.enabled,
}; };
_$SnNewsArticleImpl _$$SnNewsArticleImplFromJson(Map<String, dynamic> json) => _SnNewsArticle _$SnNewsArticleFromJson(Map<String, dynamic> json) =>
_$SnNewsArticleImpl( _SnNewsArticle(
id: (json['id'] as num).toInt(), id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String), createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String), updatedAt: DateTime.parse(json['updated_at'] as String),
@@ -44,7 +44,7 @@ _$SnNewsArticleImpl _$$SnNewsArticleImplFromJson(Map<String, dynamic> json) =>
: DateTime.parse(json['published_at'] as String), : DateTime.parse(json['published_at'] as String),
); );
Map<String, dynamic> _$$SnNewsArticleImplToJson(_$SnNewsArticleImpl instance) => Map<String, dynamic> _$SnNewsArticleToJson(_SnNewsArticle instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id, 'id': instance.id,
'created_at': instance.createdAt.toIso8601String(), 'created_at': instance.createdAt.toIso8601String(),

View File

@@ -4,7 +4,7 @@ part 'notification.freezed.dart';
part 'notification.g.dart'; part 'notification.g.dart';
@freezed @freezed
class SnNotification with _$SnNotification { abstract class SnNotification with _$SnNotification {
const factory SnNotification({ const factory SnNotification({
required int id, required int id,
required DateTime createdAt, required DateTime createdAt,

View File

@@ -1,3 +1,4 @@
// dart format width=80
// coverage:ignore-file // coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint // ignore_for_file: type=lint
@@ -9,156 +10,92 @@ part of 'notification.dart';
// FreezedGenerator // FreezedGenerator
// ************************************************************************** // **************************************************************************
// dart format off
T _$identity<T>(T value) => value; T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
SnNotification _$SnNotificationFromJson(Map<String, dynamic> json) {
return _SnNotification.fromJson(json);
}
/// @nodoc /// @nodoc
mixin _$SnNotification { mixin _$SnNotification {
int get id => throw _privateConstructorUsedError; int get id;
DateTime get createdAt => throw _privateConstructorUsedError; DateTime get createdAt;
DateTime get updatedAt => throw _privateConstructorUsedError; DateTime get updatedAt;
DateTime? get deletedAt => throw _privateConstructorUsedError; DateTime? get deletedAt;
String get topic => throw _privateConstructorUsedError; String get topic;
String get title => throw _privateConstructorUsedError; String get title;
String? get subtitle => throw _privateConstructorUsedError; String? get subtitle;
String get body => throw _privateConstructorUsedError; String get body;
Map<String, dynamic> get metadata => throw _privateConstructorUsedError; Map<String, dynamic> get metadata;
int get priority => throw _privateConstructorUsedError; int get priority;
int? get senderId => throw _privateConstructorUsedError; int? get senderId;
int get accountId => throw _privateConstructorUsedError; int get accountId;
DateTime? get readAt => throw _privateConstructorUsedError; DateTime? get readAt;
/// Serializes this SnNotification to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of SnNotification /// Create a copy of SnNotification
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
$SnNotificationCopyWith<SnNotification> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $SnNotificationCopyWith<$Res> {
factory $SnNotificationCopyWith(
SnNotification value, $Res Function(SnNotification) then) =
_$SnNotificationCopyWithImpl<$Res, SnNotification>;
@useResult
$Res call(
{int id,
DateTime createdAt,
DateTime updatedAt,
DateTime? deletedAt,
String topic,
String title,
String? subtitle,
String body,
Map<String, dynamic> metadata,
int priority,
int? senderId,
int accountId,
DateTime? readAt});
}
/// @nodoc
class _$SnNotificationCopyWithImpl<$Res, $Val extends SnNotification>
implements $SnNotificationCopyWith<$Res> {
_$SnNotificationCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of SnNotification
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
$SnNotificationCopyWith<SnNotification> get copyWith =>
_$SnNotificationCopyWithImpl<SnNotification>(
this as SnNotification, _$identity);
/// Serializes this SnNotification to a JSON map.
Map<String, dynamic> toJson();
@override @override
$Res call({ bool operator ==(Object other) {
Object? id = null, return identical(this, other) ||
Object? createdAt = null, (other.runtimeType == runtimeType &&
Object? updatedAt = null, other is SnNotification &&
Object? deletedAt = freezed, (identical(other.id, id) || other.id == id) &&
Object? topic = null, (identical(other.createdAt, createdAt) ||
Object? title = null, other.createdAt == createdAt) &&
Object? subtitle = freezed, (identical(other.updatedAt, updatedAt) ||
Object? body = null, other.updatedAt == updatedAt) &&
Object? metadata = null, (identical(other.deletedAt, deletedAt) ||
Object? priority = null, other.deletedAt == deletedAt) &&
Object? senderId = freezed, (identical(other.topic, topic) || other.topic == topic) &&
Object? accountId = null, (identical(other.title, title) || other.title == title) &&
Object? readAt = freezed, (identical(other.subtitle, subtitle) ||
}) { other.subtitle == subtitle) &&
return _then(_value.copyWith( (identical(other.body, body) || other.body == body) &&
id: null == id const DeepCollectionEquality().equals(other.metadata, metadata) &&
? _value.id (identical(other.priority, priority) ||
: id // ignore: cast_nullable_to_non_nullable other.priority == priority) &&
as int, (identical(other.senderId, senderId) ||
createdAt: null == createdAt other.senderId == senderId) &&
? _value.createdAt (identical(other.accountId, accountId) ||
: createdAt // ignore: cast_nullable_to_non_nullable other.accountId == accountId) &&
as DateTime, (identical(other.readAt, readAt) || other.readAt == readAt));
updatedAt: null == updatedAt }
? _value.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable @JsonKey(includeFromJson: false, includeToJson: false)
as DateTime, @override
deletedAt: freezed == deletedAt int get hashCode => Object.hash(
? _value.deletedAt runtimeType,
: deletedAt // ignore: cast_nullable_to_non_nullable id,
as DateTime?, createdAt,
topic: null == topic updatedAt,
? _value.topic deletedAt,
: topic // ignore: cast_nullable_to_non_nullable topic,
as String, title,
title: null == title subtitle,
? _value.title body,
: title // ignore: cast_nullable_to_non_nullable const DeepCollectionEquality().hash(metadata),
as String, priority,
subtitle: freezed == subtitle senderId,
? _value.subtitle accountId,
: subtitle // ignore: cast_nullable_to_non_nullable readAt);
as String?,
body: null == body @override
? _value.body String toString() {
: body // ignore: cast_nullable_to_non_nullable return 'SnNotification(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, topic: $topic, title: $title, subtitle: $subtitle, body: $body, metadata: $metadata, priority: $priority, senderId: $senderId, accountId: $accountId, readAt: $readAt)';
as String,
metadata: null == metadata
? _value.metadata
: metadata // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>,
priority: null == priority
? _value.priority
: priority // ignore: cast_nullable_to_non_nullable
as int,
senderId: freezed == senderId
? _value.senderId
: senderId // ignore: cast_nullable_to_non_nullable
as int?,
accountId: null == accountId
? _value.accountId
: accountId // ignore: cast_nullable_to_non_nullable
as int,
readAt: freezed == readAt
? _value.readAt
: readAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
) as $Val);
} }
} }
/// @nodoc /// @nodoc
abstract class _$$SnNotificationImplCopyWith<$Res> abstract mixin class $SnNotificationCopyWith<$Res> {
implements $SnNotificationCopyWith<$Res> { factory $SnNotificationCopyWith(
factory _$$SnNotificationImplCopyWith(_$SnNotificationImpl value, SnNotification value, $Res Function(SnNotification) _then) =
$Res Function(_$SnNotificationImpl) then) = _$SnNotificationCopyWithImpl;
__$$SnNotificationImplCopyWithImpl<$Res>;
@override
@useResult @useResult
$Res call( $Res call(
{int id, {int id,
@@ -177,12 +114,12 @@ abstract class _$$SnNotificationImplCopyWith<$Res>
} }
/// @nodoc /// @nodoc
class __$$SnNotificationImplCopyWithImpl<$Res> class _$SnNotificationCopyWithImpl<$Res>
extends _$SnNotificationCopyWithImpl<$Res, _$SnNotificationImpl> implements $SnNotificationCopyWith<$Res> {
implements _$$SnNotificationImplCopyWith<$Res> { _$SnNotificationCopyWithImpl(this._self, this._then);
__$$SnNotificationImplCopyWithImpl(
_$SnNotificationImpl _value, $Res Function(_$SnNotificationImpl) _then) final SnNotification _self;
: super(_value, _then); final $Res Function(SnNotification) _then;
/// Create a copy of SnNotification /// Create a copy of SnNotification
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@@ -203,57 +140,57 @@ class __$$SnNotificationImplCopyWithImpl<$Res>
Object? accountId = null, Object? accountId = null,
Object? readAt = freezed, Object? readAt = freezed,
}) { }) {
return _then(_$SnNotificationImpl( return _then(_self.copyWith(
id: null == id id: null == id
? _value.id ? _self.id
: id // ignore: cast_nullable_to_non_nullable : id // ignore: cast_nullable_to_non_nullable
as int, as int,
createdAt: null == createdAt createdAt: null == createdAt
? _value.createdAt ? _self.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime, as DateTime,
updatedAt: null == updatedAt updatedAt: null == updatedAt
? _value.updatedAt ? _self.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable : updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime, as DateTime,
deletedAt: freezed == deletedAt deletedAt: freezed == deletedAt
? _value.deletedAt ? _self.deletedAt
: deletedAt // ignore: cast_nullable_to_non_nullable : deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?, as DateTime?,
topic: null == topic topic: null == topic
? _value.topic ? _self.topic
: topic // ignore: cast_nullable_to_non_nullable : topic // ignore: cast_nullable_to_non_nullable
as String, as String,
title: null == title title: null == title
? _value.title ? _self.title
: title // ignore: cast_nullable_to_non_nullable : title // ignore: cast_nullable_to_non_nullable
as String, as String,
subtitle: freezed == subtitle subtitle: freezed == subtitle
? _value.subtitle ? _self.subtitle
: subtitle // ignore: cast_nullable_to_non_nullable : subtitle // ignore: cast_nullable_to_non_nullable
as String?, as String?,
body: null == body body: null == body
? _value.body ? _self.body
: body // ignore: cast_nullable_to_non_nullable : body // ignore: cast_nullable_to_non_nullable
as String, as String,
metadata: null == metadata metadata: null == metadata
? _value._metadata ? _self.metadata
: metadata // ignore: cast_nullable_to_non_nullable : metadata // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>, as Map<String, dynamic>,
priority: null == priority priority: null == priority
? _value.priority ? _self.priority
: priority // ignore: cast_nullable_to_non_nullable : priority // ignore: cast_nullable_to_non_nullable
as int, as int,
senderId: freezed == senderId senderId: freezed == senderId
? _value.senderId ? _self.senderId
: senderId // ignore: cast_nullable_to_non_nullable : senderId // ignore: cast_nullable_to_non_nullable
as int?, as int?,
accountId: null == accountId accountId: null == accountId
? _value.accountId ? _self.accountId
: accountId // ignore: cast_nullable_to_non_nullable : accountId // ignore: cast_nullable_to_non_nullable
as int, as int,
readAt: freezed == readAt readAt: freezed == readAt
? _value.readAt ? _self.readAt
: readAt // ignore: cast_nullable_to_non_nullable : readAt // ignore: cast_nullable_to_non_nullable
as DateTime?, as DateTime?,
)); ));
@@ -262,8 +199,8 @@ class __$$SnNotificationImplCopyWithImpl<$Res>
/// @nodoc /// @nodoc
@JsonSerializable() @JsonSerializable()
class _$SnNotificationImpl implements _SnNotification { class _SnNotification implements SnNotification {
const _$SnNotificationImpl( const _SnNotification(
{required this.id, {required this.id,
required this.createdAt, required this.createdAt,
required this.updatedAt, required this.updatedAt,
@@ -278,9 +215,8 @@ class _$SnNotificationImpl implements _SnNotification {
required this.accountId, required this.accountId,
required this.readAt}) required this.readAt})
: _metadata = metadata; : _metadata = metadata;
factory _SnNotification.fromJson(Map<String, dynamic> json) =>
factory _$SnNotificationImpl.fromJson(Map<String, dynamic> json) => _$SnNotificationFromJson(json);
_$$SnNotificationImplFromJson(json);
@override @override
final int id; final int id;
@@ -316,16 +252,26 @@ class _$SnNotificationImpl implements _SnNotification {
@override @override
final DateTime? readAt; final DateTime? readAt;
/// Create a copy of SnNotification
/// with the given fields replaced by the non-null parameter values.
@override @override
String toString() { @JsonKey(includeFromJson: false, includeToJson: false)
return 'SnNotification(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, topic: $topic, title: $title, subtitle: $subtitle, body: $body, metadata: $metadata, priority: $priority, senderId: $senderId, accountId: $accountId, readAt: $readAt)'; @pragma('vm:prefer-inline')
_$SnNotificationCopyWith<_SnNotification> get copyWith =>
__$SnNotificationCopyWithImpl<_SnNotification>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$SnNotificationToJson(
this,
);
} }
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || return identical(this, other) ||
(other.runtimeType == runtimeType && (other.runtimeType == runtimeType &&
other is _$SnNotificationImpl && other is _SnNotification &&
(identical(other.id, id) || other.id == id) && (identical(other.id, id) || other.id == id) &&
(identical(other.createdAt, createdAt) || (identical(other.createdAt, createdAt) ||
other.createdAt == createdAt) && other.createdAt == createdAt) &&
@@ -366,73 +312,118 @@ class _$SnNotificationImpl implements _SnNotification {
accountId, accountId,
readAt); readAt);
/// Create a copy of SnNotification
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override @override
@pragma('vm:prefer-inline') String toString() {
_$$SnNotificationImplCopyWith<_$SnNotificationImpl> get copyWith => return 'SnNotification(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, topic: $topic, title: $title, subtitle: $subtitle, body: $body, metadata: $metadata, priority: $priority, senderId: $senderId, accountId: $accountId, readAt: $readAt)';
__$$SnNotificationImplCopyWithImpl<_$SnNotificationImpl>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$SnNotificationImplToJson(
this,
);
} }
} }
abstract class _SnNotification implements SnNotification { /// @nodoc
const factory _SnNotification( abstract mixin class _$SnNotificationCopyWith<$Res>
{required final int id, implements $SnNotificationCopyWith<$Res> {
required final DateTime createdAt, factory _$SnNotificationCopyWith(
required final DateTime updatedAt, _SnNotification value, $Res Function(_SnNotification) _then) =
required final DateTime? deletedAt, __$SnNotificationCopyWithImpl;
required final String topic, @override
required final String title, @useResult
required final String? subtitle, $Res call(
required final String body, {int id,
final Map<String, dynamic> metadata, DateTime createdAt,
required final int priority, DateTime updatedAt,
required final int? senderId, DateTime? deletedAt,
required final int accountId, String topic,
required final DateTime? readAt}) = _$SnNotificationImpl; String title,
String? subtitle,
String body,
Map<String, dynamic> metadata,
int priority,
int? senderId,
int accountId,
DateTime? readAt});
}
factory _SnNotification.fromJson(Map<String, dynamic> json) = /// @nodoc
_$SnNotificationImpl.fromJson; class __$SnNotificationCopyWithImpl<$Res>
implements _$SnNotificationCopyWith<$Res> {
__$SnNotificationCopyWithImpl(this._self, this._then);
@override final _SnNotification _self;
int get id; final $Res Function(_SnNotification) _then;
@override
DateTime get createdAt;
@override
DateTime get updatedAt;
@override
DateTime? get deletedAt;
@override
String get topic;
@override
String get title;
@override
String? get subtitle;
@override
String get body;
@override
Map<String, dynamic> get metadata;
@override
int get priority;
@override
int? get senderId;
@override
int get accountId;
@override
DateTime? get readAt;
/// Create a copy of SnNotification /// Create a copy of SnNotification
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@override @override
@JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline')
_$$SnNotificationImplCopyWith<_$SnNotificationImpl> get copyWith => $Res call({
throw _privateConstructorUsedError; Object? id = null,
Object? createdAt = null,
Object? updatedAt = null,
Object? deletedAt = freezed,
Object? topic = null,
Object? title = null,
Object? subtitle = freezed,
Object? body = null,
Object? metadata = null,
Object? priority = null,
Object? senderId = freezed,
Object? accountId = null,
Object? readAt = freezed,
}) {
return _then(_SnNotification(
id: null == id
? _self.id
: id // ignore: cast_nullable_to_non_nullable
as int,
createdAt: null == createdAt
? _self.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,
updatedAt: null == updatedAt
? _self.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,
deletedAt: freezed == deletedAt
? _self.deletedAt
: deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
topic: null == topic
? _self.topic
: topic // ignore: cast_nullable_to_non_nullable
as String,
title: null == title
? _self.title
: title // ignore: cast_nullable_to_non_nullable
as String,
subtitle: freezed == subtitle
? _self.subtitle
: subtitle // ignore: cast_nullable_to_non_nullable
as String?,
body: null == body
? _self.body
: body // ignore: cast_nullable_to_non_nullable
as String,
metadata: null == metadata
? _self._metadata
: metadata // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>,
priority: null == priority
? _self.priority
: priority // ignore: cast_nullable_to_non_nullable
as int,
senderId: freezed == senderId
? _self.senderId
: senderId // ignore: cast_nullable_to_non_nullable
as int?,
accountId: null == accountId
? _self.accountId
: accountId // ignore: cast_nullable_to_non_nullable
as int,
readAt: freezed == readAt
? _self.readAt
: readAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
));
}
} }
// dart format on

View File

@@ -6,8 +6,8 @@ part of 'notification.dart';
// JsonSerializableGenerator // JsonSerializableGenerator
// ************************************************************************** // **************************************************************************
_$SnNotificationImpl _$$SnNotificationImplFromJson(Map<String, dynamic> json) => _SnNotification _$SnNotificationFromJson(Map<String, dynamic> json) =>
_$SnNotificationImpl( _SnNotification(
id: (json['id'] as num).toInt(), id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String), createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String), updatedAt: DateTime.parse(json['updated_at'] as String),
@@ -27,8 +27,7 @@ _$SnNotificationImpl _$$SnNotificationImplFromJson(Map<String, dynamic> json) =>
: DateTime.parse(json['read_at'] as String), : DateTime.parse(json['read_at'] as String),
); );
Map<String, dynamic> _$$SnNotificationImplToJson( Map<String, dynamic> _$SnNotificationToJson(_SnNotification instance) =>
_$SnNotificationImpl instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id, 'id': instance.id,
'created_at': instance.createdAt.toIso8601String(), 'created_at': instance.createdAt.toIso8601String(),

View File

@@ -4,7 +4,7 @@ part 'poll.freezed.dart';
part 'poll.g.dart'; part 'poll.g.dart';
@freezed @freezed
class SnPoll with _$SnPoll { abstract class SnPoll with _$SnPoll {
const factory SnPoll({ const factory SnPoll({
required int id, required int id,
required DateTime createdAt, required DateTime createdAt,
@@ -20,7 +20,7 @@ class SnPoll with _$SnPoll {
} }
@freezed @freezed
class SnPollMetric with _$SnPollMetric { abstract class SnPollMetric with _$SnPollMetric {
const factory SnPollMetric({ const factory SnPollMetric({
required int totalAnswer, required int totalAnswer,
@Default({}) Map<String, int> byOptions, @Default({}) Map<String, int> byOptions,
@@ -32,7 +32,7 @@ class SnPollMetric with _$SnPollMetric {
} }
@freezed @freezed
class SnPollOption with _$SnPollOption { abstract class SnPollOption with _$SnPollOption {
const factory SnPollOption({ const factory SnPollOption({
required String id, required String id,
required String icon, required String icon,

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,7 @@ part of 'poll.dart';
// JsonSerializableGenerator // JsonSerializableGenerator
// ************************************************************************** // **************************************************************************
_$SnPollImpl _$$SnPollImplFromJson(Map<String, dynamic> json) => _$SnPollImpl( _SnPoll _$SnPollFromJson(Map<String, dynamic> json) => _SnPoll(
id: (json['id'] as num).toInt(), id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String), createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String), updatedAt: DateTime.parse(json['updated_at'] as String),
@@ -19,8 +19,7 @@ _$SnPollImpl _$$SnPollImplFromJson(Map<String, dynamic> json) => _$SnPollImpl(
metric: SnPollMetric.fromJson(json['metric'] as Map<String, dynamic>), metric: SnPollMetric.fromJson(json['metric'] as Map<String, dynamic>),
); );
Map<String, dynamic> _$$SnPollImplToJson(_$SnPollImpl instance) => Map<String, dynamic> _$SnPollToJson(_SnPoll instance) => <String, dynamic>{
<String, dynamic>{
'id': instance.id, 'id': instance.id,
'created_at': instance.createdAt.toIso8601String(), 'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(), 'updated_at': instance.updatedAt.toIso8601String(),
@@ -31,8 +30,8 @@ Map<String, dynamic> _$$SnPollImplToJson(_$SnPollImpl instance) =>
'metric': instance.metric.toJson(), 'metric': instance.metric.toJson(),
}; };
_$SnPollMetricImpl _$$SnPollMetricImplFromJson(Map<String, dynamic> json) => _SnPollMetric _$SnPollMetricFromJson(Map<String, dynamic> json) =>
_$SnPollMetricImpl( _SnPollMetric(
totalAnswer: (json['total_answer'] as num).toInt(), totalAnswer: (json['total_answer'] as num).toInt(),
byOptions: (json['by_options'] as Map<String, dynamic>?)?.map( byOptions: (json['by_options'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, (e as num).toInt()), (k, e) => MapEntry(k, (e as num).toInt()),
@@ -45,22 +44,22 @@ _$SnPollMetricImpl _$$SnPollMetricImplFromJson(Map<String, dynamic> json) =>
const {}, const {},
); );
Map<String, dynamic> _$$SnPollMetricImplToJson(_$SnPollMetricImpl instance) => Map<String, dynamic> _$SnPollMetricToJson(_SnPollMetric instance) =>
<String, dynamic>{ <String, dynamic>{
'total_answer': instance.totalAnswer, 'total_answer': instance.totalAnswer,
'by_options': instance.byOptions, 'by_options': instance.byOptions,
'by_options_percentage': instance.byOptionsPercentage, 'by_options_percentage': instance.byOptionsPercentage,
}; };
_$SnPollOptionImpl _$$SnPollOptionImplFromJson(Map<String, dynamic> json) => _SnPollOption _$SnPollOptionFromJson(Map<String, dynamic> json) =>
_$SnPollOptionImpl( _SnPollOption(
id: json['id'] as String, id: json['id'] as String,
icon: json['icon'] as String, icon: json['icon'] as String,
name: json['name'] as String, name: json['name'] as String,
description: json['description'] as String, description: json['description'] as String,
); );
Map<String, dynamic> _$$SnPollOptionImplToJson(_$SnPollOptionImpl instance) => Map<String, dynamic> _$SnPollOptionToJson(_SnPollOption instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id, 'id': instance.id,
'icon': instance.icon, 'icon': instance.icon,

View File

@@ -7,7 +7,7 @@ part 'post.freezed.dart';
part 'post.g.dart'; part 'post.g.dart';
@freezed @freezed
class SnPost with _$SnPost { abstract class SnPost with _$SnPost {
const SnPost._(); const SnPost._();
const factory SnPost({ const factory SnPost({
@@ -57,7 +57,7 @@ class SnPost with _$SnPost {
} }
@freezed @freezed
class SnPostTag with _$SnPostTag { abstract class SnPostTag with _$SnPostTag {
const factory SnPostTag({ const factory SnPostTag({
required int id, required int id,
required DateTime createdAt, required DateTime createdAt,
@@ -74,7 +74,7 @@ class SnPostTag with _$SnPostTag {
} }
@freezed @freezed
class SnPostCategory with _$SnPostCategory { abstract class SnPostCategory with _$SnPostCategory {
const factory SnPostCategory({ const factory SnPostCategory({
required int id, required int id,
required DateTime createdAt, required DateTime createdAt,
@@ -91,7 +91,7 @@ class SnPostCategory with _$SnPostCategory {
} }
@freezed @freezed
class SnPostPreload with _$SnPostPreload { abstract class SnPostPreload with _$SnPostPreload {
const factory SnPostPreload({ const factory SnPostPreload({
required SnAttachment? thumbnail, required SnAttachment? thumbnail,
required List<SnAttachment?>? attachments, required List<SnAttachment?>? attachments,
@@ -105,7 +105,7 @@ class SnPostPreload with _$SnPostPreload {
} }
@freezed @freezed
class SnBody with _$SnBody { abstract class SnBody with _$SnBody {
const factory SnBody({ const factory SnBody({
required List<String> attachments, required List<String> attachments,
required String content, required String content,
@@ -118,7 +118,7 @@ class SnBody with _$SnBody {
} }
@freezed @freezed
class SnMetric with _$SnMetric { abstract class SnMetric with _$SnMetric {
const factory SnMetric({ const factory SnMetric({
required int replyCount, required int replyCount,
required int reactionCount, required int reactionCount,
@@ -130,7 +130,7 @@ class SnMetric with _$SnMetric {
} }
@freezed @freezed
class SnPublisher with _$SnPublisher { abstract class SnPublisher with _$SnPublisher {
const factory SnPublisher({ const factory SnPublisher({
required int id, required int id,
required DateTime createdAt, required DateTime createdAt,
@@ -153,7 +153,7 @@ class SnPublisher with _$SnPublisher {
} }
@freezed @freezed
class SnSubscription with _$SnSubscription { abstract class SnSubscription with _$SnSubscription {
const factory SnSubscription({ const factory SnSubscription({
required int id, required int id,
required DateTime createdAt, required DateTime createdAt,

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,7 @@ part of 'post.dart';
// JsonSerializableGenerator // JsonSerializableGenerator
// ************************************************************************** // **************************************************************************
_$SnPostImpl _$$SnPostImplFromJson(Map<String, dynamic> json) => _$SnPostImpl( _SnPost _$SnPostFromJson(Map<String, dynamic> json) => _SnPost(
id: (json['id'] as num).toInt(), id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String), createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String), updatedAt: DateTime.parse(json['updated_at'] as String),
@@ -76,8 +76,7 @@ _$SnPostImpl _$$SnPostImplFromJson(Map<String, dynamic> json) => _$SnPostImpl(
: SnPostPreload.fromJson(json['preload'] as Map<String, dynamic>), : SnPostPreload.fromJson(json['preload'] as Map<String, dynamic>),
); );
Map<String, dynamic> _$$SnPostImplToJson(_$SnPostImpl instance) => Map<String, dynamic> _$SnPostToJson(_SnPost instance) => <String, dynamic>{
<String, dynamic>{
'id': instance.id, 'id': instance.id,
'created_at': instance.createdAt.toIso8601String(), 'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(), 'updated_at': instance.updatedAt.toIso8601String(),
@@ -115,8 +114,7 @@ Map<String, dynamic> _$$SnPostImplToJson(_$SnPostImpl instance) =>
'preload': instance.preload?.toJson(), 'preload': instance.preload?.toJson(),
}; };
_$SnPostTagImpl _$$SnPostTagImplFromJson(Map<String, dynamic> json) => _SnPostTag _$SnPostTagFromJson(Map<String, dynamic> json) => _SnPostTag(
_$SnPostTagImpl(
id: (json['id'] as num).toInt(), id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String), createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String), updatedAt: DateTime.parse(json['updated_at'] as String),
@@ -127,7 +125,7 @@ _$SnPostTagImpl _$$SnPostTagImplFromJson(Map<String, dynamic> json) =>
posts: json['posts'], posts: json['posts'],
); );
Map<String, dynamic> _$$SnPostTagImplToJson(_$SnPostTagImpl instance) => Map<String, dynamic> _$SnPostTagToJson(_SnPostTag instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id, 'id': instance.id,
'created_at': instance.createdAt.toIso8601String(), 'created_at': instance.createdAt.toIso8601String(),
@@ -139,8 +137,8 @@ Map<String, dynamic> _$$SnPostTagImplToJson(_$SnPostTagImpl instance) =>
'posts': instance.posts, 'posts': instance.posts,
}; };
_$SnPostCategoryImpl _$$SnPostCategoryImplFromJson(Map<String, dynamic> json) => _SnPostCategory _$SnPostCategoryFromJson(Map<String, dynamic> json) =>
_$SnPostCategoryImpl( _SnPostCategory(
id: (json['id'] as num).toInt(), id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String), createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String), updatedAt: DateTime.parse(json['updated_at'] as String),
@@ -151,8 +149,7 @@ _$SnPostCategoryImpl _$$SnPostCategoryImplFromJson(Map<String, dynamic> json) =>
posts: json['posts'], posts: json['posts'],
); );
Map<String, dynamic> _$$SnPostCategoryImplToJson( Map<String, dynamic> _$SnPostCategoryToJson(_SnPostCategory instance) =>
_$SnPostCategoryImpl instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id, 'id': instance.id,
'created_at': instance.createdAt.toIso8601String(), 'created_at': instance.createdAt.toIso8601String(),
@@ -164,8 +161,8 @@ Map<String, dynamic> _$$SnPostCategoryImplToJson(
'posts': instance.posts, 'posts': instance.posts,
}; };
_$SnPostPreloadImpl _$$SnPostPreloadImplFromJson(Map<String, dynamic> json) => _SnPostPreload _$SnPostPreloadFromJson(Map<String, dynamic> json) =>
_$SnPostPreloadImpl( _SnPostPreload(
thumbnail: json['thumbnail'] == null thumbnail: json['thumbnail'] == null
? null ? null
: SnAttachment.fromJson(json['thumbnail'] as Map<String, dynamic>), : SnAttachment.fromJson(json['thumbnail'] as Map<String, dynamic>),
@@ -185,7 +182,7 @@ _$SnPostPreloadImpl _$$SnPostPreloadImplFromJson(Map<String, dynamic> json) =>
: SnRealm.fromJson(json['realm'] as Map<String, dynamic>), : SnRealm.fromJson(json['realm'] as Map<String, dynamic>),
); );
Map<String, dynamic> _$$SnPostPreloadImplToJson(_$SnPostPreloadImpl instance) => Map<String, dynamic> _$SnPostPreloadToJson(_SnPostPreload instance) =>
<String, dynamic>{ <String, dynamic>{
'thumbnail': instance.thumbnail?.toJson(), 'thumbnail': instance.thumbnail?.toJson(),
'attachments': instance.attachments?.map((e) => e?.toJson()).toList(), 'attachments': instance.attachments?.map((e) => e?.toJson()).toList(),
@@ -194,7 +191,7 @@ Map<String, dynamic> _$$SnPostPreloadImplToJson(_$SnPostPreloadImpl instance) =>
'realm': instance.realm?.toJson(), 'realm': instance.realm?.toJson(),
}; };
_$SnBodyImpl _$$SnBodyImplFromJson(Map<String, dynamic> json) => _$SnBodyImpl( _SnBody _$SnBodyFromJson(Map<String, dynamic> json) => _SnBody(
attachments: (json['attachments'] as List<dynamic>) attachments: (json['attachments'] as List<dynamic>)
.map((e) => e as String) .map((e) => e as String)
.toList(), .toList(),
@@ -204,8 +201,7 @@ _$SnBodyImpl _$$SnBodyImplFromJson(Map<String, dynamic> json) => _$SnBodyImpl(
title: json['title'], title: json['title'],
); );
Map<String, dynamic> _$$SnBodyImplToJson(_$SnBodyImpl instance) => Map<String, dynamic> _$SnBodyToJson(_SnBody instance) => <String, dynamic>{
<String, dynamic>{
'attachments': instance.attachments, 'attachments': instance.attachments,
'content': instance.content, 'content': instance.content,
'location': instance.location, 'location': instance.location,
@@ -213,8 +209,7 @@ Map<String, dynamic> _$$SnBodyImplToJson(_$SnBodyImpl instance) =>
'title': instance.title, 'title': instance.title,
}; };
_$SnMetricImpl _$$SnMetricImplFromJson(Map<String, dynamic> json) => _SnMetric _$SnMetricFromJson(Map<String, dynamic> json) => _SnMetric(
_$SnMetricImpl(
replyCount: (json['reply_count'] as num).toInt(), replyCount: (json['reply_count'] as num).toInt(),
reactionCount: (json['reaction_count'] as num).toInt(), reactionCount: (json['reaction_count'] as num).toInt(),
reactionList: (json['reaction_list'] as Map<String, dynamic>?)?.map( reactionList: (json['reaction_list'] as Map<String, dynamic>?)?.map(
@@ -223,15 +218,13 @@ _$SnMetricImpl _$$SnMetricImplFromJson(Map<String, dynamic> json) =>
const {}, const {},
); );
Map<String, dynamic> _$$SnMetricImplToJson(_$SnMetricImpl instance) => Map<String, dynamic> _$SnMetricToJson(_SnMetric instance) => <String, dynamic>{
<String, dynamic>{
'reply_count': instance.replyCount, 'reply_count': instance.replyCount,
'reaction_count': instance.reactionCount, 'reaction_count': instance.reactionCount,
'reaction_list': instance.reactionList, 'reaction_list': instance.reactionList,
}; };
_$SnPublisherImpl _$$SnPublisherImplFromJson(Map<String, dynamic> json) => _SnPublisher _$SnPublisherFromJson(Map<String, dynamic> json) => _SnPublisher(
_$SnPublisherImpl(
id: (json['id'] as num).toInt(), id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String), createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String), updatedAt: DateTime.parse(json['updated_at'] as String),
@@ -250,7 +243,7 @@ _$SnPublisherImpl _$$SnPublisherImplFromJson(Map<String, dynamic> json) =>
accountId: (json['account_id'] as num).toInt(), accountId: (json['account_id'] as num).toInt(),
); );
Map<String, dynamic> _$$SnPublisherImplToJson(_$SnPublisherImpl instance) => Map<String, dynamic> _$SnPublisherToJson(_SnPublisher instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id, 'id': instance.id,
'created_at': instance.createdAt.toIso8601String(), 'created_at': instance.createdAt.toIso8601String(),
@@ -268,8 +261,8 @@ Map<String, dynamic> _$$SnPublisherImplToJson(_$SnPublisherImpl instance) =>
'account_id': instance.accountId, 'account_id': instance.accountId,
}; };
_$SnSubscriptionImpl _$$SnSubscriptionImplFromJson(Map<String, dynamic> json) => _SnSubscription _$SnSubscriptionFromJson(Map<String, dynamic> json) =>
_$SnSubscriptionImpl( _SnSubscription(
id: (json['id'] as num).toInt(), id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String), createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String), updatedAt: DateTime.parse(json['updated_at'] as String),
@@ -280,8 +273,7 @@ _$SnSubscriptionImpl _$$SnSubscriptionImplFromJson(Map<String, dynamic> json) =>
accountId: (json['account_id'] as num).toInt(), accountId: (json['account_id'] as num).toInt(),
); );
Map<String, dynamic> _$$SnSubscriptionImplToJson( Map<String, dynamic> _$SnSubscriptionToJson(_SnSubscription instance) =>
_$SnSubscriptionImpl instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id, 'id': instance.id,
'created_at': instance.createdAt.toIso8601String(), 'created_at': instance.createdAt.toIso8601String(),

View File

@@ -5,7 +5,7 @@ part 'realm.freezed.dart';
part 'realm.g.dart'; part 'realm.g.dart';
@freezed @freezed
class SnRealmMember with _$SnRealmMember { abstract class SnRealmMember with _$SnRealmMember {
const factory SnRealmMember({ const factory SnRealmMember({
required int id, required int id,
required DateTime createdAt, required DateTime createdAt,
@@ -23,7 +23,7 @@ class SnRealmMember with _$SnRealmMember {
} }
@freezed @freezed
class SnRealm with _$SnRealm { abstract class SnRealm with _$SnRealm {
const SnRealm._(); const SnRealm._();
const factory SnRealm({ const factory SnRealm({

File diff suppressed because it is too large Load Diff

View File

@@ -6,8 +6,8 @@ part of 'realm.dart';
// JsonSerializableGenerator // JsonSerializableGenerator
// ************************************************************************** // **************************************************************************
_$SnRealmMemberImpl _$$SnRealmMemberImplFromJson(Map<String, dynamic> json) => _SnRealmMember _$SnRealmMemberFromJson(Map<String, dynamic> json) =>
_$SnRealmMemberImpl( _SnRealmMember(
id: (json['id'] as num).toInt(), id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String), createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String), updatedAt: DateTime.parse(json['updated_at'] as String),
@@ -21,7 +21,7 @@ _$SnRealmMemberImpl _$$SnRealmMemberImplFromJson(Map<String, dynamic> json) =>
powerLevel: (json['power_level'] as num).toInt(), powerLevel: (json['power_level'] as num).toInt(),
); );
Map<String, dynamic> _$$SnRealmMemberImplToJson(_$SnRealmMemberImpl instance) => Map<String, dynamic> _$SnRealmMemberToJson(_SnRealmMember instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id, 'id': instance.id,
'created_at': instance.createdAt.toIso8601String(), 'created_at': instance.createdAt.toIso8601String(),
@@ -34,8 +34,7 @@ Map<String, dynamic> _$$SnRealmMemberImplToJson(_$SnRealmMemberImpl instance) =>
'power_level': instance.powerLevel, 'power_level': instance.powerLevel,
}; };
_$SnRealmImpl _$$SnRealmImplFromJson(Map<String, dynamic> json) => _SnRealm _$SnRealmFromJson(Map<String, dynamic> json) => _SnRealm(
_$SnRealmImpl(
id: (json['id'] as num).toInt(), id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String), createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String), updatedAt: DateTime.parse(json['updated_at'] as String),
@@ -57,8 +56,7 @@ _$SnRealmImpl _$$SnRealmImplFromJson(Map<String, dynamic> json) =>
popularity: (json['popularity'] as num?)?.toInt() ?? 0, popularity: (json['popularity'] as num?)?.toInt() ?? 0,
); );
Map<String, dynamic> _$$SnRealmImplToJson(_$SnRealmImpl instance) => Map<String, dynamic> _$SnRealmToJson(_SnRealm instance) => <String, dynamic>{
<String, dynamic>{
'id': instance.id, 'id': instance.id,
'created_at': instance.createdAt.toIso8601String(), 'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(), 'updated_at': instance.updatedAt.toIso8601String(),

View File

@@ -4,7 +4,7 @@ part 'wallet.freezed.dart';
part 'wallet.g.dart'; part 'wallet.g.dart';
@freezed @freezed
class SnWallet with _$SnWallet { abstract class SnWallet with _$SnWallet {
const factory SnWallet({ const factory SnWallet({
required int id, required int id,
required DateTime createdAt, required DateTime createdAt,
@@ -19,7 +19,7 @@ class SnWallet with _$SnWallet {
} }
@freezed @freezed
class SnTransaction with _$SnTransaction { abstract class SnTransaction with _$SnTransaction {
const factory SnTransaction({ const factory SnTransaction({
required int id, required int id,
required DateTime createdAt, required DateTime createdAt,

File diff suppressed because it is too large Load Diff

View File

@@ -6,8 +6,7 @@ part of 'wallet.dart';
// JsonSerializableGenerator // JsonSerializableGenerator
// ************************************************************************** // **************************************************************************
_$SnWalletImpl _$$SnWalletImplFromJson(Map<String, dynamic> json) => _SnWallet _$SnWalletFromJson(Map<String, dynamic> json) => _SnWallet(
_$SnWalletImpl(
id: (json['id'] as num).toInt(), id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String), createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String), updatedAt: DateTime.parse(json['updated_at'] as String),
@@ -19,8 +18,7 @@ _$SnWalletImpl _$$SnWalletImplFromJson(Map<String, dynamic> json) =>
accountId: (json['account_id'] as num).toInt(), accountId: (json['account_id'] as num).toInt(),
); );
Map<String, dynamic> _$$SnWalletImplToJson(_$SnWalletImpl instance) => Map<String, dynamic> _$SnWalletToJson(_SnWallet instance) => <String, dynamic>{
<String, dynamic>{
'id': instance.id, 'id': instance.id,
'created_at': instance.createdAt.toIso8601String(), 'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(), 'updated_at': instance.updatedAt.toIso8601String(),
@@ -30,8 +28,8 @@ Map<String, dynamic> _$$SnWalletImplToJson(_$SnWalletImpl instance) =>
'account_id': instance.accountId, 'account_id': instance.accountId,
}; };
_$SnTransactionImpl _$$SnTransactionImplFromJson(Map<String, dynamic> json) => _SnTransaction _$SnTransactionFromJson(Map<String, dynamic> json) =>
_$SnTransactionImpl( _SnTransaction(
id: (json['id'] as num).toInt(), id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String), createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String), updatedAt: DateTime.parse(json['updated_at'] as String),
@@ -50,7 +48,7 @@ _$SnTransactionImpl _$$SnTransactionImplFromJson(Map<String, dynamic> json) =>
payeeId: (json['payee_id'] as num?)?.toInt(), payeeId: (json['payee_id'] as num?)?.toInt(),
); );
Map<String, dynamic> _$$SnTransactionImplToJson(_$SnTransactionImpl instance) => Map<String, dynamic> _$SnTransactionToJson(_SnTransaction instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id, 'id': instance.id,
'created_at': instance.createdAt.toIso8601String(), 'created_at': instance.createdAt.toIso8601String(),

View File

@@ -4,7 +4,7 @@ part 'websocket.freezed.dart';
part 'websocket.g.dart'; part 'websocket.g.dart';
@freezed @freezed
class WebSocketPackage with _$WebSocketPackage { abstract class WebSocketPackage with _$WebSocketPackage {
const factory WebSocketPackage({ const factory WebSocketPackage({
@JsonKey(name: 'w') @Default('unknown') String method, @JsonKey(name: 'w') @Default('unknown') String method,
@JsonKey(name: 'e') String? endpoint, @JsonKey(name: 'e') String? endpoint,

View File

@@ -1,3 +1,4 @@
// dart format width=80
// coverage:ignore-file // coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint // ignore_for_file: type=lint
@@ -9,97 +10,59 @@ part of 'websocket.dart';
// FreezedGenerator // FreezedGenerator
// ************************************************************************** // **************************************************************************
// dart format off
T _$identity<T>(T value) => value; T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
WebSocketPackage _$WebSocketPackageFromJson(Map<String, dynamic> json) {
return _WebSocketPackage.fromJson(json);
}
/// @nodoc /// @nodoc
mixin _$WebSocketPackage { mixin _$WebSocketPackage {
@JsonKey(name: 'w') @JsonKey(name: 'w')
String get method => throw _privateConstructorUsedError; String get method;
@JsonKey(name: 'e') @JsonKey(name: 'e')
String? get endpoint => throw _privateConstructorUsedError; String? get endpoint;
@JsonKey(name: 'm') @JsonKey(name: 'm')
String? get message => throw _privateConstructorUsedError; String? get message;
@JsonKey(name: 'p') @JsonKey(name: 'p')
Map<String, dynamic>? get payload => throw _privateConstructorUsedError; Map<String, dynamic>? get payload;
/// Serializes this WebSocketPackage to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of WebSocketPackage /// Create a copy of WebSocketPackage
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
$WebSocketPackageCopyWith<WebSocketPackage> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $WebSocketPackageCopyWith<$Res> {
factory $WebSocketPackageCopyWith(
WebSocketPackage value, $Res Function(WebSocketPackage) then) =
_$WebSocketPackageCopyWithImpl<$Res, WebSocketPackage>;
@useResult
$Res call(
{@JsonKey(name: 'w') String method,
@JsonKey(name: 'e') String? endpoint,
@JsonKey(name: 'm') String? message,
@JsonKey(name: 'p') Map<String, dynamic>? payload});
}
/// @nodoc
class _$WebSocketPackageCopyWithImpl<$Res, $Val extends WebSocketPackage>
implements $WebSocketPackageCopyWith<$Res> {
_$WebSocketPackageCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of WebSocketPackage
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
$WebSocketPackageCopyWith<WebSocketPackage> get copyWith =>
_$WebSocketPackageCopyWithImpl<WebSocketPackage>(
this as WebSocketPackage, _$identity);
/// Serializes this WebSocketPackage to a JSON map.
Map<String, dynamic> toJson();
@override @override
$Res call({ bool operator ==(Object other) {
Object? method = null, return identical(this, other) ||
Object? endpoint = freezed, (other.runtimeType == runtimeType &&
Object? message = freezed, other is WebSocketPackage &&
Object? payload = freezed, (identical(other.method, method) || other.method == method) &&
}) { (identical(other.endpoint, endpoint) ||
return _then(_value.copyWith( other.endpoint == endpoint) &&
method: null == method (identical(other.message, message) || other.message == message) &&
? _value.method const DeepCollectionEquality().equals(other.payload, payload));
: method // ignore: cast_nullable_to_non_nullable }
as String,
endpoint: freezed == endpoint @JsonKey(includeFromJson: false, includeToJson: false)
? _value.endpoint @override
: endpoint // ignore: cast_nullable_to_non_nullable int get hashCode => Object.hash(runtimeType, method, endpoint, message,
as String?, const DeepCollectionEquality().hash(payload));
message: freezed == message
? _value.message @override
: message // ignore: cast_nullable_to_non_nullable String toString() {
as String?, return 'WebSocketPackage(method: $method, endpoint: $endpoint, message: $message, payload: $payload)';
payload: freezed == payload
? _value.payload
: payload // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?,
) as $Val);
} }
} }
/// @nodoc /// @nodoc
abstract class _$$WebSocketPackageImplCopyWith<$Res> abstract mixin class $WebSocketPackageCopyWith<$Res> {
implements $WebSocketPackageCopyWith<$Res> { factory $WebSocketPackageCopyWith(
factory _$$WebSocketPackageImplCopyWith(_$WebSocketPackageImpl value, WebSocketPackage value, $Res Function(WebSocketPackage) _then) =
$Res Function(_$WebSocketPackageImpl) then) = _$WebSocketPackageCopyWithImpl;
__$$WebSocketPackageImplCopyWithImpl<$Res>;
@override
@useResult @useResult
$Res call( $Res call(
{@JsonKey(name: 'w') String method, {@JsonKey(name: 'w') String method,
@@ -109,12 +72,12 @@ abstract class _$$WebSocketPackageImplCopyWith<$Res>
} }
/// @nodoc /// @nodoc
class __$$WebSocketPackageImplCopyWithImpl<$Res> class _$WebSocketPackageCopyWithImpl<$Res>
extends _$WebSocketPackageCopyWithImpl<$Res, _$WebSocketPackageImpl> implements $WebSocketPackageCopyWith<$Res> {
implements _$$WebSocketPackageImplCopyWith<$Res> { _$WebSocketPackageCopyWithImpl(this._self, this._then);
__$$WebSocketPackageImplCopyWithImpl(_$WebSocketPackageImpl _value,
$Res Function(_$WebSocketPackageImpl) _then) final WebSocketPackage _self;
: super(_value, _then); final $Res Function(WebSocketPackage) _then;
/// Create a copy of WebSocketPackage /// Create a copy of WebSocketPackage
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@@ -126,21 +89,21 @@ class __$$WebSocketPackageImplCopyWithImpl<$Res>
Object? message = freezed, Object? message = freezed,
Object? payload = freezed, Object? payload = freezed,
}) { }) {
return _then(_$WebSocketPackageImpl( return _then(_self.copyWith(
method: null == method method: null == method
? _value.method ? _self.method
: method // ignore: cast_nullable_to_non_nullable : method // ignore: cast_nullable_to_non_nullable
as String, as String,
endpoint: freezed == endpoint endpoint: freezed == endpoint
? _value.endpoint ? _self.endpoint
: endpoint // ignore: cast_nullable_to_non_nullable : endpoint // ignore: cast_nullable_to_non_nullable
as String?, as String?,
message: freezed == message message: freezed == message
? _value.message ? _self.message
: message // ignore: cast_nullable_to_non_nullable : message // ignore: cast_nullable_to_non_nullable
as String?, as String?,
payload: freezed == payload payload: freezed == payload
? _value._payload ? _self.payload
: payload // ignore: cast_nullable_to_non_nullable : payload // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?, as Map<String, dynamic>?,
)); ));
@@ -149,16 +112,15 @@ class __$$WebSocketPackageImplCopyWithImpl<$Res>
/// @nodoc /// @nodoc
@JsonSerializable() @JsonSerializable()
class _$WebSocketPackageImpl implements _WebSocketPackage { class _WebSocketPackage implements WebSocketPackage {
const _$WebSocketPackageImpl( const _WebSocketPackage(
{@JsonKey(name: 'w') this.method = 'unknown', {@JsonKey(name: 'w') this.method = 'unknown',
@JsonKey(name: 'e') this.endpoint, @JsonKey(name: 'e') this.endpoint,
@JsonKey(name: 'm') this.message, @JsonKey(name: 'm') this.message,
@JsonKey(name: 'p') final Map<String, dynamic>? payload = const {}}) @JsonKey(name: 'p') final Map<String, dynamic>? payload = const {}})
: _payload = payload; : _payload = payload;
factory _WebSocketPackage.fromJson(Map<String, dynamic> json) =>
factory _$WebSocketPackageImpl.fromJson(Map<String, dynamic> json) => _$WebSocketPackageFromJson(json);
_$$WebSocketPackageImplFromJson(json);
@override @override
@JsonKey(name: 'w') @JsonKey(name: 'w')
@@ -180,16 +142,26 @@ class _$WebSocketPackageImpl implements _WebSocketPackage {
return EqualUnmodifiableMapView(value); return EqualUnmodifiableMapView(value);
} }
/// Create a copy of WebSocketPackage
/// with the given fields replaced by the non-null parameter values.
@override @override
String toString() { @JsonKey(includeFromJson: false, includeToJson: false)
return 'WebSocketPackage(method: $method, endpoint: $endpoint, message: $message, payload: $payload)'; @pragma('vm:prefer-inline')
_$WebSocketPackageCopyWith<_WebSocketPackage> get copyWith =>
__$WebSocketPackageCopyWithImpl<_WebSocketPackage>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$WebSocketPackageToJson(
this,
);
} }
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || return identical(this, other) ||
(other.runtimeType == runtimeType && (other.runtimeType == runtimeType &&
other is _$WebSocketPackageImpl && other is _WebSocketPackage &&
(identical(other.method, method) || other.method == method) && (identical(other.method, method) || other.method == method) &&
(identical(other.endpoint, endpoint) || (identical(other.endpoint, endpoint) ||
other.endpoint == endpoint) && other.endpoint == endpoint) &&
@@ -202,51 +174,64 @@ class _$WebSocketPackageImpl implements _WebSocketPackage {
int get hashCode => Object.hash(runtimeType, method, endpoint, message, int get hashCode => Object.hash(runtimeType, method, endpoint, message,
const DeepCollectionEquality().hash(_payload)); const DeepCollectionEquality().hash(_payload));
/// Create a copy of WebSocketPackage
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override @override
@pragma('vm:prefer-inline') String toString() {
_$$WebSocketPackageImplCopyWith<_$WebSocketPackageImpl> get copyWith => return 'WebSocketPackage(method: $method, endpoint: $endpoint, message: $message, payload: $payload)';
__$$WebSocketPackageImplCopyWithImpl<_$WebSocketPackageImpl>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$WebSocketPackageImplToJson(
this,
);
} }
} }
abstract class _WebSocketPackage implements WebSocketPackage { /// @nodoc
const factory _WebSocketPackage( abstract mixin class _$WebSocketPackageCopyWith<$Res>
{@JsonKey(name: 'w') final String method, implements $WebSocketPackageCopyWith<$Res> {
@JsonKey(name: 'e') final String? endpoint, factory _$WebSocketPackageCopyWith(
@JsonKey(name: 'm') final String? message, _WebSocketPackage value, $Res Function(_WebSocketPackage) _then) =
@JsonKey(name: 'p') final Map<String, dynamic>? payload}) = __$WebSocketPackageCopyWithImpl;
_$WebSocketPackageImpl; @override
@useResult
$Res call(
{@JsonKey(name: 'w') String method,
@JsonKey(name: 'e') String? endpoint,
@JsonKey(name: 'm') String? message,
@JsonKey(name: 'p') Map<String, dynamic>? payload});
}
factory _WebSocketPackage.fromJson(Map<String, dynamic> json) = /// @nodoc
_$WebSocketPackageImpl.fromJson; class __$WebSocketPackageCopyWithImpl<$Res>
implements _$WebSocketPackageCopyWith<$Res> {
__$WebSocketPackageCopyWithImpl(this._self, this._then);
@override final _WebSocketPackage _self;
@JsonKey(name: 'w') final $Res Function(_WebSocketPackage) _then;
String get method;
@override
@JsonKey(name: 'e')
String? get endpoint;
@override
@JsonKey(name: 'm')
String? get message;
@override
@JsonKey(name: 'p')
Map<String, dynamic>? get payload;
/// Create a copy of WebSocketPackage /// Create a copy of WebSocketPackage
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@override @override
@JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline')
_$$WebSocketPackageImplCopyWith<_$WebSocketPackageImpl> get copyWith => $Res call({
throw _privateConstructorUsedError; Object? method = null,
Object? endpoint = freezed,
Object? message = freezed,
Object? payload = freezed,
}) {
return _then(_WebSocketPackage(
method: null == method
? _self.method
: method // ignore: cast_nullable_to_non_nullable
as String,
endpoint: freezed == endpoint
? _self.endpoint
: endpoint // ignore: cast_nullable_to_non_nullable
as String?,
message: freezed == message
? _self.message
: message // ignore: cast_nullable_to_non_nullable
as String?,
payload: freezed == payload
? _self._payload
: payload // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?,
));
}
} }
// dart format on

View File

@@ -6,17 +6,15 @@ part of 'websocket.dart';
// JsonSerializableGenerator // JsonSerializableGenerator
// ************************************************************************** // **************************************************************************
_$WebSocketPackageImpl _$$WebSocketPackageImplFromJson( _WebSocketPackage _$WebSocketPackageFromJson(Map<String, dynamic> json) =>
Map<String, dynamic> json) => _WebSocketPackage(
_$WebSocketPackageImpl(
method: json['w'] as String? ?? 'unknown', method: json['w'] as String? ?? 'unknown',
endpoint: json['e'] as String?, endpoint: json['e'] as String?,
message: json['m'] as String?, message: json['m'] as String?,
payload: json['p'] as Map<String, dynamic>? ?? const {}, payload: json['p'] as Map<String, dynamic>? ?? const {},
); );
Map<String, dynamic> _$$WebSocketPackageImplToJson( Map<String, dynamic> _$WebSocketPackageToJson(_WebSocketPackage instance) =>
_$WebSocketPackageImpl instance) =>
<String, dynamic>{ <String, dynamic>{
'w': instance.method, 'w': instance.method,
'e': instance.endpoint, 'e': instance.endpoint,

View File

@@ -10,6 +10,7 @@ class AccountImage extends StatelessWidget {
final Color? foregroundColor; final Color? foregroundColor;
final double? radius; final double? radius;
final Widget? fallbackWidget; final Widget? fallbackWidget;
final Widget? badge;
const AccountImage({ const AccountImage({
super.key, super.key,
@@ -18,6 +19,7 @@ class AccountImage extends StatelessWidget {
this.foregroundColor, this.foregroundColor,
this.radius, this.radius,
this.fallbackWidget, this.fallbackWidget,
this.badge,
}); });
@override @override
@@ -26,26 +28,36 @@ class AccountImage extends StatelessWidget {
final url = sn.getAttachmentUrl(content ?? ''); final url = sn.getAttachmentUrl(content ?? '');
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio; final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
return CircleAvatar( return Stack(
key: Key('attachment-${content.hashCode}'), children: [
radius: radius, CircleAvatar(
backgroundColor: backgroundColor, key: Key('attachment-${content.hashCode}'),
backgroundImage: (content?.isNotEmpty ?? false) radius: radius,
? ResizeImage( backgroundColor: backgroundColor,
UniversalImage.provider(url), backgroundImage: (content?.isNotEmpty ?? false)
width: ((radius ?? 20) * devicePixelRatio * 2).round(), ? ResizeImage(
height: ((radius ?? 20) * devicePixelRatio * 2).round(), UniversalImage.provider(url),
policy: ResizeImagePolicy.fit, width: ((radius ?? 20) * devicePixelRatio * 2).round(),
) height: ((radius ?? 20) * devicePixelRatio * 2).round(),
: null, policy: ResizeImagePolicy.fit,
child: (content?.isEmpty ?? true) )
? (fallbackWidget ?? : null,
Icon( child: (content?.isEmpty ?? true)
Symbols.account_circle, ? (fallbackWidget ??
size: radius != null ? radius! * 1.2 : 24, Icon(
color: foregroundColor, Symbols.account_circle,
)) size: radius != null ? radius! * 1.2 : 24,
: null, color: foregroundColor,
))
: null,
),
if (badge != null)
Positioned(
right: -4,
bottom: -4,
child: badge!,
),
],
); );
} }
} }

View File

@@ -1,5 +1,4 @@
import 'dart:async'; import 'dart:async';
import 'dart:developer';
import 'package:cross_file/cross_file.dart'; import 'package:cross_file/cross_file.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
@@ -8,6 +7,7 @@ import 'package:gap/gap.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
import 'package:surface/controllers/post_write_controller.dart'; import 'package:surface/controllers/post_write_controller.dart';
import 'package:surface/logger.dart';
import 'package:surface/widgets/dialog.dart'; import 'package:surface/widgets/dialog.dart';
import 'package:video_compress/video_compress.dart'; import 'package:video_compress/video_compress.dart';
@@ -17,10 +17,12 @@ class PendingVideoCompressDialog extends StatefulWidget {
const PendingVideoCompressDialog({super.key, required this.media}); const PendingVideoCompressDialog({super.key, required this.media});
@override @override
State<PendingVideoCompressDialog> createState() => _PendingVideoCompressDialogState(); State<PendingVideoCompressDialog> createState() =>
_PendingVideoCompressDialogState();
} }
class _PendingVideoCompressDialogState extends State<PendingVideoCompressDialog> { class _PendingVideoCompressDialogState
extends State<PendingVideoCompressDialog> {
VideoQuality _quality = VideoQuality.DefaultQuality; VideoQuality _quality = VideoQuality.DefaultQuality;
bool _isBusy = false; bool _isBusy = false;
@@ -50,7 +52,7 @@ class _PendingVideoCompressDialogState extends State<PendingVideoCompressDialog>
void initState() { void initState() {
super.initState(); super.initState();
_progressSubscription = VideoCompress.compressProgress$.subscribe((event) { _progressSubscription = VideoCompress.compressProgress$.subscribe((event) {
log('[Compress] Progress: $event'); logging.debug('[Paperclip.VideoCompress] Progress: $event');
setState(() { setState(() {
_progress = event / 100; _progress = event / 100;
_isBusy = event < 100; _isBusy = event < 100;
@@ -132,7 +134,9 @@ class _PendingVideoCompressDialogState extends State<PendingVideoCompressDialog>
), ),
), ),
const Gap(8), const Gap(8),
Text('attachmentCompressQualityHint', style: Theme.of(context).textTheme.bodySmall!).tr(), Text('attachmentCompressQualityHint',
style: Theme.of(context).textTheme.bodySmall!)
.tr(),
if (_isBusy) if (_isBusy)
TweenAnimationBuilder<double>( TweenAnimationBuilder<double>(
tween: Tween(begin: 0, end: _progress ?? 0), tween: Tween(begin: 0, end: _progress ?? 0),

View File

@@ -11,6 +11,7 @@ import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/config.dart'; import 'package:surface/providers/config.dart';
import 'package:surface/providers/user_directory.dart'; import 'package:surface/providers/user_directory.dart';
import 'package:surface/providers/userinfo.dart'; import 'package:surface/providers/userinfo.dart';
import 'package:surface/screens/account/profile_page.dart';
import 'package:surface/types/chat.dart'; import 'package:surface/types/chat.dart';
import 'package:surface/widgets/account/account_image.dart'; import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/account/account_popover.dart'; import 'package:surface/widgets/account/account_popover.dart';
@@ -105,6 +106,22 @@ class ChatMessage extends StatelessWidget {
GestureDetector( GestureDetector(
child: AccountImage( child: AccountImage(
content: user?.avatar, content: user?.avatar,
badge: (user?.badges.isNotEmpty ?? false)
? Icon(
kBadgesMeta[user!.badges.first.type]?.$2 ??
Symbols.question_mark,
color: kBadgesMeta[user.badges.first.type]?.$3,
fill: 1,
size: 18,
shadows: [
Shadow(
offset: Offset(1, 1),
blurRadius: 5.0,
color: Color.fromARGB(150, 0, 0, 0),
),
],
)
: null,
), ),
onTap: () { onTap: () {
if (user == null) return; if (user == null) return;
@@ -161,7 +178,7 @@ class ChatMessage extends StatelessWidget {
if (data.preload?.quoteEvent != null) if (data.preload?.quoteEvent != null)
StyledWidget(Container( StyledWidget(Container(
constraints: BoxConstraints( constraints: BoxConstraints(
maxWidth: 480, maxWidth: 360,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: borderRadius:
@@ -210,10 +227,12 @@ class ChatMessage extends StatelessWidget {
AttachmentList( AttachmentList(
data: data.preload!.attachments!, data: data.preload!.attachments!,
bordered: true, bordered: true,
maxHeight: 560, maxHeight: 360,
maxWidth: 480, maxWidth: 480 - 48 - padding.left,
minWidth: 480, padding: padding.copyWith(
padding: padding.copyWith(top: 8, left: 48 + padding.left), top: 8,
left: isCompact ? padding.left : 48 + padding.left,
),
), ),
if (!hasMerged && !isCompact) if (!hasMerged && !isCompact)
const Gap(12) const Gap(12)
@@ -292,14 +311,11 @@ class _ChatMessageText extends StatelessWidget {
buttonItems: items, buttonItems: items,
); );
}, },
child: Container( child: MarkdownTextContent(
constraints: const BoxConstraints(maxWidth: 480), content: data.body['text'],
child: MarkdownTextContent( isAutoWarp: true,
content: data.body['text'], isEnlargeSticker:
isAutoWarp: true, RegExp(r"^:([-\w]+):$").hasMatch(data.body['text'] ?? ''),
isEnlargeSticker:
RegExp(r"^:([-\w]+):$").hasMatch(data.body['text'] ?? ''),
),
), ),
), ),
if (data.updatedAt != data.createdAt) if (data.updatedAt != data.createdAt)

View File

@@ -28,7 +28,8 @@ class ChatMessageInput extends StatefulWidget {
final ChatMessageController controller; final ChatMessageController controller;
final SnChannelMember? otherMember; final SnChannelMember? otherMember;
const ChatMessageInput({super.key, required this.controller, this.otherMember}); const ChatMessageInput(
{super.key, required this.controller, this.otherMember});
@override @override
State<ChatMessageInput> createState() => ChatMessageInputState(); State<ChatMessageInput> createState() => ChatMessageInputState();
@@ -45,12 +46,20 @@ class ChatMessageInputState extends State<ChatMessageInput> {
final HotKey _pasteHotKey = HotKey( final HotKey _pasteHotKey = HotKey(
key: PhysicalKeyboardKey.keyV, key: PhysicalKeyboardKey.keyV,
modifiers: [(!kIsWeb && Platform.isMacOS) ? HotKeyModifier.meta : HotKeyModifier.control], modifiers: [
(!kIsWeb && Platform.isMacOS)
? HotKeyModifier.meta
: HotKeyModifier.control
],
scope: HotKeyScope.inapp, scope: HotKeyScope.inapp,
); );
final HotKey _newLineHotKey = HotKey( final HotKey _newLineHotKey = HotKey(
key: PhysicalKeyboardKey.enter, key: PhysicalKeyboardKey.enter,
modifiers: [(!kIsWeb && Platform.isMacOS) ? HotKeyModifier.meta : HotKeyModifier.control], modifiers: [
(!kIsWeb && Platform.isMacOS)
? HotKeyModifier.meta
: HotKeyModifier.control
],
scope: HotKeyScope.inapp, scope: HotKeyScope.inapp,
); );
@@ -100,7 +109,8 @@ class ChatMessageInputState extends State<ChatMessageInput> {
void setEdit(SnChatMessage? value) { void setEdit(SnChatMessage? value) {
_contentController.text = value?.body['text'] ?? ''; _contentController.text = value?.body['text'] ?? '';
_attachments.clear(); _attachments.clear();
_attachments.addAll(value?.preload?.attachments?.map((e) => PostWriteMedia(e)) ?? []); _attachments.addAll(
value?.preload?.attachments?.map((e) => PostWriteMedia(e)) ?? []);
setState(() => _editingMessage = value); setState(() => _editingMessage = value);
} }
@@ -139,7 +149,9 @@ class ChatMessageInputState extends State<ChatMessageInput> {
media.name, media.name,
'messaging', 'messaging',
null, null,
mimetype: media.raw != null && media.type == SnMediaType.image ? 'image/png' : null, mimetype: media.raw != null && media.type == SnMediaType.image
? 'image/png'
: null,
); );
final item = await attach.chunkedUploadParts( final item = await attach.chunkedUploadParts(
@@ -171,7 +183,10 @@ class ChatMessageInputState extends State<ChatMessageInput> {
widget.controller.sendMessage( widget.controller.sendMessage(
_editingMessage != null ? 'messages.edit' : 'messages.new', _editingMessage != null ? 'messages.edit' : 'messages.new',
_contentController.text, _contentController.text,
attachments: _attachments.where((e) => e.attachment != null).map((e) => e.attachment!.rid).toList(), attachments: _attachments
.where((e) => e.attachment != null)
.map((e) => e.attachment!.rid)
.toList(),
relatedId: _editingMessage?.id, relatedId: _editingMessage?.id,
quoteId: _replyingMessage?.id, quoteId: _replyingMessage?.id,
editingMessage: _editingMessage, editingMessage: _editingMessage,
@@ -232,12 +247,15 @@ class ChatMessageInputState extends State<ChatMessageInput> {
TweenAnimationBuilder<double>( TweenAnimationBuilder<double>(
tween: Tween(begin: 0, end: _progress), tween: Tween(begin: 0, end: _progress),
duration: Duration(milliseconds: 300), duration: Duration(milliseconds: 300),
builder: (context, value, _) => LinearProgressIndicator(value: value, minHeight: 2), builder: (context, value, _) =>
LinearProgressIndicator(value: value, minHeight: 2),
) )
else if (_isBusy) else if (_isBusy)
const LinearProgressIndicator(value: null, minHeight: 2), const LinearProgressIndicator(value: null, minHeight: 2),
Padding( Padding(
padding: _attachments.isNotEmpty ? const EdgeInsets.only(top: 8) : EdgeInsets.zero, padding: _attachments.isNotEmpty
? const EdgeInsets.only(top: 8)
: EdgeInsets.zero,
child: PostMediaPendingList( child: PostMediaPendingList(
attachments: _attachments, attachments: _attachments,
isBusy: _isBusy, isBusy: _isBusy,
@@ -249,9 +267,8 @@ class ChatMessageInputState extends State<ChatMessageInput> {
}, },
onUpdateBusy: (state) => setState(() => _isBusy = state), onUpdateBusy: (state) => setState(() => _isBusy = state),
), ),
) ).height(_attachments.isNotEmpty ? 80 + 8 : 0, animate: true).animate(
.height(_attachments.isNotEmpty ? 80 + 8 : 0, animate: true) const Duration(milliseconds: 300), Curves.fastEaseInToSlowEaseOut),
.animate(const Duration(milliseconds: 300), Curves.fastEaseInToSlowEaseOut),
SingleChildScrollView( SingleChildScrollView(
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
child: _replyingMessage != null child: _replyingMessage != null
@@ -272,7 +289,8 @@ class ChatMessageInputState extends State<ChatMessageInput> {
const Gap(8), const Gap(8),
Expanded( Expanded(
child: Text( child: Text(
_replyingMessage?.body['text'] ?? '${_replyingMessage?.sender.nick}', _replyingMessage?.body['text'] ??
'${_replyingMessage?.sender.nick}',
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
@@ -289,9 +307,8 @@ class ChatMessageInputState extends State<ChatMessageInput> {
).padding(vertical: 8), ).padding(vertical: 8),
) )
: const SizedBox.shrink(), : const SizedBox.shrink(),
) ).height(_replyingMessage != null ? 38 : 0, animate: true).animate(
.height(_replyingMessage != null ? 38 : 0, animate: true) const Duration(milliseconds: 300), Curves.fastEaseInToSlowEaseOut),
.animate(const Duration(milliseconds: 300), Curves.fastEaseInToSlowEaseOut),
SingleChildScrollView( SingleChildScrollView(
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
child: _editingMessage != null child: _editingMessage != null
@@ -312,7 +329,8 @@ class ChatMessageInputState extends State<ChatMessageInput> {
const Gap(8), const Gap(8),
Expanded( Expanded(
child: Text( child: Text(
_editingMessage?.body['text'] ?? '${_editingMessage?.sender.nick}', _editingMessage?.body['text'] ??
'${_editingMessage?.sender.nick}',
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
@@ -330,12 +348,13 @@ class ChatMessageInputState extends State<ChatMessageInput> {
).padding(vertical: 8), ).padding(vertical: 8),
) )
: const SizedBox.shrink(), : const SizedBox.shrink(),
) ).height(_editingMessage != null ? 38 : 0, animate: true).animate(
.height(_editingMessage != null ? 38 : 0, animate: true) const Duration(milliseconds: 300), Curves.fastEaseInToSlowEaseOut),
.animate(const Duration(milliseconds: 300), Curves.fastEaseInToSlowEaseOut), Container(
SizedBox( padding: EdgeInsets.symmetric(horizontal: 16),
height: 56, constraints: BoxConstraints(minHeight: 56, maxHeight: 240),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
Expanded( Expanded(
child: TextField( child: TextField(
@@ -347,11 +366,14 @@ class ChatMessageInputState extends State<ChatMessageInput> {
? 'fieldChatMessageDirect'.tr(args: [ ? 'fieldChatMessageDirect'.tr(args: [
'@${ud.getAccountFromCache(widget.otherMember?.accountId)?.name}', '@${ud.getAccountFromCache(widget.otherMember?.accountId)?.name}',
]) ])
: 'fieldChatMessage'.tr(args: [widget.controller.channel?.name ?? 'loading'.tr()]), : 'fieldChatMessage'.tr(args: [
widget.controller.channel?.name ?? 'loading'.tr()
]),
border: InputBorder.none, border: InputBorder.none,
), ),
textInputAction: TextInputAction.send, textInputAction: TextInputAction.send,
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
onSubmitted: (_) { onSubmitted: (_) {
if (_isBusy) return; if (_isBusy) return;
_sendMessage(); _sendMessage();
@@ -366,7 +388,8 @@ class ChatMessageInputState extends State<ChatMessageInput> {
Symbols.mood, Symbols.mood,
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
), ),
visualDensity: const VisualDensity(horizontal: -4, vertical: -4), visualDensity:
const VisualDensity(horizontal: -4, vertical: -4),
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
constraints: const BoxConstraints(), constraints: const BoxConstraints(),
onPressed: () { onPressed: () {
@@ -386,13 +409,14 @@ class ChatMessageInputState extends State<ChatMessageInput> {
Symbols.send, Symbols.send,
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
), ),
visualDensity: const VisualDensity(horizontal: -4, vertical: -4), visualDensity:
const VisualDensity(horizontal: -4, vertical: -4),
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
constraints: const BoxConstraints(), constraints: const BoxConstraints(),
), ),
], ],
), ),
).padding(horizontal: 16), ),
], ],
); );
} }
@@ -403,7 +427,8 @@ class _StickerPicker extends StatelessWidget {
final Function? onDismiss; final Function? onDismiss;
final Function(String)? onInsert; final Function(String)? onInsert;
const _StickerPicker({this.onDismiss, required this.originalText, this.onInsert}); const _StickerPicker(
{this.onDismiss, required this.originalText, this.onInsert});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -413,7 +438,9 @@ class _StickerPicker extends StatelessWidget {
onDismiss?.call(); onDismiss?.call();
}, },
child: Container( child: Container(
constraints: BoxConstraints(maxWidth: min(360, MediaQuery.of(context).size.width), maxHeight: 240), constraints: BoxConstraints(
maxWidth: min(360, MediaQuery.of(context).size.width),
maxHeight: 240),
child: Material( child: Material(
elevation: 8, elevation: 8,
borderRadius: const BorderRadius.all(Radius.circular(8)), borderRadius: const BorderRadius.all(Radius.circular(8)),
@@ -426,8 +453,10 @@ class _StickerPicker extends StatelessWidget {
return <Widget>[ return <Widget>[
Container( Container(
margin: EdgeInsets.only(bottom: 8), margin: EdgeInsets.only(bottom: 8),
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), padding:
color: Theme.of(context).colorScheme.surfaceContainerHigh, EdgeInsets.symmetric(horizontal: 8, vertical: 4),
color:
Theme.of(context).colorScheme.surfaceContainerHigh,
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -439,7 +468,8 @@ class _StickerPicker extends StatelessWidget {
), ),
GridView.builder( GridView.builder(
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
padding: const EdgeInsets.only(left: 8, right: 8, bottom: 8), padding:
const EdgeInsets.only(left: 8, right: 8, bottom: 8),
shrinkWrap: true, shrinkWrap: true,
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 48, maxCrossAxisExtent: 48,
@@ -462,7 +492,8 @@ class _StickerPicker extends StatelessWidget {
richMessage: TextSpan( richMessage: TextSpan(
children: [ children: [
TextSpan( TextSpan(
text: ':${element.pack.prefix}${element.alias}:\n', text:
':${element.pack.prefix}${element.alias}:\n',
style: GoogleFonts.robotoMono()), style: GoogleFonts.robotoMono()),
TextSpan(text: element.name).bold(), TextSpan(text: element.name).bold(),
], ],
@@ -471,11 +502,15 @@ class _StickerPicker extends StatelessWidget {
width: 48, width: 48,
height: 48, height: 48,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(8)), borderRadius: const BorderRadius.all(
color: Theme.of(context).colorScheme.surfaceContainerHigh, Radius.circular(8)),
color: Theme.of(context)
.colorScheme
.surfaceContainerHigh,
), ),
child: ClipRRect( child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)), borderRadius: const BorderRadius.all(
Radius.circular(8)),
child: UniversalImage( child: UniversalImage(
sn.getAttachmentUrl(element.attachment.rid), sn.getAttachmentUrl(element.attachment.rid),
width: 48, width: 48,

View File

@@ -133,7 +133,8 @@ extension AppPromptExtension on BuildContext {
), ),
recognizer: TapGestureRecognizer() recognizer: TapGestureRecognizer()
..onTap = () { ..onTap = () {
launchUrlString('https://kb.solsynth.dev/solar-network'); launchUrlString(
'https://kb.solsynth.dev/solar-network');
}, },
), ),
], ],
@@ -157,7 +158,17 @@ extension ByteFormatter on int {
if (this == 0) return '0 Bytes'; if (this == 0) return '0 Bytes';
const k = 1024; const k = 1024;
final dm = decimals < 0 ? 0 : decimals; final dm = decimals < 0 ? 0 : decimals;
final sizes = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']; final sizes = [
'Bytes',
'KiB',
'MiB',
'GiB',
'TiB',
'PiB',
'EiB',
'ZiB',
'YiB'
];
final i = (math.log(this) / math.log(k)).floor().toInt(); final i = (math.log(this) / math.log(k)).floor().toInt();
return '${(this / math.pow(k, i)).toStringAsFixed(dm)} ${sizes[i]}'; return '${(this / math.pow(k, i)).toStringAsFixed(dm)} ${sizes[i]}';
} }
@@ -167,4 +178,15 @@ extension StringFormatter on String {
String capitalize() { String capitalize() {
return "${this[0].toUpperCase()}${substring(1)}"; return "${this[0].toUpperCase()}${substring(1)}";
} }
String capitalizeEachWord() {
if (isEmpty) {
return this;
}
return split(' ')
.map((word) => word.isNotEmpty
? '${word[0].toUpperCase()}${word.substring(1)}'
: '')
.join(' ');
}
} }

View File

@@ -1,6 +1,9 @@
import 'package:dismissible_page/dismissible_page.dart'; import 'package:dismissible_page/dismissible_page.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_highlight/flutter_highlight.dart';
import 'package:flutter_highlight/theme_map.dart';
import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:flutter_markdown_latex/flutter_markdown_latex.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import 'package:markdown/markdown.dart' as markdown; import 'package:markdown/markdown.dart' as markdown;
@@ -72,21 +75,27 @@ class MarkdownTextContent extends StatelessWidget {
), ),
code: GoogleFonts.robotoMono(height: 1), code: GoogleFonts.robotoMono(height: 1),
), ),
builders: {}, builders: {
'latex': LatexElementBuilder(),
'code': HighlightBuilder(),
},
softLineBreak: true, softLineBreak: true,
extensionSet: markdown.ExtensionSet( extensionSet: markdown.ExtensionSet(
<markdown.BlockSyntax>[ <markdown.BlockSyntax>[
markdown.CodeBlockSyntax(),
...markdown.ExtensionSet.gitHubFlavored.blockSyntaxes, ...markdown.ExtensionSet.gitHubFlavored.blockSyntaxes,
markdown.CodeBlockSyntax(),
markdown.FencedCodeBlockSyntax(),
LatexBlockSyntax(),
], ],
<markdown.InlineSyntax>[ <markdown.InlineSyntax>[
...markdown.ExtensionSet.gitHubFlavored.inlineSyntaxes,
if (isAutoWarp) markdown.LineBreakSyntax(), if (isAutoWarp) markdown.LineBreakSyntax(),
_UserNameCardInlineSyntax(), _UserNameCardInlineSyntax(),
_CustomEmoteInlineSyntax(context), _CustomEmoteInlineSyntax(context),
markdown.AutolinkSyntax(), markdown.AutolinkSyntax(),
markdown.AutolinkExtensionSyntax(), markdown.AutolinkExtensionSyntax(),
markdown.CodeSyntax(), markdown.CodeSyntax(),
...markdown.ExtensionSet.gitHubFlavored.inlineSyntaxes LatexInlineSyntax(),
], ],
), ),
onTapLink: (text, href, title) async { onTapLink: (text, href, title) async {
@@ -260,3 +269,56 @@ class _CustomEmoteInlineSyntax extends markdown.InlineSyntax {
return true; return true;
} }
} }
class HighlightBuilder extends MarkdownElementBuilder {
@override
Widget? visitElementAfterWithContext(
BuildContext context,
markdown.Element element,
TextStyle? preferredStyle,
TextStyle? parentStyle,
) {
final isDark = Theme.of(context).brightness == Brightness.dark;
if (element.attributes['class'] == null &&
!element.textContent.trim().contains('\n')) {
return Container(
padding:
EdgeInsets.only(top: 0.0, right: 4.0, bottom: 1.75, left: 4.0),
margin: EdgeInsets.symmetric(horizontal: 2.0),
color: Colors.black12,
child: Text(
element.textContent,
style: GoogleFonts.robotoMono(textStyle: preferredStyle),
));
} else {
var language = 'plaintext';
final pattern = RegExp(r'^language-(.+)$');
if (element.attributes['class'] != null &&
pattern.hasMatch(element.attributes['class'] ?? '')) {
language =
pattern.firstMatch(element.attributes['class'] ?? '')?.group(1) ??
'plaintext';
}
return ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)),
child: HighlightView(
element.textContent.trim(),
language: language,
theme: {
...(isDark ? themeMap['a11y-dark']! : themeMap['a11y-light']!),
'root': (isDark
? TextStyle(
backgroundColor: Colors.transparent,
color: Color(0xfff8f8f2))
: TextStyle(
backgroundColor: Colors.transparent,
color: Color(0xff545454)))
},
padding: EdgeInsets.all(12),
textStyle: GoogleFonts.robotoMono(textStyle: preferredStyle),
),
);
}
}
}

View File

@@ -188,29 +188,19 @@ class AppRootScaffold extends StatelessWidget {
child: Text( child: Text(
'Solar Network', 'Solar Network',
style: GoogleFonts.spaceGrotesk(), style: GoogleFonts.spaceGrotesk(),
textAlign: !kIsWeb textAlign: Platform.isMacOS
? Platform.isMacOS ? TextAlign.center
? TextAlign.center : TextAlign.start,
: null
: null,
).padding(horizontal: 12, vertical: 5), ).padding(horizontal: 12, vertical: 5),
), ),
if (!Platform.isMacOS) if (!Platform.isMacOS)
Row( MinimizeWindowButton(colors: windowButtonColor),
mainAxisSize: MainAxisSize.min, if (!Platform.isMacOS)
children: [ MaximizeWindowButton(colors: windowButtonColor),
Expanded(child: MoveWindow()), if (!Platform.isMacOS)
Row( CloseWindowButton(
children: [ colors: windowButtonColor,
MinimizeWindowButton( onPressed: () => appWindow.hide(),
colors: windowButtonColor),
MaximizeWindowButton(
colors: windowButtonColor),
CloseWindowButton(
colors: windowButtonColor),
],
),
],
), ),
], ],
), ),
@@ -226,16 +216,18 @@ class AppRootScaffold extends StatelessWidget {
child: NotifyIndicator()), child: NotifyIndicator()),
if (ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE)) if (ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE))
Positioned( Positioned(
bottom: safeBottom > 0 ? safeBottom : 16, bottom: safeBottom > 0 ? safeBottom : 16,
left: 0, left: 0,
right: 0, right: 0,
child: ConnectionIndicator()) child: ConnectionIndicator(),
)
else else
Positioned( Positioned(
top: safeTop > 0 ? safeTop : 16, top: safeTop > 0 ? safeTop : 16,
left: 0, left: 0,
right: 0, right: 0,
child: ConnectionIndicator()), child: ConnectionIndicator(),
),
], ],
), ),
drawer: !isExpandedDrawer ? AppNavigationDrawer() : null, drawer: !isExpandedDrawer ? AppNavigationDrawer() : null,

View File

@@ -92,10 +92,9 @@ class OpenablePostItem extends StatelessWidget {
openColor: Colors.transparent, openColor: Colors.transparent,
openElevation: 0, openElevation: 0,
transitionType: ContainerTransitionType.fade, transitionType: ContainerTransitionType.fade,
closedColor: closedColor: Theme.of(context).colorScheme.surfaceContainerLow.withOpacity(
Theme.of(context).colorScheme.surfaceContainerLow.withOpacity( cfg.prefs.getBool(kAppBackgroundStoreKey) == true ? 0.75 : 1,
cfg.prefs.getBool(kAppBackgroundStoreKey) == true ? 0.75 : 1, ),
),
closedShape: const RoundedRectangleBorder( closedShape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(16)), borderRadius: BorderRadius.all(Radius.circular(16)),
), ),
@@ -136,11 +135,9 @@ class PostItem extends StatelessWidget {
final box = context.findRenderObject() as RenderBox?; final box = context.findRenderObject() as RenderBox?;
final url = 'https://solsynth.dev/posts/${data.id}'; final url = 'https://solsynth.dev/posts/${data.id}';
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) { if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
Share.shareUri(Uri.parse(url), Share.shareUri(Uri.parse(url), sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size);
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size);
} else { } else {
Share.share(url, Share.share(url, sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size);
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size);
} }
} }
@@ -158,8 +155,7 @@ class PostItem extends StatelessWidget {
child: MultiProvider( child: MultiProvider(
providers: [ providers: [
Provider<SnNetworkProvider>(create: (_) => context.read()), Provider<SnNetworkProvider>(create: (_) => context.read()),
ChangeNotifierProvider<ConfigProvider>( ChangeNotifierProvider<ConfigProvider>(create: (_) => context.read()),
create: (_) => context.read()),
], ],
child: ResponsiveBreakpoints.builder( child: ResponsiveBreakpoints.builder(
breakpoints: ResponsiveBreakpoints.of(context).breakpoints, breakpoints: ResponsiveBreakpoints.of(context).breakpoints,
@@ -187,8 +183,7 @@ class PostItem extends StatelessWidget {
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size, sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
); );
} else { } else {
await FileSaver.instance.saveFile( await FileSaver.instance.saveFile(name: 'Solar Network Post #${data.id}.png', file: imageFile);
name: 'Solar Network Post #${data.id}.png', file: imageFile);
} }
await imageFile.delete(); await imageFile.delete();
@@ -202,9 +197,7 @@ class PostItem extends StatelessWidget {
final isAuthor = ua.isAuthorized && data.publisher.accountId == ua.user?.id; final isAuthor = ua.isAuthorized && data.publisher.accountId == ua.user?.id;
// Video full view // Video full view
if (showFullPost && if (showFullPost && data.type == 'video' && ResponsiveBreakpoints.of(context).largerThan(TABLET)) {
data.type == 'video' &&
ResponsiveBreakpoints.of(context).largerThan(TABLET)) {
return Row( return Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@@ -224,8 +217,7 @@ class PostItem extends StatelessWidget {
if (onDeleted != null) {} if (onDeleted != null) {}
}, },
).padding(bottom: 8), ).padding(bottom: 8),
if (data.preload?.video != null) if (data.preload?.video != null) _PostVideoPlayer(data: data).padding(bottom: 8),
_PostVideoPlayer(data: data).padding(bottom: 8),
_PostHeadline(data: data).padding(horizontal: 4, bottom: 8), _PostHeadline(data: data).padding(horizontal: 4, bottom: 8),
_PostFeaturedComment(data: data), _PostFeaturedComment(data: data),
_PostBottomAction( _PostBottomAction(
@@ -273,8 +265,7 @@ class PostItem extends StatelessWidget {
if (onDeleted != null) {} if (onDeleted != null) {}
}, },
).padding(horizontal: 12, top: 8, bottom: 8), ).padding(horizontal: 12, top: 8, bottom: 8),
if (data.preload?.video != null) if (data.preload?.video != null) _PostVideoPlayer(data: data).padding(horizontal: 12, bottom: 8),
_PostVideoPlayer(data: data).padding(horizontal: 12, bottom: 8),
Container( Container(
width: double.infinity, width: double.infinity,
margin: const EdgeInsets.only(bottom: 4, left: 12, right: 12), margin: const EdgeInsets.only(bottom: 4, left: 12, right: 12),
@@ -317,13 +308,8 @@ class PostItem extends StatelessWidget {
], ],
), ),
), ),
Text('postArticle') Text('postArticle').tr().fontSize(13).opacity(0.75).padding(horizontal: 24, bottom: 8),
.tr() _PostFeaturedComment(data: data, maxWidth: maxWidth).padding(horizontal: 12),
.fontSize(13)
.opacity(0.75)
.padding(horizontal: 24, bottom: 8),
_PostFeaturedComment(data: data, maxWidth: maxWidth)
.padding(horizontal: 12),
_PostBottomAction( _PostBottomAction(
data: data, data: data,
showComments: showComments, showComments: showComments,
@@ -338,8 +324,7 @@ class PostItem extends StatelessWidget {
} }
final displayableAttachments = data.preload?.attachments final displayableAttachments = data.preload?.attachments
?.where((ele) => ?.where((ele) => ele?.mediaType != SnMediaType.image || data.type != 'article')
ele?.mediaType != SnMediaType.image || data.type != 'article')
.toList(); .toList();
final cfg = context.read<ConfigProvider>(); final cfg = context.read<ConfigProvider>();
@@ -364,13 +349,9 @@ class PostItem extends StatelessWidget {
if (onDeleted != null) onDeleted!(); if (onDeleted != null) onDeleted!();
}, },
).padding(horizontal: 12, vertical: 8), ).padding(horizontal: 12, vertical: 8),
if (data.preload?.video != null) if (data.preload?.video != null) _PostVideoPlayer(data: data).padding(horizontal: 12, bottom: 8),
_PostVideoPlayer(data: data).padding(horizontal: 12, bottom: 8), if (data.type == 'question') _PostQuestionHint(data: data).padding(horizontal: 16, bottom: 8),
if (data.type == 'question') if (data.body['title'] != null || data.body['description'] != null)
_PostQuestionHint(data: data)
.padding(horizontal: 16, bottom: 8),
if (data.body['title'] != null ||
data.body['description'] != null)
_PostHeadline( _PostHeadline(
data: data, data: data,
isEnlarge: data.type == 'article' && showFullPost, isEnlarge: data.type == 'article' && showFullPost,
@@ -384,8 +365,7 @@ class PostItem extends StatelessWidget {
if (data.repostTo != null) if (data.repostTo != null)
_PostQuoteContent(child: data.repostTo!).padding( _PostQuoteContent(child: data.repostTo!).padding(
horizontal: 12, horizontal: 12,
bottom: bottom: data.preload?.attachments?.isNotEmpty ?? false ? 12 : 0,
data.preload?.attachments?.isNotEmpty ?? false ? 12 : 0,
), ),
if (data.visibility > 0) if (data.visibility > 0)
_PostVisibilityHint(data: data).padding( _PostVisibilityHint(data: data).padding(
@@ -397,9 +377,7 @@ class PostItem extends StatelessWidget {
horizontal: 16, horizontal: 16,
vertical: 4, vertical: 4,
), ),
if (data.tags.isNotEmpty) if (data.tags.isNotEmpty) _PostTagsList(data: data).padding(horizontal: 16, top: 4, bottom: 6),
_PostTagsList(data: data)
.padding(horizontal: 16, top: 4, bottom: 6),
], ],
), ),
), ),
@@ -412,16 +390,12 @@ class PostItem extends StatelessWidget {
fit: showFullPost ? BoxFit.cover : BoxFit.contain, fit: showFullPost ? BoxFit.cover : BoxFit.contain,
padding: const EdgeInsets.symmetric(horizontal: 12), padding: const EdgeInsets.symmetric(horizontal: 12),
), ),
if (data.preload?.poll != null) if (data.preload?.poll != null) PostPoll(poll: data.preload!.poll!).padding(horizontal: 12, vertical: 4),
PostPoll(poll: data.preload!.poll!) if (data.body['content'] != null && (cfg.prefs.getBool(kAppExpandPostLink) ?? true))
.padding(horizontal: 12, vertical: 4),
if (data.body['content'] != null &&
(cfg.prefs.getBool(kAppExpandPostLink) ?? true))
LinkPreviewWidget( LinkPreviewWidget(
text: data.body['content'], text: data.body['content'],
).padding(horizontal: 4), ).padding(horizontal: 4),
_PostFeaturedComment(data: data, maxWidth: maxWidth) _PostFeaturedComment(data: data, maxWidth: maxWidth).padding(horizontal: 12),
.padding(horizontal: 12),
Container( Container(
constraints: BoxConstraints(maxWidth: maxWidth ?? double.infinity), constraints: BoxConstraints(maxWidth: maxWidth ?? double.infinity),
child: Column( child: Column(
@@ -483,8 +457,7 @@ class PostShareImageWidget extends StatelessWidget {
showMenu: false, showMenu: false,
isRelativeDate: false, isRelativeDate: false,
).padding(horizontal: 16, bottom: 8), ).padding(horizontal: 16, bottom: 8),
if (data.type == 'question') if (data.type == 'question') _PostQuestionHint(data: data).padding(horizontal: 16, bottom: 8),
_PostQuestionHint(data: data).padding(horizontal: 16, bottom: 8),
_PostHeadline( _PostHeadline(
data: data, data: data,
isEnlarge: data.type == 'article', isEnlarge: data.type == 'article',
@@ -499,8 +472,7 @@ class PostShareImageWidget extends StatelessWidget {
child: data.repostTo!, child: data.repostTo!,
isRelativeDate: false, isRelativeDate: false,
).padding(horizontal: 16, bottom: 8), ).padding(horizontal: 16, bottom: 8),
if (data.type != 'article' && if (data.type != 'article' && (data.preload?.attachments?.isNotEmpty ?? false))
(data.preload?.attachments?.isNotEmpty ?? false))
StyledWidget(AttachmentList( StyledWidget(AttachmentList(
data: data.preload!.attachments!, data: data.preload!.attachments!,
columned: true, columned: true,
@@ -509,8 +481,7 @@ class PostShareImageWidget extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (data.visibility > 0) _PostVisibilityHint(data: data), if (data.visibility > 0) _PostVisibilityHint(data: data),
if (data.body['content_truncated'] == true) if (data.body['content_truncated'] == true) _PostTruncatedHint(data: data),
_PostTruncatedHint(data: data),
], ],
).padding(horizontal: 16), ).padding(horizontal: 16),
_PostBottomAction( _PostBottomAction(
@@ -570,8 +541,7 @@ class PostShareImageWidget extends StatelessWidget {
version: QrVersions.auto, version: QrVersions.auto,
size: 100, size: 100,
gapless: true, gapless: true,
embeddedImage: embeddedImage: AssetImage('assets/icon/icon-light-radius.png'),
AssetImage('assets/icon/icon-light-radius.png'),
embeddedImageStyle: QrEmbeddedImageStyle( embeddedImageStyle: QrEmbeddedImageStyle(
size: Size(28, 28), size: Size(28, 28),
), ),
@@ -602,11 +572,9 @@ class _PostQuestionHint extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Row( return Row(
children: [ children: [
Icon(data.body['answer'] == null ? Symbols.help : Symbols.check_circle, Icon(data.body['answer'] == null ? Symbols.help : Symbols.check_circle, size: 20),
size: 20),
const Gap(4), const Gap(4),
if (data.body['answer'] == null && if (data.body['answer'] == null && data.body['reward']?.toDouble() != null)
data.body['reward']?.toDouble() != null)
Text('postQuestionUnansweredWithReward'.tr(args: [ Text('postQuestionUnansweredWithReward'.tr(args: [
'${data.body['reward']}', '${data.body['reward']}',
])).opacity(0.75) ])).opacity(0.75)
@@ -642,9 +610,7 @@ class _PostBottomAction extends StatelessWidget {
); );
final String? mostTypicalReaction = data.metric.reactionList.isNotEmpty final String? mostTypicalReaction = data.metric.reactionList.isNotEmpty
? data.metric.reactionList.entries ? data.metric.reactionList.entries.reduce((a, b) => a.value > b.value ? a : b).key
.reduce((a, b) => a.value > b.value ? a : b)
.key
: null; : null;
return Row( return Row(
@@ -658,8 +624,7 @@ class _PostBottomAction extends StatelessWidget {
InkWell( InkWell(
child: Row( child: Row(
children: [ children: [
if (mostTypicalReaction == null || if (mostTypicalReaction == null || kTemplateReactions[mostTypicalReaction] == null)
kTemplateReactions[mostTypicalReaction] == null)
Icon(Symbols.add_reaction, size: 20, color: iconColor) Icon(Symbols.add_reaction, size: 20, color: iconColor)
else else
Text( Text(
@@ -671,8 +636,7 @@ class _PostBottomAction extends StatelessWidget {
), ),
), ),
const Gap(8), const Gap(8),
if (data.totalUpvote > 0 && if (data.totalUpvote > 0 && data.totalUpvote >= data.totalDownvote)
data.totalUpvote >= data.totalDownvote)
Text('postReactionUpvote').plural( Text('postReactionUpvote').plural(
data.totalUpvote, data.totalUpvote,
) )
@@ -691,12 +655,8 @@ class _PostBottomAction extends StatelessWidget {
data: data, data: data,
onChanged: (value, attr, delta) { onChanged: (value, attr, delta) {
onChanged(data.copyWith( onChanged(data.copyWith(
totalUpvote: attr == 1 totalUpvote: attr == 1 ? data.totalUpvote + delta : data.totalUpvote,
? data.totalUpvote + delta totalDownvote: attr == 2 ? data.totalDownvote + delta : data.totalDownvote,
: data.totalUpvote,
totalDownvote: attr == 2
? data.totalDownvote + delta
: data.totalDownvote,
metric: data.metric.copyWith(reactionList: value), metric: data.metric.copyWith(reactionList: value),
)); ));
}, },
@@ -803,7 +763,7 @@ class _PostHeadline extends StatelessWidget {
children: [ children: [
Text( Text(
'articleWrittenAt'.tr( 'articleWrittenAt'.tr(
args: [DateFormat('y/M/d HH:mm').format(data.createdAt)], args: [DateFormat('y/M/d HH:mm').format(data.createdAt.toLocal())],
), ),
style: TextStyle(fontSize: 13), style: TextStyle(fontSize: 13),
), ),
@@ -811,7 +771,7 @@ class _PostHeadline extends StatelessWidget {
if (data.editedAt != null) if (data.editedAt != null)
Text( Text(
'articleEditedAt'.tr( 'articleEditedAt'.tr(
args: [DateFormat('y/M/d HH:mm').format(data.editedAt!)], args: [DateFormat('y/M/d HH:mm').format(data.editedAt!.toLocal())],
), ),
style: TextStyle(fontSize: 13), style: TextStyle(fontSize: 13),
), ),
@@ -944,10 +904,8 @@ class _PostContentHeader extends StatelessWidget {
const Gap(4), const Gap(4),
Text( Text(
isRelativeDate isRelativeDate
? RelativeTime(context) ? RelativeTime(context).format((data.publishedAt ?? data.createdAt).toLocal())
.format(data.publishedAt ?? data.createdAt) : DateFormat('y/M/d HH:mm').format((data.publishedAt ?? data.createdAt).toLocal()),
: DateFormat('y/M/d HH:mm')
.format(data.publishedAt ?? data.createdAt),
).fontSize(13), ).fontSize(13),
], ],
).opacity(0.8), ).opacity(0.8),
@@ -965,10 +923,8 @@ class _PostContentHeader extends StatelessWidget {
const Gap(4), const Gap(4),
Text( Text(
isRelativeDate isRelativeDate
? RelativeTime(context) ? RelativeTime(context).format((data.publishedAt ?? data.createdAt).toLocal())
.format(data.publishedAt ?? data.createdAt) : DateFormat('y/M/d HH:mm').format((data.publishedAt ?? data.createdAt).toLocal()),
: DateFormat('y/M/d HH:mm')
.format(data.publishedAt ?? data.createdAt),
).fontSize(13), ).fontSize(13),
], ],
).opacity(0.8), ).opacity(0.8),
@@ -1151,8 +1107,7 @@ class _PostContentBody extends StatelessWidget {
if (data.body['content'] == null) return const SizedBox.shrink(); if (data.body['content'] == null) return const SizedBox.shrink();
final content = MarkdownTextContent( final content = MarkdownTextContent(
isAutoWarp: data.type == 'story', isAutoWarp: data.type == 'story',
isEnlargeSticker: isEnlargeSticker: RegExp(r"^:([-\w]+):$").hasMatch(data.body['content'] ?? ''),
RegExp(r"^:([-\w]+):$").hasMatch(data.body['content'] ?? ''),
textScaler: isEnlarge ? TextScaler.linear(1.1) : null, textScaler: isEnlarge ? TextScaler.linear(1.1) : null,
content: data.body['content'], content: data.body['content'],
attachments: data.preload?.attachments, attachments: data.preload?.attachments,
@@ -1201,12 +1156,10 @@ class _PostQuoteContent extends StatelessWidget {
onDeleted: () {}, onDeleted: () {},
).padding(bottom: 4), ).padding(bottom: 4),
_PostContentBody(data: child), _PostContentBody(data: child),
if (child.visibility > 0) if (child.visibility > 0) _PostVisibilityHint(data: child).padding(top: 4),
_PostVisibilityHint(data: child).padding(top: 4),
], ],
).padding(horizontal: 16), ).padding(horizontal: 16),
if (child.type != 'article' && if (child.type != 'article' && (child.preload?.attachments?.isNotEmpty ?? false))
(child.preload?.attachments?.isNotEmpty ?? false))
ClipRRect( ClipRRect(
borderRadius: const BorderRadius.only( borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(8), bottomLeft: Radius.circular(8),
@@ -1357,9 +1310,7 @@ class _PostTruncatedHint extends StatelessWidget {
const Gap(4), const Gap(4),
Text('postReadEstimate').tr(args: [ Text('postReadEstimate').tr(args: [
'${Duration( '${Duration(
seconds: (data.body['content_length'] as num).toDouble() * seconds: (data.body['content_length'] as num).toDouble() * 60 ~/ kHumanReadSpeed,
60 ~/
kHumanReadSpeed,
).inSeconds}s', ).inSeconds}s',
]), ]),
], ],
@@ -1398,8 +1349,7 @@ class _PostFeaturedCommentState extends State<_PostFeaturedComment> {
// If this is a answered question, fetch the answer instead // If this is a answered question, fetch the answer instead
if (widget.data.type == 'question' && widget.data.body['answer'] != null) { if (widget.data.type == 'question' && widget.data.body['answer'] != null) {
final sn = context.read<SnNetworkProvider>(); final sn = context.read<SnNetworkProvider>();
final resp = final resp = await sn.client.get('/cgi/co/posts/${widget.data.body['answer']}');
await sn.client.get('/cgi/co/posts/${widget.data.body['answer']}');
_isAnswer = true; _isAnswer = true;
setState(() => _featuredComment = SnPost.fromJson(resp.data)); setState(() => _featuredComment = SnPost.fromJson(resp.data));
return; return;
@@ -1407,11 +1357,9 @@ class _PostFeaturedCommentState extends State<_PostFeaturedComment> {
try { try {
final sn = context.read<SnNetworkProvider>(); final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.get( final resp = await sn.client.get('/cgi/co/posts/${widget.data.id}/replies/featured', queryParameters: {
'/cgi/co/posts/${widget.data.id}/replies/featured', 'take': 1,
queryParameters: { });
'take': 1,
});
setState(() => _featuredComment = SnPost.fromJson(resp.data[0])); setState(() => _featuredComment = SnPost.fromJson(resp.data[0]));
} catch (err) { } catch (err) {
if (!mounted) return; if (!mounted) return;
@@ -1440,9 +1388,7 @@ class _PostFeaturedCommentState extends State<_PostFeaturedComment> {
width: double.infinity, width: double.infinity,
child: Material( child: Material(
borderRadius: const BorderRadius.all(Radius.circular(8)), borderRadius: const BorderRadius.all(Radius.circular(8)),
color: _isAnswer color: _isAnswer ? Colors.green.withOpacity(0.5) : Theme.of(context).colorScheme.surfaceContainerHigh,
? Colors.green.withOpacity(0.5)
: Theme.of(context).colorScheme.surfaceContainerHigh,
child: InkWell( child: InkWell(
borderRadius: const BorderRadius.all(Radius.circular(8)), borderRadius: const BorderRadius.all(Radius.circular(8)),
onTap: () { onTap: () {
@@ -1462,17 +1408,11 @@ class _PostFeaturedCommentState extends State<_PostFeaturedComment> {
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
const Gap(2), const Gap(2),
Icon(_isAnswer ? Symbols.task_alt : Symbols.prompt_suggestion, Icon(_isAnswer ? Symbols.task_alt : Symbols.prompt_suggestion, size: 20),
size: 20),
const Gap(10), const Gap(10),
Text( Text(
_isAnswer _isAnswer ? 'postQuestionAnswerTitle' : 'postFeaturedComment',
? 'postQuestionAnswerTitle' style: Theme.of(context).textTheme.titleMedium!.copyWith(fontSize: 15),
: 'postFeaturedComment',
style: Theme.of(context)
.textTheme
.titleMedium!
.copyWith(fontSize: 15),
).tr(), ).tr(),
], ],
), ),
@@ -1610,8 +1550,7 @@ class _PostGetInsightPopupState extends State<_PostGetInsightPopup> {
} }
RegExp cleanThinkingRegExp = RegExp(r'<think>[\s\S]*?</think>'); RegExp cleanThinkingRegExp = RegExp(r'<think>[\s\S]*?</think>');
setState( setState(() => _response = out.replaceAll(cleanThinkingRegExp, '').trim());
() => _response = out.replaceAll(cleanThinkingRegExp, '').trim());
} catch (err) { } catch (err) {
if (!mounted) return; if (!mounted) return;
context.showErrorDialog(err); context.showErrorDialog(err);
@@ -1634,16 +1573,11 @@ class _PostGetInsightPopupState extends State<_PostGetInsightPopup> {
children: [ children: [
const Icon(Symbols.book_4_spark, size: 24), const Icon(Symbols.book_4_spark, size: 24),
const Gap(16), const Gap(16),
Text('postGetInsightTitle', Text('postGetInsightTitle', style: Theme.of(context).textTheme.titleLarge).tr(),
style: Theme.of(context).textTheme.titleLarge)
.tr(),
], ],
).padding(horizontal: 20, top: 16, bottom: 12), ).padding(horizontal: 20, top: 16, bottom: 12),
const Gap(4), const Gap(4),
Text('postGetInsightDescription', Text('postGetInsightDescription', style: Theme.of(context).textTheme.bodySmall).tr().padding(horizontal: 20),
style: Theme.of(context).textTheme.bodySmall)
.tr()
.padding(horizontal: 20),
const Gap(4), const Gap(4),
if (_response == null) if (_response == null)
Expanded( Expanded(
@@ -1661,16 +1595,12 @@ class _PostGetInsightPopupState extends State<_PostGetInsightPopup> {
leading: const Icon(Symbols.info), leading: const Icon(Symbols.info),
title: Text('aiThinkingProcess'.tr()), title: Text('aiThinkingProcess'.tr()),
tilePadding: const EdgeInsets.symmetric(horizontal: 20), tilePadding: const EdgeInsets.symmetric(horizontal: 20),
collapsedBackgroundColor: collapsedBackgroundColor: Theme.of(context).colorScheme.surfaceContainerHigh,
Theme.of(context).colorScheme.surfaceContainerHigh,
minTileHeight: 32, minTileHeight: 32,
children: [ children: [
SelectableText( SelectableText(
_thinkingProcess!, _thinkingProcess!,
style: Theme.of(context) style: Theme.of(context).textTheme.bodyMedium!.copyWith(fontStyle: FontStyle.italic),
.textTheme
.bodyMedium!
.copyWith(fontStyle: FontStyle.italic),
).padding(horizontal: 20, vertical: 8), ).padding(horizontal: 20, vertical: 8),
], ],
).padding(vertical: 8), ).padding(vertical: 8),
@@ -1707,8 +1637,7 @@ class _PostVideoPlayer extends StatelessWidget {
aspectRatio: 16 / 9, aspectRatio: 16 / 9,
child: ClipRRect( child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)), borderRadius: const BorderRadius.all(Radius.circular(8)),
child: AttachmentItem( child: AttachmentItem(data: data.preload!.video!, heroTag: 'post-video-${data.id}'),
data: data.preload!.video!, heroTag: 'post-video-${data.id}'),
), ),
), ),
); );

View File

@@ -67,7 +67,10 @@ class PostMediaPendingList extends StatelessWidget {
if (result == null) return; if (result == null) return;
final rawBytes = (await result.uiImage.toByteData(format: ImageByteFormat.png))!.buffer.asUint8List(); final rawBytes =
(await result.uiImage.toByteData(format: ImageByteFormat.png))!
.buffer
.asUint8List();
if (onUpdate != null) { if (onUpdate != null) {
final updatedMedia = PostWriteMedia.fromBytes( final updatedMedia = PostWriteMedia.fromBytes(
@@ -133,7 +136,8 @@ class PostMediaPendingList extends StatelessWidget {
final result = await showDialog<SnAttachmentBoost?>( final result = await showDialog<SnAttachmentBoost?>(
context: context, context: context,
builder: (context) => PendingAttachmentBoostDialog(media: attachments[idx]), builder: (context) =>
PendingAttachmentBoostDialog(media: attachments[idx]),
); );
if (result == null) return; if (result == null) return;
@@ -165,11 +169,15 @@ class PostMediaPendingList extends StatelessWidget {
onUpdate!(idx, PostWriteMedia(result)); onUpdate!(idx, PostWriteMedia(result));
} }
ContextMenu _createContextMenu(BuildContext context, int idx, PostWriteMedia media) { ContextMenu _createContextMenu(
final canCompressVideo = !kIsWeb && (Platform.isAndroid || Platform.isIOS || Platform.isMacOS); BuildContext context, int idx, PostWriteMedia media) {
final canCompressVideo =
!kIsWeb && (Platform.isAndroid || Platform.isIOS || Platform.isMacOS);
return ContextMenu( return ContextMenu(
entries: [ entries: [
if (media.attachment == null && media.type == SnMediaType.video && canCompressVideo) if (media.attachment == null &&
media.type == SnMediaType.video &&
canCompressVideo)
MenuItem( MenuItem(
label: 'attachmentCompressVideo'.tr(), label: 'attachmentCompressVideo'.tr(),
icon: Symbols.compress, icon: Symbols.compress,
@@ -312,12 +320,15 @@ class _PostMediaPendingItem extends StatelessWidget {
AspectRatio( AspectRatio(
aspectRatio: 1, aspectRatio: 1,
child: switch (media.type) { child: switch (media.type) {
SnMediaType.image => LayoutBuilder(builder: (context, constraints) { SnMediaType.image =>
LayoutBuilder(builder: (context, constraints) {
return Image( return Image(
image: media.getImageProvider( image: media.getImageProvider(
context, context,
width: (constraints.maxWidth * devicePixelRatio).round(), width:
height: (constraints.maxHeight * devicePixelRatio).round(), (constraints.maxWidth * devicePixelRatio).round(),
height:
(constraints.maxHeight * devicePixelRatio).round(),
)!, )!,
fit: BoxFit.contain, fit: BoxFit.contain,
); );
@@ -326,28 +337,34 @@ class _PostMediaPendingItem extends StatelessWidget {
fit: StackFit.expand, fit: StackFit.expand,
children: [ children: [
if (media.attachment?.thumbnail != null) if (media.attachment?.thumbnail != null)
AutoResizeUniversalImage(sn.getAttachmentUrl(media.attachment!.thumbnail!.rid)), AutoResizeUniversalImage(sn.getAttachmentUrl(
const Icon(Symbols.videocam, color: Colors.white, shadows: [ media.attachment!.thumbnail!.rid)),
Shadow( const Icon(Symbols.videocam,
offset: Offset(1, 1), color: Colors.white,
blurRadius: 8.0, shadows: [
color: Color.fromARGB(255, 0, 0, 0), Shadow(
), offset: Offset(1, 1),
]), blurRadius: 8.0,
color: Color.fromARGB(255, 0, 0, 0),
),
]),
], ],
), ),
SnMediaType.audio => Stack( SnMediaType.audio => Stack(
fit: StackFit.expand, fit: StackFit.expand,
children: [ children: [
if (media.attachment?.thumbnail != null) if (media.attachment?.thumbnail != null)
AutoResizeUniversalImage(sn.getAttachmentUrl(media.attachment!.thumbnail!.rid)), AutoResizeUniversalImage(sn.getAttachmentUrl(
const Icon(Symbols.audio_file, color: Colors.white, shadows: [ media.attachment!.thumbnail!.rid)),
Shadow( const Icon(Symbols.audio_file,
offset: Offset(1, 1), color: Colors.white,
blurRadius: 8.0, shadows: [
color: Color.fromARGB(255, 0, 0, 0), Shadow(
), offset: Offset(1, 1),
]), blurRadius: 8.0,
color: Color.fromARGB(255, 0, 0, 0),
),
]),
], ],
), ),
_ => Container( _ => Container(
@@ -356,7 +373,8 @@ class _PostMediaPendingItem extends StatelessWidget {
), ),
}, },
), ),
if (media.type != SnMediaType.image) const VerticalDivider(width: 1, thickness: 1), if (media.type != SnMediaType.image)
const VerticalDivider(width: 1, thickness: 1),
if (media.type != SnMediaType.image) if (media.type != SnMediaType.image)
SizedBox( SizedBox(
width: 160, width: 160,
@@ -374,7 +392,8 @@ class _PostMediaPendingItem extends StatelessWidget {
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
) )
else if (media.file != null) else if (media.file != null)
Text(media.file!.name, maxLines: 1, overflow: TextOverflow.ellipsis) Text(media.file!.name,
maxLines: 1, overflow: TextOverflow.ellipsis)
else else
Text('unknown'.tr()), Text('unknown'.tr()),
if (media.attachment != null) if (media.attachment != null)
@@ -387,7 +406,9 @@ class _PostMediaPendingItem extends StatelessWidget {
FutureBuilder<int?>( FutureBuilder<int?>(
future: media.length(), future: media.length(),
builder: (context, snapshot) { builder: (context, snapshot) {
if (!snapshot.hasData) return const SizedBox.shrink(); if (!snapshot.hasData) {
return const SizedBox.shrink();
}
return Text( return Text(
snapshot.data!.formatBytes(), snapshot.data!.formatBytes(),
style: GoogleFonts.robotoMono(fontSize: 13), style: GoogleFonts.robotoMono(fontSize: 13),
@@ -398,7 +419,8 @@ class _PostMediaPendingItem extends StatelessWidget {
], ],
), ),
), ),
if (media.attachment != null && media.attachment!.boosts.isNotEmpty) if (media.attachment != null &&
media.attachment!.boosts.isNotEmpty)
Row( Row(
children: [ children: [
Icon(Symbols.bolt, size: 16), Icon(Symbols.bolt, size: 16),
@@ -406,7 +428,8 @@ class _PostMediaPendingItem extends StatelessWidget {
Text('attachmentGotBoosted').tr().fontSize(13), Text('attachmentGotBoosted').tr().fontSize(13),
], ],
), ),
if (media.attachment != null && media.attachment!.compressedId != null) if (media.attachment != null &&
media.attachment!.compressedId != null)
Row( Row(
children: [ children: [
Icon(Symbols.compress, size: 16), Icon(Symbols.compress, size: 16),
@@ -527,12 +550,20 @@ class AddPostMediaButton extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return PopupMenuButton( return PopupMenuButton(
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
style: ButtonStyle(
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
),
icon: Icon( icon: Icon(
Symbols.add_photo_alternate, Symbols.add_photo_alternate,
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
), ),
itemBuilder: (context) => [ itemBuilder: (context) => [
if (!kIsWeb && !Platform.isLinux && !Platform.isMacOS && !Platform.isWindows) if (!kIsWeb &&
!Platform.isLinux &&
!Platform.isMacOS &&
!Platform.isWindows)
PopupMenuItem( PopupMenuItem(
child: Row( child: Row(
children: [ children: [
@@ -545,7 +576,10 @@ class AddPostMediaButton extends StatelessWidget {
_takeMedia(false); _takeMedia(false);
}, },
), ),
if (!kIsWeb && !Platform.isLinux && !Platform.isMacOS && !Platform.isWindows) if (!kIsWeb &&
!Platform.isLinux &&
!Platform.isMacOS &&
!Platform.isWindows)
PopupMenuItem( PopupMenuItem(
child: Row( child: Row(
children: [ children: [

View File

@@ -9,6 +9,7 @@
#include <bitsdojo_window_linux/bitsdojo_window_plugin.h> #include <bitsdojo_window_linux/bitsdojo_window_plugin.h>
#include <file_saver/file_saver_plugin.h> #include <file_saver/file_saver_plugin.h>
#include <file_selector_linux/file_selector_plugin.h> #include <file_selector_linux/file_selector_plugin.h>
#include <flutter_timezone/flutter_timezone_plugin.h>
#include <flutter_udid/flutter_udid_plugin.h> #include <flutter_udid/flutter_udid_plugin.h>
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h> #include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
#include <hotkey_manager_linux/hotkey_manager_linux_plugin.h> #include <hotkey_manager_linux/hotkey_manager_linux_plugin.h>
@@ -30,6 +31,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
file_selector_plugin_register_with_registrar(file_selector_linux_registrar); file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
g_autoptr(FlPluginRegistrar) flutter_timezone_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterTimezonePlugin");
flutter_timezone_plugin_register_with_registrar(flutter_timezone_registrar);
g_autoptr(FlPluginRegistrar) flutter_udid_registrar = g_autoptr(FlPluginRegistrar) flutter_udid_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterUdidPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterUdidPlugin");
flutter_udid_plugin_register_with_registrar(flutter_udid_registrar); flutter_udid_plugin_register_with_registrar(flutter_udid_registrar);

View File

@@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
bitsdojo_window_linux bitsdojo_window_linux
file_saver file_saver
file_selector_linux file_selector_linux
flutter_timezone
flutter_udid flutter_udid
flutter_webrtc flutter_webrtc
hotkey_manager_linux hotkey_manager_linux

View File

@@ -15,9 +15,11 @@ import firebase_analytics
import firebase_core import firebase_core
import firebase_messaging import firebase_messaging
import flutter_inappwebview_macos import flutter_inappwebview_macos
import flutter_timezone
import flutter_udid import flutter_udid
import flutter_webrtc import flutter_webrtc
import gal import gal
import geolocator_apple
import hotkey_manager_macos import hotkey_manager_macos
import in_app_review import in_app_review
import livekit_client import livekit_client
@@ -48,9 +50,11 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin")) FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin"))
InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin")) InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin"))
FlutterTimezonePlugin.register(with: registry.registrar(forPlugin: "FlutterTimezonePlugin"))
FlutterUdidPlugin.register(with: registry.registrar(forPlugin: "FlutterUdidPlugin")) FlutterUdidPlugin.register(with: registry.registrar(forPlugin: "FlutterUdidPlugin"))
FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin")) FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin"))
GalPlugin.register(with: registry.registrar(forPlugin: "GalPlugin")) GalPlugin.register(with: registry.registrar(forPlugin: "GalPlugin"))
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
HotkeyManagerMacosPlugin.register(with: registry.registrar(forPlugin: "HotkeyManagerMacosPlugin")) HotkeyManagerMacosPlugin.register(with: registry.registrar(forPlugin: "HotkeyManagerMacosPlugin"))
InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin")) InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin"))
LiveKitPlugin.register(with: registry.registrar(forPlugin: "LiveKitPlugin")) LiveKitPlugin.register(with: registry.registrar(forPlugin: "LiveKitPlugin"))

View File

@@ -193,7 +193,7 @@ PODS:
- sqlite3_flutter_libs (0.0.1): - sqlite3_flutter_libs (0.0.1):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
- sqlite3 (~> 3.49.0) - sqlite3 (~> 3.49.1)
- sqlite3/dbstatvtab - sqlite3/dbstatvtab
- sqlite3/fts5 - sqlite3/fts5
- sqlite3/perf-threadsafe - sqlite3/perf-threadsafe
@@ -378,7 +378,7 @@ SPEC CHECKSUMS:
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983 sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983
sqlite3_flutter_libs: 069c435986dd4b63461aecd68f4b30be4a9e9daa sqlite3_flutter_libs: cc304edcb8e1d8c595d1b08c7aeb46a47691d9db
tray_manager: 9064e219c56d75c476e46b9a21182087930baf90 tray_manager: 9064e219c56d75c476e46b9a21182087930baf90
url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404 url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404
video_compress: c896234f100791b5fef7f049afa38f6d2ef7b42f video_compress: c896234f100791b5fef7f049afa38f6d2ef7b42f

View File

@@ -13,10 +13,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: _flutterfire_internals name: _flutterfire_internals
sha256: "401dd18096f5eaa140404ccbbbf346f83c850e6f27049698a7ee75a3488ddb32" sha256: "7fd72d77a7487c26faab1d274af23fb008763ddc10800261abbfb2c067f183d5"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.52" version: "1.3.53"
analyzer: analyzer:
dependency: transitive dependency: transitive
description: description:
@@ -45,10 +45,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: archive name: archive
sha256: "528579c7e4579719f04b21eeeeddfd73a18b31dabc22766893b7d1be7f49b967" sha256: "0c64e928dcbefddecd234205422bcfc2b5e6d31be0b86fef0d0dd48d7b4c9742"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.3" version: "4.0.4"
args: args:
dependency: transitive dependency: transitive
description: description:
@@ -173,10 +173,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: built_value name: built_value
sha256: "28a712df2576b63c6c005c465989a348604960c0958d28be5303ba9baa841ac2" sha256: "8b158ab94ec6913e480dc3f752418348b5ae099eb75868b5f4775f0572999c61"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.9.3" version: "8.9.4"
cached_network_image: cached_network_image:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -345,6 +345,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.8" version: "1.0.8"
dart_earcut:
dependency: transitive
description:
name: dart_earcut
sha256: e485001bfc05dcbc437d7bfb666316182e3522d4c3f9668048e004d0eb2ce43b
url: "https://pub.dev"
source: hosted
version: "1.2.0"
dart_style: dart_style:
dependency: transitive dependency: transitive
description: description:
@@ -373,10 +381,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: device_info_plus name: device_info_plus
sha256: "72d146c6d7098689ff5c5f66bcf593ac11efc530095385356e131070333e64da" sha256: "610739247975c2d0de43482afa13ec1018f63c9fddf97ef3d8dc895faa3b4543"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "11.3.0" version: "11.3.2"
device_info_plus_platform_interface: device_info_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
@@ -405,10 +413,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: dio_web_adapter name: dio_web_adapter
sha256: e485c7a39ff2b384fa1d7e09b4e25f755804de8384358049124830b04fc4f93a sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.0" version: "2.1.1"
dismissible_page: dismissible_page:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -525,10 +533,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: file_picker name: file_picker
sha256: "6f6bfa8797f296965bdc3e1f702574ab49a540c19b9237b401e7c2b25dfe594c" sha256: "7423298f08f6fc8cce05792bae329f9a93653fc9c08712831b1a55540127995d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "9.0.0" version: "9.0.2"
file_saver: file_saver:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -573,34 +581,34 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: firebase_analytics name: firebase_analytics
sha256: "6abce50b79729d8a13c3d4ae05ac612d5ef2f57394330bc5e581ca0e762325f4" sha256: "81a582e9348216fcf6b30878487369325bf78b8ddd752ed176949c8e4fd4aaac"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "11.4.3" version: "11.4.4"
firebase_analytics_platform_interface: firebase_analytics_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: firebase_analytics_platform_interface name: firebase_analytics_platform_interface
sha256: cd9ae65870bf23ab7e63a04fe9c1b38522fd3556a8c32288afd3f5cb10d4b8f4 sha256: "5ae7bd4a551b67009cd0676f5407331b202eaf16e0a80dcf7b40cd0a34a18746"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.3.3" version: "4.3.4"
firebase_analytics_web: firebase_analytics_web:
dependency: transitive dependency: transitive
description: description:
name: firebase_analytics_web name: firebase_analytics_web
sha256: "5654ed7e39d7a8099e60748924327159785512d78d913e965f9ca93c533af910" sha256: "15fd7459fea2a00958dbf9b86cd8ad14d3ce2db13950308af7c7717e89ccc5c2"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.10+9" version: "0.5.10+10"
firebase_core: firebase_core:
dependency: "direct main" dependency: "direct main"
description: description:
name: firebase_core name: firebase_core
sha256: "6a4ea0f1d533443c8afc3d809cd36a4e2b8f2e2e711f697974f55bb31d71d1b8" sha256: f4d8f49574a4e396f34567f3eec4d38ab9c3910818dec22ca42b2a467c685d8b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.12.0" version: "3.12.1"
firebase_core_platform_interface: firebase_core_platform_interface:
dependency: transitive dependency: transitive
description: description:
@@ -613,34 +621,34 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: firebase_core_web name: firebase_core_web
sha256: e47f5c2776de018fa19bc9f6f723df136bc75cdb164d64b65305babd715c8e41 sha256: faa5a76f6380a9b90b53bc3bdcb85bc7926a382e0709b9b5edac9f7746651493
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.21.0" version: "2.21.1"
firebase_messaging: firebase_messaging:
dependency: "direct main" dependency: "direct main"
description: description:
name: firebase_messaging name: firebase_messaging
sha256: "8755a083a20bac4485e8b46d223f6f2eab34e659a76a75f8cf3cded53bc98a15" sha256: "5fc345c6341f9dc69fd0ffcbf508c784fd6d1b9e9f249587f30434dd8b6aa281"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "15.2.3" version: "15.2.4"
firebase_messaging_platform_interface: firebase_messaging_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: firebase_messaging_platform_interface name: firebase_messaging_platform_interface
sha256: "8cc771079677460de53ad8fcca5bc3074d58c5fc4f9d89b19585e5bfd9c64292" sha256: a935924cf40925985c8049df4968b1dde5c704f570f3ce380b31d3de6990dd94
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.6.3" version: "4.6.4"
firebase_messaging_web: firebase_messaging_web:
dependency: transitive dependency: transitive
description: description:
name: firebase_messaging_web name: firebase_messaging_web
sha256: caa73059b0396c97f691683c4cfc3f897c8543801579b7dd4851c431d8e4e091 sha256: fafebf6a1921931334f3f10edb5037a5712288efdd022881e2d093e5654a2fd4
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.10.3" version: "3.10.4"
fixnum: fixnum:
dependency: transitive dependency: transitive
description: description:
@@ -679,7 +687,7 @@ packages:
source: hosted source: hosted
version: "3.2.2" version: "3.2.2"
flutter_cache_manager: flutter_cache_manager:
dependency: transitive dependency: "direct main"
description: description:
name: flutter_cache_manager name: flutter_cache_manager
sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386"
@@ -710,6 +718,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" version: "2.3.0"
flutter_highlight:
dependency: "direct main"
description:
name: flutter_highlight
sha256: "7b96333867aa07e122e245c033b8ad622e4e3a42a1a2372cbb098a2541d8782c"
url: "https://pub.dev"
source: hosted
version: "0.7.0"
flutter_inappwebview: flutter_inappwebview:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -795,6 +811,14 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_map:
dependency: "direct main"
description:
name: flutter_map
sha256: bbf145e8220531f2f727608c431871c7457f3b134e513543913afd00fdc1cd47
url: "https://pub.dev"
source: hosted
version: "8.1.0"
flutter_markdown: flutter_markdown:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -803,6 +827,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.6+2" version: "0.7.6+2"
flutter_markdown_latex:
dependency: "direct main"
description:
name: flutter_markdown_latex
sha256: "839e76a84abb3632ffcebbd450cf93c7e9894af65622527d23f0084cee1bfd04"
url: "https://pub.dev"
source: hosted
version: "0.3.4"
flutter_math_fork:
dependency: transitive
description:
name: flutter_math_fork
sha256: "284bab89b2fbf1bc3a0baf13d011c1dd324d004e35d177626b77f2fc056366ac"
url: "https://pub.dev"
source: hosted
version: "0.7.3"
flutter_native_splash: flutter_native_splash:
dependency: "direct dev" dependency: "direct dev"
description: description:
@@ -815,10 +855,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: flutter_plugin_android_lifecycle name: flutter_plugin_android_lifecycle
sha256: "615a505aef59b151b46bbeef55b36ce2b6ed299d160c51d84281946f0aa0ce0e" sha256: "1c2b787f99bdca1f3718543f81d38aa1b124817dfeb9fb196201bea85b6134bf"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.24" version: "2.0.26"
flutter_shaders: flutter_shaders:
dependency: transitive dependency: transitive
description: description:
@@ -848,6 +888,14 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_timezone:
dependency: "direct main"
description:
name: flutter_timezone
sha256: bc286cecb0366d88e6c4644e3962ebd1ce1d233abc658eb1e0cd803389f84b64
url: "https://pub.dev"
source: hosted
version: "4.1.0"
flutter_udid: flutter_udid:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -873,18 +921,18 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: freezed name: freezed
sha256: "59a584c24b3acdc5250bb856d0d3e9c0b798ed14a4af1ddb7dc1c7b41df91c9c" sha256: a3d6429368603a591ca7c1795799a247998fb213ded509070c2c59708b25df31
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.8" version: "3.0.2"
freezed_annotation: freezed_annotation:
dependency: "direct main" dependency: "direct main"
description: description:
name: freezed_annotation name: freezed_annotation
sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 sha256: c87ff004c8aa6af2d531668b46a4ea379f7191dc6dfa066acd53d506da6e044b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.4" version: "3.0.0"
frontend_server_client: frontend_server_client:
dependency: transitive dependency: transitive
description: description:
@@ -909,6 +957,54 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.1" version: "3.0.1"
geolocator:
dependency: "direct main"
description:
name: geolocator
sha256: d2ec66329cab29cb297d51d96c067d457ca519dca8589665fa0b82ebacb7dbe4
url: "https://pub.dev"
source: hosted
version: "13.0.2"
geolocator_android:
dependency: transitive
description:
name: geolocator_android
sha256: "7aefc530db47d90d0580b552df3242440a10fe60814496a979aa67aa98b1fd47"
url: "https://pub.dev"
source: hosted
version: "4.6.1"
geolocator_apple:
dependency: transitive
description:
name: geolocator_apple
sha256: c4ecead17985ede9634f21500072edfcb3dba0ef7b97f8d7bc556d2d722b3ba3
url: "https://pub.dev"
source: hosted
version: "2.3.9"
geolocator_platform_interface:
dependency: transitive
description:
name: geolocator_platform_interface
sha256: "386ce3d9cce47838355000070b1d0b13efb5bc430f8ecda7e9238c8409ace012"
url: "https://pub.dev"
source: hosted
version: "4.2.4"
geolocator_web:
dependency: transitive
description:
name: geolocator_web
sha256: "2ed69328e05cd94e7eb48bb0535f5fc0c0c44d1c4fa1e9737267484d05c29b5e"
url: "https://pub.dev"
source: hosted
version: "4.1.1"
geolocator_windows:
dependency: transitive
description:
name: geolocator_windows
sha256: "53da08937d07c24b0d9952eb57a3b474e29aae2abf9dd717f7e1230995f13f0e"
url: "https://pub.dev"
source: hosted
version: "0.2.3"
glob: glob:
dependency: transitive dependency: transitive
description: description:
@@ -921,10 +1017,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: go_router name: go_router
sha256: "04539267a740931c6d4479a10d466717ca5901c6fdfd3fcda09391bbb8ebd651" sha256: f02fd7d2a4dc512fec615529824fdd217fecb3a3d3de68360293a551f21634b3
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "14.8.0" version: "14.8.1"
google_fonts: google_fonts:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -941,6 +1037,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.2" version: "2.3.2"
group_button:
dependency: transitive
description:
name: group_button
sha256: "0610fcf28ed122bfb4b410fce161a390f7f2531d55d1d65c5375982001415940"
url: "https://pub.dev"
source: hosted
version: "5.3.4"
highlight:
dependency: transitive
description:
name: highlight
sha256: "5353a83ffe3e3eca7df0abfb72dcf3fa66cc56b953728e7113ad4ad88497cf21"
url: "https://pub.dev"
source: hosted
version: "0.7.0"
home_widget: home_widget:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -1157,6 +1269,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.9.4" version: "6.9.4"
latlong2:
dependency: transitive
description:
name: latlong2
sha256: "98227922caf49e6056f91b6c56945ea1c7b166f28ffcd5fb8e72fc0b453cc8fe"
url: "https://pub.dev"
source: hosted
version: "0.9.1"
leak_tracker: leak_tracker:
dependency: transitive dependency: transitive
description: description:
@@ -1197,6 +1317,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.1.1" version: "5.1.1"
lists:
dependency: transitive
description:
name: lists
sha256: "4ca5c19ae4350de036a7e996cdd1ee39c93ac0a2b840f4915459b7d0a7d4ab27"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
livekit_client: livekit_client:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -1213,6 +1341,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.1.6" version: "0.1.6"
logger:
dependency: transitive
description:
name: logger
sha256: be4b23575aac7ebf01f225a241eb7f6b5641eeaf43c6a8613510fc2f8cf187d1
url: "https://pub.dev"
source: hosted
version: "2.5.0"
logging: logging:
dependency: transitive dependency: transitive
description: description:
@@ -1349,6 +1485,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.16.0" version: "1.16.0"
mgrs_dart:
dependency: transitive
description:
name: mgrs_dart
sha256: fb89ae62f05fa0bb90f70c31fc870bcbcfd516c843fb554452ab3396f78586f7
url: "https://pub.dev"
source: hosted
version: "2.0.0"
mime: mime:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -1393,18 +1537,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: package_info_plus name: package_info_plus
sha256: "67eae327b1b0faf761964a1d2e5d323c797f3799db0e85aa232db8d9e922bc35" sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.2.1" version: "8.3.0"
package_info_plus_platform_interface: package_info_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: package_info_plus_platform_interface name: package_info_plus_platform_interface
sha256: "205ec83335c2ab9107bbba3f8997f9356d72ca3c715d2f038fc773d0366b4c76" sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.0" version: "3.2.0"
pasteboard: pasteboard:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -1565,6 +1709,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.8" version: "2.1.8"
polylabel:
dependency: transitive
description:
name: polylabel
sha256: "41b9099afb2aa6c1730bdd8a0fab1400d287694ec7615dd8516935fa3144214b"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
pool: pool:
dependency: transitive dependency: transitive
description: description:
@@ -1589,6 +1741,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.1" version: "6.0.1"
proj4dart:
dependency: transitive
description:
name: proj4dart
sha256: c8a659ac9b6864aa47c171e78d41bbe6f5e1d7bd790a5814249e6b68bc44324e
url: "https://pub.dev"
source: hosted
version: "2.1.0"
protobuf: protobuf:
dependency: transitive dependency: transitive
description: description:
@@ -1950,10 +2110,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: sqlite3_flutter_libs name: sqlite3_flutter_libs
sha256: "57fafacd815c981735406215966ff7caaa8eab984b094f52e692accefcbd9233" sha256: "7adb4cc96dc08648a5eb1d80a7619070796ca6db03901ff2b6dcb15ee30468f3"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.30" version: "0.5.31"
sqlparser: sqlparser:
dependency: transitive dependency: transitive
description: description:
@@ -2018,6 +2178,38 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.3.1" version: "3.3.1"
talker:
dependency: "direct main"
description:
name: talker
sha256: "5ab7d974ad92042b3e2382441c41ec4c6e5b3fa2b4b024d8ccbfc4bc2244b7bb"
url: "https://pub.dev"
source: hosted
version: "4.6.14"
talker_dio_logger:
dependency: "direct main"
description:
name: talker_dio_logger
sha256: "71780c52951d36e94964ca06158d827dfc67aa2fb75c8b880603cfefa4377b39"
url: "https://pub.dev"
source: hosted
version: "4.6.14"
talker_flutter:
dependency: "direct main"
description:
name: talker_flutter
sha256: "0cc816260b226c0ff930909c9f22984316b652b140f5eabb97ae9813ee0de135"
url: "https://pub.dev"
source: hosted
version: "4.6.14"
talker_logger:
dependency: transitive
description:
name: talker_logger
sha256: "16ff0cfdf011f65b37957c9ff7ef7043dd9f1c8af3ccb4a44ac4a448defb9eb5"
url: "https://pub.dev"
source: hosted
version: "4.6.14"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
@@ -2050,6 +2242,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.2" version: "0.3.2"
tuple:
dependency: transitive
description:
name: tuple
sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151
url: "https://pub.dev"
source: hosted
version: "2.0.2"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@@ -2066,6 +2266,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.1.3" version: "0.1.3"
unicode:
dependency: transitive
description:
name: unicode
sha256: "0f69e46593d65245774d4f17125c6084d2c20b4e473a983f6e21b7d7762218f1"
url: "https://pub.dev"
source: hosted
version: "0.3.1"
universal_io: universal_io:
dependency: transitive dependency: transitive
description: description:
@@ -2262,10 +2470,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: web name: web
sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.0" version: "1.1.1"
web_socket: web_socket:
dependency: transitive dependency: transitive
description: description:
@@ -2302,10 +2510,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: win32_registry name: win32_registry
sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852" sha256: "6f1b564492d0147b330dd794fee8f512cec4977957f310f9951b5f9d83618dae"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.5" version: "2.1.0"
wkt_parser:
dependency: transitive
description:
name: wkt_parser
sha256: "8a555fc60de3116c00aad67891bcab20f81a958e4219cc106e3c037aa3937f13"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
workmanager: workmanager:
dependency: "direct main" dependency: "direct main"
description: description:

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 # 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+72 version: 2.3.2+75
environment: environment:
sdk: ^3.5.4 sdk: ^3.5.4
@@ -47,7 +47,7 @@ dependencies:
dio: ^5.7.0 dio: ^5.7.0
dio_smart_retry: ^7.0.1 dio_smart_retry: ^7.0.1
very_good_infinite_list: ^0.9.0 very_good_infinite_list: ^0.9.0
freezed_annotation: ^2.4.4 freezed_annotation: ^3.0.0
json_annotation: ^4.9.0 json_annotation: ^4.9.0
gap: ^3.0.1 gap: ^3.0.1
markdown: ^7.2.2 markdown: ^7.2.2
@@ -128,6 +128,15 @@ dependencies:
drift: ^2.25.1 drift: ^2.25.1
drift_flutter: ^0.2.4 drift_flutter: ^0.2.4
local_notifier: ^0.1.6 local_notifier: ^0.1.6
flutter_markdown_latex: ^0.3.4
flutter_highlight: ^0.7.0
talker_flutter: ^4.6.14
talker_dio_logger: ^4.6.14
talker: ^4.6.14
flutter_cache_manager: ^3.4.1
flutter_timezone: ^4.1.0
flutter_map: ^8.1.0
geolocator: ^13.0.2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@@ -140,8 +149,8 @@ dev_dependencies:
# rules and activating additional ones. # rules and activating additional ones.
flutter_lints: ^5.0.0 flutter_lints: ^5.0.0
build_runner: ^2.4.15 build_runner: ^2.4.15
freezed: ^2.5.7 freezed: ^3.0.2
json_serializable: ^6.8.0 json_serializable: ^6.9.4
icons_launcher: ^3.0.0 icons_launcher: ^3.0.0
flutter_native_splash: ^2.4.2 flutter_native_splash: ^2.4.2
flutter_launcher_icons: ^0.14.1 flutter_launcher_icons: ^0.14.1

View File

@@ -12,9 +12,11 @@
#include <file_selector_windows/file_selector_windows.h> #include <file_selector_windows/file_selector_windows.h>
#include <firebase_core/firebase_core_plugin_c_api.h> #include <firebase_core/firebase_core_plugin_c_api.h>
#include <flutter_inappwebview_windows/flutter_inappwebview_windows_plugin_c_api.h> #include <flutter_inappwebview_windows/flutter_inappwebview_windows_plugin_c_api.h>
#include <flutter_timezone/flutter_timezone_plugin_c_api.h>
#include <flutter_udid/flutter_udid_plugin_c_api.h> #include <flutter_udid/flutter_udid_plugin_c_api.h>
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h> #include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
#include <gal/gal_plugin_c_api.h> #include <gal/gal_plugin_c_api.h>
#include <geolocator_windows/geolocator_windows.h>
#include <hotkey_manager_windows/hotkey_manager_windows_plugin_c_api.h> #include <hotkey_manager_windows/hotkey_manager_windows_plugin_c_api.h>
#include <livekit_client/live_kit_plugin.h> #include <livekit_client/live_kit_plugin.h>
#include <local_notifier/local_notifier_plugin.h> #include <local_notifier/local_notifier_plugin.h>
@@ -41,12 +43,16 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi")); registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar( FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi")); registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi"));
FlutterTimezonePluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterTimezonePluginCApi"));
FlutterUdidPluginCApiRegisterWithRegistrar( FlutterUdidPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterUdidPluginCApi")); registry->GetRegistrarForPlugin("FlutterUdidPluginCApi"));
FlutterWebRTCPluginRegisterWithRegistrar( FlutterWebRTCPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterWebRTCPlugin")); registry->GetRegistrarForPlugin("FlutterWebRTCPlugin"));
GalPluginCApiRegisterWithRegistrar( GalPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("GalPluginCApi")); registry->GetRegistrarForPlugin("GalPluginCApi"));
GeolocatorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("GeolocatorWindows"));
HotkeyManagerWindowsPluginCApiRegisterWithRegistrar( HotkeyManagerWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("HotkeyManagerWindowsPluginCApi")); registry->GetRegistrarForPlugin("HotkeyManagerWindowsPluginCApi"));
LiveKitPluginRegisterWithRegistrar( LiveKitPluginRegisterWithRegistrar(

View File

@@ -9,9 +9,11 @@ list(APPEND FLUTTER_PLUGIN_LIST
file_selector_windows file_selector_windows
firebase_core firebase_core
flutter_inappwebview_windows flutter_inappwebview_windows
flutter_timezone
flutter_udid flutter_udid
flutter_webrtc flutter_webrtc
gal gal
geolocator_windows
hotkey_manager_windows hotkey_manager_windows
livekit_client livekit_client
local_notifier local_notifier