Compare commits
	
		
			34 Commits
		
	
	
		
			2.3.2+70
			...
			997934f680
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 997934f680 | |||
| 26e69d6264 | |||
| 153eabcbf2 | |||
| 6d0145c335 | |||
| 81a79f9476 | |||
| 537f404fe0 | |||
| eb29f76b9a | |||
| 56816dc060 | |||
| 899d5f3e5e | |||
| c8c455bb57 | |||
| 5468fc0748 | |||
| 78516abf2e | |||
| 0424f98eb5 | |||
| 2188b8b2e2 | |||
| 0bf614a75c | |||
| 9f21f744a4 | |||
| b94cda6205 | |||
| 3c0e4046a4 | |||
| 338c22a606 | |||
| 25dd895e0d | |||
| ea9ef9e82a | |||
| edd86eda77 | |||
| 671b857a79 | |||
| 408fd0f35e | |||
| 30184d08b1 | |||
|  | 95f257c47a | ||
|  | 41297c6712 | ||
| a8e0ade0c8 | |||
| 3338e699c4 | |||
| e07da3efa5 | |||
| 4f7f015250 | |||
| 2a4c15d0dc | |||
| 70ef894ec5 | |||
| bb9179d5f9 | 
							
								
								
									
										87
									
								
								.github/ISSUE_TEMPLATE/bug_report.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								.github/ISSUE_TEMPLATE/bug_report.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | |||||||
|  | name: Bug report | ||||||
|  | description: Create a report to help us address issues you are facing | ||||||
|  | title: "[Bug] " | ||||||
|  | labels: [Bug] | ||||||
|  | body: | ||||||
|  |   - type: markdown | ||||||
|  |     attributes: | ||||||
|  |       value: | | ||||||
|  |         Thanks for taking the time to make us better! | ||||||
|  |  | ||||||
|  |   - type: checkboxes | ||||||
|  |     id: duplication | ||||||
|  |     attributes: | ||||||
|  |       label: ⠀ | ||||||
|  |       options: | ||||||
|  |         - label: This issue is not duplicated with any other open or closed issues | ||||||
|  |           required: true | ||||||
|  |  | ||||||
|  |   - type: textarea | ||||||
|  |     id: description | ||||||
|  |     attributes: | ||||||
|  |       label: Describe the bug | ||||||
|  |       description: A clear and concise description of what the bug is | ||||||
|  |       placeholder: | | ||||||
|  |         Example: | ||||||
|  |           App crashes on startup every time after changing settings. | ||||||
|  |     validations: | ||||||
|  |       required: true | ||||||
|  |  | ||||||
|  |   - type: textarea | ||||||
|  |     id: expected | ||||||
|  |     attributes: | ||||||
|  |       label: Expected behavior | ||||||
|  |       description: A clear and concise description of what you expected to happen | ||||||
|  |       placeholder: | | ||||||
|  |         Example: | ||||||
|  |           App started normally, everything worked fine. | ||||||
|  |     validations: | ||||||
|  |       required: true | ||||||
|  |  | ||||||
|  |   - type: textarea | ||||||
|  |     id: reproduce | ||||||
|  |     attributes: | ||||||
|  |       label: Steps to reproduce | ||||||
|  |       description: Steps to reproduce the bug | ||||||
|  |       placeholder: | | ||||||
|  |         Example: | ||||||
|  |           1. Change "HyperNet Server" to "127.0.1" in "Network" settings | ||||||
|  |           2. Restart the app | ||||||
|  |           3. Crash | ||||||
|  |     validations: | ||||||
|  |       required: true | ||||||
|  |  | ||||||
|  |   - type: textarea | ||||||
|  |     id: environment | ||||||
|  |     attributes: | ||||||
|  |       label: Device information | ||||||
|  |       description: Provide details about your system environment | ||||||
|  |       placeholder: | | ||||||
|  |         Example: | ||||||
|  |           Device: Google Pixel 8 Pro | ||||||
|  |           System: Baklava (BP22.250124.009) | ||||||
|  |           Version*: 2.3.2 | ||||||
|  |     validations: | ||||||
|  |       required: true | ||||||
|  |  | ||||||
|  |   - type: textarea | ||||||
|  |     id: screenshots | ||||||
|  |     attributes: | ||||||
|  |       label: Screenshots | ||||||
|  |       description: If applicable, add screenshots to help explain your problem | ||||||
|  |       placeholder: | | ||||||
|  |         Example: | ||||||
|  |           setting_items.jpg | ||||||
|  |           crash_screen.jpg | ||||||
|  |     validations: | ||||||
|  |       required: false | ||||||
|  |  | ||||||
|  |   - type: textarea | ||||||
|  |     id: additional | ||||||
|  |     attributes: | ||||||
|  |       label: Additional context | ||||||
|  |       description: Add any other context about the problem here | ||||||
|  |       placeholder: | | ||||||
|  |         Crash report or other useful informations | ||||||
|  |     validations: | ||||||
|  |       required: false | ||||||
							
								
								
									
										83
									
								
								.github/ISSUE_TEMPLATE/bug_report_zh.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								.github/ISSUE_TEMPLATE/bug_report_zh.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,83 @@ | |||||||
|  | name: 问题反馈 | ||||||
|  | description: 提交 Bug 或其它问题的反馈 | ||||||
|  | title: "[Bug] 标题" | ||||||
|  | labels: [Bug] | ||||||
|  | body: | ||||||
|  |   - type: markdown | ||||||
|  |     attributes: | ||||||
|  |       value: | | ||||||
|  |         非常感谢,你将要提交的反馈会让我们变得更好! | ||||||
|  |  | ||||||
|  |   - type: checkboxes | ||||||
|  |     id: duplication | ||||||
|  |     attributes: | ||||||
|  |       label: ⠀ | ||||||
|  |       options: | ||||||
|  |         - label: 我已经搜索并确认此 issue 不与其它任何 issue 重复 | ||||||
|  |           required: true | ||||||
|  |  | ||||||
|  |   - type: textarea | ||||||
|  |     id: description | ||||||
|  |     attributes: | ||||||
|  |       label: 问题描述 | ||||||
|  |       description: 清楚且详细地描述你遇到的 Bug 或问题 | ||||||
|  |       placeholder: | | ||||||
|  |         发生了什么?生动地描述你所看到的一切 | ||||||
|  |     validations: | ||||||
|  |       required: true | ||||||
|  |  | ||||||
|  |   - type: textarea | ||||||
|  |     id: expected | ||||||
|  |     attributes: | ||||||
|  |       label: 期望表现 | ||||||
|  |       description: 清楚且详细地描述你期望发生的事 | ||||||
|  |       placeholder: | | ||||||
|  |         什么功能应该正常运行,运行后会有什么结果 | ||||||
|  |         什么界面应该正常显示,应该会显示什么内容 | ||||||
|  |     validations: | ||||||
|  |       required: true | ||||||
|  |  | ||||||
|  |   - type: textarea | ||||||
|  |     id: reproduce | ||||||
|  |     attributes: | ||||||
|  |       label: 复现步骤 | ||||||
|  |       description: 能够复现问题的每一步 | ||||||
|  |       placeholder: | | ||||||
|  |         1. 尽可能详细地描述每一步 | ||||||
|  |         2. 更改的设置、添加的好友... | ||||||
|  |         3. 这里也可以描述你看到的界面 | ||||||
|  |     validations: | ||||||
|  |       required: true | ||||||
|  |  | ||||||
|  |   - type: textarea | ||||||
|  |     id: environment | ||||||
|  |     attributes: | ||||||
|  |       label: 环境/版本 | ||||||
|  |       description: 提供运行时的环境信息 | ||||||
|  |       placeholder: | | ||||||
|  |         示例: | ||||||
|  |           设备型号: Google Pixel 8 Pro | ||||||
|  |           系统板本: Baklava (BP22.250124.009) | ||||||
|  |           程序版本: 2.3.2 | ||||||
|  |     validations: | ||||||
|  |       required: true | ||||||
|  |  | ||||||
|  |   - type: textarea | ||||||
|  |     id: screenshots | ||||||
|  |     attributes: | ||||||
|  |       label: 屏幕截图/录制 | ||||||
|  |       description: 提供截屏或录屏来更好地描述问题 | ||||||
|  |       placeholder: | | ||||||
|  |         错误显示的界面/崩溃时的界面、先前改动的设置 | ||||||
|  |     validations: | ||||||
|  |       required: false | ||||||
|  |  | ||||||
|  |   - type: textarea | ||||||
|  |     id: additional | ||||||
|  |     attributes: | ||||||
|  |       label: 更多信息 | ||||||
|  |       description: 任何与问题有关且有用的信息 | ||||||
|  |       placeholder: | | ||||||
|  |         崩溃报告、日志,或是你的用户名 | ||||||
|  |     validations: | ||||||
|  |       required: false | ||||||
							
								
								
									
										5
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | blank_issues_enabled: true | ||||||
|  | contact_links: | ||||||
|  |   - name: Solsynth Releases | ||||||
|  |     url: https://files.solsynth.dev/production01/solian | ||||||
|  |     about: Another place to download released apps | ||||||
							
								
								
									
										59
									
								
								.github/ISSUE_TEMPLATE/feature_request.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								.github/ISSUE_TEMPLATE/feature_request.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | |||||||
|  | name: Feature request | ||||||
|  | description: Suggest features you want to add or suggest to modify existing features | ||||||
|  | title: "[Feature] " | ||||||
|  | labels: [Feature] | ||||||
|  | body: | ||||||
|  |   - type: markdown | ||||||
|  |     attributes: | ||||||
|  |       value: | | ||||||
|  |         Thanks for taking the time to make us better! | ||||||
|  |  | ||||||
|  |   - type: checkboxes | ||||||
|  |     id: duplication | ||||||
|  |     attributes: | ||||||
|  |       label: ⠀ | ||||||
|  |       options: | ||||||
|  |         - label: This issue is not duplicated with any other open or closed issues | ||||||
|  |           required: true | ||||||
|  |  | ||||||
|  |   - type: textarea | ||||||
|  |     id: description | ||||||
|  |     attributes: | ||||||
|  |       label: Describe the feature | ||||||
|  |       description: A clear and concise description of what the feature is | ||||||
|  |       placeholder: | | ||||||
|  |         Example: | ||||||
|  |           A Quick Settings tile to start the service, long press to launch the app. | ||||||
|  |     validations: | ||||||
|  |       required: true | ||||||
|  |  | ||||||
|  |   - type: textarea | ||||||
|  |     id: reasons | ||||||
|  |     attributes: | ||||||
|  |       label: Reason for adding | ||||||
|  |       description: Explain why this feature would be useful to you | ||||||
|  |       placeholder: | | ||||||
|  |         Example: | ||||||
|  |           Start the service quickly from the Quick Settings tile and save lots of time. | ||||||
|  |     validations: | ||||||
|  |       required: true | ||||||
|  |  | ||||||
|  |   - type: textarea | ||||||
|  |     id: examples | ||||||
|  |     attributes: | ||||||
|  |       label: Example(s) | ||||||
|  |       description: Post screenshots/drawings/links/etc of the feature request, or proof-of-concept images about the feature | ||||||
|  |       placeholder: | | ||||||
|  |         Example: | ||||||
|  |           shazam_toggle.jpg | ||||||
|  |           nekobox_switch.jpg | ||||||
|  |     validations: | ||||||
|  |       required: false | ||||||
|  |  | ||||||
|  |   - type: textarea | ||||||
|  |     id: additional | ||||||
|  |     attributes: | ||||||
|  |       label: Additional context | ||||||
|  |       description: Add any other context about the feature here | ||||||
|  |     validations: | ||||||
|  |       required: false | ||||||
							
								
								
									
										49
									
								
								.github/ISSUE_TEMPLATE/feature_request_zh.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								.github/ISSUE_TEMPLATE/feature_request_zh.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | |||||||
|  | name: 功能建议 | ||||||
|  | description: 提出你想要添加或更改的功能 | ||||||
|  | title: "[Feature] 标题" | ||||||
|  | labels: [Feature] | ||||||
|  | body: | ||||||
|  |   - type: markdown | ||||||
|  |     attributes: | ||||||
|  |       value: | | ||||||
|  |         非常感谢,你将要提交的请求会让我们变得更好! | ||||||
|  |  | ||||||
|  |   - type: checkboxes | ||||||
|  |     id: duplication | ||||||
|  |     attributes: | ||||||
|  |       label: ⠀ | ||||||
|  |       options: | ||||||
|  |         - label: 我已经搜索并确认此 issue 不与其它任何 issue 重复 | ||||||
|  |           required: true | ||||||
|  |  | ||||||
|  |   - type: textarea | ||||||
|  |     id: description | ||||||
|  |     attributes: | ||||||
|  |       label: 功能描述 | ||||||
|  |       description: 清楚且详细地描述要添加/更改后的功能 | ||||||
|  |     validations: | ||||||
|  |       required: true | ||||||
|  |  | ||||||
|  |   - type: textarea | ||||||
|  |     id: reasons | ||||||
|  |     attributes: | ||||||
|  |       label: 添加/更改理由 | ||||||
|  |       description: 解释为什么要这样做,对用户有什么好处 | ||||||
|  |     validations: | ||||||
|  |       required: true | ||||||
|  |  | ||||||
|  |   - type: textarea | ||||||
|  |     id: examples | ||||||
|  |     attributes: | ||||||
|  |       label: 功能示例 | ||||||
|  |       description: 相似/已存在功能的截图,或画出大致的界面 | ||||||
|  |     validations: | ||||||
|  |       required: false | ||||||
|  |  | ||||||
|  |   - type: textarea | ||||||
|  |     id: additional | ||||||
|  |     attributes: | ||||||
|  |       label: 更多信息 | ||||||
|  |       description: 任何与功能有关且有用的信息,或已存在功能的代码/仓库 | ||||||
|  |     validations: | ||||||
|  |       required: false | ||||||
							
								
								
									
										1
									
								
								.github/workflows/nightly.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/nightly.yml
									
									
									
									
										vendored
									
									
								
							| @@ -55,6 +55,7 @@ jobs: | |||||||
|           sudo apt-get install libmpv-dev mpv |           sudo apt-get install libmpv-dev mpv | ||||||
|           sudo apt-get install libayatana-appindicator3-dev |           sudo apt-get install libayatana-appindicator3-dev | ||||||
|           sudo apt-get install keybinder-3.0 |           sudo apt-get install keybinder-3.0 | ||||||
|  |           sudo apt-get install libnotify-dev | ||||||
|       - run: flutter pub get |       - run: flutter pub get | ||||||
|       - run: flutter build linux |       - run: flutter build linux | ||||||
|       - name: Archive production artifacts |       - name: Archive production artifacts | ||||||
|   | |||||||
| @@ -12,9 +12,9 @@ post { | |||||||
|  |  | ||||||
| body:json { | body:json { | ||||||
|   { |   { | ||||||
|     "alias": "BaLoading", |     "alias": "Deadge", | ||||||
|     "name": "BaLoading", |     "name": "Dead", | ||||||
|     "attachment_id": "2JCI2uh21mKkfk9P", |     "attachment_id": "pcbFd0u4zgdM39HM", | ||||||
|     "pack_id": 3 |     "pack_id": 4 | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ meta { | |||||||
| } | } | ||||||
|  |  | ||||||
| post { | post { | ||||||
|   url: {{endpoint}}/cgi/id/dev/notify/122 |   url: {{endpoint}}/cgi/id/dev/notify/328 | ||||||
|   body: json |   body: json | ||||||
|   auth: inherit |   auth: inherit | ||||||
| } | } | ||||||
| @@ -15,9 +15,9 @@ body:json { | |||||||
|     "client_id": "{{third_client_id}}", |     "client_id": "{{third_client_id}}", | ||||||
|     "client_secret":"{{third_client_tk}}", |     "client_secret":"{{third_client_tk}}", | ||||||
|     "type": "general", |     "type": "general", | ||||||
|     "subject": "处理该帐号 @solian 的决定", |     "subject": "处理该发布者 @vedal987 的决定", | ||||||
|     "subtitle": "违反用户协议", |     "subtitle": "一条来自 Solar Network 客户支持的信息", | ||||||
|     "content": "您的帐号违反了我们用户协议中关于冒充我们官方的行为,至此做出停权的决定。还请见谅。该决定是最终决定,不接受上诉。", |     "content": "您的发布者违反了我们用户协议中的「禁止冒充他人」的相关条例,经管理决定,将相关内容隐藏。冒充他人的判定无论作者是否有主观意志,只要造成了误解我们就有责任处理。希望您能理解,本次决定未作出任何帐号相关的连带处罚。", | ||||||
|     "priority": 10 |     "priority": 10 | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -548,6 +548,7 @@ | |||||||
|   "termAcceptNextWithAgree": "By clicking the \"Next\", it means you agree to our terms and its updates.", |   "termAcceptNextWithAgree": "By clicking the \"Next\", it means you agree to our terms and its updates.", | ||||||
|   "unauthorized": "Unauthorized", |   "unauthorized": "Unauthorized", | ||||||
|   "unauthorizedDescription": "Login to explore the entire Solar Network.", |   "unauthorizedDescription": "Login to explore the entire Solar Network.", | ||||||
|  |   "projectDetail": "Project Details", | ||||||
|   "serviceStatus": "Service Status", |   "serviceStatus": "Service Status", | ||||||
|   "termRelated": "Related Terms", |   "termRelated": "Related Terms", | ||||||
|   "appDetails": "App Details", |   "appDetails": "App Details", | ||||||
| @@ -583,6 +584,7 @@ | |||||||
|   "colorSchemeBlack": "Black", |   "colorSchemeBlack": "Black", | ||||||
|   "colorSchemeApplied": "Color scheme has been applied, may need restart the app to take effect.", |   "colorSchemeApplied": "Color scheme has been applied, may need restart the app to take effect.", | ||||||
|   "postFeaturedComment": "Featured Comment", |   "postFeaturedComment": "Featured Comment", | ||||||
|  |   "postCategory": "Category", | ||||||
|   "postCategoryTechnology": "Technology", |   "postCategoryTechnology": "Technology", | ||||||
|   "postCategoryGaming": "Gaming", |   "postCategoryGaming": "Gaming", | ||||||
|   "postCategoryLife": "Life", |   "postCategoryLife": "Life", | ||||||
| @@ -625,6 +627,7 @@ | |||||||
|   "realmJoin": "Join Realm", |   "realmJoin": "Join Realm", | ||||||
|   "realmCommunityHint": "This realm is a community realm, you can freely join.", |   "realmCommunityHint": "This realm is a community realm, you can freely join.", | ||||||
|   "realmCommunityPublicChannelsHint": "The public channels in this realm", |   "realmCommunityPublicChannelsHint": "The public channels in this realm", | ||||||
|  |   "realmCommunityPublishersHint": "The publishers in this realm", | ||||||
|   "realmJoined": "Joined realm {}.", |   "realmJoined": "Joined realm {}.", | ||||||
|   "join": "Join", |   "join": "Join", | ||||||
|   "pollEditorNew": "New Poll", |   "pollEditorNew": "New Poll", | ||||||
| @@ -665,5 +668,55 @@ | |||||||
|     "zero": "No views", |     "zero": "No views", | ||||||
|     "one": "{} view", |     "one": "{} view", | ||||||
|     "other": "{} views" |     "other": "{} views" | ||||||
|   } |   }, | ||||||
|  |   "attachmentBillingUploaded": "Used space", | ||||||
|  |   "attachmentBillingDiscount": "Free space", | ||||||
|  |   "attachmentBillingRatio": "Usage", | ||||||
|  |   "attachmentBillingHint": "Sliding Window Pricing®\nFees will only apply if the size of the file uploaded within 24 hours exceeds the free space.", | ||||||
|  |   "postThumbnail": "Post Thumbnail", | ||||||
|  |   "accountRealms": "Realms", | ||||||
|  |   "postInGlobal": "Global", | ||||||
|  |   "postInGlobalDescription": "Do not link this post with any realm.", | ||||||
|  |   "postChannelGlobal": "Global", | ||||||
|  |   "postChannelFriends": "Friends", | ||||||
|  |   "postChannelFollowing": "Following", | ||||||
|  |   "postChannelRealm": "Realms", | ||||||
|  |   "postFilterReset": "Reset Filter", | ||||||
|  |   "postFilterResetDescription": "Clear filter and show all posts.", | ||||||
|  |   "postFilterWithCategory": "Viewing posts in {}", | ||||||
|  |   "databaseSize": "Database Size", | ||||||
|  |   "databaseDelete": "Delete Database", | ||||||
|  |   "databaseDeleteDescription": "Remove the database on your local disk, the content will be fetched from server again.", | ||||||
|  |   "databaseDeleted": "The local database has been deleted.", | ||||||
|  |   "settingsEnablePushNotifications": "Enable Push Notifications", | ||||||
|  |   "settingsEnablePushNotificationsDescription": "Re-enable and request permission to receive push notifications. Just in case it didn't run automatically.", | ||||||
|  |   "settingsEnabledPushNotifications": "Push notification has been enabled.", | ||||||
|  |   "screenStickers": "Stickers", | ||||||
|  |   "stickersDiscovery": "Discovery", | ||||||
|  |   "stickersOwned": "Owned", | ||||||
|  |   "stickersCreated": "Created", | ||||||
|  |   "stickersAdd": "Add Sticker Pack", | ||||||
|  |   "stickersAdded": "Sticker pack has been added.", | ||||||
|  |   "add": "Add", | ||||||
|  |   "stickersRemoved": "Sticker pack has been removed, you can add it again anytime.", | ||||||
|  |   "stickersReload": "Reload Stickers", | ||||||
|  |   "stickersReloadDescription": "Reload stickers from the server, update the sticker picker.", | ||||||
|  |   "stickersReloaded": "Sticker packs has been reloaded.", | ||||||
|  |   "stickersPackDelete": "Delete Pack {}", | ||||||
|  |   "stickersPackDeleteDescription": "Are you sure you want to delete this sticker pack? This operation is irreversible.", | ||||||
|  |   "stickersPackDeleted": "Sticker pack has been deleted.", | ||||||
|  |   "stickersDelete": "Delete Sticker {}", | ||||||
|  |   "stickersDeleteDescription": "Are you sure you want to delete this sticker? This operation is irreversible.", | ||||||
|  |   "stickersDeleted": "Sticker has been deleted.", | ||||||
|  |   "fieldStickerName": "Sticker Name", | ||||||
|  |   "fieldStickerAlias": "Sticker Alias", | ||||||
|  |   "fieldStickerAliasHint": "The unique sticker placeholder with the pack prefix.", | ||||||
|  |   "fieldStickerPackName": "Name", | ||||||
|  |   "fieldStickerPackDescription": "Description", | ||||||
|  |   "fieldStickerPackPrefix": "Prefix", | ||||||
|  |   "fieldStickerAttachment": "Attachment", | ||||||
|  |   "stickersNew": "New Sticker", | ||||||
|  |   "stickersNewDescription": "Create a new sticker belongs to this pack.", | ||||||
|  |   "stickersPackNew": "New Sticker Pack", | ||||||
|  |   "trayMenuShow": "Show" | ||||||
| } | } | ||||||
|   | |||||||
| @@ -546,6 +546,7 @@ | |||||||
|   "termAcceptNextWithAgree": "点击 “下一步”,即表示你同意我们的各项条款,包括其之后的更新。", |   "termAcceptNextWithAgree": "点击 “下一步”,即表示你同意我们的各项条款,包括其之后的更新。", | ||||||
|   "unauthorized": "未登陆", |   "unauthorized": "未登陆", | ||||||
|   "unauthorizedDescription": "登陆以探索整个 Solar Network。", |   "unauthorizedDescription": "登陆以探索整个 Solar Network。", | ||||||
|  |   "projectDetail": "项目详情", | ||||||
|   "serviceStatus": "服务状态", |   "serviceStatus": "服务状态", | ||||||
|   "termRelated": "相关条款", |   "termRelated": "相关条款", | ||||||
|   "appDetails": "应用程序详情", |   "appDetails": "应用程序详情", | ||||||
| @@ -581,6 +582,7 @@ | |||||||
|   "colorSchemeBlack": "黑色", |   "colorSchemeBlack": "黑色", | ||||||
|   "colorSchemeApplied": "主题色已应用,可能需要重启来生效。", |   "colorSchemeApplied": "主题色已应用,可能需要重启来生效。", | ||||||
|   "postFeaturedComment": "精选评论", |   "postFeaturedComment": "精选评论", | ||||||
|  |   "postCategory": "分类", | ||||||
|   "postCategoryTechnology": "技术", |   "postCategoryTechnology": "技术", | ||||||
|   "postCategoryGaming": "游戏", |   "postCategoryGaming": "游戏", | ||||||
|   "postCategoryLife": "生活", |   "postCategoryLife": "生活", | ||||||
| @@ -624,6 +626,7 @@ | |||||||
|   "realmJoin": "加入领域", |   "realmJoin": "加入领域", | ||||||
|   "realmCommunityHint": "该领域是一个社区领域,你可以自由加入。", |   "realmCommunityHint": "该领域是一个社区领域,你可以自由加入。", | ||||||
|   "realmCommunityPublicChannelsHint": "该领域包含的公共频道", |   "realmCommunityPublicChannelsHint": "该领域包含的公共频道", | ||||||
|  |   "realmCommunityPublishersHint": "该领域的发布者", | ||||||
|   "realmJoined": "已加入领域 {}。", |   "realmJoined": "已加入领域 {}。", | ||||||
|   "join": "加入", |   "join": "加入", | ||||||
|   "pollEditorNew": "新投票", |   "pollEditorNew": "新投票", | ||||||
| @@ -664,5 +667,54 @@ | |||||||
|     "zero": "{} 次浏览", |     "zero": "{} 次浏览", | ||||||
|     "one": "{} 次浏览", |     "one": "{} 次浏览", | ||||||
|     "other": "{} 次浏览" |     "other": "{} 次浏览" | ||||||
|   } |   }, | ||||||
|  |   "attachmentBillingUploaded": "已占用的字节数", | ||||||
|  |   "attachmentBillingDiscount": "免费的字节数", | ||||||
|  |   "attachmentBillingHint": "滑动窗口计价®\n在24小时内上传的文件大小超出免费空间才会适用扣费。", | ||||||
|  |   "postThumbnail": "帖子缩略图", | ||||||
|  |   "accountRealms": "领域", | ||||||
|  |   "postInGlobal": "全站", | ||||||
|  |   "postInGlobalDescription": "不关联此帖子与任何领域。", | ||||||
|  |   "postChannelGlobal": "全站", | ||||||
|  |   "postChannelFriends": "好友", | ||||||
|  |   "postChannelFollowing": "关注", | ||||||
|  |   "postChannelRealm": "领域", | ||||||
|  |   "postFilterReset": "重置过滤器", | ||||||
|  |   "postFilterResetDescription": "清除过滤器并显示所有帖子。", | ||||||
|  |   "postFilterWithCategory": "查看{}区中的帖子", | ||||||
|  |   "databaseSize": "数据库大小", | ||||||
|  |   "databaseDelete": "删除数据库", | ||||||
|  |   "databaseDeleteDescription": "删除本地数据库,内容将从服务器重新获取。", | ||||||
|  |   "databaseDeleted": "本地数据库已被删除。", | ||||||
|  |   "settingsEnablePushNotifications": "启用推送数据", | ||||||
|  |   "settingsEnablePushNotificationsDescription": "重新启用并请求推送权限,以防自动激活失败。", | ||||||
|  |   "settingsEnabledPushNotifications": "推送通知已经注册。", | ||||||
|  |   "screenStickers": "贴图", | ||||||
|  |   "stickersDiscovery": "发现", | ||||||
|  |   "stickersOwned": "由我拥有", | ||||||
|  |   "stickersCreated": "由我发布", | ||||||
|  |   "stickersAdd": "添加贴图包", | ||||||
|  |   "stickersAdded": "贴图包已添加。", | ||||||
|  |   "add": "添加", | ||||||
|  |   "stickersRemoved": "贴图包已被移除,你可以随时再次添加回来。", | ||||||
|  |   "stickersReload": "重载贴图包", | ||||||
|  |   "stickersReloadDescription": "从服务器重新加载添加过的贴图,更新贴图选择器。", | ||||||
|  |   "stickersReloaded": "贴图包已重载。", | ||||||
|  |   "stickersPackDelete": "删除贴图包 {}", | ||||||
|  |   "stickersPackDeleteDescription": "你确定要删除这个贴图包吗?这个操作不可撤销。", | ||||||
|  |   "stickersPackDeleted": "贴图包已被删除。", | ||||||
|  |   "stickersDelete": "删除贴图 {}", | ||||||
|  |   "stickersDeleteDescription": "你确定要删除这个贴图吗?这个操作不可撤销。", | ||||||
|  |   "stickersDeleted": "贴图已被删除。", | ||||||
|  |   "fieldStickerName": "贴图名称", | ||||||
|  |   "fieldStickerAlias": "贴图别名", | ||||||
|  |   "fieldStickerAliasHint": "和贴图包前缀组合成为本贴图的唯一占位符。", | ||||||
|  |   "fieldStickerPackName": "名称", | ||||||
|  |   "fieldStickerPackDescription": "描述", | ||||||
|  |   "fieldStickerPackPrefix": "贴图包前缀", | ||||||
|  |   "fieldStickerAttachment": "附件", | ||||||
|  |   "stickersNew": "新建贴图", | ||||||
|  |   "stickersNewDescription": "创建一个新的贴图。", | ||||||
|  |   "stickersPackNew": "新建贴图包", | ||||||
|  |   "trayMenuShow": "显示" | ||||||
| } | } | ||||||
|   | |||||||
| @@ -546,6 +546,7 @@ | |||||||
|   "termAcceptNextWithAgree": "點擊 “下一步”,即表示你同意我們的各項條款,包括其之後的更新。", |   "termAcceptNextWithAgree": "點擊 “下一步”,即表示你同意我們的各項條款,包括其之後的更新。", | ||||||
|   "unauthorized": "未登陸", |   "unauthorized": "未登陸", | ||||||
|   "unauthorizedDescription": "登陸以探索整個 Solar Network。", |   "unauthorizedDescription": "登陸以探索整個 Solar Network。", | ||||||
|  |   "projectDetail": "項目詳情", | ||||||
|   "serviceStatus": "服務狀態", |   "serviceStatus": "服務狀態", | ||||||
|   "termRelated": "相關條款", |   "termRelated": "相關條款", | ||||||
|   "appDetails": "應用程序詳情", |   "appDetails": "應用程序詳情", | ||||||
| @@ -581,6 +582,7 @@ | |||||||
|   "colorSchemeBlack": "黑色", |   "colorSchemeBlack": "黑色", | ||||||
|   "colorSchemeApplied": "主題色已應用,可能需要重啓來生效。", |   "colorSchemeApplied": "主題色已應用,可能需要重啓來生效。", | ||||||
|   "postFeaturedComment": "精選評論", |   "postFeaturedComment": "精選評論", | ||||||
|  |   "postCategory": "分類", | ||||||
|   "postCategoryTechnology": "技術", |   "postCategoryTechnology": "技術", | ||||||
|   "postCategoryGaming": "遊戲", |   "postCategoryGaming": "遊戲", | ||||||
|   "postCategoryLife": "生活", |   "postCategoryLife": "生活", | ||||||
| @@ -624,6 +626,7 @@ | |||||||
|   "realmJoin": "加入領域", |   "realmJoin": "加入領域", | ||||||
|   "realmCommunityHint": "該領域是一個社區領域,你可以自由加入。", |   "realmCommunityHint": "該領域是一個社區領域,你可以自由加入。", | ||||||
|   "realmCommunityPublicChannelsHint": "該領域包含的公共頻道", |   "realmCommunityPublicChannelsHint": "該領域包含的公共頻道", | ||||||
|  |   "realmCommunityPublishersHint": "該領域的發佈者", | ||||||
|   "realmJoined": "已加入領域 {}。", |   "realmJoined": "已加入領域 {}。", | ||||||
|   "join": "加入", |   "join": "加入", | ||||||
|   "pollEditorNew": "新投票", |   "pollEditorNew": "新投票", | ||||||
| @@ -664,5 +667,53 @@ | |||||||
|     "zero": "{} 次瀏覽", |     "zero": "{} 次瀏覽", | ||||||
|     "one": "{} 次瀏覽", |     "one": "{} 次瀏覽", | ||||||
|     "other": "{} 次瀏覽" |     "other": "{} 次瀏覽" | ||||||
|   } |   }, | ||||||
|  |   "attachmentBillingUploaded": "已佔用的字節數", | ||||||
|  |   "attachmentBillingDiscount": "免費的字節數", | ||||||
|  |   "attachmentBillingHint": "滑動窗口計價®\n在24小時內上傳的文件大小超出免費空間才會適用扣費。", | ||||||
|  |   "postThumbnail": "帖子縮略圖", | ||||||
|  |   "accountRealms": "領域", | ||||||
|  |   "postInGlobal": "全站", | ||||||
|  |   "postInGlobalDescription": "不關聯此帖子與任何領域。", | ||||||
|  |   "postChannelGlobal": "全站", | ||||||
|  |   "postChannelFriends": "好友", | ||||||
|  |   "postChannelFollowing": "關注", | ||||||
|  |   "postChannelRealm": "領域", | ||||||
|  |   "postFilterReset": "重置過濾器", | ||||||
|  |   "postFilterResetDescription": "清除過濾器並顯示所有帖子。", | ||||||
|  |   "postFilterWithCategory": "查看{}區中的帖子", | ||||||
|  |   "databaseSize": "數據庫大小", | ||||||
|  |   "databaseDelete": "刪除數據庫", | ||||||
|  |   "databaseDeleteDescription": "刪除本地數據庫,內容將從服務器重新獲取。", | ||||||
|  |   "databaseDeleted": "本地數據庫已被刪除。", | ||||||
|  |   "settingsEnablePushNotifications": "啓用推送數據", | ||||||
|  |   "settingsEnablePushNotificationsDescription": "重新啓用並請求推送權限,以防自動激活失敗。", | ||||||
|  |   "settingsEnabledPushNotifications": "推送通知已經註冊。", | ||||||
|  |   "screenStickers": "貼圖", | ||||||
|  |   "stickersDiscovery": "發現", | ||||||
|  |   "stickersOwned": "由我擁有", | ||||||
|  |   "stickersCreated": "由我發佈", | ||||||
|  |   "stickersAdd": "添加貼圖包", | ||||||
|  |   "stickersAdded": "貼圖包已添加。", | ||||||
|  |   "add": "添加", | ||||||
|  |   "stickersRemoved": "貼圖包已被移除,你可以隨時再次添加回來。", | ||||||
|  |   "stickersReload": "重載貼圖包", | ||||||
|  |   "stickersReloadDescription": "從服務器重新加載添加過的貼圖,更新貼圖選擇器。", | ||||||
|  |   "stickersReloaded": "貼圖包已重載。", | ||||||
|  |   "stickersPackDelete": "刪除貼圖包 {}", | ||||||
|  |   "stickersPackDeleteDescription": "你確定要刪除這個貼圖包嗎?這個操作不可撤銷。", | ||||||
|  |   "stickersPackDeleted": "貼圖包已被刪除。", | ||||||
|  |   "stickersDelete": "刪除貼圖 {}", | ||||||
|  |   "stickersDeleteDescription": "你確定要刪除這個貼圖嗎?這個操作不可撤銷。", | ||||||
|  |   "stickersDeleted": "貼圖已被刪除。", | ||||||
|  |   "fieldStickerName": "貼圖名稱", | ||||||
|  |   "fieldStickerAlias": "貼圖別名", | ||||||
|  |   "fieldStickerAliasHint": "和貼圖包前綴組合成為本貼圖的唯一佔位符。", | ||||||
|  |   "fieldStickerPackName": "名稱", | ||||||
|  |   "fieldStickerPackDescription": "描述", | ||||||
|  |   "fieldStickerPackPrefix": "貼圖包前綴", | ||||||
|  |   "fieldStickerAttachment": "附件", | ||||||
|  |   "stickersNew": "新建貼圖", | ||||||
|  |   "stickersNewDescription": "創建一個新的貼圖。", | ||||||
|  |   "stickersPackNew": "新建貼圖包" | ||||||
| } | } | ||||||
|   | |||||||
| @@ -546,6 +546,7 @@ | |||||||
|   "termAcceptNextWithAgree": "點擊 “下一步”,即表示你同意我們的各項條款,包括其之後的更新。", |   "termAcceptNextWithAgree": "點擊 “下一步”,即表示你同意我們的各項條款,包括其之後的更新。", | ||||||
|   "unauthorized": "未登陸", |   "unauthorized": "未登陸", | ||||||
|   "unauthorizedDescription": "登陸以探索整個 Solar Network。", |   "unauthorizedDescription": "登陸以探索整個 Solar Network。", | ||||||
|  |   "projectDetail": "項目詳情", | ||||||
|   "serviceStatus": "服務狀態", |   "serviceStatus": "服務狀態", | ||||||
|   "termRelated": "相關條款", |   "termRelated": "相關條款", | ||||||
|   "appDetails": "應用程序詳情", |   "appDetails": "應用程序詳情", | ||||||
| @@ -581,6 +582,7 @@ | |||||||
|   "colorSchemeBlack": "黑色", |   "colorSchemeBlack": "黑色", | ||||||
|   "colorSchemeApplied": "主題色已應用,可能需要重啟來生效。", |   "colorSchemeApplied": "主題色已應用,可能需要重啟來生效。", | ||||||
|   "postFeaturedComment": "精選評論", |   "postFeaturedComment": "精選評論", | ||||||
|  |   "postCategory": "分類", | ||||||
|   "postCategoryTechnology": "技術", |   "postCategoryTechnology": "技術", | ||||||
|   "postCategoryGaming": "遊戲", |   "postCategoryGaming": "遊戲", | ||||||
|   "postCategoryLife": "生活", |   "postCategoryLife": "生活", | ||||||
| @@ -624,6 +626,7 @@ | |||||||
|   "realmJoin": "加入領域", |   "realmJoin": "加入領域", | ||||||
|   "realmCommunityHint": "該領域是一個社區領域,你可以自由加入。", |   "realmCommunityHint": "該領域是一個社區領域,你可以自由加入。", | ||||||
|   "realmCommunityPublicChannelsHint": "該領域包含的公共頻道", |   "realmCommunityPublicChannelsHint": "該領域包含的公共頻道", | ||||||
|  |   "realmCommunityPublishersHint": "該領域的發佈者", | ||||||
|   "realmJoined": "已加入領域 {}。", |   "realmJoined": "已加入領域 {}。", | ||||||
|   "join": "加入", |   "join": "加入", | ||||||
|   "pollEditorNew": "新投票", |   "pollEditorNew": "新投票", | ||||||
| @@ -664,5 +667,53 @@ | |||||||
|     "zero": "{} 次瀏覽", |     "zero": "{} 次瀏覽", | ||||||
|     "one": "{} 次瀏覽", |     "one": "{} 次瀏覽", | ||||||
|     "other": "{} 次瀏覽" |     "other": "{} 次瀏覽" | ||||||
|   } |   }, | ||||||
|  |   "attachmentBillingUploaded": "已佔用的字節數", | ||||||
|  |   "attachmentBillingDiscount": "免費的字節數", | ||||||
|  |   "attachmentBillingHint": "滑動窗口計價®\n在24小時內上傳的文件大小超出免費空間才會適用扣費。", | ||||||
|  |   "postThumbnail": "帖子縮略圖", | ||||||
|  |   "accountRealms": "領域", | ||||||
|  |   "postInGlobal": "全站", | ||||||
|  |   "postInGlobalDescription": "不關聯此帖子與任何領域。", | ||||||
|  |   "postChannelGlobal": "全站", | ||||||
|  |   "postChannelFriends": "好友", | ||||||
|  |   "postChannelFollowing": "關注", | ||||||
|  |   "postChannelRealm": "領域", | ||||||
|  |   "postFilterReset": "重置過濾器", | ||||||
|  |   "postFilterResetDescription": "清除過濾器並顯示所有帖子。", | ||||||
|  |   "postFilterWithCategory": "查看{}區中的帖子", | ||||||
|  |   "databaseSize": "數據庫大小", | ||||||
|  |   "databaseDelete": "刪除數據庫", | ||||||
|  |   "databaseDeleteDescription": "刪除本地數據庫,內容將從服務器重新獲取。", | ||||||
|  |   "databaseDeleted": "本地數據庫已被刪除。", | ||||||
|  |   "settingsEnablePushNotifications": "啟用推送數據", | ||||||
|  |   "settingsEnablePushNotificationsDescription": "重新啟用並請求推送權限,以防自動激活失敗。", | ||||||
|  |   "settingsEnabledPushNotifications": "推送通知已經註冊。", | ||||||
|  |   "screenStickers": "貼圖", | ||||||
|  |   "stickersDiscovery": "發現", | ||||||
|  |   "stickersOwned": "由我擁有", | ||||||
|  |   "stickersCreated": "由我發佈", | ||||||
|  |   "stickersAdd": "添加貼圖包", | ||||||
|  |   "stickersAdded": "貼圖包已添加。", | ||||||
|  |   "add": "添加", | ||||||
|  |   "stickersRemoved": "貼圖包已被移除,你可以隨時再次添加回來。", | ||||||
|  |   "stickersReload": "重載貼圖包", | ||||||
|  |   "stickersReloadDescription": "從服務器重新加載添加過的貼圖,更新貼圖選擇器。", | ||||||
|  |   "stickersReloaded": "貼圖包已重載。", | ||||||
|  |   "stickersPackDelete": "刪除貼圖包 {}", | ||||||
|  |   "stickersPackDeleteDescription": "你確定要刪除這個貼圖包嗎?這個操作不可撤銷。", | ||||||
|  |   "stickersPackDeleted": "貼圖包已被刪除。", | ||||||
|  |   "stickersDelete": "刪除貼圖 {}", | ||||||
|  |   "stickersDeleteDescription": "你確定要刪除這個貼圖嗎?這個操作不可撤銷。", | ||||||
|  |   "stickersDeleted": "貼圖已被刪除。", | ||||||
|  |   "fieldStickerName": "貼圖名稱", | ||||||
|  |   "fieldStickerAlias": "貼圖別名", | ||||||
|  |   "fieldStickerAliasHint": "和貼圖包前綴組合成為本貼圖的唯一佔位符。", | ||||||
|  |   "fieldStickerPackName": "名稱", | ||||||
|  |   "fieldStickerPackDescription": "描述", | ||||||
|  |   "fieldStickerPackPrefix": "貼圖包前綴", | ||||||
|  |   "fieldStickerAttachment": "附件", | ||||||
|  |   "stickersNew": "新建貼圖", | ||||||
|  |   "stickersNewDescription": "創建一個新的貼圖。", | ||||||
|  |   "stickersPackNew": "新建貼圖包" | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										117
									
								
								ios/Podfile.lock
									
									
									
									
									
								
							
							
						
						
									
										117
									
								
								ios/Podfile.lock
									
									
									
									
									
								
							| @@ -42,58 +42,58 @@ PODS: | |||||||
|     - Flutter |     - Flutter | ||||||
|   - file_saver (0.0.1): |   - file_saver (0.0.1): | ||||||
|     - Flutter |     - Flutter | ||||||
|   - Firebase/Analytics (11.7.0): |   - Firebase/Analytics (11.8.0): | ||||||
|     - Firebase/Core |     - Firebase/Core | ||||||
|   - Firebase/Core (11.7.0): |   - Firebase/Core (11.8.0): | ||||||
|     - Firebase/CoreOnly |     - Firebase/CoreOnly | ||||||
|     - FirebaseAnalytics (~> 11.7.0) |     - FirebaseAnalytics (~> 11.8.0) | ||||||
|   - Firebase/CoreOnly (11.7.0): |   - Firebase/CoreOnly (11.8.0): | ||||||
|     - FirebaseCore (~> 11.7.0) |     - FirebaseCore (~> 11.8.0) | ||||||
|   - Firebase/Messaging (11.7.0): |   - Firebase/Messaging (11.8.0): | ||||||
|     - Firebase/CoreOnly |     - Firebase/CoreOnly | ||||||
|     - FirebaseMessaging (~> 11.7.0) |     - FirebaseMessaging (~> 11.8.0) | ||||||
|   - firebase_analytics (11.4.2): |   - firebase_analytics (11.4.3): | ||||||
|     - Firebase/Analytics (= 11.7.0) |     - Firebase/Analytics (= 11.8.0) | ||||||
|     - firebase_core |     - firebase_core | ||||||
|     - Flutter |     - Flutter | ||||||
|   - firebase_core (3.11.0): |   - firebase_core (3.12.0): | ||||||
|     - Firebase/CoreOnly (= 11.7.0) |     - Firebase/CoreOnly (= 11.8.0) | ||||||
|     - Flutter |     - Flutter | ||||||
|   - firebase_messaging (15.2.2): |   - firebase_messaging (15.2.3): | ||||||
|     - Firebase/Messaging (= 11.7.0) |     - Firebase/Messaging (= 11.8.0) | ||||||
|     - firebase_core |     - firebase_core | ||||||
|     - Flutter |     - Flutter | ||||||
|   - FirebaseAnalytics (11.7.0): |   - FirebaseAnalytics (11.8.0): | ||||||
|     - FirebaseAnalytics/AdIdSupport (= 11.7.0) |     - FirebaseAnalytics/AdIdSupport (= 11.8.0) | ||||||
|     - FirebaseCore (~> 11.7.0) |     - FirebaseCore (~> 11.8.0) | ||||||
|     - FirebaseInstallations (~> 11.0) |     - FirebaseInstallations (~> 11.0) | ||||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.0) |     - GoogleUtilities/AppDelegateSwizzler (~> 8.0) | ||||||
|     - GoogleUtilities/MethodSwizzler (~> 8.0) |     - GoogleUtilities/MethodSwizzler (~> 8.0) | ||||||
|     - GoogleUtilities/Network (~> 8.0) |     - GoogleUtilities/Network (~> 8.0) | ||||||
|     - "GoogleUtilities/NSData+zlib (~> 8.0)" |     - "GoogleUtilities/NSData+zlib (~> 8.0)" | ||||||
|     - nanopb (~> 3.30910.0) |     - nanopb (~> 3.30910.0) | ||||||
|   - FirebaseAnalytics/AdIdSupport (11.7.0): |   - FirebaseAnalytics/AdIdSupport (11.8.0): | ||||||
|     - FirebaseCore (~> 11.7.0) |     - FirebaseCore (~> 11.8.0) | ||||||
|     - FirebaseInstallations (~> 11.0) |     - FirebaseInstallations (~> 11.0) | ||||||
|     - GoogleAppMeasurement (= 11.7.0) |     - GoogleAppMeasurement (= 11.8.0) | ||||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.0) |     - GoogleUtilities/AppDelegateSwizzler (~> 8.0) | ||||||
|     - GoogleUtilities/MethodSwizzler (~> 8.0) |     - GoogleUtilities/MethodSwizzler (~> 8.0) | ||||||
|     - GoogleUtilities/Network (~> 8.0) |     - GoogleUtilities/Network (~> 8.0) | ||||||
|     - "GoogleUtilities/NSData+zlib (~> 8.0)" |     - "GoogleUtilities/NSData+zlib (~> 8.0)" | ||||||
|     - nanopb (~> 3.30910.0) |     - nanopb (~> 3.30910.0) | ||||||
|   - FirebaseCore (11.7.0): |   - FirebaseCore (11.8.1): | ||||||
|     - FirebaseCoreInternal (~> 11.7.0) |     - FirebaseCoreInternal (~> 11.8.0) | ||||||
|     - GoogleUtilities/Environment (~> 8.0) |     - GoogleUtilities/Environment (~> 8.0) | ||||||
|     - GoogleUtilities/Logger (~> 8.0) |     - GoogleUtilities/Logger (~> 8.0) | ||||||
|   - FirebaseCoreInternal (11.7.0): |   - FirebaseCoreInternal (11.8.0): | ||||||
|     - "GoogleUtilities/NSData+zlib (~> 8.0)" |     - "GoogleUtilities/NSData+zlib (~> 8.0)" | ||||||
|   - FirebaseInstallations (11.7.0): |   - FirebaseInstallations (11.8.0): | ||||||
|     - FirebaseCore (~> 11.7.0) |     - FirebaseCore (~> 11.8.0) | ||||||
|     - GoogleUtilities/Environment (~> 8.0) |     - GoogleUtilities/Environment (~> 8.0) | ||||||
|     - GoogleUtilities/UserDefaults (~> 8.0) |     - GoogleUtilities/UserDefaults (~> 8.0) | ||||||
|     - PromisesObjC (~> 2.4) |     - PromisesObjC (~> 2.4) | ||||||
|   - FirebaseMessaging (11.7.0): |   - FirebaseMessaging (11.8.0): | ||||||
|     - FirebaseCore (~> 11.7.0) |     - FirebaseCore (~> 11.8.0) | ||||||
|     - FirebaseInstallations (~> 11.0) |     - FirebaseInstallations (~> 11.0) | ||||||
|     - GoogleDataTransport (~> 10.0) |     - GoogleDataTransport (~> 10.0) | ||||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.0) |     - GoogleUtilities/AppDelegateSwizzler (~> 8.0) | ||||||
| @@ -122,21 +122,21 @@ PODS: | |||||||
|   - gal (1.0.0): |   - gal (1.0.0): | ||||||
|     - Flutter |     - Flutter | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|   - GoogleAppMeasurement (11.7.0): |   - GoogleAppMeasurement (11.8.0): | ||||||
|     - GoogleAppMeasurement/AdIdSupport (= 11.7.0) |     - GoogleAppMeasurement/AdIdSupport (= 11.8.0) | ||||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.0) |     - GoogleUtilities/AppDelegateSwizzler (~> 8.0) | ||||||
|     - GoogleUtilities/MethodSwizzler (~> 8.0) |     - GoogleUtilities/MethodSwizzler (~> 8.0) | ||||||
|     - GoogleUtilities/Network (~> 8.0) |     - GoogleUtilities/Network (~> 8.0) | ||||||
|     - "GoogleUtilities/NSData+zlib (~> 8.0)" |     - "GoogleUtilities/NSData+zlib (~> 8.0)" | ||||||
|     - nanopb (~> 3.30910.0) |     - nanopb (~> 3.30910.0) | ||||||
|   - GoogleAppMeasurement/AdIdSupport (11.7.0): |   - GoogleAppMeasurement/AdIdSupport (11.8.0): | ||||||
|     - GoogleAppMeasurement/WithoutAdIdSupport (= 11.7.0) |     - GoogleAppMeasurement/WithoutAdIdSupport (= 11.8.0) | ||||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.0) |     - GoogleUtilities/AppDelegateSwizzler (~> 8.0) | ||||||
|     - GoogleUtilities/MethodSwizzler (~> 8.0) |     - GoogleUtilities/MethodSwizzler (~> 8.0) | ||||||
|     - GoogleUtilities/Network (~> 8.0) |     - GoogleUtilities/Network (~> 8.0) | ||||||
|     - "GoogleUtilities/NSData+zlib (~> 8.0)" |     - "GoogleUtilities/NSData+zlib (~> 8.0)" | ||||||
|     - nanopb (~> 3.30910.0) |     - nanopb (~> 3.30910.0) | ||||||
|   - GoogleAppMeasurement/WithoutAdIdSupport (11.7.0): |   - GoogleAppMeasurement/WithoutAdIdSupport (11.8.0): | ||||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.0) |     - GoogleUtilities/AppDelegateSwizzler (~> 8.0) | ||||||
|     - GoogleUtilities/MethodSwizzler (~> 8.0) |     - GoogleUtilities/MethodSwizzler (~> 8.0) | ||||||
|     - GoogleUtilities/Network (~> 8.0) |     - GoogleUtilities/Network (~> 8.0) | ||||||
| @@ -210,9 +210,9 @@ PODS: | |||||||
|   - SAMKeychain (1.5.3) |   - SAMKeychain (1.5.3) | ||||||
|   - screen_brightness_ios (0.1.0): |   - screen_brightness_ios (0.1.0): | ||||||
|     - Flutter |     - Flutter | ||||||
|   - SDWebImage (5.20.0): |   - SDWebImage (5.20.1): | ||||||
|     - SDWebImage/Core (= 5.20.0) |     - SDWebImage/Core (= 5.20.1) | ||||||
|   - SDWebImage/Core (5.20.0) |   - SDWebImage/Core (5.20.1) | ||||||
|   - share_plus (0.0.1): |   - share_plus (0.0.1): | ||||||
|     - Flutter |     - Flutter | ||||||
|   - shared_preferences_foundation (0.0.1): |   - shared_preferences_foundation (0.0.1): | ||||||
| @@ -221,6 +221,25 @@ PODS: | |||||||
|   - sqflite_darwin (0.0.4): |   - sqflite_darwin (0.0.4): | ||||||
|     - Flutter |     - Flutter | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|  |   - sqlite3 (3.49.1): | ||||||
|  |     - sqlite3/common (= 3.49.1) | ||||||
|  |   - sqlite3/common (3.49.1) | ||||||
|  |   - sqlite3/dbstatvtab (3.49.1): | ||||||
|  |     - sqlite3/common | ||||||
|  |   - sqlite3/fts5 (3.49.1): | ||||||
|  |     - sqlite3/common | ||||||
|  |   - sqlite3/perf-threadsafe (3.49.1): | ||||||
|  |     - sqlite3/common | ||||||
|  |   - sqlite3/rtree (3.49.1): | ||||||
|  |     - sqlite3/common | ||||||
|  |   - sqlite3_flutter_libs (0.0.1): | ||||||
|  |     - Flutter | ||||||
|  |     - FlutterMacOS | ||||||
|  |     - sqlite3 (~> 3.49.0) | ||||||
|  |     - sqlite3/dbstatvtab | ||||||
|  |     - sqlite3/fts5 | ||||||
|  |     - sqlite3/perf-threadsafe | ||||||
|  |     - sqlite3/rtree | ||||||
|   - SwiftyGif (5.4.5) |   - SwiftyGif (5.4.5) | ||||||
|   - url_launcher_ios (0.0.1): |   - url_launcher_ios (0.0.1): | ||||||
|     - Flutter |     - Flutter | ||||||
| @@ -268,6 +287,7 @@ DEPENDENCIES: | |||||||
|   - 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`) | ||||||
|   - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) |   - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) | ||||||
|  |   - sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`) | ||||||
|   - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) |   - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) | ||||||
|   - video_compress (from `.symlinks/plugins/video_compress/ios`) |   - video_compress (from `.symlinks/plugins/video_compress/ios`) | ||||||
|   - volume_controller (from `.symlinks/plugins/volume_controller/ios`) |   - volume_controller (from `.symlinks/plugins/volume_controller/ios`) | ||||||
| @@ -294,6 +314,7 @@ SPEC REPOS: | |||||||
|     - PromisesObjC |     - PromisesObjC | ||||||
|     - SAMKeychain |     - SAMKeychain | ||||||
|     - SDWebImage |     - SDWebImage | ||||||
|  |     - sqlite3 | ||||||
|     - SwiftyGif |     - SwiftyGif | ||||||
|     - WebRTC-SDK |     - WebRTC-SDK | ||||||
|  |  | ||||||
| @@ -360,6 +381,8 @@ EXTERNAL SOURCES: | |||||||
|     :path: ".symlinks/plugins/shared_preferences_foundation/darwin" |     :path: ".symlinks/plugins/shared_preferences_foundation/darwin" | ||||||
|   sqflite_darwin: |   sqflite_darwin: | ||||||
|     :path: ".symlinks/plugins/sqflite_darwin/darwin" |     :path: ".symlinks/plugins/sqflite_darwin/darwin" | ||||||
|  |   sqlite3_flutter_libs: | ||||||
|  |     :path: ".symlinks/plugins/sqlite3_flutter_libs/darwin" | ||||||
|   url_launcher_ios: |   url_launcher_ios: | ||||||
|     :path: ".symlinks/plugins/url_launcher_ios/ios" |     :path: ".symlinks/plugins/url_launcher_ios/ios" | ||||||
|   video_compress: |   video_compress: | ||||||
| @@ -380,23 +403,23 @@ SPEC CHECKSUMS: | |||||||
|   DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 |   DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 | ||||||
|   file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49 |   file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49 | ||||||
|   file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808 |   file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808 | ||||||
|   Firebase: a64bf6a8546e6eab54f1c715cd6151f39d2329f4 |   Firebase: d80354ed7f6df5f9aca55e9eb47cc4b634735eaf | ||||||
|   firebase_analytics: 7236e6115c1b4e62c2270faa29c052a317e31107 |   firebase_analytics: 7ec1166af61987fa968766eb11587c562a5650ee | ||||||
|   firebase_core: aa979ae726f00b3ef4ccf59dfb96170af84efbd4 |   firebase_core: 6e223dfa350b2edceb729cea505eaaef59330682 | ||||||
|   firebase_messaging: 3af84b6a90aeac4d7a67fbf4c43a91e7083bea1f |   firebase_messaging: 07fde77ae28c08616a1d4d870450efc2b38cf40d | ||||||
|   FirebaseAnalytics: bc9e565af9044ba8d6c6e4157e4edca8e5fdf7ec |   FirebaseAnalytics: 4fd42def128146e24e480e89f310e3d8534ea42b | ||||||
|   FirebaseCore: 3227e35f4197a924206fbcdc0349325baf4f5de4 |   FirebaseCore: 99fe0c4b44a39f37d99e6404e02009d2db5d718d | ||||||
|   FirebaseCoreInternal: d6c17dafc8dc33614733a8b52df78fcb4394c881 |   FirebaseCoreInternal: df24ce5af28864660ecbd13596fc8dd3a8c34629 | ||||||
|   FirebaseInstallations: 9347e719c3d52d8d7b9074b2c32407dd027305e9 |   FirebaseInstallations: 6c963bd2a86aca0481eef4f48f5a4df783ae5917 | ||||||
|   FirebaseMessaging: 00ece041b71ddb52a2862ffdee73fb6e9824bd0c |   FirebaseMessaging: 487b634ccdf6f7b7ff180fdcb2a9935490f764e8 | ||||||
|   Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 |   Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 | ||||||
|   flutter_app_update: 65f61da626cb111d1b24674abc4b01728d7723bc |   flutter_app_update: 65f61da626cb111d1b24674abc4b01728d7723bc | ||||||
|   flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4 |   flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4 | ||||||
|   flutter_native_splash: f71420956eb811e6d310720fee915f1d42852e7a |   flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29 | ||||||
|   flutter_udid: b2417673f287ee62817a1de3d1643f47b9f508ab |   flutter_udid: b2417673f287ee62817a1de3d1643f47b9f508ab | ||||||
|   flutter_webrtc: 90260f83024b1b96d239a575ea4e3708e79344d1 |   flutter_webrtc: 90260f83024b1b96d239a575ea4e3708e79344d1 | ||||||
|   gal: 6a522c75909f1244732d4596d11d6a2f86ff37a5 |   gal: 6a522c75909f1244732d4596d11d6a2f86ff37a5 | ||||||
|   GoogleAppMeasurement: 0471a5b5bff51f3a91b1e76df22c952d04c63967 |   GoogleAppMeasurement: fc0817122bd4d4189164f85374e06773b9561896 | ||||||
|   GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 |   GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 | ||||||
|   GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d |   GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d | ||||||
|   home_widget: 0434835a4c9a75704264feff6be17ea40e0f0d57 |   home_widget: 0434835a4c9a75704264feff6be17ea40e0f0d57 | ||||||
| @@ -417,10 +440,12 @@ SPEC CHECKSUMS: | |||||||
|   receive_sharing_intent: 79c848f5b045674ad60b9fea3bafea59962ad2c1 |   receive_sharing_intent: 79c848f5b045674ad60b9fea3bafea59962ad2c1 | ||||||
|   SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c |   SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c | ||||||
|   screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625 |   screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625 | ||||||
|   SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8 |   SDWebImage: 33d0f23bddeb5d209ae959153883247be6703713 | ||||||
|   share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f |   share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f | ||||||
|   shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 |   shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 | ||||||
|   sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d |   sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d | ||||||
|  |   sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983 | ||||||
|  |   sqlite3_flutter_libs: 069c435986dd4b63461aecd68f4b30be4a9e9daa | ||||||
|   SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 |   SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 | ||||||
|   url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe |   url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe | ||||||
|   video_compress: fce97e4fb1dfd88175aa07d2ffc8a2f297f87fbe |   video_compress: fce97e4fb1dfd88175aa07d2ffc8a2f297f87fbe | ||||||
|   | |||||||
| @@ -59,6 +59,7 @@ | |||||||
|       ignoresPersistentStateOnLaunch = "NO" |       ignoresPersistentStateOnLaunch = "NO" | ||||||
|       debugDocumentVersioning = "YES" |       debugDocumentVersioning = "YES" | ||||||
|       debugServiceExtension = "internal" |       debugServiceExtension = "internal" | ||||||
|  |       enableGPUValidationMode = "1" | ||||||
|       allowLocationSimulation = "YES"> |       allowLocationSimulation = "YES"> | ||||||
|       <BuildableProductRunnable |       <BuildableProductRunnable | ||||||
|          runnableDebuggingMode = "0"> |          runnableDebuggingMode = "0"> | ||||||
|   | |||||||
| @@ -1,12 +1,14 @@ | |||||||
| import 'dart:async'; | import 'dart:async'; | ||||||
| import 'dart:convert'; | import 'dart:convert'; | ||||||
|  | import 'dart:developer'; | ||||||
| import 'dart:math' as math; | import 'dart:math' as math; | ||||||
|  |  | ||||||
| import 'package:collection/collection.dart'; |  | ||||||
| import 'package:dio/dio.dart'; | import 'package:dio/dio.dart'; | ||||||
|  | import 'package:drift/drift.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:hive/hive.dart'; |  | ||||||
| import 'package:provider/provider.dart'; | import 'package:provider/provider.dart'; | ||||||
|  | import 'package:surface/database/database.dart'; | ||||||
|  | import 'package:surface/providers/database.dart'; | ||||||
| import 'package:surface/providers/sn_attachment.dart'; | import 'package:surface/providers/sn_attachment.dart'; | ||||||
| import 'package:surface/providers/sn_network.dart'; | import 'package:surface/providers/sn_network.dart'; | ||||||
| import 'package:surface/providers/user_directory.dart'; | import 'package:surface/providers/user_directory.dart'; | ||||||
| @@ -16,13 +18,13 @@ import 'package:surface/types/websocket.dart'; | |||||||
| import 'package:uuid/uuid.dart'; | import 'package:uuid/uuid.dart'; | ||||||
|  |  | ||||||
| class ChatMessageController extends ChangeNotifier { | class ChatMessageController extends ChangeNotifier { | ||||||
|   static const kChatMessageBoxPrefix = 'nex_chat_messages_'; |  | ||||||
|   static const kSingleBatchLoadLimit = 100; |   static const kSingleBatchLoadLimit = 100; | ||||||
|  |  | ||||||
|   late final SnNetworkProvider _sn; |   late final SnNetworkProvider _sn; | ||||||
|   late final UserDirectoryProvider _ud; |   late final UserDirectoryProvider _ud; | ||||||
|   late final WebSocketProvider _ws; |   late final WebSocketProvider _ws; | ||||||
|   late final SnAttachmentProvider _attach; |   late final SnAttachmentProvider _attach; | ||||||
|  |   late final DatabaseProvider _dt; | ||||||
|  |  | ||||||
|   StreamSubscription? _wsSubscription; |   StreamSubscription? _wsSubscription; | ||||||
|  |  | ||||||
| @@ -31,6 +33,7 @@ class ChatMessageController extends ChangeNotifier { | |||||||
|     _ud = context.read<UserDirectoryProvider>(); |     _ud = context.read<UserDirectoryProvider>(); | ||||||
|     _ws = context.read<WebSocketProvider>(); |     _ws = context.read<WebSocketProvider>(); | ||||||
|     _attach = context.read<SnAttachmentProvider>(); |     _attach = context.read<SnAttachmentProvider>(); | ||||||
|  |     _dt = context.read<DatabaseProvider>(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   bool isPending = true; |   bool isPending = true; | ||||||
| @@ -38,9 +41,9 @@ class ChatMessageController extends ChangeNotifier { | |||||||
|  |  | ||||||
|   int? messageTotal; |   int? messageTotal; | ||||||
|  |  | ||||||
|   bool get isAllLoaded => messageTotal != null && messages.length >= messageTotal!; |   bool get isAllLoaded => | ||||||
|  |       messageTotal != null && messages.length >= messageTotal!; | ||||||
|  |  | ||||||
|   String? _boxKey; |  | ||||||
|   SnChannel? channel; |   SnChannel? channel; | ||||||
|   SnChannelMember? profile; |   SnChannelMember? profile; | ||||||
|  |  | ||||||
| @@ -51,25 +54,17 @@ class ChatMessageController extends ChangeNotifier { | |||||||
|   /// Stored as a list of nonce to provide the loading state |   /// Stored as a list of nonce to provide the loading state | ||||||
|   final List<String> unconfirmedMessages = List.empty(growable: true); |   final List<String> unconfirmedMessages = List.empty(growable: true); | ||||||
|  |  | ||||||
|   Box<SnChatMessage>? get _box => (_boxKey == null || isPending) ? null : Hive.box<SnChatMessage>(_boxKey!); |  | ||||||
|  |  | ||||||
|   final List<SnChannelMember> typingMembers = List.empty(growable: true); |   final List<SnChannelMember> typingMembers = List.empty(growable: true); | ||||||
|   final Map<int, Timer> typingInactiveTimer = {}; |   final Map<int, Timer> typingInactiveTimer = {}; | ||||||
|  |  | ||||||
|   Future<void> initialize(SnChannel chan) async { |   Future<void> initialize(SnChannel chan) async { | ||||||
|     channel = chan; |     channel = chan; | ||||||
|  |  | ||||||
|     // Initialize local data |  | ||||||
|     _boxKey = '$kChatMessageBoxPrefix${chan.id}'; |  | ||||||
|     await Hive.openBox<SnChatMessage>(_boxKey!); |  | ||||||
|  |  | ||||||
|     // Fetch channel profile |     // Fetch channel profile | ||||||
|     final resp = await _sn.client.get( |     final resp = await _sn.client.get( | ||||||
|       '/cgi/im/channels/${chan.keyPath}/me', |       '/cgi/im/channels/${chan.keyPath}/me', | ||||||
|     ); |     ); | ||||||
|     profile = SnChannelMember.fromJson( |     profile = SnChannelMember.fromJson(resp.data); | ||||||
|       resp.data as Map<String, dynamic>, |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     _wsSubscription = _ws.pk.stream.listen((event) { |     _wsSubscription = _ws.pk.stream.listen((event) { | ||||||
|       switch (event.method) { |       switch (event.method) { | ||||||
| @@ -87,7 +82,8 @@ class ChatMessageController extends ChangeNotifier { | |||||||
|             notifyListeners(); |             notifyListeners(); | ||||||
|           } |           } | ||||||
|           typingInactiveTimer[member.id]?.cancel(); |           typingInactiveTimer[member.id]?.cancel(); | ||||||
|           typingInactiveTimer[member.id] = Timer(const Duration(seconds: 3), () { |           typingInactiveTimer[member.id] = | ||||||
|  |               Timer(const Duration(seconds: 3), () { | ||||||
|             typingMembers.removeWhere((x) => x.id == member.id); |             typingMembers.removeWhere((x) => x.id == member.id); | ||||||
|             typingInactiveTimer.remove(member.id); |             typingInactiveTimer.remove(member.id); | ||||||
|             notifyListeners(); |             notifyListeners(); | ||||||
| @@ -129,10 +125,16 @@ class ChatMessageController extends ChangeNotifier { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   Future<void> _saveMessageToLocal(Iterable<SnChatMessage> messages) async { |   Future<void> _saveMessageToLocal(Iterable<SnChatMessage> messages) async { | ||||||
|     if (_box == null) return; |     await _dt.db.snLocalChatMessage.insertAll( | ||||||
|     await _box!.putAll({ |         messages.map( | ||||||
|       for (final message in messages) message.id: message, |           (ele) => SnLocalChatMessageCompanion.insert( | ||||||
|     }); |             id: Value(ele.id), | ||||||
|  |             content: ele, | ||||||
|  |             channelId: channel!.id, | ||||||
|  |             createdAt: Value(ele.createdAt), | ||||||
|  |           ), | ||||||
|  |         ), | ||||||
|  |         onConflict: DoNothing()); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   Future<void> _addUnconfirmedMessage(SnChatMessage message) async { |   Future<void> _addUnconfirmedMessage(SnChatMessage message) async { | ||||||
| @@ -184,8 +186,21 @@ class ChatMessageController extends ChangeNotifier { | |||||||
|     await _applyMessage(message); |     await _applyMessage(message); | ||||||
|     notifyListeners(); |     notifyListeners(); | ||||||
|  |  | ||||||
|     if (_box == null) return; |     if (isCheckedUpdate) { | ||||||
|     await _box!.put(message.id, message); |       await _dt.db.snLocalChatMessage.insertOne( | ||||||
|  |         SnLocalChatMessageCompanion.insert( | ||||||
|  |           id: Value(message.id), | ||||||
|  |           content: message, | ||||||
|  |           channelId: channel!.id, | ||||||
|  |           createdAt: Value(message.createdAt), | ||||||
|  |         ), | ||||||
|  |         onConflict: DoUpdate((_) => SnLocalChatMessageCompanion.custom( | ||||||
|  |               content: Constant(jsonEncode(message.toJson())), | ||||||
|  |             )), | ||||||
|  |       ); | ||||||
|  |     } else { | ||||||
|  |       incomeStrandedQueue.add(message); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   Future<void> _applyMessage(SnChatMessage message) async { |   Future<void> _applyMessage(SnChatMessage message) async { | ||||||
| @@ -194,7 +209,8 @@ class ChatMessageController extends ChangeNotifier { | |||||||
|     switch (message.type) { |     switch (message.type) { | ||||||
|       case 'messages.edit': |       case 'messages.edit': | ||||||
|         if (message.relatedEventId != null) { |         if (message.relatedEventId != null) { | ||||||
|           final idx = messages.indexWhere((x) => x.id == message.relatedEventId); |           final idx = | ||||||
|  |               messages.indexWhere((x) => x.id == message.relatedEventId); | ||||||
|           if (idx != -1) { |           if (idx != -1) { | ||||||
|             final newBody = message.body; |             final newBody = message.body; | ||||||
|             newBody.remove('related_event'); |             newBody.remove('related_event'); | ||||||
| @@ -202,16 +218,24 @@ class ChatMessageController extends ChangeNotifier { | |||||||
|               body: newBody, |               body: newBody, | ||||||
|               updatedAt: message.updatedAt, |               updatedAt: message.updatedAt, | ||||||
|             ); |             ); | ||||||
|             if (_box!.containsKey(message.relatedEventId)) { |             if (message.relatedEventId != null) { | ||||||
|               await _box!.put(message.relatedEventId, messages[idx]); |               await (_dt.db.snLocalChatMessage.update() | ||||||
|  |                     ..where((e) => e.id.equals(message.relatedEventId!))) | ||||||
|  |                   .write( | ||||||
|  |                 SnLocalChatMessageCompanion.custom( | ||||||
|  |                   content: Constant(jsonEncode(messages[idx].toJson())), | ||||||
|  |                 ), | ||||||
|  |               ); | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|       case 'messages.delete': |       case 'messages.delete': | ||||||
|         if (message.relatedEventId != null) { |         if (message.relatedEventId != null) { | ||||||
|           messages.removeWhere((x) => x.id == message.relatedEventId); |           messages.removeWhere((x) => x.id == message.relatedEventId); | ||||||
|           if (_box!.containsKey(message.relatedEventId)) { |           if (message.relatedEventId != null) { | ||||||
|             await _box!.delete(message.relatedEventId); |             await (_dt.db.snLocalChatMessage.delete() | ||||||
|  |                   ..where((e) => e.id.equals(message.relatedEventId!))) | ||||||
|  |                 .go(); | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -233,7 +257,8 @@ class ChatMessageController extends ChangeNotifier { | |||||||
|       'algorithm': 'plain', |       'algorithm': 'plain', | ||||||
|       if (quoteId != null) 'quote_event': quoteId, |       if (quoteId != null) 'quote_event': quoteId, | ||||||
|       if (relatedId != null) 'related_event': relatedId, |       if (relatedId != null) 'related_event': relatedId, | ||||||
|       if (attachments != null && attachments.isNotEmpty) 'attachments': attachments, |       if (attachments != null && attachments.isNotEmpty) | ||||||
|  |         'attachments': attachments, | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     // Mock the message locally |     // Mock the message locally | ||||||
| @@ -287,20 +312,34 @@ class ChatMessageController extends ChangeNotifier { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   bool isCheckedUpdate = false; | ||||||
|  |   List<SnChatMessage> incomeStrandedQueue = List.empty(growable: true); | ||||||
|  |  | ||||||
|   /// Check the local storage is up to date with the server. |   /// Check the local storage is up to date with the server. | ||||||
|   /// If the local storage is not up to date, it will be updated. |   /// If the local storage is not up to date, it will be updated. | ||||||
|   Future<void> checkUpdate() async { |   Future<void> checkUpdate() async { | ||||||
|     if (_box == null) return; |  | ||||||
|     if (_box!.isEmpty) return; |  | ||||||
|  |  | ||||||
|     isLoading = true; |     isLoading = true; | ||||||
|     notifyListeners(); |     notifyListeners(); | ||||||
|  |  | ||||||
|  |     final mostRecentMessage = await (_dt.db.snLocalChatMessage.select() | ||||||
|  |           ..limit(1) | ||||||
|  |           ..orderBy([ | ||||||
|  |             (e) => | ||||||
|  |                 OrderingTerm(expression: e.createdAt, mode: OrderingMode.desc) | ||||||
|  |           ])) | ||||||
|  |         .getSingleOrNull(); | ||||||
|  |     if (mostRecentMessage == null) { | ||||||
|  |       // Initial load | ||||||
|  |       await loadMessages(take: 20); | ||||||
|  |       isCheckedUpdate = true; | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     try { |     try { | ||||||
|       final resp = await _sn.client.get( |       final resp = await _sn.client.get( | ||||||
|         '/cgi/im/channels/${channel!.keyPath}/events/update', |         '/cgi/im/channels/${channel!.keyPath}/events/update', | ||||||
|         queryParameters: { |         queryParameters: { | ||||||
|           'pivot': _box!.values.last.id, |           'pivot': mostRecentMessage.content.id, | ||||||
|         }, |         }, | ||||||
|       ); |       ); | ||||||
|       if (resp.data['up_to_date'] == true) return; |       if (resp.data['up_to_date'] == true) return; | ||||||
| @@ -316,6 +355,12 @@ class ChatMessageController extends ChangeNotifier { | |||||||
|     } finally { |     } finally { | ||||||
|       await loadMessages(); |       await loadMessages(); | ||||||
|       isLoading = false; |       isLoading = false; | ||||||
|  |  | ||||||
|  |       isCheckedUpdate = true; | ||||||
|  |       _saveMessageToLocal(incomeStrandedQueue).then((_) { | ||||||
|  |         incomeStrandedQueue.clear(); | ||||||
|  |       }); | ||||||
|  |  | ||||||
|       notifyListeners(); |       notifyListeners(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| @@ -324,13 +369,18 @@ class ChatMessageController extends ChangeNotifier { | |||||||
|   /// If it was not found in local storage we will look it up in remote |   /// If it was not found in local storage we will look it up in remote | ||||||
|   Future<SnChatMessage?> getMessage(int id) async { |   Future<SnChatMessage?> getMessage(int id) async { | ||||||
|     SnChatMessage? out; |     SnChatMessage? out; | ||||||
|     if (_box != null && _box!.containsKey(id)) { |     final local = await (_dt.db.snLocalChatMessage.select() | ||||||
|       out = _box!.get(id); |           ..limit(1) | ||||||
|  |           ..where((e) => e.id.equals(id))) | ||||||
|  |         .getSingleOrNull(); | ||||||
|  |     if (local != null) { | ||||||
|  |       out = local.content; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (out == null) { |     if (out == null) { | ||||||
|       try { |       try { | ||||||
|         final resp = await _sn.client.get('/cgi/im/channels/${channel!.keyPath}/events/$id'); |         final resp = await _sn.client | ||||||
|  |             .get('/cgi/im/channels/${channel!.keyPath}/events/$id'); | ||||||
|         out = SnChatMessage.fromJson(resp.data); |         out = SnChatMessage.fromJson(resp.data); | ||||||
|         _saveMessageToLocal([out]); |         _saveMessageToLocal([out]); | ||||||
|       } catch (_) { |       } catch (_) { | ||||||
| @@ -364,16 +414,21 @@ class ChatMessageController extends ChangeNotifier { | |||||||
|     bool forceLocal = false, |     bool forceLocal = false, | ||||||
|     bool forceRemote = false, |     bool forceRemote = false, | ||||||
|   }) async { |   }) async { | ||||||
|  |     final localTotal = await _dt.db.snLocalChatMessage | ||||||
|  |         .count(where: (e) => e.channelId.equals(channel!.id)) | ||||||
|  |         .getSingle(); | ||||||
|  |  | ||||||
|     late List<SnChatMessage> out; |     late List<SnChatMessage> out; | ||||||
|     if (_box != null && (_box!.length >= take + offset || forceLocal) && !forceRemote) { |     if ((localTotal >= take + offset || forceLocal) && !forceRemote) { | ||||||
|       out = _box!.keys |       final result = await (_dt.db.snLocalChatMessage.select() | ||||||
|           .toList() |             ..where((e) => e.channelId.equals(channel!.id)) | ||||||
|           .cast<int>() |             ..orderBy([ | ||||||
|           .sorted((a, b) => b.compareTo(a)) |               (e) => | ||||||
|           .skip(offset) |                   OrderingTerm(expression: e.createdAt, mode: OrderingMode.desc) | ||||||
|           .take(take) |             ]) | ||||||
|           .map((key) => _box!.get(key)!) |             ..limit(take, offset: offset)) | ||||||
|           .toList(); |           .get(); | ||||||
|  |       out = result.map((e) => e.content).toList(); | ||||||
|     } else { |     } else { | ||||||
|       final resp = await _sn.client.get( |       final resp = await _sn.client.get( | ||||||
|         '/cgi/im/channels/${channel!.keyPath}/events', |         '/cgi/im/channels/${channel!.keyPath}/events', | ||||||
| @@ -408,7 +463,8 @@ class ChatMessageController extends ChangeNotifier { | |||||||
|           quoteEvent: quoteEvent, |           quoteEvent: quoteEvent, | ||||||
|           attachments: attachments |           attachments: attachments | ||||||
|               .where( |               .where( | ||||||
|                 (ele) => out[i].body['attachments']?.contains(ele?.rid) ?? false, |                 (ele) => | ||||||
|  |                     out[i].body['attachments']?.contains(ele?.rid) ?? false, | ||||||
|               ) |               ) | ||||||
|               .toList(), |               .toList(), | ||||||
|         ), |         ), | ||||||
| @@ -416,7 +472,10 @@ class ChatMessageController extends ChangeNotifier { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Preload sender accounts |     // Preload sender accounts | ||||||
|     final accountId = out.where((ele) => ele.sender.accountId >= 0).map((ele) => ele.sender.accountId).toSet(); |     final accountId = out | ||||||
|  |         .where((ele) => ele.sender.accountId >= 0) | ||||||
|  |         .map((ele) => ele.sender.accountId) | ||||||
|  |         .toSet(); | ||||||
|     await _ud.listAccount(accountId); |     await _ud.listAccount(accountId); | ||||||
|  |  | ||||||
|     return out; |     return out; | ||||||
| @@ -441,10 +500,45 @@ class ChatMessageController extends ChangeNotifier { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   Timer? _readEventDebounce; | ||||||
|  |   int? _readEventAnchor; | ||||||
|  |  | ||||||
|  |   void readEvent(int id) { | ||||||
|  |     if (_readEventAnchor != null) { | ||||||
|  |       _readEventAnchor = math.max(_readEventAnchor!, id); | ||||||
|  |     } else { | ||||||
|  |       _readEventAnchor = id; | ||||||
|  |     } | ||||||
|  |     if (_readEventDebounce?.isActive ?? false) { | ||||||
|  |       _readEventDebounce?.cancel(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     _readEventDebounce = Timer(const Duration(milliseconds: 500), () { | ||||||
|  |       _sendReadEvent(); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void _sendReadEvent() { | ||||||
|  |     _ws.conn?.sink.add(jsonEncode( | ||||||
|  |       WebSocketPackage( | ||||||
|  |         method: 'events.read', | ||||||
|  |         endpoint: 'im', | ||||||
|  |         payload: { | ||||||
|  |           'channel_member_id': profile!.id, | ||||||
|  |           'event_id': _readEventAnchor, | ||||||
|  |         }, | ||||||
|  |       ).toJson(), | ||||||
|  |     )); | ||||||
|  |     log('[Messaging] Send read event request: $_readEventAnchor'); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   void dispose() { |   void dispose() { | ||||||
|     _box?.close(); |  | ||||||
|     _wsSubscription?.cancel(); |     _wsSubscription?.cancel(); | ||||||
|  |     if (_readEventDebounce?.isActive ?? false) { | ||||||
|  |       _sendReadEvent(); | ||||||
|  |     } | ||||||
|  |     _readEventDebounce?.cancel(); | ||||||
|     super.dispose(); |     super.dispose(); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -18,6 +18,7 @@ import 'package:surface/providers/sn_network.dart'; | |||||||
| import 'package:surface/types/attachment.dart'; | import 'package:surface/types/attachment.dart'; | ||||||
| import 'package:surface/types/poll.dart'; | import 'package:surface/types/poll.dart'; | ||||||
| import 'package:surface/types/post.dart'; | import 'package:surface/types/post.dart'; | ||||||
|  | import 'package:surface/types/realm.dart'; | ||||||
| import 'package:surface/widgets/dialog.dart'; | import 'package:surface/widgets/dialog.dart'; | ||||||
| import 'package:surface/widgets/universal_image.dart'; | import 'package:surface/widgets/universal_image.dart'; | ||||||
| import 'package:video_compress/video_compress.dart'; | import 'package:video_compress/video_compress.dart'; | ||||||
| @@ -161,7 +162,8 @@ class PostWriteController extends ChangeNotifier { | |||||||
|   ContentInsertionConfiguration get contentInsertionConfiguration => ContentInsertionConfiguration( |   ContentInsertionConfiguration get contentInsertionConfiguration => ContentInsertionConfiguration( | ||||||
|         onContentInserted: (KeyboardInsertedContent content) { |         onContentInserted: (KeyboardInsertedContent content) { | ||||||
|           if (content.hasData) { |           if (content.hasData) { | ||||||
|         addAttachments([PostWriteMedia.fromBytes(content.data!, 'attachmentInsertedImage'.tr(), SnMediaType.image)]); |             addAttachments( | ||||||
|  |                 [PostWriteMedia.fromBytes(content.data!, 'attachmentInsertedImage'.tr(), SnMediaType.image)]); | ||||||
|           } |           } | ||||||
|         }, |         }, | ||||||
|       ); |       ); | ||||||
| @@ -196,6 +198,7 @@ class PostWriteController extends ChangeNotifier { | |||||||
|   bool isLoading = false, isBusy = false; |   bool isLoading = false, isBusy = false; | ||||||
|   double? progress; |   double? progress; | ||||||
|  |  | ||||||
|  |   SnRealm? realm; | ||||||
|   SnPublisher? publisher; |   SnPublisher? publisher; | ||||||
|   SnPost? editingPost, repostingPost, replyingPost; |   SnPost? editingPost, repostingPost, replyingPost; | ||||||
|  |  | ||||||
| @@ -244,6 +247,9 @@ class PostWriteController extends ChangeNotifier { | |||||||
|         if (post.preload?.thumbnail != null && (post.preload?.thumbnail?.rid.isNotEmpty ?? false)) { |         if (post.preload?.thumbnail != null && (post.preload?.thumbnail?.rid.isNotEmpty ?? false)) { | ||||||
|           thumbnail = PostWriteMedia(post.preload!.thumbnail); |           thumbnail = PostWriteMedia(post.preload!.thumbnail); | ||||||
|         } |         } | ||||||
|  |         if (post.preload?.realm != null) { | ||||||
|  |           realm = post.preload!.realm!; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         editingPost = post; |         editingPost = post; | ||||||
|       } |       } | ||||||
| @@ -379,6 +385,7 @@ class PostWriteController extends ChangeNotifier { | |||||||
|           if (replyingPost != null) 'reply_to': replyingPost!.toJson(), |           if (replyingPost != null) 'reply_to': replyingPost!.toJson(), | ||||||
|           if (repostingPost != null) 'repost_to': repostingPost!.toJson(), |           if (repostingPost != null) 'repost_to': repostingPost!.toJson(), | ||||||
|           if (poll != null) 'poll': poll!.toJson(), |           if (poll != null) 'poll': poll!.toJson(), | ||||||
|  |           if (realm != null) 'realm': realm!.toJson(), | ||||||
|         }), |         }), | ||||||
|       ); |       ); | ||||||
|     }); |     }); | ||||||
| @@ -409,6 +416,7 @@ class PostWriteController extends ChangeNotifier { | |||||||
|       replyingPost = data['reply_to'] != null ? SnPost.fromJson(data['reply_to']) : null; |       replyingPost = data['reply_to'] != null ? SnPost.fromJson(data['reply_to']) : null; | ||||||
|       repostingPost = data['repost_to'] != null ? SnPost.fromJson(data['repost_to']) : null; |       repostingPost = data['repost_to'] != null ? SnPost.fromJson(data['repost_to']) : null; | ||||||
|       poll = data['poll'] != null ? SnPoll.fromJson(data['poll']) : null; |       poll = data['poll'] != null ? SnPoll.fromJson(data['poll']) : null; | ||||||
|  |       realm = data['realm'] != null ? SnRealm.fromJson(data['realm']) : null; | ||||||
|       temporaryRestored = true; |       temporaryRestored = true; | ||||||
|       notifyListeners(); |       notifyListeners(); | ||||||
|     }); |     }); | ||||||
| @@ -525,6 +533,7 @@ class PostWriteController extends ChangeNotifier { | |||||||
|           if (reward != null) 'reward': reward, |           if (reward != null) 'reward': reward, | ||||||
|           if (videoAttachment != null) 'video': videoAttachment!.rid, |           if (videoAttachment != null) 'video': videoAttachment!.rid, | ||||||
|           if (poll != null) 'poll': poll!.id, |           if (poll != null) 'poll': poll!.id, | ||||||
|  |           if (realm != null) 'realm': realm!.id, | ||||||
|         }, |         }, | ||||||
|         onSendProgress: (count, total) { |         onSendProgress: (count, total) { | ||||||
|           progress = baseProgressVal + (count / total) * (kPostingProgressWeight / 2); |           progress = baseProgressVal + (count / total) * (kPostingProgressWeight / 2); | ||||||
| @@ -571,17 +580,8 @@ class PostWriteController extends ChangeNotifier { | |||||||
|     notifyListeners(); |     notifyListeners(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   void setThumbnail(int? idx) { |   void setThumbnail(SnAttachment? value) { | ||||||
|     if (idx == null) { |     thumbnail = value == null ? null : PostWriteMedia(value); | ||||||
|       attachments.add(thumbnail!); |  | ||||||
|       thumbnail = null; |  | ||||||
|     } else { |  | ||||||
|       if (thumbnail != null) { |  | ||||||
|         attachments.add(thumbnail!); |  | ||||||
|       } |  | ||||||
|       thumbnail = attachments[idx]; |  | ||||||
|       attachments.removeAt(idx); |  | ||||||
|     } |  | ||||||
|     notifyListeners(); |     notifyListeners(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -633,6 +633,11 @@ class PostWriteController extends ChangeNotifier { | |||||||
|     notifyListeners(); |     notifyListeners(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   void setRealm(SnRealm? value) { | ||||||
|  |     realm = value; | ||||||
|  |     notifyListeners(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   void setProgress(double? value) { |   void setProgress(double? value) { | ||||||
|     progress = value; |     progress = value; | ||||||
|     _temporaryPlanSave(); |     _temporaryPlanSave(); | ||||||
|   | |||||||
							
								
								
									
										74
									
								
								lib/database/chat.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								lib/database/chat.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | |||||||
|  | import 'dart:convert'; | ||||||
|  |  | ||||||
|  | import 'package:drift/drift.dart'; | ||||||
|  | import 'package:surface/types/chat.dart'; | ||||||
|  |  | ||||||
|  | class SnChannelConverter extends TypeConverter<SnChannel, String> | ||||||
|  |     with JsonTypeConverter2<SnChannel, String, Map<String, Object?>> { | ||||||
|  |   const SnChannelConverter(); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   SnChannel fromSql(String fromDb) { | ||||||
|  |     return fromJson(jsonDecode(fromDb) as Map<String, dynamic>); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String toSql(SnChannel value) { | ||||||
|  |     return jsonEncode(toJson(value)); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   SnChannel fromJson(Map<String, Object?> json) { | ||||||
|  |     return SnChannel.fromJson(json); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Map<String, Object?> toJson(SnChannel value) { | ||||||
|  |     return value.toJson(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class SnLocalChatChannel extends Table { | ||||||
|  |   IntColumn get id => integer().autoIncrement()(); | ||||||
|  |  | ||||||
|  |   TextColumn get alias => text()(); | ||||||
|  |  | ||||||
|  |   TextColumn get content => text().map(const SnChannelConverter())(); | ||||||
|  |  | ||||||
|  |   DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class SnMessageConverter extends TypeConverter<SnChatMessage, String> | ||||||
|  |     with JsonTypeConverter2<SnChatMessage, String, Map<String, Object?>> { | ||||||
|  |   const SnMessageConverter(); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   SnChatMessage fromSql(String fromDb) { | ||||||
|  |     return fromJson(jsonDecode(fromDb) as Map<String, dynamic>); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String toSql(SnChatMessage value) { | ||||||
|  |     return jsonEncode(toJson(value)); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   SnChatMessage fromJson(Map<String, Object?> json) { | ||||||
|  |     return SnChatMessage.fromJson(json); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Map<String, Object?> toJson(SnChatMessage value) { | ||||||
|  |     return value.toJson(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class SnLocalChatMessage extends Table { | ||||||
|  |   IntColumn get id => integer().autoIncrement()(); | ||||||
|  |  | ||||||
|  |   IntColumn get channelId => integer()(); | ||||||
|  |  | ||||||
|  |   TextColumn get content => text().map(const SnMessageConverter())(); | ||||||
|  |  | ||||||
|  |   DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); | ||||||
|  | } | ||||||
							
								
								
									
										28
									
								
								lib/database/database.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								lib/database/database.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | import 'package:drift/drift.dart'; | ||||||
|  | import 'package:drift_flutter/drift_flutter.dart'; | ||||||
|  | import 'package:path_provider/path_provider.dart'; | ||||||
|  | import 'package:surface/database/chat.dart'; | ||||||
|  | import 'package:surface/types/chat.dart'; | ||||||
|  |  | ||||||
|  | part 'database.g.dart'; | ||||||
|  |  | ||||||
|  | @DriftDatabase(tables: [SnLocalChatChannel, SnLocalChatMessage]) | ||||||
|  | class AppDatabase extends _$AppDatabase { | ||||||
|  |   AppDatabase() : super(_openConnection()); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get schemaVersion => 1; | ||||||
|  |  | ||||||
|  |   static QueryExecutor _openConnection() { | ||||||
|  |     return driftDatabase( | ||||||
|  |       name: 'solar_network_data', | ||||||
|  |       native: const DriftNativeOptions( | ||||||
|  |         databaseDirectory: getApplicationSupportDirectory, | ||||||
|  |       ), | ||||||
|  |       web: DriftWebOptions( | ||||||
|  |         sqlite3Wasm: Uri.parse('sqlite3.wasm'), | ||||||
|  |         driftWorker: Uri.parse('drift_worker.dart.js'), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										880
									
								
								lib/database/database.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										880
									
								
								lib/database/database.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,880 @@ | |||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  |  | ||||||
|  | part of 'database.dart'; | ||||||
|  |  | ||||||
|  | // ignore_for_file: type=lint | ||||||
|  | class $SnLocalChatChannelTable extends SnLocalChatChannel | ||||||
|  |     with TableInfo<$SnLocalChatChannelTable, SnLocalChatChannelData> { | ||||||
|  |   @override | ||||||
|  |   final GeneratedDatabase attachedDatabase; | ||||||
|  |   final String? _alias; | ||||||
|  |   $SnLocalChatChannelTable(this.attachedDatabase, [this._alias]); | ||||||
|  |   static const VerificationMeta _idMeta = const VerificationMeta('id'); | ||||||
|  |   @override | ||||||
|  |   late final GeneratedColumn<int> id = GeneratedColumn<int>( | ||||||
|  |       'id', aliasedName, false, | ||||||
|  |       hasAutoIncrement: true, | ||||||
|  |       type: DriftSqlType.int, | ||||||
|  |       requiredDuringInsert: false, | ||||||
|  |       defaultConstraints: | ||||||
|  |           GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); | ||||||
|  |   static const VerificationMeta _aliasMeta = const VerificationMeta('alias'); | ||||||
|  |   @override | ||||||
|  |   late final GeneratedColumn<String> alias = GeneratedColumn<String>( | ||||||
|  |       'alias', aliasedName, false, | ||||||
|  |       type: DriftSqlType.string, requiredDuringInsert: true); | ||||||
|  |   static const VerificationMeta _contentMeta = | ||||||
|  |       const VerificationMeta('content'); | ||||||
|  |   @override | ||||||
|  |   late final GeneratedColumnWithTypeConverter<SnChannel, String> content = | ||||||
|  |       GeneratedColumn<String>('content', aliasedName, false, | ||||||
|  |               type: DriftSqlType.string, requiredDuringInsert: true) | ||||||
|  |           .withConverter<SnChannel>($SnLocalChatChannelTable.$convertercontent); | ||||||
|  |   static const VerificationMeta _createdAtMeta = | ||||||
|  |       const VerificationMeta('createdAt'); | ||||||
|  |   @override | ||||||
|  |   late final GeneratedColumn<DateTime> createdAt = GeneratedColumn<DateTime>( | ||||||
|  |       'created_at', aliasedName, false, | ||||||
|  |       type: DriftSqlType.dateTime, | ||||||
|  |       requiredDuringInsert: false, | ||||||
|  |       defaultValue: currentDateAndTime); | ||||||
|  |   @override | ||||||
|  |   List<GeneratedColumn> get $columns => [id, alias, content, createdAt]; | ||||||
|  |   @override | ||||||
|  |   String get aliasedName => _alias ?? actualTableName; | ||||||
|  |   @override | ||||||
|  |   String get actualTableName => $name; | ||||||
|  |   static const String $name = 'sn_local_chat_channel'; | ||||||
|  |   @override | ||||||
|  |   VerificationContext validateIntegrity( | ||||||
|  |       Insertable<SnLocalChatChannelData> instance, | ||||||
|  |       {bool isInserting = false}) { | ||||||
|  |     final context = VerificationContext(); | ||||||
|  |     final data = instance.toColumns(true); | ||||||
|  |     if (data.containsKey('id')) { | ||||||
|  |       context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); | ||||||
|  |     } | ||||||
|  |     if (data.containsKey('alias')) { | ||||||
|  |       context.handle( | ||||||
|  |           _aliasMeta, alias.isAcceptableOrUnknown(data['alias']!, _aliasMeta)); | ||||||
|  |     } else if (isInserting) { | ||||||
|  |       context.missing(_aliasMeta); | ||||||
|  |     } | ||||||
|  |     context.handle(_contentMeta, const VerificationResult.success()); | ||||||
|  |     if (data.containsKey('created_at')) { | ||||||
|  |       context.handle(_createdAtMeta, | ||||||
|  |           createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); | ||||||
|  |     } | ||||||
|  |     return context; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Set<GeneratedColumn> get $primaryKey => {id}; | ||||||
|  |   @override | ||||||
|  |   SnLocalChatChannelData map(Map<String, dynamic> data, {String? tablePrefix}) { | ||||||
|  |     final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; | ||||||
|  |     return SnLocalChatChannelData( | ||||||
|  |       id: attachedDatabase.typeMapping | ||||||
|  |           .read(DriftSqlType.int, data['${effectivePrefix}id'])!, | ||||||
|  |       alias: attachedDatabase.typeMapping | ||||||
|  |           .read(DriftSqlType.string, data['${effectivePrefix}alias'])!, | ||||||
|  |       content: $SnLocalChatChannelTable.$convertercontent.fromSql( | ||||||
|  |           attachedDatabase.typeMapping | ||||||
|  |               .read(DriftSqlType.string, data['${effectivePrefix}content'])!), | ||||||
|  |       createdAt: attachedDatabase.typeMapping | ||||||
|  |           .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   $SnLocalChatChannelTable createAlias(String alias) { | ||||||
|  |     return $SnLocalChatChannelTable(attachedDatabase, alias); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static JsonTypeConverter2<SnChannel, String, Map<String, Object?>> | ||||||
|  |       $convertercontent = const SnChannelConverter(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class SnLocalChatChannelData extends DataClass | ||||||
|  |     implements Insertable<SnLocalChatChannelData> { | ||||||
|  |   final int id; | ||||||
|  |   final String alias; | ||||||
|  |   final SnChannel content; | ||||||
|  |   final DateTime createdAt; | ||||||
|  |   const SnLocalChatChannelData( | ||||||
|  |       {required this.id, | ||||||
|  |       required this.alias, | ||||||
|  |       required this.content, | ||||||
|  |       required this.createdAt}); | ||||||
|  |   @override | ||||||
|  |   Map<String, Expression> toColumns(bool nullToAbsent) { | ||||||
|  |     final map = <String, Expression>{}; | ||||||
|  |     map['id'] = Variable<int>(id); | ||||||
|  |     map['alias'] = Variable<String>(alias); | ||||||
|  |     { | ||||||
|  |       map['content'] = Variable<String>( | ||||||
|  |           $SnLocalChatChannelTable.$convertercontent.toSql(content)); | ||||||
|  |     } | ||||||
|  |     map['created_at'] = Variable<DateTime>(createdAt); | ||||||
|  |     return map; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   SnLocalChatChannelCompanion toCompanion(bool nullToAbsent) { | ||||||
|  |     return SnLocalChatChannelCompanion( | ||||||
|  |       id: Value(id), | ||||||
|  |       alias: Value(alias), | ||||||
|  |       content: Value(content), | ||||||
|  |       createdAt: Value(createdAt), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   factory SnLocalChatChannelData.fromJson(Map<String, dynamic> json, | ||||||
|  |       {ValueSerializer? serializer}) { | ||||||
|  |     serializer ??= driftRuntimeOptions.defaultSerializer; | ||||||
|  |     return SnLocalChatChannelData( | ||||||
|  |       id: serializer.fromJson<int>(json['id']), | ||||||
|  |       alias: serializer.fromJson<String>(json['alias']), | ||||||
|  |       content: $SnLocalChatChannelTable.$convertercontent | ||||||
|  |           .fromJson(serializer.fromJson<Map<String, Object?>>(json['content'])), | ||||||
|  |       createdAt: serializer.fromJson<DateTime>(json['createdAt']), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |   @override | ||||||
|  |   Map<String, dynamic> toJson({ValueSerializer? serializer}) { | ||||||
|  |     serializer ??= driftRuntimeOptions.defaultSerializer; | ||||||
|  |     return <String, dynamic>{ | ||||||
|  |       'id': serializer.toJson<int>(id), | ||||||
|  |       'alias': serializer.toJson<String>(alias), | ||||||
|  |       'content': serializer.toJson<Map<String, Object?>>( | ||||||
|  |           $SnLocalChatChannelTable.$convertercontent.toJson(content)), | ||||||
|  |       'createdAt': serializer.toJson<DateTime>(createdAt), | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   SnLocalChatChannelData copyWith( | ||||||
|  |           {int? id, String? alias, SnChannel? content, DateTime? createdAt}) => | ||||||
|  |       SnLocalChatChannelData( | ||||||
|  |         id: id ?? this.id, | ||||||
|  |         alias: alias ?? this.alias, | ||||||
|  |         content: content ?? this.content, | ||||||
|  |         createdAt: createdAt ?? this.createdAt, | ||||||
|  |       ); | ||||||
|  |   SnLocalChatChannelData copyWithCompanion(SnLocalChatChannelCompanion data) { | ||||||
|  |     return SnLocalChatChannelData( | ||||||
|  |       id: data.id.present ? data.id.value : this.id, | ||||||
|  |       alias: data.alias.present ? data.alias.value : this.alias, | ||||||
|  |       content: data.content.present ? data.content.value : this.content, | ||||||
|  |       createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String toString() { | ||||||
|  |     return (StringBuffer('SnLocalChatChannelData(') | ||||||
|  |           ..write('id: $id, ') | ||||||
|  |           ..write('alias: $alias, ') | ||||||
|  |           ..write('content: $content, ') | ||||||
|  |           ..write('createdAt: $createdAt') | ||||||
|  |           ..write(')')) | ||||||
|  |         .toString(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode => Object.hash(id, alias, content, createdAt); | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) => | ||||||
|  |       identical(this, other) || | ||||||
|  |       (other is SnLocalChatChannelData && | ||||||
|  |           other.id == this.id && | ||||||
|  |           other.alias == this.alias && | ||||||
|  |           other.content == this.content && | ||||||
|  |           other.createdAt == this.createdAt); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class SnLocalChatChannelCompanion | ||||||
|  |     extends UpdateCompanion<SnLocalChatChannelData> { | ||||||
|  |   final Value<int> id; | ||||||
|  |   final Value<String> alias; | ||||||
|  |   final Value<SnChannel> content; | ||||||
|  |   final Value<DateTime> createdAt; | ||||||
|  |   const SnLocalChatChannelCompanion({ | ||||||
|  |     this.id = const Value.absent(), | ||||||
|  |     this.alias = const Value.absent(), | ||||||
|  |     this.content = const Value.absent(), | ||||||
|  |     this.createdAt = const Value.absent(), | ||||||
|  |   }); | ||||||
|  |   SnLocalChatChannelCompanion.insert({ | ||||||
|  |     this.id = const Value.absent(), | ||||||
|  |     required String alias, | ||||||
|  |     required SnChannel content, | ||||||
|  |     this.createdAt = const Value.absent(), | ||||||
|  |   })  : alias = Value(alias), | ||||||
|  |         content = Value(content); | ||||||
|  |   static Insertable<SnLocalChatChannelData> custom({ | ||||||
|  |     Expression<int>? id, | ||||||
|  |     Expression<String>? alias, | ||||||
|  |     Expression<String>? content, | ||||||
|  |     Expression<DateTime>? createdAt, | ||||||
|  |   }) { | ||||||
|  |     return RawValuesInsertable({ | ||||||
|  |       if (id != null) 'id': id, | ||||||
|  |       if (alias != null) 'alias': alias, | ||||||
|  |       if (content != null) 'content': content, | ||||||
|  |       if (createdAt != null) 'created_at': createdAt, | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   SnLocalChatChannelCompanion copyWith( | ||||||
|  |       {Value<int>? id, | ||||||
|  |       Value<String>? alias, | ||||||
|  |       Value<SnChannel>? content, | ||||||
|  |       Value<DateTime>? createdAt}) { | ||||||
|  |     return SnLocalChatChannelCompanion( | ||||||
|  |       id: id ?? this.id, | ||||||
|  |       alias: alias ?? this.alias, | ||||||
|  |       content: content ?? this.content, | ||||||
|  |       createdAt: createdAt ?? this.createdAt, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Map<String, Expression> toColumns(bool nullToAbsent) { | ||||||
|  |     final map = <String, Expression>{}; | ||||||
|  |     if (id.present) { | ||||||
|  |       map['id'] = Variable<int>(id.value); | ||||||
|  |     } | ||||||
|  |     if (alias.present) { | ||||||
|  |       map['alias'] = Variable<String>(alias.value); | ||||||
|  |     } | ||||||
|  |     if (content.present) { | ||||||
|  |       map['content'] = Variable<String>( | ||||||
|  |           $SnLocalChatChannelTable.$convertercontent.toSql(content.value)); | ||||||
|  |     } | ||||||
|  |     if (createdAt.present) { | ||||||
|  |       map['created_at'] = Variable<DateTime>(createdAt.value); | ||||||
|  |     } | ||||||
|  |     return map; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String toString() { | ||||||
|  |     return (StringBuffer('SnLocalChatChannelCompanion(') | ||||||
|  |           ..write('id: $id, ') | ||||||
|  |           ..write('alias: $alias, ') | ||||||
|  |           ..write('content: $content, ') | ||||||
|  |           ..write('createdAt: $createdAt') | ||||||
|  |           ..write(')')) | ||||||
|  |         .toString(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class $SnLocalChatMessageTable extends SnLocalChatMessage | ||||||
|  |     with TableInfo<$SnLocalChatMessageTable, SnLocalChatMessageData> { | ||||||
|  |   @override | ||||||
|  |   final GeneratedDatabase attachedDatabase; | ||||||
|  |   final String? _alias; | ||||||
|  |   $SnLocalChatMessageTable(this.attachedDatabase, [this._alias]); | ||||||
|  |   static const VerificationMeta _idMeta = const VerificationMeta('id'); | ||||||
|  |   @override | ||||||
|  |   late final GeneratedColumn<int> id = GeneratedColumn<int>( | ||||||
|  |       'id', aliasedName, false, | ||||||
|  |       hasAutoIncrement: true, | ||||||
|  |       type: DriftSqlType.int, | ||||||
|  |       requiredDuringInsert: false, | ||||||
|  |       defaultConstraints: | ||||||
|  |           GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); | ||||||
|  |   static const VerificationMeta _channelIdMeta = | ||||||
|  |       const VerificationMeta('channelId'); | ||||||
|  |   @override | ||||||
|  |   late final GeneratedColumn<int> channelId = GeneratedColumn<int>( | ||||||
|  |       'channel_id', aliasedName, false, | ||||||
|  |       type: DriftSqlType.int, requiredDuringInsert: true); | ||||||
|  |   static const VerificationMeta _contentMeta = | ||||||
|  |       const VerificationMeta('content'); | ||||||
|  |   @override | ||||||
|  |   late final GeneratedColumnWithTypeConverter<SnChatMessage, String> content = | ||||||
|  |       GeneratedColumn<String>('content', aliasedName, false, | ||||||
|  |               type: DriftSqlType.string, requiredDuringInsert: true) | ||||||
|  |           .withConverter<SnChatMessage>( | ||||||
|  |               $SnLocalChatMessageTable.$convertercontent); | ||||||
|  |   static const VerificationMeta _createdAtMeta = | ||||||
|  |       const VerificationMeta('createdAt'); | ||||||
|  |   @override | ||||||
|  |   late final GeneratedColumn<DateTime> createdAt = GeneratedColumn<DateTime>( | ||||||
|  |       'created_at', aliasedName, false, | ||||||
|  |       type: DriftSqlType.dateTime, | ||||||
|  |       requiredDuringInsert: false, | ||||||
|  |       defaultValue: currentDateAndTime); | ||||||
|  |   @override | ||||||
|  |   List<GeneratedColumn> get $columns => [id, channelId, content, createdAt]; | ||||||
|  |   @override | ||||||
|  |   String get aliasedName => _alias ?? actualTableName; | ||||||
|  |   @override | ||||||
|  |   String get actualTableName => $name; | ||||||
|  |   static const String $name = 'sn_local_chat_message'; | ||||||
|  |   @override | ||||||
|  |   VerificationContext validateIntegrity( | ||||||
|  |       Insertable<SnLocalChatMessageData> instance, | ||||||
|  |       {bool isInserting = false}) { | ||||||
|  |     final context = VerificationContext(); | ||||||
|  |     final data = instance.toColumns(true); | ||||||
|  |     if (data.containsKey('id')) { | ||||||
|  |       context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); | ||||||
|  |     } | ||||||
|  |     if (data.containsKey('channel_id')) { | ||||||
|  |       context.handle(_channelIdMeta, | ||||||
|  |           channelId.isAcceptableOrUnknown(data['channel_id']!, _channelIdMeta)); | ||||||
|  |     } else if (isInserting) { | ||||||
|  |       context.missing(_channelIdMeta); | ||||||
|  |     } | ||||||
|  |     context.handle(_contentMeta, const VerificationResult.success()); | ||||||
|  |     if (data.containsKey('created_at')) { | ||||||
|  |       context.handle(_createdAtMeta, | ||||||
|  |           createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); | ||||||
|  |     } | ||||||
|  |     return context; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Set<GeneratedColumn> get $primaryKey => {id}; | ||||||
|  |   @override | ||||||
|  |   SnLocalChatMessageData map(Map<String, dynamic> data, {String? tablePrefix}) { | ||||||
|  |     final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; | ||||||
|  |     return SnLocalChatMessageData( | ||||||
|  |       id: attachedDatabase.typeMapping | ||||||
|  |           .read(DriftSqlType.int, data['${effectivePrefix}id'])!, | ||||||
|  |       channelId: attachedDatabase.typeMapping | ||||||
|  |           .read(DriftSqlType.int, data['${effectivePrefix}channel_id'])!, | ||||||
|  |       content: $SnLocalChatMessageTable.$convertercontent.fromSql( | ||||||
|  |           attachedDatabase.typeMapping | ||||||
|  |               .read(DriftSqlType.string, data['${effectivePrefix}content'])!), | ||||||
|  |       createdAt: attachedDatabase.typeMapping | ||||||
|  |           .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   $SnLocalChatMessageTable createAlias(String alias) { | ||||||
|  |     return $SnLocalChatMessageTable(attachedDatabase, alias); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static JsonTypeConverter2<SnChatMessage, String, Map<String, Object?>> | ||||||
|  |       $convertercontent = const SnMessageConverter(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class SnLocalChatMessageData extends DataClass | ||||||
|  |     implements Insertable<SnLocalChatMessageData> { | ||||||
|  |   final int id; | ||||||
|  |   final int channelId; | ||||||
|  |   final SnChatMessage content; | ||||||
|  |   final DateTime createdAt; | ||||||
|  |   const SnLocalChatMessageData( | ||||||
|  |       {required this.id, | ||||||
|  |       required this.channelId, | ||||||
|  |       required this.content, | ||||||
|  |       required this.createdAt}); | ||||||
|  |   @override | ||||||
|  |   Map<String, Expression> toColumns(bool nullToAbsent) { | ||||||
|  |     final map = <String, Expression>{}; | ||||||
|  |     map['id'] = Variable<int>(id); | ||||||
|  |     map['channel_id'] = Variable<int>(channelId); | ||||||
|  |     { | ||||||
|  |       map['content'] = Variable<String>( | ||||||
|  |           $SnLocalChatMessageTable.$convertercontent.toSql(content)); | ||||||
|  |     } | ||||||
|  |     map['created_at'] = Variable<DateTime>(createdAt); | ||||||
|  |     return map; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   SnLocalChatMessageCompanion toCompanion(bool nullToAbsent) { | ||||||
|  |     return SnLocalChatMessageCompanion( | ||||||
|  |       id: Value(id), | ||||||
|  |       channelId: Value(channelId), | ||||||
|  |       content: Value(content), | ||||||
|  |       createdAt: Value(createdAt), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   factory SnLocalChatMessageData.fromJson(Map<String, dynamic> json, | ||||||
|  |       {ValueSerializer? serializer}) { | ||||||
|  |     serializer ??= driftRuntimeOptions.defaultSerializer; | ||||||
|  |     return SnLocalChatMessageData( | ||||||
|  |       id: serializer.fromJson<int>(json['id']), | ||||||
|  |       channelId: serializer.fromJson<int>(json['channelId']), | ||||||
|  |       content: $SnLocalChatMessageTable.$convertercontent | ||||||
|  |           .fromJson(serializer.fromJson<Map<String, Object?>>(json['content'])), | ||||||
|  |       createdAt: serializer.fromJson<DateTime>(json['createdAt']), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |   @override | ||||||
|  |   Map<String, dynamic> toJson({ValueSerializer? serializer}) { | ||||||
|  |     serializer ??= driftRuntimeOptions.defaultSerializer; | ||||||
|  |     return <String, dynamic>{ | ||||||
|  |       'id': serializer.toJson<int>(id), | ||||||
|  |       'channelId': serializer.toJson<int>(channelId), | ||||||
|  |       'content': serializer.toJson<Map<String, Object?>>( | ||||||
|  |           $SnLocalChatMessageTable.$convertercontent.toJson(content)), | ||||||
|  |       'createdAt': serializer.toJson<DateTime>(createdAt), | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   SnLocalChatMessageData copyWith( | ||||||
|  |           {int? id, | ||||||
|  |           int? channelId, | ||||||
|  |           SnChatMessage? content, | ||||||
|  |           DateTime? createdAt}) => | ||||||
|  |       SnLocalChatMessageData( | ||||||
|  |         id: id ?? this.id, | ||||||
|  |         channelId: channelId ?? this.channelId, | ||||||
|  |         content: content ?? this.content, | ||||||
|  |         createdAt: createdAt ?? this.createdAt, | ||||||
|  |       ); | ||||||
|  |   SnLocalChatMessageData copyWithCompanion(SnLocalChatMessageCompanion data) { | ||||||
|  |     return SnLocalChatMessageData( | ||||||
|  |       id: data.id.present ? data.id.value : this.id, | ||||||
|  |       channelId: data.channelId.present ? data.channelId.value : this.channelId, | ||||||
|  |       content: data.content.present ? data.content.value : this.content, | ||||||
|  |       createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String toString() { | ||||||
|  |     return (StringBuffer('SnLocalChatMessageData(') | ||||||
|  |           ..write('id: $id, ') | ||||||
|  |           ..write('channelId: $channelId, ') | ||||||
|  |           ..write('content: $content, ') | ||||||
|  |           ..write('createdAt: $createdAt') | ||||||
|  |           ..write(')')) | ||||||
|  |         .toString(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode => Object.hash(id, channelId, content, createdAt); | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) => | ||||||
|  |       identical(this, other) || | ||||||
|  |       (other is SnLocalChatMessageData && | ||||||
|  |           other.id == this.id && | ||||||
|  |           other.channelId == this.channelId && | ||||||
|  |           other.content == this.content && | ||||||
|  |           other.createdAt == this.createdAt); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class SnLocalChatMessageCompanion | ||||||
|  |     extends UpdateCompanion<SnLocalChatMessageData> { | ||||||
|  |   final Value<int> id; | ||||||
|  |   final Value<int> channelId; | ||||||
|  |   final Value<SnChatMessage> content; | ||||||
|  |   final Value<DateTime> createdAt; | ||||||
|  |   const SnLocalChatMessageCompanion({ | ||||||
|  |     this.id = const Value.absent(), | ||||||
|  |     this.channelId = const Value.absent(), | ||||||
|  |     this.content = const Value.absent(), | ||||||
|  |     this.createdAt = const Value.absent(), | ||||||
|  |   }); | ||||||
|  |   SnLocalChatMessageCompanion.insert({ | ||||||
|  |     this.id = const Value.absent(), | ||||||
|  |     required int channelId, | ||||||
|  |     required SnChatMessage content, | ||||||
|  |     this.createdAt = const Value.absent(), | ||||||
|  |   })  : channelId = Value(channelId), | ||||||
|  |         content = Value(content); | ||||||
|  |   static Insertable<SnLocalChatMessageData> custom({ | ||||||
|  |     Expression<int>? id, | ||||||
|  |     Expression<int>? channelId, | ||||||
|  |     Expression<String>? content, | ||||||
|  |     Expression<DateTime>? createdAt, | ||||||
|  |   }) { | ||||||
|  |     return RawValuesInsertable({ | ||||||
|  |       if (id != null) 'id': id, | ||||||
|  |       if (channelId != null) 'channel_id': channelId, | ||||||
|  |       if (content != null) 'content': content, | ||||||
|  |       if (createdAt != null) 'created_at': createdAt, | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   SnLocalChatMessageCompanion copyWith( | ||||||
|  |       {Value<int>? id, | ||||||
|  |       Value<int>? channelId, | ||||||
|  |       Value<SnChatMessage>? content, | ||||||
|  |       Value<DateTime>? createdAt}) { | ||||||
|  |     return SnLocalChatMessageCompanion( | ||||||
|  |       id: id ?? this.id, | ||||||
|  |       channelId: channelId ?? this.channelId, | ||||||
|  |       content: content ?? this.content, | ||||||
|  |       createdAt: createdAt ?? this.createdAt, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Map<String, Expression> toColumns(bool nullToAbsent) { | ||||||
|  |     final map = <String, Expression>{}; | ||||||
|  |     if (id.present) { | ||||||
|  |       map['id'] = Variable<int>(id.value); | ||||||
|  |     } | ||||||
|  |     if (channelId.present) { | ||||||
|  |       map['channel_id'] = Variable<int>(channelId.value); | ||||||
|  |     } | ||||||
|  |     if (content.present) { | ||||||
|  |       map['content'] = Variable<String>( | ||||||
|  |           $SnLocalChatMessageTable.$convertercontent.toSql(content.value)); | ||||||
|  |     } | ||||||
|  |     if (createdAt.present) { | ||||||
|  |       map['created_at'] = Variable<DateTime>(createdAt.value); | ||||||
|  |     } | ||||||
|  |     return map; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String toString() { | ||||||
|  |     return (StringBuffer('SnLocalChatMessageCompanion(') | ||||||
|  |           ..write('id: $id, ') | ||||||
|  |           ..write('channelId: $channelId, ') | ||||||
|  |           ..write('content: $content, ') | ||||||
|  |           ..write('createdAt: $createdAt') | ||||||
|  |           ..write(')')) | ||||||
|  |         .toString(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | abstract class _$AppDatabase extends GeneratedDatabase { | ||||||
|  |   _$AppDatabase(QueryExecutor e) : super(e); | ||||||
|  |   $AppDatabaseManager get managers => $AppDatabaseManager(this); | ||||||
|  |   late final $SnLocalChatChannelTable snLocalChatChannel = | ||||||
|  |       $SnLocalChatChannelTable(this); | ||||||
|  |   late final $SnLocalChatMessageTable snLocalChatMessage = | ||||||
|  |       $SnLocalChatMessageTable(this); | ||||||
|  |   @override | ||||||
|  |   Iterable<TableInfo<Table, Object?>> get allTables => | ||||||
|  |       allSchemaEntities.whereType<TableInfo<Table, Object?>>(); | ||||||
|  |   @override | ||||||
|  |   List<DatabaseSchemaEntity> get allSchemaEntities => | ||||||
|  |       [snLocalChatChannel, snLocalChatMessage]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | typedef $$SnLocalChatChannelTableCreateCompanionBuilder | ||||||
|  |     = SnLocalChatChannelCompanion Function({ | ||||||
|  |   Value<int> id, | ||||||
|  |   required String alias, | ||||||
|  |   required SnChannel content, | ||||||
|  |   Value<DateTime> createdAt, | ||||||
|  | }); | ||||||
|  | typedef $$SnLocalChatChannelTableUpdateCompanionBuilder | ||||||
|  |     = SnLocalChatChannelCompanion Function({ | ||||||
|  |   Value<int> id, | ||||||
|  |   Value<String> alias, | ||||||
|  |   Value<SnChannel> content, | ||||||
|  |   Value<DateTime> createdAt, | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | class $$SnLocalChatChannelTableFilterComposer | ||||||
|  |     extends Composer<_$AppDatabase, $SnLocalChatChannelTable> { | ||||||
|  |   $$SnLocalChatChannelTableFilterComposer({ | ||||||
|  |     required super.$db, | ||||||
|  |     required super.$table, | ||||||
|  |     super.joinBuilder, | ||||||
|  |     super.$addJoinBuilderToRootComposer, | ||||||
|  |     super.$removeJoinBuilderFromRootComposer, | ||||||
|  |   }); | ||||||
|  |   ColumnFilters<int> get id => $composableBuilder( | ||||||
|  |       column: $table.id, builder: (column) => ColumnFilters(column)); | ||||||
|  |  | ||||||
|  |   ColumnFilters<String> get alias => $composableBuilder( | ||||||
|  |       column: $table.alias, builder: (column) => ColumnFilters(column)); | ||||||
|  |  | ||||||
|  |   ColumnWithTypeConverterFilters<SnChannel, SnChannel, String> get content => | ||||||
|  |       $composableBuilder( | ||||||
|  |           column: $table.content, | ||||||
|  |           builder: (column) => ColumnWithTypeConverterFilters(column)); | ||||||
|  |  | ||||||
|  |   ColumnFilters<DateTime> get createdAt => $composableBuilder( | ||||||
|  |       column: $table.createdAt, builder: (column) => ColumnFilters(column)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class $$SnLocalChatChannelTableOrderingComposer | ||||||
|  |     extends Composer<_$AppDatabase, $SnLocalChatChannelTable> { | ||||||
|  |   $$SnLocalChatChannelTableOrderingComposer({ | ||||||
|  |     required super.$db, | ||||||
|  |     required super.$table, | ||||||
|  |     super.joinBuilder, | ||||||
|  |     super.$addJoinBuilderToRootComposer, | ||||||
|  |     super.$removeJoinBuilderFromRootComposer, | ||||||
|  |   }); | ||||||
|  |   ColumnOrderings<int> get id => $composableBuilder( | ||||||
|  |       column: $table.id, builder: (column) => ColumnOrderings(column)); | ||||||
|  |  | ||||||
|  |   ColumnOrderings<String> get alias => $composableBuilder( | ||||||
|  |       column: $table.alias, builder: (column) => ColumnOrderings(column)); | ||||||
|  |  | ||||||
|  |   ColumnOrderings<String> get content => $composableBuilder( | ||||||
|  |       column: $table.content, builder: (column) => ColumnOrderings(column)); | ||||||
|  |  | ||||||
|  |   ColumnOrderings<DateTime> get createdAt => $composableBuilder( | ||||||
|  |       column: $table.createdAt, builder: (column) => ColumnOrderings(column)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class $$SnLocalChatChannelTableAnnotationComposer | ||||||
|  |     extends Composer<_$AppDatabase, $SnLocalChatChannelTable> { | ||||||
|  |   $$SnLocalChatChannelTableAnnotationComposer({ | ||||||
|  |     required super.$db, | ||||||
|  |     required super.$table, | ||||||
|  |     super.joinBuilder, | ||||||
|  |     super.$addJoinBuilderToRootComposer, | ||||||
|  |     super.$removeJoinBuilderFromRootComposer, | ||||||
|  |   }); | ||||||
|  |   GeneratedColumn<int> get id => | ||||||
|  |       $composableBuilder(column: $table.id, builder: (column) => column); | ||||||
|  |  | ||||||
|  |   GeneratedColumn<String> get alias => | ||||||
|  |       $composableBuilder(column: $table.alias, builder: (column) => column); | ||||||
|  |  | ||||||
|  |   GeneratedColumnWithTypeConverter<SnChannel, String> get content => | ||||||
|  |       $composableBuilder(column: $table.content, builder: (column) => column); | ||||||
|  |  | ||||||
|  |   GeneratedColumn<DateTime> get createdAt => | ||||||
|  |       $composableBuilder(column: $table.createdAt, builder: (column) => column); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class $$SnLocalChatChannelTableTableManager extends RootTableManager< | ||||||
|  |     _$AppDatabase, | ||||||
|  |     $SnLocalChatChannelTable, | ||||||
|  |     SnLocalChatChannelData, | ||||||
|  |     $$SnLocalChatChannelTableFilterComposer, | ||||||
|  |     $$SnLocalChatChannelTableOrderingComposer, | ||||||
|  |     $$SnLocalChatChannelTableAnnotationComposer, | ||||||
|  |     $$SnLocalChatChannelTableCreateCompanionBuilder, | ||||||
|  |     $$SnLocalChatChannelTableUpdateCompanionBuilder, | ||||||
|  |     ( | ||||||
|  |       SnLocalChatChannelData, | ||||||
|  |       BaseReferences<_$AppDatabase, $SnLocalChatChannelTable, | ||||||
|  |           SnLocalChatChannelData> | ||||||
|  |     ), | ||||||
|  |     SnLocalChatChannelData, | ||||||
|  |     PrefetchHooks Function()> { | ||||||
|  |   $$SnLocalChatChannelTableTableManager( | ||||||
|  |       _$AppDatabase db, $SnLocalChatChannelTable table) | ||||||
|  |       : super(TableManagerState( | ||||||
|  |           db: db, | ||||||
|  |           table: table, | ||||||
|  |           createFilteringComposer: () => | ||||||
|  |               $$SnLocalChatChannelTableFilterComposer($db: db, $table: table), | ||||||
|  |           createOrderingComposer: () => | ||||||
|  |               $$SnLocalChatChannelTableOrderingComposer($db: db, $table: table), | ||||||
|  |           createComputedFieldComposer: () => | ||||||
|  |               $$SnLocalChatChannelTableAnnotationComposer( | ||||||
|  |                   $db: db, $table: table), | ||||||
|  |           updateCompanionCallback: ({ | ||||||
|  |             Value<int> id = const Value.absent(), | ||||||
|  |             Value<String> alias = const Value.absent(), | ||||||
|  |             Value<SnChannel> content = const Value.absent(), | ||||||
|  |             Value<DateTime> createdAt = const Value.absent(), | ||||||
|  |           }) => | ||||||
|  |               SnLocalChatChannelCompanion( | ||||||
|  |             id: id, | ||||||
|  |             alias: alias, | ||||||
|  |             content: content, | ||||||
|  |             createdAt: createdAt, | ||||||
|  |           ), | ||||||
|  |           createCompanionCallback: ({ | ||||||
|  |             Value<int> id = const Value.absent(), | ||||||
|  |             required String alias, | ||||||
|  |             required SnChannel content, | ||||||
|  |             Value<DateTime> createdAt = const Value.absent(), | ||||||
|  |           }) => | ||||||
|  |               SnLocalChatChannelCompanion.insert( | ||||||
|  |             id: id, | ||||||
|  |             alias: alias, | ||||||
|  |             content: content, | ||||||
|  |             createdAt: createdAt, | ||||||
|  |           ), | ||||||
|  |           withReferenceMapper: (p0) => p0 | ||||||
|  |               .map((e) => (e.readTable(table), BaseReferences(db, table, e))) | ||||||
|  |               .toList(), | ||||||
|  |           prefetchHooksCallback: null, | ||||||
|  |         )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | typedef $$SnLocalChatChannelTableProcessedTableManager = ProcessedTableManager< | ||||||
|  |     _$AppDatabase, | ||||||
|  |     $SnLocalChatChannelTable, | ||||||
|  |     SnLocalChatChannelData, | ||||||
|  |     $$SnLocalChatChannelTableFilterComposer, | ||||||
|  |     $$SnLocalChatChannelTableOrderingComposer, | ||||||
|  |     $$SnLocalChatChannelTableAnnotationComposer, | ||||||
|  |     $$SnLocalChatChannelTableCreateCompanionBuilder, | ||||||
|  |     $$SnLocalChatChannelTableUpdateCompanionBuilder, | ||||||
|  |     ( | ||||||
|  |       SnLocalChatChannelData, | ||||||
|  |       BaseReferences<_$AppDatabase, $SnLocalChatChannelTable, | ||||||
|  |           SnLocalChatChannelData> | ||||||
|  |     ), | ||||||
|  |     SnLocalChatChannelData, | ||||||
|  |     PrefetchHooks Function()>; | ||||||
|  | typedef $$SnLocalChatMessageTableCreateCompanionBuilder | ||||||
|  |     = SnLocalChatMessageCompanion Function({ | ||||||
|  |   Value<int> id, | ||||||
|  |   required int channelId, | ||||||
|  |   required SnChatMessage content, | ||||||
|  |   Value<DateTime> createdAt, | ||||||
|  | }); | ||||||
|  | typedef $$SnLocalChatMessageTableUpdateCompanionBuilder | ||||||
|  |     = SnLocalChatMessageCompanion Function({ | ||||||
|  |   Value<int> id, | ||||||
|  |   Value<int> channelId, | ||||||
|  |   Value<SnChatMessage> content, | ||||||
|  |   Value<DateTime> createdAt, | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | class $$SnLocalChatMessageTableFilterComposer | ||||||
|  |     extends Composer<_$AppDatabase, $SnLocalChatMessageTable> { | ||||||
|  |   $$SnLocalChatMessageTableFilterComposer({ | ||||||
|  |     required super.$db, | ||||||
|  |     required super.$table, | ||||||
|  |     super.joinBuilder, | ||||||
|  |     super.$addJoinBuilderToRootComposer, | ||||||
|  |     super.$removeJoinBuilderFromRootComposer, | ||||||
|  |   }); | ||||||
|  |   ColumnFilters<int> get id => $composableBuilder( | ||||||
|  |       column: $table.id, builder: (column) => ColumnFilters(column)); | ||||||
|  |  | ||||||
|  |   ColumnFilters<int> get channelId => $composableBuilder( | ||||||
|  |       column: $table.channelId, builder: (column) => ColumnFilters(column)); | ||||||
|  |  | ||||||
|  |   ColumnWithTypeConverterFilters<SnChatMessage, SnChatMessage, String> | ||||||
|  |       get content => $composableBuilder( | ||||||
|  |           column: $table.content, | ||||||
|  |           builder: (column) => ColumnWithTypeConverterFilters(column)); | ||||||
|  |  | ||||||
|  |   ColumnFilters<DateTime> get createdAt => $composableBuilder( | ||||||
|  |       column: $table.createdAt, builder: (column) => ColumnFilters(column)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class $$SnLocalChatMessageTableOrderingComposer | ||||||
|  |     extends Composer<_$AppDatabase, $SnLocalChatMessageTable> { | ||||||
|  |   $$SnLocalChatMessageTableOrderingComposer({ | ||||||
|  |     required super.$db, | ||||||
|  |     required super.$table, | ||||||
|  |     super.joinBuilder, | ||||||
|  |     super.$addJoinBuilderToRootComposer, | ||||||
|  |     super.$removeJoinBuilderFromRootComposer, | ||||||
|  |   }); | ||||||
|  |   ColumnOrderings<int> get id => $composableBuilder( | ||||||
|  |       column: $table.id, builder: (column) => ColumnOrderings(column)); | ||||||
|  |  | ||||||
|  |   ColumnOrderings<int> get channelId => $composableBuilder( | ||||||
|  |       column: $table.channelId, builder: (column) => ColumnOrderings(column)); | ||||||
|  |  | ||||||
|  |   ColumnOrderings<String> get content => $composableBuilder( | ||||||
|  |       column: $table.content, builder: (column) => ColumnOrderings(column)); | ||||||
|  |  | ||||||
|  |   ColumnOrderings<DateTime> get createdAt => $composableBuilder( | ||||||
|  |       column: $table.createdAt, builder: (column) => ColumnOrderings(column)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class $$SnLocalChatMessageTableAnnotationComposer | ||||||
|  |     extends Composer<_$AppDatabase, $SnLocalChatMessageTable> { | ||||||
|  |   $$SnLocalChatMessageTableAnnotationComposer({ | ||||||
|  |     required super.$db, | ||||||
|  |     required super.$table, | ||||||
|  |     super.joinBuilder, | ||||||
|  |     super.$addJoinBuilderToRootComposer, | ||||||
|  |     super.$removeJoinBuilderFromRootComposer, | ||||||
|  |   }); | ||||||
|  |   GeneratedColumn<int> get id => | ||||||
|  |       $composableBuilder(column: $table.id, builder: (column) => column); | ||||||
|  |  | ||||||
|  |   GeneratedColumn<int> get channelId => | ||||||
|  |       $composableBuilder(column: $table.channelId, builder: (column) => column); | ||||||
|  |  | ||||||
|  |   GeneratedColumnWithTypeConverter<SnChatMessage, String> get content => | ||||||
|  |       $composableBuilder(column: $table.content, builder: (column) => column); | ||||||
|  |  | ||||||
|  |   GeneratedColumn<DateTime> get createdAt => | ||||||
|  |       $composableBuilder(column: $table.createdAt, builder: (column) => column); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class $$SnLocalChatMessageTableTableManager extends RootTableManager< | ||||||
|  |     _$AppDatabase, | ||||||
|  |     $SnLocalChatMessageTable, | ||||||
|  |     SnLocalChatMessageData, | ||||||
|  |     $$SnLocalChatMessageTableFilterComposer, | ||||||
|  |     $$SnLocalChatMessageTableOrderingComposer, | ||||||
|  |     $$SnLocalChatMessageTableAnnotationComposer, | ||||||
|  |     $$SnLocalChatMessageTableCreateCompanionBuilder, | ||||||
|  |     $$SnLocalChatMessageTableUpdateCompanionBuilder, | ||||||
|  |     ( | ||||||
|  |       SnLocalChatMessageData, | ||||||
|  |       BaseReferences<_$AppDatabase, $SnLocalChatMessageTable, | ||||||
|  |           SnLocalChatMessageData> | ||||||
|  |     ), | ||||||
|  |     SnLocalChatMessageData, | ||||||
|  |     PrefetchHooks Function()> { | ||||||
|  |   $$SnLocalChatMessageTableTableManager( | ||||||
|  |       _$AppDatabase db, $SnLocalChatMessageTable table) | ||||||
|  |       : super(TableManagerState( | ||||||
|  |           db: db, | ||||||
|  |           table: table, | ||||||
|  |           createFilteringComposer: () => | ||||||
|  |               $$SnLocalChatMessageTableFilterComposer($db: db, $table: table), | ||||||
|  |           createOrderingComposer: () => | ||||||
|  |               $$SnLocalChatMessageTableOrderingComposer($db: db, $table: table), | ||||||
|  |           createComputedFieldComposer: () => | ||||||
|  |               $$SnLocalChatMessageTableAnnotationComposer( | ||||||
|  |                   $db: db, $table: table), | ||||||
|  |           updateCompanionCallback: ({ | ||||||
|  |             Value<int> id = const Value.absent(), | ||||||
|  |             Value<int> channelId = const Value.absent(), | ||||||
|  |             Value<SnChatMessage> content = const Value.absent(), | ||||||
|  |             Value<DateTime> createdAt = const Value.absent(), | ||||||
|  |           }) => | ||||||
|  |               SnLocalChatMessageCompanion( | ||||||
|  |             id: id, | ||||||
|  |             channelId: channelId, | ||||||
|  |             content: content, | ||||||
|  |             createdAt: createdAt, | ||||||
|  |           ), | ||||||
|  |           createCompanionCallback: ({ | ||||||
|  |             Value<int> id = const Value.absent(), | ||||||
|  |             required int channelId, | ||||||
|  |             required SnChatMessage content, | ||||||
|  |             Value<DateTime> createdAt = const Value.absent(), | ||||||
|  |           }) => | ||||||
|  |               SnLocalChatMessageCompanion.insert( | ||||||
|  |             id: id, | ||||||
|  |             channelId: channelId, | ||||||
|  |             content: content, | ||||||
|  |             createdAt: createdAt, | ||||||
|  |           ), | ||||||
|  |           withReferenceMapper: (p0) => p0 | ||||||
|  |               .map((e) => (e.readTable(table), BaseReferences(db, table, e))) | ||||||
|  |               .toList(), | ||||||
|  |           prefetchHooksCallback: null, | ||||||
|  |         )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | typedef $$SnLocalChatMessageTableProcessedTableManager = ProcessedTableManager< | ||||||
|  |     _$AppDatabase, | ||||||
|  |     $SnLocalChatMessageTable, | ||||||
|  |     SnLocalChatMessageData, | ||||||
|  |     $$SnLocalChatMessageTableFilterComposer, | ||||||
|  |     $$SnLocalChatMessageTableOrderingComposer, | ||||||
|  |     $$SnLocalChatMessageTableAnnotationComposer, | ||||||
|  |     $$SnLocalChatMessageTableCreateCompanionBuilder, | ||||||
|  |     $$SnLocalChatMessageTableUpdateCompanionBuilder, | ||||||
|  |     ( | ||||||
|  |       SnLocalChatMessageData, | ||||||
|  |       BaseReferences<_$AppDatabase, $SnLocalChatMessageTable, | ||||||
|  |           SnLocalChatMessageData> | ||||||
|  |     ), | ||||||
|  |     SnLocalChatMessageData, | ||||||
|  |     PrefetchHooks Function()>; | ||||||
|  |  | ||||||
|  | class $AppDatabaseManager { | ||||||
|  |   final _$AppDatabase _db; | ||||||
|  |   $AppDatabaseManager(this._db); | ||||||
|  |   $$SnLocalChatChannelTableTableManager get snLocalChatChannel => | ||||||
|  |       $$SnLocalChatChannelTableTableManager(_db, _db.snLocalChatChannel); | ||||||
|  |   $$SnLocalChatMessageTableTableManager get snLocalChatMessage => | ||||||
|  |       $$SnLocalChatMessageTableTableManager(_db, _db.snLocalChatMessage); | ||||||
|  | } | ||||||
							
								
								
									
										8
									
								
								lib/database/drift_worker.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								lib/database/drift_worker.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | import 'package:drift/wasm.dart'; | ||||||
|  |  | ||||||
|  | // Use `dart compile js -O4 ./drift_worker.dart` to compile this file. | ||||||
|  | // And place it in the web/ directory. | ||||||
|  |  | ||||||
|  | // When compiled with dart2js, this file defines a dedicated or shared web | ||||||
|  | // worker used by drift. | ||||||
|  | void main() => WasmDatabase.workerMainForOpen(); | ||||||
| @@ -13,7 +13,6 @@ import 'package:flutter/foundation.dart'; | |||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:flutter/services.dart'; | import 'package:flutter/services.dart'; | ||||||
| import 'package:go_router/go_router.dart'; | import 'package:go_router/go_router.dart'; | ||||||
| import 'package:hive_flutter/hive_flutter.dart'; |  | ||||||
| import 'package:hotkey_manager/hotkey_manager.dart'; | import 'package:hotkey_manager/hotkey_manager.dart'; | ||||||
| import 'package:package_info_plus/package_info_plus.dart'; | import 'package:package_info_plus/package_info_plus.dart'; | ||||||
| import 'package:provider/provider.dart'; | import 'package:provider/provider.dart'; | ||||||
| @@ -24,6 +23,7 @@ import 'package:surface/firebase_options.dart'; | |||||||
| import 'package:surface/providers/channel.dart'; | import 'package:surface/providers/channel.dart'; | ||||||
| import 'package:surface/providers/chat_call.dart'; | import 'package:surface/providers/chat_call.dart'; | ||||||
| import 'package:surface/providers/config.dart'; | import 'package:surface/providers/config.dart'; | ||||||
|  | import 'package:surface/providers/database.dart'; | ||||||
| import 'package:surface/providers/link_preview.dart'; | import 'package:surface/providers/link_preview.dart'; | ||||||
| import 'package:surface/providers/navigation.dart'; | import 'package:surface/providers/navigation.dart'; | ||||||
| import 'package:surface/providers/notification.dart'; | import 'package:surface/providers/notification.dart'; | ||||||
| @@ -31,6 +31,7 @@ import 'package:surface/providers/post.dart'; | |||||||
| import 'package:surface/providers/relationship.dart'; | import 'package:surface/providers/relationship.dart'; | ||||||
| import 'package:surface/providers/sn_attachment.dart'; | import 'package:surface/providers/sn_attachment.dart'; | ||||||
| import 'package:surface/providers/sn_network.dart'; | import 'package:surface/providers/sn_network.dart'; | ||||||
|  | import 'package:surface/providers/sn_realm.dart'; | ||||||
| import 'package:surface/providers/sn_sticker.dart'; | import 'package:surface/providers/sn_sticker.dart'; | ||||||
| import 'package:surface/providers/special_day.dart'; | import 'package:surface/providers/special_day.dart'; | ||||||
| import 'package:surface/providers/theme.dart'; | import 'package:surface/providers/theme.dart'; | ||||||
| @@ -39,8 +40,6 @@ import 'package:surface/providers/userinfo.dart'; | |||||||
| import 'package:surface/providers/websocket.dart'; | import 'package:surface/providers/websocket.dart'; | ||||||
| import 'package:surface/providers/widget.dart'; | import 'package:surface/providers/widget.dart'; | ||||||
| import 'package:surface/router.dart'; | import 'package:surface/router.dart'; | ||||||
| import 'package:surface/types/chat.dart'; |  | ||||||
| import 'package:surface/types/realm.dart'; |  | ||||||
| import 'package:flutter_web_plugins/url_strategy.dart' show usePathUrlStrategy; | import 'package:flutter_web_plugins/url_strategy.dart' show usePathUrlStrategy; | ||||||
| import 'package:surface/widgets/dialog.dart'; | import 'package:surface/widgets/dialog.dart'; | ||||||
| import 'package:tray_manager/tray_manager.dart'; | import 'package:tray_manager/tray_manager.dart'; | ||||||
| @@ -49,6 +48,7 @@ import 'package:workmanager/workmanager.dart'; | |||||||
| import 'package:in_app_review/in_app_review.dart'; | import 'package:in_app_review/in_app_review.dart'; | ||||||
| import 'package:image_picker_android/image_picker_android.dart'; | import 'package:image_picker_android/image_picker_android.dart'; | ||||||
| import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; | import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; | ||||||
|  | import 'package:local_notifier/local_notifier.dart'; | ||||||
|  |  | ||||||
| @pragma('vm:entry-point') | @pragma('vm:entry-point') | ||||||
| void appBackgroundDispatcher() { | void appBackgroundDispatcher() { | ||||||
| @@ -81,13 +81,7 @@ void main() async { | |||||||
|  |  | ||||||
|   await EasyLocalization.ensureInitialized(); |   await EasyLocalization.ensureInitialized(); | ||||||
|  |  | ||||||
|   await Hive.initFlutter(); |   if (!kIsWeb && !Platform.isLinux) { | ||||||
|   Hive.registerAdapter(SnChannelImplAdapter()); |  | ||||||
|   Hive.registerAdapter(SnRealmImplAdapter()); |  | ||||||
|   Hive.registerAdapter(SnChannelMemberImplAdapter()); |  | ||||||
|   Hive.registerAdapter(SnChatMessageImplAdapter()); |  | ||||||
|  |  | ||||||
|   if (kIsWeb && !Platform.isLinux) { |  | ||||||
|     await Firebase.initializeApp( |     await Firebase.initializeApp( | ||||||
|       options: DefaultFirebaseOptions.currentPlatform, |       options: DefaultFirebaseOptions.currentPlatform, | ||||||
|     ); |     ); | ||||||
| @@ -113,7 +107,8 @@ void main() async { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (!kIsWeb && Platform.isAndroid) { |   if (!kIsWeb && Platform.isAndroid) { | ||||||
|     final ImagePickerPlatform imagePickerImplementation = ImagePickerPlatform.instance; |     final ImagePickerPlatform imagePickerImplementation = | ||||||
|  |         ImagePickerPlatform.instance; | ||||||
|     if (imagePickerImplementation is ImagePickerAndroid) { |     if (imagePickerImplementation is ImagePickerAndroid) { | ||||||
|       imagePickerImplementation.useAndroidPhotoPicker = true; |       imagePickerImplementation.useAndroidPhotoPicker = true; | ||||||
|     } |     } | ||||||
| @@ -141,6 +136,9 @@ class SolianApp extends StatelessWidget { | |||||||
|         assetLoader: JsonAssetLoader(), |         assetLoader: JsonAssetLoader(), | ||||||
|         child: MultiProvider( |         child: MultiProvider( | ||||||
|           providers: [ |           providers: [ | ||||||
|  |             // Infrastructure layer | ||||||
|  |             Provider(create: (ctx) => DatabaseProvider(ctx)), | ||||||
|  |  | ||||||
|             // System extensions layer |             // System extensions layer | ||||||
|             Provider(create: (ctx) => HomeWidgetProvider(ctx)), |             Provider(create: (ctx) => HomeWidgetProvider(ctx)), | ||||||
|  |  | ||||||
| @@ -155,6 +153,7 @@ class SolianApp extends StatelessWidget { | |||||||
|             Provider(create: (ctx) => SnNetworkProvider(ctx)), |             Provider(create: (ctx) => SnNetworkProvider(ctx)), | ||||||
|             Provider(create: (ctx) => UserDirectoryProvider(ctx)), |             Provider(create: (ctx) => UserDirectoryProvider(ctx)), | ||||||
|             Provider(create: (ctx) => SnAttachmentProvider(ctx)), |             Provider(create: (ctx) => SnAttachmentProvider(ctx)), | ||||||
|  |             Provider(create: (ctx) => SnRealmProvider(ctx)), | ||||||
|             Provider(create: (ctx) => SnPostContentProvider(ctx)), |             Provider(create: (ctx) => SnPostContentProvider(ctx)), | ||||||
|             Provider(create: (ctx) => SnRelationshipProvider(ctx)), |             Provider(create: (ctx) => SnRelationshipProvider(ctx)), | ||||||
|             Provider(create: (ctx) => SnLinkPreviewProvider(ctx)), |             Provider(create: (ctx) => SnLinkPreviewProvider(ctx)), | ||||||
| @@ -228,7 +227,8 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener { | |||||||
|     if (prefs.containsKey('first_boot_time')) { |     if (prefs.containsKey('first_boot_time')) { | ||||||
|       final rawTime = prefs.getString('first_boot_time'); |       final rawTime = prefs.getString('first_boot_time'); | ||||||
|       final time = DateTime.tryParse(rawTime ?? ''); |       final time = DateTime.tryParse(rawTime ?? ''); | ||||||
|       if (time != null && time.isBefore(DateTime.now().subtract(const Duration(days: 3)))) { |       if (time != null && | ||||||
|  |           time.isBefore(DateTime.now().subtract(const Duration(days: 3)))) { | ||||||
|         final inAppReview = InAppReview.instance; |         final inAppReview = InAppReview.instance; | ||||||
|         if (prefs.getBool('rating_requested') == true) return; |         if (prefs.getBool('rating_requested') == true) return; | ||||||
|         if (await inAppReview.isAvailable()) { |         if (await inAppReview.isAvailable()) { | ||||||
| @@ -256,13 +256,18 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener { | |||||||
|       ).get( |       ).get( | ||||||
|         'https://git.solsynth.dev/api/v1/repos/HyperNet/Surface/tags?page=1&limit=1', |         'https://git.solsynth.dev/api/v1/repos/HyperNet/Surface/tags?page=1&limit=1', | ||||||
|       ); |       ); | ||||||
|       final remoteVersionString = (resp.data as List).firstOrNull?['name'] ?? '0.0.0+0'; |       final remoteVersionString = | ||||||
|  |           (resp.data as List).firstOrNull?['name'] ?? '0.0.0+0'; | ||||||
|       final remoteVersion = Version.parse(remoteVersionString.split('+').first); |       final remoteVersion = Version.parse(remoteVersionString.split('+').first); | ||||||
|       final localVersion = Version.parse(localVersionString.split('+').first); |       final localVersion = Version.parse(localVersionString.split('+').first); | ||||||
|       final remoteBuildNumber = int.tryParse(remoteVersionString.split('+').last) ?? 0; |       final remoteBuildNumber = | ||||||
|       final localBuildNumber = int.tryParse(localVersionString.split('+').last) ?? 0; |           int.tryParse(remoteVersionString.split('+').last) ?? 0; | ||||||
|  |       final localBuildNumber = | ||||||
|  |           int.tryParse(localVersionString.split('+').last) ?? 0; | ||||||
|       log("[Update] Local: $localVersionString, Remote: $remoteVersionString"); |       log("[Update] Local: $localVersionString, Remote: $remoteVersionString"); | ||||||
|       if ((remoteVersion > localVersion || remoteBuildNumber > localBuildNumber) && mounted) { |       if ((remoteVersion > localVersion || | ||||||
|  |               remoteBuildNumber > localBuildNumber) && | ||||||
|  |           mounted) { | ||||||
|         final config = context.read<ConfigProvider>(); |         final config = context.read<ConfigProvider>(); | ||||||
|         config.setUpdate(remoteVersionString); |         config.setUpdate(remoteVersionString); | ||||||
|         log("[Update] Update available: $remoteVersionString"); |         log("[Update] Update available: $remoteVersionString"); | ||||||
| @@ -299,7 +304,8 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener { | |||||||
|       await notify.registerPushNotifications(); |       await notify.registerPushNotifications(); | ||||||
|       if (!mounted) return; |       if (!mounted) return; | ||||||
|       final sticker = context.read<SnStickerProvider>(); |       final sticker = context.read<SnStickerProvider>(); | ||||||
|       await sticker.listStickerEagerly(); |       await sticker.listSticker(); | ||||||
|  |       log('[Bootstrap] Everything initialized!'); | ||||||
|     } catch (err) { |     } catch (err) { | ||||||
|       if (!mounted) return; |       if (!mounted) return; | ||||||
|       await context.showErrorDialog(err); |       await context.showErrorDialog(err); | ||||||
| @@ -329,7 +335,9 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener { | |||||||
|   Future<void> _trayInitialization() async { |   Future<void> _trayInitialization() async { | ||||||
|     if (kIsWeb || Platform.isAndroid || Platform.isIOS) return; |     if (kIsWeb || Platform.isAndroid || Platform.isIOS) return; | ||||||
|  |  | ||||||
|     final icon = Platform.isWindows ? 'assets/icon/tray-icon.ico' : 'assets/icon/tray-icon.png'; |     final icon = Platform.isWindows | ||||||
|  |         ? 'assets/icon/tray-icon.ico' | ||||||
|  |         : 'assets/icon/tray-icon.png'; | ||||||
|     final appVersion = await PackageInfo.fromPlatform(); |     final appVersion = await PackageInfo.fromPlatform(); | ||||||
|  |  | ||||||
|     trayManager.addListener(this); |     trayManager.addListener(this); | ||||||
| @@ -343,6 +351,10 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener { | |||||||
|           disabled: true, |           disabled: true, | ||||||
|         ), |         ), | ||||||
|         MenuItem.separator(), |         MenuItem.separator(), | ||||||
|  |         MenuItem( | ||||||
|  |           key: 'window_show', | ||||||
|  |           label: 'trayMenuShow'.tr(), | ||||||
|  |         ), | ||||||
|         MenuItem( |         MenuItem( | ||||||
|           key: 'exit', |           key: 'exit', | ||||||
|           label: 'trayMenuExit'.tr(), |           label: 'trayMenuExit'.tr(), | ||||||
| @@ -352,6 +364,15 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener { | |||||||
|     await trayManager.setContextMenu(menu); |     await trayManager.setContextMenu(menu); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   Future<void> _notifyInitialization() async { | ||||||
|  |     if (kIsWeb || Platform.isAndroid || Platform.isIOS) return; | ||||||
|  |  | ||||||
|  |     await localNotifier.setup( | ||||||
|  |       appName: 'solian', | ||||||
|  |       shortcutPolicy: ShortcutPolicy.requireCreate, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   AppLifecycleListener? _appLifecycleListener; |   AppLifecycleListener? _appLifecycleListener; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
| @@ -366,6 +387,7 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener { | |||||||
|  |  | ||||||
|     _trayInitialization(); |     _trayInitialization(); | ||||||
|     _hotkeyInitialization(); |     _hotkeyInitialization(); | ||||||
|  |     _notifyInitialization(); | ||||||
|     _initialize().then((_) { |     _initialize().then((_) { | ||||||
|       _postInitialization(); |       _postInitialization(); | ||||||
|       _tryRequestRating(); |       _tryRequestRating(); | ||||||
| @@ -401,6 +423,9 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener { | |||||||
|   @override |   @override | ||||||
|   void onTrayMenuItemClick(MenuItem menuItem) { |   void onTrayMenuItemClick(MenuItem menuItem) { | ||||||
|     switch (menuItem.key) { |     switch (menuItem.key) { | ||||||
|  |       case 'window_show': | ||||||
|  |         appWindow.show(); | ||||||
|  |         break; | ||||||
|       case 'exit': |       case 'exit': | ||||||
|         _appLifecycleListener?.dispose(); |         _appLifecycleListener?.dispose(); | ||||||
|         SystemChannels.platform.invokeMethod('SystemNavigator.pop'); |         SystemChannels.platform.invokeMethod('SystemNavigator.pop'); | ||||||
| @@ -427,8 +452,16 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener { | |||||||
|         }); |         }); | ||||||
|         return false; |         return false; | ||||||
|       }, |       }, | ||||||
|       child: SizeChangedLayoutNotifier( |       child: OrientationBuilder( | ||||||
|  |         builder: (context, orientation) { | ||||||
|  |           final cfg = context.read<ConfigProvider>(); | ||||||
|  |           WidgetsBinding.instance.addPostFrameCallback((_) { | ||||||
|  |             cfg.calcDrawerSize(context); | ||||||
|  |           }); | ||||||
|  |           return SizeChangedLayoutNotifier( | ||||||
|             child: widget.child, |             child: widget.child, | ||||||
|  |           ); | ||||||
|  |         }, | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -1,48 +1,54 @@ | |||||||
|  | import 'dart:convert'; | ||||||
|  |  | ||||||
|  | import 'package:drift/drift.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:hive_flutter/hive_flutter.dart'; |  | ||||||
| import 'package:provider/provider.dart'; | import 'package:provider/provider.dart'; | ||||||
| import 'package:surface/controllers/chat_message_controller.dart'; | import 'package:surface/database/database.dart'; | ||||||
|  | import 'package:surface/providers/database.dart'; | ||||||
| import 'package:surface/providers/sn_network.dart'; | import 'package:surface/providers/sn_network.dart'; | ||||||
|  | import 'package:surface/providers/sn_realm.dart'; | ||||||
| import 'package:surface/providers/user_directory.dart'; | import 'package:surface/providers/user_directory.dart'; | ||||||
| import 'package:surface/types/chat.dart'; | import 'package:surface/types/chat.dart'; | ||||||
| import 'package:surface/types/realm.dart'; |  | ||||||
|  |  | ||||||
| class ChatChannelProvider extends ChangeNotifier { | class ChatChannelProvider extends ChangeNotifier { | ||||||
|   static const kChatChannelBoxName = 'nex_chat_channels'; |   static const kChatChannelBoxName = 'nex_chat_channels'; | ||||||
|  |  | ||||||
|   late final SnNetworkProvider _sn; |   late final SnNetworkProvider _sn; | ||||||
|   late final UserDirectoryProvider _ud; |   late final UserDirectoryProvider _ud; | ||||||
|  |   late final DatabaseProvider _dt; | ||||||
|   Box<SnChannel>? get _channelBox => Hive.box<SnChannel>(kChatChannelBoxName); |   late final SnRealmProvider _rels; | ||||||
|  |  | ||||||
|   ChatChannelProvider(BuildContext context) { |   ChatChannelProvider(BuildContext context) { | ||||||
|     _sn = context.read<SnNetworkProvider>(); |     _sn = context.read<SnNetworkProvider>(); | ||||||
|     _ud = context.read<UserDirectoryProvider>(); |     _ud = context.read<UserDirectoryProvider>(); | ||||||
|     _initializeLocalData(); |     _dt = context.read<DatabaseProvider>(); | ||||||
|   } |     _rels = context.read<SnRealmProvider>(); | ||||||
|  |  | ||||||
|   Future<void> _initializeLocalData() async { |  | ||||||
|     await Hive.openBox<SnChannel>(kChatChannelBoxName); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   Future<void> _saveChannelToLocal(Iterable<SnChannel> channels) async { |   Future<void> _saveChannelToLocal(Iterable<SnChannel> channels) async { | ||||||
|     if (_channelBox == null) return; |     await Future.wait( | ||||||
|     await _channelBox!.putAll({ |       channels.map( | ||||||
|       for (final channel in channels) channel.key: channel, |         (ele) => _dt.db.snLocalChatChannel.insertOne( | ||||||
|     }); |           SnLocalChatChannelCompanion.insert( | ||||||
|  |             id: Value(ele.id), | ||||||
|  |             alias: ele.key, | ||||||
|  |             content: ele, | ||||||
|  |             createdAt: Value(ele.createdAt), | ||||||
|  |           ), | ||||||
|  |           onConflict: DoUpdate( | ||||||
|  |             (_) => SnLocalChatChannelCompanion.custom( | ||||||
|  |               content: Constant(jsonEncode(ele.toJson())), | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   Future<List<SnChannel>> _fetchChannelsFromServer({ |   Future<List<SnChannel>> _fetchChannelsFromServer({ | ||||||
|     String scope = 'global', |  | ||||||
|     bool direct = false, |  | ||||||
|     bool doNotSave = false, |     bool doNotSave = false, | ||||||
|   }) async { |   }) async { | ||||||
|     final resp = await _sn.client.get( |     final resp = await _sn.client.get('/cgi/im/channels/me/available'); | ||||||
|       '/cgi/im/channels/$scope/me/available', |  | ||||||
|       queryParameters: { |  | ||||||
|         'direct': direct, |  | ||||||
|       }, |  | ||||||
|     ); |  | ||||||
|     final out = List<SnChannel>.from( |     final out = List<SnChannel>.from( | ||||||
|       resp.data?.map((e) => SnChannel.fromJson(e)) ?? [], |       resp.data?.map((e) => SnChannel.fromJson(e)) ?? [], | ||||||
|     ); |     ); | ||||||
| @@ -54,18 +60,21 @@ class ChatChannelProvider extends ChangeNotifier { | |||||||
|   /// It will use the local storage as much as possible. |   /// It will use the local storage as much as possible. | ||||||
|   /// The alias should include the scope, formatted as `scope:alias`. |   /// The alias should include the scope, formatted as `scope:alias`. | ||||||
|   Future<SnChannel> getChannel(String key) async { |   Future<SnChannel> getChannel(String key) async { | ||||||
|     if (_channelBox != null) { |     final local = await (_dt.db.snLocalChatChannel.select() | ||||||
|       final local = _channelBox!.get(key); |           ..where((e) => e.alias.equals(key))) | ||||||
|       if (local != null) return local; |         .getSingleOrNull(); | ||||||
|  |     if (local != null) { | ||||||
|  |       final out = local.content; | ||||||
|  |       return out.copyWith(realm: await _rels.getRealm(out.realmId!)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     var resp = await _sn.client.get('/cgi/im/channels/$key'); |     var resp = | ||||||
|  |         await _sn.client.get('/cgi/im/channels/${key.replaceAll(':', '/')}'); | ||||||
|     var out = SnChannel.fromJson(resp.data); |     var out = SnChannel.fromJson(resp.data); | ||||||
|  |  | ||||||
|     // Preload realm of the channel |     // Preload realm of the channel | ||||||
|     if (out.realmId != null) { |     if (out.realmId != null) { | ||||||
|       resp = await _sn.client.get('/cgi/id/realms/${out.realmId}'); |       out = out.copyWith(realm: await _rels.getRealm(out.realmId!)); | ||||||
|       out = out.copyWith(realm: SnRealm.fromJson(resp.data)); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     _saveChannelToLocal([out]); |     _saveChannelToLocal([out]); | ||||||
| @@ -77,42 +86,39 @@ class ChatChannelProvider extends ChangeNotifier { | |||||||
|   /// And the second time is when the data was fetched from the server. |   /// And the second time is when the data was fetched from the server. | ||||||
|   /// But there is some exception that will only cause one of them to be emitted. |   /// But there is some exception that will only cause one of them to be emitted. | ||||||
|   /// Like the local storage is broken or the server is down. |   /// Like the local storage is broken or the server is down. | ||||||
|   Stream<List<SnChannel>> fetchChannels() async* { |   Stream<List<SnChannel>> fetchChannels( | ||||||
|     if (_channelBox != null) yield _channelBox!.values.toList(); |       {bool noRemote = false, bool noLocal = false}) async* { | ||||||
|  |     if (!noLocal) { | ||||||
|     var resp = await _sn.client.get('/cgi/id/realms/me/available'); |       final local = await (_dt.db.snLocalChatChannel.select() | ||||||
|     final realms = List<SnRealm>.from( |             ..orderBy([ | ||||||
|       resp.data?.map((e) => SnRealm.fromJson(e)) ?? [], |               (e) => | ||||||
|  |                   OrderingTerm(expression: e.createdAt, mode: OrderingMode.desc) | ||||||
|  |             ])) | ||||||
|  |           .get(); | ||||||
|  |       final out = local.map((e) => e.content).toList(); | ||||||
|  |       for (var idx = 0; idx < out.length; idx++) { | ||||||
|  |         final channel = out[idx]; | ||||||
|  |         if (channel.realmId != null) { | ||||||
|  |           out[idx] = out[idx].copyWith( | ||||||
|  |             realm: await _rels.getRealm(channel.realmId!), | ||||||
|           ); |           ); | ||||||
|     final realmMap = { |  | ||||||
|       for (final realm in realms) realm.alias: realm, |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     final scopeToFetch = {'global', ...realms.map((e) => e.alias)}; |  | ||||||
|  |  | ||||||
|     final List<SnChannel> result = List.empty(growable: true); |  | ||||||
|     final directMessages = await _fetchChannelsFromServer( |  | ||||||
|       scope: scopeToFetch.first, |  | ||||||
|       direct: true, |  | ||||||
|     ); |  | ||||||
|     result.addAll(directMessages); |  | ||||||
|  |  | ||||||
|     final nonBelongsChannels = await _fetchChannelsFromServer( |  | ||||||
|       scope: scopeToFetch.first, |  | ||||||
|       direct: false, |  | ||||||
|     ); |  | ||||||
|     result.addAll(nonBelongsChannels); |  | ||||||
|  |  | ||||||
|     for (final scope in scopeToFetch.skip(1)) { |  | ||||||
|       final channel = await _fetchChannelsFromServer( |  | ||||||
|         scope: scope, |  | ||||||
|         direct: false, |  | ||||||
|         doNotSave: true, |  | ||||||
|       ); |  | ||||||
|       final out = channel.map((ele) => ele.copyWith(realm: realmMap[scope])); |  | ||||||
|       _saveChannelToLocal(out); |  | ||||||
|       result.addAll(out); |  | ||||||
|         } |         } | ||||||
|  |       } | ||||||
|  |       yield out; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (noRemote) return; | ||||||
|  |     final List<SnChannel> result = List.empty(growable: true); | ||||||
|  |     final channels = await _fetchChannelsFromServer(); | ||||||
|  |     for (var idx = 0; idx < channels.length; idx++) { | ||||||
|  |       final channel = channels[idx]; | ||||||
|  |       if (channel.realmId != null) { | ||||||
|  |         channels[idx] = channels[idx].copyWith( | ||||||
|  |           realm: await _rels.getRealm(channel.realmId!), | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     result.addAll(channels); | ||||||
|  |  | ||||||
|     yield result; |     yield result; | ||||||
|   } |   } | ||||||
| @@ -120,23 +126,23 @@ class ChatChannelProvider extends ChangeNotifier { | |||||||
|   Future<List<SnChatMessage>> getLastMessages( |   Future<List<SnChatMessage>> getLastMessages( | ||||||
|     Iterable<SnChannel> channels, |     Iterable<SnChannel> channels, | ||||||
|   ) async { |   ) async { | ||||||
|     final result = List<SnChatMessage>.empty(growable: true); |     final result = List<Future<SnLocalChatMessageData?>>.empty(growable: true); | ||||||
|     for (final channel in channels) { |     for (final channel in channels) { | ||||||
|       final channelBox = await Hive.openBox<SnChatMessage>( |       final out = (_dt.db.snLocalChatMessage.select() | ||||||
|         '${ChatMessageController.kChatMessageBoxPrefix}${channel.id}', |             ..where((e) => e.channelId.equals(channel.id)) | ||||||
|       ); |             ..orderBy([ | ||||||
|       final lastMessage = |               (e) => | ||||||
|           channelBox.isNotEmpty ? channelBox.values.reduce((a, b) => a.createdAt.isAfter(b.createdAt) ? a : b) : null; |                   OrderingTerm(expression: e.createdAt, mode: OrderingMode.desc) | ||||||
|       if (lastMessage != null) result.add(lastMessage); |             ]) | ||||||
|       channelBox.close(); |             ..limit(1)) | ||||||
|  |           .getSingleOrNull(); | ||||||
|  |       result.add(out); | ||||||
|     } |     } | ||||||
|     await _ud.listAccount(result.map((ele) => ele.sender.accountId).toSet()); |     final out = (await Future.wait(result)) | ||||||
|     return result; |         .where((e) => e != null) | ||||||
|   } |         .map((e) => e!.content) | ||||||
|  |         .toList(); | ||||||
|   @override |     await _ud.listAccount(out.map((ele) => ele.sender.accountId).toSet()); | ||||||
|   void dispose() { |     return out; | ||||||
|     _channelBox?.close(); |  | ||||||
|     super.dispose(); |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ const kAppDrawerPreferCollapse = 'app_drawer_prefer_collapse'; | |||||||
| const kAppNotifyWithHaptic = 'app_notify_with_haptic'; | const kAppNotifyWithHaptic = 'app_notify_with_haptic'; | ||||||
| const kAppExpandPostLink = 'app_expand_post_link'; | const kAppExpandPostLink = 'app_expand_post_link'; | ||||||
| const kAppExpandChatLink = 'app_expand_chat_link'; | const kAppExpandChatLink = 'app_expand_chat_link'; | ||||||
|  | const kAppRealmCompactView = 'app_realm_compact_view'; | ||||||
|  |  | ||||||
| const Map<String, FilterQuality> kImageQualityLevel = { | const Map<String, FilterQuality> kImageQualityLevel = { | ||||||
|   'settingsImageQualityLowest': FilterQuality.none, |   'settingsImageQualityLowest': FilterQuality.none, | ||||||
| @@ -72,6 +73,13 @@ class ConfigProvider extends ChangeNotifier { | |||||||
|     return prefs.getString(kNetworkServerStoreKey) ?? kNetworkServerDefault; |     return prefs.getString(kNetworkServerStoreKey) ?? kNetworkServerDefault; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   bool get realmCompactView { | ||||||
|  |     return prefs.getBool(kAppRealmCompactView) ?? false; | ||||||
|  |   } | ||||||
|  |   set realmCompactView(bool value) { | ||||||
|  |     prefs.setBool(kAppRealmCompactView, value); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   set serverUrl(String url) { |   set serverUrl(String url) { | ||||||
|     prefs.setString(kNetworkServerStoreKey, url); |     prefs.setString(kNetworkServerStoreKey, url); | ||||||
|     _home.saveWidgetData("nex_server_url", url); |     _home.saveWidgetData("nex_server_url", url); | ||||||
|   | |||||||
							
								
								
									
										31
									
								
								lib/providers/database.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								lib/providers/database.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | |||||||
|  | import 'dart:io'; | ||||||
|  |  | ||||||
|  | import 'package:flutter/foundation.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:path/path.dart' show join; | ||||||
|  | import 'package:path_provider/path_provider.dart'; | ||||||
|  | import 'package:surface/database/database.dart'; | ||||||
|  |  | ||||||
|  | class DatabaseProvider { | ||||||
|  |   late AppDatabase db; | ||||||
|  |  | ||||||
|  |   DatabaseProvider(BuildContext context) { | ||||||
|  |     db = AppDatabase(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Future<int> getDatabaseSize() async { | ||||||
|  |     if (kIsWeb) return 0; | ||||||
|  |     final basepath = await getApplicationSupportDirectory(); | ||||||
|  |     return await File(join(basepath.path, 'solar_network_data.sqlite')) | ||||||
|  |         .length(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Future<void> removeDatabase() async { | ||||||
|  |     if (kIsWeb) return; | ||||||
|  |     final basepath = await getApplicationSupportDirectory(); | ||||||
|  |     final file = File(join(basepath.path, 'solar_network_data.sqlite')); | ||||||
|  |     db.close(); | ||||||
|  |     await file.delete(); | ||||||
|  |     db = AppDatabase(); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -63,6 +63,11 @@ class NavigationProvider extends ChangeNotifier { | |||||||
|       screen: 'news', |       screen: 'news', | ||||||
|       label: 'screenNews', |       label: 'screenNews', | ||||||
|     ), |     ), | ||||||
|  |     AppNavDestination( | ||||||
|  |       icon: Icon(Symbols.emoji_emotions, weight: 400, opticalSize: 20), | ||||||
|  |       screen: 'stickers', | ||||||
|  |       label: 'screenStickers', | ||||||
|  |     ), | ||||||
|     AppNavDestination( |     AppNavDestination( | ||||||
|       icon: Icon(Symbols.photo_library, weight: 400, opticalSize: 20), |       icon: Icon(Symbols.photo_library, weight: 400, opticalSize: 20), | ||||||
|       screen: 'album', |       screen: 'album', | ||||||
| @@ -88,7 +93,8 @@ class NavigationProvider extends ChangeNotifier { | |||||||
|  |  | ||||||
|   List<AppNavDestination> destinations = []; |   List<AppNavDestination> destinations = []; | ||||||
|  |  | ||||||
|   int get pinnedDestinationCount => destinations.where((ele) => ele.isPinned).length; |   int get pinnedDestinationCount => | ||||||
|  |       destinations.where((ele) => ele.isPinned).length; | ||||||
|  |  | ||||||
|   NavigationProvider() { |   NavigationProvider() { | ||||||
|     buildDestinations(kDefaultPinnedDestination); |     buildDestinations(kDefaultPinnedDestination); | ||||||
| @@ -117,13 +123,17 @@ class NavigationProvider extends ChangeNotifier { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   bool isIndexInRange(int min, int max) { |   bool isIndexInRange(int min, int max) { | ||||||
|     return _currentIndex != null && _currentIndex! >= min && _currentIndex! < max; |     return _currentIndex != null && | ||||||
|  |         _currentIndex! >= min && | ||||||
|  |         _currentIndex! < max; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   void autoDetectIndex(GoRouter? state) { |   void autoDetectIndex(GoRouter? state) { | ||||||
|     if (state == null) return; |     if (state == null) return; | ||||||
|     final idx = destinations.indexWhere( |     final idx = destinations.indexWhere( | ||||||
|       (ele) => ele.screen == state.routerDelegate.currentConfiguration.last.route.name, |       (ele) => | ||||||
|  |           ele.screen == | ||||||
|  |           state.routerDelegate.currentConfiguration.last.route.name, | ||||||
|     ); |     ); | ||||||
|     _currentIndex = idx == -1 ? null : idx; |     _currentIndex = idx == -1 ? null : idx; | ||||||
|     notifyListeners(); |     notifyListeners(); | ||||||
|   | |||||||
| @@ -1,11 +1,13 @@ | |||||||
| import 'dart:developer'; | import 'dart:developer'; | ||||||
| import 'dart:io'; | import 'dart:io'; | ||||||
|  |  | ||||||
|  | import 'package:bitsdojo_window/bitsdojo_window.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/services.dart'; | ||||||
| import 'package:flutter_udid/flutter_udid.dart'; | import 'package:flutter_udid/flutter_udid.dart'; | ||||||
|  | import 'package:local_notifier/local_notifier.dart'; | ||||||
| import 'package:provider/provider.dart'; | import 'package:provider/provider.dart'; | ||||||
| import 'package:surface/providers/config.dart'; | import 'package:surface/providers/config.dart'; | ||||||
| import 'package:surface/providers/sn_network.dart'; | import 'package:surface/providers/sn_network.dart'; | ||||||
| @@ -84,7 +86,7 @@ class NotificationProvider extends ChangeNotifier { | |||||||
|         showingCount++; |         showingCount++; | ||||||
|         showingTrayCount++; |         showingTrayCount++; | ||||||
|         notifications.add(notification); |         notifications.add(notification); | ||||||
|         Future.delayed(const Duration(seconds: 3), () { |         Future.delayed(const Duration(seconds: 5), () { | ||||||
|           if (showingCount >= 0) showingCount--; |           if (showingCount >= 0) showingCount--; | ||||||
|           notifyListeners(); |           notifyListeners(); | ||||||
|         }); |         }); | ||||||
| @@ -92,6 +94,20 @@ class NotificationProvider extends ChangeNotifier { | |||||||
|         updateTray(); |         updateTray(); | ||||||
|         final doHaptic = _cfg.prefs.getBool(kAppNotifyWithHaptic) ?? true; |         final doHaptic = _cfg.prefs.getBool(kAppNotifyWithHaptic) ?? true; | ||||||
|         if (doHaptic) HapticFeedback.mediumImpact(); |         if (doHaptic) HapticFeedback.mediumImpact(); | ||||||
|  |  | ||||||
|  |         if (!kIsWeb) { | ||||||
|  |           if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) { | ||||||
|  |             LocalNotification notify = LocalNotification( | ||||||
|  |               title: notification.title, | ||||||
|  |               subtitle: notification.subtitle, | ||||||
|  |               body: notification.body, | ||||||
|  |             ); | ||||||
|  |             notify.onClick = () { | ||||||
|  |               appWindow.show(); | ||||||
|  |             }; | ||||||
|  |             notify.show(); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -2,19 +2,23 @@ import 'package:flutter/material.dart'; | |||||||
| import 'package:provider/provider.dart'; | import 'package:provider/provider.dart'; | ||||||
| import 'package:surface/providers/sn_attachment.dart'; | import 'package:surface/providers/sn_attachment.dart'; | ||||||
| import 'package:surface/providers/sn_network.dart'; | import 'package:surface/providers/sn_network.dart'; | ||||||
|  | import 'package:surface/providers/sn_realm.dart'; | ||||||
| import 'package:surface/providers/user_directory.dart'; | import 'package:surface/providers/user_directory.dart'; | ||||||
| import 'package:surface/types/poll.dart'; | import 'package:surface/types/poll.dart'; | ||||||
| import 'package:surface/types/post.dart'; | import 'package:surface/types/post.dart'; | ||||||
|  | import 'package:surface/types/realm.dart'; | ||||||
|  |  | ||||||
| class SnPostContentProvider { | class SnPostContentProvider { | ||||||
|   late final SnNetworkProvider _sn; |   late final SnNetworkProvider _sn; | ||||||
|   late final UserDirectoryProvider _ud; |   late final UserDirectoryProvider _ud; | ||||||
|   late final SnAttachmentProvider _attach; |   late final SnAttachmentProvider _attach; | ||||||
|  |   late final SnRealmProvider _realm; | ||||||
|  |  | ||||||
|   SnPostContentProvider(BuildContext context) { |   SnPostContentProvider(BuildContext context) { | ||||||
|     _sn = context.read<SnNetworkProvider>(); |     _sn = context.read<SnNetworkProvider>(); | ||||||
|     _ud = context.read<UserDirectoryProvider>(); |     _ud = context.read<UserDirectoryProvider>(); | ||||||
|     _attach = context.read<SnAttachmentProvider>(); |     _attach = context.read<SnAttachmentProvider>(); | ||||||
|  |     _realm = context.read<SnRealmProvider>(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   Future<SnPoll> _fetchPoll(int id) async { |   Future<SnPoll> _fetchPoll(int id) async { | ||||||
| @@ -42,9 +46,13 @@ class SnPostContentProvider { | |||||||
|     final attachments = await _attach.getMultiple(rids.toList()); |     final attachments = await _attach.getMultiple(rids.toList()); | ||||||
|     for (var i = 0; i < out.length; i++) { |     for (var i = 0; i < out.length; i++) { | ||||||
|       SnPoll? poll; |       SnPoll? poll; | ||||||
|  |       SnRealm? realm; | ||||||
|       if (out[i].pollId != null) { |       if (out[i].pollId != null) { | ||||||
|         poll = await _fetchPoll(out[i].pollId!); |         poll = await _fetchPoll(out[i].pollId!); | ||||||
|       } |       } | ||||||
|  |       if (out[i].realmId != null) { | ||||||
|  |         realm = await _realm.getRealm(out[i].realmId!); | ||||||
|  |       } | ||||||
|  |  | ||||||
|       out[i] = out[i].copyWith( |       out[i] = out[i].copyWith( | ||||||
|         preload: SnPostPreload( |         preload: SnPostPreload( | ||||||
| @@ -52,6 +60,7 @@ class SnPostContentProvider { | |||||||
|           attachments: attachments.where((ele) => out[i].body['attachments']?.contains(ele?.rid) ?? false).toList(), |           attachments: attachments.where((ele) => out[i].body['attachments']?.contains(ele?.rid) ?? false).toList(), | ||||||
|           video: attachments.where((ele) => ele?.rid == out[i].body['video']).firstOrNull, |           video: attachments.where((ele) => ele?.rid == out[i].body['video']).firstOrNull, | ||||||
|           poll: poll, |           poll: poll, | ||||||
|  |           realm: realm, | ||||||
|         ), |         ), | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
| @@ -81,9 +90,13 @@ class SnPostContentProvider { | |||||||
|     final attachments = await _attach.getMultiple(rids.toList()); |     final attachments = await _attach.getMultiple(rids.toList()); | ||||||
|  |  | ||||||
|     SnPoll? poll; |     SnPoll? poll; | ||||||
|  |     SnRealm? realm; | ||||||
|     if (out.pollId != null) { |     if (out.pollId != null) { | ||||||
|       poll = await _fetchPoll(out.pollId!); |       poll = await _fetchPoll(out.pollId!); | ||||||
|     } |     } | ||||||
|  |     if (out.realmId != null) { | ||||||
|  |       realm = await _realm.getRealm(out.realmId!); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     out = out.copyWith( |     out = out.copyWith( | ||||||
|       preload: SnPostPreload( |       preload: SnPostPreload( | ||||||
| @@ -91,6 +104,7 @@ class SnPostContentProvider { | |||||||
|         attachments: attachments.where((ele) => out.body['attachments']?.contains(ele?.rid) ?? false).toList(), |         attachments: attachments.where((ele) => out.body['attachments']?.contains(ele?.rid) ?? false).toList(), | ||||||
|         video: attachments.where((ele) => ele?.rid == out.body['video']).firstOrNull, |         video: attachments.where((ele) => ele?.rid == out.body['video']).firstOrNull, | ||||||
|         poll: poll, |         poll: poll, | ||||||
|  |         realm: realm, | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
| @@ -112,6 +126,8 @@ class SnPostContentProvider { | |||||||
|     String? author, |     String? author, | ||||||
|     Iterable<String>? categories, |     Iterable<String>? categories, | ||||||
|     Iterable<String>? tags, |     Iterable<String>? tags, | ||||||
|  |     String? realm, | ||||||
|  |     String? channel, | ||||||
|   }) async { |   }) async { | ||||||
|     final resp = await _sn.client.get('/cgi/co/posts', queryParameters: { |     final resp = await _sn.client.get('/cgi/co/posts', queryParameters: { | ||||||
|       'take': take, |       'take': take, | ||||||
| @@ -120,6 +136,8 @@ class SnPostContentProvider { | |||||||
|       if (author != null) 'author': author, |       if (author != null) 'author': author, | ||||||
|       if (tags?.isNotEmpty ?? false) 'tags': tags!.join(','), |       if (tags?.isNotEmpty ?? false) 'tags': tags!.join(','), | ||||||
|       if (categories?.isNotEmpty ?? false) 'categories': categories!.join(','), |       if (categories?.isNotEmpty ?? false) 'categories': categories!.join(','), | ||||||
|  |       if (realm != null) 'realm': realm, | ||||||
|  |       if (channel != null) 'channel': channel, | ||||||
|     }); |     }); | ||||||
|     final List<SnPost> out = await _preloadRelatedDataInBatch( |     final List<SnPost> out = await _preloadRelatedDataInBatch( | ||||||
|       List.from(resp.data['data']?.map((e) => SnPost.fromJson(e)) ?? []), |       List.from(resp.data['data']?.map((e) => SnPost.fromJson(e)) ?? []), | ||||||
|   | |||||||
							
								
								
									
										37
									
								
								lib/providers/sn_realm.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								lib/providers/sn_realm.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:provider/provider.dart'; | ||||||
|  | import 'package:surface/providers/sn_network.dart'; | ||||||
|  | import 'package:surface/types/realm.dart'; | ||||||
|  |  | ||||||
|  | class SnRealmProvider { | ||||||
|  |   late final SnNetworkProvider _sn; | ||||||
|  |  | ||||||
|  |   SnRealmProvider(BuildContext context) { | ||||||
|  |     _sn = context.read<SnNetworkProvider>(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   final Map<String, SnRealm> _cache = {}; | ||||||
|  |  | ||||||
|  |   Future<List<SnRealm>> listAvailableRealms() async { | ||||||
|  |     final resp = await _sn.client.get('/cgi/id/realms/me/available'); | ||||||
|  |     final out = List<SnRealm>.from( | ||||||
|  |       resp.data?.map((e) => SnRealm.fromJson(e)) ?? [], | ||||||
|  |     ); | ||||||
|  |     for (final realm in out) { | ||||||
|  |       _cache[realm.alias] = realm; | ||||||
|  |       _cache[realm.id.toString()] = realm; | ||||||
|  |     } | ||||||
|  |     return out; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Future<SnRealm> getRealm(dynamic aliasOrId) async { | ||||||
|  |     if (_cache.containsKey(aliasOrId.toString())) { | ||||||
|  |       return _cache[aliasOrId.toString()]!; | ||||||
|  |     } | ||||||
|  |     final resp = await _sn.client.get('/cgi/id/realms/$aliasOrId'); | ||||||
|  |     final out = SnRealm.fromJson(resp.data); | ||||||
|  |     _cache[out.alias] = out; | ||||||
|  |     _cache[out.id.toString()] = out; | ||||||
|  |     return out; | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -11,7 +11,8 @@ class SnStickerProvider { | |||||||
|  |  | ||||||
|   final Map<int, List<SnSticker>> stickersByPack = {}; |   final Map<int, List<SnSticker>> stickersByPack = {}; | ||||||
|  |  | ||||||
|   List<SnSticker> get stickers => _cache.values.where((ele) => ele != null).cast<SnSticker>().toList(); |   List<SnSticker> get stickers => | ||||||
|  |       _cache.values.where((ele) => ele != null).cast<SnSticker>().toList(); | ||||||
|  |  | ||||||
|   SnStickerProvider(BuildContext context) { |   SnStickerProvider(BuildContext context) { | ||||||
|     _sn = context.read<SnNetworkProvider>(); |     _sn = context.read<SnNetworkProvider>(); | ||||||
| @@ -23,8 +24,18 @@ class SnStickerProvider { | |||||||
|  |  | ||||||
|   void _cacheSticker(SnSticker sticker) { |   void _cacheSticker(SnSticker sticker) { | ||||||
|     _cache['${sticker.pack.prefix}:${sticker.alias}'] = sticker; |     _cache['${sticker.pack.prefix}:${sticker.alias}'] = sticker; | ||||||
|     if (stickersByPack[sticker.pack.id] == null) stickersByPack[sticker.pack.id] = List.empty(growable: true); |     if (stickersByPack[sticker.pack.id] == null) { | ||||||
|     if (!stickersByPack[sticker.pack.id]!.contains(sticker)) stickersByPack[sticker.pack.id]!.add(sticker); |       stickersByPack[sticker.pack.id] = List.empty(growable: true); | ||||||
|  |     } | ||||||
|  |     if (!stickersByPack[sticker.pack.id]!.contains(sticker)) { | ||||||
|  |       stickersByPack[sticker.pack.id]!.add(sticker); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void putSticker(Iterable<SnSticker> sticker) { | ||||||
|  |     for (final ele in sticker) { | ||||||
|  |       _cacheSticker(ele); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   Future<SnSticker?> lookupSticker(String alias) async { |   Future<SnSticker?> lookupSticker(String alias) async { | ||||||
| @@ -46,26 +57,14 @@ class SnStickerProvider { | |||||||
|     return null; |     return null; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   Future<void> listStickerEagerly() async { |   Future<void> listSticker() async { | ||||||
|     var count = await listSticker(); |  | ||||||
|     for (var page = 1; count > 0; count -= 10) { |  | ||||||
|       await listSticker(page: page); |  | ||||||
|       page++; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   Future<int> listSticker({int page = 0}) async { |  | ||||||
|     try { |     try { | ||||||
|       final resp = await _sn.client.get('/cgi/uc/stickers', queryParameters: { |       final resp = await _sn.client.get('/cgi/uc/stickers'); | ||||||
|         'take': 10, |  | ||||||
|         'offset': page * 10, |  | ||||||
|       }); |  | ||||||
|       final data = resp.data; |       final data = resp.data; | ||||||
|       final stickers = List.from(data['data']).map((ele) => SnSticker.fromJson(ele)); |       final stickers = List.from(data).map((ele) => SnSticker.fromJson(ele)); | ||||||
|       for (final sticker in stickers) { |       for (final sticker in stickers) { | ||||||
|         _cacheSticker(sticker); |         _cacheSticker(sticker); | ||||||
|       } |       } | ||||||
|       return data['count'] as int; |  | ||||||
|     } catch (err) { |     } catch (err) { | ||||||
|       log('[Sticker] Failed to list stickers: $err'); |       log('[Sticker] Failed to list stickers: $err'); | ||||||
|       rethrow; |       rethrow; | ||||||
|   | |||||||
| @@ -34,13 +34,15 @@ import 'package:surface/screens/realm/realm_detail.dart'; | |||||||
| import 'package:surface/screens/realm/realm_discovery.dart'; | import 'package:surface/screens/realm/realm_discovery.dart'; | ||||||
| import 'package:surface/screens/settings.dart'; | import 'package:surface/screens/settings.dart'; | ||||||
| import 'package:surface/screens/sharing.dart'; | import 'package:surface/screens/sharing.dart'; | ||||||
|  | import 'package:surface/screens/stickers.dart'; | ||||||
|  | import 'package:surface/screens/stickers/pack_detail.dart'; | ||||||
| import 'package:surface/screens/wallet.dart'; | import 'package:surface/screens/wallet.dart'; | ||||||
| import 'package:surface/types/post.dart'; | import 'package:surface/types/post.dart'; | ||||||
| import 'package:surface/widgets/about.dart'; | import 'package:surface/widgets/about.dart'; | ||||||
| import 'package:surface/widgets/navigation/app_scaffold.dart'; | import 'package:surface/widgets/navigation/app_scaffold.dart'; | ||||||
|  |  | ||||||
| Widget _fadeThroughTransition( | Widget _fadeThroughTransition(BuildContext context, Animation<double> animation, | ||||||
|     BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) { |     Animation<double> secondaryAnimation, Widget child) { | ||||||
|   return FadeThroughTransition( |   return FadeThroughTransition( | ||||||
|     animation: animation, |     animation: animation, | ||||||
|     secondaryAnimation: secondaryAnimation, |     secondaryAnimation: secondaryAnimation, | ||||||
| @@ -82,13 +84,15 @@ final _appRoutes = [ | |||||||
|         name: 'postSearch', |         name: 'postSearch', | ||||||
|         builder: (context, state) => PostSearchScreen( |         builder: (context, state) => PostSearchScreen( | ||||||
|           initialTags: state.uri.queryParameters['tags']?.split(','), |           initialTags: state.uri.queryParameters['tags']?.split(','), | ||||||
|           initialCategories: state.uri.queryParameters['categories']?.split(','), |           initialCategories: | ||||||
|  |               state.uri.queryParameters['categories']?.split(','), | ||||||
|         ), |         ), | ||||||
|       ), |       ), | ||||||
|       GoRoute( |       GoRoute( | ||||||
|         path: '/publishers/:name', |         path: '/publishers/:name', | ||||||
|         name: 'postPublisher', |         name: 'postPublisher', | ||||||
|         builder: (context, state) => PostPublisherScreen(name: state.pathParameters['name']!), |         builder: (context, state) => | ||||||
|  |             PostPublisherScreen(name: state.pathParameters['name']!), | ||||||
|       ), |       ), | ||||||
|       GoRoute( |       GoRoute( | ||||||
|         path: '/:slug', |         path: '/:slug', | ||||||
| @@ -100,7 +104,11 @@ final _appRoutes = [ | |||||||
|       ), |       ), | ||||||
|     ], |     ], | ||||||
|   ), |   ), | ||||||
|   GoRoute(path: '/account', name: 'account', builder: (context, state) => const AccountScreen(), routes: [ |   GoRoute( | ||||||
|  |       path: '/account', | ||||||
|  |       name: 'account', | ||||||
|  |       builder: (context, state) => const AccountScreen(), | ||||||
|  |       routes: [ | ||||||
|         GoRoute( |         GoRoute( | ||||||
|           path: '/wallet', |           path: '/wallet', | ||||||
|           name: 'accountWallet', |           name: 'accountWallet', | ||||||
| @@ -208,11 +216,16 @@ final _appRoutes = [ | |||||||
|       GoRoute( |       GoRoute( | ||||||
|         path: '/:alias', |         path: '/:alias', | ||||||
|         name: 'realmDetail', |         name: 'realmDetail', | ||||||
|         builder: (context, state) => RealmDetailScreen(alias: state.pathParameters['alias']!), |         builder: (context, state) => | ||||||
|  |             RealmDetailScreen(alias: state.pathParameters['alias']!), | ||||||
|       ), |       ), | ||||||
|     ], |     ], | ||||||
|   ), |   ), | ||||||
|   GoRoute(path: '/news', name: 'news', builder: (context, state) => const NewsScreen(), routes: [ |   GoRoute( | ||||||
|  |     path: '/news', | ||||||
|  |     name: 'news', | ||||||
|  |     builder: (context, state) => const NewsScreen(), | ||||||
|  |     routes: [ | ||||||
|       GoRoute( |       GoRoute( | ||||||
|         path: '/:hash', |         path: '/:hash', | ||||||
|         name: 'newsDetail', |         name: 'newsDetail', | ||||||
| @@ -220,7 +233,22 @@ final _appRoutes = [ | |||||||
|           hash: state.pathParameters['hash']!, |           hash: state.pathParameters['hash']!, | ||||||
|         ), |         ), | ||||||
|       ), |       ), | ||||||
|   ]), |     ], | ||||||
|  |   ), | ||||||
|  |   GoRoute( | ||||||
|  |     path: '/stickers', | ||||||
|  |     name: 'stickers', | ||||||
|  |     builder: (context, state) => const StickerScreen(), | ||||||
|  |     routes: [ | ||||||
|  |       GoRoute( | ||||||
|  |         path: '/packs/:id', | ||||||
|  |         name: 'stickerPack', | ||||||
|  |         builder: (context, state) => StickerPackScreen( | ||||||
|  |           id: int.tryParse(state.pathParameters['id']!)!, | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ], | ||||||
|  |   ), | ||||||
|   GoRoute( |   GoRoute( | ||||||
|     path: '/album', |     path: '/album', | ||||||
|     name: 'album', |     name: 'album', | ||||||
|   | |||||||
| @@ -4,10 +4,10 @@ import 'package:easy_localization/easy_localization.dart'; | |||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:gap/gap.dart'; | import 'package:gap/gap.dart'; | ||||||
| import 'package:go_router/go_router.dart'; | import 'package:go_router/go_router.dart'; | ||||||
| import 'package:hive_flutter/hive_flutter.dart'; |  | ||||||
| import 'package:material_symbols_icons/symbols.dart'; | import 'package:material_symbols_icons/symbols.dart'; | ||||||
| import 'package:provider/provider.dart'; | import 'package:provider/provider.dart'; | ||||||
| import 'package:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  | import 'package:surface/providers/database.dart'; | ||||||
| import 'package:surface/providers/sn_network.dart'; | import 'package:surface/providers/sn_network.dart'; | ||||||
| import 'package:surface/providers/userinfo.dart'; | import 'package:surface/providers/userinfo.dart'; | ||||||
| import 'package:surface/providers/websocket.dart'; | import 'package:surface/providers/websocket.dart'; | ||||||
| @@ -45,7 +45,8 @@ class AccountScreen extends StatelessWidget { | |||||||
|             ? Stack( |             ? Stack( | ||||||
|                 fit: StackFit.expand, |                 fit: StackFit.expand, | ||||||
|                 children: [ |                 children: [ | ||||||
|                   AutoResizeUniversalImage(sn.getAttachmentUrl(ua.user!.banner), fit: BoxFit.cover), |                   AutoResizeUniversalImage(sn.getAttachmentUrl(ua.user!.banner), | ||||||
|  |                       fit: BoxFit.cover), | ||||||
|                   Positioned( |                   Positioned( | ||||||
|                     top: 0, |                     top: 0, | ||||||
|                     left: 0, |                     left: 0, | ||||||
| @@ -79,7 +80,9 @@ class AccountScreen extends StatelessWidget { | |||||||
|         ], |         ], | ||||||
|       ), |       ), | ||||||
|       body: SingleChildScrollView( |       body: SingleChildScrollView( | ||||||
|         child: ua.isAuthorized ? _AuthorizedAccountScreen() : _UnauthorizedAccountScreen(), |         child: ua.isAuthorized | ||||||
|  |             ? _AuthorizedAccountScreen() | ||||||
|  |             : _UnauthorizedAccountScreen(), | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| @@ -115,12 +118,15 @@ class _AuthorizedAccountScreen extends StatelessWidget { | |||||||
|                     crossAxisAlignment: CrossAxisAlignment.baseline, |                     crossAxisAlignment: CrossAxisAlignment.baseline, | ||||||
|                     textBaseline: TextBaseline.alphabetic, |                     textBaseline: TextBaseline.alphabetic, | ||||||
|                     children: [ |                     children: [ | ||||||
|                       Text(ua.user!.nick).textStyle(Theme.of(context).textTheme.titleLarge!), |                       Text(ua.user!.nick) | ||||||
|  |                           .textStyle(Theme.of(context).textTheme.titleLarge!), | ||||||
|                       const Gap(4), |                       const Gap(4), | ||||||
|                       Text('@${ua.user!.name}').textStyle(Theme.of(context).textTheme.bodySmall!), |                       Text('@${ua.user!.name}') | ||||||
|  |                           .textStyle(Theme.of(context).textTheme.bodySmall!), | ||||||
|                     ], |                     ], | ||||||
|                   ), |                   ), | ||||||
|                   Text(ua.user!.description).textStyle(Theme.of(context).textTheme.bodyMedium!), |                   Text(ua.user!.description) | ||||||
|  |                       .textStyle(Theme.of(context).textTheme.bodyMedium!), | ||||||
|                 ], |                 ], | ||||||
|               ), |               ), | ||||||
|             ); |             ); | ||||||
| @@ -193,8 +199,7 @@ class _AuthorizedAccountScreen extends StatelessWidget { | |||||||
|             ua.logoutUser(); |             ua.logoutUser(); | ||||||
|             final ws = context.read<WebSocketProvider>(); |             final ws = context.read<WebSocketProvider>(); | ||||||
|             ws.disconnect(); |             ws.disconnect(); | ||||||
|             await Hive.deleteFromDisk(); |             context.read<DatabaseProvider>().removeDatabase(); | ||||||
|             await Hive.initFlutter(); |  | ||||||
|           }, |           }, | ||||||
|         ), |         ), | ||||||
|       ], |       ], | ||||||
| @@ -220,7 +225,9 @@ class _UnauthorizedAccountScreen extends StatelessWidget { | |||||||
|                   child: Icon(Symbols.waving_hand, size: 28), |                   child: Icon(Symbols.waving_hand, size: 28), | ||||||
|                 ), |                 ), | ||||||
|                 const Gap(8), |                 const Gap(8), | ||||||
|                 Text('accountIntroTitle').tr().textStyle(Theme.of(context).textTheme.titleLarge!), |                 Text('accountIntroTitle') | ||||||
|  |                     .tr() | ||||||
|  |                     .textStyle(Theme.of(context).textTheme.titleLarge!), | ||||||
|                 Text('accountIntroSubtitle').tr(), |                 Text('accountIntroSubtitle').tr(), | ||||||
|               ], |               ], | ||||||
|             ).padding(all: 20), |             ).padding(all: 20), | ||||||
|   | |||||||
| @@ -2,6 +2,9 @@ import 'package:dismissible_page/dismissible_page.dart'; | |||||||
| 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_staggered_grid_view/flutter_staggered_grid_view.dart'; | import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; | ||||||
|  | import 'package:gap/gap.dart'; | ||||||
|  | import 'package:google_fonts/google_fonts.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
| import 'package:provider/provider.dart'; | import 'package:provider/provider.dart'; | ||||||
| import 'package:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
| import 'package:surface/providers/sn_network.dart'; | import 'package:surface/providers/sn_network.dart'; | ||||||
| @@ -27,9 +30,23 @@ class _AlbumScreenState extends State<AlbumScreen> { | |||||||
|   bool _isBusy = false; |   bool _isBusy = false; | ||||||
|   int? _totalCount; |   int? _totalCount; | ||||||
|  |  | ||||||
|  |   SnAttachmentBilling? _billing; | ||||||
|  |  | ||||||
|   final List<SnAttachment> _attachments = List.empty(growable: true); |   final List<SnAttachment> _attachments = List.empty(growable: true); | ||||||
|   final List<String> _heroTags = List.empty(growable: true); |   final List<String> _heroTags = List.empty(growable: true); | ||||||
|  |  | ||||||
|  |   Future<void> _fetchBillingStatus() async { | ||||||
|  |     try { | ||||||
|  |       final sn = context.read<SnNetworkProvider>(); | ||||||
|  |       final resp = await sn.client.get('/cgi/uc/billing'); | ||||||
|  |       final out = SnAttachmentBilling.fromJson(resp.data); | ||||||
|  |       setState(() => _billing = out); | ||||||
|  |     } catch (err) { | ||||||
|  |       if (!mounted) return; | ||||||
|  |       context.showErrorDialog(err); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|   Future<void> _fetchAttachments() async { |   Future<void> _fetchAttachments() async { | ||||||
|     setState(() => _isBusy = true); |     setState(() => _isBusy = true); | ||||||
|  |  | ||||||
| @@ -62,6 +79,7 @@ class _AlbumScreenState extends State<AlbumScreen> { | |||||||
|   @override |   @override | ||||||
|   void initState() { |   void initState() { | ||||||
|     super.initState(); |     super.initState(); | ||||||
|  |     _fetchBillingStatus(); | ||||||
|     _fetchAttachments(); |     _fetchAttachments(); | ||||||
|     _scrollController.addListener(() { |     _scrollController.addListener(() { | ||||||
|       if (_scrollController.position.atEdge) { |       if (_scrollController.position.atEdge) { | ||||||
| @@ -91,6 +109,48 @@ class _AlbumScreenState extends State<AlbumScreen> { | |||||||
|             leading: AutoAppBarLeading(), |             leading: AutoAppBarLeading(), | ||||||
|             title: Text('screenAlbum').tr(), |             title: Text('screenAlbum').tr(), | ||||||
|           ), |           ), | ||||||
|  |           SliverToBoxAdapter( | ||||||
|  |             child: Card( | ||||||
|  |               child: Row( | ||||||
|  |                 children: [ | ||||||
|  |                   SizedBox( | ||||||
|  |                     width: 80, | ||||||
|  |                     height: 80, | ||||||
|  |                     child: CircularProgressIndicator( | ||||||
|  |                       value: _billing?.includedRatio ?? 0, | ||||||
|  |                       strokeWidth: 8, | ||||||
|  |                       backgroundColor: Theme.of(context).colorScheme.surfaceContainerHigh, | ||||||
|  |                     ), | ||||||
|  |                   ).padding(all: 12), | ||||||
|  |                   const Gap(24), | ||||||
|  |                   Expanded( | ||||||
|  |                     child: Column( | ||||||
|  |                       crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |                       children: [ | ||||||
|  |                         Text('attachmentBillingUploaded').tr().bold(), | ||||||
|  |                         Text( | ||||||
|  |                           (_billing?.currentBytes ?? 0).formatBytes(decimals: 4), | ||||||
|  |                           style: GoogleFonts.robotoMono(), | ||||||
|  |                         ), | ||||||
|  |                         Text('attachmentBillingDiscount').tr().bold(), | ||||||
|  |                         Text( | ||||||
|  |                           '${(_billing?.discountFileSize ?? 0).formatBytes(decimals: 2)} · ${((_billing?.includedRatio ?? 0) * 100).toStringAsFixed(2)}%', | ||||||
|  |                           style: GoogleFonts.robotoMono(), | ||||||
|  |                         ), | ||||||
|  |                       ], | ||||||
|  |                     ), | ||||||
|  |                   ), | ||||||
|  |                   Tooltip( | ||||||
|  |                     message: 'attachmentBillingHint'.tr(), | ||||||
|  |                     child: IconButton( | ||||||
|  |                       icon: const Icon(Symbols.info), | ||||||
|  |                       onPressed: () {}, | ||||||
|  |                     ), | ||||||
|  |                   ), | ||||||
|  |                 ], | ||||||
|  |               ).padding(horizontal: 24, vertical: 8), | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|           SliverMasonryGrid.extent( |           SliverMasonryGrid.extent( | ||||||
|             childCount: _attachments.length, |             childCount: _attachments.length, | ||||||
|             maxCrossAxisExtent: 320, |             maxCrossAxisExtent: 320, | ||||||
|   | |||||||
| @@ -5,21 +5,23 @@ import 'package:gap/gap.dart'; | |||||||
| import 'package:go_router/go_router.dart'; | import 'package:go_router/go_router.dart'; | ||||||
| import 'package:material_symbols_icons/symbols.dart'; | import 'package:material_symbols_icons/symbols.dart'; | ||||||
| import 'package:provider/provider.dart'; | import 'package:provider/provider.dart'; | ||||||
|  | import 'package:responsive_framework/responsive_framework.dart'; | ||||||
| import 'package:surface/providers/channel.dart'; | import 'package:surface/providers/channel.dart'; | ||||||
|  | import 'package:surface/providers/sn_network.dart'; | ||||||
| import 'package:surface/providers/user_directory.dart'; | import 'package:surface/providers/user_directory.dart'; | ||||||
|  | import 'package:surface/providers/userinfo.dart'; | ||||||
|  | import 'package:surface/screens/chat/room.dart'; | ||||||
| import 'package:surface/types/chat.dart'; | import 'package:surface/types/chat.dart'; | ||||||
| import 'package:surface/widgets/account/account_image.dart'; | import 'package:surface/widgets/account/account_image.dart'; | ||||||
| import 'package:surface/widgets/account/account_select.dart'; | import 'package:surface/widgets/account/account_select.dart'; | ||||||
| import 'package:surface/widgets/app_bar_leading.dart'; | import 'package:surface/widgets/app_bar_leading.dart'; | ||||||
| import 'package:surface/widgets/dialog.dart'; | import 'package:surface/widgets/dialog.dart'; | ||||||
| import 'package:surface/widgets/loading_indicator.dart'; | import 'package:surface/widgets/loading_indicator.dart'; | ||||||
|  | import 'package:surface/widgets/navigation/app_background.dart'; | ||||||
| import 'package:surface/widgets/navigation/app_scaffold.dart'; | import 'package:surface/widgets/navigation/app_scaffold.dart'; | ||||||
| import 'package:surface/widgets/unauthorized_hint.dart'; | import 'package:surface/widgets/unauthorized_hint.dart'; | ||||||
| import 'package:uuid/uuid.dart'; | import 'package:uuid/uuid.dart'; | ||||||
|  |  | ||||||
| import '../providers/sn_network.dart'; |  | ||||||
| import '../providers/userinfo.dart'; |  | ||||||
|  |  | ||||||
| class ChatScreen extends StatefulWidget { | class ChatScreen extends StatefulWidget { | ||||||
|   const ChatScreen({super.key}); |   const ChatScreen({super.key}); | ||||||
|  |  | ||||||
| @@ -34,8 +36,18 @@ class _ChatScreenState extends State<ChatScreen> { | |||||||
|  |  | ||||||
|   List<SnChannel>? _channels; |   List<SnChannel>? _channels; | ||||||
|   Map<int, SnChatMessage>? _lastMessages; |   Map<int, SnChatMessage>? _lastMessages; | ||||||
|  |   Map<int, int>? _unreadCounts; | ||||||
|  |  | ||||||
|   void _refreshChannels() { |   Future<void> _fetchWhatsNew() async { | ||||||
|  |     final sn = context.read<SnNetworkProvider>(); | ||||||
|  |     final resp = await sn.client.get('/cgi/im/whats-new'); | ||||||
|  |     final List<dynamic> out = resp.data; | ||||||
|  |     setState(() { | ||||||
|  |       _unreadCounts = {for (var v in out) v['channel_id']: v['count']}; | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void _refreshChannels({bool noRemote = false}) { | ||||||
|     final ua = context.read<UserProvider>(); |     final ua = context.read<UserProvider>(); | ||||||
|     if (!ua.isAuthorized) { |     if (!ua.isAuthorized) { | ||||||
|       setState(() => _isBusy = false); |       setState(() => _isBusy = false); | ||||||
| @@ -43,12 +55,15 @@ class _ChatScreenState extends State<ChatScreen> { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     final chan = context.read<ChatChannelProvider>(); |     final chan = context.read<ChatChannelProvider>(); | ||||||
|     chan.fetchChannels().listen((channels) async { |     chan.fetchChannels(noRemote: noRemote).listen((channels) async { | ||||||
|       final lastMessages = await chan.getLastMessages(channels); |       final lastMessages = await chan.getLastMessages(channels); | ||||||
|       _lastMessages = {for (final val in lastMessages) val.channelId: val}; |       _lastMessages = {for (final val in lastMessages) val.channelId: val}; | ||||||
|       channels.sort((a, b) { |       channels.sort((a, b) { | ||||||
|         if (_lastMessages!.containsKey(a.id) && _lastMessages!.containsKey(b.id)) { |         if (_lastMessages!.containsKey(a.id) && | ||||||
|           return _lastMessages![b.id]!.createdAt.compareTo(_lastMessages![a.id]!.createdAt); |             _lastMessages!.containsKey(b.id)) { | ||||||
|  |           return _lastMessages![b.id]! | ||||||
|  |               .createdAt | ||||||
|  |               .compareTo(_lastMessages![a.id]!.createdAt); | ||||||
|         } |         } | ||||||
|         if (_lastMessages!.containsKey(a.id)) return -1; |         if (_lastMessages!.containsKey(a.id)) return -1; | ||||||
|         if (_lastMessages!.containsKey(b.id)) return 1; |         if (_lastMessages!.containsKey(b.id)) return 1; | ||||||
| @@ -86,7 +101,8 @@ class _ChatScreenState extends State<ChatScreen> { | |||||||
|   void _newDirectMessage() async { |   void _newDirectMessage() async { | ||||||
|     final user = await showModalBottomSheet( |     final user = await showModalBottomSheet( | ||||||
|       context: context, |       context: context, | ||||||
|       builder: (context) => AccountSelect(title: 'channelNewDirectMessage'.tr()), |       builder: (context) => | ||||||
|  |           AccountSelect(title: 'channelNewDirectMessage'.tr()), | ||||||
|     ); |     ); | ||||||
|     if (user == null) return; |     if (user == null) return; | ||||||
|     if (!mounted) return; |     if (!mounted) return; | ||||||
| @@ -98,7 +114,8 @@ class _ChatScreenState extends State<ChatScreen> { | |||||||
|       await sn.client.post('/cgi/im/channels/global/dm', data: { |       await sn.client.post('/cgi/im/channels/global/dm', data: { | ||||||
|         'alias': uuid.v4().replaceAll('-', '').substring(0, 12), |         'alias': uuid.v4().replaceAll('-', '').substring(0, 12), | ||||||
|         'name': 'DM', |         'name': 'DM', | ||||||
|         'description': 'A direct message channel between @${ua.user?.name} and @${user.name}', |         'description': | ||||||
|  |             'A direct message channel between @${ua.user?.name} and @${user.name}', | ||||||
|         'related_user': user.id, |         'related_user': user.id, | ||||||
|       }); |       }); | ||||||
|       _fabKey.currentState!.toggle(); |       _fabKey.currentState!.toggle(); | ||||||
| @@ -109,10 +126,13 @@ class _ChatScreenState extends State<ChatScreen> { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   SnChannel? _focusChannel; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   void initState() { |   void initState() { | ||||||
|     super.initState(); |     super.initState(); | ||||||
|     _refreshChannels(); |     _refreshChannels(); | ||||||
|  |     _fetchWhatsNew(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
| @@ -132,7 +152,10 @@ class _ChatScreenState extends State<ChatScreen> { | |||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return AppScaffold( |     final doExpand = ResponsiveBreakpoints.of(context).largerOrEqualTo(DESKTOP); | ||||||
|  |  | ||||||
|  |     final chatList = AppScaffold( | ||||||
|  |       noBackground: doExpand, | ||||||
|       appBar: AppBar( |       appBar: AppBar( | ||||||
|         leading: AutoAppBarLeading(), |         leading: AutoAppBarLeading(), | ||||||
|         title: Text('screenChat').tr(), |         title: Text('screenChat').tr(), | ||||||
| @@ -144,20 +167,27 @@ class _ChatScreenState extends State<ChatScreen> { | |||||||
|         type: ExpandableFabType.up, |         type: ExpandableFabType.up, | ||||||
|         childrenAnimation: ExpandableFabAnimation.none, |         childrenAnimation: ExpandableFabAnimation.none, | ||||||
|         overlayStyle: ExpandableFabOverlayStyle( |         overlayStyle: ExpandableFabOverlayStyle( | ||||||
|           color: Theme.of(context).colorScheme.surface.withAlpha((255 * 0.5).round()), |           color: Theme.of(context) | ||||||
|  |               .colorScheme | ||||||
|  |               .surface | ||||||
|  |               .withAlpha((255 * 0.5).round()), | ||||||
|         ), |         ), | ||||||
|         openButtonBuilder: RotateFloatingActionButtonBuilder( |         openButtonBuilder: RotateFloatingActionButtonBuilder( | ||||||
|           child: const Icon(Symbols.add, size: 28), |           child: const Icon(Symbols.add, size: 28), | ||||||
|           fabSize: ExpandableFabSize.regular, |           fabSize: ExpandableFabSize.regular, | ||||||
|           foregroundColor: Theme.of(context).floatingActionButtonTheme.foregroundColor, |           foregroundColor: | ||||||
|           backgroundColor: Theme.of(context).floatingActionButtonTheme.backgroundColor, |               Theme.of(context).floatingActionButtonTheme.foregroundColor, | ||||||
|  |           backgroundColor: | ||||||
|  |               Theme.of(context).floatingActionButtonTheme.backgroundColor, | ||||||
|           shape: const CircleBorder(), |           shape: const CircleBorder(), | ||||||
|         ), |         ), | ||||||
|         closeButtonBuilder: DefaultFloatingActionButtonBuilder( |         closeButtonBuilder: DefaultFloatingActionButtonBuilder( | ||||||
|           child: const Icon(Symbols.close, size: 28), |           child: const Icon(Symbols.close, size: 28), | ||||||
|           fabSize: ExpandableFabSize.regular, |           fabSize: ExpandableFabSize.regular, | ||||||
|           foregroundColor: Theme.of(context).floatingActionButtonTheme.foregroundColor, |           foregroundColor: | ||||||
|           backgroundColor: Theme.of(context).floatingActionButtonTheme.backgroundColor, |               Theme.of(context).floatingActionButtonTheme.foregroundColor, | ||||||
|  |           backgroundColor: | ||||||
|  |               Theme.of(context).floatingActionButtonTheme.backgroundColor, | ||||||
|           shape: const CircleBorder(), |           shape: const CircleBorder(), | ||||||
|         ), |         ), | ||||||
|         children: [ |         children: [ | ||||||
| @@ -200,7 +230,10 @@ class _ChatScreenState extends State<ChatScreen> { | |||||||
|               context: context, |               context: context, | ||||||
|               removeTop: true, |               removeTop: true, | ||||||
|               child: RefreshIndicator( |               child: RefreshIndicator( | ||||||
|                 onRefresh: () => Future.sync(() => _refreshChannels()), |                 onRefresh: () => Future.wait([ | ||||||
|  |                   Future.sync(() => _refreshChannels()), | ||||||
|  |                   _fetchWhatsNew(), | ||||||
|  |                 ]), | ||||||
|                 child: ListView.builder( |                 child: ListView.builder( | ||||||
|                   itemCount: _channels?.length ?? 0, |                   itemCount: _channels?.length ?? 0, | ||||||
|                   itemBuilder: (context, idx) { |                   itemBuilder: (context, idx) { | ||||||
| @@ -208,46 +241,29 @@ class _ChatScreenState extends State<ChatScreen> { | |||||||
|                     final lastMessage = _lastMessages?[channel.id]; |                     final lastMessage = _lastMessages?[channel.id]; | ||||||
|  |  | ||||||
|                     if (channel.type == 1) { |                     if (channel.type == 1) { | ||||||
|                       final otherMember = channel.members?.cast<SnChannelMember?>().firstWhere( |                       final otherMember = | ||||||
|  |                           channel.members?.cast<SnChannelMember?>().firstWhere( | ||||||
|                                 (ele) => ele?.accountId != ua.user?.id, |                                 (ele) => ele?.accountId != ua.user?.id, | ||||||
|                                 orElse: () => null, |                                 orElse: () => null, | ||||||
|                               ); |                               ); | ||||||
|  |  | ||||||
|                       return ListTile( |                       return ListTile( | ||||||
|                         title: Text(ud.getAccountFromCache(otherMember?.accountId)?.nick ?? channel.name), |                         title: Row( | ||||||
|                         subtitle: lastMessage != null |                           children: [ | ||||||
|                             ? Text( |                             Expanded( | ||||||
|                                 '${ud.getAccountFromCache(lastMessage.sender.accountId)?.nick}: ${lastMessage.body['text'] ?? 'Unable preview'}', |                               child: Text(ud | ||||||
|                                 maxLines: 1, |                                       .getAccountFromCache( | ||||||
|                                 overflow: TextOverflow.ellipsis, |                                           otherMember?.accountId) | ||||||
|                               ) |                                       ?.nick ?? | ||||||
|                             : Text( |                                   channel.name), | ||||||
|                                 'channelDirectMessageDescription'.tr(args: [ |  | ||||||
|                                   '@${ud.getAccountFromCache(otherMember?.accountId)?.name}', |  | ||||||
|                                 ]), |  | ||||||
|                                 maxLines: 1, |  | ||||||
|                                 overflow: TextOverflow.ellipsis, |  | ||||||
|                             ), |                             ), | ||||||
|                         contentPadding: const EdgeInsets.symmetric(horizontal: 16), |                             const Gap(8), | ||||||
|                         leading: AccountImage( |                             if (_unreadCounts?[channel.id] != null) | ||||||
|                           content: ud.getAccountFromCache(otherMember?.accountId)?.avatar, |                               Badge( | ||||||
|  |                                 label: Text('${_unreadCounts![channel.id]}'), | ||||||
|  |                               ), | ||||||
|  |                           ], | ||||||
|                         ), |                         ), | ||||||
|                         onTap: () { |  | ||||||
|                           GoRouter.of(context).pushNamed( |  | ||||||
|                             'chatRoom', |  | ||||||
|                             pathParameters: { |  | ||||||
|                               'scope': channel.realm?.alias ?? 'global', |  | ||||||
|                               'alias': channel.alias, |  | ||||||
|                             }, |  | ||||||
|                           ).then((value) { |  | ||||||
|                             if (mounted) _refreshChannels(); |  | ||||||
|                           }); |  | ||||||
|                         }, |  | ||||||
|                       ); |  | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                     return ListTile( |  | ||||||
|                       title: Text(channel.name), |  | ||||||
|                         subtitle: lastMessage != null |                         subtitle: lastMessage != null | ||||||
|                             ? Text( |                             ? Text( | ||||||
|                                 '${ud.getAccountFromCache(lastMessage.sender.accountId)?.nick}: ${lastMessage.body['text'] ?? 'Unable preview'}', |                                 '${ud.getAccountFromCache(lastMessage.sender.accountId)?.nick}: ${lastMessage.body['text'] ?? 'Unable preview'}', | ||||||
| @@ -259,12 +275,18 @@ class _ChatScreenState extends State<ChatScreen> { | |||||||
|                                 maxLines: 1, |                                 maxLines: 1, | ||||||
|                                 overflow: TextOverflow.ellipsis, |                                 overflow: TextOverflow.ellipsis, | ||||||
|                               ), |                               ), | ||||||
|                       contentPadding: const EdgeInsets.symmetric(horizontal: 16), |                         contentPadding: | ||||||
|  |                             const EdgeInsets.symmetric(horizontal: 16), | ||||||
|                         leading: AccountImage( |                         leading: AccountImage( | ||||||
|                         content: null, |                           content: ud | ||||||
|                         fallbackWidget: const Icon(Symbols.chat, size: 20), |                               .getAccountFromCache(otherMember?.accountId) | ||||||
|  |                               ?.avatar, | ||||||
|                         ), |                         ), | ||||||
|                         onTap: () { |                         onTap: () { | ||||||
|  |                           if (doExpand) { | ||||||
|  |                             setState(() => _focusChannel = channel); | ||||||
|  |                             return; | ||||||
|  |                           } | ||||||
|                           GoRouter.of(context).pushNamed( |                           GoRouter.of(context).pushNamed( | ||||||
|                             'chatRoom', |                             'chatRoom', | ||||||
|                             pathParameters: { |                             pathParameters: { | ||||||
| @@ -272,7 +294,53 @@ class _ChatScreenState extends State<ChatScreen> { | |||||||
|                               'alias': channel.alias, |                               'alias': channel.alias, | ||||||
|                             }, |                             }, | ||||||
|                           ).then((value) { |                           ).then((value) { | ||||||
|                           if (value == true) _refreshChannels(); |                             if (mounted) _refreshChannels(noRemote: true); | ||||||
|  |                           }); | ||||||
|  |                         }, | ||||||
|  |                       ); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     return ListTile( | ||||||
|  |                       title: Row( | ||||||
|  |                         children: [ | ||||||
|  |                           Expanded(child: Text(channel.name)), | ||||||
|  |                           const Gap(8), | ||||||
|  |                           if (_unreadCounts?[channel.id] != null) | ||||||
|  |                             Badge( | ||||||
|  |                               label: Text('${_unreadCounts![channel.id]}'), | ||||||
|  |                             ), | ||||||
|  |                         ], | ||||||
|  |                       ), | ||||||
|  |                       subtitle: lastMessage != null | ||||||
|  |                           ? Text( | ||||||
|  |                               '${ud.getAccountFromCache(lastMessage.sender.accountId)?.nick}: ${lastMessage.body['text'] ?? 'Unable preview'}', | ||||||
|  |                               maxLines: 1, | ||||||
|  |                               overflow: TextOverflow.ellipsis, | ||||||
|  |                             ) | ||||||
|  |                           : Text( | ||||||
|  |                               channel.description, | ||||||
|  |                               maxLines: 1, | ||||||
|  |                               overflow: TextOverflow.ellipsis, | ||||||
|  |                             ), | ||||||
|  |                       contentPadding: | ||||||
|  |                           const EdgeInsets.symmetric(horizontal: 16), | ||||||
|  |                       leading: AccountImage( | ||||||
|  |                         content: null, | ||||||
|  |                         fallbackWidget: const Icon(Symbols.chat, size: 20), | ||||||
|  |                       ), | ||||||
|  |                       onTap: () { | ||||||
|  |                         if (doExpand) { | ||||||
|  |                           setState(() => _focusChannel = channel); | ||||||
|  |                           return; | ||||||
|  |                         } | ||||||
|  |                         GoRouter.of(context).pushNamed( | ||||||
|  |                           'chatRoom', | ||||||
|  |                           pathParameters: { | ||||||
|  |                             'scope': channel.realm?.alias ?? 'global', | ||||||
|  |                             'alias': channel.alias, | ||||||
|  |                           }, | ||||||
|  |                         ).then((value) { | ||||||
|  |                           if (value == true) _refreshChannels(noRemote: true); | ||||||
|                         }); |                         }); | ||||||
|                       }, |                       }, | ||||||
|                     ); |                     ); | ||||||
| @@ -284,5 +352,27 @@ class _ChatScreenState extends State<ChatScreen> { | |||||||
|         ], |         ], | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|  |     if (doExpand) { | ||||||
|  |       return AppBackground( | ||||||
|  |         isRoot: true, | ||||||
|  |         child: Row( | ||||||
|  |           children: [ | ||||||
|  |             SizedBox(width: 340, child: chatList), | ||||||
|  |             const VerticalDivider(width: 1), | ||||||
|  |             if (_focusChannel != null) | ||||||
|  |               Expanded( | ||||||
|  |                 child: ChatRoomScreen( | ||||||
|  |                   key: ValueKey(_focusChannel!.id), | ||||||
|  |                   scope: _focusChannel!.realm?.alias ?? 'global', | ||||||
|  |                   alias: _focusChannel!.alias, | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return chatList; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -104,7 +104,7 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> { | |||||||
|     try { |     try { | ||||||
|       final sn = context.read<SnNetworkProvider>(); |       final sn = context.read<SnNetworkProvider>(); | ||||||
|       await sn.client.delete( |       await sn.client.delete( | ||||||
|         '/cgi/im/channels/${_channel!.realm?.alias ?? 'global'}/${_channel!.id}/members/me', |         '/cgi/im/channels/${_channel!.realm?.alias ?? 'global'}/${_channel!.alias}/me', | ||||||
|       ); |       ); | ||||||
|       if (!mounted) return; |       if (!mounted) return; | ||||||
|       Navigator.pop(context, false); |       Navigator.pop(context, false); | ||||||
|   | |||||||
| @@ -95,6 +95,10 @@ class _ChatManageScreenState extends State<ChatManageScreen> { | |||||||
|       'description': _descriptionController.text, |       'description': _descriptionController.text, | ||||||
|       'is_public': _isPublic, |       'is_public': _isPublic, | ||||||
|       'is_community': _isCommunity, |       'is_community': _isCommunity, | ||||||
|  |       if (_editingChannel != null && _belongToRealm == null) | ||||||
|  |         'new_belongs_realm': 'global' | ||||||
|  |       else if (_editingChannel != null && _belongToRealm?.id != _editingChannel?.realm?.id) | ||||||
|  |         'new_belongs_realm': _belongToRealm!.alias, | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     try { |     try { | ||||||
| @@ -171,7 +175,6 @@ class _ChatManageScreenState extends State<ChatManageScreen> { | |||||||
|                 items: [ |                 items: [ | ||||||
|                   ...(_realms?.map( |                   ...(_realms?.map( | ||||||
|                         (SnRealm item) => DropdownMenuItem<SnRealm>( |                         (SnRealm item) => DropdownMenuItem<SnRealm>( | ||||||
|                           enabled: _editingChannel == null || _editingChannel?.realmId == item.id, |  | ||||||
|                           value: item, |                           value: item, | ||||||
|                           child: Row( |                           child: Row( | ||||||
|                             children: [ |                             children: [ | ||||||
| @@ -204,7 +207,6 @@ class _ChatManageScreenState extends State<ChatManageScreen> { | |||||||
|                       ) ?? |                       ) ?? | ||||||
|                       []), |                       []), | ||||||
|                   DropdownMenuItem<SnRealm>( |                   DropdownMenuItem<SnRealm>( | ||||||
|                     enabled: _editingChannel == null, |  | ||||||
|                     value: null, |                     value: null, | ||||||
|                     child: Row( |                     child: Row( | ||||||
|                       children: [ |                       children: [ | ||||||
|   | |||||||
| @@ -39,7 +39,8 @@ class ChatRoomScreen extends StatefulWidget { | |||||||
|   final String alias; |   final String alias; | ||||||
|   final ChatRoomScreenExtra? extra; |   final ChatRoomScreenExtra? extra; | ||||||
|  |  | ||||||
|   const ChatRoomScreen({super.key, required this.scope, required this.alias, this.extra}); |   const ChatRoomScreen( | ||||||
|  |       {super.key, required this.scope, required this.alias, this.extra}); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   State<ChatRoomScreen> createState() => _ChatRoomScreenState(); |   State<ChatRoomScreen> createState() => _ChatRoomScreenState(); | ||||||
| @@ -58,6 +59,7 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> { | |||||||
|  |  | ||||||
|   StreamSubscription? _wsSubscription; |   StreamSubscription? _wsSubscription; | ||||||
|  |  | ||||||
|  |   // TODO fetch user identity and ask them to join the channel or not | ||||||
|   Future<void> _fetchChannel() async { |   Future<void> _fetchChannel() async { | ||||||
|     setState(() => _isBusy = true); |     setState(() => _isBusy = true); | ||||||
|  |  | ||||||
| @@ -191,10 +193,12 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> { | |||||||
|         WidgetsBinding.instance.addPostFrameCallback((_) { |         WidgetsBinding.instance.addPostFrameCallback((_) { | ||||||
|           log('[ChatInput] Setting initial text and attachments...'); |           log('[ChatInput] Setting initial text and attachments...'); | ||||||
|           if (widget.extra!.initialText != null) { |           if (widget.extra!.initialText != null) { | ||||||
|             _inputGlobalKey.currentState?.setInitialText(widget.extra!.initialText!); |             _inputGlobalKey.currentState | ||||||
|  |                 ?.setInitialText(widget.extra!.initialText!); | ||||||
|           } |           } | ||||||
|           if (widget.extra!.initialAttachments != null) { |           if (widget.extra!.initialAttachments != null) { | ||||||
|             _inputGlobalKey.currentState?.setInitialAttachments(widget.extra!.initialAttachments!); |             _inputGlobalKey.currentState | ||||||
|  |                 ?.setInitialAttachments(widget.extra!.initialAttachments!); | ||||||
|           } |           } | ||||||
|         }); |         }); | ||||||
|       } |       } | ||||||
| @@ -240,12 +244,15 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> { | |||||||
|       appBar: AppBar( |       appBar: AppBar( | ||||||
|         title: Text( |         title: Text( | ||||||
|           _channel?.type == 1 |           _channel?.type == 1 | ||||||
|               ? ud.getAccountFromCache(_otherMember?.accountId)?.nick ?? _channel!.name |               ? ud.getAccountFromCache(_otherMember?.accountId)?.nick ?? | ||||||
|  |                   _channel!.name | ||||||
|               : _channel?.name ?? 'loading'.tr(), |               : _channel?.name ?? 'loading'.tr(), | ||||||
|         ), |         ), | ||||||
|         actions: [ |         actions: [ | ||||||
|           IconButton( |           IconButton( | ||||||
|             icon: _ongoingCall == null ? const Icon(Symbols.call) : const Icon(Symbols.call_end), |             icon: _ongoingCall == null | ||||||
|  |                 ? const Icon(Symbols.call) | ||||||
|  |                 : const Icon(Symbols.call_end), | ||||||
|             onPressed: _isCalling |             onPressed: _isCalling | ||||||
|                 ? null |                 ? null | ||||||
|                 : _ongoingCall == null |                 : _ongoingCall == null | ||||||
| @@ -295,9 +302,9 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> { | |||||||
|                       ) |                       ) | ||||||
|                   ], |                   ], | ||||||
|                 ), |                 ), | ||||||
|               ) |               ).height(_ongoingCall != null ? 54 : 0, animate: true).animate( | ||||||
|                   .height(_ongoingCall != null ? 54 : 0, animate: true) |                   const Duration(milliseconds: 300), | ||||||
|                   .animate(const Duration(milliseconds: 300), Curves.fastLinearToSlowEaseIn), |                   Curves.fastLinearToSlowEaseIn), | ||||||
|               if (_messageController.isPending) |               if (_messageController.isPending) | ||||||
|                 Expanded( |                 Expanded( | ||||||
|                   child: const CircularProgressIndicator().center(), |                   child: const CircularProgressIndicator().center(), | ||||||
| @@ -315,6 +322,7 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> { | |||||||
|                     }, |                     }, | ||||||
|                     itemBuilder: (context, idx) { |                     itemBuilder: (context, idx) { | ||||||
|                       final message = _messageController.messages[idx]; |                       final message = _messageController.messages[idx]; | ||||||
|  |                       _messageController.readEvent(message.id); | ||||||
|  |  | ||||||
|                       bool canMerge = false, canMergePrevious = false; |                       bool canMerge = false, canMergePrevious = false; | ||||||
|                       if (idx > 0) { |                       if (idx > 0) { | ||||||
| @@ -336,7 +344,8 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> { | |||||||
|                           data: message, |                           data: message, | ||||||
|                           isMerged: canMerge, |                           isMerged: canMerge, | ||||||
|                           hasMerged: canMergePrevious, |                           hasMerged: canMergePrevious, | ||||||
|                           isPending: _messageController.unconfirmedMessages.contains(message.uuid), |                           isPending: _messageController.unconfirmedMessages | ||||||
|  |                               .contains(message.uuid), | ||||||
|                           onReply: (value) { |                           onReply: (value) { | ||||||
|                             _inputGlobalKey.currentState?.setReply(value); |                             _inputGlobalKey.currentState?.setReply(value); | ||||||
|                           }, |                           }, | ||||||
|   | |||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | import 'package:dropdown_button2/dropdown_button2.dart'; | ||||||
| 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_expandable_fab/flutter_expandable_fab.dart'; | import 'package:flutter_expandable_fab/flutter_expandable_fab.dart'; | ||||||
| @@ -8,7 +9,10 @@ import 'package:provider/provider.dart'; | |||||||
| import 'package:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
| import 'package:surface/providers/post.dart'; | import 'package:surface/providers/post.dart'; | ||||||
| import 'package:surface/providers/sn_network.dart'; | import 'package:surface/providers/sn_network.dart'; | ||||||
|  | import 'package:surface/providers/sn_realm.dart'; | ||||||
| import 'package:surface/types/post.dart'; | import 'package:surface/types/post.dart'; | ||||||
|  | import 'package:surface/types/realm.dart'; | ||||||
|  | import 'package:surface/widgets/account/account_image.dart'; | ||||||
| import 'package:surface/widgets/app_bar_leading.dart'; | import 'package:surface/widgets/app_bar_leading.dart'; | ||||||
| import 'package:surface/widgets/dialog.dart'; | import 'package:surface/widgets/dialog.dart'; | ||||||
| import 'package:surface/widgets/navigation/app_scaffold.dart'; | import 'package:surface/widgets/navigation/app_scaffold.dart'; | ||||||
| @@ -35,61 +39,54 @@ class ExploreScreen extends StatefulWidget { | |||||||
|   State<ExploreScreen> createState() => _ExploreScreenState(); |   State<ExploreScreen> createState() => _ExploreScreenState(); | ||||||
| } | } | ||||||
|  |  | ||||||
| class _ExploreScreenState extends State<ExploreScreen> { | // You know what? I'm not going to make this a global variable. | ||||||
|  | // Cuz the global key make the selected category not update to child widget when the category is changed. | ||||||
|  | SnPostCategory? _selectedCategory; | ||||||
|  |  | ||||||
|  | class _ExploreScreenState extends State<ExploreScreen> | ||||||
|  |     with SingleTickerProviderStateMixin { | ||||||
|  |   late final TabController _tabController = | ||||||
|  |       TabController(length: 4, vsync: this); | ||||||
|  |  | ||||||
|   final _fabKey = GlobalKey<ExpandableFabState>(); |   final _fabKey = GlobalKey<ExpandableFabState>(); | ||||||
|  |   final _listKeys = List.generate(4, (_) => GlobalKey<_PostListWidgetState>()); | ||||||
|  |  | ||||||
|   bool _isBusy = true; |  | ||||||
|  |  | ||||||
|   final List<SnPost> _posts = List.empty(growable: true); |  | ||||||
|   final List<SnPostCategory> _categories = List.empty(growable: true); |   final List<SnPostCategory> _categories = List.empty(growable: true); | ||||||
|   int? _postCount; |  | ||||||
|  |  | ||||||
|   String? _selectedCategory; |  | ||||||
|  |  | ||||||
|   Future<void> _fetchCategories() async { |   Future<void> _fetchCategories() async { | ||||||
|     _categories.clear(); |     _categories.clear(); | ||||||
|     try { |     try { | ||||||
|       final sn = context.read<SnNetworkProvider>(); |       final sn = context.read<SnNetworkProvider>(); | ||||||
|       final resp = await sn.client.get('/cgi/co/categories?take=100'); |       final resp = await sn.client.get('/cgi/co/categories?take=100'); | ||||||
|       _categories.addAll(resp.data.map((e) => SnPostCategory.fromJson(e)).cast<SnPostCategory>() ?? []); |       setState(() { | ||||||
|  |         _categories.addAll(resp.data | ||||||
|  |                 .map((e) => SnPostCategory.fromJson(e)) | ||||||
|  |                 .cast<SnPostCategory>() ?? | ||||||
|  |             []); | ||||||
|  |       }); | ||||||
|     } catch (err) { |     } catch (err) { | ||||||
|       if (!mounted) return; |       if (mounted) context.showErrorDialog(err); | ||||||
|       context.showErrorDialog(err); |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   Future<void> _fetchPosts() async { |   void _clearFilter() { | ||||||
|     if (_postCount != null && _posts.length >= _postCount!) return; |     _selectedCategory = null; | ||||||
|  |  | ||||||
|     setState(() => _isBusy = true); |  | ||||||
|  |  | ||||||
|     final pt = context.read<SnPostContentProvider>(); |  | ||||||
|     final result = await pt.listPosts( |  | ||||||
|       take: 10, |  | ||||||
|       offset: _posts.length, |  | ||||||
|       categories: _selectedCategory != null ? [_selectedCategory!] : null, |  | ||||||
|     ); |  | ||||||
|     final out = result.$1; |  | ||||||
|  |  | ||||||
|     if (!mounted) return; |  | ||||||
|  |  | ||||||
|     _postCount = result.$2; |  | ||||||
|     _posts.addAll(out); |  | ||||||
|  |  | ||||||
|     if (mounted) setState(() => _isBusy = false); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   Future<void> _refreshPosts() { |  | ||||||
|     _postCount = null; |  | ||||||
|     _posts.clear(); |  | ||||||
|     return _fetchPosts(); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   void initState() { |   void initState() { | ||||||
|     super.initState(); |  | ||||||
|     _fetchPosts(); |  | ||||||
|     _fetchCategories(); |     _fetchCategories(); | ||||||
|  |     super.initState(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void dispose() { | ||||||
|  |     _tabController.dispose(); | ||||||
|  |     super.dispose(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Future<void> refreshPosts() async { | ||||||
|  |     await _listKeys[_tabController.index].currentState?.refreshPosts(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
| @@ -102,20 +99,27 @@ class _ExploreScreenState extends State<ExploreScreen> { | |||||||
|         type: ExpandableFabType.up, |         type: ExpandableFabType.up, | ||||||
|         childrenAnimation: ExpandableFabAnimation.none, |         childrenAnimation: ExpandableFabAnimation.none, | ||||||
|         overlayStyle: ExpandableFabOverlayStyle( |         overlayStyle: ExpandableFabOverlayStyle( | ||||||
|           color: Theme.of(context).colorScheme.surface.withAlpha((255 * 0.5).round()), |           color: Theme.of(context) | ||||||
|  |               .colorScheme | ||||||
|  |               .surface | ||||||
|  |               .withAlpha((255 * 0.5).round()), | ||||||
|         ), |         ), | ||||||
|         openButtonBuilder: RotateFloatingActionButtonBuilder( |         openButtonBuilder: RotateFloatingActionButtonBuilder( | ||||||
|           child: const Icon(Symbols.add, size: 28), |           child: const Icon(Symbols.add, size: 28), | ||||||
|           fabSize: ExpandableFabSize.regular, |           fabSize: ExpandableFabSize.regular, | ||||||
|           foregroundColor: Theme.of(context).floatingActionButtonTheme.foregroundColor, |           foregroundColor: | ||||||
|           backgroundColor: Theme.of(context).floatingActionButtonTheme.backgroundColor, |               Theme.of(context).floatingActionButtonTheme.foregroundColor, | ||||||
|  |           backgroundColor: | ||||||
|  |               Theme.of(context).floatingActionButtonTheme.backgroundColor, | ||||||
|           shape: const CircleBorder(), |           shape: const CircleBorder(), | ||||||
|         ), |         ), | ||||||
|         closeButtonBuilder: DefaultFloatingActionButtonBuilder( |         closeButtonBuilder: DefaultFloatingActionButtonBuilder( | ||||||
|           child: const Icon(Symbols.close, size: 28), |           child: const Icon(Symbols.close, size: 28), | ||||||
|           fabSize: ExpandableFabSize.regular, |           fabSize: ExpandableFabSize.regular, | ||||||
|           foregroundColor: Theme.of(context).floatingActionButtonTheme.foregroundColor, |           foregroundColor: | ||||||
|           backgroundColor: Theme.of(context).floatingActionButtonTheme.backgroundColor, |               Theme.of(context).floatingActionButtonTheme.foregroundColor, | ||||||
|  |           backgroundColor: | ||||||
|  |               Theme.of(context).floatingActionButtonTheme.backgroundColor, | ||||||
|           shape: const CircleBorder(), |           shape: const CircleBorder(), | ||||||
|         ), |         ), | ||||||
|         children: [ |         children: [ | ||||||
| @@ -131,7 +135,7 @@ class _ExploreScreenState extends State<ExploreScreen> { | |||||||
|                     'mode': 'stories', |                     'mode': 'stories', | ||||||
|                   }).then((value) { |                   }).then((value) { | ||||||
|                     if (value == true) { |                     if (value == true) { | ||||||
|                       _refreshPosts(); |                       refreshPosts(); | ||||||
|                     } |                     } | ||||||
|                   }); |                   }); | ||||||
|                   _fabKey.currentState!.toggle(); |                   _fabKey.currentState!.toggle(); | ||||||
| @@ -152,7 +156,7 @@ class _ExploreScreenState extends State<ExploreScreen> { | |||||||
|                     'mode': 'articles', |                     'mode': 'articles', | ||||||
|                   }).then((value) { |                   }).then((value) { | ||||||
|                     if (value == true) { |                     if (value == true) { | ||||||
|                       _refreshPosts(); |                       refreshPosts(); | ||||||
|                     } |                     } | ||||||
|                   }); |                   }); | ||||||
|                   _fabKey.currentState!.toggle(); |                   _fabKey.currentState!.toggle(); | ||||||
| @@ -173,7 +177,7 @@ class _ExploreScreenState extends State<ExploreScreen> { | |||||||
|                     'mode': 'questions', |                     'mode': 'questions', | ||||||
|                   }).then((value) { |                   }).then((value) { | ||||||
|                     if (value == true) { |                     if (value == true) { | ||||||
|                       _refreshPosts(); |                       refreshPosts(); | ||||||
|                     } |                     } | ||||||
|                   }); |                   }); | ||||||
|                   _fabKey.currentState!.toggle(); |                   _fabKey.currentState!.toggle(); | ||||||
| @@ -194,7 +198,7 @@ class _ExploreScreenState extends State<ExploreScreen> { | |||||||
|                     'mode': 'videos', |                     'mode': 'videos', | ||||||
|                   }).then((value) { |                   }).then((value) { | ||||||
|                     if (value == true) { |                     if (value == true) { | ||||||
|                       _refreshPosts(); |                       refreshPosts(); | ||||||
|                     } |                     } | ||||||
|                   }); |                   }); | ||||||
|                   _fabKey.currentState!.toggle(); |                   _fabKey.currentState!.toggle(); | ||||||
| @@ -205,17 +209,34 @@ class _ExploreScreenState extends State<ExploreScreen> { | |||||||
|           ), |           ), | ||||||
|         ], |         ], | ||||||
|       ), |       ), | ||||||
|       body: RefreshIndicator( |       body: NestedScrollView( | ||||||
|         displacement: 40 + MediaQuery.of(context).padding.top, |         headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { | ||||||
|         onRefresh: () => _refreshPosts(), |           return [ | ||||||
|         child: CustomScrollView( |             SliverOverlapAbsorber( | ||||||
|           slivers: [ |               handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), | ||||||
|             SliverAppBar( |               sliver: SliverAppBar( | ||||||
|                 leading: AutoAppBarLeading(), |                 leading: AutoAppBarLeading(), | ||||||
|                 title: Text('screenExplore').tr(), |                 title: Text('screenExplore').tr(), | ||||||
|                 floating: true, |                 floating: true, | ||||||
|                 snap: true, |                 snap: true, | ||||||
|                 actions: [ |                 actions: [ | ||||||
|  |                   IconButton( | ||||||
|  |                     icon: const Icon(Symbols.category), | ||||||
|  |                     onPressed: () { | ||||||
|  |                       showModalBottomSheet( | ||||||
|  |                         context: context, | ||||||
|  |                         builder: (context) => _PostCategoryPickerPopup( | ||||||
|  |                           categories: _categories, | ||||||
|  |                           selected: _selectedCategory, | ||||||
|  |                         ), | ||||||
|  |                       ).then((value) { | ||||||
|  |                         if (value != null && context.mounted) { | ||||||
|  |                           _selectedCategory = value == false ? null : value; | ||||||
|  |                           refreshPosts(); | ||||||
|  |                         } | ||||||
|  |                       }); | ||||||
|  |                     }, | ||||||
|  |                   ), | ||||||
|                   IconButton( |                   IconButton( | ||||||
|                     icon: const Icon(Symbols.search), |                     icon: const Icon(Symbols.search), | ||||||
|                     onPressed: () { |                     onPressed: () { | ||||||
| @@ -224,41 +245,287 @@ class _ExploreScreenState extends State<ExploreScreen> { | |||||||
|                   ), |                   ), | ||||||
|                   const Gap(8), |                   const Gap(8), | ||||||
|                 ], |                 ], | ||||||
|               bottom: PreferredSize( |                 bottom: TabBar( | ||||||
|                 preferredSize: const Size.fromHeight(50), |                   controller: _tabController, | ||||||
|                 child: SizedBox( |                   tabs: [ | ||||||
|                   height: 50, |                     Tab( | ||||||
|                   child: SingleChildScrollView( |  | ||||||
|                     scrollDirection: Axis.horizontal, |  | ||||||
|                     padding: const EdgeInsets.only(left: 8, right: 8, bottom: 12), |  | ||||||
|                       child: Row( |                       child: Row( | ||||||
|                       mainAxisAlignment: MainAxisAlignment.center, |                         mainAxisSize: MainAxisSize.min, | ||||||
|                       children: _categories.map((ele) { |                         crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|                         return StyledWidget(ChoiceChip( |                         children: [ | ||||||
|                           avatar: Icon(kCategoryIcons[ele.alias] ?? Symbols.question_mark), |                           Icon(Symbols.globe, | ||||||
|                           label: Text( |                               size: 20, | ||||||
|                             'postCategory${ele.alias.capitalize()}'.trExists() |                               color: Theme.of(context) | ||||||
|                                 ? 'postCategory${ele.alias.capitalize()}'.tr() |                                   .appBarTheme | ||||||
|                                 : ele.name, |                                   .foregroundColor), | ||||||
|  |                           const Gap(8), | ||||||
|  |                           Flexible( | ||||||
|  |                             child: Text( | ||||||
|  |                               'postChannelGlobal', | ||||||
|  |                               maxLines: 1, | ||||||
|  |                             ).tr().textColor( | ||||||
|  |                                 Theme.of(context).appBarTheme.foregroundColor), | ||||||
|                           ), |                           ), | ||||||
|                           selected: _selectedCategory == ele.alias, |                         ], | ||||||
|                           onSelected: (value) { |                       ), | ||||||
|                             _selectedCategory = value ? ele.alias : null; |                     ), | ||||||
|                             _refreshPosts(); |                     Tab( | ||||||
|  |                       child: Row( | ||||||
|  |                         mainAxisSize: MainAxisSize.min, | ||||||
|  |                         crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|  |                         children: [ | ||||||
|  |                           Icon(Symbols.group, | ||||||
|  |                               size: 20, | ||||||
|  |                               color: Theme.of(context) | ||||||
|  |                                   .appBarTheme | ||||||
|  |                                   .foregroundColor), | ||||||
|  |                           const Gap(8), | ||||||
|  |                           Flexible( | ||||||
|  |                             child: Text( | ||||||
|  |                               'postChannelFriends', | ||||||
|  |                               maxLines: 1, | ||||||
|  |                               textAlign: TextAlign.center, | ||||||
|  |                             ).tr().textColor( | ||||||
|  |                                 Theme.of(context).appBarTheme.foregroundColor), | ||||||
|  |                           ), | ||||||
|  |                         ], | ||||||
|  |                       ), | ||||||
|  |                     ), | ||||||
|  |                     Tab( | ||||||
|  |                       child: Row( | ||||||
|  |                         mainAxisSize: MainAxisSize.min, | ||||||
|  |                         crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|  |                         children: [ | ||||||
|  |                           Icon(Symbols.subscriptions, | ||||||
|  |                               size: 20, | ||||||
|  |                               color: Theme.of(context) | ||||||
|  |                                   .appBarTheme | ||||||
|  |                                   .foregroundColor), | ||||||
|  |                           const Gap(8), | ||||||
|  |                           Flexible( | ||||||
|  |                             child: Text( | ||||||
|  |                               'postChannelFollowing', | ||||||
|  |                               maxLines: 1, | ||||||
|  |                             ).tr().textColor( | ||||||
|  |                                 Theme.of(context).appBarTheme.foregroundColor), | ||||||
|  |                           ), | ||||||
|  |                         ], | ||||||
|  |                       ), | ||||||
|  |                     ), | ||||||
|  |                     Tab( | ||||||
|  |                       child: Row( | ||||||
|  |                         mainAxisSize: MainAxisSize.min, | ||||||
|  |                         crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|  |                         children: [ | ||||||
|  |                           Icon(Symbols.workspaces, | ||||||
|  |                               size: 20, | ||||||
|  |                               color: Theme.of(context) | ||||||
|  |                                   .appBarTheme | ||||||
|  |                                   .foregroundColor), | ||||||
|  |                           const Gap(8), | ||||||
|  |                           Flexible( | ||||||
|  |                             child: Text( | ||||||
|  |                               'postChannelRealm', | ||||||
|  |                               maxLines: 1, | ||||||
|  |                             ).tr().textColor( | ||||||
|  |                                 Theme.of(context).appBarTheme.foregroundColor), | ||||||
|  |                           ), | ||||||
|  |                         ], | ||||||
|  |                       ), | ||||||
|  |                     ), | ||||||
|  |                   ], | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |           ]; | ||||||
|         }, |         }, | ||||||
|                         )).padding(horizontal: 4); |         body: TabBarView( | ||||||
|                       }).toList(), |           controller: _tabController, | ||||||
|  |           children: [ | ||||||
|  |             _PostListWidget( | ||||||
|  |               key: _listKeys[0], | ||||||
|  |               onClearFilter: _clearFilter, | ||||||
|  |             ), | ||||||
|  |             _PostListWidget( | ||||||
|  |               key: _listKeys[1], | ||||||
|  |               channel: 'friends', | ||||||
|  |               onClearFilter: _clearFilter, | ||||||
|  |             ), | ||||||
|  |             _PostListWidget( | ||||||
|  |               key: _listKeys[2], | ||||||
|  |               channel: 'following', | ||||||
|  |               onClearFilter: _clearFilter, | ||||||
|  |             ), | ||||||
|  |             _PostListWidget( | ||||||
|  |               key: _listKeys[3], | ||||||
|  |               withRealm: true, | ||||||
|  |               onClearFilter: _clearFilter, | ||||||
|  |             ), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _PostListWidget extends StatefulWidget { | ||||||
|  |   final String? channel; | ||||||
|  |   final bool withRealm; | ||||||
|  |   final Function onClearFilter; | ||||||
|  |  | ||||||
|  |   const _PostListWidget( | ||||||
|  |       {super.key, | ||||||
|  |       this.channel, | ||||||
|  |       this.withRealm = false, | ||||||
|  |       required this.onClearFilter}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   State<_PostListWidget> createState() => _PostListWidgetState(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _PostListWidgetState extends State<_PostListWidget> { | ||||||
|  |   bool _isBusy = false; | ||||||
|  |  | ||||||
|  |   final List<SnPost> _posts = List.empty(growable: true); | ||||||
|  |   final List<SnRealm> _realms = List.empty(growable: true); | ||||||
|  |   SnRealm? _selectedRealm; | ||||||
|  |   int? _postCount; | ||||||
|  |  | ||||||
|  |   Future<void> _fetchRealms() async { | ||||||
|  |     try { | ||||||
|  |       final rels = context.read<SnRealmProvider>(); | ||||||
|  |       final out = await rels.listAvailableRealms(); | ||||||
|  |       setState(() { | ||||||
|  |         _realms.addAll(out); | ||||||
|  |         _selectedRealm = out.firstOrNull; | ||||||
|  |       }); | ||||||
|  |     } catch (err) { | ||||||
|  |       if (!mounted) return; | ||||||
|  |       context.showErrorDialog(err); | ||||||
|  |       rethrow; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Future<void> _fetchPosts() async { | ||||||
|  |     if (_postCount != null && _posts.length >= _postCount!) return; | ||||||
|  |  | ||||||
|  |     setState(() => _isBusy = true); | ||||||
|  |  | ||||||
|  |     final pt = context.read<SnPostContentProvider>(); | ||||||
|  |     final result = await pt.listPosts( | ||||||
|  |       take: 10, | ||||||
|  |       offset: _posts.length, | ||||||
|  |       categories: _selectedCategory != null ? [_selectedCategory!.alias] : null, | ||||||
|  |       channel: widget.channel, | ||||||
|  |       realm: _selectedRealm?.alias, | ||||||
|  |     ); | ||||||
|  |     final out = result.$1; | ||||||
|  |  | ||||||
|  |     if (!mounted) return; | ||||||
|  |  | ||||||
|  |     _postCount = result.$2; | ||||||
|  |     _posts.addAll(out); | ||||||
|  |  | ||||||
|  |     if (mounted) setState(() => _isBusy = false); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Future<void> refreshPosts() { | ||||||
|  |     _postCount = null; | ||||||
|  |     _posts.clear(); | ||||||
|  |     return _fetchPosts(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void initState() { | ||||||
|  |     super.initState(); | ||||||
|  |     if (widget.withRealm) { | ||||||
|  |       _fetchRealms().then((_) { | ||||||
|  |         _fetchPosts(); | ||||||
|  |       }); | ||||||
|  |     } else { | ||||||
|  |       _fetchPosts(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return Column( | ||||||
|  |       children: [ | ||||||
|  |         if (_selectedCategory != null) | ||||||
|  |           MaterialBanner( | ||||||
|  |             content: Text( | ||||||
|  |               'postFilterWithCategory'.tr(args: [ | ||||||
|  |                 'postCategory${_selectedCategory!.alias.capitalize()}'.trExists() | ||||||
|  |                     ? 'postCategory${_selectedCategory!.alias.capitalize()}' | ||||||
|  |                         .tr() | ||||||
|  |                     : _selectedCategory!.name, | ||||||
|  |               ]), | ||||||
|  |             ), | ||||||
|  |             leading: Icon(kCategoryIcons[_selectedCategory!.alias] ?? | ||||||
|  |                 Symbols.question_mark), | ||||||
|  |             actions: [ | ||||||
|  |               IconButton( | ||||||
|  |                 icon: const Icon(Symbols.clear), | ||||||
|  |                 onPressed: () { | ||||||
|  |                   widget.onClearFilter.call(); | ||||||
|  |                   refreshPosts(); | ||||||
|  |                 }, | ||||||
|  |               ), | ||||||
|  |             ], | ||||||
|  |             padding: const EdgeInsets.only(left: 20, right: 4), | ||||||
|  |           ), | ||||||
|  |         if (widget.withRealm) | ||||||
|  |           DropdownButtonHideUnderline( | ||||||
|  |             child: DropdownButton2<SnRealm>( | ||||||
|  |               isExpanded: true, | ||||||
|  |               items: _realms | ||||||
|  |                   .map( | ||||||
|  |                     (ele) => DropdownMenuItem<SnRealm>( | ||||||
|  |                       value: ele, | ||||||
|  |                       child: Row( | ||||||
|  |                         children: [ | ||||||
|  |                           AccountImage( | ||||||
|  |                             content: ele.avatar, | ||||||
|  |                             fallbackWidget: const Icon(Symbols.group, size: 16), | ||||||
|  |                             radius: 14, | ||||||
|  |                           ), | ||||||
|  |                           const Gap(8), | ||||||
|  |                           Text( | ||||||
|  |                             ele.name, | ||||||
|  |                             style: Theme.of(context).textTheme.bodyMedium, | ||||||
|  |                           ), | ||||||
|  |                         ], | ||||||
|  |                       ), | ||||||
|  |                     ), | ||||||
|  |                   ) | ||||||
|  |                   .toList(), | ||||||
|  |               value: _selectedRealm, | ||||||
|  |               onChanged: (SnRealm? value) { | ||||||
|  |                 setState(() => _selectedRealm = value); | ||||||
|  |                 refreshPosts(); | ||||||
|  |               }, | ||||||
|  |               buttonStyleData: const ButtonStyleData( | ||||||
|  |                 padding: EdgeInsets.only(left: 4, right: 12), | ||||||
|  |               ), | ||||||
|  |               menuItemStyleData: const MenuItemStyleData( | ||||||
|  |                 height: 48, | ||||||
|               ), |               ), | ||||||
|             ), |             ), | ||||||
|           ), |           ), | ||||||
|               ), |         if (widget.withRealm) const Divider(height: 1), | ||||||
|             ), |         Expanded( | ||||||
|             const SliverGap(12), |           child: MediaQuery.removePadding( | ||||||
|             SliverInfiniteList( |             context: context, | ||||||
|  |             removeTop: true, | ||||||
|  |             child: RefreshIndicator( | ||||||
|  |               displacement: 40 + MediaQuery.of(context).padding.top, | ||||||
|  |               onRefresh: () => refreshPosts(), | ||||||
|  |               child: InfiniteList( | ||||||
|                 itemCount: _posts.length, |                 itemCount: _posts.length, | ||||||
|                 isLoading: _isBusy, |                 isLoading: _isBusy, | ||||||
|                 centerLoading: true, |                 centerLoading: true, | ||||||
|               hasReachedMax: _postCount != null && _posts.length >= _postCount!, |                 hasReachedMax: | ||||||
|  |                     _postCount != null && _posts.length >= _postCount!, | ||||||
|                 onFetchData: _fetchPosts, |                 onFetchData: _fetchPosts, | ||||||
|                 itemBuilder: (context, idx) { |                 itemBuilder: (context, idx) { | ||||||
|                   return OpenablePostItem( |                   return OpenablePostItem( | ||||||
| @@ -268,15 +535,93 @@ class _ExploreScreenState extends State<ExploreScreen> { | |||||||
|                       setState(() => _posts[idx] = data); |                       setState(() => _posts[idx] = data); | ||||||
|                     }, |                     }, | ||||||
|                     onDeleted: () { |                     onDeleted: () { | ||||||
|                     _refreshPosts(); |                       refreshPosts(); | ||||||
|                     }, |                     }, | ||||||
|                   ); |                   ); | ||||||
|                 }, |                 }, | ||||||
|                 separatorBuilder: (_, __) => const Gap(8), |                 separatorBuilder: (_, __) => const Gap(8), | ||||||
|               ), |               ), | ||||||
|  |             ), | ||||||
|  |           ).padding(top: 8), | ||||||
|  |         ), | ||||||
|  |       ], | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _PostCategoryPickerPopup extends StatelessWidget { | ||||||
|  |   final List<SnPostCategory> categories; | ||||||
|  |   final SnPostCategory? selected; | ||||||
|  |  | ||||||
|  |   const _PostCategoryPickerPopup({required this.categories, this.selected}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return Column( | ||||||
|  |       crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |       children: [ | ||||||
|  |         Row( | ||||||
|  |           crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|  |           children: [ | ||||||
|  |             const Icon(Symbols.category, size: 24), | ||||||
|  |             const Gap(16), | ||||||
|  |             Text('postCategory') | ||||||
|  |                 .tr() | ||||||
|  |                 .textStyle(Theme.of(context).textTheme.titleLarge!), | ||||||
|  |           ], | ||||||
|  |         ).padding(horizontal: 20, top: 16, bottom: 12), | ||||||
|  |         ListTile( | ||||||
|  |           leading: const Icon(Symbols.clear), | ||||||
|  |           title: Text('postFilterReset').tr(), | ||||||
|  |           subtitle: Text('postFilterResetDescription').tr(), | ||||||
|  |           contentPadding: const EdgeInsets.symmetric(horizontal: 20), | ||||||
|  |           onTap: () { | ||||||
|  |             Navigator.pop(context, false); | ||||||
|  |           }, | ||||||
|  |         ), | ||||||
|  |         const Divider(height: 1), | ||||||
|  |         Expanded( | ||||||
|  |           child: GridView.count( | ||||||
|  |             crossAxisCount: 4, | ||||||
|  |             shrinkWrap: true, | ||||||
|  |             physics: const NeverScrollableScrollPhysics(), | ||||||
|  |             childAspectRatio: 1, | ||||||
|  |             children: categories | ||||||
|  |                 .map( | ||||||
|  |                   (ele) => InkWell( | ||||||
|  |                     onTap: () { | ||||||
|  |                       _selectedCategory = ele; | ||||||
|  |                       Navigator.pop(context, ele); | ||||||
|  |                     }, | ||||||
|  |                     child: Column( | ||||||
|  |                       crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|  |                       mainAxisAlignment: MainAxisAlignment.center, | ||||||
|  |                       mainAxisSize: MainAxisSize.min, | ||||||
|  |                       children: [ | ||||||
|  |                         Icon( | ||||||
|  |                           kCategoryIcons[ele.alias] ?? Symbols.question_mark, | ||||||
|  |                           color: selected == ele | ||||||
|  |                               ? Theme.of(context).colorScheme.primary | ||||||
|  |                               : null, | ||||||
|  |                         ), | ||||||
|  |                         const Gap(4), | ||||||
|  |                         Text( | ||||||
|  |                           'postCategory${ele.alias.capitalize()}'.trExists() | ||||||
|  |                               ? 'postCategory${ele.alias.capitalize()}'.tr() | ||||||
|  |                               : ele.name, | ||||||
|  |                         ) | ||||||
|  |                             .textStyle(Theme.of(context).textTheme.titleMedium!) | ||||||
|  |                             .textColor(selected == ele | ||||||
|  |                                 ? Theme.of(context).colorScheme.primary | ||||||
|  |                                 : null), | ||||||
|  |                       ], | ||||||
|  |                     ), | ||||||
|  |                   ), | ||||||
|  |                 ) | ||||||
|  |                 .toList(), | ||||||
|  |           ), | ||||||
|  |         ), | ||||||
|       ], |       ], | ||||||
|         ), |  | ||||||
|       ), |  | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -20,6 +20,7 @@ import 'package:surface/providers/sn_attachment.dart'; | |||||||
| import 'package:surface/providers/sn_network.dart'; | import 'package:surface/providers/sn_network.dart'; | ||||||
| import 'package:surface/types/attachment.dart'; | import 'package:surface/types/attachment.dart'; | ||||||
| import 'package:surface/types/post.dart'; | import 'package:surface/types/post.dart'; | ||||||
|  | import 'package:surface/types/realm.dart'; | ||||||
| import 'package:surface/widgets/account/account_image.dart'; | import 'package:surface/widgets/account/account_image.dart'; | ||||||
| import 'package:surface/widgets/attachment/attachment_input.dart'; | import 'package:surface/widgets/attachment/attachment_input.dart'; | ||||||
| import 'package:surface/widgets/attachment/attachment_item.dart'; | import 'package:surface/widgets/attachment/attachment_item.dart'; | ||||||
| @@ -35,6 +36,8 @@ import 'package:provider/provider.dart'; | |||||||
| import 'package:surface/widgets/post/post_poll_editor.dart'; | import 'package:surface/widgets/post/post_poll_editor.dart'; | ||||||
| import 'package:uuid/uuid.dart'; | import 'package:uuid/uuid.dart'; | ||||||
|  |  | ||||||
|  | import '../../providers/sn_realm.dart'; | ||||||
|  |  | ||||||
| class PostEditorExtra { | class PostEditorExtra { | ||||||
|   final String? text; |   final String? text; | ||||||
|   final String? title; |   final String? title; | ||||||
| @@ -79,6 +82,7 @@ class _PostEditorScreenState extends State<PostEditorScreen> { | |||||||
|   bool get _isLoading => _isFetching || _writeController.isLoading; |   bool get _isLoading => _isFetching || _writeController.isLoading; | ||||||
|  |  | ||||||
|   List<SnPublisher>? _publishers; |   List<SnPublisher>? _publishers; | ||||||
|  |   List<SnRealm>? _realms; | ||||||
|  |  | ||||||
|   Future<void> _fetchPublishers() async { |   Future<void> _fetchPublishers() async { | ||||||
|     setState(() => _isFetching = true); |     setState(() => _isFetching = true); | ||||||
| @@ -101,6 +105,16 @@ class _PostEditorScreenState extends State<PostEditorScreen> { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   Future<void> _fetchRealms() async { | ||||||
|  |     final rels = context.read<SnRealmProvider>(); | ||||||
|  |     try { | ||||||
|  |       _realms = await rels.listAvailableRealms(); | ||||||
|  |     } catch (err) { | ||||||
|  |       if (!mounted) return; | ||||||
|  |       context.showErrorDialog(err); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|   void _updateMeta() { |   void _updateMeta() { | ||||||
|     showModalBottomSheet( |     showModalBottomSheet( | ||||||
|       context: context, |       context: context, | ||||||
| @@ -144,6 +158,19 @@ class _PostEditorScreenState extends State<PostEditorScreen> { | |||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   void _showRealmPopup() { | ||||||
|  |     showModalBottomSheet( | ||||||
|  |       context: context, | ||||||
|  |       builder: (context) => _PostRealmPopup( | ||||||
|  |         controller: _writeController, | ||||||
|  |         realms: _realms, | ||||||
|  |         onUpdate: () { | ||||||
|  |           _fetchRealms(); | ||||||
|  |         }, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   void _showPollEditorDialog() async { |   void _showPollEditorDialog() async { | ||||||
|     final poll = await showDialog<dynamic>( |     final poll = await showDialog<dynamic>( | ||||||
|       context: context, |       context: context, | ||||||
| @@ -161,6 +188,20 @@ class _PostEditorScreenState extends State<PostEditorScreen> { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   void _showThumbnailEditorDialog() async { | ||||||
|  |     final attachment = await showDialog<SnAttachment?>( | ||||||
|  |       context: context, | ||||||
|  |       builder: (context) => AttachmentInputDialog( | ||||||
|  |         title: 'postThumbnail'.tr(), | ||||||
|  |         pool: 'interactive', | ||||||
|  |         mediaType: SnMediaType.image, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |     if (!context.mounted) return; | ||||||
|  |     if (attachment == null) return; | ||||||
|  |     _writeController.setThumbnail(attachment); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   void dispose() { |   void dispose() { | ||||||
|     _writeController.dispose(); |     _writeController.dispose(); | ||||||
| @@ -180,6 +221,7 @@ class _PostEditorScreenState extends State<PostEditorScreen> { | |||||||
|     } else { |     } else { | ||||||
|       _writeController.setMode(widget.mode); |       _writeController.setMode(widget.mode); | ||||||
|     } |     } | ||||||
|  |     _fetchRealms(); | ||||||
|     _fetchPublishers(); |     _fetchPublishers(); | ||||||
|     _writeController.fetchRelatedPost( |     _writeController.fetchRelatedPost( | ||||||
|       context, |       context, | ||||||
| @@ -321,18 +363,22 @@ class _PostEditorScreenState extends State<PostEditorScreen> { | |||||||
|                         'stories' => _PostStoryEditor( |                         'stories' => _PostStoryEditor( | ||||||
|                             controller: _writeController, |                             controller: _writeController, | ||||||
|                             onTapPublisher: _showPublisherPopup, |                             onTapPublisher: _showPublisherPopup, | ||||||
|  |                             onTapRealm: _showRealmPopup, | ||||||
|                           ), |                           ), | ||||||
|                         'articles' => _PostArticleEditor( |                         'articles' => _PostArticleEditor( | ||||||
|                             controller: _writeController, |                             controller: _writeController, | ||||||
|                             onTapPublisher: _showPublisherPopup, |                             onTapPublisher: _showPublisherPopup, | ||||||
|  |                             onTapRealm: _showRealmPopup, | ||||||
|                           ), |                           ), | ||||||
|                         'questions' => _PostQuestionEditor( |                         'questions' => _PostQuestionEditor( | ||||||
|                             controller: _writeController, |                             controller: _writeController, | ||||||
|                             onTapPublisher: _showPublisherPopup, |                             onTapPublisher: _showPublisherPopup, | ||||||
|  |                             onTapRealm: _showRealmPopup, | ||||||
|                           ), |                           ), | ||||||
|                         'videos' => _PostVideoEditor( |                         'videos' => _PostVideoEditor( | ||||||
|                             controller: _writeController, |                             controller: _writeController, | ||||||
|                             onTapPublisher: _showPublisherPopup, |                             onTapPublisher: _showPublisherPopup, | ||||||
|  |                             onTapRealm: _showRealmPopup, | ||||||
|                           ), |                           ), | ||||||
|                         _ => const Placeholder(), |                         _ => const Placeholder(), | ||||||
|                       }) |                       }) | ||||||
| @@ -344,15 +390,11 @@ class _PostEditorScreenState extends State<PostEditorScreen> { | |||||||
|                         left: 0, |                         left: 0, | ||||||
|                         right: 0, |                         right: 0, | ||||||
|                         child: PostMediaPendingList( |                         child: PostMediaPendingList( | ||||||
|                           thumbnail: _writeController.thumbnail, |  | ||||||
|                           attachments: _writeController.attachments, |                           attachments: _writeController.attachments, | ||||||
|                           isBusy: _writeController.isBusy, |                           isBusy: _writeController.isBusy, | ||||||
|                           onUpload: (int idx) async { |                           onUpload: (int idx) async { | ||||||
|                             await _writeController.uploadSingleAttachment(context, idx); |                             await _writeController.uploadSingleAttachment(context, idx); | ||||||
|                           }, |                           }, | ||||||
|                           onPostSetThumbnail: (int? idx) { |  | ||||||
|                             _writeController.setThumbnail(idx); |  | ||||||
|                           }, |  | ||||||
|                           onInsertLink: (int idx) async { |                           onInsertLink: (int idx) async { | ||||||
|                             _writeController.contentController.text += |                             _writeController.contentController.text += | ||||||
|                                 '\n'; |                                 '\n'; | ||||||
| @@ -453,6 +495,22 @@ class _PostEditorScreenState extends State<PostEditorScreen> { | |||||||
|                                         _showPollEditorDialog(); |                                         _showPollEditorDialog(); | ||||||
|                                       }, |                                       }, | ||||||
|                                     ), |                                     ), | ||||||
|  |                                   if (_writeController.mode == 'articles') | ||||||
|  |                                     IconButton( | ||||||
|  |                                       icon: Icon(Symbols.full_coverage, color: Theme.of(context).colorScheme.primary), | ||||||
|  |                                       style: ButtonStyle( | ||||||
|  |                                         backgroundColor: _writeController.thumbnail == null | ||||||
|  |                                             ? null | ||||||
|  |                                             : WidgetStatePropertyAll(Theme.of(context).colorScheme.surfaceContainer), | ||||||
|  |                                       ), | ||||||
|  |                                       onPressed: () { | ||||||
|  |                                         if (_writeController.thumbnail != null) { | ||||||
|  |                                           _writeController.setThumbnail(null); | ||||||
|  |                                           return; | ||||||
|  |                                         } | ||||||
|  |                                         _showThumbnailEditorDialog(); | ||||||
|  |                                       }, | ||||||
|  |                                     ), | ||||||
|                                 ], |                                 ], | ||||||
|                               ), |                               ), | ||||||
|                             ), |                             ), | ||||||
| @@ -549,11 +607,65 @@ class _PostPublisherPopup extends StatelessWidget { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | class _PostRealmPopup extends StatelessWidget { | ||||||
|  |   final PostWriteController controller; | ||||||
|  |   final List<SnRealm>? realms; | ||||||
|  |   final Function onUpdate; | ||||||
|  |  | ||||||
|  |   const _PostRealmPopup({required this.controller, this.realms, required this.onUpdate}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return Column( | ||||||
|  |       crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |       children: [ | ||||||
|  |         Row( | ||||||
|  |           crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|  |           children: [ | ||||||
|  |             const Icon(Symbols.face, size: 24), | ||||||
|  |             const Gap(16), | ||||||
|  |             Text('accountRealms', style: Theme.of(context).textTheme.titleLarge).tr(), | ||||||
|  |           ], | ||||||
|  |         ).padding(horizontal: 20, top: 16, bottom: 12), | ||||||
|  |         ListTile( | ||||||
|  |           leading: const Icon(Symbols.close), | ||||||
|  |           title: Text('postInGlobal').tr(), | ||||||
|  |           subtitle: Text('postInGlobalDescription').tr(), | ||||||
|  |           contentPadding: const EdgeInsets.symmetric(horizontal: 24), | ||||||
|  |           onTap: () { | ||||||
|  |             controller.setRealm(null); | ||||||
|  |             Navigator.pop(context, true); | ||||||
|  |           }, | ||||||
|  |         ), | ||||||
|  |         const Divider(height: 1), | ||||||
|  |         Expanded( | ||||||
|  |           child: ListView.builder( | ||||||
|  |             itemCount: realms?.length ?? 0, | ||||||
|  |             itemBuilder: (context, idx) { | ||||||
|  |               final realm = realms![idx]; | ||||||
|  |               return ListTile( | ||||||
|  |                 title: Text(realm.name), | ||||||
|  |                 subtitle: Text('@${realm.alias}'), | ||||||
|  |                 leading: AccountImage(content: realm.avatar, radius: 18), | ||||||
|  |                 onTap: () { | ||||||
|  |                   controller.setRealm(realm); | ||||||
|  |                   Navigator.pop(context, true); | ||||||
|  |                 }, | ||||||
|  |               ); | ||||||
|  |             }, | ||||||
|  |           ), | ||||||
|  |         ), | ||||||
|  |       ], | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| class _PostStoryEditor extends StatelessWidget { | class _PostStoryEditor extends StatelessWidget { | ||||||
|   final PostWriteController controller; |   final PostWriteController controller; | ||||||
|   final Function? onTapPublisher; |   final Function? onTapPublisher; | ||||||
|  |   final Function? onTapRealm; | ||||||
|  |  | ||||||
|   const _PostStoryEditor({required this.controller, this.onTapPublisher}); |   const _PostStoryEditor({required this.controller, this.onTapPublisher, this.onTapRealm}); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
| @@ -562,6 +674,8 @@ class _PostStoryEditor extends StatelessWidget { | |||||||
|       constraints: const BoxConstraints(maxWidth: 640), |       constraints: const BoxConstraints(maxWidth: 640), | ||||||
|       child: Row( |       child: Row( | ||||||
|         crossAxisAlignment: CrossAxisAlignment.start, |         crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |         children: [ | ||||||
|  |           Column( | ||||||
|             children: [ |             children: [ | ||||||
|               Material( |               Material( | ||||||
|                 elevation: 2, |                 elevation: 2, | ||||||
| @@ -575,6 +689,23 @@ class _PostStoryEditor extends StatelessWidget { | |||||||
|                   ), |                   ), | ||||||
|                 ), |                 ), | ||||||
|               ), |               ), | ||||||
|  |               const Gap(11), | ||||||
|  |               Material( | ||||||
|  |                 elevation: 1, | ||||||
|  |                 borderRadius: const BorderRadius.all(Radius.circular(24)), | ||||||
|  |                 child: GestureDetector( | ||||||
|  |                   onTap: () { | ||||||
|  |                     onTapRealm?.call(); | ||||||
|  |                   }, | ||||||
|  |                   child: AccountImage( | ||||||
|  |                     content: controller.realm?.avatar, | ||||||
|  |                     fallbackWidget: const Icon(Symbols.globe, size: 20), | ||||||
|  |                     radius: 14, | ||||||
|  |                   ), | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |             ], | ||||||
|  |           ), | ||||||
|           Expanded( |           Expanded( | ||||||
|             child: Column( |             child: Column( | ||||||
|               children: [ |               children: [ | ||||||
| @@ -616,8 +747,9 @@ class _PostStoryEditor extends StatelessWidget { | |||||||
| class _PostArticleEditor extends StatelessWidget { | class _PostArticleEditor extends StatelessWidget { | ||||||
|   final PostWriteController controller; |   final PostWriteController controller; | ||||||
|   final Function? onTapPublisher; |   final Function? onTapPublisher; | ||||||
|  |   final Function? onTapRealm; | ||||||
|  |  | ||||||
|   const _PostArticleEditor({required this.controller, this.onTapPublisher}); |   const _PostArticleEditor({required this.controller, this.onTapPublisher, this.onTapRealm}); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
| @@ -638,6 +770,21 @@ class _PostArticleEditor extends StatelessWidget { | |||||||
|                   ], |                   ], | ||||||
|                 ), |                 ), | ||||||
|               ), |               ), | ||||||
|  |               Material( | ||||||
|  |                 elevation: 1, | ||||||
|  |                 borderRadius: const BorderRadius.all(Radius.circular(24)), | ||||||
|  |                 child: GestureDetector( | ||||||
|  |                   onTap: () { | ||||||
|  |                     onTapRealm?.call(); | ||||||
|  |                   }, | ||||||
|  |                   child: AccountImage( | ||||||
|  |                     content: controller.realm?.avatar, | ||||||
|  |                     fallbackWidget: const Icon(Symbols.globe, size: 20), | ||||||
|  |                     radius: 14, | ||||||
|  |                   ), | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |               const Gap(8), | ||||||
|             ], |             ], | ||||||
|           ).padding(horizontal: 12, vertical: 8), |           ).padding(horizontal: 12, vertical: 8), | ||||||
|           onTap: () { |           onTap: () { | ||||||
| @@ -668,7 +815,24 @@ class _PostArticleEditor extends StatelessWidget { | |||||||
|         onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), |         onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||||
|         contentInsertionConfiguration: controller.contentInsertionConfiguration, |         contentInsertionConfiguration: controller.contentInsertionConfiguration, | ||||||
|       ).padding(horizontal: 16), |       ).padding(horizontal: 16), | ||||||
|       const Gap(4), |       if (controller.thumbnail != null) | ||||||
|  |         Container( | ||||||
|  |           margin: const EdgeInsets.only(left: 12, right: 12, top: 8, bottom: 4), | ||||||
|  |           decoration: BoxDecoration( | ||||||
|  |             borderRadius: BorderRadius.circular(8), | ||||||
|  |             border: Border.all(color: Theme.of(context).dividerColor), | ||||||
|  |           ), | ||||||
|  |           child: ClipRRect( | ||||||
|  |             borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||||
|  |             child: AspectRatio( | ||||||
|  |               aspectRatio: 16 / 9, | ||||||
|  |               child: AttachmentItem( | ||||||
|  |                 data: controller.thumbnail!.attachment!, | ||||||
|  |                 heroTag: "post-editor-thumbnail-preview", | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |         ), | ||||||
|     ]; |     ]; | ||||||
|  |  | ||||||
|     if (ResponsiveBreakpoints.of(context).largerThan(MOBILE)) { |     if (ResponsiveBreakpoints.of(context).largerThan(MOBILE)) { | ||||||
| @@ -740,8 +904,9 @@ class _PostArticleEditor extends StatelessWidget { | |||||||
| class _PostQuestionEditor extends StatelessWidget { | class _PostQuestionEditor extends StatelessWidget { | ||||||
|   final PostWriteController controller; |   final PostWriteController controller; | ||||||
|   final Function? onTapPublisher; |   final Function? onTapPublisher; | ||||||
|  |   final Function? onTapRealm; | ||||||
|  |  | ||||||
|   const _PostQuestionEditor({required this.controller, this.onTapPublisher}); |   const _PostQuestionEditor({required this.controller, this.onTapPublisher, this.onTapRealm}); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
| @@ -750,9 +915,11 @@ class _PostQuestionEditor extends StatelessWidget { | |||||||
|       constraints: const BoxConstraints(maxWidth: 640), |       constraints: const BoxConstraints(maxWidth: 640), | ||||||
|       child: Row( |       child: Row( | ||||||
|         crossAxisAlignment: CrossAxisAlignment.start, |         crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |         children: [ | ||||||
|  |           Column( | ||||||
|             children: [ |             children: [ | ||||||
|               Material( |               Material( | ||||||
|             elevation: 1, |                 elevation: 2, | ||||||
|                 borderRadius: const BorderRadius.all(Radius.circular(24)), |                 borderRadius: const BorderRadius.all(Radius.circular(24)), | ||||||
|                 child: GestureDetector( |                 child: GestureDetector( | ||||||
|                   onTap: () { |                   onTap: () { | ||||||
| @@ -763,6 +930,23 @@ class _PostQuestionEditor extends StatelessWidget { | |||||||
|                   ), |                   ), | ||||||
|                 ), |                 ), | ||||||
|               ), |               ), | ||||||
|  |               const Gap(11), | ||||||
|  |               Material( | ||||||
|  |                 elevation: 1, | ||||||
|  |                 borderRadius: const BorderRadius.all(Radius.circular(24)), | ||||||
|  |                 child: GestureDetector( | ||||||
|  |                   onTap: () { | ||||||
|  |                     onTapRealm?.call(); | ||||||
|  |                   }, | ||||||
|  |                   child: AccountImage( | ||||||
|  |                     content: controller.realm?.avatar, | ||||||
|  |                     fallbackWidget: const Icon(Symbols.globe, size: 20), | ||||||
|  |                     radius: 14, | ||||||
|  |                   ), | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |             ], | ||||||
|  |           ), | ||||||
|           Expanded( |           Expanded( | ||||||
|             child: Column( |             child: Column( | ||||||
|               children: [ |               children: [ | ||||||
| @@ -815,8 +999,9 @@ class _PostQuestionEditor extends StatelessWidget { | |||||||
| class _PostVideoEditor extends StatelessWidget { | class _PostVideoEditor extends StatelessWidget { | ||||||
|   final PostWriteController controller; |   final PostWriteController controller; | ||||||
|   final Function? onTapPublisher; |   final Function? onTapPublisher; | ||||||
|  |   final Function? onTapRealm; | ||||||
|  |  | ||||||
|   const _PostVideoEditor({required this.controller, this.onTapPublisher}); |   const _PostVideoEditor({required this.controller, this.onTapPublisher, this.onTapRealm}); | ||||||
|  |  | ||||||
|   void _selectVideo(BuildContext context) async { |   void _selectVideo(BuildContext context) async { | ||||||
|     final video = await showDialog<SnAttachment?>( |     final video = await showDialog<SnAttachment?>( | ||||||
| @@ -903,30 +1088,38 @@ class _PostVideoEditor extends StatelessWidget { | |||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return Column( |     return Column( | ||||||
|  |       children: [ | ||||||
|  |         Column( | ||||||
|           children: [ |           children: [ | ||||||
|             Material( |             Material( | ||||||
|           color: Theme.of(context).colorScheme.surfaceContainerHigh, |               elevation: 2, | ||||||
|           child: InkWell( |               borderRadius: const BorderRadius.all(Radius.circular(24)), | ||||||
|             child: Row( |               child: GestureDetector( | ||||||
|               children: [ |  | ||||||
|                 AccountImage(content: controller.publisher?.avatar, radius: 20), |  | ||||||
|                 const Gap(8), |  | ||||||
|                 Expanded( |  | ||||||
|                   child: Column( |  | ||||||
|                     crossAxisAlignment: CrossAxisAlignment.start, |  | ||||||
|                     children: [ |  | ||||||
|                       Text(controller.publisher?.nick ?? 'loading'.tr()).bold(), |  | ||||||
|                       Text('@${controller.publisher?.name}'), |  | ||||||
|                     ], |  | ||||||
|                   ), |  | ||||||
|                 ), |  | ||||||
|               ], |  | ||||||
|             ).padding(horizontal: 12, vertical: 8), |  | ||||||
|                 onTap: () { |                 onTap: () { | ||||||
|                   onTapPublisher?.call(); |                   onTapPublisher?.call(); | ||||||
|                 }, |                 }, | ||||||
|  |                 child: AccountImage( | ||||||
|  |                   content: controller.publisher?.avatar, | ||||||
|                 ), |                 ), | ||||||
|               ), |               ), | ||||||
|  |             ), | ||||||
|  |             const Gap(11), | ||||||
|  |             Material( | ||||||
|  |               elevation: 1, | ||||||
|  |               borderRadius: const BorderRadius.all(Radius.circular(24)), | ||||||
|  |               child: GestureDetector( | ||||||
|  |                 onTap: () { | ||||||
|  |                   onTapRealm?.call(); | ||||||
|  |                 }, | ||||||
|  |                 child: AccountImage( | ||||||
|  |                   content: controller.realm?.avatar, | ||||||
|  |                   fallbackWidget: const Icon(Symbols.globe, size: 20), | ||||||
|  |                   radius: 14, | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|         const Gap(16), |         const Gap(16), | ||||||
|         TextField( |         TextField( | ||||||
|           controller: controller.titleController, |           controller: controller.titleController, | ||||||
|   | |||||||
| @@ -4,17 +4,16 @@ import 'package:gap/gap.dart'; | |||||||
| import 'package:go_router/go_router.dart'; | import 'package:go_router/go_router.dart'; | ||||||
| import 'package:material_symbols_icons/symbols.dart'; | import 'package:material_symbols_icons/symbols.dart'; | ||||||
| import 'package:provider/provider.dart'; | import 'package:provider/provider.dart'; | ||||||
| import 'package:styled_widget/styled_widget.dart'; | import 'package:surface/providers/config.dart'; | ||||||
| import 'package:surface/providers/sn_network.dart'; | import 'package:surface/providers/sn_network.dart'; | ||||||
| import 'package:surface/providers/userinfo.dart'; | import 'package:surface/providers/userinfo.dart'; | ||||||
| import 'package:surface/types/realm.dart'; | import 'package:surface/types/realm.dart'; | ||||||
| import 'package:surface/widgets/account/account_image.dart'; |  | ||||||
| import 'package:surface/widgets/app_bar_leading.dart'; | import 'package:surface/widgets/app_bar_leading.dart'; | ||||||
| import 'package:surface/widgets/dialog.dart'; | import 'package:surface/widgets/dialog.dart'; | ||||||
| import 'package:surface/widgets/loading_indicator.dart'; | import 'package:surface/widgets/loading_indicator.dart'; | ||||||
| import 'package:surface/widgets/navigation/app_scaffold.dart'; | import 'package:surface/widgets/navigation/app_scaffold.dart'; | ||||||
|  | import 'package:surface/widgets/realm/realm_item.dart'; | ||||||
| import 'package:surface/widgets/unauthorized_hint.dart'; | import 'package:surface/widgets/unauthorized_hint.dart'; | ||||||
| import 'package:surface/widgets/universal_image.dart'; |  | ||||||
|  |  | ||||||
| class RealmScreen extends StatefulWidget { | class RealmScreen extends StatefulWidget { | ||||||
|   const RealmScreen({super.key}); |   const RealmScreen({super.key}); | ||||||
| @@ -75,12 +74,12 @@ class _RealmScreenState extends State<RealmScreen> { | |||||||
|   @override |   @override | ||||||
|   void initState() { |   void initState() { | ||||||
|     super.initState(); |     super.initState(); | ||||||
|  |     _isCompactView = context.read<ConfigProvider>().realmCompactView; | ||||||
|     _fetchRealms(); |     _fetchRealms(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     final sn = context.read<SnNetworkProvider>(); |  | ||||||
|     final ua = context.read<UserProvider>(); |     final ua = context.read<UserProvider>(); | ||||||
|  |  | ||||||
|     if (!ua.isAuthorized) { |     if (!ua.isAuthorized) { | ||||||
| @@ -110,6 +109,7 @@ class _RealmScreenState extends State<RealmScreen> { | |||||||
|             icon: !_isCompactView ? const Icon(Symbols.view_list) : const Icon(Symbols.view_module), |             icon: !_isCompactView ? const Icon(Symbols.view_list) : const Icon(Symbols.view_module), | ||||||
|             onPressed: () { |             onPressed: () { | ||||||
|               setState(() => _isCompactView = !_isCompactView); |               setState(() => _isCompactView = !_isCompactView); | ||||||
|  |               context.read<ConfigProvider>().realmCompactView = _isCompactView; | ||||||
|             }, |             }, | ||||||
|           ), |           ), | ||||||
|           const Gap(8), |           const Gap(8), | ||||||
| @@ -134,21 +134,12 @@ class _RealmScreenState extends State<RealmScreen> { | |||||||
|                   itemCount: _realms?.length ?? 0, |                   itemCount: _realms?.length ?? 0, | ||||||
|                   itemBuilder: (context, idx) { |                   itemBuilder: (context, idx) { | ||||||
|                     final realm = _realms![idx]; |                     final realm = _realms![idx]; | ||||||
|                     if (_isCompactView) { |  | ||||||
|                       return ListTile( |                     return RealmItemWidget( | ||||||
|                         contentPadding: const EdgeInsets.symmetric(horizontal: 16), |                       showPopularity: false, | ||||||
|                         leading: AccountImage( |                       item: realm, | ||||||
|                           content: realm.avatar, |                       isListView: _isCompactView, | ||||||
|                           fallbackWidget: const Icon(Symbols.group, size: 20), |                       actionListView: [ | ||||||
|                         ), |  | ||||||
|                         title: Text(realm.name), |  | ||||||
|                         subtitle: Text( |  | ||||||
|                           realm.description, |  | ||||||
|                           maxLines: 1, |  | ||||||
|                           overflow: TextOverflow.ellipsis, |  | ||||||
|                         ), |  | ||||||
|                         trailing: PopupMenuButton( |  | ||||||
|                           itemBuilder: (BuildContext context) => [ |  | ||||||
|                         PopupMenuItem( |                         PopupMenuItem( | ||||||
|                           child: Row( |                           child: Row( | ||||||
|                             children: [ |                             children: [ | ||||||
| @@ -181,82 +172,8 @@ class _RealmScreenState extends State<RealmScreen> { | |||||||
|                           }, |                           }, | ||||||
|                         ), |                         ), | ||||||
|                       ], |                       ], | ||||||
|                         ), |                       onUpdate: _fetchRealms, | ||||||
|                         onTap: () { |  | ||||||
|                           GoRouter.of(context).pushNamed( |  | ||||||
|                             'realmDetail', |  | ||||||
|                             pathParameters: {'alias': realm.alias}, |  | ||||||
|                           ).then((value) { |  | ||||||
|                             if (value == true) { |  | ||||||
|                               _fetchRealms(); |  | ||||||
|                             } |  | ||||||
|                           }); |  | ||||||
|                         }, |  | ||||||
|                     ); |                     ); | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                     return Container( |  | ||||||
|                       constraints: BoxConstraints(maxWidth: 640), |  | ||||||
|                       child: Card( |  | ||||||
|                         margin: const EdgeInsets.all(12), |  | ||||||
|                         child: InkWell( |  | ||||||
|                           borderRadius: const BorderRadius.all(Radius.circular(8)), |  | ||||||
|                           child: Column( |  | ||||||
|                             crossAxisAlignment: CrossAxisAlignment.start, |  | ||||||
|                             children: [ |  | ||||||
|                               AspectRatio( |  | ||||||
|                                 aspectRatio: 16 / 7, |  | ||||||
|                                 child: Stack( |  | ||||||
|                                   clipBehavior: Clip.none, |  | ||||||
|                                   fit: StackFit.expand, |  | ||||||
|                                   children: [ |  | ||||||
|                                     ClipRRect( |  | ||||||
|                                       borderRadius: const BorderRadius.all(Radius.circular(8)), |  | ||||||
|                                       child: Container( |  | ||||||
|                                         color: Theme.of(context).colorScheme.surfaceContainer, |  | ||||||
|                                         child: (realm.banner?.isEmpty ?? true) |  | ||||||
|                                             ? const SizedBox.shrink() |  | ||||||
|                                             : AutoResizeUniversalImage( |  | ||||||
|                                                 sn.getAttachmentUrl(realm.banner!), |  | ||||||
|                                                 fit: BoxFit.cover, |  | ||||||
|                                               ), |  | ||||||
|                                       ), |  | ||||||
|                                     ), |  | ||||||
|                                     Positioned( |  | ||||||
|                                       bottom: -30, |  | ||||||
|                                       left: 18, |  | ||||||
|                                       child: AccountImage( |  | ||||||
|                                         content: realm.avatar, |  | ||||||
|                                         radius: 24, |  | ||||||
|                                         fallbackWidget: const Icon(Symbols.group, size: 24), |  | ||||||
|                                       ), |  | ||||||
|                                     ), |  | ||||||
|                                   ], |  | ||||||
|                                 ), |  | ||||||
|                               ), |  | ||||||
|                               const Gap(20 + 12), |  | ||||||
|                               Column( |  | ||||||
|                                 crossAxisAlignment: CrossAxisAlignment.start, |  | ||||||
|                                 children: [ |  | ||||||
|                                   Text(realm.name).textStyle(Theme.of(context).textTheme.titleMedium!), |  | ||||||
|                                   Text(realm.description).textStyle(Theme.of(context).textTheme.bodySmall!), |  | ||||||
|                                 ], |  | ||||||
|                               ).padding(horizontal: 24, bottom: 14), |  | ||||||
|                             ], |  | ||||||
|                           ), |  | ||||||
|                           onTap: () { |  | ||||||
|                             GoRouter.of(context).pushNamed( |  | ||||||
|                               'realmDetail', |  | ||||||
|                               pathParameters: {'alias': realm.alias}, |  | ||||||
|                             ).then((value) { |  | ||||||
|                               if (value == true) { |  | ||||||
|                                 _fetchRealms(); |  | ||||||
|                               } |  | ||||||
|                             }); |  | ||||||
|                           }, |  | ||||||
|                         ), |  | ||||||
|                       ), |  | ||||||
|                     ).center(); |  | ||||||
|                   }, |                   }, | ||||||
|                 ), |                 ), | ||||||
|               ), |               ), | ||||||
|   | |||||||
| @@ -5,16 +5,19 @@ import 'package:go_router/go_router.dart'; | |||||||
| import 'package:material_symbols_icons/symbols.dart'; | import 'package:material_symbols_icons/symbols.dart'; | ||||||
| import 'package:provider/provider.dart'; | import 'package:provider/provider.dart'; | ||||||
| import 'package:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  | import 'package:surface/providers/post.dart'; | ||||||
| import 'package:surface/providers/sn_network.dart'; | import 'package:surface/providers/sn_network.dart'; | ||||||
| import 'package:surface/providers/user_directory.dart'; | import 'package:surface/providers/user_directory.dart'; | ||||||
| import 'package:surface/providers/userinfo.dart'; | import 'package:surface/providers/userinfo.dart'; | ||||||
| import 'package:surface/types/account.dart'; | import 'package:surface/types/account.dart'; | ||||||
|  | import 'package:surface/types/chat.dart'; | ||||||
| import 'package:surface/types/post.dart'; | import 'package:surface/types/post.dart'; | ||||||
| import 'package:surface/types/realm.dart'; | import 'package:surface/types/realm.dart'; | ||||||
| import 'package:surface/widgets/account/account_image.dart'; | import 'package:surface/widgets/account/account_image.dart'; | ||||||
| import 'package:surface/widgets/account/account_select.dart'; | import 'package:surface/widgets/account/account_select.dart'; | ||||||
| import 'package:surface/widgets/dialog.dart'; | import 'package:surface/widgets/dialog.dart'; | ||||||
| import 'package:surface/widgets/navigation/app_scaffold.dart'; | import 'package:surface/widgets/navigation/app_scaffold.dart'; | ||||||
|  | import 'package:surface/widgets/post/post_item.dart'; | ||||||
| import 'package:very_good_infinite_list/very_good_infinite_list.dart'; | import 'package:very_good_infinite_list/very_good_infinite_list.dart'; | ||||||
|  |  | ||||||
| class RealmDetailScreen extends StatefulWidget { | class RealmDetailScreen extends StatefulWidget { | ||||||
| @@ -60,18 +63,36 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   List<SnChannel>? _channels; | ||||||
|  |  | ||||||
|  |   Future<void> _fetchChannels() async { | ||||||
|  |     try { | ||||||
|  |       final sn = context.read<SnNetworkProvider>(); | ||||||
|  |       final resp = await sn.client.get('/cgi/im/channels/${widget.alias}/public'); | ||||||
|  |       _channels = List<SnChannel>.from( | ||||||
|  |         resp.data.map((e) => SnChannel.fromJson(e)).cast<SnChannel>(), | ||||||
|  |       ); | ||||||
|  |     } catch (err) { | ||||||
|  |       if (mounted) context.showErrorDialog(err); | ||||||
|  |       rethrow; | ||||||
|  |     } finally { | ||||||
|  |       setState(() {}); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   void initState() { |   void initState() { | ||||||
|     super.initState(); |     super.initState(); | ||||||
|     _fetchRealm().then((_) { |     _fetchRealm().then((_) { | ||||||
|       _fetchPublishers(); |       _fetchPublishers(); | ||||||
|  |       _fetchChannels(); | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return DefaultTabController( |     return DefaultTabController( | ||||||
|       length: 3, |       length: 4, | ||||||
|       child: AppScaffold( |       child: AppScaffold( | ||||||
|         body: NestedScrollView( |         body: NestedScrollView( | ||||||
|           headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { |           headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { | ||||||
| @@ -83,6 +104,7 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> { | |||||||
|                   bottom: TabBar( |                   bottom: TabBar( | ||||||
|                     tabs: [ |                     tabs: [ | ||||||
|                       Tab(icon: Icon(Symbols.home, color: Theme.of(context).appBarTheme.foregroundColor)), |                       Tab(icon: Icon(Symbols.home, color: Theme.of(context).appBarTheme.foregroundColor)), | ||||||
|  |                       Tab(icon: Icon(Symbols.explore, color: Theme.of(context).appBarTheme.foregroundColor)), | ||||||
|                       Tab(icon: Icon(Symbols.group, color: Theme.of(context).appBarTheme.foregroundColor)), |                       Tab(icon: Icon(Symbols.group, color: Theme.of(context).appBarTheme.foregroundColor)), | ||||||
|                       Tab(icon: Icon(Symbols.settings, color: Theme.of(context).appBarTheme.foregroundColor)), |                       Tab(icon: Icon(Symbols.settings, color: Theme.of(context).appBarTheme.foregroundColor)), | ||||||
|                     ], |                     ], | ||||||
| @@ -93,7 +115,8 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> { | |||||||
|           }, |           }, | ||||||
|           body: TabBarView( |           body: TabBarView( | ||||||
|             children: [ |             children: [ | ||||||
|               _RealmDetailHomeWidget(realm: _realm, publishers: _publishers), |               _RealmDetailHomeWidget(realm: _realm, publishers: _publishers, channels: _channels), | ||||||
|  |               _RealmPostListWidget(realm: _realm), | ||||||
|               _RealmMemberListWidget(realm: _realm), |               _RealmMemberListWidget(realm: _realm), | ||||||
|               _RealmSettingsWidget( |               _RealmSettingsWidget( | ||||||
|                 realm: _realm, |                 realm: _realm, | ||||||
| @@ -112,8 +135,9 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> { | |||||||
| class _RealmDetailHomeWidget extends StatelessWidget { | class _RealmDetailHomeWidget extends StatelessWidget { | ||||||
|   final SnRealm? realm; |   final SnRealm? realm; | ||||||
|   final List<SnPublisher>? publishers; |   final List<SnPublisher>? publishers; | ||||||
|  |   final List<SnChannel>? channels; | ||||||
|  |  | ||||||
|   const _RealmDetailHomeWidget({required this.realm, this.publishers}); |   const _RealmDetailHomeWidget({required this.realm, this.publishers, this.channels}); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
| @@ -135,10 +159,20 @@ class _RealmDetailHomeWidget extends StatelessWidget { | |||||||
|             ], |             ], | ||||||
|           ).padding(horizontal: 24), |           ).padding(horizontal: 24), | ||||||
|         const Gap(16), |         const Gap(16), | ||||||
|         const Divider(), |         const Divider(height: 1), | ||||||
|         Expanded( |         Expanded( | ||||||
|           child: ListView.builder( |           child: CustomScrollView( | ||||||
|             padding: EdgeInsets.zero, |             slivers: [ | ||||||
|  |               if (publishers?.isNotEmpty ?? false) | ||||||
|  |                 SliverToBoxAdapter( | ||||||
|  |                   child: Container( | ||||||
|  |                     width: double.infinity, | ||||||
|  |                     color: Theme.of(context).colorScheme.surfaceContainerHigh, | ||||||
|  |                     child: Text('realmCommunityPublishersHint'.tr(), style: Theme.of(context).textTheme.bodyMedium) | ||||||
|  |                         .padding(horizontal: 24, vertical: 8), | ||||||
|  |                   ), | ||||||
|  |                 ), | ||||||
|  |               SliverList.builder( | ||||||
|                 itemCount: publishers?.length ?? 0, |                 itemCount: publishers?.length ?? 0, | ||||||
|                 itemBuilder: (context, idx) { |                 itemBuilder: (context, idx) { | ||||||
|                   final ele = publishers![idx]; |                   final ele = publishers![idx]; | ||||||
| @@ -160,12 +194,114 @@ class _RealmDetailHomeWidget extends StatelessWidget { | |||||||
|                   ); |                   ); | ||||||
|                 }, |                 }, | ||||||
|               ), |               ), | ||||||
|  |               if (channels?.isNotEmpty ?? false) | ||||||
|  |                 SliverToBoxAdapter( | ||||||
|  |                   child: Container( | ||||||
|  |                     width: double.infinity, | ||||||
|  |                     color: Theme.of(context).colorScheme.surfaceContainerHigh, | ||||||
|  |                     child: Text('realmCommunityPublicChannelsHint'.tr(), style: Theme.of(context).textTheme.bodyMedium) | ||||||
|  |                         .padding(horizontal: 24, vertical: 8), | ||||||
|  |                   ), | ||||||
|  |                 ), | ||||||
|  |               SliverList.builder( | ||||||
|  |                 itemCount: channels?.length ?? 0, | ||||||
|  |                 itemBuilder: (context, idx) { | ||||||
|  |                   final ele = channels![idx]; | ||||||
|  |                   return ListTile( | ||||||
|  |                     contentPadding: const EdgeInsets.symmetric(horizontal: 20), | ||||||
|  |                     leading: AccountImage( | ||||||
|  |                       content: null, | ||||||
|  |                       fallbackWidget: const Icon(Symbols.chat, size: 20), | ||||||
|  |                     ), | ||||||
|  |                     title: Text(ele.name), | ||||||
|  |                     subtitle: Text('#${ele.alias}'), | ||||||
|  |                     trailing: const Icon(Symbols.chevron_right), | ||||||
|  |                     onTap: () { | ||||||
|  |                       GoRouter.of(context).pushNamed( | ||||||
|  |                         'chatRoom', | ||||||
|  |                         pathParameters: { | ||||||
|  |                           'scope': realm?.alias ?? 'global', | ||||||
|  |                           'alias': ele.alias, | ||||||
|  |                         }, | ||||||
|  |                       ); | ||||||
|  |                     }, | ||||||
|  |                   ); | ||||||
|  |                 }, | ||||||
|  |               ), | ||||||
|  |             ], | ||||||
|  |           ), | ||||||
|         ), |         ), | ||||||
|       ], |       ], | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | class _RealmPostListWidget extends StatefulWidget { | ||||||
|  |   final SnRealm? realm; | ||||||
|  |  | ||||||
|  |   const _RealmPostListWidget({this.realm}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   State<_RealmPostListWidget> createState() => _RealmPostListWidgetState(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _RealmPostListWidgetState extends State<_RealmPostListWidget> { | ||||||
|  |   bool _isBusy = false; | ||||||
|  |   int? _totalCount; | ||||||
|  |   final List<SnPost> _posts = List.empty(growable: true); | ||||||
|  |  | ||||||
|  |   Future<void> _fetchPosts() async { | ||||||
|  |     setState(() => _isBusy = true); | ||||||
|  |  | ||||||
|  |     try { | ||||||
|  |       final pt = context.read<SnPostContentProvider>(); | ||||||
|  |       final out = await pt.listPosts( | ||||||
|  |         take: 10, | ||||||
|  |         offset: _posts.length, | ||||||
|  |         realm: widget.realm?.id.toString(), | ||||||
|  |       ); | ||||||
|  |       _totalCount = out.$2; | ||||||
|  |       _posts.addAll(out.$1); | ||||||
|  |     } catch (err) { | ||||||
|  |       if (!mounted) return; | ||||||
|  |       context.showErrorDialog(err); | ||||||
|  |     } finally { | ||||||
|  |       setState(() => _isBusy = false); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return MediaQuery.removePadding( | ||||||
|  |       context: context, | ||||||
|  |       removeTop: true, | ||||||
|  |       child: RefreshIndicator( | ||||||
|  |         onRefresh: _fetchPosts, | ||||||
|  |         child: InfiniteList( | ||||||
|  |           itemCount: _posts.length, | ||||||
|  |           isLoading: _isBusy, | ||||||
|  |           hasReachedMax: _totalCount != null && _posts.length >= _totalCount!, | ||||||
|  |           onFetchData: _fetchPosts, | ||||||
|  |           itemBuilder: (context, idx) { | ||||||
|  |             final post = _posts[idx]; | ||||||
|  |             return OpenablePostItem( | ||||||
|  |               data: post, | ||||||
|  |               maxWidth: 640, | ||||||
|  |               onChanged: (data) { | ||||||
|  |                 setState(() => _posts[idx] = data); | ||||||
|  |               }, | ||||||
|  |               onDeleted: () { | ||||||
|  |                 setState(() => _posts.removeAt(idx)); | ||||||
|  |               }, | ||||||
|  |             ); | ||||||
|  |           }, | ||||||
|  |           separatorBuilder: (_, __) => const Gap(8), | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ).padding(top: 8); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| class _RealmMemberListWidget extends StatefulWidget { | class _RealmMemberListWidget extends StatefulWidget { | ||||||
|   final SnRealm? realm; |   final SnRealm? realm; | ||||||
|  |  | ||||||
| @@ -365,7 +501,7 @@ class _RealmSettingsWidgetState extends State<_RealmSettingsWidget> { | |||||||
|     final sn = context.read<SnNetworkProvider>(); |     final sn = context.read<SnNetworkProvider>(); | ||||||
|  |  | ||||||
|     try { |     try { | ||||||
|       await sn.client.delete('/cgi/id/realms/${widget.realm!.alias}/members/me'); |       await sn.client.delete('/cgi/id/realms/${widget.realm!.alias}/me'); | ||||||
|       if (!mounted) return; |       if (!mounted) return; | ||||||
|       Navigator.pop(context, true); |       Navigator.pop(context, true); | ||||||
|     } catch (err) { |     } catch (err) { | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ import 'package:gap/gap.dart'; | |||||||
| import 'package:material_symbols_icons/symbols.dart'; | import 'package:material_symbols_icons/symbols.dart'; | ||||||
| import 'package:provider/provider.dart'; | import 'package:provider/provider.dart'; | ||||||
| import 'package:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  | import 'package:surface/providers/config.dart'; | ||||||
| import 'package:surface/providers/sn_network.dart'; | import 'package:surface/providers/sn_network.dart'; | ||||||
| import 'package:surface/providers/userinfo.dart'; | import 'package:surface/providers/userinfo.dart'; | ||||||
| import 'package:surface/types/chat.dart'; | import 'package:surface/types/chat.dart'; | ||||||
| @@ -12,7 +13,7 @@ import 'package:surface/widgets/account/account_image.dart'; | |||||||
| import 'package:surface/widgets/dialog.dart'; | import 'package:surface/widgets/dialog.dart'; | ||||||
| import 'package:surface/widgets/loading_indicator.dart'; | import 'package:surface/widgets/loading_indicator.dart'; | ||||||
| import 'package:surface/widgets/navigation/app_scaffold.dart'; | import 'package:surface/widgets/navigation/app_scaffold.dart'; | ||||||
| import 'package:surface/widgets/universal_image.dart'; | import 'package:surface/widgets/realm/realm_item.dart'; | ||||||
|  |  | ||||||
| class RealmDiscoveryScreen extends StatefulWidget { | class RealmDiscoveryScreen extends StatefulWidget { | ||||||
|   const RealmDiscoveryScreen({super.key}); |   const RealmDiscoveryScreen({super.key}); | ||||||
| @@ -24,6 +25,7 @@ class RealmDiscoveryScreen extends StatefulWidget { | |||||||
| class _RealmDiscoveryScreenState extends State<RealmDiscoveryScreen> { | class _RealmDiscoveryScreenState extends State<RealmDiscoveryScreen> { | ||||||
|   List<SnRealm>? _realms; |   List<SnRealm>? _realms; | ||||||
|   bool _isBusy = false; |   bool _isBusy = false; | ||||||
|  |   bool _isCompactView = false; | ||||||
|  |  | ||||||
|   Future<void> _fetchRealms() async { |   Future<void> _fetchRealms() async { | ||||||
|     try { |     try { | ||||||
| @@ -44,16 +46,25 @@ class _RealmDiscoveryScreenState extends State<RealmDiscoveryScreen> { | |||||||
|   @override |   @override | ||||||
|   void initState() { |   void initState() { | ||||||
|     super.initState(); |     super.initState(); | ||||||
|  |     _isCompactView = context.read<ConfigProvider>().realmCompactView; | ||||||
|     _fetchRealms(); |     _fetchRealms(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     final sn = context.read<SnNetworkProvider>(); |  | ||||||
|  |  | ||||||
|     return AppScaffold( |     return AppScaffold( | ||||||
|       appBar: AppBar( |       appBar: AppBar( | ||||||
|         title: Text('screenRealmDiscovery').tr(), |         title: Text('screenRealmDiscovery').tr(), | ||||||
|  |         actions: [ | ||||||
|  |           IconButton( | ||||||
|  |             icon: _isCompactView ? const Icon(Symbols.view_list) : const Icon(Symbols.view_module), | ||||||
|  |             onPressed: () { | ||||||
|  |               setState(() => _isCompactView = !_isCompactView); | ||||||
|  |               context.read<ConfigProvider>().realmCompactView = _isCompactView; | ||||||
|  |             }, | ||||||
|  |           ), | ||||||
|  |           const Gap(8), | ||||||
|  |         ], | ||||||
|       ), |       ), | ||||||
|       body: Column( |       body: Column( | ||||||
|         children: [ |         children: [ | ||||||
| @@ -66,64 +77,16 @@ class _RealmDiscoveryScreenState extends State<RealmDiscoveryScreen> { | |||||||
|                 itemCount: _realms?.length ?? 0, |                 itemCount: _realms?.length ?? 0, | ||||||
|                 itemBuilder: (context, idx) { |                 itemBuilder: (context, idx) { | ||||||
|                   final realm = _realms![idx]; |                   final realm = _realms![idx]; | ||||||
|                   return Container( |                   return RealmItemWidget( | ||||||
|                     constraints: BoxConstraints(maxWidth: 640), |                     item: realm, | ||||||
|                     child: Card( |                     isListView: _isCompactView, | ||||||
|                       margin: const EdgeInsets.all(12), |  | ||||||
|                       child: InkWell( |  | ||||||
|                         borderRadius: const BorderRadius.all(Radius.circular(8)), |  | ||||||
|                         child: Column( |  | ||||||
|                           crossAxisAlignment: CrossAxisAlignment.start, |  | ||||||
|                           children: [ |  | ||||||
|                             AspectRatio( |  | ||||||
|                               aspectRatio: 16 / 7, |  | ||||||
|                               child: Stack( |  | ||||||
|                                 clipBehavior: Clip.none, |  | ||||||
|                                 fit: StackFit.expand, |  | ||||||
|                                 children: [ |  | ||||||
|                                   ClipRRect( |  | ||||||
|                                     borderRadius: const BorderRadius.all(Radius.circular(8)), |  | ||||||
|                                     child: Container( |  | ||||||
|                                       color: Theme.of(context).colorScheme.surfaceContainer, |  | ||||||
|                                       child: (realm.banner?.isEmpty ?? true) |  | ||||||
|                                           ? const SizedBox.shrink() |  | ||||||
|                                           : AutoResizeUniversalImage( |  | ||||||
|                                               sn.getAttachmentUrl(realm.banner!), |  | ||||||
|                                               fit: BoxFit.cover, |  | ||||||
|                                             ), |  | ||||||
|                                     ), |  | ||||||
|                                   ), |  | ||||||
|                                   Positioned( |  | ||||||
|                                     bottom: -30, |  | ||||||
|                                     left: 18, |  | ||||||
|                                     child: AccountImage( |  | ||||||
|                                       content: realm.avatar, |  | ||||||
|                                       radius: 24, |  | ||||||
|                                       fallbackWidget: const Icon(Symbols.group, size: 24), |  | ||||||
|                                     ), |  | ||||||
|                                   ), |  | ||||||
|                                 ], |  | ||||||
|                               ), |  | ||||||
|                             ), |  | ||||||
|                             const Gap(20 + 12), |  | ||||||
|                             Column( |  | ||||||
|                               crossAxisAlignment: CrossAxisAlignment.start, |  | ||||||
|                               children: [ |  | ||||||
|                                 Text(realm.name).textStyle(Theme.of(context).textTheme.titleMedium!), |  | ||||||
|                                 Text(realm.description).textStyle(Theme.of(context).textTheme.bodySmall!), |  | ||||||
|                               ], |  | ||||||
|                             ).padding(horizontal: 24, bottom: 14), |  | ||||||
|                           ], |  | ||||||
|                         ), |  | ||||||
|                     onTap: () { |                     onTap: () { | ||||||
|                       showModalBottomSheet( |                       showModalBottomSheet( | ||||||
|                         context: context, |                         context: context, | ||||||
|                         builder: (context) => _RealmJoinPopup(realm: realm), |                         builder: (context) => _RealmJoinPopup(realm: realm), | ||||||
|                       ); |                       ); | ||||||
|                     }, |                     }, | ||||||
|                       ), |                   ); | ||||||
|                     ), |  | ||||||
|                   ).center(); |  | ||||||
|                 }, |                 }, | ||||||
|               ), |               ), | ||||||
|             ), |             ), | ||||||
| @@ -235,6 +198,8 @@ class _RealmJoinPopupState extends State<_RealmJoinPopup> { | |||||||
|                   ), |                   ), | ||||||
|                   Text( |                   Text( | ||||||
|                     widget.realm.description, |                     widget.realm.description, | ||||||
|  |                     maxLines: 3, | ||||||
|  |                     overflow: TextOverflow.ellipsis, | ||||||
|                     style: Theme.of(context).textTheme.bodyMedium, |                     style: Theme.of(context).textTheme.bodyMedium, | ||||||
|                   ), |                   ), | ||||||
|                 ], |                 ], | ||||||
|   | |||||||
| @@ -5,8 +5,10 @@ import 'package:dropdown_button2/dropdown_button2.dart'; | |||||||
| import 'package:easy_localization/easy_localization.dart'; | import 'package:easy_localization/easy_localization.dart'; | ||||||
| import 'package:flutter/foundation.dart'; | import 'package:flutter/foundation.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:flutter/services.dart'; | ||||||
| import 'package:flutter_colorpicker/flutter_colorpicker.dart'; | import 'package:flutter_colorpicker/flutter_colorpicker.dart'; | ||||||
| import 'package:go_router/go_router.dart'; | import 'package:go_router/go_router.dart'; | ||||||
|  | import 'package:google_fonts/google_fonts.dart'; | ||||||
| import 'package:image_picker/image_picker.dart'; | import 'package:image_picker/image_picker.dart'; | ||||||
| import 'package:material_symbols_icons/symbols.dart'; | import 'package:material_symbols_icons/symbols.dart'; | ||||||
| import 'package:path_provider/path_provider.dart'; | import 'package:path_provider/path_provider.dart'; | ||||||
| @@ -14,7 +16,10 @@ import 'package:provider/provider.dart'; | |||||||
| import 'package:shared_preferences/shared_preferences.dart'; | import 'package:shared_preferences/shared_preferences.dart'; | ||||||
| import 'package:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
| import 'package:surface/providers/config.dart'; | import 'package:surface/providers/config.dart'; | ||||||
|  | import 'package:surface/providers/database.dart'; | ||||||
|  | import 'package:surface/providers/notification.dart'; | ||||||
| import 'package:surface/providers/sn_network.dart'; | import 'package:surface/providers/sn_network.dart'; | ||||||
|  | import 'package:surface/providers/sn_sticker.dart'; | ||||||
| import 'package:surface/providers/theme.dart'; | import 'package:surface/providers/theme.dart'; | ||||||
| import 'package:surface/theme.dart'; | import 'package:surface/theme.dart'; | ||||||
| import 'package:surface/widgets/dialog.dart'; | import 'package:surface/widgets/dialog.dart'; | ||||||
| @@ -67,6 +72,7 @@ class _SettingsScreenState extends State<SettingsScreen> { | |||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     final sn = context.read<SnNetworkProvider>(); |     final sn = context.read<SnNetworkProvider>(); | ||||||
|  |     final dt = context.read<DatabaseProvider>(); | ||||||
|  |  | ||||||
|     return AppScaffold( |     return AppScaffold( | ||||||
|       appBar: AppBar( |       appBar: AppBar( | ||||||
| @@ -81,7 +87,11 @@ class _SettingsScreenState extends State<SettingsScreen> { | |||||||
|             Column( |             Column( | ||||||
|               crossAxisAlignment: CrossAxisAlignment.start, |               crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|               children: [ |               children: [ | ||||||
|                 Text('settingsAppearance').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4), |                 Text('settingsAppearance') | ||||||
|  |                     .bold() | ||||||
|  |                     .fontSize(17) | ||||||
|  |                     .tr() | ||||||
|  |                     .padding(horizontal: 20, bottom: 4), | ||||||
|                 ListTile( |                 ListTile( | ||||||
|                   title: Text('settingsDisplayLanguage').tr(), |                   title: Text('settingsDisplayLanguage').tr(), | ||||||
|                   subtitle: Text('settingsDisplayLanguageDescription').tr(), |                   subtitle: Text('settingsDisplayLanguageDescription').tr(), | ||||||
| @@ -91,15 +101,21 @@ class _SettingsScreenState extends State<SettingsScreen> { | |||||||
|                     child: DropdownButton2<Locale?>( |                     child: DropdownButton2<Locale?>( | ||||||
|                       isExpanded: true, |                       isExpanded: true, | ||||||
|                       items: [ |                       items: [ | ||||||
|                         ...EasyLocalization.of(context)!.supportedLocales.mapIndexed((idx, ele) { |                         ...EasyLocalization.of(context)! | ||||||
|  |                             .supportedLocales | ||||||
|  |                             .mapIndexed((idx, ele) { | ||||||
|                           return DropdownMenuItem<Locale?>( |                           return DropdownMenuItem<Locale?>( | ||||||
|                             value: ele, |                             value: ele, | ||||||
|                             child: Text('${ele.languageCode}-${ele.countryCode}').fontSize(14), |                             child: | ||||||
|  |                                 Text('${ele.languageCode}-${ele.countryCode}') | ||||||
|  |                                     .fontSize(14), | ||||||
|                           ); |                           ); | ||||||
|                         }), |                         }), | ||||||
|                         DropdownMenuItem<Locale?>( |                         DropdownMenuItem<Locale?>( | ||||||
|                           value: null, |                           value: null, | ||||||
|                           child: Text('settingsDisplayLanguageSystem').tr().fontSize(14), |                           child: Text('settingsDisplayLanguageSystem') | ||||||
|  |                               .tr() | ||||||
|  |                               .fontSize(14), | ||||||
|                         ), |                         ), | ||||||
|                       ], |                       ], | ||||||
|                       value: EasyLocalization.of(context)!.currentLocale, |                       value: EasyLocalization.of(context)!.currentLocale, | ||||||
| @@ -132,10 +148,12 @@ class _SettingsScreenState extends State<SettingsScreen> { | |||||||
|                     leading: const Icon(Symbols.image), |                     leading: const Icon(Symbols.image), | ||||||
|                     trailing: const Icon(Symbols.chevron_right), |                     trailing: const Icon(Symbols.chevron_right), | ||||||
|                     onTap: () async { |                     onTap: () async { | ||||||
|                       final image = await ImagePicker().pickImage(source: ImageSource.gallery); |                       final image = await ImagePicker() | ||||||
|  |                           .pickImage(source: ImageSource.gallery); | ||||||
|                       if (image == null) return; |                       if (image == null) return; | ||||||
|  |  | ||||||
|                       await File(image.path).copy('$_docBasepath/app_background_image'); |                       await File(image.path) | ||||||
|  |                           .copy('$_docBasepath/app_background_image'); | ||||||
|                       _prefs.setBool(kAppBackgroundStoreKey, true); |                       _prefs.setBool(kAppBackgroundStoreKey, true); | ||||||
|  |  | ||||||
|                       setState(() {}); |                       setState(() {}); | ||||||
| @@ -143,7 +161,8 @@ class _SettingsScreenState extends State<SettingsScreen> { | |||||||
|                   ), |                   ), | ||||||
|                 if (!kIsWeb) |                 if (!kIsWeb) | ||||||
|                   FutureBuilder<bool>( |                   FutureBuilder<bool>( | ||||||
|                       future: File('$_docBasepath/app_background_image').exists(), |                       future: | ||||||
|  |                           File('$_docBasepath/app_background_image').exists(), | ||||||
|                       builder: (context, snapshot) { |                       builder: (context, snapshot) { | ||||||
|                         if (!snapshot.hasData || !snapshot.data!) { |                         if (!snapshot.hasData || !snapshot.data!) { | ||||||
|                           return const SizedBox.shrink(); |                           return const SizedBox.shrink(); | ||||||
| @@ -151,12 +170,16 @@ class _SettingsScreenState extends State<SettingsScreen> { | |||||||
|  |  | ||||||
|                         return ListTile( |                         return ListTile( | ||||||
|                           title: Text('settingsBackgroundImageClear').tr(), |                           title: Text('settingsBackgroundImageClear').tr(), | ||||||
|                           subtitle: Text('settingsBackgroundImageClearDescription').tr(), |                           subtitle: | ||||||
|                           contentPadding: const EdgeInsets.symmetric(horizontal: 24), |                               Text('settingsBackgroundImageClearDescription') | ||||||
|  |                                   .tr(), | ||||||
|  |                           contentPadding: | ||||||
|  |                               const EdgeInsets.symmetric(horizontal: 24), | ||||||
|                           leading: const Icon(Symbols.texture), |                           leading: const Icon(Symbols.texture), | ||||||
|                           trailing: const Icon(Symbols.chevron_right), |                           trailing: const Icon(Symbols.chevron_right), | ||||||
|                           onTap: () { |                           onTap: () { | ||||||
|                             File('$_docBasepath/app_background_image').deleteSync(); |                             File('$_docBasepath/app_background_image') | ||||||
|  |                                 .deleteSync(); | ||||||
|                             _prefs.remove(kAppBackgroundStoreKey); |                             _prefs.remove(kAppBackgroundStoreKey); | ||||||
|                             setState(() {}); |                             setState(() {}); | ||||||
|                           }, |                           }, | ||||||
| @@ -186,11 +209,12 @@ class _SettingsScreenState extends State<SettingsScreen> { | |||||||
|                   contentPadding: const EdgeInsets.symmetric(horizontal: 24), |                   contentPadding: const EdgeInsets.symmetric(horizontal: 24), | ||||||
|                   trailing: const Icon(Symbols.chevron_right), |                   trailing: const Icon(Symbols.chevron_right), | ||||||
|                   onTap: () async { |                   onTap: () async { | ||||||
|                     Color pickerColor = Color(_prefs.getInt(kAppColorSchemeStoreKey) ?? Colors.indigo.value); |                     Color pickerColor = Color( | ||||||
|  |                         _prefs.getInt(kAppColorSchemeStoreKey) ?? | ||||||
|  |                             Colors.indigo.value); | ||||||
|                     final color = await showDialog<Color?>( |                     final color = await showDialog<Color?>( | ||||||
|                       context: context, |                       context: context, | ||||||
|                       builder: (context) => |                       builder: (context) => AlertDialog( | ||||||
|                           AlertDialog( |  | ||||||
|                         content: SingleChildScrollView( |                         content: SingleChildScrollView( | ||||||
|                           child: ColorPicker( |                           child: ColorPicker( | ||||||
|                             pickerColor: pickerColor, |                             pickerColor: pickerColor, | ||||||
| @@ -248,16 +272,17 @@ class _SettingsScreenState extends State<SettingsScreen> { | |||||||
|                       ], |                       ], | ||||||
|                       value: _prefs.getInt(kAppColorSchemeStoreKey) == null |                       value: _prefs.getInt(kAppColorSchemeStoreKey) == null | ||||||
|                           ? 1 |                           ? 1 | ||||||
|                           : kColorSchemes.values |                           : kColorSchemes.values.toList().indexWhere((ele) => | ||||||
|                           .toList() |                               ele.value == | ||||||
|                           .indexWhere((ele) => ele.value == _prefs.getInt(kAppColorSchemeStoreKey)), |                               _prefs.getInt(kAppColorSchemeStoreKey)), | ||||||
|                       onChanged: (int? value) { |                       onChanged: (int? value) { | ||||||
|                         if (value != null && value != -1) { |                         if (value != null && value != -1) { | ||||||
|                           _prefs.setInt(kAppColorSchemeStoreKey, kColorSchemes.values |                           _prefs.setInt(kAppColorSchemeStoreKey, | ||||||
|                               .elementAt(value) |                               kColorSchemes.values.elementAt(value).value); | ||||||
|                               .value); |  | ||||||
|                           final th = context.read<ThemeProvider>(); |                           final th = context.read<ThemeProvider>(); | ||||||
|                           th.reloadTheme(seedColorOverride: kColorSchemes.values.elementAt(value)); |                           th.reloadTheme( | ||||||
|  |                               seedColorOverride: | ||||||
|  |                                   kColorSchemes.values.elementAt(value)); | ||||||
|                           setState(() {}); |                           setState(() {}); | ||||||
|  |  | ||||||
|                           context.showSnackbar('colorSchemeApplied'.tr()); |                           context.showSnackbar('colorSchemeApplied'.tr()); | ||||||
| @@ -293,7 +318,8 @@ class _SettingsScreenState extends State<SettingsScreen> { | |||||||
|                 CheckboxListTile( |                 CheckboxListTile( | ||||||
|                   secondary: const Icon(Symbols.left_panel_close), |                   secondary: const Icon(Symbols.left_panel_close), | ||||||
|                   title: Text('settingsDrawerPreferCollapse').tr(), |                   title: Text('settingsDrawerPreferCollapse').tr(), | ||||||
|                   subtitle: Text('settingsDrawerPreferCollapseDescription').tr(), |                   subtitle: | ||||||
|  |                       Text('settingsDrawerPreferCollapseDescription').tr(), | ||||||
|                   contentPadding: const EdgeInsets.only(left: 24, right: 17), |                   contentPadding: const EdgeInsets.only(left: 24, right: 17), | ||||||
|                   value: _prefs.getBool(kAppDrawerPreferCollapse) ?? false, |                   value: _prefs.getBool(kAppDrawerPreferCollapse) ?? false, | ||||||
|                   onChanged: (value) { |                   onChanged: (value) { | ||||||
| @@ -308,7 +334,11 @@ class _SettingsScreenState extends State<SettingsScreen> { | |||||||
|             Column( |             Column( | ||||||
|               crossAxisAlignment: CrossAxisAlignment.start, |               crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|               children: [ |               children: [ | ||||||
|                 Text('settingsFeatures').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4), |                 Text('settingsFeatures') | ||||||
|  |                     .bold() | ||||||
|  |                     .fontSize(17) | ||||||
|  |                     .tr() | ||||||
|  |                     .padding(horizontal: 20, bottom: 4), | ||||||
|                 CheckboxListTile( |                 CheckboxListTile( | ||||||
|                   secondary: const Icon(Symbols.vibration), |                   secondary: const Icon(Symbols.vibration), | ||||||
|                   contentPadding: const EdgeInsets.only(left: 24, right: 17), |                   contentPadding: const EdgeInsets.only(left: 24, right: 17), | ||||||
| @@ -350,7 +380,11 @@ class _SettingsScreenState extends State<SettingsScreen> { | |||||||
|             Column( |             Column( | ||||||
|               crossAxisAlignment: CrossAxisAlignment.start, |               crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|               children: [ |               children: [ | ||||||
|                 Text('settingsNetwork').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4), |                 Text('settingsNetwork') | ||||||
|  |                     .bold() | ||||||
|  |                     .fontSize(17) | ||||||
|  |                     .tr() | ||||||
|  |                     .padding(horizontal: 20, bottom: 4), | ||||||
|                 TextField( |                 TextField( | ||||||
|                   controller: _serverUrlController, |                   controller: _serverUrlController, | ||||||
|                   decoration: InputDecoration( |                   decoration: InputDecoration( | ||||||
| @@ -371,7 +405,8 @@ class _SettingsScreenState extends State<SettingsScreen> { | |||||||
|                       }, |                       }, | ||||||
|                     ), |                     ), | ||||||
|                   ), |                   ), | ||||||
|                   onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), |                   onTapOutside: (_) => | ||||||
|  |                       FocusManager.instance.primaryFocus?.unfocus(), | ||||||
|                 ).padding(horizontal: 16, top: 8, bottom: 4), |                 ).padding(horizontal: 16, top: 8, bottom: 4), | ||||||
|                 ListTile( |                 ListTile( | ||||||
|                   title: Text('settingsNetworkServerPreset').tr(), |                   title: Text('settingsNetworkServerPreset').tr(), | ||||||
| @@ -383,12 +418,13 @@ class _SettingsScreenState extends State<SettingsScreen> { | |||||||
|                       isExpanded: true, |                       isExpanded: true, | ||||||
|                       items: [ |                       items: [ | ||||||
|                         ...kNetworkServerDirectory, |                         ...kNetworkServerDirectory, | ||||||
|                         if (!kNetworkServerDirectory.map((ele) => ele.$2).contains(_serverUrlController.text)) |                         if (!kNetworkServerDirectory | ||||||
|  |                             .map((ele) => ele.$2) | ||||||
|  |                             .contains(_serverUrlController.text)) | ||||||
|                           ('Custom', _serverUrlController.text), |                           ('Custom', _serverUrlController.text), | ||||||
|                       ] |                       ] | ||||||
|                           .map( |                           .map( | ||||||
|                             (item) => |                             (item) => DropdownMenuItem<String>( | ||||||
|                             DropdownMenuItem<String>( |  | ||||||
|                               value: item.$2, |                               value: item.$2, | ||||||
|                               child: Column( |                               child: Column( | ||||||
|                                 mainAxisSize: MainAxisSize.max, |                                 mainAxisSize: MainAxisSize.max, | ||||||
| @@ -396,7 +432,8 @@ class _SettingsScreenState extends State<SettingsScreen> { | |||||||
|                                 crossAxisAlignment: CrossAxisAlignment.start, |                                 crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|                                 children: [ |                                 children: [ | ||||||
|                                   Text(item.$1).fontSize(14), |                                   Text(item.$1).fontSize(14), | ||||||
|                                   Text(item.$2, overflow: TextOverflow.ellipsis).fontSize(11) |                                   Text(item.$2, overflow: TextOverflow.ellipsis) | ||||||
|  |                                       .fontSize(11) | ||||||
|                                 ], |                                 ], | ||||||
|                               ), |                               ), | ||||||
|                             ), |                             ), | ||||||
| @@ -442,7 +479,11 @@ class _SettingsScreenState extends State<SettingsScreen> { | |||||||
|             Column( |             Column( | ||||||
|               crossAxisAlignment: CrossAxisAlignment.start, |               crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|               children: [ |               children: [ | ||||||
|                 Text('settingsPerformance').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4), |                 Text('settingsPerformance') | ||||||
|  |                     .bold() | ||||||
|  |                     .fontSize(17) | ||||||
|  |                     .tr() | ||||||
|  |                     .padding(horizontal: 20, bottom: 4), | ||||||
|                 ListTile( |                 ListTile( | ||||||
|                   title: Text('settingsImageQuality').tr(), |                   title: Text('settingsImageQuality').tr(), | ||||||
|                   subtitle: Text('settingsImageQualityDescription').tr(), |                   subtitle: Text('settingsImageQualityDescription').tr(), | ||||||
| @@ -450,13 +491,13 @@ class _SettingsScreenState extends State<SettingsScreen> { | |||||||
|                   leading: const Icon(Symbols.image), |                   leading: const Icon(Symbols.image), | ||||||
|                   trailing: DropdownButtonHideUnderline( |                   trailing: DropdownButtonHideUnderline( | ||||||
|                     child: DropdownButton2<FilterQuality>( |                     child: DropdownButton2<FilterQuality>( | ||||||
|                       value: kImageQualityLevel.values.elementAtOrNull(_prefs.getInt('app_image_quality') ?? 3) ?? |                       value: kImageQualityLevel.values.elementAtOrNull( | ||||||
|  |                               _prefs.getInt('app_image_quality') ?? 3) ?? | ||||||
|                           FilterQuality.high, |                           FilterQuality.high, | ||||||
|                       isExpanded: true, |                       isExpanded: true, | ||||||
|                       items: kImageQualityLevel.entries |                       items: kImageQualityLevel.entries | ||||||
|                           .map( |                           .map( | ||||||
|                             (item) => |                             (item) => DropdownMenuItem<FilterQuality>( | ||||||
|                             DropdownMenuItem<FilterQuality>( |  | ||||||
|                               value: item.value, |                               value: item.value, | ||||||
|                               child: Text(item.key).tr().fontSize(14), |                               child: Text(item.key).tr().fontSize(14), | ||||||
|                             ), |                             ), | ||||||
| @@ -464,7 +505,8 @@ class _SettingsScreenState extends State<SettingsScreen> { | |||||||
|                           .toList(), |                           .toList(), | ||||||
|                       onChanged: (FilterQuality? value) { |                       onChanged: (FilterQuality? value) { | ||||||
|                         if (value == null) return; |                         if (value == null) return; | ||||||
|                         _prefs.setInt('app_image_quality', kImageQualityLevel.values.toList().indexOf(value)); |                         _prefs.setInt('app_image_quality', | ||||||
|  |                             kImageQualityLevel.values.toList().indexOf(value)); | ||||||
|                         setState(() {}); |                         setState(() {}); | ||||||
|                       }, |                       }, | ||||||
|                       buttonStyleData: const ButtonStyleData( |                       buttonStyleData: const ButtonStyleData( | ||||||
| @@ -486,7 +528,82 @@ class _SettingsScreenState extends State<SettingsScreen> { | |||||||
|             Column( |             Column( | ||||||
|               crossAxisAlignment: CrossAxisAlignment.start, |               crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|               children: [ |               children: [ | ||||||
|                 Text('settingsMisc').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4), |                 Text('settingsMisc') | ||||||
|  |                     .bold() | ||||||
|  |                     .fontSize(17) | ||||||
|  |                     .tr() | ||||||
|  |                     .padding(horizontal: 20, bottom: 4), | ||||||
|  |                 ListTile( | ||||||
|  |                   leading: const Icon(Symbols.database), | ||||||
|  |                   contentPadding: const EdgeInsets.symmetric(horizontal: 24), | ||||||
|  |                   title: Text('databaseSize').tr(), | ||||||
|  |                   subtitle: FutureBuilder( | ||||||
|  |                     future: dt.getDatabaseSize(), | ||||||
|  |                     builder: (context, snapshot) { | ||||||
|  |                       if (!snapshot.hasData || kIsWeb) { | ||||||
|  |                         return Text('unknown').tr(); | ||||||
|  |                       } | ||||||
|  |                       return Text( | ||||||
|  |                         snapshot.data!.formatBytes(), | ||||||
|  |                         style: GoogleFonts.robotoMono(), | ||||||
|  |                       ); | ||||||
|  |                     }, | ||||||
|  |                   ), | ||||||
|  |                 ), | ||||||
|  |                 ListTile( | ||||||
|  |                   leading: const Icon(Symbols.database_off), | ||||||
|  |                   contentPadding: const EdgeInsets.symmetric(horizontal: 24), | ||||||
|  |                   title: Text('databaseDelete').tr(), | ||||||
|  |                   subtitle: Text('databaseDeleteDescription').tr(), | ||||||
|  |                   trailing: const Icon(Symbols.chevron_right), | ||||||
|  |                   onTap: () async { | ||||||
|  |                     await dt.removeDatabase(); | ||||||
|  |                     if (!context.mounted) return; | ||||||
|  |                     HapticFeedback.heavyImpact(); | ||||||
|  |                     context.showSnackbar('databaseDeleted'.tr()); | ||||||
|  |                     setState(() {}); | ||||||
|  |                   }, | ||||||
|  |                 ), | ||||||
|  |                 ListTile( | ||||||
|  |                   leading: const Icon(Symbols.notifications), | ||||||
|  |                   title: Text('settingsEnablePushNotifications').tr(), | ||||||
|  |                   subtitle: | ||||||
|  |                       Text('settingsEnablePushNotificationsDescription').tr(), | ||||||
|  |                   contentPadding: const EdgeInsets.symmetric(horizontal: 24), | ||||||
|  |                   trailing: const Icon(Symbols.chevron_right), | ||||||
|  |                   onTap: () async { | ||||||
|  |                     final nty = context.read<NotificationProvider>(); | ||||||
|  |                     try { | ||||||
|  |                       await nty.registerPushNotifications(); | ||||||
|  |                       if (!context.mounted) return; | ||||||
|  |                       HapticFeedback.heavyImpact(); | ||||||
|  |                       context.showSnackbar( | ||||||
|  |                           'settingsEnabledPushNotifications'.tr()); | ||||||
|  |                     } catch (err) { | ||||||
|  |                       if (!mounted) return; | ||||||
|  |                       context.showErrorDialog(err); | ||||||
|  |                     } | ||||||
|  |                   }, | ||||||
|  |                 ), | ||||||
|  |                 ListTile( | ||||||
|  |                   leading: const Icon(Symbols.refresh), | ||||||
|  |                   title: Text('stickersReload').tr(), | ||||||
|  |                   subtitle: Text('stickersReloadDescription').tr(), | ||||||
|  |                   contentPadding: const EdgeInsets.symmetric(horizontal: 24), | ||||||
|  |                   trailing: const Icon(Symbols.chevron_right), | ||||||
|  |                   onTap: () async { | ||||||
|  |                     final stickers = context.read<SnStickerProvider>(); | ||||||
|  |                     try { | ||||||
|  |                       await stickers.listSticker(); | ||||||
|  |                       if (!context.mounted) return; | ||||||
|  |                       HapticFeedback.heavyImpact(); | ||||||
|  |                       context.showSnackbar('stickersReloaded'.tr()); | ||||||
|  |                     } catch (err) { | ||||||
|  |                       if (!context.mounted) return; | ||||||
|  |                       context.showErrorDialog(err); | ||||||
|  |                     } | ||||||
|  |                   }, | ||||||
|  |                 ), | ||||||
|                 ListTile( |                 ListTile( | ||||||
|                   title: Text('settingsMiscAbout').tr(), |                   title: Text('settingsMiscAbout').tr(), | ||||||
|                   subtitle: Text('settingsMiscAboutDescription').tr(), |                   subtitle: Text('settingsMiscAboutDescription').tr(), | ||||||
|   | |||||||
							
								
								
									
										464
									
								
								lib/screens/stickers.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										464
									
								
								lib/screens/stickers.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,464 @@ | |||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:gap/gap.dart'; | ||||||
|  | import 'package:go_router/go_router.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:provider/provider.dart'; | ||||||
|  | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  | import 'package:surface/providers/sn_network.dart'; | ||||||
|  | import 'package:surface/providers/sn_sticker.dart'; | ||||||
|  | import 'package:surface/providers/userinfo.dart'; | ||||||
|  | import 'package:surface/types/attachment.dart'; | ||||||
|  | import 'package:surface/widgets/app_bar_leading.dart'; | ||||||
|  | import 'package:surface/widgets/attachment/attachment_item.dart'; | ||||||
|  | import 'package:surface/widgets/dialog.dart'; | ||||||
|  | import 'package:surface/widgets/loading_indicator.dart'; | ||||||
|  | import 'package:surface/widgets/navigation/app_scaffold.dart'; | ||||||
|  | import 'package:very_good_infinite_list/very_good_infinite_list.dart'; | ||||||
|  |  | ||||||
|  | class StickerScreen extends StatefulWidget { | ||||||
|  |   const StickerScreen({super.key}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   State<StickerScreen> createState() => _StickerScreenState(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _StickerScreenState extends State<StickerScreen> | ||||||
|  |     with SingleTickerProviderStateMixin { | ||||||
|  |   late final TabController _tabController = | ||||||
|  |       TabController(length: 3, vsync: this); | ||||||
|  |  | ||||||
|  |   bool _isBusy = false; | ||||||
|  |   int? _totalCount; | ||||||
|  |   final List<SnStickerPack> _packs = List.empty(growable: true); | ||||||
|  |  | ||||||
|  |   Future<void> _fetchPacks() async { | ||||||
|  |     try { | ||||||
|  |       setState(() => _isBusy = true); | ||||||
|  |       final sn = context.read<SnNetworkProvider>(); | ||||||
|  |       final ua = context.read<UserProvider>(); | ||||||
|  |       final resp = await sn.client.get( | ||||||
|  |         _tabController.index == 1 | ||||||
|  |             ? '/cgi/uc/stickers/packs/own' | ||||||
|  |             : '/cgi/uc/stickers/packs', | ||||||
|  |         queryParameters: { | ||||||
|  |           'take': 10, | ||||||
|  |           'offset': _packs.length, | ||||||
|  |           if (_tabController.index == 2) 'author': ua.user?.id, | ||||||
|  |         }, | ||||||
|  |       ); | ||||||
|  |       if (resp.data is Map<String, dynamic>) { | ||||||
|  |         _totalCount = resp.data['count'] as int?; | ||||||
|  |         final out = List<SnStickerPack>.from( | ||||||
|  |           resp.data['data'].map((ele) => SnStickerPack.fromJson(ele)), | ||||||
|  |         ); | ||||||
|  |         _packs.addAll(out); | ||||||
|  |       } else { | ||||||
|  |         _totalCount = 0; | ||||||
|  |         final out = List<SnStickerPack>.from( | ||||||
|  |           resp.data.map((ele) => SnStickerPack.fromJson(ele)), | ||||||
|  |         ); | ||||||
|  |         _packs.addAll(out); | ||||||
|  |       } | ||||||
|  |     } catch (err) { | ||||||
|  |       if (!mounted) return; | ||||||
|  |       context.showErrorDialog(err); | ||||||
|  |     } finally { | ||||||
|  |       setState(() => _isBusy = false); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Future<void> _removePack(SnStickerPack pack) async { | ||||||
|  |     try { | ||||||
|  |       setState(() => _isBusy = true); | ||||||
|  |       final sn = context.read<SnNetworkProvider>(); | ||||||
|  |       await sn.client.delete('/cgi/uc/stickers/packs/${pack.id}/own'); | ||||||
|  |       if (!mounted) return; | ||||||
|  |       context.showSnackbar('stickersRemoved'.tr()); | ||||||
|  |       _refreshPacks(); | ||||||
|  |     } catch (err) { | ||||||
|  |       if (!mounted) return; | ||||||
|  |       context.showErrorDialog(err); | ||||||
|  |     } finally { | ||||||
|  |       setState(() => _isBusy = false); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Future<void> _deletePack(SnStickerPack pack) async { | ||||||
|  |     final confirm = await context.showConfirmDialog( | ||||||
|  |       'stickersPackDelete'.tr(args: [pack.name]), | ||||||
|  |       'stickersPackDeleteDescription'.tr(), | ||||||
|  |     ); | ||||||
|  |     if (!confirm) return; | ||||||
|  |     if (!mounted) return; | ||||||
|  |  | ||||||
|  |     try { | ||||||
|  |       setState(() => _isBusy = true); | ||||||
|  |       final sn = context.read<SnNetworkProvider>(); | ||||||
|  |       await sn.client.delete('/cgi/uc/stickers/packs/${pack.id}'); | ||||||
|  |       if (!mounted) return; | ||||||
|  |       context.showSnackbar('stickersDeleted'.tr()); | ||||||
|  |       _refreshPacks(); | ||||||
|  |     } catch (err) { | ||||||
|  |       if (!mounted) return; | ||||||
|  |       context.showErrorDialog(err); | ||||||
|  |     } finally { | ||||||
|  |       setState(() => _isBusy = false); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Future<void> _refreshPacks() async { | ||||||
|  |     _packs.clear(); | ||||||
|  |     _totalCount = null; | ||||||
|  |     await _fetchPacks(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void initState() { | ||||||
|  |     super.initState(); | ||||||
|  |     _fetchPacks(); | ||||||
|  |     _tabController.addListener(() { | ||||||
|  |       if (_tabController.indexIsChanging) { | ||||||
|  |         _refreshPacks(); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void dispose() { | ||||||
|  |     _tabController.dispose(); | ||||||
|  |     super.dispose(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return AppScaffold( | ||||||
|  |       appBar: AppBar( | ||||||
|  |         leading: AutoAppBarLeading(), | ||||||
|  |         title: Text('screenStickers').tr(), | ||||||
|  |         actions: [ | ||||||
|  |           IconButton( | ||||||
|  |             icon: const Icon(Symbols.add_circle), | ||||||
|  |             onPressed: () { | ||||||
|  |               showDialog( | ||||||
|  |                 context: context, | ||||||
|  |                 builder: (context) => _StickerPackCreateDialog(), | ||||||
|  |               ).then((value) { | ||||||
|  |                 if (value == true) _refreshPacks(); | ||||||
|  |               }); | ||||||
|  |             }, | ||||||
|  |           ), | ||||||
|  |           const Gap(8), | ||||||
|  |         ], | ||||||
|  |         bottom: TabBar( | ||||||
|  |           controller: _tabController, | ||||||
|  |           tabs: [ | ||||||
|  |             Tab( | ||||||
|  |               child: Text('stickersDiscovery'.tr()).textColor( | ||||||
|  |                 Theme.of(context).appBarTheme.foregroundColor, | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |             Tab( | ||||||
|  |               child: Text('stickersOwned'.tr()).textColor( | ||||||
|  |                 Theme.of(context).appBarTheme.foregroundColor, | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |             Tab( | ||||||
|  |               child: Text('stickersCreated'.tr()).textColor( | ||||||
|  |                 Theme.of(context).appBarTheme.foregroundColor, | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |       body: MediaQuery.removePadding( | ||||||
|  |         context: context, | ||||||
|  |         removeTop: true, | ||||||
|  |         child: RefreshIndicator( | ||||||
|  |           onRefresh: _refreshPacks, | ||||||
|  |           child: InfiniteList( | ||||||
|  |             itemCount: _packs.length, | ||||||
|  |             onFetchData: _fetchPacks, | ||||||
|  |             hasReachedMax: _totalCount != null && _packs.length >= _totalCount!, | ||||||
|  |             isLoading: _isBusy, | ||||||
|  |             itemBuilder: (context, idx) { | ||||||
|  |               final pack = _packs[idx]; | ||||||
|  |               return ListTile( | ||||||
|  |                 title: Text(pack.name), | ||||||
|  |                 subtitle: Text( | ||||||
|  |                   pack.description, | ||||||
|  |                   maxLines: 1, | ||||||
|  |                   overflow: TextOverflow.ellipsis, | ||||||
|  |                 ), | ||||||
|  |                 contentPadding: const EdgeInsets.symmetric(horizontal: 16), | ||||||
|  |                 trailing: _tabController.index == 1 | ||||||
|  |                     ? IconButton( | ||||||
|  |                         onPressed: () { | ||||||
|  |                           _removePack(pack); | ||||||
|  |                         }, | ||||||
|  |                         icon: const Icon(Symbols.remove), | ||||||
|  |                       ) | ||||||
|  |                     : _tabController.index == 2 | ||||||
|  |                         ? IconButton( | ||||||
|  |                             onPressed: () { | ||||||
|  |                               _deletePack(pack); | ||||||
|  |                             }, | ||||||
|  |                             icon: const Icon(Symbols.delete), | ||||||
|  |                           ) | ||||||
|  |                         : null, | ||||||
|  |                 onTap: () { | ||||||
|  |                   if (_tabController.index == 0) { | ||||||
|  |                     showModalBottomSheet( | ||||||
|  |                       context: context, | ||||||
|  |                       builder: (context) => _StickerPackAddPopup(pack: pack), | ||||||
|  |                     ).then((value) { | ||||||
|  |                       if (value == true && _tabController.index == 1) { | ||||||
|  |                         _refreshPacks(); | ||||||
|  |                       } | ||||||
|  |                     }); | ||||||
|  |                   } else { | ||||||
|  |                     GoRouter.of(context).pushNamed( | ||||||
|  |                       'stickerPack', | ||||||
|  |                       pathParameters: { | ||||||
|  |                         'id': pack.id.toString(), | ||||||
|  |                       }, | ||||||
|  |                     ); | ||||||
|  |                   } | ||||||
|  |                 }, | ||||||
|  |               ); | ||||||
|  |             }, | ||||||
|  |           ), | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _StickerPackAddPopup extends StatefulWidget { | ||||||
|  |   final SnStickerPack pack; | ||||||
|  |   const _StickerPackAddPopup({required this.pack}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   State<_StickerPackAddPopup> createState() => _StickerPackAddPopupState(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _StickerPackAddPopupState extends State<_StickerPackAddPopup> { | ||||||
|  |   SnStickerPack? _pack; | ||||||
|  |  | ||||||
|  |   bool _isBusy = false; | ||||||
|  |  | ||||||
|  |   Future<void> _fetchPack() async { | ||||||
|  |     try { | ||||||
|  |       setState(() => _isBusy = true); | ||||||
|  |       final sn = context.read<SnNetworkProvider>(); | ||||||
|  |       final resp = | ||||||
|  |           await sn.client.get('/cgi/uc/stickers/packs/${widget.pack.id}'); | ||||||
|  |       _pack = SnStickerPack.fromJson(resp.data); | ||||||
|  |     } catch (err) { | ||||||
|  |       if (!mounted) return; | ||||||
|  |       context.showErrorDialog(err); | ||||||
|  |     } finally { | ||||||
|  |       setState(() => _isBusy = false); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void initState() { | ||||||
|  |     super.initState(); | ||||||
|  |     _fetchPack(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   bool _isAdding = false; | ||||||
|  |  | ||||||
|  |   Future<void> _addPack() async { | ||||||
|  |     if (_pack == null) return; | ||||||
|  |  | ||||||
|  |     try { | ||||||
|  |       setState(() => _isAdding = true); | ||||||
|  |       final sn = context.read<SnNetworkProvider>(); | ||||||
|  |       final stickers = context.read<SnStickerProvider>(); | ||||||
|  |       await sn.client.post( | ||||||
|  |         '/cgi/uc/stickers/packs/${widget.pack.id}/own', | ||||||
|  |       ); | ||||||
|  |       if (!mounted) return; | ||||||
|  |       context.showSnackbar('stickersAdded'.tr()); | ||||||
|  |       if (_pack?.stickers != null) stickers.putSticker(_pack!.stickers!); | ||||||
|  |       Navigator.pop(context, true); | ||||||
|  |     } catch (err) { | ||||||
|  |       if (!mounted) return; | ||||||
|  |       context.showErrorDialog(err); | ||||||
|  |     } finally { | ||||||
|  |       setState(() => _isAdding = false); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return Column( | ||||||
|  |       crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |       children: [ | ||||||
|  |         Row( | ||||||
|  |           crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|  |           children: [ | ||||||
|  |             const Icon(Symbols.add, size: 24), | ||||||
|  |             const Gap(16), | ||||||
|  |             Text('stickersAdd', style: Theme.of(context).textTheme.titleLarge) | ||||||
|  |                 .tr(), | ||||||
|  |           ], | ||||||
|  |         ).padding(horizontal: 20, top: 16, bottom: 12), | ||||||
|  |         Row( | ||||||
|  |           crossAxisAlignment: CrossAxisAlignment.end, | ||||||
|  |           children: [ | ||||||
|  |             Expanded( | ||||||
|  |               child: Column( | ||||||
|  |                 crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |                 children: [ | ||||||
|  |                   Text(widget.pack.name).bold(), | ||||||
|  |                   Text( | ||||||
|  |                     widget.pack.description, | ||||||
|  |                     maxLines: 2, | ||||||
|  |                     overflow: TextOverflow.ellipsis, | ||||||
|  |                   ), | ||||||
|  |                 ], | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |             ElevatedButton( | ||||||
|  |               onPressed: _isAdding ? null : _addPack, | ||||||
|  |               child: Text('add').tr(), | ||||||
|  |             ), | ||||||
|  |           ], | ||||||
|  |         ).padding(horizontal: 24), | ||||||
|  |         LoadingIndicator(isActive: _isBusy), | ||||||
|  |         if (_pack?.stickers != null) | ||||||
|  |           Expanded( | ||||||
|  |             child: GridView.extent( | ||||||
|  |               padding: EdgeInsets.only(left: 20, right: 20, top: 8), | ||||||
|  |               maxCrossAxisExtent: 48, | ||||||
|  |               mainAxisSpacing: 8, | ||||||
|  |               crossAxisSpacing: 8, | ||||||
|  |               children: _pack!.stickers! | ||||||
|  |                   .map( | ||||||
|  |                     (ele) => ClipRRect( | ||||||
|  |                       borderRadius: BorderRadius.circular(8), | ||||||
|  |                       child: Container( | ||||||
|  |                         color: | ||||||
|  |                             Theme.of(context).colorScheme.surfaceContainerHigh, | ||||||
|  |                         child: AttachmentItem( | ||||||
|  |                           data: ele.attachment, | ||||||
|  |                           heroTag: 'sticker-pack-${ele.attachment.rid}', | ||||||
|  |                           fit: BoxFit.contain, | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|  |                     ), | ||||||
|  |                   ) | ||||||
|  |                   .toList(), | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |       ], | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _StickerPackCreateDialog extends StatefulWidget { | ||||||
|  |   const _StickerPackCreateDialog(); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   State<_StickerPackCreateDialog> createState() => | ||||||
|  |       _StickerPackCreateDialogState(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _StickerPackCreateDialogState extends State<_StickerPackCreateDialog> { | ||||||
|  |   final TextEditingController _nameController = TextEditingController(); | ||||||
|  |   final TextEditingController _prefixController = TextEditingController(); | ||||||
|  |   final TextEditingController _descriptionController = TextEditingController(); | ||||||
|  |  | ||||||
|  |   bool _isBusy = false; | ||||||
|  |  | ||||||
|  |   Future<void> _createPack() async { | ||||||
|  |     if (_nameController.text.isEmpty || | ||||||
|  |         _prefixController.text.isEmpty || | ||||||
|  |         _descriptionController.text.isEmpty) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     setState(() => _isBusy = true); | ||||||
|  |  | ||||||
|  |     try { | ||||||
|  |       final sn = context.read<SnNetworkProvider>(); | ||||||
|  |       await sn.client.post( | ||||||
|  |         '/cgi/uc/stickers/packs', | ||||||
|  |         data: { | ||||||
|  |           'name': _nameController.text, | ||||||
|  |           'prefix': _prefixController.text, | ||||||
|  |           'description': _descriptionController.text, | ||||||
|  |         }, | ||||||
|  |       ); | ||||||
|  |       if (!mounted) return; | ||||||
|  |       Navigator.pop(context, true); | ||||||
|  |     } catch (err) { | ||||||
|  |       if (!mounted) return; | ||||||
|  |       context.showErrorDialog(err); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void dispose() { | ||||||
|  |     _nameController.dispose(); | ||||||
|  |     _prefixController.dispose(); | ||||||
|  |     _descriptionController.dispose(); | ||||||
|  |     super.dispose(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return AlertDialog( | ||||||
|  |       title: Text('stickersPackNew').tr(), | ||||||
|  |       content: Column( | ||||||
|  |         mainAxisSize: MainAxisSize.min, | ||||||
|  |         crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |         children: [ | ||||||
|  |           TextField( | ||||||
|  |             controller: _nameController, | ||||||
|  |             decoration: InputDecoration( | ||||||
|  |               border: const UnderlineInputBorder(), | ||||||
|  |               labelText: 'fieldStickerPackName'.tr(), | ||||||
|  |             ), | ||||||
|  |             onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||||
|  |           ), | ||||||
|  |           const Gap(4), | ||||||
|  |           TextField( | ||||||
|  |             controller: _prefixController, | ||||||
|  |             decoration: InputDecoration( | ||||||
|  |               border: const UnderlineInputBorder(), | ||||||
|  |               labelText: 'fieldStickerPackPrefix'.tr(), | ||||||
|  |             ), | ||||||
|  |             onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||||
|  |           ), | ||||||
|  |           const Gap(4), | ||||||
|  |           TextField( | ||||||
|  |             controller: _descriptionController, | ||||||
|  |             decoration: InputDecoration( | ||||||
|  |               border: const UnderlineInputBorder(), | ||||||
|  |               labelText: 'fieldStickerPackDescription'.tr(), | ||||||
|  |             ), | ||||||
|  |             onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||||
|  |           ), | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|  |       actions: [ | ||||||
|  |         TextButton( | ||||||
|  |           onPressed: _isBusy | ||||||
|  |               ? null | ||||||
|  |               : () { | ||||||
|  |                   Navigator.pop(context); | ||||||
|  |                 }, | ||||||
|  |           child: Text('dialogDismiss').tr(), | ||||||
|  |         ), | ||||||
|  |         TextButton( | ||||||
|  |           onPressed: _isBusy ? null : () => _createPack(), | ||||||
|  |           child: Text('dialogConfirm').tr(), | ||||||
|  |         ), | ||||||
|  |       ], | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										266
									
								
								lib/screens/stickers/pack_detail.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										266
									
								
								lib/screens/stickers/pack_detail.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,266 @@ | |||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:gap/gap.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:provider/provider.dart'; | ||||||
|  | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  | import 'package:surface/providers/sn_network.dart'; | ||||||
|  | import 'package:surface/types/attachment.dart'; | ||||||
|  | import 'package:surface/widgets/attachment/attachment_input.dart'; | ||||||
|  | import 'package:surface/widgets/attachment/attachment_item.dart'; | ||||||
|  | import 'package:surface/widgets/dialog.dart'; | ||||||
|  | import 'package:surface/widgets/loading_indicator.dart'; | ||||||
|  | import 'package:surface/widgets/navigation/app_scaffold.dart'; | ||||||
|  |  | ||||||
|  | class StickerPackScreen extends StatefulWidget { | ||||||
|  |   final int id; | ||||||
|  |   const StickerPackScreen({super.key, required this.id}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   State<StickerPackScreen> createState() => _StickerPackScreenState(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _StickerPackScreenState extends State<StickerPackScreen> { | ||||||
|  |   SnStickerPack? _pack; | ||||||
|  |  | ||||||
|  |   Future<void> _fetchPack() async { | ||||||
|  |     try { | ||||||
|  |       setState(() => _isBusy = true); | ||||||
|  |       final sn = context.read<SnNetworkProvider>(); | ||||||
|  |       final resp = await sn.client.get('/cgi/uc/stickers/packs/${widget.id}'); | ||||||
|  |       _pack = SnStickerPack.fromJson(resp.data); | ||||||
|  |     } catch (err) { | ||||||
|  |       if (!mounted) return; | ||||||
|  |       context.showErrorDialog(err); | ||||||
|  |     } finally { | ||||||
|  |       setState(() => _isBusy = false); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   bool _isBusy = false; | ||||||
|  |  | ||||||
|  |   Future<void> _deleteSticker(SnSticker sticker) async { | ||||||
|  |     final confirm = await context.showConfirmDialog( | ||||||
|  |       'stickersDelete'.tr(args: [sticker.name]), | ||||||
|  |       'stickersDeleteDescription'.tr(), | ||||||
|  |     ); | ||||||
|  |     if (!confirm) return; | ||||||
|  |     if (!mounted) return; | ||||||
|  |  | ||||||
|  |     try { | ||||||
|  |       setState(() => _isBusy = true); | ||||||
|  |       final sn = context.read<SnNetworkProvider>(); | ||||||
|  |       await sn.client.delete('/cgi/uc/stickers/${sticker.id}'); | ||||||
|  |       if (!mounted) return; | ||||||
|  |       context.showSnackbar('stickersDeleted'.tr()); | ||||||
|  |       _fetchPack(); | ||||||
|  |     } catch (err) { | ||||||
|  |       if (!mounted) return; | ||||||
|  |       context.showErrorDialog(err); | ||||||
|  |     } finally { | ||||||
|  |       setState(() => _isBusy = false); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void initState() { | ||||||
|  |     super.initState(); | ||||||
|  |     _fetchPack(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return AppScaffold( | ||||||
|  |       appBar: AppBar( | ||||||
|  |         title: Text(_pack?.name ?? 'loading'.tr()), | ||||||
|  |       ), | ||||||
|  |       body: Column( | ||||||
|  |         crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |         children: [ | ||||||
|  |           LoadingIndicator(isActive: _isBusy), | ||||||
|  |           if (_pack != null) | ||||||
|  |             Column( | ||||||
|  |               crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |               children: [ | ||||||
|  |                 Text(_pack!.name).bold(), | ||||||
|  |                 Text( | ||||||
|  |                   _pack!.description, | ||||||
|  |                   maxLines: 2, | ||||||
|  |                   overflow: TextOverflow.ellipsis, | ||||||
|  |                 ), | ||||||
|  |               ], | ||||||
|  |             ).padding(horizontal: 24, vertical: 16), | ||||||
|  |           const Divider(height: 1), | ||||||
|  |           ListTile( | ||||||
|  |             leading: const Icon(Symbols.add), | ||||||
|  |             title: Text('stickersNew').tr(), | ||||||
|  |             subtitle: Text('stickersNewDescription').tr(), | ||||||
|  |             contentPadding: const EdgeInsets.symmetric(horizontal: 24), | ||||||
|  |             onTap: () { | ||||||
|  |               showDialog( | ||||||
|  |                 context: context, | ||||||
|  |                 builder: (context) => _StickerCreateDialog(pack: _pack!), | ||||||
|  |               ).then((value) { | ||||||
|  |                 if (value) _fetchPack(); | ||||||
|  |               }); | ||||||
|  |             }, | ||||||
|  |           ), | ||||||
|  |           const Divider(height: 1), | ||||||
|  |           if (_pack?.stickers != null) | ||||||
|  |             Expanded( | ||||||
|  |               child: GridView.extent( | ||||||
|  |                 padding: EdgeInsets.only(left: 20, right: 20, top: 16), | ||||||
|  |                 maxCrossAxisExtent: 48, | ||||||
|  |                 mainAxisSpacing: 8, | ||||||
|  |                 crossAxisSpacing: 8, | ||||||
|  |                 children: _pack!.stickers! | ||||||
|  |                     .map( | ||||||
|  |                       (ele) => GestureDetector( | ||||||
|  |                         child: ClipRRect( | ||||||
|  |                           borderRadius: BorderRadius.circular(8), | ||||||
|  |                           child: Container( | ||||||
|  |                             color: Theme.of(context) | ||||||
|  |                                 .colorScheme | ||||||
|  |                                 .surfaceContainerHigh, | ||||||
|  |                             child: AttachmentItem( | ||||||
|  |                               data: ele.attachment, | ||||||
|  |                               heroTag: 'sticker-pack-${ele.attachment.rid}', | ||||||
|  |                               fit: BoxFit.contain, | ||||||
|  |                             ), | ||||||
|  |                           ), | ||||||
|  |                         ), | ||||||
|  |                         onTap: () { | ||||||
|  |                           _deleteSticker(ele); | ||||||
|  |                         }, | ||||||
|  |                       ), | ||||||
|  |                     ) | ||||||
|  |                     .toList(), | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _StickerCreateDialog extends StatefulWidget { | ||||||
|  |   final SnStickerPack pack; | ||||||
|  |   const _StickerCreateDialog({required this.pack}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   State<_StickerCreateDialog> createState() => _StickerCreateDialogState(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _StickerCreateDialogState extends State<_StickerCreateDialog> { | ||||||
|  |   final TextEditingController _nameController = TextEditingController(); | ||||||
|  |   final TextEditingController _aliasController = TextEditingController(); | ||||||
|  |   final TextEditingController _attachmentController = TextEditingController(); | ||||||
|  |  | ||||||
|  |   bool _isBusy = false; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void dispose() { | ||||||
|  |     _nameController.dispose(); | ||||||
|  |     _aliasController.dispose(); | ||||||
|  |     _attachmentController.dispose(); | ||||||
|  |     super.dispose(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Future<void> _createSticker() async { | ||||||
|  |     if (_nameController.text.isEmpty || | ||||||
|  |         _aliasController.text.isEmpty || | ||||||
|  |         _attachmentController.text.isEmpty) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     setState(() => _isBusy = true); | ||||||
|  |  | ||||||
|  |     try { | ||||||
|  |       final sn = context.read<SnNetworkProvider>(); | ||||||
|  |       await sn.client.post( | ||||||
|  |         '/cgi/uc/stickers', | ||||||
|  |         data: { | ||||||
|  |           'name': _nameController.text, | ||||||
|  |           'alias': _aliasController.text, | ||||||
|  |           'attachment_id': _attachmentController.text, | ||||||
|  |           'pack_id': widget.pack.id, | ||||||
|  |         }, | ||||||
|  |       ); | ||||||
|  |       if (!mounted) return; | ||||||
|  |       Navigator.pop(context, true); | ||||||
|  |     } catch (err) { | ||||||
|  |       if (!mounted) return; | ||||||
|  |       context.showErrorDialog(err); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return AlertDialog( | ||||||
|  |       title: Text('stickersNew'.tr()), | ||||||
|  |       content: Column( | ||||||
|  |         mainAxisSize: MainAxisSize.min, | ||||||
|  |         crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |         children: [ | ||||||
|  |           TextField( | ||||||
|  |             controller: _nameController, | ||||||
|  |             decoration: InputDecoration( | ||||||
|  |               border: const UnderlineInputBorder(), | ||||||
|  |               labelText: 'fieldStickerName'.tr(), | ||||||
|  |             ), | ||||||
|  |             onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||||
|  |           ), | ||||||
|  |           const Gap(4), | ||||||
|  |           TextField( | ||||||
|  |             controller: _aliasController, | ||||||
|  |             decoration: InputDecoration( | ||||||
|  |               border: const UnderlineInputBorder(), | ||||||
|  |               labelText: 'fieldStickerAlias'.tr(), | ||||||
|  |               helperText: 'fieldStickerAliasHint'.tr(), | ||||||
|  |             ), | ||||||
|  |             onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||||
|  |           ), | ||||||
|  |           const Gap(4), | ||||||
|  |           TextField( | ||||||
|  |             controller: _attachmentController, | ||||||
|  |             decoration: InputDecoration( | ||||||
|  |               border: const UnderlineInputBorder(), | ||||||
|  |               labelText: 'fieldStickerAttachment'.tr(), | ||||||
|  |             ), | ||||||
|  |             readOnly: true, | ||||||
|  |             onTap: () async { | ||||||
|  |               final attachment = await showDialog<SnAttachment?>( | ||||||
|  |                 context: context, | ||||||
|  |                 builder: (context) => AttachmentInputDialog( | ||||||
|  |                   title: 'fieldStickerAttachment'.tr(), | ||||||
|  |                   pool: 'sticker', | ||||||
|  |                   mediaType: SnMediaType.image, | ||||||
|  |                 ), | ||||||
|  |               ); | ||||||
|  |               if (attachment != null) { | ||||||
|  |                 setState(() { | ||||||
|  |                   _attachmentController.text = attachment.rid; | ||||||
|  |                 }); | ||||||
|  |               } | ||||||
|  |             }, | ||||||
|  |             onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||||
|  |           ), | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|  |       actions: [ | ||||||
|  |         TextButton( | ||||||
|  |           onPressed: _isBusy | ||||||
|  |               ? null | ||||||
|  |               : () { | ||||||
|  |                   Navigator.pop(context); | ||||||
|  |                 }, | ||||||
|  |           child: Text('dialogDismiss').tr(), | ||||||
|  |         ), | ||||||
|  |         TextButton( | ||||||
|  |           onPressed: _isBusy ? null : () => _createSticker(), | ||||||
|  |           child: Text('dialogConfirm').tr(), | ||||||
|  |         ), | ||||||
|  |       ], | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -1,5 +1,4 @@ | |||||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | import 'package:freezed_annotation/freezed_annotation.dart'; | ||||||
| import 'package:hive_flutter/hive_flutter.dart'; |  | ||||||
|  |  | ||||||
| part 'account.freezed.dart'; | part 'account.freezed.dart'; | ||||||
| part 'account.g.dart'; | part 'account.g.dart'; | ||||||
| @@ -9,7 +8,7 @@ class SnAccount with _$SnAccount { | |||||||
|   const SnAccount._(); |   const SnAccount._(); | ||||||
|  |  | ||||||
|   const factory SnAccount({ |   const factory SnAccount({ | ||||||
|     @HiveField(0) required int id, |     required int id, | ||||||
|     required DateTime createdAt, |     required DateTime createdAt, | ||||||
|     required DateTime updatedAt, |     required DateTime updatedAt, | ||||||
|     required DateTime? deletedAt, |     required DateTime? deletedAt, | ||||||
|   | |||||||
| @@ -20,7 +20,6 @@ SnAccount _$SnAccountFromJson(Map<String, dynamic> json) { | |||||||
|  |  | ||||||
| /// @nodoc | /// @nodoc | ||||||
| mixin _$SnAccount { | mixin _$SnAccount { | ||||||
|   @HiveField(0) |  | ||||||
|   int get id => throw _privateConstructorUsedError; |   int get id => throw _privateConstructorUsedError; | ||||||
|   DateTime get createdAt => throw _privateConstructorUsedError; |   DateTime get createdAt => throw _privateConstructorUsedError; | ||||||
|   DateTime get updatedAt => throw _privateConstructorUsedError; |   DateTime get updatedAt => throw _privateConstructorUsedError; | ||||||
| @@ -58,7 +57,7 @@ abstract class $SnAccountCopyWith<$Res> { | |||||||
|       _$SnAccountCopyWithImpl<$Res, SnAccount>; |       _$SnAccountCopyWithImpl<$Res, SnAccount>; | ||||||
|   @useResult |   @useResult | ||||||
|   $Res call( |   $Res call( | ||||||
|       {@HiveField(0) int id, |       {int id, | ||||||
|       DateTime createdAt, |       DateTime createdAt, | ||||||
|       DateTime updatedAt, |       DateTime updatedAt, | ||||||
|       DateTime? deletedAt, |       DateTime? deletedAt, | ||||||
| @@ -226,7 +225,7 @@ abstract class _$$SnAccountImplCopyWith<$Res> | |||||||
|   @override |   @override | ||||||
|   @useResult |   @useResult | ||||||
|   $Res call( |   $Res call( | ||||||
|       {@HiveField(0) int id, |       {int id, | ||||||
|       DateTime createdAt, |       DateTime createdAt, | ||||||
|       DateTime updatedAt, |       DateTime updatedAt, | ||||||
|       DateTime? deletedAt, |       DateTime? deletedAt, | ||||||
| @@ -374,7 +373,7 @@ class __$$SnAccountImplCopyWithImpl<$Res> | |||||||
| @JsonSerializable() | @JsonSerializable() | ||||||
| class _$SnAccountImpl extends _SnAccount { | class _$SnAccountImpl extends _SnAccount { | ||||||
|   const _$SnAccountImpl( |   const _$SnAccountImpl( | ||||||
|       {@HiveField(0) required this.id, |       {required this.id, | ||||||
|       required this.createdAt, |       required this.createdAt, | ||||||
|       required this.updatedAt, |       required this.updatedAt, | ||||||
|       required this.deletedAt, |       required this.deletedAt, | ||||||
| @@ -403,7 +402,6 @@ class _$SnAccountImpl extends _SnAccount { | |||||||
|       _$$SnAccountImplFromJson(json); |       _$$SnAccountImplFromJson(json); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   @HiveField(0) |  | ||||||
|   final int id; |   final int id; | ||||||
|   @override |   @override | ||||||
|   final DateTime createdAt; |   final DateTime createdAt; | ||||||
| @@ -556,7 +554,7 @@ class _$SnAccountImpl extends _SnAccount { | |||||||
|  |  | ||||||
| abstract class _SnAccount extends SnAccount { | abstract class _SnAccount extends SnAccount { | ||||||
|   const factory _SnAccount( |   const factory _SnAccount( | ||||||
|       {@HiveField(0) required final int id, |       {required final int id, | ||||||
|       required final DateTime createdAt, |       required final DateTime createdAt, | ||||||
|       required final DateTime updatedAt, |       required final DateTime updatedAt, | ||||||
|       required final DateTime? deletedAt, |       required final DateTime? deletedAt, | ||||||
| @@ -582,7 +580,6 @@ abstract class _SnAccount extends SnAccount { | |||||||
|       _$SnAccountImpl.fromJson; |       _$SnAccountImpl.fromJson; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   @HiveField(0) |  | ||||||
|   int get id; |   int get id; | ||||||
|   @override |   @override | ||||||
|   DateTime get createdAt; |   DateTime get createdAt; | ||||||
|   | |||||||
| @@ -177,3 +177,14 @@ class SnStickerPack with _$SnStickerPack { | |||||||
|  |  | ||||||
|   factory SnStickerPack.fromJson(Map<String, Object?> json) => _$SnStickerPackFromJson(json); |   factory SnStickerPack.fromJson(Map<String, Object?> json) => _$SnStickerPackFromJson(json); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @freezed | ||||||
|  | class SnAttachmentBilling with _$SnAttachmentBilling { | ||||||
|  |   const factory SnAttachmentBilling({ | ||||||
|  |     required int currentBytes, | ||||||
|  |     required int discountFileSize, | ||||||
|  |     required double includedRatio, | ||||||
|  |   }) = _SnAttachmentBilling; | ||||||
|  |  | ||||||
|  |   factory SnAttachmentBilling.fromJson(Map<String, Object?> json) => _$SnAttachmentBillingFromJson(json); | ||||||
|  | } | ||||||
|   | |||||||
| @@ -3007,3 +3007,195 @@ abstract class _SnStickerPack implements SnStickerPack { | |||||||
|   _$$SnStickerPackImplCopyWith<_$SnStickerPackImpl> get copyWith => |   _$$SnStickerPackImplCopyWith<_$SnStickerPackImpl> get copyWith => | ||||||
|       throw _privateConstructorUsedError; |       throw _privateConstructorUsedError; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | SnAttachmentBilling _$SnAttachmentBillingFromJson(Map<String, dynamic> json) { | ||||||
|  |   return _SnAttachmentBilling.fromJson(json); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | mixin _$SnAttachmentBilling { | ||||||
|  |   int get currentBytes => throw _privateConstructorUsedError; | ||||||
|  |   int get discountFileSize => throw _privateConstructorUsedError; | ||||||
|  |   double get includedRatio => throw _privateConstructorUsedError; | ||||||
|  |  | ||||||
|  |   /// Serializes this SnAttachmentBilling to a JSON map. | ||||||
|  |   Map<String, dynamic> toJson() => throw _privateConstructorUsedError; | ||||||
|  |  | ||||||
|  |   /// Create a copy of SnAttachmentBilling | ||||||
|  |   /// with the given fields replaced by the non-null parameter values. | ||||||
|  |   @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  |   $SnAttachmentBillingCopyWith<SnAttachmentBilling> get copyWith => | ||||||
|  |       throw _privateConstructorUsedError; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract class $SnAttachmentBillingCopyWith<$Res> { | ||||||
|  |   factory $SnAttachmentBillingCopyWith( | ||||||
|  |           SnAttachmentBilling value, $Res Function(SnAttachmentBilling) then) = | ||||||
|  |       _$SnAttachmentBillingCopyWithImpl<$Res, SnAttachmentBilling>; | ||||||
|  |   @useResult | ||||||
|  |   $Res call({int currentBytes, int discountFileSize, double includedRatio}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | class _$SnAttachmentBillingCopyWithImpl<$Res, $Val extends SnAttachmentBilling> | ||||||
|  |     implements $SnAttachmentBillingCopyWith<$Res> { | ||||||
|  |   _$SnAttachmentBillingCopyWithImpl(this._value, this._then); | ||||||
|  |  | ||||||
|  |   // ignore: unused_field | ||||||
|  |   final $Val _value; | ||||||
|  |   // ignore: unused_field | ||||||
|  |   final $Res Function($Val) _then; | ||||||
|  |  | ||||||
|  |   /// Create a copy of SnAttachmentBilling | ||||||
|  |   /// with the given fields replaced by the non-null parameter values. | ||||||
|  |   @pragma('vm:prefer-inline') | ||||||
|  |   @override | ||||||
|  |   $Res call({ | ||||||
|  |     Object? currentBytes = null, | ||||||
|  |     Object? discountFileSize = null, | ||||||
|  |     Object? includedRatio = null, | ||||||
|  |   }) { | ||||||
|  |     return _then(_value.copyWith( | ||||||
|  |       currentBytes: null == currentBytes | ||||||
|  |           ? _value.currentBytes | ||||||
|  |           : currentBytes // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as int, | ||||||
|  |       discountFileSize: null == discountFileSize | ||||||
|  |           ? _value.discountFileSize | ||||||
|  |           : discountFileSize // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as int, | ||||||
|  |       includedRatio: null == includedRatio | ||||||
|  |           ? _value.includedRatio | ||||||
|  |           : includedRatio // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as double, | ||||||
|  |     ) as $Val); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract class _$$SnAttachmentBillingImplCopyWith<$Res> | ||||||
|  |     implements $SnAttachmentBillingCopyWith<$Res> { | ||||||
|  |   factory _$$SnAttachmentBillingImplCopyWith(_$SnAttachmentBillingImpl value, | ||||||
|  |           $Res Function(_$SnAttachmentBillingImpl) then) = | ||||||
|  |       __$$SnAttachmentBillingImplCopyWithImpl<$Res>; | ||||||
|  |   @override | ||||||
|  |   @useResult | ||||||
|  |   $Res call({int currentBytes, int discountFileSize, double includedRatio}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | class __$$SnAttachmentBillingImplCopyWithImpl<$Res> | ||||||
|  |     extends _$SnAttachmentBillingCopyWithImpl<$Res, _$SnAttachmentBillingImpl> | ||||||
|  |     implements _$$SnAttachmentBillingImplCopyWith<$Res> { | ||||||
|  |   __$$SnAttachmentBillingImplCopyWithImpl(_$SnAttachmentBillingImpl _value, | ||||||
|  |       $Res Function(_$SnAttachmentBillingImpl) _then) | ||||||
|  |       : super(_value, _then); | ||||||
|  |  | ||||||
|  |   /// Create a copy of SnAttachmentBilling | ||||||
|  |   /// with the given fields replaced by the non-null parameter values. | ||||||
|  |   @pragma('vm:prefer-inline') | ||||||
|  |   @override | ||||||
|  |   $Res call({ | ||||||
|  |     Object? currentBytes = null, | ||||||
|  |     Object? discountFileSize = null, | ||||||
|  |     Object? includedRatio = null, | ||||||
|  |   }) { | ||||||
|  |     return _then(_$SnAttachmentBillingImpl( | ||||||
|  |       currentBytes: null == currentBytes | ||||||
|  |           ? _value.currentBytes | ||||||
|  |           : currentBytes // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as int, | ||||||
|  |       discountFileSize: null == discountFileSize | ||||||
|  |           ? _value.discountFileSize | ||||||
|  |           : discountFileSize // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as int, | ||||||
|  |       includedRatio: null == includedRatio | ||||||
|  |           ? _value.includedRatio | ||||||
|  |           : includedRatio // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as double, | ||||||
|  |     )); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | @JsonSerializable() | ||||||
|  | class _$SnAttachmentBillingImpl implements _SnAttachmentBilling { | ||||||
|  |   const _$SnAttachmentBillingImpl( | ||||||
|  |       {required this.currentBytes, | ||||||
|  |       required this.discountFileSize, | ||||||
|  |       required this.includedRatio}); | ||||||
|  |  | ||||||
|  |   factory _$SnAttachmentBillingImpl.fromJson(Map<String, dynamic> json) => | ||||||
|  |       _$$SnAttachmentBillingImplFromJson(json); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   final int currentBytes; | ||||||
|  |   @override | ||||||
|  |   final int discountFileSize; | ||||||
|  |   @override | ||||||
|  |   final double includedRatio; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String toString() { | ||||||
|  |     return 'SnAttachmentBilling(currentBytes: $currentBytes, discountFileSize: $discountFileSize, includedRatio: $includedRatio)'; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     return identical(this, other) || | ||||||
|  |         (other.runtimeType == runtimeType && | ||||||
|  |             other is _$SnAttachmentBillingImpl && | ||||||
|  |             (identical(other.currentBytes, currentBytes) || | ||||||
|  |                 other.currentBytes == currentBytes) && | ||||||
|  |             (identical(other.discountFileSize, discountFileSize) || | ||||||
|  |                 other.discountFileSize == discountFileSize) && | ||||||
|  |             (identical(other.includedRatio, includedRatio) || | ||||||
|  |                 other.includedRatio == includedRatio)); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  |   @override | ||||||
|  |   int get hashCode => | ||||||
|  |       Object.hash(runtimeType, currentBytes, discountFileSize, includedRatio); | ||||||
|  |  | ||||||
|  |   /// Create a copy of SnAttachmentBilling | ||||||
|  |   /// with the given fields replaced by the non-null parameter values. | ||||||
|  |   @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  |   @override | ||||||
|  |   @pragma('vm:prefer-inline') | ||||||
|  |   _$$SnAttachmentBillingImplCopyWith<_$SnAttachmentBillingImpl> get copyWith => | ||||||
|  |       __$$SnAttachmentBillingImplCopyWithImpl<_$SnAttachmentBillingImpl>( | ||||||
|  |           this, _$identity); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Map<String, dynamic> toJson() { | ||||||
|  |     return _$$SnAttachmentBillingImplToJson( | ||||||
|  |       this, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | abstract class _SnAttachmentBilling implements SnAttachmentBilling { | ||||||
|  |   const factory _SnAttachmentBilling( | ||||||
|  |       {required final int currentBytes, | ||||||
|  |       required final int discountFileSize, | ||||||
|  |       required final double includedRatio}) = _$SnAttachmentBillingImpl; | ||||||
|  |  | ||||||
|  |   factory _SnAttachmentBilling.fromJson(Map<String, dynamic> json) = | ||||||
|  |       _$SnAttachmentBillingImpl.fromJson; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get currentBytes; | ||||||
|  |   @override | ||||||
|  |   int get discountFileSize; | ||||||
|  |   @override | ||||||
|  |   double get includedRatio; | ||||||
|  |  | ||||||
|  |   /// Create a copy of SnAttachmentBilling | ||||||
|  |   /// with the given fields replaced by the non-null parameter values. | ||||||
|  |   @override | ||||||
|  |   @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  |   _$$SnAttachmentBillingImplCopyWith<_$SnAttachmentBillingImpl> get copyWith => | ||||||
|  |       throw _privateConstructorUsedError; | ||||||
|  | } | ||||||
|   | |||||||
| @@ -281,3 +281,19 @@ Map<String, dynamic> _$$SnStickerPackImplToJson(_$SnStickerPackImpl instance) => | |||||||
|       'stickers': instance.stickers?.map((e) => e.toJson()).toList(), |       'stickers': instance.stickers?.map((e) => e.toJson()).toList(), | ||||||
|       'account_id': instance.accountId, |       'account_id': instance.accountId, | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|  | _$SnAttachmentBillingImpl _$$SnAttachmentBillingImplFromJson( | ||||||
|  |         Map<String, dynamic> json) => | ||||||
|  |     _$SnAttachmentBillingImpl( | ||||||
|  |       currentBytes: (json['current_bytes'] as num).toInt(), | ||||||
|  |       discountFileSize: (json['discount_file_size'] as num).toInt(), | ||||||
|  |       includedRatio: (json['included_ratio'] as num).toDouble(), | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  | Map<String, dynamic> _$$SnAttachmentBillingImplToJson( | ||||||
|  |         _$SnAttachmentBillingImpl instance) => | ||||||
|  |     <String, dynamic>{ | ||||||
|  |       'current_bytes': instance.currentBytes, | ||||||
|  |       'discount_file_size': instance.discountFileSize, | ||||||
|  |       'included_ratio': instance.includedRatio, | ||||||
|  |     }; | ||||||
|   | |||||||
| @@ -1,5 +1,4 @@ | |||||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | import 'package:freezed_annotation/freezed_annotation.dart'; | ||||||
| import 'package:hive_flutter/hive_flutter.dart'; |  | ||||||
| import 'package:livekit_client/livekit_client.dart'; | import 'package:livekit_client/livekit_client.dart'; | ||||||
| import 'package:surface/types/account.dart'; | import 'package:surface/types/account.dart'; | ||||||
| import 'package:surface/types/attachment.dart'; | import 'package:surface/types/attachment.dart'; | ||||||
| @@ -12,23 +11,22 @@ part 'chat.g.dart'; | |||||||
| class SnChannel with _$SnChannel { | class SnChannel with _$SnChannel { | ||||||
|   const SnChannel._(); |   const SnChannel._(); | ||||||
|  |  | ||||||
|   @HiveType(typeId: 2) |  | ||||||
|   const factory SnChannel({ |   const factory SnChannel({ | ||||||
|     @HiveField(0) required int id, |     required int id, | ||||||
|     @HiveField(1) required DateTime createdAt, |     required DateTime createdAt, | ||||||
|     @HiveField(2) required DateTime updatedAt, |     required DateTime updatedAt, | ||||||
|     @HiveField(3) required dynamic deletedAt, |     required dynamic deletedAt, | ||||||
|     @HiveField(4) required String alias, |     required String alias, | ||||||
|     @HiveField(5) required String name, |     required String name, | ||||||
|     @HiveField(6) required String description, |     required String description, | ||||||
|     @HiveField(7) required List<SnChannelMember>? members, |     required List<SnChannelMember>? members, | ||||||
|     List<SnChatMessage>? messages, |     List<SnChatMessage>? messages, | ||||||
|     @HiveField(8) required int type, |     required int type, | ||||||
|     @HiveField(9) required int accountId, |     required int accountId, | ||||||
|     @HiveField(10) required SnRealm? realm, |     required SnRealm? realm, | ||||||
|     @HiveField(11) required int? realmId, |     required int? realmId, | ||||||
|     @HiveField(12) required bool isPublic, |     required bool isPublic, | ||||||
|     @HiveField(13) required bool isCommunity, |     required bool isCommunity, | ||||||
|   }) = _SnChannel; |   }) = _SnChannel; | ||||||
|  |  | ||||||
|   factory SnChannel.fromJson(Map<String, dynamic> json) => |   factory SnChannel.fromJson(Map<String, dynamic> json) => | ||||||
| @@ -42,19 +40,18 @@ class SnChannel with _$SnChannel { | |||||||
| class SnChannelMember with _$SnChannelMember { | class SnChannelMember with _$SnChannelMember { | ||||||
|   const SnChannelMember._(); |   const SnChannelMember._(); | ||||||
|  |  | ||||||
|   @HiveType(typeId: 3) |  | ||||||
|   const factory SnChannelMember({ |   const factory SnChannelMember({ | ||||||
|     @HiveField(0) required int id, |     required int id, | ||||||
|     @HiveField(1) required DateTime createdAt, |     required DateTime createdAt, | ||||||
|     @HiveField(2) required DateTime updatedAt, |     required DateTime updatedAt, | ||||||
|     @HiveField(3) required DateTime? deletedAt, |     required DateTime? deletedAt, | ||||||
|     @HiveField(4) required int channelId, |     required int channelId, | ||||||
|     @HiveField(5) required int accountId, |     required int accountId, | ||||||
|     @HiveField(6) required String? nick, |     required String? nick, | ||||||
|     @HiveField(7) required SnChannel? channel, |     required SnChannel? channel, | ||||||
|     @HiveField(8) required SnAccount? account, |     required SnAccount? account, | ||||||
|     @Default(0) int notify, |     @Default(0) int notify, | ||||||
|     @HiveField(9) required int powerLevel, |     required int powerLevel, | ||||||
|     dynamic calls, |     dynamic calls, | ||||||
|     dynamic events, |     dynamic events, | ||||||
|   }) = _SnChannelMember; |   }) = _SnChannelMember; | ||||||
| @@ -67,21 +64,20 @@ class SnChannelMember with _$SnChannelMember { | |||||||
| class SnChatMessage with _$SnChatMessage { | class SnChatMessage with _$SnChatMessage { | ||||||
|   const SnChatMessage._(); |   const SnChatMessage._(); | ||||||
|  |  | ||||||
|   @HiveType(typeId: 4) |  | ||||||
|   const factory SnChatMessage({ |   const factory SnChatMessage({ | ||||||
|     @HiveField(0) required int id, |     required int id, | ||||||
|     @HiveField(1) required DateTime createdAt, |     required DateTime createdAt, | ||||||
|     @HiveField(2) required DateTime updatedAt, |     required DateTime updatedAt, | ||||||
|     @HiveField(3) required DateTime? deletedAt, |     required DateTime? deletedAt, | ||||||
|     @HiveField(4) required String uuid, |     required String uuid, | ||||||
|     @HiveField(5) @Default({}) Map<String, dynamic> body, |     @Default({}) Map<String, dynamic> body, | ||||||
|     @HiveField(6) required String type, |     required String type, | ||||||
|     @HiveField(7) required SnChannel channel, |     required SnChannel channel, | ||||||
|     @HiveField(8) required SnChannelMember sender, |     required SnChannelMember sender, | ||||||
|     @HiveField(9) required int channelId, |     required int channelId, | ||||||
|     @HiveField(10) required int senderId, |     required int senderId, | ||||||
|     @HiveField(11) required int? quoteEventId, |     required int? quoteEventId, | ||||||
|     @HiveField(12) required int? relatedEventId, |     required int? relatedEventId, | ||||||
|     SnChatMessagePreload? preload, |     SnChatMessagePreload? preload, | ||||||
|   }) = _SnChatMessage; |   }) = _SnChatMessage; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -20,34 +20,20 @@ SnChannel _$SnChannelFromJson(Map<String, dynamic> json) { | |||||||
|  |  | ||||||
| /// @nodoc | /// @nodoc | ||||||
| mixin _$SnChannel { | mixin _$SnChannel { | ||||||
|   @HiveField(0) |  | ||||||
|   int get id => throw _privateConstructorUsedError; |   int get id => throw _privateConstructorUsedError; | ||||||
|   @HiveField(1) |  | ||||||
|   DateTime get createdAt => throw _privateConstructorUsedError; |   DateTime get createdAt => throw _privateConstructorUsedError; | ||||||
|   @HiveField(2) |  | ||||||
|   DateTime get updatedAt => throw _privateConstructorUsedError; |   DateTime get updatedAt => throw _privateConstructorUsedError; | ||||||
|   @HiveField(3) |  | ||||||
|   dynamic get deletedAt => throw _privateConstructorUsedError; |   dynamic get deletedAt => throw _privateConstructorUsedError; | ||||||
|   @HiveField(4) |  | ||||||
|   String get alias => throw _privateConstructorUsedError; |   String get alias => throw _privateConstructorUsedError; | ||||||
|   @HiveField(5) |  | ||||||
|   String get name => throw _privateConstructorUsedError; |   String get name => throw _privateConstructorUsedError; | ||||||
|   @HiveField(6) |  | ||||||
|   String get description => throw _privateConstructorUsedError; |   String get description => throw _privateConstructorUsedError; | ||||||
|   @HiveField(7) |  | ||||||
|   List<SnChannelMember>? get members => throw _privateConstructorUsedError; |   List<SnChannelMember>? get members => throw _privateConstructorUsedError; | ||||||
|   List<SnChatMessage>? get messages => throw _privateConstructorUsedError; |   List<SnChatMessage>? get messages => throw _privateConstructorUsedError; | ||||||
|   @HiveField(8) |  | ||||||
|   int get type => throw _privateConstructorUsedError; |   int get type => throw _privateConstructorUsedError; | ||||||
|   @HiveField(9) |  | ||||||
|   int get accountId => throw _privateConstructorUsedError; |   int get accountId => throw _privateConstructorUsedError; | ||||||
|   @HiveField(10) |  | ||||||
|   SnRealm? get realm => throw _privateConstructorUsedError; |   SnRealm? get realm => throw _privateConstructorUsedError; | ||||||
|   @HiveField(11) |  | ||||||
|   int? get realmId => throw _privateConstructorUsedError; |   int? get realmId => throw _privateConstructorUsedError; | ||||||
|   @HiveField(12) |  | ||||||
|   bool get isPublic => throw _privateConstructorUsedError; |   bool get isPublic => throw _privateConstructorUsedError; | ||||||
|   @HiveField(13) |  | ||||||
|   bool get isCommunity => throw _privateConstructorUsedError; |   bool get isCommunity => throw _privateConstructorUsedError; | ||||||
|  |  | ||||||
|   /// Serializes this SnChannel to a JSON map. |   /// Serializes this SnChannel to a JSON map. | ||||||
| @@ -66,21 +52,21 @@ abstract class $SnChannelCopyWith<$Res> { | |||||||
|       _$SnChannelCopyWithImpl<$Res, SnChannel>; |       _$SnChannelCopyWithImpl<$Res, SnChannel>; | ||||||
|   @useResult |   @useResult | ||||||
|   $Res call( |   $Res call( | ||||||
|       {@HiveField(0) int id, |       {int id, | ||||||
|       @HiveField(1) DateTime createdAt, |       DateTime createdAt, | ||||||
|       @HiveField(2) DateTime updatedAt, |       DateTime updatedAt, | ||||||
|       @HiveField(3) dynamic deletedAt, |       dynamic deletedAt, | ||||||
|       @HiveField(4) String alias, |       String alias, | ||||||
|       @HiveField(5) String name, |       String name, | ||||||
|       @HiveField(6) String description, |       String description, | ||||||
|       @HiveField(7) List<SnChannelMember>? members, |       List<SnChannelMember>? members, | ||||||
|       List<SnChatMessage>? messages, |       List<SnChatMessage>? messages, | ||||||
|       @HiveField(8) int type, |       int type, | ||||||
|       @HiveField(9) int accountId, |       int accountId, | ||||||
|       @HiveField(10) SnRealm? realm, |       SnRealm? realm, | ||||||
|       @HiveField(11) int? realmId, |       int? realmId, | ||||||
|       @HiveField(12) bool isPublic, |       bool isPublic, | ||||||
|       @HiveField(13) bool isCommunity}); |       bool isCommunity}); | ||||||
|  |  | ||||||
|   $SnRealmCopyWith<$Res>? get realm; |   $SnRealmCopyWith<$Res>? get realm; | ||||||
| } | } | ||||||
| @@ -204,21 +190,21 @@ abstract class _$$SnChannelImplCopyWith<$Res> | |||||||
|   @override |   @override | ||||||
|   @useResult |   @useResult | ||||||
|   $Res call( |   $Res call( | ||||||
|       {@HiveField(0) int id, |       {int id, | ||||||
|       @HiveField(1) DateTime createdAt, |       DateTime createdAt, | ||||||
|       @HiveField(2) DateTime updatedAt, |       DateTime updatedAt, | ||||||
|       @HiveField(3) dynamic deletedAt, |       dynamic deletedAt, | ||||||
|       @HiveField(4) String alias, |       String alias, | ||||||
|       @HiveField(5) String name, |       String name, | ||||||
|       @HiveField(6) String description, |       String description, | ||||||
|       @HiveField(7) List<SnChannelMember>? members, |       List<SnChannelMember>? members, | ||||||
|       List<SnChatMessage>? messages, |       List<SnChatMessage>? messages, | ||||||
|       @HiveField(8) int type, |       int type, | ||||||
|       @HiveField(9) int accountId, |       int accountId, | ||||||
|       @HiveField(10) SnRealm? realm, |       SnRealm? realm, | ||||||
|       @HiveField(11) int? realmId, |       int? realmId, | ||||||
|       @HiveField(12) bool isPublic, |       bool isPublic, | ||||||
|       @HiveField(13) bool isCommunity}); |       bool isCommunity}); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   $SnRealmCopyWith<$Res>? get realm; |   $SnRealmCopyWith<$Res>? get realm; | ||||||
| @@ -320,24 +306,23 @@ class __$$SnChannelImplCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// @nodoc | /// @nodoc | ||||||
| @JsonSerializable() | @JsonSerializable() | ||||||
| @HiveType(typeId: 2) |  | ||||||
| class _$SnChannelImpl extends _SnChannel { | class _$SnChannelImpl extends _SnChannel { | ||||||
|   const _$SnChannelImpl( |   const _$SnChannelImpl( | ||||||
|       {@HiveField(0) required this.id, |       {required this.id, | ||||||
|       @HiveField(1) required this.createdAt, |       required this.createdAt, | ||||||
|       @HiveField(2) required this.updatedAt, |       required this.updatedAt, | ||||||
|       @HiveField(3) required this.deletedAt, |       required this.deletedAt, | ||||||
|       @HiveField(4) required this.alias, |       required this.alias, | ||||||
|       @HiveField(5) required this.name, |       required this.name, | ||||||
|       @HiveField(6) required this.description, |       required this.description, | ||||||
|       @HiveField(7) required final List<SnChannelMember>? members, |       required final List<SnChannelMember>? members, | ||||||
|       final List<SnChatMessage>? messages, |       final List<SnChatMessage>? messages, | ||||||
|       @HiveField(8) required this.type, |       required this.type, | ||||||
|       @HiveField(9) required this.accountId, |       required this.accountId, | ||||||
|       @HiveField(10) required this.realm, |       required this.realm, | ||||||
|       @HiveField(11) required this.realmId, |       required this.realmId, | ||||||
|       @HiveField(12) required this.isPublic, |       required this.isPublic, | ||||||
|       @HiveField(13) required this.isCommunity}) |       required this.isCommunity}) | ||||||
|       : _members = members, |       : _members = members, | ||||||
|         _messages = messages, |         _messages = messages, | ||||||
|         super._(); |         super._(); | ||||||
| @@ -346,29 +331,21 @@ class _$SnChannelImpl extends _SnChannel { | |||||||
|       _$$SnChannelImplFromJson(json); |       _$$SnChannelImplFromJson(json); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   @HiveField(0) |  | ||||||
|   final int id; |   final int id; | ||||||
|   @override |   @override | ||||||
|   @HiveField(1) |  | ||||||
|   final DateTime createdAt; |   final DateTime createdAt; | ||||||
|   @override |   @override | ||||||
|   @HiveField(2) |  | ||||||
|   final DateTime updatedAt; |   final DateTime updatedAt; | ||||||
|   @override |   @override | ||||||
|   @HiveField(3) |  | ||||||
|   final dynamic deletedAt; |   final dynamic deletedAt; | ||||||
|   @override |   @override | ||||||
|   @HiveField(4) |  | ||||||
|   final String alias; |   final String alias; | ||||||
|   @override |   @override | ||||||
|   @HiveField(5) |  | ||||||
|   final String name; |   final String name; | ||||||
|   @override |   @override | ||||||
|   @HiveField(6) |  | ||||||
|   final String description; |   final String description; | ||||||
|   final List<SnChannelMember>? _members; |   final List<SnChannelMember>? _members; | ||||||
|   @override |   @override | ||||||
|   @HiveField(7) |  | ||||||
|   List<SnChannelMember>? get members { |   List<SnChannelMember>? get members { | ||||||
|     final value = _members; |     final value = _members; | ||||||
|     if (value == null) return null; |     if (value == null) return null; | ||||||
| @@ -388,22 +365,16 @@ class _$SnChannelImpl extends _SnChannel { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   @HiveField(8) |  | ||||||
|   final int type; |   final int type; | ||||||
|   @override |   @override | ||||||
|   @HiveField(9) |  | ||||||
|   final int accountId; |   final int accountId; | ||||||
|   @override |   @override | ||||||
|   @HiveField(10) |  | ||||||
|   final SnRealm? realm; |   final SnRealm? realm; | ||||||
|   @override |   @override | ||||||
|   @HiveField(11) |  | ||||||
|   final int? realmId; |   final int? realmId; | ||||||
|   @override |   @override | ||||||
|   @HiveField(12) |  | ||||||
|   final bool isPublic; |   final bool isPublic; | ||||||
|   @override |   @override | ||||||
|   @HiveField(13) |  | ||||||
|   final bool isCommunity; |   final bool isCommunity; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
| @@ -477,69 +448,55 @@ class _$SnChannelImpl extends _SnChannel { | |||||||
|  |  | ||||||
| abstract class _SnChannel extends SnChannel { | abstract class _SnChannel extends SnChannel { | ||||||
|   const factory _SnChannel( |   const factory _SnChannel( | ||||||
|       {@HiveField(0) required final int id, |       {required final int id, | ||||||
|       @HiveField(1) required final DateTime createdAt, |       required final DateTime createdAt, | ||||||
|       @HiveField(2) required final DateTime updatedAt, |       required final DateTime updatedAt, | ||||||
|       @HiveField(3) required final dynamic deletedAt, |       required final dynamic deletedAt, | ||||||
|       @HiveField(4) required final String alias, |       required final String alias, | ||||||
|       @HiveField(5) required final String name, |       required final String name, | ||||||
|       @HiveField(6) required final String description, |       required final String description, | ||||||
|       @HiveField(7) required final List<SnChannelMember>? members, |       required final List<SnChannelMember>? members, | ||||||
|       final List<SnChatMessage>? messages, |       final List<SnChatMessage>? messages, | ||||||
|       @HiveField(8) required final int type, |       required final int type, | ||||||
|       @HiveField(9) required final int accountId, |       required final int accountId, | ||||||
|       @HiveField(10) required final SnRealm? realm, |       required final SnRealm? realm, | ||||||
|       @HiveField(11) required final int? realmId, |       required final int? realmId, | ||||||
|       @HiveField(12) required final bool isPublic, |       required final bool isPublic, | ||||||
|       @HiveField(13) required final bool isCommunity}) = _$SnChannelImpl; |       required final bool isCommunity}) = _$SnChannelImpl; | ||||||
|   const _SnChannel._() : super._(); |   const _SnChannel._() : super._(); | ||||||
|  |  | ||||||
|   factory _SnChannel.fromJson(Map<String, dynamic> json) = |   factory _SnChannel.fromJson(Map<String, dynamic> json) = | ||||||
|       _$SnChannelImpl.fromJson; |       _$SnChannelImpl.fromJson; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   @HiveField(0) |  | ||||||
|   int get id; |   int get id; | ||||||
|   @override |   @override | ||||||
|   @HiveField(1) |  | ||||||
|   DateTime get createdAt; |   DateTime get createdAt; | ||||||
|   @override |   @override | ||||||
|   @HiveField(2) |  | ||||||
|   DateTime get updatedAt; |   DateTime get updatedAt; | ||||||
|   @override |   @override | ||||||
|   @HiveField(3) |  | ||||||
|   dynamic get deletedAt; |   dynamic get deletedAt; | ||||||
|   @override |   @override | ||||||
|   @HiveField(4) |  | ||||||
|   String get alias; |   String get alias; | ||||||
|   @override |   @override | ||||||
|   @HiveField(5) |  | ||||||
|   String get name; |   String get name; | ||||||
|   @override |   @override | ||||||
|   @HiveField(6) |  | ||||||
|   String get description; |   String get description; | ||||||
|   @override |   @override | ||||||
|   @HiveField(7) |  | ||||||
|   List<SnChannelMember>? get members; |   List<SnChannelMember>? get members; | ||||||
|   @override |   @override | ||||||
|   List<SnChatMessage>? get messages; |   List<SnChatMessage>? get messages; | ||||||
|   @override |   @override | ||||||
|   @HiveField(8) |  | ||||||
|   int get type; |   int get type; | ||||||
|   @override |   @override | ||||||
|   @HiveField(9) |  | ||||||
|   int get accountId; |   int get accountId; | ||||||
|   @override |   @override | ||||||
|   @HiveField(10) |  | ||||||
|   SnRealm? get realm; |   SnRealm? get realm; | ||||||
|   @override |   @override | ||||||
|   @HiveField(11) |  | ||||||
|   int? get realmId; |   int? get realmId; | ||||||
|   @override |   @override | ||||||
|   @HiveField(12) |  | ||||||
|   bool get isPublic; |   bool get isPublic; | ||||||
|   @override |   @override | ||||||
|   @HiveField(13) |  | ||||||
|   bool get isCommunity; |   bool get isCommunity; | ||||||
|  |  | ||||||
|   /// Create a copy of SnChannel |   /// Create a copy of SnChannel | ||||||
| @@ -556,26 +513,16 @@ SnChannelMember _$SnChannelMemberFromJson(Map<String, dynamic> json) { | |||||||
|  |  | ||||||
| /// @nodoc | /// @nodoc | ||||||
| mixin _$SnChannelMember { | mixin _$SnChannelMember { | ||||||
|   @HiveField(0) |  | ||||||
|   int get id => throw _privateConstructorUsedError; |   int get id => throw _privateConstructorUsedError; | ||||||
|   @HiveField(1) |  | ||||||
|   DateTime get createdAt => throw _privateConstructorUsedError; |   DateTime get createdAt => throw _privateConstructorUsedError; | ||||||
|   @HiveField(2) |  | ||||||
|   DateTime get updatedAt => throw _privateConstructorUsedError; |   DateTime get updatedAt => throw _privateConstructorUsedError; | ||||||
|   @HiveField(3) |  | ||||||
|   DateTime? get deletedAt => throw _privateConstructorUsedError; |   DateTime? get deletedAt => throw _privateConstructorUsedError; | ||||||
|   @HiveField(4) |  | ||||||
|   int get channelId => throw _privateConstructorUsedError; |   int get channelId => throw _privateConstructorUsedError; | ||||||
|   @HiveField(5) |  | ||||||
|   int get accountId => throw _privateConstructorUsedError; |   int get accountId => throw _privateConstructorUsedError; | ||||||
|   @HiveField(6) |  | ||||||
|   String? get nick => throw _privateConstructorUsedError; |   String? get nick => throw _privateConstructorUsedError; | ||||||
|   @HiveField(7) |  | ||||||
|   SnChannel? get channel => throw _privateConstructorUsedError; |   SnChannel? get channel => throw _privateConstructorUsedError; | ||||||
|   @HiveField(8) |  | ||||||
|   SnAccount? get account => throw _privateConstructorUsedError; |   SnAccount? get account => throw _privateConstructorUsedError; | ||||||
|   int get notify => throw _privateConstructorUsedError; |   int get notify => throw _privateConstructorUsedError; | ||||||
|   @HiveField(9) |  | ||||||
|   int get powerLevel => throw _privateConstructorUsedError; |   int get powerLevel => throw _privateConstructorUsedError; | ||||||
|   dynamic get calls => throw _privateConstructorUsedError; |   dynamic get calls => throw _privateConstructorUsedError; | ||||||
|   dynamic get events => throw _privateConstructorUsedError; |   dynamic get events => throw _privateConstructorUsedError; | ||||||
| @@ -597,17 +544,17 @@ abstract class $SnChannelMemberCopyWith<$Res> { | |||||||
|       _$SnChannelMemberCopyWithImpl<$Res, SnChannelMember>; |       _$SnChannelMemberCopyWithImpl<$Res, SnChannelMember>; | ||||||
|   @useResult |   @useResult | ||||||
|   $Res call( |   $Res call( | ||||||
|       {@HiveField(0) int id, |       {int id, | ||||||
|       @HiveField(1) DateTime createdAt, |       DateTime createdAt, | ||||||
|       @HiveField(2) DateTime updatedAt, |       DateTime updatedAt, | ||||||
|       @HiveField(3) DateTime? deletedAt, |       DateTime? deletedAt, | ||||||
|       @HiveField(4) int channelId, |       int channelId, | ||||||
|       @HiveField(5) int accountId, |       int accountId, | ||||||
|       @HiveField(6) String? nick, |       String? nick, | ||||||
|       @HiveField(7) SnChannel? channel, |       SnChannel? channel, | ||||||
|       @HiveField(8) SnAccount? account, |       SnAccount? account, | ||||||
|       int notify, |       int notify, | ||||||
|       @HiveField(9) int powerLevel, |       int powerLevel, | ||||||
|       dynamic calls, |       dynamic calls, | ||||||
|       dynamic events}); |       dynamic events}); | ||||||
|  |  | ||||||
| @@ -738,17 +685,17 @@ abstract class _$$SnChannelMemberImplCopyWith<$Res> | |||||||
|   @override |   @override | ||||||
|   @useResult |   @useResult | ||||||
|   $Res call( |   $Res call( | ||||||
|       {@HiveField(0) int id, |       {int id, | ||||||
|       @HiveField(1) DateTime createdAt, |       DateTime createdAt, | ||||||
|       @HiveField(2) DateTime updatedAt, |       DateTime updatedAt, | ||||||
|       @HiveField(3) DateTime? deletedAt, |       DateTime? deletedAt, | ||||||
|       @HiveField(4) int channelId, |       int channelId, | ||||||
|       @HiveField(5) int accountId, |       int accountId, | ||||||
|       @HiveField(6) String? nick, |       String? nick, | ||||||
|       @HiveField(7) SnChannel? channel, |       SnChannel? channel, | ||||||
|       @HiveField(8) SnAccount? account, |       SnAccount? account, | ||||||
|       int notify, |       int notify, | ||||||
|       @HiveField(9) int powerLevel, |       int powerLevel, | ||||||
|       dynamic calls, |       dynamic calls, | ||||||
|       dynamic events}); |       dynamic events}); | ||||||
|  |  | ||||||
| @@ -844,20 +791,19 @@ class __$$SnChannelMemberImplCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// @nodoc | /// @nodoc | ||||||
| @JsonSerializable() | @JsonSerializable() | ||||||
| @HiveType(typeId: 3) |  | ||||||
| class _$SnChannelMemberImpl extends _SnChannelMember { | class _$SnChannelMemberImpl extends _SnChannelMember { | ||||||
|   const _$SnChannelMemberImpl( |   const _$SnChannelMemberImpl( | ||||||
|       {@HiveField(0) required this.id, |       {required this.id, | ||||||
|       @HiveField(1) required this.createdAt, |       required this.createdAt, | ||||||
|       @HiveField(2) required this.updatedAt, |       required this.updatedAt, | ||||||
|       @HiveField(3) required this.deletedAt, |       required this.deletedAt, | ||||||
|       @HiveField(4) required this.channelId, |       required this.channelId, | ||||||
|       @HiveField(5) required this.accountId, |       required this.accountId, | ||||||
|       @HiveField(6) required this.nick, |       required this.nick, | ||||||
|       @HiveField(7) required this.channel, |       required this.channel, | ||||||
|       @HiveField(8) required this.account, |       required this.account, | ||||||
|       this.notify = 0, |       this.notify = 0, | ||||||
|       @HiveField(9) required this.powerLevel, |       required this.powerLevel, | ||||||
|       this.calls, |       this.calls, | ||||||
|       this.events}) |       this.events}) | ||||||
|       : super._(); |       : super._(); | ||||||
| @@ -866,37 +812,27 @@ class _$SnChannelMemberImpl extends _SnChannelMember { | |||||||
|       _$$SnChannelMemberImplFromJson(json); |       _$$SnChannelMemberImplFromJson(json); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   @HiveField(0) |  | ||||||
|   final int id; |   final int id; | ||||||
|   @override |   @override | ||||||
|   @HiveField(1) |  | ||||||
|   final DateTime createdAt; |   final DateTime createdAt; | ||||||
|   @override |   @override | ||||||
|   @HiveField(2) |  | ||||||
|   final DateTime updatedAt; |   final DateTime updatedAt; | ||||||
|   @override |   @override | ||||||
|   @HiveField(3) |  | ||||||
|   final DateTime? deletedAt; |   final DateTime? deletedAt; | ||||||
|   @override |   @override | ||||||
|   @HiveField(4) |  | ||||||
|   final int channelId; |   final int channelId; | ||||||
|   @override |   @override | ||||||
|   @HiveField(5) |  | ||||||
|   final int accountId; |   final int accountId; | ||||||
|   @override |   @override | ||||||
|   @HiveField(6) |  | ||||||
|   final String? nick; |   final String? nick; | ||||||
|   @override |   @override | ||||||
|   @HiveField(7) |  | ||||||
|   final SnChannel? channel; |   final SnChannel? channel; | ||||||
|   @override |   @override | ||||||
|   @HiveField(8) |  | ||||||
|   final SnAccount? account; |   final SnAccount? account; | ||||||
|   @override |   @override | ||||||
|   @JsonKey() |   @JsonKey() | ||||||
|   final int notify; |   final int notify; | ||||||
|   @override |   @override | ||||||
|   @HiveField(9) |  | ||||||
|   final int powerLevel; |   final int powerLevel; | ||||||
|   @override |   @override | ||||||
|   final dynamic calls; |   final dynamic calls; | ||||||
| @@ -971,17 +907,17 @@ class _$SnChannelMemberImpl extends _SnChannelMember { | |||||||
|  |  | ||||||
| abstract class _SnChannelMember extends SnChannelMember { | abstract class _SnChannelMember extends SnChannelMember { | ||||||
|   const factory _SnChannelMember( |   const factory _SnChannelMember( | ||||||
|       {@HiveField(0) required final int id, |       {required final int id, | ||||||
|       @HiveField(1) required final DateTime createdAt, |       required final DateTime createdAt, | ||||||
|       @HiveField(2) required final DateTime updatedAt, |       required final DateTime updatedAt, | ||||||
|       @HiveField(3) required final DateTime? deletedAt, |       required final DateTime? deletedAt, | ||||||
|       @HiveField(4) required final int channelId, |       required final int channelId, | ||||||
|       @HiveField(5) required final int accountId, |       required final int accountId, | ||||||
|       @HiveField(6) required final String? nick, |       required final String? nick, | ||||||
|       @HiveField(7) required final SnChannel? channel, |       required final SnChannel? channel, | ||||||
|       @HiveField(8) required final SnAccount? account, |       required final SnAccount? account, | ||||||
|       final int notify, |       final int notify, | ||||||
|       @HiveField(9) required final int powerLevel, |       required final int powerLevel, | ||||||
|       final dynamic calls, |       final dynamic calls, | ||||||
|       final dynamic events}) = _$SnChannelMemberImpl; |       final dynamic events}) = _$SnChannelMemberImpl; | ||||||
|   const _SnChannelMember._() : super._(); |   const _SnChannelMember._() : super._(); | ||||||
| @@ -990,36 +926,26 @@ abstract class _SnChannelMember extends SnChannelMember { | |||||||
|       _$SnChannelMemberImpl.fromJson; |       _$SnChannelMemberImpl.fromJson; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   @HiveField(0) |  | ||||||
|   int get id; |   int get id; | ||||||
|   @override |   @override | ||||||
|   @HiveField(1) |  | ||||||
|   DateTime get createdAt; |   DateTime get createdAt; | ||||||
|   @override |   @override | ||||||
|   @HiveField(2) |  | ||||||
|   DateTime get updatedAt; |   DateTime get updatedAt; | ||||||
|   @override |   @override | ||||||
|   @HiveField(3) |  | ||||||
|   DateTime? get deletedAt; |   DateTime? get deletedAt; | ||||||
|   @override |   @override | ||||||
|   @HiveField(4) |  | ||||||
|   int get channelId; |   int get channelId; | ||||||
|   @override |   @override | ||||||
|   @HiveField(5) |  | ||||||
|   int get accountId; |   int get accountId; | ||||||
|   @override |   @override | ||||||
|   @HiveField(6) |  | ||||||
|   String? get nick; |   String? get nick; | ||||||
|   @override |   @override | ||||||
|   @HiveField(7) |  | ||||||
|   SnChannel? get channel; |   SnChannel? get channel; | ||||||
|   @override |   @override | ||||||
|   @HiveField(8) |  | ||||||
|   SnAccount? get account; |   SnAccount? get account; | ||||||
|   @override |   @override | ||||||
|   int get notify; |   int get notify; | ||||||
|   @override |   @override | ||||||
|   @HiveField(9) |  | ||||||
|   int get powerLevel; |   int get powerLevel; | ||||||
|   @override |   @override | ||||||
|   dynamic get calls; |   dynamic get calls; | ||||||
| @@ -1040,31 +966,18 @@ SnChatMessage _$SnChatMessageFromJson(Map<String, dynamic> json) { | |||||||
|  |  | ||||||
| /// @nodoc | /// @nodoc | ||||||
| mixin _$SnChatMessage { | mixin _$SnChatMessage { | ||||||
|   @HiveField(0) |  | ||||||
|   int get id => throw _privateConstructorUsedError; |   int get id => throw _privateConstructorUsedError; | ||||||
|   @HiveField(1) |  | ||||||
|   DateTime get createdAt => throw _privateConstructorUsedError; |   DateTime get createdAt => throw _privateConstructorUsedError; | ||||||
|   @HiveField(2) |  | ||||||
|   DateTime get updatedAt => throw _privateConstructorUsedError; |   DateTime get updatedAt => throw _privateConstructorUsedError; | ||||||
|   @HiveField(3) |  | ||||||
|   DateTime? get deletedAt => throw _privateConstructorUsedError; |   DateTime? get deletedAt => throw _privateConstructorUsedError; | ||||||
|   @HiveField(4) |  | ||||||
|   String get uuid => throw _privateConstructorUsedError; |   String get uuid => throw _privateConstructorUsedError; | ||||||
|   @HiveField(5) |  | ||||||
|   Map<String, dynamic> get body => throw _privateConstructorUsedError; |   Map<String, dynamic> get body => throw _privateConstructorUsedError; | ||||||
|   @HiveField(6) |  | ||||||
|   String get type => throw _privateConstructorUsedError; |   String get type => throw _privateConstructorUsedError; | ||||||
|   @HiveField(7) |  | ||||||
|   SnChannel get channel => throw _privateConstructorUsedError; |   SnChannel get channel => throw _privateConstructorUsedError; | ||||||
|   @HiveField(8) |  | ||||||
|   SnChannelMember get sender => throw _privateConstructorUsedError; |   SnChannelMember get sender => throw _privateConstructorUsedError; | ||||||
|   @HiveField(9) |  | ||||||
|   int get channelId => throw _privateConstructorUsedError; |   int get channelId => throw _privateConstructorUsedError; | ||||||
|   @HiveField(10) |  | ||||||
|   int get senderId => throw _privateConstructorUsedError; |   int get senderId => throw _privateConstructorUsedError; | ||||||
|   @HiveField(11) |  | ||||||
|   int? get quoteEventId => throw _privateConstructorUsedError; |   int? get quoteEventId => throw _privateConstructorUsedError; | ||||||
|   @HiveField(12) |  | ||||||
|   int? get relatedEventId => throw _privateConstructorUsedError; |   int? get relatedEventId => throw _privateConstructorUsedError; | ||||||
|   SnChatMessagePreload? get preload => throw _privateConstructorUsedError; |   SnChatMessagePreload? get preload => throw _privateConstructorUsedError; | ||||||
|  |  | ||||||
| @@ -1085,19 +998,19 @@ abstract class $SnChatMessageCopyWith<$Res> { | |||||||
|       _$SnChatMessageCopyWithImpl<$Res, SnChatMessage>; |       _$SnChatMessageCopyWithImpl<$Res, SnChatMessage>; | ||||||
|   @useResult |   @useResult | ||||||
|   $Res call( |   $Res call( | ||||||
|       {@HiveField(0) int id, |       {int id, | ||||||
|       @HiveField(1) DateTime createdAt, |       DateTime createdAt, | ||||||
|       @HiveField(2) DateTime updatedAt, |       DateTime updatedAt, | ||||||
|       @HiveField(3) DateTime? deletedAt, |       DateTime? deletedAt, | ||||||
|       @HiveField(4) String uuid, |       String uuid, | ||||||
|       @HiveField(5) Map<String, dynamic> body, |       Map<String, dynamic> body, | ||||||
|       @HiveField(6) String type, |       String type, | ||||||
|       @HiveField(7) SnChannel channel, |       SnChannel channel, | ||||||
|       @HiveField(8) SnChannelMember sender, |       SnChannelMember sender, | ||||||
|       @HiveField(9) int channelId, |       int channelId, | ||||||
|       @HiveField(10) int senderId, |       int senderId, | ||||||
|       @HiveField(11) int? quoteEventId, |       int? quoteEventId, | ||||||
|       @HiveField(12) int? relatedEventId, |       int? relatedEventId, | ||||||
|       SnChatMessagePreload? preload}); |       SnChatMessagePreload? preload}); | ||||||
|  |  | ||||||
|   $SnChannelCopyWith<$Res> get channel; |   $SnChannelCopyWith<$Res> get channel; | ||||||
| @@ -1239,19 +1152,19 @@ abstract class _$$SnChatMessageImplCopyWith<$Res> | |||||||
|   @override |   @override | ||||||
|   @useResult |   @useResult | ||||||
|   $Res call( |   $Res call( | ||||||
|       {@HiveField(0) int id, |       {int id, | ||||||
|       @HiveField(1) DateTime createdAt, |       DateTime createdAt, | ||||||
|       @HiveField(2) DateTime updatedAt, |       DateTime updatedAt, | ||||||
|       @HiveField(3) DateTime? deletedAt, |       DateTime? deletedAt, | ||||||
|       @HiveField(4) String uuid, |       String uuid, | ||||||
|       @HiveField(5) Map<String, dynamic> body, |       Map<String, dynamic> body, | ||||||
|       @HiveField(6) String type, |       String type, | ||||||
|       @HiveField(7) SnChannel channel, |       SnChannel channel, | ||||||
|       @HiveField(8) SnChannelMember sender, |       SnChannelMember sender, | ||||||
|       @HiveField(9) int channelId, |       int channelId, | ||||||
|       @HiveField(10) int senderId, |       int senderId, | ||||||
|       @HiveField(11) int? quoteEventId, |       int? quoteEventId, | ||||||
|       @HiveField(12) int? relatedEventId, |       int? relatedEventId, | ||||||
|       SnChatMessagePreload? preload}); |       SnChatMessagePreload? preload}); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
| @@ -1353,22 +1266,21 @@ class __$$SnChatMessageImplCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// @nodoc | /// @nodoc | ||||||
| @JsonSerializable() | @JsonSerializable() | ||||||
| @HiveType(typeId: 4) |  | ||||||
| class _$SnChatMessageImpl extends _SnChatMessage { | class _$SnChatMessageImpl extends _SnChatMessage { | ||||||
|   const _$SnChatMessageImpl( |   const _$SnChatMessageImpl( | ||||||
|       {@HiveField(0) required this.id, |       {required this.id, | ||||||
|       @HiveField(1) required this.createdAt, |       required this.createdAt, | ||||||
|       @HiveField(2) required this.updatedAt, |       required this.updatedAt, | ||||||
|       @HiveField(3) required this.deletedAt, |       required this.deletedAt, | ||||||
|       @HiveField(4) required this.uuid, |       required this.uuid, | ||||||
|       @HiveField(5) final Map<String, dynamic> body = const {}, |       final Map<String, dynamic> body = const {}, | ||||||
|       @HiveField(6) required this.type, |       required this.type, | ||||||
|       @HiveField(7) required this.channel, |       required this.channel, | ||||||
|       @HiveField(8) required this.sender, |       required this.sender, | ||||||
|       @HiveField(9) required this.channelId, |       required this.channelId, | ||||||
|       @HiveField(10) required this.senderId, |       required this.senderId, | ||||||
|       @HiveField(11) required this.quoteEventId, |       required this.quoteEventId, | ||||||
|       @HiveField(12) required this.relatedEventId, |       required this.relatedEventId, | ||||||
|       this.preload}) |       this.preload}) | ||||||
|       : _body = body, |       : _body = body, | ||||||
|         super._(); |         super._(); | ||||||
| @@ -1377,24 +1289,18 @@ class _$SnChatMessageImpl extends _SnChatMessage { | |||||||
|       _$$SnChatMessageImplFromJson(json); |       _$$SnChatMessageImplFromJson(json); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   @HiveField(0) |  | ||||||
|   final int id; |   final int id; | ||||||
|   @override |   @override | ||||||
|   @HiveField(1) |  | ||||||
|   final DateTime createdAt; |   final DateTime createdAt; | ||||||
|   @override |   @override | ||||||
|   @HiveField(2) |  | ||||||
|   final DateTime updatedAt; |   final DateTime updatedAt; | ||||||
|   @override |   @override | ||||||
|   @HiveField(3) |  | ||||||
|   final DateTime? deletedAt; |   final DateTime? deletedAt; | ||||||
|   @override |   @override | ||||||
|   @HiveField(4) |  | ||||||
|   final String uuid; |   final String uuid; | ||||||
|   final Map<String, dynamic> _body; |   final Map<String, dynamic> _body; | ||||||
|   @override |   @override | ||||||
|   @JsonKey() |   @JsonKey() | ||||||
|   @HiveField(5) |  | ||||||
|   Map<String, dynamic> get body { |   Map<String, dynamic> get body { | ||||||
|     if (_body is EqualUnmodifiableMapView) return _body; |     if (_body is EqualUnmodifiableMapView) return _body; | ||||||
|     // ignore: implicit_dynamic_type |     // ignore: implicit_dynamic_type | ||||||
| @@ -1402,25 +1308,18 @@ class _$SnChatMessageImpl extends _SnChatMessage { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   @HiveField(6) |  | ||||||
|   final String type; |   final String type; | ||||||
|   @override |   @override | ||||||
|   @HiveField(7) |  | ||||||
|   final SnChannel channel; |   final SnChannel channel; | ||||||
|   @override |   @override | ||||||
|   @HiveField(8) |  | ||||||
|   final SnChannelMember sender; |   final SnChannelMember sender; | ||||||
|   @override |   @override | ||||||
|   @HiveField(9) |  | ||||||
|   final int channelId; |   final int channelId; | ||||||
|   @override |   @override | ||||||
|   @HiveField(10) |  | ||||||
|   final int senderId; |   final int senderId; | ||||||
|   @override |   @override | ||||||
|   @HiveField(11) |  | ||||||
|   final int? quoteEventId; |   final int? quoteEventId; | ||||||
|   @override |   @override | ||||||
|   @HiveField(12) |  | ||||||
|   final int? relatedEventId; |   final int? relatedEventId; | ||||||
|   @override |   @override | ||||||
|   final SnChatMessagePreload? preload; |   final SnChatMessagePreload? preload; | ||||||
| @@ -1495,19 +1394,19 @@ class _$SnChatMessageImpl extends _SnChatMessage { | |||||||
|  |  | ||||||
| abstract class _SnChatMessage extends SnChatMessage { | abstract class _SnChatMessage extends SnChatMessage { | ||||||
|   const factory _SnChatMessage( |   const factory _SnChatMessage( | ||||||
|       {@HiveField(0) required final int id, |       {required final int id, | ||||||
|       @HiveField(1) required final DateTime createdAt, |       required final DateTime createdAt, | ||||||
|       @HiveField(2) required final DateTime updatedAt, |       required final DateTime updatedAt, | ||||||
|       @HiveField(3) required final DateTime? deletedAt, |       required final DateTime? deletedAt, | ||||||
|       @HiveField(4) required final String uuid, |       required final String uuid, | ||||||
|       @HiveField(5) final Map<String, dynamic> body, |       final Map<String, dynamic> body, | ||||||
|       @HiveField(6) required final String type, |       required final String type, | ||||||
|       @HiveField(7) required final SnChannel channel, |       required final SnChannel channel, | ||||||
|       @HiveField(8) required final SnChannelMember sender, |       required final SnChannelMember sender, | ||||||
|       @HiveField(9) required final int channelId, |       required final int channelId, | ||||||
|       @HiveField(10) required final int senderId, |       required final int senderId, | ||||||
|       @HiveField(11) required final int? quoteEventId, |       required final int? quoteEventId, | ||||||
|       @HiveField(12) required final int? relatedEventId, |       required final int? relatedEventId, | ||||||
|       final SnChatMessagePreload? preload}) = _$SnChatMessageImpl; |       final SnChatMessagePreload? preload}) = _$SnChatMessageImpl; | ||||||
|   const _SnChatMessage._() : super._(); |   const _SnChatMessage._() : super._(); | ||||||
|  |  | ||||||
| @@ -1515,43 +1414,30 @@ abstract class _SnChatMessage extends SnChatMessage { | |||||||
|       _$SnChatMessageImpl.fromJson; |       _$SnChatMessageImpl.fromJson; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   @HiveField(0) |  | ||||||
|   int get id; |   int get id; | ||||||
|   @override |   @override | ||||||
|   @HiveField(1) |  | ||||||
|   DateTime get createdAt; |   DateTime get createdAt; | ||||||
|   @override |   @override | ||||||
|   @HiveField(2) |  | ||||||
|   DateTime get updatedAt; |   DateTime get updatedAt; | ||||||
|   @override |   @override | ||||||
|   @HiveField(3) |  | ||||||
|   DateTime? get deletedAt; |   DateTime? get deletedAt; | ||||||
|   @override |   @override | ||||||
|   @HiveField(4) |  | ||||||
|   String get uuid; |   String get uuid; | ||||||
|   @override |   @override | ||||||
|   @HiveField(5) |  | ||||||
|   Map<String, dynamic> get body; |   Map<String, dynamic> get body; | ||||||
|   @override |   @override | ||||||
|   @HiveField(6) |  | ||||||
|   String get type; |   String get type; | ||||||
|   @override |   @override | ||||||
|   @HiveField(7) |  | ||||||
|   SnChannel get channel; |   SnChannel get channel; | ||||||
|   @override |   @override | ||||||
|   @HiveField(8) |  | ||||||
|   SnChannelMember get sender; |   SnChannelMember get sender; | ||||||
|   @override |   @override | ||||||
|   @HiveField(9) |  | ||||||
|   int get channelId; |   int get channelId; | ||||||
|   @override |   @override | ||||||
|   @HiveField(10) |  | ||||||
|   int get senderId; |   int get senderId; | ||||||
|   @override |   @override | ||||||
|   @HiveField(11) |  | ||||||
|   int? get quoteEventId; |   int? get quoteEventId; | ||||||
|   @override |   @override | ||||||
|   @HiveField(12) |  | ||||||
|   int? get relatedEventId; |   int? get relatedEventId; | ||||||
|   @override |   @override | ||||||
|   SnChatMessagePreload? get preload; |   SnChatMessagePreload? get preload; | ||||||
|   | |||||||
| @@ -2,214 +2,6 @@ | |||||||
|  |  | ||||||
| part of 'chat.dart'; | part of 'chat.dart'; | ||||||
|  |  | ||||||
| // ************************************************************************** |  | ||||||
| // TypeAdapterGenerator |  | ||||||
| // ************************************************************************** |  | ||||||
|  |  | ||||||
| class SnChannelImplAdapter extends TypeAdapter<_$SnChannelImpl> { |  | ||||||
|   @override |  | ||||||
|   final int typeId = 2; |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   _$SnChannelImpl read(BinaryReader reader) { |  | ||||||
|     final numOfFields = reader.readByte(); |  | ||||||
|     final fields = <int, dynamic>{ |  | ||||||
|       for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), |  | ||||||
|     }; |  | ||||||
|     return _$SnChannelImpl( |  | ||||||
|       id: fields[0] as int, |  | ||||||
|       createdAt: fields[1] as DateTime, |  | ||||||
|       updatedAt: fields[2] as DateTime, |  | ||||||
|       deletedAt: fields[3] as dynamic, |  | ||||||
|       alias: fields[4] as String, |  | ||||||
|       name: fields[5] as String, |  | ||||||
|       description: fields[6] as String, |  | ||||||
|       members: (fields[7] as List?)?.cast<SnChannelMember>(), |  | ||||||
|       type: fields[8] as int, |  | ||||||
|       accountId: fields[9] as int, |  | ||||||
|       realm: fields[10] as SnRealm?, |  | ||||||
|       realmId: fields[11] as int?, |  | ||||||
|       isPublic: fields[12] as bool, |  | ||||||
|       isCommunity: fields[13] as bool, |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   void write(BinaryWriter writer, _$SnChannelImpl obj) { |  | ||||||
|     writer |  | ||||||
|       ..writeByte(14) |  | ||||||
|       ..writeByte(0) |  | ||||||
|       ..write(obj.id) |  | ||||||
|       ..writeByte(1) |  | ||||||
|       ..write(obj.createdAt) |  | ||||||
|       ..writeByte(2) |  | ||||||
|       ..write(obj.updatedAt) |  | ||||||
|       ..writeByte(3) |  | ||||||
|       ..write(obj.deletedAt) |  | ||||||
|       ..writeByte(4) |  | ||||||
|       ..write(obj.alias) |  | ||||||
|       ..writeByte(5) |  | ||||||
|       ..write(obj.name) |  | ||||||
|       ..writeByte(6) |  | ||||||
|       ..write(obj.description) |  | ||||||
|       ..writeByte(8) |  | ||||||
|       ..write(obj.type) |  | ||||||
|       ..writeByte(9) |  | ||||||
|       ..write(obj.accountId) |  | ||||||
|       ..writeByte(10) |  | ||||||
|       ..write(obj.realm) |  | ||||||
|       ..writeByte(11) |  | ||||||
|       ..write(obj.realmId) |  | ||||||
|       ..writeByte(12) |  | ||||||
|       ..write(obj.isPublic) |  | ||||||
|       ..writeByte(13) |  | ||||||
|       ..write(obj.isCommunity) |  | ||||||
|       ..writeByte(7) |  | ||||||
|       ..write(obj.members); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   int get hashCode => typeId.hashCode; |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   bool operator ==(Object other) => |  | ||||||
|       identical(this, other) || |  | ||||||
|       other is SnChannelImplAdapter && |  | ||||||
|           runtimeType == other.runtimeType && |  | ||||||
|           typeId == other.typeId; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class SnChannelMemberImplAdapter extends TypeAdapter<_$SnChannelMemberImpl> { |  | ||||||
|   @override |  | ||||||
|   final int typeId = 3; |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   _$SnChannelMemberImpl read(BinaryReader reader) { |  | ||||||
|     final numOfFields = reader.readByte(); |  | ||||||
|     final fields = <int, dynamic>{ |  | ||||||
|       for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), |  | ||||||
|     }; |  | ||||||
|     return _$SnChannelMemberImpl( |  | ||||||
|       id: fields[0] as int, |  | ||||||
|       createdAt: fields[1] as DateTime, |  | ||||||
|       updatedAt: fields[2] as DateTime, |  | ||||||
|       deletedAt: fields[3] as DateTime?, |  | ||||||
|       channelId: fields[4] as int, |  | ||||||
|       accountId: fields[5] as int, |  | ||||||
|       nick: fields[6] as String?, |  | ||||||
|       channel: fields[7] as SnChannel?, |  | ||||||
|       account: fields[8] as SnAccount?, |  | ||||||
|       powerLevel: fields[9] as int, |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   void write(BinaryWriter writer, _$SnChannelMemberImpl obj) { |  | ||||||
|     writer |  | ||||||
|       ..writeByte(10) |  | ||||||
|       ..writeByte(0) |  | ||||||
|       ..write(obj.id) |  | ||||||
|       ..writeByte(1) |  | ||||||
|       ..write(obj.createdAt) |  | ||||||
|       ..writeByte(2) |  | ||||||
|       ..write(obj.updatedAt) |  | ||||||
|       ..writeByte(3) |  | ||||||
|       ..write(obj.deletedAt) |  | ||||||
|       ..writeByte(4) |  | ||||||
|       ..write(obj.channelId) |  | ||||||
|       ..writeByte(5) |  | ||||||
|       ..write(obj.accountId) |  | ||||||
|       ..writeByte(6) |  | ||||||
|       ..write(obj.nick) |  | ||||||
|       ..writeByte(7) |  | ||||||
|       ..write(obj.channel) |  | ||||||
|       ..writeByte(8) |  | ||||||
|       ..write(obj.account) |  | ||||||
|       ..writeByte(9) |  | ||||||
|       ..write(obj.powerLevel); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   int get hashCode => typeId.hashCode; |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   bool operator ==(Object other) => |  | ||||||
|       identical(this, other) || |  | ||||||
|       other is SnChannelMemberImplAdapter && |  | ||||||
|           runtimeType == other.runtimeType && |  | ||||||
|           typeId == other.typeId; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class SnChatMessageImplAdapter extends TypeAdapter<_$SnChatMessageImpl> { |  | ||||||
|   @override |  | ||||||
|   final int typeId = 4; |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   _$SnChatMessageImpl read(BinaryReader reader) { |  | ||||||
|     final numOfFields = reader.readByte(); |  | ||||||
|     final fields = <int, dynamic>{ |  | ||||||
|       for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), |  | ||||||
|     }; |  | ||||||
|     return _$SnChatMessageImpl( |  | ||||||
|       id: fields[0] as int, |  | ||||||
|       createdAt: fields[1] as DateTime, |  | ||||||
|       updatedAt: fields[2] as DateTime, |  | ||||||
|       deletedAt: fields[3] as DateTime?, |  | ||||||
|       uuid: fields[4] as String, |  | ||||||
|       body: (fields[5] as Map).cast<String, dynamic>(), |  | ||||||
|       type: fields[6] as String, |  | ||||||
|       channel: fields[7] as SnChannel, |  | ||||||
|       sender: fields[8] as SnChannelMember, |  | ||||||
|       channelId: fields[9] as int, |  | ||||||
|       senderId: fields[10] as int, |  | ||||||
|       quoteEventId: fields[11] as int?, |  | ||||||
|       relatedEventId: fields[12] as int?, |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   void write(BinaryWriter writer, _$SnChatMessageImpl obj) { |  | ||||||
|     writer |  | ||||||
|       ..writeByte(13) |  | ||||||
|       ..writeByte(0) |  | ||||||
|       ..write(obj.id) |  | ||||||
|       ..writeByte(1) |  | ||||||
|       ..write(obj.createdAt) |  | ||||||
|       ..writeByte(2) |  | ||||||
|       ..write(obj.updatedAt) |  | ||||||
|       ..writeByte(3) |  | ||||||
|       ..write(obj.deletedAt) |  | ||||||
|       ..writeByte(4) |  | ||||||
|       ..write(obj.uuid) |  | ||||||
|       ..writeByte(6) |  | ||||||
|       ..write(obj.type) |  | ||||||
|       ..writeByte(7) |  | ||||||
|       ..write(obj.channel) |  | ||||||
|       ..writeByte(8) |  | ||||||
|       ..write(obj.sender) |  | ||||||
|       ..writeByte(9) |  | ||||||
|       ..write(obj.channelId) |  | ||||||
|       ..writeByte(10) |  | ||||||
|       ..write(obj.senderId) |  | ||||||
|       ..writeByte(11) |  | ||||||
|       ..write(obj.quoteEventId) |  | ||||||
|       ..writeByte(12) |  | ||||||
|       ..write(obj.relatedEventId) |  | ||||||
|       ..writeByte(5) |  | ||||||
|       ..write(obj.body); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   int get hashCode => typeId.hashCode; |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   bool operator ==(Object other) => |  | ||||||
|       identical(this, other) || |  | ||||||
|       other is SnChatMessageImplAdapter && |  | ||||||
|           runtimeType == other.runtimeType && |  | ||||||
|           typeId == other.typeId; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ************************************************************************** | // ************************************************************************** | ||||||
| // JsonSerializableGenerator | // JsonSerializableGenerator | ||||||
| // ************************************************************************** | // ************************************************************************** | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | import 'package:freezed_annotation/freezed_annotation.dart'; | ||||||
| import 'package:surface/types/attachment.dart'; | import 'package:surface/types/attachment.dart'; | ||||||
| import 'package:surface/types/poll.dart'; | import 'package:surface/types/poll.dart'; | ||||||
|  | import 'package:surface/types/realm.dart'; | ||||||
|  |  | ||||||
| part 'post.freezed.dart'; | part 'post.freezed.dart'; | ||||||
| part 'post.g.dart'; | part 'post.g.dart'; | ||||||
| @@ -24,6 +25,7 @@ class SnPost with _$SnPost { | |||||||
|     required List<SnPost>? replies, |     required List<SnPost>? replies, | ||||||
|     required int? replyId, |     required int? replyId, | ||||||
|     required int? repostId, |     required int? repostId, | ||||||
|  |     required int? realmId, | ||||||
|     required SnPost? replyTo, |     required SnPost? replyTo, | ||||||
|     required SnPost? repostTo, |     required SnPost? repostTo, | ||||||
|     required List<int>? visibleUsersList, |     required List<int>? visibleUsersList, | ||||||
| @@ -95,6 +97,7 @@ class SnPostPreload with _$SnPostPreload { | |||||||
|     required List<SnAttachment?>? attachments, |     required List<SnAttachment?>? attachments, | ||||||
|     required SnAttachment? video, |     required SnAttachment? video, | ||||||
|     required SnPoll? poll, |     required SnPoll? poll, | ||||||
|  |     required SnRealm? realm, | ||||||
|   }) = _SnPostPreload; |   }) = _SnPostPreload; | ||||||
|  |  | ||||||
|   factory SnPostPreload.fromJson(Map<String, Object?> json) => |   factory SnPostPreload.fromJson(Map<String, Object?> json) => | ||||||
|   | |||||||
| @@ -34,6 +34,7 @@ mixin _$SnPost { | |||||||
|   List<SnPost>? get replies => throw _privateConstructorUsedError; |   List<SnPost>? get replies => throw _privateConstructorUsedError; | ||||||
|   int? get replyId => throw _privateConstructorUsedError; |   int? get replyId => throw _privateConstructorUsedError; | ||||||
|   int? get repostId => throw _privateConstructorUsedError; |   int? get repostId => throw _privateConstructorUsedError; | ||||||
|  |   int? get realmId => throw _privateConstructorUsedError; | ||||||
|   SnPost? get replyTo => throw _privateConstructorUsedError; |   SnPost? get replyTo => throw _privateConstructorUsedError; | ||||||
|   SnPost? get repostTo => throw _privateConstructorUsedError; |   SnPost? get repostTo => throw _privateConstructorUsedError; | ||||||
|   List<int>? get visibleUsersList => throw _privateConstructorUsedError; |   List<int>? get visibleUsersList => throw _privateConstructorUsedError; | ||||||
| @@ -84,6 +85,7 @@ abstract class $SnPostCopyWith<$Res> { | |||||||
|       List<SnPost>? replies, |       List<SnPost>? replies, | ||||||
|       int? replyId, |       int? replyId, | ||||||
|       int? repostId, |       int? repostId, | ||||||
|  |       int? realmId, | ||||||
|       SnPost? replyTo, |       SnPost? replyTo, | ||||||
|       SnPost? repostTo, |       SnPost? repostTo, | ||||||
|       List<int>? visibleUsersList, |       List<int>? visibleUsersList, | ||||||
| @@ -141,6 +143,7 @@ class _$SnPostCopyWithImpl<$Res, $Val extends SnPost> | |||||||
|     Object? replies = freezed, |     Object? replies = freezed, | ||||||
|     Object? replyId = freezed, |     Object? replyId = freezed, | ||||||
|     Object? repostId = freezed, |     Object? repostId = freezed, | ||||||
|  |     Object? realmId = freezed, | ||||||
|     Object? replyTo = freezed, |     Object? replyTo = freezed, | ||||||
|     Object? repostTo = freezed, |     Object? repostTo = freezed, | ||||||
|     Object? visibleUsersList = freezed, |     Object? visibleUsersList = freezed, | ||||||
| @@ -219,6 +222,10 @@ class _$SnPostCopyWithImpl<$Res, $Val extends SnPost> | |||||||
|           ? _value.repostId |           ? _value.repostId | ||||||
|           : repostId // ignore: cast_nullable_to_non_nullable |           : repostId // ignore: cast_nullable_to_non_nullable | ||||||
|               as int?, |               as int?, | ||||||
|  |       realmId: freezed == realmId | ||||||
|  |           ? _value.realmId | ||||||
|  |           : realmId // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as int?, | ||||||
|       replyTo: freezed == replyTo |       replyTo: freezed == replyTo | ||||||
|           ? _value.replyTo |           ? _value.replyTo | ||||||
|           : replyTo // ignore: cast_nullable_to_non_nullable |           : replyTo // ignore: cast_nullable_to_non_nullable | ||||||
| @@ -387,6 +394,7 @@ abstract class _$$SnPostImplCopyWith<$Res> implements $SnPostCopyWith<$Res> { | |||||||
|       List<SnPost>? replies, |       List<SnPost>? replies, | ||||||
|       int? replyId, |       int? replyId, | ||||||
|       int? repostId, |       int? repostId, | ||||||
|  |       int? realmId, | ||||||
|       SnPost? replyTo, |       SnPost? replyTo, | ||||||
|       SnPost? repostTo, |       SnPost? repostTo, | ||||||
|       List<int>? visibleUsersList, |       List<int>? visibleUsersList, | ||||||
| @@ -447,6 +455,7 @@ class __$$SnPostImplCopyWithImpl<$Res> | |||||||
|     Object? replies = freezed, |     Object? replies = freezed, | ||||||
|     Object? replyId = freezed, |     Object? replyId = freezed, | ||||||
|     Object? repostId = freezed, |     Object? repostId = freezed, | ||||||
|  |     Object? realmId = freezed, | ||||||
|     Object? replyTo = freezed, |     Object? replyTo = freezed, | ||||||
|     Object? repostTo = freezed, |     Object? repostTo = freezed, | ||||||
|     Object? visibleUsersList = freezed, |     Object? visibleUsersList = freezed, | ||||||
| @@ -525,6 +534,10 @@ class __$$SnPostImplCopyWithImpl<$Res> | |||||||
|           ? _value.repostId |           ? _value.repostId | ||||||
|           : repostId // ignore: cast_nullable_to_non_nullable |           : repostId // ignore: cast_nullable_to_non_nullable | ||||||
|               as int?, |               as int?, | ||||||
|  |       realmId: freezed == realmId | ||||||
|  |           ? _value.realmId | ||||||
|  |           : realmId // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as int?, | ||||||
|       replyTo: freezed == replyTo |       replyTo: freezed == replyTo | ||||||
|           ? _value.replyTo |           ? _value.replyTo | ||||||
|           : replyTo // ignore: cast_nullable_to_non_nullable |           : replyTo // ignore: cast_nullable_to_non_nullable | ||||||
| @@ -627,6 +640,7 @@ class _$SnPostImpl extends _SnPost { | |||||||
|       required final List<SnPost>? replies, |       required final List<SnPost>? replies, | ||||||
|       required this.replyId, |       required this.replyId, | ||||||
|       required this.repostId, |       required this.repostId, | ||||||
|  |       required this.realmId, | ||||||
|       required this.replyTo, |       required this.replyTo, | ||||||
|       required this.repostTo, |       required this.repostTo, | ||||||
|       required final List<int>? visibleUsersList, |       required final List<int>? visibleUsersList, | ||||||
| @@ -715,6 +729,8 @@ class _$SnPostImpl extends _SnPost { | |||||||
|   @override |   @override | ||||||
|   final int? repostId; |   final int? repostId; | ||||||
|   @override |   @override | ||||||
|  |   final int? realmId; | ||||||
|  |   @override | ||||||
|   final SnPost? replyTo; |   final SnPost? replyTo; | ||||||
|   @override |   @override | ||||||
|   final SnPost? repostTo; |   final SnPost? repostTo; | ||||||
| @@ -777,7 +793,7 @@ class _$SnPostImpl extends _SnPost { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   String toString() { |   String toString() { | ||||||
|     return 'SnPost(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, type: $type, body: $body, language: $language, alias: $alias, aliasPrefix: $aliasPrefix, tags: $tags, categories: $categories, replies: $replies, replyId: $replyId, repostId: $repostId, replyTo: $replyTo, repostTo: $repostTo, visibleUsersList: $visibleUsersList, invisibleUsersList: $invisibleUsersList, visibility: $visibility, editedAt: $editedAt, pinnedAt: $pinnedAt, lockedAt: $lockedAt, isDraft: $isDraft, publishedAt: $publishedAt, publishedUntil: $publishedUntil, totalUpvote: $totalUpvote, totalDownvote: $totalDownvote, totalViews: $totalViews, totalAggregatedViews: $totalAggregatedViews, publisherId: $publisherId, pollId: $pollId, publisher: $publisher, metric: $metric, preload: $preload)'; |     return 'SnPost(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, type: $type, body: $body, language: $language, alias: $alias, aliasPrefix: $aliasPrefix, tags: $tags, categories: $categories, replies: $replies, replyId: $replyId, repostId: $repostId, realmId: $realmId, replyTo: $replyTo, repostTo: $repostTo, visibleUsersList: $visibleUsersList, invisibleUsersList: $invisibleUsersList, visibility: $visibility, editedAt: $editedAt, pinnedAt: $pinnedAt, lockedAt: $lockedAt, isDraft: $isDraft, publishedAt: $publishedAt, publishedUntil: $publishedUntil, totalUpvote: $totalUpvote, totalDownvote: $totalDownvote, totalViews: $totalViews, totalAggregatedViews: $totalAggregatedViews, publisherId: $publisherId, pollId: $pollId, publisher: $publisher, metric: $metric, preload: $preload)'; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
| @@ -806,6 +822,7 @@ class _$SnPostImpl extends _SnPost { | |||||||
|             (identical(other.replyId, replyId) || other.replyId == replyId) && |             (identical(other.replyId, replyId) || other.replyId == replyId) && | ||||||
|             (identical(other.repostId, repostId) || |             (identical(other.repostId, repostId) || | ||||||
|                 other.repostId == repostId) && |                 other.repostId == repostId) && | ||||||
|  |             (identical(other.realmId, realmId) || other.realmId == realmId) && | ||||||
|             (identical(other.replyTo, replyTo) || other.replyTo == replyTo) && |             (identical(other.replyTo, replyTo) || other.replyTo == replyTo) && | ||||||
|             (identical(other.repostTo, repostTo) || |             (identical(other.repostTo, repostTo) || | ||||||
|                 other.repostTo == repostTo) && |                 other.repostTo == repostTo) && | ||||||
| @@ -861,6 +878,7 @@ class _$SnPostImpl extends _SnPost { | |||||||
|         const DeepCollectionEquality().hash(_replies), |         const DeepCollectionEquality().hash(_replies), | ||||||
|         replyId, |         replyId, | ||||||
|         repostId, |         repostId, | ||||||
|  |         realmId, | ||||||
|         replyTo, |         replyTo, | ||||||
|         repostTo, |         repostTo, | ||||||
|         const DeepCollectionEquality().hash(_visibleUsersList), |         const DeepCollectionEquality().hash(_visibleUsersList), | ||||||
| @@ -915,6 +933,7 @@ abstract class _SnPost extends SnPost { | |||||||
|       required final List<SnPost>? replies, |       required final List<SnPost>? replies, | ||||||
|       required final int? replyId, |       required final int? replyId, | ||||||
|       required final int? repostId, |       required final int? repostId, | ||||||
|  |       required final int? realmId, | ||||||
|       required final SnPost? replyTo, |       required final SnPost? replyTo, | ||||||
|       required final SnPost? repostTo, |       required final SnPost? repostTo, | ||||||
|       required final List<int>? visibleUsersList, |       required final List<int>? visibleUsersList, | ||||||
| @@ -968,6 +987,8 @@ abstract class _SnPost extends SnPost { | |||||||
|   @override |   @override | ||||||
|   int? get repostId; |   int? get repostId; | ||||||
|   @override |   @override | ||||||
|  |   int? get realmId; | ||||||
|  |   @override | ||||||
|   SnPost? get replyTo; |   SnPost? get replyTo; | ||||||
|   @override |   @override | ||||||
|   SnPost? get repostTo; |   SnPost? get repostTo; | ||||||
| @@ -1636,6 +1657,7 @@ mixin _$SnPostPreload { | |||||||
|   List<SnAttachment?>? get attachments => throw _privateConstructorUsedError; |   List<SnAttachment?>? get attachments => throw _privateConstructorUsedError; | ||||||
|   SnAttachment? get video => throw _privateConstructorUsedError; |   SnAttachment? get video => throw _privateConstructorUsedError; | ||||||
|   SnPoll? get poll => throw _privateConstructorUsedError; |   SnPoll? get poll => throw _privateConstructorUsedError; | ||||||
|  |   SnRealm? get realm => throw _privateConstructorUsedError; | ||||||
|  |  | ||||||
|   /// Serializes this SnPostPreload to a JSON map. |   /// Serializes this SnPostPreload to a JSON map. | ||||||
|   Map<String, dynamic> toJson() => throw _privateConstructorUsedError; |   Map<String, dynamic> toJson() => throw _privateConstructorUsedError; | ||||||
| @@ -1657,11 +1679,13 @@ abstract class $SnPostPreloadCopyWith<$Res> { | |||||||
|       {SnAttachment? thumbnail, |       {SnAttachment? thumbnail, | ||||||
|       List<SnAttachment?>? attachments, |       List<SnAttachment?>? attachments, | ||||||
|       SnAttachment? video, |       SnAttachment? video, | ||||||
|       SnPoll? poll}); |       SnPoll? poll, | ||||||
|  |       SnRealm? realm}); | ||||||
|  |  | ||||||
|   $SnAttachmentCopyWith<$Res>? get thumbnail; |   $SnAttachmentCopyWith<$Res>? get thumbnail; | ||||||
|   $SnAttachmentCopyWith<$Res>? get video; |   $SnAttachmentCopyWith<$Res>? get video; | ||||||
|   $SnPollCopyWith<$Res>? get poll; |   $SnPollCopyWith<$Res>? get poll; | ||||||
|  |   $SnRealmCopyWith<$Res>? get realm; | ||||||
| } | } | ||||||
|  |  | ||||||
| /// @nodoc | /// @nodoc | ||||||
| @@ -1683,6 +1707,7 @@ class _$SnPostPreloadCopyWithImpl<$Res, $Val extends SnPostPreload> | |||||||
|     Object? attachments = freezed, |     Object? attachments = freezed, | ||||||
|     Object? video = freezed, |     Object? video = freezed, | ||||||
|     Object? poll = freezed, |     Object? poll = freezed, | ||||||
|  |     Object? realm = freezed, | ||||||
|   }) { |   }) { | ||||||
|     return _then(_value.copyWith( |     return _then(_value.copyWith( | ||||||
|       thumbnail: freezed == thumbnail |       thumbnail: freezed == thumbnail | ||||||
| @@ -1701,6 +1726,10 @@ class _$SnPostPreloadCopyWithImpl<$Res, $Val extends SnPostPreload> | |||||||
|           ? _value.poll |           ? _value.poll | ||||||
|           : poll // ignore: cast_nullable_to_non_nullable |           : poll // ignore: cast_nullable_to_non_nullable | ||||||
|               as SnPoll?, |               as SnPoll?, | ||||||
|  |       realm: freezed == realm | ||||||
|  |           ? _value.realm | ||||||
|  |           : realm // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as SnRealm?, | ||||||
|     ) as $Val); |     ) as $Val); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -1745,6 +1774,20 @@ class _$SnPostPreloadCopyWithImpl<$Res, $Val extends SnPostPreload> | |||||||
|       return _then(_value.copyWith(poll: value) as $Val); |       return _then(_value.copyWith(poll: value) as $Val); | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   /// Create a copy of SnPostPreload | ||||||
|  |   /// with the given fields replaced by the non-null parameter values. | ||||||
|  |   @override | ||||||
|  |   @pragma('vm:prefer-inline') | ||||||
|  |   $SnRealmCopyWith<$Res>? get realm { | ||||||
|  |     if (_value.realm == null) { | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return $SnRealmCopyWith<$Res>(_value.realm!, (value) { | ||||||
|  |       return _then(_value.copyWith(realm: value) as $Val); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| /// @nodoc | /// @nodoc | ||||||
| @@ -1759,7 +1802,8 @@ abstract class _$$SnPostPreloadImplCopyWith<$Res> | |||||||
|       {SnAttachment? thumbnail, |       {SnAttachment? thumbnail, | ||||||
|       List<SnAttachment?>? attachments, |       List<SnAttachment?>? attachments, | ||||||
|       SnAttachment? video, |       SnAttachment? video, | ||||||
|       SnPoll? poll}); |       SnPoll? poll, | ||||||
|  |       SnRealm? realm}); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   $SnAttachmentCopyWith<$Res>? get thumbnail; |   $SnAttachmentCopyWith<$Res>? get thumbnail; | ||||||
| @@ -1767,6 +1811,8 @@ abstract class _$$SnPostPreloadImplCopyWith<$Res> | |||||||
|   $SnAttachmentCopyWith<$Res>? get video; |   $SnAttachmentCopyWith<$Res>? get video; | ||||||
|   @override |   @override | ||||||
|   $SnPollCopyWith<$Res>? get poll; |   $SnPollCopyWith<$Res>? get poll; | ||||||
|  |   @override | ||||||
|  |   $SnRealmCopyWith<$Res>? get realm; | ||||||
| } | } | ||||||
|  |  | ||||||
| /// @nodoc | /// @nodoc | ||||||
| @@ -1786,6 +1832,7 @@ class __$$SnPostPreloadImplCopyWithImpl<$Res> | |||||||
|     Object? attachments = freezed, |     Object? attachments = freezed, | ||||||
|     Object? video = freezed, |     Object? video = freezed, | ||||||
|     Object? poll = freezed, |     Object? poll = freezed, | ||||||
|  |     Object? realm = freezed, | ||||||
|   }) { |   }) { | ||||||
|     return _then(_$SnPostPreloadImpl( |     return _then(_$SnPostPreloadImpl( | ||||||
|       thumbnail: freezed == thumbnail |       thumbnail: freezed == thumbnail | ||||||
| @@ -1804,6 +1851,10 @@ class __$$SnPostPreloadImplCopyWithImpl<$Res> | |||||||
|           ? _value.poll |           ? _value.poll | ||||||
|           : poll // ignore: cast_nullable_to_non_nullable |           : poll // ignore: cast_nullable_to_non_nullable | ||||||
|               as SnPoll?, |               as SnPoll?, | ||||||
|  |       realm: freezed == realm | ||||||
|  |           ? _value.realm | ||||||
|  |           : realm // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as SnRealm?, | ||||||
|     )); |     )); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @@ -1815,7 +1866,8 @@ class _$SnPostPreloadImpl implements _SnPostPreload { | |||||||
|       {required this.thumbnail, |       {required this.thumbnail, | ||||||
|       required final List<SnAttachment?>? attachments, |       required final List<SnAttachment?>? attachments, | ||||||
|       required this.video, |       required this.video, | ||||||
|       required this.poll}) |       required this.poll, | ||||||
|  |       required this.realm}) | ||||||
|       : _attachments = attachments; |       : _attachments = attachments; | ||||||
|  |  | ||||||
|   factory _$SnPostPreloadImpl.fromJson(Map<String, dynamic> json) => |   factory _$SnPostPreloadImpl.fromJson(Map<String, dynamic> json) => | ||||||
| @@ -1837,10 +1889,12 @@ class _$SnPostPreloadImpl implements _SnPostPreload { | |||||||
|   final SnAttachment? video; |   final SnAttachment? video; | ||||||
|   @override |   @override | ||||||
|   final SnPoll? poll; |   final SnPoll? poll; | ||||||
|  |   @override | ||||||
|  |   final SnRealm? realm; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   String toString() { |   String toString() { | ||||||
|     return 'SnPostPreload(thumbnail: $thumbnail, attachments: $attachments, video: $video, poll: $poll)'; |     return 'SnPostPreload(thumbnail: $thumbnail, attachments: $attachments, video: $video, poll: $poll, realm: $realm)'; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
| @@ -1853,13 +1907,14 @@ class _$SnPostPreloadImpl implements _SnPostPreload { | |||||||
|             const DeepCollectionEquality() |             const DeepCollectionEquality() | ||||||
|                 .equals(other._attachments, _attachments) && |                 .equals(other._attachments, _attachments) && | ||||||
|             (identical(other.video, video) || other.video == video) && |             (identical(other.video, video) || other.video == video) && | ||||||
|             (identical(other.poll, poll) || other.poll == poll)); |             (identical(other.poll, poll) || other.poll == poll) && | ||||||
|  |             (identical(other.realm, realm) || other.realm == realm)); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @JsonKey(includeFromJson: false, includeToJson: false) |   @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|   @override |   @override | ||||||
|   int get hashCode => Object.hash(runtimeType, thumbnail, |   int get hashCode => Object.hash(runtimeType, thumbnail, | ||||||
|       const DeepCollectionEquality().hash(_attachments), video, poll); |       const DeepCollectionEquality().hash(_attachments), video, poll, realm); | ||||||
|  |  | ||||||
|   /// Create a copy of SnPostPreload |   /// Create a copy of SnPostPreload | ||||||
|   /// with the given fields replaced by the non-null parameter values. |   /// with the given fields replaced by the non-null parameter values. | ||||||
| @@ -1882,7 +1937,8 @@ abstract class _SnPostPreload implements SnPostPreload { | |||||||
|       {required final SnAttachment? thumbnail, |       {required final SnAttachment? thumbnail, | ||||||
|       required final List<SnAttachment?>? attachments, |       required final List<SnAttachment?>? attachments, | ||||||
|       required final SnAttachment? video, |       required final SnAttachment? video, | ||||||
|       required final SnPoll? poll}) = _$SnPostPreloadImpl; |       required final SnPoll? poll, | ||||||
|  |       required final SnRealm? realm}) = _$SnPostPreloadImpl; | ||||||
|  |  | ||||||
|   factory _SnPostPreload.fromJson(Map<String, dynamic> json) = |   factory _SnPostPreload.fromJson(Map<String, dynamic> json) = | ||||||
|       _$SnPostPreloadImpl.fromJson; |       _$SnPostPreloadImpl.fromJson; | ||||||
| @@ -1895,6 +1951,8 @@ abstract class _SnPostPreload implements SnPostPreload { | |||||||
|   SnAttachment? get video; |   SnAttachment? get video; | ||||||
|   @override |   @override | ||||||
|   SnPoll? get poll; |   SnPoll? get poll; | ||||||
|  |   @override | ||||||
|  |   SnRealm? get realm; | ||||||
|  |  | ||||||
|   /// Create a copy of SnPostPreload |   /// Create a copy of SnPostPreload | ||||||
|   /// with the given fields replaced by the non-null parameter values. |   /// with the given fields replaced by the non-null parameter values. | ||||||
|   | |||||||
| @@ -31,6 +31,7 @@ _$SnPostImpl _$$SnPostImplFromJson(Map<String, dynamic> json) => _$SnPostImpl( | |||||||
|           .toList(), |           .toList(), | ||||||
|       replyId: (json['reply_id'] as num?)?.toInt(), |       replyId: (json['reply_id'] as num?)?.toInt(), | ||||||
|       repostId: (json['repost_id'] as num?)?.toInt(), |       repostId: (json['repost_id'] as num?)?.toInt(), | ||||||
|  |       realmId: (json['realm_id'] as num?)?.toInt(), | ||||||
|       replyTo: json['reply_to'] == null |       replyTo: json['reply_to'] == null | ||||||
|           ? null |           ? null | ||||||
|           : SnPost.fromJson(json['reply_to'] as Map<String, dynamic>), |           : SnPost.fromJson(json['reply_to'] as Map<String, dynamic>), | ||||||
| @@ -91,6 +92,7 @@ Map<String, dynamic> _$$SnPostImplToJson(_$SnPostImpl instance) => | |||||||
|       'replies': instance.replies?.map((e) => e.toJson()).toList(), |       'replies': instance.replies?.map((e) => e.toJson()).toList(), | ||||||
|       'reply_id': instance.replyId, |       'reply_id': instance.replyId, | ||||||
|       'repost_id': instance.repostId, |       'repost_id': instance.repostId, | ||||||
|  |       'realm_id': instance.realmId, | ||||||
|       'reply_to': instance.replyTo?.toJson(), |       'reply_to': instance.replyTo?.toJson(), | ||||||
|       'repost_to': instance.repostTo?.toJson(), |       'repost_to': instance.repostTo?.toJson(), | ||||||
|       'visible_users_list': instance.visibleUsersList, |       'visible_users_list': instance.visibleUsersList, | ||||||
| @@ -178,6 +180,9 @@ _$SnPostPreloadImpl _$$SnPostPreloadImplFromJson(Map<String, dynamic> json) => | |||||||
|       poll: json['poll'] == null |       poll: json['poll'] == null | ||||||
|           ? null |           ? null | ||||||
|           : SnPoll.fromJson(json['poll'] as Map<String, dynamic>), |           : SnPoll.fromJson(json['poll'] as Map<String, dynamic>), | ||||||
|  |       realm: json['realm'] == null | ||||||
|  |           ? null | ||||||
|  |           : SnRealm.fromJson(json['realm'] as Map<String, dynamic>), | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
| Map<String, dynamic> _$$SnPostPreloadImplToJson(_$SnPostPreloadImpl instance) => | Map<String, dynamic> _$$SnPostPreloadImplToJson(_$SnPostPreloadImpl instance) => | ||||||
| @@ -186,6 +191,7 @@ Map<String, dynamic> _$$SnPostPreloadImplToJson(_$SnPostPreloadImpl instance) => | |||||||
|       'attachments': instance.attachments?.map((e) => e?.toJson()).toList(), |       'attachments': instance.attachments?.map((e) => e?.toJson()).toList(), | ||||||
|       'video': instance.video?.toJson(), |       'video': instance.video?.toJson(), | ||||||
|       'poll': instance.poll?.toJson(), |       'poll': instance.poll?.toJson(), | ||||||
|  |       'realm': instance.realm?.toJson(), | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
| _$SnBodyImpl _$$SnBodyImplFromJson(Map<String, dynamic> json) => _$SnBodyImpl( | _$SnBodyImpl _$$SnBodyImplFromJson(Map<String, dynamic> json) => _$SnBodyImpl( | ||||||
|   | |||||||
| @@ -1,5 +1,4 @@ | |||||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | import 'package:freezed_annotation/freezed_annotation.dart'; | ||||||
| import 'package:hive_flutter/hive_flutter.dart'; |  | ||||||
| import 'package:surface/types/account.dart'; | import 'package:surface/types/account.dart'; | ||||||
|  |  | ||||||
| part 'realm.freezed.dart'; | part 'realm.freezed.dart'; | ||||||
| @@ -27,22 +26,22 @@ class SnRealmMember with _$SnRealmMember { | |||||||
| class SnRealm with _$SnRealm { | class SnRealm with _$SnRealm { | ||||||
|   const SnRealm._(); |   const SnRealm._(); | ||||||
|  |  | ||||||
|   @HiveType(typeId: 1) |  | ||||||
|   const factory SnRealm({ |   const factory SnRealm({ | ||||||
|     @HiveField(0) required int id, |     required int id, | ||||||
|     @HiveField(1) required DateTime createdAt, |     required DateTime createdAt, | ||||||
|     @HiveField(2) required DateTime updatedAt, |     required DateTime updatedAt, | ||||||
|     @HiveField(3) required DateTime? deletedAt, |     required DateTime? deletedAt, | ||||||
|     @HiveField(4) required String alias, |     required String alias, | ||||||
|     @HiveField(5) required String name, |     required String name, | ||||||
|     @HiveField(6) required String description, |     required String description, | ||||||
|     List<SnRealmMember>? members, |     List<SnRealmMember>? members, | ||||||
|     @HiveField(7) required String? avatar, |     required String? avatar, | ||||||
|     @HiveField(8) required String? banner, |     required String? banner, | ||||||
|     @HiveField(9) required Map<String, dynamic>? accessPolicy, |     required Map<String, dynamic>? accessPolicy, | ||||||
|     @HiveField(10) required int accountId, |     required int accountId, | ||||||
|     @HiveField(11) required bool isPublic, |     required bool isPublic, | ||||||
|     @HiveField(12) required bool isCommunity, |     required bool isCommunity, | ||||||
|  |     @Default(0) int popularity, | ||||||
|   }) = _SnRealm; |   }) = _SnRealm; | ||||||
|  |  | ||||||
|   factory SnRealm.fromJson(Map<String, dynamic> json) => |   factory SnRealm.fromJson(Map<String, dynamic> json) => | ||||||
|   | |||||||
| @@ -367,33 +367,21 @@ SnRealm _$SnRealmFromJson(Map<String, dynamic> json) { | |||||||
|  |  | ||||||
| /// @nodoc | /// @nodoc | ||||||
| mixin _$SnRealm { | mixin _$SnRealm { | ||||||
|   @HiveField(0) |  | ||||||
|   int get id => throw _privateConstructorUsedError; |   int get id => throw _privateConstructorUsedError; | ||||||
|   @HiveField(1) |  | ||||||
|   DateTime get createdAt => throw _privateConstructorUsedError; |   DateTime get createdAt => throw _privateConstructorUsedError; | ||||||
|   @HiveField(2) |  | ||||||
|   DateTime get updatedAt => throw _privateConstructorUsedError; |   DateTime get updatedAt => throw _privateConstructorUsedError; | ||||||
|   @HiveField(3) |  | ||||||
|   DateTime? get deletedAt => throw _privateConstructorUsedError; |   DateTime? get deletedAt => throw _privateConstructorUsedError; | ||||||
|   @HiveField(4) |  | ||||||
|   String get alias => throw _privateConstructorUsedError; |   String get alias => throw _privateConstructorUsedError; | ||||||
|   @HiveField(5) |  | ||||||
|   String get name => throw _privateConstructorUsedError; |   String get name => throw _privateConstructorUsedError; | ||||||
|   @HiveField(6) |  | ||||||
|   String get description => throw _privateConstructorUsedError; |   String get description => throw _privateConstructorUsedError; | ||||||
|   List<SnRealmMember>? get members => throw _privateConstructorUsedError; |   List<SnRealmMember>? get members => throw _privateConstructorUsedError; | ||||||
|   @HiveField(7) |  | ||||||
|   String? get avatar => throw _privateConstructorUsedError; |   String? get avatar => throw _privateConstructorUsedError; | ||||||
|   @HiveField(8) |  | ||||||
|   String? get banner => throw _privateConstructorUsedError; |   String? get banner => throw _privateConstructorUsedError; | ||||||
|   @HiveField(9) |  | ||||||
|   Map<String, dynamic>? get accessPolicy => throw _privateConstructorUsedError; |   Map<String, dynamic>? get accessPolicy => throw _privateConstructorUsedError; | ||||||
|   @HiveField(10) |  | ||||||
|   int get accountId => throw _privateConstructorUsedError; |   int get accountId => throw _privateConstructorUsedError; | ||||||
|   @HiveField(11) |  | ||||||
|   bool get isPublic => throw _privateConstructorUsedError; |   bool get isPublic => throw _privateConstructorUsedError; | ||||||
|   @HiveField(12) |  | ||||||
|   bool get isCommunity => throw _privateConstructorUsedError; |   bool get isCommunity => throw _privateConstructorUsedError; | ||||||
|  |   int get popularity => throw _privateConstructorUsedError; | ||||||
|  |  | ||||||
|   /// Serializes this SnRealm to a JSON map. |   /// Serializes this SnRealm to a JSON map. | ||||||
|   Map<String, dynamic> toJson() => throw _privateConstructorUsedError; |   Map<String, dynamic> toJson() => throw _privateConstructorUsedError; | ||||||
| @@ -410,20 +398,21 @@ abstract class $SnRealmCopyWith<$Res> { | |||||||
|       _$SnRealmCopyWithImpl<$Res, SnRealm>; |       _$SnRealmCopyWithImpl<$Res, SnRealm>; | ||||||
|   @useResult |   @useResult | ||||||
|   $Res call( |   $Res call( | ||||||
|       {@HiveField(0) int id, |       {int id, | ||||||
|       @HiveField(1) DateTime createdAt, |       DateTime createdAt, | ||||||
|       @HiveField(2) DateTime updatedAt, |       DateTime updatedAt, | ||||||
|       @HiveField(3) DateTime? deletedAt, |       DateTime? deletedAt, | ||||||
|       @HiveField(4) String alias, |       String alias, | ||||||
|       @HiveField(5) String name, |       String name, | ||||||
|       @HiveField(6) String description, |       String description, | ||||||
|       List<SnRealmMember>? members, |       List<SnRealmMember>? members, | ||||||
|       @HiveField(7) String? avatar, |       String? avatar, | ||||||
|       @HiveField(8) String? banner, |       String? banner, | ||||||
|       @HiveField(9) Map<String, dynamic>? accessPolicy, |       Map<String, dynamic>? accessPolicy, | ||||||
|       @HiveField(10) int accountId, |       int accountId, | ||||||
|       @HiveField(11) bool isPublic, |       bool isPublic, | ||||||
|       @HiveField(12) bool isCommunity}); |       bool isCommunity, | ||||||
|  |       int popularity}); | ||||||
| } | } | ||||||
|  |  | ||||||
| /// @nodoc | /// @nodoc | ||||||
| @@ -455,6 +444,7 @@ class _$SnRealmCopyWithImpl<$Res, $Val extends SnRealm> | |||||||
|     Object? accountId = null, |     Object? accountId = null, | ||||||
|     Object? isPublic = null, |     Object? isPublic = null, | ||||||
|     Object? isCommunity = null, |     Object? isCommunity = null, | ||||||
|  |     Object? popularity = null, | ||||||
|   }) { |   }) { | ||||||
|     return _then(_value.copyWith( |     return _then(_value.copyWith( | ||||||
|       id: null == id |       id: null == id | ||||||
| @@ -513,6 +503,10 @@ class _$SnRealmCopyWithImpl<$Res, $Val extends SnRealm> | |||||||
|           ? _value.isCommunity |           ? _value.isCommunity | ||||||
|           : isCommunity // ignore: cast_nullable_to_non_nullable |           : isCommunity // ignore: cast_nullable_to_non_nullable | ||||||
|               as bool, |               as bool, | ||||||
|  |       popularity: null == popularity | ||||||
|  |           ? _value.popularity | ||||||
|  |           : popularity // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as int, | ||||||
|     ) as $Val); |     ) as $Val); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @@ -525,20 +519,21 @@ abstract class _$$SnRealmImplCopyWith<$Res> implements $SnRealmCopyWith<$Res> { | |||||||
|   @override |   @override | ||||||
|   @useResult |   @useResult | ||||||
|   $Res call( |   $Res call( | ||||||
|       {@HiveField(0) int id, |       {int id, | ||||||
|       @HiveField(1) DateTime createdAt, |       DateTime createdAt, | ||||||
|       @HiveField(2) DateTime updatedAt, |       DateTime updatedAt, | ||||||
|       @HiveField(3) DateTime? deletedAt, |       DateTime? deletedAt, | ||||||
|       @HiveField(4) String alias, |       String alias, | ||||||
|       @HiveField(5) String name, |       String name, | ||||||
|       @HiveField(6) String description, |       String description, | ||||||
|       List<SnRealmMember>? members, |       List<SnRealmMember>? members, | ||||||
|       @HiveField(7) String? avatar, |       String? avatar, | ||||||
|       @HiveField(8) String? banner, |       String? banner, | ||||||
|       @HiveField(9) Map<String, dynamic>? accessPolicy, |       Map<String, dynamic>? accessPolicy, | ||||||
|       @HiveField(10) int accountId, |       int accountId, | ||||||
|       @HiveField(11) bool isPublic, |       bool isPublic, | ||||||
|       @HiveField(12) bool isCommunity}); |       bool isCommunity, | ||||||
|  |       int popularity}); | ||||||
| } | } | ||||||
|  |  | ||||||
| /// @nodoc | /// @nodoc | ||||||
| @@ -568,6 +563,7 @@ class __$$SnRealmImplCopyWithImpl<$Res> | |||||||
|     Object? accountId = null, |     Object? accountId = null, | ||||||
|     Object? isPublic = null, |     Object? isPublic = null, | ||||||
|     Object? isCommunity = null, |     Object? isCommunity = null, | ||||||
|  |     Object? popularity = null, | ||||||
|   }) { |   }) { | ||||||
|     return _then(_$SnRealmImpl( |     return _then(_$SnRealmImpl( | ||||||
|       id: null == id |       id: null == id | ||||||
| @@ -626,29 +622,33 @@ class __$$SnRealmImplCopyWithImpl<$Res> | |||||||
|           ? _value.isCommunity |           ? _value.isCommunity | ||||||
|           : isCommunity // ignore: cast_nullable_to_non_nullable |           : isCommunity // ignore: cast_nullable_to_non_nullable | ||||||
|               as bool, |               as bool, | ||||||
|  |       popularity: null == popularity | ||||||
|  |           ? _value.popularity | ||||||
|  |           : popularity // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as int, | ||||||
|     )); |     )); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| /// @nodoc | /// @nodoc | ||||||
| @JsonSerializable() | @JsonSerializable() | ||||||
| @HiveType(typeId: 1) |  | ||||||
| class _$SnRealmImpl extends _SnRealm { | class _$SnRealmImpl extends _SnRealm { | ||||||
|   const _$SnRealmImpl( |   const _$SnRealmImpl( | ||||||
|       {@HiveField(0) required this.id, |       {required this.id, | ||||||
|       @HiveField(1) required this.createdAt, |       required this.createdAt, | ||||||
|       @HiveField(2) required this.updatedAt, |       required this.updatedAt, | ||||||
|       @HiveField(3) required this.deletedAt, |       required this.deletedAt, | ||||||
|       @HiveField(4) required this.alias, |       required this.alias, | ||||||
|       @HiveField(5) required this.name, |       required this.name, | ||||||
|       @HiveField(6) required this.description, |       required this.description, | ||||||
|       final List<SnRealmMember>? members, |       final List<SnRealmMember>? members, | ||||||
|       @HiveField(7) required this.avatar, |       required this.avatar, | ||||||
|       @HiveField(8) required this.banner, |       required this.banner, | ||||||
|       @HiveField(9) required final Map<String, dynamic>? accessPolicy, |       required final Map<String, dynamic>? accessPolicy, | ||||||
|       @HiveField(10) required this.accountId, |       required this.accountId, | ||||||
|       @HiveField(11) required this.isPublic, |       required this.isPublic, | ||||||
|       @HiveField(12) required this.isCommunity}) |       required this.isCommunity, | ||||||
|  |       this.popularity = 0}) | ||||||
|       : _members = members, |       : _members = members, | ||||||
|         _accessPolicy = accessPolicy, |         _accessPolicy = accessPolicy, | ||||||
|         super._(); |         super._(); | ||||||
| @@ -657,25 +657,18 @@ class _$SnRealmImpl extends _SnRealm { | |||||||
|       _$$SnRealmImplFromJson(json); |       _$$SnRealmImplFromJson(json); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   @HiveField(0) |  | ||||||
|   final int id; |   final int id; | ||||||
|   @override |   @override | ||||||
|   @HiveField(1) |  | ||||||
|   final DateTime createdAt; |   final DateTime createdAt; | ||||||
|   @override |   @override | ||||||
|   @HiveField(2) |  | ||||||
|   final DateTime updatedAt; |   final DateTime updatedAt; | ||||||
|   @override |   @override | ||||||
|   @HiveField(3) |  | ||||||
|   final DateTime? deletedAt; |   final DateTime? deletedAt; | ||||||
|   @override |   @override | ||||||
|   @HiveField(4) |  | ||||||
|   final String alias; |   final String alias; | ||||||
|   @override |   @override | ||||||
|   @HiveField(5) |  | ||||||
|   final String name; |   final String name; | ||||||
|   @override |   @override | ||||||
|   @HiveField(6) |  | ||||||
|   final String description; |   final String description; | ||||||
|   final List<SnRealmMember>? _members; |   final List<SnRealmMember>? _members; | ||||||
|   @override |   @override | ||||||
| @@ -688,14 +681,11 @@ class _$SnRealmImpl extends _SnRealm { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   @HiveField(7) |  | ||||||
|   final String? avatar; |   final String? avatar; | ||||||
|   @override |   @override | ||||||
|   @HiveField(8) |  | ||||||
|   final String? banner; |   final String? banner; | ||||||
|   final Map<String, dynamic>? _accessPolicy; |   final Map<String, dynamic>? _accessPolicy; | ||||||
|   @override |   @override | ||||||
|   @HiveField(9) |  | ||||||
|   Map<String, dynamic>? get accessPolicy { |   Map<String, dynamic>? get accessPolicy { | ||||||
|     final value = _accessPolicy; |     final value = _accessPolicy; | ||||||
|     if (value == null) return null; |     if (value == null) return null; | ||||||
| @@ -705,18 +695,18 @@ class _$SnRealmImpl extends _SnRealm { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   @HiveField(10) |  | ||||||
|   final int accountId; |   final int accountId; | ||||||
|   @override |   @override | ||||||
|   @HiveField(11) |  | ||||||
|   final bool isPublic; |   final bool isPublic; | ||||||
|   @override |   @override | ||||||
|   @HiveField(12) |  | ||||||
|   final bool isCommunity; |   final bool isCommunity; | ||||||
|  |   @override | ||||||
|  |   @JsonKey() | ||||||
|  |   final int popularity; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   String toString() { |   String toString() { | ||||||
|     return 'SnRealm(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, alias: $alias, name: $name, description: $description, members: $members, avatar: $avatar, banner: $banner, accessPolicy: $accessPolicy, accountId: $accountId, isPublic: $isPublic, isCommunity: $isCommunity)'; |     return 'SnRealm(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, alias: $alias, name: $name, description: $description, members: $members, avatar: $avatar, banner: $banner, accessPolicy: $accessPolicy, accountId: $accountId, isPublic: $isPublic, isCommunity: $isCommunity, popularity: $popularity)'; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
| @@ -745,7 +735,9 @@ class _$SnRealmImpl extends _SnRealm { | |||||||
|             (identical(other.isPublic, isPublic) || |             (identical(other.isPublic, isPublic) || | ||||||
|                 other.isPublic == isPublic) && |                 other.isPublic == isPublic) && | ||||||
|             (identical(other.isCommunity, isCommunity) || |             (identical(other.isCommunity, isCommunity) || | ||||||
|                 other.isCommunity == isCommunity)); |                 other.isCommunity == isCommunity) && | ||||||
|  |             (identical(other.popularity, popularity) || | ||||||
|  |                 other.popularity == popularity)); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @JsonKey(includeFromJson: false, includeToJson: false) |   @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @@ -765,7 +757,8 @@ class _$SnRealmImpl extends _SnRealm { | |||||||
|       const DeepCollectionEquality().hash(_accessPolicy), |       const DeepCollectionEquality().hash(_accessPolicy), | ||||||
|       accountId, |       accountId, | ||||||
|       isPublic, |       isPublic, | ||||||
|       isCommunity); |       isCommunity, | ||||||
|  |       popularity); | ||||||
|  |  | ||||||
|   /// Create a copy of SnRealm |   /// Create a copy of SnRealm | ||||||
|   /// with the given fields replaced by the non-null parameter values. |   /// with the given fields replaced by the non-null parameter values. | ||||||
| @@ -785,65 +778,55 @@ class _$SnRealmImpl extends _SnRealm { | |||||||
|  |  | ||||||
| abstract class _SnRealm extends SnRealm { | abstract class _SnRealm extends SnRealm { | ||||||
|   const factory _SnRealm( |   const factory _SnRealm( | ||||||
|       {@HiveField(0) required final int id, |       {required final int id, | ||||||
|       @HiveField(1) required final DateTime createdAt, |       required final DateTime createdAt, | ||||||
|       @HiveField(2) required final DateTime updatedAt, |       required final DateTime updatedAt, | ||||||
|       @HiveField(3) required final DateTime? deletedAt, |       required final DateTime? deletedAt, | ||||||
|       @HiveField(4) required final String alias, |       required final String alias, | ||||||
|       @HiveField(5) required final String name, |       required final String name, | ||||||
|       @HiveField(6) required final String description, |       required final String description, | ||||||
|       final List<SnRealmMember>? members, |       final List<SnRealmMember>? members, | ||||||
|       @HiveField(7) required final String? avatar, |       required final String? avatar, | ||||||
|       @HiveField(8) required final String? banner, |       required final String? banner, | ||||||
|       @HiveField(9) required final Map<String, dynamic>? accessPolicy, |       required final Map<String, dynamic>? accessPolicy, | ||||||
|       @HiveField(10) required final int accountId, |       required final int accountId, | ||||||
|       @HiveField(11) required final bool isPublic, |       required final bool isPublic, | ||||||
|       @HiveField(12) required final bool isCommunity}) = _$SnRealmImpl; |       required final bool isCommunity, | ||||||
|  |       final int popularity}) = _$SnRealmImpl; | ||||||
|   const _SnRealm._() : super._(); |   const _SnRealm._() : super._(); | ||||||
|  |  | ||||||
|   factory _SnRealm.fromJson(Map<String, dynamic> json) = _$SnRealmImpl.fromJson; |   factory _SnRealm.fromJson(Map<String, dynamic> json) = _$SnRealmImpl.fromJson; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   @HiveField(0) |  | ||||||
|   int get id; |   int get id; | ||||||
|   @override |   @override | ||||||
|   @HiveField(1) |  | ||||||
|   DateTime get createdAt; |   DateTime get createdAt; | ||||||
|   @override |   @override | ||||||
|   @HiveField(2) |  | ||||||
|   DateTime get updatedAt; |   DateTime get updatedAt; | ||||||
|   @override |   @override | ||||||
|   @HiveField(3) |  | ||||||
|   DateTime? get deletedAt; |   DateTime? get deletedAt; | ||||||
|   @override |   @override | ||||||
|   @HiveField(4) |  | ||||||
|   String get alias; |   String get alias; | ||||||
|   @override |   @override | ||||||
|   @HiveField(5) |  | ||||||
|   String get name; |   String get name; | ||||||
|   @override |   @override | ||||||
|   @HiveField(6) |  | ||||||
|   String get description; |   String get description; | ||||||
|   @override |   @override | ||||||
|   List<SnRealmMember>? get members; |   List<SnRealmMember>? get members; | ||||||
|   @override |   @override | ||||||
|   @HiveField(7) |  | ||||||
|   String? get avatar; |   String? get avatar; | ||||||
|   @override |   @override | ||||||
|   @HiveField(8) |  | ||||||
|   String? get banner; |   String? get banner; | ||||||
|   @override |   @override | ||||||
|   @HiveField(9) |  | ||||||
|   Map<String, dynamic>? get accessPolicy; |   Map<String, dynamic>? get accessPolicy; | ||||||
|   @override |   @override | ||||||
|   @HiveField(10) |  | ||||||
|   int get accountId; |   int get accountId; | ||||||
|   @override |   @override | ||||||
|   @HiveField(11) |  | ||||||
|   bool get isPublic; |   bool get isPublic; | ||||||
|   @override |   @override | ||||||
|   @HiveField(12) |  | ||||||
|   bool get isCommunity; |   bool get isCommunity; | ||||||
|  |   @override | ||||||
|  |   int get popularity; | ||||||
|  |  | ||||||
|   /// Create a copy of SnRealm |   /// Create a copy of SnRealm | ||||||
|   /// with the given fields replaced by the non-null parameter values. |   /// with the given fields replaced by the non-null parameter values. | ||||||
|   | |||||||
| @@ -2,80 +2,6 @@ | |||||||
|  |  | ||||||
| part of 'realm.dart'; | part of 'realm.dart'; | ||||||
|  |  | ||||||
| // ************************************************************************** |  | ||||||
| // TypeAdapterGenerator |  | ||||||
| // ************************************************************************** |  | ||||||
|  |  | ||||||
| class SnRealmImplAdapter extends TypeAdapter<_$SnRealmImpl> { |  | ||||||
|   @override |  | ||||||
|   final int typeId = 1; |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   _$SnRealmImpl read(BinaryReader reader) { |  | ||||||
|     final numOfFields = reader.readByte(); |  | ||||||
|     final fields = <int, dynamic>{ |  | ||||||
|       for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), |  | ||||||
|     }; |  | ||||||
|     return _$SnRealmImpl( |  | ||||||
|       id: fields[0] as int, |  | ||||||
|       createdAt: fields[1] as DateTime, |  | ||||||
|       updatedAt: fields[2] as DateTime, |  | ||||||
|       deletedAt: fields[3] as DateTime?, |  | ||||||
|       alias: fields[4] as String, |  | ||||||
|       name: fields[5] as String, |  | ||||||
|       description: fields[6] as String, |  | ||||||
|       avatar: fields[7] as String?, |  | ||||||
|       banner: fields[8] as String?, |  | ||||||
|       accessPolicy: (fields[9] as Map?)?.cast<String, dynamic>(), |  | ||||||
|       accountId: fields[10] as int, |  | ||||||
|       isPublic: fields[11] as bool, |  | ||||||
|       isCommunity: fields[12] as bool, |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   void write(BinaryWriter writer, _$SnRealmImpl obj) { |  | ||||||
|     writer |  | ||||||
|       ..writeByte(13) |  | ||||||
|       ..writeByte(0) |  | ||||||
|       ..write(obj.id) |  | ||||||
|       ..writeByte(1) |  | ||||||
|       ..write(obj.createdAt) |  | ||||||
|       ..writeByte(2) |  | ||||||
|       ..write(obj.updatedAt) |  | ||||||
|       ..writeByte(3) |  | ||||||
|       ..write(obj.deletedAt) |  | ||||||
|       ..writeByte(4) |  | ||||||
|       ..write(obj.alias) |  | ||||||
|       ..writeByte(5) |  | ||||||
|       ..write(obj.name) |  | ||||||
|       ..writeByte(6) |  | ||||||
|       ..write(obj.description) |  | ||||||
|       ..writeByte(7) |  | ||||||
|       ..write(obj.avatar) |  | ||||||
|       ..writeByte(8) |  | ||||||
|       ..write(obj.banner) |  | ||||||
|       ..writeByte(10) |  | ||||||
|       ..write(obj.accountId) |  | ||||||
|       ..writeByte(11) |  | ||||||
|       ..write(obj.isPublic) |  | ||||||
|       ..writeByte(12) |  | ||||||
|       ..write(obj.isCommunity) |  | ||||||
|       ..writeByte(9) |  | ||||||
|       ..write(obj.accessPolicy); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   int get hashCode => typeId.hashCode; |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   bool operator ==(Object other) => |  | ||||||
|       identical(this, other) || |  | ||||||
|       other is SnRealmImplAdapter && |  | ||||||
|           runtimeType == other.runtimeType && |  | ||||||
|           typeId == other.typeId; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ************************************************************************** | // ************************************************************************** | ||||||
| // JsonSerializableGenerator | // JsonSerializableGenerator | ||||||
| // ************************************************************************** | // ************************************************************************** | ||||||
| @@ -128,6 +54,7 @@ _$SnRealmImpl _$$SnRealmImplFromJson(Map<String, dynamic> json) => | |||||||
|       accountId: (json['account_id'] as num).toInt(), |       accountId: (json['account_id'] as num).toInt(), | ||||||
|       isPublic: json['is_public'] as bool, |       isPublic: json['is_public'] as bool, | ||||||
|       isCommunity: json['is_community'] as bool, |       isCommunity: json['is_community'] as bool, | ||||||
|  |       popularity: (json['popularity'] as num?)?.toInt() ?? 0, | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
| Map<String, dynamic> _$$SnRealmImplToJson(_$SnRealmImpl instance) => | Map<String, dynamic> _$$SnRealmImplToJson(_$SnRealmImpl instance) => | ||||||
| @@ -146,4 +73,5 @@ Map<String, dynamic> _$$SnRealmImplToJson(_$SnRealmImpl instance) => | |||||||
|       'account_id': instance.accountId, |       'account_id': instance.accountId, | ||||||
|       'is_public': instance.isPublic, |       'is_public': instance.isPublic, | ||||||
|       'is_community': instance.isCommunity, |       'is_community': instance.isCommunity, | ||||||
|  |       'popularity': instance.popularity, | ||||||
|     }; |     }; | ||||||
|   | |||||||
| @@ -97,6 +97,13 @@ class AboutScreen extends StatelessWidget { | |||||||
|                       launchUrlString('https://status.solsynth.dev'); |                       launchUrlString('https://status.solsynth.dev'); | ||||||
|                     }, |                     }, | ||||||
|                   ), |                   ), | ||||||
|  |                   TextButton( | ||||||
|  |                     style: denseButtonStyle, | ||||||
|  |                     child: Text('projectDetail').tr(), | ||||||
|  |                     onPressed: () { | ||||||
|  |                       launchUrlString('https://solsynth.dev/products/solar-network'); | ||||||
|  |                     }, | ||||||
|  |                   ), | ||||||
|                 ], |                 ], | ||||||
|               ), |               ), | ||||||
|             ).center(), |             ).center(), | ||||||
| @@ -108,6 +115,12 @@ class AboutScreen extends StatelessWidget { | |||||||
|                 fontSize: 12, |                 fontSize: 12, | ||||||
|               ), |               ), | ||||||
|             ), |             ), | ||||||
|  |             InkWell( | ||||||
|  |               child: Text('GitHub', style: TextStyle(fontSize: 12)), | ||||||
|  |               onTap: () { | ||||||
|  |                 launchUrlString('https://github.com/Solsynth/HyperNet.Surface'); | ||||||
|  |               }, | ||||||
|  |             ) | ||||||
|           ], |           ], | ||||||
|         ), |         ), | ||||||
|       ), |       ), | ||||||
|   | |||||||
| @@ -109,11 +109,13 @@ class ChatMessage extends StatelessWidget { | |||||||
|                       onTap: () { |                       onTap: () { | ||||||
|                         if (user == null) return; |                         if (user == null) return; | ||||||
|                         showPopover( |                         showPopover( | ||||||
|                           backgroundColor: Theme.of(context).colorScheme.surface, |                           backgroundColor: | ||||||
|  |                               Theme.of(context).colorScheme.surface, | ||||||
|                           context: context, |                           context: context, | ||||||
|                           transition: PopoverTransition.other, |                           transition: PopoverTransition.other, | ||||||
|                           bodyBuilder: (context) => SizedBox( |                           bodyBuilder: (context) => SizedBox( | ||||||
|                             width: math.min(400, MediaQuery.of(context).size.width - 10), |                             width: math.min( | ||||||
|  |                                 400, MediaQuery.of(context).size.width - 10), | ||||||
|                             child: AccountPopoverCard( |                             child: AccountPopoverCard( | ||||||
|                               data: user, |                               data: user, | ||||||
|                             ), |                             ), | ||||||
| @@ -144,11 +146,14 @@ class ChatMessage extends StatelessWidget { | |||||||
|                                     radius: 12, |                                     radius: 12, | ||||||
|                                   ).padding(right: 8), |                                   ).padding(right: 8), | ||||||
|                                 Text( |                                 Text( | ||||||
|                                   (data.sender.nick?.isNotEmpty ?? false) ? data.sender.nick! : user?.nick ?? 'unknown', |                                   (data.sender.nick?.isNotEmpty ?? false) | ||||||
|  |                                       ? data.sender.nick! | ||||||
|  |                                       : user?.nick ?? 'unknown', | ||||||
|                                 ).bold(), |                                 ).bold(), | ||||||
|                                 const Gap(8), |                                 const Gap(8), | ||||||
|                                 Text( |                                 Text( | ||||||
|                                   dateFormatter.format(data.createdAt.toLocal()), |                                   dateFormatter | ||||||
|  |                                       .format(data.createdAt.toLocal()), | ||||||
|                                 ).fontSize(13), |                                 ).fontSize(13), | ||||||
|                               ], |                               ], | ||||||
|                             ).height(21), |                             ).height(21), | ||||||
| @@ -159,7 +164,8 @@ class ChatMessage extends StatelessWidget { | |||||||
|                                 maxWidth: 480, |                                 maxWidth: 480, | ||||||
|                               ), |                               ), | ||||||
|                               decoration: BoxDecoration( |                               decoration: BoxDecoration( | ||||||
|                                 borderRadius: const BorderRadius.all(Radius.circular(8)), |                                 borderRadius: | ||||||
|  |                                     const BorderRadius.all(Radius.circular(8)), | ||||||
|                                 border: Border.all( |                                 border: Border.all( | ||||||
|                                   color: Theme.of(context).dividerColor, |                                   color: Theme.of(context).dividerColor, | ||||||
|                                   width: 1, |                                   width: 1, | ||||||
| @@ -207,9 +213,12 @@ class ChatMessage extends StatelessWidget { | |||||||
|                 maxHeight: 560, |                 maxHeight: 560, | ||||||
|                 maxWidth: 480, |                 maxWidth: 480, | ||||||
|                 minWidth: 480, |                 minWidth: 480, | ||||||
|                 padding: padding.copyWith(top: 8), |                 padding: padding.copyWith(top: 8, left: 48 + padding.left), | ||||||
|               ), |               ), | ||||||
|             if (!hasMerged && !isCompact) const Gap(12) else if (!isCompact) const Gap(8), |             if (!hasMerged && !isCompact) | ||||||
|  |               const Gap(12) | ||||||
|  |             else if (!isCompact) | ||||||
|  |               const Gap(8), | ||||||
|           ], |           ], | ||||||
|         ), |         ), | ||||||
|       ), |       ), | ||||||
| @@ -223,7 +232,8 @@ class _ChatMessageText extends StatelessWidget { | |||||||
|   final Function(SnChatMessage)? onEdit; |   final Function(SnChatMessage)? onEdit; | ||||||
|   final Function(SnChatMessage)? onDelete; |   final Function(SnChatMessage)? onDelete; | ||||||
|  |  | ||||||
|   const _ChatMessageText({required this.data, this.onReply, this.onEdit, this.onDelete}); |   const _ChatMessageText( | ||||||
|  |       {required this.data, this.onReply, this.onEdit, this.onDelete}); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
| @@ -237,7 +247,8 @@ class _ChatMessageText extends StatelessWidget { | |||||||
|         children: [ |         children: [ | ||||||
|           SelectionArea( |           SelectionArea( | ||||||
|             contextMenuBuilder: (context, editableTextState) { |             contextMenuBuilder: (context, editableTextState) { | ||||||
|               final List<ContextMenuButtonItem> items = editableTextState.contextMenuButtonItems; |               final List<ContextMenuButtonItem> items = | ||||||
|  |                   editableTextState.contextMenuButtonItems; | ||||||
|  |  | ||||||
|               if (onReply != null) { |               if (onReply != null) { | ||||||
|                 items.insert( |                 items.insert( | ||||||
| @@ -286,6 +297,8 @@ class _ChatMessageText extends StatelessWidget { | |||||||
|               child: MarkdownTextContent( |               child: MarkdownTextContent( | ||||||
|                 content: data.body['text'], |                 content: data.body['text'], | ||||||
|                 isAutoWarp: true, |                 isAutoWarp: true, | ||||||
|  |                 isEnlargeSticker: | ||||||
|  |                     RegExp(r"^:([-\w]+):$").hasMatch(data.body['text'] ?? ''), | ||||||
|               ), |               ), | ||||||
|             ), |             ), | ||||||
|           ), |           ), | ||||||
|   | |||||||
| @@ -44,7 +44,9 @@ class MarkdownTextContent extends StatelessWidget { | |||||||
|         Theme.of(context), |         Theme.of(context), | ||||||
|       ).copyWith( |       ).copyWith( | ||||||
|         textScaler: textScaler, |         textScaler: textScaler, | ||||||
|         p: textColor != null ? Theme.of(context).textTheme.bodyMedium!.copyWith(color: textColor) : null, |         p: textColor != null | ||||||
|  |             ? Theme.of(context).textTheme.bodyMedium!.copyWith(color: textColor) | ||||||
|  |             : null, | ||||||
|         blockquote: TextStyle( |         blockquote: TextStyle( | ||||||
|           color: Theme.of(context).colorScheme.onSurfaceVariant, |           color: Theme.of(context).colorScheme.onSurfaceVariant, | ||||||
|         ), |         ), | ||||||
| @@ -115,7 +117,7 @@ class MarkdownTextContent extends StatelessWidget { | |||||||
|               final alias = segments[1]; |               final alias = segments[1]; | ||||||
|               final st = context.read<SnStickerProvider>(); |               final st = context.read<SnStickerProvider>(); | ||||||
|               final sn = context.read<SnNetworkProvider>(); |               final sn = context.read<SnNetworkProvider>(); | ||||||
|               final double size = isEnlargeSticker ? 128 : 32; |               final double size = isEnlargeSticker ? 96 : 32; | ||||||
|               return Container( |               return Container( | ||||||
|                 width: size, |                 width: size, | ||||||
|                 height: size, |                 height: size, | ||||||
| @@ -131,7 +133,8 @@ class MarkdownTextContent extends StatelessWidget { | |||||||
|                       if (snapshot.hasData) { |                       if (snapshot.hasData) { | ||||||
|                         return GestureDetector( |                         return GestureDetector( | ||||||
|                             child: UniversalImage( |                             child: UniversalImage( | ||||||
|                               sn.getAttachmentUrl(snapshot.data!.attachment.rid), |                               sn.getAttachmentUrl( | ||||||
|  |                                   snapshot.data!.attachment.rid), | ||||||
|                               fit: BoxFit.contain, |                               fit: BoxFit.contain, | ||||||
|                               width: size, |                               width: size, | ||||||
|                               height: size, |                               height: size, | ||||||
| @@ -177,7 +180,9 @@ class MarkdownTextContent extends StatelessWidget { | |||||||
|                       borderRadius: const BorderRadius.all(Radius.circular(8)), |                       borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||||
|                       child: AspectRatio( |                       child: AspectRatio( | ||||||
|                         aspectRatio: attachment.metadata['ratio'] ?? |                         aspectRatio: attachment.metadata['ratio'] ?? | ||||||
|                             switch (attachment.mimetype.split('/').firstOrNull) { |                             switch (attachment.mimetype | ||||||
|  |                                     .split('/') | ||||||
|  |                                     .firstOrNull) { | ||||||
|                               'audio' => 16 / 9, |                               'audio' => 16 / 9, | ||||||
|                               'video' => 16 / 9, |                               'video' => 16 / 9, | ||||||
|                               _ => 1, |                               _ => 1, | ||||||
|   | |||||||
| @@ -31,6 +31,7 @@ class AppScaffold extends StatelessWidget { | |||||||
|   final AppBar? appBar; |   final AppBar? appBar; | ||||||
|   final DrawerCallback? onDrawerChanged; |   final DrawerCallback? onDrawerChanged; | ||||||
|   final DrawerCallback? onEndDrawerChanged; |   final DrawerCallback? onEndDrawerChanged; | ||||||
|  |   final bool noBackground; | ||||||
|  |  | ||||||
|   const AppScaffold({ |   const AppScaffold({ | ||||||
|     super.key, |     super.key, | ||||||
| @@ -45,6 +46,7 @@ class AppScaffold extends StatelessWidget { | |||||||
|     this.endDrawer, |     this.endDrawer, | ||||||
|     this.onDrawerChanged, |     this.onDrawerChanged, | ||||||
|     this.onEndDrawerChanged, |     this.onEndDrawerChanged, | ||||||
|  |     this.noBackground = false, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
| @@ -52,20 +54,23 @@ class AppScaffold extends StatelessWidget { | |||||||
|     final appBarHeight = appBar?.preferredSize.height ?? 0; |     final appBarHeight = appBar?.preferredSize.height ?? 0; | ||||||
|     final safeTop = MediaQuery.of(context).padding.top; |     final safeTop = MediaQuery.of(context).padding.top; | ||||||
|  |  | ||||||
|  |     final content = Column( | ||||||
|  |       children: [ | ||||||
|  |         IgnorePointer( | ||||||
|  |           child: SizedBox(height: appBar != null ? appBarHeight + safeTop : 0), | ||||||
|  |         ), | ||||||
|  |         if (body != null) Expanded(child: body!), | ||||||
|  |       ], | ||||||
|  |     ); | ||||||
|  |  | ||||||
|     return Scaffold( |     return Scaffold( | ||||||
|       extendBody: true, |       extendBody: true, | ||||||
|       extendBodyBehindAppBar: true, |       extendBodyBehindAppBar: true, | ||||||
|       backgroundColor: Theme.of(context).scaffoldBackgroundColor, |       backgroundColor: Theme.of(context).scaffoldBackgroundColor, | ||||||
|       body: SizedBox.expand( |       body: SizedBox.expand( | ||||||
|         child: AppBackground( |         child: noBackground | ||||||
|           isRoot: true, |             ? content | ||||||
|           child: Column( |             : AppBackground(isRoot: true, child: content), | ||||||
|             children: [ |  | ||||||
|               IgnorePointer(child: SizedBox(height: appBar != null ? appBarHeight + safeTop : 0)), |  | ||||||
|               if (body != null) Expanded(child: body!), |  | ||||||
|             ], |  | ||||||
|           ), |  | ||||||
|         ), |  | ||||||
|       ), |       ), | ||||||
|       appBar: appBar, |       appBar: appBar, | ||||||
|       bottomNavigationBar: bottomNavigationBar, |       bottomNavigationBar: bottomNavigationBar, | ||||||
| @@ -107,11 +112,19 @@ class AppRootScaffold extends StatelessWidget { | |||||||
|     final isCollapseDrawer = cfg.drawerIsCollapsed; |     final isCollapseDrawer = cfg.drawerIsCollapsed; | ||||||
|     final isExpandedDrawer = cfg.drawerIsExpanded; |     final isExpandedDrawer = cfg.drawerIsExpanded; | ||||||
|  |  | ||||||
|     final routeName = GoRouter.of(context).routerDelegate.currentConfiguration.last.route.name; |     final routeName = GoRouter.of(context) | ||||||
|     final isShowBottomNavigation = NavigationProvider.kShowBottomNavScreen.contains(routeName) |         .routerDelegate | ||||||
|  |         .currentConfiguration | ||||||
|  |         .last | ||||||
|  |         .route | ||||||
|  |         .name; | ||||||
|  |     final isShowBottomNavigation = | ||||||
|  |         NavigationProvider.kShowBottomNavScreen.contains(routeName) | ||||||
|             ? ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE) |             ? ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE) | ||||||
|             : false; |             : false; | ||||||
|     final isPopable = !NavigationProvider.kAllDestination.map((ele) => ele.screen).contains(routeName); |     final isPopable = !NavigationProvider.kAllDestination | ||||||
|  |         .map((ele) => ele.screen) | ||||||
|  |         .contains(routeName); | ||||||
|  |  | ||||||
|     final innerWidget = isCollapseDrawer |     final innerWidget = isCollapseDrawer | ||||||
|         ? body |         ? body | ||||||
| @@ -126,7 +139,9 @@ class AppRootScaffold extends StatelessWidget { | |||||||
|                     ), |                     ), | ||||||
|                   ), |                   ), | ||||||
|                 ), |                 ), | ||||||
|                 child: isExpandedDrawer ? AppNavigationDrawer(elevation: 0) : AppRailNavigation(), |                 child: isExpandedDrawer | ||||||
|  |                     ? AppNavigationDrawer(elevation: 0) | ||||||
|  |                     : AppRailNavigation(), | ||||||
|               ), |               ), | ||||||
|               Expanded(child: body), |               Expanded(child: body), | ||||||
|             ], |             ], | ||||||
| @@ -150,7 +165,8 @@ class AppRootScaffold extends StatelessWidget { | |||||||
|         children: [ |         children: [ | ||||||
|           Column( |           Column( | ||||||
|             children: [ |             children: [ | ||||||
|               if (!kIsWeb && (Platform.isWindows || Platform.isLinux || Platform.isMacOS)) |               if (!kIsWeb && | ||||||
|  |                   (Platform.isWindows || Platform.isLinux || Platform.isMacOS)) | ||||||
|                 WindowTitleBarBox( |                 WindowTitleBarBox( | ||||||
|                   child: Container( |                   child: Container( | ||||||
|                     decoration: BoxDecoration( |                     decoration: BoxDecoration( | ||||||
| @@ -164,12 +180,19 @@ class AppRootScaffold extends StatelessWidget { | |||||||
|                     child: MoveWindow( |                     child: MoveWindow( | ||||||
|                       child: Row( |                       child: Row( | ||||||
|                         crossAxisAlignment: CrossAxisAlignment.center, |                         crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|                         mainAxisAlignment: Platform.isMacOS ? MainAxisAlignment.center : MainAxisAlignment.start, |                         mainAxisAlignment: Platform.isMacOS | ||||||
|  |                             ? MainAxisAlignment.center | ||||||
|  |                             : MainAxisAlignment.start, | ||||||
|                         children: [ |                         children: [ | ||||||
|                           Expanded( |                           Expanded( | ||||||
|                             child: Text( |                             child: Text( | ||||||
|                               'Solar Network', |                               'Solar Network', | ||||||
|                               style: GoogleFonts.spaceGrotesk(), |                               style: GoogleFonts.spaceGrotesk(), | ||||||
|  |                               textAlign: !kIsWeb | ||||||
|  |                                   ? Platform.isMacOS | ||||||
|  |                                       ? TextAlign.center | ||||||
|  |                                       : null | ||||||
|  |                                   : null, | ||||||
|                             ).padding(horizontal: 12, vertical: 5), |                             ).padding(horizontal: 12, vertical: 5), | ||||||
|                           ), |                           ), | ||||||
|                           if (!Platform.isMacOS) |                           if (!Platform.isMacOS) | ||||||
| @@ -179,9 +202,12 @@ class AppRootScaffold extends StatelessWidget { | |||||||
|                                 Expanded(child: MoveWindow()), |                                 Expanded(child: MoveWindow()), | ||||||
|                                 Row( |                                 Row( | ||||||
|                                   children: [ |                                   children: [ | ||||||
|                                     MinimizeWindowButton(colors: windowButtonColor), |                                     MinimizeWindowButton( | ||||||
|                                     MaximizeWindowButton(colors: windowButtonColor), |                                         colors: windowButtonColor), | ||||||
|                                     CloseWindowButton(colors: windowButtonColor), |                                     MaximizeWindowButton( | ||||||
|  |                                         colors: windowButtonColor), | ||||||
|  |                                     CloseWindowButton( | ||||||
|  |                                         colors: windowButtonColor), | ||||||
|                                   ], |                                   ], | ||||||
|                                 ), |                                 ), | ||||||
|                               ], |                               ], | ||||||
| @@ -194,16 +220,28 @@ class AppRootScaffold extends StatelessWidget { | |||||||
|               Expanded(child: innerWidget), |               Expanded(child: innerWidget), | ||||||
|             ], |             ], | ||||||
|           ), |           ), | ||||||
|           Positioned(top: safeTop > 0 ? safeTop : 16, right: 8, child: NotifyIndicator()), |           Positioned( | ||||||
|  |               top: safeTop > 0 ? safeTop : 16, | ||||||
|  |               right: 8, | ||||||
|  |               child: NotifyIndicator()), | ||||||
|           if (ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE)) |           if (ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE)) | ||||||
|             Positioned(bottom: safeBottom > 0 ? safeBottom : 16, left: 0, right: 0, child: ConnectionIndicator()) |             Positioned( | ||||||
|  |                 bottom: safeBottom > 0 ? safeBottom : 16, | ||||||
|  |                 left: 0, | ||||||
|  |                 right: 0, | ||||||
|  |                 child: ConnectionIndicator()) | ||||||
|           else |           else | ||||||
|             Positioned(top: safeTop > 0 ? safeTop : 16, left: 0, right: 0, child: ConnectionIndicator()), |             Positioned( | ||||||
|  |                 top: safeTop > 0 ? safeTop : 16, | ||||||
|  |                 left: 0, | ||||||
|  |                 right: 0, | ||||||
|  |                 child: ConnectionIndicator()), | ||||||
|         ], |         ], | ||||||
|       ), |       ), | ||||||
|       drawer: !isExpandedDrawer ? AppNavigationDrawer() : null, |       drawer: !isExpandedDrawer ? AppNavigationDrawer() : null, | ||||||
|       drawerEdgeDragWidth: isPopable ? 0 : null, |       drawerEdgeDragWidth: isPopable ? 0 : null, | ||||||
|       bottomNavigationBar: isShowBottomNavigation ? AppBottomNavigationBar() : null, |       bottomNavigationBar: | ||||||
|  |           isShowBottomNavigation ? AppBottomNavigationBar() : null, | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -23,7 +23,8 @@ class NotifyIndicator extends StatefulWidget { | |||||||
|   State<NotifyIndicator> createState() => _NotifyIndicatorState(); |   State<NotifyIndicator> createState() => _NotifyIndicatorState(); | ||||||
| } | } | ||||||
|  |  | ||||||
| class _NotifyIndicatorState extends State<NotifyIndicator> with SingleTickerProviderStateMixin { | class _NotifyIndicatorState extends State<NotifyIndicator> | ||||||
|  |     with SingleTickerProviderStateMixin { | ||||||
|   late final AnimationController _animationController = AnimationController( |   late final AnimationController _animationController = AnimationController( | ||||||
|     vsync: this, |     vsync: this, | ||||||
|     duration: const Duration(milliseconds: 300), |     duration: const Duration(milliseconds: 300), | ||||||
| @@ -101,7 +102,8 @@ class _NotifyIndicatorState extends State<NotifyIndicator> with SingleTickerProv | |||||||
|                   padding: const EdgeInsets.symmetric(vertical: 16), |                   padding: const EdgeInsets.symmetric(vertical: 16), | ||||||
|                   width: double.infinity, |                   width: double.infinity, | ||||||
|                   constraints: BoxConstraints( |                   constraints: BoxConstraints( | ||||||
|                     maxWidth: isMobile ? MediaQuery.of(context).size.width - 16 : 360, |                     maxWidth: | ||||||
|  |                         isMobile ? MediaQuery.of(context).size.width - 16 : 360, | ||||||
|                   ), |                   ), | ||||||
|                   child: Material( |                   child: Material( | ||||||
|                     elevation: 2, |                     elevation: 2, | ||||||
| @@ -118,7 +120,8 @@ class _NotifyIndicatorState extends State<NotifyIndicator> with SingleTickerProv | |||||||
|                             ), |                             ), | ||||||
|                           ) |                           ) | ||||||
|                         else |                         else | ||||||
|                           Icon(kNotificationTopicIcons[current?.topic] ?? Symbols.notifications), |                           Icon(kNotificationTopicIcons[current?.topic] ?? | ||||||
|  |                               Symbols.notifications), | ||||||
|                         const Gap(16), |                         const Gap(16), | ||||||
|                         Expanded( |                         Expanded( | ||||||
|                           child: Column( |                           child: Column( | ||||||
| @@ -126,14 +129,20 @@ class _NotifyIndicatorState extends State<NotifyIndicator> with SingleTickerProv | |||||||
|                             children: [ |                             children: [ | ||||||
|                               Text( |                               Text( | ||||||
|                                 current?.title ?? 'Notification', |                                 current?.title ?? 'Notification', | ||||||
|                                 style: Theme.of(context).textTheme.bodyMedium!.copyWith( |                                 style: Theme.of(context) | ||||||
|  |                                     .textTheme | ||||||
|  |                                     .bodyMedium! | ||||||
|  |                                     .copyWith( | ||||||
|                                       fontWeight: FontWeight.bold, |                                       fontWeight: FontWeight.bold, | ||||||
|                                     ), |                                     ), | ||||||
|                               ), |                               ), | ||||||
|                               if (current?.subtitle?.isNotEmpty ?? false) |                               if (current?.subtitle?.isNotEmpty ?? false) | ||||||
|                                 Text( |                                 Text( | ||||||
|                                   current!.subtitle!, |                                   current!.subtitle!, | ||||||
|                                   style: Theme.of(context).textTheme.bodyMedium!.copyWith( |                                   style: Theme.of(context) | ||||||
|  |                                       .textTheme | ||||||
|  |                                       .bodyMedium! | ||||||
|  |                                       .copyWith( | ||||||
|                                         fontWeight: FontWeight.bold, |                                         fontWeight: FontWeight.bold, | ||||||
|                                       ), |                                       ), | ||||||
|                                 ), |                                 ), | ||||||
| @@ -148,18 +157,25 @@ class _NotifyIndicatorState extends State<NotifyIndicator> with SingleTickerProv | |||||||
|                         Column( |                         Column( | ||||||
|                           crossAxisAlignment: CrossAxisAlignment.end, |                           crossAxisAlignment: CrossAxisAlignment.end, | ||||||
|                           children: [ |                           children: [ | ||||||
|                             Text(DateFormat('HH:mm').format(current?.createdAt.toLocal() ?? DateTime.now())) |                             Text( | ||||||
|                                 .fontSize(12) |                               DateFormat('HH:mm').format( | ||||||
|                                 .padding(right: 2), |                                   (current?.createdAt ?? DateTime.now()) | ||||||
|  |                                               .millisecondsSinceEpoch > | ||||||
|  |                                           0 | ||||||
|  |                                       ? (current?.createdAt ?? DateTime.now()) | ||||||
|  |                                       : DateTime.now()), | ||||||
|  |                             ).fontSize(12).padding(right: 2), | ||||||
|                             const Gap(6), |                             const Gap(6), | ||||||
|                             if (current?.metadata['image'] != null) |                             if (current?.metadata['image'] != null) | ||||||
|                               SizedBox( |                               SizedBox( | ||||||
|                                 width: 40, |                                 width: 40, | ||||||
|                                 height: 40, |                                 height: 40, | ||||||
|                                 child: ClipRRect( |                                 child: ClipRRect( | ||||||
|                                   borderRadius: const BorderRadius.all(Radius.circular(8)), |                                   borderRadius: const BorderRadius.all( | ||||||
|  |                                       Radius.circular(8)), | ||||||
|                                   child: AutoResizeUniversalImage( |                                   child: AutoResizeUniversalImage( | ||||||
|                                     sn.getAttachmentUrl(current?.metadata['image']), |                                     sn.getAttachmentUrl( | ||||||
|  |                                         current?.metadata['image']), | ||||||
|                                     fit: BoxFit.cover, |                                     fit: BoxFit.cover, | ||||||
|                                   ), |                                   ), | ||||||
|                                 ), |                                 ), | ||||||
|   | |||||||
| @@ -92,7 +92,8 @@ class OpenablePostItem extends StatelessWidget { | |||||||
|         openColor: Colors.transparent, |         openColor: Colors.transparent, | ||||||
|         openElevation: 0, |         openElevation: 0, | ||||||
|         transitionType: ContainerTransitionType.fade, |         transitionType: ContainerTransitionType.fade, | ||||||
|         closedColor: Theme.of(context).colorScheme.surfaceContainerLow.withOpacity( |         closedColor: | ||||||
|  |             Theme.of(context).colorScheme.surfaceContainerLow.withOpacity( | ||||||
|                   cfg.prefs.getBool(kAppBackgroundStoreKey) == true ? 0.75 : 1, |                   cfg.prefs.getBool(kAppBackgroundStoreKey) == true ? 0.75 : 1, | ||||||
|                 ), |                 ), | ||||||
|         closedShape: const RoundedRectangleBorder( |         closedShape: const RoundedRectangleBorder( | ||||||
| @@ -135,9 +136,11 @@ class PostItem extends StatelessWidget { | |||||||
|     final box = context.findRenderObject() as RenderBox?; |     final box = context.findRenderObject() as RenderBox?; | ||||||
|     final url = 'https://solsynth.dev/posts/${data.id}'; |     final url = 'https://solsynth.dev/posts/${data.id}'; | ||||||
|     if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) { |     if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) { | ||||||
|       Share.shareUri(Uri.parse(url), sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size); |       Share.shareUri(Uri.parse(url), | ||||||
|  |           sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size); | ||||||
|     } else { |     } else { | ||||||
|       Share.share(url, sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size); |       Share.share(url, | ||||||
|  |           sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -155,7 +158,8 @@ class PostItem extends StatelessWidget { | |||||||
|             child: MultiProvider( |             child: MultiProvider( | ||||||
|               providers: [ |               providers: [ | ||||||
|                 Provider<SnNetworkProvider>(create: (_) => context.read()), |                 Provider<SnNetworkProvider>(create: (_) => context.read()), | ||||||
|                 ChangeNotifierProvider<ConfigProvider>(create: (_) => context.read()), |                 ChangeNotifierProvider<ConfigProvider>( | ||||||
|  |                     create: (_) => context.read()), | ||||||
|               ], |               ], | ||||||
|               child: ResponsiveBreakpoints.builder( |               child: ResponsiveBreakpoints.builder( | ||||||
|                 breakpoints: ResponsiveBreakpoints.of(context).breakpoints, |                 breakpoints: ResponsiveBreakpoints.of(context).breakpoints, | ||||||
| @@ -183,7 +187,8 @@ class PostItem extends StatelessWidget { | |||||||
|         sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size, |         sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size, | ||||||
|       ); |       ); | ||||||
|     } else { |     } else { | ||||||
|       await FileSaver.instance.saveFile(name: 'Solar Network Post #${data.id}.png', file: imageFile); |       await FileSaver.instance.saveFile( | ||||||
|  |           name: 'Solar Network Post #${data.id}.png', file: imageFile); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     await imageFile.delete(); |     await imageFile.delete(); | ||||||
| @@ -197,7 +202,9 @@ class PostItem extends StatelessWidget { | |||||||
|     final isAuthor = ua.isAuthorized && data.publisher.accountId == ua.user?.id; |     final isAuthor = ua.isAuthorized && data.publisher.accountId == ua.user?.id; | ||||||
|  |  | ||||||
|     // Video full view |     // Video full view | ||||||
|     if (showFullPost && data.type == 'video' && ResponsiveBreakpoints.of(context).largerThan(TABLET)) { |     if (showFullPost && | ||||||
|  |         data.type == 'video' && | ||||||
|  |         ResponsiveBreakpoints.of(context).largerThan(TABLET)) { | ||||||
|       return Row( |       return Row( | ||||||
|         crossAxisAlignment: CrossAxisAlignment.start, |         crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|         children: [ |         children: [ | ||||||
| @@ -217,7 +224,8 @@ class PostItem extends StatelessWidget { | |||||||
|                     if (onDeleted != null) {} |                     if (onDeleted != null) {} | ||||||
|                   }, |                   }, | ||||||
|                 ).padding(bottom: 8), |                 ).padding(bottom: 8), | ||||||
|                 if (data.preload?.video != null) _PostVideoPlayer(data: data).padding(bottom: 8), |                 if (data.preload?.video != null) | ||||||
|  |                   _PostVideoPlayer(data: data).padding(bottom: 8), | ||||||
|                 _PostHeadline(data: data).padding(horizontal: 4, bottom: 8), |                 _PostHeadline(data: data).padding(horizontal: 4, bottom: 8), | ||||||
|                 _PostFeaturedComment(data: data), |                 _PostFeaturedComment(data: data), | ||||||
|                 _PostBottomAction( |                 _PostBottomAction( | ||||||
| @@ -265,7 +273,8 @@ class PostItem extends StatelessWidget { | |||||||
|                 if (onDeleted != null) {} |                 if (onDeleted != null) {} | ||||||
|               }, |               }, | ||||||
|             ).padding(horizontal: 12, top: 8, bottom: 8), |             ).padding(horizontal: 12, top: 8, bottom: 8), | ||||||
|             if (data.preload?.video != null) _PostVideoPlayer(data: data).padding(horizontal: 12, bottom: 8), |             if (data.preload?.video != null) | ||||||
|  |               _PostVideoPlayer(data: data).padding(horizontal: 12, bottom: 8), | ||||||
|             Container( |             Container( | ||||||
|               width: double.infinity, |               width: double.infinity, | ||||||
|               margin: const EdgeInsets.only(bottom: 4, left: 12, right: 12), |               margin: const EdgeInsets.only(bottom: 4, left: 12, right: 12), | ||||||
| @@ -308,8 +317,13 @@ class PostItem extends StatelessWidget { | |||||||
|                 ], |                 ], | ||||||
|               ), |               ), | ||||||
|             ), |             ), | ||||||
|             Text('postArticle').tr().fontSize(13).opacity(0.75).padding(horizontal: 24, bottom: 8), |             Text('postArticle') | ||||||
|             _PostFeaturedComment(data: data, maxWidth: maxWidth).padding(horizontal: 12), |                 .tr() | ||||||
|  |                 .fontSize(13) | ||||||
|  |                 .opacity(0.75) | ||||||
|  |                 .padding(horizontal: 24, bottom: 8), | ||||||
|  |             _PostFeaturedComment(data: data, maxWidth: maxWidth) | ||||||
|  |                 .padding(horizontal: 12), | ||||||
|             _PostBottomAction( |             _PostBottomAction( | ||||||
|               data: data, |               data: data, | ||||||
|               showComments: showComments, |               showComments: showComments, | ||||||
| @@ -324,7 +338,8 @@ class PostItem extends StatelessWidget { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     final displayableAttachments = data.preload?.attachments |     final displayableAttachments = data.preload?.attachments | ||||||
|         ?.where((ele) => ele?.mediaType != SnMediaType.image || data.type != 'article') |         ?.where((ele) => | ||||||
|  |             ele?.mediaType != SnMediaType.image || data.type != 'article') | ||||||
|         .toList(); |         .toList(); | ||||||
|  |  | ||||||
|     final cfg = context.read<ConfigProvider>(); |     final cfg = context.read<ConfigProvider>(); | ||||||
| @@ -349,9 +364,13 @@ class PostItem extends StatelessWidget { | |||||||
|                   if (onDeleted != null) onDeleted!(); |                   if (onDeleted != null) onDeleted!(); | ||||||
|                 }, |                 }, | ||||||
|               ).padding(horizontal: 12, vertical: 8), |               ).padding(horizontal: 12, vertical: 8), | ||||||
|               if (data.preload?.video != null) _PostVideoPlayer(data: data).padding(horizontal: 12, bottom: 8), |               if (data.preload?.video != null) | ||||||
|               if (data.type == 'question') _PostQuestionHint(data: data).padding(horizontal: 16, bottom: 8), |                 _PostVideoPlayer(data: data).padding(horizontal: 12, bottom: 8), | ||||||
|               if (data.body['title'] != null || data.body['description'] != null) |               if (data.type == 'question') | ||||||
|  |                 _PostQuestionHint(data: data) | ||||||
|  |                     .padding(horizontal: 16, bottom: 8), | ||||||
|  |               if (data.body['title'] != null || | ||||||
|  |                   data.body['description'] != null) | ||||||
|                 _PostHeadline( |                 _PostHeadline( | ||||||
|                   data: data, |                   data: data, | ||||||
|                   isEnlarge: data.type == 'article' && showFullPost, |                   isEnlarge: data.type == 'article' && showFullPost, | ||||||
| @@ -365,7 +384,8 @@ class PostItem extends StatelessWidget { | |||||||
|               if (data.repostTo != null) |               if (data.repostTo != null) | ||||||
|                 _PostQuoteContent(child: data.repostTo!).padding( |                 _PostQuoteContent(child: data.repostTo!).padding( | ||||||
|                   horizontal: 12, |                   horizontal: 12, | ||||||
|                   bottom: data.preload?.attachments?.isNotEmpty ?? false ? 12 : 0, |                   bottom: | ||||||
|  |                       data.preload?.attachments?.isNotEmpty ?? false ? 12 : 0, | ||||||
|                 ), |                 ), | ||||||
|               if (data.visibility > 0) |               if (data.visibility > 0) | ||||||
|                 _PostVisibilityHint(data: data).padding( |                 _PostVisibilityHint(data: data).padding( | ||||||
| @@ -377,7 +397,9 @@ class PostItem extends StatelessWidget { | |||||||
|                   horizontal: 16, |                   horizontal: 16, | ||||||
|                   vertical: 4, |                   vertical: 4, | ||||||
|                 ), |                 ), | ||||||
|               if (data.tags.isNotEmpty) _PostTagsList(data: data).padding(horizontal: 16, top: 4, bottom: 6), |               if (data.tags.isNotEmpty) | ||||||
|  |                 _PostTagsList(data: data) | ||||||
|  |                     .padding(horizontal: 16, top: 4, bottom: 6), | ||||||
|             ], |             ], | ||||||
|           ), |           ), | ||||||
|         ), |         ), | ||||||
| @@ -390,12 +412,16 @@ class PostItem extends StatelessWidget { | |||||||
|             fit: showFullPost ? BoxFit.cover : BoxFit.contain, |             fit: showFullPost ? BoxFit.cover : BoxFit.contain, | ||||||
|             padding: const EdgeInsets.symmetric(horizontal: 12), |             padding: const EdgeInsets.symmetric(horizontal: 12), | ||||||
|           ), |           ), | ||||||
|         if (data.preload?.poll != null) PostPoll(poll: data.preload!.poll!).padding(horizontal: 12, vertical: 4), |         if (data.preload?.poll != null) | ||||||
|         if (data.body['content'] != null && (cfg.prefs.getBool(kAppExpandPostLink) ?? true)) |           PostPoll(poll: data.preload!.poll!) | ||||||
|  |               .padding(horizontal: 12, vertical: 4), | ||||||
|  |         if (data.body['content'] != null && | ||||||
|  |             (cfg.prefs.getBool(kAppExpandPostLink) ?? true)) | ||||||
|           LinkPreviewWidget( |           LinkPreviewWidget( | ||||||
|             text: data.body['content'], |             text: data.body['content'], | ||||||
|           ).padding(horizontal: 4), |           ).padding(horizontal: 4), | ||||||
|         _PostFeaturedComment(data: data, maxWidth: maxWidth).padding(horizontal: 12), |         _PostFeaturedComment(data: data, maxWidth: maxWidth) | ||||||
|  |             .padding(horizontal: 12), | ||||||
|         Container( |         Container( | ||||||
|           constraints: BoxConstraints(maxWidth: maxWidth ?? double.infinity), |           constraints: BoxConstraints(maxWidth: maxWidth ?? double.infinity), | ||||||
|           child: Column( |           child: Column( | ||||||
| @@ -457,7 +483,8 @@ class PostShareImageWidget extends StatelessWidget { | |||||||
|             showMenu: false, |             showMenu: false, | ||||||
|             isRelativeDate: false, |             isRelativeDate: false, | ||||||
|           ).padding(horizontal: 16, bottom: 8), |           ).padding(horizontal: 16, bottom: 8), | ||||||
|           if (data.type == 'question') _PostQuestionHint(data: data).padding(horizontal: 16, bottom: 8), |           if (data.type == 'question') | ||||||
|  |             _PostQuestionHint(data: data).padding(horizontal: 16, bottom: 8), | ||||||
|           _PostHeadline( |           _PostHeadline( | ||||||
|             data: data, |             data: data, | ||||||
|             isEnlarge: data.type == 'article', |             isEnlarge: data.type == 'article', | ||||||
| @@ -472,7 +499,8 @@ class PostShareImageWidget extends StatelessWidget { | |||||||
|               child: data.repostTo!, |               child: data.repostTo!, | ||||||
|               isRelativeDate: false, |               isRelativeDate: false, | ||||||
|             ).padding(horizontal: 16, bottom: 8), |             ).padding(horizontal: 16, bottom: 8), | ||||||
|           if (data.type != 'article' && (data.preload?.attachments?.isNotEmpty ?? false)) |           if (data.type != 'article' && | ||||||
|  |               (data.preload?.attachments?.isNotEmpty ?? false)) | ||||||
|             StyledWidget(AttachmentList( |             StyledWidget(AttachmentList( | ||||||
|               data: data.preload!.attachments!, |               data: data.preload!.attachments!, | ||||||
|               columned: true, |               columned: true, | ||||||
| @@ -481,7 +509,8 @@ class PostShareImageWidget extends StatelessWidget { | |||||||
|             crossAxisAlignment: CrossAxisAlignment.start, |             crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|             children: [ |             children: [ | ||||||
|               if (data.visibility > 0) _PostVisibilityHint(data: data), |               if (data.visibility > 0) _PostVisibilityHint(data: data), | ||||||
|               if (data.body['content_truncated'] == true) _PostTruncatedHint(data: data), |               if (data.body['content_truncated'] == true) | ||||||
|  |                 _PostTruncatedHint(data: data), | ||||||
|             ], |             ], | ||||||
|           ).padding(horizontal: 16), |           ).padding(horizontal: 16), | ||||||
|           _PostBottomAction( |           _PostBottomAction( | ||||||
| @@ -541,7 +570,8 @@ class PostShareImageWidget extends StatelessWidget { | |||||||
|                   version: QrVersions.auto, |                   version: QrVersions.auto, | ||||||
|                   size: 100, |                   size: 100, | ||||||
|                   gapless: true, |                   gapless: true, | ||||||
|                   embeddedImage: AssetImage('assets/icon/icon-light-radius.png'), |                   embeddedImage: | ||||||
|  |                       AssetImage('assets/icon/icon-light-radius.png'), | ||||||
|                   embeddedImageStyle: QrEmbeddedImageStyle( |                   embeddedImageStyle: QrEmbeddedImageStyle( | ||||||
|                     size: Size(28, 28), |                     size: Size(28, 28), | ||||||
|                   ), |                   ), | ||||||
| @@ -572,9 +602,11 @@ class _PostQuestionHint extends StatelessWidget { | |||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return Row( |     return Row( | ||||||
|       children: [ |       children: [ | ||||||
|         Icon(data.body['answer'] == null ? Symbols.help : Symbols.check_circle, size: 20), |         Icon(data.body['answer'] == null ? Symbols.help : Symbols.check_circle, | ||||||
|  |             size: 20), | ||||||
|         const Gap(4), |         const Gap(4), | ||||||
|         if (data.body['answer'] == null && data.body['reward']?.toDouble() != null) |         if (data.body['answer'] == null && | ||||||
|  |             data.body['reward']?.toDouble() != null) | ||||||
|           Text('postQuestionUnansweredWithReward'.tr(args: [ |           Text('postQuestionUnansweredWithReward'.tr(args: [ | ||||||
|             '${data.body['reward']}', |             '${data.body['reward']}', | ||||||
|           ])).opacity(0.75) |           ])).opacity(0.75) | ||||||
| @@ -610,7 +642,9 @@ class _PostBottomAction extends StatelessWidget { | |||||||
|         ); |         ); | ||||||
|  |  | ||||||
|     final String? mostTypicalReaction = data.metric.reactionList.isNotEmpty |     final String? mostTypicalReaction = data.metric.reactionList.isNotEmpty | ||||||
|         ? data.metric.reactionList.entries.reduce((a, b) => a.value > b.value ? a : b).key |         ? data.metric.reactionList.entries | ||||||
|  |             .reduce((a, b) => a.value > b.value ? a : b) | ||||||
|  |             .key | ||||||
|         : null; |         : null; | ||||||
|  |  | ||||||
|     return Row( |     return Row( | ||||||
| @@ -624,7 +658,8 @@ class _PostBottomAction extends StatelessWidget { | |||||||
|                 InkWell( |                 InkWell( | ||||||
|                   child: Row( |                   child: Row( | ||||||
|                     children: [ |                     children: [ | ||||||
|                       if (mostTypicalReaction == null || kTemplateReactions[mostTypicalReaction] == null) |                       if (mostTypicalReaction == null || | ||||||
|  |                           kTemplateReactions[mostTypicalReaction] == null) | ||||||
|                         Icon(Symbols.add_reaction, size: 20, color: iconColor) |                         Icon(Symbols.add_reaction, size: 20, color: iconColor) | ||||||
|                       else |                       else | ||||||
|                         Text( |                         Text( | ||||||
| @@ -636,7 +671,8 @@ class _PostBottomAction extends StatelessWidget { | |||||||
|                           ), |                           ), | ||||||
|                         ), |                         ), | ||||||
|                       const Gap(8), |                       const Gap(8), | ||||||
|                       if (data.totalUpvote > 0 && data.totalUpvote >= data.totalDownvote) |                       if (data.totalUpvote > 0 && | ||||||
|  |                           data.totalUpvote >= data.totalDownvote) | ||||||
|                         Text('postReactionUpvote').plural( |                         Text('postReactionUpvote').plural( | ||||||
|                           data.totalUpvote, |                           data.totalUpvote, | ||||||
|                         ) |                         ) | ||||||
| @@ -655,8 +691,12 @@ class _PostBottomAction extends StatelessWidget { | |||||||
|                         data: data, |                         data: data, | ||||||
|                         onChanged: (value, attr, delta) { |                         onChanged: (value, attr, delta) { | ||||||
|                           onChanged(data.copyWith( |                           onChanged(data.copyWith( | ||||||
|                             totalUpvote: attr == 1 ? data.totalUpvote + delta : data.totalUpvote, |                             totalUpvote: attr == 1 | ||||||
|                             totalDownvote: attr == 2 ? data.totalDownvote + delta : data.totalDownvote, |                                 ? data.totalUpvote + delta | ||||||
|  |                                 : data.totalUpvote, | ||||||
|  |                             totalDownvote: attr == 2 | ||||||
|  |                                 ? data.totalDownvote + delta | ||||||
|  |                                 : data.totalDownvote, | ||||||
|                             metric: data.metric.copyWith(reactionList: value), |                             metric: data.metric.copyWith(reactionList: value), | ||||||
|                           )); |                           )); | ||||||
|                         }, |                         }, | ||||||
| @@ -904,8 +944,10 @@ class _PostContentHeader extends StatelessWidget { | |||||||
|                   const Gap(4), |                   const Gap(4), | ||||||
|                   Text( |                   Text( | ||||||
|                     isRelativeDate |                     isRelativeDate | ||||||
|                         ? RelativeTime(context).format(data.publishedAt ?? data.createdAt) |                         ? RelativeTime(context) | ||||||
|                         : DateFormat('y/M/d HH:mm').format(data.publishedAt ?? data.createdAt), |                             .format(data.publishedAt ?? data.createdAt) | ||||||
|  |                         : DateFormat('y/M/d HH:mm') | ||||||
|  |                             .format(data.publishedAt ?? data.createdAt), | ||||||
|                   ).fontSize(13), |                   ).fontSize(13), | ||||||
|                 ], |                 ], | ||||||
|               ).opacity(0.8), |               ).opacity(0.8), | ||||||
| @@ -923,8 +965,10 @@ class _PostContentHeader extends StatelessWidget { | |||||||
|                     const Gap(4), |                     const Gap(4), | ||||||
|                     Text( |                     Text( | ||||||
|                       isRelativeDate |                       isRelativeDate | ||||||
|                           ? RelativeTime(context).format(data.publishedAt ?? data.createdAt) |                           ? RelativeTime(context) | ||||||
|                           : DateFormat('y/M/d HH:mm').format(data.publishedAt ?? data.createdAt), |                               .format(data.publishedAt ?? data.createdAt) | ||||||
|  |                           : DateFormat('y/M/d HH:mm') | ||||||
|  |                               .format(data.publishedAt ?? data.createdAt), | ||||||
|                     ).fontSize(13), |                     ).fontSize(13), | ||||||
|                   ], |                   ], | ||||||
|                 ).opacity(0.8), |                 ).opacity(0.8), | ||||||
| @@ -1107,7 +1151,8 @@ class _PostContentBody extends StatelessWidget { | |||||||
|     if (data.body['content'] == null) return const SizedBox.shrink(); |     if (data.body['content'] == null) return const SizedBox.shrink(); | ||||||
|     final content = MarkdownTextContent( |     final content = MarkdownTextContent( | ||||||
|       isAutoWarp: data.type == 'story', |       isAutoWarp: data.type == 'story', | ||||||
|       isEnlargeSticker: true, |       isEnlargeSticker: | ||||||
|  |           RegExp(r"^:([-\w]+):$").hasMatch(data.body['content'] ?? ''), | ||||||
|       textScaler: isEnlarge ? TextScaler.linear(1.1) : null, |       textScaler: isEnlarge ? TextScaler.linear(1.1) : null, | ||||||
|       content: data.body['content'], |       content: data.body['content'], | ||||||
|       attachments: data.preload?.attachments, |       attachments: data.preload?.attachments, | ||||||
| @@ -1156,10 +1201,12 @@ class _PostQuoteContent extends StatelessWidget { | |||||||
|                   onDeleted: () {}, |                   onDeleted: () {}, | ||||||
|                 ).padding(bottom: 4), |                 ).padding(bottom: 4), | ||||||
|                 _PostContentBody(data: child), |                 _PostContentBody(data: child), | ||||||
|                 if (child.visibility > 0) _PostVisibilityHint(data: child).padding(top: 4), |                 if (child.visibility > 0) | ||||||
|  |                   _PostVisibilityHint(data: child).padding(top: 4), | ||||||
|               ], |               ], | ||||||
|             ).padding(horizontal: 16), |             ).padding(horizontal: 16), | ||||||
|             if (child.type != 'article' && (child.preload?.attachments?.isNotEmpty ?? false)) |             if (child.type != 'article' && | ||||||
|  |                 (child.preload?.attachments?.isNotEmpty ?? false)) | ||||||
|               ClipRRect( |               ClipRRect( | ||||||
|                 borderRadius: const BorderRadius.only( |                 borderRadius: const BorderRadius.only( | ||||||
|                   bottomLeft: Radius.circular(8), |                   bottomLeft: Radius.circular(8), | ||||||
| @@ -1310,7 +1357,9 @@ class _PostTruncatedHint extends StatelessWidget { | |||||||
|                 const Gap(4), |                 const Gap(4), | ||||||
|                 Text('postReadEstimate').tr(args: [ |                 Text('postReadEstimate').tr(args: [ | ||||||
|                   '${Duration( |                   '${Duration( | ||||||
|                     seconds: (data.body['content_length'] as num).toDouble() * 60 ~/ kHumanReadSpeed, |                     seconds: (data.body['content_length'] as num).toDouble() * | ||||||
|  |                         60 ~/ | ||||||
|  |                         kHumanReadSpeed, | ||||||
|                   ).inSeconds}s', |                   ).inSeconds}s', | ||||||
|                 ]), |                 ]), | ||||||
|               ], |               ], | ||||||
| @@ -1349,7 +1398,8 @@ class _PostFeaturedCommentState extends State<_PostFeaturedComment> { | |||||||
|     // If this is a answered question, fetch the answer instead |     // If this is a answered question, fetch the answer instead | ||||||
|     if (widget.data.type == 'question' && widget.data.body['answer'] != null) { |     if (widget.data.type == 'question' && widget.data.body['answer'] != null) { | ||||||
|       final sn = context.read<SnNetworkProvider>(); |       final sn = context.read<SnNetworkProvider>(); | ||||||
|       final resp = await sn.client.get('/cgi/co/posts/${widget.data.body['answer']}'); |       final resp = | ||||||
|  |           await sn.client.get('/cgi/co/posts/${widget.data.body['answer']}'); | ||||||
|       _isAnswer = true; |       _isAnswer = true; | ||||||
|       setState(() => _featuredComment = SnPost.fromJson(resp.data)); |       setState(() => _featuredComment = SnPost.fromJson(resp.data)); | ||||||
|       return; |       return; | ||||||
| @@ -1357,7 +1407,9 @@ class _PostFeaturedCommentState extends State<_PostFeaturedComment> { | |||||||
|  |  | ||||||
|     try { |     try { | ||||||
|       final sn = context.read<SnNetworkProvider>(); |       final sn = context.read<SnNetworkProvider>(); | ||||||
|       final resp = await sn.client.get('/cgi/co/posts/${widget.data.id}/replies/featured', queryParameters: { |       final resp = await sn.client.get( | ||||||
|  |           '/cgi/co/posts/${widget.data.id}/replies/featured', | ||||||
|  |           queryParameters: { | ||||||
|             'take': 1, |             'take': 1, | ||||||
|           }); |           }); | ||||||
|       setState(() => _featuredComment = SnPost.fromJson(resp.data[0])); |       setState(() => _featuredComment = SnPost.fromJson(resp.data[0])); | ||||||
| @@ -1388,7 +1440,9 @@ class _PostFeaturedCommentState extends State<_PostFeaturedComment> { | |||||||
|       width: double.infinity, |       width: double.infinity, | ||||||
|       child: Material( |       child: Material( | ||||||
|         borderRadius: const BorderRadius.all(Radius.circular(8)), |         borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||||
|         color: _isAnswer ? Colors.green.withOpacity(0.5) : Theme.of(context).colorScheme.surfaceContainerHigh, |         color: _isAnswer | ||||||
|  |             ? Colors.green.withOpacity(0.5) | ||||||
|  |             : Theme.of(context).colorScheme.surfaceContainerHigh, | ||||||
|         child: InkWell( |         child: InkWell( | ||||||
|           borderRadius: const BorderRadius.all(Radius.circular(8)), |           borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||||
|           onTap: () { |           onTap: () { | ||||||
| @@ -1408,11 +1462,17 @@ class _PostFeaturedCommentState extends State<_PostFeaturedComment> { | |||||||
|                 crossAxisAlignment: CrossAxisAlignment.center, |                 crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|                 children: [ |                 children: [ | ||||||
|                   const Gap(2), |                   const Gap(2), | ||||||
|                   Icon(_isAnswer ? Symbols.task_alt : Symbols.prompt_suggestion, size: 20), |                   Icon(_isAnswer ? Symbols.task_alt : Symbols.prompt_suggestion, | ||||||
|  |                       size: 20), | ||||||
|                   const Gap(10), |                   const Gap(10), | ||||||
|                   Text( |                   Text( | ||||||
|                     _isAnswer ? 'postQuestionAnswerTitle' : 'postFeaturedComment', |                     _isAnswer | ||||||
|                     style: Theme.of(context).textTheme.titleMedium!.copyWith(fontSize: 15), |                         ? 'postQuestionAnswerTitle' | ||||||
|  |                         : 'postFeaturedComment', | ||||||
|  |                     style: Theme.of(context) | ||||||
|  |                         .textTheme | ||||||
|  |                         .titleMedium! | ||||||
|  |                         .copyWith(fontSize: 15), | ||||||
|                   ).tr(), |                   ).tr(), | ||||||
|                 ], |                 ], | ||||||
|               ), |               ), | ||||||
| @@ -1550,7 +1610,8 @@ class _PostGetInsightPopupState extends State<_PostGetInsightPopup> { | |||||||
|       } |       } | ||||||
|  |  | ||||||
|       RegExp cleanThinkingRegExp = RegExp(r'<think>[\s\S]*?</think>'); |       RegExp cleanThinkingRegExp = RegExp(r'<think>[\s\S]*?</think>'); | ||||||
|       setState(() => _response = out.replaceAll(cleanThinkingRegExp, '').trim()); |       setState( | ||||||
|  |           () => _response = out.replaceAll(cleanThinkingRegExp, '').trim()); | ||||||
|     } catch (err) { |     } catch (err) { | ||||||
|       if (!mounted) return; |       if (!mounted) return; | ||||||
|       context.showErrorDialog(err); |       context.showErrorDialog(err); | ||||||
| @@ -1573,11 +1634,16 @@ class _PostGetInsightPopupState extends State<_PostGetInsightPopup> { | |||||||
|           children: [ |           children: [ | ||||||
|             const Icon(Symbols.book_4_spark, size: 24), |             const Icon(Symbols.book_4_spark, size: 24), | ||||||
|             const Gap(16), |             const Gap(16), | ||||||
|             Text('postGetInsightTitle', style: Theme.of(context).textTheme.titleLarge).tr(), |             Text('postGetInsightTitle', | ||||||
|  |                     style: Theme.of(context).textTheme.titleLarge) | ||||||
|  |                 .tr(), | ||||||
|           ], |           ], | ||||||
|         ).padding(horizontal: 20, top: 16, bottom: 12), |         ).padding(horizontal: 20, top: 16, bottom: 12), | ||||||
|         const Gap(4), |         const Gap(4), | ||||||
|         Text('postGetInsightDescription', style: Theme.of(context).textTheme.bodySmall).tr().padding(horizontal: 20), |         Text('postGetInsightDescription', | ||||||
|  |                 style: Theme.of(context).textTheme.bodySmall) | ||||||
|  |             .tr() | ||||||
|  |             .padding(horizontal: 20), | ||||||
|         const Gap(4), |         const Gap(4), | ||||||
|         if (_response == null) |         if (_response == null) | ||||||
|           Expanded( |           Expanded( | ||||||
| @@ -1595,12 +1661,16 @@ class _PostGetInsightPopupState extends State<_PostGetInsightPopup> { | |||||||
|                       leading: const Icon(Symbols.info), |                       leading: const Icon(Symbols.info), | ||||||
|                       title: Text('aiThinkingProcess'.tr()), |                       title: Text('aiThinkingProcess'.tr()), | ||||||
|                       tilePadding: const EdgeInsets.symmetric(horizontal: 20), |                       tilePadding: const EdgeInsets.symmetric(horizontal: 20), | ||||||
|                       collapsedBackgroundColor: Theme.of(context).colorScheme.surfaceContainerHigh, |                       collapsedBackgroundColor: | ||||||
|  |                           Theme.of(context).colorScheme.surfaceContainerHigh, | ||||||
|                       minTileHeight: 32, |                       minTileHeight: 32, | ||||||
|                       children: [ |                       children: [ | ||||||
|                         SelectableText( |                         SelectableText( | ||||||
|                           _thinkingProcess!, |                           _thinkingProcess!, | ||||||
|                           style: Theme.of(context).textTheme.bodyMedium!.copyWith(fontStyle: FontStyle.italic), |                           style: Theme.of(context) | ||||||
|  |                               .textTheme | ||||||
|  |                               .bodyMedium! | ||||||
|  |                               .copyWith(fontStyle: FontStyle.italic), | ||||||
|                         ).padding(horizontal: 20, vertical: 8), |                         ).padding(horizontal: 20, vertical: 8), | ||||||
|                       ], |                       ], | ||||||
|                     ).padding(vertical: 8), |                     ).padding(vertical: 8), | ||||||
| @@ -1637,7 +1707,8 @@ class _PostVideoPlayer extends StatelessWidget { | |||||||
|         aspectRatio: 16 / 9, |         aspectRatio: 16 / 9, | ||||||
|         child: ClipRRect( |         child: ClipRRect( | ||||||
|           borderRadius: const BorderRadius.all(Radius.circular(8)), |           borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||||
|           child: AttachmentItem(data: data.preload!.video!, heroTag: 'post-video-${data.id}'), |           child: AttachmentItem( | ||||||
|  |               data: data.preload!.video!, heroTag: 'post-video-${data.id}'), | ||||||
|         ), |         ), | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   | |||||||
| @@ -30,25 +30,21 @@ import 'package:surface/widgets/universal_image.dart'; | |||||||
| import '../attachment/pending_attachment_compress.dart'; | import '../attachment/pending_attachment_compress.dart'; | ||||||
|  |  | ||||||
| class PostMediaPendingList extends StatelessWidget { | class PostMediaPendingList extends StatelessWidget { | ||||||
|   final PostWriteMedia? thumbnail; |  | ||||||
|   final List<PostWriteMedia> attachments; |   final List<PostWriteMedia> attachments; | ||||||
|   final bool isBusy; |   final bool isBusy; | ||||||
|   final Future<void> Function(int idx, PostWriteMedia updatedMedia)? onUpdate; |   final Future<void> Function(int idx, PostWriteMedia updatedMedia)? onUpdate; | ||||||
|   final Future<void> Function(int idx)? onRemove; |   final Future<void> Function(int idx)? onRemove; | ||||||
|   final Future<void> Function(int idx)? onUpload; |   final Future<void> Function(int idx)? onUpload; | ||||||
|   final void Function(int? idx)? onPostSetThumbnail; |  | ||||||
|   final void Function(int idx)? onInsertLink; |   final void Function(int idx)? onInsertLink; | ||||||
|   final void Function(bool state)? onUpdateBusy; |   final void Function(bool state)? onUpdateBusy; | ||||||
|  |  | ||||||
|   const PostMediaPendingList({ |   const PostMediaPendingList({ | ||||||
|     super.key, |     super.key, | ||||||
|     this.thumbnail, |  | ||||||
|     required this.attachments, |     required this.attachments, | ||||||
|     required this.isBusy, |     required this.isBusy, | ||||||
|     this.onUpdate, |     this.onUpdate, | ||||||
|     this.onRemove, |     this.onRemove, | ||||||
|     this.onUpload, |     this.onUpload, | ||||||
|     this.onPostSetThumbnail, |  | ||||||
|     this.onInsertLink, |     this.onInsertLink, | ||||||
|     this.onUpdateBusy, |     this.onUpdateBusy, | ||||||
|   }); |   }); | ||||||
| @@ -116,7 +112,7 @@ class PostMediaPendingList extends StatelessWidget { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   Future<void> _deleteAttachment(BuildContext context, int idx) async { |   Future<void> _deleteAttachment(BuildContext context, int idx) async { | ||||||
|     final media = idx == -1 ? thumbnail! : attachments[idx]; |     final media = attachments[idx]; | ||||||
|     if (media.attachment == null) return; |     if (media.attachment == null) return; | ||||||
|  |  | ||||||
|     try { |     try { | ||||||
| @@ -212,22 +208,6 @@ class PostMediaPendingList extends StatelessWidget { | |||||||
|               onSelected: () { |               onSelected: () { | ||||||
|                 onUpload!(idx); |                 onUpload!(idx); | ||||||
|               }), |               }), | ||||||
|         if (media.attachment != null && media.type == SnMediaType.image && onPostSetThumbnail != null && idx != -1) |  | ||||||
|           MenuItem( |  | ||||||
|             label: 'attachmentSetAsPostThumbnail'.tr(), |  | ||||||
|             icon: Symbols.gallery_thumbnail, |  | ||||||
|             onSelected: () { |  | ||||||
|               onPostSetThumbnail!(idx); |  | ||||||
|             }, |  | ||||||
|           ) |  | ||||||
|         else if (media.attachment != null && media.type == SnMediaType.image && onPostSetThumbnail != null) |  | ||||||
|           MenuItem( |  | ||||||
|             label: 'attachmentUnsetAsPostThumbnail'.tr(), |  | ||||||
|             icon: Symbols.cancel, |  | ||||||
|             onSelected: () { |  | ||||||
|               onPostSetThumbnail!(null); |  | ||||||
|             }, |  | ||||||
|           ), |  | ||||||
|         if (media.attachment != null && onInsertLink != null) |         if (media.attachment != null && onInsertLink != null) | ||||||
|           MenuItem( |           MenuItem( | ||||||
|             label: 'attachmentInsertLink'.tr(), |             label: 'attachmentInsertLink'.tr(), | ||||||
| @@ -291,23 +271,9 @@ class PostMediaPendingList extends StatelessWidget { | |||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return Container( |     return Container( | ||||||
|       constraints: const BoxConstraints(maxHeight: 120), |       constraints: const BoxConstraints(maxHeight: 120), | ||||||
|       child: Row( |  | ||||||
|         children: [ |  | ||||||
|           const Gap(16), |  | ||||||
|           if (thumbnail != null) |  | ||||||
|             ContextMenuArea( |  | ||||||
|               contextMenu: _createContextMenu(context, -1, thumbnail!), |  | ||||||
|               child: _PostMediaPendingItem(media: thumbnail!), |  | ||||||
|             ), |  | ||||||
|           if (thumbnail != null) |  | ||||||
|             const VerticalDivider(width: 1, thickness: 1).padding( |  | ||||||
|               horizontal: 12, |  | ||||||
|               vertical: 16, |  | ||||||
|             ), |  | ||||||
|           Expanded( |  | ||||||
|       child: ListView.separated( |       child: ListView.separated( | ||||||
|         scrollDirection: Axis.horizontal, |         scrollDirection: Axis.horizontal, | ||||||
|               padding: const EdgeInsets.only(right: 8), |         padding: const EdgeInsets.symmetric(horizontal: 8), | ||||||
|         separatorBuilder: (context, index) => const Gap(8), |         separatorBuilder: (context, index) => const Gap(8), | ||||||
|         itemCount: attachments.length, |         itemCount: attachments.length, | ||||||
|         itemBuilder: (context, idx) { |         itemBuilder: (context, idx) { | ||||||
| @@ -318,9 +284,6 @@ class PostMediaPendingList extends StatelessWidget { | |||||||
|           ); |           ); | ||||||
|         }, |         }, | ||||||
|       ), |       ), | ||||||
|           ), |  | ||||||
|         ], |  | ||||||
|       ), |  | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										149
									
								
								lib/widgets/realm/realm_item.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								lib/widgets/realm/realm_item.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,149 @@ | |||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:gap/gap.dart'; | ||||||
|  | import 'package:go_router/go_router.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:provider/provider.dart'; | ||||||
|  | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  | import 'package:surface/providers/sn_network.dart'; | ||||||
|  | import 'package:surface/types/realm.dart'; | ||||||
|  | import 'package:surface/widgets/account/account_image.dart'; | ||||||
|  | import 'package:surface/widgets/universal_image.dart'; | ||||||
|  |  | ||||||
|  | class RealmItemWidget extends StatelessWidget { | ||||||
|  |   final SnRealm item; | ||||||
|  |   final bool isListView; | ||||||
|  |   final List<PopupMenuItem>? actionListView; | ||||||
|  |   final Function? onUpdate; | ||||||
|  |   final Function? onTap; | ||||||
|  |   final bool showPopularity; | ||||||
|  |  | ||||||
|  |   const RealmItemWidget({ | ||||||
|  |     super.key, | ||||||
|  |     required this.item, | ||||||
|  |     required this.isListView, | ||||||
|  |     this.actionListView, | ||||||
|  |     this.onUpdate, | ||||||
|  |     this.onTap, | ||||||
|  |     this.showPopularity = true, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     if (isListView) { | ||||||
|  |       return ListTile( | ||||||
|  |         contentPadding: const EdgeInsets.symmetric(horizontal: 16), | ||||||
|  |         leading: AccountImage( | ||||||
|  |           content: item.avatar, | ||||||
|  |           fallbackWidget: const Icon(Symbols.group, size: 20), | ||||||
|  |         ), | ||||||
|  |         title: Text(item.name), | ||||||
|  |         subtitle: Row( | ||||||
|  |           children: [ | ||||||
|  |             if (showPopularity) const Icon(Symbols.local_fire_department, size: 18).padding(right: 1), | ||||||
|  |             if (showPopularity) Text(item.popularity.toString()), | ||||||
|  |             if (showPopularity) const Gap(6), | ||||||
|  |             Expanded( | ||||||
|  |               child: Text( | ||||||
|  |                 item.description, | ||||||
|  |                 maxLines: 1, | ||||||
|  |                 overflow: TextOverflow.ellipsis, | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|  |         trailing: | ||||||
|  |             actionListView != null ? PopupMenuButton(itemBuilder: (BuildContext context) => actionListView!) : null, | ||||||
|  |         onTap: () { | ||||||
|  |           if (onTap != null) { | ||||||
|  |             onTap!(); | ||||||
|  |             return; | ||||||
|  |           } | ||||||
|  |           GoRouter.of(context).pushNamed( | ||||||
|  |             'realmDetail', | ||||||
|  |             pathParameters: {'alias': item.alias}, | ||||||
|  |           ).then((value) { | ||||||
|  |             if (value == true) { | ||||||
|  |               onUpdate?.call(); | ||||||
|  |             } | ||||||
|  |           }); | ||||||
|  |         }, | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     final sn = context.read<SnNetworkProvider>(); | ||||||
|  |  | ||||||
|  |     return Container( | ||||||
|  |       constraints: BoxConstraints(maxWidth: 640), | ||||||
|  |       child: Card( | ||||||
|  |         margin: const EdgeInsets.all(12), | ||||||
|  |         child: InkWell( | ||||||
|  |           borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||||
|  |           child: Column( | ||||||
|  |             crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |             children: [ | ||||||
|  |               AspectRatio( | ||||||
|  |                 aspectRatio: 16 / 7, | ||||||
|  |                 child: Stack( | ||||||
|  |                   clipBehavior: Clip.none, | ||||||
|  |                   fit: StackFit.expand, | ||||||
|  |                   children: [ | ||||||
|  |                     ClipRRect( | ||||||
|  |                       borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||||
|  |                       child: Container( | ||||||
|  |                         color: Theme.of(context).colorScheme.surfaceContainer, | ||||||
|  |                         child: (item.banner?.isEmpty ?? true) | ||||||
|  |                             ? const SizedBox.shrink() | ||||||
|  |                             : AutoResizeUniversalImage( | ||||||
|  |                                 sn.getAttachmentUrl(item.banner!), | ||||||
|  |                                 fit: BoxFit.cover, | ||||||
|  |                               ), | ||||||
|  |                       ), | ||||||
|  |                     ), | ||||||
|  |                     Positioned( | ||||||
|  |                       bottom: -30, | ||||||
|  |                       left: 18, | ||||||
|  |                       child: AccountImage( | ||||||
|  |                         content: item.avatar, | ||||||
|  |                         radius: 24, | ||||||
|  |                         fallbackWidget: const Icon(Symbols.group, size: 24), | ||||||
|  |                       ), | ||||||
|  |                     ), | ||||||
|  |                   ], | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |               const Gap(20 + 12), | ||||||
|  |               Column( | ||||||
|  |                 crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |                 children: [ | ||||||
|  |                   Text(item.name).textStyle(Theme.of(context).textTheme.titleMedium!), | ||||||
|  |                   if (showPopularity) | ||||||
|  |                     Row( | ||||||
|  |                       children: [ | ||||||
|  |                         Text(item.popularity.toString()), | ||||||
|  |                         const Icon(Symbols.local_fire_department, size: 16).padding(bottom: 2), | ||||||
|  |                       ], | ||||||
|  |                     ).padding(top: 6), | ||||||
|  |                   Text(item.description).textStyle(Theme.of(context).textTheme.bodySmall!), | ||||||
|  |                 ], | ||||||
|  |               ).padding(horizontal: 24, bottom: 14), | ||||||
|  |             ], | ||||||
|  |           ), | ||||||
|  |           onTap: () { | ||||||
|  |             if (onTap != null) { | ||||||
|  |               onTap!(); | ||||||
|  |               return; | ||||||
|  |             } | ||||||
|  |             GoRouter.of(context).pushNamed( | ||||||
|  |               'realmDetail', | ||||||
|  |               pathParameters: {'alias': item.alias}, | ||||||
|  |             ).then((value) { | ||||||
|  |               if (value == true) { | ||||||
|  |                 onUpdate?.call(); | ||||||
|  |               } | ||||||
|  |             }); | ||||||
|  |           }, | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ).center(); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -12,9 +12,11 @@ | |||||||
| #include <flutter_udid/flutter_udid_plugin.h> | #include <flutter_udid/flutter_udid_plugin.h> | ||||||
| #include <flutter_webrtc/flutter_web_r_t_c_plugin.h> | #include <flutter_webrtc/flutter_web_r_t_c_plugin.h> | ||||||
| #include <hotkey_manager_linux/hotkey_manager_linux_plugin.h> | #include <hotkey_manager_linux/hotkey_manager_linux_plugin.h> | ||||||
|  | #include <local_notifier/local_notifier_plugin.h> | ||||||
| #include <media_kit_libs_linux/media_kit_libs_linux_plugin.h> | #include <media_kit_libs_linux/media_kit_libs_linux_plugin.h> | ||||||
| #include <media_kit_video/media_kit_video_plugin.h> | #include <media_kit_video/media_kit_video_plugin.h> | ||||||
| #include <pasteboard/pasteboard_plugin.h> | #include <pasteboard/pasteboard_plugin.h> | ||||||
|  | #include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h> | ||||||
| #include <tray_manager/tray_manager_plugin.h> | #include <tray_manager/tray_manager_plugin.h> | ||||||
| #include <url_launcher_linux/url_launcher_plugin.h> | #include <url_launcher_linux/url_launcher_plugin.h> | ||||||
|  |  | ||||||
| @@ -37,6 +39,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { | |||||||
|   g_autoptr(FlPluginRegistrar) hotkey_manager_linux_registrar = |   g_autoptr(FlPluginRegistrar) hotkey_manager_linux_registrar = | ||||||
|       fl_plugin_registry_get_registrar_for_plugin(registry, "HotkeyManagerLinuxPlugin"); |       fl_plugin_registry_get_registrar_for_plugin(registry, "HotkeyManagerLinuxPlugin"); | ||||||
|   hotkey_manager_linux_plugin_register_with_registrar(hotkey_manager_linux_registrar); |   hotkey_manager_linux_plugin_register_with_registrar(hotkey_manager_linux_registrar); | ||||||
|  |   g_autoptr(FlPluginRegistrar) local_notifier_registrar = | ||||||
|  |       fl_plugin_registry_get_registrar_for_plugin(registry, "LocalNotifierPlugin"); | ||||||
|  |   local_notifier_plugin_register_with_registrar(local_notifier_registrar); | ||||||
|   g_autoptr(FlPluginRegistrar) media_kit_libs_linux_registrar = |   g_autoptr(FlPluginRegistrar) media_kit_libs_linux_registrar = | ||||||
|       fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitLibsLinuxPlugin"); |       fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitLibsLinuxPlugin"); | ||||||
|   media_kit_libs_linux_plugin_register_with_registrar(media_kit_libs_linux_registrar); |   media_kit_libs_linux_plugin_register_with_registrar(media_kit_libs_linux_registrar); | ||||||
| @@ -46,6 +51,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { | |||||||
|   g_autoptr(FlPluginRegistrar) pasteboard_registrar = |   g_autoptr(FlPluginRegistrar) pasteboard_registrar = | ||||||
|       fl_plugin_registry_get_registrar_for_plugin(registry, "PasteboardPlugin"); |       fl_plugin_registry_get_registrar_for_plugin(registry, "PasteboardPlugin"); | ||||||
|   pasteboard_plugin_register_with_registrar(pasteboard_registrar); |   pasteboard_plugin_register_with_registrar(pasteboard_registrar); | ||||||
|  |   g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar = | ||||||
|  |       fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin"); | ||||||
|  |   sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar); | ||||||
|   g_autoptr(FlPluginRegistrar) tray_manager_registrar = |   g_autoptr(FlPluginRegistrar) tray_manager_registrar = | ||||||
|       fl_plugin_registry_get_registrar_for_plugin(registry, "TrayManagerPlugin"); |       fl_plugin_registry_get_registrar_for_plugin(registry, "TrayManagerPlugin"); | ||||||
|   tray_manager_plugin_register_with_registrar(tray_manager_registrar); |   tray_manager_plugin_register_with_registrar(tray_manager_registrar); | ||||||
|   | |||||||
| @@ -9,9 +9,11 @@ list(APPEND FLUTTER_PLUGIN_LIST | |||||||
|   flutter_udid |   flutter_udid | ||||||
|   flutter_webrtc |   flutter_webrtc | ||||||
|   hotkey_manager_linux |   hotkey_manager_linux | ||||||
|  |   local_notifier | ||||||
|   media_kit_libs_linux |   media_kit_libs_linux | ||||||
|   media_kit_video |   media_kit_video | ||||||
|   pasteboard |   pasteboard | ||||||
|  |   sqlite3_flutter_libs | ||||||
|   tray_manager |   tray_manager | ||||||
|   url_launcher_linux |   url_launcher_linux | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -21,6 +21,7 @@ import gal | |||||||
| import hotkey_manager_macos | import hotkey_manager_macos | ||||||
| import in_app_review | import in_app_review | ||||||
| import livekit_client | import livekit_client | ||||||
|  | import local_notifier | ||||||
| import media_kit_libs_macos_video | import media_kit_libs_macos_video | ||||||
| import media_kit_video | import media_kit_video | ||||||
| import package_info_plus | import package_info_plus | ||||||
| @@ -30,6 +31,7 @@ import screen_brightness_macos | |||||||
| import share_plus | import share_plus | ||||||
| import shared_preferences_foundation | import shared_preferences_foundation | ||||||
| import sqflite_darwin | import sqflite_darwin | ||||||
|  | import sqlite3_flutter_libs | ||||||
| import tray_manager | import tray_manager | ||||||
| import url_launcher_macos | import url_launcher_macos | ||||||
| import video_compress | import video_compress | ||||||
| @@ -52,6 +54,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { | |||||||
|   HotkeyManagerMacosPlugin.register(with: registry.registrar(forPlugin: "HotkeyManagerMacosPlugin")) |   HotkeyManagerMacosPlugin.register(with: registry.registrar(forPlugin: "HotkeyManagerMacosPlugin")) | ||||||
|   InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin")) |   InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin")) | ||||||
|   LiveKitPlugin.register(with: registry.registrar(forPlugin: "LiveKitPlugin")) |   LiveKitPlugin.register(with: registry.registrar(forPlugin: "LiveKitPlugin")) | ||||||
|  |   LocalNotifierPlugin.register(with: registry.registrar(forPlugin: "LocalNotifierPlugin")) | ||||||
|   MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin")) |   MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin")) | ||||||
|   MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin")) |   MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin")) | ||||||
|   FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) |   FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) | ||||||
| @@ -61,6 +64,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { | |||||||
|   SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) |   SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) | ||||||
|   SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) |   SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) | ||||||
|   SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) |   SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) | ||||||
|  |   Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin")) | ||||||
|   TrayManagerPlugin.register(with: registry.registrar(forPlugin: "TrayManagerPlugin")) |   TrayManagerPlugin.register(with: registry.registrar(forPlugin: "TrayManagerPlugin")) | ||||||
|   UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) |   UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) | ||||||
|   VideoCompressPlugin.register(with: registry.registrar(forPlugin: "VideoCompressPlugin")) |   VideoCompressPlugin.register(with: registry.registrar(forPlugin: "VideoCompressPlugin")) | ||||||
|   | |||||||
| @@ -13,59 +13,59 @@ PODS: | |||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|   - file_selector_macos (0.0.1): |   - file_selector_macos (0.0.1): | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|   - Firebase/Analytics (11.7.0): |   - Firebase/Analytics (11.8.0): | ||||||
|     - Firebase/Core |     - Firebase/Core | ||||||
|   - Firebase/Core (11.7.0): |   - Firebase/Core (11.8.0): | ||||||
|     - Firebase/CoreOnly |     - Firebase/CoreOnly | ||||||
|     - FirebaseAnalytics (~> 11.7.0) |     - FirebaseAnalytics (~> 11.8.0) | ||||||
|   - Firebase/CoreOnly (11.7.0): |   - Firebase/CoreOnly (11.8.0): | ||||||
|     - FirebaseCore (~> 11.7.0) |     - FirebaseCore (~> 11.8.0) | ||||||
|   - Firebase/Messaging (11.7.0): |   - Firebase/Messaging (11.8.0): | ||||||
|     - Firebase/CoreOnly |     - Firebase/CoreOnly | ||||||
|     - FirebaseMessaging (~> 11.7.0) |     - FirebaseMessaging (~> 11.8.0) | ||||||
|   - firebase_analytics (11.4.2): |   - firebase_analytics (11.4.3): | ||||||
|     - Firebase/Analytics (= 11.7.0) |     - Firebase/Analytics (= 11.8.0) | ||||||
|     - firebase_core |     - firebase_core | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|   - firebase_core (3.11.0): |   - firebase_core (3.12.0): | ||||||
|     - Firebase/CoreOnly (~> 11.7.0) |     - Firebase/CoreOnly (~> 11.8.0) | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|   - firebase_messaging (15.2.2): |   - firebase_messaging (15.2.3): | ||||||
|     - Firebase/CoreOnly (~> 11.7.0) |     - Firebase/CoreOnly (~> 11.8.0) | ||||||
|     - Firebase/Messaging (~> 11.7.0) |     - Firebase/Messaging (~> 11.8.0) | ||||||
|     - firebase_core |     - firebase_core | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|   - FirebaseAnalytics (11.7.0): |   - FirebaseAnalytics (11.8.0): | ||||||
|     - FirebaseAnalytics/AdIdSupport (= 11.7.0) |     - FirebaseAnalytics/AdIdSupport (= 11.8.0) | ||||||
|     - FirebaseCore (~> 11.7.0) |     - FirebaseCore (~> 11.8.0) | ||||||
|     - FirebaseInstallations (~> 11.0) |     - FirebaseInstallations (~> 11.0) | ||||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.0) |     - GoogleUtilities/AppDelegateSwizzler (~> 8.0) | ||||||
|     - GoogleUtilities/MethodSwizzler (~> 8.0) |     - GoogleUtilities/MethodSwizzler (~> 8.0) | ||||||
|     - GoogleUtilities/Network (~> 8.0) |     - GoogleUtilities/Network (~> 8.0) | ||||||
|     - "GoogleUtilities/NSData+zlib (~> 8.0)" |     - "GoogleUtilities/NSData+zlib (~> 8.0)" | ||||||
|     - nanopb (~> 3.30910.0) |     - nanopb (~> 3.30910.0) | ||||||
|   - FirebaseAnalytics/AdIdSupport (11.7.0): |   - FirebaseAnalytics/AdIdSupport (11.8.0): | ||||||
|     - FirebaseCore (~> 11.7.0) |     - FirebaseCore (~> 11.8.0) | ||||||
|     - FirebaseInstallations (~> 11.0) |     - FirebaseInstallations (~> 11.0) | ||||||
|     - GoogleAppMeasurement (= 11.7.0) |     - GoogleAppMeasurement (= 11.8.0) | ||||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.0) |     - GoogleUtilities/AppDelegateSwizzler (~> 8.0) | ||||||
|     - GoogleUtilities/MethodSwizzler (~> 8.0) |     - GoogleUtilities/MethodSwizzler (~> 8.0) | ||||||
|     - GoogleUtilities/Network (~> 8.0) |     - GoogleUtilities/Network (~> 8.0) | ||||||
|     - "GoogleUtilities/NSData+zlib (~> 8.0)" |     - "GoogleUtilities/NSData+zlib (~> 8.0)" | ||||||
|     - nanopb (~> 3.30910.0) |     - nanopb (~> 3.30910.0) | ||||||
|   - FirebaseCore (11.7.0): |   - FirebaseCore (11.8.1): | ||||||
|     - FirebaseCoreInternal (~> 11.7.0) |     - FirebaseCoreInternal (~> 11.8.0) | ||||||
|     - GoogleUtilities/Environment (~> 8.0) |     - GoogleUtilities/Environment (~> 8.0) | ||||||
|     - GoogleUtilities/Logger (~> 8.0) |     - GoogleUtilities/Logger (~> 8.0) | ||||||
|   - FirebaseCoreInternal (11.7.0): |   - FirebaseCoreInternal (11.8.0): | ||||||
|     - "GoogleUtilities/NSData+zlib (~> 8.0)" |     - "GoogleUtilities/NSData+zlib (~> 8.0)" | ||||||
|   - FirebaseInstallations (11.7.0): |   - FirebaseInstallations (11.8.0): | ||||||
|     - FirebaseCore (~> 11.7.0) |     - FirebaseCore (~> 11.8.0) | ||||||
|     - GoogleUtilities/Environment (~> 8.0) |     - GoogleUtilities/Environment (~> 8.0) | ||||||
|     - GoogleUtilities/UserDefaults (~> 8.0) |     - GoogleUtilities/UserDefaults (~> 8.0) | ||||||
|     - PromisesObjC (~> 2.4) |     - PromisesObjC (~> 2.4) | ||||||
|   - FirebaseMessaging (11.7.0): |   - FirebaseMessaging (11.8.0): | ||||||
|     - FirebaseCore (~> 11.7.0) |     - FirebaseCore (~> 11.8.0) | ||||||
|     - FirebaseInstallations (~> 11.0) |     - FirebaseInstallations (~> 11.0) | ||||||
|     - GoogleDataTransport (~> 10.0) |     - GoogleDataTransport (~> 10.0) | ||||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.0) |     - GoogleUtilities/AppDelegateSwizzler (~> 8.0) | ||||||
| @@ -86,21 +86,21 @@ PODS: | |||||||
|   - gal (1.0.0): |   - gal (1.0.0): | ||||||
|     - Flutter |     - Flutter | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|   - GoogleAppMeasurement (11.7.0): |   - GoogleAppMeasurement (11.8.0): | ||||||
|     - GoogleAppMeasurement/AdIdSupport (= 11.7.0) |     - GoogleAppMeasurement/AdIdSupport (= 11.8.0) | ||||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.0) |     - GoogleUtilities/AppDelegateSwizzler (~> 8.0) | ||||||
|     - GoogleUtilities/MethodSwizzler (~> 8.0) |     - GoogleUtilities/MethodSwizzler (~> 8.0) | ||||||
|     - GoogleUtilities/Network (~> 8.0) |     - GoogleUtilities/Network (~> 8.0) | ||||||
|     - "GoogleUtilities/NSData+zlib (~> 8.0)" |     - "GoogleUtilities/NSData+zlib (~> 8.0)" | ||||||
|     - nanopb (~> 3.30910.0) |     - nanopb (~> 3.30910.0) | ||||||
|   - GoogleAppMeasurement/AdIdSupport (11.7.0): |   - GoogleAppMeasurement/AdIdSupport (11.8.0): | ||||||
|     - GoogleAppMeasurement/WithoutAdIdSupport (= 11.7.0) |     - GoogleAppMeasurement/WithoutAdIdSupport (= 11.8.0) | ||||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.0) |     - GoogleUtilities/AppDelegateSwizzler (~> 8.0) | ||||||
|     - GoogleUtilities/MethodSwizzler (~> 8.0) |     - GoogleUtilities/MethodSwizzler (~> 8.0) | ||||||
|     - GoogleUtilities/Network (~> 8.0) |     - GoogleUtilities/Network (~> 8.0) | ||||||
|     - "GoogleUtilities/NSData+zlib (~> 8.0)" |     - "GoogleUtilities/NSData+zlib (~> 8.0)" | ||||||
|     - nanopb (~> 3.30910.0) |     - nanopb (~> 3.30910.0) | ||||||
|   - GoogleAppMeasurement/WithoutAdIdSupport (11.7.0): |   - GoogleAppMeasurement/WithoutAdIdSupport (11.8.0): | ||||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.0) |     - GoogleUtilities/AppDelegateSwizzler (~> 8.0) | ||||||
|     - GoogleUtilities/MethodSwizzler (~> 8.0) |     - GoogleUtilities/MethodSwizzler (~> 8.0) | ||||||
|     - GoogleUtilities/Network (~> 8.0) |     - GoogleUtilities/Network (~> 8.0) | ||||||
| @@ -177,6 +177,25 @@ PODS: | |||||||
|   - sqflite_darwin (0.0.4): |   - sqflite_darwin (0.0.4): | ||||||
|     - Flutter |     - Flutter | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|  |   - sqlite3 (3.49.1): | ||||||
|  |     - sqlite3/common (= 3.49.1) | ||||||
|  |   - sqlite3/common (3.49.1) | ||||||
|  |   - sqlite3/dbstatvtab (3.49.1): | ||||||
|  |     - sqlite3/common | ||||||
|  |   - sqlite3/fts5 (3.49.1): | ||||||
|  |     - sqlite3/common | ||||||
|  |   - sqlite3/perf-threadsafe (3.49.1): | ||||||
|  |     - sqlite3/common | ||||||
|  |   - sqlite3/rtree (3.49.1): | ||||||
|  |     - sqlite3/common | ||||||
|  |   - sqlite3_flutter_libs (0.0.1): | ||||||
|  |     - Flutter | ||||||
|  |     - FlutterMacOS | ||||||
|  |     - sqlite3 (~> 3.49.0) | ||||||
|  |     - sqlite3/dbstatvtab | ||||||
|  |     - sqlite3/fts5 | ||||||
|  |     - sqlite3/perf-threadsafe | ||||||
|  |     - sqlite3/rtree | ||||||
|   - tray_manager (0.0.1): |   - tray_manager (0.0.1): | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|   - url_launcher_macos (0.0.1): |   - url_launcher_macos (0.0.1): | ||||||
| @@ -216,6 +235,7 @@ DEPENDENCIES: | |||||||
|   - share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`) |   - share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`) | ||||||
|   - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) |   - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) | ||||||
|   - sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`) |   - sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`) | ||||||
|  |   - sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin`) | ||||||
|   - tray_manager (from `Flutter/ephemeral/.symlinks/plugins/tray_manager/macos`) |   - tray_manager (from `Flutter/ephemeral/.symlinks/plugins/tray_manager/macos`) | ||||||
|   - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) |   - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) | ||||||
|   - video_compress (from `Flutter/ephemeral/.symlinks/plugins/video_compress/macos`) |   - video_compress (from `Flutter/ephemeral/.symlinks/plugins/video_compress/macos`) | ||||||
| @@ -237,6 +257,7 @@ SPEC REPOS: | |||||||
|     - OrderedSet |     - OrderedSet | ||||||
|     - PromisesObjC |     - PromisesObjC | ||||||
|     - SAMKeychain |     - SAMKeychain | ||||||
|  |     - sqlite3 | ||||||
|     - WebRTC-SDK |     - WebRTC-SDK | ||||||
|  |  | ||||||
| EXTERNAL SOURCES: | EXTERNAL SOURCES: | ||||||
| @@ -296,6 +317,8 @@ EXTERNAL SOURCES: | |||||||
|     :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin |     :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin | ||||||
|   sqflite_darwin: |   sqflite_darwin: | ||||||
|     :path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin |     :path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin | ||||||
|  |   sqlite3_flutter_libs: | ||||||
|  |     :path: Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin | ||||||
|   tray_manager: |   tray_manager: | ||||||
|     :path: Flutter/ephemeral/.symlinks/plugins/tray_manager/macos |     :path: Flutter/ephemeral/.symlinks/plugins/tray_manager/macos | ||||||
|   url_launcher_macos: |   url_launcher_macos: | ||||||
| @@ -313,21 +336,21 @@ SPEC CHECKSUMS: | |||||||
|   file_picker: e716a70a9fe5fd9e09ebc922d7541464289443af |   file_picker: e716a70a9fe5fd9e09ebc922d7541464289443af | ||||||
|   file_saver: 44e6fbf666677faf097302460e214e977fdd977b |   file_saver: 44e6fbf666677faf097302460e214e977fdd977b | ||||||
|   file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d |   file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d | ||||||
|   Firebase: a64bf6a8546e6eab54f1c715cd6151f39d2329f4 |   Firebase: d80354ed7f6df5f9aca55e9eb47cc4b634735eaf | ||||||
|   firebase_analytics: 41d88c024a7756462a803e36236ba74f24cdc2c5 |   firebase_analytics: 1a71372a9735d7046d2c69db848a8d178f9fb587 | ||||||
|   firebase_core: 751d3d919b95d4ae46ab049d0d64d42d4eec086b |   firebase_core: 68e1d27035b096239f147a041643e14e156f1481 | ||||||
|   firebase_messaging: cc174f19945e9541e140e3cb0118448e59b38c6c |   firebase_messaging: 89b5e0e28413dd878a58d2f286cdc03887b5d467 | ||||||
|   FirebaseAnalytics: bc9e565af9044ba8d6c6e4157e4edca8e5fdf7ec |   FirebaseAnalytics: 4fd42def128146e24e480e89f310e3d8534ea42b | ||||||
|   FirebaseCore: 3227e35f4197a924206fbcdc0349325baf4f5de4 |   FirebaseCore: 99fe0c4b44a39f37d99e6404e02009d2db5d718d | ||||||
|   FirebaseCoreInternal: d6c17dafc8dc33614733a8b52df78fcb4394c881 |   FirebaseCoreInternal: df24ce5af28864660ecbd13596fc8dd3a8c34629 | ||||||
|   FirebaseInstallations: 9347e719c3d52d8d7b9074b2c32407dd027305e9 |   FirebaseInstallations: 6c963bd2a86aca0481eef4f48f5a4df783ae5917 | ||||||
|   FirebaseMessaging: 00ece041b71ddb52a2862ffdee73fb6e9824bd0c |   FirebaseMessaging: 487b634ccdf6f7b7ff180fdcb2a9935490f764e8 | ||||||
|   flutter_inappwebview_macos: bdf207b8f4ebd58e86ae06cd96b147de99a67c9b |   flutter_inappwebview_macos: bdf207b8f4ebd58e86ae06cd96b147de99a67c9b | ||||||
|   flutter_udid: 2e7b3da4b5fdfba86a396b97898f5fe8f4ec1a52 |   flutter_udid: 2e7b3da4b5fdfba86a396b97898f5fe8f4ec1a52 | ||||||
|   flutter_webrtc: d55fd3f5c75b42940b6b4b2cf376a5797398d1f8 |   flutter_webrtc: d55fd3f5c75b42940b6b4b2cf376a5797398d1f8 | ||||||
|   FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 |   FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 | ||||||
|   gal: 6a522c75909f1244732d4596d11d6a2f86ff37a5 |   gal: 6a522c75909f1244732d4596d11d6a2f86ff37a5 | ||||||
|   GoogleAppMeasurement: 0471a5b5bff51f3a91b1e76df22c952d04c63967 |   GoogleAppMeasurement: fc0817122bd4d4189164f85374e06773b9561896 | ||||||
|   GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 |   GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 | ||||||
|   GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d |   GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d | ||||||
|   HotKey: 400beb7caa29054ea8d864c96f5ba7e5b4852277 |   HotKey: 400beb7caa29054ea8d864c96f5ba7e5b4852277 | ||||||
| @@ -348,6 +371,8 @@ SPEC CHECKSUMS: | |||||||
|   share_plus: 1fa619de8392a4398bfaf176d441853922614e89 |   share_plus: 1fa619de8392a4398bfaf176d441853922614e89 | ||||||
|   shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 |   shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 | ||||||
|   sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d |   sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d | ||||||
|  |   sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983 | ||||||
|  |   sqlite3_flutter_libs: 069c435986dd4b63461aecd68f4b30be4a9e9daa | ||||||
|   tray_manager: 9064e219c56d75c476e46b9a21182087930baf90 |   tray_manager: 9064e219c56d75c476e46b9a21182087930baf90 | ||||||
|   url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404 |   url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404 | ||||||
|   video_compress: c896234f100791b5fef7f049afa38f6d2ef7b42f |   video_compress: c896234f100791b5fef7f049afa38f6d2ef7b42f | ||||||
|   | |||||||
| @@ -59,6 +59,7 @@ | |||||||
|       ignoresPersistentStateOnLaunch = "NO" |       ignoresPersistentStateOnLaunch = "NO" | ||||||
|       debugDocumentVersioning = "YES" |       debugDocumentVersioning = "YES" | ||||||
|       debugServiceExtension = "internal" |       debugServiceExtension = "internal" | ||||||
|  |       enableGPUValidationMode = "1" | ||||||
|       allowLocationSimulation = "YES"> |       allowLocationSimulation = "YES"> | ||||||
|       <BuildableProductRunnable |       <BuildableProductRunnable | ||||||
|          runnableDebuggingMode = "0"> |          runnableDebuggingMode = "0"> | ||||||
|   | |||||||
							
								
								
									
										347
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										347
									
								
								pubspec.lock
									
									
									
									
									
								
							| @@ -5,31 +5,26 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: _fe_analyzer_shared |       name: _fe_analyzer_shared | ||||||
|       sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" |       sha256: dc27559385e905ad30838356c5f5d574014ba39872d732111cd07ac0beff4c57 | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "76.0.0" |     version: "80.0.0" | ||||||
|   _flutterfire_internals: |   _flutterfire_internals: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: _flutterfire_internals |       name: _flutterfire_internals | ||||||
|       sha256: e051259913915ea5bc8fe18664596bea08592fd123930605d562969cd7315fcd |       sha256: "401dd18096f5eaa140404ccbbbf346f83c850e6f27049698a7ee75a3488ddb32" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.3.51" |     version: "1.3.52" | ||||||
|   _macros: |  | ||||||
|     dependency: transitive |  | ||||||
|     description: dart |  | ||||||
|     source: sdk |  | ||||||
|     version: "0.3.3" |  | ||||||
|   analyzer: |   analyzer: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: analyzer |       name: analyzer | ||||||
|       sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" |       sha256: "192d1c5b944e7e53b24b5586db760db934b177d4147c42fbca8c8c5f1eb8d11e" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "6.11.0" |     version: "7.3.0" | ||||||
|   animations: |   animations: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
| @@ -50,10 +45,10 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: archive |       name: archive | ||||||
|       sha256: "6199c74e3db4fbfbd04f66d739e72fe11c8a8957d5f219f1f4482dbde6420b5a" |       sha256: "528579c7e4579719f04b21eeeeddfd73a18b31dabc22766893b7d1be7f49b967" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "4.0.2" |     version: "4.0.3" | ||||||
|   args: |   args: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -66,10 +61,10 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: async |       name: async | ||||||
|       sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" |       sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.11.0" |     version: "2.12.0" | ||||||
|   bitsdojo_window: |   bitsdojo_window: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
| @@ -114,10 +109,10 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: boolean_selector |       name: boolean_selector | ||||||
|       sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" |       sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.1.1" |     version: "2.1.2" | ||||||
|   build: |   build: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -191,7 +186,7 @@ packages: | |||||||
|     source: hosted |     source: hosted | ||||||
|     version: "3.4.1" |     version: "3.4.1" | ||||||
|   cached_network_image_platform_interface: |   cached_network_image_platform_interface: | ||||||
|     dependency: transitive |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: cached_network_image_platform_interface |       name: cached_network_image_platform_interface | ||||||
|       sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" |       sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" | ||||||
| @@ -226,10 +221,18 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: characters |       name: characters | ||||||
|       sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" |       sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.3.0" |     version: "1.4.0" | ||||||
|  |   charcode: | ||||||
|  |     dependency: transitive | ||||||
|  |     description: | ||||||
|  |       name: charcode | ||||||
|  |       sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "1.4.0" | ||||||
|   checked_yaml: |   checked_yaml: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -250,10 +253,10 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: clock |       name: clock | ||||||
|       sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf |       sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.1.1" |     version: "1.1.2" | ||||||
|   code_builder: |   code_builder: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -266,10 +269,10 @@ packages: | |||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: collection |       name: collection | ||||||
|       sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf |       sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.19.0" |     version: "1.19.1" | ||||||
|   connectivity_plus: |   connectivity_plus: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -346,18 +349,18 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: dart_style |       name: dart_style | ||||||
|       sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820" |       sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.3.8" |     version: "3.0.1" | ||||||
|   dart_webrtc: |   dart_webrtc: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: dart_webrtc |       name: dart_webrtc | ||||||
|       sha256: "03df5b41b23bc185ebcf4b0ffc92d002e295bf56287fb5f9d2c321ddaf7760cc" |       sha256: b34e90bc82f33c1023cf98661369c37bccd648c8a4cf882a875d9f5d8bbef694 | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.5.1" |     version: "1.5.2+hotfix.1" | ||||||
|   dbus: |   dbus: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -414,6 +417,30 @@ packages: | |||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.0.2" |     version: "1.0.2" | ||||||
|  |   drift: | ||||||
|  |     dependency: "direct main" | ||||||
|  |     description: | ||||||
|  |       name: drift | ||||||
|  |       sha256: "97d5832657d49f26e7a8e07de397ddc63790b039372878d5117af816d0fdb5cb" | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "2.25.1" | ||||||
|  |   drift_dev: | ||||||
|  |     dependency: "direct dev" | ||||||
|  |     description: | ||||||
|  |       name: drift_dev | ||||||
|  |       sha256: f1db88482dbb016b9bbddddf746d5d0a6938b156ff20e07320052981f97388cc | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "2.25.2" | ||||||
|  |   drift_flutter: | ||||||
|  |     dependency: "direct main" | ||||||
|  |     description: | ||||||
|  |       name: drift_flutter | ||||||
|  |       sha256: "0cadbf3b8733409a6cf61d18ba2e94e149df81df7de26f48ae0695b48fd71922" | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "0.2.4" | ||||||
|   dropdown_button2: |   dropdown_button2: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
| @@ -474,18 +501,18 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: fake_async |       name: fake_async | ||||||
|       sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" |       sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.3.1" |     version: "1.3.2" | ||||||
|   ffi: |   ffi: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: ffi |       name: ffi | ||||||
|       sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" |       sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.1.3" |     version: "2.1.4" | ||||||
|   file: |   file: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -498,10 +525,10 @@ packages: | |||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: file_picker |       name: file_picker | ||||||
|       sha256: ab13ae8ef5580a411c458d6207b6774a6c237d77ac37011b13994879f68a8810 |       sha256: "6f6bfa8797f296965bdc3e1f702574ab49a540c19b9237b401e7c2b25dfe594c" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "8.3.7" |     version: "9.0.0" | ||||||
|   file_saver: |   file_saver: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
| @@ -546,34 +573,34 @@ packages: | |||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: firebase_analytics |       name: firebase_analytics | ||||||
|       sha256: "47428047a0778f72af53a3c7cb5d556e1cb25e2327cc8aa40d544971dc6245b2" |       sha256: "6abce50b79729d8a13c3d4ae05ac612d5ef2f57394330bc5e581ca0e762325f4" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "11.4.2" |     version: "11.4.3" | ||||||
|   firebase_analytics_platform_interface: |   firebase_analytics_platform_interface: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: firebase_analytics_platform_interface |       name: firebase_analytics_platform_interface | ||||||
|       sha256: "1076f4b041f76143e14878c70f0758f17fe5910c0cd992db9e93bd3c3584512b" |       sha256: cd9ae65870bf23ab7e63a04fe9c1b38522fd3556a8c32288afd3f5cb10d4b8f4 | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "4.3.2" |     version: "4.3.3" | ||||||
|   firebase_analytics_web: |   firebase_analytics_web: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: firebase_analytics_web |       name: firebase_analytics_web | ||||||
|       sha256: "8f6dd64ea6d28b7f5b9e739d183a9e1c7f17027794a3e9aba1879621d42426ef" |       sha256: "5654ed7e39d7a8099e60748924327159785512d78d913e965f9ca93c533af910" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "0.5.10+8" |     version: "0.5.10+9" | ||||||
|   firebase_core: |   firebase_core: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: firebase_core |       name: firebase_core | ||||||
|       sha256: "93dc4dd12f9b02c5767f235307f609e61ed9211047132d07f9e02c668f0bfc33" |       sha256: "6a4ea0f1d533443c8afc3d809cd36a4e2b8f2e2e711f697974f55bb31d71d1b8" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "3.11.0" |     version: "3.12.0" | ||||||
|   firebase_core_platform_interface: |   firebase_core_platform_interface: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -586,34 +613,34 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: firebase_core_web |       name: firebase_core_web | ||||||
|       sha256: "0e13c80f0de8acaa5d0519cbe23c8b4cc138a2d5d508b5755c861bdfc9762678" |       sha256: e47f5c2776de018fa19bc9f6f723df136bc75cdb164d64b65305babd715c8e41 | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.20.0" |     version: "2.21.0" | ||||||
|   firebase_messaging: |   firebase_messaging: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: firebase_messaging |       name: firebase_messaging | ||||||
|       sha256: "3dee3b0cbfe719e64773cb7d1cad57c58b2235a8c136f5715fe733a54058c783" |       sha256: "8755a083a20bac4485e8b46d223f6f2eab34e659a76a75f8cf3cded53bc98a15" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "15.2.2" |     version: "15.2.3" | ||||||
|   firebase_messaging_platform_interface: |   firebase_messaging_platform_interface: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: firebase_messaging_platform_interface |       name: firebase_messaging_platform_interface | ||||||
|       sha256: e9ea726b9bb864fc6223bb66422bd9877b9973ae51967754a769b0d01e201c1e |       sha256: "8cc771079677460de53ad8fcca5bc3074d58c5fc4f9d89b19585e5bfd9c64292" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "4.6.2" |     version: "4.6.3" | ||||||
|   firebase_messaging_web: |   firebase_messaging_web: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: firebase_messaging_web |       name: firebase_messaging_web | ||||||
|       sha256: "5f7b40e8bf861a37f8b8196e347d8a919750421a45f0b45d1bb74e98fa72726e" |       sha256: caa73059b0396c97f691683c4cfc3f897c8543801579b7dd4851c431d8e4e091 | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "3.10.2" |     version: "3.10.3" | ||||||
|   fixnum: |   fixnum: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -780,10 +807,10 @@ packages: | |||||||
|     dependency: "direct dev" |     dependency: "direct dev" | ||||||
|     description: |     description: | ||||||
|       name: flutter_native_splash |       name: flutter_native_splash | ||||||
|       sha256: "7062602e0dbd29141fb8eb19220b5871ca650be5197ab9c1f193a28b17537bc7" |       sha256: edb09c35ee9230c4b03f13dd45bb3a276d0801865f0a4650b7e2a3bba61a803a | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.4.4" |     version: "2.4.5" | ||||||
|   flutter_plugin_android_lifecycle: |   flutter_plugin_android_lifecycle: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -838,18 +865,18 @@ packages: | |||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: flutter_webrtc |       name: flutter_webrtc | ||||||
|       sha256: "572df3de6c828e571db4b75b4a96a15c2f34fa3d420a84438f44a3158b22e81a" |       sha256: "6ea3a86d95b61cfe42d5715426d355b3cece6c88d0119de428d56f6c653811ce" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "0.12.9" |     version: "0.12.11" | ||||||
|   freezed: |   freezed: | ||||||
|     dependency: "direct dev" |     dependency: "direct dev" | ||||||
|     description: |     description: | ||||||
|       name: freezed |       name: freezed | ||||||
|       sha256: "44c19278dd9d89292cf46e97dc0c1e52ce03275f40a97c5a348e802a924bf40e" |       sha256: "59a584c24b3acdc5250bb856d0d3e9c0b798ed14a4af1ddb7dc1c7b41df91c9c" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.5.7" |     version: "2.5.8" | ||||||
|   freezed_annotation: |   freezed_annotation: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
| @@ -914,30 +941,6 @@ packages: | |||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.3.2" |     version: "2.3.2" | ||||||
|   hive: |  | ||||||
|     dependency: "direct main" |  | ||||||
|     description: |  | ||||||
|       name: hive |  | ||||||
|       sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" |  | ||||||
|       url: "https://pub.dev" |  | ||||||
|     source: hosted |  | ||||||
|     version: "2.2.3" |  | ||||||
|   hive_flutter: |  | ||||||
|     dependency: "direct main" |  | ||||||
|     description: |  | ||||||
|       name: hive_flutter |  | ||||||
|       sha256: dca1da446b1d808a51689fb5d0c6c9510c0a2ba01e22805d492c73b68e33eecc |  | ||||||
|       url: "https://pub.dev" |  | ||||||
|     source: hosted |  | ||||||
|     version: "1.1.0" |  | ||||||
|   hive_generator: |  | ||||||
|     dependency: "direct dev" |  | ||||||
|     description: |  | ||||||
|       name: hive_generator |  | ||||||
|       sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4" |  | ||||||
|       url: "https://pub.dev" |  | ||||||
|     source: hosted |  | ||||||
|     version: "2.0.1" |  | ||||||
|   home_widget: |   home_widget: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
| @@ -1030,10 +1033,10 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: image |       name: image | ||||||
|       sha256: "8346ad4b5173924b5ddddab782fc7d8a6300178c8b1dc427775405a01701c4a6" |       sha256: "13d3349ace88f12f4a0d175eb5c12dcdd39d35c4c109a8a13dfeb6d0bd9e31c3" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "4.5.2" |     version: "4.5.3" | ||||||
|   image_picker: |   image_picker: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
| @@ -1046,10 +1049,10 @@ packages: | |||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: image_picker_android |       name: image_picker_android | ||||||
|       sha256: b62d34a506e12bb965e824b6db4fbf709ee4589cf5d3e99b45ab2287b008ee0c |       sha256: "82652a75e3dd667a91187769a6a2cc81bd8c111bbead698d8e938d2b63e5e89a" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "0.8.12+20" |     version: "0.8.12+21" | ||||||
|   image_picker_for_web: |   image_picker_for_web: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -1083,7 +1086,7 @@ packages: | |||||||
|     source: hosted |     source: hosted | ||||||
|     version: "0.2.1+2" |     version: "0.2.1+2" | ||||||
|   image_picker_platform_interface: |   image_picker_platform_interface: | ||||||
|     dependency: transitive |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: image_picker_platform_interface |       name: image_picker_platform_interface | ||||||
|       sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0" |       sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0" | ||||||
| @@ -1150,26 +1153,26 @@ packages: | |||||||
|     dependency: "direct dev" |     dependency: "direct dev" | ||||||
|     description: |     description: | ||||||
|       name: json_serializable |       name: json_serializable | ||||||
|       sha256: c2fcb3920cf2b6ae6845954186420fca40bc0a8abcc84903b7801f17d7050d7c |       sha256: "81f04dee10969f89f604e1249382d46b97a1ccad53872875369622b5bfc9e58a" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "6.9.0" |     version: "6.9.4" | ||||||
|   leak_tracker: |   leak_tracker: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: leak_tracker |       name: leak_tracker | ||||||
|       sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" |       sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "10.0.7" |     version: "10.0.8" | ||||||
|   leak_tracker_flutter_testing: |   leak_tracker_flutter_testing: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: leak_tracker_flutter_testing |       name: leak_tracker_flutter_testing | ||||||
|       sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" |       sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "3.0.8" |     version: "3.0.9" | ||||||
|   leak_tracker_testing: |   leak_tracker_testing: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -1198,10 +1201,18 @@ packages: | |||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: livekit_client |       name: livekit_client | ||||||
|       sha256: "0cfb2f48eff7a93ea8927696dc6f218aebd2fcd1fcc1b1a7b2f53ff3597fdb52" |       sha256: "753bbf484c6b70f10f3dc1dc808dfe3755f472d80eb9682323cff07ad8e2609d" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.3.6" |     version: "2.4.0" | ||||||
|  |   local_notifier: | ||||||
|  |     dependency: "direct main" | ||||||
|  |     description: | ||||||
|  |       name: local_notifier | ||||||
|  |       sha256: f6cfc933c6fbc961f4e52b5c880f68e41b2d3cd29aad557cc654fd211093a025 | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "0.1.6" | ||||||
|   logging: |   logging: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -1210,14 +1221,6 @@ packages: | |||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.3.0" |     version: "1.3.0" | ||||||
|   macros: |  | ||||||
|     dependency: transitive |  | ||||||
|     description: |  | ||||||
|       name: macros |  | ||||||
|       sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" |  | ||||||
|       url: "https://pub.dev" |  | ||||||
|     source: hosted |  | ||||||
|     version: "0.1.3-main.0" |  | ||||||
|   markdown: |   markdown: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
| @@ -1238,10 +1241,10 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: matcher |       name: matcher | ||||||
|       sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb |       sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "0.12.16+1" |     version: "0.12.17" | ||||||
|   material_color_utilities: |   material_color_utilities: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -1342,10 +1345,10 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: meta |       name: meta | ||||||
|       sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 |       sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.15.0" |     version: "1.16.0" | ||||||
|   mime: |   mime: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
| @@ -1414,10 +1417,10 @@ packages: | |||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: path |       name: path | ||||||
|       sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" |       sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.9.0" |     version: "1.9.1" | ||||||
|   path_parsing: |   path_parsing: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -1486,26 +1489,26 @@ packages: | |||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: permission_handler |       name: permission_handler | ||||||
|       sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb" |       sha256: "59adad729136f01ea9e35a48f5d1395e25cba6cea552249ddbe9cf950f5d7849" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "11.3.1" |     version: "11.4.0" | ||||||
|   permission_handler_android: |   permission_handler_android: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: permission_handler_android |       name: permission_handler_android | ||||||
|       sha256: "71bbecfee799e65aff7c744761a57e817e73b738fedf62ab7afd5593da21f9f1" |       sha256: d3971dcdd76182a0c198c096b5db2f0884b0d4196723d21a866fc4cdea057ebc | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "12.0.13" |     version: "12.1.0" | ||||||
|   permission_handler_apple: |   permission_handler_apple: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: permission_handler_apple |       name: permission_handler_apple | ||||||
|       sha256: e6f6d73b12438ef13e648c4ae56bd106ec60d17e90a59c4545db6781229082a0 |       sha256: f84a188e79a35c687c132a0a0556c254747a08561e99ab933f12f6ca71ef3c98 | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "9.4.5" |     version: "9.4.6" | ||||||
|   permission_handler_html: |   permission_handler_html: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -1518,10 +1521,10 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: permission_handler_platform_interface |       name: permission_handler_platform_interface | ||||||
|       sha256: e9c8eadee926c4532d0305dff94b85bf961f16759c3af791486613152af4b4f9 |       sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "4.2.3" |     version: "4.3.0" | ||||||
|   permission_handler_windows: |   permission_handler_windows: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -1534,10 +1537,10 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: petitparser |       name: petitparser | ||||||
|       sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 |       sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "6.0.2" |     version: "6.1.0" | ||||||
|   photo_view: |   photo_view: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
| @@ -1554,14 +1557,6 @@ packages: | |||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "3.1.6" |     version: "3.1.6" | ||||||
|   platform_detect: |  | ||||||
|     dependency: transitive |  | ||||||
|     description: |  | ||||||
|       name: platform_detect |  | ||||||
|       sha256: a62f99417fc4fa2d099ce0ccdbb1bd3977920f2a64292c326271f049d4bc3a4f |  | ||||||
|       url: "https://pub.dev" |  | ||||||
|     source: hosted |  | ||||||
|     version: "2.1.0" |  | ||||||
|   plugin_platform_interface: |   plugin_platform_interface: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -1642,6 +1637,14 @@ packages: | |||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "4.1.0" |     version: "4.1.0" | ||||||
|  |   recase: | ||||||
|  |     dependency: transitive | ||||||
|  |     description: | ||||||
|  |       name: recase | ||||||
|  |       sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213 | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "4.1.0" | ||||||
|   receive_sharing_intent: |   receive_sharing_intent: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
| @@ -1774,10 +1777,10 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: shared_preferences_android |       name: shared_preferences_android | ||||||
|       sha256: ea86be7b7114f9e94fddfbb52649e59a03d6627ccd2387ebddcd6624719e9f16 |       sha256: a768fc8ede5f0c8e6150476e14f38e2417c0864ca36bb4582be8e21925a03c22 | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.4.5" |     version: "2.4.6" | ||||||
|   shared_preferences_foundation: |   shared_preferences_foundation: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -1806,10 +1809,10 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: shared_preferences_web |       name: shared_preferences_web | ||||||
|       sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e |       sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.4.2" |     version: "2.4.3" | ||||||
|   shared_preferences_windows: |   shared_preferences_windows: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -1867,10 +1870,10 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: source_gen |       name: source_gen | ||||||
|       sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" |       sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.5.0" |     version: "2.0.0" | ||||||
|   source_helper: |   source_helper: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -1883,10 +1886,10 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: source_span |       name: source_span | ||||||
|       sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" |       sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.10.0" |     version: "1.10.1" | ||||||
|   sprintf: |   sprintf: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -1899,34 +1902,34 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: sqflite |       name: sqflite | ||||||
|       sha256: "2d7299468485dca85efeeadf5d38986909c5eb0cd71fd3db2c2f000e6c9454bb" |       sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.4.1" |     version: "2.4.2" | ||||||
|   sqflite_android: |   sqflite_android: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: sqflite_android |       name: sqflite_android | ||||||
|       sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3" |       sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.4.0" |     version: "2.4.1" | ||||||
|   sqflite_common: |   sqflite_common: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: sqflite_common |       name: sqflite_common | ||||||
|       sha256: "761b9740ecbd4d3e66b8916d784e581861fd3c3553eda85e167bc49fdb68f709" |       sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.5.4+6" |     version: "2.5.5" | ||||||
|   sqflite_darwin: |   sqflite_darwin: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: sqflite_darwin |       name: sqflite_darwin | ||||||
|       sha256: "22adfd9a2c7d634041e96d6241e6e1c8138ca6817018afc5d443fef91dcefa9c" |       sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.4.1+1" |     version: "2.4.2" | ||||||
|   sqflite_platform_interface: |   sqflite_platform_interface: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -1935,22 +1938,46 @@ packages: | |||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.4.0" |     version: "2.4.0" | ||||||
|  |   sqlite3: | ||||||
|  |     dependency: transitive | ||||||
|  |     description: | ||||||
|  |       name: sqlite3 | ||||||
|  |       sha256: "32b632dda27d664f85520093ed6f735ae5c49b5b75345afb8b19411bc59bb53d" | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "2.7.4" | ||||||
|  |   sqlite3_flutter_libs: | ||||||
|  |     dependency: transitive | ||||||
|  |     description: | ||||||
|  |       name: sqlite3_flutter_libs | ||||||
|  |       sha256: "57fafacd815c981735406215966ff7caaa8eab984b094f52e692accefcbd9233" | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "0.5.30" | ||||||
|  |   sqlparser: | ||||||
|  |     dependency: transitive | ||||||
|  |     description: | ||||||
|  |       name: sqlparser | ||||||
|  |       sha256: "27dd0a9f0c02e22ac0eb42a23df9ea079ce69b52bb4a3b478d64e0ef34a263ee" | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "0.41.0" | ||||||
|   stack_trace: |   stack_trace: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: stack_trace |       name: stack_trace | ||||||
|       sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" |       sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.12.0" |     version: "1.12.1" | ||||||
|   stream_channel: |   stream_channel: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: stream_channel |       name: stream_channel | ||||||
|       sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 |       sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.1.2" |     version: "2.1.4" | ||||||
|   stream_transform: |   stream_transform: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -1963,10 +1990,10 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: string_scanner |       name: string_scanner | ||||||
|       sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" |       sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.3.0" |     version: "1.4.1" | ||||||
|   styled_widget: |   styled_widget: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
| @@ -1987,26 +2014,26 @@ packages: | |||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: synchronized |       name: synchronized | ||||||
|       sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" |       sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "3.3.0+3" |     version: "3.3.1" | ||||||
|   term_glyph: |   term_glyph: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: term_glyph |       name: term_glyph | ||||||
|       sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 |       sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.2.1" |     version: "1.2.2" | ||||||
|   test_api: |   test_api: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: test_api |       name: test_api | ||||||
|       sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" |       sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "0.7.3" |     version: "0.7.4" | ||||||
|   timing: |   timing: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -2195,10 +2222,10 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: vm_service |       name: vm_service | ||||||
|       sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b |       sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "14.3.0" |     version: "14.3.1" | ||||||
|   volume_controller: |   volume_controller: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -2259,18 +2286,18 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: webrtc_interface |       name: webrtc_interface | ||||||
|       sha256: "10fc6dc0ac16f909f5e434c18902415211d759313c87261f1e4ec5b4f6a04c26" |       sha256: e05f00091c9c70a15bab4ccb1b6c46d9a16a6075002f02cfac3641eccb05e25d | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.2.1" |     version: "1.2.1+hotfix.1" | ||||||
|   win32: |   win32: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: win32 |       name: win32 | ||||||
|       sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e |       sha256: b89e6e24d1454e149ab20fbb225af58660f0c0bf4475544650700d8e2da54aef | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "5.10.1" |     version: "5.11.0" | ||||||
|   win32_registry: |   win32_registry: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -2312,5 +2339,5 @@ packages: | |||||||
|     source: hosted |     source: hosted | ||||||
|     version: "3.1.3" |     version: "3.1.3" | ||||||
| sdks: | sdks: | ||||||
|   dart: ">=3.6.0 <4.0.0" |   dart: ">=3.7.0 <4.0.0" | ||||||
|   flutter: ">=3.27.0" |   flutter: ">=3.27.0" | ||||||
|   | |||||||
							
								
								
									
										15
									
								
								pubspec.yaml
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								pubspec.yaml
									
									
									
									
									
								
							| @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev | |||||||
| # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html | ||||||
| # In Windows, build-name is used as the major, minor, and patch parts | # In Windows, build-name is used as the major, minor, and patch parts | ||||||
| # of the product and file versions while build-number is used as the build suffix. | # of the product and file versions while build-number is used as the build suffix. | ||||||
| version: 2.3.2+70 | version: 2.3.2+71 | ||||||
|  |  | ||||||
| environment: | environment: | ||||||
|   sdk: ^3.5.4 |   sdk: ^3.5.4 | ||||||
| @@ -59,7 +59,7 @@ dependencies: | |||||||
|   relative_time: ^5.0.0 |   relative_time: ^5.0.0 | ||||||
|   image_picker: ^1.1.2 |   image_picker: ^1.1.2 | ||||||
|   cross_file: ^0.3.4+2 |   cross_file: ^0.3.4+2 | ||||||
|   file_picker: ^8.1.6 # pinned due to compile failed on android, https://github.com/miguelpruivo/flutter_file_picker/issues/1643 |   file_picker: ^9.0.0 # pinned due to compile failed on android, https://github.com/miguelpruivo/flutter_file_picker/issues/1643 | ||||||
|   croppy: ^1.3.1 |   croppy: ^1.3.1 | ||||||
|   flutter_expandable_fab: ^2.3.0 |   flutter_expandable_fab: ^2.3.0 | ||||||
|   dropdown_button2: ^2.3.9 |   dropdown_button2: ^2.3.9 | ||||||
| @@ -72,8 +72,6 @@ dependencies: | |||||||
|   collection: ^1.18.0 |   collection: ^1.18.0 | ||||||
|   mime: ^2.0.0 |   mime: ^2.0.0 | ||||||
|   web_socket_channel: ^3.0.1 |   web_socket_channel: ^3.0.1 | ||||||
|   hive: ^2.2.3 |  | ||||||
|   hive_flutter: ^1.1.0 |  | ||||||
|   swipe_to: ^1.0.6 |   swipe_to: ^1.0.6 | ||||||
|   firebase_core: ^3.8.0 |   firebase_core: ^3.8.0 | ||||||
|   firebase_messaging: ^15.1.5 |   firebase_messaging: ^15.1.5 | ||||||
| @@ -121,6 +119,11 @@ dependencies: | |||||||
|   tray_manager: ^0.3.2 |   tray_manager: ^0.3.2 | ||||||
|   hotkey_manager: ^0.2.3 |   hotkey_manager: ^0.2.3 | ||||||
|   image_picker_android: ^0.8.12+20 |   image_picker_android: ^0.8.12+20 | ||||||
|  |   cached_network_image_platform_interface: ^4.1.1 | ||||||
|  |   image_picker_platform_interface: ^2.10.1 | ||||||
|  |   drift: ^2.25.1 | ||||||
|  |   drift_flutter: ^0.2.4 | ||||||
|  |   local_notifier: ^0.1.6 | ||||||
|  |  | ||||||
| dev_dependencies: | dev_dependencies: | ||||||
|   flutter_test: |   flutter_test: | ||||||
| @@ -132,13 +135,13 @@ dev_dependencies: | |||||||
|   # package. See that file for information about deactivating specific lint |   # package. See that file for information about deactivating specific lint | ||||||
|   # rules and activating additional ones. |   # rules and activating additional ones. | ||||||
|   flutter_lints: ^5.0.0 |   flutter_lints: ^5.0.0 | ||||||
|   build_runner: ^2.4.13 |   build_runner: ^2.4.15 | ||||||
|   freezed: ^2.5.7 |   freezed: ^2.5.7 | ||||||
|   json_serializable: ^6.8.0 |   json_serializable: ^6.8.0 | ||||||
|   icons_launcher: ^3.0.0 |   icons_launcher: ^3.0.0 | ||||||
|   flutter_native_splash: ^2.4.2 |   flutter_native_splash: ^2.4.2 | ||||||
|   hive_generator: ^2.0.1 |  | ||||||
|   flutter_launcher_icons: ^0.14.1 |   flutter_launcher_icons: ^0.14.1 | ||||||
|  |   drift_dev: ^2.25.2 | ||||||
|  |  | ||||||
| # For information on the generic Dart part of this file, see the | # For information on the generic Dart part of this file, see the | ||||||
| # following page: https://dart.dev/tools/pub/pubspec | # following page: https://dart.dev/tools/pub/pubspec | ||||||
|   | |||||||
							
								
								
									
										13631
									
								
								web/drift_worker.dart.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13631
									
								
								web/drift_worker.dart.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -1,11 +1,11 @@ | |||||||
| { | { | ||||||
|     "name": "surface", |     "name": "Solar Network", | ||||||
|     "short_name": "surface", |     "short_name": "Solian", | ||||||
|     "start_url": ".", |     "start_url": ".", | ||||||
|     "display": "standalone", |     "display": "standalone", | ||||||
|     "background_color": "#ffffff", |     "background_color": "#ffffff", | ||||||
|     "theme_color": "#ffffff", |     "theme_color": "#ffffff", | ||||||
|     "description": "A new Flutter project.", |     "description": "The Solar Network is a social network app.", | ||||||
|     "orientation": "portrait-primary", |     "orientation": "portrait-primary", | ||||||
|     "prefer_related_applications": false, |     "prefer_related_applications": false, | ||||||
|     "icons": [ |     "icons": [ | ||||||
|   | |||||||
							
								
								
									
										
											BIN
										
									
								
								web/sqlite3.wasm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								web/sqlite3.wasm
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -17,12 +17,14 @@ | |||||||
| #include <gal/gal_plugin_c_api.h> | #include <gal/gal_plugin_c_api.h> | ||||||
| #include <hotkey_manager_windows/hotkey_manager_windows_plugin_c_api.h> | #include <hotkey_manager_windows/hotkey_manager_windows_plugin_c_api.h> | ||||||
| #include <livekit_client/live_kit_plugin.h> | #include <livekit_client/live_kit_plugin.h> | ||||||
|  | #include <local_notifier/local_notifier_plugin.h> | ||||||
| #include <media_kit_libs_windows_video/media_kit_libs_windows_video_plugin_c_api.h> | #include <media_kit_libs_windows_video/media_kit_libs_windows_video_plugin_c_api.h> | ||||||
| #include <media_kit_video/media_kit_video_plugin_c_api.h> | #include <media_kit_video/media_kit_video_plugin_c_api.h> | ||||||
| #include <pasteboard/pasteboard_plugin.h> | #include <pasteboard/pasteboard_plugin.h> | ||||||
| #include <permission_handler_windows/permission_handler_windows_plugin.h> | #include <permission_handler_windows/permission_handler_windows_plugin.h> | ||||||
| #include <screen_brightness_windows/screen_brightness_windows_plugin.h> | #include <screen_brightness_windows/screen_brightness_windows_plugin.h> | ||||||
| #include <share_plus/share_plus_windows_plugin_c_api.h> | #include <share_plus/share_plus_windows_plugin_c_api.h> | ||||||
|  | #include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h> | ||||||
| #include <tray_manager/tray_manager_plugin.h> | #include <tray_manager/tray_manager_plugin.h> | ||||||
| #include <url_launcher_windows/url_launcher_windows.h> | #include <url_launcher_windows/url_launcher_windows.h> | ||||||
|  |  | ||||||
| @@ -49,6 +51,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { | |||||||
|       registry->GetRegistrarForPlugin("HotkeyManagerWindowsPluginCApi")); |       registry->GetRegistrarForPlugin("HotkeyManagerWindowsPluginCApi")); | ||||||
|   LiveKitPluginRegisterWithRegistrar( |   LiveKitPluginRegisterWithRegistrar( | ||||||
|       registry->GetRegistrarForPlugin("LiveKitPlugin")); |       registry->GetRegistrarForPlugin("LiveKitPlugin")); | ||||||
|  |   LocalNotifierPluginRegisterWithRegistrar( | ||||||
|  |       registry->GetRegistrarForPlugin("LocalNotifierPlugin")); | ||||||
|   MediaKitLibsWindowsVideoPluginCApiRegisterWithRegistrar( |   MediaKitLibsWindowsVideoPluginCApiRegisterWithRegistrar( | ||||||
|       registry->GetRegistrarForPlugin("MediaKitLibsWindowsVideoPluginCApi")); |       registry->GetRegistrarForPlugin("MediaKitLibsWindowsVideoPluginCApi")); | ||||||
|   MediaKitVideoPluginCApiRegisterWithRegistrar( |   MediaKitVideoPluginCApiRegisterWithRegistrar( | ||||||
| @@ -61,6 +65,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { | |||||||
|       registry->GetRegistrarForPlugin("ScreenBrightnessWindowsPlugin")); |       registry->GetRegistrarForPlugin("ScreenBrightnessWindowsPlugin")); | ||||||
|   SharePlusWindowsPluginCApiRegisterWithRegistrar( |   SharePlusWindowsPluginCApiRegisterWithRegistrar( | ||||||
|       registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); |       registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); | ||||||
|  |   Sqlite3FlutterLibsPluginRegisterWithRegistrar( | ||||||
|  |       registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin")); | ||||||
|   TrayManagerPluginRegisterWithRegistrar( |   TrayManagerPluginRegisterWithRegistrar( | ||||||
|       registry->GetRegistrarForPlugin("TrayManagerPlugin")); |       registry->GetRegistrarForPlugin("TrayManagerPlugin")); | ||||||
|   UrlLauncherWindowsRegisterWithRegistrar( |   UrlLauncherWindowsRegisterWithRegistrar( | ||||||
|   | |||||||
| @@ -14,12 +14,14 @@ list(APPEND FLUTTER_PLUGIN_LIST | |||||||
|   gal |   gal | ||||||
|   hotkey_manager_windows |   hotkey_manager_windows | ||||||
|   livekit_client |   livekit_client | ||||||
|  |   local_notifier | ||||||
|   media_kit_libs_windows_video |   media_kit_libs_windows_video | ||||||
|   media_kit_video |   media_kit_video | ||||||
|   pasteboard |   pasteboard | ||||||
|   permission_handler_windows |   permission_handler_windows | ||||||
|   screen_brightness_windows |   screen_brightness_windows | ||||||
|   share_plus |   share_plus | ||||||
|  |   sqlite3_flutter_libs | ||||||
|   tray_manager |   tray_manager | ||||||
|   url_launcher_windows |   url_launcher_windows | ||||||
| ) | ) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user