Compare commits
43 Commits
3.2.0+128
...
baed28bef7
Author | SHA1 | Date | |
---|---|---|---|
baed28bef7 | |||
7a6eecf628 | |||
6a63bc235b | |||
ecc100ac45 | |||
573b76d3ff | |||
f7dad5e419 | |||
9f2f1c0848 | |||
580d9fd979 | |||
3b375abc09 | |||
c527b5e67c | |||
e9f09bbe54 | |||
3aece9316c | |||
a61c889c6c | |||
0dd3221a56 | |||
66918521f8 | |||
bb1846e462 | |||
a976a6eaf4 | |||
4252f66fd3 | |||
f2d780b48f | |||
300541f9bb | |||
43787bb813 | |||
3417c51a3b | |||
f98e603e82 | |||
c9b71701c8 | |||
28e98488f1 | |||
b4d476613e | |||
b48a1aac44 | |||
596d212593 | |||
54f290327e | |||
16f248ceab | |||
856d811187 | |||
d07b194c04 | |||
2554b58be6 | |||
a627b5838e | |||
c479a9f381 | |||
02057e663b | |||
6501594100 | |||
c6599edc3d | |||
709a0620b6 | |||
f9b2a96c7c | |||
4dca6189cb | |||
c7f5b63fe5 | |||
96c2f45c85 |
@@ -62,4 +62,3 @@ If you want to build the release version, use the flutter build command. Learn m
|
||||
```bash
|
||||
flutter build <platform>
|
||||
```
|
||||
|
||||
|
@@ -338,6 +338,7 @@
|
||||
"notifications": "Notifications",
|
||||
"posts": "Posts",
|
||||
"settingsBackgroundImage": "Background Image",
|
||||
"settingsBackgroundImageEnable": "Show Background Image",
|
||||
"settingsBackgroundImageClear": "Clear Background Image",
|
||||
"settingsBackgroundGenerateColor": "Generate color scheme from Bacground Image",
|
||||
"messageNone": "No content to display",
|
||||
@@ -386,6 +387,7 @@
|
||||
"postSettings": "Settings",
|
||||
"postPublisherUnselected": "Publisher Unspecified",
|
||||
"postType": "Post Type",
|
||||
"postTypePost": "Post",
|
||||
"articleAttachmentHint": "Attachments must be uploaded and inserted into the article body to be visible.",
|
||||
"postVisibility": "Post Visibility",
|
||||
"postVisibilityPublic": "Public",
|
||||
@@ -633,8 +635,9 @@
|
||||
"chatJoin": "Join the Chat",
|
||||
"realmJoin": "Join the Realm",
|
||||
"realmJoinSuccess": "Successfully joined the realm.",
|
||||
"discoverRealms": "Discover realms",
|
||||
"discoverPublishers": "Discover publishers",
|
||||
"discoverRealms": "Realms",
|
||||
"discoverPublishers": "Publishers",
|
||||
"discoverShuffledPost": "Random Posts",
|
||||
"search": "Search",
|
||||
"publisherMembers": "Collaborators",
|
||||
"developerHub": "Developer Hub",
|
||||
@@ -692,7 +695,7 @@
|
||||
"publisherFeatureDevelopDescription": "Unlock development abilities for your publisher, including custom apps, API keys, and more.",
|
||||
"publisherFeatureDevelopHint": "Currently, this feature is under active development, you need send a request to unlock this feature.",
|
||||
"learnMore": "Learn More",
|
||||
"discoverWebArticles": "Articles from external sites",
|
||||
"discoverWebArticles": "Web Feed Articles",
|
||||
"webArticlesStand": "Article Stand",
|
||||
"about": "About",
|
||||
"membershipCancel": "Cancel Membership",
|
||||
@@ -867,7 +870,7 @@
|
||||
"failedToLoadUserInfoNetwork": "It seems be network issue, you can tap the button below to try again.",
|
||||
"failedToLoadUserInfoUnauthorized": "It seems your session has been logged out or not available anymore, you can still try agian to fetch the user info if you want.",
|
||||
"okay": "Okay",
|
||||
"postDetails": "Post Details",
|
||||
"postDetail": "Post Detail",
|
||||
"postCount": {
|
||||
"zero": "No posts",
|
||||
"one": "{} post",
|
||||
@@ -883,6 +886,7 @@
|
||||
"stellarProgram": "Stellar Program",
|
||||
"socialCredits": "Social Credits",
|
||||
"credits": "Credits",
|
||||
"creditsStatus": "Credits Status",
|
||||
"socialCreditsDescription": "Social Credit is a way for Solar Network to evaluate users. It is calculated based on their behavior and interactions. With a base score of 100, higher scores indicate a user's credibility within the community. Scores change over time to reflect a user's recent behavior. Users with higher credit ratings enjoy more benefits, while users with lower credit ratings may have some functionality restricted.",
|
||||
"socialCreditsLevelPoor": "Poor",
|
||||
"socialCreditsLevelNormal": "Normal",
|
||||
@@ -926,5 +930,46 @@
|
||||
"newSecretGenerated": "New Secret Generated",
|
||||
"copySecretHint": "Please copy this secret and store it somewhere safe. You will not be able to see it again.",
|
||||
"expiresIn": "Expires In (seconds)",
|
||||
"isOidc": "OIDC Compliant"
|
||||
"isOidc": "OIDC Compliant",
|
||||
"pinPost": "Pin Post",
|
||||
"unpinPost": "Unpin Post",
|
||||
"pinnedPost": "Pinned",
|
||||
"publisherPage": "Publisher Page",
|
||||
"realmPage": "Realm Page",
|
||||
"replyPage": "Reply Page",
|
||||
"pinPostPublisherHint": "Pin this post to your publisher page",
|
||||
"pinPostRealmHint": "Pin this post to the realm page",
|
||||
"pinPostRealmDisabledHint": "This post doesn't belong to any realm",
|
||||
"pinPostReplyHint": "Pin this post to the reply page",
|
||||
"pinPostReplyDisabledHint": "This post is not a reply",
|
||||
"pin": "Pin",
|
||||
"unpinPostHint": "Are you sure you want to unpin this post?",
|
||||
"all": "All",
|
||||
"statusPresent": "Present",
|
||||
"accountAutomated": "Automated",
|
||||
"chatBreakClearButton": "Clear",
|
||||
"chatBreak5m": "5m",
|
||||
"chatBreak10m": "10m",
|
||||
"chatBreak15m": "15m",
|
||||
"chatBreak30m": "30m",
|
||||
"chatBreakCustomMinutes": "Custom (minutes)",
|
||||
"errorGeneric": "Error: {}",
|
||||
"searchMessages": "Search Messages",
|
||||
"messagesCount": "{} messages",
|
||||
"dotSeparator": "·",
|
||||
"roleValidationHint": "Role must be between 0 and 100",
|
||||
"searchMessagesHint": "Search messages...",
|
||||
"searchLinks": "Links",
|
||||
"searchAttachments": "Attachments",
|
||||
"noMessagesFound": "No messages found",
|
||||
"openInBrowser": "Open in Browser",
|
||||
"highlightPost": "Highlight Post",
|
||||
"filters": "Filters",
|
||||
"apply": "Apply",
|
||||
"pubName": "Pub Name",
|
||||
"realm": "Realm",
|
||||
"shuffle": "Shuffle",
|
||||
"pinned": "Pinned",
|
||||
"noResultsFound": "No results found",
|
||||
"toggleFilters": "Toggle filters"
|
||||
}
|
@@ -304,6 +304,7 @@
|
||||
"notifications": "通知",
|
||||
"posts": "帖子",
|
||||
"settingsBackgroundImage": "背景图片",
|
||||
"settingsBackgroundImageEnable": "显示背景图片",
|
||||
"settingsBackgroundImageClear": "清除背景图片",
|
||||
"settingsBackgroundGenerateColor": "从背景图像生成主题色",
|
||||
"messageNone": "没有内容可显示",
|
||||
@@ -829,7 +830,7 @@
|
||||
"failedToLoadUserInfoNetwork": "这看起来是个网络问题,你可以按下面的按钮来重试",
|
||||
"failedToLoadUserInfoUnauthorized": "看来您的会话已被注销或不再可用,如果您愿意,您仍然可以再次尝试获取用户信息。",
|
||||
"okay": "了解",
|
||||
"postDetails": "帖子详情",
|
||||
"postDetail": "帖子详情",
|
||||
"mimeType": "类型",
|
||||
"fileSize": "大小",
|
||||
"fileHash": "哈希",
|
||||
@@ -855,5 +856,9 @@
|
||||
"newSecretGenerated": "已生成新密钥",
|
||||
"copySecretHint": "请复制此密钥并将其存放在安全的地方。您将无法再次看到它。",
|
||||
"expiresIn": "过期时间(秒)",
|
||||
"isOidc": "OIDC 兼容"
|
||||
"isOidc": "OIDC 兼容",
|
||||
"statusPresent": "至今",
|
||||
"accountAutomated": "机器人",
|
||||
"openInBrowser": "在浏览器中打开",
|
||||
"highlightPost": "精选帖子"
|
||||
}
|
||||
|
@@ -303,7 +303,8 @@
|
||||
"notifications": "通知",
|
||||
"posts": "帖子",
|
||||
"settingsBackgroundImage": "背景圖片",
|
||||
"settingsBackgroundImageClear": "清除背景圖片",
|
||||
"settingsBackgroundImageEnable": "顯示背景圖片",
|
||||
"settingsBackgroundImageClear": "清除背景圖片",
|
||||
"settingsBackgroundGenerateColor": "從背景圖像生成主題色",
|
||||
"messageNone": "沒有內容可顯示",
|
||||
"unreadMessages": {
|
||||
|
12
assets/icons/icon-outline.svg
Normal file
12
assets/icons/icon-outline.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="192" height="192" fill="none">
|
||||
<path stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="12"
|
||||
d="M54 147h86" />
|
||||
<path stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="10"
|
||||
d="M57 111s-2-4.5-2-10m22 22s-4 7-11 4m9-22s-2-4.5-2-10" />
|
||||
<path stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="12"
|
||||
d="M54 147a32 32 0 0 1-11.999-61.665A39 39 0 0 1 81 46m59 101a30 30 0 0 0 29.933-28" />
|
||||
<circle cx="132" cy="75" r="4" stroke="#fff" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="8" />
|
||||
<path stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="10"
|
||||
d="M112.5 41.217C100.843 47.961 93 60.564 93 75c0 6.375 1.53 12.393 4.242 17.707m69.513-35.419A38.84 38.84 0 0 1 171 75c0 14.433-7.84 27.034-19.493 33.779m-.793-43.317A20.9 20.9 0 0 1 153 75c0 7.77-4.221 14.556-10.495 18.188m-21.003-36.38C115.224 60.44 111 67.226 111 75a20.9 20.9 0 0 0 2.284 9.533" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
BIN
assets/images/media-offline.jpg
Normal file
BIN
assets/images/media-offline.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 461 KiB |
Binary file not shown.
Before Width: | Height: | Size: 307 KiB |
@@ -21,6 +21,6 @@
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>12.0</string>
|
||||
<string>13.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
162
ios/Podfile.lock
162
ios/Podfile.lock
@@ -40,83 +40,85 @@ PODS:
|
||||
- file_picker (0.0.1):
|
||||
- DKImagePickerController/PhotoGallery
|
||||
- Flutter
|
||||
- Firebase/CoreOnly (12.0.0):
|
||||
- FirebaseCore (~> 12.0.0)
|
||||
- Firebase/Crashlytics (12.0.0):
|
||||
- file_saver (0.0.1):
|
||||
- Flutter
|
||||
- Firebase/CoreOnly (12.2.0):
|
||||
- FirebaseCore (~> 12.2.0)
|
||||
- Firebase/Crashlytics (12.2.0):
|
||||
- Firebase/CoreOnly
|
||||
- FirebaseCrashlytics (~> 12.0.0)
|
||||
- Firebase/Messaging (12.0.0):
|
||||
- FirebaseCrashlytics (~> 12.2.0)
|
||||
- Firebase/Messaging (12.2.0):
|
||||
- Firebase/CoreOnly
|
||||
- FirebaseMessaging (~> 12.0.0)
|
||||
- firebase_analytics (12.0.0):
|
||||
- FirebaseMessaging (~> 12.2.0)
|
||||
- firebase_analytics (12.0.1):
|
||||
- firebase_core
|
||||
- FirebaseAnalytics (= 12.0.0)
|
||||
- FirebaseAnalytics (= 12.2.0)
|
||||
- Flutter
|
||||
- firebase_core (4.0.0):
|
||||
- Firebase/CoreOnly (= 12.0.0)
|
||||
- firebase_core (4.1.0):
|
||||
- Firebase/CoreOnly (= 12.2.0)
|
||||
- Flutter
|
||||
- firebase_crashlytics (5.0.0):
|
||||
- Firebase/Crashlytics (= 12.0.0)
|
||||
- firebase_crashlytics (5.0.1):
|
||||
- Firebase/Crashlytics (= 12.2.0)
|
||||
- firebase_core
|
||||
- Flutter
|
||||
- firebase_messaging (16.0.0):
|
||||
- Firebase/Messaging (= 12.0.0)
|
||||
- firebase_messaging (16.0.1):
|
||||
- Firebase/Messaging (= 12.2.0)
|
||||
- firebase_core
|
||||
- Flutter
|
||||
- FirebaseAnalytics (12.0.0):
|
||||
- FirebaseAnalytics/Default (= 12.0.0)
|
||||
- FirebaseCore (~> 12.0.0)
|
||||
- FirebaseInstallations (~> 12.0.0)
|
||||
- FirebaseAnalytics (12.2.0):
|
||||
- FirebaseAnalytics/Default (= 12.2.0)
|
||||
- FirebaseCore (~> 12.2.0)
|
||||
- FirebaseInstallations (~> 12.2.0)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
||||
- GoogleUtilities/Network (~> 8.1)
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||
- nanopb (~> 3.30910.0)
|
||||
- FirebaseAnalytics/Default (12.0.0):
|
||||
- FirebaseCore (~> 12.0.0)
|
||||
- FirebaseInstallations (~> 12.0.0)
|
||||
- GoogleAppMeasurement/Default (= 12.0.0)
|
||||
- FirebaseAnalytics/Default (12.2.0):
|
||||
- FirebaseCore (~> 12.2.0)
|
||||
- FirebaseInstallations (~> 12.2.0)
|
||||
- GoogleAppMeasurement/Default (= 12.2.0)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
||||
- GoogleUtilities/Network (~> 8.1)
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||
- nanopb (~> 3.30910.0)
|
||||
- FirebaseCore (12.0.0):
|
||||
- FirebaseCoreInternal (~> 12.0.0)
|
||||
- FirebaseCore (12.2.0):
|
||||
- FirebaseCoreInternal (~> 12.2.0)
|
||||
- GoogleUtilities/Environment (~> 8.1)
|
||||
- GoogleUtilities/Logger (~> 8.1)
|
||||
- FirebaseCoreExtension (12.0.0):
|
||||
- FirebaseCore (~> 12.0.0)
|
||||
- FirebaseCoreInternal (12.0.0):
|
||||
- FirebaseCoreExtension (12.2.0):
|
||||
- FirebaseCore (~> 12.2.0)
|
||||
- FirebaseCoreInternal (12.2.0):
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||
- FirebaseCrashlytics (12.0.0):
|
||||
- FirebaseCore (~> 12.0.0)
|
||||
- FirebaseInstallations (~> 12.0.0)
|
||||
- FirebaseRemoteConfigInterop (~> 12.0.0)
|
||||
- FirebaseSessions (~> 12.0.0)
|
||||
- FirebaseCrashlytics (12.2.0):
|
||||
- FirebaseCore (~> 12.2.0)
|
||||
- FirebaseInstallations (~> 12.2.0)
|
||||
- FirebaseRemoteConfigInterop (~> 12.2.0)
|
||||
- FirebaseSessions (~> 12.2.0)
|
||||
- GoogleDataTransport (~> 10.1)
|
||||
- GoogleUtilities/Environment (~> 8.1)
|
||||
- nanopb (~> 3.30910.0)
|
||||
- PromisesObjC (~> 2.4)
|
||||
- FirebaseInstallations (12.0.0):
|
||||
- FirebaseCore (~> 12.0.0)
|
||||
- FirebaseInstallations (12.2.0):
|
||||
- FirebaseCore (~> 12.2.0)
|
||||
- GoogleUtilities/Environment (~> 8.1)
|
||||
- GoogleUtilities/UserDefaults (~> 8.1)
|
||||
- PromisesObjC (~> 2.4)
|
||||
- FirebaseMessaging (12.0.0):
|
||||
- FirebaseCore (~> 12.0.0)
|
||||
- FirebaseInstallations (~> 12.0.0)
|
||||
- FirebaseMessaging (12.2.0):
|
||||
- FirebaseCore (~> 12.2.0)
|
||||
- FirebaseInstallations (~> 12.2.0)
|
||||
- GoogleDataTransport (~> 10.1)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||
- GoogleUtilities/Environment (~> 8.1)
|
||||
- GoogleUtilities/Reachability (~> 8.1)
|
||||
- GoogleUtilities/UserDefaults (~> 8.1)
|
||||
- nanopb (~> 3.30910.0)
|
||||
- FirebaseRemoteConfigInterop (12.0.0)
|
||||
- FirebaseSessions (12.0.0):
|
||||
- FirebaseCore (~> 12.0.0)
|
||||
- FirebaseCoreExtension (~> 12.0.0)
|
||||
- FirebaseInstallations (~> 12.0.0)
|
||||
- FirebaseRemoteConfigInterop (12.2.0)
|
||||
- FirebaseSessions (12.2.0):
|
||||
- FirebaseCore (~> 12.2.0)
|
||||
- FirebaseCoreExtension (~> 12.2.0)
|
||||
- FirebaseInstallations (~> 12.2.0)
|
||||
- GoogleDataTransport (~> 10.1)
|
||||
- GoogleUtilities/Environment (~> 8.1)
|
||||
- GoogleUtilities/UserDefaults (~> 8.1)
|
||||
@@ -145,33 +147,33 @@ PODS:
|
||||
- flutter_udid (0.0.1):
|
||||
- Flutter
|
||||
- SAMKeychain
|
||||
- flutter_webrtc (1.0.0):
|
||||
- flutter_webrtc (1.1.0):
|
||||
- Flutter
|
||||
- WebRTC-SDK (= 137.7151.02)
|
||||
- WebRTC-SDK (= 137.7151.03)
|
||||
- gal (1.0.0):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- GoogleAdsOnDeviceConversion (2.1.0):
|
||||
- GoogleAdsOnDeviceConversion (2.3.0):
|
||||
- GoogleUtilities/Logger (~> 8.1)
|
||||
- GoogleUtilities/Network (~> 8.1)
|
||||
- nanopb (~> 3.30910.0)
|
||||
- GoogleAppMeasurement/Core (12.0.0):
|
||||
- GoogleAppMeasurement/Core (12.2.0):
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
||||
- GoogleUtilities/Network (~> 8.1)
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||
- nanopb (~> 3.30910.0)
|
||||
- GoogleAppMeasurement/Default (12.0.0):
|
||||
- GoogleAdsOnDeviceConversion (= 2.1.0)
|
||||
- GoogleAppMeasurement/Core (= 12.0.0)
|
||||
- GoogleAppMeasurement/IdentitySupport (= 12.0.0)
|
||||
- GoogleAppMeasurement/Default (12.2.0):
|
||||
- GoogleAdsOnDeviceConversion (= 2.3.0)
|
||||
- GoogleAppMeasurement/Core (= 12.2.0)
|
||||
- GoogleAppMeasurement/IdentitySupport (= 12.2.0)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
||||
- GoogleUtilities/Network (~> 8.1)
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||
- nanopb (~> 3.30910.0)
|
||||
- GoogleAppMeasurement/IdentitySupport (12.0.0):
|
||||
- GoogleAppMeasurement/Core (= 12.0.0)
|
||||
- GoogleAppMeasurement/IdentitySupport (12.2.0):
|
||||
- GoogleAppMeasurement/Core (= 12.2.0)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
||||
- GoogleUtilities/Network (~> 8.1)
|
||||
@@ -215,7 +217,7 @@ PODS:
|
||||
- livekit_client (2.5.0):
|
||||
- Flutter
|
||||
- flutter_webrtc
|
||||
- WebRTC-SDK (= 137.7151.02)
|
||||
- WebRTC-SDK (= 137.7151.03)
|
||||
- local_auth_darwin (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
@@ -248,9 +250,9 @@ PODS:
|
||||
- record_ios (1.1.0):
|
||||
- Flutter
|
||||
- SAMKeychain (1.5.3)
|
||||
- SDWebImage (5.21.1):
|
||||
- SDWebImage/Core (= 5.21.1)
|
||||
- SDWebImage/Core (5.21.1)
|
||||
- SDWebImage (5.21.2):
|
||||
- SDWebImage/Core (= 5.21.2)
|
||||
- SDWebImage/Core (5.21.2)
|
||||
- share_plus (0.0.1):
|
||||
- Flutter
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
@@ -295,7 +297,7 @@ PODS:
|
||||
- Flutter
|
||||
- wakelock_plus (0.0.1):
|
||||
- Flutter
|
||||
- WebRTC-SDK (137.7151.02)
|
||||
- WebRTC-SDK (137.7151.03)
|
||||
|
||||
DEPENDENCIES:
|
||||
- Alamofire
|
||||
@@ -303,6 +305,7 @@ DEPENDENCIES:
|
||||
- croppy (from `.symlinks/plugins/croppy/ios`)
|
||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
||||
- file_saver (from `.symlinks/plugins/file_saver/ios`)
|
||||
- firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`)
|
||||
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
||||
- firebase_crashlytics (from `.symlinks/plugins/firebase_crashlytics/ios`)
|
||||
@@ -381,6 +384,8 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||
file_picker:
|
||||
:path: ".symlinks/plugins/file_picker/ios"
|
||||
file_saver:
|
||||
:path: ".symlinks/plugins/file_saver/ios"
|
||||
firebase_analytics:
|
||||
:path: ".symlinks/plugins/firebase_analytics/ios"
|
||||
firebase_core:
|
||||
@@ -464,21 +469,22 @@ SPEC CHECKSUMS:
|
||||
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
|
||||
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
||||
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
|
||||
Firebase: 800d487043c0557d9faed71477a38d9aafb08a41
|
||||
firebase_analytics: cd56fc56f75c1df30a6ff5290cd56e230996a76d
|
||||
firebase_core: 633e1851ffe1b9ab875f6467a4f574c79cef02e4
|
||||
firebase_crashlytics: 2c6c1a17900a38081d938330e9f48e60ec5b255d
|
||||
firebase_messaging: d17feef781edc84ebefe62624fb384358ad96361
|
||||
FirebaseAnalytics: 6d790cd1b159b4eb61a99948df0934ce505a34f7
|
||||
FirebaseCore: 055f4ab117d5964158c833f3d5e7ec6d91648d4a
|
||||
FirebaseCoreExtension: 639afb3de6abd611952be78a794c54a47fa0f361
|
||||
FirebaseCoreInternal: dedc28e569a4be85f38f3d6af1070a2e12018d55
|
||||
FirebaseCrashlytics: db75aa0cab8d00f68406fa247c32fe17ade884d7
|
||||
FirebaseInstallations: d4c7c958f99c8860d7fcece786314ae790e2f988
|
||||
FirebaseMessaging: af49f8d7c0a3d2a017d9302c80946f45a7777dde
|
||||
FirebaseRemoteConfigInterop: bfa0ea72ba3dc5af739777296424e46bd6f42613
|
||||
FirebaseSessions: 4e784acda213108aafef536535cdfc03504acc42
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
file_saver: 6cdbcddd690cb02b0c1a0c225b37cd805c2bf8b6
|
||||
Firebase: 26f6f8d460603af3df970ad505b16b15f5e2e9a1
|
||||
firebase_analytics: 111ff65791a430356bd6c7e4d7339537fc6a15ae
|
||||
firebase_core: 3ff52146406557dddd01d570e807e203ec7e1302
|
||||
firebase_crashlytics: 3637078b718a52dc9fb4d64e37c969e86b87ff6f
|
||||
firebase_messaging: 3dcc998dd98e1e54af75d0cccae8606eba43553c
|
||||
FirebaseAnalytics: e04e23bc070e3014aa5cf4980f9df7ce5cd79ec8
|
||||
FirebaseCore: 311c48a147ad4a0ab7febbaed89e8025c67510cd
|
||||
FirebaseCoreExtension: 73af080c22a2f7b44cefa391dc08f7e4ee162cb5
|
||||
FirebaseCoreInternal: 56ea29f3dad2894f81b060f706f9d53509b6ed3b
|
||||
FirebaseCrashlytics: f83cbf176d5c637ade108c0aacf1ccbd5ec499bf
|
||||
FirebaseInstallations: 3e884b01feabdf67582a80f3250425a00979b4ed
|
||||
FirebaseMessaging: 43ec73bbfedd0c385a849bb91593ab4ad4b9e48e
|
||||
FirebaseRemoteConfigInterop: 0896fd52ab72586a355c8f389ff85aaa9e5375e1
|
||||
FirebaseSessions: f4692789e770bec66ce17d772c0e9561c4f11737
|
||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||
flutter_app_update: 816fdb2e30e4832a7c45e3f108d391c42ef040a9
|
||||
flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
|
||||
flutter_keyboard_visibility: 4625131e43015dbbe759d9b20daaf77e0e3f6619
|
||||
@@ -487,16 +493,16 @@ SPEC CHECKSUMS:
|
||||
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
|
||||
flutter_timezone: 7c838e17ffd4645d261e87037e5bebf6d38fe544
|
||||
flutter_udid: f7c3884e6ec2951efe4f9de082257fc77c4d15e9
|
||||
flutter_webrtc: 6f7da106613d52ade777d5b4875a43f48c28b457
|
||||
flutter_webrtc: b0b2e04411747142962164a1cfa43a1af9a0afac
|
||||
gal: baecd024ebfd13c441269ca7404792a7152fde89
|
||||
GoogleAdsOnDeviceConversion: 2be6297a4f048459e0ae17fad9bfd2844e10cf64
|
||||
GoogleAppMeasurement: 8f6ab04ad6ae493b53fcf56bd26323fb2f1384f3
|
||||
GoogleAdsOnDeviceConversion: 9090c435cde08903e8dd1ba2c77fbec9e46d9afe
|
||||
GoogleAppMeasurement: 09f341dfa8527d1612a09cbfe809a242c0b737af
|
||||
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
||||
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
|
||||
irondash_engine_context: 8e58ca8e0212ee9d1c7dc6a42121849986c88486
|
||||
Kingfisher: ff0d31a1f07bdff6a1ebb3ba08b8e6e567b6500c
|
||||
livekit_client: e3b79b99405428aac439b6b76a254cd9a11dbbfb
|
||||
livekit_client: f810c81bbbc229a84f60b09e66603ac4e93f7599
|
||||
local_auth_darwin: d2e8c53ef0c4f43c646462e3415432c4dab3ae19
|
||||
media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854
|
||||
media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474
|
||||
@@ -512,7 +518,7 @@ SPEC CHECKSUMS:
|
||||
receive_sharing_intent: 222384f00ffe7e952bbfabaa9e3967cb87e5fe00
|
||||
record_ios: f75fa1d57f840012775c0e93a38a7f3ceea1a374
|
||||
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
|
||||
SDWebImage: f29024626962457f3470184232766516dee8dfea
|
||||
SDWebImage: 9f177d83116802728e122410fb25ad88f5c7608a
|
||||
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
||||
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
|
||||
sign_in_with_apple: c5dcc141574c8c54d5ac99dd2163c0c72ad22418
|
||||
@@ -524,7 +530,7 @@ SPEC CHECKSUMS:
|
||||
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
|
||||
volume_controller: 3657a1f65bedb98fa41ff7dc5793537919f31b12
|
||||
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
|
||||
WebRTC-SDK: d20de357dcbf7c9696b124b39f3ff62125107e4b
|
||||
WebRTC-SDK: 69d4e56b0b4b27d788e87bab9b9a1326ed05b1e3
|
||||
|
||||
PODFILE CHECKSUM: c818292390b02fa379036ea099713a332bd7193f
|
||||
|
||||
|
@@ -853,7 +853,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
@@ -897,6 +897,7 @@
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -915,6 +916,7 @@
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -931,6 +933,7 @@
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -1078,7 +1081,7 @@
|
||||
INFOPLIST_FILE = SolianShareExtension/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = SolianShareExtension;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@@ -1121,7 +1124,7 @@
|
||||
INFOPLIST_FILE = SolianShareExtension/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = SolianShareExtension;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@@ -1161,7 +1164,7 @@
|
||||
INFOPLIST_FILE = SolianShareExtension/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = SolianShareExtension;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@@ -1348,7 +1351,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1399,7 +1402,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
|
@@ -47,6 +47,7 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
private func processNotification(request: UNNotificationRequest, content: UNMutableNotificationContent) throws {
|
||||
switch content.userInfo["type"] as? String {
|
||||
case "messages.new":
|
||||
content.categoryIdentifier = "REPLYABLE_MESSAGE"
|
||||
try handleMessagingNotification(request: request, content: content)
|
||||
default:
|
||||
try handleDefaultNotification(content: content)
|
||||
@@ -60,8 +61,6 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
|
||||
let pfpIdentifier = meta["pfp"] as? String
|
||||
|
||||
content.categoryIdentifier = "REPLYABLE_MESSAGE"
|
||||
|
||||
let metaCopy = meta as? [String: Any] ?? [:]
|
||||
let pfpUrl = pfpIdentifier != nil ? getAttachmentUrl(for: pfpIdentifier!) : nil
|
||||
|
||||
|
@@ -68,6 +68,34 @@ class AppDatabase extends _$AppDatabase {
|
||||
return (delete(chatMessages)..where((m) => m.id.equals(id))).go();
|
||||
}
|
||||
|
||||
Future<int> getTotalMessagesForRoom(String roomId) {
|
||||
return (select(chatMessages)..where((m) => m.roomId.equals(roomId))).get().then((list) => list.length);
|
||||
}
|
||||
|
||||
Future<List<LocalChatMessage>> searchMessages(
|
||||
String roomId,
|
||||
String query,
|
||||
) async {
|
||||
var selectStatement = select(chatMessages)
|
||||
..where((m) => m.roomId.equals(roomId));
|
||||
|
||||
if (query.isNotEmpty) {
|
||||
selectStatement =
|
||||
selectStatement
|
||||
..where((m) => m.content.like('%${query.toLowerCase()}%'));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
final messages =
|
||||
await (selectStatement
|
||||
..orderBy([(m) => OrderingTerm.desc(m.createdAt)]))
|
||||
.get();
|
||||
return messages.map((msg) => companionToMessage(msg)).toList();
|
||||
}
|
||||
|
||||
// Convert between Drift and model objects
|
||||
ChatMessagesCompanion messageToCompanion(LocalChatMessage message) {
|
||||
return ChatMessagesCompanion(
|
||||
|
@@ -8,7 +8,7 @@ import 'package:firebase_crashlytics/firebase_crashlytics.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
@@ -30,7 +30,6 @@ import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
|
||||
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
import 'package:flutter_langdetect/flutter_langdetect.dart' as langdetect;
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
||||
@@ -52,7 +51,6 @@ void main() async {
|
||||
}
|
||||
|
||||
try {
|
||||
await langdetect.initLangDetect();
|
||||
await EasyLocalization.ensureInitialized();
|
||||
|
||||
if (kIsWeb || !Platform.isLinux) {
|
||||
@@ -169,12 +167,12 @@ class IslandApp extends HookConsumerWidget {
|
||||
final theme = ref.watch(themeProvider);
|
||||
|
||||
void handleMessage(RemoteMessage notification) {
|
||||
if (notification.data['action_uri'] != null) {
|
||||
var uri = notification.data['action_uri'] as String;
|
||||
if (notification.data['meta']?['action_uri'] != null) {
|
||||
var uri = notification.data['meta']['action_uri'] as String;
|
||||
if (uri.startsWith('/')) {
|
||||
// In-app routes
|
||||
final router = ref.read(routerProvider);
|
||||
router.go(notification.data['action_uri']);
|
||||
router.push(notification.data['meta']['action_uri']);
|
||||
} else {
|
||||
// External links
|
||||
launchUrlString(uri);
|
||||
@@ -186,27 +184,6 @@ class IslandApp extends HookConsumerWidget {
|
||||
if (!kIsWeb && Platform.isLinux) {
|
||||
return null;
|
||||
}
|
||||
const channel = MethodChannel('dev.solsynth.solian/notifications');
|
||||
|
||||
Future<void> handleInitialLink() async {
|
||||
final String? link = await channel.invokeMethod('initialLink');
|
||||
if (link != null) {
|
||||
final router = ref.read(routerProvider);
|
||||
router.go(link);
|
||||
}
|
||||
}
|
||||
|
||||
if (!kIsWeb && Platform.isAndroid) {
|
||||
handleInitialLink();
|
||||
}
|
||||
|
||||
channel.setMethodCallHandler((call) async {
|
||||
if (call.method == 'newLink') {
|
||||
final String link = call.arguments;
|
||||
final router = ref.read(routerProvider);
|
||||
router.go(link);
|
||||
}
|
||||
});
|
||||
|
||||
// When the app is opened from a terminated state.
|
||||
FirebaseMessaging.instance.getInitialMessage().then((message) {
|
||||
@@ -246,6 +223,7 @@ class IslandApp extends HookConsumerWidget {
|
||||
if (user.value != null) {
|
||||
final apiClient = ref.read(apiClientProvider);
|
||||
subscribePushNotification(apiClient);
|
||||
initializeLocalNotifications();
|
||||
final wsNotifier = ref.read(websocketStateProvider.notifier);
|
||||
wsNotifier.connect();
|
||||
}
|
||||
|
@@ -71,6 +71,8 @@ sealed class SnAccountProfile with _$SnAccountProfile {
|
||||
SnAccountBadge? activeBadge,
|
||||
required int experience,
|
||||
required int level,
|
||||
@Default(100) double socialCredits,
|
||||
@Default(0) int socialCreditsLevel,
|
||||
required double levelingProgress,
|
||||
required SnCloudFile? picture,
|
||||
required SnCloudFile? background,
|
||||
|
@@ -613,7 +613,7 @@ as String,
|
||||
/// @nodoc
|
||||
mixin _$SnAccountProfile {
|
||||
|
||||
String get id; String get firstName; String get middleName; String get lastName; String get bio; String get gender; String get pronouns; String get location; String get timeZone; DateTime? get birthday;@ProfileLinkConverter() List<ProfileLink> get links; DateTime? get lastSeenAt; SnAccountBadge? get activeBadge; int get experience; int get level; double get levelingProgress; SnCloudFile? get picture; SnCloudFile? get background; SnVerificationMark? get verification; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
|
||||
String get id; String get firstName; String get middleName; String get lastName; String get bio; String get gender; String get pronouns; String get location; String get timeZone; DateTime? get birthday;@ProfileLinkConverter() List<ProfileLink> get links; DateTime? get lastSeenAt; SnAccountBadge? get activeBadge; int get experience; int get level; double get socialCredits; int get socialCreditsLevel; double get levelingProgress; SnCloudFile? get picture; SnCloudFile? get background; SnVerificationMark? get verification; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
|
||||
/// Create a copy of SnAccountProfile
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -626,16 +626,16 @@ $SnAccountProfileCopyWith<SnAccountProfile> get copyWith => _$SnAccountProfileCo
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAccountProfile&&(identical(other.id, id) || other.id == id)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.middleName, middleName) || other.middleName == middleName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.gender, gender) || other.gender == gender)&&(identical(other.pronouns, pronouns) || other.pronouns == pronouns)&&(identical(other.location, location) || other.location == location)&&(identical(other.timeZone, timeZone) || other.timeZone == timeZone)&&(identical(other.birthday, birthday) || other.birthday == birthday)&&const DeepCollectionEquality().equals(other.links, links)&&(identical(other.lastSeenAt, lastSeenAt) || other.lastSeenAt == lastSeenAt)&&(identical(other.activeBadge, activeBadge) || other.activeBadge == activeBadge)&&(identical(other.experience, experience) || other.experience == experience)&&(identical(other.level, level) || other.level == level)&&(identical(other.levelingProgress, levelingProgress) || other.levelingProgress == levelingProgress)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.verification, verification) || other.verification == verification)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAccountProfile&&(identical(other.id, id) || other.id == id)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.middleName, middleName) || other.middleName == middleName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.gender, gender) || other.gender == gender)&&(identical(other.pronouns, pronouns) || other.pronouns == pronouns)&&(identical(other.location, location) || other.location == location)&&(identical(other.timeZone, timeZone) || other.timeZone == timeZone)&&(identical(other.birthday, birthday) || other.birthday == birthday)&&const DeepCollectionEquality().equals(other.links, links)&&(identical(other.lastSeenAt, lastSeenAt) || other.lastSeenAt == lastSeenAt)&&(identical(other.activeBadge, activeBadge) || other.activeBadge == activeBadge)&&(identical(other.experience, experience) || other.experience == experience)&&(identical(other.level, level) || other.level == level)&&(identical(other.socialCredits, socialCredits) || other.socialCredits == socialCredits)&&(identical(other.socialCreditsLevel, socialCreditsLevel) || other.socialCreditsLevel == socialCreditsLevel)&&(identical(other.levelingProgress, levelingProgress) || other.levelingProgress == levelingProgress)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.verification, verification) || other.verification == verification)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hashAll([runtimeType,id,firstName,middleName,lastName,bio,gender,pronouns,location,timeZone,birthday,const DeepCollectionEquality().hash(links),lastSeenAt,activeBadge,experience,level,levelingProgress,picture,background,verification,createdAt,updatedAt,deletedAt]);
|
||||
int get hashCode => Object.hashAll([runtimeType,id,firstName,middleName,lastName,bio,gender,pronouns,location,timeZone,birthday,const DeepCollectionEquality().hash(links),lastSeenAt,activeBadge,experience,level,socialCredits,socialCreditsLevel,levelingProgress,picture,background,verification,createdAt,updatedAt,deletedAt]);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnAccountProfile(id: $id, firstName: $firstName, middleName: $middleName, lastName: $lastName, bio: $bio, gender: $gender, pronouns: $pronouns, location: $location, timeZone: $timeZone, birthday: $birthday, links: $links, lastSeenAt: $lastSeenAt, activeBadge: $activeBadge, experience: $experience, level: $level, levelingProgress: $levelingProgress, picture: $picture, background: $background, verification: $verification, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||
return 'SnAccountProfile(id: $id, firstName: $firstName, middleName: $middleName, lastName: $lastName, bio: $bio, gender: $gender, pronouns: $pronouns, location: $location, timeZone: $timeZone, birthday: $birthday, links: $links, lastSeenAt: $lastSeenAt, activeBadge: $activeBadge, experience: $experience, level: $level, socialCredits: $socialCredits, socialCreditsLevel: $socialCreditsLevel, levelingProgress: $levelingProgress, picture: $picture, background: $background, verification: $verification, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||
}
|
||||
|
||||
|
||||
@@ -646,7 +646,7 @@ abstract mixin class $SnAccountProfileCopyWith<$Res> {
|
||||
factory $SnAccountProfileCopyWith(SnAccountProfile value, $Res Function(SnAccountProfile) _then) = _$SnAccountProfileCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday,@ProfileLinkConverter() List<ProfileLink> links, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||
String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday,@ProfileLinkConverter() List<ProfileLink> links, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double socialCredits, int socialCreditsLevel, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||
});
|
||||
|
||||
|
||||
@@ -663,7 +663,7 @@ class _$SnAccountProfileCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of SnAccountProfile
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? firstName = null,Object? middleName = null,Object? lastName = null,Object? bio = null,Object? gender = null,Object? pronouns = null,Object? location = null,Object? timeZone = null,Object? birthday = freezed,Object? links = null,Object? lastSeenAt = freezed,Object? activeBadge = freezed,Object? experience = null,Object? level = null,Object? levelingProgress = null,Object? picture = freezed,Object? background = freezed,Object? verification = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? firstName = null,Object? middleName = null,Object? lastName = null,Object? bio = null,Object? gender = null,Object? pronouns = null,Object? location = null,Object? timeZone = null,Object? birthday = freezed,Object? links = null,Object? lastSeenAt = freezed,Object? activeBadge = freezed,Object? experience = null,Object? level = null,Object? socialCredits = null,Object? socialCreditsLevel = null,Object? levelingProgress = null,Object? picture = freezed,Object? background = freezed,Object? verification = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||
return _then(_self.copyWith(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,firstName: null == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable
|
||||
@@ -680,6 +680,8 @@ as List<ProfileLink>,lastSeenAt: freezed == lastSeenAt ? _self.lastSeenAt : last
|
||||
as DateTime?,activeBadge: freezed == activeBadge ? _self.activeBadge : activeBadge // ignore: cast_nullable_to_non_nullable
|
||||
as SnAccountBadge?,experience: null == experience ? _self.experience : experience // ignore: cast_nullable_to_non_nullable
|
||||
as int,level: null == level ? _self.level : level // ignore: cast_nullable_to_non_nullable
|
||||
as int,socialCredits: null == socialCredits ? _self.socialCredits : socialCredits // ignore: cast_nullable_to_non_nullable
|
||||
as double,socialCreditsLevel: null == socialCreditsLevel ? _self.socialCreditsLevel : socialCreditsLevel // ignore: cast_nullable_to_non_nullable
|
||||
as int,levelingProgress: null == levelingProgress ? _self.levelingProgress : levelingProgress // ignore: cast_nullable_to_non_nullable
|
||||
as double,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable
|
||||
as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable
|
||||
@@ -817,10 +819,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday, @ProfileLinkConverter() List<ProfileLink> links, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday, @ProfileLinkConverter() List<ProfileLink> links, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double socialCredits, int socialCreditsLevel, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnAccountProfile() when $default != null:
|
||||
return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.bio,_that.gender,_that.pronouns,_that.location,_that.timeZone,_that.birthday,_that.links,_that.lastSeenAt,_that.activeBadge,_that.experience,_that.level,_that.levelingProgress,_that.picture,_that.background,_that.verification,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
|
||||
return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.bio,_that.gender,_that.pronouns,_that.location,_that.timeZone,_that.birthday,_that.links,_that.lastSeenAt,_that.activeBadge,_that.experience,_that.level,_that.socialCredits,_that.socialCreditsLevel,_that.levelingProgress,_that.picture,_that.background,_that.verification,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
@@ -838,10 +840,10 @@ return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.b
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday, @ProfileLinkConverter() List<ProfileLink> links, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday, @ProfileLinkConverter() List<ProfileLink> links, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double socialCredits, int socialCreditsLevel, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnAccountProfile():
|
||||
return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.bio,_that.gender,_that.pronouns,_that.location,_that.timeZone,_that.birthday,_that.links,_that.lastSeenAt,_that.activeBadge,_that.experience,_that.level,_that.levelingProgress,_that.picture,_that.background,_that.verification,_that.createdAt,_that.updatedAt,_that.deletedAt);}
|
||||
return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.bio,_that.gender,_that.pronouns,_that.location,_that.timeZone,_that.birthday,_that.links,_that.lastSeenAt,_that.activeBadge,_that.experience,_that.level,_that.socialCredits,_that.socialCreditsLevel,_that.levelingProgress,_that.picture,_that.background,_that.verification,_that.createdAt,_that.updatedAt,_that.deletedAt);}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
@@ -855,10 +857,10 @@ return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.b
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday, @ProfileLinkConverter() List<ProfileLink> links, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday, @ProfileLinkConverter() List<ProfileLink> links, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double socialCredits, int socialCreditsLevel, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnAccountProfile() when $default != null:
|
||||
return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.bio,_that.gender,_that.pronouns,_that.location,_that.timeZone,_that.birthday,_that.links,_that.lastSeenAt,_that.activeBadge,_that.experience,_that.level,_that.levelingProgress,_that.picture,_that.background,_that.verification,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
|
||||
return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.bio,_that.gender,_that.pronouns,_that.location,_that.timeZone,_that.birthday,_that.links,_that.lastSeenAt,_that.activeBadge,_that.experience,_that.level,_that.socialCredits,_that.socialCreditsLevel,_that.levelingProgress,_that.picture,_that.background,_that.verification,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
@@ -870,7 +872,7 @@ return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.b
|
||||
@JsonSerializable()
|
||||
|
||||
class _SnAccountProfile implements SnAccountProfile {
|
||||
const _SnAccountProfile({required this.id, this.firstName = '', this.middleName = '', this.lastName = '', this.bio = '', this.gender = '', this.pronouns = '', this.location = '', this.timeZone = '', this.birthday, @ProfileLinkConverter() final List<ProfileLink> links = const [], this.lastSeenAt, this.activeBadge, required this.experience, required this.level, required this.levelingProgress, required this.picture, required this.background, required this.verification, required this.createdAt, required this.updatedAt, required this.deletedAt}): _links = links;
|
||||
const _SnAccountProfile({required this.id, this.firstName = '', this.middleName = '', this.lastName = '', this.bio = '', this.gender = '', this.pronouns = '', this.location = '', this.timeZone = '', this.birthday, @ProfileLinkConverter() final List<ProfileLink> links = const [], this.lastSeenAt, this.activeBadge, required this.experience, required this.level, this.socialCredits = 100, this.socialCreditsLevel = 0, required this.levelingProgress, required this.picture, required this.background, required this.verification, required this.createdAt, required this.updatedAt, required this.deletedAt}): _links = links;
|
||||
factory _SnAccountProfile.fromJson(Map<String, dynamic> json) => _$SnAccountProfileFromJson(json);
|
||||
|
||||
@override final String id;
|
||||
@@ -894,6 +896,8 @@ class _SnAccountProfile implements SnAccountProfile {
|
||||
@override final SnAccountBadge? activeBadge;
|
||||
@override final int experience;
|
||||
@override final int level;
|
||||
@override@JsonKey() final double socialCredits;
|
||||
@override@JsonKey() final int socialCreditsLevel;
|
||||
@override final double levelingProgress;
|
||||
@override final SnCloudFile? picture;
|
||||
@override final SnCloudFile? background;
|
||||
@@ -915,16 +919,16 @@ Map<String, dynamic> toJson() {
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAccountProfile&&(identical(other.id, id) || other.id == id)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.middleName, middleName) || other.middleName == middleName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.gender, gender) || other.gender == gender)&&(identical(other.pronouns, pronouns) || other.pronouns == pronouns)&&(identical(other.location, location) || other.location == location)&&(identical(other.timeZone, timeZone) || other.timeZone == timeZone)&&(identical(other.birthday, birthday) || other.birthday == birthday)&&const DeepCollectionEquality().equals(other._links, _links)&&(identical(other.lastSeenAt, lastSeenAt) || other.lastSeenAt == lastSeenAt)&&(identical(other.activeBadge, activeBadge) || other.activeBadge == activeBadge)&&(identical(other.experience, experience) || other.experience == experience)&&(identical(other.level, level) || other.level == level)&&(identical(other.levelingProgress, levelingProgress) || other.levelingProgress == levelingProgress)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.verification, verification) || other.verification == verification)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAccountProfile&&(identical(other.id, id) || other.id == id)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.middleName, middleName) || other.middleName == middleName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.gender, gender) || other.gender == gender)&&(identical(other.pronouns, pronouns) || other.pronouns == pronouns)&&(identical(other.location, location) || other.location == location)&&(identical(other.timeZone, timeZone) || other.timeZone == timeZone)&&(identical(other.birthday, birthday) || other.birthday == birthday)&&const DeepCollectionEquality().equals(other._links, _links)&&(identical(other.lastSeenAt, lastSeenAt) || other.lastSeenAt == lastSeenAt)&&(identical(other.activeBadge, activeBadge) || other.activeBadge == activeBadge)&&(identical(other.experience, experience) || other.experience == experience)&&(identical(other.level, level) || other.level == level)&&(identical(other.socialCredits, socialCredits) || other.socialCredits == socialCredits)&&(identical(other.socialCreditsLevel, socialCreditsLevel) || other.socialCreditsLevel == socialCreditsLevel)&&(identical(other.levelingProgress, levelingProgress) || other.levelingProgress == levelingProgress)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.verification, verification) || other.verification == verification)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hashAll([runtimeType,id,firstName,middleName,lastName,bio,gender,pronouns,location,timeZone,birthday,const DeepCollectionEquality().hash(_links),lastSeenAt,activeBadge,experience,level,levelingProgress,picture,background,verification,createdAt,updatedAt,deletedAt]);
|
||||
int get hashCode => Object.hashAll([runtimeType,id,firstName,middleName,lastName,bio,gender,pronouns,location,timeZone,birthday,const DeepCollectionEquality().hash(_links),lastSeenAt,activeBadge,experience,level,socialCredits,socialCreditsLevel,levelingProgress,picture,background,verification,createdAt,updatedAt,deletedAt]);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnAccountProfile(id: $id, firstName: $firstName, middleName: $middleName, lastName: $lastName, bio: $bio, gender: $gender, pronouns: $pronouns, location: $location, timeZone: $timeZone, birthday: $birthday, links: $links, lastSeenAt: $lastSeenAt, activeBadge: $activeBadge, experience: $experience, level: $level, levelingProgress: $levelingProgress, picture: $picture, background: $background, verification: $verification, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||
return 'SnAccountProfile(id: $id, firstName: $firstName, middleName: $middleName, lastName: $lastName, bio: $bio, gender: $gender, pronouns: $pronouns, location: $location, timeZone: $timeZone, birthday: $birthday, links: $links, lastSeenAt: $lastSeenAt, activeBadge: $activeBadge, experience: $experience, level: $level, socialCredits: $socialCredits, socialCreditsLevel: $socialCreditsLevel, levelingProgress: $levelingProgress, picture: $picture, background: $background, verification: $verification, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||
}
|
||||
|
||||
|
||||
@@ -935,7 +939,7 @@ abstract mixin class _$SnAccountProfileCopyWith<$Res> implements $SnAccountProfi
|
||||
factory _$SnAccountProfileCopyWith(_SnAccountProfile value, $Res Function(_SnAccountProfile) _then) = __$SnAccountProfileCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday,@ProfileLinkConverter() List<ProfileLink> links, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||
String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday,@ProfileLinkConverter() List<ProfileLink> links, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double socialCredits, int socialCreditsLevel, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||
});
|
||||
|
||||
|
||||
@@ -952,7 +956,7 @@ class __$SnAccountProfileCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of SnAccountProfile
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? firstName = null,Object? middleName = null,Object? lastName = null,Object? bio = null,Object? gender = null,Object? pronouns = null,Object? location = null,Object? timeZone = null,Object? birthday = freezed,Object? links = null,Object? lastSeenAt = freezed,Object? activeBadge = freezed,Object? experience = null,Object? level = null,Object? levelingProgress = null,Object? picture = freezed,Object? background = freezed,Object? verification = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? firstName = null,Object? middleName = null,Object? lastName = null,Object? bio = null,Object? gender = null,Object? pronouns = null,Object? location = null,Object? timeZone = null,Object? birthday = freezed,Object? links = null,Object? lastSeenAt = freezed,Object? activeBadge = freezed,Object? experience = null,Object? level = null,Object? socialCredits = null,Object? socialCreditsLevel = null,Object? levelingProgress = null,Object? picture = freezed,Object? background = freezed,Object? verification = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||
return _then(_SnAccountProfile(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,firstName: null == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable
|
||||
@@ -969,6 +973,8 @@ as List<ProfileLink>,lastSeenAt: freezed == lastSeenAt ? _self.lastSeenAt : last
|
||||
as DateTime?,activeBadge: freezed == activeBadge ? _self.activeBadge : activeBadge // ignore: cast_nullable_to_non_nullable
|
||||
as SnAccountBadge?,experience: null == experience ? _self.experience : experience // ignore: cast_nullable_to_non_nullable
|
||||
as int,level: null == level ? _self.level : level // ignore: cast_nullable_to_non_nullable
|
||||
as int,socialCredits: null == socialCredits ? _self.socialCredits : socialCredits // ignore: cast_nullable_to_non_nullable
|
||||
as double,socialCreditsLevel: null == socialCreditsLevel ? _self.socialCreditsLevel : socialCreditsLevel // ignore: cast_nullable_to_non_nullable
|
||||
as int,levelingProgress: null == levelingProgress ? _self.levelingProgress : levelingProgress // ignore: cast_nullable_to_non_nullable
|
||||
as double,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable
|
||||
as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable
|
||||
|
@@ -86,6 +86,8 @@ _SnAccountProfile _$SnAccountProfileFromJson(Map<String, dynamic> json) =>
|
||||
),
|
||||
experience: (json['experience'] as num).toInt(),
|
||||
level: (json['level'] as num).toInt(),
|
||||
socialCredits: (json['social_credits'] as num?)?.toDouble() ?? 100,
|
||||
socialCreditsLevel: (json['social_credits_level'] as num?)?.toInt() ?? 0,
|
||||
levelingProgress: (json['leveling_progress'] as num).toDouble(),
|
||||
picture:
|
||||
json['picture'] == null
|
||||
@@ -128,6 +130,8 @@ Map<String, dynamic> _$SnAccountProfileToJson(_SnAccountProfile instance) =>
|
||||
'active_badge': instance.activeBadge?.toJson(),
|
||||
'experience': instance.experience,
|
||||
'level': instance.level,
|
||||
'social_credits': instance.socialCredits,
|
||||
'social_credits_level': instance.socialCreditsLevel,
|
||||
'leveling_progress': instance.levelingProgress,
|
||||
'picture': instance.picture?.toJson(),
|
||||
'background': instance.background?.toJson(),
|
||||
|
@@ -54,7 +54,7 @@ sealed class SnEventCalendarEntry with _$SnEventCalendarEntry {
|
||||
const factory SnEventCalendarEntry({
|
||||
required DateTime date,
|
||||
required SnCheckInResult? checkInResult,
|
||||
required List<dynamic> statuses,
|
||||
required List<SnAccountStatus> statuses,
|
||||
}) = _SnEventCalendarEntry;
|
||||
|
||||
factory SnEventCalendarEntry.fromJson(Map<String, dynamic> json) =>
|
||||
|
@@ -861,7 +861,7 @@ as String,
|
||||
/// @nodoc
|
||||
mixin _$SnEventCalendarEntry {
|
||||
|
||||
DateTime get date; SnCheckInResult? get checkInResult; List<dynamic> get statuses;
|
||||
DateTime get date; SnCheckInResult? get checkInResult; List<SnAccountStatus> get statuses;
|
||||
/// Create a copy of SnEventCalendarEntry
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -894,7 +894,7 @@ abstract mixin class $SnEventCalendarEntryCopyWith<$Res> {
|
||||
factory $SnEventCalendarEntryCopyWith(SnEventCalendarEntry value, $Res Function(SnEventCalendarEntry) _then) = _$SnEventCalendarEntryCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
DateTime date, SnCheckInResult? checkInResult, List<dynamic> statuses
|
||||
DateTime date, SnCheckInResult? checkInResult, List<SnAccountStatus> statuses
|
||||
});
|
||||
|
||||
|
||||
@@ -916,7 +916,7 @@ class _$SnEventCalendarEntryCopyWithImpl<$Res>
|
||||
date: null == date ? _self.date : date // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,checkInResult: freezed == checkInResult ? _self.checkInResult : checkInResult // ignore: cast_nullable_to_non_nullable
|
||||
as SnCheckInResult?,statuses: null == statuses ? _self.statuses : statuses // ignore: cast_nullable_to_non_nullable
|
||||
as List<dynamic>,
|
||||
as List<SnAccountStatus>,
|
||||
));
|
||||
}
|
||||
/// Create a copy of SnEventCalendarEntry
|
||||
@@ -1010,7 +1010,7 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( DateTime date, SnCheckInResult? checkInResult, List<dynamic> statuses)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( DateTime date, SnCheckInResult? checkInResult, List<SnAccountStatus> statuses)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnEventCalendarEntry() when $default != null:
|
||||
return $default(_that.date,_that.checkInResult,_that.statuses);case _:
|
||||
@@ -1031,7 +1031,7 @@ return $default(_that.date,_that.checkInResult,_that.statuses);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( DateTime date, SnCheckInResult? checkInResult, List<dynamic> statuses) $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( DateTime date, SnCheckInResult? checkInResult, List<SnAccountStatus> statuses) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnEventCalendarEntry():
|
||||
return $default(_that.date,_that.checkInResult,_that.statuses);}
|
||||
@@ -1048,7 +1048,7 @@ return $default(_that.date,_that.checkInResult,_that.statuses);}
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( DateTime date, SnCheckInResult? checkInResult, List<dynamic> statuses)? $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( DateTime date, SnCheckInResult? checkInResult, List<SnAccountStatus> statuses)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnEventCalendarEntry() when $default != null:
|
||||
return $default(_that.date,_that.checkInResult,_that.statuses);case _:
|
||||
@@ -1063,13 +1063,13 @@ return $default(_that.date,_that.checkInResult,_that.statuses);case _:
|
||||
@JsonSerializable()
|
||||
|
||||
class _SnEventCalendarEntry implements SnEventCalendarEntry {
|
||||
const _SnEventCalendarEntry({required this.date, required this.checkInResult, required final List<dynamic> statuses}): _statuses = statuses;
|
||||
const _SnEventCalendarEntry({required this.date, required this.checkInResult, required final List<SnAccountStatus> statuses}): _statuses = statuses;
|
||||
factory _SnEventCalendarEntry.fromJson(Map<String, dynamic> json) => _$SnEventCalendarEntryFromJson(json);
|
||||
|
||||
@override final DateTime date;
|
||||
@override final SnCheckInResult? checkInResult;
|
||||
final List<dynamic> _statuses;
|
||||
@override List<dynamic> get statuses {
|
||||
final List<SnAccountStatus> _statuses;
|
||||
@override List<SnAccountStatus> get statuses {
|
||||
if (_statuses is EqualUnmodifiableListView) return _statuses;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_statuses);
|
||||
@@ -1109,7 +1109,7 @@ abstract mixin class _$SnEventCalendarEntryCopyWith<$Res> implements $SnEventCal
|
||||
factory _$SnEventCalendarEntryCopyWith(_SnEventCalendarEntry value, $Res Function(_SnEventCalendarEntry) _then) = __$SnEventCalendarEntryCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
DateTime date, SnCheckInResult? checkInResult, List<dynamic> statuses
|
||||
DateTime date, SnCheckInResult? checkInResult, List<SnAccountStatus> statuses
|
||||
});
|
||||
|
||||
|
||||
@@ -1131,7 +1131,7 @@ class __$SnEventCalendarEntryCopyWithImpl<$Res>
|
||||
date: null == date ? _self.date : date // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,checkInResult: freezed == checkInResult ? _self.checkInResult : checkInResult // ignore: cast_nullable_to_non_nullable
|
||||
as SnCheckInResult?,statuses: null == statuses ? _self._statuses : statuses // ignore: cast_nullable_to_non_nullable
|
||||
as List<dynamic>,
|
||||
as List<SnAccountStatus>,
|
||||
));
|
||||
}
|
||||
|
||||
|
@@ -87,7 +87,10 @@ _SnEventCalendarEntry _$SnEventCalendarEntryFromJson(
|
||||
: SnCheckInResult.fromJson(
|
||||
json['check_in_result'] as Map<String, dynamic>,
|
||||
),
|
||||
statuses: json['statuses'] as List<dynamic>,
|
||||
statuses:
|
||||
(json['statuses'] as List<dynamic>)
|
||||
.map((e) => SnAccountStatus.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SnEventCalendarEntryToJson(
|
||||
@@ -95,5 +98,5 @@ Map<String, dynamic> _$SnEventCalendarEntryToJson(
|
||||
) => <String, dynamic>{
|
||||
'date': instance.date.toIso8601String(),
|
||||
'check_in_result': instance.checkInResult?.toJson(),
|
||||
'statuses': instance.statuses,
|
||||
'statuses': instance.statuses.map((e) => e.toJson()).toList(),
|
||||
};
|
||||
|
@@ -27,6 +27,7 @@ sealed class SnPost with _$SnPost {
|
||||
@Default(0) int upvotes,
|
||||
@Default(0) int downvotes,
|
||||
@Default(0) int repliesCount,
|
||||
int? pinMode,
|
||||
String? threadedPostId,
|
||||
SnPost? threadedPost,
|
||||
String? repliedPostId,
|
||||
|
@@ -15,7 +15,7 @@ T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$SnPost {
|
||||
|
||||
String get id; String? get title; String? get description; String? get language; DateTime? get editedAt; DateTime? get publishedAt; int get visibility; String? get content; String? get slug; int get type; Map<String, dynamic>? get meta; int get viewsUnique; int get viewsTotal; int get upvotes; int get downvotes; int get repliesCount; String? get threadedPostId; SnPost? get threadedPost; String? get repliedPostId; SnPost? get repliedPost; String? get forwardedPostId; SnPost? get forwardedPost; String? get realmId; SnRealm? get realm; List<SnCloudFile> get attachments; SnPublisher get publisher; Map<String, int> get reactionsCount; Map<String, bool> get reactionsMade; List<dynamic> get reactions; List<SnPostTag> get tags; List<SnPostCategory> get categories; List<dynamic> get collections; DateTime? get createdAt; DateTime? get updatedAt; DateTime? get deletedAt; bool get isTruncated;
|
||||
String get id; String? get title; String? get description; String? get language; DateTime? get editedAt; DateTime? get publishedAt; int get visibility; String? get content; String? get slug; int get type; Map<String, dynamic>? get meta; int get viewsUnique; int get viewsTotal; int get upvotes; int get downvotes; int get repliesCount; int? get pinMode; String? get threadedPostId; SnPost? get threadedPost; String? get repliedPostId; SnPost? get repliedPost; String? get forwardedPostId; SnPost? get forwardedPost; String? get realmId; SnRealm? get realm; List<SnCloudFile> get attachments; SnPublisher get publisher; Map<String, int> get reactionsCount; Map<String, bool> get reactionsMade; List<dynamic> get reactions; List<SnPostTag> get tags; List<SnPostCategory> get categories; List<dynamic> get collections; DateTime? get createdAt; DateTime? get updatedAt; DateTime? get deletedAt; bool get isTruncated;
|
||||
/// Create a copy of SnPost
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -28,16 +28,16 @@ $SnPostCopyWith<SnPost> get copyWith => _$SnPostCopyWithImpl<SnPost>(this as SnP
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.language, language) || other.language == language)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.content, content) || other.content == content)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.meta, meta)&&(identical(other.viewsUnique, viewsUnique) || other.viewsUnique == viewsUnique)&&(identical(other.viewsTotal, viewsTotal) || other.viewsTotal == viewsTotal)&&(identical(other.upvotes, upvotes) || other.upvotes == upvotes)&&(identical(other.downvotes, downvotes) || other.downvotes == downvotes)&&(identical(other.repliesCount, repliesCount) || other.repliesCount == repliesCount)&&(identical(other.threadedPostId, threadedPostId) || other.threadedPostId == threadedPostId)&&(identical(other.threadedPost, threadedPost) || other.threadedPost == threadedPost)&&(identical(other.repliedPostId, repliedPostId) || other.repliedPostId == repliedPostId)&&(identical(other.repliedPost, repliedPost) || other.repliedPost == repliedPost)&&(identical(other.forwardedPostId, forwardedPostId) || other.forwardedPostId == forwardedPostId)&&(identical(other.forwardedPost, forwardedPost) || other.forwardedPost == forwardedPost)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&const DeepCollectionEquality().equals(other.attachments, attachments)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&const DeepCollectionEquality().equals(other.reactionsCount, reactionsCount)&&const DeepCollectionEquality().equals(other.reactionsMade, reactionsMade)&&const DeepCollectionEquality().equals(other.reactions, reactions)&&const DeepCollectionEquality().equals(other.tags, tags)&&const DeepCollectionEquality().equals(other.categories, categories)&&const DeepCollectionEquality().equals(other.collections, collections)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.isTruncated, isTruncated) || other.isTruncated == isTruncated));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.language, language) || other.language == language)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.content, content) || other.content == content)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.meta, meta)&&(identical(other.viewsUnique, viewsUnique) || other.viewsUnique == viewsUnique)&&(identical(other.viewsTotal, viewsTotal) || other.viewsTotal == viewsTotal)&&(identical(other.upvotes, upvotes) || other.upvotes == upvotes)&&(identical(other.downvotes, downvotes) || other.downvotes == downvotes)&&(identical(other.repliesCount, repliesCount) || other.repliesCount == repliesCount)&&(identical(other.pinMode, pinMode) || other.pinMode == pinMode)&&(identical(other.threadedPostId, threadedPostId) || other.threadedPostId == threadedPostId)&&(identical(other.threadedPost, threadedPost) || other.threadedPost == threadedPost)&&(identical(other.repliedPostId, repliedPostId) || other.repliedPostId == repliedPostId)&&(identical(other.repliedPost, repliedPost) || other.repliedPost == repliedPost)&&(identical(other.forwardedPostId, forwardedPostId) || other.forwardedPostId == forwardedPostId)&&(identical(other.forwardedPost, forwardedPost) || other.forwardedPost == forwardedPost)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&const DeepCollectionEquality().equals(other.attachments, attachments)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&const DeepCollectionEquality().equals(other.reactionsCount, reactionsCount)&&const DeepCollectionEquality().equals(other.reactionsMade, reactionsMade)&&const DeepCollectionEquality().equals(other.reactions, reactions)&&const DeepCollectionEquality().equals(other.tags, tags)&&const DeepCollectionEquality().equals(other.categories, categories)&&const DeepCollectionEquality().equals(other.collections, collections)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.isTruncated, isTruncated) || other.isTruncated == isTruncated));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hashAll([runtimeType,id,title,description,language,editedAt,publishedAt,visibility,content,slug,type,const DeepCollectionEquality().hash(meta),viewsUnique,viewsTotal,upvotes,downvotes,repliesCount,threadedPostId,threadedPost,repliedPostId,repliedPost,forwardedPostId,forwardedPost,realmId,realm,const DeepCollectionEquality().hash(attachments),publisher,const DeepCollectionEquality().hash(reactionsCount),const DeepCollectionEquality().hash(reactionsMade),const DeepCollectionEquality().hash(reactions),const DeepCollectionEquality().hash(tags),const DeepCollectionEquality().hash(categories),const DeepCollectionEquality().hash(collections),createdAt,updatedAt,deletedAt,isTruncated]);
|
||||
int get hashCode => Object.hashAll([runtimeType,id,title,description,language,editedAt,publishedAt,visibility,content,slug,type,const DeepCollectionEquality().hash(meta),viewsUnique,viewsTotal,upvotes,downvotes,repliesCount,pinMode,threadedPostId,threadedPost,repliedPostId,repliedPost,forwardedPostId,forwardedPost,realmId,realm,const DeepCollectionEquality().hash(attachments),publisher,const DeepCollectionEquality().hash(reactionsCount),const DeepCollectionEquality().hash(reactionsMade),const DeepCollectionEquality().hash(reactions),const DeepCollectionEquality().hash(tags),const DeepCollectionEquality().hash(categories),const DeepCollectionEquality().hash(collections),createdAt,updatedAt,deletedAt,isTruncated]);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnPost(id: $id, title: $title, description: $description, language: $language, editedAt: $editedAt, publishedAt: $publishedAt, visibility: $visibility, content: $content, slug: $slug, type: $type, meta: $meta, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, repliesCount: $repliesCount, threadedPostId: $threadedPostId, threadedPost: $threadedPost, repliedPostId: $repliedPostId, repliedPost: $repliedPost, forwardedPostId: $forwardedPostId, forwardedPost: $forwardedPost, realmId: $realmId, realm: $realm, attachments: $attachments, publisher: $publisher, reactionsCount: $reactionsCount, reactionsMade: $reactionsMade, reactions: $reactions, tags: $tags, categories: $categories, collections: $collections, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, isTruncated: $isTruncated)';
|
||||
return 'SnPost(id: $id, title: $title, description: $description, language: $language, editedAt: $editedAt, publishedAt: $publishedAt, visibility: $visibility, content: $content, slug: $slug, type: $type, meta: $meta, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, repliesCount: $repliesCount, pinMode: $pinMode, threadedPostId: $threadedPostId, threadedPost: $threadedPost, repliedPostId: $repliedPostId, repliedPost: $repliedPost, forwardedPostId: $forwardedPostId, forwardedPost: $forwardedPost, realmId: $realmId, realm: $realm, attachments: $attachments, publisher: $publisher, reactionsCount: $reactionsCount, reactionsMade: $reactionsMade, reactions: $reactions, tags: $tags, categories: $categories, collections: $collections, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, isTruncated: $isTruncated)';
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ abstract mixin class $SnPostCopyWith<$Res> {
|
||||
factory $SnPostCopyWith(SnPost value, $Res Function(SnPost) _then) = _$SnPostCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, String? slug, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, String? realmId, SnRealm? realm, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated
|
||||
String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, String? slug, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, int? pinMode, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, String? realmId, SnRealm? realm, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated
|
||||
});
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ class _$SnPostCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of SnPost
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = freezed,Object? visibility = null,Object? content = freezed,Object? slug = freezed,Object? type = null,Object? meta = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? repliesCount = null,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = freezed,Object? realmId = freezed,Object? realm = freezed,Object? attachments = null,Object? publisher = null,Object? reactionsCount = null,Object? reactionsMade = null,Object? reactions = null,Object? tags = null,Object? categories = null,Object? collections = null,Object? createdAt = freezed,Object? updatedAt = freezed,Object? deletedAt = freezed,Object? isTruncated = null,}) {
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = freezed,Object? visibility = null,Object? content = freezed,Object? slug = freezed,Object? type = null,Object? meta = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? repliesCount = null,Object? pinMode = freezed,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = freezed,Object? realmId = freezed,Object? realm = freezed,Object? attachments = null,Object? publisher = null,Object? reactionsCount = null,Object? reactionsMade = null,Object? reactions = null,Object? tags = null,Object? categories = null,Object? collections = null,Object? createdAt = freezed,Object? updatedAt = freezed,Object? deletedAt = freezed,Object? isTruncated = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
|
||||
@@ -83,7 +83,8 @@ as int,viewsTotal: null == viewsTotal ? _self.viewsTotal : viewsTotal // ignore:
|
||||
as int,upvotes: null == upvotes ? _self.upvotes : upvotes // ignore: cast_nullable_to_non_nullable
|
||||
as int,downvotes: null == downvotes ? _self.downvotes : downvotes // ignore: cast_nullable_to_non_nullable
|
||||
as int,repliesCount: null == repliesCount ? _self.repliesCount : repliesCount // ignore: cast_nullable_to_non_nullable
|
||||
as int,threadedPostId: freezed == threadedPostId ? _self.threadedPostId : threadedPostId // ignore: cast_nullable_to_non_nullable
|
||||
as int,pinMode: freezed == pinMode ? _self.pinMode : pinMode // ignore: cast_nullable_to_non_nullable
|
||||
as int?,threadedPostId: freezed == threadedPostId ? _self.threadedPostId : threadedPostId // ignore: cast_nullable_to_non_nullable
|
||||
as String?,threadedPost: freezed == threadedPost ? _self.threadedPost : threadedPost // ignore: cast_nullable_to_non_nullable
|
||||
as SnPost?,repliedPostId: freezed == repliedPostId ? _self.repliedPostId : repliedPostId // ignore: cast_nullable_to_non_nullable
|
||||
as String?,repliedPost: freezed == repliedPost ? _self.repliedPost : repliedPost // ignore: cast_nullable_to_non_nullable
|
||||
@@ -242,10 +243,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, String? slug, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, String? realmId, SnRealm? realm, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, String? slug, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, int? pinMode, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, String? realmId, SnRealm? realm, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnPost() when $default != null:
|
||||
return $default(_that.id,_that.title,_that.description,_that.language,_that.editedAt,_that.publishedAt,_that.visibility,_that.content,_that.slug,_that.type,_that.meta,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_that.realmId,_that.realm,_that.attachments,_that.publisher,_that.reactionsCount,_that.reactionsMade,_that.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);case _:
|
||||
return $default(_that.id,_that.title,_that.description,_that.language,_that.editedAt,_that.publishedAt,_that.visibility,_that.content,_that.slug,_that.type,_that.meta,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.pinMode,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_that.realmId,_that.realm,_that.attachments,_that.publisher,_that.reactionsCount,_that.reactionsMade,_that.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
@@ -263,10 +264,10 @@ return $default(_that.id,_that.title,_that.description,_that.language,_that.edit
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, String? slug, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, String? realmId, SnRealm? realm, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated) $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, String? slug, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, int? pinMode, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, String? realmId, SnRealm? realm, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnPost():
|
||||
return $default(_that.id,_that.title,_that.description,_that.language,_that.editedAt,_that.publishedAt,_that.visibility,_that.content,_that.slug,_that.type,_that.meta,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_that.realmId,_that.realm,_that.attachments,_that.publisher,_that.reactionsCount,_that.reactionsMade,_that.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);}
|
||||
return $default(_that.id,_that.title,_that.description,_that.language,_that.editedAt,_that.publishedAt,_that.visibility,_that.content,_that.slug,_that.type,_that.meta,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.pinMode,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_that.realmId,_that.realm,_that.attachments,_that.publisher,_that.reactionsCount,_that.reactionsMade,_that.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
@@ -280,10 +281,10 @@ return $default(_that.id,_that.title,_that.description,_that.language,_that.edit
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, String? slug, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, String? realmId, SnRealm? realm, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated)? $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, String? slug, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, int? pinMode, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, String? realmId, SnRealm? realm, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnPost() when $default != null:
|
||||
return $default(_that.id,_that.title,_that.description,_that.language,_that.editedAt,_that.publishedAt,_that.visibility,_that.content,_that.slug,_that.type,_that.meta,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_that.realmId,_that.realm,_that.attachments,_that.publisher,_that.reactionsCount,_that.reactionsMade,_that.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);case _:
|
||||
return $default(_that.id,_that.title,_that.description,_that.language,_that.editedAt,_that.publishedAt,_that.visibility,_that.content,_that.slug,_that.type,_that.meta,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.pinMode,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_that.realmId,_that.realm,_that.attachments,_that.publisher,_that.reactionsCount,_that.reactionsMade,_that.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
@@ -295,7 +296,7 @@ return $default(_that.id,_that.title,_that.description,_that.language,_that.edit
|
||||
@JsonSerializable()
|
||||
|
||||
class _SnPost implements SnPost {
|
||||
const _SnPost({required this.id, this.title, this.description, this.language, this.editedAt, this.publishedAt = null, this.visibility = 0, this.content, this.slug, this.type = 0, final Map<String, dynamic>? meta, this.viewsUnique = 0, this.viewsTotal = 0, this.upvotes = 0, this.downvotes = 0, this.repliesCount = 0, this.threadedPostId, this.threadedPost, this.repliedPostId, this.repliedPost, this.forwardedPostId, this.forwardedPost, this.realmId, this.realm, final List<SnCloudFile> attachments = const [], required this.publisher, final Map<String, int> reactionsCount = const {}, final Map<String, bool> reactionsMade = const {}, final List<dynamic> reactions = const [], final List<SnPostTag> tags = const [], final List<SnPostCategory> categories = const [], final List<dynamic> collections = const [], this.createdAt = null, this.updatedAt = null, this.deletedAt, this.isTruncated = false}): _meta = meta,_attachments = attachments,_reactionsCount = reactionsCount,_reactionsMade = reactionsMade,_reactions = reactions,_tags = tags,_categories = categories,_collections = collections;
|
||||
const _SnPost({required this.id, this.title, this.description, this.language, this.editedAt, this.publishedAt = null, this.visibility = 0, this.content, this.slug, this.type = 0, final Map<String, dynamic>? meta, this.viewsUnique = 0, this.viewsTotal = 0, this.upvotes = 0, this.downvotes = 0, this.repliesCount = 0, this.pinMode, this.threadedPostId, this.threadedPost, this.repliedPostId, this.repliedPost, this.forwardedPostId, this.forwardedPost, this.realmId, this.realm, final List<SnCloudFile> attachments = const [], required this.publisher, final Map<String, int> reactionsCount = const {}, final Map<String, bool> reactionsMade = const {}, final List<dynamic> reactions = const [], final List<SnPostTag> tags = const [], final List<SnPostCategory> categories = const [], final List<dynamic> collections = const [], this.createdAt = null, this.updatedAt = null, this.deletedAt, this.isTruncated = false}): _meta = meta,_attachments = attachments,_reactionsCount = reactionsCount,_reactionsMade = reactionsMade,_reactions = reactions,_tags = tags,_categories = categories,_collections = collections;
|
||||
factory _SnPost.fromJson(Map<String, dynamic> json) => _$SnPostFromJson(json);
|
||||
|
||||
@override final String id;
|
||||
@@ -322,6 +323,7 @@ class _SnPost implements SnPost {
|
||||
@override@JsonKey() final int upvotes;
|
||||
@override@JsonKey() final int downvotes;
|
||||
@override@JsonKey() final int repliesCount;
|
||||
@override final int? pinMode;
|
||||
@override final String? threadedPostId;
|
||||
@override final SnPost? threadedPost;
|
||||
@override final String? repliedPostId;
|
||||
@@ -398,16 +400,16 @@ Map<String, dynamic> toJson() {
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.language, language) || other.language == language)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.content, content) || other.content == content)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._meta, _meta)&&(identical(other.viewsUnique, viewsUnique) || other.viewsUnique == viewsUnique)&&(identical(other.viewsTotal, viewsTotal) || other.viewsTotal == viewsTotal)&&(identical(other.upvotes, upvotes) || other.upvotes == upvotes)&&(identical(other.downvotes, downvotes) || other.downvotes == downvotes)&&(identical(other.repliesCount, repliesCount) || other.repliesCount == repliesCount)&&(identical(other.threadedPostId, threadedPostId) || other.threadedPostId == threadedPostId)&&(identical(other.threadedPost, threadedPost) || other.threadedPost == threadedPost)&&(identical(other.repliedPostId, repliedPostId) || other.repliedPostId == repliedPostId)&&(identical(other.repliedPost, repliedPost) || other.repliedPost == repliedPost)&&(identical(other.forwardedPostId, forwardedPostId) || other.forwardedPostId == forwardedPostId)&&(identical(other.forwardedPost, forwardedPost) || other.forwardedPost == forwardedPost)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&const DeepCollectionEquality().equals(other._attachments, _attachments)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&const DeepCollectionEquality().equals(other._reactionsCount, _reactionsCount)&&const DeepCollectionEquality().equals(other._reactionsMade, _reactionsMade)&&const DeepCollectionEquality().equals(other._reactions, _reactions)&&const DeepCollectionEquality().equals(other._tags, _tags)&&const DeepCollectionEquality().equals(other._categories, _categories)&&const DeepCollectionEquality().equals(other._collections, _collections)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.isTruncated, isTruncated) || other.isTruncated == isTruncated));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.language, language) || other.language == language)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.content, content) || other.content == content)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._meta, _meta)&&(identical(other.viewsUnique, viewsUnique) || other.viewsUnique == viewsUnique)&&(identical(other.viewsTotal, viewsTotal) || other.viewsTotal == viewsTotal)&&(identical(other.upvotes, upvotes) || other.upvotes == upvotes)&&(identical(other.downvotes, downvotes) || other.downvotes == downvotes)&&(identical(other.repliesCount, repliesCount) || other.repliesCount == repliesCount)&&(identical(other.pinMode, pinMode) || other.pinMode == pinMode)&&(identical(other.threadedPostId, threadedPostId) || other.threadedPostId == threadedPostId)&&(identical(other.threadedPost, threadedPost) || other.threadedPost == threadedPost)&&(identical(other.repliedPostId, repliedPostId) || other.repliedPostId == repliedPostId)&&(identical(other.repliedPost, repliedPost) || other.repliedPost == repliedPost)&&(identical(other.forwardedPostId, forwardedPostId) || other.forwardedPostId == forwardedPostId)&&(identical(other.forwardedPost, forwardedPost) || other.forwardedPost == forwardedPost)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&const DeepCollectionEquality().equals(other._attachments, _attachments)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&const DeepCollectionEquality().equals(other._reactionsCount, _reactionsCount)&&const DeepCollectionEquality().equals(other._reactionsMade, _reactionsMade)&&const DeepCollectionEquality().equals(other._reactions, _reactions)&&const DeepCollectionEquality().equals(other._tags, _tags)&&const DeepCollectionEquality().equals(other._categories, _categories)&&const DeepCollectionEquality().equals(other._collections, _collections)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.isTruncated, isTruncated) || other.isTruncated == isTruncated));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hashAll([runtimeType,id,title,description,language,editedAt,publishedAt,visibility,content,slug,type,const DeepCollectionEquality().hash(_meta),viewsUnique,viewsTotal,upvotes,downvotes,repliesCount,threadedPostId,threadedPost,repliedPostId,repliedPost,forwardedPostId,forwardedPost,realmId,realm,const DeepCollectionEquality().hash(_attachments),publisher,const DeepCollectionEquality().hash(_reactionsCount),const DeepCollectionEquality().hash(_reactionsMade),const DeepCollectionEquality().hash(_reactions),const DeepCollectionEquality().hash(_tags),const DeepCollectionEquality().hash(_categories),const DeepCollectionEquality().hash(_collections),createdAt,updatedAt,deletedAt,isTruncated]);
|
||||
int get hashCode => Object.hashAll([runtimeType,id,title,description,language,editedAt,publishedAt,visibility,content,slug,type,const DeepCollectionEquality().hash(_meta),viewsUnique,viewsTotal,upvotes,downvotes,repliesCount,pinMode,threadedPostId,threadedPost,repliedPostId,repliedPost,forwardedPostId,forwardedPost,realmId,realm,const DeepCollectionEquality().hash(_attachments),publisher,const DeepCollectionEquality().hash(_reactionsCount),const DeepCollectionEquality().hash(_reactionsMade),const DeepCollectionEquality().hash(_reactions),const DeepCollectionEquality().hash(_tags),const DeepCollectionEquality().hash(_categories),const DeepCollectionEquality().hash(_collections),createdAt,updatedAt,deletedAt,isTruncated]);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnPost(id: $id, title: $title, description: $description, language: $language, editedAt: $editedAt, publishedAt: $publishedAt, visibility: $visibility, content: $content, slug: $slug, type: $type, meta: $meta, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, repliesCount: $repliesCount, threadedPostId: $threadedPostId, threadedPost: $threadedPost, repliedPostId: $repliedPostId, repliedPost: $repliedPost, forwardedPostId: $forwardedPostId, forwardedPost: $forwardedPost, realmId: $realmId, realm: $realm, attachments: $attachments, publisher: $publisher, reactionsCount: $reactionsCount, reactionsMade: $reactionsMade, reactions: $reactions, tags: $tags, categories: $categories, collections: $collections, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, isTruncated: $isTruncated)';
|
||||
return 'SnPost(id: $id, title: $title, description: $description, language: $language, editedAt: $editedAt, publishedAt: $publishedAt, visibility: $visibility, content: $content, slug: $slug, type: $type, meta: $meta, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, repliesCount: $repliesCount, pinMode: $pinMode, threadedPostId: $threadedPostId, threadedPost: $threadedPost, repliedPostId: $repliedPostId, repliedPost: $repliedPost, forwardedPostId: $forwardedPostId, forwardedPost: $forwardedPost, realmId: $realmId, realm: $realm, attachments: $attachments, publisher: $publisher, reactionsCount: $reactionsCount, reactionsMade: $reactionsMade, reactions: $reactions, tags: $tags, categories: $categories, collections: $collections, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, isTruncated: $isTruncated)';
|
||||
}
|
||||
|
||||
|
||||
@@ -418,7 +420,7 @@ abstract mixin class _$SnPostCopyWith<$Res> implements $SnPostCopyWith<$Res> {
|
||||
factory _$SnPostCopyWith(_SnPost value, $Res Function(_SnPost) _then) = __$SnPostCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, String? slug, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, String? realmId, SnRealm? realm, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated
|
||||
String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, String? slug, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, int? pinMode, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, String? realmId, SnRealm? realm, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated
|
||||
});
|
||||
|
||||
|
||||
@@ -435,7 +437,7 @@ class __$SnPostCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of SnPost
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = freezed,Object? visibility = null,Object? content = freezed,Object? slug = freezed,Object? type = null,Object? meta = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? repliesCount = null,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = freezed,Object? realmId = freezed,Object? realm = freezed,Object? attachments = null,Object? publisher = null,Object? reactionsCount = null,Object? reactionsMade = null,Object? reactions = null,Object? tags = null,Object? categories = null,Object? collections = null,Object? createdAt = freezed,Object? updatedAt = freezed,Object? deletedAt = freezed,Object? isTruncated = null,}) {
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = freezed,Object? visibility = null,Object? content = freezed,Object? slug = freezed,Object? type = null,Object? meta = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? repliesCount = null,Object? pinMode = freezed,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = freezed,Object? realmId = freezed,Object? realm = freezed,Object? attachments = null,Object? publisher = null,Object? reactionsCount = null,Object? reactionsMade = null,Object? reactions = null,Object? tags = null,Object? categories = null,Object? collections = null,Object? createdAt = freezed,Object? updatedAt = freezed,Object? deletedAt = freezed,Object? isTruncated = null,}) {
|
||||
return _then(_SnPost(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
|
||||
@@ -453,7 +455,8 @@ as int,viewsTotal: null == viewsTotal ? _self.viewsTotal : viewsTotal // ignore:
|
||||
as int,upvotes: null == upvotes ? _self.upvotes : upvotes // ignore: cast_nullable_to_non_nullable
|
||||
as int,downvotes: null == downvotes ? _self.downvotes : downvotes // ignore: cast_nullable_to_non_nullable
|
||||
as int,repliesCount: null == repliesCount ? _self.repliesCount : repliesCount // ignore: cast_nullable_to_non_nullable
|
||||
as int,threadedPostId: freezed == threadedPostId ? _self.threadedPostId : threadedPostId // ignore: cast_nullable_to_non_nullable
|
||||
as int,pinMode: freezed == pinMode ? _self.pinMode : pinMode // ignore: cast_nullable_to_non_nullable
|
||||
as int?,threadedPostId: freezed == threadedPostId ? _self.threadedPostId : threadedPostId // ignore: cast_nullable_to_non_nullable
|
||||
as String?,threadedPost: freezed == threadedPost ? _self.threadedPost : threadedPost // ignore: cast_nullable_to_non_nullable
|
||||
as SnPost?,repliedPostId: freezed == repliedPostId ? _self.repliedPostId : repliedPostId // ignore: cast_nullable_to_non_nullable
|
||||
as String?,repliedPost: freezed == repliedPost ? _self.repliedPost : repliedPost // ignore: cast_nullable_to_non_nullable
|
||||
|
@@ -29,6 +29,7 @@ _SnPost _$SnPostFromJson(Map<String, dynamic> json) => _SnPost(
|
||||
upvotes: (json['upvotes'] as num?)?.toInt() ?? 0,
|
||||
downvotes: (json['downvotes'] as num?)?.toInt() ?? 0,
|
||||
repliesCount: (json['replies_count'] as num?)?.toInt() ?? 0,
|
||||
pinMode: (json['pin_mode'] as num?)?.toInt(),
|
||||
threadedPostId: json['threaded_post_id'] as String?,
|
||||
threadedPost:
|
||||
json['threaded_post'] == null
|
||||
@@ -109,6 +110,7 @@ Map<String, dynamic> _$SnPostToJson(_SnPost instance) => <String, dynamic>{
|
||||
'upvotes': instance.upvotes,
|
||||
'downvotes': instance.downvotes,
|
||||
'replies_count': instance.repliesCount,
|
||||
'pin_mode': instance.pinMode,
|
||||
'threaded_post_id': instance.threadedPostId,
|
||||
'threaded_post': instance.threadedPost?.toJson(),
|
||||
'replied_post_id': instance.repliedPostId,
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:island/models/post.dart';
|
||||
import 'package:island/services/text.dart';
|
||||
import 'package:island/utils/text.dart';
|
||||
|
||||
part 'post_category.freezed.dart';
|
||||
part 'post_category.g.dart';
|
||||
|
@@ -15,6 +15,7 @@ const kNetworkServerStoreKey = 'app_server_url';
|
||||
|
||||
const kAppbarTransparentStoreKey = 'app_bar_transparent';
|
||||
const kAppBackgroundStoreKey = 'app_has_background';
|
||||
const kAppShowBackgroundImage = 'app_show_background_image';
|
||||
const kAppColorSchemeStoreKey = 'app_color_scheme';
|
||||
const kAppNotifyWithHaptic = 'app_notify_with_haptic';
|
||||
const kAppCustomFonts = 'app_custom_fonts';
|
||||
@@ -58,6 +59,7 @@ sealed class AppSettings with _$AppSettings {
|
||||
required bool aprilFoolFeatures,
|
||||
required bool enterToSend,
|
||||
required bool appBarTransparent,
|
||||
required bool showBackgroundImage,
|
||||
required String? customFonts,
|
||||
required int? appColorScheme, // The color stored via the int type
|
||||
required Size? windowSize, // The window size for desktop platforms
|
||||
@@ -75,6 +77,7 @@ class AppSettingsNotifier extends _$AppSettingsNotifier {
|
||||
aprilFoolFeatures: prefs.getBool(kAppAprilFoolFeatures) ?? true,
|
||||
enterToSend: prefs.getBool(kAppEnterToSend) ?? true,
|
||||
appBarTransparent: prefs.getBool(kAppbarTransparentStoreKey) ?? false,
|
||||
showBackgroundImage: prefs.getBool(kAppShowBackgroundImage) ?? true,
|
||||
customFonts: prefs.getString(kAppCustomFonts),
|
||||
appColorScheme: prefs.getInt(kAppColorSchemeStoreKey),
|
||||
windowSize: _getWindowSizeFromPrefs(prefs),
|
||||
@@ -129,6 +132,12 @@ class AppSettingsNotifier extends _$AppSettingsNotifier {
|
||||
ref.read(themeProvider.notifier).reloadTheme();
|
||||
}
|
||||
|
||||
void setShowBackgroundImage(bool value) {
|
||||
final prefs = ref.read(sharedPreferencesProvider);
|
||||
prefs.setBool(kAppShowBackgroundImage, value);
|
||||
state = state.copyWith(showBackgroundImage: value);
|
||||
}
|
||||
|
||||
void setCustomFonts(String? value) {
|
||||
final prefs = ref.read(sharedPreferencesProvider);
|
||||
prefs.setString(kAppCustomFonts, value ?? '');
|
||||
|
@@ -14,7 +14,7 @@ T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$AppSettings {
|
||||
|
||||
bool get autoTranslate; bool get soundEffects; bool get aprilFoolFeatures; bool get enterToSend; bool get appBarTransparent; String? get customFonts; int? get appColorScheme;// The color stored via the int type
|
||||
bool get autoTranslate; bool get soundEffects; bool get aprilFoolFeatures; bool get enterToSend; bool get appBarTransparent; bool get showBackgroundImage; String? get customFonts; int? get appColorScheme;// The color stored via the int type
|
||||
Size? get windowSize;
|
||||
/// Create a copy of AppSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -26,16 +26,16 @@ $AppSettingsCopyWith<AppSettings> get copyWith => _$AppSettingsCopyWithImpl<AppS
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is AppSettings&&(identical(other.autoTranslate, autoTranslate) || other.autoTranslate == autoTranslate)&&(identical(other.soundEffects, soundEffects) || other.soundEffects == soundEffects)&&(identical(other.aprilFoolFeatures, aprilFoolFeatures) || other.aprilFoolFeatures == aprilFoolFeatures)&&(identical(other.enterToSend, enterToSend) || other.enterToSend == enterToSend)&&(identical(other.appBarTransparent, appBarTransparent) || other.appBarTransparent == appBarTransparent)&&(identical(other.customFonts, customFonts) || other.customFonts == customFonts)&&(identical(other.appColorScheme, appColorScheme) || other.appColorScheme == appColorScheme)&&(identical(other.windowSize, windowSize) || other.windowSize == windowSize));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is AppSettings&&(identical(other.autoTranslate, autoTranslate) || other.autoTranslate == autoTranslate)&&(identical(other.soundEffects, soundEffects) || other.soundEffects == soundEffects)&&(identical(other.aprilFoolFeatures, aprilFoolFeatures) || other.aprilFoolFeatures == aprilFoolFeatures)&&(identical(other.enterToSend, enterToSend) || other.enterToSend == enterToSend)&&(identical(other.appBarTransparent, appBarTransparent) || other.appBarTransparent == appBarTransparent)&&(identical(other.showBackgroundImage, showBackgroundImage) || other.showBackgroundImage == showBackgroundImage)&&(identical(other.customFonts, customFonts) || other.customFonts == customFonts)&&(identical(other.appColorScheme, appColorScheme) || other.appColorScheme == appColorScheme)&&(identical(other.windowSize, windowSize) || other.windowSize == windowSize));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,autoTranslate,soundEffects,aprilFoolFeatures,enterToSend,appBarTransparent,customFonts,appColorScheme,windowSize);
|
||||
int get hashCode => Object.hash(runtimeType,autoTranslate,soundEffects,aprilFoolFeatures,enterToSend,appBarTransparent,showBackgroundImage,customFonts,appColorScheme,windowSize);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AppSettings(autoTranslate: $autoTranslate, soundEffects: $soundEffects, aprilFoolFeatures: $aprilFoolFeatures, enterToSend: $enterToSend, appBarTransparent: $appBarTransparent, customFonts: $customFonts, appColorScheme: $appColorScheme, windowSize: $windowSize)';
|
||||
return 'AppSettings(autoTranslate: $autoTranslate, soundEffects: $soundEffects, aprilFoolFeatures: $aprilFoolFeatures, enterToSend: $enterToSend, appBarTransparent: $appBarTransparent, showBackgroundImage: $showBackgroundImage, customFonts: $customFonts, appColorScheme: $appColorScheme, windowSize: $windowSize)';
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ abstract mixin class $AppSettingsCopyWith<$Res> {
|
||||
factory $AppSettingsCopyWith(AppSettings value, $Res Function(AppSettings) _then) = _$AppSettingsCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
bool autoTranslate, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, String? customFonts, int? appColorScheme, Size? windowSize
|
||||
bool autoTranslate, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize
|
||||
});
|
||||
|
||||
|
||||
@@ -63,13 +63,14 @@ class _$AppSettingsCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of AppSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? autoTranslate = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? customFonts = freezed,Object? appColorScheme = freezed,Object? windowSize = freezed,}) {
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? autoTranslate = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? showBackgroundImage = null,Object? customFonts = freezed,Object? appColorScheme = freezed,Object? windowSize = freezed,}) {
|
||||
return _then(_self.copyWith(
|
||||
autoTranslate: null == autoTranslate ? _self.autoTranslate : autoTranslate // ignore: cast_nullable_to_non_nullable
|
||||
as bool,soundEffects: null == soundEffects ? _self.soundEffects : soundEffects // ignore: cast_nullable_to_non_nullable
|
||||
as bool,aprilFoolFeatures: null == aprilFoolFeatures ? _self.aprilFoolFeatures : aprilFoolFeatures // ignore: cast_nullable_to_non_nullable
|
||||
as bool,enterToSend: null == enterToSend ? _self.enterToSend : enterToSend // ignore: cast_nullable_to_non_nullable
|
||||
as bool,appBarTransparent: null == appBarTransparent ? _self.appBarTransparent : appBarTransparent // ignore: cast_nullable_to_non_nullable
|
||||
as bool,showBackgroundImage: null == showBackgroundImage ? _self.showBackgroundImage : showBackgroundImage // ignore: cast_nullable_to_non_nullable
|
||||
as bool,customFonts: freezed == customFonts ? _self.customFonts : customFonts // ignore: cast_nullable_to_non_nullable
|
||||
as String?,appColorScheme: freezed == appColorScheme ? _self.appColorScheme : appColorScheme // ignore: cast_nullable_to_non_nullable
|
||||
as int?,windowSize: freezed == windowSize ? _self.windowSize : windowSize // ignore: cast_nullable_to_non_nullable
|
||||
@@ -155,10 +156,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool autoTranslate, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, String? customFonts, int? appColorScheme, Size? windowSize)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool autoTranslate, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _AppSettings() when $default != null:
|
||||
return $default(_that.autoTranslate,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.customFonts,_that.appColorScheme,_that.windowSize);case _:
|
||||
return $default(_that.autoTranslate,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.customFonts,_that.appColorScheme,_that.windowSize);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
@@ -176,10 +177,10 @@ return $default(_that.autoTranslate,_that.soundEffects,_that.aprilFoolFeatures,_
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool autoTranslate, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, String? customFonts, int? appColorScheme, Size? windowSize) $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool autoTranslate, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _AppSettings():
|
||||
return $default(_that.autoTranslate,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.customFonts,_that.appColorScheme,_that.windowSize);}
|
||||
return $default(_that.autoTranslate,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.customFonts,_that.appColorScheme,_that.windowSize);}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
@@ -193,10 +194,10 @@ return $default(_that.autoTranslate,_that.soundEffects,_that.aprilFoolFeatures,_
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool autoTranslate, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, String? customFonts, int? appColorScheme, Size? windowSize)? $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool autoTranslate, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _AppSettings() when $default != null:
|
||||
return $default(_that.autoTranslate,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.customFonts,_that.appColorScheme,_that.windowSize);case _:
|
||||
return $default(_that.autoTranslate,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.customFonts,_that.appColorScheme,_that.windowSize);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
@@ -208,7 +209,7 @@ return $default(_that.autoTranslate,_that.soundEffects,_that.aprilFoolFeatures,_
|
||||
|
||||
|
||||
class _AppSettings implements AppSettings {
|
||||
const _AppSettings({required this.autoTranslate, required this.soundEffects, required this.aprilFoolFeatures, required this.enterToSend, required this.appBarTransparent, required this.customFonts, required this.appColorScheme, required this.windowSize});
|
||||
const _AppSettings({required this.autoTranslate, required this.soundEffects, required this.aprilFoolFeatures, required this.enterToSend, required this.appBarTransparent, required this.showBackgroundImage, required this.customFonts, required this.appColorScheme, required this.windowSize});
|
||||
|
||||
|
||||
@override final bool autoTranslate;
|
||||
@@ -216,6 +217,7 @@ class _AppSettings implements AppSettings {
|
||||
@override final bool aprilFoolFeatures;
|
||||
@override final bool enterToSend;
|
||||
@override final bool appBarTransparent;
|
||||
@override final bool showBackgroundImage;
|
||||
@override final String? customFonts;
|
||||
@override final int? appColorScheme;
|
||||
// The color stored via the int type
|
||||
@@ -231,16 +233,16 @@ _$AppSettingsCopyWith<_AppSettings> get copyWith => __$AppSettingsCopyWithImpl<_
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppSettings&&(identical(other.autoTranslate, autoTranslate) || other.autoTranslate == autoTranslate)&&(identical(other.soundEffects, soundEffects) || other.soundEffects == soundEffects)&&(identical(other.aprilFoolFeatures, aprilFoolFeatures) || other.aprilFoolFeatures == aprilFoolFeatures)&&(identical(other.enterToSend, enterToSend) || other.enterToSend == enterToSend)&&(identical(other.appBarTransparent, appBarTransparent) || other.appBarTransparent == appBarTransparent)&&(identical(other.customFonts, customFonts) || other.customFonts == customFonts)&&(identical(other.appColorScheme, appColorScheme) || other.appColorScheme == appColorScheme)&&(identical(other.windowSize, windowSize) || other.windowSize == windowSize));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppSettings&&(identical(other.autoTranslate, autoTranslate) || other.autoTranslate == autoTranslate)&&(identical(other.soundEffects, soundEffects) || other.soundEffects == soundEffects)&&(identical(other.aprilFoolFeatures, aprilFoolFeatures) || other.aprilFoolFeatures == aprilFoolFeatures)&&(identical(other.enterToSend, enterToSend) || other.enterToSend == enterToSend)&&(identical(other.appBarTransparent, appBarTransparent) || other.appBarTransparent == appBarTransparent)&&(identical(other.showBackgroundImage, showBackgroundImage) || other.showBackgroundImage == showBackgroundImage)&&(identical(other.customFonts, customFonts) || other.customFonts == customFonts)&&(identical(other.appColorScheme, appColorScheme) || other.appColorScheme == appColorScheme)&&(identical(other.windowSize, windowSize) || other.windowSize == windowSize));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,autoTranslate,soundEffects,aprilFoolFeatures,enterToSend,appBarTransparent,customFonts,appColorScheme,windowSize);
|
||||
int get hashCode => Object.hash(runtimeType,autoTranslate,soundEffects,aprilFoolFeatures,enterToSend,appBarTransparent,showBackgroundImage,customFonts,appColorScheme,windowSize);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AppSettings(autoTranslate: $autoTranslate, soundEffects: $soundEffects, aprilFoolFeatures: $aprilFoolFeatures, enterToSend: $enterToSend, appBarTransparent: $appBarTransparent, customFonts: $customFonts, appColorScheme: $appColorScheme, windowSize: $windowSize)';
|
||||
return 'AppSettings(autoTranslate: $autoTranslate, soundEffects: $soundEffects, aprilFoolFeatures: $aprilFoolFeatures, enterToSend: $enterToSend, appBarTransparent: $appBarTransparent, showBackgroundImage: $showBackgroundImage, customFonts: $customFonts, appColorScheme: $appColorScheme, windowSize: $windowSize)';
|
||||
}
|
||||
|
||||
|
||||
@@ -251,7 +253,7 @@ abstract mixin class _$AppSettingsCopyWith<$Res> implements $AppSettingsCopyWith
|
||||
factory _$AppSettingsCopyWith(_AppSettings value, $Res Function(_AppSettings) _then) = __$AppSettingsCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
bool autoTranslate, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, String? customFonts, int? appColorScheme, Size? windowSize
|
||||
bool autoTranslate, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize
|
||||
});
|
||||
|
||||
|
||||
@@ -268,13 +270,14 @@ class __$AppSettingsCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of AppSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? autoTranslate = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? customFonts = freezed,Object? appColorScheme = freezed,Object? windowSize = freezed,}) {
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? autoTranslate = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? showBackgroundImage = null,Object? customFonts = freezed,Object? appColorScheme = freezed,Object? windowSize = freezed,}) {
|
||||
return _then(_AppSettings(
|
||||
autoTranslate: null == autoTranslate ? _self.autoTranslate : autoTranslate // ignore: cast_nullable_to_non_nullable
|
||||
as bool,soundEffects: null == soundEffects ? _self.soundEffects : soundEffects // ignore: cast_nullable_to_non_nullable
|
||||
as bool,aprilFoolFeatures: null == aprilFoolFeatures ? _self.aprilFoolFeatures : aprilFoolFeatures // ignore: cast_nullable_to_non_nullable
|
||||
as bool,enterToSend: null == enterToSend ? _self.enterToSend : enterToSend // ignore: cast_nullable_to_non_nullable
|
||||
as bool,appBarTransparent: null == appBarTransparent ? _self.appBarTransparent : appBarTransparent // ignore: cast_nullable_to_non_nullable
|
||||
as bool,showBackgroundImage: null == showBackgroundImage ? _self.showBackgroundImage : showBackgroundImage // ignore: cast_nullable_to_non_nullable
|
||||
as bool,customFonts: freezed == customFonts ? _self.customFonts : customFonts // ignore: cast_nullable_to_non_nullable
|
||||
as String?,appColorScheme: freezed == appColorScheme ? _self.appColorScheme : appColorScheme // ignore: cast_nullable_to_non_nullable
|
||||
as int?,windowSize: freezed == windowSize ? _self.windowSize : windowSize // ignore: cast_nullable_to_non_nullable
|
||||
|
@@ -7,7 +7,7 @@ part of 'config.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$appSettingsNotifierHash() =>
|
||||
r'c4f40a3bc4311c6360c2b5e44f8df5e5d7c1bd75';
|
||||
r'e3c13307eabb0201487b85ab67b1ab493e588e71';
|
||||
|
||||
/// See also [AppSettingsNotifier].
|
||||
@ProviderFor(AppSettingsNotifier)
|
||||
|
@@ -1,11 +1,9 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:flutter_langdetect/flutter_langdetect.dart' as langdetect;
|
||||
|
||||
part 'translate.freezed.dart';
|
||||
part 'translate.g.dart';
|
||||
@@ -29,10 +27,17 @@ Future<String> translateString(Ref ref, TranslateQuery query) async {
|
||||
|
||||
@riverpod
|
||||
String? detectStringLanguage(Ref ref, String text) {
|
||||
try {
|
||||
return langdetect.detectLangs(text).firstOrNull?.lang;
|
||||
} catch (err) {
|
||||
log('[Language] Unable to detect text\'s language: $text');
|
||||
return null;
|
||||
bool isChinese(String text) {
|
||||
final chineseRegex = RegExp(r'[\u4e00-\u9fff]');
|
||||
return chineseRegex.hasMatch(text);
|
||||
}
|
||||
|
||||
bool isEnglish(String text) {
|
||||
final englishRegex = RegExp(r'[a-zA-Z]');
|
||||
return englishRegex.hasMatch(text) && !isChinese(text);
|
||||
}
|
||||
|
||||
if (isChinese(text)) return "zh";
|
||||
if (isEnglish(text)) return "en";
|
||||
return null;
|
||||
}
|
||||
|
@@ -149,7 +149,7 @@ class _TranslateStringProviderElement
|
||||
}
|
||||
|
||||
String _$detectStringLanguageHash() =>
|
||||
r'697b68464b3d00927cc43ccc1ba8ba93f2a470ed';
|
||||
r'24fbf52edbbffcc8dc4f09f7206f82d69728e703';
|
||||
|
||||
/// See also [detectStringLanguage].
|
||||
@ProviderFor(detectStringLanguage)
|
||||
|
@@ -38,6 +38,7 @@ import 'package:island/screens/chat/chat.dart';
|
||||
import 'package:island/screens/chat/room.dart';
|
||||
import 'package:island/screens/chat/room_detail.dart';
|
||||
import 'package:island/screens/chat/call.dart';
|
||||
import 'package:island/screens/chat/search_messages_screen.dart';
|
||||
import 'package:island/screens/creators/hub.dart';
|
||||
import 'package:island/screens/creators/posts/post_manage_list.dart';
|
||||
import 'package:island/screens/creators/stickers/stickers.dart';
|
||||
@@ -555,6 +556,14 @@ final routerProvider = Provider<GoRouter>((ref) {
|
||||
return ChatDetailScreen(id: id);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
name: 'searchMessages',
|
||||
path: '/chat/:id/search',
|
||||
builder: (context, state) {
|
||||
final id = state.pathParameters['id']!;
|
||||
return SearchMessagesScreen(roomId: id);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
|
@@ -68,6 +68,7 @@ class AccountScreen extends HookConsumerWidget {
|
||||
body: SingleChildScrollView(
|
||||
padding: getTabbedPadding(context),
|
||||
child: Column(
|
||||
spacing: 4,
|
||||
children: <Widget>[
|
||||
Card(
|
||||
child: Column(
|
||||
@@ -112,20 +113,22 @@ class AccountScreen extends HookConsumerWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||
textBaseline: TextBaseline.alphabetic,
|
||||
children: [
|
||||
AccountName(
|
||||
account: user.value!,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
Flexible(
|
||||
child: AccountName(
|
||||
account: user.value!,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
Text('@${user.value!.name}'),
|
||||
Flexible(child: Text('@${user.value!.name}')),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
(user.value!.profile.bio.isNotEmpty)
|
||||
? user.value!.profile.bio
|
||||
: 'No description yet.',
|
||||
: 'descriptionNone'.tr(),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
@@ -158,8 +161,16 @@ class AccountScreen extends HookConsumerWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(Symbols.draw, size: 28).padding(bottom: 8),
|
||||
Text('creatorHub').tr().fontSize(16).bold(),
|
||||
Text('creatorHubDescription').tr(),
|
||||
Text(
|
||||
'creatorHub',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
).tr().fontSize(16).bold(),
|
||||
Text(
|
||||
'creatorHubDescription',
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
).tr(),
|
||||
],
|
||||
).padding(horizontal: 16, vertical: 12),
|
||||
onTap: () {
|
||||
@@ -176,8 +187,16 @@ class AccountScreen extends HookConsumerWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(Symbols.code, size: 28).padding(bottom: 8),
|
||||
Text('developerPortal').tr().fontSize(16).bold(),
|
||||
Text('developerPortalDescription').tr(),
|
||||
Text(
|
||||
'developerPortal',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
).tr().fontSize(16).bold(),
|
||||
Text(
|
||||
'developerPortalDescription',
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
).tr(),
|
||||
],
|
||||
).padding(horizontal: 16, vertical: 12),
|
||||
onTap: () {
|
||||
|
@@ -95,8 +95,24 @@ class LevelingScreen extends HookConsumerWidget {
|
||||
title: Text('levelingProgress'.tr()),
|
||||
bottom: TabBar(
|
||||
tabs: [
|
||||
Tab(text: 'leveling'.tr()),
|
||||
Tab(text: 'stellarProgram'.tr()),
|
||||
Tab(
|
||||
child: Text(
|
||||
'leveling'.tr(),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
||||
),
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
child: Text(
|
||||
'stellarProgram'.tr(),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@@ -14,7 +14,6 @@ import 'package:island/screens/account/me/settings_connections.dart';
|
||||
import 'package:island/screens/account/me/settings_contacts.dart';
|
||||
import 'package:island/screens/auth/captcha.dart';
|
||||
import 'package:island/screens/auth/login.dart';
|
||||
import 'package:island/services/responsive.dart';
|
||||
import 'package:island/widgets/account/account_devices.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
@@ -57,7 +56,6 @@ class AccountSettingsScreen extends HookConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final isDesktop =
|
||||
!kIsWeb && (Platform.isWindows || Platform.isMacOS || Platform.isLinux);
|
||||
final isWide = isWideScreen(context);
|
||||
|
||||
Future<void> requestAccountDeletion() async {
|
||||
final confirm = await showConfirmAlert(
|
||||
@@ -440,51 +438,19 @@ class AccountSettingsScreen extends HookConsumerWidget {
|
||||
|
||||
// Create a responsive layout based on screen width
|
||||
Widget buildSettingsList() {
|
||||
if (isWide) {
|
||||
// Two-column layout for wide screens
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_SettingsSection(
|
||||
title: 'accountSecurityTitle',
|
||||
children: securitySettings,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_SettingsSection(
|
||||
title: 'accountDangerZoneTitle',
|
||||
children: dangerZoneSettings,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
// Single column layout for narrow screens
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_SettingsSection(
|
||||
title: 'accountSecurityTitle',
|
||||
children: securitySettings,
|
||||
),
|
||||
_SettingsSection(
|
||||
title: 'accountDangerZoneTitle',
|
||||
children: dangerZoneSettings,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_SettingsSection(
|
||||
title: 'accountSecurityTitle',
|
||||
children: securitySettings,
|
||||
),
|
||||
_SettingsSection(
|
||||
title: 'accountDangerZoneTitle',
|
||||
children: dangerZoneSettings,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return AppScaffold(
|
||||
|
@@ -1,14 +1,16 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/auth.dart';
|
||||
import 'package:island/pods/config.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/screens/account/me/account_settings.dart';
|
||||
import 'package:island/screens/auth/oidc.native.dart';
|
||||
import 'package:island/services/text.dart';
|
||||
import 'package:island/utils/text.dart';
|
||||
import 'package:island/services/time.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/widgets/content/sheet.dart';
|
||||
@@ -16,6 +18,7 @@ import 'package:island/widgets/response.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
// Helper function to get provider icon and localized name
|
||||
Widget getProviderIcon(String provider, {double size = 24, Color? color}) {
|
||||
@@ -165,9 +168,7 @@ class AccountConnectionNewSheet extends HookConsumerWidget {
|
||||
scopes: [AppleIDAuthorizationScopes.email],
|
||||
webAuthenticationOptions: WebAuthenticationOptions(
|
||||
clientId: 'dev.solsynth.solarpass',
|
||||
redirectUri: Uri.parse(
|
||||
'https://id.solian.app/auth/callback/apple',
|
||||
),
|
||||
redirectUri: Uri.parse('https://id.solian.app/auth/callback'),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -195,17 +196,25 @@ class AccountConnectionNewSheet extends HookConsumerWidget {
|
||||
case 'github':
|
||||
case 'discord':
|
||||
case 'afdian':
|
||||
await Navigator.of(context, rootNavigator: true).push(
|
||||
MaterialPageRoute(
|
||||
builder:
|
||||
(context) => OidcScreen(
|
||||
provider: selectedProvider.value.toLowerCase(),
|
||||
title:
|
||||
'Connect with ${selectedProvider.value.capitalizeEachWord()}',
|
||||
),
|
||||
),
|
||||
);
|
||||
if (context.mounted) Navigator.pop(context, true);
|
||||
if (kIsWeb) {
|
||||
final serverUrl = ref.watch(serverUrlProvider);
|
||||
final accessToken = ref.watch(tokenProvider);
|
||||
launchUrlString(
|
||||
'$serverUrl/id/auth/login/${selectedProvider.value}?tk=${accessToken!.token}',
|
||||
);
|
||||
} else {
|
||||
await Navigator.of(context, rootNavigator: true).push(
|
||||
MaterialPageRoute(
|
||||
builder:
|
||||
(context) => OidcScreen(
|
||||
provider: selectedProvider.value.toLowerCase(),
|
||||
title:
|
||||
'Connect with ${selectedProvider.value.capitalizeEachWord()}',
|
||||
),
|
||||
),
|
||||
);
|
||||
if (context.mounted) Navigator.pop(context, true);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
showSnackBar('accountConnectionAddError'.tr());
|
||||
|
@@ -2,6 +2,7 @@ import 'package:dio/dio.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
@@ -16,7 +17,7 @@ import 'package:island/pods/network.dart';
|
||||
import 'package:island/pods/userinfo.dart';
|
||||
import 'package:island/services/color.dart';
|
||||
import 'package:island/services/responsive.dart';
|
||||
import 'package:island/services/text.dart';
|
||||
import 'package:island/utils/text.dart';
|
||||
import 'package:island/services/time.dart';
|
||||
import 'package:island/services/timezone/native.dart';
|
||||
import 'package:island/widgets/account/account_name.dart';
|
||||
@@ -279,6 +280,36 @@ class AccountProfileScreen extends HookConsumerWidget {
|
||||
if (data.profile.lastName.isNotEmpty) Text(data.profile.lastName),
|
||||
],
|
||||
),
|
||||
Tooltip(
|
||||
message: 'creditsStatus'.tr(),
|
||||
child: Row(
|
||||
spacing: 6,
|
||||
children: [
|
||||
Icon(Symbols.star, size: 17, fill: 1).padding(right: 2),
|
||||
Text('${data.profile.socialCredits.toStringAsFixed(2)} pts'),
|
||||
Text('·').bold(),
|
||||
switch (data.profile.socialCreditsLevel) {
|
||||
-1 => Text('socialCreditsLevelPoor').tr(),
|
||||
0 => Text('socialCreditsLevelNormal').tr(),
|
||||
1 => Text('socialCreditsLevelGood').tr(),
|
||||
2 => Text('socialCreditsLevelExcellent').tr(),
|
||||
_ => Text('unknown').tr(),
|
||||
},
|
||||
],
|
||||
),
|
||||
),
|
||||
InkWell(
|
||||
child: Row(
|
||||
spacing: 6,
|
||||
children: [
|
||||
Icon(Symbols.fingerprint, size: 17, fill: 1).padding(right: 2),
|
||||
Text(data.id),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
Clipboard.setData(ClipboardData(text: data.id));
|
||||
},
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
|
@@ -4,6 +4,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/pods/userinfo.dart';
|
||||
import 'package:island/widgets/account/account_pfc.dart';
|
||||
import 'package:island/widgets/account/account_picker.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
@@ -99,7 +100,10 @@ class RelationshipListTile extends StatelessWidget {
|
||||
|
||||
return ListTile(
|
||||
contentPadding: const EdgeInsets.only(left: 16, right: 12),
|
||||
leading: ProfilePictureWidget(fileId: account.profile.picture?.id),
|
||||
leading: AccountPfcGestureDetector(
|
||||
uname: account.name,
|
||||
child: ProfilePictureWidget(fileId: account.profile.picture?.id),
|
||||
),
|
||||
title: Row(
|
||||
spacing: 6,
|
||||
children: [
|
||||
|
@@ -700,45 +700,48 @@ class _LoginLookupScreen extends HookConsumerWidget {
|
||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
onSubmitted: isBusy.value ? null : (_) => performNewTicket(),
|
||||
).padding(horizontal: 7),
|
||||
Row(
|
||||
spacing: 6,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Text("loginOr").tr().fontSize(11).opacity(0.85),
|
||||
const Gap(8),
|
||||
Spacer(),
|
||||
IconButton.filledTonal(
|
||||
onPressed: () => withOidc('github'),
|
||||
padding: EdgeInsets.zero,
|
||||
icon: getProviderIcon(
|
||||
"github",
|
||||
size: 16,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
if (!kIsWeb)
|
||||
Row(
|
||||
spacing: 6,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Text("loginOr").tr().fontSize(11).opacity(0.85),
|
||||
const Gap(8),
|
||||
Spacer(),
|
||||
IconButton.filledTonal(
|
||||
onPressed: () => withOidc('github'),
|
||||
padding: EdgeInsets.zero,
|
||||
icon: getProviderIcon(
|
||||
"github",
|
||||
size: 16,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
),
|
||||
tooltip: 'GitHub',
|
||||
),
|
||||
tooltip: 'GitHub',
|
||||
),
|
||||
IconButton.filledTonal(
|
||||
onPressed: () => withOidc('google'),
|
||||
padding: EdgeInsets.zero,
|
||||
icon: getProviderIcon(
|
||||
"google",
|
||||
size: 16,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
IconButton.filledTonal(
|
||||
onPressed: () => withOidc('google'),
|
||||
padding: EdgeInsets.zero,
|
||||
icon: getProviderIcon(
|
||||
"google",
|
||||
size: 16,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
),
|
||||
tooltip: 'Google',
|
||||
),
|
||||
tooltip: 'Google',
|
||||
),
|
||||
IconButton.filledTonal(
|
||||
onPressed: withApple,
|
||||
padding: EdgeInsets.zero,
|
||||
icon: getProviderIcon(
|
||||
"apple",
|
||||
size: 16,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
IconButton.filledTonal(
|
||||
onPressed: withApple,
|
||||
padding: EdgeInsets.zero,
|
||||
icon: getProviderIcon(
|
||||
"apple",
|
||||
size: 16,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
),
|
||||
tooltip: 'Apple Account',
|
||||
),
|
||||
tooltip: 'Apple Account',
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 8, vertical: 8),
|
||||
],
|
||||
).padding(horizontal: 8, vertical: 8)
|
||||
else
|
||||
const Gap(12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
|
@@ -4,6 +4,7 @@ import "dart:developer" as developer;
|
||||
import "dart:io";
|
||||
import "package:dio/dio.dart";
|
||||
import "package:easy_localization/easy_localization.dart";
|
||||
import "package:file_picker/file_picker.dart";
|
||||
import "package:flutter/foundation.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:go_router/go_router.dart";
|
||||
@@ -72,6 +73,207 @@ class _AppLifecycleObserver extends WidgetsBindingObserver {
|
||||
}
|
||||
}
|
||||
|
||||
class _PublicRoomPreview extends HookConsumerWidget {
|
||||
final String id;
|
||||
final SnChatRoom room;
|
||||
|
||||
const _PublicRoomPreview({required this.id, required this.room});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final messages = ref.watch(messagesNotifierProvider(id));
|
||||
final messagesNotifier = ref.read(messagesNotifierProvider(id).notifier);
|
||||
final scrollController = useScrollController();
|
||||
|
||||
final listController = useMemoized(() => ListController(), []);
|
||||
|
||||
var isLoading = false;
|
||||
|
||||
// Add scroll listener for pagination
|
||||
useEffect(() {
|
||||
void onScroll() {
|
||||
if (scrollController.position.pixels >=
|
||||
scrollController.position.maxScrollExtent - 200) {
|
||||
if (isLoading) return;
|
||||
isLoading = true;
|
||||
messagesNotifier.loadMore().then((_) => isLoading = false);
|
||||
}
|
||||
}
|
||||
|
||||
scrollController.addListener(onScroll);
|
||||
return () => scrollController.removeListener(onScroll);
|
||||
}, [scrollController]);
|
||||
|
||||
Widget chatMessageListWidget(List<LocalChatMessage> messageList) =>
|
||||
SuperListView.builder(
|
||||
listController: listController,
|
||||
padding: EdgeInsets.symmetric(vertical: 16),
|
||||
controller: scrollController,
|
||||
reverse: true, // Show newest messages at the bottom
|
||||
itemCount: messageList.length,
|
||||
findChildIndexCallback: (key) {
|
||||
final valueKey = key as ValueKey;
|
||||
final messageId = valueKey.value as String;
|
||||
return messageList.indexWhere((m) => m.id == messageId);
|
||||
},
|
||||
extentEstimation: (_, _) => 40,
|
||||
itemBuilder: (context, index) {
|
||||
final message = messageList[index];
|
||||
final nextMessage =
|
||||
index < messageList.length - 1 ? messageList[index + 1] : null;
|
||||
final isLastInGroup =
|
||||
nextMessage == null ||
|
||||
nextMessage.senderId != message.senderId ||
|
||||
nextMessage.createdAt
|
||||
.difference(message.createdAt)
|
||||
.inMinutes
|
||||
.abs() >
|
||||
3;
|
||||
|
||||
return MessageItem(
|
||||
message: message,
|
||||
isCurrentUser: false, // User is not a member, so not current user
|
||||
onAction: null, // No actions allowed in preview mode
|
||||
onJump: (_) {}, // No jump functionality in preview
|
||||
progress: null,
|
||||
showAvatar: isLastInGroup,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
final compactHeader = isWideScreen(context);
|
||||
|
||||
Widget comfortHeaderWidget() => Column(
|
||||
spacing: 4,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 26,
|
||||
width: 26,
|
||||
child:
|
||||
(room.type == 1 && room.picture?.id == null)
|
||||
? SplitAvatarWidget(
|
||||
filesId:
|
||||
room.members!
|
||||
.map((e) => e.account.profile.picture?.id)
|
||||
.toList(),
|
||||
)
|
||||
: room.picture?.id != null
|
||||
? ProfilePictureWidget(
|
||||
fileId: room.picture?.id,
|
||||
fallbackIcon: Symbols.chat,
|
||||
)
|
||||
: CircleAvatar(
|
||||
child: Text(
|
||||
room.name![0].toUpperCase(),
|
||||
style: const TextStyle(fontSize: 12),
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
(room.type == 1 && room.name == null)
|
||||
? room.members!.map((e) => e.account.nick).join(', ')
|
||||
: room.name!,
|
||||
).fontSize(15),
|
||||
],
|
||||
);
|
||||
|
||||
Widget compactHeaderWidget() => Row(
|
||||
spacing: 8,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 26,
|
||||
width: 26,
|
||||
child:
|
||||
(room.type == 1 && room.picture?.id == null)
|
||||
? SplitAvatarWidget(
|
||||
filesId:
|
||||
room.members!
|
||||
.map((e) => e.account.profile.picture?.id)
|
||||
.toList(),
|
||||
)
|
||||
: room.picture?.id != null
|
||||
? ProfilePictureWidget(
|
||||
fileId: room.picture?.id,
|
||||
fallbackIcon: Symbols.chat,
|
||||
)
|
||||
: CircleAvatar(
|
||||
child: Text(
|
||||
room.name![0].toUpperCase(),
|
||||
style: const TextStyle(fontSize: 12),
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
(room.type == 1 && room.name == null)
|
||||
? room.members!.map((e) => e.account.nick).join(', ')
|
||||
: room.name!,
|
||||
).fontSize(19),
|
||||
],
|
||||
);
|
||||
|
||||
return AppScaffold(
|
||||
appBar: AppBar(
|
||||
leading: !compactHeader ? const Center(child: PageBackButton()) : null,
|
||||
automaticallyImplyLeading: false,
|
||||
toolbarHeight: compactHeader ? null : 64,
|
||||
title: compactHeader ? compactHeaderWidget() : comfortHeaderWidget(),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.more_vert),
|
||||
onPressed: () {
|
||||
context.pushNamed('chatDetail', pathParameters: {'id': id});
|
||||
},
|
||||
),
|
||||
const Gap(8),
|
||||
],
|
||||
),
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(
|
||||
child: messages.when(
|
||||
data:
|
||||
(messageList) =>
|
||||
messageList.isEmpty
|
||||
? Center(child: Text('No messages yet'.tr()))
|
||||
: chatMessageListWidget(messageList),
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error:
|
||||
(error, _) => ResponseErrorWidget(
|
||||
error: error,
|
||||
onRetry: () => messagesNotifier.loadInitial(),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Join button at the bottom for public rooms
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: FilledButton.tonalIcon(
|
||||
onPressed: () async {
|
||||
try {
|
||||
showLoadingModal(context);
|
||||
final apiClient = ref.read(apiClientProvider);
|
||||
await apiClient.post('/sphere/chat/${room.id}/members/me');
|
||||
ref.invalidate(chatroomIdentityProvider(id));
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
} finally {
|
||||
if (context.mounted) hideLoadingModal(context);
|
||||
}
|
||||
},
|
||||
label: Text('chatJoin').tr(),
|
||||
icon: const Icon(Icons.add),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class MessagesNotifier extends _$MessagesNotifier {
|
||||
late final Dio _apiClient;
|
||||
@@ -82,6 +284,9 @@ class MessagesNotifier extends _$MessagesNotifier {
|
||||
final Map<String, LocalChatMessage> _pendingMessages = {};
|
||||
final Map<String, Map<int, double>> _fileUploadProgress = {};
|
||||
int? _totalCount;
|
||||
String? _searchQuery;
|
||||
bool? _withLinks;
|
||||
bool? _withAttachments;
|
||||
|
||||
late final String _roomId;
|
||||
int _currentPage = 0;
|
||||
@@ -96,28 +301,42 @@ class MessagesNotifier extends _$MessagesNotifier {
|
||||
_database = ref.watch(databaseProvider);
|
||||
final room = await ref.watch(chatroomProvider(roomId).future);
|
||||
final identity = await ref.watch(chatroomIdentityProvider(roomId).future);
|
||||
if (room == null || identity == null) {
|
||||
throw Exception('Room or identity not found');
|
||||
|
||||
if (room == null) {
|
||||
throw Exception('Room not found');
|
||||
}
|
||||
_room = room;
|
||||
_identity = identity;
|
||||
|
||||
// Allow building even if identity is null for public rooms
|
||||
if (identity != null) {
|
||||
_identity = identity;
|
||||
}
|
||||
|
||||
developer.log(
|
||||
'MessagesNotifier built for room $roomId',
|
||||
name: 'MessagesNotifier',
|
||||
);
|
||||
|
||||
ref.listen(appLifecycleStateProvider, (_, next) {
|
||||
if (next.hasValue && next.value == AppLifecycleState.resumed) {
|
||||
developer.log(
|
||||
'App resumed, syncing messages',
|
||||
name: 'MessagesNotifier',
|
||||
);
|
||||
syncMessages();
|
||||
}
|
||||
});
|
||||
// Only setup sync and lifecycle listeners if user is a member
|
||||
if (identity != null) {
|
||||
ref.listen(appLifecycleStateProvider, (_, next) {
|
||||
if (next.hasValue && next.value == AppLifecycleState.resumed) {
|
||||
developer.log(
|
||||
'App resumed, syncing messages',
|
||||
name: 'MessagesNotifier',
|
||||
);
|
||||
syncMessages();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return await loadInitial();
|
||||
loadInitial();
|
||||
return [];
|
||||
}
|
||||
|
||||
List<LocalChatMessage> _sortMessages(List<LocalChatMessage> messages) {
|
||||
messages.sort((a, b) => b.createdAt.compareTo(a.createdAt));
|
||||
return messages;
|
||||
}
|
||||
|
||||
Future<List<LocalChatMessage>> _getCachedMessages({
|
||||
@@ -128,13 +347,32 @@ class MessagesNotifier extends _$MessagesNotifier {
|
||||
'Getting cached messages from offset $offset, take $take',
|
||||
name: 'MessagesNotifier',
|
||||
);
|
||||
final dbMessages = await _database.getMessagesForRoom(
|
||||
_roomId,
|
||||
offset: offset,
|
||||
limit: take,
|
||||
);
|
||||
final dbLocalMessages =
|
||||
dbMessages.map(_database.companionToMessage).toList();
|
||||
final List<LocalChatMessage> dbMessages;
|
||||
if (_searchQuery != null && _searchQuery!.isNotEmpty) {
|
||||
dbMessages = await _database.searchMessages(_roomId, _searchQuery ?? '');
|
||||
} else {
|
||||
final chatMessagesFromDb = await _database.getMessagesForRoom(
|
||||
_roomId,
|
||||
offset: offset,
|
||||
limit: take,
|
||||
);
|
||||
dbMessages =
|
||||
chatMessagesFromDb.map(_database.companionToMessage).toList();
|
||||
}
|
||||
|
||||
List<LocalChatMessage> filteredMessages = dbMessages;
|
||||
|
||||
if (_withLinks == true) {
|
||||
filteredMessages =
|
||||
filteredMessages.where((msg) => _hasLink(msg)).toList();
|
||||
}
|
||||
|
||||
if (_withAttachments == true) {
|
||||
filteredMessages =
|
||||
filteredMessages.where((msg) => _hasAttachment(msg)).toList();
|
||||
}
|
||||
|
||||
final dbLocalMessages = filteredMessages;
|
||||
|
||||
if (offset == 0) {
|
||||
final pendingForRoom =
|
||||
@@ -143,7 +381,7 @@ class MessagesNotifier extends _$MessagesNotifier {
|
||||
.toList();
|
||||
|
||||
final allMessages = [...pendingForRoom, ...dbLocalMessages];
|
||||
allMessages.sort((a, b) => b.createdAt.compareTo(a.createdAt));
|
||||
_sortMessages(allMessages); // Use the helper function
|
||||
|
||||
final uniqueMessages = <LocalChatMessage>[];
|
||||
final seenIds = <String>{};
|
||||
@@ -218,7 +456,7 @@ class MessagesNotifier extends _$MessagesNotifier {
|
||||
_isSyncing = true;
|
||||
|
||||
developer.log('Starting message sync', name: 'MessagesNotifier');
|
||||
ref.read(isSyncingProvider.notifier).state = true;
|
||||
Future.microtask(() => ref.read(isSyncingProvider.notifier).state = true);
|
||||
try {
|
||||
final dbMessages = await _database.getMessagesForRoom(
|
||||
_room.id,
|
||||
@@ -279,7 +517,9 @@ class MessagesNotifier extends _$MessagesNotifier {
|
||||
showErrorAlert(err);
|
||||
} finally {
|
||||
developer.log('Finished message sync', name: 'MessagesNotifier');
|
||||
ref.read(isSyncingProvider.notifier).state = false;
|
||||
Future.microtask(
|
||||
() => ref.read(isSyncingProvider.notifier).state = false,
|
||||
);
|
||||
_isSyncing = false;
|
||||
}
|
||||
}
|
||||
@@ -290,7 +530,9 @@ class MessagesNotifier extends _$MessagesNotifier {
|
||||
bool synced = false,
|
||||
}) async {
|
||||
try {
|
||||
if (offset == 0 && !synced) {
|
||||
if (offset == 0 &&
|
||||
!synced &&
|
||||
(_searchQuery == null || _searchQuery!.isEmpty)) {
|
||||
_fetchAndCacheMessages(offset: 0, take: take).catchError((_) {
|
||||
return <LocalChatMessage>[];
|
||||
});
|
||||
@@ -305,7 +547,11 @@ class MessagesNotifier extends _$MessagesNotifier {
|
||||
return localMessages;
|
||||
}
|
||||
|
||||
return await _fetchAndCacheMessages(offset: offset, take: take);
|
||||
if (_searchQuery == null || _searchQuery!.isEmpty) {
|
||||
return await _fetchAndCacheMessages(offset: offset, take: take);
|
||||
} else {
|
||||
return []; // If searching, and no local messages, don't fetch from network
|
||||
}
|
||||
} catch (e) {
|
||||
final localMessages = await _getCachedMessages(
|
||||
offset: offset,
|
||||
@@ -319,13 +565,15 @@ class MessagesNotifier extends _$MessagesNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<LocalChatMessage>> loadInitial() async {
|
||||
Future<void> loadInitial() async {
|
||||
developer.log('Loading initial messages', name: 'MessagesNotifier');
|
||||
syncMessages();
|
||||
if (_searchQuery == null || _searchQuery!.isEmpty) {
|
||||
syncMessages();
|
||||
}
|
||||
final messages = await _getCachedMessages(offset: 0, take: 100);
|
||||
_currentPage = 0;
|
||||
_hasMore = messages.length == _pageSize;
|
||||
return messages;
|
||||
state = AsyncValue.data(messages);
|
||||
}
|
||||
|
||||
Future<void> loadMore() async {
|
||||
@@ -344,7 +592,9 @@ class MessagesNotifier extends _$MessagesNotifier {
|
||||
_hasMore = false;
|
||||
}
|
||||
|
||||
state = AsyncValue.data([...currentMessages, ...newMessages]);
|
||||
state = AsyncValue.data(
|
||||
_sortMessages([...currentMessages, ...newMessages]),
|
||||
);
|
||||
} catch (err, stackTrace) {
|
||||
developer.log(
|
||||
'Error loading more messages',
|
||||
@@ -455,10 +705,13 @@ class MessagesNotifier extends _$MessagesNotifier {
|
||||
|
||||
final currentMessages = state.value ?? [];
|
||||
if (editingTo != null) {
|
||||
final newMessages = currentMessages
|
||||
.where((m) => m.id != localMessage.id) // remove pending message
|
||||
.map((m) => m.id == editingTo.id ? updatedMessage : m) // update original message
|
||||
.toList();
|
||||
final newMessages =
|
||||
currentMessages
|
||||
.where((m) => m.id != localMessage.id) // remove pending message
|
||||
.map(
|
||||
(m) => m.id == editingTo.id ? updatedMessage : m,
|
||||
) // update original message
|
||||
.toList();
|
||||
state = AsyncValue.data(newMessages);
|
||||
} else {
|
||||
final newMessages =
|
||||
@@ -566,7 +819,7 @@ class MessagesNotifier extends _$MessagesNotifier {
|
||||
}
|
||||
return m;
|
||||
}).toList();
|
||||
state = AsyncValue.data(newMessages);
|
||||
state = AsyncValue.data(_sortMessages(newMessages));
|
||||
showErrorAlert(e);
|
||||
}
|
||||
}
|
||||
@@ -626,7 +879,7 @@ class MessagesNotifier extends _$MessagesNotifier {
|
||||
if (index >= 0) {
|
||||
final newList = [...currentMessages];
|
||||
newList[index] = updatedMessage;
|
||||
state = AsyncValue.data(newList);
|
||||
state = AsyncValue.data(_sortMessages(newList));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -686,6 +939,20 @@ class MessagesNotifier extends _$MessagesNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
void searchMessages(String query, {bool? withLinks, bool? withAttachments}) {
|
||||
_searchQuery = query.trim();
|
||||
_withLinks = withLinks;
|
||||
_withAttachments = withAttachments;
|
||||
loadInitial();
|
||||
}
|
||||
|
||||
void clearSearch() {
|
||||
_searchQuery = null;
|
||||
_withLinks = null;
|
||||
_withAttachments = null;
|
||||
loadInitial();
|
||||
}
|
||||
|
||||
Future<LocalChatMessage?> fetchMessageById(String messageId) async {
|
||||
developer.log(
|
||||
'Fetching message by id $messageId',
|
||||
@@ -715,6 +982,18 @@ class MessagesNotifier extends _$MessagesNotifier {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
bool _hasLink(LocalChatMessage message) {
|
||||
final content = message.toRemoteMessage().content;
|
||||
if (content == null) return false;
|
||||
final urlRegex = RegExp(r'https?://[^\s/$.?#].[^\s]*');
|
||||
return urlRegex.hasMatch(content);
|
||||
}
|
||||
|
||||
bool _hasAttachment(LocalChatMessage message) {
|
||||
final remoteMessage = message.toRemoteMessage();
|
||||
return remoteMessage.attachments.isNotEmpty;
|
||||
}
|
||||
}
|
||||
|
||||
class ChatRoomScreen extends HookConsumerWidget {
|
||||
@@ -734,57 +1013,77 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
);
|
||||
} else if (chatIdentity.value == null) {
|
||||
// Identity was not found, user was not joined
|
||||
return AppScaffold(
|
||||
appBar: AppBar(leading: const PageBackButton()),
|
||||
body: Center(
|
||||
child:
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 280),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
chatRoom.value?.isCommunity == true
|
||||
? Symbols.person_add
|
||||
: Symbols.person_remove,
|
||||
size: 36,
|
||||
fill: 1,
|
||||
).padding(bottom: 4),
|
||||
Text('chatNotJoined').tr(),
|
||||
if (chatRoom.value?.isCommunity != true)
|
||||
Text(
|
||||
'chatUnableJoin',
|
||||
textAlign: TextAlign.center,
|
||||
).tr().bold()
|
||||
else
|
||||
FilledButton.tonalIcon(
|
||||
onPressed: () async {
|
||||
try {
|
||||
showLoadingModal(context);
|
||||
final apiClient = ref.read(apiClientProvider);
|
||||
if (chatRoom.value == null) {
|
||||
hideLoadingModal(context);
|
||||
return;
|
||||
}
|
||||
|
||||
await apiClient.post(
|
||||
'/sphere/chat/${chatRoom.value!.id}/members/me',
|
||||
);
|
||||
ref.invalidate(chatroomIdentityProvider(id));
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
} finally {
|
||||
if (context.mounted) hideLoadingModal(context);
|
||||
}
|
||||
},
|
||||
label: Text('chatJoin').tr(),
|
||||
icon: const Icon(Icons.add),
|
||||
).padding(top: 8),
|
||||
],
|
||||
),
|
||||
).center(),
|
||||
),
|
||||
return chatRoom.when(
|
||||
data: (room) {
|
||||
if (room!.isPublic) {
|
||||
// Show public room preview with messages but no input
|
||||
return _PublicRoomPreview(id: id, room: room);
|
||||
} else {
|
||||
// Show regular "not joined" screen for private rooms
|
||||
return AppScaffold(
|
||||
appBar: AppBar(leading: const PageBackButton()),
|
||||
body: Center(
|
||||
child:
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 280),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
room.isCommunity == true
|
||||
? Symbols.person_add
|
||||
: Symbols.person_remove,
|
||||
size: 36,
|
||||
fill: 1,
|
||||
).padding(bottom: 4),
|
||||
Text('chatNotJoined').tr(),
|
||||
if (room.isCommunity != true)
|
||||
Text(
|
||||
'chatUnableJoin',
|
||||
textAlign: TextAlign.center,
|
||||
).tr().bold()
|
||||
else
|
||||
FilledButton.tonalIcon(
|
||||
onPressed: () async {
|
||||
try {
|
||||
showLoadingModal(context);
|
||||
final apiClient = ref.read(apiClientProvider);
|
||||
await apiClient.post(
|
||||
'/sphere/chat/${room.id}/members/me',
|
||||
);
|
||||
ref.invalidate(chatroomIdentityProvider(id));
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
} finally {
|
||||
if (context.mounted) {
|
||||
hideLoadingModal(context);
|
||||
}
|
||||
}
|
||||
},
|
||||
label: Text('chatJoin').tr(),
|
||||
icon: const Icon(Icons.add),
|
||||
).padding(top: 8),
|
||||
],
|
||||
),
|
||||
).center(),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
loading:
|
||||
() => AppScaffold(
|
||||
appBar: AppBar(leading: const PageBackButton()),
|
||||
body: CircularProgressIndicator().center(),
|
||||
),
|
||||
error:
|
||||
(error, _) => AppScaffold(
|
||||
appBar: AppBar(leading: const PageBackButton()),
|
||||
body: ResponseErrorWidget(
|
||||
error: error,
|
||||
onRetry: () => ref.refresh(chatroomProvider(id)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -953,26 +1252,32 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
}, [id]);
|
||||
|
||||
Future<void> pickPhotoMedia() async {
|
||||
final result = await ref
|
||||
.watch(imagePickerProvider)
|
||||
.pickMultiImage(requestFullMetadata: true);
|
||||
if (result.isEmpty) return;
|
||||
final result = await FilePicker.platform.pickFiles(
|
||||
type: FileType.image,
|
||||
allowMultiple: true,
|
||||
allowCompression: false,
|
||||
);
|
||||
if (result == null || result.count == 0) return;
|
||||
attachments.value = [
|
||||
...attachments.value,
|
||||
...result.map(
|
||||
(e) => UniversalFile(data: e, type: UniversalFileType.image),
|
||||
...result.files.map(
|
||||
(e) => UniversalFile(data: e.xFile, type: UniversalFileType.image),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
Future<void> pickVideoMedia() async {
|
||||
final result = await ref
|
||||
.watch(imagePickerProvider)
|
||||
.pickVideo(source: ImageSource.gallery);
|
||||
if (result == null) return;
|
||||
final result = await FilePicker.platform.pickFiles(
|
||||
type: FileType.video,
|
||||
allowMultiple: true,
|
||||
allowCompression: false,
|
||||
);
|
||||
if (result == null || result.count == 0) return;
|
||||
attachments.value = [
|
||||
...attachments.value,
|
||||
UniversalFile(data: result, type: UniversalFileType.video),
|
||||
...result.files.map(
|
||||
(e) => UniversalFile(data: e.xFile, type: UniversalFileType.video),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1089,6 +1394,8 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
],
|
||||
);
|
||||
|
||||
const messageKeyPrefix = 'message-';
|
||||
|
||||
Widget chatMessageListWidget(List<LocalChatMessage> messageList) =>
|
||||
SuperListView.builder(
|
||||
listController: listController,
|
||||
@@ -1098,7 +1405,9 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
itemCount: messageList.length,
|
||||
findChildIndexCallback: (key) {
|
||||
final valueKey = key as ValueKey;
|
||||
final messageId = valueKey.value as String;
|
||||
final messageId = (valueKey.value as String).substring(
|
||||
messageKeyPrefix.length,
|
||||
);
|
||||
return messageList.indexWhere((m) => m.id == messageId);
|
||||
},
|
||||
extentEstimation: (_, _) => 40,
|
||||
@@ -1115,10 +1424,13 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
.abs() >
|
||||
3;
|
||||
|
||||
final key = ValueKey('$messageKeyPrefix${message.id}');
|
||||
|
||||
return chatIdentity.when(
|
||||
skipError: true,
|
||||
data:
|
||||
(identity) => MessageItem(
|
||||
key: key,
|
||||
message: message,
|
||||
isCurrentUser: identity?.id == message.senderId,
|
||||
onAction: (action) {
|
||||
@@ -1161,6 +1473,7 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
),
|
||||
loading:
|
||||
() => MessageItem(
|
||||
key: key,
|
||||
message: message,
|
||||
isCurrentUser: false,
|
||||
onAction: null,
|
||||
@@ -1168,7 +1481,7 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
showAvatar: false,
|
||||
onJump: (_) {},
|
||||
),
|
||||
error: (_, _) => const SizedBox.shrink(),
|
||||
error: (_, _) => SizedBox.shrink(key: key),
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -1549,7 +1862,7 @@ class _ChatInput extends HookConsumerWidget {
|
||||
children: [
|
||||
IconButton(
|
||||
tooltip: 'stickers'.tr(),
|
||||
icon: const Icon(Symbols.emoji_symbols),
|
||||
icon: const Icon(Symbols.add_reaction),
|
||||
onPressed: () {
|
||||
final size = MediaQuery.of(context).size;
|
||||
showStickerPickerPopover(
|
||||
@@ -1659,8 +1972,13 @@ class _ChatInput extends HookConsumerWidget {
|
||||
horizontal: 12,
|
||||
vertical: 4,
|
||||
),
|
||||
counterText:
|
||||
messageController.text.length > 1024
|
||||
? '${messageController.text.length}/4096'
|
||||
: null,
|
||||
),
|
||||
maxLines: null,
|
||||
maxLines: 3,
|
||||
minLines: 1,
|
||||
onTapOutside:
|
||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
|
@@ -6,7 +6,7 @@ part of 'room.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$messagesNotifierHash() => r'32afe6ea24086d869cc47bd3389c8fd734409ca0';
|
||||
String _$messagesNotifierHash() => r'fc3b66dfb8dd3fc55d142dae5c5e7bdc67eca5d4';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
@@ -9,6 +9,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/chat.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/screens/chat/chat.dart';
|
||||
import 'package:island/widgets/account/account_pfc.dart';
|
||||
import 'package:island/widgets/account/account_picker.dart';
|
||||
import 'package:island/widgets/account/status.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
@@ -19,10 +20,17 @@ import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:island/pods/database.dart';
|
||||
|
||||
part 'room_detail.freezed.dart';
|
||||
part 'room_detail.g.dart';
|
||||
|
||||
@riverpod
|
||||
Future<int> totalMessagesCount(Ref ref, String roomId) async {
|
||||
final database = ref.watch(databaseProvider);
|
||||
return database.getTotalMessagesForRoom(roomId);
|
||||
}
|
||||
|
||||
class ChatDetailScreen extends HookConsumerWidget {
|
||||
final String id;
|
||||
const ChatDetailScreen({super.key, required this.id});
|
||||
@@ -31,6 +39,7 @@ class ChatDetailScreen extends HookConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final roomState = ref.watch(chatroomProvider(id));
|
||||
final roomIdentity = ref.watch(chatroomIdentityProvider(id));
|
||||
final totalMessages = ref.watch(totalMessagesCountProvider(id));
|
||||
|
||||
const kNotifyLevelText = [
|
||||
'chatNotifyLevelAll',
|
||||
@@ -131,7 +140,7 @@ class ChatDetailScreen extends HookConsumerWidget {
|
||||
const Text('chatBreakDescription').tr(),
|
||||
const Gap(16),
|
||||
ListTile(
|
||||
title: const Text('Clear').tr(),
|
||||
title: const Text('chatBreakClearButton').tr(),
|
||||
subtitle: const Text('chatBreakClear').tr(),
|
||||
leading: const Icon(Icons.notifications_active),
|
||||
onTap: () {
|
||||
@@ -143,8 +152,10 @@ class ChatDetailScreen extends HookConsumerWidget {
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('5m'),
|
||||
subtitle: const Text('chatBreakHour').tr(args: ['5m']),
|
||||
title: const Text('chatBreak5m').tr(),
|
||||
subtitle: const Text(
|
||||
'chatBreakHour',
|
||||
).tr(args: ['chatBreak5m'.tr()]),
|
||||
leading: const Icon(Symbols.circle),
|
||||
onTap: () {
|
||||
setChatBreak(now.add(const Duration(minutes: 5)));
|
||||
@@ -155,8 +166,10 @@ class ChatDetailScreen extends HookConsumerWidget {
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('10m'),
|
||||
subtitle: const Text('chatBreakHour').tr(args: ['10m']),
|
||||
title: const Text('chatBreak10m').tr(),
|
||||
subtitle: const Text(
|
||||
'chatBreakHour',
|
||||
).tr(args: ['chatBreak10m'.tr()]),
|
||||
leading: const Icon(Symbols.circle),
|
||||
onTap: () {
|
||||
setChatBreak(now.add(const Duration(minutes: 10)));
|
||||
@@ -167,8 +180,10 @@ class ChatDetailScreen extends HookConsumerWidget {
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('15m'),
|
||||
subtitle: const Text('chatBreakHour').tr(args: ['15m']),
|
||||
title: const Text('chatBreak15m').tr(),
|
||||
subtitle: const Text(
|
||||
'chatBreakHour',
|
||||
).tr(args: ['chatBreak15m'.tr()]),
|
||||
leading: const Icon(Symbols.timer_3),
|
||||
onTap: () {
|
||||
setChatBreak(now.add(const Duration(minutes: 15)));
|
||||
@@ -179,8 +194,10 @@ class ChatDetailScreen extends HookConsumerWidget {
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('30m'),
|
||||
subtitle: const Text('chatBreakHour').tr(args: ['30m']),
|
||||
title: const Text('chatBreak30m').tr(),
|
||||
subtitle: const Text(
|
||||
'chatBreakHour',
|
||||
).tr(args: ['chatBreak30m'.tr()]),
|
||||
leading: const Icon(Symbols.timer),
|
||||
onTap: () {
|
||||
setChatBreak(now.add(const Duration(minutes: 30)));
|
||||
@@ -194,8 +211,8 @@ class ChatDetailScreen extends HookConsumerWidget {
|
||||
TextField(
|
||||
controller: durationController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Custom (minutes)'.tr(),
|
||||
hintText: 'Enter minutes'.tr(),
|
||||
labelText: 'chatBreakCustomMinutes'.tr(),
|
||||
hintText: 'chatBreakEnterMinutes'.tr(),
|
||||
border: const OutlineInputBorder(),
|
||||
suffixIcon: IconButton(
|
||||
icon: const Icon(Icons.check),
|
||||
@@ -238,7 +255,10 @@ class ChatDetailScreen extends HookConsumerWidget {
|
||||
return AppScaffold(
|
||||
body: roomState.when(
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (error, _) => Center(child: Text('Error: $error')),
|
||||
error:
|
||||
(error, _) => Center(
|
||||
child: Text('errorGeneric'.tr(args: [error.toString()])),
|
||||
),
|
||||
data:
|
||||
(currentRoom) => CustomScrollView(
|
||||
slivers: [
|
||||
@@ -358,6 +378,36 @@ class ChatDetailScreen extends HookConsumerWidget {
|
||||
: const Text('chatBreakNone').tr(),
|
||||
onTap: () => showChatBreakDialog(),
|
||||
),
|
||||
ListTile(
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
horizontal: 24,
|
||||
),
|
||||
leading: const Icon(Icons.search),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
title: const Text('searchMessages').tr(),
|
||||
subtitle: totalMessages.when(
|
||||
data:
|
||||
(count) => Text(
|
||||
'messagesCount'.tr(
|
||||
args: [count.toString()],
|
||||
),
|
||||
),
|
||||
loading:
|
||||
() => const CircularProgressIndicator(),
|
||||
error:
|
||||
(err, stack) => Text(
|
||||
'errorGeneric'.tr(
|
||||
args: [err.toString()],
|
||||
),
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
context.pushNamed(
|
||||
'searchMessages',
|
||||
pathParameters: {'id': id},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
error: (_, _) => const SizedBox.shrink(),
|
||||
@@ -666,8 +716,11 @@ class _ChatMemberListSheet extends HookConsumerWidget {
|
||||
final member = data.items[index];
|
||||
return ListTile(
|
||||
contentPadding: EdgeInsets.only(left: 16, right: 12),
|
||||
leading: ProfilePictureWidget(
|
||||
fileId: member.account.profile.picture?.id,
|
||||
leading: AccountPfcGestureDetector(
|
||||
uname: member.account.name,
|
||||
child: ProfilePictureWidget(
|
||||
fileId: member.account.profile.picture?.id,
|
||||
),
|
||||
),
|
||||
title: Row(
|
||||
spacing: 6,
|
||||
@@ -848,7 +901,7 @@ class _ChatMemberRoleSheet extends HookConsumerWidget {
|
||||
try {
|
||||
final newRole = int.parse(roleController.text);
|
||||
if (newRole < 0 || newRole > 100) {
|
||||
throw 'Role must be between 0 and 100';
|
||||
throw 'roleValidationHint'.tr();
|
||||
}
|
||||
|
||||
final apiClient = ref.read(apiClientProvider);
|
||||
|
@@ -6,8 +6,8 @@ part of 'room_detail.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$chatMemberListNotifierHash() =>
|
||||
r'3ea30150278523e9f6b23f9200ea9a9fbae9c973';
|
||||
String _$totalMessagesCountHash() =>
|
||||
r'd55f1507aba2acdce5e468c1c2e15dba7640c571';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
@@ -30,6 +30,128 @@ class _SystemHash {
|
||||
}
|
||||
}
|
||||
|
||||
/// See also [totalMessagesCount].
|
||||
@ProviderFor(totalMessagesCount)
|
||||
const totalMessagesCountProvider = TotalMessagesCountFamily();
|
||||
|
||||
/// See also [totalMessagesCount].
|
||||
class TotalMessagesCountFamily extends Family<AsyncValue<int>> {
|
||||
/// See also [totalMessagesCount].
|
||||
const TotalMessagesCountFamily();
|
||||
|
||||
/// See also [totalMessagesCount].
|
||||
TotalMessagesCountProvider call(String roomId) {
|
||||
return TotalMessagesCountProvider(roomId);
|
||||
}
|
||||
|
||||
@override
|
||||
TotalMessagesCountProvider getProviderOverride(
|
||||
covariant TotalMessagesCountProvider provider,
|
||||
) {
|
||||
return call(provider.roomId);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'totalMessagesCountProvider';
|
||||
}
|
||||
|
||||
/// See also [totalMessagesCount].
|
||||
class TotalMessagesCountProvider extends AutoDisposeFutureProvider<int> {
|
||||
/// See also [totalMessagesCount].
|
||||
TotalMessagesCountProvider(String roomId)
|
||||
: this._internal(
|
||||
(ref) => totalMessagesCount(ref as TotalMessagesCountRef, roomId),
|
||||
from: totalMessagesCountProvider,
|
||||
name: r'totalMessagesCountProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$totalMessagesCountHash,
|
||||
dependencies: TotalMessagesCountFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
TotalMessagesCountFamily._allTransitiveDependencies,
|
||||
roomId: roomId,
|
||||
);
|
||||
|
||||
TotalMessagesCountProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.roomId,
|
||||
}) : super.internal();
|
||||
|
||||
final String roomId;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
FutureOr<int> Function(TotalMessagesCountRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: TotalMessagesCountProvider._internal(
|
||||
(ref) => create(ref as TotalMessagesCountRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
roomId: roomId,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeFutureProviderElement<int> createElement() {
|
||||
return _TotalMessagesCountProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is TotalMessagesCountProvider && other.roomId == roomId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, roomId.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin TotalMessagesCountRef on AutoDisposeFutureProviderRef<int> {
|
||||
/// The parameter `roomId` of this provider.
|
||||
String get roomId;
|
||||
}
|
||||
|
||||
class _TotalMessagesCountProviderElement
|
||||
extends AutoDisposeFutureProviderElement<int>
|
||||
with TotalMessagesCountRef {
|
||||
_TotalMessagesCountProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String get roomId => (origin as TotalMessagesCountProvider).roomId;
|
||||
}
|
||||
|
||||
String _$chatMemberListNotifierHash() =>
|
||||
r'3ea30150278523e9f6b23f9200ea9a9fbae9c973';
|
||||
|
||||
abstract class _$ChatMemberListNotifier
|
||||
extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnChatMember>> {
|
||||
late final String roomId;
|
||||
|
139
lib/screens/chat/search_messages_screen.dart
Normal file
139
lib/screens/chat/search_messages_screen.dart
Normal file
@@ -0,0 +1,139 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/screens/chat/room.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:island/widgets/chat/message_item.dart';
|
||||
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
||||
import 'package:super_sliver_list/super_sliver_list.dart';
|
||||
|
||||
class SearchMessagesScreen extends HookConsumerWidget {
|
||||
final String roomId;
|
||||
|
||||
const SearchMessagesScreen({super.key, required this.roomId});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final searchController = useTextEditingController();
|
||||
final withLinks = useState(false);
|
||||
final withAttachments = useState(false);
|
||||
|
||||
final messagesNotifier = ref.read(
|
||||
messagesNotifierProvider(roomId).notifier,
|
||||
);
|
||||
final messages = ref.watch(messagesNotifierProvider(roomId));
|
||||
|
||||
useEffect(() {
|
||||
// Clear search when screen is disposed
|
||||
return () {
|
||||
messagesNotifier.clearSearch();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return AppScaffold(
|
||||
appBar: AppBar(title: const Text('searchMessages').tr()),
|
||||
body: Column(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
TextField(
|
||||
controller: searchController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'searchMessagesHint'.tr(),
|
||||
border: InputBorder.none,
|
||||
isDense: true,
|
||||
contentPadding: EdgeInsets.only(
|
||||
left: 16,
|
||||
right: 16,
|
||||
top: 12,
|
||||
bottom: 16,
|
||||
),
|
||||
suffix: IconButton(
|
||||
iconSize: 18,
|
||||
visualDensity: VisualDensity.compact,
|
||||
icon: const Icon(Icons.clear),
|
||||
onPressed: () {
|
||||
searchController.clear();
|
||||
messagesNotifier.clearSearch();
|
||||
},
|
||||
),
|
||||
),
|
||||
onChanged: (query) {
|
||||
messagesNotifier.searchMessages(
|
||||
query,
|
||||
withLinks: withLinks.value,
|
||||
withAttachments: withAttachments.value,
|
||||
);
|
||||
},
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CheckboxListTile(
|
||||
secondary: const Icon(Symbols.link),
|
||||
title: const Text('searchLinks').tr(),
|
||||
value: withLinks.value,
|
||||
onChanged: (bool? value) {
|
||||
withLinks.value = value!;
|
||||
messagesNotifier.searchMessages(
|
||||
searchController.text,
|
||||
withLinks: withLinks.value,
|
||||
withAttachments: withAttachments.value,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: CheckboxListTile(
|
||||
secondary: const Icon(Symbols.file_copy),
|
||||
title: const Text('searchAttachments').tr(),
|
||||
value: withAttachments.value,
|
||||
onChanged: (bool? value) {
|
||||
withAttachments.value = value!;
|
||||
messagesNotifier.searchMessages(
|
||||
searchController.text,
|
||||
withLinks: withLinks.value,
|
||||
withAttachments: withAttachments.value,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const Divider(height: 1),
|
||||
Expanded(
|
||||
child: messages.when(
|
||||
data:
|
||||
(messageList) =>
|
||||
messageList.isEmpty
|
||||
? Center(child: Text('noMessagesFound'.tr()))
|
||||
: SuperListView.builder(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
reverse: true, // Show newest messages at the bottom
|
||||
itemCount: messageList.length,
|
||||
itemBuilder: (context, index) {
|
||||
final message = messageList[index];
|
||||
// Simplified MessageItem for search results, no grouping logic
|
||||
return MessageItem(
|
||||
message: message,
|
||||
isCurrentUser:
|
||||
false, // Or determine based on actual user
|
||||
onAction: null,
|
||||
onJump: (_) {},
|
||||
progress: null,
|
||||
showAvatar: true,
|
||||
);
|
||||
},
|
||||
),
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (error, _) => Center(child: Text('errorGeneric'.tr(args: [error.toString()]))),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@@ -11,7 +11,7 @@ import 'package:island/models/publisher.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/screens/creators/publishers.dart';
|
||||
import 'package:island/services/responsive.dart';
|
||||
import 'package:island/services/text.dart';
|
||||
import 'package:island/utils/text.dart';
|
||||
import 'package:island/widgets/account/account_picker.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
|
@@ -9,6 +9,7 @@ import 'package:island/widgets/poll/poll_feedback.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
||||
import 'package:island/widgets/extended_refresh_indicator.dart';
|
||||
|
||||
part 'poll_list.g.dart';
|
||||
|
||||
@@ -86,7 +87,7 @@ class CreatorPollListScreen extends HookConsumerWidget {
|
||||
onPressed: () => _createPoll(context),
|
||||
child: const Icon(Icons.add),
|
||||
),
|
||||
body: RefreshIndicator(
|
||||
body: ExtendedRefreshIndicator(
|
||||
onRefresh: () => ref.refresh(pollListNotifierProvider(pubName).future),
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
|
@@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart';
|
||||
import 'package:island/pods/webfeed.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:island/widgets/empty_state.dart';
|
||||
import 'package:island/widgets/extended_refresh_indicator.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
|
||||
class WebFeedListScreen extends ConsumerWidget {
|
||||
@@ -20,7 +21,10 @@ class WebFeedListScreen extends ConsumerWidget {
|
||||
floatingActionButton: FloatingActionButton(
|
||||
child: const Icon(Symbols.add),
|
||||
onPressed: () {
|
||||
context.pushNamed('creatorFeedNew', pathParameters: {'name': pubName});
|
||||
context.pushNamed(
|
||||
'creatorFeedNew',
|
||||
pathParameters: {'name': pubName},
|
||||
);
|
||||
},
|
||||
),
|
||||
body: feedsAsync.when(
|
||||
@@ -32,7 +36,7 @@ class WebFeedListScreen extends ConsumerWidget {
|
||||
description: 'Add a new web feed to get started',
|
||||
);
|
||||
}
|
||||
return RefreshIndicator(
|
||||
return ExtendedRefreshIndicator(
|
||||
onRefresh: () => ref.refresh(webFeedListProvider(pubName).future),
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.only(top: 8),
|
||||
@@ -62,7 +66,10 @@ class WebFeedListScreen extends ConsumerWidget {
|
||||
),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
onTap: () {
|
||||
context.pushNamed('creatorFeedEdit', pathParameters: {'name': pubName, 'feedId': feed.id});
|
||||
context.pushNamed(
|
||||
'creatorFeedEdit',
|
||||
pathParameters: {'name': pubName, 'feedId': feed.id},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
@@ -27,7 +27,9 @@ class AppDetailScreen extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final tabController = useTabController(initialLength: 2);
|
||||
final appData = ref.watch(customAppProvider(publisherName, projectId, appId));
|
||||
final appData = ref.watch(
|
||||
customAppProvider(publisherName, projectId, appId),
|
||||
);
|
||||
|
||||
return AppScaffold(
|
||||
appBar: AppBar(
|
||||
@@ -35,23 +37,43 @@ class AppDetailScreen extends HookConsumerWidget {
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.edit),
|
||||
onPressed: appData.value == null
|
||||
? null
|
||||
: () {
|
||||
context.pushNamed(
|
||||
'developerAppEdit',
|
||||
pathParameters: {
|
||||
'name': publisherName,
|
||||
'projectId': projectId,
|
||||
'id': appId,
|
||||
},
|
||||
);
|
||||
},
|
||||
onPressed:
|
||||
appData.value == null
|
||||
? null
|
||||
: () {
|
||||
context.pushNamed(
|
||||
'developerAppEdit',
|
||||
pathParameters: {
|
||||
'name': publisherName,
|
||||
'projectId': projectId,
|
||||
'id': appId,
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
bottom: TabBar(
|
||||
controller: tabController,
|
||||
tabs: [Tab(text: 'overview'.tr()), Tab(text: 'secrets'.tr())],
|
||||
tabs: [
|
||||
Tab(
|
||||
child: Text(
|
||||
'overview'.tr(),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
||||
),
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
child: Text(
|
||||
'secrets'.tr(),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: appData.when(
|
||||
@@ -70,12 +92,14 @@ class AppDetailScreen extends HookConsumerWidget {
|
||||
);
|
||||
},
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (err, stack) => ResponseErrorWidget(
|
||||
error: err,
|
||||
onRetry: () => ref.invalidate(
|
||||
customAppProvider(publisherName, projectId, appId),
|
||||
),
|
||||
),
|
||||
error:
|
||||
(err, stack) => ResponseErrorWidget(
|
||||
error: err,
|
||||
onRetry:
|
||||
() => ref.invalidate(
|
||||
customAppProvider(publisherName, projectId, appId),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -98,12 +122,13 @@ class _AppOverview extends StatelessWidget {
|
||||
children: [
|
||||
Container(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
child: app.background != null
|
||||
? CloudFileWidget(
|
||||
item: app.background!,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
child:
|
||||
app.background != null
|
||||
? CloudFileWidget(
|
||||
item: app.background!,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
Positioned(
|
||||
left: 20,
|
||||
|
@@ -52,7 +52,26 @@ class BotDetailScreen extends HookConsumerWidget {
|
||||
],
|
||||
bottom: TabBar(
|
||||
controller: tabController,
|
||||
tabs: [Tab(text: 'overview'.tr()), Tab(text: 'keys'.tr())],
|
||||
tabs: [
|
||||
Tab(
|
||||
child: Text(
|
||||
'overview'.tr(),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
||||
),
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
child: Text(
|
||||
'keys'.tr(),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: botData.when(
|
||||
|
@@ -9,6 +9,7 @@ import 'package:island/widgets/content/cloud_files.dart';
|
||||
import 'package:island/widgets/response.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:island/widgets/extended_refresh_indicator.dart';
|
||||
|
||||
part 'bots.g.dart';
|
||||
|
||||
@@ -60,7 +61,7 @@ class BotsScreen extends HookConsumerWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
return RefreshIndicator(
|
||||
return ExtendedRefreshIndicator(
|
||||
onRefresh:
|
||||
() => ref.refresh(botsProvider(publisherName, projectId).future),
|
||||
child: ListView.builder(
|
||||
|
@@ -58,7 +58,26 @@ class ProjectDetailScreen extends HookConsumerWidget {
|
||||
],
|
||||
bottom: TabBar(
|
||||
controller: tabController,
|
||||
tabs: [Tab(text: 'customApps'.tr()), Tab(text: 'bots'.tr())],
|
||||
tabs: [
|
||||
Tab(
|
||||
child: Text(
|
||||
'customApps'.tr(),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
||||
),
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
child: Text(
|
||||
'bots'.tr(),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: TabBarView(
|
||||
|
@@ -137,13 +137,32 @@ class ArticlesScreen extends ConsumerWidget {
|
||||
return DefaultTabController(
|
||||
length: feeds.length + 1,
|
||||
child: AppScaffold(
|
||||
isNoBackground: false,
|
||||
appBar: AppBar(
|
||||
title: const Text('Articles'),
|
||||
bottom: TabBar(
|
||||
isScrollable: true,
|
||||
tabs: [
|
||||
const Tab(text: 'All'),
|
||||
...feeds.map((feed) => Tab(text: feed.title)),
|
||||
Tab(
|
||||
child: Text(
|
||||
'All',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
||||
),
|
||||
),
|
||||
),
|
||||
...feeds.map(
|
||||
(feed) => Tab(
|
||||
child: Text(
|
||||
feed.title,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -192,11 +211,13 @@ class ArticlesScreen extends ConsumerWidget {
|
||||
},
|
||||
loading:
|
||||
() => AppScaffold(
|
||||
isNoBackground: false,
|
||||
appBar: AppBar(title: const Text('Articles')),
|
||||
body: const Center(child: CircularProgressIndicator()),
|
||||
),
|
||||
error:
|
||||
(err, stack) => AppScaffold(
|
||||
isNoBackground: false,
|
||||
appBar: AppBar(title: const Text('Articles')),
|
||||
body: Center(child: Text('Error: $err')),
|
||||
),
|
||||
|
@@ -72,7 +72,7 @@ class MarketplaceWebFeedsScreen extends HookConsumerWidget {
|
||||
searchController.clear();
|
||||
}
|
||||
return null;
|
||||
}, [query.value]);
|
||||
}, [query]);
|
||||
|
||||
// Clean up timer on dispose
|
||||
useEffect(() {
|
||||
|
@@ -27,6 +27,7 @@ import 'package:island/pods/network.dart';
|
||||
import 'package:island/widgets/realm/realm_card.dart';
|
||||
import 'package:island/widgets/publisher/publisher_card.dart';
|
||||
import 'package:island/widgets/web_article_card.dart';
|
||||
import 'package:island/widgets/extended_refresh_indicator.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
part 'explore.g.dart';
|
||||
@@ -368,7 +369,7 @@ class ExploreScreen extends HookConsumerWidget {
|
||||
|
||||
final isWide = isWideScreen(context);
|
||||
|
||||
return RefreshIndicator(
|
||||
return ExtendedRefreshIndicator(
|
||||
onRefresh: () => Future.sync(activitiesNotifier.forceRefresh),
|
||||
child: PagingHelperView(
|
||||
provider: activityListNotifierProvider(filter),
|
||||
@@ -399,6 +400,69 @@ class _DiscoveryActivityItem extends StatelessWidget {
|
||||
final items = data['items'] as List;
|
||||
final type = items.firstOrNull?['type'] ?? 'unknown';
|
||||
|
||||
var flexWeights = isWideScreen(context) ? <int>[3, 2, 1] : <int>[4, 1];
|
||||
if (type == 'post') flexWeights = <int>[3, 2];
|
||||
|
||||
final height = type == 'post' ? 280.0 : 180.0;
|
||||
|
||||
final contentWidget = switch (type) {
|
||||
'post' => ListView.separated(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: items.length,
|
||||
separatorBuilder: (context, index) => const Gap(12),
|
||||
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
itemBuilder: (context, index) {
|
||||
final item = items[index];
|
||||
return Container(
|
||||
width: 320,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
width: 1 / MediaQuery.of(context).devicePixelRatio,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.5),
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
child: SingleChildScrollView(
|
||||
child: PostActionableItem(
|
||||
item: SnPost.fromJson(item['data']),
|
||||
isCompact: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
_ => CarouselView.weighted(
|
||||
flexWeights: flexWeights,
|
||||
consumeMaxWeight: false,
|
||||
enableSplash: false,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||
),
|
||||
itemSnapping: false,
|
||||
children: [
|
||||
for (final item in items)
|
||||
switch (type) {
|
||||
'realm' => RealmCard(
|
||||
realm: SnRealm.fromJson(item['data']),
|
||||
maxWidth: 280,
|
||||
),
|
||||
'publisher' => PublisherCard(
|
||||
publisher: SnPublisher.fromJson(item['data']),
|
||||
maxWidth: 280,
|
||||
),
|
||||
'article' => WebArticleCard(
|
||||
article: SnWebArticle.fromJson(item['data']),
|
||||
maxWidth: 280,
|
||||
),
|
||||
_ => const Placeholder(),
|
||||
},
|
||||
],
|
||||
),
|
||||
};
|
||||
|
||||
return Card(
|
||||
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: Column(
|
||||
@@ -407,13 +471,20 @@ class _DiscoveryActivityItem extends StatelessWidget {
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Symbols.explore, size: 19),
|
||||
Icon(switch (type) {
|
||||
'realm' => Symbols.public,
|
||||
'publisher' => Symbols.account_circle,
|
||||
'article' => Symbols.auto_stories,
|
||||
'post' => Symbols.shuffle,
|
||||
_ => Symbols.explore,
|
||||
}, size: 19),
|
||||
const Gap(8),
|
||||
Text(
|
||||
(switch (type) {
|
||||
'realm' => 'discoverRealms',
|
||||
'publisher' => 'discoverPublishers',
|
||||
'article' => 'discoverWebArticles',
|
||||
'post' => 'discoverShuffledPost',
|
||||
_ => 'unknown',
|
||||
}).tr(),
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
@@ -421,37 +492,8 @@ class _DiscoveryActivityItem extends StatelessWidget {
|
||||
],
|
||||
).padding(horizontal: 20, top: 8, bottom: 4),
|
||||
SizedBox(
|
||||
height: 180,
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 200),
|
||||
child: CarouselView.weighted(
|
||||
flexWeights:
|
||||
isWideScreen(context) ? <int>[3, 2, 1] : <int>[4, 1],
|
||||
consumeMaxWeight: false,
|
||||
enableSplash: false,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||
),
|
||||
children: [
|
||||
for (final item in items)
|
||||
switch (type) {
|
||||
'realm' => RealmCard(
|
||||
realm: SnRealm.fromJson(item['data']),
|
||||
maxWidth: 280,
|
||||
),
|
||||
'publisher' => PublisherCard(
|
||||
publisher: SnPublisher.fromJson(item['data']),
|
||||
maxWidth: 280,
|
||||
),
|
||||
'article' => WebArticleCard(
|
||||
article: SnWebArticle.fromJson(item['data']),
|
||||
maxWidth: 280,
|
||||
),
|
||||
_ => Placeholder(),
|
||||
},
|
||||
],
|
||||
),
|
||||
),
|
||||
height: height,
|
||||
child: contentWidget,
|
||||
).padding(bottom: 8, horizontal: 8),
|
||||
],
|
||||
),
|
||||
@@ -569,7 +611,8 @@ class ActivityListNotifier extends _$ActivityListNotifier
|
||||
if (cursor != null) 'cursor': cursor,
|
||||
'take': take,
|
||||
if (filter != null) 'filter': filter,
|
||||
if (kDebugMode) 'debugInclude': 'realms,publishers,articles',
|
||||
if (kDebugMode)
|
||||
'debugInclude': 'realms,publishers,articles,shuffledPosts',
|
||||
};
|
||||
|
||||
final response = await client.get(
|
||||
@@ -584,12 +627,13 @@ class ActivityListNotifier extends _$ActivityListNotifier
|
||||
|
||||
final hasMore = (items.firstOrNull?.type ?? 'empty') != 'empty';
|
||||
final nextCursor =
|
||||
items
|
||||
.map((x) => x.createdAt)
|
||||
.lastOrNull
|
||||
?.toUtc()
|
||||
.toIso8601String()
|
||||
.toString();
|
||||
items.isNotEmpty
|
||||
? items
|
||||
.map((x) => x.createdAt)
|
||||
.reduce((a, b) => a.isBefore(b) ? a : b)
|
||||
.toUtc()
|
||||
.toIso8601String()
|
||||
: null;
|
||||
|
||||
return CursorPagingData(
|
||||
items: items,
|
||||
|
@@ -7,7 +7,7 @@ part of 'explore.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$activityListNotifierHash() =>
|
||||
r'b75fd5c08d5f84ca433e16b7387d317ea72b91c9';
|
||||
r'a4968856ac34b59d47cfd4a7cbb39289aef2a1b1';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
@@ -8,6 +8,7 @@ import 'package:island/pods/network.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:island/widgets/post/post_list.dart';
|
||||
import 'package:island/widgets/response.dart';
|
||||
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
@@ -27,6 +28,49 @@ Future<SnPostTag> postTag(Ref ref, String slug) async {
|
||||
return SnPostTag.fromJson(resp.data);
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<bool> postCategorySubscriptionStatus(
|
||||
Ref ref,
|
||||
String slug,
|
||||
bool isCategory,
|
||||
) async {
|
||||
final apiClient = ref.watch(apiClientProvider);
|
||||
try {
|
||||
final resp = await apiClient.get(
|
||||
'/sphere/posts/${isCategory ? 'categories' : 'tags'}/$slug/subscription',
|
||||
);
|
||||
return resp.statusCode == 200;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _subscribeToCategoryOrTag(
|
||||
WidgetRef ref, {
|
||||
required String slug,
|
||||
required bool isCategory,
|
||||
}) async {
|
||||
final apiClient = ref.read(apiClientProvider);
|
||||
await apiClient.post(
|
||||
'/sphere/posts/${isCategory ? 'categories' : 'tags'}/$slug/subscribe',
|
||||
);
|
||||
// Invalidate the subscription status to refresh it
|
||||
ref.invalidate(postCategorySubscriptionStatusProvider(slug, isCategory));
|
||||
}
|
||||
|
||||
Future<void> _unsubscribeFromCategoryOrTag(
|
||||
WidgetRef ref, {
|
||||
required String slug,
|
||||
required bool isCategory,
|
||||
}) async {
|
||||
final apiClient = ref.read(apiClientProvider);
|
||||
await apiClient.post(
|
||||
'/sphere/posts/${isCategory ? 'categories' : 'tags'}/$slug/unsubscribe',
|
||||
);
|
||||
// Invalidate the subscription status to refresh it
|
||||
ref.invalidate(postCategorySubscriptionStatusProvider(slug, isCategory));
|
||||
}
|
||||
|
||||
class PostCategoryDetailScreen extends HookConsumerWidget {
|
||||
final String slug;
|
||||
final bool isCategory;
|
||||
@@ -41,6 +85,9 @@ class PostCategoryDetailScreen extends HookConsumerWidget {
|
||||
final postCategory =
|
||||
isCategory ? ref.watch(postCategoryProvider(slug)) : null;
|
||||
final postTag = isCategory ? null : ref.watch(postTagProvider(slug));
|
||||
final subscriptionStatus = ref.watch(
|
||||
postCategorySubscriptionStatusProvider(slug, isCategory),
|
||||
);
|
||||
|
||||
final postFilterTitle =
|
||||
isCategory
|
||||
@@ -50,57 +97,158 @@ class PostCategoryDetailScreen extends HookConsumerWidget {
|
||||
return AppScaffold(
|
||||
isNoBackground: false,
|
||||
appBar: AppBar(title: Text(postFilterTitle).tr()),
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (isCategory)
|
||||
postCategory!.when(
|
||||
data:
|
||||
(category) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(category.categoryDisplayTitle).bold().fontSize(15),
|
||||
Text('A category'),
|
||||
],
|
||||
).padding(horizontal: 24, vertical: 16),
|
||||
error:
|
||||
(error, _) => ResponseErrorWidget(
|
||||
error: error,
|
||||
onRetry: () => ref.invalidate(postCategoryProvider(slug)),
|
||||
body: Expanded(
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
if (isCategory)
|
||||
SliverToBoxAdapter(
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 540),
|
||||
child: Card(
|
||||
margin: EdgeInsets.only(top: 8),
|
||||
child: postCategory!.when(
|
||||
data:
|
||||
(category) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
category.categoryDisplayTitle,
|
||||
).bold().fontSize(15),
|
||||
Text('A category'),
|
||||
const Gap(8),
|
||||
subscriptionStatus.when(
|
||||
data:
|
||||
(isSubscribed) =>
|
||||
isSubscribed
|
||||
? FilledButton.icon(
|
||||
onPressed: () async {
|
||||
await _unsubscribeFromCategoryOrTag(
|
||||
ref,
|
||||
slug: slug,
|
||||
isCategory: isCategory,
|
||||
);
|
||||
},
|
||||
icon: const Icon(
|
||||
Symbols.remove_circle,
|
||||
),
|
||||
label: Text('unsubscribe'.tr()),
|
||||
)
|
||||
: FilledButton.icon(
|
||||
onPressed: () async {
|
||||
await _subscribeToCategoryOrTag(
|
||||
ref,
|
||||
slug: slug,
|
||||
isCategory: isCategory,
|
||||
);
|
||||
},
|
||||
icon: const Icon(
|
||||
Symbols.add_circle,
|
||||
),
|
||||
label: Text('subscribe'.tr()),
|
||||
),
|
||||
error:
|
||||
(error, _) => Text(
|
||||
'Error loading subscription status',
|
||||
),
|
||||
loading:
|
||||
() =>
|
||||
CircularProgressIndicator().center(),
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 24, vertical: 16),
|
||||
error:
|
||||
(error, _) => ResponseErrorWidget(
|
||||
error: error,
|
||||
onRetry:
|
||||
() => ref.invalidate(
|
||||
postCategoryProvider(slug),
|
||||
),
|
||||
),
|
||||
loading: () => ResponseLoadingWidget(),
|
||||
),
|
||||
),
|
||||
),
|
||||
loading: () => ResponseLoadingWidget(),
|
||||
)
|
||||
else
|
||||
postTag!.when(
|
||||
data:
|
||||
(tag) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(tag.name ?? '#${tag.slug}').bold().fontSize(15),
|
||||
Text('A tag'),
|
||||
],
|
||||
).padding(horizontal: 24, vertical: 16),
|
||||
error:
|
||||
(error, _) => ResponseErrorWidget(
|
||||
error: error,
|
||||
onRetry: () => ref.invalidate(postTagProvider(slug)),
|
||||
),
|
||||
loading: () => ResponseLoadingWidget(),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
Expanded(
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
const SliverGap(4),
|
||||
SliverPostList(
|
||||
categories: isCategory ? [slug] : null,
|
||||
tags: isCategory ? null : [slug],
|
||||
),
|
||||
SliverGap(MediaQuery.of(context).padding.bottom + 8),
|
||||
],
|
||||
)
|
||||
else
|
||||
SliverToBoxAdapter(
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 540),
|
||||
child: Card(
|
||||
margin: EdgeInsets.only(top: 8),
|
||||
child: postTag!.when(
|
||||
data:
|
||||
(tag) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
tag.name ?? '#${tag.slug}',
|
||||
).bold().fontSize(15),
|
||||
Text('A tag'),
|
||||
const Gap(8),
|
||||
subscriptionStatus.when(
|
||||
data:
|
||||
(isSubscribed) =>
|
||||
isSubscribed
|
||||
? FilledButton.icon(
|
||||
onPressed: () async {
|
||||
await _unsubscribeFromCategoryOrTag(
|
||||
ref,
|
||||
slug: slug,
|
||||
isCategory: isCategory,
|
||||
);
|
||||
},
|
||||
icon: const Icon(
|
||||
Symbols.add_circle,
|
||||
),
|
||||
label: Text('unsubscribe'.tr()),
|
||||
)
|
||||
: FilledButton.icon(
|
||||
onPressed: () async {
|
||||
await _subscribeToCategoryOrTag(
|
||||
ref,
|
||||
slug: slug,
|
||||
isCategory: isCategory,
|
||||
);
|
||||
},
|
||||
icon: const Icon(
|
||||
Symbols.remove_circle,
|
||||
),
|
||||
label: Text('subscribe'.tr()),
|
||||
),
|
||||
error:
|
||||
(error, _) => Text(
|
||||
'Error loading subscription status',
|
||||
),
|
||||
loading:
|
||||
() =>
|
||||
CircularProgressIndicator().center(),
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 24, vertical: 16),
|
||||
error:
|
||||
(error, _) => ResponseErrorWidget(
|
||||
error: error,
|
||||
onRetry:
|
||||
() => ref.invalidate(postTagProvider(slug)),
|
||||
),
|
||||
loading: () => ResponseLoadingWidget(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SliverGap(4),
|
||||
SliverPostList(
|
||||
categories: isCategory ? [slug] : null,
|
||||
tags: isCategory ? null : [slug],
|
||||
maxWidth: 540 + 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
SliverGap(MediaQuery.of(context).padding.bottom + 8),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@@ -266,5 +266,146 @@ class _PostTagProviderElement
|
||||
String get slug => (origin as PostTagProvider).slug;
|
||||
}
|
||||
|
||||
String _$postCategorySubscriptionStatusHash() =>
|
||||
r'407dc7fcaeffc461b591b4ee2418811aa4f0a63f';
|
||||
|
||||
/// See also [postCategorySubscriptionStatus].
|
||||
@ProviderFor(postCategorySubscriptionStatus)
|
||||
const postCategorySubscriptionStatusProvider =
|
||||
PostCategorySubscriptionStatusFamily();
|
||||
|
||||
/// See also [postCategorySubscriptionStatus].
|
||||
class PostCategorySubscriptionStatusFamily extends Family<AsyncValue<bool>> {
|
||||
/// See also [postCategorySubscriptionStatus].
|
||||
const PostCategorySubscriptionStatusFamily();
|
||||
|
||||
/// See also [postCategorySubscriptionStatus].
|
||||
PostCategorySubscriptionStatusProvider call(String slug, bool isCategory) {
|
||||
return PostCategorySubscriptionStatusProvider(slug, isCategory);
|
||||
}
|
||||
|
||||
@override
|
||||
PostCategorySubscriptionStatusProvider getProviderOverride(
|
||||
covariant PostCategorySubscriptionStatusProvider provider,
|
||||
) {
|
||||
return call(provider.slug, provider.isCategory);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'postCategorySubscriptionStatusProvider';
|
||||
}
|
||||
|
||||
/// See also [postCategorySubscriptionStatus].
|
||||
class PostCategorySubscriptionStatusProvider
|
||||
extends AutoDisposeFutureProvider<bool> {
|
||||
/// See also [postCategorySubscriptionStatus].
|
||||
PostCategorySubscriptionStatusProvider(String slug, bool isCategory)
|
||||
: this._internal(
|
||||
(ref) => postCategorySubscriptionStatus(
|
||||
ref as PostCategorySubscriptionStatusRef,
|
||||
slug,
|
||||
isCategory,
|
||||
),
|
||||
from: postCategorySubscriptionStatusProvider,
|
||||
name: r'postCategorySubscriptionStatusProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$postCategorySubscriptionStatusHash,
|
||||
dependencies: PostCategorySubscriptionStatusFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
PostCategorySubscriptionStatusFamily._allTransitiveDependencies,
|
||||
slug: slug,
|
||||
isCategory: isCategory,
|
||||
);
|
||||
|
||||
PostCategorySubscriptionStatusProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.slug,
|
||||
required this.isCategory,
|
||||
}) : super.internal();
|
||||
|
||||
final String slug;
|
||||
final bool isCategory;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
FutureOr<bool> Function(PostCategorySubscriptionStatusRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: PostCategorySubscriptionStatusProvider._internal(
|
||||
(ref) => create(ref as PostCategorySubscriptionStatusRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
slug: slug,
|
||||
isCategory: isCategory,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeFutureProviderElement<bool> createElement() {
|
||||
return _PostCategorySubscriptionStatusProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is PostCategorySubscriptionStatusProvider &&
|
||||
other.slug == slug &&
|
||||
other.isCategory == isCategory;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, slug.hashCode);
|
||||
hash = _SystemHash.combine(hash, isCategory.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin PostCategorySubscriptionStatusRef on AutoDisposeFutureProviderRef<bool> {
|
||||
/// The parameter `slug` of this provider.
|
||||
String get slug;
|
||||
|
||||
/// The parameter `isCategory` of this provider.
|
||||
bool get isCategory;
|
||||
}
|
||||
|
||||
class _PostCategorySubscriptionStatusProviderElement
|
||||
extends AutoDisposeFutureProviderElement<bool>
|
||||
with PostCategorySubscriptionStatusRef {
|
||||
_PostCategorySubscriptionStatusProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String get slug => (origin as PostCategorySubscriptionStatusProvider).slug;
|
||||
@override
|
||||
bool get isCategory =>
|
||||
(origin as PostCategorySubscriptionStatusProvider).isCategory;
|
||||
}
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
|
@@ -1,5 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/post.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
@@ -7,6 +9,7 @@ import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:island/widgets/post/post_item.dart';
|
||||
import 'package:island/widgets/response.dart';
|
||||
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
final postSearchNotifierProvider = StateNotifierProvider.autoDispose<
|
||||
PostSearchNotifier,
|
||||
@@ -18,6 +21,13 @@ class PostSearchNotifier
|
||||
final AutoDisposeRef ref;
|
||||
static const int _pageSize = 20;
|
||||
String _currentQuery = '';
|
||||
String? _pubName;
|
||||
String? _realm;
|
||||
int? _type;
|
||||
List<String>? _categories;
|
||||
List<String>? _tags;
|
||||
bool _shuffle = false;
|
||||
bool? _pinned;
|
||||
bool _isLoading = false;
|
||||
|
||||
PostSearchNotifier(this.ref) : super(const AsyncValue.loading()) {
|
||||
@@ -26,11 +36,38 @@ class PostSearchNotifier
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> search(String query) async {
|
||||
Future<void> search(
|
||||
String query, {
|
||||
String? pubName,
|
||||
String? realm,
|
||||
int? type,
|
||||
List<String>? categories,
|
||||
List<String>? tags,
|
||||
bool shuffle = false,
|
||||
bool? pinned,
|
||||
}) async {
|
||||
if (_isLoading) return;
|
||||
|
||||
_currentQuery = query.trim();
|
||||
if (_currentQuery.isEmpty) {
|
||||
_pubName = pubName;
|
||||
_realm = realm;
|
||||
_type = type;
|
||||
_categories = categories;
|
||||
_tags = tags;
|
||||
_shuffle = shuffle;
|
||||
_pinned = pinned;
|
||||
|
||||
// Allow search even with empty query if any filters are applied
|
||||
final hasFilters =
|
||||
pubName != null ||
|
||||
realm != null ||
|
||||
type != null ||
|
||||
categories != null ||
|
||||
tags != null ||
|
||||
shuffle ||
|
||||
pinned != null;
|
||||
|
||||
if (_currentQuery.isEmpty && !hasFilters) {
|
||||
state = AsyncValue.data(
|
||||
CursorPagingData(items: [], hasMore: false, nextCursor: null),
|
||||
);
|
||||
@@ -57,6 +94,13 @@ class PostSearchNotifier
|
||||
'offset': offset,
|
||||
'take': _pageSize,
|
||||
'vector': false,
|
||||
if (_pubName != null) 'pub': _pubName,
|
||||
if (_realm != null) 'realm': _realm,
|
||||
if (_type != null) 'type': _type,
|
||||
if (_tags != null) 'tags': _tags,
|
||||
if (_categories != null) 'categories': _categories,
|
||||
if (_shuffle) 'shuffle': true,
|
||||
if (_pinned != null) 'pinned': _pinned,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -80,100 +124,269 @@ class PostSearchNotifier
|
||||
}
|
||||
}
|
||||
|
||||
class PostSearchScreen extends ConsumerStatefulWidget {
|
||||
class PostSearchScreen extends HookConsumerWidget {
|
||||
const PostSearchScreen({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<PostSearchScreen> createState() => _PostSearchScreenState();
|
||||
}
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final searchController = useTextEditingController();
|
||||
final debounce = useMemoized(() => Duration(milliseconds: 500));
|
||||
final debounceTimer = useRef<Timer?>(null);
|
||||
final showFilters = useState(false);
|
||||
final pubNameController = useTextEditingController();
|
||||
final realmController = useTextEditingController();
|
||||
final typeValue = useState<int?>(null);
|
||||
final selectedCategories = useState<List<String>>([]);
|
||||
final selectedTags = useState<List<String>>([]);
|
||||
final shuffleValue = useState(false);
|
||||
final pinnedValue = useState<bool?>(null);
|
||||
|
||||
class _PostSearchScreenState extends ConsumerState<PostSearchScreen> {
|
||||
final _searchController = TextEditingController();
|
||||
final _debounce = Duration(milliseconds: 500);
|
||||
Timer? _debounceTimer;
|
||||
useEffect(() {
|
||||
return () {
|
||||
searchController.dispose();
|
||||
pubNameController.dispose();
|
||||
realmController.dispose();
|
||||
debounceTimer.value?.cancel();
|
||||
};
|
||||
}, []);
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_searchController.dispose();
|
||||
_debounceTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
void onSearchChanged(String query) {
|
||||
if (debounceTimer.value?.isActive ?? false) debounceTimer.value!.cancel();
|
||||
|
||||
void _onSearchChanged(String query) {
|
||||
if (_debounceTimer?.isActive ?? false) _debounceTimer!.cancel();
|
||||
debounceTimer.value = Timer(debounce, () {
|
||||
ref.read(postSearchNotifierProvider.notifier).search(query);
|
||||
});
|
||||
}
|
||||
|
||||
_debounceTimer = Timer(_debounce, () {
|
||||
ref.read(postSearchNotifierProvider.notifier).search(query);
|
||||
});
|
||||
}
|
||||
void onSearchWithFilters(String query) {
|
||||
if (debounceTimer.value?.isActive ?? false) debounceTimer.value!.cancel();
|
||||
|
||||
debounceTimer.value = Timer(debounce, () {
|
||||
ref
|
||||
.read(postSearchNotifierProvider.notifier)
|
||||
.search(
|
||||
query,
|
||||
pubName:
|
||||
pubNameController.text.isNotEmpty
|
||||
? pubNameController.text
|
||||
: null,
|
||||
realm:
|
||||
realmController.text.isNotEmpty ? realmController.text : null,
|
||||
type: typeValue.value,
|
||||
categories:
|
||||
selectedCategories.value.isNotEmpty
|
||||
? selectedCategories.value
|
||||
: null,
|
||||
tags: selectedTags.value.isNotEmpty ? selectedTags.value : null,
|
||||
shuffle: shuffleValue.value,
|
||||
pinned: pinnedValue.value,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void toggleFilters() {
|
||||
showFilters.value = !showFilters.value;
|
||||
}
|
||||
|
||||
void applyFilters() {
|
||||
onSearchWithFilters(searchController.text);
|
||||
}
|
||||
|
||||
void clearFilters() {
|
||||
pubNameController.clear();
|
||||
realmController.clear();
|
||||
typeValue.value = null;
|
||||
selectedCategories.value = [];
|
||||
selectedTags.value = [];
|
||||
shuffleValue.value = false;
|
||||
pinnedValue.value = null;
|
||||
onSearchChanged(searchController.text);
|
||||
}
|
||||
|
||||
Widget buildFilterPanel() {
|
||||
return Card(
|
||||
margin: EdgeInsets.symmetric(vertical: 8, horizontal: 8),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'filters'.tr(),
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
).padding(left: 4),
|
||||
Row(
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: applyFilters,
|
||||
child: Text('apply'.tr()),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: clearFilters,
|
||||
child: Text('clear'.tr()),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: pubNameController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'pubName'.tr(),
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
onChanged:
|
||||
(value) => onSearchWithFilters(searchController.text),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
TextField(
|
||||
controller: realmController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'realm'.tr(),
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
onChanged:
|
||||
(value) => onSearchWithFilters(searchController.text),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: shuffleValue.value,
|
||||
onChanged: (value) {
|
||||
shuffleValue.value = value ?? false;
|
||||
onSearchWithFilters(searchController.text);
|
||||
},
|
||||
),
|
||||
Text('shuffle'.tr()),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: pinnedValue.value ?? false,
|
||||
onChanged: (value) {
|
||||
pinnedValue.value = value;
|
||||
onSearchWithFilters(searchController.text);
|
||||
},
|
||||
),
|
||||
Text('pinned'.tr()),
|
||||
],
|
||||
),
|
||||
// TODO: Add dropdown for type selection
|
||||
// TODO: Add multi-select for categories and tags
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppScaffold(
|
||||
isNoBackground: false,
|
||||
appBar: AppBar(
|
||||
title: TextField(
|
||||
controller: _searchController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Search posts...',
|
||||
border: InputBorder.none,
|
||||
hintStyle: TextStyle(
|
||||
color: Theme.of(context).appBarTheme.foregroundColor,
|
||||
title: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: searchController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'search'.tr(),
|
||||
border: InputBorder.none,
|
||||
hintStyle: TextStyle(
|
||||
color: Theme.of(context).appBarTheme.foregroundColor,
|
||||
),
|
||||
),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).appBarTheme.foregroundColor,
|
||||
),
|
||||
onChanged: onSearchChanged,
|
||||
onSubmitted: (value) {
|
||||
onSearchWithFilters(value);
|
||||
},
|
||||
autofocus: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).appBarTheme.foregroundColor,
|
||||
),
|
||||
onChanged: _onSearchChanged,
|
||||
onSubmitted: (value) {
|
||||
ref.read(postSearchNotifierProvider.notifier).search(value);
|
||||
},
|
||||
autofocus: true,
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
showFilters.value
|
||||
? Icons.filter_alt
|
||||
: Icons.filter_alt_outlined,
|
||||
),
|
||||
onPressed: toggleFilters,
|
||||
tooltip: 'toggleFilters'.tr(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final searchState = ref.watch(postSearchNotifierProvider);
|
||||
|
||||
return searchState.when(
|
||||
data: (data) {
|
||||
if (data.items.isEmpty && _searchController.text.isNotEmpty) {
|
||||
return const Center(child: Text('No results found'));
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
itemCount: data.items.length + (data.hasMore ? 1 : 0),
|
||||
itemBuilder: (context, index) {
|
||||
if (index >= data.items.length) {
|
||||
ref
|
||||
.read(postSearchNotifierProvider.notifier)
|
||||
.fetch(cursor: data.nextCursor);
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
if (showFilters.value)
|
||||
SliverToBoxAdapter(
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 600),
|
||||
child: buildFilterPanel(),
|
||||
),
|
||||
),
|
||||
),
|
||||
searchState.when(
|
||||
data: (data) {
|
||||
if (data.items.isEmpty && searchController.text.isNotEmpty) {
|
||||
return SliverFillRemaining(
|
||||
child: Center(child: Text('noResultsFound'.tr())),
|
||||
);
|
||||
}
|
||||
|
||||
final post = data.items[index];
|
||||
return Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: 600),
|
||||
child: Card(
|
||||
margin: EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 4,
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
if (index >= data.items.length) {
|
||||
ref
|
||||
.read(postSearchNotifierProvider.notifier)
|
||||
.fetch(cursor: data.nextCursor);
|
||||
return Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
final post = data.items[index];
|
||||
return Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: 600),
|
||||
child: Card(
|
||||
margin: EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 4,
|
||||
),
|
||||
child: PostActionableItem(
|
||||
item: post,
|
||||
borderRadius: 8,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: PostActionableItem(item: post, borderRadius: 8),
|
||||
),
|
||||
),
|
||||
);
|
||||
}, childCount: data.items.length + (data.hasMore ? 1 : 0)),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error:
|
||||
(error, stack) => ResponseErrorWidget(
|
||||
error: error,
|
||||
onRetry: () => ref.invalidate(postSearchNotifierProvider),
|
||||
),
|
||||
loading:
|
||||
() => SliverFillRemaining(
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
),
|
||||
error:
|
||||
(error, stack) => SliverFillRemaining(
|
||||
child: ResponseErrorWidget(
|
||||
error: error,
|
||||
onRetry:
|
||||
() => ref.invalidate(postSearchNotifierProvider),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
|
@@ -288,7 +288,11 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
||||
controller: categoryTabController,
|
||||
dividerColor: Colors.transparent,
|
||||
splashBorderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
tabs: [Tab(text: 'All'), Tab(text: 'Posts'), Tab(text: 'Articles')],
|
||||
tabs: [
|
||||
Tab(text: 'all'.tr()),
|
||||
Tab(text: 'postTypePost'.tr()),
|
||||
Tab(text: 'postArticle'.tr()),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -345,12 +349,14 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
SliverGap(16),
|
||||
SliverPostList(pubName: name, pinned: true),
|
||||
SliverToBoxAdapter(
|
||||
child: publisherCategoryTabWidget(),
|
||||
),
|
||||
SliverPostList(
|
||||
key: ValueKey(categoryTab.value),
|
||||
pubName: name,
|
||||
pinned: false,
|
||||
type: switch (categoryTab.value) {
|
||||
1 => 0,
|
||||
2 => 1,
|
||||
@@ -433,10 +439,12 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
||||
child: publisherVerificationWidget(data),
|
||||
),
|
||||
SliverToBoxAdapter(child: publisherBioWidget(data)),
|
||||
SliverPostList(pubName: name, pinned: true),
|
||||
SliverToBoxAdapter(child: publisherCategoryTabWidget()),
|
||||
SliverPostList(
|
||||
key: ValueKey(categoryTab.value),
|
||||
pubName: name,
|
||||
pinned: false,
|
||||
type: switch (categoryTab.value) {
|
||||
1 => 0,
|
||||
2 => 1,
|
||||
|
@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:island/models/chat.dart';
|
||||
import 'package:island/services/color.dart';
|
||||
import 'package:island/services/responsive.dart';
|
||||
import 'package:island/widgets/account/account_pfc.dart';
|
||||
import 'package:island/widgets/account/status.dart';
|
||||
import 'package:island/widgets/post/post_list.dart';
|
||||
import 'package:palette_generator/palette_generator.dart';
|
||||
@@ -244,7 +245,10 @@ class RealmDetailScreen extends HookConsumerWidget {
|
||||
Flexible(
|
||||
flex: 3,
|
||||
child: CustomScrollView(
|
||||
slivers: [SliverPostList(realm: slug)],
|
||||
slivers: [
|
||||
SliverPostList(realm: slug, pinned: true),
|
||||
SliverPostList(realm: slug, pinned: false),
|
||||
],
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
@@ -359,7 +363,8 @@ class RealmDetailScreen extends HookConsumerWidget {
|
||||
SliverToBoxAdapter(
|
||||
child: realmChatRoomListWidget(realm),
|
||||
),
|
||||
SliverPostList(realm: slug),
|
||||
SliverPostList(realm: slug, pinned: true),
|
||||
SliverPostList(realm: slug, pinned: false),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -654,8 +659,11 @@ class _RealmMemberListSheet extends HookConsumerWidget {
|
||||
final member = data.items[index];
|
||||
return ListTile(
|
||||
contentPadding: EdgeInsets.only(left: 16, right: 12),
|
||||
leading: ProfilePictureWidget(
|
||||
fileId: member.account!.profile.picture?.id,
|
||||
leading: AccountPfcGestureDetector(
|
||||
uname: member.account!.name,
|
||||
child: ProfilePictureWidget(
|
||||
fileId: member.account!.profile.picture?.id,
|
||||
),
|
||||
),
|
||||
title: Row(
|
||||
spacing: 6,
|
||||
|
@@ -23,6 +23,7 @@ import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:island/widgets/realm/realm_list_tile.dart';
|
||||
import 'package:island/widgets/extended_refresh_indicator.dart';
|
||||
|
||||
part 'realms.g.dart';
|
||||
|
||||
@@ -90,7 +91,7 @@ class RealmListScreen extends HookConsumerWidget {
|
||||
},
|
||||
),
|
||||
floatingActionButtonLocation: TabbedFabLocation(context),
|
||||
body: RefreshIndicator(
|
||||
body: ExtendedRefreshIndicator(
|
||||
child: realms.when(
|
||||
data:
|
||||
(value) => Column(
|
||||
|
@@ -219,6 +219,33 @@ class SettingsScreen extends HookConsumerWidget {
|
||||
},
|
||||
),
|
||||
|
||||
// Background image enabled
|
||||
if (!kIsWeb && docBasepath.value != null)
|
||||
FutureBuilder<bool>(
|
||||
future:
|
||||
File('${docBasepath.value}/$kAppBackgroundImagePath').exists(),
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData || !snapshot.data!) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return ListTile(
|
||||
minLeadingWidth: 48,
|
||||
title: Text('settingsBackgroundImageEnable').tr(),
|
||||
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
||||
leading: const Icon(Symbols.image),
|
||||
trailing: Switch(
|
||||
value: settings.showBackgroundImage,
|
||||
onChanged: (value) {
|
||||
ref
|
||||
.read(appSettingsNotifierProvider.notifier)
|
||||
.setShowBackgroundImage(value);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
// Clear background image option
|
||||
if (!kIsWeb && docBasepath.value != null)
|
||||
FutureBuilder<bool>(
|
||||
@@ -426,63 +453,8 @@ class SettingsScreen extends HookConsumerWidget {
|
||||
];
|
||||
|
||||
// Desktop-specific settings
|
||||
final desktopSettings =
|
||||
!isDesktop
|
||||
? <Widget>[]
|
||||
: <Widget>[
|
||||
ListTile(
|
||||
minLeadingWidth: 48,
|
||||
title: Text('settingsKeyboardShortcuts').tr(),
|
||||
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
||||
leading: const Icon(Symbols.keyboard),
|
||||
onTap: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder:
|
||||
(context) => AlertDialog(
|
||||
title: Text('settingsKeyboardShortcuts').tr(),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_ShortcutRow(
|
||||
shortcut: 'Ctrl+F',
|
||||
description:
|
||||
'settingsKeyboardShortcutSearch'.tr(),
|
||||
),
|
||||
_ShortcutRow(
|
||||
shortcut: 'Ctrl+,',
|
||||
description:
|
||||
'settingsKeyboardShortcutSettings'.tr(),
|
||||
),
|
||||
_ShortcutRow(
|
||||
shortcut: 'Ctrl+N',
|
||||
description:
|
||||
'settingsKeyboardShortcutNewMessage'.tr(),
|
||||
),
|
||||
_ShortcutRow(
|
||||
shortcut: 'Esc',
|
||||
description:
|
||||
'settingsKeyboardShortcutCloseDialog'
|
||||
.tr(),
|
||||
),
|
||||
// Add more shortcuts as needed
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text('close').tr(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
),
|
||||
];
|
||||
// But nothing for now
|
||||
final desktopSettings = !isDesktop ? <Widget>[] : <Widget>[];
|
||||
|
||||
// Create a responsive layout based on screen width
|
||||
Widget buildSettingsList() {
|
||||
@@ -553,34 +525,7 @@ class SettingsScreen extends HookConsumerWidget {
|
||||
|
||||
return AppScaffold(
|
||||
isNoBackground: false,
|
||||
appBar: AppBar(
|
||||
title: Text('settings').tr(),
|
||||
actions:
|
||||
isDesktop
|
||||
? [
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.help_outline),
|
||||
onPressed: () {
|
||||
// Show help dialog
|
||||
showDialog(
|
||||
context: context,
|
||||
builder:
|
||||
(context) => AlertDialog(
|
||||
title: Text('settingsHelp').tr(),
|
||||
content: Text('settingsHelpContent').tr(),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text('close').tr(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
]
|
||||
: null,
|
||||
),
|
||||
appBar: AppBar(title: Text('settings').tr()),
|
||||
body: Focus(
|
||||
autofocus: true,
|
||||
onKeyEvent: (node, event) {
|
||||
@@ -630,35 +575,3 @@ class _SettingsSection extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper widget for displaying keyboard shortcuts
|
||||
class _ShortcutRow extends StatelessWidget {
|
||||
final String shortcut;
|
||||
final String description;
|
||||
|
||||
const _ShortcutRow({required this.shortcut, required this.description});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.outline.withOpacity(0.5),
|
||||
),
|
||||
),
|
||||
child: Text(shortcut, style: TextStyle(fontFamily: 'monospace')),
|
||||
),
|
||||
SizedBox(width: 16),
|
||||
Text(description),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -77,7 +77,7 @@ class MarketplaceStickersScreen extends HookConsumerWidget {
|
||||
searchController.clear();
|
||||
}
|
||||
return null;
|
||||
}, [query.value]);
|
||||
}, [query]);
|
||||
|
||||
// Clean up timer on dispose
|
||||
useEffect(() {
|
||||
|
58
lib/screens/tray_manager.dart
Normal file
58
lib/screens/tray_manager.dart
Normal file
@@ -0,0 +1,58 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:tray_manager/tray_manager.dart';
|
||||
|
||||
class TrayService {
|
||||
TrayService._();
|
||||
|
||||
static final TrayService _instance = TrayService._();
|
||||
|
||||
static TrayService get instance => _instance;
|
||||
|
||||
bool _checkPlatformAvalability() {
|
||||
if (kIsWeb) return false;
|
||||
if (Platform.isAndroid || Platform.isIOS) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<void> initialize(TrayListener listener) async {
|
||||
if (!_checkPlatformAvalability()) return;
|
||||
|
||||
await trayManager.setIcon(
|
||||
Platform.isWindows
|
||||
? 'assets/icons/icon.ico'
|
||||
: 'assets/icons/icon-outline.svg',
|
||||
);
|
||||
|
||||
final menu = Menu(
|
||||
items: [
|
||||
MenuItem(key: 'show_window', label: 'Show Window'),
|
||||
MenuItem.separator(),
|
||||
MenuItem(key: 'exit_app', label: 'Exit App'),
|
||||
],
|
||||
);
|
||||
await trayManager.setContextMenu(menu);
|
||||
|
||||
trayManager.addListener(listener);
|
||||
}
|
||||
|
||||
Future<void> dispose(TrayListener listener) async {
|
||||
if (!_checkPlatformAvalability()) return;
|
||||
|
||||
trayManager.removeListener(listener);
|
||||
await trayManager.destroy();
|
||||
}
|
||||
|
||||
void handleAction(MenuItem item) {
|
||||
switch (item.key) {
|
||||
case 'show_window':
|
||||
appWindow.show();
|
||||
break;
|
||||
case 'exit_app':
|
||||
appWindow.close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@@ -6,6 +6,7 @@ import 'package:dio/dio.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:island/main.dart';
|
||||
@@ -16,54 +17,159 @@ import 'package:island/widgets/app_notification.dart';
|
||||
import 'package:top_snackbar_flutter/top_snack_bar.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
||||
FlutterLocalNotificationsPlugin();
|
||||
|
||||
AppLifecycleState _appLifecycleState = AppLifecycleState.resumed;
|
||||
|
||||
void _onAppLifecycleChanged(AppLifecycleState state) {
|
||||
_appLifecycleState = state;
|
||||
}
|
||||
|
||||
Future<void> initializeLocalNotifications() async {
|
||||
const AndroidInitializationSettings initializationSettingsAndroid =
|
||||
AndroidInitializationSettings('@mipmap/ic_launcher');
|
||||
|
||||
const DarwinInitializationSettings initializationSettingsIOS =
|
||||
DarwinInitializationSettings();
|
||||
|
||||
const DarwinInitializationSettings initializationSettingsMacOS =
|
||||
DarwinInitializationSettings();
|
||||
|
||||
const LinuxInitializationSettings initializationSettingsLinux =
|
||||
LinuxInitializationSettings(defaultActionName: 'Open notification');
|
||||
|
||||
const WindowsInitializationSettings initializationSettingsWindows =
|
||||
WindowsInitializationSettings(
|
||||
appName: 'Island',
|
||||
appUserModelId: 'dev.solsynth.solian',
|
||||
guid: 'dev.solsynth.solian',
|
||||
);
|
||||
|
||||
const InitializationSettings initializationSettings = InitializationSettings(
|
||||
android: initializationSettingsAndroid,
|
||||
iOS: initializationSettingsIOS,
|
||||
macOS: initializationSettingsMacOS,
|
||||
linux: initializationSettingsLinux,
|
||||
windows: initializationSettingsWindows,
|
||||
);
|
||||
|
||||
await flutterLocalNotificationsPlugin.initialize(
|
||||
initializationSettings,
|
||||
onDidReceiveNotificationResponse: (NotificationResponse response) async {
|
||||
final payload = response.payload;
|
||||
if (payload != null) {
|
||||
if (payload.startsWith('/')) {
|
||||
// In-app routes
|
||||
rootNavigatorKey.currentContext?.push(payload);
|
||||
} else {
|
||||
// External URLs
|
||||
launchUrlString(payload);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
WidgetsBinding.instance.addObserver(
|
||||
LifecycleEventHandler(onAppLifecycleChanged: _onAppLifecycleChanged),
|
||||
);
|
||||
}
|
||||
|
||||
class LifecycleEventHandler extends WidgetsBindingObserver {
|
||||
final void Function(AppLifecycleState) onAppLifecycleChanged;
|
||||
|
||||
LifecycleEventHandler({required this.onAppLifecycleChanged});
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
onAppLifecycleChanged(state);
|
||||
}
|
||||
}
|
||||
|
||||
StreamSubscription<WebSocketPacket> setupNotificationListener(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
) {
|
||||
final ws = ref.watch(websocketProvider);
|
||||
return ws.dataStream.listen((pkt) {
|
||||
return ws.dataStream.listen((pkt) async {
|
||||
if (pkt.type == "notifications.new") {
|
||||
final notification = SnNotification.fromJson(pkt.data!);
|
||||
showTopSnackBar(
|
||||
globalOverlay.currentState!,
|
||||
Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 480),
|
||||
child: NotificationCard(notification: notification),
|
||||
if (_appLifecycleState == AppLifecycleState.resumed) {
|
||||
// App is focused, show in-app notification
|
||||
log(
|
||||
'[Notification] Showing in-app notification: ${notification.title}',
|
||||
);
|
||||
showTopSnackBar(
|
||||
globalOverlay.currentState!,
|
||||
Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 480),
|
||||
child: NotificationCard(notification: notification),
|
||||
),
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
if (notification.meta['action_uri'] != null) {
|
||||
var uri = notification.meta['action_uri'] as String;
|
||||
if (uri.startsWith('/')) {
|
||||
// In-app routes
|
||||
rootNavigatorKey.currentContext?.push(
|
||||
notification.meta['action_uri'],
|
||||
);
|
||||
} else {
|
||||
// External URLs
|
||||
launchUrlString(uri);
|
||||
onTap: () {
|
||||
if (notification.meta['action_uri'] != null) {
|
||||
var uri = notification.meta['action_uri'] as String;
|
||||
if (uri.startsWith('/')) {
|
||||
// In-app routes
|
||||
rootNavigatorKey.currentContext?.push(
|
||||
notification.meta['action_uri'],
|
||||
);
|
||||
} else {
|
||||
// External URLs
|
||||
launchUrlString(uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onDismissed: () {},
|
||||
dismissType: DismissType.onSwipe,
|
||||
displayDuration: const Duration(seconds: 5),
|
||||
snackBarPosition: SnackBarPosition.top,
|
||||
padding: EdgeInsets.only(
|
||||
left: 16,
|
||||
right: 16,
|
||||
top:
|
||||
(!kIsWeb &&
|
||||
(Platform.isMacOS ||
|
||||
Platform.isWindows ||
|
||||
Platform.isLinux))
|
||||
? 28
|
||||
// ignore: use_build_context_synchronously
|
||||
: MediaQuery.of(context).padding.top + 16,
|
||||
bottom: 16,
|
||||
),
|
||||
);
|
||||
},
|
||||
onDismissed: () {},
|
||||
dismissType: DismissType.onSwipe,
|
||||
displayDuration: const Duration(seconds: 5),
|
||||
snackBarPosition: SnackBarPosition.top,
|
||||
padding: EdgeInsets.only(
|
||||
left: 16,
|
||||
right: 16,
|
||||
top:
|
||||
(!kIsWeb &&
|
||||
(Platform.isMacOS ||
|
||||
Platform.isWindows ||
|
||||
Platform.isLinux))
|
||||
? 28
|
||||
// ignore: use_build_context_synchronously
|
||||
: MediaQuery.of(context).padding.top + 16,
|
||||
bottom: 16,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// App is in background, show system notification (only on supported platforms)
|
||||
if (!kIsWeb && !Platform.isIOS) {
|
||||
log(
|
||||
'[Notification] Showing system notification: ${notification.title}',
|
||||
);
|
||||
const AndroidNotificationDetails androidNotificationDetails =
|
||||
AndroidNotificationDetails(
|
||||
'channel_id',
|
||||
'channel_name',
|
||||
channelDescription: 'channel_description',
|
||||
importance: Importance.max,
|
||||
priority: Priority.high,
|
||||
ticker: 'ticker',
|
||||
);
|
||||
const NotificationDetails notificationDetails = NotificationDetails(
|
||||
android: androidNotificationDetails,
|
||||
);
|
||||
await flutterLocalNotificationsPlugin.show(
|
||||
0,
|
||||
notification.title,
|
||||
notification.content,
|
||||
notificationDetails,
|
||||
payload: notification.meta['action_uri'] as String?,
|
||||
);
|
||||
} else {
|
||||
log(
|
||||
'[Notification] Skipping system notification for unsupported platform: ${notification.title}',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -72,7 +178,7 @@ Future<void> subscribePushNotification(
|
||||
Dio apiClient, {
|
||||
bool detailedErrors = false,
|
||||
}) async {
|
||||
if (Platform.isLinux) {
|
||||
if (!kIsWeb && Platform.isLinux) {
|
||||
return;
|
||||
}
|
||||
await FirebaseMessaging.instance.requestPermission(
|
||||
|
@@ -1,5 +1,11 @@
|
||||
import 'package:flutter_udid/flutter_udid.dart';
|
||||
|
||||
String? _cachedUdid;
|
||||
|
||||
Future<String> getUdid() async {
|
||||
return await FlutterUdid.consistentUdid;
|
||||
if (_cachedUdid != null) {
|
||||
return _cachedUdid!;
|
||||
}
|
||||
_cachedUdid = await FlutterUdid.consistentUdid;
|
||||
return _cachedUdid!;
|
||||
}
|
||||
|
9
lib/utils/format.dart
Normal file
9
lib/utils/format.dart
Normal file
@@ -0,0 +1,9 @@
|
||||
String formatFileSize(int bytes) {
|
||||
if (bytes <= 0) return '0 B';
|
||||
if (bytes < 1024) return '$bytes B';
|
||||
if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(2)} KB';
|
||||
if (bytes < 1024 * 1024 * 1024) {
|
||||
return '${(bytes / (1024 * 1024)).toStringAsFixed(2)} MB';
|
||||
}
|
||||
return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(2)} GB';
|
||||
}
|
@@ -12,6 +12,7 @@ import 'package:island/widgets/content/sheet.dart';
|
||||
import 'package:island/widgets/response.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:island/widgets/extended_refresh_indicator.dart';
|
||||
|
||||
part 'account_devices.g.dart';
|
||||
|
||||
@@ -177,7 +178,7 @@ class AccountSessionSheet extends HookConsumerWidget {
|
||||
titleText: 'authSessions'.tr(),
|
||||
child: authDevices.when(
|
||||
data:
|
||||
(data) => RefreshIndicator(
|
||||
(data) => ExtendedRefreshIndicator(
|
||||
onRefresh:
|
||||
() => Future.sync(() => ref.invalidate(authDevicesProvider)),
|
||||
child: ListView.builder(
|
||||
|
@@ -37,11 +37,28 @@ class AccountName extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 4,
|
||||
children: [
|
||||
Flexible(child: Text(account.nick, style: nameStyle)),
|
||||
Flexible(
|
||||
child: Text(
|
||||
account.nick,
|
||||
style: nameStyle,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
if (account.perkSubscription != null)
|
||||
StellarMembershipMark(membership: account.perkSubscription!),
|
||||
if (account.profile.verification != null)
|
||||
VerificationMark(mark: account.profile.verification!),
|
||||
if (account.automatedId != null)
|
||||
Tooltip(
|
||||
message: 'accountAutomated'.tr(),
|
||||
child: Icon(
|
||||
Symbols.smart_toy,
|
||||
size: 16,
|
||||
color: nameStyle.color,
|
||||
fill: 1,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -141,7 +158,7 @@ class VerificationStatusCard extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Icon(
|
||||
mark.type == 4
|
||||
@@ -152,7 +169,7 @@ class VerificationStatusCard extends StatelessWidget {
|
||||
size: 32,
|
||||
color: kVerificationMarkColors[mark.type],
|
||||
fill: 1,
|
||||
),
|
||||
).alignment(Alignment.centerLeft),
|
||||
const Gap(8),
|
||||
Text(mark.title ?? 'No title').bold(),
|
||||
Text(mark.description ?? 'descriptionNone'.tr()),
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:flutter_popup_card/flutter_popup_card.dart';
|
||||
@@ -74,27 +75,75 @@ class AccountProfileCard extends HookConsumerWidget {
|
||||
uname: data.name,
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
if (data.profile.timeZone.isNotEmpty)
|
||||
Row(
|
||||
Tooltip(
|
||||
message: 'creditsStatus'.tr(),
|
||||
child: Row(
|
||||
spacing: 6,
|
||||
children: [
|
||||
Icon(
|
||||
Symbols.alarm,
|
||||
Symbols.star,
|
||||
size: 17,
|
||||
fill: 1,
|
||||
).padding(right: 2),
|
||||
Text(
|
||||
getTzInfo(
|
||||
data.profile.timeZone,
|
||||
).$2.formatCustomGlobal('HH:mm'),
|
||||
).fontSize(12),
|
||||
Text(
|
||||
getTzInfo(
|
||||
data.profile.timeZone,
|
||||
).$1.formatOffsetLocal(),
|
||||
'${data.profile.socialCredits.toStringAsFixed(2)} pts',
|
||||
).fontSize(12),
|
||||
switch (data.profile.socialCreditsLevel) {
|
||||
-1 => Text('socialCreditsLevelPoor').tr(),
|
||||
0 => Text('socialCreditsLevelNormal').tr(),
|
||||
1 => Text('socialCreditsLevelGood').tr(),
|
||||
2 => Text('socialCreditsLevelExcellent').tr(),
|
||||
_ => Text('unknown').tr(),
|
||||
}.fontSize(12),
|
||||
],
|
||||
).padding(top: 2),
|
||||
),
|
||||
),
|
||||
if (data.automatedId != null)
|
||||
Row(
|
||||
spacing: 6,
|
||||
children: [
|
||||
Icon(
|
||||
Symbols.smart_toy,
|
||||
size: 17,
|
||||
fill: 1,
|
||||
).padding(right: 2),
|
||||
Text('accountAutomated').tr().fontSize(12),
|
||||
],
|
||||
),
|
||||
if (data.profile.timeZone.isNotEmpty && !kIsWeb)
|
||||
() {
|
||||
try {
|
||||
final tzInfo = getTzInfo(data.profile.timeZone);
|
||||
return Row(
|
||||
spacing: 6,
|
||||
children: [
|
||||
Icon(
|
||||
Symbols.alarm,
|
||||
size: 17,
|
||||
fill: 1,
|
||||
).padding(right: 2),
|
||||
Text(
|
||||
tzInfo.$2.formatCustomGlobal('HH:mm'),
|
||||
).fontSize(12),
|
||||
Text(
|
||||
tzInfo.$1.formatOffsetLocal(),
|
||||
).fontSize(12),
|
||||
],
|
||||
).padding(top: 2);
|
||||
} catch (e) {
|
||||
return Row(
|
||||
spacing: 6,
|
||||
children: [
|
||||
Icon(
|
||||
Symbols.alarm,
|
||||
size: 17,
|
||||
fill: 1,
|
||||
).padding(right: 2),
|
||||
Text('timezoneNotFound'.tr()).fontSize(12),
|
||||
],
|
||||
).padding(top: 2);
|
||||
}
|
||||
}(),
|
||||
if (data.badges.isNotEmpty)
|
||||
BadgeList(badges: data.badges).padding(top: 12),
|
||||
LevelingProgressCard(
|
||||
|
@@ -2,7 +2,9 @@ import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/account.dart';
|
||||
import 'package:island/models/activity.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:island/widgets/account/event_details_widget.dart';
|
||||
import 'package:table_calendar/table_calendar.dart';
|
||||
@@ -87,24 +89,56 @@ class EventCalendarWidget extends HookConsumerWidget {
|
||||
return Center(child: Text(text));
|
||||
},
|
||||
markerBuilder: (context, day, events) {
|
||||
var checkInResult =
|
||||
final checkInResult =
|
||||
events.whereType<SnCheckInResult>().firstOrNull;
|
||||
final statuses = events.whereType<SnAccountStatus>().toList();
|
||||
|
||||
final textColor =
|
||||
isSameDay(selectedDay.value, day)
|
||||
? Colors.white
|
||||
: isSameDay(DateTime.now(), day)
|
||||
? Colors.white
|
||||
: Theme.of(context).colorScheme.onSurface;
|
||||
|
||||
final shadow =
|
||||
isSameDay(selectedDay.value, day) ||
|
||||
isSameDay(DateTime.now(), day)
|
||||
? [
|
||||
Shadow(
|
||||
color: Colors.black.withOpacity(0.5),
|
||||
offset: const Offset(0, 1),
|
||||
blurRadius: 4,
|
||||
),
|
||||
]
|
||||
: null;
|
||||
|
||||
if (checkInResult != null) {
|
||||
return Positioned(
|
||||
top: 32,
|
||||
child: Text(
|
||||
'checkInResultT${checkInResult.level}'.tr(),
|
||||
style: TextStyle(
|
||||
fontSize: 9,
|
||||
color:
|
||||
isSameDay(selectedDay.value, day)
|
||||
? Theme.of(context).colorScheme.onPrimaryContainer
|
||||
: isSameDay(DateTime.now(), day)
|
||||
? Theme.of(
|
||||
context,
|
||||
).colorScheme.onSecondaryContainer
|
||||
: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
child: Row(
|
||||
spacing: 2,
|
||||
children: [
|
||||
Text(
|
||||
'checkInResultT${checkInResult.level}'.tr(),
|
||||
style: TextStyle(
|
||||
fontSize: 9,
|
||||
color: textColor,
|
||||
shadows: shadow,
|
||||
),
|
||||
),
|
||||
if (statuses.isNotEmpty) ...[
|
||||
Icon(
|
||||
switch (statuses.first.attitude) {
|
||||
0 => Symbols.sentiment_satisfied,
|
||||
2 => Symbols.sentiment_dissatisfied,
|
||||
_ => Symbols.sentiment_neutral,
|
||||
},
|
||||
size: 12,
|
||||
color: textColor,
|
||||
shadows: shadow,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:island/models/activity.dart';
|
||||
import 'package:island/services/time.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
@@ -53,6 +54,33 @@ class EventDetailsWidget extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
).padding(top: 8),
|
||||
if (event!.statuses.isNotEmpty) ...[
|
||||
const Gap(16),
|
||||
Text('statusLabel').tr().fontSize(16).bold(),
|
||||
],
|
||||
for (final status in event!.statuses) ...[
|
||||
Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
Icon(switch (status.attitude) {
|
||||
0 => Symbols.sentiment_satisfied,
|
||||
2 => Symbols.sentiment_dissatisfied,
|
||||
_ => Symbols.sentiment_neutral,
|
||||
}),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(status.label),
|
||||
Text(
|
||||
'${status.createdAt.formatSystem()} - ${status.clearedAt?.formatSystem() ?? 'present'.tr()}',
|
||||
).fontSize(11).opacity(0.8),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
).padding(vertical: 8),
|
||||
],
|
||||
],
|
||||
),
|
||||
if (event?.checkInResult == null && (event?.statuses.isEmpty ?? true))
|
||||
|
@@ -60,7 +60,9 @@ class AccountStatusCreationWidget extends HookConsumerWidget {
|
||||
spacing: 4,
|
||||
children: [
|
||||
Icon(Symbols.keyboard_arrow_up),
|
||||
Text('statusCreateHint').tr(),
|
||||
Expanded(
|
||||
child: Text('statusCreateHint', maxLines: 1).tr(),
|
||||
),
|
||||
],
|
||||
),
|
||||
).opacity(0.85),
|
||||
|
@@ -17,8 +17,8 @@ class NotificationCard extends HookConsumerWidget {
|
||||
return Card(
|
||||
elevation: 4,
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(bottom: Radius.circular(8)),
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
@@ -3,6 +3,7 @@ import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
@@ -153,7 +154,7 @@ class _WindowSizeObserver extends WidgetsBindingObserver {
|
||||
|
||||
final rootScaffoldKey = GlobalKey<ScaffoldState>();
|
||||
|
||||
class AppScaffold extends StatelessWidget {
|
||||
class AppScaffold extends HookConsumerWidget {
|
||||
final Widget? body;
|
||||
final PreferredSizeWidget? bottomNavigationBar;
|
||||
final PreferredSizeWidget? bottomSheet;
|
||||
@@ -186,7 +187,14 @@ class AppScaffold extends StatelessWidget {
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final focusNode = useFocusNode();
|
||||
|
||||
useEffect(() {
|
||||
focusNode.requestFocus();
|
||||
return null;
|
||||
}, []);
|
||||
|
||||
final appBarHeight = appBar?.preferredSize.height ?? 0;
|
||||
final safeTop = MediaQuery.of(context).padding.top;
|
||||
|
||||
@@ -201,29 +209,59 @@ class AppScaffold extends StatelessWidget {
|
||||
],
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
extendBody: extendBody ?? true,
|
||||
extendBodyBehindAppBar: true,
|
||||
backgroundColor:
|
||||
noBackground
|
||||
? Colors.transparent
|
||||
: Theme.of(context).scaffoldBackgroundColor,
|
||||
body:
|
||||
noBackground ? content : AppBackground(isRoot: true, child: content),
|
||||
appBar: appBar,
|
||||
bottomNavigationBar: bottomNavigationBar,
|
||||
bottomSheet: bottomSheet,
|
||||
drawer: drawer,
|
||||
endDrawer: endDrawer,
|
||||
floatingActionButton: floatingActionButton,
|
||||
floatingActionButtonAnimator: floatingActionButtonAnimator,
|
||||
floatingActionButtonLocation: floatingActionButtonLocation,
|
||||
onDrawerChanged: onDrawerChanged,
|
||||
onEndDrawerChanged: onEndDrawerChanged,
|
||||
return Shortcuts(
|
||||
shortcuts: <LogicalKeySet, Intent>{
|
||||
LogicalKeySet(LogicalKeyboardKey.escape): const PopIntent(),
|
||||
},
|
||||
child: Actions(
|
||||
actions: <Type, Action<Intent>>{PopIntent: PopAction(context)},
|
||||
child: Focus(
|
||||
focusNode: focusNode,
|
||||
child: Scaffold(
|
||||
extendBody: extendBody ?? true,
|
||||
extendBodyBehindAppBar: true,
|
||||
backgroundColor:
|
||||
noBackground
|
||||
? Colors.transparent
|
||||
: Theme.of(context).scaffoldBackgroundColor,
|
||||
body:
|
||||
noBackground
|
||||
? content
|
||||
: AppBackground(isRoot: true, child: content),
|
||||
appBar: appBar,
|
||||
bottomNavigationBar: bottomNavigationBar,
|
||||
bottomSheet: bottomSheet,
|
||||
drawer: drawer,
|
||||
endDrawer: endDrawer,
|
||||
floatingActionButton: floatingActionButton,
|
||||
floatingActionButtonAnimator: floatingActionButtonAnimator,
|
||||
floatingActionButtonLocation: floatingActionButtonLocation,
|
||||
onDrawerChanged: onDrawerChanged,
|
||||
onEndDrawerChanged: onEndDrawerChanged,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PopIntent extends Intent {
|
||||
const PopIntent();
|
||||
}
|
||||
|
||||
class PopAction extends Action<PopIntent> {
|
||||
final BuildContext context;
|
||||
|
||||
PopAction(this.context);
|
||||
|
||||
@override
|
||||
void invoke(PopIntent intent) {
|
||||
if (context.canPop()) {
|
||||
context.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PageBackButton extends StatelessWidget {
|
||||
final Color? color;
|
||||
final List<Shadow>? shadows;
|
||||
@@ -271,11 +309,12 @@ class AppBackground extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final imageFileAsync = ref.watch(backgroundImageFileProvider);
|
||||
final settings = ref.watch(appSettingsNotifierProvider);
|
||||
|
||||
if (isRoot || !isWideScreen(context)) {
|
||||
return imageFileAsync.when(
|
||||
data: (file) {
|
||||
if (file != null) {
|
||||
if (file != null && settings.showBackgroundImage) {
|
||||
return Container(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: Container(
|
||||
|
@@ -1,15 +1,18 @@
|
||||
import 'dart:async';
|
||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/pods/websocket.dart';
|
||||
import 'package:island/screens/tray_manager.dart';
|
||||
import 'package:island/services/notify.dart';
|
||||
import 'package:island/services/sharing_intent.dart';
|
||||
import 'package:island/services/update_service.dart';
|
||||
import 'package:island/widgets/content/network_status_sheet.dart';
|
||||
import 'package:island/widgets/tour/tour.dart';
|
||||
import 'package:tray_manager/tray_manager.dart';
|
||||
|
||||
class AppWrapper extends HookConsumerWidget {
|
||||
class AppWrapper extends HookConsumerWidget with TrayListener {
|
||||
final Widget child;
|
||||
const AppWrapper({super.key, required this.child});
|
||||
|
||||
@@ -20,10 +23,16 @@ class AppWrapper extends HookConsumerWidget {
|
||||
Future(() {
|
||||
if (context.mounted) ntySubs = setupNotificationListener(context, ref);
|
||||
});
|
||||
|
||||
final sharingService = SharingIntentService();
|
||||
sharingService.initialize(context);
|
||||
|
||||
UpdateService().checkForUpdates(context);
|
||||
|
||||
TrayService.instance.initialize(this);
|
||||
|
||||
return () {
|
||||
TrayService.instance.dispose(this);
|
||||
sharingService.dispose();
|
||||
ntySubs?.cancel();
|
||||
};
|
||||
@@ -52,4 +61,27 @@ class AppWrapper extends HookConsumerWidget {
|
||||
|
||||
return TourTriggerWidget(key: UniqueKey(), child: child);
|
||||
}
|
||||
|
||||
void _trayIconPrimaryAction() {
|
||||
appWindow.show();
|
||||
}
|
||||
|
||||
void _trayIconSecondaryAction() {
|
||||
trayManager.popUpContextMenu();
|
||||
}
|
||||
|
||||
@override
|
||||
void onTrayIconMouseUp() {
|
||||
_trayIconPrimaryAction();
|
||||
}
|
||||
|
||||
@override
|
||||
void onTrayIconRightMouseDown() {
|
||||
_trayIconSecondaryAction();
|
||||
}
|
||||
|
||||
@override
|
||||
void onTrayMenuItemClick(MenuItem menuItem) {
|
||||
TrayService.instance.handleAction(menuItem);
|
||||
}
|
||||
}
|
||||
|
@@ -10,6 +10,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/file.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/services/file.dart';
|
||||
import 'package:island/utils/format.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/widgets/content/cloud_files.dart';
|
||||
import 'package:island/widgets/content/sheet.dart';
|
||||
@@ -284,6 +285,13 @@ class AttachmentPreview extends HookConsumerWidget {
|
||||
Builder(
|
||||
key: ValueKey(item.hashCode),
|
||||
builder: (context) {
|
||||
final fallbackIcon = switch (item.type) {
|
||||
UniversalFileType.video => Symbols.video_file,
|
||||
UniversalFileType.audio => Symbols.audio_file,
|
||||
UniversalFileType.image => Symbols.image,
|
||||
_ => Symbols.insert_drive_file,
|
||||
};
|
||||
|
||||
if (item.isOnCloud) {
|
||||
return CloudFileWidget(item: item.data);
|
||||
} else if (item.data is XFile) {
|
||||
@@ -309,9 +317,23 @@ class AttachmentPreview extends HookConsumerWidget {
|
||||
: Image.file(File(file.path));
|
||||
default:
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Symbols.document_scanner),
|
||||
Icon(fallbackIcon),
|
||||
const Gap(6),
|
||||
Text(file.name),
|
||||
FutureBuilder(
|
||||
future: file.length(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
final size = snapshot.data as int;
|
||||
return Text(
|
||||
formatFileSize(size),
|
||||
).fontSize(11);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -321,7 +343,14 @@ class AttachmentPreview extends HookConsumerWidget {
|
||||
return Image.memory(item.data);
|
||||
default:
|
||||
return Column(
|
||||
children: [const Icon(Symbols.document_scanner)],
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(fallbackIcon),
|
||||
const Gap(6),
|
||||
Text(
|
||||
formatFileSize(item.data.length),
|
||||
).fontSize(11),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -1,9 +1,12 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math' as math;
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:dismissible_page/dismissible_page.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:file_saver/file_saver.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_blurhash/flutter_blurhash.dart';
|
||||
@@ -14,6 +17,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/file.dart';
|
||||
import 'package:island/pods/config.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/utils/format.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/widgets/content/cloud_files.dart';
|
||||
import 'package:island/widgets/content/sensitive.dart';
|
||||
@@ -321,7 +325,7 @@ class CloudFileZoomIn extends HookConsumerWidget {
|
||||
Future<void> saveToGallery() async {
|
||||
try {
|
||||
// Show loading indicator
|
||||
showSnackBar('Saving image to gallery...');
|
||||
showSnackBar('Saving image...');
|
||||
|
||||
// Get the image URL
|
||||
final client = ref.watch(apiClientProvider);
|
||||
@@ -339,25 +343,23 @@ class CloudFileZoomIn extends HookConsumerWidget {
|
||||
filePath,
|
||||
queryParameters: {'original': true},
|
||||
);
|
||||
await Gal.putImage(filePath, album: 'Solar Network');
|
||||
|
||||
// Show success message
|
||||
showSnackBar('Image saved to gallery');
|
||||
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
|
||||
// Save to gallery
|
||||
await Gal.putImage(filePath, album: 'Solar Network');
|
||||
// Show success message
|
||||
showSnackBar('Image saved to gallery');
|
||||
} else {
|
||||
await FileSaver.instance.saveFile(
|
||||
name: item.name.isEmpty ? '${item.id}.$extName' : item.name,
|
||||
file: File(filePath),
|
||||
);
|
||||
showSnackBar('Image saved to $filePath');
|
||||
}
|
||||
} catch (e) {
|
||||
showErrorAlert(e);
|
||||
}
|
||||
}
|
||||
|
||||
String formatFileSize(int bytes) {
|
||||
if (bytes <= 0) return '0 B';
|
||||
if (bytes < 1024) return '$bytes B';
|
||||
if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(2)} KB';
|
||||
if (bytes < 1024 * 1024 * 1024) {
|
||||
return '${(bytes / (1024 * 1024)).toStringAsFixed(2)} MB';
|
||||
}
|
||||
return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(2)} GB';
|
||||
}
|
||||
|
||||
void showInfoSheet() {
|
||||
final theme = Theme.of(context);
|
||||
final exifData = item.fileMeta?['exif'] as Map<String, dynamic>? ?? {};
|
||||
@@ -437,7 +439,24 @@ class CloudFileZoomIn extends HookConsumerWidget {
|
||||
).padding(horizontal: 24, vertical: 16),
|
||||
const Divider(height: 1),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.file_present),
|
||||
leading: const Icon(Symbols.tag),
|
||||
title: Text('ID').tr(),
|
||||
subtitle: Text(
|
||||
item.id,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Icons.copy),
|
||||
onPressed: () {
|
||||
Clipboard.setData(ClipboardData(text: item.id));
|
||||
showSnackBar('File ID copied to clipboard');
|
||||
},
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Symbols.file_present),
|
||||
title: Text('Name').tr(),
|
||||
subtitle: Text(
|
||||
item.name,
|
||||
@@ -623,6 +642,10 @@ class CloudFileZoomIn extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
final shadow = [
|
||||
Shadow(color: Colors.black54, blurRadius: 5.0, offset: Offset(1.0, 1.0)),
|
||||
];
|
||||
|
||||
return DismissiblePage(
|
||||
isFullScreen: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
@@ -660,22 +683,17 @@ class CloudFileZoomIn extends HookConsumerWidget {
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.save_alt,
|
||||
color: Colors.white,
|
||||
shadows: [
|
||||
Shadow(
|
||||
color: Colors.black54,
|
||||
blurRadius: 5.0,
|
||||
offset: Offset(1.0, 1.0),
|
||||
),
|
||||
],
|
||||
if (!kIsWeb)
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.save_alt,
|
||||
color: Colors.white,
|
||||
shadows: shadow,
|
||||
),
|
||||
onPressed: () async {
|
||||
saveToGallery();
|
||||
},
|
||||
),
|
||||
onPressed: () async {
|
||||
saveToGallery();
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
showOriginal.value = !showOriginal.value;
|
||||
@@ -683,29 +701,13 @@ class CloudFileZoomIn extends HookConsumerWidget {
|
||||
icon: Icon(
|
||||
showOriginal.value ? Symbols.hd : Symbols.sd,
|
||||
color: Colors.white,
|
||||
shadows: [
|
||||
Shadow(
|
||||
color: Colors.black54,
|
||||
blurRadius: 5.0,
|
||||
offset: Offset(1.0, 1.0),
|
||||
),
|
||||
],
|
||||
shadows: shadow,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.close,
|
||||
color: Colors.white,
|
||||
shadows: [
|
||||
Shadow(
|
||||
color: Colors.black54,
|
||||
blurRadius: 5.0,
|
||||
offset: Offset(1.0, 1.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
icon: Icon(Icons.close, color: Colors.white, shadows: shadow),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
],
|
||||
@@ -722,26 +724,24 @@ class CloudFileZoomIn extends HookConsumerWidget {
|
||||
icon: Icon(
|
||||
Icons.info_outline,
|
||||
color: Colors.white,
|
||||
shadows: [
|
||||
Shadow(
|
||||
color: Colors.black54,
|
||||
blurRadius: 5.0,
|
||||
offset: Offset(1.0, 1.0),
|
||||
),
|
||||
],
|
||||
shadows: shadow,
|
||||
),
|
||||
onPressed: showInfoSheet,
|
||||
),
|
||||
Spacer(),
|
||||
IconButton(
|
||||
icon: Icon(Icons.remove, color: Colors.white),
|
||||
icon: Icon(
|
||||
Icons.remove,
|
||||
color: Colors.white,
|
||||
shadows: shadow,
|
||||
),
|
||||
onPressed: () {
|
||||
photoViewController.scale =
|
||||
(photoViewController.scale ?? 1) - 0.05;
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.add, color: Colors.white),
|
||||
icon: Icon(Icons.add, color: Colors.white, shadows: shadow),
|
||||
onPressed: () {
|
||||
photoViewController.scale =
|
||||
(photoViewController.scale ?? 1) + 0.05;
|
||||
@@ -752,13 +752,7 @@ class CloudFileZoomIn extends HookConsumerWidget {
|
||||
icon: Icon(
|
||||
Icons.rotate_left,
|
||||
color: Colors.white,
|
||||
shadows: [
|
||||
Shadow(
|
||||
color: Colors.black54,
|
||||
blurRadius: 5.0,
|
||||
offset: Offset(1.0, 1.0),
|
||||
),
|
||||
],
|
||||
shadows: shadow,
|
||||
),
|
||||
onPressed: () {
|
||||
rotation.value = (rotation.value - 1) % 4;
|
||||
|
@@ -1,15 +1,19 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/file.dart';
|
||||
import 'package:island/pods/config.dart';
|
||||
import 'package:island/services/time.dart';
|
||||
import 'package:island/utils/format.dart';
|
||||
import 'package:island/widgets/content/audio.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
import 'image.dart';
|
||||
import 'video.dart';
|
||||
@@ -60,7 +64,45 @@ class CloudFileWidget extends HookConsumerWidget {
|
||||
child: UniversalAudio(uri: uri, filename: item.name),
|
||||
),
|
||||
),
|
||||
_ => Text('Unable render for ${item.mimeType}'),
|
||||
_ => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Symbols.insert_drive_file,
|
||||
size: 48,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
const Gap(8),
|
||||
Text(
|
||||
item.name,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
formatFileSize(item.size),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const Gap(8),
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
launchUrlString(
|
||||
'https://fs.solian.app/files/${item.id}',
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
},
|
||||
icon: const Icon(Symbols.launch),
|
||||
label: Text('openInBrowser').tr(),
|
||||
),
|
||||
],
|
||||
).padding(all: 8),
|
||||
};
|
||||
|
||||
if (heroTag != null) {
|
||||
|
@@ -52,12 +52,10 @@ class UniversalImage extends StatelessWidget {
|
||||
},
|
||||
errorWidget: (context, url, error) {
|
||||
return Image.asset(
|
||||
'assets/images/media-offline.png',
|
||||
'assets/images/media-offline.jpg',
|
||||
fit: BoxFit.cover,
|
||||
key: Key('image-broke-$uri'),
|
||||
);
|
||||
// return const Center(
|
||||
// child: Icon(Icons.broken_image, color: Colors.white, size: 16),
|
||||
// );
|
||||
},
|
||||
),
|
||||
],
|
||||
|
67
lib/widgets/extended_refresh_indicator.dart
Normal file
67
lib/widgets/extended_refresh_indicator.dart
Normal file
@@ -0,0 +1,67 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class RefreshIntent extends Intent {
|
||||
const RefreshIntent();
|
||||
}
|
||||
|
||||
class ExtendedRefreshIndicator extends StatefulWidget {
|
||||
final Widget child;
|
||||
final RefreshCallback onRefresh;
|
||||
|
||||
const ExtendedRefreshIndicator({
|
||||
super.key,
|
||||
required this.child,
|
||||
required this.onRefresh,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ExtendedRefreshIndicator> createState() =>
|
||||
_ExtendedRefreshIndicatorState();
|
||||
}
|
||||
|
||||
class _ExtendedRefreshIndicatorState extends State<ExtendedRefreshIndicator> {
|
||||
late final FocusNode _focusNode;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_focusNode = FocusNode();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_focusNode.requestFocus();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Shortcuts(
|
||||
shortcuts: <LogicalKeySet, Intent>{
|
||||
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyR):
|
||||
const RefreshIntent(),
|
||||
LogicalKeySet(LogicalKeyboardKey.meta, LogicalKeyboardKey.keyR):
|
||||
const RefreshIntent(),
|
||||
LogicalKeySet(LogicalKeyboardKey.f5): const RefreshIntent(),
|
||||
},
|
||||
child: Actions(
|
||||
actions: <Type, Action<Intent>>{
|
||||
RefreshIntent: CallbackAction<RefreshIntent>(
|
||||
onInvoke: (RefreshIntent intent) => widget.onRefresh(),
|
||||
),
|
||||
},
|
||||
child: Focus(
|
||||
focusNode: _focusNode,
|
||||
child: RefreshIndicator(
|
||||
onRefresh: widget.onRefresh,
|
||||
child: widget.child,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
@@ -388,26 +389,32 @@ class ComposeLogic {
|
||||
}
|
||||
|
||||
static Future<void> pickPhotoMedia(WidgetRef ref, ComposeState state) async {
|
||||
final result = await ref
|
||||
.watch(imagePickerProvider)
|
||||
.pickMultiImage(requestFullMetadata: true);
|
||||
if (result.isEmpty) return;
|
||||
final result = await FilePicker.platform.pickFiles(
|
||||
type: FileType.image,
|
||||
allowMultiple: true,
|
||||
allowCompression: false,
|
||||
);
|
||||
if (result == null || result.count == 0) return;
|
||||
state.attachments.value = [
|
||||
...state.attachments.value,
|
||||
...result.map(
|
||||
(e) => UniversalFile(data: e, type: UniversalFileType.image),
|
||||
...result.files.map(
|
||||
(e) => UniversalFile(data: e.xFile, type: UniversalFileType.image),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
static Future<void> pickVideoMedia(WidgetRef ref, ComposeState state) async {
|
||||
final result = await ref
|
||||
.watch(imagePickerProvider)
|
||||
.pickVideo(source: ImageSource.gallery);
|
||||
if (result == null) return;
|
||||
final result = await FilePicker.platform.pickFiles(
|
||||
type: FileType.video,
|
||||
allowMultiple: true,
|
||||
allowCompression: false,
|
||||
);
|
||||
if (result == null || result.count == 0) return;
|
||||
state.attachments.value = [
|
||||
...state.attachments.value,
|
||||
UniversalFile(data: result, type: UniversalFileType.video),
|
||||
...result.files.map(
|
||||
(e) => UniversalFile(data: e.xFile, type: UniversalFileType.video),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
@@ -47,7 +48,7 @@ class PostFeaturedList extends HookConsumerWidget {
|
||||
'PostFeaturedList: isCollapsed changed to ${isCollapsed.value}',
|
||||
);
|
||||
return null;
|
||||
}, [isCollapsed.value]);
|
||||
}, [isCollapsed]);
|
||||
|
||||
useEffect(() {
|
||||
if (featuredPostsAsync.hasValue && featuredPostsAsync.value!.isNotEmpty) {
|
||||
@@ -92,7 +93,7 @@ class PostFeaturedList extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}, [featuredPostsAsync.value]);
|
||||
}, [featuredPostsAsync]);
|
||||
|
||||
return ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
@@ -105,7 +106,7 @@ class PostFeaturedList extends HookConsumerWidget {
|
||||
spacing: 8,
|
||||
children: [
|
||||
const Icon(Symbols.highlight),
|
||||
Text('Highlight Posts'),
|
||||
const Text('highlightPost').tr(),
|
||||
Spacer(),
|
||||
IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
|
@@ -18,6 +18,7 @@ import 'package:island/screens/posts/compose.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/widgets/content/markdown.dart';
|
||||
import 'package:island/widgets/post/post_item_screenshot.dart';
|
||||
import 'package:island/widgets/post/post_pin_sheet.dart';
|
||||
import 'package:island/widgets/post/post_shared.dart';
|
||||
import 'package:island/widgets/safety/abuse_report_helper.dart';
|
||||
import 'package:island/widgets/share/share_sheet.dart';
|
||||
@@ -35,6 +36,7 @@ class PostActionableItem extends HookConsumerWidget {
|
||||
final bool isShowReference;
|
||||
final bool isEmbedReply;
|
||||
final bool isEmbedOpenable;
|
||||
final bool isCompact;
|
||||
final double? borderRadius;
|
||||
final VoidCallback? onRefresh;
|
||||
final Function(SnPost)? onUpdate;
|
||||
@@ -47,6 +49,7 @@ class PostActionableItem extends HookConsumerWidget {
|
||||
this.isShowReference = true,
|
||||
this.isEmbedReply = true,
|
||||
this.isEmbedOpenable = false,
|
||||
this.isCompact = false,
|
||||
this.borderRadius,
|
||||
this.onRefresh,
|
||||
this.onUpdate,
|
||||
@@ -75,6 +78,7 @@ class PostActionableItem extends HookConsumerWidget {
|
||||
isEmbedReply: isEmbedReply,
|
||||
isEmbedOpenable: isEmbedOpenable,
|
||||
isTextSelectable: false,
|
||||
isCompact: isCompact,
|
||||
onRefresh: onRefresh,
|
||||
onUpdate: onUpdate,
|
||||
onOpen: onOpen,
|
||||
@@ -202,6 +206,45 @@ class PostActionableItem extends HookConsumerWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
if (isAuthor && item.pinMode == null)
|
||||
MenuAction(
|
||||
title: 'pinPost'.tr(),
|
||||
image: MenuImage.icon(Symbols.keep),
|
||||
callback: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (context) => PostPinSheet(post: item),
|
||||
).then((value) {
|
||||
if (value is int) {
|
||||
onUpdate?.call(item.copyWith(pinMode: value));
|
||||
}
|
||||
});
|
||||
},
|
||||
)
|
||||
else if (isAuthor && item.pinMode != null)
|
||||
MenuAction(
|
||||
title: 'unpinPost'.tr(),
|
||||
image: MenuImage.icon(Symbols.keep_off),
|
||||
callback: () {
|
||||
showConfirmAlert('unpinPostHint'.tr(), 'unpinPost'.tr()).then(
|
||||
(confirm) async {
|
||||
if (confirm) {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
try {
|
||||
if (context.mounted) showLoadingModal(context);
|
||||
await client.delete('/sphere/posts/${item.id}/pin');
|
||||
onUpdate?.call(item.copyWith(pinMode: null));
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
} finally {
|
||||
if (context.mounted) hideLoadingModal(context);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
MenuSeparator(),
|
||||
MenuAction(
|
||||
title: 'share'.tr(),
|
||||
@@ -258,6 +301,7 @@ class PostItem extends HookConsumerWidget {
|
||||
final bool isEmbedOpenable;
|
||||
final bool isTextSelectable;
|
||||
final bool isTranslatable;
|
||||
final bool isCompact;
|
||||
final VoidCallback? onRefresh;
|
||||
final Function(SnPost)? onUpdate;
|
||||
final VoidCallback? onOpen;
|
||||
@@ -271,6 +315,7 @@ class PostItem extends HookConsumerWidget {
|
||||
this.isEmbedOpenable = false,
|
||||
this.isTextSelectable = true,
|
||||
this.isTranslatable = true,
|
||||
this.isCompact = false,
|
||||
this.onRefresh,
|
||||
this.onUpdate,
|
||||
this.onOpen,
|
||||
@@ -300,7 +345,14 @@ class PostItem extends HookConsumerWidget {
|
||||
final delta = isRemoving ? -1 : 1;
|
||||
final reactionsCount = Map<String, int>.from(item.reactionsCount);
|
||||
reactionsCount[symbol] = (reactionsCount[symbol] ?? 0) + delta;
|
||||
onUpdate?.call(item.copyWith(reactionsCount: reactionsCount));
|
||||
final reactionsMade = Map<String, bool>.from(item.reactionsMade);
|
||||
reactionsMade[symbol] = delta == 1 ? true : false;
|
||||
onUpdate?.call(
|
||||
item.copyWith(
|
||||
reactionsCount: reactionsCount,
|
||||
reactionsMade: reactionsMade,
|
||||
),
|
||||
);
|
||||
HapticFeedback.heavyImpact();
|
||||
});
|
||||
reacting.value = false;
|
||||
@@ -418,54 +470,64 @@ class PostItem extends HookConsumerWidget {
|
||||
PostHeader(
|
||||
item: item,
|
||||
isFullPost: isFullPost,
|
||||
isCompact: isCompact,
|
||||
renderingPadding: renderingPadding,
|
||||
trailing: IconButton(
|
||||
icon:
|
||||
mostReaction == null
|
||||
? const Icon(Symbols.add_reaction)
|
||||
: Badge(
|
||||
label: Center(
|
||||
child: Text(
|
||||
'x${item.reactionsCount[mostReaction]}',
|
||||
style: const TextStyle(fontSize: 11),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
offset: const Offset(4, 20),
|
||||
backgroundColor: Theme.of(
|
||||
context,
|
||||
).colorScheme.primary.withOpacity(0.75),
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
child: Text(
|
||||
kReactionTemplates[mostReaction]?.icon ?? '',
|
||||
style: const TextStyle(fontSize: 20),
|
||||
trailing:
|
||||
isCompact
|
||||
? null
|
||||
: IconButton(
|
||||
icon:
|
||||
mostReaction == null
|
||||
? const Icon(Symbols.add_reaction)
|
||||
: Badge(
|
||||
label: Center(
|
||||
child: Text(
|
||||
'x${item.reactionsCount[mostReaction]}',
|
||||
style: const TextStyle(fontSize: 11),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
offset: const Offset(4, 20),
|
||||
backgroundColor: Theme.of(
|
||||
context,
|
||||
).colorScheme.primary.withOpacity(0.75),
|
||||
textColor:
|
||||
Theme.of(context).colorScheme.onPrimary,
|
||||
child: Text(
|
||||
kReactionTemplates[mostReaction]?.icon ?? '',
|
||||
style: const TextStyle(fontSize: 20),
|
||||
),
|
||||
),
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStatePropertyAll(
|
||||
(item.reactionsMade[mostReaction] ?? false)
|
||||
? Theme.of(
|
||||
context,
|
||||
).colorScheme.primary.withOpacity(0.5)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStatePropertyAll(
|
||||
(item.reactionsMade[mostReaction] ?? false)
|
||||
? Theme.of(context).colorScheme.primary.withOpacity(0.5)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
builder: (BuildContext context) {
|
||||
return _PostReactionSheet(
|
||||
reactionsCount: item.reactionsCount,
|
||||
reactionsMade: item.reactionsMade,
|
||||
onReact: (symbol, attitude) {
|
||||
reactPost(symbol, attitude);
|
||||
onPressed: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
builder: (BuildContext context) {
|
||||
return _PostReactionSheet(
|
||||
reactionsCount: item.reactionsCount,
|
||||
reactionsMade: item.reactionsMade,
|
||||
onReact: (symbol, attitude) {
|
||||
reactPost(symbol, attitude);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
padding: EdgeInsets.zero,
|
||||
visualDensity: const VisualDensity(horizontal: -3, vertical: -3),
|
||||
),
|
||||
padding: EdgeInsets.zero,
|
||||
visualDensity: const VisualDensity(
|
||||
horizontal: -3,
|
||||
vertical: -3,
|
||||
),
|
||||
),
|
||||
),
|
||||
PostBody(
|
||||
item: item,
|
||||
|
@@ -10,6 +10,7 @@ import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/widgets/post/post_item.dart';
|
||||
import 'package:island/widgets/post/post_shared.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:super_context_menu/super_context_menu.dart';
|
||||
|
||||
class PostItemCreator extends HookConsumerWidget {
|
||||
@@ -33,7 +34,7 @@ class PostItemCreator extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final renderingPadding =
|
||||
padding ?? const EdgeInsets.symmetric(horizontal: 16, vertical: 16);
|
||||
padding ?? const EdgeInsets.symmetric(horizontal: 8, vertical: 8);
|
||||
|
||||
return ContextMenuWidget(
|
||||
menuProvider: (_) {
|
||||
@@ -97,18 +98,22 @@ class PostItemCreator extends HookConsumerWidget {
|
||||
context.goNamed('postDetail', pathParameters: {'id': item.id});
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: renderingPadding,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
PostHeader(item: item),
|
||||
PostBody(item: item),
|
||||
ReferencedPostWidget(item: item),
|
||||
const Gap(16),
|
||||
_buildAnalyticsSection(context),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Gap(renderingPadding.vertical),
|
||||
PostHeader(item: item, renderingPadding: renderingPadding),
|
||||
PostBody(item: item, renderingPadding: renderingPadding),
|
||||
ReferencedPostWidget(
|
||||
item: item,
|
||||
renderingPadding: renderingPadding,
|
||||
),
|
||||
const Gap(16),
|
||||
_buildAnalyticsSection(
|
||||
context,
|
||||
).padding(horizontal: renderingPadding.horizontal),
|
||||
Gap(renderingPadding.vertical),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@@ -21,6 +21,7 @@ class PostListNotifier extends _$PostListNotifier
|
||||
int? type,
|
||||
List<String>? categories,
|
||||
List<String>? tags,
|
||||
bool? pinned,
|
||||
bool shuffle = false,
|
||||
}) {
|
||||
return fetch(cursor: null);
|
||||
@@ -40,6 +41,7 @@ class PostListNotifier extends _$PostListNotifier
|
||||
if (tags != null) 'tags': tags,
|
||||
if (categories != null) 'categories': categories,
|
||||
if (shuffle) 'shuffle': true,
|
||||
if (pinned != null) 'pinned': pinned,
|
||||
};
|
||||
|
||||
final response = await client.get(
|
||||
@@ -77,12 +79,14 @@ class SliverPostList extends HookConsumerWidget {
|
||||
final List<String>? categories;
|
||||
final List<String>? tags;
|
||||
final bool shuffle;
|
||||
final bool? pinned;
|
||||
final PostItemType itemType;
|
||||
final Color? backgroundColor;
|
||||
final EdgeInsets? padding;
|
||||
final bool isOpenable;
|
||||
final Function? onRefresh;
|
||||
final Function(SnPost)? onUpdate;
|
||||
final double? maxWidth;
|
||||
|
||||
const SliverPostList({
|
||||
super.key,
|
||||
@@ -92,43 +96,31 @@ class SliverPostList extends HookConsumerWidget {
|
||||
this.categories,
|
||||
this.tags,
|
||||
this.shuffle = false,
|
||||
this.pinned,
|
||||
this.itemType = PostItemType.regular,
|
||||
this.backgroundColor,
|
||||
this.padding,
|
||||
this.isOpenable = true,
|
||||
this.onRefresh,
|
||||
this.onUpdate,
|
||||
this.maxWidth,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final provider = postListNotifierProvider(
|
||||
pubName: pubName,
|
||||
realm: realm,
|
||||
type: type,
|
||||
categories: categories,
|
||||
tags: tags,
|
||||
shuffle: shuffle,
|
||||
pinned: pinned,
|
||||
);
|
||||
return PagingHelperSliverView(
|
||||
provider: postListNotifierProvider(
|
||||
pubName: pubName,
|
||||
realm: realm,
|
||||
type: type,
|
||||
categories: categories,
|
||||
tags: tags,
|
||||
shuffle: shuffle,
|
||||
),
|
||||
futureRefreshable:
|
||||
postListNotifierProvider(
|
||||
pubName: pubName,
|
||||
realm: realm,
|
||||
type: type,
|
||||
categories: categories,
|
||||
tags: tags,
|
||||
shuffle: shuffle,
|
||||
).future,
|
||||
notifierRefreshable:
|
||||
postListNotifierProvider(
|
||||
pubName: pubName,
|
||||
realm: realm,
|
||||
type: type,
|
||||
categories: categories,
|
||||
tags: tags,
|
||||
shuffle: shuffle,
|
||||
).notifier,
|
||||
provider: provider,
|
||||
futureRefreshable: provider.future,
|
||||
notifierRefreshable: provider.notifier,
|
||||
contentBuilder:
|
||||
(data, widgetCount, endItemView) => SliverList.builder(
|
||||
itemCount: widgetCount,
|
||||
@@ -139,6 +131,15 @@ class SliverPostList extends HookConsumerWidget {
|
||||
|
||||
final post = data.items[index];
|
||||
|
||||
if (maxWidth != null) {
|
||||
return Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: maxWidth!),
|
||||
child: _buildPostItem(post),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return _buildPostItem(post);
|
||||
},
|
||||
),
|
||||
|
@@ -6,7 +6,7 @@ part of 'post_list.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$postListNotifierHash() => r'faa0b939fae56367ff120ce63d9deb17b1995c9c';
|
||||
String _$postListNotifierHash() => r'3c0a8154ded4bcd8f5456f7a4ea2e542f57efa85';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
@@ -36,6 +36,7 @@ abstract class _$PostListNotifier
|
||||
late final int? type;
|
||||
late final List<String>? categories;
|
||||
late final List<String>? tags;
|
||||
late final bool? pinned;
|
||||
late final bool shuffle;
|
||||
|
||||
FutureOr<CursorPagingData<SnPost>> build({
|
||||
@@ -44,6 +45,7 @@ abstract class _$PostListNotifier
|
||||
int? type,
|
||||
List<String>? categories,
|
||||
List<String>? tags,
|
||||
bool? pinned,
|
||||
bool shuffle = false,
|
||||
});
|
||||
}
|
||||
@@ -65,6 +67,7 @@ class PostListNotifierFamily
|
||||
int? type,
|
||||
List<String>? categories,
|
||||
List<String>? tags,
|
||||
bool? pinned,
|
||||
bool shuffle = false,
|
||||
}) {
|
||||
return PostListNotifierProvider(
|
||||
@@ -73,6 +76,7 @@ class PostListNotifierFamily
|
||||
type: type,
|
||||
categories: categories,
|
||||
tags: tags,
|
||||
pinned: pinned,
|
||||
shuffle: shuffle,
|
||||
);
|
||||
}
|
||||
@@ -87,6 +91,7 @@ class PostListNotifierFamily
|
||||
type: provider.type,
|
||||
categories: provider.categories,
|
||||
tags: provider.tags,
|
||||
pinned: provider.pinned,
|
||||
shuffle: provider.shuffle,
|
||||
);
|
||||
}
|
||||
@@ -120,6 +125,7 @@ class PostListNotifierProvider
|
||||
int? type,
|
||||
List<String>? categories,
|
||||
List<String>? tags,
|
||||
bool? pinned,
|
||||
bool shuffle = false,
|
||||
}) : this._internal(
|
||||
() =>
|
||||
@@ -129,6 +135,7 @@ class PostListNotifierProvider
|
||||
..type = type
|
||||
..categories = categories
|
||||
..tags = tags
|
||||
..pinned = pinned
|
||||
..shuffle = shuffle,
|
||||
from: postListNotifierProvider,
|
||||
name: r'postListNotifierProvider',
|
||||
@@ -144,6 +151,7 @@ class PostListNotifierProvider
|
||||
type: type,
|
||||
categories: categories,
|
||||
tags: tags,
|
||||
pinned: pinned,
|
||||
shuffle: shuffle,
|
||||
);
|
||||
|
||||
@@ -159,6 +167,7 @@ class PostListNotifierProvider
|
||||
required this.type,
|
||||
required this.categories,
|
||||
required this.tags,
|
||||
required this.pinned,
|
||||
required this.shuffle,
|
||||
}) : super.internal();
|
||||
|
||||
@@ -167,6 +176,7 @@ class PostListNotifierProvider
|
||||
final int? type;
|
||||
final List<String>? categories;
|
||||
final List<String>? tags;
|
||||
final bool? pinned;
|
||||
final bool shuffle;
|
||||
|
||||
@override
|
||||
@@ -179,6 +189,7 @@ class PostListNotifierProvider
|
||||
type: type,
|
||||
categories: categories,
|
||||
tags: tags,
|
||||
pinned: pinned,
|
||||
shuffle: shuffle,
|
||||
);
|
||||
}
|
||||
@@ -195,6 +206,7 @@ class PostListNotifierProvider
|
||||
..type = type
|
||||
..categories = categories
|
||||
..tags = tags
|
||||
..pinned = pinned
|
||||
..shuffle = shuffle,
|
||||
from: from,
|
||||
name: null,
|
||||
@@ -206,6 +218,7 @@ class PostListNotifierProvider
|
||||
type: type,
|
||||
categories: categories,
|
||||
tags: tags,
|
||||
pinned: pinned,
|
||||
shuffle: shuffle,
|
||||
),
|
||||
);
|
||||
@@ -228,6 +241,7 @@ class PostListNotifierProvider
|
||||
other.type == type &&
|
||||
other.categories == categories &&
|
||||
other.tags == tags &&
|
||||
other.pinned == pinned &&
|
||||
other.shuffle == shuffle;
|
||||
}
|
||||
|
||||
@@ -239,6 +253,7 @@ class PostListNotifierProvider
|
||||
hash = _SystemHash.combine(hash, type.hashCode);
|
||||
hash = _SystemHash.combine(hash, categories.hashCode);
|
||||
hash = _SystemHash.combine(hash, tags.hashCode);
|
||||
hash = _SystemHash.combine(hash, pinned.hashCode);
|
||||
hash = _SystemHash.combine(hash, shuffle.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
@@ -264,6 +279,9 @@ mixin PostListNotifierRef
|
||||
/// The parameter `tags` of this provider.
|
||||
List<String>? get tags;
|
||||
|
||||
/// The parameter `pinned` of this provider.
|
||||
bool? get pinned;
|
||||
|
||||
/// The parameter `shuffle` of this provider.
|
||||
bool get shuffle;
|
||||
}
|
||||
@@ -289,6 +307,8 @@ class _PostListNotifierProviderElement
|
||||
@override
|
||||
List<String>? get tags => (origin as PostListNotifierProvider).tags;
|
||||
@override
|
||||
bool? get pinned => (origin as PostListNotifierProvider).pinned;
|
||||
@override
|
||||
bool get shuffle => (origin as PostListNotifierProvider).shuffle;
|
||||
}
|
||||
|
||||
|
124
lib/widgets/post/post_pin_sheet.dart
Normal file
124
lib/widgets/post/post_pin_sheet.dart
Normal file
@@ -0,0 +1,124 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/post.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/widgets/content/sheet.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
class PostPinSheet extends HookConsumerWidget {
|
||||
final SnPost post;
|
||||
const PostPinSheet({super.key, required this.post});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final mode = useState(0);
|
||||
|
||||
Future<void> pinPost() async {
|
||||
try {
|
||||
showLoadingModal(context);
|
||||
final client = ref.watch(apiClientProvider);
|
||||
await client.post(
|
||||
'/sphere/posts/${post.id}/pin',
|
||||
data: {'mode': mode.value},
|
||||
);
|
||||
|
||||
if (context.mounted) Navigator.of(context).pop(mode.value);
|
||||
} catch (e) {
|
||||
showErrorAlert(e);
|
||||
} finally {
|
||||
if (context.mounted) hideLoadingModal(context);
|
||||
}
|
||||
}
|
||||
|
||||
return SheetScaffold(
|
||||
titleText: 'pinPost'.tr(),
|
||||
heightFactor: 0.6,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Publisher page pin option (always available)
|
||||
ListTile(
|
||||
leading: Radio<int>(
|
||||
value: 0,
|
||||
groupValue: mode.value,
|
||||
onChanged: (value) {
|
||||
mode.value = value!;
|
||||
},
|
||||
),
|
||||
title: Text('publisherPage'.tr()),
|
||||
subtitle: Text('pinPostPublisherHint'.tr()),
|
||||
onTap: () {
|
||||
mode.value = 0;
|
||||
},
|
||||
),
|
||||
|
||||
// Realm page pin option (show always, but disabled when not available)
|
||||
ListTile(
|
||||
leading: Radio<int>(
|
||||
value: 1,
|
||||
groupValue: mode.value,
|
||||
onChanged:
|
||||
post.realmId != null && post.realmId!.isNotEmpty
|
||||
? (value) {
|
||||
mode.value = value!;
|
||||
}
|
||||
: null,
|
||||
),
|
||||
title: Text('realmPage'.tr()),
|
||||
subtitle:
|
||||
post.realmId != null && post.realmId!.isNotEmpty
|
||||
? Text('pinPostRealmHint'.tr())
|
||||
: Text('pinPostRealmDisabledHint'.tr()),
|
||||
onTap:
|
||||
post.realmId != null && post.realmId!.isNotEmpty
|
||||
? () {
|
||||
mode.value = 1;
|
||||
}
|
||||
: null,
|
||||
enabled: post.realmId != null && post.realmId!.isNotEmpty,
|
||||
),
|
||||
|
||||
// Reply page pin option (show always, but disabled when not available)
|
||||
// Disabled for now because im being lazy
|
||||
// ListTile(
|
||||
// leading: Radio<int>(
|
||||
// value: 2,
|
||||
// groupValue: mode.value,
|
||||
// onChanged:
|
||||
// post.repliedPostId != null && post.repliedPostId!.isNotEmpty
|
||||
// ? (value) {
|
||||
// mode.value = value!;
|
||||
// }
|
||||
// : null,
|
||||
// ),
|
||||
// title: Text('replyPage'.tr()),
|
||||
// subtitle:
|
||||
// post.repliedPostId != null && post.repliedPostId!.isNotEmpty
|
||||
// ? Text('pinPostReplyHint'.tr())
|
||||
// : Text('pinPostReplyDisabledHint'.tr()),
|
||||
// onTap:
|
||||
// post.repliedPostId != null && post.repliedPostId!.isNotEmpty
|
||||
// ? () {
|
||||
// mode.value = 2;
|
||||
// }
|
||||
// : null,
|
||||
// enabled:
|
||||
// post.repliedPostId != null && post.repliedPostId!.isNotEmpty,
|
||||
// ),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Pin button
|
||||
FilledButton.icon(
|
||||
onPressed: pinPost,
|
||||
icon: const Icon(Symbols.keep),
|
||||
label: Text('pin'.tr()),
|
||||
).padding(horizontal: 24),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@@ -532,6 +532,7 @@ class PostHeader extends StatelessWidget {
|
||||
final bool isInteractive;
|
||||
final EdgeInsets renderingPadding;
|
||||
final bool isRelativeTime;
|
||||
final bool isCompact;
|
||||
|
||||
const PostHeader({
|
||||
super.key,
|
||||
@@ -541,111 +542,140 @@ class PostHeader extends StatelessWidget {
|
||||
this.isInteractive = true,
|
||||
this.renderingPadding = EdgeInsets.zero,
|
||||
this.isRelativeTime = true,
|
||||
this.isCompact = false,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
spacing: 12,
|
||||
return Column(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap:
|
||||
isInteractive
|
||||
? () {
|
||||
context.pushNamed(
|
||||
'publisherProfile',
|
||||
pathParameters: {'name': item.publisher.name},
|
||||
);
|
||||
}
|
||||
: null,
|
||||
child: ProfilePictureWidget(
|
||||
file: item.publisher.picture,
|
||||
radius: 16,
|
||||
borderRadius: item.publisher.type == 0 ? null : 6,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
if (item.pinMode != null)
|
||||
Row(
|
||||
spacing: 4,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
spacing: 4,
|
||||
const Icon(Symbols.keep, size: 15, fill: 1),
|
||||
Text('pinnedPost').tr().fontSize(13),
|
||||
],
|
||||
).opacity(0.8).padding(horizontal: 8, bottom: 4),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
spacing: 12,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap:
|
||||
isInteractive
|
||||
? () {
|
||||
context.pushNamed(
|
||||
'publisherProfile',
|
||||
pathParameters: {'name': item.publisher.name},
|
||||
);
|
||||
}
|
||||
: null,
|
||||
child: ProfilePictureWidget(
|
||||
file: item.publisher.picture,
|
||||
radius: 16,
|
||||
borderRadius: item.publisher.type == 0 ? null : 6,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(item.publisher.nick).bold(),
|
||||
if (item.publisher.verification != null)
|
||||
VerificationMark(mark: item.publisher.verification!),
|
||||
if (item.realm == null)
|
||||
Text('@${item.publisher.name}').fontSize(11)
|
||||
else
|
||||
...([
|
||||
const Icon(Symbols.arrow_right, size: 14),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
spacing: 4,
|
||||
children: [
|
||||
Flexible(
|
||||
child: InkWell(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 5,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
item.realm!.name,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
ProfilePictureWidget(
|
||||
file: item.realm!.picture,
|
||||
fallbackIcon: Symbols.group,
|
||||
radius: 9,
|
||||
child:
|
||||
Text(
|
||||
item.publisher.nick,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
).bold(),
|
||||
),
|
||||
if (item.publisher.verification != null)
|
||||
VerificationMark(mark: item.publisher.verification!),
|
||||
if (item.realm == null)
|
||||
Flexible(
|
||||
child:
|
||||
isCompact
|
||||
? const SizedBox.shrink()
|
||||
: Text(
|
||||
'@${item.publisher.name}',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
).fontSize(11),
|
||||
)
|
||||
else
|
||||
...([
|
||||
const Icon(Symbols.arrow_right, size: 14),
|
||||
Flexible(
|
||||
child: InkWell(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 5,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
item.realm!.name,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
ProfilePictureWidget(
|
||||
file: item.realm!.picture,
|
||||
fallbackIcon: Symbols.group,
|
||||
radius: 9,
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
GoRouter.of(context).pushNamed(
|
||||
'realmDetail',
|
||||
pathParameters: {'slug': item.realm!.slug},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
spacing: 6,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
!isFullPost && isRelativeTime
|
||||
? (item.publishedAt ?? item.createdAt)!
|
||||
.formatRelative(context)
|
||||
: (item.publishedAt ?? item.createdAt)!
|
||||
.formatSystem(),
|
||||
).fontSize(10),
|
||||
if (item.editedAt != null)
|
||||
Text(
|
||||
'editedAt'.tr(
|
||||
args: [
|
||||
!isFullPost && isRelativeTime
|
||||
? item.editedAt!.formatRelative(context)
|
||||
: item.editedAt!.formatSystem(),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
GoRouter.of(context).pushNamed(
|
||||
'realmDetail',
|
||||
pathParameters: {'slug': item.realm!.slug},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
]),
|
||||
).fontSize(10),
|
||||
if (item.visibility != 0)
|
||||
Text(
|
||||
PostVisibilityHelpers.getVisibilityText(
|
||||
item.visibility,
|
||||
).tr(),
|
||||
).fontSize(10),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
spacing: 6,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
!isFullPost && isRelativeTime
|
||||
? (item.publishedAt ?? item.createdAt)!.formatRelative(
|
||||
context,
|
||||
)
|
||||
: (item.publishedAt ?? item.createdAt)!.formatSystem(),
|
||||
).fontSize(10),
|
||||
if (item.editedAt != null)
|
||||
Text(
|
||||
'editedAt'.tr(
|
||||
args: [
|
||||
!isFullPost && isRelativeTime
|
||||
? item.editedAt!.formatRelative(context)
|
||||
: item.editedAt!.formatSystem(),
|
||||
],
|
||||
),
|
||||
).fontSize(10),
|
||||
if (item.visibility != 0)
|
||||
Text(
|
||||
PostVisibilityHelpers.getVisibilityText(
|
||||
item.visibility,
|
||||
).tr(),
|
||||
).fontSize(10),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (trailing != null) trailing!,
|
||||
],
|
||||
),
|
||||
if (trailing != null) trailing!,
|
||||
],
|
||||
).padding(horizontal: renderingPadding.horizontal, bottom: 4);
|
||||
}
|
||||
@@ -750,7 +780,9 @@ class PostBody extends ConsumerWidget {
|
||||
).padding(bottom: 4),
|
||||
MarkdownTextContent(
|
||||
content:
|
||||
item.isTruncated ? '${item.content!}...' : item.content!,
|
||||
item.isTruncated
|
||||
? '${item.content!}...'
|
||||
: item.content ?? '',
|
||||
isSelectable: isTextSelectable,
|
||||
),
|
||||
if (translationSection != null) translationSection!,
|
||||
|
@@ -25,7 +25,7 @@ class PostShuffleScreen extends HookConsumerWidget {
|
||||
return cardSwiperController.dispose;
|
||||
}, []);
|
||||
|
||||
const kBottomControlHeight = 64.0;
|
||||
const kBottomControlHeight = 80.0;
|
||||
|
||||
return AppScaffold(
|
||||
appBar: AppBar(title: const Text('postShuffle').tr()),
|
||||
@@ -36,45 +36,50 @@ class PostShuffleScreen extends HookConsumerWidget {
|
||||
bottom:
|
||||
kBottomControlHeight + MediaQuery.of(context).padding.bottom,
|
||||
),
|
||||
child:
|
||||
(postListState.value?.items.length ?? 0) > 0
|
||||
? CardSwiper(
|
||||
controller: cardSwiperController,
|
||||
cardsCount: postListState.value!.items.length,
|
||||
cardBuilder: (
|
||||
context,
|
||||
index,
|
||||
horizontalOffsetPercentage,
|
||||
verticalOffsetPercentage,
|
||||
) {
|
||||
return Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: 540),
|
||||
child: SingleChildScrollView(
|
||||
child: Card(
|
||||
margin: EdgeInsets.zero,
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(8),
|
||||
),
|
||||
child: PostActionableItem(
|
||||
item: postListState.value!.items[index],
|
||||
),
|
||||
child: Builder(
|
||||
key: ValueKey(postListState.value?.items.length ?? 0),
|
||||
builder: (context) {
|
||||
if ((postListState.value?.items.length ?? 0) > 0) {
|
||||
return CardSwiper(
|
||||
controller: cardSwiperController,
|
||||
cardsCount: postListState.value!.items.length,
|
||||
isLoop: false,
|
||||
cardBuilder: (
|
||||
context,
|
||||
index,
|
||||
horizontalOffsetPercentage,
|
||||
verticalOffsetPercentage,
|
||||
) {
|
||||
return Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: 540),
|
||||
child: SingleChildScrollView(
|
||||
child: Card(
|
||||
margin: EdgeInsets.zero,
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(8),
|
||||
),
|
||||
child: PostActionableItem(
|
||||
item: postListState.value!.items[index],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
onEnd: () {
|
||||
if (postListState.value?.hasMore ?? true) {
|
||||
postListNotifier.fetch(
|
||||
cursor: postListState.value?.nextCursor,
|
||||
);
|
||||
}
|
||||
},
|
||||
)
|
||||
: Center(child: CircularProgressIndicator()),
|
||||
),
|
||||
);
|
||||
},
|
||||
onEnd: () async {
|
||||
if (postListState.value?.hasMore ?? true) {
|
||||
postListNotifier.forceRefresh();
|
||||
}
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 0,
|
||||
|
@@ -12,6 +12,7 @@ import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:flutter_popup_card/flutter_popup_card.dart';
|
||||
import 'package:island/widgets/extended_refresh_indicator.dart';
|
||||
|
||||
part 'picker.g.dart';
|
||||
|
||||
@@ -208,7 +209,7 @@ class _PackSwitcherState extends State<_PackSwitcher> {
|
||||
|
||||
// Content
|
||||
Expanded(
|
||||
child: RefreshIndicator(
|
||||
child: ExtendedRefreshIndicator(
|
||||
onRefresh: widget.onRefresh,
|
||||
child: _StickersGrid(
|
||||
pack: selectedPack,
|
||||
|
@@ -7,6 +7,7 @@
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <bitsdojo_window_linux/bitsdojo_window_plugin.h>
|
||||
#include <file_saver/file_saver_plugin.h>
|
||||
#include <file_selector_linux/file_selector_plugin.h>
|
||||
#include <flutter_platform_alert/flutter_platform_alert_plugin.h>
|
||||
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
|
||||
@@ -21,6 +22,7 @@
|
||||
#include <record_linux/record_linux_plugin.h>
|
||||
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
|
||||
#include <super_native_extensions/super_native_extensions_plugin.h>
|
||||
#include <tray_manager/tray_manager_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
#include <volume_controller/volume_controller_plugin.h>
|
||||
|
||||
@@ -28,6 +30,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) bitsdojo_window_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "BitsdojoWindowPlugin");
|
||||
bitsdojo_window_plugin_register_with_registrar(bitsdojo_window_linux_registrar);
|
||||
g_autoptr(FlPluginRegistrar) file_saver_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSaverPlugin");
|
||||
file_saver_plugin_register_with_registrar(file_saver_registrar);
|
||||
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
|
||||
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
|
||||
@@ -70,6 +75,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) super_native_extensions_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "SuperNativeExtensionsPlugin");
|
||||
super_native_extensions_plugin_register_with_registrar(super_native_extensions_registrar);
|
||||
g_autoptr(FlPluginRegistrar) tray_manager_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "TrayManagerPlugin");
|
||||
tray_manager_plugin_register_with_registrar(tray_manager_registrar);
|
||||
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||
|
@@ -4,6 +4,7 @@
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
bitsdojo_window_linux
|
||||
file_saver
|
||||
file_selector_linux
|
||||
flutter_platform_alert
|
||||
flutter_secure_storage_linux
|
||||
@@ -18,6 +19,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
||||
record_linux
|
||||
sqlite3_flutter_libs
|
||||
super_native_extensions
|
||||
tray_manager
|
||||
url_launcher_linux
|
||||
volume_controller
|
||||
)
|
||||
|
@@ -9,12 +9,14 @@ import bitsdojo_window_macos
|
||||
import connectivity_plus
|
||||
import device_info_plus
|
||||
import file_picker
|
||||
import file_saver
|
||||
import file_selector_macos
|
||||
import firebase_analytics
|
||||
import firebase_core
|
||||
import firebase_crashlytics
|
||||
import firebase_messaging
|
||||
import flutter_inappwebview_macos
|
||||
import flutter_local_notifications
|
||||
import flutter_platform_alert
|
||||
import flutter_secure_storage_macos
|
||||
import flutter_timezone
|
||||
@@ -36,6 +38,7 @@ import sign_in_with_apple
|
||||
import sqflite_darwin
|
||||
import sqlite3_flutter_libs
|
||||
import super_native_extensions
|
||||
import tray_manager
|
||||
import url_launcher_macos
|
||||
import volume_controller
|
||||
import wakelock_plus
|
||||
@@ -45,12 +48,14 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
|
||||
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
||||
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
|
||||
FileSaverPlugin.register(with: registry.registrar(forPlugin: "FileSaverPlugin"))
|
||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||
FirebaseAnalyticsPlugin.register(with: registry.registrar(forPlugin: "FirebaseAnalyticsPlugin"))
|
||||
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
||||
FLTFirebaseCrashlyticsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCrashlyticsPlugin"))
|
||||
FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin"))
|
||||
InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin"))
|
||||
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
||||
FlutterPlatformAlertPlugin.register(with: registry.registrar(forPlugin: "FlutterPlatformAlertPlugin"))
|
||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||
FlutterTimezonePlugin.register(with: registry.registrar(forPlugin: "FlutterTimezonePlugin"))
|
||||
@@ -72,6 +77,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||
Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin"))
|
||||
SuperNativeExtensionsPlugin.register(with: registry.registrar(forPlugin: "SuperNativeExtensionsPlugin"))
|
||||
TrayManagerPlugin.register(with: registry.registrar(forPlugin: "TrayManagerPlugin"))
|
||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||
VolumeControllerPlugin.register(with: registry.registrar(forPlugin: "VolumeControllerPlugin"))
|
||||
WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin"))
|
||||
|
@@ -9,87 +9,89 @@ PODS:
|
||||
- FlutterMacOS
|
||||
- file_picker (0.0.1):
|
||||
- FlutterMacOS
|
||||
- file_saver (0.0.1):
|
||||
- FlutterMacOS
|
||||
- file_selector_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
- Firebase/CoreOnly (12.0.0):
|
||||
- FirebaseCore (~> 12.0.0)
|
||||
- Firebase/Crashlytics (12.0.0):
|
||||
- Firebase/CoreOnly (12.2.0):
|
||||
- FirebaseCore (~> 12.2.0)
|
||||
- Firebase/Crashlytics (12.2.0):
|
||||
- Firebase/CoreOnly
|
||||
- FirebaseCrashlytics (~> 12.0.0)
|
||||
- Firebase/Messaging (12.0.0):
|
||||
- FirebaseCrashlytics (~> 12.2.0)
|
||||
- Firebase/Messaging (12.2.0):
|
||||
- Firebase/CoreOnly
|
||||
- FirebaseMessaging (~> 12.0.0)
|
||||
- firebase_analytics (12.0.0):
|
||||
- FirebaseMessaging (~> 12.2.0)
|
||||
- firebase_analytics (12.0.1):
|
||||
- firebase_core
|
||||
- FirebaseAnalytics (= 12.0.0)
|
||||
- FirebaseAnalytics (= 12.2.0)
|
||||
- FlutterMacOS
|
||||
- firebase_core (4.0.0):
|
||||
- Firebase/CoreOnly (~> 12.0.0)
|
||||
- firebase_core (4.1.0):
|
||||
- Firebase/CoreOnly (~> 12.2.0)
|
||||
- FlutterMacOS
|
||||
- firebase_crashlytics (5.0.0):
|
||||
- Firebase/CoreOnly (~> 12.0.0)
|
||||
- Firebase/Crashlytics (~> 12.0.0)
|
||||
- firebase_crashlytics (5.0.1):
|
||||
- Firebase/CoreOnly (~> 12.2.0)
|
||||
- Firebase/Crashlytics (~> 12.2.0)
|
||||
- firebase_core
|
||||
- FlutterMacOS
|
||||
- firebase_messaging (16.0.0):
|
||||
- Firebase/CoreOnly (~> 12.0.0)
|
||||
- Firebase/Messaging (~> 12.0.0)
|
||||
- firebase_messaging (16.0.1):
|
||||
- Firebase/CoreOnly (~> 12.2.0)
|
||||
- Firebase/Messaging (~> 12.2.0)
|
||||
- firebase_core
|
||||
- FlutterMacOS
|
||||
- FirebaseAnalytics (12.0.0):
|
||||
- FirebaseAnalytics/Default (= 12.0.0)
|
||||
- FirebaseCore (~> 12.0.0)
|
||||
- FirebaseInstallations (~> 12.0.0)
|
||||
- FirebaseAnalytics (12.2.0):
|
||||
- FirebaseAnalytics/Default (= 12.2.0)
|
||||
- FirebaseCore (~> 12.2.0)
|
||||
- FirebaseInstallations (~> 12.2.0)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
||||
- GoogleUtilities/Network (~> 8.1)
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||
- nanopb (~> 3.30910.0)
|
||||
- FirebaseAnalytics/Default (12.0.0):
|
||||
- FirebaseCore (~> 12.0.0)
|
||||
- FirebaseInstallations (~> 12.0.0)
|
||||
- GoogleAppMeasurement/Default (= 12.0.0)
|
||||
- FirebaseAnalytics/Default (12.2.0):
|
||||
- FirebaseCore (~> 12.2.0)
|
||||
- FirebaseInstallations (~> 12.2.0)
|
||||
- GoogleAppMeasurement/Default (= 12.2.0)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
||||
- GoogleUtilities/Network (~> 8.1)
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||
- nanopb (~> 3.30910.0)
|
||||
- FirebaseCore (12.0.0):
|
||||
- FirebaseCoreInternal (~> 12.0.0)
|
||||
- FirebaseCore (12.2.0):
|
||||
- FirebaseCoreInternal (~> 12.2.0)
|
||||
- GoogleUtilities/Environment (~> 8.1)
|
||||
- GoogleUtilities/Logger (~> 8.1)
|
||||
- FirebaseCoreExtension (12.0.0):
|
||||
- FirebaseCore (~> 12.0.0)
|
||||
- FirebaseCoreInternal (12.0.0):
|
||||
- FirebaseCoreExtension (12.2.0):
|
||||
- FirebaseCore (~> 12.2.0)
|
||||
- FirebaseCoreInternal (12.2.0):
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||
- FirebaseCrashlytics (12.0.0):
|
||||
- FirebaseCore (~> 12.0.0)
|
||||
- FirebaseInstallations (~> 12.0.0)
|
||||
- FirebaseRemoteConfigInterop (~> 12.0.0)
|
||||
- FirebaseSessions (~> 12.0.0)
|
||||
- FirebaseCrashlytics (12.2.0):
|
||||
- FirebaseCore (~> 12.2.0)
|
||||
- FirebaseInstallations (~> 12.2.0)
|
||||
- FirebaseRemoteConfigInterop (~> 12.2.0)
|
||||
- FirebaseSessions (~> 12.2.0)
|
||||
- GoogleDataTransport (~> 10.1)
|
||||
- GoogleUtilities/Environment (~> 8.1)
|
||||
- nanopb (~> 3.30910.0)
|
||||
- PromisesObjC (~> 2.4)
|
||||
- FirebaseInstallations (12.0.0):
|
||||
- FirebaseCore (~> 12.0.0)
|
||||
- FirebaseInstallations (12.2.0):
|
||||
- FirebaseCore (~> 12.2.0)
|
||||
- GoogleUtilities/Environment (~> 8.1)
|
||||
- GoogleUtilities/UserDefaults (~> 8.1)
|
||||
- PromisesObjC (~> 2.4)
|
||||
- FirebaseMessaging (12.0.0):
|
||||
- FirebaseCore (~> 12.0.0)
|
||||
- FirebaseInstallations (~> 12.0.0)
|
||||
- FirebaseMessaging (12.2.0):
|
||||
- FirebaseCore (~> 12.2.0)
|
||||
- FirebaseInstallations (~> 12.2.0)
|
||||
- GoogleDataTransport (~> 10.1)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||
- GoogleUtilities/Environment (~> 8.1)
|
||||
- GoogleUtilities/Reachability (~> 8.1)
|
||||
- GoogleUtilities/UserDefaults (~> 8.1)
|
||||
- nanopb (~> 3.30910.0)
|
||||
- FirebaseRemoteConfigInterop (12.0.0)
|
||||
- FirebaseSessions (12.0.0):
|
||||
- FirebaseCore (~> 12.0.0)
|
||||
- FirebaseCoreExtension (~> 12.0.0)
|
||||
- FirebaseInstallations (~> 12.0.0)
|
||||
- FirebaseRemoteConfigInterop (12.2.0)
|
||||
- FirebaseSessions (12.2.0):
|
||||
- FirebaseCore (~> 12.2.0)
|
||||
- FirebaseCoreExtension (~> 12.2.0)
|
||||
- FirebaseInstallations (~> 12.2.0)
|
||||
- GoogleDataTransport (~> 10.1)
|
||||
- GoogleUtilities/Environment (~> 8.1)
|
||||
- GoogleUtilities/UserDefaults (~> 8.1)
|
||||
@@ -98,6 +100,8 @@ PODS:
|
||||
- flutter_inappwebview_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
- OrderedSet (~> 6.0.3)
|
||||
- flutter_local_notifications (0.0.1):
|
||||
- FlutterMacOS
|
||||
- flutter_platform_alert (0.0.1):
|
||||
- FlutterMacOS
|
||||
- flutter_secure_storage_macos (6.1.3):
|
||||
@@ -107,30 +111,30 @@ PODS:
|
||||
- flutter_udid (0.0.1):
|
||||
- FlutterMacOS
|
||||
- SAMKeychain
|
||||
- flutter_webrtc (1.0.0):
|
||||
- flutter_webrtc (1.1.0):
|
||||
- FlutterMacOS
|
||||
- WebRTC-SDK (= 137.7151.02)
|
||||
- WebRTC-SDK (= 137.7151.03)
|
||||
- FlutterMacOS (1.0.0)
|
||||
- gal (1.0.0):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- GoogleAppMeasurement/Core (12.0.0):
|
||||
- GoogleAppMeasurement/Core (12.2.0):
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
||||
- GoogleUtilities/Network (~> 8.1)
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||
- nanopb (~> 3.30910.0)
|
||||
- GoogleAppMeasurement/Default (12.0.0):
|
||||
- GoogleAdsOnDeviceConversion (= 2.1.0)
|
||||
- GoogleAppMeasurement/Core (= 12.0.0)
|
||||
- GoogleAppMeasurement/IdentitySupport (= 12.0.0)
|
||||
- GoogleAppMeasurement/Default (12.2.0):
|
||||
- GoogleAdsOnDeviceConversion (= 2.3.0)
|
||||
- GoogleAppMeasurement/Core (= 12.2.0)
|
||||
- GoogleAppMeasurement/IdentitySupport (= 12.2.0)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
||||
- GoogleUtilities/Network (~> 8.1)
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||
- nanopb (~> 3.30910.0)
|
||||
- GoogleAppMeasurement/IdentitySupport (12.0.0):
|
||||
- GoogleAppMeasurement/Core (= 12.0.0)
|
||||
- GoogleAppMeasurement/IdentitySupport (12.2.0):
|
||||
- GoogleAppMeasurement/Core (= 12.2.0)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
||||
- GoogleUtilities/Network (~> 8.1)
|
||||
@@ -171,7 +175,7 @@ PODS:
|
||||
- livekit_client (2.5.0):
|
||||
- flutter_webrtc
|
||||
- FlutterMacOS
|
||||
- WebRTC-SDK (= 137.7151.02)
|
||||
- WebRTC-SDK (= 137.7151.03)
|
||||
- local_auth_darwin (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
@@ -235,13 +239,15 @@ PODS:
|
||||
- sqlite3/session
|
||||
- super_native_extensions (0.0.1):
|
||||
- FlutterMacOS
|
||||
- tray_manager (0.0.1):
|
||||
- FlutterMacOS
|
||||
- url_launcher_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
- volume_controller (0.0.1):
|
||||
- FlutterMacOS
|
||||
- wakelock_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
- WebRTC-SDK (137.7151.02)
|
||||
- WebRTC-SDK (137.7151.03)
|
||||
|
||||
DEPENDENCIES:
|
||||
- bitsdojo_window_macos (from `Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos`)
|
||||
@@ -249,12 +255,14 @@ DEPENDENCIES:
|
||||
- croppy (from `Flutter/ephemeral/.symlinks/plugins/croppy/macos`)
|
||||
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
|
||||
- file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`)
|
||||
- file_saver (from `Flutter/ephemeral/.symlinks/plugins/file_saver/macos`)
|
||||
- file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`)
|
||||
- firebase_analytics (from `Flutter/ephemeral/.symlinks/plugins/firebase_analytics/macos`)
|
||||
- firebase_core (from `Flutter/ephemeral/.symlinks/plugins/firebase_core/macos`)
|
||||
- firebase_crashlytics (from `Flutter/ephemeral/.symlinks/plugins/firebase_crashlytics/macos`)
|
||||
- firebase_messaging (from `Flutter/ephemeral/.symlinks/plugins/firebase_messaging/macos`)
|
||||
- flutter_inappwebview_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos`)
|
||||
- flutter_local_notifications (from `Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos`)
|
||||
- flutter_platform_alert (from `Flutter/ephemeral/.symlinks/plugins/flutter_platform_alert/macos`)
|
||||
- flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`)
|
||||
- flutter_timezone (from `Flutter/ephemeral/.symlinks/plugins/flutter_timezone/macos`)
|
||||
@@ -277,6 +285,7 @@ DEPENDENCIES:
|
||||
- sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`)
|
||||
- sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin`)
|
||||
- super_native_extensions (from `Flutter/ephemeral/.symlinks/plugins/super_native_extensions/macos`)
|
||||
- tray_manager (from `Flutter/ephemeral/.symlinks/plugins/tray_manager/macos`)
|
||||
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
||||
- volume_controller (from `Flutter/ephemeral/.symlinks/plugins/volume_controller/macos`)
|
||||
- wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`)
|
||||
@@ -315,6 +324,8 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos
|
||||
file_picker:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/file_picker/macos
|
||||
file_saver:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/file_saver/macos
|
||||
file_selector_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos
|
||||
firebase_analytics:
|
||||
@@ -327,6 +338,8 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/firebase_messaging/macos
|
||||
flutter_inappwebview_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos
|
||||
flutter_local_notifications:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos
|
||||
flutter_platform_alert:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/flutter_platform_alert/macos
|
||||
flutter_secure_storage_macos:
|
||||
@@ -371,6 +384,8 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin
|
||||
super_native_extensions:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/super_native_extensions/macos
|
||||
tray_manager:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/tray_manager/macos
|
||||
url_launcher_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
||||
volume_controller:
|
||||
@@ -384,34 +399,36 @@ SPEC CHECKSUMS:
|
||||
croppy: d9bfc8c02f3cd1851f669a421df298a474b78f43
|
||||
device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76
|
||||
file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a
|
||||
file_saver: e35bd97de451dde55ff8c38862ed7ad0f3418d0f
|
||||
file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31
|
||||
Firebase: 800d487043c0557d9faed71477a38d9aafb08a41
|
||||
firebase_analytics: 53f0dc87ad10f56a6df8746da60d8a5fe41f886f
|
||||
firebase_core: eeea10f64026b68cd0bc3dee079ab4717e22909e
|
||||
firebase_crashlytics: 7be1dacc38809971354def57193b280636a3d51a
|
||||
firebase_messaging: 5eefcd5bde556bfacdd9968e11c52f39032dfbe5
|
||||
FirebaseAnalytics: 6d790cd1b159b4eb61a99948df0934ce505a34f7
|
||||
FirebaseCore: 055f4ab117d5964158c833f3d5e7ec6d91648d4a
|
||||
FirebaseCoreExtension: 639afb3de6abd611952be78a794c54a47fa0f361
|
||||
FirebaseCoreInternal: dedc28e569a4be85f38f3d6af1070a2e12018d55
|
||||
FirebaseCrashlytics: db75aa0cab8d00f68406fa247c32fe17ade884d7
|
||||
FirebaseInstallations: d4c7c958f99c8860d7fcece786314ae790e2f988
|
||||
FirebaseMessaging: af49f8d7c0a3d2a017d9302c80946f45a7777dde
|
||||
FirebaseRemoteConfigInterop: bfa0ea72ba3dc5af739777296424e46bd6f42613
|
||||
FirebaseSessions: 4e784acda213108aafef536535cdfc03504acc42
|
||||
Firebase: 26f6f8d460603af3df970ad505b16b15f5e2e9a1
|
||||
firebase_analytics: efe6e51156f4565f3791d99072e8e3b0fcca0e91
|
||||
firebase_core: a8d3b82b0a87bd1d0ebc21e686b37e939c56e6e1
|
||||
firebase_crashlytics: fdbe67a1229a9e583ebf2b155541491aa83927bb
|
||||
firebase_messaging: 6fb526705903e2e56e38a6ff56b43668b052b01b
|
||||
FirebaseAnalytics: e04e23bc070e3014aa5cf4980f9df7ce5cd79ec8
|
||||
FirebaseCore: 311c48a147ad4a0ab7febbaed89e8025c67510cd
|
||||
FirebaseCoreExtension: 73af080c22a2f7b44cefa391dc08f7e4ee162cb5
|
||||
FirebaseCoreInternal: 56ea29f3dad2894f81b060f706f9d53509b6ed3b
|
||||
FirebaseCrashlytics: f83cbf176d5c637ade108c0aacf1ccbd5ec499bf
|
||||
FirebaseInstallations: 3e884b01feabdf67582a80f3250425a00979b4ed
|
||||
FirebaseMessaging: 43ec73bbfedd0c385a849bb91593ab4ad4b9e48e
|
||||
FirebaseRemoteConfigInterop: 0896fd52ab72586a355c8f389ff85aaa9e5375e1
|
||||
FirebaseSessions: f4692789e770bec66ce17d772c0e9561c4f11737
|
||||
flutter_inappwebview_macos: c2d68649f9f8f1831bfcd98d73fd6256366d9d1d
|
||||
flutter_local_notifications: 4bf37a31afde695b56091b4ae3e4d9c7a7e6cda0
|
||||
flutter_platform_alert: 8fa7a7c21f95b26d08b4a3891936ca27e375f284
|
||||
flutter_secure_storage_macos: 7f45e30f838cf2659862a4e4e3ee1c347c2b3b54
|
||||
flutter_timezone: d59eea86178cbd7943cd2431cc2eaa9850f935d8
|
||||
flutter_udid: d26e455e8c06174e6aff476e147defc6cae38495
|
||||
flutter_webrtc: 0d70bd8782c19bde286dc52f766eebbea26de201
|
||||
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||
flutter_webrtc: 1ce7fe9a42f085286378355a575e682edd7f114d
|
||||
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
|
||||
gal: baecd024ebfd13c441269ca7404792a7152fde89
|
||||
GoogleAppMeasurement: 8f6ab04ad6ae493b53fcf56bd26323fb2f1384f3
|
||||
GoogleAppMeasurement: 09f341dfa8527d1612a09cbfe809a242c0b737af
|
||||
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
||||
irondash_engine_context: 893c7d96d20ce361d7e996f39d360c4c2f9869ba
|
||||
livekit_client: 0b0515e03858b86a7c14cc7fd6f772331f6ee84c
|
||||
livekit_client: 5a5c0f1081978542bbf9a986c7ac9bffcdb73906
|
||||
local_auth_darwin: d2e8c53ef0c4f43c646462e3415432c4dab3ae19
|
||||
media_kit_libs_macos_video: 85a23e549b5f480e72cae3e5634b5514bc692f65
|
||||
media_kit_video: fa6564e3799a0a28bff39442334817088b7ca758
|
||||
@@ -431,10 +448,11 @@ SPEC CHECKSUMS:
|
||||
sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b
|
||||
sqlite3_flutter_libs: 83f8e9f5b6554077f1d93119fe20ebaa5f3a9ef1
|
||||
super_native_extensions: c2795d6d9aedf4a79fae25cb6160b71b50549189
|
||||
tray_manager: a104b5c81b578d83f3c3d0f40a997c8b10810166
|
||||
url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673
|
||||
volume_controller: 5c068e6d085c80dadd33fc2c918d2114b775b3dd
|
||||
wakelock_plus: 21ddc249ac4b8d018838dbdabd65c5976c308497
|
||||
WebRTC-SDK: d20de357dcbf7c9696b124b39f3ff62125107e4b
|
||||
WebRTC-SDK: 69d4e56b0b4b27d788e87bab9b9a1326ed05b1e3
|
||||
|
||||
PODFILE CHECKSUM: 346bfb2deb41d4a6ebd6f6799f92188bde2d246f
|
||||
|
||||
|
@@ -586,7 +586,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
@@ -674,7 +674,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = macosx;
|
||||
@@ -724,7 +724,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
|
@@ -32,5 +32,14 @@
|
||||
<string>public.app-category.social-networking</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>NSSupportsAutomaticTermination</key>
|
||||
<false/>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
<true/>
|
||||
<key>UISceneConfigurations</key>
|
||||
<dict/>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
|
238
pubspec.lock
238
pubspec.lock
@@ -13,10 +13,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _flutterfire_internals
|
||||
sha256: bb84ee51e527053dd8e25ecc9f97a6abfdc19130fb4d883e4e8585e23e7e6dd8
|
||||
sha256: "948f7d74f41dd6f2d563ea9f4c21d7ea764f8e047d2b24138974c19c24d37eb6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.60"
|
||||
version: "1.3.61"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -189,10 +189,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: built_value
|
||||
sha256: ba95c961bafcd8686d1cf63be864eb59447e795e124d98d6a27d91fcd13602fb
|
||||
sha256: a30f0a0e38671e89a492c44d005b5545b830a961575bbd8336d42869ff71066d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.11.1"
|
||||
version: "8.12.0"
|
||||
cached_network_image:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -569,6 +569,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.3.2"
|
||||
file_saver:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: file_saver
|
||||
sha256: "9d93db09bd4da9e43238f9dd485360fc51a5c138eea5ef5f407ec56e58079ac0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.1"
|
||||
file_selector_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -605,34 +613,34 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: firebase_analytics
|
||||
sha256: "07146e89e11302c6b07e3465c2c556ebcdd0053a3c5b1aa9bfd3203b778e5b4c"
|
||||
sha256: dde9d6a7b69b07551a77cfb913c81c64804f7602b07541328322c321e73f2a0e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "12.0.0"
|
||||
version: "12.0.1"
|
||||
firebase_analytics_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_analytics_platform_interface
|
||||
sha256: "27e81a0efc821bec6cba64abc1083b91c8ddbad28eeb4c6f6b7c78a59d06f259"
|
||||
sha256: "4008d82a58edcbedec34a7b39f457eed24181cb9c89782c104828c42e4c859b2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.0"
|
||||
version: "5.0.1"
|
||||
firebase_analytics_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_analytics_web
|
||||
sha256: "7d87f47462042a7d9125e3123db2783bc72917d85e2719d4cb6aeaec209605e1"
|
||||
sha256: db2a2e8803f5471a5f89b4abacae95ae27e0644f77526879fb81a2c1abc12b5f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.0"
|
||||
version: "0.6.0+1"
|
||||
firebase_core:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: firebase_core
|
||||
sha256: "6b343e6f7b72a4f32d7ce8df8c9a28d8f54b4ac20d7c6500f3e8b3969afca457"
|
||||
sha256: "967dae9a65f69377beb9f4ab292ea63ce5befa1ce24682cab1b69ca4b7a46927"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
version: "4.1.0"
|
||||
firebase_core_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -645,50 +653,50 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_core_web
|
||||
sha256: "5d28b14dd32282fb7ce2b22b897362453755b6b8541d491127dc72b755bb7b16"
|
||||
sha256: f7ee08febc1c4451588ce58ffcf28edaee857e9a196fee88b85deb889990094a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
version: "3.1.0"
|
||||
firebase_crashlytics:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: firebase_crashlytics
|
||||
sha256: "95b6871850b1a7e3b09c284c59a0c71fafcad3eee8ac1b6f06aaf8979290cbb8"
|
||||
sha256: f2e175a967712ee1f616ab8843390891a315428ba497ce3d256d4c46f32db6f8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.0"
|
||||
version: "5.0.1"
|
||||
firebase_crashlytics_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_crashlytics_platform_interface
|
||||
sha256: ba5b7a916f1ebedc6db35b33abdc618f202fc25e0792088dfba698e19fec9c09
|
||||
sha256: b49b90af4a1fd8f30b58abd90af88371969bea51b62838a4f4e737c2098b725e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.8.11"
|
||||
version: "3.8.12"
|
||||
firebase_messaging:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: firebase_messaging
|
||||
sha256: "10272b553a49c13a6cedfd00121047157521f82a5d3f2a1706b9dd28342cc482"
|
||||
sha256: aad5dcdea5698499b70d74d5a53b1f6a9972f85f97225e4b7ac006dd8d4f9bac
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "16.0.0"
|
||||
version: "16.0.1"
|
||||
firebase_messaging_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_messaging_platform_interface
|
||||
sha256: b846a305feb3f74ee3f0aace447f65a4696bc6550bc828ecf5a84a1b77473d16
|
||||
sha256: "825bc11767bf50a43dccf49b3026f847ec31d0f176139bfc48d662cc128b5014"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.7.0"
|
||||
version: "4.7.1"
|
||||
firebase_messaging_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_messaging_web
|
||||
sha256: "28714749880f7242c5fb3b1ee6c66b41f61453f02ae348b43c82957df80b87ae"
|
||||
sha256: db8dbdd79921245c4de02407e33cae2d1868683be18a5ba948d2af5311e3ef5d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
version: "4.0.1"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -701,10 +709,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: fl_chart
|
||||
sha256: "577aeac8ca414c25333334d7c4bb246775234c0e44b38b10a82b559dd4d764e7"
|
||||
sha256: d3f82f4a38e33ba23d05a08ff304d7d8b22d2a59a5503f20bd802966e915db89
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
version: "1.1.0"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@@ -878,14 +886,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
flutter_langdetect:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_langdetect
|
||||
sha256: "93bd865c7d5723eac614744abb32234ee4f593505a293bc17ef097bd55fbdf38"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.2"
|
||||
flutter_launcher_icons:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@@ -902,6 +902,38 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.0"
|
||||
flutter_local_notifications:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_local_notifications
|
||||
sha256: a9966c850de5e445331b854fa42df96a8020066d67f125a5964cbc6556643f68
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "19.4.1"
|
||||
flutter_local_notifications_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_local_notifications_linux
|
||||
sha256: e3c277b2daab8e36ac5a6820536668d07e83851aeeb79c446e525a70710770a5
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.0"
|
||||
flutter_local_notifications_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_local_notifications_platform_interface
|
||||
sha256: "277d25d960c15674ce78ca97f57d0bae2ee401c844b6ac80fcd972a9c99d09fe"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.1.0"
|
||||
flutter_local_notifications_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_local_notifications_windows
|
||||
sha256: ed46d7ae4ec9d19e4c8fa2badac5fe27ba87a3fe387343ce726f927af074ec98
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
flutter_localizations:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
@@ -959,10 +991,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
sha256: "6382ce712ff69b0f719640ce957559dde459e55ecd433c767e06d139ddf16cab"
|
||||
sha256: b0694b7fb1689b0e6cc193b3f1fcac6423c4f93c74fb20b806c6b6f196db0c31
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.29"
|
||||
version: "2.0.30"
|
||||
flutter_popup_card:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1031,10 +1063,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_svg
|
||||
sha256: cd57f7969b4679317c17af6fd16ee233c1e60a82ed209d8a475c54fd6fd6f845
|
||||
sha256: b9c2ad5872518a27507ab432d1fb97e8813b05f0fc693f9d40fad06d073e0678
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
version: "2.2.1"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
@@ -1073,18 +1105,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_webrtc
|
||||
sha256: "69095ba39b83da3de48286dfc0769aa8e9f10491f70058dc8d8ecc960ef7a260"
|
||||
sha256: "945d0a38b90fbca8257eadb167d8fb9fa7075d9a1939fd2953c10054454d1de2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
version: "1.1.0"
|
||||
font_awesome_flutter:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: font_awesome_flutter
|
||||
sha256: b738e35f8bb4957896c34957baf922f99c5d415b38ddc8b070d14b7fa95715d4
|
||||
sha256: "27af5982e6c510dec1ba038eff634fa284676ee84e3fd807225c80c4ad869177"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.9.1"
|
||||
version: "10.10.0"
|
||||
freezed:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@@ -1145,18 +1177,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: go_router
|
||||
sha256: ced3fdc143c1437234ac3b8e985f3286cf138968bb83ca9a6f94d22f2951c6b9
|
||||
sha256: eb059dfe59f08546e9787f895bd01652076f996bcbf485a8609ef990419ad227
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "16.2.0"
|
||||
version: "16.2.1"
|
||||
google_fonts:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: google_fonts
|
||||
sha256: df9763500dadba0155373e9cb44e202ce21bd9ed5de6bdbd05c5854e86839cb8
|
||||
sha256: ebc94ed30fd13cefd397cb1658b593f21571f014b7d1197eeb41fb95f05d899a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.0"
|
||||
version: "6.3.1"
|
||||
graphs:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1249,10 +1281,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: image_picker_android
|
||||
sha256: e83b2b05141469c5e19d77e1dfa11096b6b1567d09065b2265d7c6904560050c
|
||||
sha256: "28f3987ca0ec702d346eae1d90eda59603a2101b52f1e234ded62cff1d5cfa6e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.13"
|
||||
version: "0.8.13+1"
|
||||
image_picker_for_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1361,26 +1393,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
|
||||
sha256: "8dcda04c3fc16c14f48a7bb586d4be1f0d1572731b6d81d51772ef47c02081e0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.9"
|
||||
version: "11.0.1"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
|
||||
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.9"
|
||||
version: "3.0.10"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
||||
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "3.0.2"
|
||||
lint:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1401,10 +1433,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: livekit_client
|
||||
sha256: b3db2d8afa8d1dbe4fd8dfc965fc9d661cb51a8d864ad199919575ce919a40fb
|
||||
sha256: "011affc0fca22b2f9b0e8827219dad9948f84f2bf057980693de13039de904c7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.0+hotfix.1"
|
||||
version: "2.5.0+hotfix.3"
|
||||
local_auth:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1417,10 +1449,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: local_auth_android
|
||||
sha256: "316503f6772dea9c0c038bb7aac4f68ab00112d707d258c770f7fc3c250a2d88"
|
||||
sha256: "48924f4a8b3cc45994ad5993e2e232d3b00788a305c1bf1c7db32cef281ce9a3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.51"
|
||||
version: "1.0.52"
|
||||
local_auth_darwin:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1449,10 +1481,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: logger
|
||||
sha256: "7ad7215c15420a102ec687bb320a7312afd449bac63bfb1c60d9787c27b9767f"
|
||||
sha256: "55d6c23a6c15db14920e037fe7e0dc32e7cdaf3b64b4b25df2d541b5b6b81c0c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
version: "2.6.1"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1505,10 +1537,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: material_symbols_icons
|
||||
sha256: b1342194e859b2774f920b484c46f54a37a845488e23d570385fbe3ede92ee9f
|
||||
sha256: "2cfd19bf1c3016b0de7298eb3d3444fcb6ef093d934deb870ceb946af89cfa58"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2867.0"
|
||||
version: "4.2872.0"
|
||||
media_kit:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1573,6 +1605,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
menu_base:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: menu_base
|
||||
sha256: "820368014a171bd1241030278e6c2617354f492f5c703d7b7d4570a6b8b84405"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.1"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1713,10 +1753,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9
|
||||
sha256: "993381400e94d18469750e5b9dcb8206f15bc09f9da86b9e44a9b0092a0066db"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.17"
|
||||
version: "2.2.18"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1753,10 +1793,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: petitparser
|
||||
sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646"
|
||||
sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.0"
|
||||
version: "7.0.1"
|
||||
photo_view:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1921,10 +1961,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: record_ios
|
||||
sha256: "895c9467faec72d8e718a3142b51114958f42f18053836a8b484a74f9372f51a"
|
||||
sha256: "13e241ed9cbc220534a40ae6b66222e21288db364d96dd66fb762ebd3cb77c71"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
version: "1.1.2"
|
||||
record_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -2041,10 +2081,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: screen_brightness_android
|
||||
sha256: fb5fa43cb89d0c9b8534556c427db1e97e46594ac5d66ebdcf16063b773d54ed
|
||||
sha256: d34f5321abd03bc3474f4c381f53d189117eba0b039eac1916aa92cca5fd0a96
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
version: "2.1.3"
|
||||
screen_brightness_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -2105,10 +2145,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: "5bcf0772a761b04f8c6bf814721713de6f3e5d9d89caf8d3fe031b02a342379e"
|
||||
sha256: a2608114b1ffdcbc9c120eb71a0e207c71da56202852d4aab8a5e30a82269e74
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.11"
|
||||
version: "2.4.12"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -2165,6 +2205,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
shortid:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shortid
|
||||
sha256: d0b40e3dbb50497dad107e19c54ca7de0d1a274eb9b4404991e443dadb9ebedb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.2"
|
||||
sign_in_with_apple:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -2246,10 +2294,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_android
|
||||
sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b"
|
||||
sha256: ecd684501ebc2ae9a83536e8b15731642b9570dc8623e0073d227d0ee2bfea88
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
version: "2.4.2+2"
|
||||
sqflite_common:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -2294,10 +2342,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqlparser
|
||||
sha256: "7c859c803cf7e9a84d6db918bac824545045692bbe94a6386bd3a45132235d09"
|
||||
sha256: "57090342af1ce32bb499aa641f4ecdd2d6231b9403cea537ac059e803cc20d67"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.41.1"
|
||||
version: "0.41.2"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -2398,10 +2446,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
|
||||
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.4"
|
||||
version: "0.7.6"
|
||||
textfield_tags:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -2435,6 +2483,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.0"
|
||||
tray_manager:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: tray_manager
|
||||
sha256: "537e539f48cd82d8ee2240d4330158c7b44c7e043e8e18b5811f2f8f6b7df25a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.1"
|
||||
tuple:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -2496,10 +2552,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: "0aedad096a85b49df2e4725fa32118f9fa580f3b14af7a2d2221896a02cd5656"
|
||||
sha256: "69ee86740f2847b9a4ba6cffa74ed12ce500bbe2b07f3dc1e643439da60637b7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.17"
|
||||
version: "6.3.18"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -2576,18 +2632,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics_compiler
|
||||
sha256: ca81fdfaf62a5ab45d7296614aea108d2c7d0efca8393e96174bf4d51e6725b0
|
||||
sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.18"
|
||||
version: "1.1.19"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
||||
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
version: "2.2.0"
|
||||
very_good_infinite_list:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -2608,10 +2664,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
|
||||
sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.0.0"
|
||||
version: "15.0.2"
|
||||
volume_controller:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -2640,10 +2696,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: watcher
|
||||
sha256: "0b7fd4a0bbc4b92641dbf20adfd7e3fd1398fe17102d94b674234563e110088a"
|
||||
sha256: "5bf046f41320ac97a469d506261797f35254fa61c641741ef32dacda98b7d39c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
version: "1.1.3"
|
||||
waveform_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -2712,10 +2768,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xml
|
||||
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
|
||||
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.5.0"
|
||||
version: "6.6.1"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -2725,5 +2781,5 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.8.0 <4.0.0"
|
||||
dart: ">=3.9.0 <4.0.0"
|
||||
flutter: ">=3.32.0"
|
||||
|
30
pubspec.yaml
30
pubspec.yaml
@@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 3.2.0+128
|
||||
version: 3.2.0+129
|
||||
|
||||
environment:
|
||||
sdk: ^3.7.2
|
||||
@@ -39,7 +39,7 @@ dependencies:
|
||||
flutter_hooks: ^0.21.3+1
|
||||
hooks_riverpod: ^2.6.1
|
||||
bitsdojo_window: ^0.1.6
|
||||
go_router: ^16.2.0
|
||||
go_router: ^16.2.1
|
||||
styled_widget: ^0.4.1
|
||||
shared_preferences: ^2.5.3
|
||||
flutter_riverpod: ^2.6.1
|
||||
@@ -53,7 +53,7 @@ dependencies:
|
||||
flutter_highlight: ^0.7.0
|
||||
uuid: ^4.5.1
|
||||
url_launcher: ^6.3.2
|
||||
google_fonts: ^6.3.0
|
||||
google_fonts: ^6.3.1
|
||||
gap: ^3.0.1
|
||||
cached_network_image: ^3.4.1
|
||||
web: ^1.1.1
|
||||
@@ -76,14 +76,14 @@ dependencies:
|
||||
file_picker: ^10.3.2
|
||||
riverpod_annotation: ^2.6.1
|
||||
image_picker_platform_interface: ^2.11.0
|
||||
image_picker_android: ^0.8.13
|
||||
image_picker_android: ^0.8.13+1
|
||||
super_context_menu: ^0.9.1
|
||||
modal_bottom_sheet: ^3.0.0
|
||||
firebase_messaging: ^16.0.0
|
||||
firebase_messaging: ^16.0.1
|
||||
flutter_udid: ^4.0.0
|
||||
firebase_core: ^4.0.0
|
||||
firebase_core: ^4.1.0
|
||||
web_socket_channel: ^3.0.3
|
||||
material_symbols_icons: ^4.2867.0
|
||||
material_symbols_icons: ^4.2872.0
|
||||
drift: ^2.28.1
|
||||
drift_flutter: ^0.2.5
|
||||
path: ^1.9.1
|
||||
@@ -103,8 +103,7 @@ dependencies:
|
||||
gal: ^2.3.2
|
||||
dismissible_page: ^1.0.2
|
||||
super_sliver_list: ^0.4.1
|
||||
flutter_webrtc: ^1.0.0
|
||||
livekit_client: ^2.5.0+hotfix.1
|
||||
livekit_client: ^2.5.0+hotfix.3
|
||||
pasteboard: ^0.4.0
|
||||
flutter_colorpicker: ^1.1.0
|
||||
record: ^6.1.1
|
||||
@@ -114,9 +113,9 @@ dependencies:
|
||||
flutter_popup_card: ^0.0.6
|
||||
timezone: ^0.10.1
|
||||
flutter_timezone: ^4.1.1
|
||||
fl_chart: ^1.0.0
|
||||
fl_chart: ^1.1.0
|
||||
sign_in_with_apple: ^7.0.1
|
||||
flutter_svg: ^2.2.0
|
||||
flutter_svg: ^2.2.1
|
||||
native_exif: ^0.6.2
|
||||
local_auth: ^2.3.0
|
||||
flutter_secure_storage: ^9.2.4
|
||||
@@ -131,14 +130,17 @@ dependencies:
|
||||
mime: ^2.0.0
|
||||
html2md: ^1.3.2
|
||||
flutter_typeahead: ^5.2.0
|
||||
flutter_langdetect: ^0.0.2
|
||||
waveform_flutter: ^1.2.0
|
||||
flutter_app_update: ^3.2.2
|
||||
firebase_crashlytics: ^5.0.0
|
||||
firebase_analytics: ^12.0.0
|
||||
firebase_crashlytics: ^5.0.1
|
||||
firebase_analytics: ^12.0.1
|
||||
material_color_utilities: ^0.11.1
|
||||
screenshot: ^3.0.0
|
||||
flutter_card_swiper: ^7.0.2
|
||||
file_saver: ^0.3.1
|
||||
tray_manager: ^0.5.1
|
||||
flutter_webrtc: ^1.1.0
|
||||
flutter_local_notifications: ^19.4.1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <bitsdojo_window_windows/bitsdojo_window_plugin.h>
|
||||
#include <connectivity_plus/connectivity_plus_windows_plugin.h>
|
||||
#include <file_saver/file_saver_plugin.h>
|
||||
#include <file_selector_windows/file_selector_windows.h>
|
||||
#include <firebase_core/firebase_core_plugin_c_api.h>
|
||||
#include <flutter_inappwebview_windows/flutter_inappwebview_windows_plugin_c_api.h>
|
||||
@@ -27,6 +28,7 @@
|
||||
#include <share_plus/share_plus_windows_plugin_c_api.h>
|
||||
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
|
||||
#include <super_native_extensions/super_native_extensions_plugin_c_api.h>
|
||||
#include <tray_manager/tray_manager_plugin.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
#include <volume_controller/volume_controller_plugin_c_api.h>
|
||||
|
||||
@@ -35,6 +37,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
registry->GetRegistrarForPlugin("BitsdojoWindowPlugin"));
|
||||
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
|
||||
FileSaverPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FileSaverPlugin"));
|
||||
FileSelectorWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||
FirebaseCorePluginCApiRegisterWithRegistrar(
|
||||
@@ -73,6 +77,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin"));
|
||||
SuperNativeExtensionsPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("SuperNativeExtensionsPluginCApi"));
|
||||
TrayManagerPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("TrayManagerPlugin"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||
VolumeControllerPluginCApiRegisterWithRegistrar(
|
||||
|
@@ -5,6 +5,7 @@
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
bitsdojo_window_windows
|
||||
connectivity_plus
|
||||
file_saver
|
||||
file_selector_windows
|
||||
firebase_core
|
||||
flutter_inappwebview_windows
|
||||
@@ -24,12 +25,14 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
||||
share_plus
|
||||
sqlite3_flutter_libs
|
||||
super_native_extensions
|
||||
tray_manager
|
||||
url_launcher_windows
|
||||
volume_controller
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
croppy
|
||||
flutter_local_notifications_windows
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
Reference in New Issue
Block a user