Compare commits
66 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
738ed357bf
|
|||
|
0876ab9b74
|
|||
|
7071399cd8
|
|||
|
af23df6e48
|
|||
|
e7e7cc424b
|
|||
|
56ad8f60ea
|
|||
|
026dd3eb01
|
|||
|
72baf0ca5c
|
|||
|
82cb8c7ff9
|
|||
|
a266177628
|
|||
|
2474c7f97c
|
|||
|
1716afd66c
|
|||
|
78a3cd6dd2
|
|||
|
d655840e85
|
|||
|
2e3e988125
|
|||
|
2a94ed5171
|
|||
|
0948810993
|
|||
|
689965c582
|
|||
|
ac82fdb8c8
|
|||
|
d94baab877
|
|||
|
0a179acb13
|
|||
|
33686b83e3
|
|||
|
09abe79f6a
|
|||
|
b0b227f36b
|
|||
|
62a45317a9
|
|||
|
f727882b93
|
|||
|
ba6d6ef97a
|
|||
|
c904826c49
|
|||
|
595aa45378
|
|||
|
a481b1b82f
|
|||
|
2df31e4244
|
|||
|
9c1eb8e5bc
|
|||
|
4d095aa333
|
|||
|
fb62ce7735
|
|||
|
b258df56c9
|
|||
|
2bf54099f9
|
|||
|
eb89d9223a
|
|||
|
87a54625aa
|
|||
|
30b2c0a0b4
|
|||
|
59c34ada40
|
|||
|
67a522753e
|
|||
|
e6338e8a5a
|
|||
|
cb7eef943c
|
|||
|
7a56e7882e
|
|||
|
b0085c2ab0
|
|||
|
d3f990691e
|
|||
|
46a773cfe9
|
|||
|
f5fb5d8a98
|
|||
|
4d87ca7cca
|
|||
|
e16a04bd5a
|
|||
|
d68b39f80f
|
|||
|
b7360f1f91
|
|||
|
5f094aca4b
|
|||
|
6010c17900
|
|||
|
2ee6b3514c
|
|||
|
8c83ee9b88
|
|||
|
18c81503f1
|
|||
|
53137aed3f
|
|||
|
b2aa8b8ec1
|
|||
|
b13a4f5bcf
|
|||
|
8fe703ef6d
|
|||
|
2297fb3c47
|
|||
|
580663dcda
|
|||
|
de20803119
|
|||
|
fb51d2076f
|
|||
|
d8485954fa
|
@@ -32,6 +32,7 @@
|
|||||||
"fieldEmailAddressMustBeValid": "The email address must be valid.",
|
"fieldEmailAddressMustBeValid": "The email address must be valid.",
|
||||||
"logout": "Logout",
|
"logout": "Logout",
|
||||||
"updateYourProfile": "Profile Settings",
|
"updateYourProfile": "Profile Settings",
|
||||||
|
"updateYourProfileDescription": "Adjust how you looks on the Solar Network.",
|
||||||
"accountBasicInfo": "Basic Info",
|
"accountBasicInfo": "Basic Info",
|
||||||
"accountProfile": "Your Profile",
|
"accountProfile": "Your Profile",
|
||||||
"saveChanges": "Save Changes",
|
"saveChanges": "Save Changes",
|
||||||
@@ -69,15 +70,18 @@
|
|||||||
"authFactorPin": "Pin Code",
|
"authFactorPin": "Pin Code",
|
||||||
"authFactorPinDescription": "It consists of 6 digits. It cannot be used to log in. When performing some dangerous operations, the system will ask you to enter this PIN for confirmation.",
|
"authFactorPinDescription": "It consists of 6 digits. It cannot be used to log in. When performing some dangerous operations, the system will ask you to enter this PIN for confirmation.",
|
||||||
"realms": "Realms",
|
"realms": "Realms",
|
||||||
|
"realmsDescription": "Manage realms you've joined.",
|
||||||
"createRealm": "Create a Realm",
|
"createRealm": "Create a Realm",
|
||||||
"createRealmHint": "Meet friends with same interests, build communities, and more.",
|
"createRealmHint": "Meet friends with same interests, build communities, and more.",
|
||||||
"editRealm": "Edit Realm",
|
"editRealm": "Edit Realm",
|
||||||
"deleteRealm": "Delete Realm",
|
"deleteRealm": "Delete Realm",
|
||||||
"deleteRealmHint": "Are you sure to delete this realm? This will also deleted all the channels, publishers, and posts under this realm.",
|
"deleteRealmHint": "Are you sure to delete this realm? This will also deleted all the channels, publishers, and posts under this realm.",
|
||||||
"explore": "Explore",
|
"explore": "Explore",
|
||||||
|
"exploreDescription": "Explore contents on the Solar Network.",
|
||||||
"exploreFilterSubscriptions": "Subscriptions",
|
"exploreFilterSubscriptions": "Subscriptions",
|
||||||
"exploreFilterFriends": "Friends",
|
"exploreFilterFriends": "Friends",
|
||||||
"account": "Account",
|
"account": "Account",
|
||||||
|
"accountDescription": "Information about your account.",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"slug": "Slug",
|
"slug": "Slug",
|
||||||
"slugHint": "The slug will be used in the URL to access this resource, it should be unique and URL safe.",
|
"slugHint": "The slug will be used in the URL to access this resource, it should be unique and URL safe.",
|
||||||
@@ -86,6 +90,7 @@
|
|||||||
"deleteChatRoom": "Delete Room",
|
"deleteChatRoom": "Delete Room",
|
||||||
"deleteChatRoomHint": "Are you sure to delete this room? This action cannot be undone.",
|
"deleteChatRoomHint": "Are you sure to delete this room? This action cannot be undone.",
|
||||||
"chat": "Chat",
|
"chat": "Chat",
|
||||||
|
"chatDescription": "Group Chats and Direct Messages",
|
||||||
"chatTabAll": "All",
|
"chatTabAll": "All",
|
||||||
"chatTabDirect": "Direct Messages",
|
"chatTabDirect": "Direct Messages",
|
||||||
"chatTabGroup": "Group Chats",
|
"chatTabGroup": "Group Chats",
|
||||||
@@ -138,6 +143,7 @@
|
|||||||
"connectionConnected": "Connected",
|
"connectionConnected": "Connected",
|
||||||
"connectionDisconnected": "Disconnected",
|
"connectionDisconnected": "Disconnected",
|
||||||
"connectionReconnecting": "Reconnecting",
|
"connectionReconnecting": "Reconnecting",
|
||||||
|
"connectionServerDown": "Unable to Connect",
|
||||||
"accountConnections": "Account Connections",
|
"accountConnections": "Account Connections",
|
||||||
"accountConnectionsDescription": "Manage your external account connections",
|
"accountConnectionsDescription": "Manage your external account connections",
|
||||||
"accountConnectionAdd": "Add Connection",
|
"accountConnectionAdd": "Add Connection",
|
||||||
@@ -192,7 +198,9 @@
|
|||||||
"statusActivityTitle": "{} is {} {}",
|
"statusActivityTitle": "{} is {} {}",
|
||||||
"statusActivityEndedTitle": "{} is {} {} until {}",
|
"statusActivityEndedTitle": "{} is {} {} until {}",
|
||||||
"appSettings": "App Settings",
|
"appSettings": "App Settings",
|
||||||
|
"appSettingsDescription": "Customize your app.",
|
||||||
"accountSettings": "Account Settings",
|
"accountSettings": "Account Settings",
|
||||||
|
"accountSettingsDescription": "Manage your preferences on the Solar Network.",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"language": "Language",
|
"language": "Language",
|
||||||
"accountLanguageHint": "This language will be used for email and push notifications.",
|
"accountLanguageHint": "This language will be used for email and push notifications.",
|
||||||
@@ -251,6 +259,7 @@
|
|||||||
"translatorBadgeName": "Translator",
|
"translatorBadgeName": "Translator",
|
||||||
"translatorBadgeDescription": "Helping translate Solar Network into different languages",
|
"translatorBadgeDescription": "Helping translate Solar Network into different languages",
|
||||||
"wallet": "Wallet",
|
"wallet": "Wallet",
|
||||||
|
"walletDescription": "Your source point wallet.",
|
||||||
"walletCurrencyPoints": "New Solar Points",
|
"walletCurrencyPoints": "New Solar Points",
|
||||||
"walletCurrencyShortPoints": "NSP",
|
"walletCurrencyShortPoints": "NSP",
|
||||||
"walletCurrencyGolds": "The Solar Dollars",
|
"walletCurrencyGolds": "The Solar Dollars",
|
||||||
@@ -258,6 +267,7 @@
|
|||||||
"retry": "Retry",
|
"retry": "Retry",
|
||||||
"creatorHubUnselectedHint": "Pick / create a publisher to get started.",
|
"creatorHubUnselectedHint": "Pick / create a publisher to get started.",
|
||||||
"relationships": "Relationships",
|
"relationships": "Relationships",
|
||||||
|
"relationshipsDescription": "Friends and connections.",
|
||||||
"addFriend": "Send a Friend Request",
|
"addFriend": "Send a Friend Request",
|
||||||
"addFriendShort": "Add as Friend",
|
"addFriendShort": "Add as Friend",
|
||||||
"addFriendHint": "Add a friend to your relationship list.",
|
"addFriendHint": "Add a friend to your relationship list.",
|
||||||
@@ -301,6 +311,7 @@
|
|||||||
"settingsServerUrl": "Server URL",
|
"settingsServerUrl": "Server URL",
|
||||||
"settingsApplied": "The settings has been applied.",
|
"settingsApplied": "The settings has been applied.",
|
||||||
"notifications": "Notifications",
|
"notifications": "Notifications",
|
||||||
|
"notificationsDescription": "See what's happended related to you recently.",
|
||||||
"posts": "Posts",
|
"posts": "Posts",
|
||||||
"settingsBackgroundImage": "Background Image",
|
"settingsBackgroundImage": "Background Image",
|
||||||
"settingsBackgroundImageClear": "Clear Background Image",
|
"settingsBackgroundImageClear": "Clear Background Image",
|
||||||
@@ -316,7 +327,7 @@
|
|||||||
"settingsAutoTranslate": "Auto Translate",
|
"settingsAutoTranslate": "Auto Translate",
|
||||||
"settingsHideBottomNav": "Hide Bottom Navigation",
|
"settingsHideBottomNav": "Hide Bottom Navigation",
|
||||||
"settingsSoundEffects": "Sound Effects",
|
"settingsSoundEffects": "Sound Effects",
|
||||||
"settingsAprilFoolFeatures": "April Fool Features",
|
"settingsFestivalFeatures": "Festival Limited Features",
|
||||||
"settingsEnterToSend": "Enter to Send",
|
"settingsEnterToSend": "Enter to Send",
|
||||||
"settingsTransparentAppBar": "Transparent App Bar",
|
"settingsTransparentAppBar": "Transparent App Bar",
|
||||||
"settingsCustomFonts": "Custom Fonts",
|
"settingsCustomFonts": "Custom Fonts",
|
||||||
@@ -670,7 +681,9 @@
|
|||||||
"publisherFeatureDevelopHint": "Currently, this feature is under active development, you need send a request to unlock this feature.",
|
"publisherFeatureDevelopHint": "Currently, this feature is under active development, you need send a request to unlock this feature.",
|
||||||
"learnMore": "Learn More",
|
"learnMore": "Learn More",
|
||||||
"webArticlesStand": "Article Stand",
|
"webArticlesStand": "Article Stand",
|
||||||
|
"webArticlesStandDescription": "Explore external sites articles.",
|
||||||
"about": "About",
|
"about": "About",
|
||||||
|
"aboutDescription": "Learn more about the Solar Network.",
|
||||||
"somethingWentWrong": "Something went wrong",
|
"somethingWentWrong": "Something went wrong",
|
||||||
"editedAt": "Edited at {}",
|
"editedAt": "Edited at {}",
|
||||||
"addAudio": "Add audio",
|
"addAudio": "Add audio",
|
||||||
@@ -685,12 +698,13 @@
|
|||||||
"articleAttachmentHint": "Attachments must be uploaded and inserted into the article body to be visible.",
|
"articleAttachmentHint": "Attachments must be uploaded and inserted into the article body to be visible.",
|
||||||
"postVisibility": "Post Visibility",
|
"postVisibility": "Post Visibility",
|
||||||
"currentMembershipMember": "A member of Stellar Program · {}",
|
"currentMembershipMember": "A member of Stellar Program · {}",
|
||||||
"membershipPriceStellar": "1200 NSP per month, level 3+ required",
|
"membershipPriceStellar": "1200 NSP per month, level 20+ required",
|
||||||
"membershipPriceNova": "2400 NSP per month, level 6+ required",
|
"membershipPriceNova": "2400 NSP per month, level 40+ required",
|
||||||
"membershipPriceSupernova": "3600 NSP per month, level 9+ required",
|
"membershipPriceSupernova": "3600 NSP per month, level 60+ required",
|
||||||
"sharePostPhoto": "Share Post as Photo",
|
"sharePostPhoto": "Share Post as Photo",
|
||||||
"wouldYouLikeToNavigateToChat": "Would You like to navigate to the chat?",
|
"wouldYouLikeToNavigateToChat": "Would You like to navigate to the chat?",
|
||||||
"abuseReports": "Abuse Reports",
|
"abuseReports": "Abuse Reports",
|
||||||
|
"abuseReportsDescription": "View and manage abuse reports.",
|
||||||
"membershipCancel": "Cancel Membership",
|
"membershipCancel": "Cancel Membership",
|
||||||
"membershipCancelConfirm": "Are you sure to cancel your membership?",
|
"membershipCancelConfirm": "Are you sure to cancel your membership?",
|
||||||
"membershipCancelHint": "Are you sure to cancel your membership? You will not be charged again. Your membership will remain active until the end of the current billing period. And you will not able to resubscribe until the end of the current subscription ends.",
|
"membershipCancelHint": "Are you sure to cancel your membership? You will not be charged again. Your membership will remain active until the end of the current billing period. And you will not able to resubscribe until the end of the current subscription ends.",
|
||||||
@@ -708,7 +722,7 @@
|
|||||||
"aboutScreenDeveloperSectionTitle": "Developer",
|
"aboutScreenDeveloperSectionTitle": "Developer",
|
||||||
"aboutScreenContactUsTitle": "Contact Us",
|
"aboutScreenContactUsTitle": "Contact Us",
|
||||||
"aboutScreenLicenseTitle": "License",
|
"aboutScreenLicenseTitle": "License",
|
||||||
"aboutScreenLicenseContent": "GNU Affero General Public License v3.0",
|
"aboutScreenLicenseContent": "AGPLv3",
|
||||||
"aboutScreenCopyright": "All rights reserved © Solsynth {}",
|
"aboutScreenCopyright": "All rights reserved © Solsynth {}",
|
||||||
"aboutScreenMadeWith": "Made with ❤︎️ by Solar Network Team",
|
"aboutScreenMadeWith": "Made with ❤︎️ by Solar Network Team",
|
||||||
"aboutScreenFailedToLoadPackageInfo": "Failed to load package info: {error}",
|
"aboutScreenFailedToLoadPackageInfo": "Failed to load package info: {error}",
|
||||||
@@ -762,6 +776,7 @@
|
|||||||
"publisherCannotBeEmpty": "Publisher cannot be empty",
|
"publisherCannotBeEmpty": "Publisher cannot be empty",
|
||||||
"operationFailed": "Operation failed: {}",
|
"operationFailed": "Operation failed: {}",
|
||||||
"stickerMarketplace": "Sticker Marketplace",
|
"stickerMarketplace": "Sticker Marketplace",
|
||||||
|
"stickerMarketplaceDescription": "Browse and add sticker packs from the Solar Network marketplace.",
|
||||||
"stickerPackAdded": "Sticker pack added to your collection",
|
"stickerPackAdded": "Sticker pack added to your collection",
|
||||||
"stickerPackRemoved": "Sticker pack removed from your collection",
|
"stickerPackRemoved": "Sticker pack removed from your collection",
|
||||||
"addPack": "Add Pack",
|
"addPack": "Add Pack",
|
||||||
@@ -789,6 +804,7 @@
|
|||||||
"joinedAt": "Joined at {}",
|
"joinedAt": "Joined at {}",
|
||||||
"searchAccounts": "Search accounts...",
|
"searchAccounts": "Search accounts...",
|
||||||
"webFeeds": "Web Feeds",
|
"webFeeds": "Web Feeds",
|
||||||
|
"webFeedsDescription": "Browse and subscribe to web feeds from the Solar Network.",
|
||||||
"polls": "Polls",
|
"polls": "Polls",
|
||||||
"sharePostSlogan": "Explore more on the Solar Network",
|
"sharePostSlogan": "Explore more on the Solar Network",
|
||||||
"filesListAdditional": {
|
"filesListAdditional": {
|
||||||
@@ -867,6 +883,7 @@
|
|||||||
"contactMethodPublic": "Public",
|
"contactMethodPublic": "Public",
|
||||||
"contactMethodPrivate": "Private",
|
"contactMethodPrivate": "Private",
|
||||||
"discoverRealms": "Realms",
|
"discoverRealms": "Realms",
|
||||||
|
"discoverRealmsDescription": "Discover new realms and join them.",
|
||||||
"discoverPublishers": "Publishers",
|
"discoverPublishers": "Publishers",
|
||||||
"discoverShuffledPost": "Random Posts",
|
"discoverShuffledPost": "Random Posts",
|
||||||
"projects": "Projects",
|
"projects": "Projects",
|
||||||
@@ -910,7 +927,9 @@
|
|||||||
"fileHash": "File Hash",
|
"fileHash": "File Hash",
|
||||||
"exifData": "EXIF Data",
|
"exifData": "EXIF Data",
|
||||||
"postShuffle": "Shuffle Posts",
|
"postShuffle": "Shuffle Posts",
|
||||||
|
"postShuffleDescription": "Shuffle posts to see the posts randomly.",
|
||||||
"leveling": "Leveling",
|
"leveling": "Leveling",
|
||||||
|
"levelingDescription": "See your leveling progress and history.",
|
||||||
"levelingHistory": "Leveling History",
|
"levelingHistory": "Leveling History",
|
||||||
"stellarProgram": "Stellar Program",
|
"stellarProgram": "Stellar Program",
|
||||||
"socialCredits": "Social Credits",
|
"socialCredits": "Social Credits",
|
||||||
@@ -1002,6 +1021,7 @@
|
|||||||
"noResultsFound": "No results found",
|
"noResultsFound": "No results found",
|
||||||
"toggleFilters": "Toggle filters",
|
"toggleFilters": "Toggle filters",
|
||||||
"notableDayNext": "{} is in",
|
"notableDayNext": "{} is in",
|
||||||
|
"notableDayToday": "{} is today!",
|
||||||
"expandPoll": "Expand Poll",
|
"expandPoll": "Expand Poll",
|
||||||
"collapsePoll": "Collapse Poll",
|
"collapsePoll": "Collapse Poll",
|
||||||
"embedView": "Embed View",
|
"embedView": "Embed View",
|
||||||
@@ -1053,6 +1073,7 @@
|
|||||||
"fileSizeExceeded": "File size exceeds the maximum limit of {}",
|
"fileSizeExceeded": "File size exceeds the maximum limit of {}",
|
||||||
"fileTypeNotAccepted": "File type is not accepted by this pool",
|
"fileTypeNotAccepted": "File type is not accepted by this pool",
|
||||||
"files": "Files",
|
"files": "Files",
|
||||||
|
"filesDescription": "Manage your files on the Solar Network Drive.",
|
||||||
"confirmDeleteFile": "Are you sure you want to delete this file?",
|
"confirmDeleteFile": "Are you sure you want to delete this file?",
|
||||||
"deleteFile": "Delete File",
|
"deleteFile": "Delete File",
|
||||||
"failedToDeleteFile": "Failed to delete file",
|
"failedToDeleteFile": "Failed to delete file",
|
||||||
@@ -1117,6 +1138,7 @@
|
|||||||
"installUpdate": "Install update",
|
"installUpdate": "Install update",
|
||||||
"openReleasePage": "Open release page",
|
"openReleasePage": "Open release page",
|
||||||
"postCompose": "Compose Post",
|
"postCompose": "Compose Post",
|
||||||
|
"postComposeDescription": "Compose a new post",
|
||||||
"postPublish": "Publish Post",
|
"postPublish": "Publish Post",
|
||||||
"restoreDraftTitle": "Restore Draft",
|
"restoreDraftTitle": "Restore Draft",
|
||||||
"restoreDraftMessage": "A draft was found. Do you want to restore it?",
|
"restoreDraftMessage": "A draft was found. Do you want to restore it?",
|
||||||
@@ -1319,6 +1341,7 @@
|
|||||||
"backToHub": "Back to Hub",
|
"backToHub": "Back to Hub",
|
||||||
"advancedFilters": "Advanced Filters",
|
"advancedFilters": "Advanced Filters",
|
||||||
"searchPosts": "Search Posts",
|
"searchPosts": "Search Posts",
|
||||||
|
"searchPostsDescription": "Search posts by title, content, or else.",
|
||||||
"sortBy": "Sort by",
|
"sortBy": "Sort by",
|
||||||
"fromDate": "From Date",
|
"fromDate": "From Date",
|
||||||
"toDate": "To Date",
|
"toDate": "To Date",
|
||||||
@@ -1489,10 +1512,35 @@
|
|||||||
"createAccountAgreeTerms": "I've read these terms and agree to the terms of service.",
|
"createAccountAgreeTerms": "I've read these terms and agree to the terms of service.",
|
||||||
"createAccountProfile": "Create your profile",
|
"createAccountProfile": "Create your profile",
|
||||||
"createAccountToS": "Review Terms & Conditions",
|
"createAccountToS": "Review Terms & Conditions",
|
||||||
"accountActivationAlert": "Remember to activate your account",
|
"accountActivationAlert": "Activate your account",
|
||||||
"accountActivationAlertHint": "Unactivated account may leads to various of permission issues, activate your account by clicking the link we sent to your email inbox.",
|
"accountActivationAlertHint": "Unactivated account may leads to various of permission issues, activate your account by clicking the link we sent to your email inbox.",
|
||||||
"accountActivationResendHint": "Didn't see it? Try click the button below to resend one. If you need to update your email while your account was unactivated, feel free to contact our customer service.",
|
"accountActivationResendHint": "Didn't see it? Try click the button below to resend one. If you need to update your email while your account was unactivated, feel free to contact our customer service.",
|
||||||
"accountActivationResend": "Resend",
|
"accountActivationResend": "Resend",
|
||||||
"ipAddress": "IP Address",
|
"ipAddress": "IP Address",
|
||||||
"noFurtherData": "No further data"
|
"noFurtherData": "No further data",
|
||||||
|
"searchAnything": "Search Anything...",
|
||||||
|
"tapToViewAllNotifications": "Tap to view all notifications",
|
||||||
|
"mostRecent": "Most Recent",
|
||||||
|
"noNotificationsYet": "No notifications yet",
|
||||||
|
"recentChats": "Recent Chats",
|
||||||
|
"noFeaturedPostsAvailable": "No featured posts available",
|
||||||
|
"searchChatsAndPages": "Search chats and pages...",
|
||||||
|
"dashboard": "Dashboard",
|
||||||
|
"dashboardDescription": "All your data in one place.",
|
||||||
|
"postTagsCategories": "Post Tags and Categories",
|
||||||
|
"postTagsCategoriesDescription": "Browse posts by category and tags.",
|
||||||
|
"debugLogs": "Debug Logs",
|
||||||
|
"debugLogsDescription": "View debug logs for troubleshooting.",
|
||||||
|
"pinChatRoom": "Pin Chat Room",
|
||||||
|
"pinChatRoomDescription": "Pin this chat room to the top.",
|
||||||
|
"chatRoomPinned": "Chat room pinned successfully.",
|
||||||
|
"chatRoomUnpinned": "Chat room unpinned successfully.",
|
||||||
|
"pinnedChatRoom": "Pinned Rooms",
|
||||||
|
"settingsGroupedChatList": "Grouped Chat List",
|
||||||
|
"settingsNotifyWithHaptic": "Notification with Haptic Feedback",
|
||||||
|
"settingsDashSearchEngine": "Search Engine for web",
|
||||||
|
"settingsDashSearchEngineHelper": "Use %s as the placeholder for the query.",
|
||||||
|
"settingsDefaultScreen": "Default Screen",
|
||||||
|
"notableDayChristmas": "Christmas",
|
||||||
|
"notableDayNewYear": "New Year"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -705,7 +705,7 @@
|
|||||||
"aboutScreenDeveloperSectionTitle": "开发者",
|
"aboutScreenDeveloperSectionTitle": "开发者",
|
||||||
"aboutScreenContactUsTitle": "联系我们",
|
"aboutScreenContactUsTitle": "联系我们",
|
||||||
"aboutScreenLicenseTitle": "许可",
|
"aboutScreenLicenseTitle": "许可",
|
||||||
"aboutScreenLicenseContent": "无法翻译",
|
"aboutScreenLicenseContent": "AGPLv3",
|
||||||
"aboutScreenCopyright": "版权所有 © Solsynth {}",
|
"aboutScreenCopyright": "版权所有 © Solsynth {}",
|
||||||
"aboutScreenMadeWith": "由 Solar Network 团队用 ❤︎️ 制作",
|
"aboutScreenMadeWith": "由 Solar Network 团队用 ❤︎️ 制作",
|
||||||
"aboutScreenFailedToLoadPackageInfo": "无法加载包信息:{error}",
|
"aboutScreenFailedToLoadPackageInfo": "无法加载包信息:{error}",
|
||||||
@@ -999,6 +999,7 @@
|
|||||||
"noResultsFound": "未找到结果",
|
"noResultsFound": "未找到结果",
|
||||||
"toggleFilters": "切换过滤器",
|
"toggleFilters": "切换过滤器",
|
||||||
"notableDayNext": "距离 {} 还有",
|
"notableDayNext": "距离 {} 还有",
|
||||||
|
"notableDayToday": "今天是 {}!",
|
||||||
"expandPoll": "展开投票",
|
"expandPoll": "展开投票",
|
||||||
"collapsePoll": "折叠投票",
|
"collapsePoll": "折叠投票",
|
||||||
"embedView": "嵌入视图",
|
"embedView": "嵌入视图",
|
||||||
@@ -1486,7 +1487,7 @@
|
|||||||
"createAccountAgreeTerms": "我已阅读并同意服务条款。",
|
"createAccountAgreeTerms": "我已阅读并同意服务条款。",
|
||||||
"createAccountProfile": "创建您的个人资料",
|
"createAccountProfile": "创建您的个人资料",
|
||||||
"createAccountToS": "查看条款与条件",
|
"createAccountToS": "查看条款与条件",
|
||||||
"accountActivationAlert": "请记住激活您的账户",
|
"accountActivationAlert": "激活您的账户",
|
||||||
"accountActivationAlertHint": "未激活的账户可能会导致各种权限问题,请点击我们发送到您邮箱收件箱的链接来激活您的账户。",
|
"accountActivationAlertHint": "未激活的账户可能会导致各种权限问题,请点击我们发送到您邮箱收件箱的链接来激活您的账户。",
|
||||||
"accountActivationResendHint": "没收到?请尝试点击下方按钮重新发送。如果您在账户未激活期间需要更新邮箱,请随时联系我们的客服。",
|
"accountActivationResendHint": "没收到?请尝试点击下方按钮重新发送。如果您在账户未激活期间需要更新邮箱,请随时联系我们的客服。",
|
||||||
"accountActivationResend": "重新发送",
|
"accountActivationResend": "重新发送",
|
||||||
|
|||||||
159
ios/Podfile.lock
159
ios/Podfile.lock
@@ -1,5 +1,5 @@
|
|||||||
PODS:
|
PODS:
|
||||||
- Alamofire (5.10.2)
|
- Alamofire (5.11.0)
|
||||||
- connectivity_plus (0.0.1):
|
- connectivity_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- croppy (0.0.1):
|
- croppy (0.0.1):
|
||||||
@@ -42,83 +42,83 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
- file_saver (0.0.1):
|
- file_saver (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- Firebase/CoreOnly (12.4.0):
|
- Firebase/CoreOnly (12.6.0):
|
||||||
- FirebaseCore (~> 12.4.0)
|
- FirebaseCore (~> 12.6.0)
|
||||||
- Firebase/Crashlytics (12.4.0):
|
- Firebase/Crashlytics (12.6.0):
|
||||||
- Firebase/CoreOnly
|
- Firebase/CoreOnly
|
||||||
- FirebaseCrashlytics (~> 12.4.0)
|
- FirebaseCrashlytics (~> 12.6.0)
|
||||||
- Firebase/Messaging (12.4.0):
|
- Firebase/Messaging (12.6.0):
|
||||||
- Firebase/CoreOnly
|
- Firebase/CoreOnly
|
||||||
- FirebaseMessaging (~> 12.4.0)
|
- FirebaseMessaging (~> 12.6.0)
|
||||||
- firebase_analytics (12.0.4):
|
- firebase_analytics (12.1.0):
|
||||||
- firebase_core
|
- firebase_core
|
||||||
- FirebaseAnalytics (= 12.4.0)
|
- FirebaseAnalytics (= 12.6.0)
|
||||||
- Flutter
|
- Flutter
|
||||||
- firebase_core (4.2.1):
|
- firebase_core (4.3.0):
|
||||||
- Firebase/CoreOnly (= 12.4.0)
|
- Firebase/CoreOnly (= 12.6.0)
|
||||||
- Flutter
|
- Flutter
|
||||||
- firebase_crashlytics (5.0.5):
|
- firebase_crashlytics (5.0.6):
|
||||||
- Firebase/Crashlytics (= 12.4.0)
|
- Firebase/Crashlytics (= 12.6.0)
|
||||||
- firebase_core
|
- firebase_core
|
||||||
- Flutter
|
- Flutter
|
||||||
- firebase_messaging (16.0.4):
|
- firebase_messaging (16.1.0):
|
||||||
- Firebase/Messaging (= 12.4.0)
|
- Firebase/Messaging (= 12.6.0)
|
||||||
- firebase_core
|
- firebase_core
|
||||||
- Flutter
|
- Flutter
|
||||||
- FirebaseAnalytics (12.4.0):
|
- FirebaseAnalytics (12.6.0):
|
||||||
- FirebaseAnalytics/Default (= 12.4.0)
|
- FirebaseAnalytics/Default (= 12.6.0)
|
||||||
- FirebaseCore (~> 12.4.0)
|
- FirebaseCore (~> 12.6.0)
|
||||||
- FirebaseInstallations (~> 12.4.0)
|
- FirebaseInstallations (~> 12.6.0)
|
||||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||||
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
||||||
- GoogleUtilities/Network (~> 8.1)
|
- GoogleUtilities/Network (~> 8.1)
|
||||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||||
- nanopb (~> 3.30910.0)
|
- nanopb (~> 3.30910.0)
|
||||||
- FirebaseAnalytics/Default (12.4.0):
|
- FirebaseAnalytics/Default (12.6.0):
|
||||||
- FirebaseCore (~> 12.4.0)
|
- FirebaseCore (~> 12.6.0)
|
||||||
- FirebaseInstallations (~> 12.4.0)
|
- FirebaseInstallations (~> 12.6.0)
|
||||||
- GoogleAppMeasurement/Default (= 12.4.0)
|
- GoogleAppMeasurement/Default (= 12.6.0)
|
||||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||||
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
||||||
- GoogleUtilities/Network (~> 8.1)
|
- GoogleUtilities/Network (~> 8.1)
|
||||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||||
- nanopb (~> 3.30910.0)
|
- nanopb (~> 3.30910.0)
|
||||||
- FirebaseCore (12.4.0):
|
- FirebaseCore (12.6.0):
|
||||||
- FirebaseCoreInternal (~> 12.4.0)
|
- FirebaseCoreInternal (~> 12.6.0)
|
||||||
- GoogleUtilities/Environment (~> 8.1)
|
- GoogleUtilities/Environment (~> 8.1)
|
||||||
- GoogleUtilities/Logger (~> 8.1)
|
- GoogleUtilities/Logger (~> 8.1)
|
||||||
- FirebaseCoreExtension (12.4.0):
|
- FirebaseCoreExtension (12.6.0):
|
||||||
- FirebaseCore (~> 12.4.0)
|
- FirebaseCore (~> 12.6.0)
|
||||||
- FirebaseCoreInternal (12.4.0):
|
- FirebaseCoreInternal (12.6.0):
|
||||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||||
- FirebaseCrashlytics (12.4.0):
|
- FirebaseCrashlytics (12.6.0):
|
||||||
- FirebaseCore (~> 12.4.0)
|
- FirebaseCore (~> 12.6.0)
|
||||||
- FirebaseInstallations (~> 12.4.0)
|
- FirebaseInstallations (~> 12.6.0)
|
||||||
- FirebaseRemoteConfigInterop (~> 12.4.0)
|
- FirebaseRemoteConfigInterop (~> 12.6.0)
|
||||||
- FirebaseSessions (~> 12.4.0)
|
- FirebaseSessions (~> 12.6.0)
|
||||||
- GoogleDataTransport (~> 10.1)
|
- GoogleDataTransport (~> 10.1)
|
||||||
- GoogleUtilities/Environment (~> 8.1)
|
- GoogleUtilities/Environment (~> 8.1)
|
||||||
- nanopb (~> 3.30910.0)
|
- nanopb (~> 3.30910.0)
|
||||||
- PromisesObjC (~> 2.4)
|
- PromisesObjC (~> 2.4)
|
||||||
- FirebaseInstallations (12.4.0):
|
- FirebaseInstallations (12.6.0):
|
||||||
- FirebaseCore (~> 12.4.0)
|
- FirebaseCore (~> 12.6.0)
|
||||||
- GoogleUtilities/Environment (~> 8.1)
|
- GoogleUtilities/Environment (~> 8.1)
|
||||||
- GoogleUtilities/UserDefaults (~> 8.1)
|
- GoogleUtilities/UserDefaults (~> 8.1)
|
||||||
- PromisesObjC (~> 2.4)
|
- PromisesObjC (~> 2.4)
|
||||||
- FirebaseMessaging (12.4.0):
|
- FirebaseMessaging (12.6.0):
|
||||||
- FirebaseCore (~> 12.4.0)
|
- FirebaseCore (~> 12.6.0)
|
||||||
- FirebaseInstallations (~> 12.4.0)
|
- FirebaseInstallations (~> 12.6.0)
|
||||||
- GoogleDataTransport (~> 10.1)
|
- GoogleDataTransport (~> 10.1)
|
||||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||||
- GoogleUtilities/Environment (~> 8.1)
|
- GoogleUtilities/Environment (~> 8.1)
|
||||||
- GoogleUtilities/Reachability (~> 8.1)
|
- GoogleUtilities/Reachability (~> 8.1)
|
||||||
- GoogleUtilities/UserDefaults (~> 8.1)
|
- GoogleUtilities/UserDefaults (~> 8.1)
|
||||||
- nanopb (~> 3.30910.0)
|
- nanopb (~> 3.30910.0)
|
||||||
- FirebaseRemoteConfigInterop (12.4.0)
|
- FirebaseRemoteConfigInterop (12.6.0)
|
||||||
- FirebaseSessions (12.4.0):
|
- FirebaseSessions (12.6.0):
|
||||||
- FirebaseCore (~> 12.4.0)
|
- FirebaseCore (~> 12.6.0)
|
||||||
- FirebaseCoreExtension (~> 12.4.0)
|
- FirebaseCoreExtension (~> 12.6.0)
|
||||||
- FirebaseInstallations (~> 12.4.0)
|
- FirebaseInstallations (~> 12.6.0)
|
||||||
- GoogleDataTransport (~> 10.1)
|
- GoogleDataTransport (~> 10.1)
|
||||||
- GoogleUtilities/Environment (~> 8.1)
|
- GoogleUtilities/Environment (~> 8.1)
|
||||||
- GoogleUtilities/UserDefaults (~> 8.1)
|
- GoogleUtilities/UserDefaults (~> 8.1)
|
||||||
@@ -140,8 +140,9 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
- flutter_native_splash (2.4.3):
|
- flutter_native_splash (2.4.3):
|
||||||
- Flutter
|
- Flutter
|
||||||
- flutter_secure_storage (6.0.0):
|
- flutter_secure_storage_darwin (10.0.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
- flutter_timezone (0.0.1):
|
- flutter_timezone (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- flutter_udid (0.0.1):
|
- flutter_udid (0.0.1):
|
||||||
@@ -153,28 +154,28 @@ PODS:
|
|||||||
- gal (1.0.0):
|
- gal (1.0.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- GoogleAdsOnDeviceConversion (3.1.0):
|
- GoogleAdsOnDeviceConversion (3.2.0):
|
||||||
- GoogleUtilities/Environment (~> 8.1)
|
- GoogleUtilities/Environment (~> 8.1)
|
||||||
- GoogleUtilities/Logger (~> 8.1)
|
- GoogleUtilities/Logger (~> 8.1)
|
||||||
- GoogleUtilities/Network (~> 8.1)
|
- GoogleUtilities/Network (~> 8.1)
|
||||||
- nanopb (~> 3.30910.0)
|
- nanopb (~> 3.30910.0)
|
||||||
- GoogleAppMeasurement/Core (12.4.0):
|
- GoogleAppMeasurement/Core (12.6.0):
|
||||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||||
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
||||||
- GoogleUtilities/Network (~> 8.1)
|
- GoogleUtilities/Network (~> 8.1)
|
||||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||||
- nanopb (~> 3.30910.0)
|
- nanopb (~> 3.30910.0)
|
||||||
- GoogleAppMeasurement/Default (12.4.0):
|
- GoogleAppMeasurement/Default (12.6.0):
|
||||||
- GoogleAdsOnDeviceConversion (~> 3.1.0)
|
- GoogleAdsOnDeviceConversion (~> 3.2.0)
|
||||||
- GoogleAppMeasurement/Core (= 12.4.0)
|
- GoogleAppMeasurement/Core (= 12.6.0)
|
||||||
- GoogleAppMeasurement/IdentitySupport (= 12.4.0)
|
- GoogleAppMeasurement/IdentitySupport (= 12.6.0)
|
||||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||||
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
||||||
- GoogleUtilities/Network (~> 8.1)
|
- GoogleUtilities/Network (~> 8.1)
|
||||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||||
- nanopb (~> 3.30910.0)
|
- nanopb (~> 3.30910.0)
|
||||||
- GoogleAppMeasurement/IdentitySupport (12.4.0):
|
- GoogleAppMeasurement/IdentitySupport (12.6.0):
|
||||||
- GoogleAppMeasurement/Core (= 12.4.0)
|
- GoogleAppMeasurement/Core (= 12.6.0)
|
||||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||||
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
||||||
- GoogleUtilities/Network (~> 8.1)
|
- GoogleUtilities/Network (~> 8.1)
|
||||||
@@ -212,6 +213,8 @@ PODS:
|
|||||||
- GoogleUtilities/Privacy
|
- GoogleUtilities/Privacy
|
||||||
- image_picker_ios (0.0.1):
|
- image_picker_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- in_app_review (2.0.0):
|
||||||
|
- Flutter
|
||||||
- irondash_engine_context (0.0.1):
|
- irondash_engine_context (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- KeychainAccess (4.2.2)
|
- KeychainAccess (4.2.2)
|
||||||
@@ -273,6 +276,8 @@ PODS:
|
|||||||
- SDWebImage (5.21.5):
|
- SDWebImage (5.21.5):
|
||||||
- SDWebImage/Core (= 5.21.5)
|
- SDWebImage/Core (= 5.21.5)
|
||||||
- SDWebImage/Core (5.21.5)
|
- SDWebImage/Core (5.21.5)
|
||||||
|
- sensors_plus (0.0.1):
|
||||||
|
- Flutter
|
||||||
- share_plus (0.0.1):
|
- share_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- shared_preferences_foundation (0.0.1):
|
- shared_preferences_foundation (0.0.1):
|
||||||
@@ -336,12 +341,13 @@ DEPENDENCIES:
|
|||||||
- flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`)
|
- flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`)
|
||||||
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
||||||
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
||||||
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
- flutter_secure_storage_darwin (from `.symlinks/plugins/flutter_secure_storage_darwin/darwin`)
|
||||||
- flutter_timezone (from `.symlinks/plugins/flutter_timezone/ios`)
|
- flutter_timezone (from `.symlinks/plugins/flutter_timezone/ios`)
|
||||||
- flutter_udid (from `.symlinks/plugins/flutter_udid/ios`)
|
- flutter_udid (from `.symlinks/plugins/flutter_udid/ios`)
|
||||||
- flutter_webrtc (from `.symlinks/plugins/flutter_webrtc/ios`)
|
- flutter_webrtc (from `.symlinks/plugins/flutter_webrtc/ios`)
|
||||||
- gal (from `.symlinks/plugins/gal/darwin`)
|
- gal (from `.symlinks/plugins/gal/darwin`)
|
||||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||||
|
- in_app_review (from `.symlinks/plugins/in_app_review/ios`)
|
||||||
- irondash_engine_context (from `.symlinks/plugins/irondash_engine_context/ios`)
|
- irondash_engine_context (from `.symlinks/plugins/irondash_engine_context/ios`)
|
||||||
- Kingfisher (~> 8.0)
|
- Kingfisher (~> 8.0)
|
||||||
- KingfisherWebP
|
- KingfisherWebP
|
||||||
@@ -358,6 +364,7 @@ DEPENDENCIES:
|
|||||||
- protocol_handler_ios (from `.symlinks/plugins/protocol_handler_ios/ios`)
|
- protocol_handler_ios (from `.symlinks/plugins/protocol_handler_ios/ios`)
|
||||||
- receive_sharing_intent (from `.symlinks/plugins/receive_sharing_intent/ios`)
|
- receive_sharing_intent (from `.symlinks/plugins/receive_sharing_intent/ios`)
|
||||||
- record_ios (from `.symlinks/plugins/record_ios/ios`)
|
- record_ios (from `.symlinks/plugins/record_ios/ios`)
|
||||||
|
- sensors_plus (from `.symlinks/plugins/sensors_plus/ios`)
|
||||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||||
- sign_in_with_apple (from `.symlinks/plugins/sign_in_with_apple/ios`)
|
- sign_in_with_apple (from `.symlinks/plugins/sign_in_with_apple/ios`)
|
||||||
@@ -431,8 +438,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/flutter_local_notifications/ios"
|
:path: ".symlinks/plugins/flutter_local_notifications/ios"
|
||||||
flutter_native_splash:
|
flutter_native_splash:
|
||||||
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
||||||
flutter_secure_storage:
|
flutter_secure_storage_darwin:
|
||||||
:path: ".symlinks/plugins/flutter_secure_storage/ios"
|
:path: ".symlinks/plugins/flutter_secure_storage_darwin/darwin"
|
||||||
flutter_timezone:
|
flutter_timezone:
|
||||||
:path: ".symlinks/plugins/flutter_timezone/ios"
|
:path: ".symlinks/plugins/flutter_timezone/ios"
|
||||||
flutter_udid:
|
flutter_udid:
|
||||||
@@ -443,6 +450,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/gal/darwin"
|
:path: ".symlinks/plugins/gal/darwin"
|
||||||
image_picker_ios:
|
image_picker_ios:
|
||||||
:path: ".symlinks/plugins/image_picker_ios/ios"
|
:path: ".symlinks/plugins/image_picker_ios/ios"
|
||||||
|
in_app_review:
|
||||||
|
:path: ".symlinks/plugins/in_app_review/ios"
|
||||||
irondash_engine_context:
|
irondash_engine_context:
|
||||||
:path: ".symlinks/plugins/irondash_engine_context/ios"
|
:path: ".symlinks/plugins/irondash_engine_context/ios"
|
||||||
livekit_client:
|
livekit_client:
|
||||||
@@ -471,6 +480,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/receive_sharing_intent/ios"
|
:path: ".symlinks/plugins/receive_sharing_intent/ios"
|
||||||
record_ios:
|
record_ios:
|
||||||
:path: ".symlinks/plugins/record_ios/ios"
|
:path: ".symlinks/plugins/record_ios/ios"
|
||||||
|
sensors_plus:
|
||||||
|
:path: ".symlinks/plugins/sensors_plus/ios"
|
||||||
share_plus:
|
share_plus:
|
||||||
:path: ".symlinks/plugins/share_plus/ios"
|
:path: ".symlinks/plugins/share_plus/ios"
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
@@ -491,7 +502,7 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/wakelock_plus/ios"
|
:path: ".symlinks/plugins/wakelock_plus/ios"
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
Alamofire: 7193b3b92c74a07f85569e1a6c4f4237291e7496
|
Alamofire: bd5e7b23a1a750975288482c1831d71e74415f86
|
||||||
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
|
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
|
||||||
croppy: 979e8ddc254f4642bffe7d52dc7193354b27ba30
|
croppy: 979e8ddc254f4642bffe7d52dc7193354b27ba30
|
||||||
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
|
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
|
||||||
@@ -499,36 +510,37 @@ SPEC CHECKSUMS:
|
|||||||
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
||||||
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
|
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
|
||||||
file_saver: 6cdbcddd690cb02b0c1a0c225b37cd805c2bf8b6
|
file_saver: 6cdbcddd690cb02b0c1a0c225b37cd805c2bf8b6
|
||||||
Firebase: f07b15ae5a6ec0f93713e30b923d9970d144af3e
|
Firebase: a451a7b61536298fd5cbfe3a746fd40443a50679
|
||||||
firebase_analytics: 67fbdd9f3c04e55048024f3da21cfc36f05e56cf
|
firebase_analytics: 4f9cca09e65f6c2944a862c6dc86f6bed9fb769c
|
||||||
firebase_core: f1aafb21c14f497e5498f7ffc4dc63cbb52b2594
|
firebase_core: ba00a168e719694f38960502ceb560285603d073
|
||||||
firebase_crashlytics: c039028126cb45e32f4c217aa392408b0963d081
|
firebase_crashlytics: 13f4b77e9ce2a84b1f8ea07f293db5b6213ce1cf
|
||||||
firebase_messaging: c17a29984eafce4b2997fe078bb0a9e0b06f5dde
|
firebase_messaging: bf0e29321927edc02a563c984dbfa5b063864b15
|
||||||
FirebaseAnalytics: 0fc2b20091f0ddd21bf73397cf8f0eb5346dc24f
|
FirebaseAnalytics: d0a97a0db6425e5a5d966340b87f92ca7b13a557
|
||||||
FirebaseCore: bb595f3114953664e3c1dc032f008a244147cfd3
|
FirebaseCore: 0e38ad5d62d980a47a64b8e9301ffa311457be04
|
||||||
FirebaseCoreExtension: 7e1f7118ee970e001a8013719fb90950ee5e0018
|
FirebaseCoreExtension: 032fd6f8509e591fda8cb76f6651f20d926b121f
|
||||||
FirebaseCoreInternal: d7f5a043c2cd01a08103ab586587c1468047bca6
|
FirebaseCoreInternal: 69bf1306a05b8ac43004f6cc1f804bb7b05b229e
|
||||||
FirebaseCrashlytics: a6ece278a837c7e88de2d9b5da0a3542f2342395
|
FirebaseCrashlytics: 3d6248c50726ee7832aef0e53cb84c9e64d9fa7e
|
||||||
FirebaseInstallations: ae9f4902cb5bf1d0c5eaa31ec1f4e5495a0714e2
|
FirebaseInstallations: 631b38da2e11a83daa4bfb482f79d286a5dfa7ad
|
||||||
FirebaseMessaging: d33971b7bb252745ea6cd31ab190d1a1df4b8ed5
|
FirebaseMessaging: a61bc42dcab3f7a346d94bbb54dab2c9435b18b2
|
||||||
FirebaseRemoteConfigInterop: 1e31ec72b89c9924367c59bfb5ec9ab60d1d6766
|
FirebaseRemoteConfigInterop: 3443b8cb8fffd76bb3e03b2a84bfd3db952fcda4
|
||||||
FirebaseSessions: ba7c7a7ca8696a8d540eb3fe3800fbe98c79786d
|
FirebaseSessions: 2e8f808347e665dff3e5843f275715f07045297d
|
||||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||||
flutter_app_update: 816fdb2e30e4832a7c45e3f108d391c42ef040a9
|
flutter_app_update: 816fdb2e30e4832a7c45e3f108d391c42ef040a9
|
||||||
flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
|
flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
|
||||||
flutter_keyboard_visibility: 4625131e43015dbbe759d9b20daaf77e0e3f6619
|
flutter_keyboard_visibility: 4625131e43015dbbe759d9b20daaf77e0e3f6619
|
||||||
flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb
|
flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb
|
||||||
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
|
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
|
||||||
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
|
flutter_secure_storage_darwin: acdb3f316ed05a3e68f856e0353b133eec373a23
|
||||||
flutter_timezone: 7c838e17ffd4645d261e87037e5bebf6d38fe544
|
flutter_timezone: 7c838e17ffd4645d261e87037e5bebf6d38fe544
|
||||||
flutter_udid: 92a5d31fe0526b7b6002a2318df702e12e7eb300
|
flutter_udid: 92a5d31fe0526b7b6002a2318df702e12e7eb300
|
||||||
flutter_webrtc: c3e21fc0dcd9d8eb246ae4d5256fcbeb2f5ecd22
|
flutter_webrtc: c3e21fc0dcd9d8eb246ae4d5256fcbeb2f5ecd22
|
||||||
gal: baecd024ebfd13c441269ca7404792a7152fde89
|
gal: baecd024ebfd13c441269ca7404792a7152fde89
|
||||||
GoogleAdsOnDeviceConversion: e03a386840803ea7eef3fd22a061930142c039c1
|
GoogleAdsOnDeviceConversion: d68c69dd9581a0f5da02617b6f377e5be483970f
|
||||||
GoogleAppMeasurement: 1e718274b7e015cefd846ac1fcf7820c70dc017d
|
GoogleAppMeasurement: 3bf40aff49a601af5da1c3345702fcb4991d35ee
|
||||||
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||||
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
||||||
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
|
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
|
||||||
|
in_app_review: 7dd1ea365263f834b8464673f9df72c80c17c937
|
||||||
irondash_engine_context: 8e58ca8e0212ee9d1c7dc6a42121849986c88486
|
irondash_engine_context: 8e58ca8e0212ee9d1c7dc6a42121849986c88486
|
||||||
KeychainAccess: c0c4f7f38f6fc7bbe58f5702e25f7bd2f65abf51
|
KeychainAccess: c0c4f7f38f6fc7bbe58f5702e25f7bd2f65abf51
|
||||||
Kingfisher: 23d18f54677d973b713e54ce6a8f5eef6e7056ba
|
Kingfisher: 23d18f54677d973b713e54ce6a8f5eef6e7056ba
|
||||||
@@ -552,6 +564,7 @@ SPEC CHECKSUMS:
|
|||||||
receive_sharing_intent: 222384f00ffe7e952bbfabaa9e3967cb87e5fe00
|
receive_sharing_intent: 222384f00ffe7e952bbfabaa9e3967cb87e5fe00
|
||||||
record_ios: f75fa1d57f840012775c0e93a38a7f3ceea1a374
|
record_ios: f75fa1d57f840012775c0e93a38a7f3ceea1a374
|
||||||
SDWebImage: e9c98383c7572d713c1a0d7dd2783b10599b9838
|
SDWebImage: e9c98383c7572d713c1a0d7dd2783b10599b9838
|
||||||
|
sensors_plus: 6a11ed0c2e1d0bd0b20b4029d3bad27d96e0c65b
|
||||||
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
||||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||||
sign_in_with_apple: c5dcc141574c8c54d5ac99dd2163c0c72ad22418
|
sign_in_with_apple: c5dcc141574c8c54d5ac99dd2163c0c72ad22418
|
||||||
|
|||||||
@@ -1085,8 +1085,8 @@
|
|||||||
baseConfigurationReference = 31EA49B10397BD4145AD765E /* Pods-Solian Watch App.debug.xcconfig */;
|
baseConfigurationReference = 31EA49B10397BD4145AD765E /* Pods-Solian Watch App.debug.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AppIcon;
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
@@ -1136,8 +1136,8 @@
|
|||||||
baseConfigurationReference = 2440CEDEAAD6D51FDA95FA62 /* Pods-Solian Watch App.release.xcconfig */;
|
baseConfigurationReference = 2440CEDEAAD6D51FDA95FA62 /* Pods-Solian Watch App.release.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AppIcon;
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
@@ -1185,8 +1185,8 @@
|
|||||||
baseConfigurationReference = 0ECC3D56D018DD87FC342699 /* Pods-Solian Watch App.profile.xcconfig */;
|
baseConfigurationReference = 0ECC3D56D018DD87FC342699 /* Pods-Solian Watch App.profile.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AppIcon;
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
@@ -1232,7 +1232,7 @@
|
|||||||
73ACDFC42E3D0E6100B63535 /* Debug */ = {
|
73ACDFC42E3D0E6100B63535 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
@@ -1274,7 +1274,7 @@
|
|||||||
73ACDFC52E3D0E6100B63535 /* Release */ = {
|
73ACDFC52E3D0E6100B63535 /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
@@ -1313,7 +1313,7 @@
|
|||||||
73ACDFC62E3D0E6100B63535 /* Profile */ = {
|
73ACDFC62E3D0E6100B63535 /* Profile */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
@@ -1353,7 +1353,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = 17FAB080A9C53193ABD9C15B /* Pods-SolianShareExtension.debug.xcconfig */;
|
baseConfigurationReference = 17FAB080A9C53193ABD9C15B /* Pods-SolianShareExtension.debug.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
@@ -1398,7 +1398,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = 27C66EFB5A705F1A822C3EB0 /* Pods-SolianShareExtension.release.xcconfig */;
|
baseConfigurationReference = 27C66EFB5A705F1A822C3EB0 /* Pods-SolianShareExtension.release.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
@@ -1440,7 +1440,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = A85FF612AE7623A9934E57CE /* Pods-SolianShareExtension.profile.xcconfig */;
|
baseConfigurationReference = A85FF612AE7623A9934E57CE /* Pods-SolianShareExtension.profile.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
@@ -1482,7 +1482,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = F830F535CB92E3F2E1653A11 /* Pods-SolianNotificationService.debug.xcconfig */;
|
baseConfigurationReference = F830F535CB92E3F2E1653A11 /* Pods-SolianNotificationService.debug.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
@@ -1524,7 +1524,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = B93771F2A63E4148DC6142F7 /* Pods-SolianNotificationService.release.xcconfig */;
|
baseConfigurationReference = B93771F2A63E4148DC6142F7 /* Pods-SolianNotificationService.release.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
@@ -1563,7 +1563,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = 8B40620B1EEBB09456406A3C /* Pods-SolianNotificationService.profile.xcconfig */;
|
baseConfigurationReference = 8B40620B1EEBB09456406A3C /* Pods-SolianNotificationService.profile.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
|
|||||||
@@ -1,334 +1 @@
|
|||||||
{
|
{"images":[{"size":"20x20","idiom":"universal","filename":"Icon-App-20x20@2x.png","scale":"2x","platform":"ios"},{"size":"20x20","idiom":"universal","filename":"Icon-App-20x20@3x.png","scale":"3x","platform":"ios"},{"size":"29x29","idiom":"universal","filename":"Icon-App-29x29@2x.png","scale":"2x","platform":"ios"},{"size":"29x29","idiom":"universal","filename":"Icon-App-29x29@3x.png","scale":"3x","platform":"ios"},{"size":"38x38","idiom":"universal","filename":"Icon-App-38x38@2x.png","scale":"2x","platform":"ios"},{"size":"38x38","idiom":"universal","filename":"Icon-App-38x38@3x.png","scale":"3x","platform":"ios"},{"size":"40x40","idiom":"universal","filename":"Icon-App-40x40@2x.png","scale":"2x","platform":"ios"},{"size":"40x40","idiom":"universal","filename":"Icon-App-40x40@3x.png","scale":"3x","platform":"ios"},{"size":"60x60","idiom":"universal","filename":"Icon-App-60x60@2x.png","scale":"2x","platform":"ios"},{"size":"60x60","idiom":"universal","filename":"Icon-App-60x60@3x.png","scale":"3x","platform":"ios"},{"size":"64x64","idiom":"universal","filename":"Icon-App-64x64@2x.png","scale":"2x","platform":"ios"},{"size":"64x64","idiom":"universal","filename":"Icon-App-64x64@3x.png","scale":"3x","platform":"ios"},{"size":"68x68","idiom":"universal","filename":"Icon-App-68x68@2x.png","scale":"2x","platform":"ios"},{"size":"76x76","idiom":"universal","filename":"Icon-App-76x76@2x.png","scale":"2x","platform":"ios"},{"size":"83.5x83.5","idiom":"universal","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x","platform":"ios"},{"size":"1024x1024","idiom":"universal","filename":"Icon-App-1024x1024@1x.png","scale":"1x","platform":"ios"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"},{"size":"20x20","idiom":"universal","filename":"Icon-App-Dark-20x20@2x.png","scale":"2x","platform":"ios","appearances":[{"appearance":"luminosity","value":"dark"}]},{"size":"20x20","idiom":"universal","filename":"Icon-App-Dark-20x20@3x.png","scale":"3x","platform":"ios","appearances":[{"appearance":"luminosity","value":"dark"}]},{"size":"29x29","idiom":"universal","filename":"Icon-App-Dark-29x29@2x.png","scale":"2x","platform":"ios","appearances":[{"appearance":"luminosity","value":"dark"}]},{"size":"29x29","idiom":"universal","filename":"Icon-App-Dark-29x29@3x.png","scale":"3x","platform":"ios","appearances":[{"appearance":"luminosity","value":"dark"}]},{"size":"38x38","idiom":"universal","filename":"Icon-App-Dark-38x38@2x.png","scale":"2x","platform":"ios","appearances":[{"appearance":"luminosity","value":"dark"}]},{"size":"38x38","idiom":"universal","filename":"Icon-App-Dark-38x38@3x.png","scale":"3x","platform":"ios","appearances":[{"appearance":"luminosity","value":"dark"}]},{"size":"40x40","idiom":"universal","filename":"Icon-App-Dark-40x40@2x.png","scale":"2x","platform":"ios","appearances":[{"appearance":"luminosity","value":"dark"}]},{"size":"40x40","idiom":"universal","filename":"Icon-App-Dark-40x40@3x.png","scale":"3x","platform":"ios","appearances":[{"appearance":"luminosity","value":"dark"}]},{"size":"60x60","idiom":"universal","filename":"Icon-App-Dark-60x60@2x.png","scale":"2x","platform":"ios","appearances":[{"appearance":"luminosity","value":"dark"}]},{"size":"60x60","idiom":"universal","filename":"Icon-App-Dark-60x60@3x.png","scale":"3x","platform":"ios","appearances":[{"appearance":"luminosity","value":"dark"}]},{"size":"64x64","idiom":"universal","filename":"Icon-App-Dark-64x64@2x.png","scale":"2x","platform":"ios","appearances":[{"appearance":"luminosity","value":"dark"}]},{"size":"64x64","idiom":"universal","filename":"Icon-App-Dark-64x64@3x.png","scale":"3x","platform":"ios","appearances":[{"appearance":"luminosity","value":"dark"}]},{"size":"68x68","idiom":"universal","filename":"Icon-App-Dark-68x68@2x.png","scale":"2x","platform":"ios","appearances":[{"appearance":"luminosity","value":"dark"}]},{"size":"76x76","idiom":"universal","filename":"Icon-App-Dark-76x76@2x.png","scale":"2x","platform":"ios","appearances":[{"appearance":"luminosity","value":"dark"}]},{"size":"83.5x83.5","idiom":"universal","filename":"Icon-App-Dark-83.5x83.5@2x.png","scale":"2x","platform":"ios","appearances":[{"appearance":"luminosity","value":"dark"}]},{"size":"1024x1024","idiom":"universal","filename":"Icon-App-Dark-1024x1024@1x.png","scale":"1x","platform":"ios","appearances":[{"appearance":"luminosity","value":"dark"}]}],"info":{"version":1,"author":"xcode"}}
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"filename" : "Icon-App-20x20@2x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"platform" : "ios",
|
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "20x20"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "Icon-App-20x20@3x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"platform" : "ios",
|
|
||||||
"scale" : "3x",
|
|
||||||
"size" : "20x20"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "Icon-App-29x29@2x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"platform" : "ios",
|
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "29x29"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "Icon-App-29x29@3x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"platform" : "ios",
|
|
||||||
"scale" : "3x",
|
|
||||||
"size" : "29x29"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "Icon-App-38x38@2x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"platform" : "ios",
|
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "38x38"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "Icon-App-38x38@3x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"platform" : "ios",
|
|
||||||
"scale" : "3x",
|
|
||||||
"size" : "38x38"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "Icon-App-40x40@2x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"platform" : "ios",
|
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "40x40"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "Icon-App-40x40@3x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"platform" : "ios",
|
|
||||||
"scale" : "3x",
|
|
||||||
"size" : "40x40"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "Icon-App-60x60@2x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"platform" : "ios",
|
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "60x60"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "Icon-App-60x60@3x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"platform" : "ios",
|
|
||||||
"scale" : "3x",
|
|
||||||
"size" : "60x60"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "Icon-App-64x64@2x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"platform" : "ios",
|
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "64x64"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "Icon-App-64x64@3x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"platform" : "ios",
|
|
||||||
"scale" : "3x",
|
|
||||||
"size" : "64x64"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "Icon-App-68x68@2x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"platform" : "ios",
|
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "68x68"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "Icon-App-76x76@2x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"platform" : "ios",
|
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "76x76"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "Icon-App-83.5x83.5@2x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"platform" : "ios",
|
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "83.5x83.5"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "Icon-App-1024x1024@1x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"platform" : "ios",
|
|
||||||
"scale" : "1x",
|
|
||||||
"size" : "1024x1024"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"filename" : "Icon-App-Dark-20x20@2x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"platform" : "ios",
|
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "20x20"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"filename" : "Icon-App-Dark-20x20@3x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"platform" : "ios",
|
|
||||||
"scale" : "3x",
|
|
||||||
"size" : "20x20"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"filename" : "Icon-App-Dark-29x29@2x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"platform" : "ios",
|
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "29x29"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"filename" : "Icon-App-Dark-29x29@3x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"platform" : "ios",
|
|
||||||
"scale" : "3x",
|
|
||||||
"size" : "29x29"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"filename" : "Icon-App-Dark-38x38@2x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"platform" : "ios",
|
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "38x38"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"filename" : "Icon-App-Dark-38x38@3x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"platform" : "ios",
|
|
||||||
"scale" : "3x",
|
|
||||||
"size" : "38x38"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"filename" : "Icon-App-Dark-40x40@2x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"platform" : "ios",
|
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "40x40"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"filename" : "Icon-App-Dark-40x40@3x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"platform" : "ios",
|
|
||||||
"scale" : "3x",
|
|
||||||
"size" : "40x40"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"filename" : "Icon-App-Dark-60x60@2x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"platform" : "ios",
|
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "60x60"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"filename" : "Icon-App-Dark-60x60@3x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"platform" : "ios",
|
|
||||||
"scale" : "3x",
|
|
||||||
"size" : "60x60"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"filename" : "Icon-App-Dark-64x64@2x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"platform" : "ios",
|
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "64x64"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"filename" : "Icon-App-Dark-64x64@3x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"platform" : "ios",
|
|
||||||
"scale" : "3x",
|
|
||||||
"size" : "64x64"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"filename" : "Icon-App-Dark-68x68@2x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"platform" : "ios",
|
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "68x68"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"filename" : "Icon-App-Dark-76x76@2x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"platform" : "ios",
|
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "76x76"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"filename" : "Icon-App-Dark-83.5x83.5@2x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"platform" : "ios",
|
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "83.5x83.5"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"filename" : "Icon-App-Dark-1024x1024@1x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"platform" : "ios",
|
|
||||||
"scale" : "1x",
|
|
||||||
"size" : "1024x1024"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "Icon-App-1024x1024@1x.png",
|
|
||||||
"idiom" : "ios-marketing",
|
|
||||||
"scale" : "1x",
|
|
||||||
"size" : "1024x1024"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
<key>BUNDLE_ID</key>
|
<key>BUNDLE_ID</key>
|
||||||
<string>dev.solsynth.solian</string>
|
<string>dev.solsynth.solian</string>
|
||||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
<true />
|
<true/>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleDisplayName</key>
|
<key>CFBundleDisplayName</key>
|
||||||
@@ -52,16 +52,15 @@
|
|||||||
<key>CLIENT_ID</key>
|
<key>CLIENT_ID</key>
|
||||||
<string>961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com</string>
|
<string>961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com</string>
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
<false />
|
<false/>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true />
|
<true/>
|
||||||
<key>NSCalendarsUsageDescription</key>
|
<key>NSCalendarsUsageDescription</key>
|
||||||
<string>Grant access to Calander help us to shows Solar Calander with your own events.</string>
|
<string>Grant access to Calander help us to shows Solar Calander with your own events.</string>
|
||||||
<key>NSCameraUsageDescription</key>
|
<key>NSCameraUsageDescription</key>
|
||||||
<string>Grant access to Camera will allow Solian take photo or video for your post.</string>
|
<string>Grant access to Camera will allow Solian take photo or video for your post.</string>
|
||||||
<key>NSFaceIDUsageDescription</key>
|
<key>NSFaceIDUsageDescription</key>
|
||||||
<string>Allow the Solar Network verify your ownership of the logged in account and continue
|
<string>Allow the Solar Network verify your ownership of the logged in account and continue your action quickly.</string>
|
||||||
your action quickly.</string>
|
|
||||||
<key>NSMicrophoneUsageDescription</key>
|
<key>NSMicrophoneUsageDescription</key>
|
||||||
<string>Grant access to Microphone will allow Solian record audio for your post.</string>
|
<string>Grant access to Microphone will allow Solian record audio for your post.</string>
|
||||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||||
@@ -78,7 +77,7 @@
|
|||||||
<key>REVERSED_CLIENT_ID</key>
|
<key>REVERSED_CLIENT_ID</key>
|
||||||
<string>com.googleusercontent.apps.961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig</string>
|
<string>com.googleusercontent.apps.961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig</string>
|
||||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
<true />
|
<true/>
|
||||||
<key>UIBackgroundModes</key>
|
<key>UIBackgroundModes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>fetch</string>
|
<string>fetch</string>
|
||||||
@@ -91,7 +90,7 @@
|
|||||||
<key>UIMainStoryboardFile</key>
|
<key>UIMainStoryboardFile</key>
|
||||||
<string>Main</string>
|
<string>Main</string>
|
||||||
<key>UIStatusBarHidden</key>
|
<key>UIStatusBarHidden</key>
|
||||||
<false />
|
<false/>
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
@@ -108,4 +107,4 @@
|
|||||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -5,16 +5,19 @@ import 'package:island/database/draft.dart';
|
|||||||
import 'package:island/models/account.dart';
|
import 'package:island/models/account.dart';
|
||||||
import 'package:island/models/chat.dart';
|
import 'package:island/models/chat.dart';
|
||||||
import 'package:island/models/post.dart';
|
import 'package:island/models/post.dart';
|
||||||
|
import 'package:island/models/realm.dart';
|
||||||
|
|
||||||
part 'drift_db.g.dart';
|
part 'drift_db.g.dart';
|
||||||
|
|
||||||
// Define the database
|
// Define the database
|
||||||
@DriftDatabase(tables: [ChatRooms, ChatMembers, ChatMessages, PostDrafts])
|
@DriftDatabase(
|
||||||
|
tables: [Realms, ChatRooms, ChatMembers, ChatMessages, PostDrafts],
|
||||||
|
)
|
||||||
class AppDatabase extends _$AppDatabase {
|
class AppDatabase extends _$AppDatabase {
|
||||||
AppDatabase(super.e);
|
AppDatabase(super.e);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 9;
|
int get schemaVersion => 12;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MigrationStrategy get migration => MigrationStrategy(
|
MigrationStrategy get migration => MigrationStrategy(
|
||||||
@@ -71,6 +74,35 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
'ALTER TABLE chat_members DROP COLUMN last_typed',
|
'ALTER TABLE chat_members DROP COLUMN last_typed',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (from < 10) {
|
||||||
|
// Add realms table and update chat_rooms foreign key
|
||||||
|
await m.createTable(realms);
|
||||||
|
// The realmId column in chat_rooms already exists, just need to ensure the foreign key constraint
|
||||||
|
}
|
||||||
|
if (from < 11) {
|
||||||
|
// Add isPinned column to chat_rooms table
|
||||||
|
await customStatement(
|
||||||
|
'ALTER TABLE chat_rooms ADD COLUMN is_pinned INTEGER DEFAULT 0',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (from < 12) {
|
||||||
|
// Add new columns to realms table
|
||||||
|
await customStatement(
|
||||||
|
'ALTER TABLE realms ADD COLUMN slug TEXT NOT NULL DEFAULT \'\'',
|
||||||
|
);
|
||||||
|
await customStatement(
|
||||||
|
'ALTER TABLE realms ADD COLUMN verified_as TEXT NULL',
|
||||||
|
);
|
||||||
|
await customStatement(
|
||||||
|
'ALTER TABLE realms ADD COLUMN verified_at DATETIME NULL',
|
||||||
|
);
|
||||||
|
await customStatement(
|
||||||
|
'ALTER TABLE realms ADD COLUMN is_community INTEGER NOT NULL DEFAULT 0',
|
||||||
|
);
|
||||||
|
await customStatement(
|
||||||
|
'ALTER TABLE realms ADD COLUMN is_public INTEGER NOT NULL DEFAULT 0',
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -92,11 +124,10 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
|
|
||||||
// Migrate existing data if any
|
// Migrate existing data if any
|
||||||
try {
|
try {
|
||||||
final oldDrafts =
|
final oldDrafts = await customSelect(
|
||||||
await customSelect(
|
'SELECT id, post, lastModified FROM post_drafts_old',
|
||||||
'SELECT id, post, lastModified FROM post_drafts_old',
|
readsFrom: {postDrafts},
|
||||||
readsFrom: {postDrafts},
|
).get();
|
||||||
).get();
|
|
||||||
|
|
||||||
for (final row in oldDrafts) {
|
for (final row in oldDrafts) {
|
||||||
final postJson = row.read<String>('post');
|
final postJson = row.read<String>('post');
|
||||||
@@ -150,9 +181,9 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<int> updateMessageStatus(String id, MessageStatus status) {
|
Future<int> updateMessageStatus(String id, MessageStatus status) {
|
||||||
return (update(chatMessages)..where(
|
return (update(chatMessages)..where((m) => m.id.equals(id))).write(
|
||||||
(m) => m.id.equals(id),
|
ChatMessagesCompanion(status: Value(status)),
|
||||||
)).write(ChatMessagesCompanion(status: Value(status)));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> deleteMessage(String id) {
|
Future<int> deleteMessage(String id) {
|
||||||
@@ -176,29 +207,28 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
|
|
||||||
if (query.isNotEmpty) {
|
if (query.isNotEmpty) {
|
||||||
final searchTerm = '%$query%';
|
final searchTerm = '%$query%';
|
||||||
selectStatement =
|
selectStatement = selectStatement
|
||||||
selectStatement..where(
|
..where(
|
||||||
(m) =>
|
(m) =>
|
||||||
m.content.like(searchTerm) |
|
m.content.like(searchTerm) |
|
||||||
m.meta.like(searchTerm) |
|
m.meta.like(searchTerm) |
|
||||||
m.attachments.like(searchTerm) |
|
m.attachments.like(searchTerm) |
|
||||||
m.type.like(searchTerm),
|
m.type.like(searchTerm),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (withAttachments == true) {
|
if (withAttachments == true) {
|
||||||
selectStatement =
|
selectStatement = selectStatement
|
||||||
selectStatement..where((m) => m.attachments.equals('[]').not());
|
..where((m) => m.attachments.equals('[]').not());
|
||||||
}
|
}
|
||||||
|
|
||||||
final messages =
|
final messages =
|
||||||
await (selectStatement
|
await (selectStatement
|
||||||
..orderBy([(m) => OrderingTerm.desc(m.createdAt)]))
|
..orderBy([(m) => OrderingTerm.desc(m.createdAt)]))
|
||||||
.get();
|
.get();
|
||||||
final messageFutures =
|
final messageFutures = messages
|
||||||
messages
|
.map((msg) => companionToMessage(msg, fetchAccount: fetchAccount))
|
||||||
.map((msg) => companionToMessage(msg, fetchAccount: fetchAccount))
|
.toList();
|
||||||
.toList();
|
|
||||||
return await Future.wait(messageFutures);
|
return await Future.wait(messageFutures);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,9 +264,9 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
final data = jsonDecode(dbMessage.data);
|
final data = jsonDecode(dbMessage.data);
|
||||||
SnChatMember? sender;
|
SnChatMember? sender;
|
||||||
try {
|
try {
|
||||||
final senderRow =
|
final senderRow = await (select(
|
||||||
await (select(chatMembers)
|
chatMembers,
|
||||||
..where((m) => m.id.equals(dbMessage.senderId))).getSingle();
|
)..where((m) => m.id.equals(dbMessage.senderId))).getSingle();
|
||||||
SnAccount senderAccount;
|
SnAccount senderAccount;
|
||||||
senderAccount = SnAccount.fromJson(senderRow.account);
|
senderAccount = SnAccount.fromJson(senderRow.account);
|
||||||
|
|
||||||
@@ -335,6 +365,7 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
picture: Value(room.picture?.toJson()),
|
picture: Value(room.picture?.toJson()),
|
||||||
background: Value(room.background?.toJson()),
|
background: Value(room.background?.toJson()),
|
||||||
realmId: Value(room.realmId),
|
realmId: Value(room.realmId),
|
||||||
|
accountId: Value(room.accountId),
|
||||||
createdAt: Value(room.createdAt),
|
createdAt: Value(room.createdAt),
|
||||||
updatedAt: Value(room.updatedAt),
|
updatedAt: Value(room.updatedAt),
|
||||||
deletedAt: Value(room.deletedAt),
|
deletedAt: Value(room.deletedAt),
|
||||||
@@ -358,6 +389,25 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RealmsCompanion companionFromRealm(SnRealm realm) {
|
||||||
|
return RealmsCompanion(
|
||||||
|
id: Value(realm.id),
|
||||||
|
slug: Value(realm.slug),
|
||||||
|
name: Value(realm.name),
|
||||||
|
description: Value(realm.description),
|
||||||
|
verifiedAs: Value(realm.verifiedAs),
|
||||||
|
verifiedAt: Value(realm.verifiedAt),
|
||||||
|
isCommunity: Value(realm.isCommunity),
|
||||||
|
isPublic: Value(realm.isPublic),
|
||||||
|
picture: Value(realm.picture?.toJson()),
|
||||||
|
background: Value(realm.background?.toJson()),
|
||||||
|
accountId: Value(realm.accountId),
|
||||||
|
createdAt: Value(realm.createdAt),
|
||||||
|
updatedAt: Value(realm.updatedAt),
|
||||||
|
deletedAt: Value(realm.deletedAt),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> saveChatRooms(
|
Future<void> saveChatRooms(
|
||||||
List<SnChatRoom> rooms, {
|
List<SnChatRoom> rooms, {
|
||||||
bool override = false,
|
bool override = false,
|
||||||
@@ -373,22 +423,46 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
if (idsToRemove.isNotEmpty) {
|
if (idsToRemove.isNotEmpty) {
|
||||||
final idsList = idsToRemove.toList();
|
final idsList = idsToRemove.toList();
|
||||||
// Remove messages
|
// Remove messages
|
||||||
await (delete(chatMessages)
|
await (delete(
|
||||||
..where((t) => t.roomId.isIn(idsList))).go();
|
chatMessages,
|
||||||
|
)..where((t) => t.roomId.isIn(idsList))).go();
|
||||||
// Remove members
|
// Remove members
|
||||||
await (delete(chatMembers)
|
await (delete(
|
||||||
..where((t) => t.chatRoomId.isIn(idsList))).go();
|
chatMembers,
|
||||||
|
)..where((t) => t.chatRoomId.isIn(idsList))).go();
|
||||||
// Remove rooms
|
// Remove rooms
|
||||||
await (delete(chatRooms)..where((t) => t.id.isIn(idsList))).go();
|
await (delete(chatRooms)..where((t) => t.id.isIn(idsList))).go();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Upsert remote rooms
|
// 2. Upsert realms first
|
||||||
|
final realmsToSave = rooms
|
||||||
|
.where((room) => room.realm != null)
|
||||||
|
.map((room) => room.realm!)
|
||||||
|
.toSet()
|
||||||
|
.toList();
|
||||||
await batch((batch) {
|
await batch((batch) {
|
||||||
|
for (final realm in realmsToSave) {
|
||||||
|
batch.insert(
|
||||||
|
realms,
|
||||||
|
companionFromRealm(realm),
|
||||||
|
mode: InsertMode.insertOrReplace,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. Upsert remote rooms
|
||||||
|
await batch((batch) async {
|
||||||
for (final room in rooms) {
|
for (final room in rooms) {
|
||||||
|
// Preserve local isPinned status
|
||||||
|
final currentRoom = await (select(
|
||||||
|
chatRooms,
|
||||||
|
)..where((r) => r.id.equals(room.id))).getSingleOrNull();
|
||||||
|
final isPinned = currentRoom?.isPinned ?? false;
|
||||||
|
|
||||||
batch.insert(
|
batch.insert(
|
||||||
chatRooms,
|
chatRooms,
|
||||||
companionFromRoom(room),
|
companionFromRoom(room).copyWith(isPinned: Value(isPinned)),
|
||||||
mode: InsertMode.insertOrReplace,
|
mode: InsertMode.insertOrReplace,
|
||||||
);
|
);
|
||||||
for (final member in room.members ?? []) {
|
for (final member in room.members ?? []) {
|
||||||
@@ -445,8 +519,9 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<PostDraft?> getPostDraftById(String id) async {
|
Future<PostDraft?> getPostDraftById(String id) async {
|
||||||
return await (select(postDrafts)
|
return await (select(
|
||||||
..where((tbl) => tbl.id.equals(id))).getSingleOrNull();
|
postDrafts,
|
||||||
|
)..where((tbl) => tbl.id.equals(id))).getSingleOrNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveMember(SnChatMember member) async {
|
Future<void> saveMember(SnChatMember member) async {
|
||||||
@@ -463,4 +538,16 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
// Then save the message
|
// Then save the message
|
||||||
return await saveMessage(messageToCompanion(message));
|
return await saveMessage(messageToCompanion(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> toggleChatRoomPinned(String roomId) async {
|
||||||
|
final room = await (select(
|
||||||
|
chatRooms,
|
||||||
|
)..where((r) => r.id.equals(roomId))).getSingleOrNull();
|
||||||
|
if (room != null) {
|
||||||
|
final newPinnedStatus = !(room.isPinned ?? false);
|
||||||
|
await (update(chatRooms)..where((r) => r.id.equals(roomId))).write(
|
||||||
|
ChatRoomsCompanion(isPinned: Value(newPinnedStatus)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -36,6 +36,26 @@ class ListMapConverter
|
|||||||
String toSql(List<Map<String, dynamic>> value) => json.encode(value);
|
String toSql(List<Map<String, dynamic>> value) => json.encode(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Realms extends Table {
|
||||||
|
TextColumn get id => text()();
|
||||||
|
TextColumn get slug => text()();
|
||||||
|
TextColumn get name => text().nullable()();
|
||||||
|
TextColumn get description => text().nullable()();
|
||||||
|
TextColumn get verifiedAs => text().nullable()();
|
||||||
|
DateTimeColumn get verifiedAt => dateTime().nullable()();
|
||||||
|
BoolColumn get isCommunity => boolean()();
|
||||||
|
BoolColumn get isPublic => boolean()();
|
||||||
|
TextColumn get picture => text().map(const MapConverter()).nullable()();
|
||||||
|
TextColumn get background => text().map(const MapConverter()).nullable()();
|
||||||
|
TextColumn get accountId => text().nullable()();
|
||||||
|
DateTimeColumn get createdAt => dateTime()();
|
||||||
|
DateTimeColumn get updatedAt => dateTime()();
|
||||||
|
DateTimeColumn get deletedAt => dateTime().nullable()();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<Column> get primaryKey => {id};
|
||||||
|
}
|
||||||
|
|
||||||
class ChatRooms extends Table {
|
class ChatRooms extends Table {
|
||||||
TextColumn get id => text()();
|
TextColumn get id => text()();
|
||||||
TextColumn get name => text().nullable()();
|
TextColumn get name => text().nullable()();
|
||||||
@@ -47,8 +67,10 @@ class ChatRooms extends Table {
|
|||||||
boolean().nullable().withDefault(const Constant(false))();
|
boolean().nullable().withDefault(const Constant(false))();
|
||||||
TextColumn get picture => text().map(const MapConverter()).nullable()();
|
TextColumn get picture => text().map(const MapConverter()).nullable()();
|
||||||
TextColumn get background => text().map(const MapConverter()).nullable()();
|
TextColumn get background => text().map(const MapConverter()).nullable()();
|
||||||
TextColumn get realmId => text().nullable()();
|
TextColumn get realmId => text().references(Realms, #id).nullable()();
|
||||||
TextColumn get accountId => text().nullable()();
|
TextColumn get accountId => text().nullable()();
|
||||||
|
BoolColumn get isPinned =>
|
||||||
|
boolean().nullable().withDefault(const Constant(false))();
|
||||||
DateTimeColumn get createdAt => dateTime()();
|
DateTimeColumn get createdAt => dateTime()();
|
||||||
DateTimeColumn get updatedAt => dateTime()();
|
DateTimeColumn get updatedAt => dateTime()();
|
||||||
DateTimeColumn get deletedAt => dateTime().nullable()();
|
DateTimeColumn get deletedAt => dateTime().nullable()();
|
||||||
@@ -91,10 +113,9 @@ class ChatMessages extends Table {
|
|||||||
TextColumn get type => text().withDefault(const Constant('text'))();
|
TextColumn get type => text().withDefault(const Constant('text'))();
|
||||||
TextColumn get meta =>
|
TextColumn get meta =>
|
||||||
text().map(const MapConverter()).withDefault(const Constant('{}'))();
|
text().map(const MapConverter()).withDefault(const Constant('{}'))();
|
||||||
TextColumn get membersMentioned =>
|
TextColumn get membersMentioned => text()
|
||||||
text()
|
.map(const ListStringConverter())
|
||||||
.map(const ListStringConverter())
|
.withDefault(const Constant('[]'))();
|
||||||
.withDefault(const Constant('[]'))();
|
|
||||||
DateTimeColumn get editedAt => dateTime().nullable()();
|
DateTimeColumn get editedAt => dateTime().nullable()();
|
||||||
TextColumn get attachments =>
|
TextColumn get attachments =>
|
||||||
text().map(const ListMapConverter()).withDefault(const Constant('[]'))();
|
text().map(const ListMapConverter()).withDefault(const Constant('[]'))();
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:hotkey_manager/hotkey_manager.dart';
|
||||||
import 'package:image_picker_android/image_picker_android.dart';
|
import 'package:image_picker_android/image_picker_android.dart';
|
||||||
import 'package:island/talker.dart';
|
import 'package:island/talker.dart';
|
||||||
import 'package:island/firebase_options.dart';
|
import 'package:island/firebase_options.dart';
|
||||||
@@ -53,7 +54,8 @@ void main() async {
|
|||||||
|
|
||||||
if (!kIsWeb && (Platform.isLinux || Platform.isMacOS || Platform.isWindows)) {
|
if (!kIsWeb && (Platform.isLinux || Platform.isMacOS || Platform.isWindows)) {
|
||||||
talker.info("[SplashScreen] Initializing desktop window manager...");
|
talker.info("[SplashScreen] Initializing desktop window manager...");
|
||||||
await protocolHandler.register('myprotocol');
|
await protocolHandler.register('solian');
|
||||||
|
await hotKeyManager.unregisterAll();
|
||||||
talker.info("[SplashScreen] Desktop window manager is ready!");
|
talker.info("[SplashScreen] Desktop window manager is ready!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ sealed class SnNotableDay with _$SnNotableDay {
|
|||||||
required DateTime date,
|
required DateTime date,
|
||||||
required String localName,
|
required String localName,
|
||||||
required String globalName,
|
required String globalName,
|
||||||
required String countryCode,
|
required String? countryCode,
|
||||||
|
required String? localizableKey,
|
||||||
required List<int> holidays,
|
required List<int> holidays,
|
||||||
}) = _SnNotableDay;
|
}) = _SnNotableDay;
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ T _$identity<T>(T value) => value;
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$SnNotableDay {
|
mixin _$SnNotableDay {
|
||||||
|
|
||||||
DateTime get date; String get localName; String get globalName; String get countryCode; List<int> get holidays;
|
DateTime get date; String get localName; String get globalName; String? get countryCode; String? get localizableKey; List<int> get holidays;
|
||||||
/// Create a copy of SnNotableDay
|
/// Create a copy of SnNotableDay
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@@ -28,16 +28,16 @@ $SnNotableDayCopyWith<SnNotableDay> get copyWith => _$SnNotableDayCopyWithImpl<S
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnNotableDay&&(identical(other.date, date) || other.date == date)&&(identical(other.localName, localName) || other.localName == localName)&&(identical(other.globalName, globalName) || other.globalName == globalName)&&(identical(other.countryCode, countryCode) || other.countryCode == countryCode)&&const DeepCollectionEquality().equals(other.holidays, holidays));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnNotableDay&&(identical(other.date, date) || other.date == date)&&(identical(other.localName, localName) || other.localName == localName)&&(identical(other.globalName, globalName) || other.globalName == globalName)&&(identical(other.countryCode, countryCode) || other.countryCode == countryCode)&&(identical(other.localizableKey, localizableKey) || other.localizableKey == localizableKey)&&const DeepCollectionEquality().equals(other.holidays, holidays));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,date,localName,globalName,countryCode,const DeepCollectionEquality().hash(holidays));
|
int get hashCode => Object.hash(runtimeType,date,localName,globalName,countryCode,localizableKey,const DeepCollectionEquality().hash(holidays));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SnNotableDay(date: $date, localName: $localName, globalName: $globalName, countryCode: $countryCode, holidays: $holidays)';
|
return 'SnNotableDay(date: $date, localName: $localName, globalName: $globalName, countryCode: $countryCode, localizableKey: $localizableKey, holidays: $holidays)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ abstract mixin class $SnNotableDayCopyWith<$Res> {
|
|||||||
factory $SnNotableDayCopyWith(SnNotableDay value, $Res Function(SnNotableDay) _then) = _$SnNotableDayCopyWithImpl;
|
factory $SnNotableDayCopyWith(SnNotableDay value, $Res Function(SnNotableDay) _then) = _$SnNotableDayCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
DateTime date, String localName, String globalName, String countryCode, List<int> holidays
|
DateTime date, String localName, String globalName, String? countryCode, String? localizableKey, List<int> holidays
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -65,13 +65,14 @@ class _$SnNotableDayCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnNotableDay
|
/// Create a copy of SnNotableDay
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? date = null,Object? localName = null,Object? globalName = null,Object? countryCode = null,Object? holidays = null,}) {
|
@pragma('vm:prefer-inline') @override $Res call({Object? date = null,Object? localName = null,Object? globalName = null,Object? countryCode = freezed,Object? localizableKey = freezed,Object? holidays = null,}) {
|
||||||
return _then(_self.copyWith(
|
return _then(_self.copyWith(
|
||||||
date: null == date ? _self.date : date // ignore: cast_nullable_to_non_nullable
|
date: null == date ? _self.date : date // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,localName: null == localName ? _self.localName : localName // ignore: cast_nullable_to_non_nullable
|
as DateTime,localName: null == localName ? _self.localName : localName // ignore: cast_nullable_to_non_nullable
|
||||||
as String,globalName: null == globalName ? _self.globalName : globalName // ignore: cast_nullable_to_non_nullable
|
as String,globalName: null == globalName ? _self.globalName : globalName // ignore: cast_nullable_to_non_nullable
|
||||||
as String,countryCode: null == countryCode ? _self.countryCode : countryCode // ignore: cast_nullable_to_non_nullable
|
as String,countryCode: freezed == countryCode ? _self.countryCode : countryCode // ignore: cast_nullable_to_non_nullable
|
||||||
as String,holidays: null == holidays ? _self.holidays : holidays // ignore: cast_nullable_to_non_nullable
|
as String?,localizableKey: freezed == localizableKey ? _self.localizableKey : localizableKey // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,holidays: null == holidays ? _self.holidays : holidays // ignore: cast_nullable_to_non_nullable
|
||||||
as List<int>,
|
as List<int>,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -154,10 +155,10 @@ return $default(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( DateTime date, String localName, String globalName, String countryCode, List<int> holidays)? $default,{required TResult orElse(),}) {final _that = this;
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( DateTime date, String localName, String globalName, String? countryCode, String? localizableKey, List<int> holidays)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _SnNotableDay() when $default != null:
|
case _SnNotableDay() when $default != null:
|
||||||
return $default(_that.date,_that.localName,_that.globalName,_that.countryCode,_that.holidays);case _:
|
return $default(_that.date,_that.localName,_that.globalName,_that.countryCode,_that.localizableKey,_that.holidays);case _:
|
||||||
return orElse();
|
return orElse();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -175,10 +176,10 @@ return $default(_that.date,_that.localName,_that.globalName,_that.countryCode,_t
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( DateTime date, String localName, String globalName, String countryCode, List<int> holidays) $default,) {final _that = this;
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( DateTime date, String localName, String globalName, String? countryCode, String? localizableKey, List<int> holidays) $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _SnNotableDay():
|
case _SnNotableDay():
|
||||||
return $default(_that.date,_that.localName,_that.globalName,_that.countryCode,_that.holidays);}
|
return $default(_that.date,_that.localName,_that.globalName,_that.countryCode,_that.localizableKey,_that.holidays);}
|
||||||
}
|
}
|
||||||
/// A variant of `when` that fallback to returning `null`
|
/// A variant of `when` that fallback to returning `null`
|
||||||
///
|
///
|
||||||
@@ -192,10 +193,10 @@ return $default(_that.date,_that.localName,_that.globalName,_that.countryCode,_t
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( DateTime date, String localName, String globalName, String countryCode, List<int> holidays)? $default,) {final _that = this;
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( DateTime date, String localName, String globalName, String? countryCode, String? localizableKey, List<int> holidays)? $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _SnNotableDay() when $default != null:
|
case _SnNotableDay() when $default != null:
|
||||||
return $default(_that.date,_that.localName,_that.globalName,_that.countryCode,_that.holidays);case _:
|
return $default(_that.date,_that.localName,_that.globalName,_that.countryCode,_that.localizableKey,_that.holidays);case _:
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -207,13 +208,14 @@ return $default(_that.date,_that.localName,_that.globalName,_that.countryCode,_t
|
|||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
||||||
class _SnNotableDay implements SnNotableDay {
|
class _SnNotableDay implements SnNotableDay {
|
||||||
const _SnNotableDay({required this.date, required this.localName, required this.globalName, required this.countryCode, required final List<int> holidays}): _holidays = holidays;
|
const _SnNotableDay({required this.date, required this.localName, required this.globalName, required this.countryCode, required this.localizableKey, required final List<int> holidays}): _holidays = holidays;
|
||||||
factory _SnNotableDay.fromJson(Map<String, dynamic> json) => _$SnNotableDayFromJson(json);
|
factory _SnNotableDay.fromJson(Map<String, dynamic> json) => _$SnNotableDayFromJson(json);
|
||||||
|
|
||||||
@override final DateTime date;
|
@override final DateTime date;
|
||||||
@override final String localName;
|
@override final String localName;
|
||||||
@override final String globalName;
|
@override final String globalName;
|
||||||
@override final String countryCode;
|
@override final String? countryCode;
|
||||||
|
@override final String? localizableKey;
|
||||||
final List<int> _holidays;
|
final List<int> _holidays;
|
||||||
@override List<int> get holidays {
|
@override List<int> get holidays {
|
||||||
if (_holidays is EqualUnmodifiableListView) return _holidays;
|
if (_holidays is EqualUnmodifiableListView) return _holidays;
|
||||||
@@ -235,16 +237,16 @@ Map<String, dynamic> toJson() {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnNotableDay&&(identical(other.date, date) || other.date == date)&&(identical(other.localName, localName) || other.localName == localName)&&(identical(other.globalName, globalName) || other.globalName == globalName)&&(identical(other.countryCode, countryCode) || other.countryCode == countryCode)&&const DeepCollectionEquality().equals(other._holidays, _holidays));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnNotableDay&&(identical(other.date, date) || other.date == date)&&(identical(other.localName, localName) || other.localName == localName)&&(identical(other.globalName, globalName) || other.globalName == globalName)&&(identical(other.countryCode, countryCode) || other.countryCode == countryCode)&&(identical(other.localizableKey, localizableKey) || other.localizableKey == localizableKey)&&const DeepCollectionEquality().equals(other._holidays, _holidays));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,date,localName,globalName,countryCode,const DeepCollectionEquality().hash(_holidays));
|
int get hashCode => Object.hash(runtimeType,date,localName,globalName,countryCode,localizableKey,const DeepCollectionEquality().hash(_holidays));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SnNotableDay(date: $date, localName: $localName, globalName: $globalName, countryCode: $countryCode, holidays: $holidays)';
|
return 'SnNotableDay(date: $date, localName: $localName, globalName: $globalName, countryCode: $countryCode, localizableKey: $localizableKey, holidays: $holidays)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -255,7 +257,7 @@ abstract mixin class _$SnNotableDayCopyWith<$Res> implements $SnNotableDayCopyWi
|
|||||||
factory _$SnNotableDayCopyWith(_SnNotableDay value, $Res Function(_SnNotableDay) _then) = __$SnNotableDayCopyWithImpl;
|
factory _$SnNotableDayCopyWith(_SnNotableDay value, $Res Function(_SnNotableDay) _then) = __$SnNotableDayCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
DateTime date, String localName, String globalName, String countryCode, List<int> holidays
|
DateTime date, String localName, String globalName, String? countryCode, String? localizableKey, List<int> holidays
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -272,13 +274,14 @@ class __$SnNotableDayCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnNotableDay
|
/// Create a copy of SnNotableDay
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? date = null,Object? localName = null,Object? globalName = null,Object? countryCode = null,Object? holidays = null,}) {
|
@override @pragma('vm:prefer-inline') $Res call({Object? date = null,Object? localName = null,Object? globalName = null,Object? countryCode = freezed,Object? localizableKey = freezed,Object? holidays = null,}) {
|
||||||
return _then(_SnNotableDay(
|
return _then(_SnNotableDay(
|
||||||
date: null == date ? _self.date : date // ignore: cast_nullable_to_non_nullable
|
date: null == date ? _self.date : date // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,localName: null == localName ? _self.localName : localName // ignore: cast_nullable_to_non_nullable
|
as DateTime,localName: null == localName ? _self.localName : localName // ignore: cast_nullable_to_non_nullable
|
||||||
as String,globalName: null == globalName ? _self.globalName : globalName // ignore: cast_nullable_to_non_nullable
|
as String,globalName: null == globalName ? _self.globalName : globalName // ignore: cast_nullable_to_non_nullable
|
||||||
as String,countryCode: null == countryCode ? _self.countryCode : countryCode // ignore: cast_nullable_to_non_nullable
|
as String,countryCode: freezed == countryCode ? _self.countryCode : countryCode // ignore: cast_nullable_to_non_nullable
|
||||||
as String,holidays: null == holidays ? _self._holidays : holidays // ignore: cast_nullable_to_non_nullable
|
as String?,localizableKey: freezed == localizableKey ? _self.localizableKey : localizableKey // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,holidays: null == holidays ? _self._holidays : holidays // ignore: cast_nullable_to_non_nullable
|
||||||
as List<int>,
|
as List<int>,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ _SnNotableDay _$SnNotableDayFromJson(Map<String, dynamic> json) =>
|
|||||||
date: DateTime.parse(json['date'] as String),
|
date: DateTime.parse(json['date'] as String),
|
||||||
localName: json['local_name'] as String,
|
localName: json['local_name'] as String,
|
||||||
globalName: json['global_name'] as String,
|
globalName: json['global_name'] as String,
|
||||||
countryCode: json['country_code'] as String,
|
countryCode: json['country_code'] as String?,
|
||||||
|
localizableKey: json['localizable_key'] as String?,
|
||||||
holidays: (json['holidays'] as List<dynamic>)
|
holidays: (json['holidays'] as List<dynamic>)
|
||||||
.map((e) => (e as num).toInt())
|
.map((e) => (e as num).toInt())
|
||||||
.toList(),
|
.toList(),
|
||||||
@@ -23,6 +24,7 @@ Map<String, dynamic> _$SnNotableDayToJson(_SnNotableDay instance) =>
|
|||||||
'local_name': instance.localName,
|
'local_name': instance.localName,
|
||||||
'global_name': instance.globalName,
|
'global_name': instance.globalName,
|
||||||
'country_code': instance.countryCode,
|
'country_code': instance.countryCode,
|
||||||
|
'localizable_key': instance.localizableKey,
|
||||||
'holidays': instance.holidays,
|
'holidays': instance.holidays,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ sealed class SnChatRoom with _$SnChatRoom {
|
|||||||
required DateTime updatedAt,
|
required DateTime updatedAt,
|
||||||
required DateTime? deletedAt,
|
required DateTime? deletedAt,
|
||||||
required List<SnChatMember>? members,
|
required List<SnChatMember>? members,
|
||||||
|
// Frontend data
|
||||||
|
@Default(false) bool isPinned,
|
||||||
}) = _SnChatRoom;
|
}) = _SnChatRoom;
|
||||||
|
|
||||||
factory SnChatRoom.fromJson(Map<String, dynamic> json) =>
|
factory SnChatRoom.fromJson(Map<String, dynamic> json) =>
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ T _$identity<T>(T value) => value;
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$SnChatRoom {
|
mixin _$SnChatRoom {
|
||||||
|
|
||||||
String get id; String? get name; String? get description; int get type; bool get isPublic; bool get isCommunity; SnCloudFile? get picture; SnCloudFile? get background; String? get realmId; String? get accountId; SnRealm? get realm; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; List<SnChatMember>? get members;
|
String get id; String? get name; String? get description; int get type; bool get isPublic; bool get isCommunity; SnCloudFile? get picture; SnCloudFile? get background; String? get realmId; String? get accountId; SnRealm? get realm; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; List<SnChatMember>? get members;// Frontend data
|
||||||
|
bool get isPinned;
|
||||||
/// Create a copy of SnChatRoom
|
/// Create a copy of SnChatRoom
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@@ -28,16 +29,16 @@ $SnChatRoomCopyWith<SnChatRoom> get copyWith => _$SnChatRoomCopyWithImpl<SnChatR
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnChatRoom&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.type, type) || other.type == type)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.isCommunity, isCommunity) || other.isCommunity == isCommunity)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&const DeepCollectionEquality().equals(other.members, members));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnChatRoom&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.type, type) || other.type == type)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.isCommunity, isCommunity) || other.isCommunity == isCommunity)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&const DeepCollectionEquality().equals(other.members, members)&&(identical(other.isPinned, isPinned) || other.isPinned == isPinned));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,id,name,description,type,isPublic,isCommunity,picture,background,realmId,accountId,realm,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(members));
|
int get hashCode => Object.hash(runtimeType,id,name,description,type,isPublic,isCommunity,picture,background,realmId,accountId,realm,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(members),isPinned);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SnChatRoom(id: $id, name: $name, description: $description, type: $type, isPublic: $isPublic, isCommunity: $isCommunity, picture: $picture, background: $background, realmId: $realmId, accountId: $accountId, realm: $realm, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, members: $members)';
|
return 'SnChatRoom(id: $id, name: $name, description: $description, type: $type, isPublic: $isPublic, isCommunity: $isCommunity, picture: $picture, background: $background, realmId: $realmId, accountId: $accountId, realm: $realm, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, members: $members, isPinned: $isPinned)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -48,7 +49,7 @@ abstract mixin class $SnChatRoomCopyWith<$Res> {
|
|||||||
factory $SnChatRoomCopyWith(SnChatRoom value, $Res Function(SnChatRoom) _then) = _$SnChatRoomCopyWithImpl;
|
factory $SnChatRoomCopyWith(SnChatRoom value, $Res Function(SnChatRoom) _then) = _$SnChatRoomCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String id, String? name, String? description, int type, bool isPublic, bool isCommunity, SnCloudFile? picture, SnCloudFile? background, String? realmId, String? accountId, SnRealm? realm, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnChatMember>? members
|
String id, String? name, String? description, int type, bool isPublic, bool isCommunity, SnCloudFile? picture, SnCloudFile? background, String? realmId, String? accountId, SnRealm? realm, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnChatMember>? members, bool isPinned
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -65,7 +66,7 @@ class _$SnChatRoomCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnChatRoom
|
/// Create a copy of SnChatRoom
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = freezed,Object? description = freezed,Object? type = null,Object? isPublic = null,Object? isCommunity = null,Object? picture = freezed,Object? background = freezed,Object? realmId = freezed,Object? accountId = freezed,Object? realm = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? members = freezed,}) {
|
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = freezed,Object? description = freezed,Object? type = null,Object? isPublic = null,Object? isCommunity = null,Object? picture = freezed,Object? background = freezed,Object? realmId = freezed,Object? accountId = freezed,Object? realm = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? members = freezed,Object? isPinned = null,}) {
|
||||||
return _then(_self.copyWith(
|
return _then(_self.copyWith(
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -82,7 +83,8 @@ as SnRealm?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore
|
|||||||
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime?,members: freezed == members ? _self.members : members // ignore: cast_nullable_to_non_nullable
|
as DateTime?,members: freezed == members ? _self.members : members // ignore: cast_nullable_to_non_nullable
|
||||||
as List<SnChatMember>?,
|
as List<SnChatMember>?,isPinned: null == isPinned ? _self.isPinned : isPinned // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
/// Create a copy of SnChatRoom
|
/// Create a copy of SnChatRoom
|
||||||
@@ -200,10 +202,10 @@ return $default(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String? name, String? description, int type, bool isPublic, bool isCommunity, SnCloudFile? picture, SnCloudFile? background, String? realmId, String? accountId, SnRealm? realm, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnChatMember>? members)? $default,{required TResult orElse(),}) {final _that = this;
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String? name, String? description, int type, bool isPublic, bool isCommunity, SnCloudFile? picture, SnCloudFile? background, String? realmId, String? accountId, SnRealm? realm, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnChatMember>? members, bool isPinned)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _SnChatRoom() when $default != null:
|
case _SnChatRoom() when $default != null:
|
||||||
return $default(_that.id,_that.name,_that.description,_that.type,_that.isPublic,_that.isCommunity,_that.picture,_that.background,_that.realmId,_that.accountId,_that.realm,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.members);case _:
|
return $default(_that.id,_that.name,_that.description,_that.type,_that.isPublic,_that.isCommunity,_that.picture,_that.background,_that.realmId,_that.accountId,_that.realm,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.members,_that.isPinned);case _:
|
||||||
return orElse();
|
return orElse();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -221,10 +223,10 @@ return $default(_that.id,_that.name,_that.description,_that.type,_that.isPublic,
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String? name, String? description, int type, bool isPublic, bool isCommunity, SnCloudFile? picture, SnCloudFile? background, String? realmId, String? accountId, SnRealm? realm, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnChatMember>? members) $default,) {final _that = this;
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String? name, String? description, int type, bool isPublic, bool isCommunity, SnCloudFile? picture, SnCloudFile? background, String? realmId, String? accountId, SnRealm? realm, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnChatMember>? members, bool isPinned) $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _SnChatRoom():
|
case _SnChatRoom():
|
||||||
return $default(_that.id,_that.name,_that.description,_that.type,_that.isPublic,_that.isCommunity,_that.picture,_that.background,_that.realmId,_that.accountId,_that.realm,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.members);}
|
return $default(_that.id,_that.name,_that.description,_that.type,_that.isPublic,_that.isCommunity,_that.picture,_that.background,_that.realmId,_that.accountId,_that.realm,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.members,_that.isPinned);}
|
||||||
}
|
}
|
||||||
/// A variant of `when` that fallback to returning `null`
|
/// A variant of `when` that fallback to returning `null`
|
||||||
///
|
///
|
||||||
@@ -238,10 +240,10 @@ return $default(_that.id,_that.name,_that.description,_that.type,_that.isPublic,
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String? name, String? description, int type, bool isPublic, bool isCommunity, SnCloudFile? picture, SnCloudFile? background, String? realmId, String? accountId, SnRealm? realm, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnChatMember>? members)? $default,) {final _that = this;
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String? name, String? description, int type, bool isPublic, bool isCommunity, SnCloudFile? picture, SnCloudFile? background, String? realmId, String? accountId, SnRealm? realm, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnChatMember>? members, bool isPinned)? $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _SnChatRoom() when $default != null:
|
case _SnChatRoom() when $default != null:
|
||||||
return $default(_that.id,_that.name,_that.description,_that.type,_that.isPublic,_that.isCommunity,_that.picture,_that.background,_that.realmId,_that.accountId,_that.realm,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.members);case _:
|
return $default(_that.id,_that.name,_that.description,_that.type,_that.isPublic,_that.isCommunity,_that.picture,_that.background,_that.realmId,_that.accountId,_that.realm,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.members,_that.isPinned);case _:
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -253,7 +255,7 @@ return $default(_that.id,_that.name,_that.description,_that.type,_that.isPublic,
|
|||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
||||||
class _SnChatRoom implements SnChatRoom {
|
class _SnChatRoom implements SnChatRoom {
|
||||||
const _SnChatRoom({required this.id, required this.name, required this.description, required this.type, this.isPublic = false, this.isCommunity = false, required this.picture, required this.background, required this.realmId, required this.accountId, required this.realm, required this.createdAt, required this.updatedAt, required this.deletedAt, required final List<SnChatMember>? members}): _members = members;
|
const _SnChatRoom({required this.id, required this.name, required this.description, required this.type, this.isPublic = false, this.isCommunity = false, required this.picture, required this.background, required this.realmId, required this.accountId, required this.realm, required this.createdAt, required this.updatedAt, required this.deletedAt, required final List<SnChatMember>? members, this.isPinned = false}): _members = members;
|
||||||
factory _SnChatRoom.fromJson(Map<String, dynamic> json) => _$SnChatRoomFromJson(json);
|
factory _SnChatRoom.fromJson(Map<String, dynamic> json) => _$SnChatRoomFromJson(json);
|
||||||
|
|
||||||
@override final String id;
|
@override final String id;
|
||||||
@@ -279,6 +281,8 @@ class _SnChatRoom implements SnChatRoom {
|
|||||||
return EqualUnmodifiableListView(value);
|
return EqualUnmodifiableListView(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Frontend data
|
||||||
|
@override@JsonKey() final bool isPinned;
|
||||||
|
|
||||||
/// Create a copy of SnChatRoom
|
/// Create a copy of SnChatRoom
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@@ -293,16 +297,16 @@ Map<String, dynamic> toJson() {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnChatRoom&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.type, type) || other.type == type)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.isCommunity, isCommunity) || other.isCommunity == isCommunity)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&const DeepCollectionEquality().equals(other._members, _members));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnChatRoom&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.type, type) || other.type == type)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.isCommunity, isCommunity) || other.isCommunity == isCommunity)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&const DeepCollectionEquality().equals(other._members, _members)&&(identical(other.isPinned, isPinned) || other.isPinned == isPinned));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,id,name,description,type,isPublic,isCommunity,picture,background,realmId,accountId,realm,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(_members));
|
int get hashCode => Object.hash(runtimeType,id,name,description,type,isPublic,isCommunity,picture,background,realmId,accountId,realm,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(_members),isPinned);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SnChatRoom(id: $id, name: $name, description: $description, type: $type, isPublic: $isPublic, isCommunity: $isCommunity, picture: $picture, background: $background, realmId: $realmId, accountId: $accountId, realm: $realm, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, members: $members)';
|
return 'SnChatRoom(id: $id, name: $name, description: $description, type: $type, isPublic: $isPublic, isCommunity: $isCommunity, picture: $picture, background: $background, realmId: $realmId, accountId: $accountId, realm: $realm, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, members: $members, isPinned: $isPinned)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -313,7 +317,7 @@ abstract mixin class _$SnChatRoomCopyWith<$Res> implements $SnChatRoomCopyWith<$
|
|||||||
factory _$SnChatRoomCopyWith(_SnChatRoom value, $Res Function(_SnChatRoom) _then) = __$SnChatRoomCopyWithImpl;
|
factory _$SnChatRoomCopyWith(_SnChatRoom value, $Res Function(_SnChatRoom) _then) = __$SnChatRoomCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String id, String? name, String? description, int type, bool isPublic, bool isCommunity, SnCloudFile? picture, SnCloudFile? background, String? realmId, String? accountId, SnRealm? realm, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnChatMember>? members
|
String id, String? name, String? description, int type, bool isPublic, bool isCommunity, SnCloudFile? picture, SnCloudFile? background, String? realmId, String? accountId, SnRealm? realm, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnChatMember>? members, bool isPinned
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -330,7 +334,7 @@ class __$SnChatRoomCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnChatRoom
|
/// Create a copy of SnChatRoom
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = freezed,Object? description = freezed,Object? type = null,Object? isPublic = null,Object? isCommunity = null,Object? picture = freezed,Object? background = freezed,Object? realmId = freezed,Object? accountId = freezed,Object? realm = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? members = freezed,}) {
|
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = freezed,Object? description = freezed,Object? type = null,Object? isPublic = null,Object? isCommunity = null,Object? picture = freezed,Object? background = freezed,Object? realmId = freezed,Object? accountId = freezed,Object? realm = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? members = freezed,Object? isPinned = null,}) {
|
||||||
return _then(_SnChatRoom(
|
return _then(_SnChatRoom(
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -347,7 +351,8 @@ as SnRealm?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore
|
|||||||
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime?,members: freezed == members ? _self._members : members // ignore: cast_nullable_to_non_nullable
|
as DateTime?,members: freezed == members ? _self._members : members // ignore: cast_nullable_to_non_nullable
|
||||||
as List<SnChatMember>?,
|
as List<SnChatMember>?,isPinned: null == isPinned ? _self.isPinned : isPinned // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ _SnChatRoom _$SnChatRoomFromJson(Map<String, dynamic> json) => _SnChatRoom(
|
|||||||
members: (json['members'] as List<dynamic>?)
|
members: (json['members'] as List<dynamic>?)
|
||||||
?.map((e) => SnChatMember.fromJson(e as Map<String, dynamic>))
|
?.map((e) => SnChatMember.fromJson(e as Map<String, dynamic>))
|
||||||
.toList(),
|
.toList(),
|
||||||
|
isPinned: json['is_pinned'] as bool? ?? false,
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$SnChatRoomToJson(_SnChatRoom instance) =>
|
Map<String, dynamic> _$SnChatRoomToJson(_SnChatRoom instance) =>
|
||||||
@@ -51,6 +52,7 @@ Map<String, dynamic> _$SnChatRoomToJson(_SnChatRoom instance) =>
|
|||||||
'updated_at': instance.updatedAt.toIso8601String(),
|
'updated_at': instance.updatedAt.toIso8601String(),
|
||||||
'deleted_at': instance.deletedAt?.toIso8601String(),
|
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||||
'members': instance.members?.map((e) => e.toJson()).toList(),
|
'members': instance.members?.map((e) => e.toJson()).toList(),
|
||||||
|
'is_pinned': instance.isPinned,
|
||||||
};
|
};
|
||||||
|
|
||||||
_SnChatMessage _$SnChatMessageFromJson(Map<String, dynamic> json) =>
|
_SnChatMessage _$SnChatMessageFromJson(Map<String, dynamic> json) =>
|
||||||
|
|||||||
16
lib/models/fortune.dart
Normal file
16
lib/models/fortune.dart
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'fortune.g.dart';
|
||||||
|
part 'fortune.freezed.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
sealed class SnFortuneSaying with _$SnFortuneSaying {
|
||||||
|
const factory SnFortuneSaying({
|
||||||
|
required String content,
|
||||||
|
required String source,
|
||||||
|
required String language,
|
||||||
|
}) = _SnFortuneSaying;
|
||||||
|
|
||||||
|
factory SnFortuneSaying.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SnFortuneSayingFromJson(json);
|
||||||
|
}
|
||||||
277
lib/models/fortune.freezed.dart
Normal file
277
lib/models/fortune.freezed.dart
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// coverage:ignore-file
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'fortune.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// dart format off
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SnFortuneSaying {
|
||||||
|
|
||||||
|
String get content; String get source; String get language;
|
||||||
|
/// Create a copy of SnFortuneSaying
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnFortuneSayingCopyWith<SnFortuneSaying> get copyWith => _$SnFortuneSayingCopyWithImpl<SnFortuneSaying>(this as SnFortuneSaying, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this SnFortuneSaying to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnFortuneSaying&&(identical(other.content, content) || other.content == content)&&(identical(other.source, source) || other.source == source)&&(identical(other.language, language) || other.language == language));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,content,source,language);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnFortuneSaying(content: $content, source: $source, language: $language)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $SnFortuneSayingCopyWith<$Res> {
|
||||||
|
factory $SnFortuneSayingCopyWith(SnFortuneSaying value, $Res Function(SnFortuneSaying) _then) = _$SnFortuneSayingCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String content, String source, String language
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$SnFortuneSayingCopyWithImpl<$Res>
|
||||||
|
implements $SnFortuneSayingCopyWith<$Res> {
|
||||||
|
_$SnFortuneSayingCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final SnFortuneSaying _self;
|
||||||
|
final $Res Function(SnFortuneSaying) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnFortuneSaying
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? content = null,Object? source = null,Object? language = null,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
content: null == content ? _self.content : content // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,source: null == source ? _self.source : source // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,language: null == language ? _self.language : language // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Adds pattern-matching-related methods to [SnFortuneSaying].
|
||||||
|
extension SnFortuneSayingPatterns on SnFortuneSaying {
|
||||||
|
/// A variant of `map` that fallback to returning `orElse`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnFortuneSaying value)? $default,{required TResult orElse(),}){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SnFortuneSaying() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// Callbacks receives the raw object, upcasted.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case final Subclass2 value:
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnFortuneSaying value) $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SnFortuneSaying():
|
||||||
|
return $default(_that);}
|
||||||
|
}
|
||||||
|
/// A variant of `map` that fallback to returning `null`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnFortuneSaying value)? $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SnFortuneSaying() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to an `orElse` callback.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String content, String source, String language)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SnFortuneSaying() when $default != null:
|
||||||
|
return $default(_that.content,_that.source,_that.language);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// As opposed to `map`, this offers destructuring.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case Subclass2(:final field2):
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String content, String source, String language) $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SnFortuneSaying():
|
||||||
|
return $default(_that.content,_that.source,_that.language);}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to returning `null`
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String content, String source, String language)? $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SnFortuneSaying() when $default != null:
|
||||||
|
return $default(_that.content,_that.source,_that.language);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _SnFortuneSaying implements SnFortuneSaying {
|
||||||
|
const _SnFortuneSaying({required this.content, required this.source, required this.language});
|
||||||
|
factory _SnFortuneSaying.fromJson(Map<String, dynamic> json) => _$SnFortuneSayingFromJson(json);
|
||||||
|
|
||||||
|
@override final String content;
|
||||||
|
@override final String source;
|
||||||
|
@override final String language;
|
||||||
|
|
||||||
|
/// Create a copy of SnFortuneSaying
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$SnFortuneSayingCopyWith<_SnFortuneSaying> get copyWith => __$SnFortuneSayingCopyWithImpl<_SnFortuneSaying>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$SnFortuneSayingToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnFortuneSaying&&(identical(other.content, content) || other.content == content)&&(identical(other.source, source) || other.source == source)&&(identical(other.language, language) || other.language == language));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,content,source,language);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnFortuneSaying(content: $content, source: $source, language: $language)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$SnFortuneSayingCopyWith<$Res> implements $SnFortuneSayingCopyWith<$Res> {
|
||||||
|
factory _$SnFortuneSayingCopyWith(_SnFortuneSaying value, $Res Function(_SnFortuneSaying) _then) = __$SnFortuneSayingCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
String content, String source, String language
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$SnFortuneSayingCopyWithImpl<$Res>
|
||||||
|
implements _$SnFortuneSayingCopyWith<$Res> {
|
||||||
|
__$SnFortuneSayingCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _SnFortuneSaying _self;
|
||||||
|
final $Res Function(_SnFortuneSaying) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnFortuneSaying
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? content = null,Object? source = null,Object? language = null,}) {
|
||||||
|
return _then(_SnFortuneSaying(
|
||||||
|
content: null == content ? _self.content : content // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,source: null == source ? _self.source : source // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,language: null == language ? _self.language : language // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// dart format on
|
||||||
21
lib/models/fortune.g.dart
Normal file
21
lib/models/fortune.g.dart
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'fortune.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_SnFortuneSaying _$SnFortuneSayingFromJson(Map<String, dynamic> json) =>
|
||||||
|
_SnFortuneSaying(
|
||||||
|
content: json['content'] as String,
|
||||||
|
source: json['source'] as String,
|
||||||
|
language: json['language'] as String,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$SnFortuneSayingToJson(_SnFortuneSaying instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'content': instance.content,
|
||||||
|
'source': instance.source,
|
||||||
|
'language': instance.language,
|
||||||
|
};
|
||||||
@@ -74,15 +74,15 @@ sealed class SnPublisherStats with _$SnPublisherStats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
sealed class SnSubscriptionStatus with _$SnSubscriptionStatus {
|
sealed class SnPublisherSubscription with _$SnPublisherSubscription {
|
||||||
const factory SnSubscriptionStatus({
|
const factory SnPublisherSubscription({
|
||||||
required bool isSubscribed,
|
required String accountId,
|
||||||
required String publisherId,
|
required String publisherId,
|
||||||
required String publisherName,
|
required SnPublisher publisher,
|
||||||
}) = _SnSubscriptionStatus;
|
}) = _SnPublisherSubscription;
|
||||||
|
|
||||||
factory SnSubscriptionStatus.fromJson(Map<String, dynamic> json) =>
|
factory SnPublisherSubscription.fromJson(Map<String, dynamic> json) =>
|
||||||
_$SnSubscriptionStatusFromJson(json);
|
_$SnPublisherSubscriptionFromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
@@ -92,8 +92,9 @@ sealed class ReactInfo with _$ReactInfo {
|
|||||||
|
|
||||||
static String getTranslationKey(String templateKey) {
|
static String getTranslationKey(String templateKey) {
|
||||||
final parts = templateKey.split('_');
|
final parts = templateKey.split('_');
|
||||||
final camelCase =
|
final camelCase = parts
|
||||||
parts.map((p) => p[0].toUpperCase() + p.substring(1)).join();
|
.map((p) => p[0].toUpperCase() + p.substring(1))
|
||||||
|
.join();
|
||||||
return 'reaction$camelCase';
|
return 'reaction$camelCase';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -856,72 +856,81 @@ as int,
|
|||||||
|
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$SnSubscriptionStatus {
|
mixin _$SnPublisherSubscription {
|
||||||
|
|
||||||
bool get isSubscribed; String get publisherId; String get publisherName;
|
String get accountId; String get publisherId; SnPublisher get publisher;
|
||||||
/// Create a copy of SnSubscriptionStatus
|
/// Create a copy of SnPublisherSubscription
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
$SnSubscriptionStatusCopyWith<SnSubscriptionStatus> get copyWith => _$SnSubscriptionStatusCopyWithImpl<SnSubscriptionStatus>(this as SnSubscriptionStatus, _$identity);
|
$SnPublisherSubscriptionCopyWith<SnPublisherSubscription> get copyWith => _$SnPublisherSubscriptionCopyWithImpl<SnPublisherSubscription>(this as SnPublisherSubscription, _$identity);
|
||||||
|
|
||||||
/// Serializes this SnSubscriptionStatus to a JSON map.
|
/// Serializes this SnPublisherSubscription to a JSON map.
|
||||||
Map<String, dynamic> toJson();
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnSubscriptionStatus&&(identical(other.isSubscribed, isSubscribed) || other.isSubscribed == isSubscribed)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.publisherName, publisherName) || other.publisherName == publisherName));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPublisherSubscription&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.publisher, publisher) || other.publisher == publisher));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,isSubscribed,publisherId,publisherName);
|
int get hashCode => Object.hash(runtimeType,accountId,publisherId,publisher);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SnSubscriptionStatus(isSubscribed: $isSubscribed, publisherId: $publisherId, publisherName: $publisherName)';
|
return 'SnPublisherSubscription(accountId: $accountId, publisherId: $publisherId, publisher: $publisher)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
abstract mixin class $SnSubscriptionStatusCopyWith<$Res> {
|
abstract mixin class $SnPublisherSubscriptionCopyWith<$Res> {
|
||||||
factory $SnSubscriptionStatusCopyWith(SnSubscriptionStatus value, $Res Function(SnSubscriptionStatus) _then) = _$SnSubscriptionStatusCopyWithImpl;
|
factory $SnPublisherSubscriptionCopyWith(SnPublisherSubscription value, $Res Function(SnPublisherSubscription) _then) = _$SnPublisherSubscriptionCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
bool isSubscribed, String publisherId, String publisherName
|
String accountId, String publisherId, SnPublisher publisher
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
$SnPublisherCopyWith<$Res> get publisher;
|
||||||
|
|
||||||
}
|
}
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
class _$SnSubscriptionStatusCopyWithImpl<$Res>
|
class _$SnPublisherSubscriptionCopyWithImpl<$Res>
|
||||||
implements $SnSubscriptionStatusCopyWith<$Res> {
|
implements $SnPublisherSubscriptionCopyWith<$Res> {
|
||||||
_$SnSubscriptionStatusCopyWithImpl(this._self, this._then);
|
_$SnPublisherSubscriptionCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
final SnSubscriptionStatus _self;
|
final SnPublisherSubscription _self;
|
||||||
final $Res Function(SnSubscriptionStatus) _then;
|
final $Res Function(SnPublisherSubscription) _then;
|
||||||
|
|
||||||
/// Create a copy of SnSubscriptionStatus
|
/// Create a copy of SnPublisherSubscription
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? isSubscribed = null,Object? publisherId = null,Object? publisherName = null,}) {
|
@pragma('vm:prefer-inline') @override $Res call({Object? accountId = null,Object? publisherId = null,Object? publisher = null,}) {
|
||||||
return _then(_self.copyWith(
|
return _then(_self.copyWith(
|
||||||
isSubscribed: null == isSubscribed ? _self.isSubscribed : isSubscribed // ignore: cast_nullable_to_non_nullable
|
accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable
|
as String,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable
|
||||||
as String,publisherName: null == publisherName ? _self.publisherName : publisherName // ignore: cast_nullable_to_non_nullable
|
as String,publisher: null == publisher ? _self.publisher : publisher // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
as SnPublisher,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
/// Create a copy of SnPublisherSubscription
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnPublisherCopyWith<$Res> get publisher {
|
||||||
|
|
||||||
|
return $SnPublisherCopyWith<$Res>(_self.publisher, (value) {
|
||||||
|
return _then(_self.copyWith(publisher: value));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Adds pattern-matching-related methods to [SnSubscriptionStatus].
|
/// Adds pattern-matching-related methods to [SnPublisherSubscription].
|
||||||
extension SnSubscriptionStatusPatterns on SnSubscriptionStatus {
|
extension SnPublisherSubscriptionPatterns on SnPublisherSubscription {
|
||||||
/// A variant of `map` that fallback to returning `orElse`.
|
/// A variant of `map` that fallback to returning `orElse`.
|
||||||
///
|
///
|
||||||
/// It is equivalent to doing:
|
/// It is equivalent to doing:
|
||||||
@@ -934,10 +943,10 @@ extension SnSubscriptionStatusPatterns on SnSubscriptionStatus {
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnSubscriptionStatus value)? $default,{required TResult orElse(),}){
|
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnPublisherSubscription value)? $default,{required TResult orElse(),}){
|
||||||
final _that = this;
|
final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _SnSubscriptionStatus() when $default != null:
|
case _SnPublisherSubscription() when $default != null:
|
||||||
return $default(_that);case _:
|
return $default(_that);case _:
|
||||||
return orElse();
|
return orElse();
|
||||||
|
|
||||||
@@ -956,10 +965,10 @@ return $default(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnSubscriptionStatus value) $default,){
|
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnPublisherSubscription value) $default,){
|
||||||
final _that = this;
|
final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _SnSubscriptionStatus():
|
case _SnPublisherSubscription():
|
||||||
return $default(_that);}
|
return $default(_that);}
|
||||||
}
|
}
|
||||||
/// A variant of `map` that fallback to returning `null`.
|
/// A variant of `map` that fallback to returning `null`.
|
||||||
@@ -974,10 +983,10 @@ return $default(_that);}
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnSubscriptionStatus value)? $default,){
|
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnPublisherSubscription value)? $default,){
|
||||||
final _that = this;
|
final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _SnSubscriptionStatus() when $default != null:
|
case _SnPublisherSubscription() when $default != null:
|
||||||
return $default(_that);case _:
|
return $default(_that);case _:
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@@ -995,10 +1004,10 @@ return $default(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool isSubscribed, String publisherId, String publisherName)? $default,{required TResult orElse(),}) {final _that = this;
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String accountId, String publisherId, SnPublisher publisher)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _SnSubscriptionStatus() when $default != null:
|
case _SnPublisherSubscription() when $default != null:
|
||||||
return $default(_that.isSubscribed,_that.publisherId,_that.publisherName);case _:
|
return $default(_that.accountId,_that.publisherId,_that.publisher);case _:
|
||||||
return orElse();
|
return orElse();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1016,10 +1025,10 @@ return $default(_that.isSubscribed,_that.publisherId,_that.publisherName);case _
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool isSubscribed, String publisherId, String publisherName) $default,) {final _that = this;
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String accountId, String publisherId, SnPublisher publisher) $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _SnSubscriptionStatus():
|
case _SnPublisherSubscription():
|
||||||
return $default(_that.isSubscribed,_that.publisherId,_that.publisherName);}
|
return $default(_that.accountId,_that.publisherId,_that.publisher);}
|
||||||
}
|
}
|
||||||
/// A variant of `when` that fallback to returning `null`
|
/// A variant of `when` that fallback to returning `null`
|
||||||
///
|
///
|
||||||
@@ -1033,10 +1042,10 @@ return $default(_that.isSubscribed,_that.publisherId,_that.publisherName);}
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool isSubscribed, String publisherId, String publisherName)? $default,) {final _that = this;
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String accountId, String publisherId, SnPublisher publisher)? $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _SnSubscriptionStatus() when $default != null:
|
case _SnPublisherSubscription() when $default != null:
|
||||||
return $default(_that.isSubscribed,_that.publisherId,_that.publisherName);case _:
|
return $default(_that.accountId,_that.publisherId,_that.publisher);case _:
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1047,74 +1056,83 @@ return $default(_that.isSubscribed,_that.publisherId,_that.publisherName);case _
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
||||||
class _SnSubscriptionStatus implements SnSubscriptionStatus {
|
class _SnPublisherSubscription implements SnPublisherSubscription {
|
||||||
const _SnSubscriptionStatus({required this.isSubscribed, required this.publisherId, required this.publisherName});
|
const _SnPublisherSubscription({required this.accountId, required this.publisherId, required this.publisher});
|
||||||
factory _SnSubscriptionStatus.fromJson(Map<String, dynamic> json) => _$SnSubscriptionStatusFromJson(json);
|
factory _SnPublisherSubscription.fromJson(Map<String, dynamic> json) => _$SnPublisherSubscriptionFromJson(json);
|
||||||
|
|
||||||
@override final bool isSubscribed;
|
@override final String accountId;
|
||||||
@override final String publisherId;
|
@override final String publisherId;
|
||||||
@override final String publisherName;
|
@override final SnPublisher publisher;
|
||||||
|
|
||||||
/// Create a copy of SnSubscriptionStatus
|
/// Create a copy of SnPublisherSubscription
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
_$SnSubscriptionStatusCopyWith<_SnSubscriptionStatus> get copyWith => __$SnSubscriptionStatusCopyWithImpl<_SnSubscriptionStatus>(this, _$identity);
|
_$SnPublisherSubscriptionCopyWith<_SnPublisherSubscription> get copyWith => __$SnPublisherSubscriptionCopyWithImpl<_SnPublisherSubscription>(this, _$identity);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
return _$SnSubscriptionStatusToJson(this, );
|
return _$SnPublisherSubscriptionToJson(this, );
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnSubscriptionStatus&&(identical(other.isSubscribed, isSubscribed) || other.isSubscribed == isSubscribed)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.publisherName, publisherName) || other.publisherName == publisherName));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPublisherSubscription&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.publisher, publisher) || other.publisher == publisher));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,isSubscribed,publisherId,publisherName);
|
int get hashCode => Object.hash(runtimeType,accountId,publisherId,publisher);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SnSubscriptionStatus(isSubscribed: $isSubscribed, publisherId: $publisherId, publisherName: $publisherName)';
|
return 'SnPublisherSubscription(accountId: $accountId, publisherId: $publisherId, publisher: $publisher)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
abstract mixin class _$SnSubscriptionStatusCopyWith<$Res> implements $SnSubscriptionStatusCopyWith<$Res> {
|
abstract mixin class _$SnPublisherSubscriptionCopyWith<$Res> implements $SnPublisherSubscriptionCopyWith<$Res> {
|
||||||
factory _$SnSubscriptionStatusCopyWith(_SnSubscriptionStatus value, $Res Function(_SnSubscriptionStatus) _then) = __$SnSubscriptionStatusCopyWithImpl;
|
factory _$SnPublisherSubscriptionCopyWith(_SnPublisherSubscription value, $Res Function(_SnPublisherSubscription) _then) = __$SnPublisherSubscriptionCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
bool isSubscribed, String publisherId, String publisherName
|
String accountId, String publisherId, SnPublisher publisher
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@override $SnPublisherCopyWith<$Res> get publisher;
|
||||||
|
|
||||||
}
|
}
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
class __$SnSubscriptionStatusCopyWithImpl<$Res>
|
class __$SnPublisherSubscriptionCopyWithImpl<$Res>
|
||||||
implements _$SnSubscriptionStatusCopyWith<$Res> {
|
implements _$SnPublisherSubscriptionCopyWith<$Res> {
|
||||||
__$SnSubscriptionStatusCopyWithImpl(this._self, this._then);
|
__$SnPublisherSubscriptionCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
final _SnSubscriptionStatus _self;
|
final _SnPublisherSubscription _self;
|
||||||
final $Res Function(_SnSubscriptionStatus) _then;
|
final $Res Function(_SnPublisherSubscription) _then;
|
||||||
|
|
||||||
/// Create a copy of SnSubscriptionStatus
|
/// Create a copy of SnPublisherSubscription
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? isSubscribed = null,Object? publisherId = null,Object? publisherName = null,}) {
|
@override @pragma('vm:prefer-inline') $Res call({Object? accountId = null,Object? publisherId = null,Object? publisher = null,}) {
|
||||||
return _then(_SnSubscriptionStatus(
|
return _then(_SnPublisherSubscription(
|
||||||
isSubscribed: null == isSubscribed ? _self.isSubscribed : isSubscribed // ignore: cast_nullable_to_non_nullable
|
accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable
|
as String,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable
|
||||||
as String,publisherName: null == publisherName ? _self.publisherName : publisherName // ignore: cast_nullable_to_non_nullable
|
as String,publisher: null == publisher ? _self.publisher : publisher // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
as SnPublisher,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a copy of SnPublisherSubscription
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnPublisherCopyWith<$Res> get publisher {
|
||||||
|
|
||||||
|
return $SnPublisherCopyWith<$Res>(_self.publisher, (value) {
|
||||||
|
return _then(_self.copyWith(publisher: value));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
|
|||||||
@@ -158,20 +158,20 @@ Map<String, dynamic> _$SnPublisherStatsToJson(_SnPublisherStats instance) =>
|
|||||||
'downvote_received': instance.downvoteReceived,
|
'downvote_received': instance.downvoteReceived,
|
||||||
};
|
};
|
||||||
|
|
||||||
_SnSubscriptionStatus _$SnSubscriptionStatusFromJson(
|
_SnPublisherSubscription _$SnPublisherSubscriptionFromJson(
|
||||||
Map<String, dynamic> json,
|
Map<String, dynamic> json,
|
||||||
) => _SnSubscriptionStatus(
|
) => _SnPublisherSubscription(
|
||||||
isSubscribed: json['is_subscribed'] as bool,
|
accountId: json['account_id'] as String,
|
||||||
publisherId: json['publisher_id'] as String,
|
publisherId: json['publisher_id'] as String,
|
||||||
publisherName: json['publisher_name'] as String,
|
publisher: SnPublisher.fromJson(json['publisher'] as Map<String, dynamic>),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$SnSubscriptionStatusToJson(
|
Map<String, dynamic> _$SnPublisherSubscriptionToJson(
|
||||||
_SnSubscriptionStatus instance,
|
_SnPublisherSubscription instance,
|
||||||
) => <String, dynamic>{
|
) => <String, dynamic>{
|
||||||
'is_subscribed': instance.isSubscribed,
|
'account_id': instance.accountId,
|
||||||
'publisher_id': instance.publisherId,
|
'publisher_id': instance.publisherId,
|
||||||
'publisher_name': instance.publisherName,
|
'publisher': instance.publisher.toJson(),
|
||||||
};
|
};
|
||||||
|
|
||||||
_SnPostEmbedView _$SnPostEmbedViewFromJson(Map<String, dynamic> json) =>
|
_SnPostEmbedView _$SnPostEmbedViewFromJson(Map<String, dynamic> json) =>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:island/models/post.dart';
|
import 'package:island/models/post.dart';
|
||||||
|
import 'package:island/models/post_tag.dart';
|
||||||
import 'package:island/utils/text.dart';
|
import 'package:island/utils/text.dart';
|
||||||
|
|
||||||
part 'post_category.freezed.dart';
|
part 'post_category.freezed.dart';
|
||||||
@@ -29,3 +30,21 @@ sealed class SnPostCategory with _$SnPostCategory {
|
|||||||
return name ?? slug;
|
return name ?? slug;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
sealed class SnCategorySubscription with _$SnCategorySubscription {
|
||||||
|
const factory SnCategorySubscription({
|
||||||
|
required String id,
|
||||||
|
required String accountId,
|
||||||
|
required String? categoryId,
|
||||||
|
required SnPostCategory? category,
|
||||||
|
required String? tagId,
|
||||||
|
required SnPostTag? tag,
|
||||||
|
required DateTime createdAt,
|
||||||
|
required DateTime updatedAt,
|
||||||
|
required DateTime? deletedAt,
|
||||||
|
}) = _SnCategorySubscription;
|
||||||
|
|
||||||
|
factory SnCategorySubscription.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SnCategorySubscriptionFromJson(json);
|
||||||
|
}
|
||||||
|
|||||||
@@ -286,4 +286,333 @@ as int,
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SnCategorySubscription {
|
||||||
|
|
||||||
|
String get id; String get accountId; String? get categoryId; SnPostCategory? get category; String? get tagId; SnPostTag? get tag; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
|
||||||
|
/// Create a copy of SnCategorySubscription
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnCategorySubscriptionCopyWith<SnCategorySubscription> get copyWith => _$SnCategorySubscriptionCopyWithImpl<SnCategorySubscription>(this as SnCategorySubscription, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this SnCategorySubscription to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnCategorySubscription&&(identical(other.id, id) || other.id == id)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.categoryId, categoryId) || other.categoryId == categoryId)&&(identical(other.category, category) || other.category == category)&&(identical(other.tagId, tagId) || other.tagId == tagId)&&(identical(other.tag, tag) || other.tag == tag)&&(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.hash(runtimeType,id,accountId,categoryId,category,tagId,tag,createdAt,updatedAt,deletedAt);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnCategorySubscription(id: $id, accountId: $accountId, categoryId: $categoryId, category: $category, tagId: $tagId, tag: $tag, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $SnCategorySubscriptionCopyWith<$Res> {
|
||||||
|
factory $SnCategorySubscriptionCopyWith(SnCategorySubscription value, $Res Function(SnCategorySubscription) _then) = _$SnCategorySubscriptionCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String id, String accountId, String? categoryId, SnPostCategory? category, String? tagId, SnPostTag? tag, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
$SnPostCategoryCopyWith<$Res>? get category;$SnPostTagCopyWith<$Res>? get tag;
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$SnCategorySubscriptionCopyWithImpl<$Res>
|
||||||
|
implements $SnCategorySubscriptionCopyWith<$Res> {
|
||||||
|
_$SnCategorySubscriptionCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final SnCategorySubscription _self;
|
||||||
|
final $Res Function(SnCategorySubscription) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnCategorySubscription
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? accountId = null,Object? categoryId = freezed,Object? category = freezed,Object? tagId = freezed,Object? tag = 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,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,categoryId: freezed == categoryId ? _self.categoryId : categoryId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,category: freezed == category ? _self.category : category // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnPostCategory?,tagId: freezed == tagId ? _self.tagId : tagId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,tag: freezed == tag ? _self.tag : tag // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnPostTag?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
/// Create a copy of SnCategorySubscription
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnPostCategoryCopyWith<$Res>? get category {
|
||||||
|
if (_self.category == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnPostCategoryCopyWith<$Res>(_self.category!, (value) {
|
||||||
|
return _then(_self.copyWith(category: value));
|
||||||
|
});
|
||||||
|
}/// Create a copy of SnCategorySubscription
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnPostTagCopyWith<$Res>? get tag {
|
||||||
|
if (_self.tag == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnPostTagCopyWith<$Res>(_self.tag!, (value) {
|
||||||
|
return _then(_self.copyWith(tag: value));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Adds pattern-matching-related methods to [SnCategorySubscription].
|
||||||
|
extension SnCategorySubscriptionPatterns on SnCategorySubscription {
|
||||||
|
/// A variant of `map` that fallback to returning `orElse`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnCategorySubscription value)? $default,{required TResult orElse(),}){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SnCategorySubscription() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// Callbacks receives the raw object, upcasted.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case final Subclass2 value:
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnCategorySubscription value) $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SnCategorySubscription():
|
||||||
|
return $default(_that);}
|
||||||
|
}
|
||||||
|
/// A variant of `map` that fallback to returning `null`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnCategorySubscription value)? $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SnCategorySubscription() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to an `orElse` callback.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String accountId, String? categoryId, SnPostCategory? category, String? tagId, SnPostTag? tag, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SnCategorySubscription() when $default != null:
|
||||||
|
return $default(_that.id,_that.accountId,_that.categoryId,_that.category,_that.tagId,_that.tag,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// As opposed to `map`, this offers destructuring.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case Subclass2(:final field2):
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String accountId, String? categoryId, SnPostCategory? category, String? tagId, SnPostTag? tag, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SnCategorySubscription():
|
||||||
|
return $default(_that.id,_that.accountId,_that.categoryId,_that.category,_that.tagId,_that.tag,_that.createdAt,_that.updatedAt,_that.deletedAt);}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to returning `null`
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String accountId, String? categoryId, SnPostCategory? category, String? tagId, SnPostTag? tag, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SnCategorySubscription() when $default != null:
|
||||||
|
return $default(_that.id,_that.accountId,_that.categoryId,_that.category,_that.tagId,_that.tag,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _SnCategorySubscription implements SnCategorySubscription {
|
||||||
|
const _SnCategorySubscription({required this.id, required this.accountId, required this.categoryId, required this.category, required this.tagId, required this.tag, required this.createdAt, required this.updatedAt, required this.deletedAt});
|
||||||
|
factory _SnCategorySubscription.fromJson(Map<String, dynamic> json) => _$SnCategorySubscriptionFromJson(json);
|
||||||
|
|
||||||
|
@override final String id;
|
||||||
|
@override final String accountId;
|
||||||
|
@override final String? categoryId;
|
||||||
|
@override final SnPostCategory? category;
|
||||||
|
@override final String? tagId;
|
||||||
|
@override final SnPostTag? tag;
|
||||||
|
@override final DateTime createdAt;
|
||||||
|
@override final DateTime updatedAt;
|
||||||
|
@override final DateTime? deletedAt;
|
||||||
|
|
||||||
|
/// Create a copy of SnCategorySubscription
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$SnCategorySubscriptionCopyWith<_SnCategorySubscription> get copyWith => __$SnCategorySubscriptionCopyWithImpl<_SnCategorySubscription>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$SnCategorySubscriptionToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnCategorySubscription&&(identical(other.id, id) || other.id == id)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.categoryId, categoryId) || other.categoryId == categoryId)&&(identical(other.category, category) || other.category == category)&&(identical(other.tagId, tagId) || other.tagId == tagId)&&(identical(other.tag, tag) || other.tag == tag)&&(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.hash(runtimeType,id,accountId,categoryId,category,tagId,tag,createdAt,updatedAt,deletedAt);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnCategorySubscription(id: $id, accountId: $accountId, categoryId: $categoryId, category: $category, tagId: $tagId, tag: $tag, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$SnCategorySubscriptionCopyWith<$Res> implements $SnCategorySubscriptionCopyWith<$Res> {
|
||||||
|
factory _$SnCategorySubscriptionCopyWith(_SnCategorySubscription value, $Res Function(_SnCategorySubscription) _then) = __$SnCategorySubscriptionCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
String id, String accountId, String? categoryId, SnPostCategory? category, String? tagId, SnPostTag? tag, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@override $SnPostCategoryCopyWith<$Res>? get category;@override $SnPostTagCopyWith<$Res>? get tag;
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$SnCategorySubscriptionCopyWithImpl<$Res>
|
||||||
|
implements _$SnCategorySubscriptionCopyWith<$Res> {
|
||||||
|
__$SnCategorySubscriptionCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _SnCategorySubscription _self;
|
||||||
|
final $Res Function(_SnCategorySubscription) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnCategorySubscription
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? accountId = null,Object? categoryId = freezed,Object? category = freezed,Object? tagId = freezed,Object? tag = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||||
|
return _then(_SnCategorySubscription(
|
||||||
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,categoryId: freezed == categoryId ? _self.categoryId : categoryId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,category: freezed == category ? _self.category : category // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnPostCategory?,tagId: freezed == tagId ? _self.tagId : tagId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,tag: freezed == tag ? _self.tag : tag // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnPostTag?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a copy of SnCategorySubscription
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnPostCategoryCopyWith<$Res>? get category {
|
||||||
|
if (_self.category == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnPostCategoryCopyWith<$Res>(_self.category!, (value) {
|
||||||
|
return _then(_self.copyWith(category: value));
|
||||||
|
});
|
||||||
|
}/// Create a copy of SnCategorySubscription
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnPostTagCopyWith<$Res>? get tag {
|
||||||
|
if (_self.tag == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnPostTagCopyWith<$Res>(_self.tag!, (value) {
|
||||||
|
return _then(_self.copyWith(tag: value));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// dart format on
|
// dart format on
|
||||||
|
|||||||
@@ -27,3 +27,37 @@ Map<String, dynamic> _$SnPostCategoryToJson(_SnPostCategory instance) =>
|
|||||||
'posts': instance.posts.map((e) => e.toJson()).toList(),
|
'posts': instance.posts.map((e) => e.toJson()).toList(),
|
||||||
'usage': instance.usage,
|
'usage': instance.usage,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_SnCategorySubscription _$SnCategorySubscriptionFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _SnCategorySubscription(
|
||||||
|
id: json['id'] as String,
|
||||||
|
accountId: json['account_id'] as String,
|
||||||
|
categoryId: json['category_id'] as String?,
|
||||||
|
category: json['category'] == null
|
||||||
|
? null
|
||||||
|
: SnPostCategory.fromJson(json['category'] as Map<String, dynamic>),
|
||||||
|
tagId: json['tag_id'] as String?,
|
||||||
|
tag: json['tag'] == null
|
||||||
|
? null
|
||||||
|
: SnPostTag.fromJson(json['tag'] as Map<String, dynamic>),
|
||||||
|
createdAt: DateTime.parse(json['created_at'] as String),
|
||||||
|
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||||||
|
deletedAt: json['deleted_at'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['deleted_at'] as String),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$SnCategorySubscriptionToJson(
|
||||||
|
_SnCategorySubscription instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'account_id': instance.accountId,
|
||||||
|
'category_id': instance.categoryId,
|
||||||
|
'category': instance.category?.toJson(),
|
||||||
|
'tag_id': instance.tagId,
|
||||||
|
'tag': instance.tag?.toJson(),
|
||||||
|
'created_at': instance.createdAt.toIso8601String(),
|
||||||
|
'updated_at': instance.updatedAt.toIso8601String(),
|
||||||
|
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||||
|
};
|
||||||
|
|||||||
@@ -3,6 +3,28 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
|||||||
part 'publication_site.freezed.dart';
|
part 'publication_site.freezed.dart';
|
||||||
part 'publication_site.g.dart';
|
part 'publication_site.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
sealed class SnPublicationSiteNavItems with _$SnPublicationSiteNavItems {
|
||||||
|
const factory SnPublicationSiteNavItems({
|
||||||
|
required String label,
|
||||||
|
required String href,
|
||||||
|
}) = _SnPublicationSiteNavItems;
|
||||||
|
|
||||||
|
factory SnPublicationSiteNavItems.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SnPublicationSiteNavItemsFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
sealed class SnPublicationSiteConfig with _$SnPublicationSiteConfig {
|
||||||
|
const factory SnPublicationSiteConfig({
|
||||||
|
String? styleOverride,
|
||||||
|
List<SnPublicationSiteNavItems>? navItems,
|
||||||
|
}) = _SnPublicationSiteConfig;
|
||||||
|
|
||||||
|
factory SnPublicationSiteConfig.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SnPublicationSiteConfigFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
sealed class SnPublicationSite with _$SnPublicationSite {
|
sealed class SnPublicationSite with _$SnPublicationSite {
|
||||||
const factory SnPublicationSite({
|
const factory SnPublicationSite({
|
||||||
@@ -16,6 +38,7 @@ sealed class SnPublicationSite with _$SnPublicationSite {
|
|||||||
required DateTime createdAt,
|
required DateTime createdAt,
|
||||||
required DateTime updatedAt,
|
required DateTime updatedAt,
|
||||||
required List<SnPublicationPage> pages,
|
required List<SnPublicationPage> pages,
|
||||||
|
required SnPublicationSiteConfig config,
|
||||||
}) = _SnPublicationSite;
|
}) = _SnPublicationSite;
|
||||||
|
|
||||||
factory SnPublicationSite.fromJson(Map<String, dynamic> json) =>
|
factory SnPublicationSite.fromJson(Map<String, dynamic> json) =>
|
||||||
|
|||||||
@@ -12,10 +12,538 @@ part of 'publication_site.dart';
|
|||||||
// dart format off
|
// dart format off
|
||||||
T _$identity<T>(T value) => value;
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SnPublicationSiteNavItems {
|
||||||
|
|
||||||
|
String get label; String get href;
|
||||||
|
/// Create a copy of SnPublicationSiteNavItems
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnPublicationSiteNavItemsCopyWith<SnPublicationSiteNavItems> get copyWith => _$SnPublicationSiteNavItemsCopyWithImpl<SnPublicationSiteNavItems>(this as SnPublicationSiteNavItems, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this SnPublicationSiteNavItems to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPublicationSiteNavItems&&(identical(other.label, label) || other.label == label)&&(identical(other.href, href) || other.href == href));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,label,href);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnPublicationSiteNavItems(label: $label, href: $href)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $SnPublicationSiteNavItemsCopyWith<$Res> {
|
||||||
|
factory $SnPublicationSiteNavItemsCopyWith(SnPublicationSiteNavItems value, $Res Function(SnPublicationSiteNavItems) _then) = _$SnPublicationSiteNavItemsCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String label, String href
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$SnPublicationSiteNavItemsCopyWithImpl<$Res>
|
||||||
|
implements $SnPublicationSiteNavItemsCopyWith<$Res> {
|
||||||
|
_$SnPublicationSiteNavItemsCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final SnPublicationSiteNavItems _self;
|
||||||
|
final $Res Function(SnPublicationSiteNavItems) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnPublicationSiteNavItems
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? label = null,Object? href = null,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
label: null == label ? _self.label : label // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,href: null == href ? _self.href : href // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Adds pattern-matching-related methods to [SnPublicationSiteNavItems].
|
||||||
|
extension SnPublicationSiteNavItemsPatterns on SnPublicationSiteNavItems {
|
||||||
|
/// A variant of `map` that fallback to returning `orElse`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnPublicationSiteNavItems value)? $default,{required TResult orElse(),}){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SnPublicationSiteNavItems() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// Callbacks receives the raw object, upcasted.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case final Subclass2 value:
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnPublicationSiteNavItems value) $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SnPublicationSiteNavItems():
|
||||||
|
return $default(_that);}
|
||||||
|
}
|
||||||
|
/// A variant of `map` that fallback to returning `null`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnPublicationSiteNavItems value)? $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SnPublicationSiteNavItems() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to an `orElse` callback.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String label, String href)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SnPublicationSiteNavItems() when $default != null:
|
||||||
|
return $default(_that.label,_that.href);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// As opposed to `map`, this offers destructuring.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case Subclass2(:final field2):
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String label, String href) $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SnPublicationSiteNavItems():
|
||||||
|
return $default(_that.label,_that.href);}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to returning `null`
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String label, String href)? $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SnPublicationSiteNavItems() when $default != null:
|
||||||
|
return $default(_that.label,_that.href);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _SnPublicationSiteNavItems implements SnPublicationSiteNavItems {
|
||||||
|
const _SnPublicationSiteNavItems({required this.label, required this.href});
|
||||||
|
factory _SnPublicationSiteNavItems.fromJson(Map<String, dynamic> json) => _$SnPublicationSiteNavItemsFromJson(json);
|
||||||
|
|
||||||
|
@override final String label;
|
||||||
|
@override final String href;
|
||||||
|
|
||||||
|
/// Create a copy of SnPublicationSiteNavItems
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$SnPublicationSiteNavItemsCopyWith<_SnPublicationSiteNavItems> get copyWith => __$SnPublicationSiteNavItemsCopyWithImpl<_SnPublicationSiteNavItems>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$SnPublicationSiteNavItemsToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPublicationSiteNavItems&&(identical(other.label, label) || other.label == label)&&(identical(other.href, href) || other.href == href));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,label,href);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnPublicationSiteNavItems(label: $label, href: $href)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$SnPublicationSiteNavItemsCopyWith<$Res> implements $SnPublicationSiteNavItemsCopyWith<$Res> {
|
||||||
|
factory _$SnPublicationSiteNavItemsCopyWith(_SnPublicationSiteNavItems value, $Res Function(_SnPublicationSiteNavItems) _then) = __$SnPublicationSiteNavItemsCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
String label, String href
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$SnPublicationSiteNavItemsCopyWithImpl<$Res>
|
||||||
|
implements _$SnPublicationSiteNavItemsCopyWith<$Res> {
|
||||||
|
__$SnPublicationSiteNavItemsCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _SnPublicationSiteNavItems _self;
|
||||||
|
final $Res Function(_SnPublicationSiteNavItems) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnPublicationSiteNavItems
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? label = null,Object? href = null,}) {
|
||||||
|
return _then(_SnPublicationSiteNavItems(
|
||||||
|
label: null == label ? _self.label : label // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,href: null == href ? _self.href : href // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SnPublicationSiteConfig {
|
||||||
|
|
||||||
|
String? get styleOverride; List<SnPublicationSiteNavItems>? get navItems;
|
||||||
|
/// Create a copy of SnPublicationSiteConfig
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnPublicationSiteConfigCopyWith<SnPublicationSiteConfig> get copyWith => _$SnPublicationSiteConfigCopyWithImpl<SnPublicationSiteConfig>(this as SnPublicationSiteConfig, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this SnPublicationSiteConfig to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPublicationSiteConfig&&(identical(other.styleOverride, styleOverride) || other.styleOverride == styleOverride)&&const DeepCollectionEquality().equals(other.navItems, navItems));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,styleOverride,const DeepCollectionEquality().hash(navItems));
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnPublicationSiteConfig(styleOverride: $styleOverride, navItems: $navItems)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $SnPublicationSiteConfigCopyWith<$Res> {
|
||||||
|
factory $SnPublicationSiteConfigCopyWith(SnPublicationSiteConfig value, $Res Function(SnPublicationSiteConfig) _then) = _$SnPublicationSiteConfigCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String? styleOverride, List<SnPublicationSiteNavItems>? navItems
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$SnPublicationSiteConfigCopyWithImpl<$Res>
|
||||||
|
implements $SnPublicationSiteConfigCopyWith<$Res> {
|
||||||
|
_$SnPublicationSiteConfigCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final SnPublicationSiteConfig _self;
|
||||||
|
final $Res Function(SnPublicationSiteConfig) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnPublicationSiteConfig
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? styleOverride = freezed,Object? navItems = freezed,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
styleOverride: freezed == styleOverride ? _self.styleOverride : styleOverride // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,navItems: freezed == navItems ? _self.navItems : navItems // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<SnPublicationSiteNavItems>?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Adds pattern-matching-related methods to [SnPublicationSiteConfig].
|
||||||
|
extension SnPublicationSiteConfigPatterns on SnPublicationSiteConfig {
|
||||||
|
/// A variant of `map` that fallback to returning `orElse`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnPublicationSiteConfig value)? $default,{required TResult orElse(),}){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SnPublicationSiteConfig() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// Callbacks receives the raw object, upcasted.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case final Subclass2 value:
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnPublicationSiteConfig value) $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SnPublicationSiteConfig():
|
||||||
|
return $default(_that);}
|
||||||
|
}
|
||||||
|
/// A variant of `map` that fallback to returning `null`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnPublicationSiteConfig value)? $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SnPublicationSiteConfig() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to an `orElse` callback.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String? styleOverride, List<SnPublicationSiteNavItems>? navItems)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SnPublicationSiteConfig() when $default != null:
|
||||||
|
return $default(_that.styleOverride,_that.navItems);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// As opposed to `map`, this offers destructuring.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case Subclass2(:final field2):
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String? styleOverride, List<SnPublicationSiteNavItems>? navItems) $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SnPublicationSiteConfig():
|
||||||
|
return $default(_that.styleOverride,_that.navItems);}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to returning `null`
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String? styleOverride, List<SnPublicationSiteNavItems>? navItems)? $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SnPublicationSiteConfig() when $default != null:
|
||||||
|
return $default(_that.styleOverride,_that.navItems);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _SnPublicationSiteConfig implements SnPublicationSiteConfig {
|
||||||
|
const _SnPublicationSiteConfig({this.styleOverride, final List<SnPublicationSiteNavItems>? navItems}): _navItems = navItems;
|
||||||
|
factory _SnPublicationSiteConfig.fromJson(Map<String, dynamic> json) => _$SnPublicationSiteConfigFromJson(json);
|
||||||
|
|
||||||
|
@override final String? styleOverride;
|
||||||
|
final List<SnPublicationSiteNavItems>? _navItems;
|
||||||
|
@override List<SnPublicationSiteNavItems>? get navItems {
|
||||||
|
final value = _navItems;
|
||||||
|
if (value == null) return null;
|
||||||
|
if (_navItems is EqualUnmodifiableListView) return _navItems;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Create a copy of SnPublicationSiteConfig
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$SnPublicationSiteConfigCopyWith<_SnPublicationSiteConfig> get copyWith => __$SnPublicationSiteConfigCopyWithImpl<_SnPublicationSiteConfig>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$SnPublicationSiteConfigToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPublicationSiteConfig&&(identical(other.styleOverride, styleOverride) || other.styleOverride == styleOverride)&&const DeepCollectionEquality().equals(other._navItems, _navItems));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,styleOverride,const DeepCollectionEquality().hash(_navItems));
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnPublicationSiteConfig(styleOverride: $styleOverride, navItems: $navItems)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$SnPublicationSiteConfigCopyWith<$Res> implements $SnPublicationSiteConfigCopyWith<$Res> {
|
||||||
|
factory _$SnPublicationSiteConfigCopyWith(_SnPublicationSiteConfig value, $Res Function(_SnPublicationSiteConfig) _then) = __$SnPublicationSiteConfigCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
String? styleOverride, List<SnPublicationSiteNavItems>? navItems
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$SnPublicationSiteConfigCopyWithImpl<$Res>
|
||||||
|
implements _$SnPublicationSiteConfigCopyWith<$Res> {
|
||||||
|
__$SnPublicationSiteConfigCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _SnPublicationSiteConfig _self;
|
||||||
|
final $Res Function(_SnPublicationSiteConfig) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnPublicationSiteConfig
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? styleOverride = freezed,Object? navItems = freezed,}) {
|
||||||
|
return _then(_SnPublicationSiteConfig(
|
||||||
|
styleOverride: freezed == styleOverride ? _self.styleOverride : styleOverride // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,navItems: freezed == navItems ? _self._navItems : navItems // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<SnPublicationSiteNavItems>?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$SnPublicationSite {
|
mixin _$SnPublicationSite {
|
||||||
|
|
||||||
String get id; String get slug; String get name; String? get description; int? get mode; String get publisherId; String get accountId; DateTime get createdAt; DateTime get updatedAt; List<SnPublicationPage> get pages;
|
String get id; String get slug; String get name; String? get description; int? get mode; String get publisherId; String get accountId; DateTime get createdAt; DateTime get updatedAt; List<SnPublicationPage> get pages; SnPublicationSiteConfig get config;
|
||||||
/// Create a copy of SnPublicationSite
|
/// Create a copy of SnPublicationSite
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@@ -28,16 +556,16 @@ $SnPublicationSiteCopyWith<SnPublicationSite> get copyWith => _$SnPublicationSit
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPublicationSite&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.mode, mode) || other.mode == mode)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&const DeepCollectionEquality().equals(other.pages, pages));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPublicationSite&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.mode, mode) || other.mode == mode)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&const DeepCollectionEquality().equals(other.pages, pages)&&(identical(other.config, config) || other.config == config));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,id,slug,name,description,mode,publisherId,accountId,createdAt,updatedAt,const DeepCollectionEquality().hash(pages));
|
int get hashCode => Object.hash(runtimeType,id,slug,name,description,mode,publisherId,accountId,createdAt,updatedAt,const DeepCollectionEquality().hash(pages),config);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SnPublicationSite(id: $id, slug: $slug, name: $name, description: $description, mode: $mode, publisherId: $publisherId, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, pages: $pages)';
|
return 'SnPublicationSite(id: $id, slug: $slug, name: $name, description: $description, mode: $mode, publisherId: $publisherId, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, pages: $pages, config: $config)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -48,11 +576,11 @@ abstract mixin class $SnPublicationSiteCopyWith<$Res> {
|
|||||||
factory $SnPublicationSiteCopyWith(SnPublicationSite value, $Res Function(SnPublicationSite) _then) = _$SnPublicationSiteCopyWithImpl;
|
factory $SnPublicationSiteCopyWith(SnPublicationSite value, $Res Function(SnPublicationSite) _then) = _$SnPublicationSiteCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String id, String slug, String name, String? description, int? mode, String publisherId, String accountId, DateTime createdAt, DateTime updatedAt, List<SnPublicationPage> pages
|
String id, String slug, String name, String? description, int? mode, String publisherId, String accountId, DateTime createdAt, DateTime updatedAt, List<SnPublicationPage> pages, SnPublicationSiteConfig config
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
$SnPublicationSiteConfigCopyWith<$Res> get config;
|
||||||
|
|
||||||
}
|
}
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@@ -65,7 +593,7 @@ class _$SnPublicationSiteCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnPublicationSite
|
/// Create a copy of SnPublicationSite
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? slug = null,Object? name = null,Object? description = freezed,Object? mode = freezed,Object? publisherId = null,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? pages = null,}) {
|
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? slug = null,Object? name = null,Object? description = freezed,Object? mode = freezed,Object? publisherId = null,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? pages = null,Object? config = null,}) {
|
||||||
return _then(_self.copyWith(
|
return _then(_self.copyWith(
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable
|
as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -77,10 +605,20 @@ as String,accountId: null == accountId ? _self.accountId : accountId // ignore:
|
|||||||
as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,pages: null == pages ? _self.pages : pages // ignore: cast_nullable_to_non_nullable
|
as DateTime,pages: null == pages ? _self.pages : pages // ignore: cast_nullable_to_non_nullable
|
||||||
as List<SnPublicationPage>,
|
as List<SnPublicationPage>,config: null == config ? _self.config : config // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnPublicationSiteConfig,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
/// Create a copy of SnPublicationSite
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnPublicationSiteConfigCopyWith<$Res> get config {
|
||||||
|
|
||||||
|
return $SnPublicationSiteConfigCopyWith<$Res>(_self.config, (value) {
|
||||||
|
return _then(_self.copyWith(config: value));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -159,10 +697,10 @@ return $default(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String slug, String name, String? description, int? mode, String publisherId, String accountId, DateTime createdAt, DateTime updatedAt, List<SnPublicationPage> pages)? $default,{required TResult orElse(),}) {final _that = this;
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String slug, String name, String? description, int? mode, String publisherId, String accountId, DateTime createdAt, DateTime updatedAt, List<SnPublicationPage> pages, SnPublicationSiteConfig config)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _SnPublicationSite() when $default != null:
|
case _SnPublicationSite() when $default != null:
|
||||||
return $default(_that.id,_that.slug,_that.name,_that.description,_that.mode,_that.publisherId,_that.accountId,_that.createdAt,_that.updatedAt,_that.pages);case _:
|
return $default(_that.id,_that.slug,_that.name,_that.description,_that.mode,_that.publisherId,_that.accountId,_that.createdAt,_that.updatedAt,_that.pages,_that.config);case _:
|
||||||
return orElse();
|
return orElse();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -180,10 +718,10 @@ return $default(_that.id,_that.slug,_that.name,_that.description,_that.mode,_tha
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String slug, String name, String? description, int? mode, String publisherId, String accountId, DateTime createdAt, DateTime updatedAt, List<SnPublicationPage> pages) $default,) {final _that = this;
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String slug, String name, String? description, int? mode, String publisherId, String accountId, DateTime createdAt, DateTime updatedAt, List<SnPublicationPage> pages, SnPublicationSiteConfig config) $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _SnPublicationSite():
|
case _SnPublicationSite():
|
||||||
return $default(_that.id,_that.slug,_that.name,_that.description,_that.mode,_that.publisherId,_that.accountId,_that.createdAt,_that.updatedAt,_that.pages);}
|
return $default(_that.id,_that.slug,_that.name,_that.description,_that.mode,_that.publisherId,_that.accountId,_that.createdAt,_that.updatedAt,_that.pages,_that.config);}
|
||||||
}
|
}
|
||||||
/// A variant of `when` that fallback to returning `null`
|
/// A variant of `when` that fallback to returning `null`
|
||||||
///
|
///
|
||||||
@@ -197,10 +735,10 @@ return $default(_that.id,_that.slug,_that.name,_that.description,_that.mode,_tha
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String slug, String name, String? description, int? mode, String publisherId, String accountId, DateTime createdAt, DateTime updatedAt, List<SnPublicationPage> pages)? $default,) {final _that = this;
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String slug, String name, String? description, int? mode, String publisherId, String accountId, DateTime createdAt, DateTime updatedAt, List<SnPublicationPage> pages, SnPublicationSiteConfig config)? $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _SnPublicationSite() when $default != null:
|
case _SnPublicationSite() when $default != null:
|
||||||
return $default(_that.id,_that.slug,_that.name,_that.description,_that.mode,_that.publisherId,_that.accountId,_that.createdAt,_that.updatedAt,_that.pages);case _:
|
return $default(_that.id,_that.slug,_that.name,_that.description,_that.mode,_that.publisherId,_that.accountId,_that.createdAt,_that.updatedAt,_that.pages,_that.config);case _:
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -212,7 +750,7 @@ return $default(_that.id,_that.slug,_that.name,_that.description,_that.mode,_tha
|
|||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
||||||
class _SnPublicationSite implements SnPublicationSite {
|
class _SnPublicationSite implements SnPublicationSite {
|
||||||
const _SnPublicationSite({required this.id, required this.slug, required this.name, this.description, this.mode, required this.publisherId, required this.accountId, required this.createdAt, required this.updatedAt, required final List<SnPublicationPage> pages}): _pages = pages;
|
const _SnPublicationSite({required this.id, required this.slug, required this.name, this.description, this.mode, required this.publisherId, required this.accountId, required this.createdAt, required this.updatedAt, required final List<SnPublicationPage> pages, required this.config}): _pages = pages;
|
||||||
factory _SnPublicationSite.fromJson(Map<String, dynamic> json) => _$SnPublicationSiteFromJson(json);
|
factory _SnPublicationSite.fromJson(Map<String, dynamic> json) => _$SnPublicationSiteFromJson(json);
|
||||||
|
|
||||||
@override final String id;
|
@override final String id;
|
||||||
@@ -231,6 +769,7 @@ class _SnPublicationSite implements SnPublicationSite {
|
|||||||
return EqualUnmodifiableListView(_pages);
|
return EqualUnmodifiableListView(_pages);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override final SnPublicationSiteConfig config;
|
||||||
|
|
||||||
/// Create a copy of SnPublicationSite
|
/// Create a copy of SnPublicationSite
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@@ -245,16 +784,16 @@ Map<String, dynamic> toJson() {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPublicationSite&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.mode, mode) || other.mode == mode)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&const DeepCollectionEquality().equals(other._pages, _pages));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPublicationSite&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.mode, mode) || other.mode == mode)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&const DeepCollectionEquality().equals(other._pages, _pages)&&(identical(other.config, config) || other.config == config));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,id,slug,name,description,mode,publisherId,accountId,createdAt,updatedAt,const DeepCollectionEquality().hash(_pages));
|
int get hashCode => Object.hash(runtimeType,id,slug,name,description,mode,publisherId,accountId,createdAt,updatedAt,const DeepCollectionEquality().hash(_pages),config);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SnPublicationSite(id: $id, slug: $slug, name: $name, description: $description, mode: $mode, publisherId: $publisherId, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, pages: $pages)';
|
return 'SnPublicationSite(id: $id, slug: $slug, name: $name, description: $description, mode: $mode, publisherId: $publisherId, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, pages: $pages, config: $config)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -265,11 +804,11 @@ abstract mixin class _$SnPublicationSiteCopyWith<$Res> implements $SnPublication
|
|||||||
factory _$SnPublicationSiteCopyWith(_SnPublicationSite value, $Res Function(_SnPublicationSite) _then) = __$SnPublicationSiteCopyWithImpl;
|
factory _$SnPublicationSiteCopyWith(_SnPublicationSite value, $Res Function(_SnPublicationSite) _then) = __$SnPublicationSiteCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String id, String slug, String name, String? description, int? mode, String publisherId, String accountId, DateTime createdAt, DateTime updatedAt, List<SnPublicationPage> pages
|
String id, String slug, String name, String? description, int? mode, String publisherId, String accountId, DateTime createdAt, DateTime updatedAt, List<SnPublicationPage> pages, SnPublicationSiteConfig config
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@override $SnPublicationSiteConfigCopyWith<$Res> get config;
|
||||||
|
|
||||||
}
|
}
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@@ -282,7 +821,7 @@ class __$SnPublicationSiteCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnPublicationSite
|
/// Create a copy of SnPublicationSite
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? slug = null,Object? name = null,Object? description = freezed,Object? mode = freezed,Object? publisherId = null,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? pages = null,}) {
|
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? slug = null,Object? name = null,Object? description = freezed,Object? mode = freezed,Object? publisherId = null,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? pages = null,Object? config = null,}) {
|
||||||
return _then(_SnPublicationSite(
|
return _then(_SnPublicationSite(
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable
|
as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -294,11 +833,21 @@ as String,accountId: null == accountId ? _self.accountId : accountId // ignore:
|
|||||||
as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,pages: null == pages ? _self._pages : pages // ignore: cast_nullable_to_non_nullable
|
as DateTime,pages: null == pages ? _self._pages : pages // ignore: cast_nullable_to_non_nullable
|
||||||
as List<SnPublicationPage>,
|
as List<SnPublicationPage>,config: null == config ? _self.config : config // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnPublicationSiteConfig,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a copy of SnPublicationSite
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnPublicationSiteConfigCopyWith<$Res> get config {
|
||||||
|
|
||||||
|
return $SnPublicationSiteConfigCopyWith<$Res>(_self.config, (value) {
|
||||||
|
return _then(_self.copyWith(config: value));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,35 @@ part of 'publication_site.dart';
|
|||||||
// JsonSerializableGenerator
|
// JsonSerializableGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
|
_SnPublicationSiteNavItems _$SnPublicationSiteNavItemsFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _SnPublicationSiteNavItems(
|
||||||
|
label: json['label'] as String,
|
||||||
|
href: json['href'] as String,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$SnPublicationSiteNavItemsToJson(
|
||||||
|
_SnPublicationSiteNavItems instance,
|
||||||
|
) => <String, dynamic>{'label': instance.label, 'href': instance.href};
|
||||||
|
|
||||||
|
_SnPublicationSiteConfig _$SnPublicationSiteConfigFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _SnPublicationSiteConfig(
|
||||||
|
styleOverride: json['style_override'] as String?,
|
||||||
|
navItems: (json['nav_items'] as List<dynamic>?)
|
||||||
|
?.map(
|
||||||
|
(e) => SnPublicationSiteNavItems.fromJson(e as Map<String, dynamic>),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$SnPublicationSiteConfigToJson(
|
||||||
|
_SnPublicationSiteConfig instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'style_override': instance.styleOverride,
|
||||||
|
'nav_items': instance.navItems?.map((e) => e.toJson()).toList(),
|
||||||
|
};
|
||||||
|
|
||||||
_SnPublicationSite _$SnPublicationSiteFromJson(Map<String, dynamic> json) =>
|
_SnPublicationSite _$SnPublicationSiteFromJson(Map<String, dynamic> json) =>
|
||||||
_SnPublicationSite(
|
_SnPublicationSite(
|
||||||
id: json['id'] as String,
|
id: json['id'] as String,
|
||||||
@@ -20,6 +49,9 @@ _SnPublicationSite _$SnPublicationSiteFromJson(Map<String, dynamic> json) =>
|
|||||||
pages: (json['pages'] as List<dynamic>)
|
pages: (json['pages'] as List<dynamic>)
|
||||||
.map((e) => SnPublicationPage.fromJson(e as Map<String, dynamic>))
|
.map((e) => SnPublicationPage.fromJson(e as Map<String, dynamic>))
|
||||||
.toList(),
|
.toList(),
|
||||||
|
config: SnPublicationSiteConfig.fromJson(
|
||||||
|
json['config'] as Map<String, dynamic>,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$SnPublicationSiteToJson(_SnPublicationSite instance) =>
|
Map<String, dynamic> _$SnPublicationSiteToJson(_SnPublicationSite instance) =>
|
||||||
@@ -34,6 +66,7 @@ Map<String, dynamic> _$SnPublicationSiteToJson(_SnPublicationSite instance) =>
|
|||||||
'created_at': instance.createdAt.toIso8601String(),
|
'created_at': instance.createdAt.toIso8601String(),
|
||||||
'updated_at': instance.updatedAt.toIso8601String(),
|
'updated_at': instance.updatedAt.toIso8601String(),
|
||||||
'pages': instance.pages.map((e) => e.toJson()).toList(),
|
'pages': instance.pages.map((e) => e.toJson()).toList(),
|
||||||
|
'config': instance.config.toJson(),
|
||||||
};
|
};
|
||||||
|
|
||||||
_SnPublicationPage _$SnPublicationPageFromJson(Map<String, dynamic> json) =>
|
_SnPublicationPage _$SnPublicationPageFromJson(Map<String, dynamic> json) =>
|
||||||
|
|||||||
231
lib/models/route_item.dart
Normal file
231
lib/models/route_item.dart
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
|
||||||
|
part 'route_item.freezed.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
sealed class RouteItem with _$RouteItem {
|
||||||
|
const factory RouteItem({
|
||||||
|
required String name,
|
||||||
|
required String path,
|
||||||
|
required String description,
|
||||||
|
@Default([]) List<String> searchableAliases,
|
||||||
|
required IconData icon,
|
||||||
|
}) = _RouteItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<RouteItem> kAvailableRoutes = [
|
||||||
|
RouteItem(
|
||||||
|
name: 'dashboard'.tr(),
|
||||||
|
path: '/',
|
||||||
|
description: 'dashboardDescription'.tr(),
|
||||||
|
searchableAliases: ['dashboard', 'home'],
|
||||||
|
icon: Symbols.home,
|
||||||
|
),
|
||||||
|
RouteItem(
|
||||||
|
name: 'explore'.tr(),
|
||||||
|
path: '/explore',
|
||||||
|
description: 'exploreDescription'.tr(),
|
||||||
|
searchableAliases: ['explore', 'discover'],
|
||||||
|
icon: Symbols.explore,
|
||||||
|
),
|
||||||
|
RouteItem(
|
||||||
|
name: 'searchPosts'.tr(),
|
||||||
|
path: '/posts/search',
|
||||||
|
description: 'searchPostsDescription'.tr(),
|
||||||
|
searchableAliases: ['search', 'posts'],
|
||||||
|
icon: Symbols.search,
|
||||||
|
),
|
||||||
|
RouteItem(
|
||||||
|
name: 'postShuffle'.tr(),
|
||||||
|
path: '/posts/shuffle',
|
||||||
|
description: 'postShuffleDescription'.tr(),
|
||||||
|
searchableAliases: ['shuffle', 'random', 'posts'],
|
||||||
|
icon: Symbols.shuffle,
|
||||||
|
),
|
||||||
|
RouteItem(
|
||||||
|
name: 'postTagsCategories'.tr(),
|
||||||
|
path: '/posts/categories',
|
||||||
|
description: 'postTagsCategoriesDescription'.tr(),
|
||||||
|
searchableAliases: ['tags', 'categories', 'posts'],
|
||||||
|
icon: Symbols.category,
|
||||||
|
),
|
||||||
|
RouteItem(
|
||||||
|
name: 'discoverRealms'.tr(),
|
||||||
|
path: '/discovery/realms',
|
||||||
|
description: 'discoverRealmsDescription'.tr(),
|
||||||
|
searchableAliases: ['realms', 'groups', 'communities'],
|
||||||
|
icon: Symbols.public,
|
||||||
|
),
|
||||||
|
RouteItem(
|
||||||
|
name: 'chat'.tr(),
|
||||||
|
path: '/chat',
|
||||||
|
description: 'chatDescription'.tr(),
|
||||||
|
searchableAliases: ['chat', 'messages', 'conversations', 'dm'],
|
||||||
|
icon: Symbols.chat,
|
||||||
|
),
|
||||||
|
RouteItem(
|
||||||
|
name: 'realms'.tr(),
|
||||||
|
path: '/realms',
|
||||||
|
description: 'realmsDescription'.tr(),
|
||||||
|
searchableAliases: ['realms', 'groups', 'communities'],
|
||||||
|
icon: Symbols.group,
|
||||||
|
),
|
||||||
|
RouteItem(
|
||||||
|
name: 'account'.tr(),
|
||||||
|
path: '/account',
|
||||||
|
description: 'accountDescription'.tr(),
|
||||||
|
searchableAliases: ['account', 'me', 'profile', 'user'],
|
||||||
|
icon: Symbols.person,
|
||||||
|
),
|
||||||
|
RouteItem(
|
||||||
|
name: 'stickerMarketplace'.tr(),
|
||||||
|
path: '/stickers',
|
||||||
|
description: 'stickerMarketplaceDescription'.tr(),
|
||||||
|
searchableAliases: ['stickers', 'marketplace', 'emojis', 'emojis'],
|
||||||
|
icon: Symbols.emoji_emotions,
|
||||||
|
),
|
||||||
|
RouteItem(
|
||||||
|
name: 'webFeeds'.tr(),
|
||||||
|
path: '/feeds',
|
||||||
|
description: 'webFeedsDescription'.tr(),
|
||||||
|
searchableAliases: ['feeds', 'web feeds', 'rss', 'news'],
|
||||||
|
icon: Symbols.feed,
|
||||||
|
),
|
||||||
|
RouteItem(
|
||||||
|
name: 'wallet'.tr(),
|
||||||
|
path: '/account/wallet',
|
||||||
|
description: 'walletDescription'.tr(),
|
||||||
|
searchableAliases: [
|
||||||
|
'wallet',
|
||||||
|
'balance',
|
||||||
|
'money',
|
||||||
|
'source points',
|
||||||
|
'gold points',
|
||||||
|
'nsp',
|
||||||
|
'shd',
|
||||||
|
],
|
||||||
|
icon: Symbols.account_balance_wallet,
|
||||||
|
),
|
||||||
|
RouteItem(
|
||||||
|
name: 'relationships'.tr(),
|
||||||
|
path: '/account/relationships',
|
||||||
|
description: 'relationshipsDescription'.tr(),
|
||||||
|
searchableAliases: ['relationships', 'friends', 'block list', 'blocks'],
|
||||||
|
icon: Symbols.people,
|
||||||
|
),
|
||||||
|
RouteItem(
|
||||||
|
name: 'updateYourProfile'.tr(),
|
||||||
|
path: '/account/me/update',
|
||||||
|
description: 'updateYourProfileDescription'.tr(),
|
||||||
|
searchableAliases: ['profile', 'update', 'edit', 'my profile'],
|
||||||
|
icon: Symbols.edit,
|
||||||
|
),
|
||||||
|
RouteItem(
|
||||||
|
name: 'leveling'.tr(),
|
||||||
|
path: '/account/me/leveling',
|
||||||
|
description: 'levelingDescription'.tr(),
|
||||||
|
searchableAliases: [
|
||||||
|
'leveling',
|
||||||
|
'level',
|
||||||
|
'levels',
|
||||||
|
'subscriptions',
|
||||||
|
'social credits',
|
||||||
|
],
|
||||||
|
icon: Symbols.trending_up,
|
||||||
|
),
|
||||||
|
RouteItem(
|
||||||
|
name: 'accountSettings'.tr(),
|
||||||
|
path: '/account/me/settings',
|
||||||
|
description: 'accountSettingsDescription'.tr(),
|
||||||
|
searchableAliases: [
|
||||||
|
'settings',
|
||||||
|
'preferences',
|
||||||
|
'account',
|
||||||
|
'account settings',
|
||||||
|
],
|
||||||
|
icon: Symbols.settings,
|
||||||
|
),
|
||||||
|
RouteItem(
|
||||||
|
name: 'abuseReports'.tr(),
|
||||||
|
path: '/safety/reports/me',
|
||||||
|
description: 'abuseReportsDescription'.tr(),
|
||||||
|
searchableAliases: ['reports', 'abuse', 'safety'],
|
||||||
|
icon: Symbols.report,
|
||||||
|
),
|
||||||
|
RouteItem(
|
||||||
|
name: 'files'.tr(),
|
||||||
|
path: '/files',
|
||||||
|
description: 'filesDescription'.tr(),
|
||||||
|
searchableAliases: ['files', 'folders', 'storage', 'drive', 'cloud'],
|
||||||
|
icon: Symbols.folder,
|
||||||
|
),
|
||||||
|
RouteItem(
|
||||||
|
name: 'aiThought'.tr(),
|
||||||
|
path: '/thought',
|
||||||
|
description: 'aiThoughtTitle'.tr(),
|
||||||
|
searchableAliases: ['thought', 'ai', 'ai thought'],
|
||||||
|
icon: Symbols.psychology,
|
||||||
|
),
|
||||||
|
RouteItem(
|
||||||
|
name: 'creatorHub'.tr(),
|
||||||
|
path: '/creators',
|
||||||
|
description: 'creatorHubDescription'.tr(),
|
||||||
|
searchableAliases: ['creators', 'hub', 'creator hub', 'creators hub'],
|
||||||
|
icon: Symbols.create,
|
||||||
|
),
|
||||||
|
RouteItem(
|
||||||
|
name: 'developerPortal'.tr(),
|
||||||
|
path: '/developers',
|
||||||
|
description: 'developerPortalDescription'.tr(),
|
||||||
|
searchableAliases: [
|
||||||
|
'developers',
|
||||||
|
'dev',
|
||||||
|
'developer',
|
||||||
|
'developer hub',
|
||||||
|
'developers hub',
|
||||||
|
],
|
||||||
|
icon: Symbols.code,
|
||||||
|
),
|
||||||
|
RouteItem(
|
||||||
|
name: 'debugLogs'.tr(),
|
||||||
|
path: '/logs',
|
||||||
|
description: 'debugLogsDescription'.tr(),
|
||||||
|
searchableAliases: ['logs', 'debug', 'debug logs'],
|
||||||
|
icon: Symbols.bug_report,
|
||||||
|
),
|
||||||
|
RouteItem(
|
||||||
|
name: 'webArticlesStand'.tr(),
|
||||||
|
path: '/feeds/articles',
|
||||||
|
description: 'webArticlesStandDescription'.tr(),
|
||||||
|
searchableAliases: ['articles', 'stand', 'feed', 'web feed'],
|
||||||
|
icon: Symbols.article,
|
||||||
|
),
|
||||||
|
RouteItem(
|
||||||
|
name: 'appSettings'.tr(),
|
||||||
|
path: '/settings',
|
||||||
|
description: 'appSettingsDescription'.tr(),
|
||||||
|
searchableAliases: ['settings', 'preferences', 'app', 'app settings'],
|
||||||
|
icon: Symbols.settings,
|
||||||
|
),
|
||||||
|
RouteItem(
|
||||||
|
name: 'about'.tr(),
|
||||||
|
path: '/about',
|
||||||
|
description: 'about'.tr(),
|
||||||
|
searchableAliases: ['about', 'info'],
|
||||||
|
icon: Symbols.info,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
sealed class SpecialAction with _$SpecialAction {
|
||||||
|
const factory SpecialAction({
|
||||||
|
required String name,
|
||||||
|
required String description,
|
||||||
|
required IconData icon,
|
||||||
|
required VoidCallback action,
|
||||||
|
@Default([]) List<String> searchableAliases,
|
||||||
|
}) = _SpecialAction;
|
||||||
|
}
|
||||||
552
lib/models/route_item.freezed.dart
Normal file
552
lib/models/route_item.freezed.dart
Normal file
@@ -0,0 +1,552 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// coverage:ignore-file
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'route_item.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// dart format off
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$RouteItem {
|
||||||
|
|
||||||
|
String get name; String get path; String get description; List<String> get searchableAliases; IconData get icon;
|
||||||
|
/// Create a copy of RouteItem
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$RouteItemCopyWith<RouteItem> get copyWith => _$RouteItemCopyWithImpl<RouteItem>(this as RouteItem, _$identity);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is RouteItem&&(identical(other.name, name) || other.name == name)&&(identical(other.path, path) || other.path == path)&&(identical(other.description, description) || other.description == description)&&const DeepCollectionEquality().equals(other.searchableAliases, searchableAliases)&&(identical(other.icon, icon) || other.icon == icon));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,name,path,description,const DeepCollectionEquality().hash(searchableAliases),icon);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'RouteItem(name: $name, path: $path, description: $description, searchableAliases: $searchableAliases, icon: $icon)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $RouteItemCopyWith<$Res> {
|
||||||
|
factory $RouteItemCopyWith(RouteItem value, $Res Function(RouteItem) _then) = _$RouteItemCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String name, String path, String description, List<String> searchableAliases, IconData icon
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$RouteItemCopyWithImpl<$Res>
|
||||||
|
implements $RouteItemCopyWith<$Res> {
|
||||||
|
_$RouteItemCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final RouteItem _self;
|
||||||
|
final $Res Function(RouteItem) _then;
|
||||||
|
|
||||||
|
/// Create a copy of RouteItem
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? name = null,Object? path = null,Object? description = null,Object? searchableAliases = null,Object? icon = null,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,path: null == path ? _self.path : path // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,searchableAliases: null == searchableAliases ? _self.searchableAliases : searchableAliases // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<String>,icon: null == icon ? _self.icon : icon // ignore: cast_nullable_to_non_nullable
|
||||||
|
as IconData,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Adds pattern-matching-related methods to [RouteItem].
|
||||||
|
extension RouteItemPatterns on RouteItem {
|
||||||
|
/// A variant of `map` that fallback to returning `orElse`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _RouteItem value)? $default,{required TResult orElse(),}){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _RouteItem() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// Callbacks receives the raw object, upcasted.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case final Subclass2 value:
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _RouteItem value) $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _RouteItem():
|
||||||
|
return $default(_that);}
|
||||||
|
}
|
||||||
|
/// A variant of `map` that fallback to returning `null`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _RouteItem value)? $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _RouteItem() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to an `orElse` callback.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String name, String path, String description, List<String> searchableAliases, IconData icon)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _RouteItem() when $default != null:
|
||||||
|
return $default(_that.name,_that.path,_that.description,_that.searchableAliases,_that.icon);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// As opposed to `map`, this offers destructuring.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case Subclass2(:final field2):
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String name, String path, String description, List<String> searchableAliases, IconData icon) $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _RouteItem():
|
||||||
|
return $default(_that.name,_that.path,_that.description,_that.searchableAliases,_that.icon);}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to returning `null`
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String name, String path, String description, List<String> searchableAliases, IconData icon)? $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _RouteItem() when $default != null:
|
||||||
|
return $default(_that.name,_that.path,_that.description,_that.searchableAliases,_that.icon);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
|
||||||
|
class _RouteItem implements RouteItem {
|
||||||
|
const _RouteItem({required this.name, required this.path, required this.description, final List<String> searchableAliases = const [], required this.icon}): _searchableAliases = searchableAliases;
|
||||||
|
|
||||||
|
|
||||||
|
@override final String name;
|
||||||
|
@override final String path;
|
||||||
|
@override final String description;
|
||||||
|
final List<String> _searchableAliases;
|
||||||
|
@override@JsonKey() List<String> get searchableAliases {
|
||||||
|
if (_searchableAliases is EqualUnmodifiableListView) return _searchableAliases;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(_searchableAliases);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override final IconData icon;
|
||||||
|
|
||||||
|
/// Create a copy of RouteItem
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$RouteItemCopyWith<_RouteItem> get copyWith => __$RouteItemCopyWithImpl<_RouteItem>(this, _$identity);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _RouteItem&&(identical(other.name, name) || other.name == name)&&(identical(other.path, path) || other.path == path)&&(identical(other.description, description) || other.description == description)&&const DeepCollectionEquality().equals(other._searchableAliases, _searchableAliases)&&(identical(other.icon, icon) || other.icon == icon));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,name,path,description,const DeepCollectionEquality().hash(_searchableAliases),icon);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'RouteItem(name: $name, path: $path, description: $description, searchableAliases: $searchableAliases, icon: $icon)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$RouteItemCopyWith<$Res> implements $RouteItemCopyWith<$Res> {
|
||||||
|
factory _$RouteItemCopyWith(_RouteItem value, $Res Function(_RouteItem) _then) = __$RouteItemCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
String name, String path, String description, List<String> searchableAliases, IconData icon
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$RouteItemCopyWithImpl<$Res>
|
||||||
|
implements _$RouteItemCopyWith<$Res> {
|
||||||
|
__$RouteItemCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _RouteItem _self;
|
||||||
|
final $Res Function(_RouteItem) _then;
|
||||||
|
|
||||||
|
/// Create a copy of RouteItem
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? name = null,Object? path = null,Object? description = null,Object? searchableAliases = null,Object? icon = null,}) {
|
||||||
|
return _then(_RouteItem(
|
||||||
|
name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,path: null == path ? _self.path : path // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,searchableAliases: null == searchableAliases ? _self._searchableAliases : searchableAliases // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<String>,icon: null == icon ? _self.icon : icon // ignore: cast_nullable_to_non_nullable
|
||||||
|
as IconData,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SpecialAction {
|
||||||
|
|
||||||
|
String get name; String get description; IconData get icon; VoidCallback get action; List<String> get searchableAliases;
|
||||||
|
/// Create a copy of SpecialAction
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SpecialActionCopyWith<SpecialAction> get copyWith => _$SpecialActionCopyWithImpl<SpecialAction>(this as SpecialAction, _$identity);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is SpecialAction&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.icon, icon) || other.icon == icon)&&(identical(other.action, action) || other.action == action)&&const DeepCollectionEquality().equals(other.searchableAliases, searchableAliases));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,name,description,icon,action,const DeepCollectionEquality().hash(searchableAliases));
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SpecialAction(name: $name, description: $description, icon: $icon, action: $action, searchableAliases: $searchableAliases)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $SpecialActionCopyWith<$Res> {
|
||||||
|
factory $SpecialActionCopyWith(SpecialAction value, $Res Function(SpecialAction) _then) = _$SpecialActionCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String name, String description, IconData icon, VoidCallback action, List<String> searchableAliases
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$SpecialActionCopyWithImpl<$Res>
|
||||||
|
implements $SpecialActionCopyWith<$Res> {
|
||||||
|
_$SpecialActionCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final SpecialAction _self;
|
||||||
|
final $Res Function(SpecialAction) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SpecialAction
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? name = null,Object? description = null,Object? icon = null,Object? action = null,Object? searchableAliases = null,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,icon: null == icon ? _self.icon : icon // ignore: cast_nullable_to_non_nullable
|
||||||
|
as IconData,action: null == action ? _self.action : action // ignore: cast_nullable_to_non_nullable
|
||||||
|
as VoidCallback,searchableAliases: null == searchableAliases ? _self.searchableAliases : searchableAliases // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<String>,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Adds pattern-matching-related methods to [SpecialAction].
|
||||||
|
extension SpecialActionPatterns on SpecialAction {
|
||||||
|
/// A variant of `map` that fallback to returning `orElse`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SpecialAction value)? $default,{required TResult orElse(),}){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SpecialAction() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// Callbacks receives the raw object, upcasted.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case final Subclass2 value:
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SpecialAction value) $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SpecialAction():
|
||||||
|
return $default(_that);}
|
||||||
|
}
|
||||||
|
/// A variant of `map` that fallback to returning `null`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SpecialAction value)? $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SpecialAction() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to an `orElse` callback.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String name, String description, IconData icon, VoidCallback action, List<String> searchableAliases)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SpecialAction() when $default != null:
|
||||||
|
return $default(_that.name,_that.description,_that.icon,_that.action,_that.searchableAliases);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// As opposed to `map`, this offers destructuring.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case Subclass2(:final field2):
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String name, String description, IconData icon, VoidCallback action, List<String> searchableAliases) $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SpecialAction():
|
||||||
|
return $default(_that.name,_that.description,_that.icon,_that.action,_that.searchableAliases);}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to returning `null`
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String name, String description, IconData icon, VoidCallback action, List<String> searchableAliases)? $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SpecialAction() when $default != null:
|
||||||
|
return $default(_that.name,_that.description,_that.icon,_that.action,_that.searchableAliases);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
|
||||||
|
class _SpecialAction implements SpecialAction {
|
||||||
|
const _SpecialAction({required this.name, required this.description, required this.icon, required this.action, final List<String> searchableAliases = const []}): _searchableAliases = searchableAliases;
|
||||||
|
|
||||||
|
|
||||||
|
@override final String name;
|
||||||
|
@override final String description;
|
||||||
|
@override final IconData icon;
|
||||||
|
@override final VoidCallback action;
|
||||||
|
final List<String> _searchableAliases;
|
||||||
|
@override@JsonKey() List<String> get searchableAliases {
|
||||||
|
if (_searchableAliases is EqualUnmodifiableListView) return _searchableAliases;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(_searchableAliases);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Create a copy of SpecialAction
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$SpecialActionCopyWith<_SpecialAction> get copyWith => __$SpecialActionCopyWithImpl<_SpecialAction>(this, _$identity);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SpecialAction&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.icon, icon) || other.icon == icon)&&(identical(other.action, action) || other.action == action)&&const DeepCollectionEquality().equals(other._searchableAliases, _searchableAliases));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,name,description,icon,action,const DeepCollectionEquality().hash(_searchableAliases));
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SpecialAction(name: $name, description: $description, icon: $icon, action: $action, searchableAliases: $searchableAliases)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$SpecialActionCopyWith<$Res> implements $SpecialActionCopyWith<$Res> {
|
||||||
|
factory _$SpecialActionCopyWith(_SpecialAction value, $Res Function(_SpecialAction) _then) = __$SpecialActionCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
String name, String description, IconData icon, VoidCallback action, List<String> searchableAliases
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$SpecialActionCopyWithImpl<$Res>
|
||||||
|
implements _$SpecialActionCopyWith<$Res> {
|
||||||
|
__$SpecialActionCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _SpecialAction _self;
|
||||||
|
final $Res Function(_SpecialAction) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SpecialAction
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? name = null,Object? description = null,Object? icon = null,Object? action = null,Object? searchableAliases = null,}) {
|
||||||
|
return _then(_SpecialAction(
|
||||||
|
name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,icon: null == icon ? _self.icon : icon // ignore: cast_nullable_to_non_nullable
|
||||||
|
as IconData,action: null == action ? _self.action : action // ignore: cast_nullable_to_non_nullable
|
||||||
|
as VoidCallback,searchableAliases: null == searchableAliases ? _self._searchableAliases : searchableAliases // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<String>,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// dart format on
|
||||||
@@ -4,6 +4,7 @@ import 'package:island/database/drift_db.dart';
|
|||||||
import 'package:island/models/account.dart';
|
import 'package:island/models/account.dart';
|
||||||
import 'package:island/models/chat.dart';
|
import 'package:island/models/chat.dart';
|
||||||
import 'package:island/models/file.dart';
|
import 'package:island/models/file.dart';
|
||||||
|
import 'package:island/models/realm.dart';
|
||||||
import 'package:island/pods/database.dart';
|
import 'package:island/pods/database.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/pods/userinfo.dart';
|
import 'package:island/pods/userinfo.dart';
|
||||||
@@ -46,99 +47,14 @@ class ChatRoomJoinedNotifier extends _$ChatRoomJoinedNotifier {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final localRoomsData = await db.select(db.chatRooms).get();
|
final localRoomsData = await db.select(db.chatRooms).get();
|
||||||
|
final localRealmsData = await db.select(db.realms).get();
|
||||||
if (localRoomsData.isNotEmpty) {
|
if (localRoomsData.isNotEmpty) {
|
||||||
final localRooms = await Future.wait(
|
final localRooms = await Future.wait(
|
||||||
localRoomsData.map((row) async {
|
localRoomsData.map((row) async {
|
||||||
final membersRows =
|
final membersRows = await (db.select(
|
||||||
await (db.select(db.chatMembers)
|
db.chatMembers,
|
||||||
..where((m) => m.chatRoomId.equals(row.id))).get();
|
)..where((m) => m.chatRoomId.equals(row.id))).get();
|
||||||
final members =
|
final members = membersRows.map((mRow) {
|
||||||
membersRows.map((mRow) {
|
|
||||||
final account = SnAccount.fromJson(mRow.account);
|
|
||||||
return SnChatMember(
|
|
||||||
id: mRow.id,
|
|
||||||
chatRoomId: mRow.chatRoomId,
|
|
||||||
accountId: mRow.accountId,
|
|
||||||
account: account,
|
|
||||||
nick: mRow.nick,
|
|
||||||
notify: mRow.notify,
|
|
||||||
joinedAt: mRow.joinedAt,
|
|
||||||
breakUntil: mRow.breakUntil,
|
|
||||||
timeoutUntil: mRow.timeoutUntil,
|
|
||||||
status: null,
|
|
||||||
createdAt: mRow.createdAt,
|
|
||||||
updatedAt: mRow.updatedAt,
|
|
||||||
deletedAt: mRow.deletedAt,
|
|
||||||
chatRoom: null,
|
|
||||||
);
|
|
||||||
}).toList();
|
|
||||||
return SnChatRoom(
|
|
||||||
id: row.id,
|
|
||||||
name: row.name,
|
|
||||||
description: row.description,
|
|
||||||
type: row.type,
|
|
||||||
isPublic: row.isPublic!,
|
|
||||||
isCommunity: row.isCommunity!,
|
|
||||||
picture:
|
|
||||||
row.picture != null
|
|
||||||
? SnCloudFile.fromJson(row.picture!)
|
|
||||||
: null,
|
|
||||||
background:
|
|
||||||
row.background != null
|
|
||||||
? SnCloudFile.fromJson(row.background!)
|
|
||||||
: null,
|
|
||||||
realmId: row.realmId,
|
|
||||||
accountId: row.accountId,
|
|
||||||
realm: null,
|
|
||||||
createdAt: row.createdAt,
|
|
||||||
updatedAt: row.updatedAt,
|
|
||||||
deletedAt: row.deletedAt,
|
|
||||||
members: members,
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Background sync
|
|
||||||
Future(() async {
|
|
||||||
try {
|
|
||||||
final client = ref.read(apiClientProvider);
|
|
||||||
final resp = await client.get('/sphere/chat');
|
|
||||||
final remoteRooms =
|
|
||||||
resp.data
|
|
||||||
.map((e) => SnChatRoom.fromJson(e))
|
|
||||||
.cast<SnChatRoom>()
|
|
||||||
.toList();
|
|
||||||
await db.saveChatRooms(remoteRooms, override: true);
|
|
||||||
// Update state with fresh data
|
|
||||||
state = AsyncData(await _buildRoomsFromDb(db));
|
|
||||||
} catch (_) {}
|
|
||||||
}).ignore();
|
|
||||||
|
|
||||||
return localRooms;
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
|
|
||||||
// Fallback to API
|
|
||||||
final client = ref.watch(apiClientProvider);
|
|
||||||
final resp = await client.get('/sphere/chat');
|
|
||||||
final rooms =
|
|
||||||
resp.data
|
|
||||||
.map((e) => SnChatRoom.fromJson(e))
|
|
||||||
.cast<SnChatRoom>()
|
|
||||||
.toList();
|
|
||||||
await db.saveChatRooms(rooms, override: true);
|
|
||||||
return rooms;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<SnChatRoom>> _buildRoomsFromDb(AppDatabase db) async {
|
|
||||||
final localRoomsData = await db.select(db.chatRooms).get();
|
|
||||||
return Future.wait(
|
|
||||||
localRoomsData.map((row) async {
|
|
||||||
final membersRows =
|
|
||||||
await (db.select(db.chatMembers)
|
|
||||||
..where((m) => m.chatRoomId.equals(row.id))).get();
|
|
||||||
final members =
|
|
||||||
membersRows.map((mRow) {
|
|
||||||
final account = SnAccount.fromJson(mRow.account);
|
final account = SnAccount.fromJson(mRow.account);
|
||||||
return SnChatMember(
|
return SnChatMember(
|
||||||
id: mRow.id,
|
id: mRow.id,
|
||||||
@@ -157,6 +73,148 @@ class ChatRoomJoinedNotifier extends _$ChatRoomJoinedNotifier {
|
|||||||
chatRoom: null,
|
chatRoom: null,
|
||||||
);
|
);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
return SnChatRoom(
|
||||||
|
id: row.id,
|
||||||
|
name: row.name,
|
||||||
|
description: row.description,
|
||||||
|
type: row.type,
|
||||||
|
isPublic: row.isPublic!,
|
||||||
|
isCommunity: row.isCommunity!,
|
||||||
|
picture: row.picture != null
|
||||||
|
? SnCloudFile.fromJson(row.picture!)
|
||||||
|
: null,
|
||||||
|
background: row.background != null
|
||||||
|
? SnCloudFile.fromJson(row.background!)
|
||||||
|
: null,
|
||||||
|
realmId: row.realmId,
|
||||||
|
accountId: row.accountId,
|
||||||
|
realm: localRealmsData
|
||||||
|
.where((e) => e.id == row.realmId)
|
||||||
|
.map((e) => _buildRealmFromTableEntry(e))
|
||||||
|
.firstOrNull,
|
||||||
|
createdAt: row.createdAt,
|
||||||
|
updatedAt: row.updatedAt,
|
||||||
|
deletedAt: row.deletedAt,
|
||||||
|
members: members,
|
||||||
|
isPinned: row.isPinned ?? false,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Background sync
|
||||||
|
Future(() async {
|
||||||
|
try {
|
||||||
|
final client = ref.read(apiClientProvider);
|
||||||
|
final resp = await client.get('/sphere/chat');
|
||||||
|
final remoteRooms = resp.data
|
||||||
|
.map((e) => SnChatRoom.fromJson(e))
|
||||||
|
.cast<SnChatRoom>()
|
||||||
|
.toList();
|
||||||
|
await db.saveChatRooms(remoteRooms, override: true);
|
||||||
|
// Update state with fresh data
|
||||||
|
state = AsyncData(await _buildRoomsFromDb(db));
|
||||||
|
} catch (_) {}
|
||||||
|
}).ignore();
|
||||||
|
|
||||||
|
return localRooms;
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
// Fallback to API
|
||||||
|
final client = ref.watch(apiClientProvider);
|
||||||
|
final resp = await client.get('/sphere/chat');
|
||||||
|
final rooms = resp.data
|
||||||
|
.map((e) => SnChatRoom.fromJson(e))
|
||||||
|
.cast<SnChatRoom>()
|
||||||
|
.toList();
|
||||||
|
await db.saveChatRooms(rooms, override: true);
|
||||||
|
return rooms;
|
||||||
|
}
|
||||||
|
|
||||||
|
SnRealm _buildRealmFromTableEntry(Realm localRealm) {
|
||||||
|
return SnRealm(
|
||||||
|
id: localRealm.id,
|
||||||
|
slug: localRealm.slug,
|
||||||
|
name: localRealm.name ?? localRealm.slug,
|
||||||
|
description: localRealm.description ?? '',
|
||||||
|
verifiedAs: localRealm.verifiedAs,
|
||||||
|
verifiedAt: localRealm.verifiedAt,
|
||||||
|
isCommunity: localRealm.isCommunity,
|
||||||
|
isPublic: localRealm.isPublic,
|
||||||
|
picture: localRealm.picture != null
|
||||||
|
? SnCloudFile.fromJson(localRealm.picture!)
|
||||||
|
: null,
|
||||||
|
background: localRealm.background != null
|
||||||
|
? SnCloudFile.fromJson(localRealm.background!)
|
||||||
|
: null,
|
||||||
|
accountId: localRealm.accountId ?? '',
|
||||||
|
createdAt: localRealm.createdAt,
|
||||||
|
updatedAt: localRealm.updatedAt,
|
||||||
|
deletedAt: localRealm.deletedAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<SnChatRoom>> _buildRoomsFromDb(AppDatabase db) async {
|
||||||
|
final localRoomsData = await db.select(db.chatRooms).get();
|
||||||
|
return Future.wait(
|
||||||
|
localRoomsData.map((row) async {
|
||||||
|
final membersRows = await (db.select(
|
||||||
|
db.chatMembers,
|
||||||
|
)..where((m) => m.chatRoomId.equals(row.id))).get();
|
||||||
|
final members = membersRows.map((mRow) {
|
||||||
|
final account = SnAccount.fromJson(mRow.account);
|
||||||
|
return SnChatMember(
|
||||||
|
id: mRow.id,
|
||||||
|
chatRoomId: mRow.chatRoomId,
|
||||||
|
accountId: mRow.accountId,
|
||||||
|
account: account,
|
||||||
|
nick: mRow.nick,
|
||||||
|
notify: mRow.notify,
|
||||||
|
joinedAt: mRow.joinedAt,
|
||||||
|
breakUntil: mRow.breakUntil,
|
||||||
|
timeoutUntil: mRow.timeoutUntil,
|
||||||
|
status: null,
|
||||||
|
createdAt: mRow.createdAt,
|
||||||
|
updatedAt: mRow.updatedAt,
|
||||||
|
deletedAt: mRow.deletedAt,
|
||||||
|
chatRoom: null,
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
// Load realm if it exists
|
||||||
|
SnRealm? realm;
|
||||||
|
if (row.realmId != null) {
|
||||||
|
try {
|
||||||
|
final realmRow = await (db.select(
|
||||||
|
db.realms,
|
||||||
|
)..where((r) => r.id.equals(row.realmId!))).getSingleOrNull();
|
||||||
|
if (realmRow != null) {
|
||||||
|
realm = SnRealm(
|
||||||
|
id: realmRow.id,
|
||||||
|
slug: '', // Not stored in DB
|
||||||
|
name: realmRow.name ?? '',
|
||||||
|
description: realmRow.description ?? '',
|
||||||
|
verifiedAs: null, // Not stored in DB
|
||||||
|
verifiedAt: null, // Not stored in DB
|
||||||
|
isCommunity: false, // Not stored in DB
|
||||||
|
isPublic: true, // Not stored in DB
|
||||||
|
picture: realmRow.picture != null
|
||||||
|
? SnCloudFile.fromJson(realmRow.picture!)
|
||||||
|
: null,
|
||||||
|
background: realmRow.background != null
|
||||||
|
? SnCloudFile.fromJson(realmRow.background!)
|
||||||
|
: null,
|
||||||
|
accountId: realmRow.accountId ?? '',
|
||||||
|
createdAt: realmRow.createdAt,
|
||||||
|
updatedAt: realmRow.updatedAt,
|
||||||
|
deletedAt: realmRow.deletedAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
// Realm not found, keep as null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return SnChatRoom(
|
return SnChatRoom(
|
||||||
id: row.id,
|
id: row.id,
|
||||||
name: row.name,
|
name: row.name,
|
||||||
@@ -164,19 +222,20 @@ class ChatRoomJoinedNotifier extends _$ChatRoomJoinedNotifier {
|
|||||||
type: row.type,
|
type: row.type,
|
||||||
isPublic: row.isPublic!,
|
isPublic: row.isPublic!,
|
||||||
isCommunity: row.isCommunity!,
|
isCommunity: row.isCommunity!,
|
||||||
picture:
|
picture: row.picture != null
|
||||||
row.picture != null ? SnCloudFile.fromJson(row.picture!) : null,
|
? SnCloudFile.fromJson(row.picture!)
|
||||||
background:
|
: null,
|
||||||
row.background != null
|
background: row.background != null
|
||||||
? SnCloudFile.fromJson(row.background!)
|
? SnCloudFile.fromJson(row.background!)
|
||||||
: null,
|
: null,
|
||||||
realmId: row.realmId,
|
realmId: row.realmId,
|
||||||
accountId: row.accountId,
|
accountId: row.accountId,
|
||||||
realm: null,
|
realm: realm,
|
||||||
createdAt: row.createdAt,
|
createdAt: row.createdAt,
|
||||||
updatedAt: row.updatedAt,
|
updatedAt: row.updatedAt,
|
||||||
deletedAt: row.deletedAt,
|
deletedAt: row.deletedAt,
|
||||||
members: members,
|
members: members,
|
||||||
|
isPinned: row.isPinned ?? false,
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -192,35 +251,34 @@ class ChatRoomNotifier extends _$ChatRoomNotifier {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Try to get from local database first
|
// Try to get from local database first
|
||||||
final localRoomData =
|
final localRoomData = await (db.select(
|
||||||
await (db.select(db.chatRooms)
|
db.chatRooms,
|
||||||
..where((r) => r.id.equals(identifier))).getSingleOrNull();
|
)..where((r) => r.id.equals(identifier))).getSingleOrNull();
|
||||||
|
|
||||||
if (localRoomData != null) {
|
if (localRoomData != null) {
|
||||||
// Fetch members for this room
|
// Fetch members for this room
|
||||||
final membersRows =
|
final membersRows = await (db.select(
|
||||||
await (db.select(db.chatMembers)
|
db.chatMembers,
|
||||||
..where((m) => m.chatRoomId.equals(localRoomData.id))).get();
|
)..where((m) => m.chatRoomId.equals(localRoomData.id))).get();
|
||||||
final members =
|
final members = membersRows.map((mRow) {
|
||||||
membersRows.map((mRow) {
|
final account = SnAccount.fromJson(mRow.account);
|
||||||
final account = SnAccount.fromJson(mRow.account);
|
return SnChatMember(
|
||||||
return SnChatMember(
|
id: mRow.id,
|
||||||
id: mRow.id,
|
chatRoomId: mRow.chatRoomId,
|
||||||
chatRoomId: mRow.chatRoomId,
|
accountId: mRow.accountId,
|
||||||
accountId: mRow.accountId,
|
account: account,
|
||||||
account: account,
|
nick: mRow.nick,
|
||||||
nick: mRow.nick,
|
notify: mRow.notify,
|
||||||
notify: mRow.notify,
|
joinedAt: mRow.joinedAt,
|
||||||
joinedAt: mRow.joinedAt,
|
breakUntil: mRow.breakUntil,
|
||||||
breakUntil: mRow.breakUntil,
|
timeoutUntil: mRow.timeoutUntil,
|
||||||
timeoutUntil: mRow.timeoutUntil,
|
status: null,
|
||||||
status: null,
|
createdAt: mRow.createdAt,
|
||||||
createdAt: mRow.createdAt,
|
updatedAt: mRow.updatedAt,
|
||||||
updatedAt: mRow.updatedAt,
|
deletedAt: mRow.deletedAt,
|
||||||
deletedAt: mRow.deletedAt,
|
chatRoom: null,
|
||||||
chatRoom: null,
|
);
|
||||||
);
|
}).toList();
|
||||||
}).toList();
|
|
||||||
|
|
||||||
final localRoom = SnChatRoom(
|
final localRoom = SnChatRoom(
|
||||||
id: localRoomData.id,
|
id: localRoomData.id,
|
||||||
@@ -229,14 +287,12 @@ class ChatRoomNotifier extends _$ChatRoomNotifier {
|
|||||||
type: localRoomData.type,
|
type: localRoomData.type,
|
||||||
isPublic: localRoomData.isPublic!,
|
isPublic: localRoomData.isPublic!,
|
||||||
isCommunity: localRoomData.isCommunity!,
|
isCommunity: localRoomData.isCommunity!,
|
||||||
picture:
|
picture: localRoomData.picture != null
|
||||||
localRoomData.picture != null
|
? SnCloudFile.fromJson(localRoomData.picture!)
|
||||||
? SnCloudFile.fromJson(localRoomData.picture!)
|
: null,
|
||||||
: null,
|
background: localRoomData.background != null
|
||||||
background:
|
? SnCloudFile.fromJson(localRoomData.background!)
|
||||||
localRoomData.background != null
|
: null,
|
||||||
? SnCloudFile.fromJson(localRoomData.background!)
|
|
||||||
: null,
|
|
||||||
realmId: localRoomData.realmId,
|
realmId: localRoomData.realmId,
|
||||||
accountId: localRoomData.accountId,
|
accountId: localRoomData.accountId,
|
||||||
realm: null,
|
realm: null,
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ final class ChatRoomJoinedNotifierProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _$chatRoomJoinedNotifierHash() =>
|
String _$chatRoomJoinedNotifierHash() =>
|
||||||
r'c8092225ba0d9c08b2b5bca6f800f1877303b4ff';
|
r'e69955be56ef2c04a8062a8a65925e0a23bfcbaa';
|
||||||
|
|
||||||
abstract class _$ChatRoomJoinedNotifier
|
abstract class _$ChatRoomJoinedNotifier
|
||||||
extends $AsyncNotifier<List<SnChatRoom>> {
|
extends $AsyncNotifier<List<SnChatRoom>> {
|
||||||
|
|||||||
@@ -93,7 +93,6 @@ class ChatSubscribeNotifier extends _$ChatSubscribeNotifier {
|
|||||||
|
|
||||||
// Set up periodic subscribe timer (every 5 minutes)
|
// Set up periodic subscribe timer (every 5 minutes)
|
||||||
_periodicSubscribeTimer = Timer.periodic(const Duration(minutes: 5), (_) {
|
_periodicSubscribeTimer = Timer.periodic(const Duration(minutes: 5), (_) {
|
||||||
final wsState = ref.read(websocketStateProvider.notifier);
|
|
||||||
wsState.sendMessage(
|
wsState.sendMessage(
|
||||||
jsonEncode(
|
jsonEncode(
|
||||||
WebSocketPacket(
|
WebSocketPacket(
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ final class ChatSubscribeNotifierProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _$chatSubscribeNotifierHash() =>
|
String _$chatSubscribeNotifierHash() =>
|
||||||
r'2b9fae96eb1f96a514a074985e5efa1c13d10aa4';
|
r'1aa164429aaab1628b5edbae11e33b0860abdcdc';
|
||||||
|
|
||||||
final class ChatSubscribeNotifierFamily extends $Family
|
final class ChatSubscribeNotifierFamily extends $Family
|
||||||
with
|
with
|
||||||
|
|||||||
@@ -22,10 +22,9 @@ const kAppColorSchemeStoreKey = 'app_color_scheme';
|
|||||||
const kAppCustomColorsStoreKey = 'app_custom_colors';
|
const kAppCustomColorsStoreKey = 'app_custom_colors';
|
||||||
const kAppNotifyWithHaptic = 'app_notify_with_haptic';
|
const kAppNotifyWithHaptic = 'app_notify_with_haptic';
|
||||||
const kAppCustomFonts = 'app_custom_fonts';
|
const kAppCustomFonts = 'app_custom_fonts';
|
||||||
const kAppAutoTranslate = 'app_auto_translate';
|
|
||||||
const kAppDataSavingMode = 'app_data_saving_mode';
|
const kAppDataSavingMode = 'app_data_saving_mode';
|
||||||
const kAppSoundEffects = 'app_sound_effects';
|
const kAppSoundEffects = 'app_sound_effects';
|
||||||
const kAppAprilFoolFeatures = 'app_april_fool_features';
|
const kAppFestivalFeatures = 'app_feastival_features';
|
||||||
const kAppWindowSize = 'app_window_size';
|
const kAppWindowSize = 'app_window_size';
|
||||||
const kAppWindowOpacity = 'app_window_opacity';
|
const kAppWindowOpacity = 'app_window_opacity';
|
||||||
const kAppCardTransparent = 'app_card_transparent';
|
const kAppCardTransparent = 'app_card_transparent';
|
||||||
@@ -33,31 +32,21 @@ const kAppEnterToSend = 'app_enter_to_send';
|
|||||||
const kAppDefaultPoolId = 'app_default_pool_id';
|
const kAppDefaultPoolId = 'app_default_pool_id';
|
||||||
const kAppMessageDisplayStyle = 'app_message_display_style';
|
const kAppMessageDisplayStyle = 'app_message_display_style';
|
||||||
const kAppThemeMode = 'app_theme_mode';
|
const kAppThemeMode = 'app_theme_mode';
|
||||||
const kMaterialYouToggleStoreKey = 'app_theme_material_you';
|
|
||||||
const kAppDisableAnimation = 'app_disable_animation';
|
const kAppDisableAnimation = 'app_disable_animation';
|
||||||
const kAppFabPosition = 'app_fab_position';
|
const kAppFabPosition = 'app_fab_position';
|
||||||
|
const kAppGroupedChatList = 'app_grouped_chat_list';
|
||||||
const kFeaturedPostsCollapsedId =
|
const kFeaturedPostsCollapsedId =
|
||||||
'featured_posts_collapsed_id'; // Key for storing the ID of the collapsed featured post
|
'featured_posts_collapsed_id'; // Key for storing the ID of the collapsed featured post
|
||||||
|
const kAppFirstLaunchAt = 'app_first_launch_at';
|
||||||
|
const kAppAskedReview = 'app_asked_review';
|
||||||
|
const kAppDashSearchEngine = 'app_dash_search_engine';
|
||||||
|
const kAppDefaultScreen = 'app_default_screen';
|
||||||
|
|
||||||
const Map<String, FilterQuality> kImageQualityLevel = {
|
// Will be overrided by the ProviderScope
|
||||||
'settingsImageQualityLowest': FilterQuality.none,
|
|
||||||
'settingsImageQualityLow': FilterQuality.low,
|
|
||||||
'settingsImageQualityMedium': FilterQuality.medium,
|
|
||||||
'settingsImageQualityHigh': FilterQuality.high,
|
|
||||||
};
|
|
||||||
|
|
||||||
final sharedPreferencesProvider = Provider<SharedPreferences>((ref) {
|
final sharedPreferencesProvider = Provider<SharedPreferences>((ref) {
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
});
|
});
|
||||||
|
|
||||||
final imageQualityProvider = Provider<FilterQuality>((ref) {
|
|
||||||
final prefs = ref.watch(sharedPreferencesProvider);
|
|
||||||
return kImageQualityLevel.values.elementAtOrNull(
|
|
||||||
prefs.getInt('app_image_quality') ?? 3,
|
|
||||||
) ??
|
|
||||||
FilterQuality.high;
|
|
||||||
});
|
|
||||||
|
|
||||||
final serverUrlProvider = Provider<String>((ref) {
|
final serverUrlProvider = Provider<String>((ref) {
|
||||||
final prefs = ref.watch(sharedPreferencesProvider);
|
final prefs = ref.watch(sharedPreferencesProvider);
|
||||||
return prefs.getString(kNetworkServerStoreKey) ?? kNetworkServerDefault;
|
return prefs.getString(kNetworkServerStoreKey) ?? kNetworkServerDefault;
|
||||||
@@ -81,13 +70,13 @@ sealed class ThemeColors with _$ThemeColors {
|
|||||||
@freezed
|
@freezed
|
||||||
sealed class AppSettings with _$AppSettings {
|
sealed class AppSettings with _$AppSettings {
|
||||||
const factory AppSettings({
|
const factory AppSettings({
|
||||||
required bool autoTranslate,
|
|
||||||
required bool dataSavingMode,
|
required bool dataSavingMode,
|
||||||
required bool soundEffects,
|
required bool soundEffects,
|
||||||
required bool aprilFoolFeatures,
|
required bool festivalFeatures,
|
||||||
required bool enterToSend,
|
required bool enterToSend,
|
||||||
required bool appBarTransparent,
|
required bool appBarTransparent,
|
||||||
required bool showBackgroundImage,
|
required bool showBackgroundImage,
|
||||||
|
required bool notifyWithHaptic,
|
||||||
required String? customFonts,
|
required String? customFonts,
|
||||||
required int? appColorScheme, // The color stored via the int type
|
required int? appColorScheme, // The color stored via the int type
|
||||||
required ThemeColors? customColors,
|
required ThemeColors? customColors,
|
||||||
@@ -97,9 +86,13 @@ sealed class AppSettings with _$AppSettings {
|
|||||||
required String? defaultPoolId,
|
required String? defaultPoolId,
|
||||||
required String messageDisplayStyle,
|
required String messageDisplayStyle,
|
||||||
required String? themeMode,
|
required String? themeMode,
|
||||||
required bool useMaterial3,
|
|
||||||
required bool disableAnimation,
|
required bool disableAnimation,
|
||||||
required String fabPosition,
|
required String fabPosition,
|
||||||
|
required bool groupedChatList,
|
||||||
|
required String? firstLaunchAt,
|
||||||
|
required bool askedReview,
|
||||||
|
required String? dashSearchEngine,
|
||||||
|
required String? defaultScreen,
|
||||||
}) = _AppSettings;
|
}) = _AppSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,13 +102,13 @@ class AppSettingsNotifier extends _$AppSettingsNotifier {
|
|||||||
AppSettings build() {
|
AppSettings build() {
|
||||||
final prefs = ref.watch(sharedPreferencesProvider);
|
final prefs = ref.watch(sharedPreferencesProvider);
|
||||||
return AppSettings(
|
return AppSettings(
|
||||||
autoTranslate: prefs.getBool(kAppAutoTranslate) ?? false,
|
|
||||||
dataSavingMode: prefs.getBool(kAppDataSavingMode) ?? false,
|
dataSavingMode: prefs.getBool(kAppDataSavingMode) ?? false,
|
||||||
soundEffects: prefs.getBool(kAppSoundEffects) ?? true,
|
soundEffects: prefs.getBool(kAppSoundEffects) ?? true,
|
||||||
aprilFoolFeatures: prefs.getBool(kAppAprilFoolFeatures) ?? true,
|
festivalFeatures: prefs.getBool(kAppFestivalFeatures) ?? true,
|
||||||
enterToSend: prefs.getBool(kAppEnterToSend) ?? true,
|
enterToSend: prefs.getBool(kAppEnterToSend) ?? true,
|
||||||
appBarTransparent: prefs.getBool(kAppbarTransparentStoreKey) ?? false,
|
appBarTransparent: prefs.getBool(kAppbarTransparentStoreKey) ?? false,
|
||||||
showBackgroundImage: prefs.getBool(kAppShowBackgroundImage) ?? true,
|
showBackgroundImage: prefs.getBool(kAppShowBackgroundImage) ?? true,
|
||||||
|
notifyWithHaptic: prefs.getBool(kAppNotifyWithHaptic) ?? true,
|
||||||
customFonts: prefs.getString(kAppCustomFonts),
|
customFonts: prefs.getString(kAppCustomFonts),
|
||||||
appColorScheme: prefs.getInt(kAppColorSchemeStoreKey),
|
appColorScheme: prefs.getInt(kAppColorSchemeStoreKey),
|
||||||
customColors: _getThemeColorsFromPrefs(prefs),
|
customColors: _getThemeColorsFromPrefs(prefs),
|
||||||
@@ -125,9 +118,13 @@ class AppSettingsNotifier extends _$AppSettingsNotifier {
|
|||||||
defaultPoolId: prefs.getString(kAppDefaultPoolId),
|
defaultPoolId: prefs.getString(kAppDefaultPoolId),
|
||||||
messageDisplayStyle: prefs.getString(kAppMessageDisplayStyle) ?? 'bubble',
|
messageDisplayStyle: prefs.getString(kAppMessageDisplayStyle) ?? 'bubble',
|
||||||
themeMode: prefs.getString(kAppThemeMode) ?? 'system',
|
themeMode: prefs.getString(kAppThemeMode) ?? 'system',
|
||||||
useMaterial3: prefs.getBool(kMaterialYouToggleStoreKey) ?? true,
|
|
||||||
disableAnimation: prefs.getBool(kAppDisableAnimation) ?? false,
|
disableAnimation: prefs.getBool(kAppDisableAnimation) ?? false,
|
||||||
fabPosition: prefs.getString(kAppFabPosition) ?? 'center',
|
fabPosition: prefs.getString(kAppFabPosition) ?? 'center',
|
||||||
|
groupedChatList: prefs.getBool(kAppGroupedChatList) ?? false,
|
||||||
|
askedReview: prefs.getBool(kAppAskedReview) ?? false,
|
||||||
|
firstLaunchAt: prefs.getString(kAppFirstLaunchAt),
|
||||||
|
dashSearchEngine: prefs.getString(kAppDashSearchEngine),
|
||||||
|
defaultScreen: prefs.getString(kAppDefaultScreen),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,12 +167,6 @@ class AppSettingsNotifier extends _$AppSettingsNotifier {
|
|||||||
state = state.copyWith(defaultPoolId: value);
|
state = state.copyWith(defaultPoolId: value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setAutoTranslate(bool value) {
|
|
||||||
final prefs = ref.read(sharedPreferencesProvider);
|
|
||||||
prefs.setBool(kAppAutoTranslate, value);
|
|
||||||
state = state.copyWith(autoTranslate: value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setDataSavingMode(bool value) {
|
void setDataSavingMode(bool value) {
|
||||||
final prefs = ref.read(sharedPreferencesProvider);
|
final prefs = ref.read(sharedPreferencesProvider);
|
||||||
prefs.setBool(kAppDataSavingMode, value);
|
prefs.setBool(kAppDataSavingMode, value);
|
||||||
@@ -188,10 +179,10 @@ class AppSettingsNotifier extends _$AppSettingsNotifier {
|
|||||||
state = state.copyWith(soundEffects: value);
|
state = state.copyWith(soundEffects: value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setAprilFoolFeatures(bool value) {
|
void setFeativalFeatures(bool value) {
|
||||||
final prefs = ref.read(sharedPreferencesProvider);
|
final prefs = ref.read(sharedPreferencesProvider);
|
||||||
prefs.setBool(kAppAprilFoolFeatures, value);
|
prefs.setBool(kAppFestivalFeatures, value);
|
||||||
state = state.copyWith(aprilFoolFeatures: value);
|
state = state.copyWith(festivalFeatures: value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setEnterToSend(bool value) {
|
void setEnterToSend(bool value) {
|
||||||
@@ -212,12 +203,24 @@ class AppSettingsNotifier extends _$AppSettingsNotifier {
|
|||||||
state = state.copyWith(showBackgroundImage: value);
|
state = state.copyWith(showBackgroundImage: value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setNotifyWithHaptic(bool value) {
|
||||||
|
final prefs = ref.read(sharedPreferencesProvider);
|
||||||
|
prefs.setBool(kAppNotifyWithHaptic, value);
|
||||||
|
state = state.copyWith(notifyWithHaptic: value);
|
||||||
|
}
|
||||||
|
|
||||||
void setCustomFonts(String? value) {
|
void setCustomFonts(String? value) {
|
||||||
final prefs = ref.read(sharedPreferencesProvider);
|
final prefs = ref.read(sharedPreferencesProvider);
|
||||||
prefs.setString(kAppCustomFonts, value ?? '');
|
prefs.setString(kAppCustomFonts, value ?? '');
|
||||||
state = state.copyWith(customFonts: value);
|
state = state.copyWith(customFonts: value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setDefaultScreen(String? value) {
|
||||||
|
final prefs = ref.read(sharedPreferencesProvider);
|
||||||
|
prefs.setString(kAppDefaultScreen, value ?? 'dashboard');
|
||||||
|
state = state.copyWith(defaultScreen: value);
|
||||||
|
}
|
||||||
|
|
||||||
void setAppColorScheme(int? value) {
|
void setAppColorScheme(int? value) {
|
||||||
final prefs = ref.read(sharedPreferencesProvider);
|
final prefs = ref.read(sharedPreferencesProvider);
|
||||||
prefs.setInt(kAppColorSchemeStoreKey, value ?? 0);
|
prefs.setInt(kAppColorSchemeStoreKey, value ?? 0);
|
||||||
@@ -263,12 +266,6 @@ class AppSettingsNotifier extends _$AppSettingsNotifier {
|
|||||||
state = state.copyWith(cardTransparency: value);
|
state = state.copyWith(cardTransparency: value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setUseMaterial3(bool value) {
|
|
||||||
final prefs = ref.read(sharedPreferencesProvider);
|
|
||||||
prefs.setBool(kMaterialYouToggleStoreKey, value);
|
|
||||||
state = state.copyWith(useMaterial3: value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setCustomColors(ThemeColors? value) {
|
void setCustomColors(ThemeColors? value) {
|
||||||
final prefs = ref.read(sharedPreferencesProvider);
|
final prefs = ref.read(sharedPreferencesProvider);
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
@@ -291,6 +288,38 @@ class AppSettingsNotifier extends _$AppSettingsNotifier {
|
|||||||
prefs.setString(kAppFabPosition, value);
|
prefs.setString(kAppFabPosition, value);
|
||||||
state = state.copyWith(fabPosition: value);
|
state = state.copyWith(fabPosition: value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setGroupedChatList(bool value) {
|
||||||
|
final prefs = ref.read(sharedPreferencesProvider);
|
||||||
|
prefs.setBool(kAppGroupedChatList, value);
|
||||||
|
state = state.copyWith(groupedChatList: value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setFirstLaunchAt(String? value) {
|
||||||
|
final prefs = ref.read(sharedPreferencesProvider);
|
||||||
|
if (value != null) {
|
||||||
|
prefs.setString(kAppFirstLaunchAt, value);
|
||||||
|
} else {
|
||||||
|
prefs.remove(kAppFirstLaunchAt);
|
||||||
|
}
|
||||||
|
state = state.copyWith(firstLaunchAt: value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setAskedReview(bool value) {
|
||||||
|
final prefs = ref.read(sharedPreferencesProvider);
|
||||||
|
prefs.setBool(kAppAskedReview, value);
|
||||||
|
state = state.copyWith(askedReview: value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setDashSearchEngine(String? value) {
|
||||||
|
final prefs = ref.read(sharedPreferencesProvider);
|
||||||
|
if (value != null) {
|
||||||
|
prefs.setString(kAppDashSearchEngine, value);
|
||||||
|
} else {
|
||||||
|
prefs.remove(kAppDashSearchEngine);
|
||||||
|
}
|
||||||
|
state = state.copyWith(dashSearchEngine: value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final updateInfoProvider =
|
final updateInfoProvider =
|
||||||
|
|||||||
@@ -286,11 +286,11 @@ as int?,
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$AppSettings {
|
mixin _$AppSettings {
|
||||||
|
|
||||||
bool get autoTranslate; bool get dataSavingMode; 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
|
bool get dataSavingMode; bool get soundEffects; bool get festivalFeatures; bool get enterToSend; bool get appBarTransparent; bool get showBackgroundImage; bool get notifyWithHaptic; String? get customFonts; int? get appColorScheme;// The color stored via the int type
|
||||||
ThemeColors? get customColors; Size? get windowSize;// The window size for desktop platforms
|
ThemeColors? get customColors; Size? get windowSize;// The window size for desktop platforms
|
||||||
double get windowOpacity;// The window opacity for desktop platforms
|
double get windowOpacity;// The window opacity for desktop platforms
|
||||||
double get cardTransparency;// The card background opacity
|
double get cardTransparency;// The card background opacity
|
||||||
String? get defaultPoolId; String get messageDisplayStyle; String? get themeMode; bool get useMaterial3; bool get disableAnimation; String get fabPosition;
|
String? get defaultPoolId; String get messageDisplayStyle; String? get themeMode; bool get disableAnimation; String get fabPosition; bool get groupedChatList; String? get firstLaunchAt; bool get askedReview; String? get dashSearchEngine; String? get defaultScreen;
|
||||||
/// Create a copy of AppSettings
|
/// Create a copy of AppSettings
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@@ -301,16 +301,16 @@ $AppSettingsCopyWith<AppSettings> get copyWith => _$AppSettingsCopyWithImpl<AppS
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is AppSettings&&(identical(other.autoTranslate, autoTranslate) || other.autoTranslate == autoTranslate)&&(identical(other.dataSavingMode, dataSavingMode) || other.dataSavingMode == dataSavingMode)&&(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.customColors, customColors) || other.customColors == customColors)&&(identical(other.windowSize, windowSize) || other.windowSize == windowSize)&&(identical(other.windowOpacity, windowOpacity) || other.windowOpacity == windowOpacity)&&(identical(other.cardTransparency, cardTransparency) || other.cardTransparency == cardTransparency)&&(identical(other.defaultPoolId, defaultPoolId) || other.defaultPoolId == defaultPoolId)&&(identical(other.messageDisplayStyle, messageDisplayStyle) || other.messageDisplayStyle == messageDisplayStyle)&&(identical(other.themeMode, themeMode) || other.themeMode == themeMode)&&(identical(other.useMaterial3, useMaterial3) || other.useMaterial3 == useMaterial3)&&(identical(other.disableAnimation, disableAnimation) || other.disableAnimation == disableAnimation)&&(identical(other.fabPosition, fabPosition) || other.fabPosition == fabPosition));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is AppSettings&&(identical(other.dataSavingMode, dataSavingMode) || other.dataSavingMode == dataSavingMode)&&(identical(other.soundEffects, soundEffects) || other.soundEffects == soundEffects)&&(identical(other.festivalFeatures, festivalFeatures) || other.festivalFeatures == festivalFeatures)&&(identical(other.enterToSend, enterToSend) || other.enterToSend == enterToSend)&&(identical(other.appBarTransparent, appBarTransparent) || other.appBarTransparent == appBarTransparent)&&(identical(other.showBackgroundImage, showBackgroundImage) || other.showBackgroundImage == showBackgroundImage)&&(identical(other.notifyWithHaptic, notifyWithHaptic) || other.notifyWithHaptic == notifyWithHaptic)&&(identical(other.customFonts, customFonts) || other.customFonts == customFonts)&&(identical(other.appColorScheme, appColorScheme) || other.appColorScheme == appColorScheme)&&(identical(other.customColors, customColors) || other.customColors == customColors)&&(identical(other.windowSize, windowSize) || other.windowSize == windowSize)&&(identical(other.windowOpacity, windowOpacity) || other.windowOpacity == windowOpacity)&&(identical(other.cardTransparency, cardTransparency) || other.cardTransparency == cardTransparency)&&(identical(other.defaultPoolId, defaultPoolId) || other.defaultPoolId == defaultPoolId)&&(identical(other.messageDisplayStyle, messageDisplayStyle) || other.messageDisplayStyle == messageDisplayStyle)&&(identical(other.themeMode, themeMode) || other.themeMode == themeMode)&&(identical(other.disableAnimation, disableAnimation) || other.disableAnimation == disableAnimation)&&(identical(other.fabPosition, fabPosition) || other.fabPosition == fabPosition)&&(identical(other.groupedChatList, groupedChatList) || other.groupedChatList == groupedChatList)&&(identical(other.firstLaunchAt, firstLaunchAt) || other.firstLaunchAt == firstLaunchAt)&&(identical(other.askedReview, askedReview) || other.askedReview == askedReview)&&(identical(other.dashSearchEngine, dashSearchEngine) || other.dashSearchEngine == dashSearchEngine)&&(identical(other.defaultScreen, defaultScreen) || other.defaultScreen == defaultScreen));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hashAll([runtimeType,autoTranslate,dataSavingMode,soundEffects,aprilFoolFeatures,enterToSend,appBarTransparent,showBackgroundImage,customFonts,appColorScheme,customColors,windowSize,windowOpacity,cardTransparency,defaultPoolId,messageDisplayStyle,themeMode,useMaterial3,disableAnimation,fabPosition]);
|
int get hashCode => Object.hashAll([runtimeType,dataSavingMode,soundEffects,festivalFeatures,enterToSend,appBarTransparent,showBackgroundImage,notifyWithHaptic,customFonts,appColorScheme,customColors,windowSize,windowOpacity,cardTransparency,defaultPoolId,messageDisplayStyle,themeMode,disableAnimation,fabPosition,groupedChatList,firstLaunchAt,askedReview,dashSearchEngine,defaultScreen]);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'AppSettings(autoTranslate: $autoTranslate, dataSavingMode: $dataSavingMode, soundEffects: $soundEffects, aprilFoolFeatures: $aprilFoolFeatures, enterToSend: $enterToSend, appBarTransparent: $appBarTransparent, showBackgroundImage: $showBackgroundImage, customFonts: $customFonts, appColorScheme: $appColorScheme, customColors: $customColors, windowSize: $windowSize, windowOpacity: $windowOpacity, cardTransparency: $cardTransparency, defaultPoolId: $defaultPoolId, messageDisplayStyle: $messageDisplayStyle, themeMode: $themeMode, useMaterial3: $useMaterial3, disableAnimation: $disableAnimation, fabPosition: $fabPosition)';
|
return 'AppSettings(dataSavingMode: $dataSavingMode, soundEffects: $soundEffects, festivalFeatures: $festivalFeatures, enterToSend: $enterToSend, appBarTransparent: $appBarTransparent, showBackgroundImage: $showBackgroundImage, notifyWithHaptic: $notifyWithHaptic, customFonts: $customFonts, appColorScheme: $appColorScheme, customColors: $customColors, windowSize: $windowSize, windowOpacity: $windowOpacity, cardTransparency: $cardTransparency, defaultPoolId: $defaultPoolId, messageDisplayStyle: $messageDisplayStyle, themeMode: $themeMode, disableAnimation: $disableAnimation, fabPosition: $fabPosition, groupedChatList: $groupedChatList, firstLaunchAt: $firstLaunchAt, askedReview: $askedReview, dashSearchEngine: $dashSearchEngine, defaultScreen: $defaultScreen)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -321,7 +321,7 @@ abstract mixin class $AppSettingsCopyWith<$Res> {
|
|||||||
factory $AppSettingsCopyWith(AppSettings value, $Res Function(AppSettings) _then) = _$AppSettingsCopyWithImpl;
|
factory $AppSettingsCopyWith(AppSettings value, $Res Function(AppSettings) _then) = _$AppSettingsCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
bool autoTranslate, bool dataSavingMode, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, ThemeColors? customColors, Size? windowSize, double windowOpacity, double cardTransparency, String? defaultPoolId, String messageDisplayStyle, String? themeMode, bool useMaterial3, bool disableAnimation, String fabPosition
|
bool dataSavingMode, bool soundEffects, bool festivalFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, bool notifyWithHaptic, String? customFonts, int? appColorScheme, ThemeColors? customColors, Size? windowSize, double windowOpacity, double cardTransparency, String? defaultPoolId, String messageDisplayStyle, String? themeMode, bool disableAnimation, String fabPosition, bool groupedChatList, String? firstLaunchAt, bool askedReview, String? dashSearchEngine, String? defaultScreen
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -338,15 +338,15 @@ class _$AppSettingsCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of AppSettings
|
/// Create a copy of AppSettings
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? autoTranslate = null,Object? dataSavingMode = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? showBackgroundImage = null,Object? customFonts = freezed,Object? appColorScheme = freezed,Object? customColors = freezed,Object? windowSize = freezed,Object? windowOpacity = null,Object? cardTransparency = null,Object? defaultPoolId = freezed,Object? messageDisplayStyle = null,Object? themeMode = freezed,Object? useMaterial3 = null,Object? disableAnimation = null,Object? fabPosition = null,}) {
|
@pragma('vm:prefer-inline') @override $Res call({Object? dataSavingMode = null,Object? soundEffects = null,Object? festivalFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? showBackgroundImage = null,Object? notifyWithHaptic = null,Object? customFonts = freezed,Object? appColorScheme = freezed,Object? customColors = freezed,Object? windowSize = freezed,Object? windowOpacity = null,Object? cardTransparency = null,Object? defaultPoolId = freezed,Object? messageDisplayStyle = null,Object? themeMode = freezed,Object? disableAnimation = null,Object? fabPosition = null,Object? groupedChatList = null,Object? firstLaunchAt = freezed,Object? askedReview = null,Object? dashSearchEngine = freezed,Object? defaultScreen = freezed,}) {
|
||||||
return _then(_self.copyWith(
|
return _then(_self.copyWith(
|
||||||
autoTranslate: null == autoTranslate ? _self.autoTranslate : autoTranslate // ignore: cast_nullable_to_non_nullable
|
dataSavingMode: null == dataSavingMode ? _self.dataSavingMode : dataSavingMode // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,dataSavingMode: null == dataSavingMode ? _self.dataSavingMode : dataSavingMode // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,soundEffects: null == soundEffects ? _self.soundEffects : soundEffects // 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,festivalFeatures: null == festivalFeatures ? _self.festivalFeatures : festivalFeatures // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,enterToSend: null == enterToSend ? _self.enterToSend : enterToSend // 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,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,showBackgroundImage: null == showBackgroundImage ? _self.showBackgroundImage : showBackgroundImage // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,notifyWithHaptic: null == notifyWithHaptic ? _self.notifyWithHaptic : notifyWithHaptic // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,customFonts: freezed == customFonts ? _self.customFonts : customFonts // 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 String?,appColorScheme: freezed == appColorScheme ? _self.appColorScheme : appColorScheme // ignore: cast_nullable_to_non_nullable
|
||||||
as int?,customColors: freezed == customColors ? _self.customColors : customColors // ignore: cast_nullable_to_non_nullable
|
as int?,customColors: freezed == customColors ? _self.customColors : customColors // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -356,10 +356,14 @@ as double,cardTransparency: null == cardTransparency ? _self.cardTransparency :
|
|||||||
as double,defaultPoolId: freezed == defaultPoolId ? _self.defaultPoolId : defaultPoolId // ignore: cast_nullable_to_non_nullable
|
as double,defaultPoolId: freezed == defaultPoolId ? _self.defaultPoolId : defaultPoolId // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,messageDisplayStyle: null == messageDisplayStyle ? _self.messageDisplayStyle : messageDisplayStyle // ignore: cast_nullable_to_non_nullable
|
as String?,messageDisplayStyle: null == messageDisplayStyle ? _self.messageDisplayStyle : messageDisplayStyle // ignore: cast_nullable_to_non_nullable
|
||||||
as String,themeMode: freezed == themeMode ? _self.themeMode : themeMode // ignore: cast_nullable_to_non_nullable
|
as String,themeMode: freezed == themeMode ? _self.themeMode : themeMode // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,useMaterial3: null == useMaterial3 ? _self.useMaterial3 : useMaterial3 // ignore: cast_nullable_to_non_nullable
|
as String?,disableAnimation: null == disableAnimation ? _self.disableAnimation : disableAnimation // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,disableAnimation: null == disableAnimation ? _self.disableAnimation : disableAnimation // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,fabPosition: null == fabPosition ? _self.fabPosition : fabPosition // ignore: cast_nullable_to_non_nullable
|
as bool,fabPosition: null == fabPosition ? _self.fabPosition : fabPosition // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
as String,groupedChatList: null == groupedChatList ? _self.groupedChatList : groupedChatList // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,firstLaunchAt: freezed == firstLaunchAt ? _self.firstLaunchAt : firstLaunchAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,askedReview: null == askedReview ? _self.askedReview : askedReview // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,dashSearchEngine: freezed == dashSearchEngine ? _self.dashSearchEngine : dashSearchEngine // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,defaultScreen: freezed == defaultScreen ? _self.defaultScreen : defaultScreen // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
/// Create a copy of AppSettings
|
/// Create a copy of AppSettings
|
||||||
@@ -453,10 +457,10 @@ return $default(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool autoTranslate, bool dataSavingMode, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, ThemeColors? customColors, Size? windowSize, double windowOpacity, double cardTransparency, String? defaultPoolId, String messageDisplayStyle, String? themeMode, bool useMaterial3, bool disableAnimation, String fabPosition)? $default,{required TResult orElse(),}) {final _that = this;
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool dataSavingMode, bool soundEffects, bool festivalFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, bool notifyWithHaptic, String? customFonts, int? appColorScheme, ThemeColors? customColors, Size? windowSize, double windowOpacity, double cardTransparency, String? defaultPoolId, String messageDisplayStyle, String? themeMode, bool disableAnimation, String fabPosition, bool groupedChatList, String? firstLaunchAt, bool askedReview, String? dashSearchEngine, String? defaultScreen)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _AppSettings() when $default != null:
|
case _AppSettings() when $default != null:
|
||||||
return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.customFonts,_that.appColorScheme,_that.customColors,_that.windowSize,_that.windowOpacity,_that.cardTransparency,_that.defaultPoolId,_that.messageDisplayStyle,_that.themeMode,_that.useMaterial3,_that.disableAnimation,_that.fabPosition);case _:
|
return $default(_that.dataSavingMode,_that.soundEffects,_that.festivalFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.notifyWithHaptic,_that.customFonts,_that.appColorScheme,_that.customColors,_that.windowSize,_that.windowOpacity,_that.cardTransparency,_that.defaultPoolId,_that.messageDisplayStyle,_that.themeMode,_that.disableAnimation,_that.fabPosition,_that.groupedChatList,_that.firstLaunchAt,_that.askedReview,_that.dashSearchEngine,_that.defaultScreen);case _:
|
||||||
return orElse();
|
return orElse();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -474,10 +478,10 @@ return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_tha
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool autoTranslate, bool dataSavingMode, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, ThemeColors? customColors, Size? windowSize, double windowOpacity, double cardTransparency, String? defaultPoolId, String messageDisplayStyle, String? themeMode, bool useMaterial3, bool disableAnimation, String fabPosition) $default,) {final _that = this;
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool dataSavingMode, bool soundEffects, bool festivalFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, bool notifyWithHaptic, String? customFonts, int? appColorScheme, ThemeColors? customColors, Size? windowSize, double windowOpacity, double cardTransparency, String? defaultPoolId, String messageDisplayStyle, String? themeMode, bool disableAnimation, String fabPosition, bool groupedChatList, String? firstLaunchAt, bool askedReview, String? dashSearchEngine, String? defaultScreen) $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _AppSettings():
|
case _AppSettings():
|
||||||
return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.customFonts,_that.appColorScheme,_that.customColors,_that.windowSize,_that.windowOpacity,_that.cardTransparency,_that.defaultPoolId,_that.messageDisplayStyle,_that.themeMode,_that.useMaterial3,_that.disableAnimation,_that.fabPosition);}
|
return $default(_that.dataSavingMode,_that.soundEffects,_that.festivalFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.notifyWithHaptic,_that.customFonts,_that.appColorScheme,_that.customColors,_that.windowSize,_that.windowOpacity,_that.cardTransparency,_that.defaultPoolId,_that.messageDisplayStyle,_that.themeMode,_that.disableAnimation,_that.fabPosition,_that.groupedChatList,_that.firstLaunchAt,_that.askedReview,_that.dashSearchEngine,_that.defaultScreen);}
|
||||||
}
|
}
|
||||||
/// A variant of `when` that fallback to returning `null`
|
/// A variant of `when` that fallback to returning `null`
|
||||||
///
|
///
|
||||||
@@ -491,10 +495,10 @@ return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_tha
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool autoTranslate, bool dataSavingMode, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, ThemeColors? customColors, Size? windowSize, double windowOpacity, double cardTransparency, String? defaultPoolId, String messageDisplayStyle, String? themeMode, bool useMaterial3, bool disableAnimation, String fabPosition)? $default,) {final _that = this;
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool dataSavingMode, bool soundEffects, bool festivalFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, bool notifyWithHaptic, String? customFonts, int? appColorScheme, ThemeColors? customColors, Size? windowSize, double windowOpacity, double cardTransparency, String? defaultPoolId, String messageDisplayStyle, String? themeMode, bool disableAnimation, String fabPosition, bool groupedChatList, String? firstLaunchAt, bool askedReview, String? dashSearchEngine, String? defaultScreen)? $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _AppSettings() when $default != null:
|
case _AppSettings() when $default != null:
|
||||||
return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.customFonts,_that.appColorScheme,_that.customColors,_that.windowSize,_that.windowOpacity,_that.cardTransparency,_that.defaultPoolId,_that.messageDisplayStyle,_that.themeMode,_that.useMaterial3,_that.disableAnimation,_that.fabPosition);case _:
|
return $default(_that.dataSavingMode,_that.soundEffects,_that.festivalFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.notifyWithHaptic,_that.customFonts,_that.appColorScheme,_that.customColors,_that.windowSize,_that.windowOpacity,_that.cardTransparency,_that.defaultPoolId,_that.messageDisplayStyle,_that.themeMode,_that.disableAnimation,_that.fabPosition,_that.groupedChatList,_that.firstLaunchAt,_that.askedReview,_that.dashSearchEngine,_that.defaultScreen);case _:
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -506,16 +510,16 @@ return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_tha
|
|||||||
|
|
||||||
|
|
||||||
class _AppSettings implements AppSettings {
|
class _AppSettings implements AppSettings {
|
||||||
const _AppSettings({required this.autoTranslate, required this.dataSavingMode, required this.soundEffects, required this.aprilFoolFeatures, required this.enterToSend, required this.appBarTransparent, required this.showBackgroundImage, required this.customFonts, required this.appColorScheme, required this.customColors, required this.windowSize, required this.windowOpacity, required this.cardTransparency, required this.defaultPoolId, required this.messageDisplayStyle, required this.themeMode, required this.useMaterial3, required this.disableAnimation, required this.fabPosition});
|
const _AppSettings({required this.dataSavingMode, required this.soundEffects, required this.festivalFeatures, required this.enterToSend, required this.appBarTransparent, required this.showBackgroundImage, required this.notifyWithHaptic, required this.customFonts, required this.appColorScheme, required this.customColors, required this.windowSize, required this.windowOpacity, required this.cardTransparency, required this.defaultPoolId, required this.messageDisplayStyle, required this.themeMode, required this.disableAnimation, required this.fabPosition, required this.groupedChatList, required this.firstLaunchAt, required this.askedReview, required this.dashSearchEngine, required this.defaultScreen});
|
||||||
|
|
||||||
|
|
||||||
@override final bool autoTranslate;
|
|
||||||
@override final bool dataSavingMode;
|
@override final bool dataSavingMode;
|
||||||
@override final bool soundEffects;
|
@override final bool soundEffects;
|
||||||
@override final bool aprilFoolFeatures;
|
@override final bool festivalFeatures;
|
||||||
@override final bool enterToSend;
|
@override final bool enterToSend;
|
||||||
@override final bool appBarTransparent;
|
@override final bool appBarTransparent;
|
||||||
@override final bool showBackgroundImage;
|
@override final bool showBackgroundImage;
|
||||||
|
@override final bool notifyWithHaptic;
|
||||||
@override final String? customFonts;
|
@override final String? customFonts;
|
||||||
@override final int? appColorScheme;
|
@override final int? appColorScheme;
|
||||||
// The color stored via the int type
|
// The color stored via the int type
|
||||||
@@ -529,9 +533,13 @@ class _AppSettings implements AppSettings {
|
|||||||
@override final String? defaultPoolId;
|
@override final String? defaultPoolId;
|
||||||
@override final String messageDisplayStyle;
|
@override final String messageDisplayStyle;
|
||||||
@override final String? themeMode;
|
@override final String? themeMode;
|
||||||
@override final bool useMaterial3;
|
|
||||||
@override final bool disableAnimation;
|
@override final bool disableAnimation;
|
||||||
@override final String fabPosition;
|
@override final String fabPosition;
|
||||||
|
@override final bool groupedChatList;
|
||||||
|
@override final String? firstLaunchAt;
|
||||||
|
@override final bool askedReview;
|
||||||
|
@override final String? dashSearchEngine;
|
||||||
|
@override final String? defaultScreen;
|
||||||
|
|
||||||
/// Create a copy of AppSettings
|
/// Create a copy of AppSettings
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@@ -543,16 +551,16 @@ _$AppSettingsCopyWith<_AppSettings> get copyWith => __$AppSettingsCopyWithImpl<_
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppSettings&&(identical(other.autoTranslate, autoTranslate) || other.autoTranslate == autoTranslate)&&(identical(other.dataSavingMode, dataSavingMode) || other.dataSavingMode == dataSavingMode)&&(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.customColors, customColors) || other.customColors == customColors)&&(identical(other.windowSize, windowSize) || other.windowSize == windowSize)&&(identical(other.windowOpacity, windowOpacity) || other.windowOpacity == windowOpacity)&&(identical(other.cardTransparency, cardTransparency) || other.cardTransparency == cardTransparency)&&(identical(other.defaultPoolId, defaultPoolId) || other.defaultPoolId == defaultPoolId)&&(identical(other.messageDisplayStyle, messageDisplayStyle) || other.messageDisplayStyle == messageDisplayStyle)&&(identical(other.themeMode, themeMode) || other.themeMode == themeMode)&&(identical(other.useMaterial3, useMaterial3) || other.useMaterial3 == useMaterial3)&&(identical(other.disableAnimation, disableAnimation) || other.disableAnimation == disableAnimation)&&(identical(other.fabPosition, fabPosition) || other.fabPosition == fabPosition));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppSettings&&(identical(other.dataSavingMode, dataSavingMode) || other.dataSavingMode == dataSavingMode)&&(identical(other.soundEffects, soundEffects) || other.soundEffects == soundEffects)&&(identical(other.festivalFeatures, festivalFeatures) || other.festivalFeatures == festivalFeatures)&&(identical(other.enterToSend, enterToSend) || other.enterToSend == enterToSend)&&(identical(other.appBarTransparent, appBarTransparent) || other.appBarTransparent == appBarTransparent)&&(identical(other.showBackgroundImage, showBackgroundImage) || other.showBackgroundImage == showBackgroundImage)&&(identical(other.notifyWithHaptic, notifyWithHaptic) || other.notifyWithHaptic == notifyWithHaptic)&&(identical(other.customFonts, customFonts) || other.customFonts == customFonts)&&(identical(other.appColorScheme, appColorScheme) || other.appColorScheme == appColorScheme)&&(identical(other.customColors, customColors) || other.customColors == customColors)&&(identical(other.windowSize, windowSize) || other.windowSize == windowSize)&&(identical(other.windowOpacity, windowOpacity) || other.windowOpacity == windowOpacity)&&(identical(other.cardTransparency, cardTransparency) || other.cardTransparency == cardTransparency)&&(identical(other.defaultPoolId, defaultPoolId) || other.defaultPoolId == defaultPoolId)&&(identical(other.messageDisplayStyle, messageDisplayStyle) || other.messageDisplayStyle == messageDisplayStyle)&&(identical(other.themeMode, themeMode) || other.themeMode == themeMode)&&(identical(other.disableAnimation, disableAnimation) || other.disableAnimation == disableAnimation)&&(identical(other.fabPosition, fabPosition) || other.fabPosition == fabPosition)&&(identical(other.groupedChatList, groupedChatList) || other.groupedChatList == groupedChatList)&&(identical(other.firstLaunchAt, firstLaunchAt) || other.firstLaunchAt == firstLaunchAt)&&(identical(other.askedReview, askedReview) || other.askedReview == askedReview)&&(identical(other.dashSearchEngine, dashSearchEngine) || other.dashSearchEngine == dashSearchEngine)&&(identical(other.defaultScreen, defaultScreen) || other.defaultScreen == defaultScreen));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hashAll([runtimeType,autoTranslate,dataSavingMode,soundEffects,aprilFoolFeatures,enterToSend,appBarTransparent,showBackgroundImage,customFonts,appColorScheme,customColors,windowSize,windowOpacity,cardTransparency,defaultPoolId,messageDisplayStyle,themeMode,useMaterial3,disableAnimation,fabPosition]);
|
int get hashCode => Object.hashAll([runtimeType,dataSavingMode,soundEffects,festivalFeatures,enterToSend,appBarTransparent,showBackgroundImage,notifyWithHaptic,customFonts,appColorScheme,customColors,windowSize,windowOpacity,cardTransparency,defaultPoolId,messageDisplayStyle,themeMode,disableAnimation,fabPosition,groupedChatList,firstLaunchAt,askedReview,dashSearchEngine,defaultScreen]);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'AppSettings(autoTranslate: $autoTranslate, dataSavingMode: $dataSavingMode, soundEffects: $soundEffects, aprilFoolFeatures: $aprilFoolFeatures, enterToSend: $enterToSend, appBarTransparent: $appBarTransparent, showBackgroundImage: $showBackgroundImage, customFonts: $customFonts, appColorScheme: $appColorScheme, customColors: $customColors, windowSize: $windowSize, windowOpacity: $windowOpacity, cardTransparency: $cardTransparency, defaultPoolId: $defaultPoolId, messageDisplayStyle: $messageDisplayStyle, themeMode: $themeMode, useMaterial3: $useMaterial3, disableAnimation: $disableAnimation, fabPosition: $fabPosition)';
|
return 'AppSettings(dataSavingMode: $dataSavingMode, soundEffects: $soundEffects, festivalFeatures: $festivalFeatures, enterToSend: $enterToSend, appBarTransparent: $appBarTransparent, showBackgroundImage: $showBackgroundImage, notifyWithHaptic: $notifyWithHaptic, customFonts: $customFonts, appColorScheme: $appColorScheme, customColors: $customColors, windowSize: $windowSize, windowOpacity: $windowOpacity, cardTransparency: $cardTransparency, defaultPoolId: $defaultPoolId, messageDisplayStyle: $messageDisplayStyle, themeMode: $themeMode, disableAnimation: $disableAnimation, fabPosition: $fabPosition, groupedChatList: $groupedChatList, firstLaunchAt: $firstLaunchAt, askedReview: $askedReview, dashSearchEngine: $dashSearchEngine, defaultScreen: $defaultScreen)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -563,7 +571,7 @@ abstract mixin class _$AppSettingsCopyWith<$Res> implements $AppSettingsCopyWith
|
|||||||
factory _$AppSettingsCopyWith(_AppSettings value, $Res Function(_AppSettings) _then) = __$AppSettingsCopyWithImpl;
|
factory _$AppSettingsCopyWith(_AppSettings value, $Res Function(_AppSettings) _then) = __$AppSettingsCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
bool autoTranslate, bool dataSavingMode, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, ThemeColors? customColors, Size? windowSize, double windowOpacity, double cardTransparency, String? defaultPoolId, String messageDisplayStyle, String? themeMode, bool useMaterial3, bool disableAnimation, String fabPosition
|
bool dataSavingMode, bool soundEffects, bool festivalFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, bool notifyWithHaptic, String? customFonts, int? appColorScheme, ThemeColors? customColors, Size? windowSize, double windowOpacity, double cardTransparency, String? defaultPoolId, String messageDisplayStyle, String? themeMode, bool disableAnimation, String fabPosition, bool groupedChatList, String? firstLaunchAt, bool askedReview, String? dashSearchEngine, String? defaultScreen
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -580,15 +588,15 @@ class __$AppSettingsCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of AppSettings
|
/// Create a copy of AppSettings
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? autoTranslate = null,Object? dataSavingMode = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? showBackgroundImage = null,Object? customFonts = freezed,Object? appColorScheme = freezed,Object? customColors = freezed,Object? windowSize = freezed,Object? windowOpacity = null,Object? cardTransparency = null,Object? defaultPoolId = freezed,Object? messageDisplayStyle = null,Object? themeMode = freezed,Object? useMaterial3 = null,Object? disableAnimation = null,Object? fabPosition = null,}) {
|
@override @pragma('vm:prefer-inline') $Res call({Object? dataSavingMode = null,Object? soundEffects = null,Object? festivalFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? showBackgroundImage = null,Object? notifyWithHaptic = null,Object? customFonts = freezed,Object? appColorScheme = freezed,Object? customColors = freezed,Object? windowSize = freezed,Object? windowOpacity = null,Object? cardTransparency = null,Object? defaultPoolId = freezed,Object? messageDisplayStyle = null,Object? themeMode = freezed,Object? disableAnimation = null,Object? fabPosition = null,Object? groupedChatList = null,Object? firstLaunchAt = freezed,Object? askedReview = null,Object? dashSearchEngine = freezed,Object? defaultScreen = freezed,}) {
|
||||||
return _then(_AppSettings(
|
return _then(_AppSettings(
|
||||||
autoTranslate: null == autoTranslate ? _self.autoTranslate : autoTranslate // ignore: cast_nullable_to_non_nullable
|
dataSavingMode: null == dataSavingMode ? _self.dataSavingMode : dataSavingMode // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,dataSavingMode: null == dataSavingMode ? _self.dataSavingMode : dataSavingMode // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,soundEffects: null == soundEffects ? _self.soundEffects : soundEffects // 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,festivalFeatures: null == festivalFeatures ? _self.festivalFeatures : festivalFeatures // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,enterToSend: null == enterToSend ? _self.enterToSend : enterToSend // 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,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,showBackgroundImage: null == showBackgroundImage ? _self.showBackgroundImage : showBackgroundImage // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,notifyWithHaptic: null == notifyWithHaptic ? _self.notifyWithHaptic : notifyWithHaptic // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,customFonts: freezed == customFonts ? _self.customFonts : customFonts // 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 String?,appColorScheme: freezed == appColorScheme ? _self.appColorScheme : appColorScheme // ignore: cast_nullable_to_non_nullable
|
||||||
as int?,customColors: freezed == customColors ? _self.customColors : customColors // ignore: cast_nullable_to_non_nullable
|
as int?,customColors: freezed == customColors ? _self.customColors : customColors // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -598,10 +606,14 @@ as double,cardTransparency: null == cardTransparency ? _self.cardTransparency :
|
|||||||
as double,defaultPoolId: freezed == defaultPoolId ? _self.defaultPoolId : defaultPoolId // ignore: cast_nullable_to_non_nullable
|
as double,defaultPoolId: freezed == defaultPoolId ? _self.defaultPoolId : defaultPoolId // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,messageDisplayStyle: null == messageDisplayStyle ? _self.messageDisplayStyle : messageDisplayStyle // ignore: cast_nullable_to_non_nullable
|
as String?,messageDisplayStyle: null == messageDisplayStyle ? _self.messageDisplayStyle : messageDisplayStyle // ignore: cast_nullable_to_non_nullable
|
||||||
as String,themeMode: freezed == themeMode ? _self.themeMode : themeMode // ignore: cast_nullable_to_non_nullable
|
as String,themeMode: freezed == themeMode ? _self.themeMode : themeMode // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,useMaterial3: null == useMaterial3 ? _self.useMaterial3 : useMaterial3 // ignore: cast_nullable_to_non_nullable
|
as String?,disableAnimation: null == disableAnimation ? _self.disableAnimation : disableAnimation // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,disableAnimation: null == disableAnimation ? _self.disableAnimation : disableAnimation // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,fabPosition: null == fabPosition ? _self.fabPosition : fabPosition // ignore: cast_nullable_to_non_nullable
|
as bool,fabPosition: null == fabPosition ? _self.fabPosition : fabPosition // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
as String,groupedChatList: null == groupedChatList ? _self.groupedChatList : groupedChatList // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,firstLaunchAt: freezed == firstLaunchAt ? _self.firstLaunchAt : firstLaunchAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,askedReview: null == askedReview ? _self.askedReview : askedReview // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,dashSearchEngine: freezed == dashSearchEngine ? _self.dashSearchEngine : dashSearchEngine // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,defaultScreen: freezed == defaultScreen ? _self.defaultScreen : defaultScreen // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ final class AppSettingsNotifierProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _$appSettingsNotifierHash() =>
|
String _$appSettingsNotifierHash() =>
|
||||||
r'22b695f2023e3251db3296858acd701f7211d757';
|
r'ef10d95a9f22e891ad6f5e0225e31508b3eb038e';
|
||||||
|
|
||||||
abstract class _$AppSettingsNotifier extends $Notifier<AppSettings> {
|
abstract class _$AppSettingsNotifier extends $Notifier<AppSettings> {
|
||||||
AppSettings build();
|
AppSettings build();
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'dart:io';
|
|||||||
import 'package:dio_smart_retry/dio_smart_retry.dart';
|
import 'package:dio_smart_retry/dio_smart_retry.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
@@ -16,6 +17,36 @@ import 'package:island/talker.dart';
|
|||||||
|
|
||||||
import 'config.dart';
|
import 'config.dart';
|
||||||
|
|
||||||
|
part 'network.g.dart';
|
||||||
|
|
||||||
|
// Network status enum to track different states
|
||||||
|
enum NetworkStatus { online, notReady, maintenance, offline }
|
||||||
|
|
||||||
|
// Provider for network status using Riverpod v3 annotation
|
||||||
|
@riverpod
|
||||||
|
class NetworkStatusNotifier extends _$NetworkStatusNotifier {
|
||||||
|
@override
|
||||||
|
NetworkStatus build() {
|
||||||
|
return NetworkStatus.online;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setOnline() {
|
||||||
|
state = NetworkStatus.online;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setMaintenance() {
|
||||||
|
state = NetworkStatus.maintenance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setOffline() {
|
||||||
|
state = NetworkStatus.offline;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setNotReady() {
|
||||||
|
state = NetworkStatus.notReady;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final imagePickerProvider = Provider((ref) => ImagePicker());
|
final imagePickerProvider = Provider((ref) => ImagePicker());
|
||||||
|
|
||||||
final userAgentProvider = FutureProvider<String>((ref) async {
|
final userAgentProvider = FutureProvider<String>((ref) async {
|
||||||
@@ -80,24 +111,66 @@ final apiClientProvider = Provider<Dio>((ref) {
|
|||||||
|
|
||||||
dio.interceptors.addAll([
|
dio.interceptors.addAll([
|
||||||
InterceptorsWrapper(
|
InterceptorsWrapper(
|
||||||
onRequest: (
|
onRequest:
|
||||||
RequestOptions options,
|
(RequestOptions options, RequestInterceptorHandler handler) async {
|
||||||
RequestInterceptorHandler handler,
|
try {
|
||||||
) async {
|
final token = await getToken(ref.watch(tokenProvider));
|
||||||
try {
|
if (token != null) {
|
||||||
final token = await getToken(ref.watch(tokenProvider));
|
options.headers['Authorization'] = 'AtField $token';
|
||||||
if (token != null) {
|
}
|
||||||
options.headers['Authorization'] = 'AtField $token';
|
} catch (err) {
|
||||||
}
|
// ignore
|
||||||
} catch (err) {
|
}
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
|
|
||||||
final userAgent = ref.read(userAgentProvider);
|
final userAgent = ref.read(userAgentProvider);
|
||||||
if (userAgent.value != null) {
|
if (userAgent.value != null) {
|
||||||
options.headers['User-Agent'] = userAgent.value;
|
options.headers['User-Agent'] = userAgent.value;
|
||||||
|
}
|
||||||
|
return handler.next(options);
|
||||||
|
},
|
||||||
|
onResponse: (response, handler) {
|
||||||
|
// Check for 503 status code (Service Unavailable/Maintenance)
|
||||||
|
if (response.statusCode == 503) {
|
||||||
|
final networkStatusNotifier = ref.read(
|
||||||
|
networkStatusProvider.notifier,
|
||||||
|
);
|
||||||
|
if (response.headers.value('X-NotReady') != null) {
|
||||||
|
networkStatusNotifier.setNotReady();
|
||||||
|
} else {
|
||||||
|
networkStatusNotifier.setMaintenance();
|
||||||
|
}
|
||||||
|
} else if (response.statusCode != null &&
|
||||||
|
response.statusCode! >= 200 &&
|
||||||
|
response.statusCode! < 300) {
|
||||||
|
// Set online status for successful responses
|
||||||
|
final networkStatusNotifier = ref.read(
|
||||||
|
networkStatusProvider.notifier,
|
||||||
|
);
|
||||||
|
networkStatusNotifier.setOnline();
|
||||||
}
|
}
|
||||||
return handler.next(options);
|
return handler.next(response);
|
||||||
|
},
|
||||||
|
onError: (error, handler) {
|
||||||
|
// Handle network errors and set offline status
|
||||||
|
if (error.type == DioExceptionType.connectionTimeout ||
|
||||||
|
error.type == DioExceptionType.receiveTimeout ||
|
||||||
|
error.type == DioExceptionType.sendTimeout ||
|
||||||
|
error.type == DioExceptionType.connectionError) {
|
||||||
|
final networkStatusNotifier = ref.read(
|
||||||
|
networkStatusProvider.notifier,
|
||||||
|
);
|
||||||
|
networkStatusNotifier.setOffline();
|
||||||
|
} else if (error.response?.statusCode == 503) {
|
||||||
|
final networkStatusNotifier = ref.read(
|
||||||
|
networkStatusProvider.notifier,
|
||||||
|
);
|
||||||
|
if (error.response?.headers.value('X-NotReady') != null) {
|
||||||
|
networkStatusNotifier.setNotReady();
|
||||||
|
} else {
|
||||||
|
networkStatusNotifier.setMaintenance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return handler.next(error);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
TalkerDioLogger(
|
TalkerDioLogger(
|
||||||
|
|||||||
64
lib/pods/network.g.dart
Normal file
64
lib/pods/network.g.dart
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'network.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint, type=warning
|
||||||
|
|
||||||
|
@ProviderFor(NetworkStatusNotifier)
|
||||||
|
const networkStatusProvider = NetworkStatusNotifierProvider._();
|
||||||
|
|
||||||
|
final class NetworkStatusNotifierProvider
|
||||||
|
extends $NotifierProvider<NetworkStatusNotifier, NetworkStatus> {
|
||||||
|
const NetworkStatusNotifierProvider._()
|
||||||
|
: super(
|
||||||
|
from: null,
|
||||||
|
argument: null,
|
||||||
|
retry: null,
|
||||||
|
name: r'networkStatusProvider',
|
||||||
|
isAutoDispose: true,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$networkStatusNotifierHash();
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
NetworkStatusNotifier create() => NetworkStatusNotifier();
|
||||||
|
|
||||||
|
/// {@macro riverpod.override_with_value}
|
||||||
|
Override overrideWithValue(NetworkStatus value) {
|
||||||
|
return $ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
providerOverride: $SyncValueProvider<NetworkStatus>(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$networkStatusNotifierHash() =>
|
||||||
|
r'6f08e3067fa5265432f28f64e10775e3039506c3';
|
||||||
|
|
||||||
|
abstract class _$NetworkStatusNotifier extends $Notifier<NetworkStatus> {
|
||||||
|
NetworkStatus build();
|
||||||
|
@$mustCallSuper
|
||||||
|
@override
|
||||||
|
void runBuild() {
|
||||||
|
final created = build();
|
||||||
|
final ref = this.ref as $Ref<NetworkStatus, NetworkStatus>;
|
||||||
|
final element =
|
||||||
|
ref.element
|
||||||
|
as $ClassProviderElement<
|
||||||
|
AnyNotifier<NetworkStatus, NetworkStatus>,
|
||||||
|
NetworkStatus,
|
||||||
|
Object?,
|
||||||
|
Object?
|
||||||
|
>;
|
||||||
|
element.handleValue(ref, created);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ part 'post_list.freezed.dart';
|
|||||||
sealed class PostListQuery with _$PostListQuery {
|
sealed class PostListQuery with _$PostListQuery {
|
||||||
const factory PostListQuery({
|
const factory PostListQuery({
|
||||||
String? pubName,
|
String? pubName,
|
||||||
|
List<String>? publishers,
|
||||||
String? realm,
|
String? realm,
|
||||||
int? type,
|
int? type,
|
||||||
List<String>? categories,
|
List<String>? categories,
|
||||||
@@ -61,35 +62,98 @@ class PostListNotifier extends AsyncNotifier<List<SnPost>>
|
|||||||
Future<List<SnPost>> fetch() async {
|
Future<List<SnPost>> fetch() async {
|
||||||
final client = ref.read(apiClientProvider);
|
final client = ref.read(apiClientProvider);
|
||||||
|
|
||||||
final queryParams = {
|
// Handle multiple publishers by making separate requests and combining results
|
||||||
'offset': fetchedCount,
|
if (currentFilter.publishers != null &&
|
||||||
'take': pageSize,
|
currentFilter.publishers!.isNotEmpty) {
|
||||||
'replies': currentFilter.includeReplies,
|
final allPosts = <SnPost>[];
|
||||||
'orderDesc': currentFilter.orderDesc,
|
var totalPostsCount = 0;
|
||||||
if (currentFilter.shuffle) 'shuffle': currentFilter.shuffle,
|
|
||||||
if (currentFilter.pubName != null) 'pub': currentFilter.pubName,
|
|
||||||
if (currentFilter.realm != null) 'realm': currentFilter.realm,
|
|
||||||
if (currentFilter.type != null) 'type': currentFilter.type,
|
|
||||||
if (currentFilter.tags != null) 'tags': currentFilter.tags,
|
|
||||||
if (currentFilter.categories != null)
|
|
||||||
'categories': currentFilter.categories,
|
|
||||||
if (currentFilter.pinned != null) 'pinned': currentFilter.pinned,
|
|
||||||
if (currentFilter.order != null) 'order': currentFilter.order,
|
|
||||||
if (currentFilter.periodStart != null)
|
|
||||||
'periodStart': currentFilter.periodStart,
|
|
||||||
if (currentFilter.periodEnd != null) 'periodEnd': currentFilter.periodEnd,
|
|
||||||
if (currentFilter.queryTerm != null) 'query': currentFilter.queryTerm,
|
|
||||||
if (currentFilter.mediaOnly != null) 'media': currentFilter.mediaOnly,
|
|
||||||
};
|
|
||||||
|
|
||||||
final response = await client.get(
|
for (final publisherName in currentFilter.publishers!) {
|
||||||
'/sphere/posts',
|
final queryParams = {
|
||||||
queryParameters: queryParams,
|
'offset': fetchedCount,
|
||||||
);
|
'take': pageSize,
|
||||||
totalCount = int.parse(response.headers.value('X-Total') ?? '0');
|
'replies': currentFilter.includeReplies,
|
||||||
return response.data
|
'orderDesc': currentFilter.orderDesc,
|
||||||
.map((json) => SnPost.fromJson(json))
|
if (currentFilter.shuffle) 'shuffle': currentFilter.shuffle,
|
||||||
.cast<SnPost>()
|
'pub': publisherName,
|
||||||
.toList();
|
if (currentFilter.realm != null) 'realm': currentFilter.realm,
|
||||||
|
if (currentFilter.type != null) 'type': currentFilter.type,
|
||||||
|
if (currentFilter.tags != null) 'tags': currentFilter.tags,
|
||||||
|
if (currentFilter.categories != null)
|
||||||
|
'categories': currentFilter.categories,
|
||||||
|
if (currentFilter.pinned != null) 'pinned': currentFilter.pinned,
|
||||||
|
if (currentFilter.order != null) 'order': currentFilter.order,
|
||||||
|
if (currentFilter.periodStart != null)
|
||||||
|
'periodStart': currentFilter.periodStart,
|
||||||
|
if (currentFilter.periodEnd != null)
|
||||||
|
'periodEnd': currentFilter.periodEnd,
|
||||||
|
if (currentFilter.queryTerm != null) 'query': currentFilter.queryTerm,
|
||||||
|
if (currentFilter.mediaOnly != null) 'media': currentFilter.mediaOnly,
|
||||||
|
};
|
||||||
|
|
||||||
|
final response = await client.get(
|
||||||
|
'/sphere/posts',
|
||||||
|
queryParameters: queryParams,
|
||||||
|
);
|
||||||
|
|
||||||
|
final posts = response.data
|
||||||
|
.map((json) => SnPost.fromJson(json))
|
||||||
|
.cast<SnPost>()
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
allPosts.addAll(posts);
|
||||||
|
totalPostsCount += int.parse(response.headers.value('X-Total') ?? '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort combined results by creation date (newest first)
|
||||||
|
allPosts.sort(
|
||||||
|
(a, b) => (b.createdAt ?? DateTime.now()).compareTo(
|
||||||
|
a.createdAt ?? DateTime.now(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Apply pagination to combined results
|
||||||
|
final startIndex = fetchedCount;
|
||||||
|
final endIndex = (fetchedCount + pageSize).clamp(0, allPosts.length);
|
||||||
|
final paginatedPosts = startIndex < allPosts.length
|
||||||
|
? allPosts.sublist(startIndex, endIndex)
|
||||||
|
: <SnPost>[];
|
||||||
|
|
||||||
|
totalCount = totalPostsCount;
|
||||||
|
return paginatedPosts;
|
||||||
|
} else {
|
||||||
|
// Single publisher or no publisher filter
|
||||||
|
final queryParams = {
|
||||||
|
'offset': fetchedCount,
|
||||||
|
'take': pageSize,
|
||||||
|
'replies': currentFilter.includeReplies,
|
||||||
|
'orderDesc': currentFilter.orderDesc,
|
||||||
|
if (currentFilter.shuffle) 'shuffle': currentFilter.shuffle,
|
||||||
|
if (currentFilter.pubName != null) 'pub': currentFilter.pubName,
|
||||||
|
if (currentFilter.realm != null) 'realm': currentFilter.realm,
|
||||||
|
if (currentFilter.type != null) 'type': currentFilter.type,
|
||||||
|
if (currentFilter.tags != null) 'tags': currentFilter.tags,
|
||||||
|
if (currentFilter.categories != null)
|
||||||
|
'categories': currentFilter.categories,
|
||||||
|
if (currentFilter.pinned != null) 'pinned': currentFilter.pinned,
|
||||||
|
if (currentFilter.order != null) 'order': currentFilter.order,
|
||||||
|
if (currentFilter.periodStart != null)
|
||||||
|
'periodStart': currentFilter.periodStart,
|
||||||
|
if (currentFilter.periodEnd != null)
|
||||||
|
'periodEnd': currentFilter.periodEnd,
|
||||||
|
if (currentFilter.queryTerm != null) 'query': currentFilter.queryTerm,
|
||||||
|
if (currentFilter.mediaOnly != null) 'media': currentFilter.mediaOnly,
|
||||||
|
};
|
||||||
|
|
||||||
|
final response = await client.get(
|
||||||
|
'/sphere/posts',
|
||||||
|
queryParameters: queryParams,
|
||||||
|
);
|
||||||
|
totalCount = int.parse(response.headers.value('X-Total') ?? '0');
|
||||||
|
return response.data
|
||||||
|
.map((json) => SnPost.fromJson(json))
|
||||||
|
.cast<SnPost>()
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ T _$identity<T>(T value) => value;
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$PostListQuery {
|
mixin _$PostListQuery {
|
||||||
|
|
||||||
String? get pubName; String? get realm; int? get type; List<String>? get categories; List<String>? get tags; bool? get pinned; bool get shuffle; bool? get includeReplies; bool? get mediaOnly; String? get queryTerm; String? get order; int? get periodStart; int? get periodEnd; bool get orderDesc;
|
String? get pubName; List<String>? get publishers; String? get realm; int? get type; List<String>? get categories; List<String>? get tags; bool? get pinned; bool get shuffle; bool? get includeReplies; bool? get mediaOnly; String? get queryTerm; String? get order; int? get periodStart; int? get periodEnd; bool get orderDesc;
|
||||||
/// Create a copy of PostListQuery
|
/// Create a copy of PostListQuery
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@@ -25,16 +25,16 @@ $PostListQueryCopyWith<PostListQuery> get copyWith => _$PostListQueryCopyWithImp
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is PostListQuery&&(identical(other.pubName, pubName) || other.pubName == pubName)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.categories, categories)&&const DeepCollectionEquality().equals(other.tags, tags)&&(identical(other.pinned, pinned) || other.pinned == pinned)&&(identical(other.shuffle, shuffle) || other.shuffle == shuffle)&&(identical(other.includeReplies, includeReplies) || other.includeReplies == includeReplies)&&(identical(other.mediaOnly, mediaOnly) || other.mediaOnly == mediaOnly)&&(identical(other.queryTerm, queryTerm) || other.queryTerm == queryTerm)&&(identical(other.order, order) || other.order == order)&&(identical(other.periodStart, periodStart) || other.periodStart == periodStart)&&(identical(other.periodEnd, periodEnd) || other.periodEnd == periodEnd)&&(identical(other.orderDesc, orderDesc) || other.orderDesc == orderDesc));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is PostListQuery&&(identical(other.pubName, pubName) || other.pubName == pubName)&&const DeepCollectionEquality().equals(other.publishers, publishers)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.categories, categories)&&const DeepCollectionEquality().equals(other.tags, tags)&&(identical(other.pinned, pinned) || other.pinned == pinned)&&(identical(other.shuffle, shuffle) || other.shuffle == shuffle)&&(identical(other.includeReplies, includeReplies) || other.includeReplies == includeReplies)&&(identical(other.mediaOnly, mediaOnly) || other.mediaOnly == mediaOnly)&&(identical(other.queryTerm, queryTerm) || other.queryTerm == queryTerm)&&(identical(other.order, order) || other.order == order)&&(identical(other.periodStart, periodStart) || other.periodStart == periodStart)&&(identical(other.periodEnd, periodEnd) || other.periodEnd == periodEnd)&&(identical(other.orderDesc, orderDesc) || other.orderDesc == orderDesc));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,pubName,realm,type,const DeepCollectionEquality().hash(categories),const DeepCollectionEquality().hash(tags),pinned,shuffle,includeReplies,mediaOnly,queryTerm,order,periodStart,periodEnd,orderDesc);
|
int get hashCode => Object.hash(runtimeType,pubName,const DeepCollectionEquality().hash(publishers),realm,type,const DeepCollectionEquality().hash(categories),const DeepCollectionEquality().hash(tags),pinned,shuffle,includeReplies,mediaOnly,queryTerm,order,periodStart,periodEnd,orderDesc);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'PostListQuery(pubName: $pubName, realm: $realm, type: $type, categories: $categories, tags: $tags, pinned: $pinned, shuffle: $shuffle, includeReplies: $includeReplies, mediaOnly: $mediaOnly, queryTerm: $queryTerm, order: $order, periodStart: $periodStart, periodEnd: $periodEnd, orderDesc: $orderDesc)';
|
return 'PostListQuery(pubName: $pubName, publishers: $publishers, realm: $realm, type: $type, categories: $categories, tags: $tags, pinned: $pinned, shuffle: $shuffle, includeReplies: $includeReplies, mediaOnly: $mediaOnly, queryTerm: $queryTerm, order: $order, periodStart: $periodStart, periodEnd: $periodEnd, orderDesc: $orderDesc)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ abstract mixin class $PostListQueryCopyWith<$Res> {
|
|||||||
factory $PostListQueryCopyWith(PostListQuery value, $Res Function(PostListQuery) _then) = _$PostListQueryCopyWithImpl;
|
factory $PostListQueryCopyWith(PostListQuery value, $Res Function(PostListQuery) _then) = _$PostListQueryCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String? pubName, String? realm, int? type, List<String>? categories, List<String>? tags, bool? pinned, bool shuffle, bool? includeReplies, bool? mediaOnly, String? queryTerm, String? order, int? periodStart, int? periodEnd, bool orderDesc
|
String? pubName, List<String>? publishers, String? realm, int? type, List<String>? categories, List<String>? tags, bool? pinned, bool shuffle, bool? includeReplies, bool? mediaOnly, String? queryTerm, String? order, int? periodStart, int? periodEnd, bool orderDesc
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -62,10 +62,11 @@ class _$PostListQueryCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of PostListQuery
|
/// Create a copy of PostListQuery
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? pubName = freezed,Object? realm = freezed,Object? type = freezed,Object? categories = freezed,Object? tags = freezed,Object? pinned = freezed,Object? shuffle = null,Object? includeReplies = freezed,Object? mediaOnly = freezed,Object? queryTerm = freezed,Object? order = freezed,Object? periodStart = freezed,Object? periodEnd = freezed,Object? orderDesc = null,}) {
|
@pragma('vm:prefer-inline') @override $Res call({Object? pubName = freezed,Object? publishers = freezed,Object? realm = freezed,Object? type = freezed,Object? categories = freezed,Object? tags = freezed,Object? pinned = freezed,Object? shuffle = null,Object? includeReplies = freezed,Object? mediaOnly = freezed,Object? queryTerm = freezed,Object? order = freezed,Object? periodStart = freezed,Object? periodEnd = freezed,Object? orderDesc = null,}) {
|
||||||
return _then(_self.copyWith(
|
return _then(_self.copyWith(
|
||||||
pubName: freezed == pubName ? _self.pubName : pubName // ignore: cast_nullable_to_non_nullable
|
pubName: freezed == pubName ? _self.pubName : pubName // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,realm: freezed == realm ? _self.realm : realm // ignore: cast_nullable_to_non_nullable
|
as String?,publishers: freezed == publishers ? _self.publishers : publishers // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<String>?,realm: freezed == realm ? _self.realm : realm // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,type: freezed == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
as String?,type: freezed == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||||
as int?,categories: freezed == categories ? _self.categories : categories // ignore: cast_nullable_to_non_nullable
|
as int?,categories: freezed == categories ? _self.categories : categories // ignore: cast_nullable_to_non_nullable
|
||||||
as List<String>?,tags: freezed == tags ? _self.tags : tags // ignore: cast_nullable_to_non_nullable
|
as List<String>?,tags: freezed == tags ? _self.tags : tags // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -160,10 +161,10 @@ return $default(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String? pubName, String? realm, int? type, List<String>? categories, List<String>? tags, bool? pinned, bool shuffle, bool? includeReplies, bool? mediaOnly, String? queryTerm, String? order, int? periodStart, int? periodEnd, bool orderDesc)? $default,{required TResult orElse(),}) {final _that = this;
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String? pubName, List<String>? publishers, String? realm, int? type, List<String>? categories, List<String>? tags, bool? pinned, bool shuffle, bool? includeReplies, bool? mediaOnly, String? queryTerm, String? order, int? periodStart, int? periodEnd, bool orderDesc)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _PostListQuery() when $default != null:
|
case _PostListQuery() when $default != null:
|
||||||
return $default(_that.pubName,_that.realm,_that.type,_that.categories,_that.tags,_that.pinned,_that.shuffle,_that.includeReplies,_that.mediaOnly,_that.queryTerm,_that.order,_that.periodStart,_that.periodEnd,_that.orderDesc);case _:
|
return $default(_that.pubName,_that.publishers,_that.realm,_that.type,_that.categories,_that.tags,_that.pinned,_that.shuffle,_that.includeReplies,_that.mediaOnly,_that.queryTerm,_that.order,_that.periodStart,_that.periodEnd,_that.orderDesc);case _:
|
||||||
return orElse();
|
return orElse();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -181,10 +182,10 @@ return $default(_that.pubName,_that.realm,_that.type,_that.categories,_that.tags
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String? pubName, String? realm, int? type, List<String>? categories, List<String>? tags, bool? pinned, bool shuffle, bool? includeReplies, bool? mediaOnly, String? queryTerm, String? order, int? periodStart, int? periodEnd, bool orderDesc) $default,) {final _that = this;
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String? pubName, List<String>? publishers, String? realm, int? type, List<String>? categories, List<String>? tags, bool? pinned, bool shuffle, bool? includeReplies, bool? mediaOnly, String? queryTerm, String? order, int? periodStart, int? periodEnd, bool orderDesc) $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _PostListQuery():
|
case _PostListQuery():
|
||||||
return $default(_that.pubName,_that.realm,_that.type,_that.categories,_that.tags,_that.pinned,_that.shuffle,_that.includeReplies,_that.mediaOnly,_that.queryTerm,_that.order,_that.periodStart,_that.periodEnd,_that.orderDesc);}
|
return $default(_that.pubName,_that.publishers,_that.realm,_that.type,_that.categories,_that.tags,_that.pinned,_that.shuffle,_that.includeReplies,_that.mediaOnly,_that.queryTerm,_that.order,_that.periodStart,_that.periodEnd,_that.orderDesc);}
|
||||||
}
|
}
|
||||||
/// A variant of `when` that fallback to returning `null`
|
/// A variant of `when` that fallback to returning `null`
|
||||||
///
|
///
|
||||||
@@ -198,10 +199,10 @@ return $default(_that.pubName,_that.realm,_that.type,_that.categories,_that.tags
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String? pubName, String? realm, int? type, List<String>? categories, List<String>? tags, bool? pinned, bool shuffle, bool? includeReplies, bool? mediaOnly, String? queryTerm, String? order, int? periodStart, int? periodEnd, bool orderDesc)? $default,) {final _that = this;
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String? pubName, List<String>? publishers, String? realm, int? type, List<String>? categories, List<String>? tags, bool? pinned, bool shuffle, bool? includeReplies, bool? mediaOnly, String? queryTerm, String? order, int? periodStart, int? periodEnd, bool orderDesc)? $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _PostListQuery() when $default != null:
|
case _PostListQuery() when $default != null:
|
||||||
return $default(_that.pubName,_that.realm,_that.type,_that.categories,_that.tags,_that.pinned,_that.shuffle,_that.includeReplies,_that.mediaOnly,_that.queryTerm,_that.order,_that.periodStart,_that.periodEnd,_that.orderDesc);case _:
|
return $default(_that.pubName,_that.publishers,_that.realm,_that.type,_that.categories,_that.tags,_that.pinned,_that.shuffle,_that.includeReplies,_that.mediaOnly,_that.queryTerm,_that.order,_that.periodStart,_that.periodEnd,_that.orderDesc);case _:
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -213,10 +214,19 @@ return $default(_that.pubName,_that.realm,_that.type,_that.categories,_that.tags
|
|||||||
|
|
||||||
|
|
||||||
class _PostListQuery implements PostListQuery {
|
class _PostListQuery implements PostListQuery {
|
||||||
const _PostListQuery({this.pubName, this.realm, this.type, final List<String>? categories, final List<String>? tags, this.pinned, this.shuffle = false, this.includeReplies, this.mediaOnly, this.queryTerm, this.order, this.periodStart, this.periodEnd, this.orderDesc = true}): _categories = categories,_tags = tags;
|
const _PostListQuery({this.pubName, final List<String>? publishers, this.realm, this.type, final List<String>? categories, final List<String>? tags, this.pinned, this.shuffle = false, this.includeReplies, this.mediaOnly, this.queryTerm, this.order, this.periodStart, this.periodEnd, this.orderDesc = true}): _publishers = publishers,_categories = categories,_tags = tags;
|
||||||
|
|
||||||
|
|
||||||
@override final String? pubName;
|
@override final String? pubName;
|
||||||
|
final List<String>? _publishers;
|
||||||
|
@override List<String>? get publishers {
|
||||||
|
final value = _publishers;
|
||||||
|
if (value == null) return null;
|
||||||
|
if (_publishers is EqualUnmodifiableListView) return _publishers;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(value);
|
||||||
|
}
|
||||||
|
|
||||||
@override final String? realm;
|
@override final String? realm;
|
||||||
@override final int? type;
|
@override final int? type;
|
||||||
final List<String>? _categories;
|
final List<String>? _categories;
|
||||||
@@ -257,16 +267,16 @@ _$PostListQueryCopyWith<_PostListQuery> get copyWith => __$PostListQueryCopyWith
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _PostListQuery&&(identical(other.pubName, pubName) || other.pubName == pubName)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._categories, _categories)&&const DeepCollectionEquality().equals(other._tags, _tags)&&(identical(other.pinned, pinned) || other.pinned == pinned)&&(identical(other.shuffle, shuffle) || other.shuffle == shuffle)&&(identical(other.includeReplies, includeReplies) || other.includeReplies == includeReplies)&&(identical(other.mediaOnly, mediaOnly) || other.mediaOnly == mediaOnly)&&(identical(other.queryTerm, queryTerm) || other.queryTerm == queryTerm)&&(identical(other.order, order) || other.order == order)&&(identical(other.periodStart, periodStart) || other.periodStart == periodStart)&&(identical(other.periodEnd, periodEnd) || other.periodEnd == periodEnd)&&(identical(other.orderDesc, orderDesc) || other.orderDesc == orderDesc));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _PostListQuery&&(identical(other.pubName, pubName) || other.pubName == pubName)&&const DeepCollectionEquality().equals(other._publishers, _publishers)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._categories, _categories)&&const DeepCollectionEquality().equals(other._tags, _tags)&&(identical(other.pinned, pinned) || other.pinned == pinned)&&(identical(other.shuffle, shuffle) || other.shuffle == shuffle)&&(identical(other.includeReplies, includeReplies) || other.includeReplies == includeReplies)&&(identical(other.mediaOnly, mediaOnly) || other.mediaOnly == mediaOnly)&&(identical(other.queryTerm, queryTerm) || other.queryTerm == queryTerm)&&(identical(other.order, order) || other.order == order)&&(identical(other.periodStart, periodStart) || other.periodStart == periodStart)&&(identical(other.periodEnd, periodEnd) || other.periodEnd == periodEnd)&&(identical(other.orderDesc, orderDesc) || other.orderDesc == orderDesc));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,pubName,realm,type,const DeepCollectionEquality().hash(_categories),const DeepCollectionEquality().hash(_tags),pinned,shuffle,includeReplies,mediaOnly,queryTerm,order,periodStart,periodEnd,orderDesc);
|
int get hashCode => Object.hash(runtimeType,pubName,const DeepCollectionEquality().hash(_publishers),realm,type,const DeepCollectionEquality().hash(_categories),const DeepCollectionEquality().hash(_tags),pinned,shuffle,includeReplies,mediaOnly,queryTerm,order,periodStart,periodEnd,orderDesc);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'PostListQuery(pubName: $pubName, realm: $realm, type: $type, categories: $categories, tags: $tags, pinned: $pinned, shuffle: $shuffle, includeReplies: $includeReplies, mediaOnly: $mediaOnly, queryTerm: $queryTerm, order: $order, periodStart: $periodStart, periodEnd: $periodEnd, orderDesc: $orderDesc)';
|
return 'PostListQuery(pubName: $pubName, publishers: $publishers, realm: $realm, type: $type, categories: $categories, tags: $tags, pinned: $pinned, shuffle: $shuffle, includeReplies: $includeReplies, mediaOnly: $mediaOnly, queryTerm: $queryTerm, order: $order, periodStart: $periodStart, periodEnd: $periodEnd, orderDesc: $orderDesc)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -277,7 +287,7 @@ abstract mixin class _$PostListQueryCopyWith<$Res> implements $PostListQueryCopy
|
|||||||
factory _$PostListQueryCopyWith(_PostListQuery value, $Res Function(_PostListQuery) _then) = __$PostListQueryCopyWithImpl;
|
factory _$PostListQueryCopyWith(_PostListQuery value, $Res Function(_PostListQuery) _then) = __$PostListQueryCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String? pubName, String? realm, int? type, List<String>? categories, List<String>? tags, bool? pinned, bool shuffle, bool? includeReplies, bool? mediaOnly, String? queryTerm, String? order, int? periodStart, int? periodEnd, bool orderDesc
|
String? pubName, List<String>? publishers, String? realm, int? type, List<String>? categories, List<String>? tags, bool? pinned, bool shuffle, bool? includeReplies, bool? mediaOnly, String? queryTerm, String? order, int? periodStart, int? periodEnd, bool orderDesc
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -294,10 +304,11 @@ class __$PostListQueryCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of PostListQuery
|
/// Create a copy of PostListQuery
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? pubName = freezed,Object? realm = freezed,Object? type = freezed,Object? categories = freezed,Object? tags = freezed,Object? pinned = freezed,Object? shuffle = null,Object? includeReplies = freezed,Object? mediaOnly = freezed,Object? queryTerm = freezed,Object? order = freezed,Object? periodStart = freezed,Object? periodEnd = freezed,Object? orderDesc = null,}) {
|
@override @pragma('vm:prefer-inline') $Res call({Object? pubName = freezed,Object? publishers = freezed,Object? realm = freezed,Object? type = freezed,Object? categories = freezed,Object? tags = freezed,Object? pinned = freezed,Object? shuffle = null,Object? includeReplies = freezed,Object? mediaOnly = freezed,Object? queryTerm = freezed,Object? order = freezed,Object? periodStart = freezed,Object? periodEnd = freezed,Object? orderDesc = null,}) {
|
||||||
return _then(_PostListQuery(
|
return _then(_PostListQuery(
|
||||||
pubName: freezed == pubName ? _self.pubName : pubName // ignore: cast_nullable_to_non_nullable
|
pubName: freezed == pubName ? _self.pubName : pubName // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,realm: freezed == realm ? _self.realm : realm // ignore: cast_nullable_to_non_nullable
|
as String?,publishers: freezed == publishers ? _self._publishers : publishers // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<String>?,realm: freezed == realm ? _self.realm : realm // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,type: freezed == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
as String?,type: freezed == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||||
as int?,categories: freezed == categories ? _self._categories : categories // ignore: cast_nullable_to_non_nullable
|
as int?,categories: freezed == categories ? _self._categories : categories // ignore: cast_nullable_to_non_nullable
|
||||||
as List<String>?,tags: freezed == tags ? _self._tags : tags // ignore: cast_nullable_to_non_nullable
|
as List<String>?,tags: freezed == tags ? _self._tags : tags // ignore: cast_nullable_to_non_nullable
|
||||||
|
|||||||
@@ -57,9 +57,6 @@ class SitePagesNotifier extends AsyncNotifier<List<SnPublicationPage>> {
|
|||||||
);
|
);
|
||||||
final newPage = SnPublicationPage.fromJson(resp.data);
|
final newPage = SnPublicationPage.fromJson(resp.data);
|
||||||
|
|
||||||
// Refresh the pages list
|
|
||||||
ref.invalidate(sitePagesProvider(arg.pubName, arg.siteSlug));
|
|
||||||
|
|
||||||
return newPage;
|
return newPage;
|
||||||
} catch (error, stackTrace) {
|
} catch (error, stackTrace) {
|
||||||
state = AsyncValue.error(error, stackTrace);
|
state = AsyncValue.error(error, stackTrace);
|
||||||
@@ -80,9 +77,6 @@ class SitePagesNotifier extends AsyncNotifier<List<SnPublicationPage>> {
|
|||||||
);
|
);
|
||||||
final updatedPage = SnPublicationPage.fromJson(resp.data);
|
final updatedPage = SnPublicationPage.fromJson(resp.data);
|
||||||
|
|
||||||
// Refresh the pages list
|
|
||||||
ref.invalidate(sitePagesProvider(arg.pubName, arg.siteSlug));
|
|
||||||
|
|
||||||
return updatedPage;
|
return updatedPage;
|
||||||
} catch (error, stackTrace) {
|
} catch (error, stackTrace) {
|
||||||
state = AsyncValue.error(error, stackTrace);
|
state = AsyncValue.error(error, stackTrace);
|
||||||
@@ -95,9 +89,6 @@ class SitePagesNotifier extends AsyncNotifier<List<SnPublicationPage>> {
|
|||||||
try {
|
try {
|
||||||
final apiClient = ref.read(apiClientProvider);
|
final apiClient = ref.read(apiClientProvider);
|
||||||
await apiClient.delete('/zone/sites/pages/$pageId');
|
await apiClient.delete('/zone/sites/pages/$pageId');
|
||||||
|
|
||||||
// Refresh the pages list
|
|
||||||
ref.invalidate(sitePagesProvider(arg.pubName, arg.siteSlug));
|
|
||||||
} catch (error, stackTrace) {
|
} catch (error, stackTrace) {
|
||||||
state = AsyncValue.error(error, stackTrace);
|
state = AsyncValue.error(error, stackTrace);
|
||||||
rethrow;
|
rethrow;
|
||||||
@@ -105,8 +96,9 @@ class SitePagesNotifier extends AsyncNotifier<List<SnPublicationPage>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final sitePagesNotifierProvider = AsyncNotifierProvider.autoDispose.family<
|
final sitePagesNotifierProvider = AsyncNotifierProvider.autoDispose
|
||||||
SitePagesNotifier,
|
.family<
|
||||||
List<SnPublicationPage>,
|
SitePagesNotifier,
|
||||||
({String pubName, String siteSlug})
|
List<SnPublicationPage>,
|
||||||
>(SitePagesNotifier.new);
|
({String pubName, String siteSlug})
|
||||||
|
>(SitePagesNotifier.new);
|
||||||
|
|||||||
@@ -1,84 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:island/models/publication_site.dart';
|
|
||||||
import 'package:island/pods/network.dart';
|
|
||||||
|
|
||||||
class SiteNotifier extends AsyncNotifier<SnPublicationSite> {
|
|
||||||
final ({String pubName, String? siteId}) arg;
|
|
||||||
SiteNotifier(this.arg);
|
|
||||||
|
|
||||||
@override
|
|
||||||
FutureOr<SnPublicationSite> build() async {
|
|
||||||
if (arg.siteId == null || arg.siteId!.isEmpty) {
|
|
||||||
return SnPublicationSite(
|
|
||||||
id: '',
|
|
||||||
slug: '',
|
|
||||||
name: '',
|
|
||||||
publisherId: arg.pubName,
|
|
||||||
accountId: '',
|
|
||||||
createdAt: DateTime.now(),
|
|
||||||
updatedAt: DateTime.now(),
|
|
||||||
pages: [],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
final client = ref.read(apiClientProvider);
|
|
||||||
final response = await client.get('/sphere/sites/${arg.siteId}');
|
|
||||||
return SnPublicationSite.fromJson(response.data);
|
|
||||||
} catch (e) {
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> saveSite(SnPublicationSite site) async {
|
|
||||||
state = const AsyncValue.loading();
|
|
||||||
try {
|
|
||||||
final client = ref.read(apiClientProvider);
|
|
||||||
final url = '/sphere/sites';
|
|
||||||
|
|
||||||
final response =
|
|
||||||
site.id.isEmpty
|
|
||||||
? await client.post(url, data: site.toJson())
|
|
||||||
: await client.patch('$url/${site.id}', data: site.toJson());
|
|
||||||
|
|
||||||
state = AsyncValue.data(SnPublicationSite.fromJson(response.data));
|
|
||||||
} catch (error, stackTrace) {
|
|
||||||
state = AsyncValue.error(error, stackTrace);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> deleteSite() async {
|
|
||||||
final siteId = arg.siteId;
|
|
||||||
if (siteId == null || siteId.isEmpty) return;
|
|
||||||
|
|
||||||
state = const AsyncValue.loading();
|
|
||||||
try {
|
|
||||||
final client = ref.read(apiClientProvider);
|
|
||||||
await client.delete('/sphere/sites/$siteId');
|
|
||||||
state = AsyncValue.data(
|
|
||||||
SnPublicationSite(
|
|
||||||
id: '',
|
|
||||||
slug: '',
|
|
||||||
name: '',
|
|
||||||
publisherId: arg.pubName,
|
|
||||||
accountId: '',
|
|
||||||
createdAt: DateTime.now(),
|
|
||||||
updatedAt: DateTime.now(),
|
|
||||||
pages: [],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} catch (error, stackTrace) {
|
|
||||||
state = AsyncValue.error(error, stackTrace);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final siteNotifierProvider = AsyncNotifierProvider.autoDispose.family<
|
|
||||||
SiteNotifier,
|
|
||||||
SnPublicationSite,
|
|
||||||
({String pubName, String? siteId})
|
|
||||||
>(SiteNotifier.new);
|
|
||||||
@@ -25,10 +25,9 @@ ThemeSet createAppThemeSet(AppSettings settings) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ThemeData createAppTheme(Brightness brightness, AppSettings settings) {
|
ThemeData createAppTheme(Brightness brightness, AppSettings settings) {
|
||||||
final seedColor =
|
final seedColor = settings.appColorScheme != null
|
||||||
settings.appColorScheme != null
|
? Color(settings.appColorScheme!)
|
||||||
? Color(settings.appColorScheme!)
|
: Colors.indigo;
|
||||||
: Colors.indigo;
|
|
||||||
|
|
||||||
var colorScheme = ColorScheme.fromSeed(
|
var colorScheme = ColorScheme.fromSeed(
|
||||||
seedColor: seedColor,
|
seedColor: seedColor,
|
||||||
@@ -38,33 +37,33 @@ ThemeData createAppTheme(Brightness brightness, AppSettings settings) {
|
|||||||
final customColors = settings.customColors;
|
final customColors = settings.customColors;
|
||||||
if (customColors != null) {
|
if (customColors != null) {
|
||||||
colorScheme = colorScheme.copyWith(
|
colorScheme = colorScheme.copyWith(
|
||||||
primary:
|
primary: customColors.primary != null
|
||||||
customColors.primary != null ? Color(customColors.primary!) : null,
|
? Color(customColors.primary!)
|
||||||
secondary:
|
: null,
|
||||||
customColors.secondary != null
|
secondary: customColors.secondary != null
|
||||||
? Color(customColors.secondary!)
|
? Color(customColors.secondary!)
|
||||||
: null,
|
: null,
|
||||||
tertiary:
|
tertiary: customColors.tertiary != null
|
||||||
customColors.tertiary != null ? Color(customColors.tertiary!) : null,
|
? Color(customColors.tertiary!)
|
||||||
surface:
|
: null,
|
||||||
customColors.surface != null ? Color(customColors.surface!) : null,
|
surface: customColors.surface != null
|
||||||
background:
|
? Color(customColors.surface!)
|
||||||
customColors.background != null
|
: null,
|
||||||
? Color(customColors.background!)
|
background: customColors.background != null
|
||||||
: null,
|
? Color(customColors.background!)
|
||||||
|
: null,
|
||||||
error: customColors.error != null ? Color(customColors.error!) : null,
|
error: customColors.error != null ? Color(customColors.error!) : null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final hasAppBarTransparent = settings.appBarTransparent;
|
final hasAppBarTransparent = settings.appBarTransparent;
|
||||||
final useM3 = settings.useMaterial3;
|
|
||||||
|
|
||||||
final inUseFonts =
|
final inUseFonts =
|
||||||
settings.customFonts?.split(',').map((ele) => ele.trim()).toList() ??
|
settings.customFonts?.split(',').map((ele) => ele.trim()).toList() ??
|
||||||
['Nunito'];
|
['Nunito'];
|
||||||
|
|
||||||
return ThemeData(
|
return ThemeData(
|
||||||
useMaterial3: useM3,
|
useMaterial3: true,
|
||||||
colorScheme: colorScheme,
|
colorScheme: colorScheme,
|
||||||
brightness: brightness,
|
brightness: brightness,
|
||||||
fontFamily: inUseFonts.firstOrNull,
|
fontFamily: inUseFonts.firstOrNull,
|
||||||
@@ -78,10 +77,12 @@ ThemeData createAppTheme(Brightness brightness, AppSettings settings) {
|
|||||||
appBarTheme: AppBarTheme(
|
appBarTheme: AppBarTheme(
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
elevation: hasAppBarTransparent ? 0 : null,
|
elevation: hasAppBarTransparent ? 0 : null,
|
||||||
backgroundColor:
|
backgroundColor: hasAppBarTransparent
|
||||||
hasAppBarTransparent ? Colors.transparent : colorScheme.primary,
|
? Colors.transparent
|
||||||
foregroundColor:
|
: colorScheme.primary,
|
||||||
hasAppBarTransparent ? colorScheme.onSurface : colorScheme.onPrimary,
|
foregroundColor: hasAppBarTransparent
|
||||||
|
? colorScheme.onSurface
|
||||||
|
: colorScheme.onPrimary,
|
||||||
),
|
),
|
||||||
cardTheme: CardThemeData(
|
cardTheme: CardThemeData(
|
||||||
color: colorScheme.surfaceContainer.withOpacity(
|
color: colorScheme.surfaceContainer.withOpacity(
|
||||||
|
|||||||
@@ -36,71 +36,41 @@ class UserInfoNotifier extends AsyncNotifier<SnAccount?> {
|
|||||||
}
|
}
|
||||||
return user;
|
return user;
|
||||||
} catch (error, stackTrace) {
|
} catch (error, stackTrace) {
|
||||||
if (!kIsWeb) {
|
if (error is DioException) {
|
||||||
if (error is DioException) {
|
if (error.response?.statusCode == 503) return null;
|
||||||
showOverlayDialog<bool>(
|
showOverlayDialog<bool>(
|
||||||
builder:
|
builder: (context, close) => AlertDialog(
|
||||||
(context, close) => AlertDialog(
|
title: Text('failedToLoadUserInfo'.tr()),
|
||||||
title: Text('failedToLoadUserInfo'.tr()),
|
content: Text(
|
||||||
content: Text(
|
[
|
||||||
[
|
(error.response?.statusCode == 401
|
||||||
(error.response?.statusCode == 401
|
? 'failedToLoadUserInfoUnauthorized'
|
||||||
? 'failedToLoadUserInfoUnauthorized'
|
: 'failedToLoadUserInfoNetwork')
|
||||||
: 'failedToLoadUserInfoNetwork')
|
.tr()
|
||||||
.tr()
|
.trim(),
|
||||||
.trim(),
|
'',
|
||||||
'',
|
'${error.response?.statusCode ?? 'Network Error'}',
|
||||||
'${error.response?.statusCode ?? 'Network Error'}',
|
if (error.response?.headers != null) error.response?.headers,
|
||||||
if (error.response?.headers != null)
|
if (error.response?.data != null)
|
||||||
error.response?.headers,
|
jsonEncode(error.response?.data),
|
||||||
if (error.response?.data != null)
|
].join('\n'),
|
||||||
jsonEncode(error.response?.data),
|
),
|
||||||
].join('\n'),
|
actions: [
|
||||||
),
|
TextButton(
|
||||||
actions: [
|
onPressed: () => close(false),
|
||||||
TextButton(
|
child: Text('okay'.tr()),
|
||||||
onPressed: () => close(false),
|
),
|
||||||
child: Text('okay'.tr()),
|
TextButton(
|
||||||
),
|
onPressed: () => close(true),
|
||||||
TextButton(
|
child: Text('retry'.tr()),
|
||||||
onPressed: () => close(true),
|
),
|
||||||
child: Text('retry'.tr()),
|
],
|
||||||
),
|
),
|
||||||
],
|
).then((value) {
|
||||||
),
|
if (value == true) {
|
||||||
).then((value) {
|
ref.invalidateSelf();
|
||||||
if (value == true) {
|
}
|
||||||
ref.invalidateSelf();
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
showOverlayDialog<bool>(
|
|
||||||
builder:
|
|
||||||
(context, close) => AlertDialog(
|
|
||||||
title: Text('failedToLoadUserInfo'.tr()),
|
|
||||||
content: Text(
|
|
||||||
[
|
|
||||||
'failedToLoadUserInfoNetwork'.tr(),
|
|
||||||
error.toString(),
|
|
||||||
].join('\n\n').trim(),
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => close(false),
|
|
||||||
child: Text('okay'.tr()),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => close(true),
|
|
||||||
child: Text('retry'.tr()),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
).then((value) {
|
|
||||||
if (value == true) {
|
|
||||||
ref.invalidateSelf();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
talker.error(
|
talker.error(
|
||||||
"[UserInfo] Failed to fetch user info...",
|
"[UserInfo] Failed to fetch user info...",
|
||||||
|
|||||||
@@ -52,6 +52,11 @@ class WebSocketService {
|
|||||||
DateTime? _heartbeatAt;
|
DateTime? _heartbeatAt;
|
||||||
Duration? heartbeatDelay;
|
Duration? heartbeatDelay;
|
||||||
|
|
||||||
|
// Reconnection tracking
|
||||||
|
int _reconnectCount = 0;
|
||||||
|
DateTime? _reconnectWindowStart;
|
||||||
|
static const int _maxReconnectsPerMinute = 5;
|
||||||
|
|
||||||
Stream<WebSocketPacket> get dataStream => _streamController.stream;
|
Stream<WebSocketPacket> get dataStream => _streamController.stream;
|
||||||
Stream<WebSocketState> get statusStream => _statusStreamController.stream;
|
Stream<WebSocketState> get statusStream => _statusStreamController.stream;
|
||||||
|
|
||||||
@@ -79,8 +84,9 @@ class WebSocketService {
|
|||||||
_scheduleHeartbeat();
|
_scheduleHeartbeat();
|
||||||
_channel!.stream.listen(
|
_channel!.stream.listen(
|
||||||
(data) {
|
(data) {
|
||||||
final dataStr =
|
final dataStr = data is Uint8List
|
||||||
data is Uint8List ? utf8.decode(data) : data.toString();
|
? utf8.decode(data)
|
||||||
|
: data.toString();
|
||||||
final packet = WebSocketPacket.fromJson(jsonDecode(dataStr));
|
final packet = WebSocketPacket.fromJson(jsonDecode(dataStr));
|
||||||
if (packet.type == 'error.dupe') {
|
if (packet.type == 'error.dupe') {
|
||||||
_statusStreamController.sink.add(WebSocketState.duplicateDevice());
|
_statusStreamController.sink.add(WebSocketState.duplicateDevice());
|
||||||
@@ -123,6 +129,35 @@ class WebSocketService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _scheduleReconnect() {
|
void _scheduleReconnect() {
|
||||||
|
// Check if we've exceeded the reconnect limit
|
||||||
|
final now = DateTime.now();
|
||||||
|
if (_reconnectWindowStart == null ||
|
||||||
|
now.difference(_reconnectWindowStart!).inMinutes >= 1) {
|
||||||
|
// Reset window if it's been more than 1 minute since the window started
|
||||||
|
_reconnectWindowStart = now;
|
||||||
|
_reconnectCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
_reconnectCount++;
|
||||||
|
|
||||||
|
if (_reconnectCount > _maxReconnectsPerMinute) {
|
||||||
|
talker.error(
|
||||||
|
'[WebSocket] Reconnect limit exceeded: $_maxReconnectsPerMinute reconnections in the last minute. Stopping auto-reconnect.',
|
||||||
|
);
|
||||||
|
_statusStreamController.sink.add(WebSocketState.serverDown());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_reconnectTimer?.cancel();
|
||||||
|
_reconnectTimer = Timer(const Duration(milliseconds: 500), () {
|
||||||
|
_statusStreamController.sink.add(WebSocketState.connecting());
|
||||||
|
connect(_ref);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void manualReconnect() {
|
||||||
|
_statusStreamController.sink.add(WebSocketState.connecting());
|
||||||
|
talker.info('[WebSocket] Manual reconnect triggered by user');
|
||||||
_reconnectTimer?.cancel();
|
_reconnectTimer?.cancel();
|
||||||
_reconnectTimer = Timer(const Duration(milliseconds: 500), () {
|
_reconnectTimer = Timer(const Duration(milliseconds: 500), () {
|
||||||
_statusStreamController.sink.add(WebSocketState.connecting());
|
_statusStreamController.sink.add(WebSocketState.connecting());
|
||||||
@@ -204,4 +239,9 @@ class WebSocketStateNotifier extends Notifier<WebSocketState> {
|
|||||||
_reconnectTimer?.cancel();
|
_reconnectTimer?.cancel();
|
||||||
state = const WebSocketState.disconnected();
|
state = const WebSocketState.disconnected();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void manualReconnect() {
|
||||||
|
final service = ref.read(websocketProvider);
|
||||||
|
service.manualReconnect();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart' show kIsWeb;
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/screens/about.dart';
|
import 'package:island/screens/about.dart';
|
||||||
|
import 'package:island/screens/dashboard/dash.dart';
|
||||||
import 'package:island/screens/developers/app_detail.dart';
|
import 'package:island/screens/developers/app_detail.dart';
|
||||||
import 'package:island/screens/developers/bot_detail.dart';
|
import 'package:island/screens/developers/bot_detail.dart';
|
||||||
import 'package:island/screens/developers/hub.dart';
|
import 'package:island/screens/developers/hub.dart';
|
||||||
@@ -18,6 +19,7 @@ import 'package:island/screens/files/file_detail.dart';
|
|||||||
import 'package:island/screens/posts/post_categories_list.dart';
|
import 'package:island/screens/posts/post_categories_list.dart';
|
||||||
import 'package:island/screens/posts/post_category_detail.dart';
|
import 'package:island/screens/posts/post_category_detail.dart';
|
||||||
import 'package:island/screens/posts/post_search.dart';
|
import 'package:island/screens/posts/post_search.dart';
|
||||||
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:island/widgets/app_wrapper.dart';
|
import 'package:island/widgets/app_wrapper.dart';
|
||||||
import 'package:island/screens/tabs.dart';
|
import 'package:island/screens/tabs.dart';
|
||||||
import 'package:island/screens/explore.dart';
|
import 'package:island/screens/explore.dart';
|
||||||
@@ -120,7 +122,12 @@ final routerProvider = Provider<GoRouter>((ref) {
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
name: 'logs',
|
name: 'logs',
|
||||||
path: '/logs',
|
path: '/logs',
|
||||||
builder: (context, state) => TalkerScreen(talker: talker),
|
builder: (context, state) => TalkerScreen(
|
||||||
|
talker: talker,
|
||||||
|
appBarTitle: 'Debug Logs',
|
||||||
|
appBarLeading: const PageBackButton(),
|
||||||
|
theme: TalkerScreenTheme.fromTheme(Theme.of(context)),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Web articles
|
// Web articles
|
||||||
@@ -185,10 +192,20 @@ final routerProvider = Provider<GoRouter>((ref) {
|
|||||||
return TabsScreen(child: child);
|
return TabsScreen(child: child);
|
||||||
},
|
},
|
||||||
routes: [
|
routes: [
|
||||||
|
// Dashboard tab
|
||||||
|
GoRoute(
|
||||||
|
name: 'dashboard',
|
||||||
|
path: '/',
|
||||||
|
pageBuilder: (context, state) => CustomTransitionPage(
|
||||||
|
key: const ValueKey('dashboard'),
|
||||||
|
child: const DashboardScreen(),
|
||||||
|
transitionsBuilder: _tabPagesTransitionBuilder,
|
||||||
|
),
|
||||||
|
),
|
||||||
// Explore tab
|
// Explore tab
|
||||||
GoRoute(
|
GoRoute(
|
||||||
name: 'explore',
|
name: 'explore',
|
||||||
path: '/',
|
path: '/explore',
|
||||||
pageBuilder: (context, state) => CustomTransitionPage(
|
pageBuilder: (context, state) => CustomTransitionPage(
|
||||||
key: const ValueKey('explore'),
|
key: const ValueKey('explore'),
|
||||||
child: const ExploreScreen(),
|
child: const ExploreScreen(),
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import 'package:island/widgets/app_scaffold.dart';
|
|||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:island/services/update_service.dart';
|
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
@@ -97,232 +96,213 @@ class _AboutScreenState extends ConsumerState<AboutScreen> {
|
|||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
isNoBackground: false,
|
isNoBackground: false,
|
||||||
appBar: AppBar(title: Text('about'.tr()), elevation: 0),
|
appBar: AppBar(title: Text('about'.tr()), elevation: 0),
|
||||||
body:
|
body: _isLoading
|
||||||
_isLoading
|
? const Center(child: CircularProgressIndicator())
|
||||||
? const Center(child: CircularProgressIndicator())
|
: _errorMessage != null
|
||||||
: _errorMessage != null
|
? Center(child: Text(_errorMessage!))
|
||||||
? Center(child: Text(_errorMessage!))
|
: Center(
|
||||||
: Center(
|
child: ConstrainedBox(
|
||||||
child: ConstrainedBox(
|
constraints: const BoxConstraints(maxWidth: 540),
|
||||||
constraints: const BoxConstraints(maxWidth: 540),
|
child: SingleChildScrollView(
|
||||||
child: SingleChildScrollView(
|
child: Column(
|
||||||
child: Column(
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
children: [
|
||||||
children: [
|
const SizedBox(height: 24),
|
||||||
const SizedBox(height: 24),
|
// App Icon and Name
|
||||||
// App Icon and Name
|
CircleAvatar(
|
||||||
CircleAvatar(
|
radius: 50,
|
||||||
radius: 50,
|
backgroundColor: theme.colorScheme.primary.withOpacity(
|
||||||
backgroundColor: theme.colorScheme.primary
|
0.1,
|
||||||
.withOpacity(0.1),
|
|
||||||
child: Image.asset(
|
|
||||||
'assets/icons/icon.png',
|
|
||||||
width: 56,
|
|
||||||
height: 56,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
child: Image.asset(
|
||||||
Text(
|
'assets/icons/icon.png',
|
||||||
_packageInfo.appName,
|
width: 56,
|
||||||
style: theme.textTheme.headlineSmall?.copyWith(
|
height: 56,
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Text(
|
),
|
||||||
'aboutScreenVersionInfo'.tr(
|
const SizedBox(height: 16),
|
||||||
args: [
|
Text(
|
||||||
_packageInfo.version,
|
_packageInfo.appName,
|
||||||
_packageInfo.buildNumber,
|
style: theme.textTheme.headlineSmall?.copyWith(
|
||||||
],
|
fontWeight: FontWeight.bold,
|
||||||
),
|
|
||||||
style: theme.textTheme.bodyMedium?.copyWith(
|
|
||||||
color: theme.textTheme.bodySmall?.color,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 32),
|
),
|
||||||
|
Text(
|
||||||
// App Info Card
|
'aboutScreenVersionInfo'.tr(
|
||||||
_buildSection(
|
args: [
|
||||||
context,
|
_packageInfo.version,
|
||||||
title: 'aboutScreenAppInfoSectionTitle'.tr(),
|
_packageInfo.buildNumber,
|
||||||
children: [
|
|
||||||
_buildInfoItem(
|
|
||||||
context,
|
|
||||||
icon: Symbols.info,
|
|
||||||
label: 'aboutScreenPackageNameLabel'.tr(),
|
|
||||||
value: _packageInfo.packageName,
|
|
||||||
),
|
|
||||||
_buildInfoItem(
|
|
||||||
context,
|
|
||||||
icon: Symbols.update,
|
|
||||||
label: 'aboutScreenVersionLabel'.tr(),
|
|
||||||
value: _packageInfo.version,
|
|
||||||
),
|
|
||||||
_buildInfoItem(
|
|
||||||
context,
|
|
||||||
icon: Symbols.build,
|
|
||||||
label: 'aboutScreenBuildNumberLabel'.tr(),
|
|
||||||
value: _packageInfo.buildNumber,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
|
color: theme.textTheme.bodySmall?.color,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 32),
|
||||||
|
|
||||||
if (_deviceInfo != null) const SizedBox(height: 16),
|
// App Info Card
|
||||||
|
_buildSection(
|
||||||
if (_deviceInfo != null)
|
context,
|
||||||
_buildSection(
|
title: 'aboutScreenAppInfoSectionTitle'.tr(),
|
||||||
|
children: [
|
||||||
|
_buildInfoItem(
|
||||||
context,
|
context,
|
||||||
title: 'Device Information',
|
icon: Symbols.info,
|
||||||
children: [
|
label: 'aboutScreenPackageNameLabel'.tr(),
|
||||||
FutureBuilder<String>(
|
value: _packageInfo.packageName,
|
||||||
future: udid.getDeviceName(),
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
final value =
|
|
||||||
snapshot.hasData
|
|
||||||
? snapshot.data!
|
|
||||||
: 'unknown'.tr();
|
|
||||||
return _buildInfoItem(
|
|
||||||
context,
|
|
||||||
icon: Symbols.label,
|
|
||||||
label: 'aboutDeviceName'.tr(),
|
|
||||||
value: value,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
_buildInfoItem(
|
|
||||||
context,
|
|
||||||
icon: Symbols.fingerprint,
|
|
||||||
label: 'aboutDeviceIdentifier'.tr(),
|
|
||||||
value: _deviceUdid ?? 'N/A',
|
|
||||||
copyable: true,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
|
_buildInfoItem(
|
||||||
|
context,
|
||||||
|
icon: Symbols.update,
|
||||||
|
label: 'aboutScreenVersionLabel'.tr(),
|
||||||
|
value: _packageInfo.version,
|
||||||
|
),
|
||||||
|
_buildInfoItem(
|
||||||
|
context,
|
||||||
|
icon: Symbols.build,
|
||||||
|
label: 'aboutScreenBuildNumberLabel'.tr(),
|
||||||
|
value: _packageInfo.buildNumber,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
if (_deviceInfo != null) const SizedBox(height: 16),
|
||||||
|
|
||||||
// Links Card
|
if (_deviceInfo != null)
|
||||||
_buildSection(
|
_buildSection(
|
||||||
context,
|
context,
|
||||||
title: 'aboutScreenLinksSectionTitle'.tr(),
|
title: 'Device Information',
|
||||||
children: [
|
children: [
|
||||||
_buildListTile(
|
FutureBuilder<String>(
|
||||||
context,
|
future: udid.getDeviceName(),
|
||||||
icon: Symbols.system_update,
|
builder: (context, snapshot) {
|
||||||
title: 'Check for updates',
|
final value = snapshot.hasData
|
||||||
onTap: () async {
|
? snapshot.data!
|
||||||
final svc = UpdateService();
|
: 'unknown'.tr();
|
||||||
showLoadingModal(context);
|
return _buildInfoItem(
|
||||||
svc.checkForUpdates(context);
|
context,
|
||||||
if (!context.mounted) return;
|
icon: Symbols.label,
|
||||||
hideLoadingModal(context);
|
label: 'aboutDeviceName'.tr(),
|
||||||
},
|
value: value,
|
||||||
),
|
|
||||||
_buildListTile(
|
|
||||||
context,
|
|
||||||
icon: Symbols.privacy_tip,
|
|
||||||
title: 'aboutScreenPrivacyPolicyTitle'.tr(),
|
|
||||||
onTap:
|
|
||||||
() => _launchURL(
|
|
||||||
'https://solsynth.dev/terms/privacy-policy',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
_buildListTile(
|
|
||||||
context,
|
|
||||||
icon: Symbols.description,
|
|
||||||
title: 'aboutScreenTermsOfServiceTitle'.tr(),
|
|
||||||
onTap:
|
|
||||||
() => _launchURL(
|
|
||||||
'https://solsynth.dev/terms/user-agreement',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
_buildListTile(
|
|
||||||
context,
|
|
||||||
icon: Symbols.code,
|
|
||||||
title: 'aboutScreenOpenSourceLicensesTitle'.tr(),
|
|
||||||
onTap: () {
|
|
||||||
showLicensePage(
|
|
||||||
context: context,
|
|
||||||
applicationName: _packageInfo.appName,
|
|
||||||
applicationVersion:
|
|
||||||
'Version ${_packageInfo.version}',
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
_buildInfoItem(
|
||||||
|
context,
|
||||||
|
icon: Symbols.fingerprint,
|
||||||
|
label: 'aboutDeviceIdentifier'.tr(),
|
||||||
|
value: _deviceUdid ?? 'N/A',
|
||||||
|
copyable: true,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// Developer Info
|
// Links Card
|
||||||
_buildSection(
|
_buildSection(
|
||||||
context,
|
context,
|
||||||
title: 'aboutScreenDeveloperSectionTitle'.tr(),
|
title: 'aboutScreenLinksSectionTitle'.tr(),
|
||||||
children: [
|
children: [
|
||||||
_buildListTile(
|
_buildListTile(
|
||||||
context,
|
context,
|
||||||
icon: Symbols.email,
|
icon: Symbols.privacy_tip,
|
||||||
title: 'aboutScreenContactUsTitle'.tr(),
|
title: 'aboutScreenPrivacyPolicyTitle'.tr(),
|
||||||
subtitle: 'lily@solsynth.dev',
|
onTap: () => _launchURL(
|
||||||
onTap:
|
'https://solsynth.dev/terms/privacy-policy',
|
||||||
() => _launchURL('mailto:lily@solsynth.dev'),
|
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
_buildListTile(
|
||||||
|
context,
|
||||||
|
icon: Symbols.description,
|
||||||
|
title: 'aboutScreenTermsOfServiceTitle'.tr(),
|
||||||
|
onTap: () => _launchURL(
|
||||||
|
'https://solsynth.dev/terms/user-agreement',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_buildListTile(
|
||||||
|
context,
|
||||||
|
icon: Symbols.code,
|
||||||
|
title: 'aboutScreenOpenSourceLicensesTitle'.tr(),
|
||||||
|
onTap: () {
|
||||||
|
showLicensePage(
|
||||||
|
context: context,
|
||||||
|
applicationName: _packageInfo.appName,
|
||||||
|
applicationVersion:
|
||||||
|
'Version ${_packageInfo.version}',
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Developer Info
|
||||||
|
_buildSection(
|
||||||
|
context,
|
||||||
|
title: 'aboutScreenDeveloperSectionTitle'.tr(),
|
||||||
|
children: [
|
||||||
|
_buildListTile(
|
||||||
|
context,
|
||||||
|
icon: Symbols.email,
|
||||||
|
title: 'aboutScreenContactUsTitle'.tr(),
|
||||||
|
subtitle: 'lily@solsynth.dev',
|
||||||
|
onTap: () => _launchURL('mailto:lily@solsynth.dev'),
|
||||||
|
),
|
||||||
|
_buildListTile(
|
||||||
|
context,
|
||||||
|
icon: Symbols.copyright,
|
||||||
|
title: 'aboutScreenLicenseTitle'.tr(),
|
||||||
|
subtitle: 'aboutScreenLicenseContent'.tr(),
|
||||||
|
onTap: () => _launchURL(
|
||||||
|
'https://github.com/Solsynth/Solian/blob/v3/LICENSE.txt',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (kIsWeb || !(Platform.isMacOS || Platform.isIOS))
|
||||||
_buildListTile(
|
_buildListTile(
|
||||||
context,
|
context,
|
||||||
icon: Symbols.copyright,
|
icon: Symbols.favorite,
|
||||||
title: 'aboutScreenLicenseTitle'.tr(),
|
title: 'donate'.tr(),
|
||||||
subtitle: 'aboutScreenLicenseContent'.tr(
|
subtitle: 'donateDescription'.tr(),
|
||||||
|
onTap: () {
|
||||||
|
launchUrlString(
|
||||||
|
'https://afdian.com/@littlesheep',
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 32),
|
||||||
|
|
||||||
|
// Copyright
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'aboutScreenCopyright'.tr(
|
||||||
args: [DateTime.now().year.toString()],
|
args: [DateTime.now().year.toString()],
|
||||||
),
|
),
|
||||||
onTap:
|
style: theme.textTheme.bodySmall,
|
||||||
() => _launchURL(
|
textAlign: TextAlign.center,
|
||||||
'https://github.com/Solsynth/Solian/blob/v3/LICENSE.txt',
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
if (kIsWeb || !(Platform.isMacOS || Platform.isIOS))
|
const Gap(1),
|
||||||
_buildListTile(
|
Text(
|
||||||
context,
|
'aboutScreenMadeWith'.tr(),
|
||||||
icon: Symbols.favorite,
|
textAlign: TextAlign.center,
|
||||||
title: 'donate'.tr(),
|
).fontSize(10).opacity(0.8),
|
||||||
subtitle: 'donateDescription'.tr(),
|
|
||||||
onTap: () {
|
|
||||||
launchUrlString(
|
|
||||||
'https://afdian.com/@littlesheep',
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
|
||||||
const SizedBox(height: 32),
|
Gap(MediaQuery.of(context).padding.bottom + 16),
|
||||||
|
],
|
||||||
// Copyright
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'aboutScreenCopyright'.tr(
|
|
||||||
args: [DateTime.now().year.toString()],
|
|
||||||
),
|
|
||||||
style: theme.textTheme.bodySmall,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
const Gap(1),
|
|
||||||
Text(
|
|
||||||
'aboutScreenMadeWith'.tr(),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
).fontSize(10).opacity(0.8),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
Gap(MediaQuery.of(context).padding.bottom + 16),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -331,128 +331,136 @@ class AccountScreen extends HookConsumerWidget {
|
|||||||
if (availableWidth > totalMin) {
|
if (availableWidth > totalMin) {
|
||||||
return Row(
|
return Row(
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
children:
|
children: children
|
||||||
children
|
.map((child) => Expanded(child: child))
|
||||||
.map((child) => Expanded(child: child))
|
.toList(),
|
||||||
.toList(),
|
|
||||||
).padding(horizontal: 12).height(48);
|
).padding(horizontal: 12).height(48);
|
||||||
} else {
|
} else {
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
child: Row(
|
child: Row(
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
children:
|
children: children
|
||||||
children
|
.map(
|
||||||
.map(
|
(child) =>
|
||||||
(child) =>
|
SizedBox(width: minWidth, child: child),
|
||||||
SizedBox(width: minWidth, child: child),
|
)
|
||||||
)
|
.toList(),
|
||||||
.toList(),
|
|
||||||
).padding(horizontal: 12),
|
).padding(horizontal: 12),
|
||||||
).height(48);
|
).height(48);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ListTile(
|
Builder(
|
||||||
minTileHeight: 48,
|
builder: (context) {
|
||||||
leading: const Icon(Symbols.notifications),
|
final menuItems = [
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
{
|
||||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
'icon': Symbols.notifications,
|
||||||
title: Row(
|
'title': 'notifications',
|
||||||
children: [
|
'badgeCount': notificationUnreadCount.value ?? 0,
|
||||||
Expanded(child: Text('notifications').tr()),
|
'onTap': () {
|
||||||
Badge.count(
|
showModalBottomSheet(
|
||||||
count: notificationUnreadCount.value ?? 0,
|
context: context,
|
||||||
isLabelVisible: (notificationUnreadCount.value ?? 0) > 0,
|
isScrollControlled: true,
|
||||||
),
|
useRootNavigator: true,
|
||||||
],
|
builder: (context) => const NotificationSheet(),
|
||||||
),
|
);
|
||||||
onTap: () {
|
},
|
||||||
showModalBottomSheet(
|
},
|
||||||
context: context,
|
if (!isWideScreen(context))
|
||||||
isScrollControlled: true,
|
{
|
||||||
useRootNavigator: true,
|
'icon': Symbols.files,
|
||||||
builder: (context) => const NotificationSheet(),
|
'title': 'files',
|
||||||
|
'onTap': () {
|
||||||
|
context.goNamed('files');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
if (!isWideScreen(context))
|
||||||
|
{
|
||||||
|
'icon': Symbols.groups_3,
|
||||||
|
'title': 'realms',
|
||||||
|
'onTap': () {
|
||||||
|
context.goNamed('realmList');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'icon': Symbols.wallet,
|
||||||
|
'title': 'wallet',
|
||||||
|
'onTap': () {
|
||||||
|
context.pushNamed('wallet');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'icon': Symbols.people,
|
||||||
|
'title': 'relationships',
|
||||||
|
'onTap': () {
|
||||||
|
context.pushNamed('relationships');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'icon': Symbols.sticker_rounded,
|
||||||
|
'title': 'stickers',
|
||||||
|
'onTap': () {
|
||||||
|
context.pushNamed('stickerMarketplace');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'icon': Symbols.rss_feed,
|
||||||
|
'title': 'webFeeds',
|
||||||
|
'onTap': () {
|
||||||
|
context.pushNamed('webFeedMarketplace');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'icon': Symbols.gavel,
|
||||||
|
'title': 'abuseReport',
|
||||||
|
'onTap': () {
|
||||||
|
context.pushNamed('reportList');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return Column(
|
||||||
|
children: menuItems.map((item) {
|
||||||
|
final icon = item['icon'] as IconData;
|
||||||
|
final title = item['title'] as String;
|
||||||
|
final badgeCount = item['badgeCount'] as int?;
|
||||||
|
final onTap = item['onTap'] as VoidCallback?;
|
||||||
|
return ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 24,
|
||||||
|
),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
dense: true,
|
||||||
|
leading: Badge(
|
||||||
|
isLabelVisible: badgeCount != null && badgeCount > 0,
|
||||||
|
label: Text(badgeCount.toString()),
|
||||||
|
child: Icon(icon, size: 24),
|
||||||
|
),
|
||||||
|
title: Text(title).tr(),
|
||||||
|
onTap: onTap,
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (!isWideScreen(context))
|
|
||||||
ListTile(
|
|
||||||
minTileHeight: 48,
|
|
||||||
leading: const Icon(Symbols.files),
|
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
|
||||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
|
||||||
title: Text('files').tr(),
|
|
||||||
onTap: () {
|
|
||||||
context.goNamed('files');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
minTileHeight: 48,
|
|
||||||
leading: const Icon(Symbols.wallet),
|
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
|
||||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
|
||||||
title: Text('wallet').tr(),
|
|
||||||
onTap: () {
|
|
||||||
context.pushNamed('wallet');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
minTileHeight: 48,
|
|
||||||
leading: const Icon(Symbols.people),
|
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
|
||||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
|
||||||
title: Text('relationships').tr(),
|
|
||||||
onTap: () {
|
|
||||||
context.pushNamed('relationships');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
minTileHeight: 48,
|
|
||||||
leading: const Icon(Symbols.emoji_emotions),
|
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
|
||||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
|
||||||
title: Text('stickers').tr(),
|
|
||||||
onTap: () {
|
|
||||||
context.pushNamed('stickerMarketplace');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
minTileHeight: 48,
|
|
||||||
leading: const Icon(Symbols.rss_feed),
|
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
|
||||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
|
||||||
title: Text('webFeeds').tr(),
|
|
||||||
onTap: () {
|
|
||||||
context.pushNamed('webFeedMarketplace');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
minTileHeight: 48,
|
|
||||||
title: Text('abuseReport').tr(),
|
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
|
||||||
leading: const Icon(Symbols.gavel),
|
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
|
||||||
onTap: () => context.pushNamed('reportList'),
|
|
||||||
),
|
|
||||||
const Divider(height: 1).padding(vertical: 8),
|
const Divider(height: 1).padding(vertical: 8),
|
||||||
ListTile(
|
ListTile(
|
||||||
minTileHeight: 48,
|
|
||||||
leading: const Icon(Symbols.info),
|
leading: const Icon(Symbols.info),
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
dense: true,
|
||||||
title: Text('about').tr(),
|
title: Text('about').tr(),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.pushNamed('about');
|
context.pushNamed('about');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
minTileHeight: 48,
|
|
||||||
leading: const Icon(Symbols.bug_report),
|
leading: const Icon(Symbols.bug_report),
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||||
title: Text('debugOptions').tr(),
|
title: Text('debugOptions').tr(),
|
||||||
|
dense: true,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
useRootNavigator: true,
|
useRootNavigator: true,
|
||||||
@@ -463,11 +471,11 @@ class AccountScreen extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
minTileHeight: 48,
|
|
||||||
leading: const Icon(Symbols.logout),
|
leading: const Icon(Symbols.logout),
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||||
title: Text('logout').tr(),
|
title: Text('logout').tr(),
|
||||||
|
dense: true,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final ws = ref.watch(websocketStateProvider.notifier);
|
final ws = ref.watch(websocketStateProvider.notifier);
|
||||||
final apiClient = ref.watch(apiClientProvider);
|
final apiClient = ref.watch(apiClientProvider);
|
||||||
@@ -495,97 +503,96 @@ class _UnauthorizedAccountScreen extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(title: const Text('account').tr()),
|
appBar: AppBar(title: const Text('account').tr()),
|
||||||
body:
|
body: ConstrainedBox(
|
||||||
ConstrainedBox(
|
constraints: const BoxConstraints(maxWidth: 360),
|
||||||
constraints: const BoxConstraints(maxWidth: 360),
|
child: Column(
|
||||||
child: Column(
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: Card(
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
onTap: () {
|
||||||
|
context.pushNamed('createAccount');
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Icon(Symbols.person_add, size: 48),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text('createAccount').tr().bold(),
|
||||||
|
Text('createAccountDescription').tr(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(8),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: Card(
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
onTap: () {
|
||||||
|
context.pushNamed('login');
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Icon(Symbols.login, size: 48),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text('login').tr().bold(),
|
||||||
|
Text('loginDescription').tr(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(8),
|
||||||
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: <Widget>[
|
children: [
|
||||||
Padding(
|
IconButton(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
onPressed: () {
|
||||||
child: Card(
|
context.pushNamed('about');
|
||||||
child: InkWell(
|
},
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
iconSize: 18,
|
||||||
onTap: () {
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
context.pushNamed('createAccount');
|
icon: const Icon(Icons.info, fill: 1),
|
||||||
},
|
tooltip: 'about'.tr(),
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Icon(Symbols.person_add, size: 48),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Text('createAccount').tr().bold(),
|
|
||||||
Text('createAccountDescription').tr(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const Gap(8),
|
IconButton(
|
||||||
Padding(
|
icon: const Icon(Icons.bug_report, fill: 1),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
onPressed: () {
|
||||||
child: Card(
|
showModalBottomSheet(
|
||||||
child: InkWell(
|
context: context,
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
builder: (context) => DebugSheet(),
|
||||||
onTap: () {
|
);
|
||||||
context.pushNamed('login');
|
},
|
||||||
},
|
iconSize: 18,
|
||||||
child: Padding(
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
padding: const EdgeInsets.all(16),
|
tooltip: 'debugOptions'.tr(),
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Icon(Symbols.login, size: 48),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Text('login').tr().bold(),
|
|
||||||
Text('loginDescription').tr(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const Gap(8),
|
IconButton(
|
||||||
Row(
|
onPressed: () {
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
context.pushNamed('settings');
|
||||||
children: [
|
},
|
||||||
IconButton(
|
icon: const Icon(Icons.settings, fill: 1),
|
||||||
onPressed: () {
|
iconSize: 18,
|
||||||
context.pushNamed('about');
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
},
|
tooltip: 'appSettings'.tr(),
|
||||||
iconSize: 18,
|
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
|
||||||
icon: const Icon(Icons.info, fill: 1),
|
|
||||||
tooltip: 'about'.tr(),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.bug_report, fill: 1),
|
|
||||||
onPressed: () {
|
|
||||||
showModalBottomSheet(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => DebugSheet(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
iconSize: 18,
|
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
|
||||||
tooltip: 'debugOptions'.tr(),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
context.pushNamed('settings');
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.settings, fill: 1),
|
|
||||||
iconSize: 18,
|
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
|
||||||
tooltip: 'appSettings'.tr(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
).center(),
|
],
|
||||||
|
),
|
||||||
|
).center(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,81 +135,73 @@ class AccountSettingsScreen extends HookConsumerWidget {
|
|||||||
ref
|
ref
|
||||||
.watch(accountConnectionsProvider)
|
.watch(accountConnectionsProvider)
|
||||||
.when(
|
.when(
|
||||||
data:
|
data: (connections) => Column(
|
||||||
(connections) => Column(
|
children: [
|
||||||
children: [
|
for (final connection in connections)
|
||||||
for (final connection in connections)
|
ListTile(
|
||||||
ListTile(
|
minLeadingWidth: 48,
|
||||||
minLeadingWidth: 48,
|
contentPadding: const EdgeInsets.only(
|
||||||
contentPadding: const EdgeInsets.only(
|
left: 16,
|
||||||
left: 16,
|
right: 17,
|
||||||
right: 17,
|
top: 2,
|
||||||
top: 2,
|
bottom: 4,
|
||||||
bottom: 4,
|
|
||||||
),
|
|
||||||
title:
|
|
||||||
Text(
|
|
||||||
getLocalizedProviderName(connection.provider),
|
|
||||||
).tr(),
|
|
||||||
subtitle:
|
|
||||||
connection.meta['email'] != null
|
|
||||||
? Text(connection.meta['email'])
|
|
||||||
: Text(connection.providedIdentifier),
|
|
||||||
leading: CircleAvatar(
|
|
||||||
child: getProviderIcon(
|
|
||||||
connection.provider,
|
|
||||||
size: 16,
|
|
||||||
color:
|
|
||||||
Theme.of(
|
|
||||||
context,
|
|
||||||
).colorScheme.onPrimaryContainer,
|
|
||||||
),
|
|
||||||
).padding(top: 4),
|
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
|
||||||
onTap: () {
|
|
||||||
showModalBottomSheet(
|
|
||||||
context: context,
|
|
||||||
builder:
|
|
||||||
(context) => AccountConnectionSheet(
|
|
||||||
connection: connection,
|
|
||||||
),
|
|
||||||
).then((value) {
|
|
||||||
if (value == true) {
|
|
||||||
ref.invalidate(accountConnectionsProvider);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
if (connections.isNotEmpty) const Divider(height: 1),
|
|
||||||
ListTile(
|
|
||||||
minLeadingWidth: 48,
|
|
||||||
contentPadding: const EdgeInsets.only(
|
|
||||||
left: 24,
|
|
||||||
right: 17,
|
|
||||||
),
|
|
||||||
title: Text('accountConnectionAdd').tr(),
|
|
||||||
leading: const Icon(Symbols.add),
|
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
|
||||||
onTap: () {
|
|
||||||
showModalBottomSheet(
|
|
||||||
context: context,
|
|
||||||
builder:
|
|
||||||
(context) =>
|
|
||||||
const AccountConnectionNewSheet(),
|
|
||||||
).then((value) {
|
|
||||||
if (value == true) {
|
|
||||||
ref.invalidate(accountConnectionsProvider);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
title: Text(
|
||||||
),
|
getLocalizedProviderName(connection.provider),
|
||||||
error:
|
).tr(),
|
||||||
(err, _) => ResponseErrorWidget(
|
subtitle: connection.meta['email'] != null
|
||||||
error: err,
|
? Text(connection.meta['email'])
|
||||||
onRetry: () => ref.invalidate(accountConnectionsProvider),
|
: Text(connection.providedIdentifier),
|
||||||
|
leading: CircleAvatar(
|
||||||
|
child: getProviderIcon(
|
||||||
|
connection.provider,
|
||||||
|
size: 16,
|
||||||
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.onPrimaryContainer,
|
||||||
|
),
|
||||||
|
).padding(top: 4),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
onTap: () {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (context) =>
|
||||||
|
AccountConnectionSheet(connection: connection),
|
||||||
|
).then((value) {
|
||||||
|
if (value == true) {
|
||||||
|
ref.invalidate(accountConnectionsProvider);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (connections.isNotEmpty) const Divider(height: 1),
|
||||||
|
ListTile(
|
||||||
|
minLeadingWidth: 48,
|
||||||
|
contentPadding: const EdgeInsets.only(
|
||||||
|
left: 24,
|
||||||
|
right: 17,
|
||||||
|
),
|
||||||
|
title: Text('accountConnectionAdd').tr(),
|
||||||
|
leading: const Icon(Symbols.add),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
onTap: () {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (context) =>
|
||||||
|
const AccountConnectionNewSheet(),
|
||||||
|
).then((value) {
|
||||||
|
if (value == true) {
|
||||||
|
ref.invalidate(accountConnectionsProvider);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
error: (err, _) => ResponseErrorWidget(
|
||||||
|
error: err,
|
||||||
|
onRetry: () => ref.invalidate(accountConnectionsProvider),
|
||||||
|
),
|
||||||
loading: () => const ResponseLoadingWidget(),
|
loading: () => const ResponseLoadingWidget(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -223,95 +215,76 @@ class AccountSettingsScreen extends HookConsumerWidget {
|
|||||||
tilePadding: const EdgeInsets.only(left: 24, right: 17),
|
tilePadding: const EdgeInsets.only(left: 24, right: 17),
|
||||||
children: [
|
children: [
|
||||||
authFactors.when(
|
authFactors.when(
|
||||||
data:
|
data: (factors) => Column(
|
||||||
(factors) => Column(
|
children: [
|
||||||
children: [
|
for (final factor in factors)
|
||||||
for (final factor in factors)
|
ListTile(
|
||||||
ListTile(
|
minLeadingWidth: 48,
|
||||||
minLeadingWidth: 48,
|
contentPadding: const EdgeInsets.only(
|
||||||
contentPadding: const EdgeInsets.only(
|
left: 16,
|
||||||
left: 16,
|
right: 17,
|
||||||
right: 17,
|
top: 2,
|
||||||
top: 2,
|
bottom: 4,
|
||||||
bottom: 4,
|
|
||||||
),
|
|
||||||
title:
|
|
||||||
Text(
|
|
||||||
kFactorTypes[factor.type]!.$1,
|
|
||||||
style:
|
|
||||||
factor.enabledAt == null
|
|
||||||
? TextStyle(
|
|
||||||
decoration: TextDecoration.lineThrough,
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
).tr(),
|
|
||||||
subtitle:
|
|
||||||
Text(
|
|
||||||
kFactorTypes[factor.type]!.$2,
|
|
||||||
style:
|
|
||||||
factor.enabledAt == null
|
|
||||||
? TextStyle(
|
|
||||||
decoration: TextDecoration.lineThrough,
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
).tr(),
|
|
||||||
leading: CircleAvatar(
|
|
||||||
backgroundColor:
|
|
||||||
factor.enabledAt == null
|
|
||||||
? Theme.of(
|
|
||||||
context,
|
|
||||||
).colorScheme.secondaryContainer
|
|
||||||
: Theme.of(
|
|
||||||
context,
|
|
||||||
).colorScheme.primaryContainer,
|
|
||||||
child: Icon(kFactorTypes[factor.type]!.$3),
|
|
||||||
).padding(top: 4),
|
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
|
||||||
isThreeLine: true,
|
|
||||||
onTap: () {
|
|
||||||
if (factor.type == 0) {
|
|
||||||
requestResetPassword();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
showModalBottomSheet(
|
|
||||||
context: context,
|
|
||||||
builder:
|
|
||||||
(context) => AuthFactorSheet(factor: factor),
|
|
||||||
).then((value) {
|
|
||||||
if (value == true) {
|
|
||||||
ref.invalidate(authFactorsProvider);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
if (factors.isNotEmpty) Divider(height: 1),
|
|
||||||
ListTile(
|
|
||||||
minLeadingWidth: 48,
|
|
||||||
contentPadding: const EdgeInsets.only(
|
|
||||||
left: 24,
|
|
||||||
right: 17,
|
|
||||||
),
|
|
||||||
title: Text('authFactorNew').tr(),
|
|
||||||
leading: const Icon(Symbols.add),
|
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
|
||||||
onTap: () {
|
|
||||||
showModalBottomSheet(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => const AuthFactorNewSheet(),
|
|
||||||
).then((value) {
|
|
||||||
if (value == true) {
|
|
||||||
ref.invalidate(authFactorsProvider);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
title: Text(
|
||||||
),
|
kFactorTypes[factor.type]!.$1,
|
||||||
error:
|
style: factor.enabledAt == null
|
||||||
(err, _) => ResponseErrorWidget(
|
? TextStyle(decoration: TextDecoration.lineThrough)
|
||||||
error: err,
|
: null,
|
||||||
onRetry: () => ref.invalidate(authFactorsProvider),
|
).tr(),
|
||||||
|
subtitle: Text(
|
||||||
|
kFactorTypes[factor.type]!.$2,
|
||||||
|
style: factor.enabledAt == null
|
||||||
|
? TextStyle(decoration: TextDecoration.lineThrough)
|
||||||
|
: null,
|
||||||
|
).tr(),
|
||||||
|
leading: CircleAvatar(
|
||||||
|
backgroundColor: factor.enabledAt == null
|
||||||
|
? Theme.of(context).colorScheme.secondaryContainer
|
||||||
|
: Theme.of(context).colorScheme.primaryContainer,
|
||||||
|
child: Icon(kFactorTypes[factor.type]!.$3),
|
||||||
|
).padding(top: 4),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
isThreeLine: true,
|
||||||
|
onTap: () {
|
||||||
|
if (factor.type == 0) {
|
||||||
|
requestResetPassword();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AuthFactorSheet(factor: factor),
|
||||||
|
).then((value) {
|
||||||
|
if (value == true) {
|
||||||
|
ref.invalidate(authFactorsProvider);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (factors.isNotEmpty) Divider(height: 1),
|
||||||
|
ListTile(
|
||||||
|
minLeadingWidth: 48,
|
||||||
|
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
||||||
|
title: Text('authFactorNew').tr(),
|
||||||
|
leading: const Icon(Symbols.add),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
onTap: () {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => const AuthFactorNewSheet(),
|
||||||
|
).then((value) {
|
||||||
|
if (value == true) {
|
||||||
|
ref.invalidate(authFactorsProvider);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
error: (err, _) => ResponseErrorWidget(
|
||||||
|
error: err,
|
||||||
|
onRetry: () => ref.invalidate(authFactorsProvider),
|
||||||
|
),
|
||||||
loading: () => ResponseLoadingWidget(),
|
loading: () => ResponseLoadingWidget(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -327,97 +300,84 @@ class AccountSettingsScreen extends HookConsumerWidget {
|
|||||||
ref
|
ref
|
||||||
.watch(contactMethodsProvider)
|
.watch(contactMethodsProvider)
|
||||||
.when(
|
.when(
|
||||||
data:
|
data: (contacts) => Column(
|
||||||
(contacts) => Column(
|
children: [
|
||||||
children: [
|
for (final contact in contacts)
|
||||||
for (final contact in contacts)
|
ListTile(
|
||||||
ListTile(
|
minLeadingWidth: 48,
|
||||||
minLeadingWidth: 48,
|
contentPadding: const EdgeInsets.only(
|
||||||
contentPadding: const EdgeInsets.only(
|
left: 16,
|
||||||
left: 16,
|
right: 17,
|
||||||
right: 17,
|
top: 2,
|
||||||
top: 2,
|
bottom: 4,
|
||||||
bottom: 4,
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
contact.content,
|
|
||||||
style:
|
|
||||||
contact.verifiedAt == null
|
|
||||||
? TextStyle(
|
|
||||||
decoration: TextDecoration.lineThrough,
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
subtitle: Text(
|
|
||||||
contact.type == 0
|
|
||||||
? 'contactMethodTypeEmail'.tr()
|
|
||||||
: 'contactMethodTypePhone'.tr(),
|
|
||||||
style:
|
|
||||||
contact.verifiedAt == null
|
|
||||||
? TextStyle(
|
|
||||||
decoration: TextDecoration.lineThrough,
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
leading: CircleAvatar(
|
|
||||||
backgroundColor:
|
|
||||||
contact.verifiedAt == null
|
|
||||||
? Theme.of(
|
|
||||||
context,
|
|
||||||
).colorScheme.secondaryContainer
|
|
||||||
: Theme.of(
|
|
||||||
context,
|
|
||||||
).colorScheme.primaryContainer,
|
|
||||||
child: Icon(
|
|
||||||
contact.type == 0
|
|
||||||
? Symbols.mail
|
|
||||||
: Symbols.phone,
|
|
||||||
),
|
|
||||||
).padding(top: 4),
|
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
|
||||||
isThreeLine: false,
|
|
||||||
onTap: () {
|
|
||||||
showModalBottomSheet(
|
|
||||||
context: context,
|
|
||||||
builder:
|
|
||||||
(context) =>
|
|
||||||
ContactMethodSheet(contact: contact),
|
|
||||||
).then((value) {
|
|
||||||
if (value == true) {
|
|
||||||
ref.invalidate(contactMethodsProvider);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
if (contacts.isNotEmpty) const Divider(height: 1),
|
|
||||||
ListTile(
|
|
||||||
minLeadingWidth: 48,
|
|
||||||
contentPadding: const EdgeInsets.only(
|
|
||||||
left: 24,
|
|
||||||
right: 17,
|
|
||||||
),
|
|
||||||
title: Text('contactMethodNew').tr(),
|
|
||||||
leading: const Icon(Symbols.add),
|
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
|
||||||
onTap: () {
|
|
||||||
showModalBottomSheet(
|
|
||||||
context: context,
|
|
||||||
builder:
|
|
||||||
(context) => const ContactMethodNewSheet(),
|
|
||||||
).then((value) {
|
|
||||||
if (value == true) {
|
|
||||||
ref.invalidate(contactMethodsProvider);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
title: Text(
|
||||||
),
|
contact.content,
|
||||||
error:
|
style: contact.verifiedAt == null
|
||||||
(err, _) => ResponseErrorWidget(
|
? TextStyle(
|
||||||
error: err,
|
decoration: TextDecoration.lineThrough,
|
||||||
onRetry: () => ref.invalidate(contactMethodsProvider),
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
subtitle: Text(
|
||||||
|
contact.type == 0
|
||||||
|
? 'contactMethodTypeEmail'.tr()
|
||||||
|
: 'contactMethodTypePhone'.tr(),
|
||||||
|
style: contact.verifiedAt == null
|
||||||
|
? TextStyle(
|
||||||
|
decoration: TextDecoration.lineThrough,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
leading: CircleAvatar(
|
||||||
|
backgroundColor: contact.verifiedAt == null
|
||||||
|
? Theme.of(context).colorScheme.secondaryContainer
|
||||||
|
: Theme.of(context).colorScheme.primaryContainer,
|
||||||
|
child: Icon(
|
||||||
|
contact.type == 0 ? Symbols.mail : Symbols.phone,
|
||||||
|
),
|
||||||
|
).padding(top: 4),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
isThreeLine: false,
|
||||||
|
onTap: () {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (context) =>
|
||||||
|
ContactMethodSheet(contact: contact),
|
||||||
|
).then((value) {
|
||||||
|
if (value == true) {
|
||||||
|
ref.invalidate(contactMethodsProvider);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (contacts.isNotEmpty) const Divider(height: 1),
|
||||||
|
ListTile(
|
||||||
|
minLeadingWidth: 48,
|
||||||
|
contentPadding: const EdgeInsets.only(
|
||||||
|
left: 24,
|
||||||
|
right: 17,
|
||||||
|
),
|
||||||
|
title: Text('contactMethodNew').tr(),
|
||||||
|
leading: const Icon(Symbols.add),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
onTap: () {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => const ContactMethodNewSheet(),
|
||||||
|
).then((value) {
|
||||||
|
if (value == true) {
|
||||||
|
ref.invalidate(contactMethodsProvider);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
error: (err, _) => ResponseErrorWidget(
|
||||||
|
error: err,
|
||||||
|
onRetry: () => ref.invalidate(contactMethodsProvider),
|
||||||
|
),
|
||||||
loading: () => const ResponseLoadingWidget(),
|
loading: () => const ResponseLoadingWidget(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -439,6 +399,7 @@ class AccountSettingsScreen extends HookConsumerWidget {
|
|||||||
// Create a responsive layout based on screen width
|
// Create a responsive layout based on screen width
|
||||||
Widget buildSettingsList() {
|
Widget buildSettingsList() {
|
||||||
return Column(
|
return Column(
|
||||||
|
spacing: 16,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_SettingsSection(
|
_SettingsSection(
|
||||||
@@ -450,38 +411,36 @@ class AccountSettingsScreen extends HookConsumerWidget {
|
|||||||
children: dangerZoneSettings,
|
children: dangerZoneSettings,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
).padding(horizontal: 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text('accountSettings').tr(),
|
title: Text('accountSettings').tr(),
|
||||||
actions:
|
actions: isDesktop
|
||||||
isDesktop
|
? [
|
||||||
? [
|
IconButton(
|
||||||
IconButton(
|
icon: const Icon(Symbols.help_outline),
|
||||||
icon: const Icon(Symbols.help_outline),
|
onPressed: () {
|
||||||
onPressed: () {
|
// Show help dialog
|
||||||
// Show help dialog
|
showDialog(
|
||||||
showDialog(
|
context: context,
|
||||||
context: context,
|
builder: (context) => AlertDialog(
|
||||||
builder:
|
title: Text('accountSettingsHelp').tr(),
|
||||||
(context) => AlertDialog(
|
content: Text('accountSettingsHelpContent').tr(),
|
||||||
title: Text('accountSettingsHelp').tr(),
|
actions: [
|
||||||
content: Text('accountSettingsHelpContent').tr(),
|
TextButton(
|
||||||
actions: [
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
TextButton(
|
child: Text('Close').tr(),
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
),
|
||||||
child: Text('Close').tr(),
|
],
|
||||||
),
|
),
|
||||||
],
|
);
|
||||||
),
|
},
|
||||||
);
|
),
|
||||||
},
|
const Gap(8),
|
||||||
),
|
]
|
||||||
const Gap(8),
|
: null,
|
||||||
]
|
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
body: Focus(
|
body: Focus(
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
@@ -513,22 +472,25 @@ class _SettingsSection extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Column(
|
return Card(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
margin: EdgeInsets.zero,
|
||||||
children: [
|
child: Column(
|
||||||
Padding(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
padding: const EdgeInsets.fromLTRB(24, 16, 24, 8),
|
children: [
|
||||||
child: Text(
|
Padding(
|
||||||
title.tr(),
|
padding: const EdgeInsets.fromLTRB(24, 16, 24, 8),
|
||||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
child: Text(
|
||||||
color: Theme.of(context).colorScheme.primary,
|
title.tr(),
|
||||||
fontWeight: FontWeight.bold,
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
...children,
|
||||||
...children,
|
const SizedBox(height: 16),
|
||||||
const SizedBox(height: 16),
|
],
|
||||||
],
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,207 +14,16 @@ import 'package:island/services/event_bus.dart';
|
|||||||
import 'package:island/services/responsive.dart';
|
import 'package:island/services/responsive.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
|
import 'package:island/widgets/chat_room_widgets.dart';
|
||||||
import 'package:island/widgets/content/cloud_files.dart';
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
import 'package:island/widgets/content/sheet.dart';
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
import 'package:island/widgets/navigation/fab_menu.dart';
|
import 'package:island/widgets/navigation/fab_menu.dart';
|
||||||
import 'package:island/widgets/response.dart';
|
import 'package:island/widgets/response.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:relative_time/relative_time.dart';
|
|
||||||
import 'package:skeletonizer/skeletonizer.dart';
|
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:super_sliver_list/super_sliver_list.dart';
|
|
||||||
import 'package:island/pods/chat/chat_room.dart';
|
import 'package:island/pods/chat/chat_room.dart';
|
||||||
|
import 'package:island/pods/config.dart';
|
||||||
class ChatRoomListTile extends HookConsumerWidget {
|
import 'package:super_sliver_list/super_sliver_list.dart';
|
||||||
final SnChatRoom room;
|
|
||||||
final bool isDirect;
|
|
||||||
final Widget? subtitle;
|
|
||||||
final Widget? trailing;
|
|
||||||
final VoidCallback? onTap;
|
|
||||||
|
|
||||||
const ChatRoomListTile({
|
|
||||||
super.key,
|
|
||||||
required this.room,
|
|
||||||
this.isDirect = false,
|
|
||||||
this.subtitle,
|
|
||||||
this.trailing,
|
|
||||||
this.onTap,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
|
||||||
final summary = ref
|
|
||||||
.watch(chatSummaryProvider)
|
|
||||||
.whenData((summaries) => summaries[room.id]);
|
|
||||||
|
|
||||||
var validMembers = room.members ?? [];
|
|
||||||
if (validMembers.isNotEmpty) {
|
|
||||||
final userInfo = ref.watch(userInfoProvider);
|
|
||||||
if (userInfo.value != null) {
|
|
||||||
validMembers = validMembers
|
|
||||||
.where((e) => e.accountId != userInfo.value!.id)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget buildSubtitle() {
|
|
||||||
if (subtitle != null) return subtitle!;
|
|
||||||
|
|
||||||
return AnimatedSwitcher(
|
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
layoutBuilder: (currentChild, previousChildren) => Stack(
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
children: [
|
|
||||||
...previousChildren,
|
|
||||||
if (currentChild != null) currentChild,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: summary.when(
|
|
||||||
data: (data) => Container(
|
|
||||||
key: const ValueKey('data'),
|
|
||||||
child: data == null
|
|
||||||
? isDirect && room.description == null
|
|
||||||
? Text(
|
|
||||||
validMembers
|
|
||||||
.map((e) => '@${e.account.name}')
|
|
||||||
.join(', '),
|
|
||||||
maxLines: 1,
|
|
||||||
)
|
|
||||||
: Text(
|
|
||||||
room.description ?? 'descriptionNone'.tr(),
|
|
||||||
maxLines: 1,
|
|
||||||
)
|
|
||||||
: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
if (data.unreadCount > 0)
|
|
||||||
Text(
|
|
||||||
'unreadMessages'.plural(data.unreadCount),
|
|
||||||
style: Theme.of(context).textTheme.bodySmall
|
|
||||||
?.copyWith(
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (data.lastMessage == null)
|
|
||||||
Text(
|
|
||||||
room.description ?? 'descriptionNone'.tr(),
|
|
||||||
maxLines: 1,
|
|
||||||
)
|
|
||||||
else
|
|
||||||
Row(
|
|
||||||
spacing: 4,
|
|
||||||
children: [
|
|
||||||
Badge(
|
|
||||||
label: Text(
|
|
||||||
data.lastMessage!.sender.account.nick,
|
|
||||||
),
|
|
||||||
textColor: Theme.of(
|
|
||||||
context,
|
|
||||||
).colorScheme.onPrimary,
|
|
||||||
backgroundColor: Theme.of(
|
|
||||||
context,
|
|
||||||
).colorScheme.primary,
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
(data.lastMessage!.content?.isNotEmpty ?? false)
|
|
||||||
? data.lastMessage!.content!
|
|
||||||
: 'messageNone'.tr(),
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.centerRight,
|
|
||||||
child: Text(
|
|
||||||
RelativeTime(
|
|
||||||
context,
|
|
||||||
).format(data.lastMessage!.createdAt),
|
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
loading: () => Container(
|
|
||||||
key: const ValueKey('loading'),
|
|
||||||
child: Builder(
|
|
||||||
builder: (context) {
|
|
||||||
final seed = DateTime.now().microsecondsSinceEpoch;
|
|
||||||
final len = 4 + (seed % 17); // 4..20 inclusive
|
|
||||||
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
|
||||||
var s = seed;
|
|
||||||
final buffer = StringBuffer();
|
|
||||||
for (var i = 0; i < len; i++) {
|
|
||||||
s = (s * 1103515245 + 12345) & 0x7fffffff;
|
|
||||||
buffer.write(chars[s % chars.length]);
|
|
||||||
}
|
|
||||||
return Skeletonizer(
|
|
||||||
enabled: true,
|
|
||||||
child: Text(buffer.toString()),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
error: (_, _) => Container(
|
|
||||||
key: const ValueKey('error'),
|
|
||||||
child: isDirect && room.description == null
|
|
||||||
? Text(
|
|
||||||
validMembers.map((e) => '@${e.account.name}').join(', '),
|
|
||||||
maxLines: 1,
|
|
||||||
)
|
|
||||||
: Text(room.description ?? 'descriptionNone'.tr(), maxLines: 1),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
String titleText;
|
|
||||||
if (isDirect && room.name == null) {
|
|
||||||
if (room.members?.isNotEmpty ?? false) {
|
|
||||||
titleText = validMembers.map((e) => e.account.nick).join(', ');
|
|
||||||
} else {
|
|
||||||
titleText = 'Direct Message';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
titleText = room.name ?? '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return ListTile(
|
|
||||||
leading: Badge(
|
|
||||||
isLabelVisible: summary.when(
|
|
||||||
data: (data) => (data?.unreadCount ?? 0) > 0,
|
|
||||||
loading: () => false,
|
|
||||||
error: (_, _) => false,
|
|
||||||
),
|
|
||||||
child: (isDirect && room.picture?.id == null)
|
|
||||||
? SplitAvatarWidget(
|
|
||||||
filesId: validMembers
|
|
||||||
.map((e) => e.account.profile.picture?.id)
|
|
||||||
.toList(),
|
|
||||||
)
|
|
||||||
: room.picture?.id == null
|
|
||||||
? CircleAvatar(child: Text(room.name![0].toUpperCase()))
|
|
||||||
: ProfilePictureWidget(fileId: room.picture?.id),
|
|
||||||
),
|
|
||||||
title: Text(titleText),
|
|
||||||
subtitle: buildSubtitle(),
|
|
||||||
trailing: trailing, // Add this line
|
|
||||||
onTap: () async {
|
|
||||||
// Clear unread count if there are unread messages
|
|
||||||
ref.read(chatSummaryProvider.future).then((summary) {
|
|
||||||
if ((summary[room.id]?.unreadCount ?? 0) > 0) {
|
|
||||||
ref.read(chatSummaryProvider.notifier).clearUnreadCount(room.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
onTap?.call();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ChatListBodyWidget extends HookConsumerWidget {
|
class ChatListBodyWidget extends HookConsumerWidget {
|
||||||
final bool isFloating;
|
final bool isFloating;
|
||||||
@@ -231,6 +40,7 @@ class ChatListBodyWidget extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final chats = ref.watch(chatRoomJoinedProvider);
|
final chats = ref.watch(chatRoomJoinedProvider);
|
||||||
|
final settings = ref.watch(appSettingsProvider);
|
||||||
|
|
||||||
Widget bodyWidget = Column(
|
Widget bodyWidget = Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -248,50 +58,231 @@ class ChatListBodyWidget extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: chats.when(
|
child: chats.when(
|
||||||
data: (items) => RefreshIndicator(
|
data: (items) {
|
||||||
onRefresh: () => Future.sync(() {
|
final filteredItems = items.where(
|
||||||
ref.invalidate(chatRoomJoinedProvider);
|
(item) =>
|
||||||
}),
|
selectedTab.value == 0 ||
|
||||||
child: SuperListView.builder(
|
(selectedTab.value == 1 && item.type == 1) ||
|
||||||
padding: EdgeInsets.only(bottom: 96),
|
(selectedTab.value == 2 && item.type != 1),
|
||||||
itemCount: items
|
);
|
||||||
.where(
|
final pinnedItems = filteredItems
|
||||||
(item) =>
|
.where((item) => item.isPinned)
|
||||||
selectedTab.value == 0 ||
|
.toList();
|
||||||
(selectedTab.value == 1 && item.type == 1) ||
|
final unpinnedItems = filteredItems
|
||||||
(selectedTab.value == 2 && item.type != 1),
|
.where((item) => !item.isPinned)
|
||||||
)
|
.toList();
|
||||||
.length,
|
|
||||||
itemBuilder: (context, index) {
|
return RefreshIndicator(
|
||||||
final filteredItems = items
|
onRefresh: () => Future.sync(() {
|
||||||
.where(
|
ref.invalidate(chatRoomJoinedProvider);
|
||||||
(item) =>
|
}),
|
||||||
selectedTab.value == 0 ||
|
child: Column(
|
||||||
(selectedTab.value == 1 && item.type == 1) ||
|
children: [
|
||||||
(selectedTab.value == 2 && item.type != 1),
|
// Always show pinned chats in their own section
|
||||||
)
|
if (pinnedItems.isNotEmpty)
|
||||||
.toList();
|
ExpansionTile(
|
||||||
final item = filteredItems[index];
|
backgroundColor: Theme.of(
|
||||||
return ChatRoomListTile(
|
context,
|
||||||
room: item,
|
).colorScheme.surfaceContainer.withOpacity(0.5),
|
||||||
isDirect: item.type == 1,
|
collapsedBackgroundColor: Theme.of(
|
||||||
onTap: () {
|
context,
|
||||||
if (isWideScreen(context)) {
|
).colorScheme.surfaceContainer.withOpacity(0.5),
|
||||||
context.replaceNamed(
|
title: Text('pinnedChatRoom'.tr()),
|
||||||
'chatRoom',
|
leading: const Icon(Symbols.keep, fill: 1),
|
||||||
pathParameters: {'id': item.id},
|
tilePadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
);
|
initiallyExpanded: true,
|
||||||
} else {
|
children: [
|
||||||
context.pushNamed(
|
for (final item in pinnedItems)
|
||||||
'chatRoom',
|
ChatRoomListTile(
|
||||||
pathParameters: {'id': item.id},
|
room: item,
|
||||||
);
|
isDirect: item.type == 1,
|
||||||
}
|
onTap: () {
|
||||||
},
|
if (isWideScreen(context)) {
|
||||||
);
|
context.replaceNamed(
|
||||||
},
|
'chatRoom',
|
||||||
),
|
pathParameters: {'id': item.id},
|
||||||
),
|
);
|
||||||
|
} else {
|
||||||
|
context.pushNamed(
|
||||||
|
'chatRoom',
|
||||||
|
pathParameters: {'id': item.id},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Consumer(
|
||||||
|
builder: (context, ref, _) {
|
||||||
|
final summaries =
|
||||||
|
ref
|
||||||
|
.watch(chatSummaryProvider)
|
||||||
|
.whenData((data) => data)
|
||||||
|
.value ??
|
||||||
|
{};
|
||||||
|
|
||||||
|
if (settings.groupedChatList &&
|
||||||
|
selectedTab.value == 0) {
|
||||||
|
// Group by realm (include both pinned and unpinned)
|
||||||
|
final realmGroups = <String?, List<SnChatRoom>>{};
|
||||||
|
final ungrouped = <SnChatRoom>[];
|
||||||
|
|
||||||
|
for (final item in filteredItems) {
|
||||||
|
if (item.realmId != null) {
|
||||||
|
realmGroups
|
||||||
|
.putIfAbsent(item.realmId, () => [])
|
||||||
|
.add(item);
|
||||||
|
} else if (!item.isPinned) {
|
||||||
|
// Only unpinned chats without realm go to ungrouped
|
||||||
|
ungrouped.add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final children = <Widget>[];
|
||||||
|
|
||||||
|
// Add realm groups
|
||||||
|
for (final entry in realmGroups.entries) {
|
||||||
|
final rooms = entry.value;
|
||||||
|
final realm = rooms.first.realm;
|
||||||
|
final realmName = realm?.name ?? 'Unknown Realm';
|
||||||
|
|
||||||
|
// Calculate total unread count for this realm
|
||||||
|
final totalUnread = rooms.fold<int>(
|
||||||
|
0,
|
||||||
|
(sum, room) =>
|
||||||
|
sum +
|
||||||
|
(summaries[room.id]?.unreadCount ?? 0),
|
||||||
|
);
|
||||||
|
|
||||||
|
children.add(
|
||||||
|
ExpansionTile(
|
||||||
|
backgroundColor: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.surfaceContainer
|
||||||
|
.withOpacity(0.5),
|
||||||
|
collapsedBackgroundColor: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.surfaceContainer
|
||||||
|
.withOpacity(0.5),
|
||||||
|
title: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(child: Text(realmName)),
|
||||||
|
Badge(
|
||||||
|
isLabelVisible: totalUnread > 0,
|
||||||
|
label: Text(totalUnread.toString()),
|
||||||
|
backgroundColor: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.primary,
|
||||||
|
textColor: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.onPrimary,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
leading: ProfilePictureWidget(
|
||||||
|
file: realm?.picture,
|
||||||
|
radius: 16,
|
||||||
|
),
|
||||||
|
tilePadding: const EdgeInsets.only(
|
||||||
|
left: 20,
|
||||||
|
right: 24,
|
||||||
|
),
|
||||||
|
children: rooms.map((room) {
|
||||||
|
return ChatRoomListTile(
|
||||||
|
room: room,
|
||||||
|
isDirect: room.type == 1,
|
||||||
|
onTap: () {
|
||||||
|
if (isWideScreen(context)) {
|
||||||
|
context.replaceNamed(
|
||||||
|
'chatRoom',
|
||||||
|
pathParameters: {'id': room.id},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
context.pushNamed(
|
||||||
|
'chatRoom',
|
||||||
|
pathParameters: {'id': room.id},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add ungrouped chats
|
||||||
|
if (ungrouped.isNotEmpty) {
|
||||||
|
children.addAll(
|
||||||
|
ungrouped.map((room) {
|
||||||
|
return ChatRoomListTile(
|
||||||
|
room: room,
|
||||||
|
isDirect: room.type == 1,
|
||||||
|
onTap: () {
|
||||||
|
if (isWideScreen(context)) {
|
||||||
|
context.replaceNamed(
|
||||||
|
'chatRoom',
|
||||||
|
pathParameters: {'id': room.id},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
context.pushNamed(
|
||||||
|
'chatRoom',
|
||||||
|
pathParameters: {'id': room.id},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListView(
|
||||||
|
padding: EdgeInsets.only(bottom: 96),
|
||||||
|
children: children,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Normal list view
|
||||||
|
return SuperListView.builder(
|
||||||
|
padding: EdgeInsets.only(bottom: 96),
|
||||||
|
itemCount: unpinnedItems
|
||||||
|
.where(
|
||||||
|
(item) =>
|
||||||
|
selectedTab.value == 0 ||
|
||||||
|
(selectedTab.value == 1 &&
|
||||||
|
item.type == 1) ||
|
||||||
|
(selectedTab.value == 2 &&
|
||||||
|
item.type != 1),
|
||||||
|
)
|
||||||
|
.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final item = unpinnedItems[index];
|
||||||
|
return ChatRoomListTile(
|
||||||
|
room: item,
|
||||||
|
isDirect: item.type == 1,
|
||||||
|
onTap: () {
|
||||||
|
if (isWideScreen(context)) {
|
||||||
|
context.replaceNamed(
|
||||||
|
'chatRoom',
|
||||||
|
pathParameters: {'id': item.id},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
context.pushNamed(
|
||||||
|
'chatRoom',
|
||||||
|
pathParameters: {'id': item.id},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
loading: () => const Center(child: CircularProgressIndicator()),
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
error: (error, stack) => ResponseErrorWidget(
|
error: (error, stack) => ResponseErrorWidget(
|
||||||
error: error,
|
error: error,
|
||||||
@@ -468,70 +459,87 @@ class ChatListScreen extends HookConsumerWidget {
|
|||||||
return const EmptyPageHolder();
|
return const EmptyPageHolder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final appbarFeColor = Theme.of(context).appBarTheme.foregroundColor;
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
extendBody: false, // Prevent conflicts with tabs navigation
|
extendBody: false, // Prevent conflicts with tabs navigation
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('chat').tr(),
|
flexibleSpace: Container(
|
||||||
bottom: TabBar(
|
height: 48,
|
||||||
controller: tabController,
|
margin: EdgeInsets.only(
|
||||||
tabs: [
|
left: 8,
|
||||||
Tab(
|
right: 8,
|
||||||
child: Text(
|
top: 4 + MediaQuery.of(context).padding.top,
|
||||||
'chatTabAll'.tr(),
|
bottom: 4,
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Tab(
|
|
||||||
child: Text(
|
|
||||||
'chatTabDirect'.tr(),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Tab(
|
|
||||||
child: Text(
|
|
||||||
'chatTabGroup'.tr(),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
IconButton(
|
|
||||||
icon: Badge(
|
|
||||||
label: Text(
|
|
||||||
chatInvites.when(
|
|
||||||
data: (invites) => invites.length.toString(),
|
|
||||||
error: (_, _) => '0',
|
|
||||||
loading: () => '0',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
isLabelVisible: chatInvites.when(
|
|
||||||
data: (invites) => invites.isNotEmpty,
|
|
||||||
error: (_, _) => false,
|
|
||||||
loading: () => false,
|
|
||||||
),
|
|
||||||
child: const Icon(Symbols.email),
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
showModalBottomSheet(
|
|
||||||
useRootNavigator: true,
|
|
||||||
isScrollControlled: true,
|
|
||||||
context: context,
|
|
||||||
builder: (context) => const _ChatInvitesSheet(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
const Gap(8),
|
child: Padding(
|
||||||
],
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Row(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Symbols.inbox,
|
||||||
|
fill: tabController.index == 0 ? 1 : 0,
|
||||||
|
),
|
||||||
|
color: appbarFeColor,
|
||||||
|
onPressed: () => tabController.animateTo(0),
|
||||||
|
tooltip: 'chatTabAll'.tr(),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Symbols.person,
|
||||||
|
fill: tabController.index == 1 ? 1 : 0,
|
||||||
|
),
|
||||||
|
color: appbarFeColor,
|
||||||
|
onPressed: () => tabController.animateTo(1),
|
||||||
|
tooltip: 'chatTabDirect'.tr(),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Symbols.group,
|
||||||
|
fill: tabController.index == 2 ? 1 : 0,
|
||||||
|
),
|
||||||
|
color: appbarFeColor,
|
||||||
|
onPressed: () => tabController.animateTo(2),
|
||||||
|
tooltip: 'chatTabGroup'.tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Badge(
|
||||||
|
label: Text(
|
||||||
|
chatInvites.when(
|
||||||
|
data: (invites) => invites.length.toString(),
|
||||||
|
error: (_, _) => '0',
|
||||||
|
loading: () => '0',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
isLabelVisible: chatInvites.when(
|
||||||
|
data: (invites) => invites.isNotEmpty,
|
||||||
|
error: (_, _) => false,
|
||||||
|
loading: () => false,
|
||||||
|
),
|
||||||
|
child: const Icon(Symbols.email),
|
||||||
|
),
|
||||||
|
color: appbarFeColor,
|
||||||
|
onPressed: () {
|
||||||
|
showModalBottomSheet(
|
||||||
|
useRootNavigator: true,
|
||||||
|
isScrollControlled: true,
|
||||||
|
context: context,
|
||||||
|
builder: (context) => const _ChatInvitesSheet(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
body: ChatListBodyWidget(
|
body: ChatListBodyWidget(
|
||||||
isFloating: false,
|
isFloating: false,
|
||||||
@@ -631,3 +639,75 @@ class _ChatInvitesSheet extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ChatRoomListTile extends HookConsumerWidget {
|
||||||
|
final SnChatRoom room;
|
||||||
|
final bool isDirect;
|
||||||
|
final Widget? subtitle;
|
||||||
|
final Widget? trailing;
|
||||||
|
final VoidCallback? onTap;
|
||||||
|
|
||||||
|
const ChatRoomListTile({
|
||||||
|
super.key,
|
||||||
|
required this.room,
|
||||||
|
this.isDirect = false,
|
||||||
|
this.subtitle,
|
||||||
|
this.trailing,
|
||||||
|
this.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final summary = ref
|
||||||
|
.watch(chatSummaryProvider)
|
||||||
|
.whenData((summaries) => summaries[room.id]);
|
||||||
|
|
||||||
|
var validMembers = room.members ?? [];
|
||||||
|
if (validMembers.isNotEmpty) {
|
||||||
|
final userInfo = ref.watch(userInfoProvider);
|
||||||
|
if (userInfo.value != null) {
|
||||||
|
validMembers = validMembers
|
||||||
|
.where((e) => e.accountId != userInfo.value!.id)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String titleText;
|
||||||
|
if (isDirect && room.name == null) {
|
||||||
|
if (room.members?.isNotEmpty ?? false) {
|
||||||
|
titleText = validMembers.map((e) => e.account.nick).join(', ');
|
||||||
|
} else {
|
||||||
|
titleText = 'Direct Message';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
titleText = room.name ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListTile(
|
||||||
|
leading: ChatRoomAvatar(
|
||||||
|
room: room,
|
||||||
|
isDirect: isDirect,
|
||||||
|
summary: summary,
|
||||||
|
validMembers: validMembers,
|
||||||
|
),
|
||||||
|
title: Text(titleText),
|
||||||
|
subtitle: ChatRoomSubtitle(
|
||||||
|
room: room,
|
||||||
|
isDirect: isDirect,
|
||||||
|
validMembers: validMembers,
|
||||||
|
summary: summary,
|
||||||
|
subtitle: subtitle,
|
||||||
|
),
|
||||||
|
trailing: trailing, // Add this line
|
||||||
|
onTap: () async {
|
||||||
|
// Clear unread count if there are unread messages
|
||||||
|
ref.read(chatSummaryProvider.future).then((summary) {
|
||||||
|
if ((summary[room.id]?.unreadCount ?? 0) > 0) {
|
||||||
|
ref.read(chatSummaryProvider.notifier).clearUnreadCount(room.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
onTap?.call();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
@@ -42,6 +43,20 @@ class ChatDetailScreen extends HookConsumerWidget {
|
|||||||
final roomIdentity = ref.watch(chatRoomIdentityProvider(id));
|
final roomIdentity = ref.watch(chatRoomIdentityProvider(id));
|
||||||
final totalMessages = ref.watch(totalMessagesCountProvider(id));
|
final totalMessages = ref.watch(totalMessagesCountProvider(id));
|
||||||
|
|
||||||
|
// Local state for pinned status to provide immediate UI feedback
|
||||||
|
final isPinned = useState<bool?>(null);
|
||||||
|
|
||||||
|
// Initialize pinned state from database
|
||||||
|
useEffect(() {
|
||||||
|
final db = ref.read(databaseProvider);
|
||||||
|
(db.select(
|
||||||
|
db.chatRooms,
|
||||||
|
)..where((r) => r.id.equals(id))).getSingleOrNull().then((room) {
|
||||||
|
isPinned.value = room?.isPinned ?? false;
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
const kNotifyLevelText = [
|
const kNotifyLevelText = [
|
||||||
'chatNotifyLevelAll',
|
'chatNotifyLevelAll',
|
||||||
'chatNotifyLevelMention',
|
'chatNotifyLevelMention',
|
||||||
@@ -83,46 +98,45 @@ class ChatDetailScreen extends HookConsumerWidget {
|
|||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
context: context,
|
context: context,
|
||||||
builder:
|
builder: (context) => SheetScaffold(
|
||||||
(context) => SheetScaffold(
|
height: 320,
|
||||||
height: 320,
|
titleText: 'chatNotifyLevel'.tr(),
|
||||||
titleText: 'chatNotifyLevel'.tr(),
|
child: Column(
|
||||||
child: Column(
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisSize: MainAxisSize.min,
|
children: [
|
||||||
children: [
|
ListTile(
|
||||||
ListTile(
|
title: const Text('chatNotifyLevelAll').tr(),
|
||||||
title: const Text('chatNotifyLevelAll').tr(),
|
subtitle: const Text('chatNotifyLevelDescription').tr(),
|
||||||
subtitle: const Text('chatNotifyLevelDescription').tr(),
|
leading: const Icon(Icons.notifications_active),
|
||||||
leading: const Icon(Icons.notifications_active),
|
selected: identity.notify == 0,
|
||||||
selected: identity.notify == 0,
|
onTap: () {
|
||||||
onTap: () {
|
setNotifyLevel(0);
|
||||||
setNotifyLevel(0);
|
Navigator.pop(context);
|
||||||
Navigator.pop(context);
|
},
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
title: const Text('chatNotifyLevelMention').tr(),
|
|
||||||
subtitle: const Text('chatNotifyLevelDescription').tr(),
|
|
||||||
leading: const Icon(Icons.alternate_email),
|
|
||||||
selected: identity.notify == 1,
|
|
||||||
onTap: () {
|
|
||||||
setNotifyLevel(1);
|
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
title: const Text('chatNotifyLevelNone').tr(),
|
|
||||||
subtitle: const Text('chatNotifyLevelDescription').tr(),
|
|
||||||
leading: const Icon(Icons.notifications_off),
|
|
||||||
selected: identity.notify == 2,
|
|
||||||
onTap: () {
|
|
||||||
setNotifyLevel(2);
|
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
ListTile(
|
||||||
|
title: const Text('chatNotifyLevelMention').tr(),
|
||||||
|
subtitle: const Text('chatNotifyLevelDescription').tr(),
|
||||||
|
leading: const Icon(Icons.alternate_email),
|
||||||
|
selected: identity.notify == 1,
|
||||||
|
onTap: () {
|
||||||
|
setNotifyLevel(1);
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: const Text('chatNotifyLevelNone').tr(),
|
||||||
|
subtitle: const Text('chatNotifyLevelDescription').tr(),
|
||||||
|
leading: const Icon(Icons.notifications_off),
|
||||||
|
selected: identity.notify == 2,
|
||||||
|
onTap: () {
|
||||||
|
setNotifyLevel(2);
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,118 +146,117 @@ class ChatDetailScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder:
|
builder: (context) => AlertDialog(
|
||||||
(context) => AlertDialog(
|
title: const Text('chatBreak').tr(),
|
||||||
title: const Text('chatBreak').tr(),
|
content: Column(
|
||||||
content: Column(
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisSize: MainAxisSize.min,
|
children: [
|
||||||
children: [
|
const Text('chatBreakDescription').tr(),
|
||||||
const Text('chatBreakDescription').tr(),
|
const Gap(16),
|
||||||
const Gap(16),
|
ListTile(
|
||||||
ListTile(
|
title: const Text('chatBreakClearButton').tr(),
|
||||||
title: const Text('chatBreakClearButton').tr(),
|
subtitle: const Text('chatBreakClear').tr(),
|
||||||
subtitle: const Text('chatBreakClear').tr(),
|
leading: const Icon(Icons.notifications_active),
|
||||||
leading: const Icon(Icons.notifications_active),
|
onTap: () {
|
||||||
onTap: () {
|
setChatBreak(now);
|
||||||
setChatBreak(now);
|
Navigator.pop(context);
|
||||||
Navigator.pop(context);
|
if (context.mounted) {
|
||||||
if (context.mounted) {
|
showSnackBar('chatBreakCleared'.tr());
|
||||||
showSnackBar('chatBreakCleared'.tr());
|
}
|
||||||
}
|
},
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
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)));
|
|
||||||
Navigator.pop(context);
|
|
||||||
if (context.mounted) {
|
|
||||||
showSnackBar('chatBreakSet'.tr(args: ['5m']));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
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)));
|
|
||||||
Navigator.pop(context);
|
|
||||||
if (context.mounted) {
|
|
||||||
showSnackBar('chatBreakSet'.tr(args: ['10m']));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
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)));
|
|
||||||
Navigator.pop(context);
|
|
||||||
if (context.mounted) {
|
|
||||||
showSnackBar('chatBreakSet'.tr(args: ['15m']));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
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)));
|
|
||||||
Navigator.pop(context);
|
|
||||||
if (context.mounted) {
|
|
||||||
showSnackBar('chatBreakSet'.tr(args: ['30m']));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const Gap(8),
|
|
||||||
TextField(
|
|
||||||
controller: durationController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'chatBreakCustomMinutes'.tr(),
|
|
||||||
hintText: 'chatBreakEnterMinutes'.tr(),
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
suffixIcon: IconButton(
|
|
||||||
icon: const Icon(Icons.check),
|
|
||||||
onPressed: () {
|
|
||||||
final minutes = int.tryParse(durationController.text);
|
|
||||||
if (minutes != null && minutes > 0) {
|
|
||||||
setChatBreak(now.add(Duration(minutes: minutes)));
|
|
||||||
Navigator.pop(context);
|
|
||||||
if (context.mounted) {
|
|
||||||
showSnackBar(
|
|
||||||
'chatBreakSet'.tr(args: ['${minutes}m']),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
onTapOutside:
|
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
actions: [
|
ListTile(
|
||||||
TextButton(
|
title: const Text('chatBreak5m').tr(),
|
||||||
onPressed: () => Navigator.pop(context),
|
subtitle: const Text(
|
||||||
child: const Text('cancel').tr(),
|
'chatBreakHour',
|
||||||
|
).tr(args: ['chatBreak5m'.tr()]),
|
||||||
|
leading: const Icon(Symbols.circle),
|
||||||
|
onTap: () {
|
||||||
|
setChatBreak(now.add(const Duration(minutes: 5)));
|
||||||
|
Navigator.pop(context);
|
||||||
|
if (context.mounted) {
|
||||||
|
showSnackBar('chatBreakSet'.tr(args: ['5m']));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
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)));
|
||||||
|
Navigator.pop(context);
|
||||||
|
if (context.mounted) {
|
||||||
|
showSnackBar('chatBreakSet'.tr(args: ['10m']));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
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)));
|
||||||
|
Navigator.pop(context);
|
||||||
|
if (context.mounted) {
|
||||||
|
showSnackBar('chatBreakSet'.tr(args: ['15m']));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
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)));
|
||||||
|
Navigator.pop(context);
|
||||||
|
if (context.mounted) {
|
||||||
|
showSnackBar('chatBreakSet'.tr(args: ['30m']));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Gap(8),
|
||||||
|
TextField(
|
||||||
|
controller: durationController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'chatBreakCustomMinutes'.tr(),
|
||||||
|
hintText: 'chatBreakEnterMinutes'.tr(),
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
icon: const Icon(Icons.check),
|
||||||
|
onPressed: () {
|
||||||
|
final minutes = int.tryParse(durationController.text);
|
||||||
|
if (minutes != null && minutes > 0) {
|
||||||
|
setChatBreak(now.add(Duration(minutes: minutes)));
|
||||||
|
Navigator.pop(context);
|
||||||
|
if (context.mounted) {
|
||||||
|
showSnackBar(
|
||||||
|
'chatBreakSet'.tr(args: ['${minutes}m']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
keyboardType: TextInputType.number,
|
||||||
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: const Text('cancel').tr(),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,175 +269,197 @@ class ChatDetailScreen extends HookConsumerWidget {
|
|||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
body: roomState.when(
|
body: roomState.when(
|
||||||
loading: () => const Center(child: CircularProgressIndicator()),
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
error:
|
error: (error, _) =>
|
||||||
(error, _) => Center(
|
Center(child: Text('errorGeneric'.tr(args: [error.toString()]))),
|
||||||
child: Text('errorGeneric'.tr(args: [error.toString()])),
|
data: (currentRoom) => CustomScrollView(
|
||||||
),
|
slivers: [
|
||||||
data:
|
SliverAppBar(
|
||||||
(currentRoom) => CustomScrollView(
|
expandedHeight: 180,
|
||||||
slivers: [
|
pinned: true,
|
||||||
SliverAppBar(
|
leading: PageBackButton(shadows: [iconShadow]),
|
||||||
expandedHeight: 180,
|
flexibleSpace: FlexibleSpaceBar(
|
||||||
pinned: true,
|
background:
|
||||||
leading: PageBackButton(shadows: [iconShadow]),
|
(currentRoom!.type == 1 &&
|
||||||
flexibleSpace: FlexibleSpaceBar(
|
currentRoom.background?.id != null)
|
||||||
background:
|
? CloudImageWidget(fileId: currentRoom.background!.id)
|
||||||
(currentRoom!.type == 1 &&
|
: (currentRoom.type == 1 &&
|
||||||
currentRoom.background?.id != null)
|
currentRoom.members!.length == 1 &&
|
||||||
? CloudImageWidget(
|
currentRoom
|
||||||
fileId: currentRoom.background!.id,
|
.members!
|
||||||
)
|
.first
|
||||||
: (currentRoom.type == 1 &&
|
.account
|
||||||
currentRoom.members!.length == 1 &&
|
.profile
|
||||||
currentRoom
|
.background
|
||||||
.members!
|
?.id !=
|
||||||
.first
|
null)
|
||||||
.account
|
? CloudImageWidget(
|
||||||
.profile
|
fileId: currentRoom
|
||||||
.background
|
.members!
|
||||||
?.id !=
|
.first
|
||||||
null)
|
.account
|
||||||
? CloudImageWidget(
|
.profile
|
||||||
fileId:
|
.background!
|
||||||
currentRoom
|
.id,
|
||||||
.members!
|
)
|
||||||
.first
|
: currentRoom.background?.id != null
|
||||||
.account
|
? CloudImageWidget(
|
||||||
.profile
|
fileId: currentRoom.background!.id,
|
||||||
.background!
|
fit: BoxFit.cover,
|
||||||
.id,
|
)
|
||||||
)
|
: Container(
|
||||||
: currentRoom.background?.id != null
|
color: Theme.of(context).appBarTheme.backgroundColor,
|
||||||
? CloudImageWidget(
|
|
||||||
fileId: currentRoom.background!.id,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
)
|
|
||||||
: Container(
|
|
||||||
color:
|
|
||||||
Theme.of(context).appBarTheme.backgroundColor,
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
(currentRoom.type == 1 && currentRoom.name == null)
|
|
||||||
? currentRoom.members!
|
|
||||||
.map((e) => e.account.nick)
|
|
||||||
.join(', ')
|
|
||||||
: currentRoom.name!,
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).appBarTheme.foregroundColor,
|
|
||||||
shadows: [iconShadow],
|
|
||||||
),
|
),
|
||||||
),
|
title: Text(
|
||||||
|
(currentRoom.type == 1 && currentRoom.name == null)
|
||||||
|
? currentRoom.members!
|
||||||
|
.map((e) => e.account.nick)
|
||||||
|
.join(', ')
|
||||||
|
: currentRoom.name!,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).appBarTheme.foregroundColor,
|
||||||
|
shadows: [iconShadow],
|
||||||
),
|
),
|
||||||
actions: [
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.people, shadows: [iconShadow]),
|
|
||||||
onPressed: () {
|
|
||||||
showModalBottomSheet(
|
|
||||||
isScrollControlled: true,
|
|
||||||
context: context,
|
|
||||||
builder:
|
|
||||||
(context) => _ChatMemberListSheet(roomId: id),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
_ChatRoomActionMenu(id: id, iconShadow: iconShadow),
|
|
||||||
const Gap(8),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(
|
),
|
||||||
child: Column(
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.people, shadows: [iconShadow]),
|
||||||
|
onPressed: () {
|
||||||
|
showModalBottomSheet(
|
||||||
|
isScrollControlled: true,
|
||||||
|
context: context,
|
||||||
|
builder: (context) => _ChatMemberListSheet(roomId: id),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
_ChatRoomActionMenu(id: id, iconShadow: iconShadow),
|
||||||
|
const Gap(8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
currentRoom.description ?? 'descriptionNone'.tr(),
|
||||||
|
style: const TextStyle(fontSize: 16),
|
||||||
|
).padding(all: 24),
|
||||||
|
const Divider(height: 1),
|
||||||
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
// Pin/Unpin Switch
|
||||||
currentRoom.description ?? 'descriptionNone'.tr(),
|
if (isPinned.value != null)
|
||||||
style: const TextStyle(fontSize: 16),
|
SwitchListTile(
|
||||||
).padding(all: 24),
|
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||||
const Divider(height: 1),
|
secondary: Icon(
|
||||||
|
Symbols.push_pin,
|
||||||
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
title: const Text('pinChatRoom').tr(),
|
||||||
|
subtitle: const Text('pinChatRoomDescription').tr(),
|
||||||
|
value: isPinned.value!,
|
||||||
|
onChanged: (value) async {
|
||||||
|
// Update local state immediately for instant UI feedback
|
||||||
|
isPinned.value = value;
|
||||||
|
final db = ref.read(databaseProvider);
|
||||||
|
await db.toggleChatRoomPinned(id);
|
||||||
|
// Re-verify the state from database in case of error
|
||||||
|
final room = await (db.select(
|
||||||
|
db.chatRooms,
|
||||||
|
)..where((r) => r.id.equals(id))).getSingleOrNull();
|
||||||
|
final actualPinned = room?.isPinned ?? false;
|
||||||
|
if (actualPinned != value) {
|
||||||
|
// Revert if database operation failed
|
||||||
|
isPinned.value = actualPinned;
|
||||||
|
}
|
||||||
|
showSnackBar(
|
||||||
|
value
|
||||||
|
? 'chatRoomPinned'.tr()
|
||||||
|
: 'chatRoomUnpinned'.tr(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
roomIdentity.when(
|
roomIdentity.when(
|
||||||
data:
|
data: (identity) => Column(
|
||||||
(identity) => Column(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
children: [
|
||||||
children: [
|
ListTile(
|
||||||
ListTile(
|
contentPadding: EdgeInsets.symmetric(
|
||||||
contentPadding: EdgeInsets.symmetric(
|
horizontal: 24,
|
||||||
horizontal: 24,
|
),
|
||||||
),
|
leading: const Icon(Symbols.notifications),
|
||||||
leading: const Icon(Symbols.notifications),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
title: const Text('chatNotifyLevel').tr(),
|
||||||
title: const Text('chatNotifyLevel').tr(),
|
subtitle: Text(
|
||||||
subtitle: Text(
|
kNotifyLevelText[identity!.notify].tr(),
|
||||||
kNotifyLevelText[identity!.notify].tr(),
|
),
|
||||||
),
|
onTap: () => showNotifyLevelBottomSheet(identity),
|
||||||
onTap:
|
|
||||||
() =>
|
|
||||||
showNotifyLevelBottomSheet(identity),
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
contentPadding: EdgeInsets.symmetric(
|
|
||||||
horizontal: 24,
|
|
||||||
),
|
|
||||||
leading: const Icon(Icons.timer),
|
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
|
||||||
title: const Text('chatBreak').tr(),
|
|
||||||
subtitle:
|
|
||||||
identity.breakUntil != null &&
|
|
||||||
identity.breakUntil!.isAfter(
|
|
||||||
DateTime.now(),
|
|
||||||
)
|
|
||||||
? Text(
|
|
||||||
DateFormat(
|
|
||||||
'yyyy-MM-dd HH:mm',
|
|
||||||
).format(identity.breakUntil!),
|
|
||||||
)
|
|
||||||
: 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: () async {
|
|
||||||
final result = await context.pushNamed(
|
|
||||||
'searchMessages',
|
|
||||||
pathParameters: {'id': id},
|
|
||||||
);
|
|
||||||
if (result is SearchMessagesResult) {
|
|
||||||
// Navigate back to room screen with message to jump to
|
|
||||||
if (context.mounted) {
|
|
||||||
context.pop(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 24,
|
||||||
|
),
|
||||||
|
leading: const Icon(Icons.timer),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
title: const Text('chatBreak').tr(),
|
||||||
|
subtitle:
|
||||||
|
identity.breakUntil != null &&
|
||||||
|
identity.breakUntil!.isAfter(
|
||||||
|
DateTime.now(),
|
||||||
|
)
|
||||||
|
? Text(
|
||||||
|
DateFormat(
|
||||||
|
'yyyy-MM-dd HH:mm',
|
||||||
|
).format(identity.breakUntil!),
|
||||||
|
)
|
||||||
|
: 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: () async {
|
||||||
|
final result = await context.pushNamed(
|
||||||
|
'searchMessages',
|
||||||
|
pathParameters: {'id': id},
|
||||||
|
);
|
||||||
|
if (result is SearchMessagesResult) {
|
||||||
|
// Navigate back to room screen with message to jump to
|
||||||
|
if (context.mounted) {
|
||||||
|
context.pop(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
error: (_, _) => const SizedBox.shrink(),
|
error: (_, _) => const SizedBox.shrink(),
|
||||||
loading: () => const SizedBox.shrink(),
|
loading: () => const SizedBox.shrink(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -447,97 +482,94 @@ class _ChatRoomActionMenu extends HookConsumerWidget {
|
|||||||
|
|
||||||
return PopupMenuButton(
|
return PopupMenuButton(
|
||||||
icon: Icon(Icons.more_vert, shadows: [iconShadow]),
|
icon: Icon(Icons.more_vert, shadows: [iconShadow]),
|
||||||
itemBuilder:
|
itemBuilder: (context) => [
|
||||||
(context) => [
|
if (isManagable)
|
||||||
if (isManagable)
|
PopupMenuItem(
|
||||||
PopupMenuItem(
|
onTap: () {
|
||||||
onTap: () {
|
showModalBottomSheet(
|
||||||
showModalBottomSheet(
|
context: context,
|
||||||
context: context,
|
useRootNavigator: true,
|
||||||
useRootNavigator: true,
|
isScrollControlled: true,
|
||||||
isScrollControlled: true,
|
builder: (context) => EditChatScreen(id: id),
|
||||||
builder: (context) => EditChatScreen(id: id),
|
).then((value) {
|
||||||
).then((value) {
|
if (value != null) {
|
||||||
if (value != null) {
|
// Invalidate to refresh room data after edit
|
||||||
// Invalidate to refresh room data after edit
|
ref.invalidate(chatMemberListProvider(id));
|
||||||
ref.invalidate(chatMemberListProvider(id));
|
}
|
||||||
}
|
});
|
||||||
});
|
},
|
||||||
},
|
child: Row(
|
||||||
child: Row(
|
children: [
|
||||||
children: [
|
Icon(
|
||||||
Icon(
|
Icons.edit,
|
||||||
Icons.edit,
|
color: Theme.of(context).colorScheme.onSecondaryContainer,
|
||||||
color: Theme.of(context).colorScheme.onSecondaryContainer,
|
|
||||||
),
|
|
||||||
const Gap(12),
|
|
||||||
const Text('editChatRoom').tr(),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
const Gap(12),
|
||||||
if (isManagable)
|
const Text('editChatRoom').tr(),
|
||||||
PopupMenuItem(
|
],
|
||||||
child: Row(
|
),
|
||||||
children: [
|
),
|
||||||
const Icon(Icons.delete, color: Colors.red),
|
if (isManagable)
|
||||||
const Gap(12),
|
PopupMenuItem(
|
||||||
const Text(
|
child: Row(
|
||||||
'deleteChatRoom',
|
children: [
|
||||||
style: TextStyle(color: Colors.red),
|
const Icon(Icons.delete, color: Colors.red),
|
||||||
).tr(),
|
const Gap(12),
|
||||||
],
|
const Text(
|
||||||
|
'deleteChatRoom',
|
||||||
|
style: TextStyle(color: Colors.red),
|
||||||
|
).tr(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
showConfirmAlert(
|
||||||
|
'deleteChatRoomHint'.tr(),
|
||||||
|
'deleteChatRoom'.tr(),
|
||||||
|
isDanger: true,
|
||||||
|
).then((confirm) async {
|
||||||
|
if (confirm) {
|
||||||
|
final client = ref.watch(apiClientProvider);
|
||||||
|
await client.delete('/sphere/chat/$id');
|
||||||
|
ref.invalidate(chatRoomJoinedProvider);
|
||||||
|
if (context.mounted) {
|
||||||
|
context.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
)
|
||||||
|
else
|
||||||
|
PopupMenuItem(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.exit_to_app,
|
||||||
|
color: Theme.of(context).colorScheme.error,
|
||||||
),
|
),
|
||||||
onTap: () {
|
const Gap(12),
|
||||||
showConfirmAlert(
|
Text(
|
||||||
'deleteChatRoomHint'.tr(),
|
'leaveChatRoom',
|
||||||
'deleteChatRoom'.tr(),
|
style: TextStyle(color: Theme.of(context).colorScheme.error),
|
||||||
isDanger: true,
|
).tr(),
|
||||||
).then((confirm) async {
|
],
|
||||||
if (confirm) {
|
),
|
||||||
final client = ref.watch(apiClientProvider);
|
onTap: () {
|
||||||
await client.delete('/sphere/chat/$id');
|
showConfirmAlert(
|
||||||
ref.invalidate(chatRoomJoinedProvider);
|
'leaveChatRoomHint'.tr(),
|
||||||
if (context.mounted) {
|
'leaveChatRoom'.tr(),
|
||||||
context.pop();
|
).then((confirm) async {
|
||||||
}
|
if (confirm) {
|
||||||
}
|
final client = ref.watch(apiClientProvider);
|
||||||
});
|
await client.delete('/sphere/chat/$id/members/me');
|
||||||
},
|
ref.invalidate(chatRoomJoinedProvider);
|
||||||
)
|
if (context.mounted) {
|
||||||
else
|
context.pop();
|
||||||
PopupMenuItem(
|
}
|
||||||
child: Row(
|
}
|
||||||
children: [
|
});
|
||||||
Icon(
|
},
|
||||||
Icons.exit_to_app,
|
),
|
||||||
color: Theme.of(context).colorScheme.error,
|
],
|
||||||
),
|
|
||||||
const Gap(12),
|
|
||||||
Text(
|
|
||||||
'leaveChatRoom',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).colorScheme.error,
|
|
||||||
),
|
|
||||||
).tr(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
showConfirmAlert(
|
|
||||||
'leaveChatRoomHint'.tr(),
|
|
||||||
'leaveChatRoom'.tr(),
|
|
||||||
).then((confirm) async {
|
|
||||||
if (confirm) {
|
|
||||||
final client = ref.watch(apiClientProvider);
|
|
||||||
await client.delete('/sphere/chat/$id/members/me');
|
|
||||||
ref.invalidate(chatRoomJoinedProvider);
|
|
||||||
if (context.mounted) {
|
|
||||||
context.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -576,11 +608,10 @@ class ChatMemberListNotifier extends AsyncNotifier<List<SnChatMember>>
|
|||||||
);
|
);
|
||||||
|
|
||||||
totalCount = int.parse(response.headers.value('X-Total') ?? '0');
|
totalCount = int.parse(response.headers.value('X-Total') ?? '0');
|
||||||
final members =
|
final members = response.data
|
||||||
response.data
|
.map((e) => SnChatMember.fromJson(e))
|
||||||
.map((e) => SnChatMember.fromJson(e))
|
.cast<SnChatMember>()
|
||||||
.cast<SnChatMember>()
|
.toList();
|
||||||
.toList();
|
|
||||||
|
|
||||||
return members;
|
return members;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ Future<List<SnPublisher>> publishersManaged(Ref ref) async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@riverpod
|
@riverpod
|
||||||
Future<SnPublisher?> publisher(Ref ref, String? identifier) async {
|
Future<SnPublisher?> publisherNullable(Ref ref, String? identifier) async {
|
||||||
if (identifier == null) return null;
|
if (identifier == null) return null;
|
||||||
final client = ref.watch(apiClientProvider);
|
final client = ref.watch(apiClientProvider);
|
||||||
final resp = await client.get('/sphere/publishers/$identifier');
|
final resp = await client.get('/sphere/publishers/$identifier');
|
||||||
@@ -93,14 +93,10 @@ class EditPublisherScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
submitting.value = true;
|
submitting.value = true;
|
||||||
try {
|
try {
|
||||||
final cloudFile =
|
final cloudFile = await FileUploader.createCloudFile(
|
||||||
await FileUploader.createCloudFile(
|
ref: ref,
|
||||||
ref: ref,
|
fileData: UniversalFile(data: result, type: UniversalFileType.image),
|
||||||
fileData: UniversalFile(
|
).future;
|
||||||
data: result,
|
|
||||||
type: UniversalFileType.image,
|
|
||||||
),
|
|
||||||
).future;
|
|
||||||
if (cloudFile == null) {
|
if (cloudFile == null) {
|
||||||
throw ArgumentError('Failed to upload the file...');
|
throw ArgumentError('Failed to upload the file...');
|
||||||
}
|
}
|
||||||
@@ -118,7 +114,7 @@ class EditPublisherScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final publisher = ref.watch(publisherProvider(name));
|
final publisher = ref.watch(publisherNullableProvider(name));
|
||||||
|
|
||||||
final formKey = useMemoized(GlobalKey<FormState>.new, const []);
|
final formKey = useMemoized(GlobalKey<FormState>.new, const []);
|
||||||
final nameController = useTextEditingController(
|
final nameController = useTextEditingController(
|
||||||
@@ -155,8 +151,8 @@ class EditPublisherScreen extends HookConsumerWidget {
|
|||||||
final resp = await client.request(
|
final resp = await client.request(
|
||||||
'/sphere${name == null
|
'/sphere${name == null
|
||||||
? currentRealm.value == null
|
? currentRealm.value == null
|
||||||
? '/publishers/individual'
|
? '/publishers/individual'
|
||||||
: '/publishers/organization/${currentRealm.value!.slug}'
|
: '/publishers/organization/${currentRealm.value!.slug}'
|
||||||
: '/publishers/$name'}',
|
: '/publishers/$name'}',
|
||||||
data: {
|
data: {
|
||||||
'name': nameController.text,
|
'name': nameController.text,
|
||||||
@@ -194,13 +190,12 @@ class EditPublisherScreen extends HookConsumerWidget {
|
|||||||
GestureDetector(
|
GestureDetector(
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
child:
|
child: background.value != null
|
||||||
background.value != null
|
? CloudImageWidget(
|
||||||
? CloudImageWidget(
|
fileId: background.value!,
|
||||||
fileId: background.value!,
|
fit: BoxFit.cover,
|
||||||
fit: BoxFit.cover,
|
)
|
||||||
)
|
: const SizedBox.shrink(),
|
||||||
: const SizedBox.shrink(),
|
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
setPicture('background');
|
setPicture('background');
|
||||||
@@ -238,14 +233,14 @@ class EditPublisherScreen extends HookConsumerWidget {
|
|||||||
prefixText: '@',
|
prefixText: '@',
|
||||||
),
|
),
|
||||||
readOnly: name != null,
|
readOnly: name != null,
|
||||||
onTapOutside:
|
onTapOutside: (_) =>
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: nickController,
|
controller: nickController,
|
||||||
decoration: InputDecoration(labelText: 'nickname'.tr()),
|
decoration: InputDecoration(labelText: 'nickname'.tr()),
|
||||||
onTapOutside:
|
onTapOutside: (_) =>
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: bioController,
|
controller: bioController,
|
||||||
@@ -255,8 +250,8 @@ class EditPublisherScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
minLines: 3,
|
minLines: 3,
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
onTapOutside:
|
onTapOutside: (_) =>
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
DropdownButtonFormField<SnRealm>(
|
DropdownButtonFormField<SnRealm>(
|
||||||
value: currentRealm.value,
|
value: currentRealm.value,
|
||||||
@@ -267,22 +262,20 @@ class EditPublisherScreen extends HookConsumerWidget {
|
|||||||
child: Text('individual'.tr()),
|
child: Text('individual'.tr()),
|
||||||
),
|
),
|
||||||
...joinedRealms.maybeWhen(
|
...joinedRealms.maybeWhen(
|
||||||
data:
|
data: (realms) => realms.map(
|
||||||
(realms) => realms.map(
|
(realm) => DropdownMenuItem(
|
||||||
(realm) => DropdownMenuItem(
|
value: realm,
|
||||||
value: realm,
|
child: Text(realm.name),
|
||||||
child: Text(realm.name),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
orElse: () => [],
|
orElse: () => [],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
onChanged:
|
onChanged: joinedRealms.isLoading
|
||||||
joinedRealms.isLoading
|
? null
|
||||||
? null
|
: (SnRealm? value) {
|
||||||
: (SnRealm? value) {
|
currentRealm.value = value;
|
||||||
currentRealm.value = value;
|
},
|
||||||
},
|
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
@@ -307,20 +300,18 @@ class EditPublisherScreen extends HookConsumerWidget {
|
|||||||
currentRealm.value!.background?.id;
|
currentRealm.value!.background?.id;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
label:
|
label: Text(
|
||||||
Text(
|
currentRealm.value == null
|
||||||
currentRealm.value == null
|
? 'syncPublisher'
|
||||||
? 'syncPublisher'
|
: 'syncPublisherRealm',
|
||||||
: 'syncPublisherRealm',
|
).tr(),
|
||||||
).tr(),
|
|
||||||
icon: const Icon(Symbols.link),
|
icon: const Icon(Symbols.link),
|
||||||
),
|
),
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
onPressed: submitting.value ? null : performAction,
|
onPressed: submitting.value ? null : performAction,
|
||||||
label:
|
label: Text(
|
||||||
Text(
|
name == null ? 'create' : 'saveChanges',
|
||||||
name == null ? 'create' : 'saveChanges',
|
).tr(),
|
||||||
).tr(),
|
|
||||||
icon: const Icon(Symbols.save),
|
icon: const Icon(Symbols.save),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -50,10 +50,10 @@ final class PublishersManagedProvider
|
|||||||
|
|
||||||
String _$publishersManagedHash() => r'ea83759fed9bd5119738b4d09f12b4476959e0a3';
|
String _$publishersManagedHash() => r'ea83759fed9bd5119738b4d09f12b4476959e0a3';
|
||||||
|
|
||||||
@ProviderFor(publisher)
|
@ProviderFor(publisherNullable)
|
||||||
const publisherProvider = PublisherFamily._();
|
const publisherNullableProvider = PublisherNullableFamily._();
|
||||||
|
|
||||||
final class PublisherProvider
|
final class PublisherNullableProvider
|
||||||
extends
|
extends
|
||||||
$FunctionalProvider<
|
$FunctionalProvider<
|
||||||
AsyncValue<SnPublisher?>,
|
AsyncValue<SnPublisher?>,
|
||||||
@@ -61,23 +61,23 @@ final class PublisherProvider
|
|||||||
FutureOr<SnPublisher?>
|
FutureOr<SnPublisher?>
|
||||||
>
|
>
|
||||||
with $FutureModifier<SnPublisher?>, $FutureProvider<SnPublisher?> {
|
with $FutureModifier<SnPublisher?>, $FutureProvider<SnPublisher?> {
|
||||||
const PublisherProvider._({
|
const PublisherNullableProvider._({
|
||||||
required PublisherFamily super.from,
|
required PublisherNullableFamily super.from,
|
||||||
required String? super.argument,
|
required String? super.argument,
|
||||||
}) : super(
|
}) : super(
|
||||||
retry: null,
|
retry: null,
|
||||||
name: r'publisherProvider',
|
name: r'publisherNullableProvider',
|
||||||
isAutoDispose: true,
|
isAutoDispose: true,
|
||||||
dependencies: null,
|
dependencies: null,
|
||||||
$allTransitiveDependencies: null,
|
$allTransitiveDependencies: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String debugGetCreateSourceHash() => _$publisherHash();
|
String debugGetCreateSourceHash() => _$publisherNullableHash();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return r'publisherProvider'
|
return r'publisherNullableProvider'
|
||||||
''
|
''
|
||||||
'($argument)';
|
'($argument)';
|
||||||
}
|
}
|
||||||
@@ -91,12 +91,12 @@ final class PublisherProvider
|
|||||||
@override
|
@override
|
||||||
FutureOr<SnPublisher?> create(Ref ref) {
|
FutureOr<SnPublisher?> create(Ref ref) {
|
||||||
final argument = this.argument as String?;
|
final argument = this.argument as String?;
|
||||||
return publisher(ref, argument);
|
return publisherNullable(ref, argument);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return other is PublisherProvider && other.argument == argument;
|
return other is PublisherNullableProvider && other.argument == argument;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -105,22 +105,22 @@ final class PublisherProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$publisherHash() => r'18fb5c6b3d79dd8af4fbee108dec1a0e8a034038';
|
String _$publisherNullableHash() => r'49b28083a2f351c5e5cde0b1a97f6c7503969041';
|
||||||
|
|
||||||
final class PublisherFamily extends $Family
|
final class PublisherNullableFamily extends $Family
|
||||||
with $FunctionalFamilyOverride<FutureOr<SnPublisher?>, String?> {
|
with $FunctionalFamilyOverride<FutureOr<SnPublisher?>, String?> {
|
||||||
const PublisherFamily._()
|
const PublisherNullableFamily._()
|
||||||
: super(
|
: super(
|
||||||
retry: null,
|
retry: null,
|
||||||
name: r'publisherProvider',
|
name: r'publisherNullableProvider',
|
||||||
dependencies: null,
|
dependencies: null,
|
||||||
$allTransitiveDependencies: null,
|
$allTransitiveDependencies: null,
|
||||||
isAutoDispose: true,
|
isAutoDispose: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
PublisherProvider call(String? identifier) =>
|
PublisherNullableProvider call(String? identifier) =>
|
||||||
PublisherProvider._(argument: identifier, from: this);
|
PublisherNullableProvider._(argument: identifier, from: this);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => r'publisherProvider';
|
String toString() => r'publisherNullableProvider';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/models/publication_site.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/screens/creators/sites/site_detail.dart';
|
import 'package:island/screens/creators/sites/site_detail.dart';
|
||||||
import 'package:island/screens/creators/sites/site_list.dart';
|
import 'package:island/screens/creators/sites/site_list.dart';
|
||||||
|
import 'package:island/screens/creators/sites/widgets/site_config_form.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/content/sheet.dart';
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
import 'package:island/widgets/response.dart';
|
import 'package:island/widgets/response.dart';
|
||||||
@@ -23,6 +25,7 @@ class SiteForm extends HookConsumerWidget {
|
|||||||
TextEditingController nameController,
|
TextEditingController nameController,
|
||||||
TextEditingController descriptionController,
|
TextEditingController descriptionController,
|
||||||
ValueNotifier<int> modeController,
|
ValueNotifier<int> modeController,
|
||||||
|
ValueNotifier<SnPublicationSiteConfig> configController,
|
||||||
Function() saveSite,
|
Function() saveSite,
|
||||||
Function() deleteSite,
|
Function() deleteSite,
|
||||||
String siteSlug,
|
String siteSlug,
|
||||||
@@ -103,38 +106,40 @@ class SiteForm extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
SiteConfigForm(
|
||||||
|
initialConfig: configController.value,
|
||||||
|
onChanged: (value) => configController.value = value,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
).padding(all: 20);
|
).padding(all: 20);
|
||||||
|
|
||||||
return SheetScaffold(
|
return SheetScaffold(
|
||||||
titleText: 'editPublicationSite'.tr(),
|
titleText: 'editPublicationSite'.tr(),
|
||||||
child: Builder(
|
child: Builder(
|
||||||
builder:
|
builder: (context) => SingleChildScrollView(
|
||||||
(context) => SingleChildScrollView(
|
child: Column(
|
||||||
child: Column(
|
children: [
|
||||||
|
Form(key: formKey, child: formFields),
|
||||||
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Form(key: formKey, child: formFields),
|
TextButton.icon(
|
||||||
Row(
|
onPressed: deleteSite,
|
||||||
children: [
|
icon: const Icon(Symbols.delete_forever),
|
||||||
TextButton.icon(
|
label: Text('deletePublicationSite'.tr()),
|
||||||
onPressed: deleteSite,
|
style: TextButton.styleFrom(foregroundColor: Colors.red),
|
||||||
icon: const Icon(Symbols.delete_forever),
|
).alignment(Alignment.centerRight),
|
||||||
label: Text('deletePublicationSite'.tr()),
|
const Spacer(),
|
||||||
style: TextButton.styleFrom(
|
TextButton.icon(
|
||||||
foregroundColor: Colors.red,
|
onPressed: saveSite,
|
||||||
),
|
icon: const Icon(Symbols.save),
|
||||||
).alignment(Alignment.centerRight),
|
label: Text('saveChanges').tr(),
|
||||||
const Spacer(),
|
),
|
||||||
TextButton.icon(
|
|
||||||
onPressed: saveSite,
|
|
||||||
icon: const Icon(Symbols.save),
|
|
||||||
label: Text('saveChanges').tr(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).padding(horizontal: 20, vertical: 12),
|
|
||||||
],
|
],
|
||||||
),
|
).padding(horizontal: 20, vertical: 12),
|
||||||
),
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -146,6 +151,9 @@ class SiteForm extends HookConsumerWidget {
|
|||||||
final nameController = useTextEditingController();
|
final nameController = useTextEditingController();
|
||||||
final descriptionController = useTextEditingController();
|
final descriptionController = useTextEditingController();
|
||||||
final modeController = useState<int>(0); // Default to fully managed (0)
|
final modeController = useState<int>(0); // Default to fully managed (0)
|
||||||
|
final configController = useState<SnPublicationSiteConfig>(
|
||||||
|
const SnPublicationSiteConfig(),
|
||||||
|
);
|
||||||
final isLoading = useState(false);
|
final isLoading = useState(false);
|
||||||
|
|
||||||
final saveSite = useCallback(() async {
|
final saveSite = useCallback(() async {
|
||||||
@@ -162,6 +170,7 @@ class SiteForm extends HookConsumerWidget {
|
|||||||
'mode': modeController.value,
|
'mode': modeController.value,
|
||||||
if (descriptionController.text.isNotEmpty)
|
if (descriptionController.text.isNotEmpty)
|
||||||
'description': descriptionController.text,
|
'description': descriptionController.text,
|
||||||
|
'config': configController.value.toJson(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (siteSlug != null) {
|
if (siteSlug != null) {
|
||||||
@@ -229,40 +238,39 @@ class SiteForm extends HookConsumerWidget {
|
|||||||
nameController.text = site.name;
|
nameController.text = site.name;
|
||||||
descriptionController.text = site.description ?? '';
|
descriptionController.text = site.description ?? '';
|
||||||
modeController.value = site.mode ?? 0;
|
modeController.value = site.mode ?? 0;
|
||||||
|
configController.value = site.config;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}, [siteAsync]);
|
}, [siteAsync]);
|
||||||
|
|
||||||
// Handle loading and error states for editing using AsyncValue
|
// Handle loading and error states for editing using AsyncValue
|
||||||
return siteAsync.when(
|
return siteAsync.when(
|
||||||
data:
|
data: (_) => _buildForm(
|
||||||
(_) => _buildForm(
|
formKey,
|
||||||
formKey,
|
slugController,
|
||||||
slugController,
|
nameController,
|
||||||
nameController,
|
descriptionController,
|
||||||
descriptionController,
|
modeController,
|
||||||
modeController,
|
configController,
|
||||||
saveSite,
|
saveSite,
|
||||||
deleteSite,
|
deleteSite,
|
||||||
editingSiteSlug,
|
editingSiteSlug,
|
||||||
),
|
),
|
||||||
loading:
|
loading: () => SheetScaffold(
|
||||||
() => SheetScaffold(
|
titleText: 'editPublicationSite'.tr(),
|
||||||
titleText: 'editPublicationSite'.tr(),
|
child: Center(child: CircularProgressIndicator()),
|
||||||
child: Center(child: CircularProgressIndicator()),
|
),
|
||||||
),
|
error: (error, _) => SheetScaffold(
|
||||||
error:
|
titleText: 'editPublicationSite'.tr(),
|
||||||
(error, _) => SheetScaffold(
|
child: ResponseErrorWidget(
|
||||||
titleText: 'editPublicationSite'.tr(),
|
error: error.toString(),
|
||||||
child: ResponseErrorWidget(
|
onRetry: () {
|
||||||
error: error.toString(),
|
ref.invalidate(
|
||||||
onRetry: () {
|
publicationSiteDetailProvider(pubName, editingSiteSlug),
|
||||||
ref.invalidate(
|
);
|
||||||
publicationSiteDetailProvider(pubName, editingSiteSlug),
|
},
|
||||||
);
|
),
|
||||||
},
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,6 +352,11 @@ class SiteForm extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
SiteConfigForm(
|
||||||
|
initialConfig: configController.value,
|
||||||
|
onChanged: (value) => configController.value = value,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
).padding(all: 20);
|
).padding(all: 20);
|
||||||
|
|
||||||
@@ -354,10 +367,9 @@ class SiteForm extends HookConsumerWidget {
|
|||||||
).padding(horizontal: 20, vertical: 12);
|
).padding(horizontal: 20, vertical: 12);
|
||||||
|
|
||||||
return SheetScaffold(
|
return SheetScaffold(
|
||||||
titleText:
|
titleText: siteSlug == null
|
||||||
siteSlug == null
|
? 'newPublicationSite'.tr()
|
||||||
? 'newPublicationSite'.tr()
|
: 'editPublicationSite'.tr(),
|
||||||
: 'editPublicationSite'.tr(),
|
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
172
lib/screens/creators/sites/widgets/site_config_form.dart
Normal file
172
lib/screens/creators/sites/widgets/site_config_form.dart
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:island/models/publication_site.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
|
class SiteConfigForm extends HookWidget {
|
||||||
|
final SnPublicationSiteConfig? initialConfig;
|
||||||
|
final ValueChanged<SnPublicationSiteConfig> onChanged;
|
||||||
|
|
||||||
|
const SiteConfigForm({
|
||||||
|
super.key,
|
||||||
|
this.initialConfig,
|
||||||
|
required this.onChanged,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final styleOverrideController = useTextEditingController(
|
||||||
|
text: initialConfig?.styleOverride,
|
||||||
|
);
|
||||||
|
final navItems = useState<List<SnPublicationSiteNavItems>>(
|
||||||
|
initialConfig?.navItems ?? [],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
void listener() {
|
||||||
|
onChanged(
|
||||||
|
SnPublicationSiteConfig(
|
||||||
|
styleOverride: styleOverrideController.text,
|
||||||
|
navItems: navItems.value,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
styleOverrideController.addListener(listener);
|
||||||
|
return () => styleOverrideController.removeListener(listener);
|
||||||
|
}, [styleOverrideController, navItems.value]);
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
|
child: ExpansionTile(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
),
|
||||||
|
title: Text('siteConfig'.tr()),
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
TextFormField(
|
||||||
|
controller: styleOverrideController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'siteConfigStyleOverride'.tr(),
|
||||||
|
hintText: "You can paste your CSS here...",
|
||||||
|
alignLabelWithHint: true,
|
||||||
|
border: const OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
minLines: 3,
|
||||||
|
maxLines: null,
|
||||||
|
).padding(bottom: 8),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text('siteConfigNavItems'.tr()).bold(),
|
||||||
|
const Spacer(),
|
||||||
|
TextButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
navItems.value = [
|
||||||
|
...navItems.value,
|
||||||
|
const SnPublicationSiteNavItems(label: '', href: ''),
|
||||||
|
];
|
||||||
|
// Trigger update manually as list mutation doesn't trigger controller listener
|
||||||
|
onChanged(
|
||||||
|
SnPublicationSiteConfig(
|
||||||
|
styleOverride: styleOverrideController.text,
|
||||||
|
navItems: navItems.value,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: const Icon(Symbols.add),
|
||||||
|
label: Text('siteConfigAddNavItem'.tr()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(bottom: 4),
|
||||||
|
if (navItems.value.isEmpty)
|
||||||
|
Text('dataEmpty'.tr()).center().padding(vertical: 20),
|
||||||
|
...navItems.value.asMap().entries.map((entry) {
|
||||||
|
final index = entry.key;
|
||||||
|
final item = entry.value;
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
spacing: 12,
|
||||||
|
children: [
|
||||||
|
TextFormField(
|
||||||
|
initialValue: item.label,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'siteConfigNavItemLabel'.tr(),
|
||||||
|
border: const OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onChanged: (value) {
|
||||||
|
final newItems = [...navItems.value];
|
||||||
|
newItems[index] = item.copyWith(label: value);
|
||||||
|
navItems.value = newItems;
|
||||||
|
onChanged(
|
||||||
|
SnPublicationSiteConfig(
|
||||||
|
styleOverride: styleOverrideController.text,
|
||||||
|
navItems: newItems,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
TextFormField(
|
||||||
|
initialValue: item.href,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'siteConfigNavItemHref'.tr(),
|
||||||
|
border: const OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onChanged: (value) {
|
||||||
|
final newItems = [...navItems.value];
|
||||||
|
newItems[index] = item.copyWith(href: value);
|
||||||
|
navItems.value = newItems;
|
||||||
|
onChanged(
|
||||||
|
SnPublicationSiteConfig(
|
||||||
|
styleOverride: styleOverrideController.text,
|
||||||
|
navItems: newItems,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
TextButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
final newItems = [...navItems.value];
|
||||||
|
newItems.removeAt(index);
|
||||||
|
navItems.value = newItems;
|
||||||
|
onChanged(
|
||||||
|
SnPublicationSiteConfig(
|
||||||
|
styleOverride: styleOverrideController.text,
|
||||||
|
navItems: newItems,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: const Icon(Symbols.delete),
|
||||||
|
label: Text('delete'.tr()),
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
).alignment(Alignment.centerRight),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 16, vertical: 20),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
).padding(all: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
557
lib/screens/dashboard/dash.dart
Normal file
557
lib/screens/dashboard/dash.dart
Normal file
@@ -0,0 +1,557 @@
|
|||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
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:go_router/go_router.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/pods/chat/chat_room.dart';
|
||||||
|
import 'package:island/pods/chat/chat_summary.dart';
|
||||||
|
import 'package:island/pods/event_calendar.dart';
|
||||||
|
import 'package:island/pods/userinfo.dart';
|
||||||
|
import 'package:island/screens/chat/chat.dart';
|
||||||
|
import 'package:island/services/event_bus.dart';
|
||||||
|
import 'package:island/services/responsive.dart';
|
||||||
|
import 'package:island/widgets/account/account_name.dart';
|
||||||
|
import 'package:island/widgets/account/fortune_graph.dart';
|
||||||
|
import 'package:island/widgets/account/friends_overview.dart';
|
||||||
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
|
import 'package:island/widgets/notification_tile.dart';
|
||||||
|
import 'package:island/widgets/post/post_featured.dart';
|
||||||
|
import 'package:island/widgets/check_in.dart';
|
||||||
|
import 'package:island/models/activity.dart';
|
||||||
|
import 'package:island/screens/notification.dart';
|
||||||
|
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
||||||
|
import 'package:slide_countdown/slide_countdown.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
class DashboardScreen extends HookConsumerWidget {
|
||||||
|
const DashboardScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
return AppScaffold(
|
||||||
|
isNoBackground: false,
|
||||||
|
body: Center(child: DashboardGrid()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DashboardGrid extends HookConsumerWidget {
|
||||||
|
const DashboardGrid({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final isWide = isWideScreen(context);
|
||||||
|
final devicePadding = MediaQuery.paddingOf(context);
|
||||||
|
|
||||||
|
final userInfo = ref.watch(userInfoProvider);
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxHeight: isWide
|
||||||
|
? math.min(640, MediaQuery.sizeOf(context).height * 0.65)
|
||||||
|
: MediaQuery.sizeOf(context).height,
|
||||||
|
),
|
||||||
|
padding: isWide
|
||||||
|
? EdgeInsets.only(top: devicePadding.top)
|
||||||
|
: EdgeInsets.only(top: 24 + devicePadding.top),
|
||||||
|
child: Column(
|
||||||
|
spacing: 16,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
// Clock card spans full width
|
||||||
|
if (isWide)
|
||||||
|
ClockCard().padding(horizontal: 24)
|
||||||
|
else
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Gap(8),
|
||||||
|
Expanded(child: ClockCard(compact: true)),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
eventBus.fire(CommandPaletteTriggerEvent());
|
||||||
|
},
|
||||||
|
icon: const Icon(Symbols.search),
|
||||||
|
tooltip: 'searchAnything'.tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 24),
|
||||||
|
// Row with two cards side by side
|
||||||
|
if (isWide)
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: isWide ? 24 : 16),
|
||||||
|
child: SearchBar(
|
||||||
|
hintText: 'searchAnything'.tr(),
|
||||||
|
constraints: const BoxConstraints(minHeight: 56),
|
||||||
|
leading: const Icon(Symbols.search).padding(horizontal: 24),
|
||||||
|
readOnly: true,
|
||||||
|
onTap: () {
|
||||||
|
eventBus.fire(CommandPaletteTriggerEvent());
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (userInfo.value != null)
|
||||||
|
Expanded(
|
||||||
|
child:
|
||||||
|
SingleChildScrollView(
|
||||||
|
padding: isWide
|
||||||
|
? const EdgeInsets.symmetric(horizontal: 24)
|
||||||
|
: EdgeInsets.only(
|
||||||
|
bottom: 64 + devicePadding.bottom,
|
||||||
|
),
|
||||||
|
scrollDirection: isWide
|
||||||
|
? Axis.horizontal
|
||||||
|
: Axis.vertical,
|
||||||
|
child: isWide
|
||||||
|
? _DashboardGridWide()
|
||||||
|
: _DashboardGridNarrow(),
|
||||||
|
)
|
||||||
|
.clipRRect(
|
||||||
|
topLeft: isWide ? 0 : 12,
|
||||||
|
topRight: isWide ? 0 : 12,
|
||||||
|
)
|
||||||
|
.padding(horizontal: isWide ? 0 : 16),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DashboardGridWide extends HookConsumerWidget {
|
||||||
|
const _DashboardGridWide();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final userInfo = ref.watch(userInfoProvider);
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
if (userInfo.value != null && userInfo.value?.activatedAt == null)
|
||||||
|
SizedBox(width: 400, child: AccountUnactivatedCard()),
|
||||||
|
SizedBox(
|
||||||
|
width: 400,
|
||||||
|
child: Column(
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
CheckInWidget(margin: EdgeInsets.zero),
|
||||||
|
Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
|
child: FortuneGraphWidget(
|
||||||
|
events: ref.watch(
|
||||||
|
eventCalendarProvider(
|
||||||
|
EventCalendarQuery(
|
||||||
|
uname: 'me',
|
||||||
|
year: DateTime.now().year,
|
||||||
|
month: DateTime.now().month,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(child: FortuneCard()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 400, child: PostFeaturedList(collapsable: false)),
|
||||||
|
SizedBox(
|
||||||
|
width: 400,
|
||||||
|
child: Column(
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
FriendsOverviewWidget(),
|
||||||
|
Expanded(child: NotificationsCard()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 400,
|
||||||
|
child: Column(
|
||||||
|
spacing: 16,
|
||||||
|
children: [Expanded(child: ChatListCard())],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DashboardGridNarrow extends HookConsumerWidget {
|
||||||
|
const _DashboardGridNarrow();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final userInfo = ref.watch(userInfoProvider);
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
if (userInfo.value != null && userInfo.value?.activatedAt == null)
|
||||||
|
AccountUnactivatedCard(),
|
||||||
|
CheckInWidget(margin: EdgeInsets.zero),
|
||||||
|
FortuneCard(),
|
||||||
|
ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxHeight: 400),
|
||||||
|
child: PostFeaturedList(),
|
||||||
|
),
|
||||||
|
FriendsOverviewWidget(),
|
||||||
|
NotificationsCard(),
|
||||||
|
ChatListCard(),
|
||||||
|
Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
|
child: FortuneGraphWidget(
|
||||||
|
events: ref.watch(
|
||||||
|
eventCalendarProvider(
|
||||||
|
EventCalendarQuery(
|
||||||
|
uname: 'me',
|
||||||
|
year: DateTime.now().year,
|
||||||
|
month: DateTime.now().month,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ClockCard extends HookConsumerWidget {
|
||||||
|
final bool compact;
|
||||||
|
const ClockCard({super.key, this.compact = false});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final time = useState(DateTime.now());
|
||||||
|
final timer = useRef<Timer?>(null);
|
||||||
|
final notableDay = ref.watch(recentNotableDayProvider);
|
||||||
|
|
||||||
|
// Determine icon based on time of day
|
||||||
|
final int hour = time.value.hour;
|
||||||
|
final IconData timeIcon = (hour >= 6 && hour < 18)
|
||||||
|
? Symbols.sunny_rounded
|
||||||
|
: Symbols.dark_mode_rounded;
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
timer.value = Timer.periodic(const Duration(seconds: 1), (_) {
|
||||||
|
time.value = DateTime.now();
|
||||||
|
});
|
||||||
|
return () => timer.value?.cancel();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
elevation: 0,
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
|
color: Colors.transparent,
|
||||||
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: compact
|
||||||
|
? EdgeInsets.zero
|
||||||
|
: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
timeIcon,
|
||||||
|
size: 32,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
spacing: 8,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||||
|
textBaseline: TextBaseline.ideographic,
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
'${time.value.hour.toString().padLeft(2, '0')}:${time.value.minute.toString().padLeft(2, '0')}:${time.value.second.toString().padLeft(2, '0')}',
|
||||||
|
style: GoogleFonts.robotoMono(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
'${time.value.month.toString().padLeft(2, '0')}/${time.value.day.toString().padLeft(2, '0')}',
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium
|
||||||
|
?.copyWith(
|
||||||
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
spacing: 5,
|
||||||
|
children: [
|
||||||
|
notableDay.when(
|
||||||
|
data: (day) => _buildNotableDayText(context, day!),
|
||||||
|
error: (err, _) =>
|
||||||
|
Text(err.toString()).fontSize(12),
|
||||||
|
loading: () =>
|
||||||
|
const Text('loading').tr().fontSize(12),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildNotableDayText(BuildContext context, SnNotableDay notableDay) {
|
||||||
|
final today = DateTime.now();
|
||||||
|
final isToday =
|
||||||
|
notableDay.date.year == today.year &&
|
||||||
|
notableDay.date.month == today.month &&
|
||||||
|
notableDay.date.day == today.day;
|
||||||
|
|
||||||
|
if (isToday) {
|
||||||
|
return Row(
|
||||||
|
spacing: 5,
|
||||||
|
children: [
|
||||||
|
Text('notableDayToday').tr(args: [notableDay.localName]).fontSize(12),
|
||||||
|
Icon(
|
||||||
|
Symbols.celebration_rounded,
|
||||||
|
size: 16,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Row(
|
||||||
|
spacing: 5,
|
||||||
|
children: [
|
||||||
|
Text('notableDayNext').tr(args: [notableDay.localName]).fontSize(12),
|
||||||
|
SlideCountdown(
|
||||||
|
decoration: const BoxDecoration(),
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
separatorStyle: const TextStyle(fontSize: 12),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
duration: notableDay.date.difference(DateTime.now()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NotificationsCard extends HookConsumerWidget {
|
||||||
|
const NotificationsCard({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final notifications = ref.watch(notificationListProvider);
|
||||||
|
final notificationsUnreadCount = ref.watch(notificationUnreadCountProvider);
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
onTap: () {
|
||||||
|
// Show notification sheet similar to explore.dart
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
useRootNavigator: true,
|
||||||
|
builder: (context) => const NotificationSheet(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Symbols.notifications,
|
||||||
|
size: 20,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
'notifications'.tr(),
|
||||||
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Badge.count(
|
||||||
|
count: notificationsUnreadCount.value ?? 0,
|
||||||
|
isLabelVisible: (notificationsUnreadCount.value ?? 0) > 0,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 16, vertical: 12),
|
||||||
|
notifications.when(
|
||||||
|
loading: () => const SkeletonNotificationTile(),
|
||||||
|
error: (error, stack) => Center(child: Text('Error: $error')),
|
||||||
|
data: (notificationList) {
|
||||||
|
if (notificationList.isEmpty) {
|
||||||
|
return Center(child: Text('noNotificationsYet').tr());
|
||||||
|
}
|
||||||
|
// Get the most recent notification (first in the list)
|
||||||
|
final recentNotification = notificationList.first;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'mostRecent'.tr(),
|
||||||
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
).padding(horizontal: 16),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
NotificationTile(
|
||||||
|
notification: recentNotification,
|
||||||
|
compact: true,
|
||||||
|
contentPadding: EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
avatarRadius: 16.0,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'tapToViewAllNotifications'.tr(),
|
||||||
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
).padding(horizontal: 16, vertical: 8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChatListCard extends HookConsumerWidget {
|
||||||
|
const ChatListCard({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final chatRooms = ref.watch(chatRoomJoinedProvider);
|
||||||
|
final chatUnreadCount = ref.watch(chatUnreadCountProvider);
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Symbols.chat,
|
||||||
|
size: 20,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
'recentChats'.tr(),
|
||||||
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Badge.count(
|
||||||
|
count: chatUnreadCount.value ?? 0,
|
||||||
|
isLabelVisible: (chatUnreadCount.value ?? 0) > 0,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 16, vertical: 16),
|
||||||
|
chatRooms.when(
|
||||||
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
|
error: (error, stack) => Center(child: Text('Error: $error')),
|
||||||
|
data: (rooms) {
|
||||||
|
if (rooms.isEmpty) {
|
||||||
|
return const Center(child: Text('No chat rooms available'));
|
||||||
|
}
|
||||||
|
// Take only the first 5 rooms
|
||||||
|
final recentRooms = rooms.take(5).toList();
|
||||||
|
return Column(
|
||||||
|
children: recentRooms.map((room) {
|
||||||
|
return ChatRoomListTile(
|
||||||
|
room: room,
|
||||||
|
isDirect: room.type == 1,
|
||||||
|
onTap: () {
|
||||||
|
context.pushNamed(
|
||||||
|
'chatRoom',
|
||||||
|
pathParameters: {'id': room.id},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FortuneCard extends HookConsumerWidget {
|
||||||
|
const FortuneCard({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final fortuneAsync = ref.watch(randomFortuneSayingProvider);
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
|
child: fortuneAsync.when(
|
||||||
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
|
error: (error, stack) => Center(child: Text('Error: $error')),
|
||||||
|
data: (fortune) {
|
||||||
|
return Row(
|
||||||
|
spacing: 8,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
fortune.content,
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.fade,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text('—— ${fortune.source}').bold(),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 16);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
).height(48);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,17 +16,14 @@ import 'package:island/pods/userinfo.dart';
|
|||||||
import 'package:island/screens/auth/login_modal.dart';
|
import 'package:island/screens/auth/login_modal.dart';
|
||||||
import 'package:island/screens/notification.dart';
|
import 'package:island/screens/notification.dart';
|
||||||
import 'package:island/services/responsive.dart';
|
import 'package:island/services/responsive.dart';
|
||||||
import 'package:island/widgets/account/account_name.dart';
|
|
||||||
import 'package:island/widgets/account/friends_overview.dart';
|
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:island/models/post.dart';
|
import 'package:island/models/post.dart';
|
||||||
import 'package:island/widgets/check_in.dart';
|
|
||||||
import 'package:island/widgets/extended_refresh_indicator.dart';
|
import 'package:island/widgets/extended_refresh_indicator.dart';
|
||||||
import 'package:island/widgets/navigation/fab_menu.dart';
|
import 'package:island/widgets/navigation/fab_menu.dart';
|
||||||
import 'package:island/widgets/paging/pagination_list.dart';
|
import 'package:island/widgets/paging/pagination_list.dart';
|
||||||
import 'package:island/widgets/post/post_featured.dart';
|
|
||||||
import 'package:island/widgets/post/post_item.dart';
|
import 'package:island/widgets/post/post_item.dart';
|
||||||
import 'package:island/widgets/post/post_item_skeleton.dart';
|
import 'package:island/widgets/post/post_item_skeleton.dart';
|
||||||
|
import 'package:island/widgets/post/post_list.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:island/widgets/realm/realm_card.dart';
|
import 'package:island/widgets/realm/realm_card.dart';
|
||||||
import 'package:island/widgets/publisher/publisher_card.dart';
|
import 'package:island/widgets/publisher/publisher_card.dart';
|
||||||
@@ -35,38 +32,8 @@ import 'package:island/services/event_bus.dart';
|
|||||||
import 'package:island/widgets/share/share_sheet.dart';
|
import 'package:island/widgets/share/share_sheet.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:super_sliver_list/super_sliver_list.dart';
|
import 'package:super_sliver_list/super_sliver_list.dart';
|
||||||
|
import 'package:island/widgets/posts/post_subscription_filter.dart';
|
||||||
Widget notificationIndicatorWidget(
|
import 'package:island/pods/post/post_list.dart';
|
||||||
BuildContext context, {
|
|
||||||
required int count,
|
|
||||||
EdgeInsets? margin,
|
|
||||||
}) => Card(
|
|
||||||
margin: margin,
|
|
||||||
child: ListTile(
|
|
||||||
shape: const RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
|
||||||
),
|
|
||||||
minTileHeight: 48,
|
|
||||||
leading: const Icon(Symbols.notifications),
|
|
||||||
title: Row(
|
|
||||||
children: [
|
|
||||||
Text('notifications').tr().fontSize(14),
|
|
||||||
const Gap(8),
|
|
||||||
Badge(label: Text(count.toString())),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
|
||||||
contentPadding: EdgeInsets.only(left: 16, right: 15),
|
|
||||||
onTap: () {
|
|
||||||
showModalBottomSheet(
|
|
||||||
context: context,
|
|
||||||
isScrollControlled: true,
|
|
||||||
useRootNavigator: true,
|
|
||||||
builder: (context) => const NotificationSheet(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
class ExploreScreen extends HookConsumerWidget {
|
class ExploreScreen extends HookConsumerWidget {
|
||||||
const ExploreScreen({super.key});
|
const ExploreScreen({super.key});
|
||||||
@@ -74,6 +41,9 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final currentFilter = useState<String?>(null);
|
final currentFilter = useState<String?>(null);
|
||||||
|
final selectedPublisherNames = useState<List<String>>([]);
|
||||||
|
final selectedCategoryIds = useState<List<String>>([]);
|
||||||
|
final selectedTagIds = useState<List<String>>([]);
|
||||||
final notifier = ref.watch(activityListProvider.notifier);
|
final notifier = ref.watch(activityListProvider.notifier);
|
||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
@@ -123,6 +93,8 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
final isWide = isWideScreen(context);
|
final isWide = isWideScreen(context);
|
||||||
|
|
||||||
|
final hasSubscriptionsSelected = selectedPublisherNames.value.isNotEmpty;
|
||||||
|
|
||||||
final filterBar = Card(
|
final filterBar = Card(
|
||||||
margin: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
|
margin: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
|
||||||
child: Row(
|
child: Row(
|
||||||
@@ -131,7 +103,9 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
spacing: 8,
|
spacing: 8,
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => handleFilterChange(null),
|
onPressed: hasSubscriptionsSelected
|
||||||
|
? null
|
||||||
|
: () => handleFilterChange(null),
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Symbols.explore,
|
Symbols.explore,
|
||||||
fill: currentFilter.value == null ? 1 : null,
|
fill: currentFilter.value == null ? 1 : null,
|
||||||
@@ -143,7 +117,9 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => handleFilterChange('subscriptions'),
|
onPressed: hasSubscriptionsSelected
|
||||||
|
? null
|
||||||
|
: () => handleFilterChange('subscriptions'),
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Symbols.subscriptions,
|
Symbols.subscriptions,
|
||||||
fill: currentFilter.value == 'subscriptions' ? 1 : null,
|
fill: currentFilter.value == 'subscriptions' ? 1 : null,
|
||||||
@@ -155,7 +131,9 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => handleFilterChange('friends'),
|
onPressed: hasSubscriptionsSelected
|
||||||
|
? null
|
||||||
|
: () => handleFilterChange('friends'),
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Symbols.people,
|
Symbols.people,
|
||||||
fill: currentFilter.value == 'friends' ? 1 : null,
|
fill: currentFilter.value == 'friends' ? 1 : null,
|
||||||
@@ -224,7 +202,12 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
final appBar = isWide
|
final appBar = isWide
|
||||||
? null
|
? null
|
||||||
: _buildAppBar(currentFilter.value, handleFilterChange, context);
|
: _buildAppBar(
|
||||||
|
currentFilter.value,
|
||||||
|
handleFilterChange,
|
||||||
|
context,
|
||||||
|
hasSubscriptionsSelected,
|
||||||
|
);
|
||||||
|
|
||||||
final dragging = useState(false);
|
final dragging = useState(false);
|
||||||
|
|
||||||
@@ -257,6 +240,10 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
query,
|
query,
|
||||||
events,
|
events,
|
||||||
selectedDay,
|
selectedDay,
|
||||||
|
currentFilter.value,
|
||||||
|
selectedPublisherNames,
|
||||||
|
selectedCategoryIds,
|
||||||
|
selectedTagIds,
|
||||||
)
|
)
|
||||||
: _buildNarrowBody(context, ref, currentFilter.value),
|
: _buildNarrowBody(context, ref, currentFilter.value),
|
||||||
),
|
),
|
||||||
@@ -309,6 +296,25 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildPostList(
|
||||||
|
BuildContext context,
|
||||||
|
WidgetRef ref,
|
||||||
|
List<String> selectedPublishers,
|
||||||
|
List<String> selectedCategories,
|
||||||
|
List<String> selectedTags,
|
||||||
|
) {
|
||||||
|
return SliverPostList(
|
||||||
|
queryKey: 'explore_filtered',
|
||||||
|
query: PostListQuery(
|
||||||
|
publishers: selectedPublishers,
|
||||||
|
categories: selectedCategories,
|
||||||
|
tags: selectedTags,
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
itemPadding: const EdgeInsets.only(bottom: 8),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildWideBody(
|
Widget _buildWideBody(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
WidgetRef ref,
|
WidgetRef ref,
|
||||||
@@ -318,10 +324,29 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
ValueNotifier<EventCalendarQuery> query,
|
ValueNotifier<EventCalendarQuery> query,
|
||||||
AsyncValue<List<dynamic>> events,
|
AsyncValue<List<dynamic>> events,
|
||||||
ValueNotifier<DateTime> selectedDay,
|
ValueNotifier<DateTime> selectedDay,
|
||||||
|
String? currentFilter,
|
||||||
|
ValueNotifier<List<String>> selectedPublishers,
|
||||||
|
ValueNotifier<List<String>> selectedCategories,
|
||||||
|
ValueNotifier<List<String>> selectedTags,
|
||||||
) {
|
) {
|
||||||
final bodyView = _buildActivityList(context, ref);
|
// Use post list when subscription filter is active and publishers are selected
|
||||||
|
final usePostList =
|
||||||
|
selectedPublishers.value.isNotEmpty ||
|
||||||
|
selectedCategories.value.isNotEmpty ||
|
||||||
|
selectedTags.value.isNotEmpty;
|
||||||
|
final bodyView = usePostList
|
||||||
|
? _buildPostList(
|
||||||
|
context,
|
||||||
|
ref,
|
||||||
|
selectedPublishers.value,
|
||||||
|
selectedCategories.value,
|
||||||
|
selectedTags.value,
|
||||||
|
)
|
||||||
|
: _buildActivityList(context, ref);
|
||||||
|
|
||||||
final notifier = ref.watch(activityListProvider.notifier);
|
final notifier = usePostList
|
||||||
|
? null // Post list handles its own refreshing
|
||||||
|
: ref.watch(activityListProvider.notifier);
|
||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
spacing: 12,
|
spacing: 12,
|
||||||
@@ -329,7 +354,9 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
Flexible(
|
Flexible(
|
||||||
flex: 3,
|
flex: 3,
|
||||||
child: ExtendedRefreshIndicator(
|
child: ExtendedRefreshIndicator(
|
||||||
onRefresh: notifier.refresh,
|
onRefresh: () async {
|
||||||
|
await notifier?.refresh();
|
||||||
|
},
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
const SliverGap(12),
|
const SliverGap(12),
|
||||||
@@ -349,24 +376,21 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
children: [
|
children: [
|
||||||
const Gap(4),
|
Gap(4 + MediaQuery.paddingOf(context).top),
|
||||||
if (user.value?.activatedAt == null)
|
PostSubscriptionFilterWidget(
|
||||||
AccountUnactivatedCard(),
|
initialSelectedPublishers: selectedPublishers.value,
|
||||||
CheckInWidget(
|
initialSelectedCategories: selectedCategories.value,
|
||||||
margin: EdgeInsets.zero,
|
initialSelectedTags: selectedTags.value,
|
||||||
onChecked: () {
|
onSelectedPublishersChanged: (names) {
|
||||||
ref.invalidate(eventCalendarProvider(query.value));
|
selectedPublishers.value = names;
|
||||||
|
},
|
||||||
|
onSelectedCategoriesChanged: (ids) {
|
||||||
|
selectedCategories.value = ids;
|
||||||
|
},
|
||||||
|
onSelectedTagsChanged: (ids) {
|
||||||
|
selectedTags.value = ids;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (notificationCount.value != null &&
|
|
||||||
notificationCount.value! > 0)
|
|
||||||
notificationIndicatorWidget(
|
|
||||||
context,
|
|
||||||
count: notificationCount.value ?? 0,
|
|
||||||
margin: EdgeInsets.zero,
|
|
||||||
),
|
|
||||||
PostFeaturedList(),
|
|
||||||
FriendsOverviewWidget(),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -416,11 +440,11 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
String? currentFilter,
|
String? currentFilter,
|
||||||
void Function(String?) handleFilterChange,
|
void Function(String?) handleFilterChange,
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
|
bool hasSubscriptionsSelected,
|
||||||
) {
|
) {
|
||||||
final foregroundColor = Theme.of(context).appBarTheme.foregroundColor;
|
final foregroundColor = Theme.of(context).appBarTheme.foregroundColor;
|
||||||
|
|
||||||
return AppBar(
|
return AppBar(
|
||||||
toolbarHeight: 48,
|
|
||||||
flexibleSpace: Container(
|
flexibleSpace: Container(
|
||||||
height: 48,
|
height: 48,
|
||||||
margin: EdgeInsets.only(
|
margin: EdgeInsets.only(
|
||||||
@@ -429,91 +453,100 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
top: 4 + MediaQuery.of(context).padding.top,
|
top: 4 + MediaQuery.of(context).padding.top,
|
||||||
bottom: 4,
|
bottom: 4,
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Padding(
|
||||||
spacing: 8,
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
children: [
|
child: Row(
|
||||||
IconButton(
|
spacing: 8,
|
||||||
onPressed: () => handleFilterChange(null),
|
children: [
|
||||||
icon: Icon(
|
IconButton(
|
||||||
Symbols.explore,
|
onPressed: hasSubscriptionsSelected
|
||||||
color: foregroundColor,
|
? null
|
||||||
fill: currentFilter == null ? 1 : null,
|
: () => handleFilterChange(null),
|
||||||
),
|
icon: Icon(
|
||||||
tooltip: 'explore'.tr(),
|
Symbols.explore,
|
||||||
isSelected: currentFilter == null,
|
color: foregroundColor,
|
||||||
color: currentFilter == null ? foregroundColor : null,
|
fill: currentFilter == null ? 1 : null,
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
onPressed: () => handleFilterChange('subscriptions'),
|
|
||||||
icon: Icon(
|
|
||||||
Symbols.subscriptions,
|
|
||||||
color: foregroundColor,
|
|
||||||
fill: currentFilter == 'subscription' ? 1 : null,
|
|
||||||
),
|
|
||||||
tooltip: 'exploreFilterSubscriptions'.tr(),
|
|
||||||
isSelected: currentFilter == 'subscriptions',
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
onPressed: () => handleFilterChange('friends'),
|
|
||||||
icon: Icon(
|
|
||||||
Symbols.people,
|
|
||||||
color: foregroundColor,
|
|
||||||
fill: currentFilter == 'friends' ? 1 : null,
|
|
||||||
),
|
|
||||||
tooltip: 'exploreFilterFriends'.tr(),
|
|
||||||
isSelected: currentFilter == 'friends',
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
context.pushNamed('articles');
|
|
||||||
},
|
|
||||||
icon: Icon(Symbols.auto_stories, color: foregroundColor),
|
|
||||||
tooltip: 'webArticlesStand'.tr(),
|
|
||||||
),
|
|
||||||
PopupMenuButton(
|
|
||||||
itemBuilder: (context) => [
|
|
||||||
PopupMenuItem(
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const Icon(Symbols.category),
|
|
||||||
const Gap(12),
|
|
||||||
Text('categoriesAndTags').tr(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
context.pushNamed('postCategories');
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
PopupMenuItem(
|
tooltip: 'explore'.tr(),
|
||||||
child: Row(
|
isSelected: currentFilter == null,
|
||||||
children: [
|
color: currentFilter == null ? foregroundColor : null,
|
||||||
const Icon(Symbols.shuffle),
|
),
|
||||||
const Gap(12),
|
IconButton(
|
||||||
Text('postShuffle').tr(),
|
onPressed: hasSubscriptionsSelected
|
||||||
],
|
? null
|
||||||
),
|
: () => handleFilterChange('subscriptions'),
|
||||||
onTap: () {
|
icon: Icon(
|
||||||
context.pushNamed('postShuffle');
|
Symbols.subscriptions,
|
||||||
},
|
color: foregroundColor,
|
||||||
|
fill: currentFilter == 'subscription' ? 1 : null,
|
||||||
),
|
),
|
||||||
PopupMenuItem(
|
tooltip: 'exploreFilterSubscriptions'.tr(),
|
||||||
child: Row(
|
isSelected: currentFilter == 'subscriptions',
|
||||||
children: [
|
),
|
||||||
const Icon(Symbols.search),
|
IconButton(
|
||||||
const Gap(12),
|
onPressed: hasSubscriptionsSelected
|
||||||
Text('search').tr(),
|
? null
|
||||||
],
|
: () => handleFilterChange('friends'),
|
||||||
),
|
icon: Icon(
|
||||||
onTap: () {
|
Symbols.people,
|
||||||
context.pushNamed('postSearch');
|
color: foregroundColor,
|
||||||
},
|
fill: currentFilter == 'friends' ? 1 : null,
|
||||||
),
|
),
|
||||||
],
|
tooltip: 'exploreFilterFriends'.tr(),
|
||||||
icon: Icon(Symbols.action_key, color: foregroundColor),
|
isSelected: currentFilter == 'friends',
|
||||||
tooltip: 'search'.tr(),
|
),
|
||||||
),
|
const Spacer(),
|
||||||
],
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
context.pushNamed('articles');
|
||||||
|
},
|
||||||
|
icon: Icon(Symbols.auto_stories, color: foregroundColor),
|
||||||
|
tooltip: 'webArticlesStand'.tr(),
|
||||||
|
),
|
||||||
|
PopupMenuButton(
|
||||||
|
itemBuilder: (context) => [
|
||||||
|
PopupMenuItem(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.category),
|
||||||
|
const Gap(12),
|
||||||
|
Text('categoriesAndTags').tr(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
context.pushNamed('postCategories');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.shuffle),
|
||||||
|
const Gap(12),
|
||||||
|
Text('postShuffle').tr(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
context.pushNamed('postShuffle');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.search),
|
||||||
|
const Gap(12),
|
||||||
|
Text('search').tr(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
context.pushNamed('postSearch');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
icon: Icon(Symbols.action_key, color: foregroundColor),
|
||||||
|
tooltip: 'search'.tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -524,9 +557,6 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
WidgetRef ref,
|
WidgetRef ref,
|
||||||
String? currentFilter,
|
String? currentFilter,
|
||||||
) {
|
) {
|
||||||
final user = ref.watch(userInfoProvider);
|
|
||||||
final notificationCount = ref.watch(notificationUnreadCountProvider);
|
|
||||||
|
|
||||||
final bodyView = _buildActivityList(context, ref);
|
final bodyView = _buildActivityList(context, ref);
|
||||||
|
|
||||||
final notifier = ref.watch(activityListProvider.notifier);
|
final notifier = ref.watch(activityListProvider.notifier);
|
||||||
@@ -536,43 +566,7 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
child: ExtendedRefreshIndicator(
|
child: ExtendedRefreshIndicator(
|
||||||
onRefresh: notifier.refresh,
|
onRefresh: notifier.refresh,
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(slivers: [SliverGap(8), bodyView]),
|
||||||
slivers: [
|
|
||||||
const SliverGap(8),
|
|
||||||
if (user.value?.activatedAt == null)
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: AccountUnactivatedCard().padding(bottom: 8),
|
|
||||||
),
|
|
||||||
if (user.value != null)
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: CheckInWidget(
|
|
||||||
margin: const EdgeInsets.only(bottom: 8),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: 8),
|
|
||||||
child: PostFeaturedList(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: FriendsOverviewWidget(
|
|
||||||
padding: const EdgeInsets.only(bottom: 8),
|
|
||||||
hideWhenEmpty: true,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (notificationCount.value != null &&
|
|
||||||
notificationCount.value! > 0)
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: notificationIndicatorWidget(
|
|
||||||
context,
|
|
||||||
count: notificationCount.value ?? 0,
|
|
||||||
margin: const EdgeInsets.only(bottom: 8),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
bodyView,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
).padding(horizontal: 8),
|
).padding(horizontal: 8),
|
||||||
);
|
);
|
||||||
@@ -634,15 +628,15 @@ class _DiscoveryActivityItem extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
for (final item in items)
|
for (final item in items)
|
||||||
switch (type) {
|
switch (type) {
|
||||||
'realm' => RealmCard(
|
'realm' => RealmDiscoveryCard(
|
||||||
realm: SnRealm.fromJson(item['data']),
|
realm: SnRealm.fromJson(item['data']),
|
||||||
maxWidth: 280,
|
maxWidth: 280,
|
||||||
),
|
),
|
||||||
'publisher' => PublisherCard(
|
'publisher' => PublisherDiscoveryCard(
|
||||||
publisher: SnPublisher.fromJson(item['data']),
|
publisher: SnPublisher.fromJson(item['data']),
|
||||||
maxWidth: 280,
|
maxWidth: 280,
|
||||||
),
|
),
|
||||||
'article' => WebArticleCard(
|
'article' => WebArticleDiscoveryCard(
|
||||||
article: SnWebArticle.fromJson(item['data']),
|
article: SnWebArticle.fromJson(item['data']),
|
||||||
maxWidth: 280,
|
maxWidth: 280,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -4,23 +4,20 @@ import 'dart:math' as math;
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/models/account.dart';
|
import 'package:island/models/account.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/pods/paging.dart';
|
import 'package:island/pods/paging.dart';
|
||||||
import 'package:island/pods/websocket.dart';
|
import 'package:island/pods/websocket.dart';
|
||||||
import 'package:island/route.dart';
|
|
||||||
import 'package:island/widgets/content/cloud_files.dart';
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
import 'package:island/widgets/content/markdown.dart';
|
import 'package:island/widgets/content/markdown.dart';
|
||||||
import 'package:island/widgets/content/sheet.dart';
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
|
import 'package:island/widgets/notification_tile.dart';
|
||||||
import 'package:island/widgets/paging/pagination_list.dart';
|
import 'package:island/widgets/paging/pagination_list.dart';
|
||||||
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
||||||
import 'package:relative_time/relative_time.dart';
|
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:skeletonizer/skeletonizer.dart';
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
|
||||||
|
|
||||||
part 'notification.g.dart';
|
part 'notification.g.dart';
|
||||||
|
|
||||||
@@ -197,31 +194,6 @@ class NotificationListNotifier extends AsyncNotifier<List<SnNotification>>
|
|||||||
class NotificationSheet extends HookConsumerWidget {
|
class NotificationSheet extends HookConsumerWidget {
|
||||||
const NotificationSheet({super.key});
|
const NotificationSheet({super.key});
|
||||||
|
|
||||||
IconData _getNotificationIcon(String topic) {
|
|
||||||
switch (topic) {
|
|
||||||
case 'post.replies':
|
|
||||||
return Symbols.reply;
|
|
||||||
case 'wallet.transactions':
|
|
||||||
return Symbols.account_balance_wallet;
|
|
||||||
case 'relationships.friends.request':
|
|
||||||
return Symbols.person_add;
|
|
||||||
case 'invites.chat':
|
|
||||||
return Symbols.chat;
|
|
||||||
case 'invites.realm':
|
|
||||||
return Symbols.domain;
|
|
||||||
case 'auth.login':
|
|
||||||
return Symbols.login;
|
|
||||||
case 'posts.new':
|
|
||||||
return Symbols.post_add;
|
|
||||||
case 'wallet.orders.paid':
|
|
||||||
return Symbols.shopping_bag;
|
|
||||||
case 'posts.reactions.new':
|
|
||||||
return Symbols.add_reaction;
|
|
||||||
default:
|
|
||||||
return Symbols.notifications;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
// Refresh unread count when sheet opens to sync across devices
|
// Refresh unread count when sheet opens to sync across devices
|
||||||
@@ -265,109 +237,7 @@ class NotificationSheet extends HookConsumerWidget {
|
|||||||
notifier: notificationListProvider.notifier,
|
notifier: notificationListProvider.notifier,
|
||||||
footerSkeletonChild: const SkeletonNotificationTile(),
|
footerSkeletonChild: const SkeletonNotificationTile(),
|
||||||
itemBuilder: (context, index, notification) {
|
itemBuilder: (context, index, notification) {
|
||||||
final pfp = notification.meta['pfp'] as String?;
|
return NotificationTile(notification: notification);
|
||||||
final images = notification.meta['images'] as List?;
|
|
||||||
final imageIds = images?.cast<String>() ?? [];
|
|
||||||
|
|
||||||
return ListTile(
|
|
||||||
isThreeLine: true,
|
|
||||||
contentPadding: EdgeInsets.symmetric(
|
|
||||||
horizontal: 16,
|
|
||||||
vertical: 8,
|
|
||||||
),
|
|
||||||
leading: pfp != null
|
|
||||||
? ProfilePictureWidget(fileId: pfp, radius: 20)
|
|
||||||
: CircleAvatar(
|
|
||||||
backgroundColor: Theme.of(
|
|
||||||
context,
|
|
||||||
).colorScheme.primaryContainer,
|
|
||||||
child: Icon(
|
|
||||||
_getNotificationIcon(notification.topic),
|
|
||||||
color: Theme.of(
|
|
||||||
context,
|
|
||||||
).colorScheme.onPrimaryContainer,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
title: Text(notification.title),
|
|
||||||
subtitle: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
if (notification.subtitle.isNotEmpty)
|
|
||||||
Text(notification.subtitle).bold(),
|
|
||||||
Row(
|
|
||||||
spacing: 6,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
DateFormat().format(
|
|
||||||
notification.createdAt.toLocal(),
|
|
||||||
),
|
|
||||||
).fontSize(11),
|
|
||||||
Text('·').fontSize(11).bold(),
|
|
||||||
Text(
|
|
||||||
RelativeTime(
|
|
||||||
context,
|
|
||||||
).format(notification.createdAt.toLocal()),
|
|
||||||
).fontSize(11),
|
|
||||||
],
|
|
||||||
).opacity(0.75).padding(bottom: 4),
|
|
||||||
MarkdownTextContent(
|
|
||||||
content: notification.content,
|
|
||||||
textStyle: Theme.of(context).textTheme.bodyMedium
|
|
||||||
?.copyWith(
|
|
||||||
color: Theme.of(
|
|
||||||
context,
|
|
||||||
).colorScheme.onSurface.withOpacity(0.8),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (imageIds.isNotEmpty)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 8),
|
|
||||||
child: Wrap(
|
|
||||||
spacing: 8,
|
|
||||||
runSpacing: 8,
|
|
||||||
children: imageIds.map((imageId) {
|
|
||||||
return SizedBox(
|
|
||||||
width: 80,
|
|
||||||
height: 80,
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
child: CloudImageWidget(
|
|
||||||
fileId: imageId,
|
|
||||||
aspectRatio: 1,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
trailing: notification.viewedAt != null
|
|
||||||
? null
|
|
||||||
: Container(
|
|
||||||
width: 12,
|
|
||||||
height: 12,
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
color: Colors.blue,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ Future<SnPostTag> postTag(Ref ref, String slug) async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@riverpod
|
@riverpod
|
||||||
Future<bool> postCategorySubscriptionStatus(
|
Future<SnCategorySubscription?> postCategorySubscription(
|
||||||
Ref ref,
|
Ref ref,
|
||||||
String slug,
|
String slug,
|
||||||
bool isCategory,
|
bool isCategory,
|
||||||
@@ -40,9 +40,10 @@ Future<bool> postCategorySubscriptionStatus(
|
|||||||
final resp = await apiClient.get(
|
final resp = await apiClient.get(
|
||||||
'/sphere/posts/${isCategory ? 'categories' : 'tags'}/$slug/subscription',
|
'/sphere/posts/${isCategory ? 'categories' : 'tags'}/$slug/subscription',
|
||||||
);
|
);
|
||||||
return resp.statusCode == 200;
|
if (resp.data == 200) return SnCategorySubscription.fromJson(resp.data);
|
||||||
|
return null;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +57,7 @@ Future<void> _subscribeToCategoryOrTag(
|
|||||||
'/sphere/posts/${isCategory ? 'categories' : 'tags'}/$slug/subscribe',
|
'/sphere/posts/${isCategory ? 'categories' : 'tags'}/$slug/subscribe',
|
||||||
);
|
);
|
||||||
// Invalidate the subscription status to refresh it
|
// Invalidate the subscription status to refresh it
|
||||||
ref.invalidate(postCategorySubscriptionStatusProvider(slug, isCategory));
|
ref.invalidate(postCategorySubscriptionProvider(slug, isCategory));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _unsubscribeFromCategoryOrTag(
|
Future<void> _unsubscribeFromCategoryOrTag(
|
||||||
@@ -69,7 +70,7 @@ Future<void> _unsubscribeFromCategoryOrTag(
|
|||||||
'/sphere/posts/${isCategory ? 'categories' : 'tags'}/$slug/unsubscribe',
|
'/sphere/posts/${isCategory ? 'categories' : 'tags'}/$slug/unsubscribe',
|
||||||
);
|
);
|
||||||
// Invalidate the subscription status to refresh it
|
// Invalidate the subscription status to refresh it
|
||||||
ref.invalidate(postCategorySubscriptionStatusProvider(slug, isCategory));
|
ref.invalidate(postCategorySubscriptionProvider(slug, isCategory));
|
||||||
}
|
}
|
||||||
|
|
||||||
class PostCategoryDetailScreen extends HookConsumerWidget {
|
class PostCategoryDetailScreen extends HookConsumerWidget {
|
||||||
@@ -88,7 +89,7 @@ class PostCategoryDetailScreen extends HookConsumerWidget {
|
|||||||
: null;
|
: null;
|
||||||
final postTag = isCategory ? null : ref.watch(postTagProvider(slug));
|
final postTag = isCategory ? null : ref.watch(postTagProvider(slug));
|
||||||
final subscriptionStatus = ref.watch(
|
final subscriptionStatus = ref.watch(
|
||||||
postCategorySubscriptionStatusProvider(slug, isCategory),
|
postCategorySubscriptionProvider(slug, isCategory),
|
||||||
);
|
);
|
||||||
|
|
||||||
final postFilterTitle = isCategory
|
final postFilterTitle = isCategory
|
||||||
@@ -118,7 +119,7 @@ class PostCategoryDetailScreen extends HookConsumerWidget {
|
|||||||
Text('A category'),
|
Text('A category'),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
subscriptionStatus.when(
|
subscriptionStatus.when(
|
||||||
data: (isSubscribed) => isSubscribed
|
data: (subscription) => subscription != null
|
||||||
? FilledButton.icon(
|
? FilledButton.icon(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await _unsubscribeFromCategoryOrTag(
|
await _unsubscribeFromCategoryOrTag(
|
||||||
@@ -176,7 +177,7 @@ class PostCategoryDetailScreen extends HookConsumerWidget {
|
|||||||
Text('A tag'),
|
Text('A tag'),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
subscriptionStatus.when(
|
subscriptionStatus.when(
|
||||||
data: (isSubscribed) => isSubscribed
|
data: (subscription) => subscription != null
|
||||||
? FilledButton.icon(
|
? FilledButton.icon(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await _unsubscribeFromCategoryOrTag(
|
await _unsubscribeFromCategoryOrTag(
|
||||||
|
|||||||
@@ -158,48 +158,55 @@ final class PostTagFamily extends $Family
|
|||||||
String toString() => r'postTagProvider';
|
String toString() => r'postTagProvider';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ProviderFor(postCategorySubscriptionStatus)
|
@ProviderFor(postCategorySubscription)
|
||||||
const postCategorySubscriptionStatusProvider =
|
const postCategorySubscriptionProvider = PostCategorySubscriptionFamily._();
|
||||||
PostCategorySubscriptionStatusFamily._();
|
|
||||||
|
|
||||||
final class PostCategorySubscriptionStatusProvider
|
final class PostCategorySubscriptionProvider
|
||||||
extends $FunctionalProvider<AsyncValue<bool>, bool, FutureOr<bool>>
|
extends
|
||||||
with $FutureModifier<bool>, $FutureProvider<bool> {
|
$FunctionalProvider<
|
||||||
const PostCategorySubscriptionStatusProvider._({
|
AsyncValue<SnCategorySubscription?>,
|
||||||
required PostCategorySubscriptionStatusFamily super.from,
|
SnCategorySubscription?,
|
||||||
|
FutureOr<SnCategorySubscription?>
|
||||||
|
>
|
||||||
|
with
|
||||||
|
$FutureModifier<SnCategorySubscription?>,
|
||||||
|
$FutureProvider<SnCategorySubscription?> {
|
||||||
|
const PostCategorySubscriptionProvider._({
|
||||||
|
required PostCategorySubscriptionFamily super.from,
|
||||||
required (String, bool) super.argument,
|
required (String, bool) super.argument,
|
||||||
}) : super(
|
}) : super(
|
||||||
retry: null,
|
retry: null,
|
||||||
name: r'postCategorySubscriptionStatusProvider',
|
name: r'postCategorySubscriptionProvider',
|
||||||
isAutoDispose: true,
|
isAutoDispose: true,
|
||||||
dependencies: null,
|
dependencies: null,
|
||||||
$allTransitiveDependencies: null,
|
$allTransitiveDependencies: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String debugGetCreateSourceHash() => _$postCategorySubscriptionStatusHash();
|
String debugGetCreateSourceHash() => _$postCategorySubscriptionHash();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return r'postCategorySubscriptionStatusProvider'
|
return r'postCategorySubscriptionProvider'
|
||||||
''
|
''
|
||||||
'$argument';
|
'$argument';
|
||||||
}
|
}
|
||||||
|
|
||||||
@$internal
|
@$internal
|
||||||
@override
|
@override
|
||||||
$FutureProviderElement<bool> $createElement($ProviderPointer pointer) =>
|
$FutureProviderElement<SnCategorySubscription?> $createElement(
|
||||||
$FutureProviderElement(pointer);
|
$ProviderPointer pointer,
|
||||||
|
) => $FutureProviderElement(pointer);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<bool> create(Ref ref) {
|
FutureOr<SnCategorySubscription?> create(Ref ref) {
|
||||||
final argument = this.argument as (String, bool);
|
final argument = this.argument as (String, bool);
|
||||||
return postCategorySubscriptionStatus(ref, argument.$1, argument.$2);
|
return postCategorySubscription(ref, argument.$1, argument.$2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return other is PostCategorySubscriptionStatusProvider &&
|
return other is PostCategorySubscriptionProvider &&
|
||||||
other.argument == argument;
|
other.argument == argument;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,26 +216,30 @@ final class PostCategorySubscriptionStatusProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$postCategorySubscriptionStatusHash() =>
|
String _$postCategorySubscriptionHash() =>
|
||||||
r'407dc7fcaeffc461b591b4ee2418811aa4f0a63f';
|
r'60fe0a68ab3d8d493eac3577187d7adcfc0244b9';
|
||||||
|
|
||||||
final class PostCategorySubscriptionStatusFamily extends $Family
|
final class PostCategorySubscriptionFamily extends $Family
|
||||||
with $FunctionalFamilyOverride<FutureOr<bool>, (String, bool)> {
|
with
|
||||||
const PostCategorySubscriptionStatusFamily._()
|
$FunctionalFamilyOverride<
|
||||||
|
FutureOr<SnCategorySubscription?>,
|
||||||
|
(String, bool)
|
||||||
|
> {
|
||||||
|
const PostCategorySubscriptionFamily._()
|
||||||
: super(
|
: super(
|
||||||
retry: null,
|
retry: null,
|
||||||
name: r'postCategorySubscriptionStatusProvider',
|
name: r'postCategorySubscriptionProvider',
|
||||||
dependencies: null,
|
dependencies: null,
|
||||||
$allTransitiveDependencies: null,
|
$allTransitiveDependencies: null,
|
||||||
isAutoDispose: true,
|
isAutoDispose: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
PostCategorySubscriptionStatusProvider call(String slug, bool isCategory) =>
|
PostCategorySubscriptionProvider call(String slug, bool isCategory) =>
|
||||||
PostCategorySubscriptionStatusProvider._(
|
PostCategorySubscriptionProvider._(
|
||||||
argument: (slug, isCategory),
|
argument: (slug, isCategory),
|
||||||
from: this,
|
from: this,
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => r'postCategorySubscriptionStatusProvider';
|
String toString() => r'postCategorySubscriptionProvider';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ part 'publisher_profile.g.dart';
|
|||||||
|
|
||||||
class _PublisherBasisWidget extends StatelessWidget {
|
class _PublisherBasisWidget extends StatelessWidget {
|
||||||
final SnPublisher data;
|
final SnPublisher data;
|
||||||
final AsyncValue<SnSubscriptionStatus> subStatus;
|
final AsyncValue<SnPublisherSubscription?> subStatus;
|
||||||
final ValueNotifier<bool> subscribing;
|
final ValueNotifier<bool> subscribing;
|
||||||
final VoidCallback subscribe;
|
final VoidCallback subscribe;
|
||||||
final VoidCallback unsubscribe;
|
final VoidCallback unsubscribe;
|
||||||
@@ -208,16 +208,16 @@ class _PublisherBasisWidget extends StatelessWidget {
|
|||||||
data: (status) => FilledButton.icon(
|
data: (status) => FilledButton.icon(
|
||||||
onPressed: subscribing.value
|
onPressed: subscribing.value
|
||||||
? null
|
? null
|
||||||
: (status.isSubscribed
|
: (status != null
|
||||||
? unsubscribe
|
? unsubscribe
|
||||||
: subscribe),
|
: subscribe),
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
status.isSubscribed
|
status != null
|
||||||
? Symbols.remove_circle
|
? Symbols.remove_circle
|
||||||
: Symbols.add_circle,
|
: Symbols.add_circle,
|
||||||
),
|
),
|
||||||
label: Text(
|
label: Text(
|
||||||
status.isSubscribed
|
status != null
|
||||||
? 'unsubscribe'
|
? 'unsubscribe'
|
||||||
: 'subscribe',
|
: 'subscribe',
|
||||||
).tr(),
|
).tr(),
|
||||||
@@ -366,13 +366,16 @@ Future<List<SnAccountBadge>> publisherBadges(Ref ref, String pubName) async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@riverpod
|
@riverpod
|
||||||
Future<SnSubscriptionStatus> publisherSubscriptionStatus(
|
Future<SnPublisherSubscription?> publisherSubscriptionStatus(
|
||||||
Ref ref,
|
Ref ref,
|
||||||
String pubName,
|
String pubName,
|
||||||
) async {
|
) async {
|
||||||
final apiClient = ref.watch(apiClientProvider);
|
final apiClient = ref.watch(apiClientProvider);
|
||||||
final resp = await apiClient.get("/sphere/publishers/$pubName/subscription");
|
final resp = await apiClient.get("/sphere/publishers/$pubName/subscription");
|
||||||
return SnSubscriptionStatus.fromJson(resp.data);
|
if (resp.statusCode == 200) {
|
||||||
|
return SnPublisherSubscription.fromJson(resp.data);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@riverpod
|
@riverpod
|
||||||
|
|||||||
@@ -168,13 +168,13 @@ const publisherSubscriptionStatusProvider =
|
|||||||
final class PublisherSubscriptionStatusProvider
|
final class PublisherSubscriptionStatusProvider
|
||||||
extends
|
extends
|
||||||
$FunctionalProvider<
|
$FunctionalProvider<
|
||||||
AsyncValue<SnSubscriptionStatus>,
|
AsyncValue<SnPublisherSubscription?>,
|
||||||
SnSubscriptionStatus,
|
SnPublisherSubscription?,
|
||||||
FutureOr<SnSubscriptionStatus>
|
FutureOr<SnPublisherSubscription?>
|
||||||
>
|
>
|
||||||
with
|
with
|
||||||
$FutureModifier<SnSubscriptionStatus>,
|
$FutureModifier<SnPublisherSubscription?>,
|
||||||
$FutureProvider<SnSubscriptionStatus> {
|
$FutureProvider<SnPublisherSubscription?> {
|
||||||
const PublisherSubscriptionStatusProvider._({
|
const PublisherSubscriptionStatusProvider._({
|
||||||
required PublisherSubscriptionStatusFamily super.from,
|
required PublisherSubscriptionStatusFamily super.from,
|
||||||
required String super.argument,
|
required String super.argument,
|
||||||
@@ -198,12 +198,12 @@ final class PublisherSubscriptionStatusProvider
|
|||||||
|
|
||||||
@$internal
|
@$internal
|
||||||
@override
|
@override
|
||||||
$FutureProviderElement<SnSubscriptionStatus> $createElement(
|
$FutureProviderElement<SnPublisherSubscription?> $createElement(
|
||||||
$ProviderPointer pointer,
|
$ProviderPointer pointer,
|
||||||
) => $FutureProviderElement(pointer);
|
) => $FutureProviderElement(pointer);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<SnSubscriptionStatus> create(Ref ref) {
|
FutureOr<SnPublisherSubscription?> create(Ref ref) {
|
||||||
final argument = this.argument as String;
|
final argument = this.argument as String;
|
||||||
return publisherSubscriptionStatus(ref, argument);
|
return publisherSubscriptionStatus(ref, argument);
|
||||||
}
|
}
|
||||||
@@ -221,10 +221,10 @@ final class PublisherSubscriptionStatusProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _$publisherSubscriptionStatusHash() =>
|
String _$publisherSubscriptionStatusHash() =>
|
||||||
r'634262ce519e1c8288267df11e08e1d4acaa4a44';
|
r'accf6a0cdf98f8b0474d94ac575e8b20448adc79';
|
||||||
|
|
||||||
final class PublisherSubscriptionStatusFamily extends $Family
|
final class PublisherSubscriptionStatusFamily extends $Family
|
||||||
with $FunctionalFamilyOverride<FutureOr<SnSubscriptionStatus>, String> {
|
with $FunctionalFamilyOverride<FutureOr<SnPublisherSubscription?>, String> {
|
||||||
const PublisherSubscriptionStatusFamily._()
|
const PublisherSubscriptionStatusFamily._()
|
||||||
: super(
|
: super(
|
||||||
retry: null,
|
retry: null,
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ class RealmListScreen extends HookConsumerWidget {
|
|||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
isNoBackground: false,
|
isNoBackground: false,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
|
leading: const PageBackButton(backTo: '/account'),
|
||||||
title: const Text('realms').tr(),
|
title: const Text('realms').tr(),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
@@ -99,33 +100,31 @@ class RealmListScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
body: ExtendedRefreshIndicator(
|
body: ExtendedRefreshIndicator(
|
||||||
child: realms.when(
|
child: realms.when(
|
||||||
data:
|
data: (value) => Column(
|
||||||
(value) => Column(
|
children: [
|
||||||
children: [
|
Expanded(
|
||||||
Expanded(
|
child: ListView.separated(
|
||||||
child: ListView.separated(
|
padding: EdgeInsets.only(
|
||||||
padding: EdgeInsets.only(
|
top: 8,
|
||||||
top: 8,
|
bottom: MediaQuery.of(context).padding.bottom + 8,
|
||||||
bottom: MediaQuery.of(context).padding.bottom + 8,
|
|
||||||
),
|
|
||||||
itemCount: value.length,
|
|
||||||
itemBuilder: (context, item) {
|
|
||||||
return ConstrainedBox(
|
|
||||||
constraints: const BoxConstraints(maxWidth: 540),
|
|
||||||
child: RealmListTile(realm: value[item]),
|
|
||||||
).padding(horizontal: 8).center();
|
|
||||||
},
|
|
||||||
separatorBuilder: (_, _) => const Gap(8),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
itemCount: value.length,
|
||||||
|
itemBuilder: (context, item) {
|
||||||
|
return ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 540),
|
||||||
|
child: RealmListTile(realm: value[item]),
|
||||||
|
).padding(horizontal: 8).center();
|
||||||
|
},
|
||||||
|
separatorBuilder: (_, _) => const Gap(8),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
loading: () => const Center(child: CircularProgressIndicator()),
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
error:
|
error: (e, _) => ResponseErrorWidget(
|
||||||
(e, _) => ResponseErrorWidget(
|
error: e,
|
||||||
error: e,
|
onRetry: () => ref.invalidate(realmsJoinedProvider),
|
||||||
onRetry: () => ref.invalidate(realmsJoinedProvider),
|
),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
onRefresh: () => ref.refresh(realmsJoinedProvider.future),
|
onRefresh: () => ref.refresh(realmsJoinedProvider.future),
|
||||||
),
|
),
|
||||||
@@ -183,57 +182,49 @@ class _RealmInviteSheet extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: invites.when(
|
child: invites.when(
|
||||||
data:
|
data: (items) => items.isEmpty
|
||||||
(items) =>
|
? Center(
|
||||||
items.isEmpty
|
child: Text('invitesEmpty', textAlign: TextAlign.center).tr(),
|
||||||
? Center(
|
)
|
||||||
child:
|
: ListView.builder(
|
||||||
Text(
|
shrinkWrap: true,
|
||||||
'invitesEmpty',
|
itemCount: items.length,
|
||||||
textAlign: TextAlign.center,
|
itemBuilder: (context, index) {
|
||||||
).tr(),
|
final invite = items[index];
|
||||||
)
|
return ListTile(
|
||||||
: ListView.builder(
|
leading: ProfilePictureWidget(
|
||||||
shrinkWrap: true,
|
fileId: invite.realm!.picture?.id,
|
||||||
itemCount: items.length,
|
fallbackIcon: Symbols.group,
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final invite = items[index];
|
|
||||||
return ListTile(
|
|
||||||
leading: ProfilePictureWidget(
|
|
||||||
fileId: invite.realm!.picture?.id,
|
|
||||||
fallbackIcon: Symbols.group,
|
|
||||||
),
|
|
||||||
title: Text(invite.realm!.name),
|
|
||||||
subtitle:
|
|
||||||
Text(
|
|
||||||
invite.role >= 100
|
|
||||||
? 'permissionOwner'
|
|
||||||
: invite.role >= 50
|
|
||||||
? 'permissionModerator'
|
|
||||||
: 'permissionMember',
|
|
||||||
).tr(),
|
|
||||||
trailing: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Symbols.check),
|
|
||||||
onPressed: () => acceptInvite(invite),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Symbols.close),
|
|
||||||
onPressed: () => declineInvite(invite),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
|
title: Text(invite.realm!.name),
|
||||||
|
subtitle: Text(
|
||||||
|
invite.role >= 100
|
||||||
|
? 'permissionOwner'
|
||||||
|
: invite.role >= 50
|
||||||
|
? 'permissionModerator'
|
||||||
|
: 'permissionMember',
|
||||||
|
).tr(),
|
||||||
|
trailing: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Symbols.check),
|
||||||
|
onPressed: () => acceptInvite(invite),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Symbols.close),
|
||||||
|
onPressed: () => declineInvite(invite),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
loading: () => const Center(child: CircularProgressIndicator()),
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
error:
|
error: (error, _) => ResponseErrorWidget(
|
||||||
(error, _) => ResponseErrorWidget(
|
error: error,
|
||||||
error: error,
|
onRetry: () => ref.invalidate(realmInvitesProvider),
|
||||||
onRetry: () => ref.invalidate(realmInvitesProvider),
|
),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -449,24 +449,33 @@ class SettingsScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
// Card background opacity settings
|
// Card background opacity settings
|
||||||
ListTile(
|
ListTile(
|
||||||
|
isThreeLine: true,
|
||||||
minLeadingWidth: 48,
|
minLeadingWidth: 48,
|
||||||
title: Text('settingsCardBackgroundOpacity').tr(),
|
title: Text('settingsCardBackgroundOpacity').tr(),
|
||||||
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
||||||
leading: const Icon(Symbols.opacity),
|
leading: const Icon(Symbols.opacity),
|
||||||
subtitle: Padding(
|
subtitle: Padding(
|
||||||
padding: const EdgeInsets.only(top: 8),
|
padding: const EdgeInsets.only(top: 8),
|
||||||
child: Slider(
|
child: SliderTheme(
|
||||||
value: settings.cardTransparency,
|
data: SliderThemeData(
|
||||||
min: 0.0,
|
trackHeight: 2,
|
||||||
max: 1.0,
|
thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 8),
|
||||||
year2023: true,
|
overlayShape: const RoundSliderOverlayShape(overlayRadius: 24),
|
||||||
padding: EdgeInsets.only(right: 24),
|
trackShape: RoundedRectSliderTrackShape(),
|
||||||
label: '${(settings.cardTransparency * 100).round()}%',
|
),
|
||||||
onChanged: (value) {
|
child: Slider(
|
||||||
ref
|
value: settings.cardTransparency,
|
||||||
.read(appSettingsProvider.notifier)
|
min: 0.0,
|
||||||
.setAppTransparentBackground(value);
|
max: 1.0,
|
||||||
},
|
year2023: true,
|
||||||
|
padding: EdgeInsets.only(right: 24),
|
||||||
|
label: '${(settings.cardTransparency * 100).round()}%',
|
||||||
|
onChanged: (value) {
|
||||||
|
ref
|
||||||
|
.read(appSettingsProvider.notifier)
|
||||||
|
.setAppTransparentBackground(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -512,7 +521,7 @@ class SettingsScreen extends HookConsumerWidget {
|
|||||||
minLeadingWidth: 48,
|
minLeadingWidth: 48,
|
||||||
title: Text('settingsBackgroundImageEnable').tr(),
|
title: Text('settingsBackgroundImageEnable').tr(),
|
||||||
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
||||||
leading: const Icon(Symbols.image),
|
leading: const Icon(Symbols.hide_image),
|
||||||
trailing: Switch(
|
trailing: Switch(
|
||||||
value: settings.showBackgroundImage,
|
value: settings.showBackgroundImage,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
@@ -713,20 +722,6 @@ class SettingsScreen extends HookConsumerWidget {
|
|||||||
];
|
];
|
||||||
|
|
||||||
final behaviorSettings = [
|
final behaviorSettings = [
|
||||||
ListTile(
|
|
||||||
minLeadingWidth: 48,
|
|
||||||
title: Text('settingsAutoTranslate').tr(),
|
|
||||||
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
|
||||||
leading: const Icon(Symbols.translate),
|
|
||||||
trailing: Switch(
|
|
||||||
value: settings.autoTranslate,
|
|
||||||
onChanged: (value) {
|
|
||||||
ref.read(appSettingsProvider.notifier).setAutoTranslate(value);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Sound effects settings
|
|
||||||
ListTile(
|
ListTile(
|
||||||
minLeadingWidth: 48,
|
minLeadingWidth: 48,
|
||||||
title: Text('settingsSoundEffects').tr(),
|
title: Text('settingsSoundEffects').tr(),
|
||||||
@@ -743,13 +738,13 @@ class SettingsScreen extends HookConsumerWidget {
|
|||||||
// April Fool features settings
|
// April Fool features settings
|
||||||
ListTile(
|
ListTile(
|
||||||
minLeadingWidth: 48,
|
minLeadingWidth: 48,
|
||||||
title: Text('settingsAprilFoolFeatures').tr(),
|
title: Text('settingsFestivalFeatures').tr(),
|
||||||
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
||||||
leading: const Icon(Symbols.celebration),
|
leading: const Icon(Symbols.celebration),
|
||||||
trailing: Switch(
|
trailing: Switch(
|
||||||
value: settings.aprilFoolFeatures,
|
value: settings.festivalFeatures,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
ref.read(appSettingsProvider.notifier).setAprilFoolFeatures(value);
|
ref.read(appSettingsProvider.notifier).setFeativalFeatures(value);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -810,6 +805,116 @@ class SettingsScreen extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// Grouped chat list settings
|
||||||
|
ListTile(
|
||||||
|
minLeadingWidth: 48,
|
||||||
|
title: Text('settingsGroupedChatList').tr(),
|
||||||
|
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
||||||
|
leading: const Icon(Symbols.chat),
|
||||||
|
trailing: Switch(
|
||||||
|
value: settings.groupedChatList,
|
||||||
|
onChanged: (value) {
|
||||||
|
ref.read(appSettingsProvider.notifier).setGroupedChatList(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Haptic feedback settings
|
||||||
|
ListTile(
|
||||||
|
minLeadingWidth: 48,
|
||||||
|
title: Text('settingsNotifyWithHaptic').tr(),
|
||||||
|
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
||||||
|
leading: const Icon(Symbols.vibration),
|
||||||
|
trailing: Switch(
|
||||||
|
value: settings.notifyWithHaptic,
|
||||||
|
onChanged: (value) {
|
||||||
|
ref.read(appSettingsProvider.notifier).setNotifyWithHaptic(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Default screen settings
|
||||||
|
ListTile(
|
||||||
|
minLeadingWidth: 48,
|
||||||
|
title: Text('settingsDefaultScreen').tr(),
|
||||||
|
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
||||||
|
leading: const Icon(Symbols.home),
|
||||||
|
trailing: DropdownButtonHideUnderline(
|
||||||
|
child: DropdownButton2<String>(
|
||||||
|
isExpanded: true,
|
||||||
|
items: [
|
||||||
|
DropdownMenuItem<String>(
|
||||||
|
value: 'dashboard',
|
||||||
|
child: Text('dashboard').tr().fontSize(14),
|
||||||
|
),
|
||||||
|
DropdownMenuItem<String>(
|
||||||
|
value: 'explore',
|
||||||
|
child: Text('explore').tr().fontSize(14),
|
||||||
|
),
|
||||||
|
DropdownMenuItem<String>(
|
||||||
|
value: 'chat',
|
||||||
|
child: Text('chat').tr().fontSize(14),
|
||||||
|
),
|
||||||
|
DropdownMenuItem<String>(
|
||||||
|
value: 'account',
|
||||||
|
child: Text('account').tr().fontSize(14),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
value: settings.defaultScreen ?? 'dashboard',
|
||||||
|
onChanged: (String? value) {
|
||||||
|
if (value != null) {
|
||||||
|
ref.read(appSettingsProvider.notifier).setDefaultScreen(value);
|
||||||
|
showSnackBar('settingsApplied'.tr());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
buttonStyleData: const ButtonStyleData(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 5),
|
||||||
|
height: 40,
|
||||||
|
width: 140,
|
||||||
|
),
|
||||||
|
menuItemStyleData: const MenuItemStyleData(height: 40),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Dash search engine settings
|
||||||
|
ListTile(
|
||||||
|
isThreeLine: true,
|
||||||
|
minLeadingWidth: 48,
|
||||||
|
title: Text('settingsDashSearchEngine').tr(),
|
||||||
|
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
||||||
|
leading: const Icon(Symbols.search),
|
||||||
|
subtitle: Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 6),
|
||||||
|
child: TextField(
|
||||||
|
controller: TextEditingController(text: settings.dashSearchEngine),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'https://google.com/?q=%s',
|
||||||
|
helperText: 'settingsDashSearchEngineHelper'.tr(),
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
icon: const Icon(Symbols.restart_alt),
|
||||||
|
onPressed: () {
|
||||||
|
ref
|
||||||
|
.read(appSettingsProvider.notifier)
|
||||||
|
.setDashSearchEngine(null);
|
||||||
|
showSnackBar('settingsApplied'.tr());
|
||||||
|
},
|
||||||
|
),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
isDense: true,
|
||||||
|
),
|
||||||
|
onSubmitted: (value) {
|
||||||
|
ref
|
||||||
|
.read(appSettingsProvider.notifier)
|
||||||
|
.setDashSearchEngine(value.isEmpty ? null : value);
|
||||||
|
showSnackBar('settingsApplied'.tr());
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Desktop-specific settings
|
// Desktop-specific settings
|
||||||
@@ -821,20 +926,33 @@ class SettingsScreen extends HookConsumerWidget {
|
|||||||
title: Text('settingsWindowOpacity').tr(),
|
title: Text('settingsWindowOpacity').tr(),
|
||||||
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
||||||
leading: const Icon(Symbols.opacity),
|
leading: const Icon(Symbols.opacity),
|
||||||
|
isThreeLine: true,
|
||||||
subtitle: Padding(
|
subtitle: Padding(
|
||||||
padding: const EdgeInsets.only(top: 8),
|
padding: const EdgeInsets.only(top: 8),
|
||||||
child: Slider(
|
child: SliderTheme(
|
||||||
value: settings.windowOpacity,
|
data: SliderThemeData(
|
||||||
min: 0.1,
|
trackHeight: 2,
|
||||||
max: 1.0,
|
thumbShape: const RoundSliderThumbShape(
|
||||||
year2023: true,
|
enabledThumbRadius: 8,
|
||||||
padding: EdgeInsets.only(right: 24),
|
),
|
||||||
label: '${(settings.windowOpacity * 100).round()}%',
|
overlayShape: const RoundSliderOverlayShape(
|
||||||
onChanged: (value) {
|
overlayRadius: 24,
|
||||||
ref
|
),
|
||||||
.read(appSettingsProvider.notifier)
|
trackShape: RoundedRectSliderTrackShape(),
|
||||||
.setWindowOpacity(value);
|
),
|
||||||
},
|
child: Slider(
|
||||||
|
value: settings.windowOpacity,
|
||||||
|
min: 0.1,
|
||||||
|
max: 1.0,
|
||||||
|
year2023: true,
|
||||||
|
padding: EdgeInsets.only(right: 24),
|
||||||
|
label: '${(settings.windowOpacity * 100).round()}%',
|
||||||
|
onChanged: (value) {
|
||||||
|
ref
|
||||||
|
.read(appSettingsProvider.notifier)
|
||||||
|
.setWindowOpacity(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -844,45 +962,52 @@ class SettingsScreen extends HookConsumerWidget {
|
|||||||
Widget buildSettingsList() {
|
Widget buildSettingsList() {
|
||||||
if (isWide) {
|
if (isWide) {
|
||||||
// Two-column layout for wide screens
|
// Two-column layout for wide screens
|
||||||
return Row(
|
return ConstrainedBox(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
constraints: const BoxConstraints(maxWidth: 920),
|
||||||
children: [
|
child: Row(
|
||||||
Expanded(
|
spacing: 16,
|
||||||
child: Column(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
children: [
|
||||||
children: [
|
Expanded(
|
||||||
_SettingsSection(
|
child: Column(
|
||||||
title: 'settingsAppearance'.tr(),
|
spacing: 16,
|
||||||
children: appearanceSettings,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
),
|
children: [
|
||||||
_SettingsSection(
|
|
||||||
title: 'settingsServer'.tr(),
|
|
||||||
children: serverSettings,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
_SettingsSection(
|
|
||||||
title: 'settingsBehavior'.tr(),
|
|
||||||
children: behaviorSettings,
|
|
||||||
),
|
|
||||||
if (desktopSettings.isNotEmpty)
|
|
||||||
_SettingsSection(
|
_SettingsSection(
|
||||||
title: 'settingsDesktop'.tr(),
|
title: 'settingsAppearance'.tr(),
|
||||||
children: desktopSettings,
|
children: appearanceSettings,
|
||||||
),
|
),
|
||||||
],
|
_SettingsSection(
|
||||||
|
title: 'settingsServer'.tr(),
|
||||||
|
children: serverSettings,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
Expanded(
|
||||||
],
|
child: Column(
|
||||||
);
|
spacing: 16,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_SettingsSection(
|
||||||
|
title: 'settingsBehavior'.tr(),
|
||||||
|
children: behaviorSettings,
|
||||||
|
),
|
||||||
|
if (desktopSettings.isNotEmpty)
|
||||||
|
_SettingsSection(
|
||||||
|
title: 'settingsDesktop'.tr(),
|
||||||
|
children: desktopSettings,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 16),
|
||||||
|
).center();
|
||||||
} else {
|
} else {
|
||||||
// Single column layout for narrow screens
|
// Single column layout for narrow screens
|
||||||
return Column(
|
return Column(
|
||||||
|
spacing: 16,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_SettingsSection(
|
_SettingsSection(
|
||||||
@@ -903,7 +1028,7 @@ class SettingsScreen extends HookConsumerWidget {
|
|||||||
children: desktopSettings,
|
children: desktopSettings,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
).padding(horizontal: 16);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -940,22 +1065,25 @@ class _SettingsSection extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Column(
|
return Card(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
margin: EdgeInsets.zero,
|
||||||
children: [
|
child: Column(
|
||||||
Padding(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
padding: const EdgeInsets.fromLTRB(24, 16, 24, 8),
|
children: [
|
||||||
child: Text(
|
Padding(
|
||||||
title,
|
padding: const EdgeInsets.fromLTRB(24, 16, 24, 8),
|
||||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
child: Text(
|
||||||
color: Theme.of(context).colorScheme.primary,
|
title,
|
||||||
fontWeight: FontWeight.bold,
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
...children,
|
||||||
...children,
|
const SizedBox(height: 16),
|
||||||
const SizedBox(height: 16),
|
],
|
||||||
],
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,9 +34,10 @@ class CurrentRouteNotifier extends Notifier<String?> {
|
|||||||
const kWideScreenRouteStart = 4;
|
const kWideScreenRouteStart = 4;
|
||||||
const kTabRoutes = [
|
const kTabRoutes = [
|
||||||
'/',
|
'/',
|
||||||
|
'/explore',
|
||||||
'/chat',
|
'/chat',
|
||||||
'/realms',
|
|
||||||
'/account',
|
'/account',
|
||||||
|
'/realms',
|
||||||
'/files',
|
'/files',
|
||||||
'/thought',
|
'/thought',
|
||||||
'/creators',
|
'/creators',
|
||||||
@@ -67,6 +68,10 @@ class TabsScreen extends HookConsumerWidget {
|
|||||||
final wideScreen = isWideScreen(context);
|
final wideScreen = isWideScreen(context);
|
||||||
|
|
||||||
final destinations = [
|
final destinations = [
|
||||||
|
NavigationDestination(
|
||||||
|
label: 'dashboard'.tr(),
|
||||||
|
icon: const Icon(Symbols.dashboard_rounded),
|
||||||
|
),
|
||||||
NavigationDestination(
|
NavigationDestination(
|
||||||
label: 'explore'.tr(),
|
label: 'explore'.tr(),
|
||||||
icon: const Icon(Symbols.explore_rounded),
|
icon: const Icon(Symbols.explore_rounded),
|
||||||
@@ -79,10 +84,7 @@ class TabsScreen extends HookConsumerWidget {
|
|||||||
child: const Icon(Symbols.forum_rounded),
|
child: const Icon(Symbols.forum_rounded),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
NavigationDestination(
|
|
||||||
label: 'realms'.tr(),
|
|
||||||
icon: const Icon(Symbols.group_rounded),
|
|
||||||
),
|
|
||||||
NavigationDestination(
|
NavigationDestination(
|
||||||
label: 'account'.tr(),
|
label: 'account'.tr(),
|
||||||
icon: Badge.count(
|
icon: Badge.count(
|
||||||
@@ -105,6 +107,10 @@ class TabsScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
if (wideScreen)
|
if (wideScreen)
|
||||||
...([
|
...([
|
||||||
|
NavigationDestination(
|
||||||
|
label: 'realms'.tr(),
|
||||||
|
icon: const Icon(Symbols.group_rounded),
|
||||||
|
),
|
||||||
NavigationDestination(
|
NavigationDestination(
|
||||||
label: 'files'.tr(),
|
label: 'files'.tr(),
|
||||||
icon: const Icon(Symbols.folder_rounded),
|
icon: const Icon(Symbols.folder_rounded),
|
||||||
@@ -154,15 +160,14 @@ class TabsScreen extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
NavigationRail(
|
NavigationRail(
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
destinations:
|
destinations: destinations
|
||||||
destinations
|
.map(
|
||||||
.map(
|
(e) => NavigationRailDestination(
|
||||||
(e) => NavigationRailDestination(
|
icon: e.icon,
|
||||||
icon: e.icon,
|
label: Text(e.label),
|
||||||
label: Text(e.label),
|
),
|
||||||
),
|
)
|
||||||
)
|
.toList(),
|
||||||
.toList(),
|
|
||||||
selectedIndex: currentIndex,
|
selectedIndex: currentIndex,
|
||||||
onDestinationSelected: onDestinationSelected,
|
onDestinationSelected: onDestinationSelected,
|
||||||
trailingAtBottom: true,
|
trailingAtBottom: true,
|
||||||
@@ -195,10 +200,9 @@ class TabsScreen extends HookConsumerWidget {
|
|||||||
child: child ?? const SizedBox.shrink(),
|
child: child ?? const SizedBox.shrink(),
|
||||||
),
|
),
|
||||||
floatingActionButton: shouldShowFab ? const FabMenu() : null,
|
floatingActionButton: shouldShowFab ? const FabMenu() : null,
|
||||||
floatingActionButtonLocation:
|
floatingActionButtonLocation: shouldShowFab
|
||||||
shouldShowFab
|
? _DockedFabLocation(context, settings.fabPosition)
|
||||||
? _DockedFabLocation(context, settings.fabPosition)
|
: null,
|
||||||
: null,
|
|
||||||
bottomNavigationBar: ConditionalBottomNav(
|
bottomNavigationBar: ConditionalBottomNav(
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: BorderRadius.only(
|
borderRadius: BorderRadius.only(
|
||||||
@@ -208,48 +212,60 @@ class TabsScreen extends HookConsumerWidget {
|
|||||||
child: MediaQuery.removePadding(
|
child: MediaQuery.removePadding(
|
||||||
context: context,
|
context: context,
|
||||||
removeTop: true,
|
removeTop: true,
|
||||||
child: BackdropFilter(
|
child: Container(
|
||||||
filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
|
decoration: BoxDecoration(
|
||||||
child: BottomAppBar(
|
color: Colors.transparent,
|
||||||
height: 56,
|
boxShadow: [
|
||||||
padding: EdgeInsets.symmetric(horizontal: 24),
|
BoxShadow(
|
||||||
shape: AutomaticNotchedShape(
|
color: Colors.black.withOpacity(0.1),
|
||||||
RoundedRectangleBorder(
|
blurRadius: 4,
|
||||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
offset: const Offset(0, 2),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
color: Theme.of(context).colorScheme.surface.withOpacity(0.8),
|
),
|
||||||
child: Row(
|
child: BackdropFilter(
|
||||||
mainAxisSize: MainAxisSize.max,
|
filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
child: BottomAppBar(
|
||||||
children: () {
|
height: 56,
|
||||||
final navItems =
|
padding: EdgeInsets.symmetric(horizontal: 24),
|
||||||
destinations.asMap().entries.map<Widget>((entry) {
|
shape: AutomaticNotchedShape(
|
||||||
|
RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
color: Theme.of(context).colorScheme.surface.withOpacity(0.8),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: () {
|
||||||
|
final navItems = destinations.asMap().entries.map<Widget>(
|
||||||
|
(entry) {
|
||||||
int index = entry.key;
|
int index = entry.key;
|
||||||
NavigationDestination dest = entry.value;
|
NavigationDestination dest = entry.value;
|
||||||
return IconButton(
|
return IconButton(
|
||||||
icon: dest.icon,
|
icon: dest.icon,
|
||||||
onPressed: () => onDestinationSelected(index),
|
onPressed: () => onDestinationSelected(index),
|
||||||
color:
|
color: index == currentIndex
|
||||||
index == currentIndex
|
? Theme.of(context).colorScheme.primary
|
||||||
? Theme.of(context).colorScheme.primary
|
: null,
|
||||||
: null,
|
|
||||||
);
|
);
|
||||||
}).toList();
|
},
|
||||||
// Add mock item to leave space for FAB based on position
|
).toList();
|
||||||
final gapIndex = switch (settings.fabPosition) {
|
// Add mock item to leave space for FAB based on position
|
||||||
'left' => 0,
|
final gapIndex = switch (settings.fabPosition) {
|
||||||
'right' => navItems.length,
|
'left' => 0,
|
||||||
_ => navItems.length ~/ 2, // center
|
'right' => navItems.length,
|
||||||
};
|
_ => navItems.length ~/ 2, // center
|
||||||
navItems.insert(
|
};
|
||||||
gapIndex,
|
navItems.insert(
|
||||||
SizedBox(
|
gapIndex,
|
||||||
width: settings.fabPosition == 'center' ? 72 : 48,
|
SizedBox(
|
||||||
),
|
width: settings.fabPosition == 'center' ? 72 : 48,
|
||||||
);
|
),
|
||||||
return navItems;
|
);
|
||||||
}(),
|
return navItems;
|
||||||
|
}(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -10,17 +10,20 @@ import "package:island/widgets/thought/thought_shared.dart";
|
|||||||
import "package:material_symbols_icons/material_symbols_icons.dart";
|
import "package:material_symbols_icons/material_symbols_icons.dart";
|
||||||
|
|
||||||
class ThoughtSheet extends HookConsumerWidget {
|
class ThoughtSheet extends HookConsumerWidget {
|
||||||
|
final String? initialMessage;
|
||||||
final List<Map<String, dynamic>> attachedMessages;
|
final List<Map<String, dynamic>> attachedMessages;
|
||||||
final List<String> attachedPosts;
|
final List<String> attachedPosts;
|
||||||
|
|
||||||
const ThoughtSheet({
|
const ThoughtSheet({
|
||||||
super.key,
|
super.key,
|
||||||
|
this.initialMessage,
|
||||||
this.attachedMessages = const [],
|
this.attachedMessages = const [],
|
||||||
this.attachedPosts = const [],
|
this.attachedPosts = const [],
|
||||||
});
|
});
|
||||||
|
|
||||||
static Future<void> show(
|
static Future<void> show(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
|
String? initialMessage,
|
||||||
List<Map<String, dynamic>> attachedMessages = const [],
|
List<Map<String, dynamic>> attachedMessages = const [],
|
||||||
List<String> attachedPosts = const [],
|
List<String> attachedPosts = const [],
|
||||||
}) {
|
}) {
|
||||||
@@ -28,11 +31,11 @@ class ThoughtSheet extends HookConsumerWidget {
|
|||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
useSafeArea: true,
|
useSafeArea: true,
|
||||||
builder:
|
builder: (context) => ThoughtSheet(
|
||||||
(context) => ThoughtSheet(
|
initialMessage: initialMessage,
|
||||||
attachedMessages: attachedMessages,
|
attachedMessages: attachedMessages,
|
||||||
attachedPosts: attachedPosts,
|
attachedPosts: attachedPosts,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,6 +43,7 @@ class ThoughtSheet extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final chatState = useThoughtChat(
|
final chatState = useThoughtChat(
|
||||||
ref,
|
ref,
|
||||||
|
initialMessage: initialMessage,
|
||||||
attachedMessages: attachedMessages,
|
attachedMessages: attachedMessages,
|
||||||
attachedPosts: attachedPosts,
|
attachedPosts: attachedPosts,
|
||||||
);
|
);
|
||||||
@@ -75,31 +79,30 @@ class ThoughtSheet extends HookConsumerWidget {
|
|||||||
return status
|
return status
|
||||||
? chatInterface
|
? chatInterface
|
||||||
: Column(
|
: Column(
|
||||||
children: [
|
children: [
|
||||||
MaterialBanner(
|
MaterialBanner(
|
||||||
leading: const Icon(Symbols.error),
|
leading: const Icon(Symbols.error),
|
||||||
content: const Text(
|
content: const Text(
|
||||||
'You have unpaid orders. Please settle your payment to continue using the service.',
|
'You have unpaid orders. Please settle your payment to continue using the service.',
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
retry();
|
|
||||||
},
|
|
||||||
child: Text('retry'.tr()),
|
|
||||||
),
|
),
|
||||||
],
|
actions: [
|
||||||
),
|
TextButton(
|
||||||
Expanded(child: chatInterface),
|
onPressed: () {
|
||||||
],
|
retry();
|
||||||
);
|
},
|
||||||
|
child: Text('retry'.tr()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Expanded(child: chatInterface),
|
||||||
|
],
|
||||||
|
);
|
||||||
},
|
},
|
||||||
orElse:
|
orElse: () => ThoughtChatInterface(
|
||||||
() => ThoughtChatInterface(
|
attachedMessages: attachedMessages,
|
||||||
attachedMessages: attachedMessages,
|
attachedPosts: attachedPosts,
|
||||||
attachedPosts: attachedPosts,
|
),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class TrayService {
|
|||||||
break;
|
break;
|
||||||
case 'exit_app':
|
case 'exit_app':
|
||||||
windowManager.destroy();
|
windowManager.destroy();
|
||||||
break;
|
exit(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,3 +23,31 @@ class OidcAuthCallbackEvent {
|
|||||||
|
|
||||||
const OidcAuthCallbackEvent(this.challengeId);
|
const OidcAuthCallbackEvent(this.challengeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Event fired to trigger the command palette
|
||||||
|
class CommandPaletteTriggerEvent {
|
||||||
|
const CommandPaletteTriggerEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Event fired to show the compose post sheet
|
||||||
|
class ShowComposeSheetEvent {
|
||||||
|
const ShowComposeSheetEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Event fired to show the notification sheet
|
||||||
|
class ShowNotificationSheetEvent {
|
||||||
|
const ShowNotificationSheetEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Event fired to show the thought sheet
|
||||||
|
class ShowThoughtSheetEvent {
|
||||||
|
final String? initialMessage;
|
||||||
|
final List<Map<String, dynamic>> attachedMessages;
|
||||||
|
final List<String> attachedPosts;
|
||||||
|
|
||||||
|
const ShowThoughtSheetEvent({
|
||||||
|
this.initialMessage,
|
||||||
|
this.attachedMessages = const [],
|
||||||
|
this.attachedPosts = const [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ import 'package:dio/dio.dart';
|
|||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:island/main.dart';
|
import 'package:island/main.dart';
|
||||||
|
import 'package:island/pods/config.dart';
|
||||||
import 'package:island/route.dart';
|
import 'package:island/route.dart';
|
||||||
import 'package:island/models/account.dart';
|
import 'package:island/models/account.dart';
|
||||||
import 'package:island/pods/websocket.dart';
|
import 'package:island/pods/websocket.dart';
|
||||||
@@ -89,6 +91,7 @@ StreamSubscription<WebSocketPacket> setupNotificationListener(
|
|||||||
BuildContext context,
|
BuildContext context,
|
||||||
WidgetRef ref,
|
WidgetRef ref,
|
||||||
) {
|
) {
|
||||||
|
final settings = ref.watch(appSettingsProvider);
|
||||||
final ws = ref.watch(websocketProvider);
|
final ws = ref.watch(websocketProvider);
|
||||||
return ws.dataStream.listen((pkt) async {
|
return ws.dataStream.listen((pkt) async {
|
||||||
if (pkt.type == "notifications.new") {
|
if (pkt.type == "notifications.new") {
|
||||||
@@ -98,6 +101,9 @@ StreamSubscription<WebSocketPacket> setupNotificationListener(
|
|||||||
talker.info(
|
talker.info(
|
||||||
'[Notification] Showing in-app notification: ${notification.title}',
|
'[Notification] Showing in-app notification: ${notification.title}',
|
||||||
);
|
);
|
||||||
|
if (settings.notifyWithHaptic) {
|
||||||
|
HapticFeedback.heavyImpact();
|
||||||
|
}
|
||||||
showTopSnackBar(
|
showTopSnackBar(
|
||||||
globalOverlay.currentState!,
|
globalOverlay.currentState!,
|
||||||
Center(
|
Center(
|
||||||
@@ -115,12 +121,12 @@ StreamSubscription<WebSocketPacket> setupNotificationListener(
|
|||||||
right: 16,
|
right: 16,
|
||||||
top:
|
top:
|
||||||
(!kIsWeb &&
|
(!kIsWeb &&
|
||||||
(Platform.isMacOS ||
|
(Platform.isMacOS ||
|
||||||
Platform.isWindows ||
|
Platform.isWindows ||
|
||||||
Platform.isLinux))
|
Platform.isLinux))
|
||||||
? 28
|
? 28
|
||||||
// ignore: use_build_context_synchronously
|
// ignore: use_build_context_synchronously
|
||||||
: MediaQuery.of(context).padding.top + 16,
|
: MediaQuery.of(context).padding.top + 16,
|
||||||
bottom: 16,
|
bottom: 16,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -219,20 +219,20 @@ class AccountName extends StatelessWidget {
|
|||||||
if (account.automatedId != null)
|
if (account.automatedId != null)
|
||||||
hideOverlay
|
hideOverlay
|
||||||
? Icon(
|
? Icon(
|
||||||
Symbols.smart_toy,
|
|
||||||
size: 16,
|
|
||||||
color: nameStyle.color,
|
|
||||||
fill: 1,
|
|
||||||
)
|
|
||||||
: Tooltip(
|
|
||||||
message: 'accountAutomated'.tr(),
|
|
||||||
child: Icon(
|
|
||||||
Symbols.smart_toy,
|
Symbols.smart_toy,
|
||||||
size: 16,
|
size: 16,
|
||||||
color: nameStyle.color,
|
color: nameStyle.color,
|
||||||
fill: 1,
|
fill: 1,
|
||||||
|
)
|
||||||
|
: Tooltip(
|
||||||
|
message: 'accountAutomated'.tr(),
|
||||||
|
child: Icon(
|
||||||
|
Symbols.smart_toy,
|
||||||
|
size: 16,
|
||||||
|
color: nameStyle.color,
|
||||||
|
fill: 1,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -275,20 +275,20 @@ class AccountName extends StatelessWidget {
|
|||||||
if (account.automatedId != null)
|
if (account.automatedId != null)
|
||||||
hideOverlay
|
hideOverlay
|
||||||
? Icon(
|
? Icon(
|
||||||
Symbols.smart_toy,
|
|
||||||
size: 16,
|
|
||||||
color: nameStyle.color,
|
|
||||||
fill: 1,
|
|
||||||
)
|
|
||||||
: Tooltip(
|
|
||||||
message: 'accountAutomated'.tr(),
|
|
||||||
child: Icon(
|
|
||||||
Symbols.smart_toy,
|
Symbols.smart_toy,
|
||||||
size: 16,
|
size: 16,
|
||||||
color: nameStyle.color,
|
color: nameStyle.color,
|
||||||
fill: 1,
|
fill: 1,
|
||||||
|
)
|
||||||
|
: Tooltip(
|
||||||
|
message: 'accountAutomated'.tr(),
|
||||||
|
child: Icon(
|
||||||
|
Symbols.smart_toy,
|
||||||
|
size: 16,
|
||||||
|
color: nameStyle.color,
|
||||||
|
fill: 1,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -310,29 +310,28 @@ class VerificationMark extends StatelessWidget {
|
|||||||
? kVerificationMarkIcons[mark.type]
|
? kVerificationMarkIcons[mark.type]
|
||||||
: Symbols.verified,
|
: Symbols.verified,
|
||||||
size: 16,
|
size: 16,
|
||||||
color:
|
color: (kVerificationMarkColors.length > mark.type && mark.type >= 0)
|
||||||
(kVerificationMarkColors.length > mark.type && mark.type >= 0)
|
? kVerificationMarkColors[mark.type]
|
||||||
? kVerificationMarkColors[mark.type]
|
: Colors.blue,
|
||||||
: Colors.blue,
|
|
||||||
fill: 1,
|
fill: 1,
|
||||||
);
|
);
|
||||||
|
|
||||||
return hideOverlay
|
return hideOverlay
|
||||||
? icon
|
? icon
|
||||||
: Tooltip(
|
: Tooltip(
|
||||||
richMessage: TextSpan(
|
richMessage: TextSpan(
|
||||||
text: mark.title ?? 'No title',
|
text: mark.title ?? 'No title',
|
||||||
children: [
|
children: [
|
||||||
TextSpan(text: '\n'),
|
TextSpan(text: '\n'),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: mark.description ?? 'descriptionNone'.tr(),
|
text: mark.description ?? 'descriptionNone'.tr(),
|
||||||
style: TextStyle(fontWeight: FontWeight.normal),
|
style: TextStyle(fontWeight: FontWeight.normal),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
child: icon,
|
child: icon,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,19 +383,19 @@ class StellarMembershipMark extends StatelessWidget {
|
|||||||
return hideOverlay
|
return hideOverlay
|
||||||
? icon
|
? icon
|
||||||
: Tooltip(
|
: Tooltip(
|
||||||
richMessage: TextSpan(
|
richMessage: TextSpan(
|
||||||
text: 'stellarMembership'.tr(),
|
text: 'stellarMembership'.tr(),
|
||||||
children: [
|
children: [
|
||||||
TextSpan(text: '\n'),
|
TextSpan(text: '\n'),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: 'currentMembershipMember'.tr(args: [tierName]),
|
text: 'currentMembershipMember'.tr(args: [tierName]),
|
||||||
style: TextStyle(fontWeight: FontWeight.normal),
|
style: TextStyle(fontWeight: FontWeight.normal),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
child: icon,
|
child: icon,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,10 +413,9 @@ class VerificationStatusCard extends StatelessWidget {
|
|||||||
? kVerificationMarkIcons[mark.type]
|
? kVerificationMarkIcons[mark.type]
|
||||||
: Symbols.verified,
|
: Symbols.verified,
|
||||||
size: 32,
|
size: 32,
|
||||||
color:
|
color: (kVerificationMarkColors.length > mark.type && mark.type >= 0)
|
||||||
(kVerificationMarkColors.length > mark.type && mark.type >= 0)
|
? kVerificationMarkColors[mark.type]
|
||||||
? kVerificationMarkColors[mark.type]
|
: Colors.blue,
|
||||||
: Colors.blue,
|
|
||||||
fill: 1,
|
fill: 1,
|
||||||
).alignment(Alignment.centerLeft),
|
).alignment(Alignment.centerLeft),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
|
|||||||
@@ -96,28 +96,28 @@ void showLoadingModal(BuildContext context) {
|
|||||||
if (_loadingOverlay != null) return;
|
if (_loadingOverlay != null) return;
|
||||||
|
|
||||||
_loadingOverlay = OverlayEntry(
|
_loadingOverlay = OverlayEntry(
|
||||||
builder:
|
builder: (context) => _FadeOverlay(
|
||||||
(context) => _FadeOverlay(
|
key: _loadingOverlayKey,
|
||||||
key: _loadingOverlayKey,
|
child: Material(
|
||||||
child: Material(
|
color: Colors.black54,
|
||||||
color: Colors.black54,
|
child: Center(
|
||||||
child: Center(
|
child: AlertDialog(
|
||||||
child: Material(
|
content: Row(
|
||||||
color: Theme.of(context).colorScheme.surface,
|
mainAxisSize: MainAxisSize.min,
|
||||||
borderRadius: BorderRadius.circular(8),
|
children: [
|
||||||
elevation: 4,
|
CircularProgressIndicator(
|
||||||
child: Column(
|
year2023: false,
|
||||||
mainAxisSize: MainAxisSize.min,
|
padding: EdgeInsets.zero,
|
||||||
children: [
|
).width(28).height(28).padding(horizontal: 8),
|
||||||
CircularProgressIndicator(year2023: false),
|
const Gap(16),
|
||||||
const Gap(24),
|
Text('loading'.tr()),
|
||||||
Text('loading'.tr()),
|
],
|
||||||
],
|
|
||||||
).padding(all: 32),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
contentPadding: EdgeInsets.symmetric(horizontal: 32, vertical: 24),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
Overlay.of(context).insert(_loadingOverlay!);
|
Overlay.of(context).insert(_loadingOverlay!);
|
||||||
@@ -187,40 +187,39 @@ Future<T?> showOverlayDialog<T>({
|
|||||||
}
|
}
|
||||||
|
|
||||||
entry = OverlayEntry(
|
entry = OverlayEntry(
|
||||||
builder:
|
builder: (context) => _FadeOverlay(
|
||||||
(context) => _FadeOverlay(
|
key: key,
|
||||||
key: key,
|
duration: const Duration(milliseconds: 150),
|
||||||
duration: const Duration(milliseconds: 150),
|
curve: Curves.easeOut,
|
||||||
curve: Curves.easeOut,
|
builder: (context, animation) {
|
||||||
builder: (context, animation) {
|
return Stack(
|
||||||
return Stack(
|
children: [
|
||||||
children: [
|
Positioned.fill(
|
||||||
Positioned.fill(
|
child: FadeTransition(
|
||||||
child: FadeTransition(
|
opacity: animation,
|
||||||
opacity: animation,
|
child: GestureDetector(
|
||||||
child: GestureDetector(
|
onTap: barrierDismissible ? () => close(null) : null,
|
||||||
onTap: barrierDismissible ? () => close(null) : null,
|
behavior: HitTestBehavior.opaque,
|
||||||
behavior: HitTestBehavior.opaque,
|
child: const ColoredBox(color: Colors.black54),
|
||||||
child: const ColoredBox(color: Colors.black54),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Center(
|
),
|
||||||
child: SlideTransition(
|
),
|
||||||
position: Tween<Offset>(
|
Center(
|
||||||
begin: const Offset(0, 0.05),
|
child: SlideTransition(
|
||||||
end: Offset.zero,
|
position: Tween<Offset>(
|
||||||
).animate(animation),
|
begin: const Offset(0, 0.05),
|
||||||
child: FadeTransition(
|
end: Offset.zero,
|
||||||
opacity: animation,
|
).animate(animation),
|
||||||
child: builder(context, close),
|
child: FadeTransition(
|
||||||
),
|
opacity: animation,
|
||||||
),
|
child: builder(context, close),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
);
|
),
|
||||||
},
|
],
|
||||||
),
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
_activeOverlayDialogs.add(() => close(null));
|
_activeOverlayDialogs.add(() => close(null));
|
||||||
@@ -252,77 +251,75 @@ void showErrorAlert(dynamic err, {IconData? icon}) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
showOverlayDialog<void>(
|
showOverlayDialog<void>(
|
||||||
builder:
|
builder: (context, close) => ConstrainedBox(
|
||||||
(context, close) => ConstrainedBox(
|
constraints: const BoxConstraints(maxWidth: kDialogMaxWidth),
|
||||||
constraints: const BoxConstraints(maxWidth: kDialogMaxWidth),
|
child: AlertDialog(
|
||||||
child: AlertDialog(
|
title: null,
|
||||||
title: null,
|
titlePadding: EdgeInsets.zero,
|
||||||
titlePadding: EdgeInsets.zero,
|
contentPadding: const EdgeInsets.fromLTRB(24, 24, 24, 0),
|
||||||
contentPadding: const EdgeInsets.fromLTRB(24, 24, 24, 0),
|
content: Column(
|
||||||
content: Column(
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisSize: MainAxisSize.min,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
children: [
|
||||||
children: [
|
Icon(
|
||||||
Icon(
|
icon ?? Icons.error_outline_rounded,
|
||||||
icon ?? Icons.error_outline_rounded,
|
size: 48,
|
||||||
size: 48,
|
color: Theme.of(context).colorScheme.error,
|
||||||
color: Theme.of(context).colorScheme.error,
|
|
||||||
),
|
|
||||||
const Gap(16),
|
|
||||||
Text(
|
|
||||||
'somethingWentWrong'.tr(),
|
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
|
||||||
),
|
|
||||||
const Gap(8),
|
|
||||||
Text(text),
|
|
||||||
const Gap(8),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
actions: [
|
const Gap(16),
|
||||||
TextButton(
|
Text(
|
||||||
onPressed: () => close(null),
|
'somethingWentWrong'.tr(),
|
||||||
child: Text(MaterialLocalizations.of(context).okButtonLabel),
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
),
|
),
|
||||||
],
|
const Gap(8),
|
||||||
),
|
Text(text),
|
||||||
|
const Gap(8),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => close(null),
|
||||||
|
child: Text(MaterialLocalizations.of(context).okButtonLabel),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void showInfoAlert(String message, String title, {IconData? icon}) {
|
void showInfoAlert(String message, String title, {IconData? icon}) {
|
||||||
showOverlayDialog<void>(
|
showOverlayDialog<void>(
|
||||||
builder:
|
builder: (context, close) => ConstrainedBox(
|
||||||
(context, close) => ConstrainedBox(
|
constraints: const BoxConstraints(maxWidth: kDialogMaxWidth),
|
||||||
constraints: const BoxConstraints(maxWidth: kDialogMaxWidth),
|
child: AlertDialog(
|
||||||
child: AlertDialog(
|
title: null,
|
||||||
title: null,
|
titlePadding: EdgeInsets.zero,
|
||||||
titlePadding: EdgeInsets.zero,
|
contentPadding: const EdgeInsets.fromLTRB(24, 24, 24, 0),
|
||||||
contentPadding: const EdgeInsets.fromLTRB(24, 24, 24, 0),
|
content: Column(
|
||||||
content: Column(
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisSize: MainAxisSize.min,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
children: [
|
||||||
children: [
|
Icon(
|
||||||
Icon(
|
icon ?? Symbols.info_rounded,
|
||||||
icon ?? Symbols.info_rounded,
|
fill: 1,
|
||||||
fill: 1,
|
size: 48,
|
||||||
size: 48,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
const Gap(16),
|
|
||||||
Text(title, style: Theme.of(context).textTheme.titleLarge),
|
|
||||||
const Gap(8),
|
|
||||||
Text(message),
|
|
||||||
const Gap(8),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
actions: [
|
const Gap(16),
|
||||||
TextButton(
|
Text(title, style: Theme.of(context).textTheme.titleLarge),
|
||||||
onPressed: () => close(null),
|
const Gap(8),
|
||||||
child: Text(MaterialLocalizations.of(context).okButtonLabel),
|
Text(message),
|
||||||
),
|
const Gap(8),
|
||||||
],
|
],
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => close(null),
|
||||||
|
child: Text(MaterialLocalizations.of(context).okButtonLabel),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,50 +330,46 @@ Future<bool> showConfirmAlert(
|
|||||||
bool isDanger = false,
|
bool isDanger = false,
|
||||||
}) async {
|
}) async {
|
||||||
final result = await showOverlayDialog<bool>(
|
final result = await showOverlayDialog<bool>(
|
||||||
builder:
|
builder: (context, close) => ConstrainedBox(
|
||||||
(context, close) => ConstrainedBox(
|
constraints: const BoxConstraints(maxWidth: kDialogMaxWidth),
|
||||||
constraints: const BoxConstraints(maxWidth: kDialogMaxWidth),
|
child: AlertDialog(
|
||||||
child: AlertDialog(
|
title: null,
|
||||||
title: null,
|
titlePadding: EdgeInsets.zero,
|
||||||
titlePadding: EdgeInsets.zero,
|
contentPadding: const EdgeInsets.fromLTRB(24, 24, 24, 0),
|
||||||
contentPadding: const EdgeInsets.fromLTRB(24, 24, 24, 0),
|
content: Column(
|
||||||
content: Column(
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisSize: MainAxisSize.min,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
children: [
|
||||||
children: [
|
Icon(
|
||||||
Icon(
|
icon ?? Symbols.help_rounded,
|
||||||
icon ?? Symbols.help_rounded,
|
size: 48,
|
||||||
size: 48,
|
fill: 1,
|
||||||
fill: 1,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
const Gap(16),
|
|
||||||
Text(title, style: Theme.of(context).textTheme.titleLarge),
|
|
||||||
const Gap(8),
|
|
||||||
Text(message),
|
|
||||||
const Gap(8),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
actions: [
|
const Gap(16),
|
||||||
TextButton(
|
Text(title, style: Theme.of(context).textTheme.titleLarge),
|
||||||
onPressed: () => close(false),
|
const Gap(8),
|
||||||
child: Text(
|
Text(message),
|
||||||
MaterialLocalizations.of(context).cancelButtonLabel,
|
const Gap(8),
|
||||||
),
|
],
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => close(true),
|
|
||||||
style:
|
|
||||||
isDanger
|
|
||||||
? TextButton.styleFrom(
|
|
||||||
foregroundColor: Theme.of(context).colorScheme.error,
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
child: Text(MaterialLocalizations.of(context).okButtonLabel),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => close(false),
|
||||||
|
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => close(true),
|
||||||
|
style: isDanger
|
||||||
|
? TextButton.styleFrom(
|
||||||
|
foregroundColor: Theme.of(context).colorScheme.error,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
child: Text(MaterialLocalizations.of(context).okButtonLabel),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
return result ?? false;
|
return result ?? false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,15 +8,19 @@ import 'package:gap/gap.dart';
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:hotkey_manager/hotkey_manager.dart';
|
||||||
import 'package:island/pods/config.dart';
|
import 'package:island/pods/config.dart';
|
||||||
import 'package:island/route.dart';
|
import 'package:island/route.dart';
|
||||||
import 'package:island/pods/userinfo.dart';
|
import 'package:island/pods/userinfo.dart';
|
||||||
import 'package:island/pods/websocket.dart';
|
import 'package:island/pods/websocket.dart';
|
||||||
|
import 'package:island/services/event_bus.dart';
|
||||||
import 'package:island/services/responsive.dart';
|
import 'package:island/services/responsive.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
|
import 'package:island/widgets/cmp/pattle.dart';
|
||||||
import 'package:island/widgets/upload_overlay.dart';
|
import 'package:island/widgets/upload_overlay.dart';
|
||||||
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:shake/shake.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
@@ -36,6 +40,13 @@ class WindowScaffold extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final isMaximized = useState(false);
|
final isMaximized = useState(false);
|
||||||
|
final showPalette = useState(false);
|
||||||
|
final keyboardFocusNode = useFocusNode();
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
keyboardFocusNode.requestFocus();
|
||||||
|
return null;
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Add window resize listener for desktop platforms
|
// Add window resize listener for desktop platforms
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
@@ -68,6 +79,15 @@ class WindowScaffold extends HookConsumerWidget {
|
|||||||
return null;
|
return null;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Event bus listener for command palette
|
||||||
|
final subscription = useMemoized(
|
||||||
|
() => eventBus.on<CommandPaletteTriggerEvent>().listen(
|
||||||
|
(_) => showPalette.value = true,
|
||||||
|
),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
useEffect(() => subscription.cancel, [subscription]);
|
||||||
|
|
||||||
final router = ref.watch(routerProvider);
|
final router = ref.watch(routerProvider);
|
||||||
|
|
||||||
final pageActionsButton = [
|
final pageActionsButton = [
|
||||||
@@ -92,135 +112,166 @@ class WindowScaffold extends HookConsumerWidget {
|
|||||||
const Gap(8),
|
const Gap(8),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
final popHotKey = HotKey(
|
||||||
|
identifier: 'return_previous_page',
|
||||||
|
key: PhysicalKeyboardKey.escape,
|
||||||
|
scope: HotKeyScope.inapp,
|
||||||
|
);
|
||||||
|
final cmpHotKey = HotKey(
|
||||||
|
identifier: 'open_command_pattle',
|
||||||
|
key: PhysicalKeyboardKey.tab,
|
||||||
|
modifiers: [HotKeyModifier.shift],
|
||||||
|
scope: HotKeyScope.inapp,
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
hotKeyManager.register(
|
||||||
|
popHotKey,
|
||||||
|
keyDownHandler: (_) {
|
||||||
|
if (closeTopmostOverlayDialog()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no overlay to close, pop the route
|
||||||
|
if (ref.watch(routerProvider).canPop()) {
|
||||||
|
ref.read(routerProvider).pop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
hotKeyManager.register(
|
||||||
|
cmpHotKey,
|
||||||
|
keyDownHandler: (_) {
|
||||||
|
showPalette.value = true;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
ShakeDetector detector = ShakeDetector.autoStart(
|
||||||
|
onPhoneShake: (_) {
|
||||||
|
showPalette.value = true;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return () {
|
||||||
|
hotKeyManager.unregister(popHotKey);
|
||||||
|
hotKeyManager.unregister(cmpHotKey);
|
||||||
|
detector.stopListening();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
if (!kIsWeb &&
|
if (!kIsWeb &&
|
||||||
(Platform.isWindows || Platform.isLinux || Platform.isMacOS)) {
|
(Platform.isWindows || Platform.isLinux || Platform.isMacOS)) {
|
||||||
return Shortcuts(
|
return Material(
|
||||||
shortcuts: <LogicalKeySet, Intent>{
|
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
LogicalKeySet(LogicalKeyboardKey.escape): const PopIntent(),
|
child: Stack(
|
||||||
},
|
fit: StackFit.expand,
|
||||||
child: Actions(
|
children: [
|
||||||
actions: <Type, Action<Intent>>{PopIntent: PopAction(ref)},
|
Column(
|
||||||
child: Material(
|
|
||||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
|
||||||
child: Stack(
|
|
||||||
fit: StackFit.expand,
|
|
||||||
children: [
|
children: [
|
||||||
Column(
|
DragToMoveArea(
|
||||||
children: [
|
child: Platform.isMacOS
|
||||||
DragToMoveArea(
|
? Stack(
|
||||||
child:
|
alignment: Alignment.center,
|
||||||
Platform.isMacOS
|
children: [
|
||||||
? Stack(
|
if (isWideScreen(context))
|
||||||
alignment: Alignment.center,
|
Row(
|
||||||
children: [
|
children: [
|
||||||
if (isWideScreen(context))
|
const Spacer(),
|
||||||
Row(
|
...pageActionsButton,
|
||||||
children: [
|
|
||||||
const Spacer(),
|
|
||||||
...pageActionsButton,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
else
|
|
||||||
SizedBox(height: 32),
|
|
||||||
Text(
|
|
||||||
'Solar Network',
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
color:
|
|
||||||
Theme.of(
|
|
||||||
context,
|
|
||||||
).colorScheme.onSurface,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
: Row(
|
else
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
SizedBox(height: 32),
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
Text(
|
||||||
|
'Solar Network',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Image.asset(
|
||||||
child: Row(
|
Theme.of(context).brightness ==
|
||||||
children: [
|
Brightness.dark
|
||||||
Image.asset(
|
? 'assets/icons/icon-dark.png'
|
||||||
Theme.of(context).brightness ==
|
: 'assets/icons/icon.png',
|
||||||
Brightness.dark
|
width: 20,
|
||||||
? 'assets/icons/icon-dark.png'
|
height: 20,
|
||||||
: 'assets/icons/icon.png',
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Text(
|
|
||||||
'Solar Network',
|
|
||||||
textAlign: TextAlign.start,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).padding(horizontal: 12, vertical: 5),
|
|
||||||
),
|
),
|
||||||
IconButton(
|
const SizedBox(width: 8),
|
||||||
icon: Icon(Symbols.minimize),
|
Text(
|
||||||
onPressed: () => windowManager.minimize(),
|
'Solar Network',
|
||||||
iconSize: 16,
|
textAlign: TextAlign.start,
|
||||||
padding: EdgeInsets.all(8),
|
|
||||||
constraints: BoxConstraints(),
|
|
||||||
color: Theme.of(context).iconTheme.color,
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(
|
|
||||||
isMaximized.value
|
|
||||||
? Symbols.fullscreen_exit
|
|
||||||
: Symbols.fullscreen,
|
|
||||||
),
|
|
||||||
onPressed: () async {
|
|
||||||
if (await windowManager.isMaximized()) {
|
|
||||||
windowManager.restore();
|
|
||||||
} else {
|
|
||||||
windowManager.maximize();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
iconSize: 16,
|
|
||||||
padding: EdgeInsets.all(8),
|
|
||||||
constraints: BoxConstraints(),
|
|
||||||
color: Theme.of(context).iconTheme.color,
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(Symbols.close),
|
|
||||||
onPressed: () => windowManager.hide(),
|
|
||||||
iconSize: 16,
|
|
||||||
padding: EdgeInsets.all(8),
|
|
||||||
constraints: BoxConstraints(),
|
|
||||||
color: Theme.of(context).iconTheme.color,
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
).padding(horizontal: 12, vertical: 5),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Symbols.minimize),
|
||||||
|
onPressed: () => windowManager.minimize(),
|
||||||
|
iconSize: 16,
|
||||||
|
padding: EdgeInsets.all(8),
|
||||||
|
constraints: BoxConstraints(),
|
||||||
|
color: Theme.of(context).iconTheme.color,
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
isMaximized.value
|
||||||
|
? Symbols.fullscreen_exit
|
||||||
|
: Symbols.fullscreen,
|
||||||
),
|
),
|
||||||
),
|
onPressed: () async {
|
||||||
Expanded(child: child),
|
if (await windowManager.isMaximized()) {
|
||||||
],
|
windowManager.restore();
|
||||||
|
} else {
|
||||||
|
windowManager.maximize();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
iconSize: 16,
|
||||||
|
padding: EdgeInsets.all(8),
|
||||||
|
constraints: BoxConstraints(),
|
||||||
|
color: Theme.of(context).iconTheme.color,
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Symbols.close),
|
||||||
|
onPressed: () => windowManager.hide(),
|
||||||
|
iconSize: 16,
|
||||||
|
padding: EdgeInsets.all(8),
|
||||||
|
constraints: BoxConstraints(),
|
||||||
|
color: Theme.of(context).iconTheme.color,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
_WebSocketIndicator(),
|
Expanded(child: child),
|
||||||
const UploadOverlay(),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
_WebSocketIndicator(),
|
||||||
|
const UploadOverlay(),
|
||||||
|
if (showPalette.value)
|
||||||
|
CommandPattleWidget(onDismiss: () => showPalette.value = false),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Shortcuts(
|
return Stack(
|
||||||
shortcuts: <LogicalKeySet, Intent>{
|
fit: StackFit.expand,
|
||||||
LogicalKeySet(LogicalKeyboardKey.escape): const PopIntent(),
|
children: [
|
||||||
},
|
Positioned.fill(child: child),
|
||||||
child: Actions(
|
_WebSocketIndicator(),
|
||||||
actions: <Type, Action<Intent>>{PopIntent: PopAction(ref)},
|
const UploadOverlay(),
|
||||||
child: Stack(
|
if (showPalette.value)
|
||||||
fit: StackFit.expand,
|
CommandPattleWidget(onDismiss: () => showPalette.value = false),
|
||||||
children: [
|
],
|
||||||
Positioned.fill(child: child),
|
|
||||||
_WebSocketIndicator(),
|
|
||||||
const UploadOverlay(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -352,29 +403,6 @@ class AppScaffold extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PopIntent extends Intent {
|
|
||||||
const PopIntent();
|
|
||||||
}
|
|
||||||
|
|
||||||
class PopAction extends Action<PopIntent> {
|
|
||||||
final WidgetRef ref;
|
|
||||||
|
|
||||||
PopAction(this.ref);
|
|
||||||
|
|
||||||
@override
|
|
||||||
void invoke(PopIntent intent) {
|
|
||||||
// First, try to close any overlay dialogs
|
|
||||||
if (closeTopmostOverlayDialog()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no overlay to close, pop the route
|
|
||||||
if (ref.watch(routerProvider).canPop()) {
|
|
||||||
ref.read(routerProvider).pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class PageBackButton extends StatelessWidget {
|
class PageBackButton extends StatelessWidget {
|
||||||
final Color? color;
|
final Color? color;
|
||||||
final List<Shadow>? shadows;
|
final List<Shadow>? shadows;
|
||||||
@@ -407,8 +435,8 @@ class PageBackButton extends StatelessWidget {
|
|||||||
color: color,
|
color: color,
|
||||||
context.canPop()
|
context.canPop()
|
||||||
? (!kIsWeb && (Platform.isMacOS || Platform.isIOS))
|
? (!kIsWeb && (Platform.isMacOS || Platform.isIOS))
|
||||||
? Symbols.arrow_back_ios_new
|
? Symbols.arrow_back_ios_new
|
||||||
: Symbols.arrow_back
|
: Symbols.arrow_back
|
||||||
: Symbols.home,
|
: Symbols.home,
|
||||||
shadows: shadows,
|
shadows: shadows,
|
||||||
),
|
),
|
||||||
@@ -463,11 +491,10 @@ class AppBackground extends ConsumerWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
loading: () => const SizedBox(),
|
loading: () => const SizedBox(),
|
||||||
error:
|
error: (_, _) => Material(
|
||||||
(_, _) => Material(
|
color: Theme.of(context).colorScheme.surface,
|
||||||
color: Theme.of(context).colorScheme.surface,
|
child: child,
|
||||||
child: child,
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -496,53 +523,113 @@ class _WebSocketIndicator extends HookConsumerWidget {
|
|||||||
final isDesktop =
|
final isDesktop =
|
||||||
!kIsWeb && (Platform.isMacOS || Platform.isWindows || Platform.isLinux);
|
!kIsWeb && (Platform.isMacOS || Platform.isWindows || Platform.isLinux);
|
||||||
|
|
||||||
|
final devicePadding = MediaQuery.of(context).padding;
|
||||||
|
|
||||||
final user = ref.watch(userInfoProvider);
|
final user = ref.watch(userInfoProvider);
|
||||||
final websocketState = ref.watch(websocketStateProvider);
|
final websocketState = ref.watch(websocketStateProvider);
|
||||||
final indicatorHeight =
|
|
||||||
MediaQuery.of(context).padding.top + (isDesktop ? 27.5 : 25);
|
|
||||||
|
|
||||||
Color indicatorColor;
|
Color indicatorColor;
|
||||||
String indicatorText;
|
String indicatorText;
|
||||||
|
Widget indicatorIcon;
|
||||||
|
bool isInteractive = true;
|
||||||
|
double opacity = 0.0;
|
||||||
|
|
||||||
if (websocketState == WebSocketState.connected()) {
|
if (websocketState == WebSocketState.connected()) {
|
||||||
indicatorColor = Colors.green;
|
indicatorColor = Colors.green;
|
||||||
indicatorText = 'connectionConnected';
|
indicatorText = 'connectionConnected';
|
||||||
|
indicatorIcon = Icon(
|
||||||
|
key: ValueKey('ws_connected'),
|
||||||
|
Symbols.power,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 16,
|
||||||
|
);
|
||||||
|
opacity = 0.0;
|
||||||
|
isInteractive = false;
|
||||||
} else if (websocketState == WebSocketState.connecting()) {
|
} else if (websocketState == WebSocketState.connecting()) {
|
||||||
indicatorColor = Colors.teal;
|
indicatorColor = Colors.teal;
|
||||||
indicatorText = 'connectionReconnecting';
|
indicatorText = 'connectionReconnecting';
|
||||||
|
indicatorIcon = SizedBox(
|
||||||
|
key: ValueKey('ws_connecting'),
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||||
|
strokeWidth: 2,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
opacity = 1.0;
|
||||||
|
isInteractive = false;
|
||||||
|
} else if (websocketState == WebSocketState.serverDown()) {
|
||||||
|
indicatorColor = Colors.red;
|
||||||
|
indicatorText = 'connectionServerDown';
|
||||||
|
isInteractive = true;
|
||||||
|
indicatorIcon = Icon(
|
||||||
|
key: ValueKey('ws_server_down'),
|
||||||
|
Symbols.power_off,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 16,
|
||||||
|
);
|
||||||
|
opacity = 1.0;
|
||||||
} else {
|
} else {
|
||||||
indicatorColor = Colors.red;
|
indicatorColor = Colors.red;
|
||||||
indicatorText = 'connectionDisconnected';
|
indicatorText = 'connectionDisconnected';
|
||||||
|
indicatorIcon = Icon(
|
||||||
|
key: ValueKey('ws_disconnected'),
|
||||||
|
Symbols.power_off,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 16,
|
||||||
|
);
|
||||||
|
opacity = 1.0;
|
||||||
|
isInteractive = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return AnimatedPositioned(
|
return Positioned(
|
||||||
duration: Duration(milliseconds: 1850),
|
top: devicePadding.top + (isDesktop ? 27.5 : 25),
|
||||||
top:
|
|
||||||
user.value == null ||
|
|
||||||
user.value == null ||
|
|
||||||
websocketState == WebSocketState.connected()
|
|
||||||
? -indicatorHeight
|
|
||||||
: 0,
|
|
||||||
curve: Curves.fastLinearToSlowEaseIn,
|
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
height: indicatorHeight,
|
|
||||||
child: IgnorePointer(
|
child: IgnorePointer(
|
||||||
child: Material(
|
ignoring: !isInteractive,
|
||||||
elevation:
|
child: Align(
|
||||||
user.value == null || websocketState == WebSocketState.connected()
|
alignment: Alignment.topCenter,
|
||||||
|
child: AnimatedOpacity(
|
||||||
|
duration: Duration(milliseconds: 300),
|
||||||
|
opacity: opacity,
|
||||||
|
child: Material(
|
||||||
|
elevation:
|
||||||
|
user.value == null ||
|
||||||
|
websocketState == WebSocketState.connected()
|
||||||
? 0
|
? 0
|
||||||
: 4,
|
: 4,
|
||||||
child: AnimatedContainer(
|
borderRadius: BorderRadius.circular(999),
|
||||||
duration: Duration(milliseconds: 300),
|
child: GestureDetector(
|
||||||
color: indicatorColor,
|
onTap: () {
|
||||||
child: Center(
|
ref.read(websocketStateProvider.notifier).manualReconnect();
|
||||||
child:
|
},
|
||||||
Text(
|
child: Container(
|
||||||
indicatorText,
|
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
style: TextStyle(color: Colors.white, fontSize: 16),
|
decoration: BoxDecoration(
|
||||||
).tr(),
|
color: indicatorColor,
|
||||||
).padding(top: MediaQuery.of(context).padding.top),
|
borderRadius: BorderRadius.circular(999),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
AnimatedSwitcher(
|
||||||
|
duration: Duration(milliseconds: 300),
|
||||||
|
child: indicatorIcon,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
indicatorText,
|
||||||
|
style: TextStyle(color: Colors.white, fontSize: 13),
|
||||||
|
).tr(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:in_app_review/in_app_review.dart';
|
||||||
import 'package:protocol_handler/protocol_handler.dart';
|
import 'package:protocol_handler/protocol_handler.dart';
|
||||||
import 'package:island/pods/activity/activity_rpc.dart';
|
import 'package:island/pods/activity/activity_rpc.dart';
|
||||||
import 'package:island/pods/config.dart';
|
import 'package:island/pods/config.dart';
|
||||||
@@ -17,109 +18,228 @@ import 'package:island/services/sharing_intent.dart';
|
|||||||
import 'package:island/services/update_service.dart';
|
import 'package:island/services/update_service.dart';
|
||||||
import 'package:island/widgets/content/network_status_sheet.dart';
|
import 'package:island/widgets/content/network_status_sheet.dart';
|
||||||
import 'package:island/widgets/tour/tour.dart';
|
import 'package:island/widgets/tour/tour.dart';
|
||||||
|
import 'package:island/widgets/post/compose_sheet.dart';
|
||||||
|
import 'package:island/screens/notification.dart';
|
||||||
|
import 'package:island/screens/thought/think_sheet.dart';
|
||||||
|
import 'package:island/services/event_bus.dart';
|
||||||
|
import 'package:snow_fall_animation/snow_fall_animation.dart';
|
||||||
import 'package:tray_manager/tray_manager.dart';
|
import 'package:tray_manager/tray_manager.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
class AppWrapper extends ConsumerStatefulWidget {
|
class AppWrapper extends HookConsumerWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
const AppWrapper({super.key, required this.child});
|
const AppWrapper({super.key, required this.child});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ConsumerState<AppWrapper> createState() => _AppWrapperState();
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
}
|
final networkStateShowing = useState(false);
|
||||||
|
final websocketState = ref.watch(websocketStateProvider);
|
||||||
|
final apiState = ref.watch(networkStatusProvider);
|
||||||
|
final isShowSnow = useState(false);
|
||||||
|
final isSnowGone = useState(false);
|
||||||
|
|
||||||
class _AppWrapperState extends ConsumerState<AppWrapper>
|
// Handle network status modal
|
||||||
with ProtocolListener, TrayListener {
|
useEffect(() {
|
||||||
StreamSubscription? ntySubs;
|
bool triedOpen = false;
|
||||||
bool networkStateShowing = false;
|
if (websocketState == WebSocketState.duplicateDevice() &&
|
||||||
|
!networkStateShowing.value &&
|
||||||
|
!triedOpen) {
|
||||||
|
networkStateShowing.value = true;
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder: (context) => NetworkStatusSheet(autoClose: true),
|
||||||
|
).then((_) => networkStateShowing.value = false);
|
||||||
|
});
|
||||||
|
triedOpen = true;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
if (apiState != NetworkStatus.online &&
|
||||||
void initState() {
|
!networkStateShowing.value &&
|
||||||
super.initState();
|
!triedOpen) {
|
||||||
protocolHandler.addListener(this);
|
networkStateShowing.value = true;
|
||||||
Future(() async {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
if (mounted) ntySubs = setupNotificationListener(context, ref);
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder: (context) => const NetworkStatusSheet(),
|
||||||
|
).then((_) => networkStateShowing.value = false);
|
||||||
|
});
|
||||||
|
triedOpen = true;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, [websocketState, apiState]);
|
||||||
|
|
||||||
|
// Initialize services and listeners
|
||||||
|
useEffect(() {
|
||||||
|
final ntySubs = setupNotificationListener(context, ref);
|
||||||
final sharingService = SharingIntentService();
|
final sharingService = SharingIntentService();
|
||||||
if (mounted) sharingService.initialize(context);
|
sharingService.initialize(context);
|
||||||
if (mounted) UpdateService().checkForUpdates(context);
|
UpdateService().checkForUpdates(context);
|
||||||
|
|
||||||
TrayService.instance.initialize(this);
|
final trayService = TrayService.instance;
|
||||||
|
trayService.initialize(
|
||||||
|
_TrayListenerImpl(
|
||||||
|
onTrayIconMouseDown: () => windowManager.show(),
|
||||||
|
onTrayIconRightMouseUp: () => trayManager.popUpContextMenu(),
|
||||||
|
onTrayMenuItemClick: (menuItem) => trayService.handleAction(menuItem),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
ref.read(rpcServerStateProvider.notifier).start();
|
ref.read(rpcServerStateProvider.notifier).start();
|
||||||
ref.read(webAuthServerStateProvider.notifier).start();
|
ref.read(webAuthServerStateProvider.notifier).start();
|
||||||
|
|
||||||
final initialUrl = await protocolHandler.getInitialUrl();
|
// Listen to special action events
|
||||||
if (initialUrl != null && mounted) {
|
final composeSheetSubs = eventBus.on<ShowComposeSheetEvent>().listen((
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
event,
|
||||||
_handleDeepLink(Uri.parse(initialUrl), ref);
|
) {
|
||||||
|
if (context.mounted) _showComposeSheet(context);
|
||||||
|
});
|
||||||
|
|
||||||
|
final notificationSheetSubs = eventBus
|
||||||
|
.on<ShowNotificationSheetEvent>()
|
||||||
|
.listen((event) {
|
||||||
|
if (context.mounted) _showNotificationSheet(context);
|
||||||
|
});
|
||||||
|
|
||||||
|
final thoughtSheetSubs = eventBus.on<ShowThoughtSheetEvent>().listen((
|
||||||
|
event,
|
||||||
|
) {
|
||||||
|
if (context.mounted) _showThoughtSheet(context, event);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Protocol handler listener
|
||||||
|
final protocolListener = _ProtocolListenerImpl(
|
||||||
|
onProtocolUrlReceived: (url) =>
|
||||||
|
_handleDeepLink(Uri.parse(url), ref, context),
|
||||||
|
);
|
||||||
|
protocolHandler.addListener(protocolListener);
|
||||||
|
|
||||||
|
// Handle initial URL
|
||||||
|
protocolHandler.getInitialUrl().then((initialUrl) {
|
||||||
|
if (initialUrl != null) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
_handleDeepLink(Uri.parse(initialUrl), ref, context);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return () {
|
||||||
|
protocolHandler.removeListener(protocolListener);
|
||||||
|
ref.read(rpcServerProvider).stop();
|
||||||
|
trayService.dispose(
|
||||||
|
_TrayListenerImpl(
|
||||||
|
onTrayIconMouseDown: () => {},
|
||||||
|
onTrayIconRightMouseUp: () => {},
|
||||||
|
onTrayMenuItemClick: (menuItem) => {},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
ntySubs?.cancel();
|
||||||
|
composeSheetSubs.cancel();
|
||||||
|
notificationSheetSubs.cancel();
|
||||||
|
thoughtSheetSubs.cancel();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
final settings = ref.watch(appSettingsProvider);
|
||||||
|
final settingsNotifier = ref.watch(appSettingsProvider.notifier);
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
if (settings.defaultScreen != null &&
|
||||||
|
settings.defaultScreen != 'dashboard') {
|
||||||
|
Future(() {
|
||||||
|
ref.read(routerProvider).goNamed(settings.defaultScreen!);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
return null;
|
||||||
}
|
}, []);
|
||||||
|
|
||||||
@override
|
final now = DateTime.now();
|
||||||
void dispose() {
|
final doesShowSnow =
|
||||||
protocolHandler.removeListener(this);
|
settings.festivalFeatures &&
|
||||||
ref.read(rpcServerProvider).stop();
|
now.month == 12 &&
|
||||||
TrayService.instance.dispose(this);
|
(now.day >= 22 && now.day <= 28);
|
||||||
ntySubs?.cancel();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
useEffect(() {
|
||||||
Widget build(BuildContext context) {
|
final now = DateTime.now();
|
||||||
final wsNotifier = ref.watch(websocketStateProvider.notifier);
|
if (doesShowSnow) {
|
||||||
final websocketState = ref.watch(websocketStateProvider);
|
isShowSnow.value = true;
|
||||||
|
Future.delayed(const Duration(seconds: 60), () {
|
||||||
if (websocketState == WebSocketState.duplicateDevice()) {
|
if (!context.mounted) return;
|
||||||
if (!networkStateShowing) {
|
isShowSnow.value = false;
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
Future.delayed(const Duration(seconds: 3), () {
|
||||||
setState(() => networkStateShowing = true);
|
if (!context.mounted) return;
|
||||||
showModalBottomSheet(
|
isSnowGone.value = true;
|
||||||
context: context,
|
});
|
||||||
isScrollControlled: true,
|
|
||||||
isDismissible: false,
|
|
||||||
builder:
|
|
||||||
(context) =>
|
|
||||||
NetworkStatusSheet(onReconnect: () => wsNotifier.connect()),
|
|
||||||
).then((_) => setState(() => networkStateShowing = false));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return TourTriggerWidget(key: UniqueKey(), child: widget.child);
|
if (settings.firstLaunchAt == null) {
|
||||||
|
settingsNotifier.setFirstLaunchAt(now.toIso8601String());
|
||||||
|
} else if (!settings.askedReview) {
|
||||||
|
final launchAt = DateTime.parse(settings.firstLaunchAt!);
|
||||||
|
final daysSinceFirstLaunch = now.difference(launchAt).inDays;
|
||||||
|
if (daysSinceFirstLaunch >= 3 &&
|
||||||
|
!kIsWeb &&
|
||||||
|
(Platform.isAndroid || Platform.isIOS || Platform.isMacOS)) {
|
||||||
|
final InAppReview inAppReview = InAppReview.instance;
|
||||||
|
Future(() async {
|
||||||
|
if (await inAppReview.isAvailable()) {
|
||||||
|
inAppReview.requestReview();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
settingsNotifier.setAskedReview(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return TourTriggerWidget(
|
||||||
|
key: const Key("app_tour_trigger"),
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
child,
|
||||||
|
if (doesShowSnow && !isSnowGone.value)
|
||||||
|
IgnorePointer(
|
||||||
|
child: AnimatedOpacity(
|
||||||
|
opacity: isShowSnow.value ? 1 : 00,
|
||||||
|
duration: const Duration(seconds: 3),
|
||||||
|
child: SnowFallAnimation(
|
||||||
|
key: const Key("app_snow_animation"),
|
||||||
|
config: SnowfallConfig(numberOfSnowflakes: 50, speed: 1.0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
void _showComposeSheet(BuildContext context) {
|
||||||
void onProtocolUrlReceived(String url) {
|
PostComposeSheet.show(context);
|
||||||
_handleDeepLink(Uri.parse(url), ref);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _trayIconPrimaryAction() {
|
void _showNotificationSheet(BuildContext context) {
|
||||||
windowManager.show();
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
useRootNavigator: true,
|
||||||
|
builder: (context) => const NotificationSheet(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _trayIconSecondaryAction() {
|
void _showThoughtSheet(BuildContext context, ShowThoughtSheetEvent event) {
|
||||||
trayManager.popUpContextMenu();
|
ThoughtSheet.show(
|
||||||
|
context,
|
||||||
|
initialMessage: event.initialMessage,
|
||||||
|
attachedMessages: event.attachedMessages,
|
||||||
|
attachedPosts: event.attachedPosts,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
void _handleDeepLink(Uri uri, WidgetRef ref, BuildContext context) async {
|
||||||
void onTrayIconMouseUp() {
|
|
||||||
_trayIconPrimaryAction();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onTrayIconRightMouseDown() {
|
|
||||||
_trayIconSecondaryAction();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onTrayMenuItemClick(MenuItem menuItem) {
|
|
||||||
TrayService.instance.handleAction(menuItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleDeepLink(Uri uri, WidgetRef ref) async {
|
|
||||||
String path = '/${uri.host}${uri.path}';
|
String path = '/${uri.host}${uri.path}';
|
||||||
|
|
||||||
// Special handling for OIDC auth callback
|
// Special handling for OIDC auth callback
|
||||||
@@ -129,9 +249,7 @@ class _AppWrapperState extends ConsumerState<AppWrapper>
|
|||||||
ref.invalidate(tokenProvider);
|
ref.invalidate(tokenProvider);
|
||||||
|
|
||||||
// Do post login tasks
|
// Do post login tasks
|
||||||
if (mounted) {
|
await performPostLogin(context, ref);
|
||||||
await performPostLogin(context, ref);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!kIsWeb &&
|
if (!kIsWeb &&
|
||||||
(Platform.isWindows || Platform.isLinux || Platform.isMacOS)) {
|
(Platform.isWindows || Platform.isLinux || Platform.isMacOS)) {
|
||||||
@@ -153,10 +271,9 @@ class _AppWrapperState extends ConsumerState<AppWrapper>
|
|||||||
|
|
||||||
final router = ref.read(routerProvider);
|
final router = ref.read(routerProvider);
|
||||||
if (uri.queryParameters.isNotEmpty) {
|
if (uri.queryParameters.isNotEmpty) {
|
||||||
path =
|
path = Uri.parse(
|
||||||
Uri.parse(
|
path,
|
||||||
path,
|
).replace(queryParameters: uri.queryParameters).toString();
|
||||||
).replace(queryParameters: uri.queryParameters).toString();
|
|
||||||
}
|
}
|
||||||
router.push(path);
|
router.push(path);
|
||||||
if (!kIsWeb &&
|
if (!kIsWeb &&
|
||||||
@@ -165,3 +282,42 @@ class _AppWrapperState extends ConsumerState<AppWrapper>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _TrayListenerImpl implements TrayListener {
|
||||||
|
final VoidCallback _primaryAction;
|
||||||
|
final VoidCallback _secondaryAction;
|
||||||
|
final void Function(MenuItem) _onTrayMenuItemClick;
|
||||||
|
|
||||||
|
_TrayListenerImpl({
|
||||||
|
required VoidCallback onTrayIconMouseDown,
|
||||||
|
required VoidCallback onTrayIconRightMouseUp,
|
||||||
|
required void Function(MenuItem) onTrayMenuItemClick,
|
||||||
|
}) : _primaryAction = onTrayIconMouseDown,
|
||||||
|
_secondaryAction = onTrayIconRightMouseUp,
|
||||||
|
_onTrayMenuItemClick = onTrayMenuItemClick;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onTrayIconMouseDown() => _primaryAction();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onTrayIconRightMouseUp() => _secondaryAction();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onTrayIconMouseUp() => _primaryAction();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onTrayIconRightMouseDown() => _secondaryAction();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onTrayMenuItemClick(MenuItem menuItem) => _onTrayMenuItemClick(menuItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ProtocolListenerImpl implements ProtocolListener {
|
||||||
|
final void Function(String) _onProtocolUrlReceived;
|
||||||
|
|
||||||
|
_ProtocolListenerImpl({required void Function(String) onProtocolUrlReceived})
|
||||||
|
: _onProtocolUrlReceived = onProtocolUrlReceived;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onProtocolUrlReceived(String url) => _onProtocolUrlReceived(url);
|
||||||
|
}
|
||||||
|
|||||||
201
lib/widgets/chat_room_widgets.dart
Normal file
201
lib/widgets/chat_room_widgets.dart
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/models/chat.dart';
|
||||||
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
|
import 'package:relative_time/relative_time.dart';
|
||||||
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
|
||||||
|
class ChatRoomAvatar extends StatelessWidget {
|
||||||
|
final SnChatRoom room;
|
||||||
|
final bool isDirect;
|
||||||
|
final AsyncValue<SnChatSummary?> summary;
|
||||||
|
final List<SnChatMember> validMembers;
|
||||||
|
|
||||||
|
const ChatRoomAvatar({
|
||||||
|
super.key,
|
||||||
|
required this.room,
|
||||||
|
required this.isDirect,
|
||||||
|
required this.summary,
|
||||||
|
required this.validMembers,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final avatarChild = (isDirect && room.picture?.id == null)
|
||||||
|
? SplitAvatarWidget(
|
||||||
|
filesId: validMembers
|
||||||
|
.map((e) => e.account.profile.picture?.id)
|
||||||
|
.toList(),
|
||||||
|
)
|
||||||
|
: room.picture?.id == null
|
||||||
|
? CircleAvatar(child: Text((room.name ?? 'DM')[0].toUpperCase()))
|
||||||
|
: ProfilePictureWidget(fileId: room.picture?.id);
|
||||||
|
|
||||||
|
final badgeChild = Badge(
|
||||||
|
isLabelVisible: summary.when(
|
||||||
|
data: (data) => (data?.unreadCount ?? 0) > 0,
|
||||||
|
loading: () => false,
|
||||||
|
error: (_, _) => false,
|
||||||
|
),
|
||||||
|
child: avatarChild,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Show realm avatar as small overlay if chat belongs to a realm
|
||||||
|
if (room.realm != null) {
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
badgeChild,
|
||||||
|
Positioned(
|
||||||
|
bottom: 0,
|
||||||
|
right: 0,
|
||||||
|
child: Container(
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.25),
|
||||||
|
blurRadius: 4,
|
||||||
|
offset: const Offset(0, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: ClipOval(
|
||||||
|
child: ProfilePictureWidget(file: room.realm!.picture),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return badgeChild;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChatRoomSubtitle extends StatelessWidget {
|
||||||
|
final SnChatRoom room;
|
||||||
|
final bool isDirect;
|
||||||
|
final List<SnChatMember> validMembers;
|
||||||
|
final AsyncValue<SnChatSummary?> summary;
|
||||||
|
final Widget? subtitle;
|
||||||
|
|
||||||
|
const ChatRoomSubtitle({
|
||||||
|
super.key,
|
||||||
|
required this.room,
|
||||||
|
required this.isDirect,
|
||||||
|
required this.validMembers,
|
||||||
|
required this.summary,
|
||||||
|
this.subtitle,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (subtitle != null) return subtitle!;
|
||||||
|
|
||||||
|
return AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
layoutBuilder: (currentChild, previousChildren) => Stack(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
children: [...previousChildren, if (currentChild != null) currentChild],
|
||||||
|
),
|
||||||
|
child: summary.when(
|
||||||
|
data: (data) => Container(
|
||||||
|
key: const ValueKey('data'),
|
||||||
|
child: data == null
|
||||||
|
? isDirect && room.description == null
|
||||||
|
? Text(
|
||||||
|
validMembers
|
||||||
|
.map((e) => '@${e.account.name}')
|
||||||
|
.join(', '),
|
||||||
|
maxLines: 1,
|
||||||
|
)
|
||||||
|
: Text(
|
||||||
|
room.description ?? 'descriptionNone'.tr(),
|
||||||
|
maxLines: 1,
|
||||||
|
)
|
||||||
|
: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
if (data.unreadCount > 0)
|
||||||
|
Text(
|
||||||
|
'unreadMessages'.plural(data.unreadCount),
|
||||||
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (data.lastMessage == null)
|
||||||
|
Text(
|
||||||
|
room.description ?? 'descriptionNone'.tr(),
|
||||||
|
maxLines: 1,
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Row(
|
||||||
|
spacing: 4,
|
||||||
|
children: [
|
||||||
|
Badge(
|
||||||
|
label: Text(data.lastMessage!.sender.account.nick),
|
||||||
|
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||||
|
backgroundColor: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.primary,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
(data.lastMessage!.content?.isNotEmpty ?? false)
|
||||||
|
? data.lastMessage!.content!
|
||||||
|
: 'messageNone'.tr(),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: Text(
|
||||||
|
RelativeTime(
|
||||||
|
context,
|
||||||
|
).format(data.lastMessage!.createdAt),
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
loading: () => Container(
|
||||||
|
key: const ValueKey('loading'),
|
||||||
|
child: Builder(
|
||||||
|
builder: (context) {
|
||||||
|
final seed = DateTime.now().microsecondsSinceEpoch;
|
||||||
|
final len = 4 + (seed % 17); // 4..20 inclusive
|
||||||
|
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
||||||
|
var s = seed;
|
||||||
|
final buffer = StringBuffer();
|
||||||
|
for (var i = 0; i < len; i++) {
|
||||||
|
s = (s * 1103515245 + 12345) & 0x7fffffff;
|
||||||
|
buffer.write(chars[s % chars.length]);
|
||||||
|
}
|
||||||
|
return Skeletonizer(
|
||||||
|
enabled: true,
|
||||||
|
child: Text(buffer.toString()),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
error: (_, _) => Container(
|
||||||
|
key: const ValueKey('error'),
|
||||||
|
child: isDirect && room.description == null
|
||||||
|
? Text(
|
||||||
|
validMembers.map((e) => '@${e.account.name}').join(', '),
|
||||||
|
maxLines: 1,
|
||||||
|
)
|
||||||
|
: Text(room.description ?? 'descriptionNone'.tr(), maxLines: 1),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/models/activity.dart';
|
import 'package:island/models/activity.dart';
|
||||||
|
import 'package:island/models/fortune.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/pods/userinfo.dart';
|
import 'package:island/pods/userinfo.dart';
|
||||||
import 'package:island/screens/auth/captcha.dart';
|
import 'package:island/screens/auth/captcha.dart';
|
||||||
@@ -17,7 +18,6 @@ import 'package:island/widgets/content/sheet.dart';
|
|||||||
import 'package:island/widgets/account/event_calendar_content.dart';
|
import 'package:island/widgets/account/event_calendar_content.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:slide_countdown/slide_countdown.dart';
|
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
part 'check_in.g.dart';
|
part 'check_in.g.dart';
|
||||||
@@ -43,12 +43,50 @@ Future<SnNotableDay?> nextNotableDay(Ref ref) async {
|
|||||||
final client = ref.watch(apiClientProvider);
|
final client = ref.watch(apiClientProvider);
|
||||||
try {
|
try {
|
||||||
final resp = await client.get('/pass/notable/me/next');
|
final resp = await client.get('/pass/notable/me/next');
|
||||||
return SnNotableDay.fromJson(resp.data);
|
final day = SnNotableDay.fromJson(resp.data);
|
||||||
|
if (day.localizableKey != null) {
|
||||||
|
final key = 'notableDay${day.localizableKey}';
|
||||||
|
if (key.trExists()) {
|
||||||
|
return day.copyWith(
|
||||||
|
localName: key.tr(),
|
||||||
|
date: day.date.toLocal().copyWith(hour: 0, second: 0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return day.copyWith(date: day.date.toLocal().copyWith(hour: 0, second: 0));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
Future<SnNotableDay?> recentNotableDay(Ref ref) async {
|
||||||
|
final client = ref.watch(apiClientProvider);
|
||||||
|
try {
|
||||||
|
final resp = await client.get('/pass/notable/me/recent');
|
||||||
|
final day = SnNotableDay.fromJson(resp.data[0]);
|
||||||
|
if (day.localizableKey != null) {
|
||||||
|
final key = 'notableDay${day.localizableKey}';
|
||||||
|
if (key.trExists()) {
|
||||||
|
return day.copyWith(
|
||||||
|
localName: key.tr(),
|
||||||
|
date: day.date.toLocal().copyWith(hour: 0, second: 0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return day.copyWith(date: day.date.toLocal().copyWith(hour: 0, second: 0));
|
||||||
|
} catch (err) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
Future<SnFortuneSaying> randomFortuneSaying(Ref ref) async {
|
||||||
|
final client = ref.watch(apiClientProvider);
|
||||||
|
final resp = await client.get('/pass/fortune/random');
|
||||||
|
return SnFortuneSaying.fromJson(resp.data[0]);
|
||||||
|
}
|
||||||
|
|
||||||
class CheckInWidget extends HookConsumerWidget {
|
class CheckInWidget extends HookConsumerWidget {
|
||||||
final EdgeInsets? margin;
|
final EdgeInsets? margin;
|
||||||
final VoidCallback? onChecked;
|
final VoidCallback? onChecked;
|
||||||
@@ -57,7 +95,6 @@ class CheckInWidget extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final todayResult = ref.watch(checkInResultTodayProvider);
|
final todayResult = ref.watch(checkInResultTodayProvider);
|
||||||
final nextNotableDay = ref.watch(nextNotableDayProvider);
|
|
||||||
|
|
||||||
// Update time every second for live progress
|
// Update time every second for live progress
|
||||||
final currentTime = useState(DateTime.now());
|
final currentTime = useState(DateTime.now());
|
||||||
@@ -68,28 +105,6 @@ class CheckInWidget extends HookConsumerWidget {
|
|||||||
return timer.cancel;
|
return timer.cancel;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
final now = currentTime.value;
|
|
||||||
|
|
||||||
final userinfo = ref.watch(userInfoProvider);
|
|
||||||
final isAdult = useMemoized(() {
|
|
||||||
final birthday = userinfo.value?.profile.birthday;
|
|
||||||
if (birthday == null) return false;
|
|
||||||
final age =
|
|
||||||
now.year -
|
|
||||||
birthday.year -
|
|
||||||
((now.month < birthday.month ||
|
|
||||||
(now.month == birthday.month && now.day < birthday.day))
|
|
||||||
? 1
|
|
||||||
: 0);
|
|
||||||
return age >= 18;
|
|
||||||
}, [userinfo]);
|
|
||||||
|
|
||||||
final progress = (now.hour * 60.0 + now.minute) / (24 * 60);
|
|
||||||
final endOfDay = DateTime(now.year, now.month, now.day, 23, 59, 59);
|
|
||||||
final timeLeft = endOfDay.difference(now);
|
|
||||||
final timeLeftFormatted =
|
|
||||||
'${timeLeft.inHours.toString().padLeft(2, '0')}:${(timeLeft.inMinutes % 60).toString().padLeft(2, '0')}:${(timeLeft.inSeconds % 60).toString().padLeft(2, '0')}';
|
|
||||||
|
|
||||||
Future<void> checkIn({String? captchatTk}) async {
|
Future<void> checkIn({String? captchatTk}) async {
|
||||||
final client = ref.read(apiClientProvider);
|
final client = ref.read(apiClientProvider);
|
||||||
try {
|
try {
|
||||||
@@ -122,58 +137,22 @@ class CheckInWidget extends HookConsumerWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
AnimatedSwitcher(
|
||||||
spacing: 6,
|
duration: const Duration(milliseconds: 300),
|
||||||
mainAxisSize: MainAxisSize.min,
|
child: todayResult.when(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
data: (result) {
|
||||||
children: [
|
return Text(
|
||||||
Icon(
|
result == null
|
||||||
switch (DateTime.now().weekday) {
|
? 'checkInNone'
|
||||||
6 || 7 => Symbols.weekend,
|
: 'checkInResultLevel${result.level}',
|
||||||
_ => isAdult ? Symbols.work : Symbols.school,
|
textAlign: TextAlign.start,
|
||||||
},
|
).tr().fontSize(15).bold();
|
||||||
fill: 1,
|
},
|
||||||
size: 16,
|
loading: () => Text('checkInNone').tr().fontSize(15).bold(),
|
||||||
).padding(right: 2),
|
error: (err, stack) =>
|
||||||
Text(
|
Text('error').tr().fontSize(15).bold(),
|
||||||
DateFormat('EEE').format(DateTime.now()),
|
),
|
||||||
).fontSize(16).bold(),
|
).padding(right: 4),
|
||||||
Text(
|
|
||||||
DateFormat('MM/dd').format(DateTime.now()),
|
|
||||||
).fontSize(16),
|
|
||||||
Tooltip(
|
|
||||||
message: timeLeftFormatted,
|
|
||||||
child: SizedBox(
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
child: CircularProgressIndicator(
|
|
||||||
trackGap: 0,
|
|
||||||
value: progress,
|
|
||||||
strokeWidth: 2,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
spacing: 5,
|
|
||||||
children: [
|
|
||||||
Text('notableDayNext')
|
|
||||||
.tr(args: [nextNotableDay.value?.localName ?? 'idk'])
|
|
||||||
.fontSize(12),
|
|
||||||
if (nextNotableDay.value != null)
|
|
||||||
SlideCountdown(
|
|
||||||
decoration: const BoxDecoration(),
|
|
||||||
style: const TextStyle(fontSize: 12),
|
|
||||||
separatorStyle: const TextStyle(fontSize: 12),
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
duration: nextNotableDay.value?.date.difference(
|
|
||||||
DateTime.now(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const Gap(2),
|
|
||||||
AnimatedSwitcher(
|
AnimatedSwitcher(
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
child: todayResult.when(
|
child: todayResult.when(
|
||||||
@@ -213,14 +192,13 @@ class CheckInWidget extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
loading: () => Text('checkInNoneHint').tr().fontSize(11),
|
loading: () => Text('checkInNoneHint').tr().fontSize(11),
|
||||||
error:
|
error: (err, stack) => Column(
|
||||||
(err, stack) => Column(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
children: [
|
||||||
children: [
|
Text('error').tr().fontSize(15).bold(),
|
||||||
Text('error').tr().fontSize(15).bold(),
|
Text(err.toString()).fontSize(11),
|
||||||
Text(err.toString()).fontSize(11),
|
],
|
||||||
],
|
),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
).alignment(Alignment.centerLeft),
|
).alignment(Alignment.centerLeft),
|
||||||
],
|
],
|
||||||
@@ -231,21 +209,6 @@ class CheckInWidget extends HookConsumerWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
spacing: 4,
|
spacing: 4,
|
||||||
children: [
|
children: [
|
||||||
AnimatedSwitcher(
|
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
child: todayResult.when(
|
|
||||||
data: (result) {
|
|
||||||
return Text(
|
|
||||||
result == null
|
|
||||||
? 'checkInNone'
|
|
||||||
: 'checkInResultLevel${result.level}',
|
|
||||||
textAlign: TextAlign.start,
|
|
||||||
).tr().fontSize(15).bold();
|
|
||||||
},
|
|
||||||
loading: () => Text('checkInNone').tr().fontSize(15).bold(),
|
|
||||||
error: (err, stack) => Text('error').tr().fontSize(15).bold(),
|
|
||||||
),
|
|
||||||
).padding(right: 4),
|
|
||||||
IconButton.outlined(
|
IconButton.outlined(
|
||||||
iconSize: 16,
|
iconSize: 16,
|
||||||
visualDensity: const VisualDensity(
|
visualDensity: const VisualDensity(
|
||||||
@@ -259,27 +222,22 @@ class CheckInWidget extends HookConsumerWidget {
|
|||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
builder:
|
builder: (context) => SheetScaffold(
|
||||||
(context) => SheetScaffold(
|
titleText: 'eventCalendar'.tr(),
|
||||||
titleText: 'eventCalendar'.tr(),
|
child: EventCalendarContent(name: 'me', isSheet: true),
|
||||||
child: EventCalendarContent(
|
),
|
||||||
name: 'me',
|
|
||||||
isSheet: true,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
icon: AnimatedSwitcher(
|
icon: AnimatedSwitcher(
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
child: todayResult.when(
|
child: todayResult.when(
|
||||||
data:
|
data: (result) => Icon(
|
||||||
(result) => Icon(
|
result == null
|
||||||
result == null
|
? Symbols.local_fire_department
|
||||||
? Symbols.local_fire_department
|
: Symbols.event,
|
||||||
: Symbols.event,
|
key: ValueKey(result != null),
|
||||||
key: ValueKey(result != null),
|
),
|
||||||
),
|
|
||||||
loading: () => const Icon(Symbols.refresh),
|
loading: () => const Icon(Symbols.refresh),
|
||||||
error: (_, _) => const Icon(Symbols.error),
|
error: (_, _) => const Icon(Symbols.error),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -86,4 +86,83 @@ final class NextNotableDayProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$nextNotableDayHash() => r'c8404308f6b0f581cc7df251bce8f3c5ac130245';
|
String _$nextNotableDayHash() => r'60d0546a086bdcb89c433c38133eb4197e4fb0a6';
|
||||||
|
|
||||||
|
@ProviderFor(recentNotableDay)
|
||||||
|
const recentNotableDayProvider = RecentNotableDayProvider._();
|
||||||
|
|
||||||
|
final class RecentNotableDayProvider
|
||||||
|
extends
|
||||||
|
$FunctionalProvider<
|
||||||
|
AsyncValue<SnNotableDay?>,
|
||||||
|
SnNotableDay?,
|
||||||
|
FutureOr<SnNotableDay?>
|
||||||
|
>
|
||||||
|
with $FutureModifier<SnNotableDay?>, $FutureProvider<SnNotableDay?> {
|
||||||
|
const RecentNotableDayProvider._()
|
||||||
|
: super(
|
||||||
|
from: null,
|
||||||
|
argument: null,
|
||||||
|
retry: null,
|
||||||
|
name: r'recentNotableDayProvider',
|
||||||
|
isAutoDispose: true,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$recentNotableDayHash();
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
$FutureProviderElement<SnNotableDay?> $createElement(
|
||||||
|
$ProviderPointer pointer,
|
||||||
|
) => $FutureProviderElement(pointer);
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<SnNotableDay?> create(Ref ref) {
|
||||||
|
return recentNotableDay(ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$recentNotableDayHash() => r'780d0f0747d753c5d535d9c2413f8e68d457d974';
|
||||||
|
|
||||||
|
@ProviderFor(randomFortuneSaying)
|
||||||
|
const randomFortuneSayingProvider = RandomFortuneSayingProvider._();
|
||||||
|
|
||||||
|
final class RandomFortuneSayingProvider
|
||||||
|
extends
|
||||||
|
$FunctionalProvider<
|
||||||
|
AsyncValue<SnFortuneSaying>,
|
||||||
|
SnFortuneSaying,
|
||||||
|
FutureOr<SnFortuneSaying>
|
||||||
|
>
|
||||||
|
with $FutureModifier<SnFortuneSaying>, $FutureProvider<SnFortuneSaying> {
|
||||||
|
const RandomFortuneSayingProvider._()
|
||||||
|
: super(
|
||||||
|
from: null,
|
||||||
|
argument: null,
|
||||||
|
retry: null,
|
||||||
|
name: r'randomFortuneSayingProvider',
|
||||||
|
isAutoDispose: true,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$randomFortuneSayingHash();
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
$FutureProviderElement<SnFortuneSaying> $createElement(
|
||||||
|
$ProviderPointer pointer,
|
||||||
|
) => $FutureProviderElement(pointer);
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<SnFortuneSaying> create(Ref ref) {
|
||||||
|
return randomFortuneSaying(ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$randomFortuneSayingHash() =>
|
||||||
|
r'861378dba8021e8555b568fb8e0390b2b24056f6';
|
||||||
|
|||||||
719
lib/widgets/cmp/pattle.dart
Normal file
719
lib/widgets/cmp/pattle.dart
Normal file
@@ -0,0 +1,719 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'dart:math' as math;
|
||||||
|
import 'dart:ui';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:island/models/chat.dart';
|
||||||
|
import 'package:island/models/route_item.dart';
|
||||||
|
import 'package:island/pods/chat/chat_room.dart';
|
||||||
|
import 'package:island/pods/chat/chat_summary.dart';
|
||||||
|
import 'package:island/pods/config.dart';
|
||||||
|
import 'package:island/pods/userinfo.dart';
|
||||||
|
import 'package:island/route.dart';
|
||||||
|
import 'package:island/services/responsive.dart';
|
||||||
|
import 'package:island/widgets/chat_room_widgets.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:relative_time/relative_time.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:island/services/event_bus.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
|
class CommandPattleWidget extends HookConsumerWidget {
|
||||||
|
final VoidCallback onDismiss;
|
||||||
|
|
||||||
|
const CommandPattleWidget({super.key, required this.onDismiss});
|
||||||
|
|
||||||
|
static List<SpecialAction> _getSpecialActions(BuildContext context) {
|
||||||
|
return [
|
||||||
|
SpecialAction(
|
||||||
|
name: 'postCompose'.tr(),
|
||||||
|
description: 'postComposeDescription'.tr(),
|
||||||
|
icon: Symbols.edit,
|
||||||
|
action: () {
|
||||||
|
eventBus.fire(const ShowComposeSheetEvent());
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SpecialAction(
|
||||||
|
name: 'notifications'.tr(),
|
||||||
|
description: 'notificationsDescription'.tr(),
|
||||||
|
searchableAliases: ['notifications', 'alert', 'bell'],
|
||||||
|
icon: Symbols.notifications,
|
||||||
|
action: () {
|
||||||
|
eventBus.fire(const ShowNotificationSheetEvent());
|
||||||
|
},
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final textController = useTextEditingController();
|
||||||
|
final focusNode = useFocusNode();
|
||||||
|
final searchQuery = useState('');
|
||||||
|
final focusedIndex = useState<int?>(null);
|
||||||
|
final scrollController = useScrollController();
|
||||||
|
|
||||||
|
final animationController = useAnimationController(
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
);
|
||||||
|
final scaleAnimation = useAnimation(
|
||||||
|
Tween<double>(begin: 0.8, end: 1.0).animate(
|
||||||
|
CurvedAnimation(parent: animationController, curve: Curves.easeOut),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final opacityAnimation = useAnimation(
|
||||||
|
Tween<double>(begin: 0.0, end: 1.0).animate(
|
||||||
|
CurvedAnimation(parent: animationController, curve: Curves.easeOut),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
focusNode.requestFocus();
|
||||||
|
animationController.forward();
|
||||||
|
return null;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
void listener() {
|
||||||
|
searchQuery.value = textController.text;
|
||||||
|
// Reset focused index when search changes
|
||||||
|
focusedIndex.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
textController.addListener(listener);
|
||||||
|
return () => textController.removeListener(listener);
|
||||||
|
}, [textController]);
|
||||||
|
|
||||||
|
final chatRooms = ref.watch(chatRoomJoinedProvider);
|
||||||
|
|
||||||
|
bool isDesktop() =>
|
||||||
|
kIsWeb ||
|
||||||
|
(!kIsWeb &&
|
||||||
|
(Platform.isWindows || Platform.isLinux || Platform.isMacOS));
|
||||||
|
|
||||||
|
final filteredChats = chatRooms.maybeWhen(
|
||||||
|
data: (rooms) {
|
||||||
|
if (searchQuery.value.isEmpty) return <SnChatRoom>[];
|
||||||
|
return rooms
|
||||||
|
.where((room) {
|
||||||
|
final title = room.name ?? '';
|
||||||
|
final desc = room.description ?? '';
|
||||||
|
final query = searchQuery.value.toLowerCase();
|
||||||
|
return title.toLowerCase().contains(query) ||
|
||||||
|
desc.toLowerCase().contains(query) ||
|
||||||
|
(room.members?.any(
|
||||||
|
(member) =>
|
||||||
|
member.account.name.contains(query) ||
|
||||||
|
member.account.nick.contains(query),
|
||||||
|
) ??
|
||||||
|
false);
|
||||||
|
})
|
||||||
|
.take(5) // Limit to 5 results
|
||||||
|
.toList();
|
||||||
|
},
|
||||||
|
orElse: () => <SnChatRoom>[],
|
||||||
|
);
|
||||||
|
|
||||||
|
final filteredRoutes = searchQuery.value.isEmpty
|
||||||
|
? <RouteItem>[]
|
||||||
|
: kAvailableRoutes
|
||||||
|
.where((route) {
|
||||||
|
final query = searchQuery.value.toLowerCase();
|
||||||
|
return route.name.toLowerCase().contains(query) ||
|
||||||
|
route.description.toLowerCase().contains(query) ||
|
||||||
|
route.searchableAliases.any(
|
||||||
|
(e) => e.toLowerCase().contains(query),
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.take(5) // Limit to 5 results
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
final filteredSpecialActions = searchQuery.value.isEmpty
|
||||||
|
? <SpecialAction>[]
|
||||||
|
: _getSpecialActions(context)
|
||||||
|
.where((action) {
|
||||||
|
final query = searchQuery.value.toLowerCase();
|
||||||
|
return action.name.toLowerCase().contains(query) ||
|
||||||
|
action.description.toLowerCase().contains(query) ||
|
||||||
|
action.searchableAliases.any(
|
||||||
|
(e) => e.toLowerCase().contains(query),
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.take(5) // Limit to 5 results
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
final filteredFallbacks =
|
||||||
|
searchQuery.value.isNotEmpty &&
|
||||||
|
filteredChats.isEmpty &&
|
||||||
|
filteredSpecialActions.isEmpty &&
|
||||||
|
filteredRoutes.isEmpty
|
||||||
|
? _getFallbackActions(ref, context, searchQuery.value)
|
||||||
|
: <FallbackAction>[];
|
||||||
|
|
||||||
|
// Combine results: fallbacks first, then chats, special actions, routes
|
||||||
|
final allResults = [
|
||||||
|
...filteredFallbacks,
|
||||||
|
...filteredChats,
|
||||||
|
...filteredSpecialActions,
|
||||||
|
...filteredRoutes,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Scroll to focused item
|
||||||
|
useEffect(() {
|
||||||
|
if (focusedIndex.value != null && allResults.isNotEmpty) {
|
||||||
|
// Wait for the next frame to ensure ScrollController is attached
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (scrollController.hasClients) {
|
||||||
|
// Estimate item height (ListTile is typically around 72-88 pixels)
|
||||||
|
const double estimatedItemHeight = 80.0;
|
||||||
|
final double itemTopOffset =
|
||||||
|
focusedIndex.value! * estimatedItemHeight;
|
||||||
|
final double viewportHeight =
|
||||||
|
scrollController.position.viewportDimension;
|
||||||
|
final double centeredOffset =
|
||||||
|
itemTopOffset -
|
||||||
|
(viewportHeight / 2) +
|
||||||
|
(estimatedItemHeight / 2);
|
||||||
|
|
||||||
|
// Animate scroll to center the focused item
|
||||||
|
scrollController.animateTo(
|
||||||
|
centeredOffset.clamp(
|
||||||
|
0.0,
|
||||||
|
scrollController.position.maxScrollExtent,
|
||||||
|
),
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
curve: Curves.easeOut,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, [focusedIndex.value]);
|
||||||
|
|
||||||
|
return KeyboardListener(
|
||||||
|
focusNode: FocusNode(),
|
||||||
|
onKeyEvent: (event) {
|
||||||
|
if (event is KeyDownEvent) {
|
||||||
|
if (event.logicalKey == LogicalKeyboardKey.escape) {
|
||||||
|
onDismiss();
|
||||||
|
} else if (isDesktop()) {
|
||||||
|
if (event.logicalKey == LogicalKeyboardKey.enter ||
|
||||||
|
event.logicalKey == LogicalKeyboardKey.numpadEnter) {
|
||||||
|
final item = allResults[focusedIndex.value ?? 0];
|
||||||
|
_executeItem(context, ref, item);
|
||||||
|
} else if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
|
||||||
|
if (allResults.isNotEmpty) {
|
||||||
|
if (focusedIndex.value == null) {
|
||||||
|
focusedIndex.value = 0;
|
||||||
|
} else {
|
||||||
|
focusedIndex.value = math.max(0, focusedIndex.value! - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
|
||||||
|
if (allResults.isNotEmpty) {
|
||||||
|
if (focusedIndex.value == null) {
|
||||||
|
focusedIndex.value = 0;
|
||||||
|
} else {
|
||||||
|
focusedIndex.value = math.min(
|
||||||
|
allResults.length - 1,
|
||||||
|
focusedIndex.value! + 1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: onDismiss,
|
||||||
|
child: BackdropFilter(
|
||||||
|
filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
|
||||||
|
child: Container(
|
||||||
|
color: Colors.black.withOpacity(0.5),
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.topCenter,
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
top: MediaQuery.of(context).size.height * 0.2,
|
||||||
|
),
|
||||||
|
child: AnimatedBuilder(
|
||||||
|
animation: animationController,
|
||||||
|
builder: (context, child) => Opacity(
|
||||||
|
opacity: opacityAnimation,
|
||||||
|
child: Transform.scale(scale: scaleAnimation, child: child),
|
||||||
|
),
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap:
|
||||||
|
() {}, // Prevent tap from dismissing when tapping inside
|
||||||
|
child: Container(
|
||||||
|
width: math.max(
|
||||||
|
MediaQuery.of(context).size.width * 0.6,
|
||||||
|
320,
|
||||||
|
),
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 600,
|
||||||
|
maxHeight: 500,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.surface,
|
||||||
|
borderRadius: BorderRadius.circular(28),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.3),
|
||||||
|
blurRadius: 10,
|
||||||
|
spreadRadius: 2,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Material(
|
||||||
|
elevation: 0,
|
||||||
|
color: Theme.of(context).colorScheme.surface,
|
||||||
|
borderRadius: BorderRadius.circular(28),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
SearchBar(
|
||||||
|
controller: textController,
|
||||||
|
focusNode: focusNode,
|
||||||
|
hintText: 'searchChatsAndPages'.tr(),
|
||||||
|
leading: CircleAvatar(
|
||||||
|
child: const Icon(Symbols.keyboard_command_key),
|
||||||
|
).padding(horizontal: 8),
|
||||||
|
onSubmitted: !isDesktop() && allResults.isNotEmpty
|
||||||
|
? (value) => _executeItem(
|
||||||
|
context,
|
||||||
|
ref,
|
||||||
|
allResults[0],
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
AnimatedSize(
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
curve: Curves.easeOut,
|
||||||
|
child: allResults.isNotEmpty
|
||||||
|
? ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxHeight: 300,
|
||||||
|
),
|
||||||
|
child: ListView.builder(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
controller: scrollController,
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: allResults.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final item = allResults[index];
|
||||||
|
if (item is SnChatRoom) {
|
||||||
|
return _ChatRoomSearchResult(
|
||||||
|
room: item,
|
||||||
|
isFocused:
|
||||||
|
index == focusedIndex.value,
|
||||||
|
onTap: () => _navigateToChat(
|
||||||
|
context,
|
||||||
|
ref,
|
||||||
|
item,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (item is SpecialAction) {
|
||||||
|
return _SpecialActionSearchResult(
|
||||||
|
action: item,
|
||||||
|
isFocused:
|
||||||
|
index == focusedIndex.value,
|
||||||
|
onTap: () {
|
||||||
|
onDismiss();
|
||||||
|
item.action();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else if (item is RouteItem) {
|
||||||
|
return _RouteSearchResult(
|
||||||
|
route: item,
|
||||||
|
isFocused:
|
||||||
|
index == focusedIndex.value,
|
||||||
|
onTap: () => _navigateToRoute(
|
||||||
|
context,
|
||||||
|
ref,
|
||||||
|
item,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (item is FallbackAction) {
|
||||||
|
return _FallbackSearchResult(
|
||||||
|
action: item,
|
||||||
|
isFocused:
|
||||||
|
index == focusedIndex.value,
|
||||||
|
onTap: () {
|
||||||
|
onDismiss();
|
||||||
|
item.action();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _navigateToChat(BuildContext context, WidgetRef ref, SnChatRoom room) {
|
||||||
|
onDismiss();
|
||||||
|
if (isWideScreen(context)) {
|
||||||
|
debugPrint('${room.name}');
|
||||||
|
ref
|
||||||
|
.read(routerProvider)
|
||||||
|
.replaceNamed('chatRoom', pathParameters: {'id': room.id});
|
||||||
|
} else {
|
||||||
|
ref
|
||||||
|
.read(routerProvider)
|
||||||
|
.pushNamed('chatRoom', pathParameters: {'id': room.id});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _navigateToRoute(BuildContext context, WidgetRef ref, RouteItem route) {
|
||||||
|
onDismiss();
|
||||||
|
ref.read(routerProvider).go(route.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _executeItem(BuildContext context, WidgetRef ref, dynamic item) {
|
||||||
|
if (item is SnChatRoom) {
|
||||||
|
_navigateToChat(context, ref, item);
|
||||||
|
} else if (item is SpecialAction) {
|
||||||
|
onDismiss();
|
||||||
|
item.action();
|
||||||
|
} else if (item is RouteItem) {
|
||||||
|
_navigateToRoute(context, ref, item);
|
||||||
|
} else if (item is FallbackAction) {
|
||||||
|
onDismiss();
|
||||||
|
item.action();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<FallbackAction> _getFallbackActions(
|
||||||
|
WidgetRef ref,
|
||||||
|
BuildContext context,
|
||||||
|
String query,
|
||||||
|
) {
|
||||||
|
final settings = ref.watch(appSettingsProvider);
|
||||||
|
|
||||||
|
final List<FallbackAction> actions = [];
|
||||||
|
|
||||||
|
// Check if query is a URL
|
||||||
|
final Uri? uri = Uri.tryParse(query);
|
||||||
|
final isValidUrl =
|
||||||
|
uri != null && (uri.scheme == 'http' || uri.scheme == 'https');
|
||||||
|
final isDomain = RegExp(
|
||||||
|
r'^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$',
|
||||||
|
).hasMatch(query);
|
||||||
|
|
||||||
|
if (isValidUrl || isDomain) {
|
||||||
|
final finalUri = isDomain ? Uri.parse('https://$query') : uri!;
|
||||||
|
actions.add(
|
||||||
|
FallbackAction(
|
||||||
|
name: 'Open URL',
|
||||||
|
description: 'Open ${finalUri.toString()} in browser',
|
||||||
|
icon: Symbols.open_in_new,
|
||||||
|
action: () async {
|
||||||
|
if (await canLaunchUrl(finalUri)) {
|
||||||
|
await launchUrl(finalUri, mode: LaunchMode.externalApplication);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ask the AI
|
||||||
|
// Bugged, DO NOT USE
|
||||||
|
// actions.add(
|
||||||
|
// FallbackAction(
|
||||||
|
// name: 'Ask the AI',
|
||||||
|
// description: 'Ask "$query" to the AI',
|
||||||
|
// icon: Symbols.bubble_chart,
|
||||||
|
// action: () {
|
||||||
|
// eventBus.fire(ShowThoughtSheetEvent(initialMessage: query));
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
|
||||||
|
// Search the web
|
||||||
|
actions.add(
|
||||||
|
FallbackAction(
|
||||||
|
name: 'Search the web',
|
||||||
|
description: 'Search "$query" on the Internet',
|
||||||
|
icon: Symbols.search,
|
||||||
|
action: () async {
|
||||||
|
final searchUri = Uri.parse(
|
||||||
|
settings.dashSearchEngine != null
|
||||||
|
? settings.dashSearchEngine!.replaceFirst('%s', query)
|
||||||
|
: 'https://www.google.com/search?q=$query',
|
||||||
|
);
|
||||||
|
if (await canLaunchUrl(searchUri)) {
|
||||||
|
await launchUrl(searchUri, mode: LaunchMode.externalApplication);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FallbackAction {
|
||||||
|
final String name;
|
||||||
|
final String description;
|
||||||
|
final IconData icon;
|
||||||
|
final VoidCallback action;
|
||||||
|
final List<String> searchableAliases;
|
||||||
|
|
||||||
|
const FallbackAction({
|
||||||
|
required this.name,
|
||||||
|
required this.description,
|
||||||
|
required this.icon,
|
||||||
|
required this.action,
|
||||||
|
this.searchableAliases = const [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RouteSearchResult extends StatelessWidget {
|
||||||
|
final RouteItem route;
|
||||||
|
final bool isFocused;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
|
||||||
|
const _RouteSearchResult({
|
||||||
|
required this.route,
|
||||||
|
required this.isFocused,
|
||||||
|
required this.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isFocused
|
||||||
|
? Theme.of(context).colorScheme.surfaceContainerHighest
|
||||||
|
: null,
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(28)),
|
||||||
|
),
|
||||||
|
child: ListTile(
|
||||||
|
leading: CircleAvatar(
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
|
foregroundColor: Theme.of(context).colorScheme.onSecondaryContainer,
|
||||||
|
child: Icon(route.icon),
|
||||||
|
),
|
||||||
|
title: Text(route.name),
|
||||||
|
subtitle: Text(route.description),
|
||||||
|
onTap: onTap,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SpecialActionSearchResult extends StatelessWidget {
|
||||||
|
final SpecialAction action;
|
||||||
|
final bool isFocused;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
|
||||||
|
const _SpecialActionSearchResult({
|
||||||
|
required this.action,
|
||||||
|
required this.isFocused,
|
||||||
|
required this.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isFocused
|
||||||
|
? Theme.of(context).colorScheme.surfaceContainerHighest
|
||||||
|
: null,
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
||||||
|
),
|
||||||
|
child: ListTile(
|
||||||
|
leading: CircleAvatar(
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.tertiaryContainer,
|
||||||
|
foregroundColor: Theme.of(context).colorScheme.onTertiaryContainer,
|
||||||
|
child: Icon(action.icon),
|
||||||
|
),
|
||||||
|
title: Text(action.name),
|
||||||
|
subtitle: Text(action.description),
|
||||||
|
onTap: onTap,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FallbackSearchResult extends StatelessWidget {
|
||||||
|
final FallbackAction action;
|
||||||
|
final bool isFocused;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
|
||||||
|
const _FallbackSearchResult({
|
||||||
|
required this.action,
|
||||||
|
required this.isFocused,
|
||||||
|
required this.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isFocused
|
||||||
|
? Theme.of(context).colorScheme.surfaceContainerHighest
|
||||||
|
: null,
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
||||||
|
),
|
||||||
|
child: ListTile(
|
||||||
|
leading: CircleAvatar(
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
|
||||||
|
foregroundColor: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||||
|
child: Icon(action.icon),
|
||||||
|
),
|
||||||
|
title: Text(action.name),
|
||||||
|
subtitle: Text(action.description),
|
||||||
|
onTap: onTap,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChatRoomSearchResult extends HookConsumerWidget {
|
||||||
|
final SnChatRoom room;
|
||||||
|
final bool isFocused;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
|
||||||
|
const _ChatRoomSearchResult({
|
||||||
|
required this.room,
|
||||||
|
required this.isFocused,
|
||||||
|
required this.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final userInfo = ref.watch(userInfoProvider);
|
||||||
|
final summary = ref
|
||||||
|
.watch(chatSummaryProvider)
|
||||||
|
.whenData((summaries) => summaries[room.id]);
|
||||||
|
|
||||||
|
var validMembers = room.members ?? [];
|
||||||
|
if (validMembers.isNotEmpty && userInfo.value != null) {
|
||||||
|
validMembers = validMembers
|
||||||
|
.where((e) => e.accountId != userInfo.value!.id)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
String titleText;
|
||||||
|
if (room.type == 1 && room.name == null) {
|
||||||
|
if (room.members?.isNotEmpty ?? false) {
|
||||||
|
titleText = validMembers.map((e) => e.account.nick).join(', ');
|
||||||
|
} else {
|
||||||
|
titleText = 'Direct Message';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
titleText = room.name ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildSubtitle() {
|
||||||
|
return summary.when(
|
||||||
|
data: (data) => data == null
|
||||||
|
? (room.type == 1 && room.description == null
|
||||||
|
? Text(
|
||||||
|
validMembers.map((e) => '@${e.account.name}').join(', '),
|
||||||
|
)
|
||||||
|
: Text(room.description ?? ''))
|
||||||
|
: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
if (data.unreadCount > 0)
|
||||||
|
Text(
|
||||||
|
'unreadMessages'.plural(data.unreadCount),
|
||||||
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (data.lastMessage == null)
|
||||||
|
room.type == 1 && room.description == null
|
||||||
|
? Text(
|
||||||
|
validMembers
|
||||||
|
.map((e) => '@${e.account.name}')
|
||||||
|
.join(', '),
|
||||||
|
)
|
||||||
|
: Text(room.description ?? '')
|
||||||
|
else
|
||||||
|
Row(
|
||||||
|
spacing: 4,
|
||||||
|
children: [
|
||||||
|
Badge(
|
||||||
|
label: Text(data.lastMessage!.sender.account.nick),
|
||||||
|
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||||
|
backgroundColor: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.primary,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
(data.lastMessage!.content?.isNotEmpty ?? false)
|
||||||
|
? data.lastMessage!.content!
|
||||||
|
: 'messageNone'.tr(),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: Text(
|
||||||
|
RelativeTime(
|
||||||
|
context,
|
||||||
|
).format(data.lastMessage!.createdAt),
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
loading: () => room.type == 1 && room.description == null
|
||||||
|
? Text(validMembers.map((e) => '@${e.account.name}').join(', '))
|
||||||
|
: Text(room.description ?? ''),
|
||||||
|
error: (_, _) => room.type == 1 && room.description == null
|
||||||
|
? Text(validMembers.map((e) => '@${e.account.name}').join(', '))
|
||||||
|
: Text(room.description ?? ''),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final isDirect = room.type == 1;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isFocused
|
||||||
|
? Theme.of(context).colorScheme.surfaceContainerHighest
|
||||||
|
: null,
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
||||||
|
),
|
||||||
|
child: ListTile(
|
||||||
|
leading: ChatRoomAvatar(
|
||||||
|
room: room,
|
||||||
|
isDirect: isDirect,
|
||||||
|
summary: summary,
|
||||||
|
validMembers: validMembers,
|
||||||
|
),
|
||||||
|
title: Text(titleText),
|
||||||
|
subtitle: buildSubtitle(),
|
||||||
|
onTap: onTap,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,8 +9,11 @@ import 'package:island/models/file.dart';
|
|||||||
import 'package:island/pods/config.dart';
|
import 'package:island/pods/config.dart';
|
||||||
import 'package:island/services/time.dart';
|
import 'package:island/services/time.dart';
|
||||||
import 'package:island/utils/format.dart';
|
import 'package:island/utils/format.dart';
|
||||||
|
import 'package:island/widgets/content/profile_decoration.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'dart:math' as math;
|
||||||
|
import 'dart:ui' as ui;
|
||||||
import 'package:island/widgets/data_saving_gate.dart';
|
import 'package:island/widgets/data_saving_gate.dart';
|
||||||
|
|
||||||
import 'file_viewer_contents.dart';
|
import 'file_viewer_contents.dart';
|
||||||
@@ -258,17 +261,15 @@ class CloudFileWidget extends HookConsumerWidget {
|
|||||||
var content = switch (item.mimeType?.split('/').firstOrNull) {
|
var content = switch (item.mimeType?.split('/').firstOrNull) {
|
||||||
'image' => AspectRatio(
|
'image' => AspectRatio(
|
||||||
aspectRatio: ratio,
|
aspectRatio: ratio,
|
||||||
child:
|
child: (useInternalGate && dataSaving && !unlocked.value)
|
||||||
(useInternalGate && dataSaving && !unlocked.value)
|
? dataPlaceHolder(Symbols.image)
|
||||||
? dataPlaceHolder(Symbols.image)
|
: cloudImage(),
|
||||||
: cloudImage(),
|
|
||||||
),
|
),
|
||||||
'video' => AspectRatio(
|
'video' => AspectRatio(
|
||||||
aspectRatio: ratio,
|
aspectRatio: ratio,
|
||||||
child:
|
child: (useInternalGate && dataSaving && !unlocked.value)
|
||||||
(useInternalGate && dataSaving && !unlocked.value)
|
? dataPlaceHolder(Symbols.play_arrow)
|
||||||
? dataPlaceHolder(Symbols.play_arrow)
|
: cloudVideo(),
|
||||||
: cloudVideo(),
|
|
||||||
),
|
),
|
||||||
'audio' => AudioFileContent(item: item, uri: uri),
|
'audio' => AudioFileContent(item: item, uri: uri),
|
||||||
_ => Builder(
|
_ => Builder(
|
||||||
@@ -383,10 +384,9 @@ class CloudVideoWidget extends HookConsumerWidget {
|
|||||||
final serverUrl = ref.watch(serverUrlProvider);
|
final serverUrl = ref.watch(serverUrlProvider);
|
||||||
final uri = '$serverUrl/drive/files/${item.id}';
|
final uri = '$serverUrl/drive/files/${item.id}';
|
||||||
|
|
||||||
var ratio =
|
var ratio = item.fileMeta?['ratio'] is num
|
||||||
item.fileMeta?['ratio'] is num
|
? item.fileMeta!['ratio'].toDouble()
|
||||||
? item.fileMeta!['ratio'].toDouble()
|
: 1.0;
|
||||||
: 1.0;
|
|
||||||
if (ratio == 0) ratio = 1.0;
|
if (ratio == 0) ratio = 1.0;
|
||||||
|
|
||||||
if (open.value) {
|
if (open.value) {
|
||||||
@@ -533,10 +533,9 @@ class CloudImageWidget extends ConsumerWidget {
|
|||||||
|
|
||||||
return AspectRatio(
|
return AspectRatio(
|
||||||
aspectRatio: aspectRatio,
|
aspectRatio: aspectRatio,
|
||||||
child:
|
child: file != null
|
||||||
file != null
|
? CloudFileWidget(item: file!, fit: fit)
|
||||||
? CloudFileWidget(item: file!, fit: fit)
|
: UniversalImage(uri: uri, blurHash: blurHash, fit: fit),
|
||||||
: UniversalImage(uri: uri, blurHash: blurHash, fit: fit),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -545,10 +544,9 @@ class CloudImageWidget extends ConsumerWidget {
|
|||||||
required String serverUrl,
|
required String serverUrl,
|
||||||
bool original = false,
|
bool original = false,
|
||||||
}) {
|
}) {
|
||||||
final uri =
|
final uri = original
|
||||||
original
|
? '$serverUrl/drive/files/$fileId?original=true'
|
||||||
? '$serverUrl/drive/files/$fileId?original=true'
|
: '$serverUrl/drive/files/$fileId';
|
||||||
: '$serverUrl/drive/files/$fileId';
|
|
||||||
return CachedNetworkImageProvider(uri);
|
return CachedNetworkImageProvider(uri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -560,6 +558,7 @@ class ProfilePictureWidget extends ConsumerWidget {
|
|||||||
final double? borderRadius;
|
final double? borderRadius;
|
||||||
final IconData? fallbackIcon;
|
final IconData? fallbackIcon;
|
||||||
final Color? fallbackColor;
|
final Color? fallbackColor;
|
||||||
|
final ProfileDecoration? decoration;
|
||||||
const ProfilePictureWidget({
|
const ProfilePictureWidget({
|
||||||
super.key,
|
super.key,
|
||||||
this.fileId,
|
this.fileId,
|
||||||
@@ -568,6 +567,7 @@ class ProfilePictureWidget extends ConsumerWidget {
|
|||||||
this.borderRadius,
|
this.borderRadius,
|
||||||
this.fallbackIcon,
|
this.fallbackIcon,
|
||||||
this.fallbackColor,
|
this.fallbackColor,
|
||||||
|
this.decoration,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -575,36 +575,49 @@ class ProfilePictureWidget extends ConsumerWidget {
|
|||||||
final serverUrl = ref.watch(serverUrlProvider);
|
final serverUrl = ref.watch(serverUrlProvider);
|
||||||
final String? id = file?.id ?? fileId;
|
final String? id = file?.id ?? fileId;
|
||||||
|
|
||||||
final fallback =
|
final fallback = Icon(
|
||||||
Icon(
|
fallbackIcon ?? Symbols.account_circle,
|
||||||
fallbackIcon ?? Symbols.account_circle,
|
size: radius,
|
||||||
size: radius,
|
color: fallbackColor ?? Theme.of(context).colorScheme.onPrimaryContainer,
|
||||||
color:
|
).center();
|
||||||
fallbackColor ?? Theme.of(context).colorScheme.onPrimaryContainer,
|
|
||||||
).center();
|
final image = id == null
|
||||||
|
? fallback
|
||||||
|
: DataSavingGate(
|
||||||
|
bypass: true,
|
||||||
|
placeholder: fallback,
|
||||||
|
content: () => UniversalImage(
|
||||||
|
uri: '$serverUrl/drive/files/$id',
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget content = Container(
|
||||||
|
width: radius * 2,
|
||||||
|
height: radius * 2,
|
||||||
|
color: Theme.of(context).colorScheme.primaryContainer,
|
||||||
|
child: decoration != null
|
||||||
|
? Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: [
|
||||||
|
image,
|
||||||
|
CustomPaint(
|
||||||
|
painter: _ProfileDecorationPainter(
|
||||||
|
text: decoration!.text,
|
||||||
|
color: decoration!.color,
|
||||||
|
textColor: decoration!.textColor ?? Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: image,
|
||||||
|
);
|
||||||
|
|
||||||
return ClipRRect(
|
return ClipRRect(
|
||||||
borderRadius:
|
borderRadius: borderRadius == null
|
||||||
borderRadius == null
|
? BorderRadius.all(Radius.circular(radius))
|
||||||
? BorderRadius.all(Radius.circular(radius))
|
: BorderRadius.all(Radius.circular(borderRadius!)),
|
||||||
: BorderRadius.all(Radius.circular(borderRadius!)),
|
child: content,
|
||||||
child: Container(
|
|
||||||
width: radius * 2,
|
|
||||||
height: radius * 2,
|
|
||||||
color: Theme.of(context).colorScheme.primaryContainer,
|
|
||||||
child:
|
|
||||||
id == null
|
|
||||||
? fallback
|
|
||||||
: DataSavingGate(
|
|
||||||
bypass: true,
|
|
||||||
placeholder: fallback,
|
|
||||||
content:
|
|
||||||
() => UniversalImage(
|
|
||||||
uri: '$serverUrl/drive/files/$id',
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -716,32 +729,29 @@ class SplitAvatarWidget extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child:
|
child: filesId.length > 4
|
||||||
filesId.length > 4
|
? Container(
|
||||||
? Container(
|
color: Theme.of(
|
||||||
color:
|
context,
|
||||||
Theme.of(
|
).colorScheme.primaryContainer,
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
'+${filesId.length - 3}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: radius * 0.4,
|
||||||
|
color: Theme.of(
|
||||||
context,
|
context,
|
||||||
).colorScheme.primaryContainer,
|
).colorScheme.onPrimaryContainer,
|
||||||
child: Center(
|
|
||||||
child: Text(
|
|
||||||
'+${filesId.length - 3}',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: radius * 0.4,
|
|
||||||
color:
|
|
||||||
Theme.of(
|
|
||||||
context,
|
|
||||||
).colorScheme.onPrimaryContainer,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
|
||||||
: _buildQuadrant(
|
|
||||||
context,
|
|
||||||
filesId[3],
|
|
||||||
ref,
|
|
||||||
radius,
|
|
||||||
),
|
),
|
||||||
|
)
|
||||||
|
: _buildQuadrant(
|
||||||
|
context,
|
||||||
|
filesId[3],
|
||||||
|
ref,
|
||||||
|
radius,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -765,14 +775,12 @@ class SplitAvatarWidget extends ConsumerWidget {
|
|||||||
width: radius,
|
width: radius,
|
||||||
height: radius,
|
height: radius,
|
||||||
color: Theme.of(context).colorScheme.primaryContainer,
|
color: Theme.of(context).colorScheme.primaryContainer,
|
||||||
child:
|
child: Icon(
|
||||||
Icon(
|
fallbackIcon,
|
||||||
fallbackIcon,
|
size: radius * 0.6,
|
||||||
size: radius * 0.6,
|
color:
|
||||||
color:
|
fallbackColor ?? Theme.of(context).colorScheme.onPrimaryContainer,
|
||||||
fallbackColor ??
|
).center(),
|
||||||
Theme.of(context).colorScheme.onPrimaryContainer,
|
|
||||||
).center(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -786,3 +794,106 @@ class SplitAvatarWidget extends ConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _ProfileDecorationPainter extends CustomPainter {
|
||||||
|
final String text;
|
||||||
|
final Color color;
|
||||||
|
final Color textColor;
|
||||||
|
|
||||||
|
_ProfileDecorationPainter({
|
||||||
|
required this.text,
|
||||||
|
required this.color,
|
||||||
|
required this.textColor,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
if (text.isEmpty) return;
|
||||||
|
|
||||||
|
final radius = size.width / 2;
|
||||||
|
final center = Offset(size.width / 2, size.height / 2);
|
||||||
|
|
||||||
|
final strokeWidth = radius * 0.4; // Increased thickness
|
||||||
|
final centerAngle = 3 * math.pi / 4;
|
||||||
|
final sweepAngle = math.pi / 1;
|
||||||
|
final startAngle = centerAngle - (sweepAngle / 2);
|
||||||
|
|
||||||
|
final arcRadius = radius - (strokeWidth / 2);
|
||||||
|
final rect = Rect.fromCircle(center: center, radius: arcRadius);
|
||||||
|
|
||||||
|
final paint = Paint()
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = strokeWidth
|
||||||
|
..shader = SweepGradient(
|
||||||
|
startAngle: startAngle,
|
||||||
|
endAngle: startAngle + sweepAngle,
|
||||||
|
colors: [color.withOpacity(0), color, color, color.withOpacity(0)],
|
||||||
|
stops: const [0.0, 0.25, 0.75, 1.0],
|
||||||
|
).createShader(rect);
|
||||||
|
|
||||||
|
canvas.drawArc(rect, startAngle, sweepAngle, false, paint);
|
||||||
|
|
||||||
|
_drawTextOnArc(canvas, center, arcRadius, text, centerAngle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _drawTextOnArc(
|
||||||
|
Canvas canvas,
|
||||||
|
Offset center,
|
||||||
|
double radius,
|
||||||
|
String text,
|
||||||
|
double centerAngle,
|
||||||
|
) {
|
||||||
|
final textStyle = TextStyle(
|
||||||
|
color: textColor,
|
||||||
|
fontSize: radius * 0.28,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
);
|
||||||
|
|
||||||
|
double totalAngle = 0;
|
||||||
|
List<double> charAngles = [];
|
||||||
|
|
||||||
|
// Calculate total angle occupied by text
|
||||||
|
for (int i = 0; i < text.length; i++) {
|
||||||
|
final char = text[i];
|
||||||
|
final span = TextSpan(text: char, style: textStyle);
|
||||||
|
final tp = TextPainter(text: span, textDirection: ui.TextDirection.ltr);
|
||||||
|
tp.layout();
|
||||||
|
final charWidth = tp.width;
|
||||||
|
final angle = charWidth / radius;
|
||||||
|
charAngles.add(angle);
|
||||||
|
totalAngle += angle;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start from "Left" of the center (High angle)
|
||||||
|
// We want to traverse from centerAngle + total/2 to centerAngle - total/2
|
||||||
|
double currentAngle = centerAngle + (totalAngle / 2);
|
||||||
|
|
||||||
|
for (int i = 0; i < text.length; i++) {
|
||||||
|
final char = text[i];
|
||||||
|
final span = TextSpan(text: char, style: textStyle);
|
||||||
|
final tp = TextPainter(text: span, textDirection: ui.TextDirection.ltr);
|
||||||
|
tp.layout();
|
||||||
|
|
||||||
|
final charAngle = charAngles[i];
|
||||||
|
final midCharAngle = currentAngle - charAngle / 2;
|
||||||
|
|
||||||
|
final x = center.dx + radius * math.cos(midCharAngle);
|
||||||
|
final y = center.dy + radius * math.sin(midCharAngle);
|
||||||
|
|
||||||
|
canvas.save();
|
||||||
|
canvas.translate(x, y);
|
||||||
|
canvas.rotate(midCharAngle - math.pi / 2);
|
||||||
|
|
||||||
|
tp.paint(canvas, Offset(-tp.width / 2, -tp.height / 2));
|
||||||
|
|
||||||
|
canvas.restore();
|
||||||
|
|
||||||
|
currentAngle -= charAngle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(covariant CustomPainter oldDelegate) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:island/models/file.dart';
|
import 'package:island/models/file.dart';
|
||||||
import 'package:island/pods/config.dart';
|
import 'package:island/pods/config.dart';
|
||||||
import 'package:island/screens/account/profile.dart';
|
import 'package:island/screens/account/profile.dart';
|
||||||
import 'package:island/screens/creators/publishers_form.dart';
|
import 'package:island/screens/posts/publisher_profile.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/content/cloud_files.dart';
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
import 'package:island/widgets/content/cloud_file_lightbox.dart';
|
import 'package:island/widgets/content/cloud_file_lightbox.dart';
|
||||||
@@ -64,14 +64,16 @@ class MarkdownTextContent extends HookConsumerWidget {
|
|||||||
final matches = stickerPattern.allMatches(content);
|
final matches = stickerPattern.allMatches(content);
|
||||||
|
|
||||||
// Content should only contain one sticker and nothing else (except whitespace)
|
// Content should only contain one sticker and nothing else (except whitespace)
|
||||||
final contentWithoutStickers =
|
final contentWithoutStickers = content
|
||||||
content.replaceAll(stickerPattern, '').trim();
|
.replaceAll(stickerPattern, '')
|
||||||
|
.trim();
|
||||||
return matches.length == 1 && contentWithoutStickers.isEmpty;
|
return matches.length == 1 && contentWithoutStickers.isEmpty;
|
||||||
}, [content]);
|
}, [content]);
|
||||||
|
|
||||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
final config =
|
final config = isDark
|
||||||
isDark ? MarkdownConfig.darkConfig : MarkdownConfig.defaultConfig;
|
? MarkdownConfig.darkConfig
|
||||||
|
: MarkdownConfig.defaultConfig;
|
||||||
|
|
||||||
final onMentionTap = useCallback((String type, String id) {
|
final onMentionTap = useCallback((String type, String id) {
|
||||||
final fullPath = '/$type/$id';
|
final fullPath = '/$type/$id';
|
||||||
@@ -128,11 +130,10 @@ class MarkdownTextContent extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
TableConfig(
|
TableConfig(
|
||||||
wrapper:
|
wrapper: (child) => SingleChildScrollView(
|
||||||
(child) => SingleChildScrollView(
|
scrollDirection: Axis.horizontal,
|
||||||
scrollDirection: Axis.horizontal,
|
child: child,
|
||||||
child: child,
|
),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
LinkConfig(
|
LinkConfig(
|
||||||
style:
|
style:
|
||||||
@@ -203,8 +204,9 @@ class MarkdownTextContent extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color:
|
color: Theme.of(
|
||||||
Theme.of(context).colorScheme.surfaceContainer,
|
context,
|
||||||
|
).colorScheme.surfaceContainer,
|
||||||
borderRadius: const BorderRadius.all(
|
borderRadius: const BorderRadius.all(
|
||||||
Radius.circular(8),
|
Radius.circular(8),
|
||||||
),
|
),
|
||||||
@@ -288,11 +290,10 @@ class _MetionInlineSyntax extends markdown.InlineSyntax {
|
|||||||
"c" => 'chat',
|
"c" => 'chat',
|
||||||
_ => '',
|
_ => '',
|
||||||
};
|
};
|
||||||
final element =
|
final element = markdown.Element('mention-chip', [markdown.Text(alias)])
|
||||||
markdown.Element('mention-chip', [markdown.Text(alias)])
|
..attributes['alias'] = alias
|
||||||
..attributes['alias'] = alias
|
..attributes['type'] = type
|
||||||
..attributes['type'] = type
|
..attributes['id'] = parts.last;
|
||||||
..attributes['id'] = parts.last;
|
|
||||||
parser.addNode(element);
|
parser.addNode(element);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -373,18 +374,19 @@ class MentionChipGenerator extends SpanNodeGeneratorWithTag {
|
|||||||
required void Function(String type, String id) onTap,
|
required void Function(String type, String id) onTap,
|
||||||
}) : super(
|
}) : super(
|
||||||
tag: 'mention-chip',
|
tag: 'mention-chip',
|
||||||
generator: (
|
generator:
|
||||||
markdown.Element element,
|
(
|
||||||
MarkdownConfig config,
|
markdown.Element element,
|
||||||
WidgetVisitor visitor,
|
MarkdownConfig config,
|
||||||
) {
|
WidgetVisitor visitor,
|
||||||
return MentionChipSpanNode(
|
) {
|
||||||
attributes: element.attributes,
|
return MentionChipSpanNode(
|
||||||
backgroundColor: backgroundColor,
|
attributes: element.attributes,
|
||||||
foregroundColor: foregroundColor,
|
backgroundColor: backgroundColor,
|
||||||
onTap: onTap,
|
foregroundColor: foregroundColor,
|
||||||
);
|
onTap: onTap,
|
||||||
},
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -440,19 +442,17 @@ class MentionChipSpanNode extends SpanNode {
|
|||||||
builder: (context, ref, _) {
|
builder: (context, ref, _) {
|
||||||
final userData = ref.watch(accountProvider(parts.last));
|
final userData = ref.watch(accountProvider(parts.last));
|
||||||
return userData.when(
|
return userData.when(
|
||||||
data:
|
data: (data) => ProfilePictureWidget(
|
||||||
(data) => ProfilePictureWidget(
|
file: data.profile.picture,
|
||||||
file: data.profile.picture,
|
fallbackIcon: Symbols.person_rounded,
|
||||||
fallbackIcon: Symbols.person_rounded,
|
radius: 9,
|
||||||
radius: 9,
|
),
|
||||||
),
|
|
||||||
error: (_, _) => const Icon(Symbols.close),
|
error: (_, _) => const Icon(Symbols.close),
|
||||||
loading:
|
loading: () => const SizedBox(
|
||||||
() => const SizedBox(
|
width: 9,
|
||||||
width: 9,
|
height: 9,
|
||||||
height: 9,
|
child: CircularProgressIndicator(),
|
||||||
child: CircularProgressIndicator(),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -460,19 +460,17 @@ class MentionChipSpanNode extends SpanNode {
|
|||||||
builder: (context, ref, _) {
|
builder: (context, ref, _) {
|
||||||
final pubData = ref.watch(publisherProvider(parts.last));
|
final pubData = ref.watch(publisherProvider(parts.last));
|
||||||
return pubData.when(
|
return pubData.when(
|
||||||
data:
|
data: (data) => ProfilePictureWidget(
|
||||||
(data) => ProfilePictureWidget(
|
file: data.picture,
|
||||||
file: data?.picture,
|
fallbackIcon: Symbols.design_services_rounded,
|
||||||
fallbackIcon: Symbols.design_services_rounded,
|
radius: 9,
|
||||||
radius: 9,
|
),
|
||||||
),
|
|
||||||
error: (_, _) => const Icon(Symbols.close),
|
error: (_, _) => const Icon(Symbols.close),
|
||||||
loading:
|
loading: () => const SizedBox(
|
||||||
() => const SizedBox(
|
width: 9,
|
||||||
width: 9,
|
height: 9,
|
||||||
height: 9,
|
child: CircularProgressIndicator(),
|
||||||
child: CircularProgressIndicator(),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -508,16 +506,17 @@ class HighlightGenerator extends SpanNodeGeneratorWithTag {
|
|||||||
HighlightGenerator({required Color highlightColor})
|
HighlightGenerator({required Color highlightColor})
|
||||||
: super(
|
: super(
|
||||||
tag: 'highlight',
|
tag: 'highlight',
|
||||||
generator: (
|
generator:
|
||||||
markdown.Element element,
|
(
|
||||||
MarkdownConfig config,
|
markdown.Element element,
|
||||||
WidgetVisitor visitor,
|
MarkdownConfig config,
|
||||||
) {
|
WidgetVisitor visitor,
|
||||||
return HighlightSpanNode(
|
) {
|
||||||
text: element.textContent,
|
return HighlightSpanNode(
|
||||||
highlightColor: highlightColor,
|
text: element.textContent,
|
||||||
);
|
highlightColor: highlightColor,
|
||||||
},
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -545,20 +544,21 @@ class SpoilerGenerator extends SpanNodeGeneratorWithTag {
|
|||||||
required VoidCallback onToggle,
|
required VoidCallback onToggle,
|
||||||
}) : super(
|
}) : super(
|
||||||
tag: 'spoiler',
|
tag: 'spoiler',
|
||||||
generator: (
|
generator:
|
||||||
markdown.Element element,
|
(
|
||||||
MarkdownConfig config,
|
markdown.Element element,
|
||||||
WidgetVisitor visitor,
|
MarkdownConfig config,
|
||||||
) {
|
WidgetVisitor visitor,
|
||||||
return SpoilerSpanNode(
|
) {
|
||||||
text: element.textContent,
|
return SpoilerSpanNode(
|
||||||
backgroundColor: backgroundColor,
|
text: element.textContent,
|
||||||
foregroundColor: foregroundColor,
|
backgroundColor: backgroundColor,
|
||||||
outlineColor: outlineColor,
|
foregroundColor: foregroundColor,
|
||||||
revealed: revealed,
|
outlineColor: outlineColor,
|
||||||
onToggle: onToggle,
|
revealed: revealed,
|
||||||
);
|
onToggle: onToggle,
|
||||||
},
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -591,35 +591,33 @@ class SpoilerSpanNode extends SpanNode {
|
|||||||
border: revealed ? Border.all(color: outlineColor, width: 1) : null,
|
border: revealed ? Border.all(color: outlineColor, width: 1) : null,
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
),
|
),
|
||||||
child:
|
child: revealed
|
||||||
revealed
|
? Row(
|
||||||
? Row(
|
spacing: 6,
|
||||||
spacing: 6,
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisSize: MainAxisSize.min,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
children: [
|
||||||
children: [
|
Icon(Symbols.visibility, size: 18).padding(top: 1),
|
||||||
Icon(Symbols.visibility, size: 18).padding(top: 1),
|
Flexible(child: Text(text)),
|
||||||
Flexible(child: Text(text)),
|
],
|
||||||
],
|
)
|
||||||
)
|
: Row(
|
||||||
: Row(
|
spacing: 6,
|
||||||
spacing: 6,
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisSize: MainAxisSize.min,
|
children: [
|
||||||
children: [
|
Icon(
|
||||||
Icon(
|
Symbols.visibility_off,
|
||||||
Symbols.visibility_off,
|
color: foregroundColor,
|
||||||
color: foregroundColor,
|
size: 18,
|
||||||
size: 18,
|
),
|
||||||
),
|
Flexible(
|
||||||
Flexible(
|
child: Text(
|
||||||
child:
|
'spoiler',
|
||||||
Text(
|
style: TextStyle(color: foregroundColor),
|
||||||
'spoiler',
|
).tr(),
|
||||||
style: TextStyle(color: foregroundColor),
|
),
|
||||||
).tr(),
|
],
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -634,19 +632,20 @@ class StickerGenerator extends SpanNodeGeneratorWithTag {
|
|||||||
required String baseUrl,
|
required String baseUrl,
|
||||||
}) : super(
|
}) : super(
|
||||||
tag: 'sticker',
|
tag: 'sticker',
|
||||||
generator: (
|
generator:
|
||||||
markdown.Element element,
|
(
|
||||||
MarkdownConfig config,
|
markdown.Element element,
|
||||||
WidgetVisitor visitor,
|
MarkdownConfig config,
|
||||||
) {
|
WidgetVisitor visitor,
|
||||||
return StickerSpanNode(
|
) {
|
||||||
placeholder: element.textContent,
|
return StickerSpanNode(
|
||||||
backgroundColor: backgroundColor,
|
placeholder: element.textContent,
|
||||||
foregroundColor: foregroundColor,
|
backgroundColor: backgroundColor,
|
||||||
isEnlarged: isEnlarged,
|
foregroundColor: foregroundColor,
|
||||||
baseUrl: baseUrl,
|
isEnlarged: isEnlarged,
|
||||||
);
|
baseUrl: baseUrl,
|
||||||
},
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,78 +1,227 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/pods/config.dart';
|
||||||
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/pods/websocket.dart';
|
import 'package:island/pods/websocket.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:island/widgets/content/sheet.dart';
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
class NetworkStatusSheet extends HookConsumerWidget {
|
class NetworkStatusSheet extends HookConsumerWidget {
|
||||||
final VoidCallback onReconnect;
|
final bool autoClose;
|
||||||
|
const NetworkStatusSheet({super.key, this.autoClose = false});
|
||||||
const NetworkStatusSheet({super.key, required this.onReconnect});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final ws = ref.watch(websocketProvider);
|
final ws = ref.watch(websocketProvider);
|
||||||
final wsState = ref.watch(websocketStateProvider);
|
final wsState = ref.watch(websocketStateProvider);
|
||||||
|
final apiState = ref.watch(networkStatusProvider);
|
||||||
|
final serverUrl = ref.watch(serverUrlProvider);
|
||||||
|
|
||||||
|
final wsNotifier = ref.watch(websocketStateProvider.notifier);
|
||||||
|
|
||||||
|
final checks = [
|
||||||
|
wsState == WebSocketState.connected(),
|
||||||
|
apiState == NetworkStatus.online,
|
||||||
|
];
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
if (!autoClose) return;
|
||||||
|
|
||||||
|
final checks = [
|
||||||
|
wsState == WebSocketState.connected(),
|
||||||
|
apiState == NetworkStatus.online,
|
||||||
|
];
|
||||||
|
if (!checks.any((e) => !e)) {
|
||||||
|
Future.delayed(Duration(seconds: 3), () {
|
||||||
|
if (context.mounted) Navigator.of(context).pop();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}, [wsState, apiState]);
|
||||||
|
|
||||||
return SheetScaffold(
|
return SheetScaffold(
|
||||||
heightFactor: 0.4,
|
heightFactor: 0.6,
|
||||||
titleText:
|
titleText: !checks.any((e) => !e)
|
||||||
wsState == WebSocketState.connected()
|
? 'Connection Status'
|
||||||
? 'Connection Status'
|
: 'Connection Issues',
|
||||||
: 'Connection Issue',
|
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
spacing: 4,
|
||||||
children: [
|
children: [
|
||||||
wsState.when(
|
Container(
|
||||||
connected:
|
decoration: BoxDecoration(
|
||||||
() => Text(
|
borderRadius: BorderRadius.circular(12),
|
||||||
'Connected to server',
|
color: !checks.any((e) => !e)
|
||||||
style: Theme.of(context).textTheme.bodyLarge,
|
? Colors.green.withOpacity(0.1)
|
||||||
),
|
: Colors.red.withOpacity(0.1),
|
||||||
connecting:
|
),
|
||||||
() => Text(
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
'Connecting to server...',
|
margin: const EdgeInsets.only(bottom: 8),
|
||||||
style: Theme.of(context).textTheme.bodyLarge,
|
child: Column(
|
||||||
),
|
mainAxisSize: MainAxisSize.min,
|
||||||
disconnected:
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
() => Text(
|
children: [
|
||||||
'Disconnected from server',
|
Text('overview').tr().bold(),
|
||||||
style: Theme.of(context).textTheme.bodyLarge,
|
Column(
|
||||||
),
|
spacing: 8,
|
||||||
serverDown:
|
mainAxisSize: MainAxisSize.min,
|
||||||
() => Text(
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
'The server is not available right now... Please try again later...',
|
children: [
|
||||||
style: Theme.of(context).textTheme.bodyLarge,
|
if (!checks.any((e) => !e))
|
||||||
),
|
Text('Everything is operational.'),
|
||||||
duplicateDevice:
|
if (!checks[0])
|
||||||
() => Text(
|
Text(
|
||||||
'Another device has connected with the same account.',
|
'WebSocket is disconnected. Realtime updates are not available. You can try tap the reconnect button below to try connect again.',
|
||||||
style: Theme.of(context).textTheme.bodyLarge,
|
),
|
||||||
),
|
if (!checks[1])
|
||||||
error:
|
...([
|
||||||
(message) => Text(
|
Text(
|
||||||
'Connection error: $message',
|
'API is unreachable, you can try again later. If the issue persists, please contact support. Or you can check the service status.',
|
||||||
style: Theme.of(context).textTheme.bodyLarge,
|
),
|
||||||
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
launchUrlString("https://status.solsynth.dev");
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
'Check Service Status',
|
||||||
|
).textColor(Colors.blueAccent).bold(),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
Row(
|
||||||
if (ws.heartbeatDelay != null)
|
spacing: 8,
|
||||||
Text(
|
children: [
|
||||||
'Last heartbeat: ${ws.heartbeatDelay!.inMilliseconds}ms',
|
Text('WebSocket').bold(),
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
wsState.when(
|
||||||
),
|
connected: () => Text('connectionConnected').tr(),
|
||||||
const SizedBox(height: 24),
|
connecting: () => Text('connectionReconnecting').tr(),
|
||||||
Center(
|
disconnected: () => Text('connectionDisconnected').tr(),
|
||||||
child: FilledButton.icon(
|
serverDown: () => Text('connectionServerDown').tr(),
|
||||||
icon: const Icon(Symbols.wifi),
|
duplicateDevice: () => Text(
|
||||||
label: const Text('Reconnect'),
|
'Another device has connected with the same account.',
|
||||||
onPressed: () {
|
),
|
||||||
onReconnect();
|
error: (message) => Text('Connection error: $message'),
|
||||||
Navigator.pop(context);
|
),
|
||||||
},
|
if (ws.heartbeatDelay != null)
|
||||||
),
|
Text('${ws.heartbeatDelay!.inMilliseconds}ms'),
|
||||||
|
AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
child: wsState.when(
|
||||||
|
connected: () => Icon(
|
||||||
|
Symbols.check_circle,
|
||||||
|
key: ValueKey(WebSocketState.connected),
|
||||||
|
color: Colors.green,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
connecting: () => Icon(
|
||||||
|
Symbols.sync,
|
||||||
|
key: ValueKey(WebSocketState.connecting),
|
||||||
|
color: Colors.orange,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
disconnected: () => Icon(
|
||||||
|
Symbols.wifi_off,
|
||||||
|
key: ValueKey(WebSocketState.disconnected),
|
||||||
|
color: Colors.grey,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
serverDown: () => Icon(
|
||||||
|
Symbols.cloud_off,
|
||||||
|
key: ValueKey(WebSocketState.serverDown),
|
||||||
|
color: Colors.red,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
duplicateDevice: () => Icon(
|
||||||
|
Symbols.devices,
|
||||||
|
key: ValueKey(WebSocketState.duplicateDevice),
|
||||||
|
color: Colors.orange,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
error: (message) => Icon(
|
||||||
|
Symbols.error,
|
||||||
|
key: ValueKey(WebSocketState.error),
|
||||||
|
color: Colors.red,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Text('API').bold(),
|
||||||
|
Text(
|
||||||
|
apiState == NetworkStatus.online
|
||||||
|
? 'Online'
|
||||||
|
: apiState == NetworkStatus.notReady
|
||||||
|
? 'Not Ready'
|
||||||
|
: apiState == NetworkStatus.maintenance
|
||||||
|
? 'Under Maintenance'
|
||||||
|
: 'Offline',
|
||||||
|
),
|
||||||
|
AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
child: apiState == NetworkStatus.online
|
||||||
|
? Icon(
|
||||||
|
Symbols.check_circle,
|
||||||
|
key: ValueKey(NetworkStatus.online),
|
||||||
|
color: Colors.green,
|
||||||
|
size: 16,
|
||||||
|
)
|
||||||
|
: apiState == NetworkStatus.notReady
|
||||||
|
? Icon(
|
||||||
|
Symbols.warning,
|
||||||
|
key: ValueKey(NetworkStatus.notReady),
|
||||||
|
color: Colors.orange,
|
||||||
|
size: 16,
|
||||||
|
)
|
||||||
|
: apiState == NetworkStatus.maintenance
|
||||||
|
? Icon(
|
||||||
|
Symbols.construction,
|
||||||
|
key: ValueKey(NetworkStatus.maintenance),
|
||||||
|
color: Colors.orange,
|
||||||
|
size: 16,
|
||||||
|
)
|
||||||
|
: Icon(
|
||||||
|
Symbols.cloud_off,
|
||||||
|
key: ValueKey(NetworkStatus.offline),
|
||||||
|
color: Colors.red,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Text('API Server').bold(),
|
||||||
|
Expanded(child: Text(serverUrl)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
FilledButton.icon(
|
||||||
|
icon: const Icon(Symbols.wifi),
|
||||||
|
label: const Text('Reconnect'),
|
||||||
|
onPressed: () {
|
||||||
|
wsNotifier.manualReconnect();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
13
lib/widgets/content/profile_decoration.dart
Normal file
13
lib/widgets/content/profile_decoration.dart
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'profile_decoration.freezed.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
sealed class ProfileDecoration with _$ProfileDecoration {
|
||||||
|
const factory ProfileDecoration({
|
||||||
|
required String text,
|
||||||
|
required Color color,
|
||||||
|
Color? textColor,
|
||||||
|
}) = _ProfileDecoration;
|
||||||
|
}
|
||||||
271
lib/widgets/content/profile_decoration.freezed.dart
Normal file
271
lib/widgets/content/profile_decoration.freezed.dart
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// coverage:ignore-file
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'profile_decoration.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// dart format off
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$ProfileDecoration {
|
||||||
|
|
||||||
|
String get text; Color get color; Color? get textColor;
|
||||||
|
/// Create a copy of ProfileDecoration
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$ProfileDecorationCopyWith<ProfileDecoration> get copyWith => _$ProfileDecorationCopyWithImpl<ProfileDecoration>(this as ProfileDecoration, _$identity);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is ProfileDecoration&&(identical(other.text, text) || other.text == text)&&(identical(other.color, color) || other.color == color)&&(identical(other.textColor, textColor) || other.textColor == textColor));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,text,color,textColor);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'ProfileDecoration(text: $text, color: $color, textColor: $textColor)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $ProfileDecorationCopyWith<$Res> {
|
||||||
|
factory $ProfileDecorationCopyWith(ProfileDecoration value, $Res Function(ProfileDecoration) _then) = _$ProfileDecorationCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String text, Color color, Color? textColor
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$ProfileDecorationCopyWithImpl<$Res>
|
||||||
|
implements $ProfileDecorationCopyWith<$Res> {
|
||||||
|
_$ProfileDecorationCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final ProfileDecoration _self;
|
||||||
|
final $Res Function(ProfileDecoration) _then;
|
||||||
|
|
||||||
|
/// Create a copy of ProfileDecoration
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? text = null,Object? color = null,Object? textColor = freezed,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
text: null == text ? _self.text : text // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,color: null == color ? _self.color : color // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Color,textColor: freezed == textColor ? _self.textColor : textColor // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Color?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Adds pattern-matching-related methods to [ProfileDecoration].
|
||||||
|
extension ProfileDecorationPatterns on ProfileDecoration {
|
||||||
|
/// A variant of `map` that fallback to returning `orElse`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _ProfileDecoration value)? $default,{required TResult orElse(),}){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _ProfileDecoration() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// Callbacks receives the raw object, upcasted.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case final Subclass2 value:
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _ProfileDecoration value) $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _ProfileDecoration():
|
||||||
|
return $default(_that);}
|
||||||
|
}
|
||||||
|
/// A variant of `map` that fallback to returning `null`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _ProfileDecoration value)? $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _ProfileDecoration() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to an `orElse` callback.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String text, Color color, Color? textColor)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _ProfileDecoration() when $default != null:
|
||||||
|
return $default(_that.text,_that.color,_that.textColor);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// As opposed to `map`, this offers destructuring.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case Subclass2(:final field2):
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String text, Color color, Color? textColor) $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _ProfileDecoration():
|
||||||
|
return $default(_that.text,_that.color,_that.textColor);}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to returning `null`
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String text, Color color, Color? textColor)? $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _ProfileDecoration() when $default != null:
|
||||||
|
return $default(_that.text,_that.color,_that.textColor);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
|
||||||
|
class _ProfileDecoration implements ProfileDecoration {
|
||||||
|
const _ProfileDecoration({required this.text, required this.color, this.textColor});
|
||||||
|
|
||||||
|
|
||||||
|
@override final String text;
|
||||||
|
@override final Color color;
|
||||||
|
@override final Color? textColor;
|
||||||
|
|
||||||
|
/// Create a copy of ProfileDecoration
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$ProfileDecorationCopyWith<_ProfileDecoration> get copyWith => __$ProfileDecorationCopyWithImpl<_ProfileDecoration>(this, _$identity);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProfileDecoration&&(identical(other.text, text) || other.text == text)&&(identical(other.color, color) || other.color == color)&&(identical(other.textColor, textColor) || other.textColor == textColor));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,text,color,textColor);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'ProfileDecoration(text: $text, color: $color, textColor: $textColor)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$ProfileDecorationCopyWith<$Res> implements $ProfileDecorationCopyWith<$Res> {
|
||||||
|
factory _$ProfileDecorationCopyWith(_ProfileDecoration value, $Res Function(_ProfileDecoration) _then) = __$ProfileDecorationCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
String text, Color color, Color? textColor
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$ProfileDecorationCopyWithImpl<$Res>
|
||||||
|
implements _$ProfileDecorationCopyWith<$Res> {
|
||||||
|
__$ProfileDecorationCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _ProfileDecoration _self;
|
||||||
|
final $Res Function(_ProfileDecoration) _then;
|
||||||
|
|
||||||
|
/// Create a copy of ProfileDecoration
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? text = null,Object? color = null,Object? textColor = freezed,}) {
|
||||||
|
return _then(_ProfileDecoration(
|
||||||
|
text: null == text ? _self.text : text // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,color: null == color ? _self.color : color // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Color,textColor: freezed == textColor ? _self.textColor : textColor // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Color?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// dart format on
|
||||||
@@ -5,7 +5,6 @@ import 'package:gap/gap.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/pods/message.dart';
|
import 'package:island/pods/message.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/pods/websocket.dart';
|
|
||||||
import 'package:island/services/update_service.dart';
|
import 'package:island/services/update_service.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/content/network_status_sheet.dart';
|
import 'package:island/widgets/content/network_status_sheet.dart';
|
||||||
@@ -67,8 +66,6 @@ class DebugSheet extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final wsNotifier = ref.watch(websocketStateProvider.notifier);
|
|
||||||
|
|
||||||
return SheetScaffold(
|
return SheetScaffold(
|
||||||
titleText: 'Debug',
|
titleText: 'Debug',
|
||||||
heightFactor: 0.6,
|
heightFactor: 0.6,
|
||||||
@@ -111,10 +108,7 @@ class DebugSheet extends HookConsumerWidget {
|
|||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
builder:
|
builder: (context) => NetworkStatusSheet(),
|
||||||
(context) => NetworkStatusSheet(
|
|
||||||
onReconnect: () => wsNotifier.connect(),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
181
lib/widgets/notification_tile.dart
Normal file
181
lib/widgets/notification_tile.dart
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:island/models/account.dart';
|
||||||
|
import 'package:island/route.dart';
|
||||||
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
|
import 'package:island/widgets/content/markdown.dart';
|
||||||
|
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
||||||
|
import 'package:relative_time/relative_time.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
|
class NotificationTile extends StatelessWidget {
|
||||||
|
final SnNotification notification;
|
||||||
|
final double? avatarRadius;
|
||||||
|
final EdgeInsets? contentPadding;
|
||||||
|
final bool showImages;
|
||||||
|
final bool compact;
|
||||||
|
|
||||||
|
const NotificationTile({
|
||||||
|
super.key,
|
||||||
|
required this.notification,
|
||||||
|
this.avatarRadius,
|
||||||
|
this.contentPadding,
|
||||||
|
this.showImages = true,
|
||||||
|
this.compact = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
IconData _getNotificationIcon(String topic) {
|
||||||
|
switch (topic) {
|
||||||
|
case 'post.replies':
|
||||||
|
return Symbols.reply;
|
||||||
|
case 'wallet.transactions':
|
||||||
|
return Symbols.account_balance_wallet;
|
||||||
|
case 'relationships.friends.request':
|
||||||
|
return Symbols.person_add;
|
||||||
|
case 'invites.chat':
|
||||||
|
return Symbols.chat;
|
||||||
|
case 'invites.realm':
|
||||||
|
return Symbols.domain;
|
||||||
|
case 'auth.login':
|
||||||
|
return Symbols.login;
|
||||||
|
case 'posts.new':
|
||||||
|
return Symbols.post_add;
|
||||||
|
case 'wallet.orders.paid':
|
||||||
|
return Symbols.shopping_bag;
|
||||||
|
case 'posts.reactions.new':
|
||||||
|
return Symbols.add_reaction;
|
||||||
|
default:
|
||||||
|
return Symbols.notifications;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final pfp = notification.meta['pfp'] as String?;
|
||||||
|
final images = notification.meta['images'] as List?;
|
||||||
|
final imageIds = images?.cast<String>() ?? [];
|
||||||
|
|
||||||
|
return ListTile(
|
||||||
|
isThreeLine: true,
|
||||||
|
contentPadding:
|
||||||
|
contentPadding ??
|
||||||
|
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
|
leading: pfp != null
|
||||||
|
? ProfilePictureWidget(
|
||||||
|
fileId: pfp,
|
||||||
|
radius: avatarRadius ?? (compact ? 16 : 20),
|
||||||
|
)
|
||||||
|
: CircleAvatar(
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
|
||||||
|
radius: avatarRadius ?? (compact ? 16 : 20),
|
||||||
|
child: Icon(
|
||||||
|
_getNotificationIcon(notification.topic),
|
||||||
|
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||||
|
size: compact ? 16 : 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
notification.title,
|
||||||
|
style: compact
|
||||||
|
? Theme.of(context).textTheme.bodySmall
|
||||||
|
: Theme.of(context).textTheme.titleMedium,
|
||||||
|
maxLines: compact ? 1 : null,
|
||||||
|
overflow: compact ? TextOverflow.ellipsis : null,
|
||||||
|
),
|
||||||
|
subtitle: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
if (notification.subtitle.isNotEmpty && !compact)
|
||||||
|
Text(notification.subtitle, maxLines: compact ? 3 : null).bold(),
|
||||||
|
Row(
|
||||||
|
spacing: 6,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
DateFormat().format(notification.createdAt.toLocal()),
|
||||||
|
style: Theme.of(
|
||||||
|
context,
|
||||||
|
).textTheme.bodySmall?.copyWith(fontSize: compact ? 10 : 11),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'·',
|
||||||
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
|
fontSize: compact ? 10 : 11,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
RelativeTime(context).format(notification.createdAt.toLocal()),
|
||||||
|
style: Theme.of(
|
||||||
|
context,
|
||||||
|
).textTheme.bodySmall?.copyWith(fontSize: compact ? 10 : 11),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).opacity(0.75).padding(bottom: compact ? 2 : 4),
|
||||||
|
MarkdownTextContent(
|
||||||
|
content: (compact && notification.content.length > 60)
|
||||||
|
? '${notification.content.substring(0, 60).replaceAll('\n', ' ')}...'
|
||||||
|
: notification.content,
|
||||||
|
textStyle:
|
||||||
|
(compact
|
||||||
|
? Theme.of(context).textTheme.bodySmall
|
||||||
|
: Theme.of(context).textTheme.bodyMedium)
|
||||||
|
?.copyWith(
|
||||||
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.onSurface.withOpacity(0.8),
|
||||||
|
fontSize: compact ? 11 : null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (showImages && imageIds.isNotEmpty)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 8),
|
||||||
|
child: Wrap(
|
||||||
|
spacing: 8,
|
||||||
|
runSpacing: 8,
|
||||||
|
children: imageIds.map((imageId) {
|
||||||
|
return SizedBox(
|
||||||
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: CloudImageWidget(
|
||||||
|
fileId: imageId,
|
||||||
|
aspectRatio: 1,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
trailing: notification.viewedAt != null
|
||||||
|
? null
|
||||||
|
: Container(
|
||||||
|
width: compact ? 8 : 12,
|
||||||
|
height: compact ? 8 : 12,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Colors.blue,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -364,7 +364,7 @@ class PaginationListFooter<T> extends HookConsumerWidget {
|
|||||||
onVisibilityChanged: (VisibilityInfo info) {
|
onVisibilityChanged: (VisibilityInfo info) {
|
||||||
hasBeenVisible.value = true;
|
hasBeenVisible.value = true;
|
||||||
if (!noti.fetchedAll && !data.isLoading && !data.hasError) {
|
if (!noti.fetchedAll && !data.isLoading && !data.hasError) {
|
||||||
noti.fetchFurther();
|
if (context.mounted) noti.fetchFurther();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: isSliver ? SliverToBoxAdapter(child: child) : child,
|
child: isSliver ? SliverToBoxAdapter(child: child) : child,
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ class PostComposeSheet extends HookConsumerWidget {
|
|||||||
final isTablet =
|
final isTablet =
|
||||||
isWideScreen(context) &&
|
isWideScreen(context) &&
|
||||||
!kIsWeb &&
|
!kIsWeb &&
|
||||||
(Platform.isAndroid || Platform.isAndroid);
|
(Platform.isAndroid || Platform.isIOS);
|
||||||
|
|
||||||
return SheetScaffold(
|
return SheetScaffold(
|
||||||
heightFactor: isTablet ? 0.95 : 0.8,
|
heightFactor: isTablet ? 0.95 : 0.8,
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ Future<List<SnPost>> featuredPosts(Ref ref) async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class PostFeaturedList extends HookConsumerWidget {
|
class PostFeaturedList extends HookConsumerWidget {
|
||||||
const PostFeaturedList({super.key});
|
final bool collapsable;
|
||||||
|
const PostFeaturedList({super.key, this.collapsable = true});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@@ -86,6 +87,7 @@ class PostFeaturedList extends HookConsumerWidget {
|
|||||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
margin: EdgeInsets.zero,
|
margin: EdgeInsets.zero,
|
||||||
child: Column(
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 48,
|
height: 48,
|
||||||
@@ -121,36 +123,37 @@ class PostFeaturedList extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
icon: const Icon(Symbols.arrow_right),
|
icon: const Icon(Symbols.arrow_right),
|
||||||
),
|
),
|
||||||
IconButton(
|
if (collapsable)
|
||||||
padding: EdgeInsets.zero,
|
IconButton(
|
||||||
visualDensity: VisualDensity.compact,
|
padding: EdgeInsets.zero,
|
||||||
constraints: const BoxConstraints(),
|
visualDensity: VisualDensity.compact,
|
||||||
onPressed: () {
|
constraints: const BoxConstraints(),
|
||||||
isCollapsed.value = !isCollapsed.value;
|
onPressed: () {
|
||||||
talker.info(
|
isCollapsed.value = !isCollapsed.value;
|
||||||
'Manual toggle. isCollapsed set to ${isCollapsed.value}',
|
|
||||||
);
|
|
||||||
if (isCollapsed.value &&
|
|
||||||
featuredPostsAsync.hasValue &&
|
|
||||||
featuredPostsAsync.value!.isNotEmpty) {
|
|
||||||
prefs.setString(
|
|
||||||
kFeaturedPostsCollapsedId,
|
|
||||||
featuredPostsAsync.value!.first.id,
|
|
||||||
);
|
|
||||||
talker.info(
|
talker.info(
|
||||||
'Stored collapsed ID: ${featuredPostsAsync.value!.first.id}',
|
'Manual toggle. isCollapsed set to ${isCollapsed.value}',
|
||||||
);
|
);
|
||||||
} else {
|
if (isCollapsed.value &&
|
||||||
prefs.remove(kFeaturedPostsCollapsedId);
|
featuredPostsAsync.hasValue &&
|
||||||
talker.info('Removed stored collapsed ID.');
|
featuredPostsAsync.value!.isNotEmpty) {
|
||||||
}
|
prefs.setString(
|
||||||
},
|
kFeaturedPostsCollapsedId,
|
||||||
icon: Icon(
|
featuredPostsAsync.value!.first.id,
|
||||||
isCollapsed.value
|
);
|
||||||
? Symbols.expand_more
|
talker.info(
|
||||||
: Symbols.expand_less,
|
'Stored collapsed ID: ${featuredPostsAsync.value!.first.id}',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
prefs.remove(kFeaturedPostsCollapsedId);
|
||||||
|
talker.info('Removed stored collapsed ID.');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon: Icon(
|
||||||
|
isCollapsed.value
|
||||||
|
? Symbols.expand_more
|
||||||
|
: Symbols.expand_less,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
).padding(horizontal: 16, vertical: 8),
|
).padding(horizontal: 16, vertical: 8),
|
||||||
),
|
),
|
||||||
@@ -158,14 +161,14 @@ class PostFeaturedList extends HookConsumerWidget {
|
|||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
child: Visibility(
|
child: Visibility(
|
||||||
visible: !isCollapsed.value,
|
visible: collapsable ? !isCollapsed.value : true,
|
||||||
child: featuredPostsAsync.when(
|
child: featuredPostsAsync.when(
|
||||||
loading:
|
loading: () =>
|
||||||
() => const Center(child: CircularProgressIndicator()),
|
const Center(child: CircularProgressIndicator()),
|
||||||
error: (error, stack) => Center(child: Text('Error: $error')),
|
error: (error, stack) => Center(child: Text('Error: $error')),
|
||||||
data: (posts) {
|
data: (posts) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: 320,
|
height: 344,
|
||||||
child: PageView.builder(
|
child: PageView.builder(
|
||||||
controller: pageViewController,
|
controller: pageViewController,
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class SliverPostList extends HookConsumerWidget {
|
|||||||
final PostItemType itemType;
|
final PostItemType itemType;
|
||||||
final Color? backgroundColor;
|
final Color? backgroundColor;
|
||||||
final EdgeInsets? padding;
|
final EdgeInsets? padding;
|
||||||
|
final EdgeInsets? itemPadding;
|
||||||
final bool isOpenable;
|
final bool isOpenable;
|
||||||
final Function? onRefresh;
|
final Function? onRefresh;
|
||||||
final Function(SnPost)? onUpdate;
|
final Function(SnPost)? onUpdate;
|
||||||
@@ -34,6 +35,7 @@ class SliverPostList extends HookConsumerWidget {
|
|||||||
this.itemType = PostItemType.regular,
|
this.itemType = PostItemType.regular,
|
||||||
this.backgroundColor,
|
this.backgroundColor,
|
||||||
this.padding,
|
this.padding,
|
||||||
|
this.itemPadding,
|
||||||
this.isOpenable = true,
|
this.isOpenable = true,
|
||||||
this.onRefresh,
|
this.onRefresh,
|
||||||
this.onUpdate,
|
this.onUpdate,
|
||||||
@@ -74,17 +76,17 @@ class SliverPostList extends HookConsumerWidget {
|
|||||||
return Center(
|
return Center(
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: BoxConstraints(maxWidth: maxWidth!),
|
constraints: BoxConstraints(maxWidth: maxWidth!),
|
||||||
child: _buildPostItem(post),
|
child: _buildPostItem(post, itemPadding),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return _buildPostItem(post);
|
return _buildPostItem(post, itemPadding);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildPostItem(SnPost post) {
|
Widget _buildPostItem(SnPost post, EdgeInsets? padding) {
|
||||||
switch (itemType) {
|
switch (itemType) {
|
||||||
case PostItemType.creator:
|
case PostItemType.creator:
|
||||||
return PostItemCreator(
|
return PostItemCreator(
|
||||||
@@ -97,7 +99,8 @@ class SliverPostList extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
case PostItemType.regular:
|
case PostItemType.regular:
|
||||||
return Card(
|
return Card(
|
||||||
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
margin:
|
||||||
|
itemPadding ?? EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
child: PostActionableItem(item: post, borderRadius: 8),
|
child: PostActionableItem(item: post, borderRadius: 8),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ class PostRepliesList extends HookConsumerWidget {
|
|||||||
isShowReference: false,
|
isShowReference: false,
|
||||||
isEmbedOpenable: true,
|
isEmbedOpenable: true,
|
||||||
onOpen: onOpen,
|
onOpen: onOpen,
|
||||||
|
onUpdate: (newPost) {},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -139,7 +139,9 @@ class PostReplyPreview extends HookConsumerWidget {
|
|||||||
spacing: 8,
|
spacing: 8,
|
||||||
children: [
|
children: [
|
||||||
ProfilePictureWidget(
|
ProfilePictureWidget(
|
||||||
file: post.publisher.picture,
|
file:
|
||||||
|
post.publisher.picture ??
|
||||||
|
post.publisher.account?.profile.picture,
|
||||||
radius: 12,
|
radius: 12,
|
||||||
).padding(top: 4),
|
).padding(top: 4),
|
||||||
if (post.content?.isNotEmpty ?? false)
|
if (post.content?.isNotEmpty ?? false)
|
||||||
@@ -218,7 +220,9 @@ class PostReplyPreview extends HookConsumerWidget {
|
|||||||
spacing: 8,
|
spacing: 8,
|
||||||
children: [
|
children: [
|
||||||
ProfilePictureWidget(
|
ProfilePictureWidget(
|
||||||
file: data.value?.publisher.picture,
|
file:
|
||||||
|
data.value?.publisher.picture ??
|
||||||
|
data.value?.publisher.account?.profile.picture,
|
||||||
radius: 12,
|
radius: 12,
|
||||||
).padding(top: 4),
|
).padding(top: 4),
|
||||||
if (data.value?.content?.isNotEmpty ?? false)
|
if (data.value?.content?.isNotEmpty ?? false)
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/pods/post/post_list.dart';
|
import 'package:island/pods/post/post_list.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
|
||||||
class PostFilterWidget extends StatefulWidget {
|
class PostFilterWidget extends HookConsumerWidget {
|
||||||
final TabController categoryTabController;
|
final TabController categoryTabController;
|
||||||
final PostListQuery initialQuery;
|
final PostListQuery initialQuery;
|
||||||
final ValueChanged<PostListQuery> onQueryChanged;
|
final ValueChanged<PostListQuery> onQueryChanged;
|
||||||
@@ -19,79 +21,55 @@ class PostFilterWidget extends StatefulWidget {
|
|||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<PostFilterWidget> createState() => _PostFilterWidgetState();
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
}
|
final includeReplies = useState<bool?>(initialQuery.includeReplies);
|
||||||
|
final mediaOnly = useState<bool>(initialQuery.mediaOnly ?? false);
|
||||||
class _PostFilterWidgetState extends State<PostFilterWidget> {
|
final queryTerm = useState<String?>(initialQuery.queryTerm);
|
||||||
late bool? _includeReplies;
|
final order = useState<String?>(initialQuery.order);
|
||||||
late bool _mediaOnly;
|
final orderDesc = useState<bool>(initialQuery.orderDesc);
|
||||||
late String? _queryTerm;
|
final periodStart = useState<int?>(initialQuery.periodStart);
|
||||||
late String? _order;
|
final periodEnd = useState<int?>(initialQuery.periodEnd);
|
||||||
late bool _orderDesc;
|
final type = useState<int?>(initialQuery.type);
|
||||||
late int? _periodStart;
|
final showAdvancedFilters = useState<bool>(false);
|
||||||
late int? _periodEnd;
|
final searchController = useTextEditingController(
|
||||||
late int? _type;
|
text: initialQuery.queryTerm,
|
||||||
late bool _showAdvancedFilters;
|
|
||||||
late TextEditingController _searchController;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_includeReplies = widget.initialQuery.includeReplies;
|
|
||||||
_mediaOnly = widget.initialQuery.mediaOnly ?? false;
|
|
||||||
_queryTerm = widget.initialQuery.queryTerm;
|
|
||||||
_order = widget.initialQuery.order;
|
|
||||||
_orderDesc = widget.initialQuery.orderDesc;
|
|
||||||
_periodStart = widget.initialQuery.periodStart;
|
|
||||||
_periodEnd = widget.initialQuery.periodEnd;
|
|
||||||
_type = widget.initialQuery.type;
|
|
||||||
_showAdvancedFilters = false;
|
|
||||||
_searchController = TextEditingController(text: _queryTerm);
|
|
||||||
|
|
||||||
widget.categoryTabController.addListener(_onTabChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
widget.categoryTabController.removeListener(_onTabChanged);
|
|
||||||
_searchController.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onTabChanged() {
|
|
||||||
final tabIndex = widget.categoryTabController.index;
|
|
||||||
setState(() {
|
|
||||||
_type = switch (tabIndex) {
|
|
||||||
1 => 0,
|
|
||||||
2 => 1,
|
|
||||||
_ => null,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
_updateQuery();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _updateQuery() {
|
|
||||||
final newQuery = widget.initialQuery.copyWith(
|
|
||||||
includeReplies: _includeReplies,
|
|
||||||
mediaOnly: _mediaOnly,
|
|
||||||
queryTerm: _queryTerm,
|
|
||||||
order: _order,
|
|
||||||
periodStart: _periodStart,
|
|
||||||
periodEnd: _periodEnd,
|
|
||||||
orderDesc: _orderDesc,
|
|
||||||
type: _type,
|
|
||||||
);
|
);
|
||||||
widget.onQueryChanged(newQuery);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
void updateQuery() {
|
||||||
Widget build(BuildContext context) {
|
final newQuery = initialQuery.copyWith(
|
||||||
|
includeReplies: includeReplies.value,
|
||||||
|
mediaOnly: mediaOnly.value,
|
||||||
|
queryTerm: queryTerm.value,
|
||||||
|
order: order.value,
|
||||||
|
periodStart: periodStart.value,
|
||||||
|
periodEnd: periodEnd.value,
|
||||||
|
orderDesc: orderDesc.value,
|
||||||
|
type: type.value,
|
||||||
|
);
|
||||||
|
onQueryChanged(newQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
void onTabChanged() {
|
||||||
|
final tabIndex = categoryTabController.index;
|
||||||
|
type.value = switch (tabIndex) {
|
||||||
|
1 => 0,
|
||||||
|
2 => 1,
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
|
updateQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
categoryTabController.addListener(onTabChanged);
|
||||||
|
return () => categoryTabController.removeListener(onTabChanged);
|
||||||
|
}, [categoryTabController]);
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
TabBar(
|
TabBar(
|
||||||
controller: widget.categoryTabController,
|
controller: categoryTabController,
|
||||||
dividerColor: Colors.transparent,
|
dividerColor: Colors.transparent,
|
||||||
splashBorderRadius: const BorderRadius.all(Radius.circular(8)),
|
splashBorderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
tabs: [
|
tabs: [
|
||||||
@@ -108,20 +86,18 @@ class _PostFilterWidgetState extends State<PostFilterWidget> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: CheckboxListTile(
|
child: CheckboxListTile(
|
||||||
title: Text('reply'.tr()),
|
title: Text('reply'.tr()),
|
||||||
value: _includeReplies,
|
value: includeReplies.value,
|
||||||
tristate: true,
|
tristate: true,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
// Cycle through: null -> false -> true -> null
|
final current = includeReplies.value;
|
||||||
setState(() {
|
if (current == null) {
|
||||||
if (_includeReplies == null) {
|
includeReplies.value = false;
|
||||||
_includeReplies = false;
|
} else if (current == false) {
|
||||||
} else if (_includeReplies == false) {
|
includeReplies.value = true;
|
||||||
_includeReplies = true;
|
} else {
|
||||||
} else {
|
includeReplies.value = null;
|
||||||
_includeReplies = null;
|
}
|
||||||
}
|
updateQuery();
|
||||||
});
|
|
||||||
_updateQuery();
|
|
||||||
},
|
},
|
||||||
dense: true,
|
dense: true,
|
||||||
controlAffinity: ListTileControlAffinity.leading,
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
@@ -131,14 +107,12 @@ class _PostFilterWidgetState extends State<PostFilterWidget> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: CheckboxListTile(
|
child: CheckboxListTile(
|
||||||
title: Text('attachments'.tr()),
|
title: Text('attachments'.tr()),
|
||||||
value: _mediaOnly,
|
value: mediaOnly.value,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
setState(() {
|
if (value != null) {
|
||||||
if (value != null) {
|
mediaOnly.value = value;
|
||||||
_mediaOnly = value;
|
}
|
||||||
}
|
updateQuery();
|
||||||
});
|
|
||||||
_updateQuery();
|
|
||||||
},
|
},
|
||||||
dense: true,
|
dense: true,
|
||||||
controlAffinity: ListTileControlAffinity.leading,
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
@@ -149,14 +123,12 @@ class _PostFilterWidgetState extends State<PostFilterWidget> {
|
|||||||
),
|
),
|
||||||
CheckboxListTile(
|
CheckboxListTile(
|
||||||
title: Text('descendingOrder'.tr()),
|
title: Text('descendingOrder'.tr()),
|
||||||
value: _orderDesc,
|
value: orderDesc.value,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
setState(() {
|
if (value != null) {
|
||||||
if (value != null) {
|
orderDesc.value = value;
|
||||||
_orderDesc = value;
|
}
|
||||||
}
|
updateQuery();
|
||||||
});
|
|
||||||
_updateQuery();
|
|
||||||
},
|
},
|
||||||
dense: true,
|
dense: true,
|
||||||
controlAffinity: ListTileControlAffinity.leading,
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
@@ -173,24 +145,24 @@ class _PostFilterWidgetState extends State<PostFilterWidget> {
|
|||||||
borderRadius: BorderRadius.all(const Radius.circular(8)),
|
borderRadius: BorderRadius.all(const Radius.circular(8)),
|
||||||
),
|
),
|
||||||
trailing: Icon(
|
trailing: Icon(
|
||||||
_showAdvancedFilters ? Symbols.expand_less : Symbols.expand_more,
|
showAdvancedFilters.value
|
||||||
|
? Symbols.expand_less
|
||||||
|
: Symbols.expand_more,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
setState(() {
|
showAdvancedFilters.value = !showAdvancedFilters.value;
|
||||||
_showAdvancedFilters = !_showAdvancedFilters;
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (_showAdvancedFilters) ...[
|
if (showAdvancedFilters.value) ...[
|
||||||
const Divider(height: 1),
|
const Divider(height: 1),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
if (!widget.hideSearch)
|
if (!hideSearch)
|
||||||
TextField(
|
TextField(
|
||||||
controller: _searchController,
|
controller: searchController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'search'.tr(),
|
labelText: 'search'.tr(),
|
||||||
hintText: 'searchPosts'.tr(),
|
hintText: 'searchPosts'.tr(),
|
||||||
@@ -204,13 +176,11 @@ class _PostFilterWidgetState extends State<PostFilterWidget> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
setState(() {
|
queryTerm.value = value.isEmpty ? null : value;
|
||||||
_queryTerm = value.isEmpty ? null : value;
|
updateQuery();
|
||||||
});
|
|
||||||
_updateQuery();
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (!widget.hideSearch) const Gap(12),
|
if (!hideSearch) const Gap(12),
|
||||||
DropdownButtonFormField<String>(
|
DropdownButtonFormField<String>(
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'sortBy'.tr(),
|
labelText: 'sortBy'.tr(),
|
||||||
@@ -222,7 +192,7 @@ class _PostFilterWidgetState extends State<PostFilterWidget> {
|
|||||||
vertical: 8,
|
vertical: 8,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
value: _order,
|
value: order.value,
|
||||||
items: [
|
items: [
|
||||||
DropdownMenuItem(value: 'date', child: Text('date'.tr())),
|
DropdownMenuItem(value: 'date', child: Text('date'.tr())),
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
@@ -231,10 +201,8 @@ class _PostFilterWidgetState extends State<PostFilterWidget> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
setState(() {
|
order.value = value;
|
||||||
_order = value;
|
updateQuery();
|
||||||
});
|
|
||||||
_updateQuery();
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const Gap(12),
|
const Gap(12),
|
||||||
@@ -245,9 +213,9 @@ class _PostFilterWidgetState extends State<PostFilterWidget> {
|
|||||||
onTap: () async {
|
onTap: () async {
|
||||||
final pickedDate = await showDatePicker(
|
final pickedDate = await showDatePicker(
|
||||||
context: context,
|
context: context,
|
||||||
initialDate: _periodStart != null
|
initialDate: periodStart.value != null
|
||||||
? DateTime.fromMillisecondsSinceEpoch(
|
? DateTime.fromMillisecondsSinceEpoch(
|
||||||
_periodStart! * 1000,
|
periodStart.value! * 1000,
|
||||||
)
|
)
|
||||||
: DateTime.now(),
|
: DateTime.now(),
|
||||||
firstDate: DateTime(2000),
|
firstDate: DateTime(2000),
|
||||||
@@ -256,11 +224,9 @@ class _PostFilterWidgetState extends State<PostFilterWidget> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (pickedDate != null) {
|
if (pickedDate != null) {
|
||||||
setState(() {
|
periodStart.value =
|
||||||
_periodStart =
|
pickedDate.millisecondsSinceEpoch ~/ 1000;
|
||||||
pickedDate.millisecondsSinceEpoch ~/ 1000;
|
updateQuery();
|
||||||
});
|
|
||||||
_updateQuery();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: InputDecorator(
|
child: InputDecorator(
|
||||||
@@ -278,9 +244,9 @@ class _PostFilterWidgetState extends State<PostFilterWidget> {
|
|||||||
suffixIcon: const Icon(Symbols.calendar_today),
|
suffixIcon: const Icon(Symbols.calendar_today),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
_periodStart != null
|
periodStart.value != null
|
||||||
? DateTime.fromMillisecondsSinceEpoch(
|
? DateTime.fromMillisecondsSinceEpoch(
|
||||||
_periodStart! * 1000,
|
periodStart.value! * 1000,
|
||||||
).toString().split(' ')[0]
|
).toString().split(' ')[0]
|
||||||
: 'selectDate'.tr(),
|
: 'selectDate'.tr(),
|
||||||
),
|
),
|
||||||
@@ -293,9 +259,9 @@ class _PostFilterWidgetState extends State<PostFilterWidget> {
|
|||||||
onTap: () async {
|
onTap: () async {
|
||||||
final pickedDate = await showDatePicker(
|
final pickedDate = await showDatePicker(
|
||||||
context: context,
|
context: context,
|
||||||
initialDate: _periodEnd != null
|
initialDate: periodEnd.value != null
|
||||||
? DateTime.fromMillisecondsSinceEpoch(
|
? DateTime.fromMillisecondsSinceEpoch(
|
||||||
_periodEnd! * 1000,
|
periodEnd.value! * 1000,
|
||||||
)
|
)
|
||||||
: DateTime.now(),
|
: DateTime.now(),
|
||||||
firstDate: DateTime(2000),
|
firstDate: DateTime(2000),
|
||||||
@@ -304,11 +270,9 @@ class _PostFilterWidgetState extends State<PostFilterWidget> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (pickedDate != null) {
|
if (pickedDate != null) {
|
||||||
setState(() {
|
periodEnd.value =
|
||||||
_periodEnd =
|
pickedDate.millisecondsSinceEpoch ~/ 1000;
|
||||||
pickedDate.millisecondsSinceEpoch ~/ 1000;
|
updateQuery();
|
||||||
});
|
|
||||||
_updateQuery();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: InputDecorator(
|
child: InputDecorator(
|
||||||
@@ -326,9 +290,9 @@ class _PostFilterWidgetState extends State<PostFilterWidget> {
|
|||||||
suffixIcon: const Icon(Symbols.calendar_today),
|
suffixIcon: const Icon(Symbols.calendar_today),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
_periodEnd != null
|
periodEnd.value != null
|
||||||
? DateTime.fromMillisecondsSinceEpoch(
|
? DateTime.fromMillisecondsSinceEpoch(
|
||||||
_periodEnd! * 1000,
|
periodEnd.value! * 1000,
|
||||||
).toString().split(' ')[0]
|
).toString().split(' ')[0]
|
||||||
: 'selectDate'.tr(),
|
: 'selectDate'.tr(),
|
||||||
),
|
),
|
||||||
|
|||||||
254
lib/widgets/posts/post_subscription_filter.dart
Normal file
254
lib/widgets/posts/post_subscription_filter.dart
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
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/post.dart';
|
||||||
|
import 'package:island/models/post_category.dart';
|
||||||
|
import 'package:island/pods/network.dart';
|
||||||
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
|
part 'post_subscription_filter.g.dart';
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
Future<List<SnPublisherSubscription>> publishersSubscriptions(Ref ref) async {
|
||||||
|
final client = ref.read(apiClientProvider);
|
||||||
|
|
||||||
|
final response = await client.get('/sphere/publishers/subscriptions');
|
||||||
|
|
||||||
|
return response.data
|
||||||
|
.map((json) => SnPublisherSubscription.fromJson(json))
|
||||||
|
.cast<SnPublisherSubscription>()
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
Future<List<SnCategorySubscription>> categoriesSubscriptions(Ref ref) async {
|
||||||
|
final client = ref.read(apiClientProvider);
|
||||||
|
|
||||||
|
final response = await client.get('/sphere/categories/subscriptions');
|
||||||
|
|
||||||
|
return response.data
|
||||||
|
.map((json) => SnCategorySubscription.fromJson(json))
|
||||||
|
.cast<SnCategorySubscription>()
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
class PostSubscriptionFilterWidget extends HookConsumerWidget {
|
||||||
|
final List<String> initialSelectedPublishers;
|
||||||
|
final List<String> initialSelectedCategories;
|
||||||
|
final List<String> initialSelectedTags;
|
||||||
|
final ValueChanged<List<String>> onSelectedPublishersChanged;
|
||||||
|
final ValueChanged<List<String>> onSelectedCategoriesChanged;
|
||||||
|
final ValueChanged<List<String>> onSelectedTagsChanged;
|
||||||
|
final bool hideSearch;
|
||||||
|
|
||||||
|
const PostSubscriptionFilterWidget({
|
||||||
|
super.key,
|
||||||
|
required this.initialSelectedPublishers,
|
||||||
|
required this.initialSelectedCategories,
|
||||||
|
required this.initialSelectedTags,
|
||||||
|
required this.onSelectedPublishersChanged,
|
||||||
|
required this.onSelectedCategoriesChanged,
|
||||||
|
required this.onSelectedTagsChanged,
|
||||||
|
this.hideSearch = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final selectedPublishers = useState<List<String>>(
|
||||||
|
initialSelectedPublishers,
|
||||||
|
);
|
||||||
|
final selectedCategories = useState<List<String>>(
|
||||||
|
initialSelectedCategories,
|
||||||
|
);
|
||||||
|
final selectedTags = useState<List<String>>(initialSelectedTags);
|
||||||
|
|
||||||
|
final publishersAsync = ref.watch(publishersSubscriptionsProvider);
|
||||||
|
final categoriesAsync = ref.watch(categoriesSubscriptionsProvider);
|
||||||
|
|
||||||
|
void updateSelection() {
|
||||||
|
onSelectedPublishersChanged(selectedPublishers.value);
|
||||||
|
onSelectedCategoriesChanged(selectedCategories.value);
|
||||||
|
onSelectedTagsChanged(selectedTags.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.subscriptions, size: 20),
|
||||||
|
Text(
|
||||||
|
'exploreFilterSubscriptions'.tr(),
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 16, top: 12),
|
||||||
|
const Gap(12),
|
||||||
|
|
||||||
|
// Publishers Section
|
||||||
|
publishersAsync.when(
|
||||||
|
data: (subscriptions) {
|
||||||
|
if (subscriptions.isEmpty) {
|
||||||
|
return Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Text('noSubscriptions'.tr()),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'publishers'.tr(),
|
||||||
|
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
).padding(bottom: 8, horizontal: 16),
|
||||||
|
...subscriptions.map((subscription) {
|
||||||
|
final isSelected = selectedPublishers.value.contains(
|
||||||
|
subscription.publisher.name,
|
||||||
|
);
|
||||||
|
final publisher = subscription.publisher;
|
||||||
|
|
||||||
|
return CheckboxListTile(
|
||||||
|
controlAffinity: ListTileControlAffinity.trailing,
|
||||||
|
title: Text(publisher.nick),
|
||||||
|
subtitle: Text('@${publisher.name}'),
|
||||||
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||||
|
),
|
||||||
|
value: isSelected,
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value == true) {
|
||||||
|
selectedPublishers.value = [
|
||||||
|
...selectedPublishers.value,
|
||||||
|
subscription.publisher.name,
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
selectedPublishers.value = selectedPublishers.value
|
||||||
|
.where(
|
||||||
|
(name) => name != subscription.publisher.name,
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
updateSelection();
|
||||||
|
},
|
||||||
|
dense: true,
|
||||||
|
secondary: ProfilePictureWidget(
|
||||||
|
file: subscription.publisher.picture,
|
||||||
|
),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
loading: () => const Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.all(16.0),
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
error: (error, stack) => Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Text('errorLoadingSubscriptions'.tr()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const Divider(height: 1).padding(vertical: 8),
|
||||||
|
|
||||||
|
// Categories Section
|
||||||
|
categoriesAsync.when(
|
||||||
|
data: (subscriptions) {
|
||||||
|
if (subscriptions.isEmpty) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'categoriesAndTags'.tr(),
|
||||||
|
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
).padding(bottom: 8, horizontal: 16),
|
||||||
|
...subscriptions.map((subscription) {
|
||||||
|
final category = subscription.category;
|
||||||
|
final tag = subscription.tag;
|
||||||
|
final slug = category?.slug ?? tag?.slug;
|
||||||
|
final displayTitle =
|
||||||
|
category?.categoryDisplayTitle ??
|
||||||
|
tag?.name ??
|
||||||
|
slug ??
|
||||||
|
'';
|
||||||
|
final isCategorySelected = selectedCategories.value
|
||||||
|
.contains(slug);
|
||||||
|
final isTagSelected = selectedTags.value.contains(slug);
|
||||||
|
|
||||||
|
return CheckboxListTile(
|
||||||
|
controlAffinity: ListTileControlAffinity.trailing,
|
||||||
|
title: Text(displayTitle),
|
||||||
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||||
|
),
|
||||||
|
secondary: category != null
|
||||||
|
? Icon(Symbols.category)
|
||||||
|
: Icon(Symbols.tag),
|
||||||
|
value: category != null
|
||||||
|
? isCategorySelected
|
||||||
|
: isTagSelected,
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value == true) {
|
||||||
|
if (category != null) {
|
||||||
|
selectedCategories.value = [
|
||||||
|
...selectedCategories.value,
|
||||||
|
slug!,
|
||||||
|
];
|
||||||
|
} else if (tag != null) {
|
||||||
|
selectedTags.value = [...selectedTags.value, slug!];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (category != null) {
|
||||||
|
selectedCategories.value = selectedCategories.value
|
||||||
|
.where((id) => id != slug)
|
||||||
|
.toList();
|
||||||
|
} else if (tag != null) {
|
||||||
|
selectedTags.value = selectedTags.value
|
||||||
|
.where((id) => id != slug)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateSelection();
|
||||||
|
},
|
||||||
|
dense: true,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
loading: () => const SizedBox.shrink(),
|
||||||
|
error: (error, stack) => const SizedBox.shrink(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
94
lib/widgets/posts/post_subscription_filter.g.dart
Normal file
94
lib/widgets/posts/post_subscription_filter.g.dart
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'post_subscription_filter.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint, type=warning
|
||||||
|
|
||||||
|
@ProviderFor(publishersSubscriptions)
|
||||||
|
const publishersSubscriptionsProvider = PublishersSubscriptionsProvider._();
|
||||||
|
|
||||||
|
final class PublishersSubscriptionsProvider
|
||||||
|
extends
|
||||||
|
$FunctionalProvider<
|
||||||
|
AsyncValue<List<SnPublisherSubscription>>,
|
||||||
|
List<SnPublisherSubscription>,
|
||||||
|
FutureOr<List<SnPublisherSubscription>>
|
||||||
|
>
|
||||||
|
with
|
||||||
|
$FutureModifier<List<SnPublisherSubscription>>,
|
||||||
|
$FutureProvider<List<SnPublisherSubscription>> {
|
||||||
|
const PublishersSubscriptionsProvider._()
|
||||||
|
: super(
|
||||||
|
from: null,
|
||||||
|
argument: null,
|
||||||
|
retry: null,
|
||||||
|
name: r'publishersSubscriptionsProvider',
|
||||||
|
isAutoDispose: true,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$publishersSubscriptionsHash();
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
$FutureProviderElement<List<SnPublisherSubscription>> $createElement(
|
||||||
|
$ProviderPointer pointer,
|
||||||
|
) => $FutureProviderElement(pointer);
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<List<SnPublisherSubscription>> create(Ref ref) {
|
||||||
|
return publishersSubscriptions(ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$publishersSubscriptionsHash() =>
|
||||||
|
r'208463c1f879a3ddab4092112e312a0cd27ebc2f';
|
||||||
|
|
||||||
|
@ProviderFor(categoriesSubscriptions)
|
||||||
|
const categoriesSubscriptionsProvider = CategoriesSubscriptionsProvider._();
|
||||||
|
|
||||||
|
final class CategoriesSubscriptionsProvider
|
||||||
|
extends
|
||||||
|
$FunctionalProvider<
|
||||||
|
AsyncValue<List<SnCategorySubscription>>,
|
||||||
|
List<SnCategorySubscription>,
|
||||||
|
FutureOr<List<SnCategorySubscription>>
|
||||||
|
>
|
||||||
|
with
|
||||||
|
$FutureModifier<List<SnCategorySubscription>>,
|
||||||
|
$FutureProvider<List<SnCategorySubscription>> {
|
||||||
|
const CategoriesSubscriptionsProvider._()
|
||||||
|
: super(
|
||||||
|
from: null,
|
||||||
|
argument: null,
|
||||||
|
retry: null,
|
||||||
|
name: r'categoriesSubscriptionsProvider',
|
||||||
|
isAutoDispose: true,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$categoriesSubscriptionsHash();
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
$FutureProviderElement<List<SnCategorySubscription>> $createElement(
|
||||||
|
$ProviderPointer pointer,
|
||||||
|
) => $FutureProviderElement(pointer);
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<List<SnCategorySubscription>> create(Ref ref) {
|
||||||
|
return categoriesSubscriptions(ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$categoriesSubscriptionsHash() =>
|
||||||
|
r'14a8f04d258d1a10aae20ca959495926840c9386';
|
||||||
@@ -5,11 +5,15 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:island/models/publisher.dart';
|
import 'package:island/models/publisher.dart';
|
||||||
import 'package:island/widgets/content/cloud_files.dart';
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
|
|
||||||
class PublisherCard extends ConsumerWidget {
|
class PublisherDiscoveryCard extends ConsumerWidget {
|
||||||
final SnPublisher publisher;
|
final SnPublisher publisher;
|
||||||
final double? maxWidth;
|
final double? maxWidth;
|
||||||
|
|
||||||
const PublisherCard({super.key, required this.publisher, this.maxWidth});
|
const PublisherDiscoveryCard({
|
||||||
|
super.key,
|
||||||
|
required this.publisher,
|
||||||
|
this.maxWidth,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
|||||||
@@ -6,21 +6,20 @@ import 'package:island/models/realm.dart';
|
|||||||
import 'package:island/widgets/content/cloud_files.dart';
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
|
||||||
class RealmCard extends ConsumerWidget {
|
class RealmDiscoveryCard extends ConsumerWidget {
|
||||||
final SnRealm realm;
|
final SnRealm realm;
|
||||||
final double? maxWidth;
|
final double? maxWidth;
|
||||||
|
|
||||||
const RealmCard({super.key, required this.realm, this.maxWidth});
|
const RealmDiscoveryCard({super.key, required this.realm, this.maxWidth});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
Widget imageWidget;
|
Widget imageWidget;
|
||||||
if (realm.picture != null) {
|
if (realm.picture != null) {
|
||||||
imageWidget =
|
imageWidget = imageWidget = CloudImageWidget(
|
||||||
imageWidget = CloudImageWidget(
|
file: realm.background,
|
||||||
file: realm.background,
|
fit: BoxFit.cover,
|
||||||
fit: BoxFit.cover,
|
);
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
imageWidget = ColoredBox(
|
imageWidget = ColoredBox(
|
||||||
color: Theme.of(context).colorScheme.secondaryContainer,
|
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ class SliverRealmList extends HookConsumerWidget {
|
|||||||
itemBuilder: (context, index, realm) {
|
itemBuilder: (context, index, realm) {
|
||||||
return ConstrainedBox(
|
return ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxWidth: 540),
|
constraints: const BoxConstraints(maxWidth: 540),
|
||||||
child: RealmListTile(realm: realm),
|
child: RealmListTile(realm: realm).padding(horizontal: 8),
|
||||||
).center();
|
).center();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -78,28 +78,24 @@ class FileItem extends HookConsumerWidget {
|
|||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
await Navigator.of(context).push(
|
await Navigator.of(context).push(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder:
|
builder: (context) => Scaffold(
|
||||||
(context) => Scaffold(
|
appBar: AppBar(
|
||||||
appBar: AppBar(
|
title: Text(file.relativePath),
|
||||||
title: Text(file.relativePath),
|
backgroundColor: Colors.transparent,
|
||||||
backgroundColor: Colors.transparent,
|
elevation: 0,
|
||||||
elevation: 0,
|
),
|
||||||
),
|
extendBodyBehindAppBar: true,
|
||||||
extendBodyBehindAppBar: true,
|
backgroundColor: Colors.black,
|
||||||
backgroundColor: Colors.black,
|
body: PhotoView(
|
||||||
body: PhotoView(
|
imageProvider: CachedNetworkImageProvider(
|
||||||
imageProvider: CachedNetworkImageProvider(
|
imageUrl,
|
||||||
imageUrl,
|
headers: token != null
|
||||||
headers:
|
? {'Authorization': 'AtField $token'}
|
||||||
token != null
|
: null,
|
||||||
? {'Authorization': 'AtField $token'}
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
heroAttributes: PhotoViewHeroAttributes(
|
|
||||||
tag: file.relativePath,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
heroAttributes: PhotoViewHeroAttributes(tag: file.relativePath),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -107,7 +103,16 @@ class FileItem extends HookConsumerWidget {
|
|||||||
|
|
||||||
Future<void> _openFile(BuildContext context, WidgetRef ref) async {
|
Future<void> _openFile(BuildContext context, WidgetRef ref) async {
|
||||||
final ext = file.relativePath.split('.').last.toLowerCase();
|
final ext = file.relativePath.split('.').last.toLowerCase();
|
||||||
final isImage = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'].contains(ext);
|
final isImage = [
|
||||||
|
'jpg',
|
||||||
|
'jpeg',
|
||||||
|
'png',
|
||||||
|
'gif',
|
||||||
|
'webp',
|
||||||
|
'bmp',
|
||||||
|
'ico',
|
||||||
|
'svg',
|
||||||
|
].contains(ext);
|
||||||
|
|
||||||
if (isImage) {
|
if (isImage) {
|
||||||
await _showImageViewer(context, ref);
|
await _showImageViewer(context, ref);
|
||||||
@@ -182,41 +187,40 @@ class FileItem extends HookConsumerWidget {
|
|||||||
: '${(file.size / 1024).toStringAsFixed(1)} KB',
|
: '${(file.size / 1024).toStringAsFixed(1)} KB',
|
||||||
),
|
),
|
||||||
trailing: PopupMenuButton<String>(
|
trailing: PopupMenuButton<String>(
|
||||||
itemBuilder:
|
itemBuilder: (context) => [
|
||||||
(context) => [
|
PopupMenuItem(
|
||||||
PopupMenuItem(
|
value: 'download',
|
||||||
value: 'download',
|
child: Row(
|
||||||
child: Row(
|
children: [
|
||||||
children: [
|
const Icon(Symbols.download),
|
||||||
const Icon(Symbols.download),
|
const Gap(16),
|
||||||
const Gap(16),
|
Text('Download'),
|
||||||
Text('Download'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (!file.isDirectory) ...[
|
|
||||||
PopupMenuItem(
|
|
||||||
value: 'edit',
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const Icon(Symbols.edit),
|
|
||||||
const Gap(16),
|
|
||||||
Text('Open'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
PopupMenuItem(
|
),
|
||||||
value: 'delete',
|
),
|
||||||
child: Row(
|
if (!file.isDirectory) ...[
|
||||||
children: [
|
PopupMenuItem(
|
||||||
const Icon(Symbols.delete, color: Colors.red),
|
value: 'edit',
|
||||||
const Gap(16),
|
child: Row(
|
||||||
Text('Delete').textColor(Colors.red),
|
children: [
|
||||||
],
|
const Icon(Symbols.edit),
|
||||||
),
|
const Gap(16),
|
||||||
|
Text('Open'),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
|
],
|
||||||
|
PopupMenuItem(
|
||||||
|
value: 'delete',
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.delete, color: Colors.red),
|
||||||
|
const Gap(16),
|
||||||
|
Text('Delete').textColor(Colors.red),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
onSelected: (value) async {
|
onSelected: (value) async {
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case 'download':
|
case 'download':
|
||||||
@@ -228,23 +232,22 @@ class FileItem extends HookConsumerWidget {
|
|||||||
case 'delete':
|
case 'delete':
|
||||||
final confirmed = await showDialog<bool>(
|
final confirmed = await showDialog<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
builder:
|
builder: (context) => AlertDialog(
|
||||||
(context) => AlertDialog(
|
title: const Text('Delete File'),
|
||||||
title: const Text('Delete File'),
|
content: Text(
|
||||||
content: Text(
|
'Are you sure you want to delete "${file.relativePath}"?',
|
||||||
'Are you sure you want to delete "${file.relativePath}"?',
|
),
|
||||||
),
|
actions: [
|
||||||
actions: [
|
TextButton(
|
||||||
TextButton(
|
onPressed: () => Navigator.of(context).pop(false),
|
||||||
onPressed: () => Navigator.of(context).pop(false),
|
child: const Text('Cancel'),
|
||||||
child: const Text('Cancel'),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.of(context).pop(true),
|
|
||||||
child: const Text('Delete'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(true),
|
||||||
|
child: const Text('Delete'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (confirmed == true) {
|
if (confirmed == true) {
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ class PageForm extends HookConsumerWidget {
|
|||||||
int _getPageType(SnPublicationPage? page) {
|
int _getPageType(SnPublicationPage? page) {
|
||||||
if (page == null) return 0; // Default to HTML
|
if (page == null) return 0; // Default to HTML
|
||||||
// Check config structure to determine type
|
// Check config structure to determine type
|
||||||
|
if (page.config?.containsKey('filter') == true ||
|
||||||
|
page.config?.containsKey('layout') == true) {
|
||||||
|
return 2; // Post Page
|
||||||
|
}
|
||||||
return page.config?.containsKey('target') == true ? 1 : 0;
|
return page.config?.containsKey('target') == true ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,10 +41,9 @@ class PageForm extends HookConsumerWidget {
|
|||||||
final pageType = useState(initialType);
|
final pageType = useState(initialType);
|
||||||
|
|
||||||
final htmlController = useTextEditingController(
|
final htmlController = useTextEditingController(
|
||||||
text:
|
text: pageType.value == 0
|
||||||
pageType.value == 0
|
? (page?.config?['html'] ?? page?.config?['content'] ?? '')
|
||||||
? (page?.config?['html'] ?? page?.config?['content'] ?? '')
|
: '',
|
||||||
: '',
|
|
||||||
);
|
);
|
||||||
final titleController = useTextEditingController(
|
final titleController = useTextEditingController(
|
||||||
text: pageType.value == 0 ? (page?.config?['title'] ?? '') : '',
|
text: pageType.value == 0 ? (page?.config?['title'] ?? '') : '',
|
||||||
@@ -49,6 +52,42 @@ class PageForm extends HookConsumerWidget {
|
|||||||
text: pageType.value == 1 ? (page?.config?['target'] ?? '') : '',
|
text: pageType.value == 1 ? (page?.config?['target'] ?? '') : '',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Post Page Controllers
|
||||||
|
final filterPubNameController = useTextEditingController(
|
||||||
|
text: pageType.value == 2
|
||||||
|
? (page?.config?['filter']?['pub_name'] ?? '')
|
||||||
|
: '',
|
||||||
|
);
|
||||||
|
final filterOrderByController = useTextEditingController(
|
||||||
|
text: pageType.value == 2
|
||||||
|
? (page?.config?['filter']?['order_by'] ?? '')
|
||||||
|
: '',
|
||||||
|
);
|
||||||
|
final filterOrderDesc = useState(
|
||||||
|
pageType.value == 2
|
||||||
|
? (page?.config?['filter']?['order_desc'] ?? true)
|
||||||
|
: true,
|
||||||
|
);
|
||||||
|
final filterTypes = useState<List<int>>(
|
||||||
|
(page?.config?['filter']?['types'] as List?)?.cast<int>() ?? [0],
|
||||||
|
);
|
||||||
|
|
||||||
|
final layoutTitleController = useTextEditingController(
|
||||||
|
text: pageType.value == 2
|
||||||
|
? (page?.config?['layout']?['title'] ?? '')
|
||||||
|
: '',
|
||||||
|
);
|
||||||
|
final layoutDescriptionController = useTextEditingController(
|
||||||
|
text: pageType.value == 2
|
||||||
|
? (page?.config?['layout']?['description'] ?? '')
|
||||||
|
: '',
|
||||||
|
);
|
||||||
|
final layoutShowPub = useState(
|
||||||
|
pageType.value == 2
|
||||||
|
? (page?.config?['layout']?['show_pub'] ?? true)
|
||||||
|
: true,
|
||||||
|
);
|
||||||
|
|
||||||
final isLoading = useState(false);
|
final isLoading = useState(false);
|
||||||
|
|
||||||
// Update controllers when page type changes
|
// Update controllers when page type changes
|
||||||
@@ -60,11 +99,36 @@ class PageForm extends HookConsumerWidget {
|
|||||||
page?.config?['html'] ?? page?.config?['content'] ?? '';
|
page?.config?['html'] ?? page?.config?['content'] ?? '';
|
||||||
titleController.text = page?.config?['title'] ?? '';
|
titleController.text = page?.config?['title'] ?? '';
|
||||||
targetController.clear();
|
targetController.clear();
|
||||||
} else {
|
filterPubNameController.clear();
|
||||||
|
filterOrderByController.clear();
|
||||||
|
layoutTitleController.clear();
|
||||||
|
layoutDescriptionController.clear();
|
||||||
|
} else if (pageType.value == 1) {
|
||||||
// Redirect mode
|
// Redirect mode
|
||||||
htmlController.clear();
|
htmlController.clear();
|
||||||
titleController.clear();
|
titleController.clear();
|
||||||
targetController.text = page?.config?['target'] ?? '';
|
targetController.text = page?.config?['target'] ?? '';
|
||||||
|
filterPubNameController.clear();
|
||||||
|
filterOrderByController.clear();
|
||||||
|
layoutTitleController.clear();
|
||||||
|
layoutDescriptionController.clear();
|
||||||
|
} else if (pageType.value == 2) {
|
||||||
|
// Post Page mode
|
||||||
|
htmlController.clear();
|
||||||
|
titleController.clear();
|
||||||
|
targetController.clear();
|
||||||
|
filterPubNameController.text =
|
||||||
|
page?.config?['filter']?['pub_name'] ?? '';
|
||||||
|
filterOrderByController.text =
|
||||||
|
page?.config?['filter']?['order_by'] ?? '';
|
||||||
|
filterOrderDesc.value =
|
||||||
|
page?.config?['filter']?['order_desc'] ?? true;
|
||||||
|
filterTypes.value =
|
||||||
|
(page?.config?['filter']?['types'] as List?)?.cast<int>() ?? [0];
|
||||||
|
layoutTitleController.text = page?.config?['layout']?['title'] ?? '';
|
||||||
|
layoutDescriptionController.text =
|
||||||
|
page?.config?['layout']?['description'] ?? '';
|
||||||
|
layoutShowPub.value = page?.config?['layout']?['show_pub'] ?? true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return null;
|
return null;
|
||||||
@@ -78,90 +142,136 @@ class PageForm extends HookConsumerWidget {
|
|||||||
htmlController.text =
|
htmlController.text =
|
||||||
page!.config?['html'] ?? page!.config?['content'] ?? '';
|
page!.config?['html'] ?? page!.config?['content'] ?? '';
|
||||||
titleController.text = page!.config?['title'] ?? '';
|
titleController.text = page!.config?['title'] ?? '';
|
||||||
} else {
|
} else if (pageType.value == 1) {
|
||||||
targetController.text = page!.config?['target'] ?? '';
|
targetController.text = page!.config?['target'] ?? '';
|
||||||
|
} else if (pageType.value == 2) {
|
||||||
|
filterPubNameController.text =
|
||||||
|
page!.config?['filter']?['pub_name'] ?? '';
|
||||||
|
filterOrderByController.text =
|
||||||
|
page!.config?['filter']?['order_by'] ?? '';
|
||||||
|
filterOrderDesc.value =
|
||||||
|
page!.config?['filter']?['order_desc'] ?? true;
|
||||||
|
filterTypes.value =
|
||||||
|
(page!.config?['filter']?['types'] as List?)?.cast<int>() ?? [0];
|
||||||
|
layoutTitleController.text = page!.config?['layout']?['title'] ?? '';
|
||||||
|
layoutDescriptionController.text =
|
||||||
|
page!.config?['layout']?['description'] ?? '';
|
||||||
|
layoutShowPub.value = page!.config?['layout']?['show_pub'] ?? true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}, [page]);
|
}, [page]);
|
||||||
|
|
||||||
final savePage = useCallback(() async {
|
final savePage = useCallback(
|
||||||
if (!formKey.currentState!.validate()) return;
|
() async {
|
||||||
|
if (!formKey.currentState!.validate()) return;
|
||||||
|
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final pagesNotifier = ref.read(
|
final provider = sitePagesNotifierProvider((
|
||||||
sitePagesNotifierProvider((
|
|
||||||
pubName: pubName,
|
pubName: pubName,
|
||||||
siteSlug: site.slug,
|
siteSlug: site.slug,
|
||||||
)).notifier,
|
));
|
||||||
);
|
final pagesNotifier = ref.read(provider.notifier);
|
||||||
|
|
||||||
late final Map<String, dynamic> pageData;
|
late final Map<String, dynamic> pageData;
|
||||||
|
|
||||||
if (pageType.value == 0) {
|
if (pageType.value == 0) {
|
||||||
// HTML page
|
// HTML page
|
||||||
pageData = {
|
pageData = {
|
||||||
'type': 0,
|
'type': 0,
|
||||||
'path': pathController.text,
|
'path': pathController.text,
|
||||||
'config': {
|
'config': {
|
||||||
'title': titleController.text,
|
'title': titleController.text,
|
||||||
'html': htmlController.text,
|
'html': htmlController.text,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} else {
|
} else if (pageType.value == 1) {
|
||||||
// Redirect page
|
// Redirect page
|
||||||
pageData = {
|
pageData = {
|
||||||
'type': 1,
|
'type': 1,
|
||||||
'path': pathController.text,
|
'path': pathController.text,
|
||||||
'config': {'target': targetController.text},
|
'config': {'target': targetController.text},
|
||||||
};
|
};
|
||||||
|
} else {
|
||||||
|
// Post Page
|
||||||
|
pageData = {
|
||||||
|
'type': 2,
|
||||||
|
'path': pathController.text,
|
||||||
|
'config': {
|
||||||
|
'filter': {
|
||||||
|
if (filterPubNameController.text.isNotEmpty)
|
||||||
|
'pub_name': filterPubNameController.text,
|
||||||
|
if (filterOrderByController.text.isNotEmpty)
|
||||||
|
'order_by': filterOrderByController.text,
|
||||||
|
'order_desc': filterOrderDesc.value,
|
||||||
|
'types': filterTypes.value,
|
||||||
|
},
|
||||||
|
'layout': {
|
||||||
|
if (layoutTitleController.text.isNotEmpty)
|
||||||
|
'title': layoutTitleController.text,
|
||||||
|
if (layoutDescriptionController.text.isNotEmpty)
|
||||||
|
'description': layoutDescriptionController.text,
|
||||||
|
'show_pub': layoutShowPub.value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (page == null) {
|
||||||
|
// Create new page
|
||||||
|
await pagesNotifier.createPage(pageData);
|
||||||
|
} else {
|
||||||
|
// Update existing page
|
||||||
|
await pagesNotifier.updatePage(page!.id, pageData);
|
||||||
|
}
|
||||||
|
ref.invalidate(provider);
|
||||||
|
|
||||||
|
if (context.mounted) {
|
||||||
|
showSnackBar(
|
||||||
|
page == null
|
||||||
|
? 'Page created successfully'
|
||||||
|
: 'Page updated successfully',
|
||||||
|
);
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
showErrorAlert(e);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
if (page == null) {
|
[
|
||||||
// Create new page
|
pageType,
|
||||||
await pagesNotifier.createPage(pageData);
|
pubName,
|
||||||
} else {
|
site.slug,
|
||||||
// Update existing page
|
page,
|
||||||
await pagesNotifier.updatePage(page!.id, pageData);
|
filterOrderDesc.value,
|
||||||
}
|
filterTypes.value,
|
||||||
|
layoutShowPub.value,
|
||||||
if (context.mounted) {
|
],
|
||||||
showSnackBar(
|
);
|
||||||
page == null
|
|
||||||
? 'Page created successfully'
|
|
||||||
: 'Page updated successfully',
|
|
||||||
);
|
|
||||||
Navigator.pop(context);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
showErrorAlert(e);
|
|
||||||
} finally {
|
|
||||||
isLoading.value = false;
|
|
||||||
}
|
|
||||||
}, [pageType, pubName, site.slug, page]);
|
|
||||||
|
|
||||||
final deletePage = useCallback(() async {
|
final deletePage = useCallback(() async {
|
||||||
if (page == null) return; // Shouldn't happen for editing
|
if (page == null) return; // Shouldn't happen for editing
|
||||||
|
|
||||||
final confirmed = await showDialog<bool>(
|
final confirmed = await showDialog<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
builder:
|
builder: (context) => AlertDialog(
|
||||||
(context) => AlertDialog(
|
title: const Text('Delete Page'),
|
||||||
title: const Text('Delete Page'),
|
content: const Text('Are you sure you want to delete this page?'),
|
||||||
content: const Text('Are you sure you want to delete this page?'),
|
actions: [
|
||||||
actions: [
|
TextButton(
|
||||||
TextButton(
|
onPressed: () => Navigator.of(context).pop(false),
|
||||||
onPressed: () => Navigator.of(context).pop(false),
|
child: const Text('Cancel'),
|
||||||
child: const Text('Cancel'),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.of(context).pop(true),
|
|
||||||
child: const Text('Delete'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(true),
|
||||||
|
child: const Text('Delete'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
if (confirmed != true) return;
|
if (confirmed != true) return;
|
||||||
|
|
||||||
@@ -227,6 +337,16 @@ class PageForm extends HookConsumerWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: 2,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Symbols.article, size: 20),
|
||||||
|
Gap(8),
|
||||||
|
Text('Post Page'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
@@ -240,37 +360,40 @@ class PageForm extends HookConsumerWidget {
|
|||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
).padding(all: 20),
|
).padding(all: 20),
|
||||||
|
|
||||||
|
// Common "Path" field for all types
|
||||||
|
TextFormField(
|
||||||
|
controller: pathController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Page Path',
|
||||||
|
hintText: '/about, /posts, etc.',
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Please enter a page path';
|
||||||
|
}
|
||||||
|
if (!RegExp(r'^[a-zA-Z0-9\-/_]+$').hasMatch(value)) {
|
||||||
|
return 'Page path can only contain letters, numbers, hyphens, underscores, and slashes';
|
||||||
|
}
|
||||||
|
if (!value.startsWith('/')) {
|
||||||
|
return 'Page path must start with /';
|
||||||
|
}
|
||||||
|
if (value.contains('//')) {
|
||||||
|
return 'Page path cannot have consecutive slashes';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
).padding(horizontal: 20),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// Conditional form fields based on page type
|
// Conditional form fields based on page type
|
||||||
if (pageType.value == 0) ...[
|
if (pageType.value == 0) ...[
|
||||||
// HTML Page fields
|
// HTML Page fields
|
||||||
TextFormField(
|
|
||||||
controller: pathController,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: 'Page Path',
|
|
||||||
hintText: '/about, /contact, etc.',
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
validator: (value) {
|
|
||||||
if (value == null || value.isEmpty) {
|
|
||||||
return 'Please enter a page path';
|
|
||||||
}
|
|
||||||
if (!RegExp(r'^[a-zA-Z0-9\-/_]+$').hasMatch(value)) {
|
|
||||||
return 'Page path can only contain letters, numbers, hyphens, underscores, and slashes';
|
|
||||||
}
|
|
||||||
if (!value.startsWith('/')) {
|
|
||||||
return 'Page path must start with /';
|
|
||||||
}
|
|
||||||
if (value.contains('//')) {
|
|
||||||
return 'Page path cannot have consecutive slashes';
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
onTapOutside:
|
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
).padding(horizontal: 20),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: titleController,
|
controller: titleController,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
@@ -286,8 +409,8 @@ class PageForm extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
onTapOutside:
|
onTapOutside: (_) =>
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
).padding(horizontal: 20),
|
).padding(horizontal: 20),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
@@ -302,8 +425,8 @@ class PageForm extends HookConsumerWidget {
|
|||||||
alignLabelWithHint: true,
|
alignLabelWithHint: true,
|
||||||
),
|
),
|
||||||
maxLines: 10,
|
maxLines: 10,
|
||||||
onTapOutside:
|
onTapOutside: (_) =>
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value == null || value.isEmpty) {
|
if (value == null || value.isEmpty) {
|
||||||
return 'Please enter HTML content for the page';
|
return 'Please enter HTML content for the page';
|
||||||
@@ -311,36 +434,8 @@ class PageForm extends HookConsumerWidget {
|
|||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
).padding(horizontal: 20),
|
).padding(horizontal: 20),
|
||||||
] else ...[
|
] else if (pageType.value == 1) ...[
|
||||||
// Redirect Page fields
|
// Redirect Page fields
|
||||||
TextFormField(
|
|
||||||
controller: pathController,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: 'Page Path',
|
|
||||||
hintText: '/old-page, /redirect, etc.',
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
validator: (value) {
|
|
||||||
if (value == null || value.isEmpty) {
|
|
||||||
return 'Please enter a page path';
|
|
||||||
}
|
|
||||||
if (!RegExp(r'^[a-zA-Z0-9\-/_]+$').hasMatch(value)) {
|
|
||||||
return 'Page path can only contain letters, numbers, hyphens, underscores, and slashes';
|
|
||||||
}
|
|
||||||
if (!value.startsWith('/')) {
|
|
||||||
return 'Page path must start with /';
|
|
||||||
}
|
|
||||||
if (value.contains('//')) {
|
|
||||||
return 'Page path cannot have consecutive slashes';
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
onTapOutside:
|
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
).padding(horizontal: 20),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: targetController,
|
controller: targetController,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
@@ -355,37 +450,170 @@ class PageForm extends HookConsumerWidget {
|
|||||||
return 'Please enter a redirect target';
|
return 'Please enter a redirect target';
|
||||||
}
|
}
|
||||||
if (!value.startsWith('/') &&
|
if (!value.startsWith('/') &&
|
||||||
|
// ignore: use_string_starts_with_pattern
|
||||||
!value.startsWith('http://') &&
|
!value.startsWith('http://') &&
|
||||||
|
// ignore: use_string_starts_with_pattern
|
||||||
!value.startsWith('https://')) {
|
!value.startsWith('https://')) {
|
||||||
return 'Target must be a relative path (/) or absolute URL (http/https)';
|
return 'Target must be a relative path (/) or absolute URL (http/https)';
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
onTapOutside:
|
onTapOutside: (_) =>
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
).padding(horizontal: 20),
|
).padding(horizontal: 20),
|
||||||
Row(
|
] else if (pageType.value == 2) ...[
|
||||||
|
// Post Page fields
|
||||||
|
const Text(
|
||||||
|
'Filter Settings',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
).alignment(Alignment.centerLeft).padding(horizontal: 24),
|
||||||
|
const Gap(8),
|
||||||
|
TextFormField(
|
||||||
|
controller: filterPubNameController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Publication Name (Optional)',
|
||||||
|
hintText: 'Filter by publication name',
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
).padding(horizontal: 20),
|
||||||
|
const Gap(16),
|
||||||
|
TextFormField(
|
||||||
|
controller: filterOrderByController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Order By (Optional)',
|
||||||
|
hintText: 'e.g. published_at',
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
).padding(horizontal: 20),
|
||||||
|
const Gap(8),
|
||||||
|
SwitchListTile(
|
||||||
|
value: filterOrderDesc.value,
|
||||||
|
onChanged: (value) => filterOrderDesc.value = value,
|
||||||
|
title: const Text('Order Descending'),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(8),
|
||||||
|
const Text(
|
||||||
|
'Content Types',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
).alignment(Alignment.centerLeft).padding(horizontal: 24),
|
||||||
|
const Gap(4),
|
||||||
|
Wrap(
|
||||||
|
spacing: 8,
|
||||||
|
runSpacing: 8,
|
||||||
|
alignment: WrapAlignment.start,
|
||||||
|
runAlignment: WrapAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
if (page != null) ...[
|
FilterChip(
|
||||||
TextButton.icon(
|
label: const Text('Regular Post'),
|
||||||
onPressed: deletePage,
|
selected: filterTypes.value.contains(0),
|
||||||
icon: const Icon(Symbols.delete_forever),
|
onSelected: (selected) {
|
||||||
label: const Text('Delete Page'),
|
final types = [...filterTypes.value];
|
||||||
style: TextButton.styleFrom(
|
if (selected) {
|
||||||
foregroundColor: Colors.red,
|
types.add(0);
|
||||||
),
|
} else {
|
||||||
).alignment(Alignment.centerRight),
|
types.remove(0);
|
||||||
const Spacer(),
|
}
|
||||||
] else
|
filterTypes.value = types;
|
||||||
const Spacer(),
|
},
|
||||||
TextButton.icon(
|
),
|
||||||
onPressed: savePage,
|
FilterChip(
|
||||||
icon: const Icon(Symbols.save),
|
label: const Text('Article'),
|
||||||
label: const Text('Save Page'),
|
selected: filterTypes.value.contains(1),
|
||||||
|
onSelected: (selected) {
|
||||||
|
final types = [...filterTypes.value];
|
||||||
|
if (selected) {
|
||||||
|
types.add(1);
|
||||||
|
} else {
|
||||||
|
types.remove(1);
|
||||||
|
}
|
||||||
|
filterTypes.value = types;
|
||||||
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
).padding(horizontal: 20, vertical: 16),
|
).padding(horizontal: 20),
|
||||||
|
const Gap(24),
|
||||||
|
const Text(
|
||||||
|
'Layout Settings',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
).alignment(Alignment.centerLeft).padding(horizontal: 24),
|
||||||
|
const Gap(8),
|
||||||
|
TextFormField(
|
||||||
|
controller: layoutTitleController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Title (Optional)',
|
||||||
|
hintText: 'Page Title',
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
).padding(horizontal: 20),
|
||||||
|
const Gap(16),
|
||||||
|
TextFormField(
|
||||||
|
controller: layoutDescriptionController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Description (Optional)',
|
||||||
|
hintText: 'Page Description',
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
).padding(horizontal: 20),
|
||||||
|
const Gap(8),
|
||||||
|
SwitchListTile(
|
||||||
|
value: layoutShowPub.value,
|
||||||
|
onChanged: (value) => layoutShowPub.value = value,
|
||||||
|
title: const Text('Show Publication Info'),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
if (page != null) ...[
|
||||||
|
TextButton.icon(
|
||||||
|
onPressed: deletePage,
|
||||||
|
icon: const Icon(Symbols.delete_forever),
|
||||||
|
label: const Text('Delete Page'),
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
).alignment(Alignment.centerRight),
|
||||||
|
const Spacer(),
|
||||||
|
] else
|
||||||
|
const Spacer(),
|
||||||
|
TextButton.icon(
|
||||||
|
onPressed: savePage,
|
||||||
|
icon: const Icon(Symbols.save),
|
||||||
|
label: const Text('Save Page'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 20, vertical: 16),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -37,29 +37,28 @@ class PageItem extends HookConsumerWidget {
|
|||||||
title: Text(page.path ?? '/'),
|
title: Text(page.path ?? '/'),
|
||||||
subtitle: Text(page.config?['title'] ?? 'Untitled'),
|
subtitle: Text(page.config?['title'] ?? 'Untitled'),
|
||||||
trailing: PopupMenuButton<String>(
|
trailing: PopupMenuButton<String>(
|
||||||
itemBuilder:
|
itemBuilder: (context) => [
|
||||||
(context) => [
|
PopupMenuItem(
|
||||||
PopupMenuItem(
|
value: 'edit',
|
||||||
value: 'edit',
|
child: Row(
|
||||||
child: Row(
|
children: [
|
||||||
children: [
|
const Icon(Symbols.edit),
|
||||||
const Icon(Symbols.edit),
|
const Gap(16),
|
||||||
const Gap(16),
|
Text('edit'.tr()),
|
||||||
Text('edit'.tr()),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
PopupMenuItem(
|
||||||
PopupMenuItem(
|
value: 'delete',
|
||||||
value: 'delete',
|
child: Row(
|
||||||
child: Row(
|
children: [
|
||||||
children: [
|
const Icon(Symbols.delete, color: Colors.red),
|
||||||
const Icon(Symbols.delete, color: Colors.red),
|
const Gap(16),
|
||||||
const Gap(16),
|
Text('delete'.tr()).textColor(Colors.red),
|
||||||
Text('delete'.tr()).textColor(Colors.red),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
|
||||||
onSelected: (value) async {
|
onSelected: (value) async {
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case 'edit':
|
case 'edit':
|
||||||
@@ -67,46 +66,27 @@ class PageItem extends HookConsumerWidget {
|
|||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
builder:
|
builder: (context) =>
|
||||||
(context) =>
|
PageForm(site: site, pubName: pubName, page: page),
|
||||||
PageForm(site: site, pubName: pubName, page: page),
|
|
||||||
).then((_) {
|
).then((_) {
|
||||||
// Refresh pages after editing
|
// Refresh pages after editing
|
||||||
ref.invalidate(sitePagesProvider(pubName, site.slug));
|
ref.invalidate(sitePagesProvider(pubName, site.slug));
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'delete':
|
case 'delete':
|
||||||
final confirmed = await showDialog<bool>(
|
final confirmed = await showConfirmAlert(
|
||||||
context: context,
|
'Are you sure you want to delete this page?',
|
||||||
builder:
|
'Delete the Page',
|
||||||
(context) => AlertDialog(
|
|
||||||
title: const Text('Delete Page'),
|
|
||||||
content: const Text(
|
|
||||||
'Are you sure you want to delete this page?',
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.of(context).pop(false),
|
|
||||||
child: const Text('Cancel'),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.of(context).pop(true),
|
|
||||||
child: const Text('Delete'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (confirmed == true) {
|
if (confirmed) {
|
||||||
try {
|
try {
|
||||||
await ref
|
final provider = sitePagesNotifierProvider((
|
||||||
.read(
|
pubName: pubName,
|
||||||
sitePagesNotifierProvider((
|
siteSlug: site.slug,
|
||||||
pubName: pubName,
|
));
|
||||||
siteSlug: site.slug,
|
await ref.read(provider.notifier).deletePage(page.id);
|
||||||
)).notifier,
|
ref.invalidate(provider);
|
||||||
)
|
|
||||||
.deletePage(page.id);
|
|
||||||
showSnackBar('Page deleted successfully');
|
showSnackBar('Page deleted successfully');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showErrorAlert(e);
|
showErrorAlert(e);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user