Compare commits
	
		
			67 Commits
		
	
	
		
			2.2.1+42
			...
			cbd1eaf1af
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| cbd1eaf1af | |||
| ac41cbd99f | |||
| 9f9c90abc4 | |||
| 87029e3538 | |||
| 127d9adc09 | |||
| c82dc7ad85 | |||
| 36bcff7a7c | |||
| 38201b547a | |||
| ed0334fcda | |||
| fbb486b90b | |||
| 9b34f385d5 | |||
| bb7b731602 | |||
| 19076f8136 | |||
| dc77a936ce | |||
| 7f58710c6f | |||
| 068ddcdcdc | |||
| f4e9252ca0 | |||
| 3b1e918117 | |||
| ed7981fdaf | |||
| 9698ca53e4 | |||
| ddc1dc7daf | |||
| 1625a957f8 | |||
| 2dc50d627e | |||
| 2ffde9a3dd | |||
| 5967a91ae1 | |||
| 32c1effcb5 | |||
| 9d0e19c56f | |||
| acf4e634fe | |||
| 25942c2338 | |||
| a4f81f6ba1 | |||
| c1b9090e51 | |||
| f494f70003 | |||
| fb2a55a909 | |||
| 4edfa7fd50 | |||
| d699cac9b1 | |||
| c0428e12c1 | |||
| 55f434ff05 | |||
| f2b3bdda2d | |||
| 1f6bf33b0e | |||
| e2027b1a32 | |||
| 2b3a58b55e | |||
| 6ac536412a | |||
| 52f8ffe4e4 | |||
| aca81431aa | |||
| 1fadd850b7 | |||
| ed2a9a21b6 | |||
| 57279eb3e4 | |||
| c403a2914a | |||
| bcb176344c | |||
| ecf362cffc | |||
| f4ab7671d8 | |||
| a2a3018917 | |||
| 0bdb664000 | |||
| 9c3b61ce57 | |||
| d06df3d278 | |||
| 547ba19e61 | |||
| cb05ff2e9e | |||
| f614da7918 | |||
| a3c8dafff9 | |||
| fa978a7cd1 | |||
| aaa0a562b4 | |||
| 590a4ce2a6 | |||
| f26edce071 | |||
| 603799ea32 | |||
| a32baf7798 | |||
| 498c9af663 | |||
| 202dbff6d3 | 
| @@ -1,12 +1,12 @@ | |||||||
| { | { | ||||||
|   "sync": { |   "sync": { | ||||||
|     "region": "solian-next", |     "region": "solian", | ||||||
|     "configPath": "roadsign.toml" |     "configPath": "roadsign.toml" | ||||||
|   }, |   }, | ||||||
|   "deployments": [ |   "deployments": [ | ||||||
|     { |     { | ||||||
|       "region": "solian-next", |       "region": "solian", | ||||||
|       "site": "solian-next-web", |       "site": "solian-web", | ||||||
|       "path": "build/web" |       "path": "build/web" | ||||||
|     } |     } | ||||||
|   ] |   ] | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ | |||||||
|         android:label="Solian" |         android:label="Solian" | ||||||
|         android:name="${applicationName}" |         android:name="${applicationName}" | ||||||
|         android:icon="@mipmap/ic_launcher" |         android:icon="@mipmap/ic_launcher" | ||||||
|  |         android:enableOnBackInvokedCallback="true" | ||||||
|         android:requestLegacyExternalStorage="true"> |         android:requestLegacyExternalStorage="true"> | ||||||
|         <activity |         <activity | ||||||
|             android:name=".MainActivity" |             android:name=".MainActivity" | ||||||
|   | |||||||
| @@ -7,11 +7,7 @@ meta { | |||||||
| post { | post { | ||||||
|   url: {{endpoint}}/cgi/uc/boosts/1/activate |   url: {{endpoint}}/cgi/uc/boosts/1/activate | ||||||
|   body: none |   body: none | ||||||
|   auth: bearer |   auth: inherit | ||||||
| } |  | ||||||
|  |  | ||||||
| auth:bearer { |  | ||||||
|   token: {{atk}} |  | ||||||
| } | } | ||||||
|  |  | ||||||
| body:json { | body:json { | ||||||
|   | |||||||
							
								
								
									
										19
									
								
								api/Paperclip/Stickers/Create Sticker Pack.bru
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								api/Paperclip/Stickers/Create Sticker Pack.bru
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | meta { | ||||||
|  |   name: Create Sticker Pack | ||||||
|  |   type: http | ||||||
|  |   seq: 1 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | post { | ||||||
|  |   url: {{endpoint}}/cgi/uc/stickers/packs | ||||||
|  |   body: json | ||||||
|  |   auth: inherit | ||||||
|  | } | ||||||
|  |  | ||||||
|  | body:json { | ||||||
|  |   { | ||||||
|  |     "prefix": "cat", | ||||||
|  |     "name": "Solar Network full of Cats!", | ||||||
|  |     "description": "The sticker packs is full of stickers which related with cats!" | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										20
									
								
								api/Paperclip/Stickers/Create Sticker.bru
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								api/Paperclip/Stickers/Create Sticker.bru
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  | meta { | ||||||
|  |   name: Create Sticker | ||||||
|  |   type: http | ||||||
|  |   seq: 2 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | post { | ||||||
|  |   url: {{endpoint}}/cgi/uc/stickers | ||||||
|  |   body: json | ||||||
|  |   auth: inherit | ||||||
|  | } | ||||||
|  |  | ||||||
|  | body:json { | ||||||
|  |   { | ||||||
|  |     "alias": "AteChip", | ||||||
|  |     "name": "Cat ate chips", | ||||||
|  |     "attachment_id": "d0b692cc64054463", | ||||||
|  |     "pack_id": 2 | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -7,11 +7,7 @@ meta { | |||||||
| post { | post { | ||||||
|   url: {{endpoint}}/cgi/id/dev/notify/all |   url: {{endpoint}}/cgi/id/dev/notify/all | ||||||
|   body: json |   body: json | ||||||
|   auth: bearer |   auth: inherit | ||||||
| } |  | ||||||
|  |  | ||||||
| auth:bearer { |  | ||||||
|   token: {{atk}} |  | ||||||
| } | } | ||||||
|  |  | ||||||
| body:json { | body:json { | ||||||
|   | |||||||
							
								
								
									
										7
									
								
								api/collection.bru
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								api/collection.bru
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | auth { | ||||||
|  |   mode: bearer | ||||||
|  | } | ||||||
|  |  | ||||||
|  | auth:bearer { | ||||||
|  |   token: {{atk}} | ||||||
|  | } | ||||||
| @@ -181,6 +181,8 @@ | |||||||
|   "settingsAppearance": "Appearance", |   "settingsAppearance": "Appearance", | ||||||
|   "settingsAppBarTransparent": "Transparent App Bar", |   "settingsAppBarTransparent": "Transparent App Bar", | ||||||
|   "settingsAppBarTransparentDescription": "Enable transparent effect for the app bar.", |   "settingsAppBarTransparentDescription": "Enable transparent effect for the app bar.", | ||||||
|  |   "settingsDrawerPreferCollapse": "Prefer Drawer Collapse", | ||||||
|  |   "settingsDrawerPreferCollapseDescription": "Make the drawer to collapse even when the screen is wide enough.", | ||||||
|   "settingsBackgroundImage": "Background Image", |   "settingsBackgroundImage": "Background Image", | ||||||
|   "settingsBackgroundImageDescription": "Set the background image that will be applied globally.", |   "settingsBackgroundImageDescription": "Set the background image that will be applied globally.", | ||||||
|   "settingsBackgroundImageClear": "Clear Existing Background Image", |   "settingsBackgroundImageClear": "Clear Existing Background Image", | ||||||
| @@ -191,6 +193,13 @@ | |||||||
|   "settingsColorSchemeDescription": "Set the application primary color.", |   "settingsColorSchemeDescription": "Set the application primary color.", | ||||||
|   "settingsColorSeed": "Color Seed", |   "settingsColorSeed": "Color Seed", | ||||||
|   "settingsColorSeedDescription": "Select one of the present color schemes.", |   "settingsColorSeedDescription": "Select one of the present color schemes.", | ||||||
|  |   "settingsFeatures": "Features", | ||||||
|  |   "settingsNotifyWithHaptic": "Haptic when Notified", | ||||||
|  |   "settingsNotifyWithHapticDescription": "Vibrate lightly when a new notification appears in the foreground.", | ||||||
|  |   "settingsExpandPostLink": "Expand Post Link", | ||||||
|  |   "settingsExpandPostLinkDescription": "Expand the post link in the post list.", | ||||||
|  |   "settingsExpandChatLink": "Expand Chat Link", | ||||||
|  |   "settingsExpandChatLinkDescription": "Expand the chat link in the chat list.", | ||||||
|   "settingsNetwork": "Network", |   "settingsNetwork": "Network", | ||||||
|   "settingsNetworkServer": "HyperNet Server", |   "settingsNetworkServer": "HyperNet Server", | ||||||
|   "settingsNetworkServerDescription": "Set the HyperNet server address, choose ours or build your own.", |   "settingsNetworkServerDescription": "Set the HyperNet server address, choose ours or build your own.", | ||||||
| @@ -213,8 +222,9 @@ | |||||||
|   "sensitiveContentCollapsed": "Sensitive content has been collapsed.", |   "sensitiveContentCollapsed": "Sensitive content has been collapsed.", | ||||||
|   "sensitiveContentDescription": "This content has been marked as sensitive, and may not be suitable for all viewers.", |   "sensitiveContentDescription": "This content has been marked as sensitive, and may not be suitable for all viewers.", | ||||||
|   "sensitiveContentReveal": "Reveal", |   "sensitiveContentReveal": "Reveal", | ||||||
|   "serverConnecting": "Connecting to server...", |   "serverConnecting": "Connecting...", | ||||||
|   "serverDisconnected": "Lost connection from server", |   "serverDisconnected": "Connection Lost", | ||||||
|  |   "serverConnected": "Connected", | ||||||
|   "fieldChatAlias": "Channel Alias", |   "fieldChatAlias": "Channel Alias", | ||||||
|   "fieldChatAliasHint": "The unique channel alias within the site, used to represent the channel in URL, leave blank to auto generate. Should be URL-Safe.", |   "fieldChatAliasHint": "The unique channel alias within the site, used to represent the channel in URL, leave blank to auto generate. Should be URL-Safe.", | ||||||
|   "fieldChatName": "Name", |   "fieldChatName": "Name", | ||||||
| @@ -281,18 +291,25 @@ | |||||||
|     "one": "{} attachment", |     "one": "{} attachment", | ||||||
|     "other": "{} attachments" |     "other": "{} attachments" | ||||||
|   }, |   }, | ||||||
|  |   "messageTyping": { | ||||||
|  |     "one": "{} is typing...", | ||||||
|  |     "other": "{} are typing..." | ||||||
|  |   }, | ||||||
|   "fieldAttachmentRandomId": "Random ID", |   "fieldAttachmentRandomId": "Random ID", | ||||||
|  |   "fieldAttachmentAlt": "Alternative text", | ||||||
|   "addAttachmentFromAlbum": "Add from album", |   "addAttachmentFromAlbum": "Add from album", | ||||||
|   "addAttachmentFromClipboard": "Paste file", |   "addAttachmentFromClipboard": "Paste file", | ||||||
|   "addAttachmentFromCameraPhoto": "Take photo", |   "addAttachmentFromCameraPhoto": "Take photo", | ||||||
|   "addAttachmentFromCameraVideo": "Take video", |   "addAttachmentFromCameraVideo": "Take video", | ||||||
|   "addAttachmentFromRandomId": "Link via RID", |   "addAttachmentFromRandomId": "Link via RID", | ||||||
|  |   "attachmentDetailInfo": "Attachment details", | ||||||
|   "attachmentPastedImage": "Pasted Image", |   "attachmentPastedImage": "Pasted Image", | ||||||
|   "attachmentInsertLink": "Insert Link", |   "attachmentInsertLink": "Insert Link", | ||||||
|   "attachmentSetAsPostThumbnail": "Set as post thumbnail", |   "attachmentSetAsPostThumbnail": "Set as post thumbnail", | ||||||
|   "attachmentUnsetAsPostThumbnail": "Unset as post thumbnail", |   "attachmentUnsetAsPostThumbnail": "Unset as post thumbnail", | ||||||
|   "attachmentCompressVideo": "Re-encode video", |   "attachmentCompressVideo": "Re-encode video", | ||||||
|   "attachmentSetThumbnail": "Set thumbnail", |   "attachmentSetThumbnail": "Set thumbnail", | ||||||
|  |   "attachmentSetAlt": "Set alternative text", | ||||||
|   "attachmentCopyRandomId": "Copy RID", |   "attachmentCopyRandomId": "Copy RID", | ||||||
|   "attachmentUpload": "Upload", |   "attachmentUpload": "Upload", | ||||||
|   "attachmentInputDialog": "Upload attachments", |   "attachmentInputDialog": "Upload attachments", | ||||||
| @@ -408,6 +425,9 @@ | |||||||
|   "celebrateBirthday": "Happy birthday, {}!", |   "celebrateBirthday": "Happy birthday, {}!", | ||||||
|   "celebrateMerryXmas": "Merry christmas, {}!", |   "celebrateMerryXmas": "Merry christmas, {}!", | ||||||
|   "celebrateNewYear": "Happy new year, {}!", |   "celebrateNewYear": "Happy new year, {}!", | ||||||
|  |   "celebrateLunarNewYear": "Happy lunar new year, {}!", | ||||||
|  |   "celebrateMidAutumn": "Happy mid-autumn festival, {}!", | ||||||
|  |   "celebrateDragonBoat": "Happy dragon boat festival, {}!", | ||||||
|   "celebrateValentineDay": "Today is valentine's day, {}!", |   "celebrateValentineDay": "Today is valentine's day, {}!", | ||||||
|   "celebrateLaborDay": "Today is labor day, {}.", |   "celebrateLaborDay": "Today is labor day, {}.", | ||||||
|   "celebrateMotherDay": "Today is mother's day, {}.", |   "celebrateMotherDay": "Today is mother's day, {}.", | ||||||
| @@ -417,6 +437,9 @@ | |||||||
|   "celebrateThanksgiving": "Today is thanksgiving day, {}!", |   "celebrateThanksgiving": "Today is thanksgiving day, {}!", | ||||||
|   "pendingBirthday": "Birthday in {}", |   "pendingBirthday": "Birthday in {}", | ||||||
|   "pendingMerryXmas": "Christmas in {}", |   "pendingMerryXmas": "Christmas in {}", | ||||||
|  |   "pendingLunarNewYear": "Lunar new year in {}", | ||||||
|  |   "pendingMidAutumn": "Mid-autumn festival in {}", | ||||||
|  |   "pendingDragonBoat": "Dragon boat festival in {}", | ||||||
|   "pendingNewYear": "New year in {}", |   "pendingNewYear": "New year in {}", | ||||||
|   "pendingValentineDay": "Valentine's day in {}", |   "pendingValentineDay": "Valentine's day in {}", | ||||||
|   "pendingLaborDay": "Labor day in {}", |   "pendingLaborDay": "Labor day in {}", | ||||||
|   | |||||||
| @@ -185,10 +185,19 @@ | |||||||
|   "settingsThemeMaterial3Description": "将应用主题设置为 Material 3 设计范式的主题。", |   "settingsThemeMaterial3Description": "将应用主题设置为 Material 3 设计范式的主题。", | ||||||
|   "settingsAppBarTransparent": "透明顶栏", |   "settingsAppBarTransparent": "透明顶栏", | ||||||
|   "settingsAppBarTransparentDescription": "为顶栏启用透明效果。", |   "settingsAppBarTransparentDescription": "为顶栏启用透明效果。", | ||||||
|  |   "settingsDrawerPreferCollapse": "侧边栏偏好折叠", | ||||||
|  |   "settingsDrawerPreferCollapseDescription": "将侧边栏优先折叠,即使屏幕宽度足够大去放下整个侧边栏。", | ||||||
|   "settingsColorScheme": "主题色", |   "settingsColorScheme": "主题色", | ||||||
|   "settingsColorSchemeDescription": "设置应用主题色。", |   "settingsColorSchemeDescription": "设置应用主题色。", | ||||||
|   "settingsColorSeed": "预设色彩主题", |   "settingsColorSeed": "预设色彩主题", | ||||||
|   "settingsColorSeedDescription": "选择一个预设色彩主题。", |   "settingsColorSeedDescription": "选择一个预设色彩主题。", | ||||||
|  |   "settingsFeatures": "功能", | ||||||
|  |   "settingsNotifyWithHaptic": "新通知时振动", | ||||||
|  |   "settingsNotifyWithHapticDescription": "在应用在前台时收到新通知出现时出发轻量的振动。", | ||||||
|  |   "settingsExpandPostLink": "展开帖子链接", | ||||||
|  |   "settingsExpandPostLinkDescription": "在帖子列表中展开显示帖子中的链接。", | ||||||
|  |   "settingsExpandChatLink": "展开聊天链接", | ||||||
|  |   "settingsExpandChatLinkDescription": "在聊天信息中展开显示内容中的链接。", | ||||||
|   "settingsNetwork": "网络", |   "settingsNetwork": "网络", | ||||||
|   "settingsNetworkServer": "HyperNet 服务器", |   "settingsNetworkServer": "HyperNet 服务器", | ||||||
|   "settingsNetworkServerDescription": "设置 HyperNet 服务器地址,选择我们提供的,或者自己搭建。", |   "settingsNetworkServerDescription": "设置 HyperNet 服务器地址,选择我们提供的,或者自己搭建。", | ||||||
| @@ -211,8 +220,9 @@ | |||||||
|   "sensitiveContentCollapsed": "敏感内容已折叠。", |   "sensitiveContentCollapsed": "敏感内容已折叠。", | ||||||
|   "sensitiveContentDescription": "此内容已被标记,可能不适合所有人查看。", |   "sensitiveContentDescription": "此内容已被标记,可能不适合所有人查看。", | ||||||
|   "sensitiveContentReveal": "显示内容", |   "sensitiveContentReveal": "显示内容", | ||||||
|   "serverConnecting": "正在连接服务器…", |   "serverConnecting": "正在连接…", | ||||||
|   "serverDisconnected": "已与服务器断开连接", |   "serverDisconnected": "已断开连接", | ||||||
|  |   "serverConnected": "已连接", | ||||||
|   "fieldChatAlias": "频道别名", |   "fieldChatAlias": "频道别名", | ||||||
|   "fieldChatAliasHint": "全站范围内唯一的频道别名,用于在 URL 中表示该频道,留空则自动生成。应遵循 URL-Safe 的原则。", |   "fieldChatAliasHint": "全站范围内唯一的频道别名,用于在 URL 中表示该频道,留空则自动生成。应遵循 URL-Safe 的原则。", | ||||||
|   "fieldChatName": "名称", |   "fieldChatName": "名称", | ||||||
| @@ -279,18 +289,25 @@ | |||||||
|     "one": "{} 个附件", |     "one": "{} 个附件", | ||||||
|     "other": "{} 个附件" |     "other": "{} 个附件" | ||||||
|   }, |   }, | ||||||
|  |   "messageTyping": { | ||||||
|  |     "one": "{} 正在输入", | ||||||
|  |     "other": "{} 正在输入" | ||||||
|  |   }, | ||||||
|   "fieldAttachmentRandomId": "访问 ID", |   "fieldAttachmentRandomId": "访问 ID", | ||||||
|  |   "fieldAttachmentAlt": "概述文字", | ||||||
|   "addAttachmentFromAlbum": "从相册中添加附件", |   "addAttachmentFromAlbum": "从相册中添加附件", | ||||||
|   "addAttachmentFromClipboard": "粘贴附件", |   "addAttachmentFromClipboard": "粘贴附件", | ||||||
|   "addAttachmentFromCameraPhoto": "拍摄照片", |   "addAttachmentFromCameraPhoto": "拍摄照片", | ||||||
|   "addAttachmentFromCameraVideo": "拍摄视频", |   "addAttachmentFromCameraVideo": "拍摄视频", | ||||||
|   "addAttachmentFromRandomId": "通过访问 ID 链接", |   "addAttachmentFromRandomId": "通过访问 ID 链接", | ||||||
|  |   "attachmentDetailInfo": "附件详细信息", | ||||||
|   "attachmentPastedImage": "粘贴的图片", |   "attachmentPastedImage": "粘贴的图片", | ||||||
|   "attachmentInsertLink": "插入连接", |   "attachmentInsertLink": "插入连接", | ||||||
|   "attachmentSetAsPostThumbnail": "设置为帖子缩略图", |   "attachmentSetAsPostThumbnail": "设置为帖子缩略图", | ||||||
|   "attachmentUnsetAsPostThumbnail": "取消设置为帖子缩略图", |   "attachmentUnsetAsPostThumbnail": "取消设置为帖子缩略图", | ||||||
|   "attachmentCompressVideo": "重新编码视频", |   "attachmentCompressVideo": "重新编码视频", | ||||||
|   "attachmentSetThumbnail": "设置缩略图", |   "attachmentSetThumbnail": "设置缩略图", | ||||||
|  |   "attachmentSetAlt": "设置概述文字", | ||||||
|   "attachmentCopyRandomId": "复制访问 ID", |   "attachmentCopyRandomId": "复制访问 ID", | ||||||
|   "attachmentUpload": "上传", |   "attachmentUpload": "上传", | ||||||
|   "attachmentInputDialog": "上传附件", |   "attachmentInputDialog": "上传附件", | ||||||
| @@ -404,6 +421,9 @@ | |||||||
|   "dailyCheckNegativeHint6": "出门", |   "dailyCheckNegativeHint6": "出门", | ||||||
|   "dailyCheckNegativeHint6Description": "忘带伞遇上大雨", |   "dailyCheckNegativeHint6Description": "忘带伞遇上大雨", | ||||||
|   "celebrateBirthday": "生日快乐,{}!", |   "celebrateBirthday": "生日快乐,{}!", | ||||||
|  |   "celebrateLunarNewYear": "春节快乐,{}!", | ||||||
|  |   "celebrateMidAutumn": "中秋节快乐,{}!", | ||||||
|  |   "celebrateDragonBoat": "端午节快乐,{}!", | ||||||
|   "celebrateMerryXmas": "圣诞快乐,{}!", |   "celebrateMerryXmas": "圣诞快乐,{}!", | ||||||
|   "celebrateNewYear": "新年快乐,{}!", |   "celebrateNewYear": "新年快乐,{}!", | ||||||
|   "celebrateValentineDay": "今天是情人节,{}!", |   "celebrateValentineDay": "今天是情人节,{}!", | ||||||
| @@ -413,6 +433,9 @@ | |||||||
|   "celebrateFatherDay": "今天是父亲节,{}。", |   "celebrateFatherDay": "今天是父亲节,{}。", | ||||||
|   "celebrateHalloween": "快乐在圣诞节,{}!", |   "celebrateHalloween": "快乐在圣诞节,{}!", | ||||||
|   "celebrateThanksgiving": "今天是感恩节,{}!", |   "celebrateThanksgiving": "今天是感恩节,{}!", | ||||||
|  |   "pendingLunarNewYear": "{} 过春节", | ||||||
|  |   "pendingMidAutumn": "{} 过中秋节", | ||||||
|  |   "pendingDragonBoat": "{} 过端午节", | ||||||
|   "pendingBirthday": "{} 过生日", |   "pendingBirthday": "{} 过生日", | ||||||
|   "pendingMerryXmas": "{} 过圣诞节", |   "pendingMerryXmas": "{} 过圣诞节", | ||||||
|   "pendingNewYear": "{} 跨年", |   "pendingNewYear": "{} 跨年", | ||||||
|   | |||||||
| @@ -185,10 +185,15 @@ | |||||||
|   "settingsThemeMaterial3Description": "將應用主題設置為 Material 3 設計範式的主題。", |   "settingsThemeMaterial3Description": "將應用主題設置為 Material 3 設計範式的主題。", | ||||||
|   "settingsAppBarTransparent": "透明頂欄", |   "settingsAppBarTransparent": "透明頂欄", | ||||||
|   "settingsAppBarTransparentDescription": "為頂欄啓用透明效果。", |   "settingsAppBarTransparentDescription": "為頂欄啓用透明效果。", | ||||||
|  |   "settingsDrawerPreferCollapse": "側邊欄偏好摺疊", | ||||||
|  |   "settingsDrawerPreferCollapseDescription": "將側邊欄優先摺疊,即使屏幕寬度足夠大去放下整個側邊欄。", | ||||||
|   "settingsColorScheme": "主題色", |   "settingsColorScheme": "主題色", | ||||||
|   "settingsColorSchemeDescription": "設置應用主題色。", |   "settingsColorSchemeDescription": "設置應用主題色。", | ||||||
|   "settingsColorSeed": "預設色彩主題", |   "settingsColorSeed": "預設色彩主題", | ||||||
|   "settingsColorSeedDescription": "選擇一個預設色彩主題。", |   "settingsColorSeedDescription": "選擇一個預設色彩主題。", | ||||||
|  |   "settingsFeatures": "功能", | ||||||
|  |   "settingsNotifyWithHaptic": "新通知時振動", | ||||||
|  |   "settingsNotifyWithHapticDescription": "在應用在前台時收到新通知出現時出發輕量的振動。", | ||||||
|   "settingsNetwork": "網絡", |   "settingsNetwork": "網絡", | ||||||
|   "settingsNetworkServer": "HyperNet 服務器", |   "settingsNetworkServer": "HyperNet 服務器", | ||||||
|   "settingsNetworkServerDescription": "設置 HyperNet 服務器地址,選擇我們提供的,或者自己搭建。", |   "settingsNetworkServerDescription": "設置 HyperNet 服務器地址,選擇我們提供的,或者自己搭建。", | ||||||
| @@ -211,8 +216,9 @@ | |||||||
|   "sensitiveContentCollapsed": "敏感內容已摺疊。", |   "sensitiveContentCollapsed": "敏感內容已摺疊。", | ||||||
|   "sensitiveContentDescription": "此內容已被標記,可能不適合所有人查看。", |   "sensitiveContentDescription": "此內容已被標記,可能不適合所有人查看。", | ||||||
|   "sensitiveContentReveal": "顯示內容", |   "sensitiveContentReveal": "顯示內容", | ||||||
|   "serverConnecting": "正在連接服務器…", |   "serverConnecting": "正在連接…", | ||||||
|   "serverDisconnected": "已與服務器斷開連接", |   "serverDisconnected": "已斷開連接", | ||||||
|  |   "serverConnected": "已連接", | ||||||
|   "fieldChatAlias": "頻道別名", |   "fieldChatAlias": "頻道別名", | ||||||
|   "fieldChatAliasHint": "全站範圍內唯一的頻道別名,用於在 URL 中表示該頻道,留空則自動生成。應遵循 URL-Safe 的原則。", |   "fieldChatAliasHint": "全站範圍內唯一的頻道別名,用於在 URL 中表示該頻道,留空則自動生成。應遵循 URL-Safe 的原則。", | ||||||
|   "fieldChatName": "名稱", |   "fieldChatName": "名稱", | ||||||
| @@ -279,18 +285,25 @@ | |||||||
|     "one": "{} 個附件", |     "one": "{} 個附件", | ||||||
|     "other": "{} 個附件" |     "other": "{} 個附件" | ||||||
|   }, |   }, | ||||||
|  |   "messageTyping": { | ||||||
|  |     "one": "{} 正在輸入", | ||||||
|  |     "other": "{} 正在輸入" | ||||||
|  |   }, | ||||||
|   "fieldAttachmentRandomId": "訪問 ID", |   "fieldAttachmentRandomId": "訪問 ID", | ||||||
|  |   "fieldAttachmentAlt": "概述文字", | ||||||
|   "addAttachmentFromAlbum": "從相冊中添加附件", |   "addAttachmentFromAlbum": "從相冊中添加附件", | ||||||
|   "addAttachmentFromClipboard": "粘貼附件", |   "addAttachmentFromClipboard": "粘貼附件", | ||||||
|   "addAttachmentFromCameraPhoto": "拍攝照片", |   "addAttachmentFromCameraPhoto": "拍攝照片", | ||||||
|   "addAttachmentFromCameraVideo": "拍攝視頻", |   "addAttachmentFromCameraVideo": "拍攝視頻", | ||||||
|   "addAttachmentFromRandomId": "通過訪問 ID 鏈接", |   "addAttachmentFromRandomId": "通過訪問 ID 鏈接", | ||||||
|  |   "attachmentDetailInfo": "附件詳細信息", | ||||||
|   "attachmentPastedImage": "粘貼的圖片", |   "attachmentPastedImage": "粘貼的圖片", | ||||||
|   "attachmentInsertLink": "插入連接", |   "attachmentInsertLink": "插入連接", | ||||||
|   "attachmentSetAsPostThumbnail": "設置為帖子縮略圖", |   "attachmentSetAsPostThumbnail": "設置為帖子縮略圖", | ||||||
|   "attachmentUnsetAsPostThumbnail": "取消設置為帖子縮略圖", |   "attachmentUnsetAsPostThumbnail": "取消設置為帖子縮略圖", | ||||||
|   "attachmentCompressVideo": "重新編碼視頻", |   "attachmentCompressVideo": "重新編碼視頻", | ||||||
|   "attachmentSetThumbnail": "設置縮略圖", |   "attachmentSetThumbnail": "設置縮略圖", | ||||||
|  |   "attachmentSetAlt": "設置概述文字", | ||||||
|   "attachmentCopyRandomId": "複製訪問 ID", |   "attachmentCopyRandomId": "複製訪問 ID", | ||||||
|   "attachmentUpload": "上傳", |   "attachmentUpload": "上傳", | ||||||
|   "attachmentInputDialog": "上傳附件", |   "attachmentInputDialog": "上傳附件", | ||||||
| @@ -404,6 +417,9 @@ | |||||||
|   "dailyCheckNegativeHint6": "出門", |   "dailyCheckNegativeHint6": "出門", | ||||||
|   "dailyCheckNegativeHint6Description": "忘帶傘遇上大雨", |   "dailyCheckNegativeHint6Description": "忘帶傘遇上大雨", | ||||||
|   "celebrateBirthday": "生日快樂,{}!", |   "celebrateBirthday": "生日快樂,{}!", | ||||||
|  |   "celebrateLunarNewYear": "春節快樂,{}!", | ||||||
|  |   "celebrateMidAutumn": "中秋節快樂,{}!", | ||||||
|  |   "celebrateDragonBoat": "端午節快樂,{}!", | ||||||
|   "celebrateMerryXmas": "聖誕快樂,{}!", |   "celebrateMerryXmas": "聖誕快樂,{}!", | ||||||
|   "celebrateNewYear": "新年快樂,{}!", |   "celebrateNewYear": "新年快樂,{}!", | ||||||
|   "celebrateValentineDay": "今天是情人節,{}!", |   "celebrateValentineDay": "今天是情人節,{}!", | ||||||
| @@ -413,6 +429,9 @@ | |||||||
|   "celebrateFatherDay": "今天是父親節,{}。", |   "celebrateFatherDay": "今天是父親節,{}。", | ||||||
|   "celebrateHalloween": "快樂在聖誕節,{}!", |   "celebrateHalloween": "快樂在聖誕節,{}!", | ||||||
|   "celebrateThanksgiving": "今天是感恩節,{}!", |   "celebrateThanksgiving": "今天是感恩節,{}!", | ||||||
|  |   "pendingLunarNewYear": "{} 過春節", | ||||||
|  |   "pendingMidAutumn": "{} 過中秋節", | ||||||
|  |   "pendingDragonBoat": "{} 過端午節", | ||||||
|   "pendingBirthday": "{} 過生日", |   "pendingBirthday": "{} 過生日", | ||||||
|   "pendingMerryXmas": "{} 過聖誕節", |   "pendingMerryXmas": "{} 過聖誕節", | ||||||
|   "pendingNewYear": "{} 跨年", |   "pendingNewYear": "{} 跨年", | ||||||
|   | |||||||
| @@ -185,10 +185,15 @@ | |||||||
|   "settingsThemeMaterial3Description": "將應用主題設置為 Material 3 設計範式的主題。", |   "settingsThemeMaterial3Description": "將應用主題設置為 Material 3 設計範式的主題。", | ||||||
|   "settingsAppBarTransparent": "透明頂欄", |   "settingsAppBarTransparent": "透明頂欄", | ||||||
|   "settingsAppBarTransparentDescription": "為頂欄啟用透明效果。", |   "settingsAppBarTransparentDescription": "為頂欄啟用透明效果。", | ||||||
|  |   "settingsDrawerPreferCollapse": "側邊欄偏好摺疊", | ||||||
|  |   "settingsDrawerPreferCollapseDescription": "將側邊欄優先摺疊,即使屏幕寬度足夠大去放下整個側邊欄。", | ||||||
|   "settingsColorScheme": "主題色", |   "settingsColorScheme": "主題色", | ||||||
|   "settingsColorSchemeDescription": "設置應用主題色。", |   "settingsColorSchemeDescription": "設置應用主題色。", | ||||||
|   "settingsColorSeed": "預設色彩主題", |   "settingsColorSeed": "預設色彩主題", | ||||||
|   "settingsColorSeedDescription": "選擇一個預設色彩主題。", |   "settingsColorSeedDescription": "選擇一個預設色彩主題。", | ||||||
|  |   "settingsFeatures": "功能", | ||||||
|  |   "settingsNotifyWithHaptic": "新通知時振動", | ||||||
|  |   "settingsNotifyWithHapticDescription": "在應用在前臺時收到新通知出現時出發輕量的振動。", | ||||||
|   "settingsNetwork": "網絡", |   "settingsNetwork": "網絡", | ||||||
|   "settingsNetworkServer": "HyperNet 服務器", |   "settingsNetworkServer": "HyperNet 服務器", | ||||||
|   "settingsNetworkServerDescription": "設置 HyperNet 服務器地址,選擇我們提供的,或者自己搭建。", |   "settingsNetworkServerDescription": "設置 HyperNet 服務器地址,選擇我們提供的,或者自己搭建。", | ||||||
| @@ -211,8 +216,9 @@ | |||||||
|   "sensitiveContentCollapsed": "敏感內容已摺疊。", |   "sensitiveContentCollapsed": "敏感內容已摺疊。", | ||||||
|   "sensitiveContentDescription": "此內容已被標記,可能不適合所有人查看。", |   "sensitiveContentDescription": "此內容已被標記,可能不適合所有人查看。", | ||||||
|   "sensitiveContentReveal": "顯示內容", |   "sensitiveContentReveal": "顯示內容", | ||||||
|   "serverConnecting": "正在連接服務器…", |   "serverConnecting": "正在連接…", | ||||||
|   "serverDisconnected": "已與服務器斷開連接", |   "serverDisconnected": "已斷開連接", | ||||||
|  |   "serverConnected": "已連接", | ||||||
|   "fieldChatAlias": "頻道別名", |   "fieldChatAlias": "頻道別名", | ||||||
|   "fieldChatAliasHint": "全站範圍內唯一的頻道別名,用於在 URL 中表示該頻道,留空則自動生成。應遵循 URL-Safe 的原則。", |   "fieldChatAliasHint": "全站範圍內唯一的頻道別名,用於在 URL 中表示該頻道,留空則自動生成。應遵循 URL-Safe 的原則。", | ||||||
|   "fieldChatName": "名稱", |   "fieldChatName": "名稱", | ||||||
| @@ -279,18 +285,25 @@ | |||||||
|     "one": "{} 個附件", |     "one": "{} 個附件", | ||||||
|     "other": "{} 個附件" |     "other": "{} 個附件" | ||||||
|   }, |   }, | ||||||
|  |   "messageTyping": { | ||||||
|  |     "one": "{} 正在輸入", | ||||||
|  |     "other": "{} 正在輸入" | ||||||
|  |   }, | ||||||
|   "fieldAttachmentRandomId": "訪問 ID", |   "fieldAttachmentRandomId": "訪問 ID", | ||||||
|  |   "fieldAttachmentAlt": "概述文字", | ||||||
|   "addAttachmentFromAlbum": "從相冊中添加附件", |   "addAttachmentFromAlbum": "從相冊中添加附件", | ||||||
|   "addAttachmentFromClipboard": "粘貼附件", |   "addAttachmentFromClipboard": "粘貼附件", | ||||||
|   "addAttachmentFromCameraPhoto": "拍攝照片", |   "addAttachmentFromCameraPhoto": "拍攝照片", | ||||||
|   "addAttachmentFromCameraVideo": "拍攝視頻", |   "addAttachmentFromCameraVideo": "拍攝視頻", | ||||||
|   "addAttachmentFromRandomId": "通過訪問 ID 鏈接", |   "addAttachmentFromRandomId": "通過訪問 ID 鏈接", | ||||||
|  |   "attachmentDetailInfo": "附件詳細信息", | ||||||
|   "attachmentPastedImage": "粘貼的圖片", |   "attachmentPastedImage": "粘貼的圖片", | ||||||
|   "attachmentInsertLink": "插入連接", |   "attachmentInsertLink": "插入連接", | ||||||
|   "attachmentSetAsPostThumbnail": "設置為帖子縮略圖", |   "attachmentSetAsPostThumbnail": "設置為帖子縮略圖", | ||||||
|   "attachmentUnsetAsPostThumbnail": "取消設置為帖子縮略圖", |   "attachmentUnsetAsPostThumbnail": "取消設置為帖子縮略圖", | ||||||
|   "attachmentCompressVideo": "重新編碼視頻", |   "attachmentCompressVideo": "重新編碼視頻", | ||||||
|   "attachmentSetThumbnail": "設置縮略圖", |   "attachmentSetThumbnail": "設置縮略圖", | ||||||
|  |   "attachmentSetAlt": "設置概述文字", | ||||||
|   "attachmentCopyRandomId": "複製訪問 ID", |   "attachmentCopyRandomId": "複製訪問 ID", | ||||||
|   "attachmentUpload": "上傳", |   "attachmentUpload": "上傳", | ||||||
|   "attachmentInputDialog": "上傳附件", |   "attachmentInputDialog": "上傳附件", | ||||||
| @@ -404,6 +417,9 @@ | |||||||
|   "dailyCheckNegativeHint6": "出門", |   "dailyCheckNegativeHint6": "出門", | ||||||
|   "dailyCheckNegativeHint6Description": "忘帶傘遇上大雨", |   "dailyCheckNegativeHint6Description": "忘帶傘遇上大雨", | ||||||
|   "celebrateBirthday": "生日快樂,{}!", |   "celebrateBirthday": "生日快樂,{}!", | ||||||
|  |   "celebrateLunarNewYear": "春節快樂,{}!", | ||||||
|  |   "celebrateMidAutumn": "中秋節快樂,{}!", | ||||||
|  |   "celebrateDragonBoat": "端午節快樂,{}!", | ||||||
|   "celebrateMerryXmas": "聖誕快樂,{}!", |   "celebrateMerryXmas": "聖誕快樂,{}!", | ||||||
|   "celebrateNewYear": "新年快樂,{}!", |   "celebrateNewYear": "新年快樂,{}!", | ||||||
|   "celebrateValentineDay": "今天是情人節,{}!", |   "celebrateValentineDay": "今天是情人節,{}!", | ||||||
| @@ -413,6 +429,9 @@ | |||||||
|   "celebrateFatherDay": "今天是父親節,{}。", |   "celebrateFatherDay": "今天是父親節,{}。", | ||||||
|   "celebrateHalloween": "快樂在聖誕節,{}!", |   "celebrateHalloween": "快樂在聖誕節,{}!", | ||||||
|   "celebrateThanksgiving": "今天是感恩節,{}!", |   "celebrateThanksgiving": "今天是感恩節,{}!", | ||||||
|  |   "pendingLunarNewYear": "{} 過春節", | ||||||
|  |   "pendingMidAutumn": "{} 過中秋節", | ||||||
|  |   "pendingDragonBoat": "{} 過端午節", | ||||||
|   "pendingBirthday": "{} 過生日", |   "pendingBirthday": "{} 過生日", | ||||||
|   "pendingMerryXmas": "{} 過聖誕節", |   "pendingMerryXmas": "{} 過聖誕節", | ||||||
|   "pendingNewYear": "{} 跨年", |   "pendingNewYear": "{} 跨年", | ||||||
|   | |||||||
| @@ -43,58 +43,58 @@ PODS: | |||||||
|     - Flutter |     - Flutter | ||||||
|   - file_saver (0.0.1): |   - file_saver (0.0.1): | ||||||
|     - Flutter |     - Flutter | ||||||
|   - Firebase/Analytics (11.4.0): |   - Firebase/Analytics (11.6.0): | ||||||
|     - Firebase/Core |     - Firebase/Core | ||||||
|   - Firebase/Core (11.4.0): |   - Firebase/Core (11.6.0): | ||||||
|     - Firebase/CoreOnly |     - Firebase/CoreOnly | ||||||
|     - FirebaseAnalytics (~> 11.4.0) |     - FirebaseAnalytics (~> 11.6.0) | ||||||
|   - Firebase/CoreOnly (11.4.0): |   - Firebase/CoreOnly (11.6.0): | ||||||
|     - FirebaseCore (= 11.4.0) |     - FirebaseCore (~> 11.6.0) | ||||||
|   - Firebase/Messaging (11.4.0): |   - Firebase/Messaging (11.6.0): | ||||||
|     - Firebase/CoreOnly |     - Firebase/CoreOnly | ||||||
|     - FirebaseMessaging (~> 11.4.0) |     - FirebaseMessaging (~> 11.6.0) | ||||||
|   - firebase_analytics (11.3.6): |   - firebase_analytics (11.4.0): | ||||||
|     - Firebase/Analytics (= 11.4.0) |     - Firebase/Analytics (= 11.6.0) | ||||||
|     - firebase_core |     - firebase_core | ||||||
|     - Flutter |     - Flutter | ||||||
|   - firebase_core (3.9.0): |   - firebase_core (3.10.0): | ||||||
|     - Firebase/CoreOnly (= 11.4.0) |     - Firebase/CoreOnly (= 11.6.0) | ||||||
|     - Flutter |     - Flutter | ||||||
|   - firebase_messaging (15.1.6): |   - firebase_messaging (15.2.0): | ||||||
|     - Firebase/Messaging (= 11.4.0) |     - Firebase/Messaging (= 11.6.0) | ||||||
|     - firebase_core |     - firebase_core | ||||||
|     - Flutter |     - Flutter | ||||||
|   - FirebaseAnalytics (11.4.0): |   - FirebaseAnalytics (11.6.0): | ||||||
|     - FirebaseAnalytics/AdIdSupport (= 11.4.0) |     - FirebaseAnalytics/AdIdSupport (= 11.6.0) | ||||||
|     - FirebaseCore (~> 11.0) |     - FirebaseCore (~> 11.6.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.4.0): |   - FirebaseAnalytics/AdIdSupport (11.6.0): | ||||||
|     - FirebaseCore (~> 11.0) |     - FirebaseCore (~> 11.6.0) | ||||||
|     - FirebaseInstallations (~> 11.0) |     - FirebaseInstallations (~> 11.0) | ||||||
|     - GoogleAppMeasurement (= 11.4.0) |     - GoogleAppMeasurement (= 11.6.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.4.0): |   - FirebaseCore (11.6.0): | ||||||
|     - FirebaseCoreInternal (~> 11.0) |     - FirebaseCoreInternal (~> 11.6.0) | ||||||
|     - GoogleUtilities/Environment (~> 8.0) |     - GoogleUtilities/Environment (~> 8.0) | ||||||
|     - GoogleUtilities/Logger (~> 8.0) |     - GoogleUtilities/Logger (~> 8.0) | ||||||
|   - FirebaseCoreInternal (11.6.0): |   - FirebaseCoreInternal (11.6.0): | ||||||
|     - "GoogleUtilities/NSData+zlib (~> 8.0)" |     - "GoogleUtilities/NSData+zlib (~> 8.0)" | ||||||
|   - FirebaseInstallations (11.4.0): |   - FirebaseInstallations (11.6.0): | ||||||
|     - FirebaseCore (~> 11.0) |     - FirebaseCore (~> 11.6.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.4.0): |   - FirebaseMessaging (11.6.0): | ||||||
|     - FirebaseCore (~> 11.0) |     - FirebaseCore (~> 11.6.0) | ||||||
|     - FirebaseInstallations (~> 11.0) |     - FirebaseInstallations (~> 11.0) | ||||||
|     - GoogleDataTransport (~> 10.0) |     - GoogleDataTransport (~> 10.0) | ||||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.0) |     - GoogleUtilities/AppDelegateSwizzler (~> 8.0) | ||||||
| @@ -110,27 +110,27 @@ PODS: | |||||||
|   - flutter_udid (0.0.1): |   - flutter_udid (0.0.1): | ||||||
|     - Flutter |     - Flutter | ||||||
|     - SAMKeychain |     - SAMKeychain | ||||||
|   - flutter_webrtc (0.12.2): |   - flutter_webrtc (0.12.6): | ||||||
|     - Flutter |     - Flutter | ||||||
|     - WebRTC-SDK (= 125.6422.06) |     - WebRTC-SDK (= 125.6422.06) | ||||||
|   - gal (1.0.0): |   - gal (1.0.0): | ||||||
|     - Flutter |     - Flutter | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|   - GoogleAppMeasurement (11.4.0): |   - GoogleAppMeasurement (11.6.0): | ||||||
|     - GoogleAppMeasurement/AdIdSupport (= 11.4.0) |     - GoogleAppMeasurement/AdIdSupport (= 11.6.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.4.0): |   - GoogleAppMeasurement/AdIdSupport (11.6.0): | ||||||
|     - GoogleAppMeasurement/WithoutAdIdSupport (= 11.4.0) |     - GoogleAppMeasurement/WithoutAdIdSupport (= 11.6.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.4.0): |   - GoogleAppMeasurement/WithoutAdIdSupport (11.6.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) | ||||||
| @@ -173,7 +173,7 @@ PODS: | |||||||
|   - in_app_review (2.0.0): |   - in_app_review (2.0.0): | ||||||
|     - Flutter |     - Flutter | ||||||
|   - Kingfisher (8.1.3) |   - Kingfisher (8.1.3) | ||||||
|   - livekit_client (2.3.4): |   - livekit_client (2.3.5): | ||||||
|     - Flutter |     - Flutter | ||||||
|     - flutter_webrtc |     - flutter_webrtc | ||||||
|     - WebRTC-SDK (= 125.6422.06) |     - WebRTC-SDK (= 125.6422.06) | ||||||
| @@ -211,6 +211,9 @@ PODS: | |||||||
|   - shared_preferences_foundation (0.0.1): |   - shared_preferences_foundation (0.0.1): | ||||||
|     - Flutter |     - Flutter | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|  |   - sqflite_darwin (0.0.4): | ||||||
|  |     - Flutter | ||||||
|  |     - FlutterMacOS | ||||||
|   - SwiftyGif (5.4.5) |   - SwiftyGif (5.4.5) | ||||||
|   - url_launcher_ios (0.0.1): |   - url_launcher_ios (0.0.1): | ||||||
|     - Flutter |     - Flutter | ||||||
| @@ -256,6 +259,7 @@ DEPENDENCIES: | |||||||
|   - screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`) |   - screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`) | ||||||
|   - share_plus (from `.symlinks/plugins/share_plus/ios`) |   - share_plus (from `.symlinks/plugins/share_plus/ios`) | ||||||
|   - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) |   - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) | ||||||
|  |   - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/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`) | ||||||
| @@ -343,6 +347,8 @@ EXTERNAL SOURCES: | |||||||
|     :path: ".symlinks/plugins/share_plus/ios" |     :path: ".symlinks/plugins/share_plus/ios" | ||||||
|   shared_preferences_foundation: |   shared_preferences_foundation: | ||||||
|     :path: ".symlinks/plugins/shared_preferences_foundation/darwin" |     :path: ".symlinks/plugins/shared_preferences_foundation/darwin" | ||||||
|  |   sqflite_darwin: | ||||||
|  |     :path: ".symlinks/plugins/sqflite_darwin/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: | ||||||
| @@ -363,29 +369,29 @@ SPEC CHECKSUMS: | |||||||
|   DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 |   DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 | ||||||
|   file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655 |   file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655 | ||||||
|   file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808 |   file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808 | ||||||
|   Firebase: cf1b19f21410b029b6786a54e9764a0cacad3c99 |   Firebase: 374a441a91ead896215703a674d58cdb3e9d772b | ||||||
|   firebase_analytics: 2815af29d49c1a994652abd37a5b001a88bc7b75 |   firebase_analytics: 07bd7cfbac54bfcdccf2bb2530f9a65486f7ef3f | ||||||
|   firebase_core: b62a5080210edad3f2934314a8b2c6f5124e8e10 |   firebase_core: feb37e79f775c2bd08dd35e02d83678291317e10 | ||||||
|   firebase_messaging: 98619a0572d82cfb3668e78859ba9f1110e268c9 |   firebase_messaging: e2f0ba891b1509668c07f5099761518a5af8fe3c | ||||||
|   FirebaseAnalytics: 3feef9ae8733c567866342a1000691baaa7cad49 |   FirebaseAnalytics: 7114c698cac995602e3b1b96663473e50d54d6e7 | ||||||
|   FirebaseCore: e0510f1523bc0eb21653cac00792e1e2bd6f1771 |   FirebaseCore: 48b0dd707581cf9c1a1220da68223fb0a562afaa | ||||||
|   FirebaseCoreInternal: d98ab91e2d80a56d7b246856a8885443b302c0c2 |   FirebaseCoreInternal: d98ab91e2d80a56d7b246856a8885443b302c0c2 | ||||||
|   FirebaseInstallations: 6ef4a1c7eb2a61ee1f74727d7f6ce2e72acf1414 |   FirebaseInstallations: efc0946fc756e4d22d8113f7c761948120322e8c | ||||||
|   FirebaseMessaging: f8a160d99c2c2e5babbbcc90c4a3e15db036aee2 |   FirebaseMessaging: e1aca1fcc23e8b9eddb0e33f375ff90944623021 | ||||||
|   Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 |   Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 | ||||||
|   flutter_app_update: 65f61da626cb111d1b24674abc4b01728d7723bc |   flutter_app_update: 65f61da626cb111d1b24674abc4b01728d7723bc | ||||||
|   flutter_native_splash: e8a1e01082d97a8099d973f919f57904c925008a |   flutter_native_splash: f71420956eb811e6d310720fee915f1d42852e7a | ||||||
|   flutter_udid: b2417673f287ee62817a1de3d1643f47b9f508ab |   flutter_udid: b2417673f287ee62817a1de3d1643f47b9f508ab | ||||||
|   flutter_webrtc: 1a53bd24f97bcfeff512f13699e721897f261563 |   flutter_webrtc: 90260f83024b1b96d239a575ea4e3708e79344d1 | ||||||
|   gal: 6a522c75909f1244732d4596d11d6a2f86ff37a5 |   gal: 6a522c75909f1244732d4596d11d6a2f86ff37a5 | ||||||
|   GoogleAppMeasurement: 987769c4ca6b968f2479fbcc9fe3ce34af454b8e |   GoogleAppMeasurement: 6a9e6317b6a6d810ad03d4a66564ca6c4c5818a3 | ||||||
|   GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 |   GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 | ||||||
|   GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d |   GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d | ||||||
|   home_widget: 0434835a4c9a75704264feff6be17ea40e0f0d57 |   home_widget: 0434835a4c9a75704264feff6be17ea40e0f0d57 | ||||||
|   image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1 |   image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1 | ||||||
|   in_app_review: a31b5257259646ea78e0e35fc914979b0031d011 |   in_app_review: a31b5257259646ea78e0e35fc914979b0031d011 | ||||||
|   Kingfisher: f2af9028b16baf9dc6c07c570072bc41cbf009ef |   Kingfisher: f2af9028b16baf9dc6c07c570072bc41cbf009ef | ||||||
|   livekit_client: 4eaa7a2968fc7e7c57888f43d90394547cc8d9e9 |   livekit_client: dcc5fd47ba69c98fc6baeb12e862c9d43807d976 | ||||||
|   media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1 |   media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1 | ||||||
|   media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a |   media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a | ||||||
|   media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e |   media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e | ||||||
| @@ -401,6 +407,7 @@ SPEC CHECKSUMS: | |||||||
|   SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8 |   SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8 | ||||||
|   share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f |   share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f | ||||||
|   shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 |   shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 | ||||||
|  |   sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d | ||||||
|   SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 |   SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 | ||||||
|   url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe |   url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe | ||||||
|   video_compress: fce97e4fb1dfd88175aa07d2ffc8a2f297f87fbe |   video_compress: fce97e4fb1dfd88175aa07d2ffc8a2f297f87fbe | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| import 'dart:async'; | import 'dart:async'; | ||||||
|  | import 'dart:convert'; | ||||||
| import 'dart:math' as math; | import 'dart:math' as math; | ||||||
|  |  | ||||||
| import 'package:collection/collection.dart'; | import 'package:collection/collection.dart'; | ||||||
| @@ -11,6 +12,7 @@ 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/websocket.dart'; | import 'package:surface/providers/websocket.dart'; | ||||||
| import 'package:surface/types/chat.dart'; | import 'package:surface/types/chat.dart'; | ||||||
|  | import 'package:surface/types/websocket.dart'; | ||||||
| import 'package:uuid/uuid.dart'; | import 'package:uuid/uuid.dart'; | ||||||
|  |  | ||||||
| class ChatMessageController extends ChangeNotifier { | class ChatMessageController extends ChangeNotifier { | ||||||
| @@ -36,8 +38,7 @@ class ChatMessageController extends ChangeNotifier { | |||||||
|  |  | ||||||
|   int? messageTotal; |   int? messageTotal; | ||||||
|  |  | ||||||
|   bool get isAllLoaded => |   bool get isAllLoaded => messageTotal != null && messages.length >= messageTotal!; | ||||||
|       messageTotal != null && messages.length >= messageTotal!; |  | ||||||
|  |  | ||||||
|   String? _boxKey; |   String? _boxKey; | ||||||
|   SnChannel? channel; |   SnChannel? channel; | ||||||
| @@ -50,8 +51,10 @@ 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 => |   Box<SnChatMessage>? get _box => (_boxKey == null || isPending) ? null : Hive.box<SnChatMessage>(_boxKey!); | ||||||
|       (_boxKey == null || isPending) ? null : Hive.box<SnChatMessage>(_boxKey!); |  | ||||||
|  |   final List<SnChannelMember> typingMembers = List.empty(growable: true); | ||||||
|  |   final Map<int, Timer> typingInactiveTimer = {}; | ||||||
|  |  | ||||||
|   Future<void> initialize(SnChannel chan) async { |   Future<void> initialize(SnChannel chan) async { | ||||||
|     channel = chan; |     channel = chan; | ||||||
| @@ -71,6 +74,7 @@ class ChatMessageController extends ChangeNotifier { | |||||||
|     _wsSubscription = _ws.stream.stream.listen((event) { |     _wsSubscription = _ws.stream.stream.listen((event) { | ||||||
|       switch (event.method) { |       switch (event.method) { | ||||||
|         case 'events.new': |         case 'events.new': | ||||||
|  |           if (event.payload?['channel_id'] != channel?.id) break; | ||||||
|           final payload = SnChatMessage.fromJson(event.payload!); |           final payload = SnChatMessage.fromJson(event.payload!); | ||||||
|           _addMessage(payload); |           _addMessage(payload); | ||||||
|           break; |           break; | ||||||
| @@ -78,22 +82,16 @@ class ChatMessageController extends ChangeNotifier { | |||||||
|           if (event.payload?['channel_id'] != channel?.id) break; |           if (event.payload?['channel_id'] != channel?.id) break; | ||||||
|           final member = SnChannelMember.fromJson(event.payload!['member']); |           final member = SnChannelMember.fromJson(event.payload!['member']); | ||||||
|           if (member.id == profile?.id) break; |           if (member.id == profile?.id) break; | ||||||
|         // TODO impl typing users |           if (!typingMembers.any((x) => x.id == member.id)) { | ||||||
|         // if (!_typingUsers.any((x) => x.id == member.id)) { |             typingMembers.add(member); | ||||||
|         //   setState(() { |             notifyListeners(); | ||||||
|         //     _typingUsers.add(member); |           } | ||||||
|         //   }); |           typingInactiveTimer[member.id]?.cancel(); | ||||||
|         // } |           typingInactiveTimer[member.id] = Timer(const Duration(seconds: 3), () { | ||||||
|         // _typingInactiveTimer[member.id]?.cancel(); |             typingMembers.removeWhere((x) => x.id == member.id); | ||||||
|         // _typingInactiveTimer[member.id] = Timer( |             typingInactiveTimer.remove(member.id); | ||||||
|         //   const Duration(seconds: 3), |             notifyListeners(); | ||||||
|         //   () { |           }); | ||||||
|         //     setState(() { |  | ||||||
|         //       _typingUsers.removeWhere((x) => x.id == member.id); |  | ||||||
|         //       _typingInactiveTimer.remove(member.id); |  | ||||||
|         //     }); |  | ||||||
|         //   }, |  | ||||||
|         // ); |  | ||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
| @@ -101,6 +99,35 @@ class ChatMessageController extends ChangeNotifier { | |||||||
|     notifyListeners(); |     notifyListeners(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   Timer? _typingNotifyTimer; | ||||||
|  |   bool _typingStatus = false; | ||||||
|  |  | ||||||
|  |   Future<void> _sendTypingStatusPackage() async { | ||||||
|  |     _ws.conn?.sink.add(jsonEncode( | ||||||
|  |       WebSocketPackage( | ||||||
|  |         method: 'status.typing', | ||||||
|  |         endpoint: 'im', | ||||||
|  |         payload: { | ||||||
|  |           'channel_id': channel!.id, | ||||||
|  |         }, | ||||||
|  |       ).toJson(), | ||||||
|  |     )); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void pingTypingStatus() { | ||||||
|  |     if (!_typingStatus) { | ||||||
|  |       _sendTypingStatusPackage(); | ||||||
|  |       _typingStatus = true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (_typingNotifyTimer == null || !_typingNotifyTimer!.isActive) { | ||||||
|  |       _typingNotifyTimer?.cancel(); | ||||||
|  |       _typingNotifyTimer = Timer(const Duration(milliseconds: 1850), () { | ||||||
|  |         _typingStatus = false; | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|   Future<void> _saveMessageToLocal(Iterable<SnChatMessage> messages) async { |   Future<void> _saveMessageToLocal(Iterable<SnChatMessage> messages) async { | ||||||
|     if (_box == null) return; |     if (_box == null) return; | ||||||
|     await _box!.putAll({ |     await _box!.putAll({ | ||||||
| @@ -167,8 +194,7 @@ 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 = |           final idx = messages.indexWhere((x) => x.id == message.relatedEventId); | ||||||
|               messages.indexWhere((x) => x.id == message.relatedEventId); |  | ||||||
|           if (idx != -1) { |           if (idx != -1) { | ||||||
|             final newBody = message.body; |             final newBody = message.body; | ||||||
|             newBody.remove('related_event'); |             newBody.remove('related_event'); | ||||||
| @@ -207,8 +233,7 @@ 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) |       if (attachments != null && attachments.isNotEmpty) 'attachments': attachments, | ||||||
|         'attachments': attachments, |  | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     // Mock the message locally |     // Mock the message locally | ||||||
| @@ -305,8 +330,7 @@ class ChatMessageController extends ChangeNotifier { | |||||||
|  |  | ||||||
|     if (out == null) { |     if (out == null) { | ||||||
|       try { |       try { | ||||||
|         final resp = await _sn.client |         final resp = await _sn.client.get('/cgi/im/channels/${channel!.keyPath}/events/$id'); | ||||||
|             .get('/cgi/im/channels/${channel!.keyPath}/events/$id'); |  | ||||||
|         out = SnChatMessage.fromJson(resp.data); |         out = SnChatMessage.fromJson(resp.data); | ||||||
|         _saveMessageToLocal([out]); |         _saveMessageToLocal([out]); | ||||||
|       } catch (_) { |       } catch (_) { | ||||||
| @@ -341,9 +365,7 @@ class ChatMessageController extends ChangeNotifier { | |||||||
|     bool forceRemote = false, |     bool forceRemote = false, | ||||||
|   }) async { |   }) async { | ||||||
|     late List<SnChatMessage> out; |     late List<SnChatMessage> out; | ||||||
|     if (_box != null && |     if (_box != null && (_box!.length >= take + offset || forceLocal) && !forceRemote) { | ||||||
|         (_box!.length >= take + offset || forceLocal) && |  | ||||||
|         !forceRemote) { |  | ||||||
|       out = _box!.keys |       out = _box!.keys | ||||||
|           .toList() |           .toList() | ||||||
|           .cast<int>() |           .cast<int>() | ||||||
| @@ -386,8 +408,7 @@ class ChatMessageController extends ChangeNotifier { | |||||||
|           quoteEvent: quoteEvent, |           quoteEvent: quoteEvent, | ||||||
|           attachments: attachments |           attachments: attachments | ||||||
|               .where( |               .where( | ||||||
|                 (ele) => |                 (ele) => out[i].body['attachments']?.contains(ele?.rid) ?? false, | ||||||
|                     out[i].body['attachments']?.contains(ele?.rid) ?? false, |  | ||||||
|               ) |               ) | ||||||
|               .toList(), |               .toList(), | ||||||
|         ), |         ), | ||||||
| @@ -395,10 +416,7 @@ class ChatMessageController extends ChangeNotifier { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Preload sender accounts |     // Preload sender accounts | ||||||
|     final accountId = out |     final accountId = out.where((ele) => ele.sender.accountId >= 0).map((ele) => ele.sender.accountId).toSet(); | ||||||
|         .where((ele) => ele.sender.accountId >= 0) |  | ||||||
|         .map((ele) => ele.sender.accountId) |  | ||||||
|         .toSet(); |  | ||||||
|     await _ud.listAccount(accountId); |     await _ud.listAccount(accountId); | ||||||
|  |  | ||||||
|     return out; |     return out; | ||||||
|   | |||||||
| @@ -104,7 +104,7 @@ class PostWriteMedia { | |||||||
|     if (attachment != null) { |     if (attachment != null) { | ||||||
|       final sn = context.read<SnNetworkProvider>(); |       final sn = context.read<SnNetworkProvider>(); | ||||||
|       final ImageProvider provider = UniversalImage.provider(sn.getAttachmentUrl(attachment!.rid)); |       final ImageProvider provider = UniversalImage.provider(sn.getAttachmentUrl(attachment!.rid)); | ||||||
|       if (width != null && height != null) { |       if (width != null && height != null && !kIsWeb) { | ||||||
|         return ResizeImage( |         return ResizeImage( | ||||||
|           provider, |           provider, | ||||||
|           width: width, |           width: width, | ||||||
| @@ -154,7 +154,10 @@ class PostWriteController extends ChangeNotifier { | |||||||
|   final TextEditingController descriptionController = TextEditingController(); |   final TextEditingController descriptionController = TextEditingController(); | ||||||
|   final TextEditingController aliasController = TextEditingController(); |   final TextEditingController aliasController = TextEditingController(); | ||||||
|  |  | ||||||
|   PostWriteController() { |   bool _temporarySaveActive = false; | ||||||
|  |  | ||||||
|  |   PostWriteController({bool doLoadFromTemporary = true}) { | ||||||
|  |     _temporarySaveActive = doLoadFromTemporary; | ||||||
|     titleController.addListener(() { |     titleController.addListener(() { | ||||||
|       _temporaryPlanSave(); |       _temporaryPlanSave(); | ||||||
|       notifyListeners(); |       notifyListeners(); | ||||||
| @@ -166,7 +169,7 @@ class PostWriteController extends ChangeNotifier { | |||||||
|     contentController.addListener(() { |     contentController.addListener(() { | ||||||
|       _temporaryPlanSave(); |       _temporaryPlanSave(); | ||||||
|     }); |     }); | ||||||
|     _temporaryLoad(); |     if (doLoadFromTemporary) _temporaryLoad(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   String mode = kTitleMap.keys.first; |   String mode = kTitleMap.keys.first; | ||||||
| @@ -213,11 +216,11 @@ class PostWriteController extends ChangeNotifier { | |||||||
|         aliasController.text = post.alias ?? ''; |         aliasController.text = post.alias ?? ''; | ||||||
|         publishedAt = post.publishedAt; |         publishedAt = post.publishedAt; | ||||||
|         publishedUntil = post.publishedUntil; |         publishedUntil = post.publishedUntil; | ||||||
|         visibleUsers = List.from(post.visibleUsersList ?? []); |         visibleUsers = List.from(post.visibleUsersList ?? [], growable: true); | ||||||
|         invisibleUsers = List.from(post.invisibleUsersList ?? []); |         invisibleUsers = List.from(post.invisibleUsersList ?? [], growable: true); | ||||||
|         visibility = post.visibility; |         visibility = post.visibility; | ||||||
|         tags = List.from(post.tags.map((ele) => ele.alias)); |         tags = List.from(post.tags.map((ele) => ele.alias), growable: true); | ||||||
|         categories = List.from(post.categories.map((ele) => ele.alias)); |         categories = List.from(post.categories.map((ele) => ele.alias), growable: true); | ||||||
|         attachments.addAll(post.preload?.attachments?.map((ele) => PostWriteMedia(ele)) ?? []); |         attachments.addAll(post.preload?.attachments?.map((ele) => PostWriteMedia(ele)) ?? []); | ||||||
|  |  | ||||||
|         if (post.preload?.thumbnail != null && (post.preload?.thumbnail?.rid.isNotEmpty ?? false)) { |         if (post.preload?.thumbnail != null && (post.preload?.thumbnail?.rid.isNotEmpty ?? false)) { | ||||||
| @@ -317,6 +320,7 @@ class PostWriteController extends ChangeNotifier { | |||||||
|   Timer? _temporarySaveTimer; |   Timer? _temporarySaveTimer; | ||||||
|  |  | ||||||
|   void _temporaryPlanSave() { |   void _temporaryPlanSave() { | ||||||
|  |     if (!_temporarySaveActive) return; | ||||||
|     _temporarySaveTimer?.cancel(); |     _temporarySaveTimer?.cancel(); | ||||||
|     _temporarySaveTimer = Timer(const Duration(seconds: 1), () { |     _temporarySaveTimer = Timer(const Duration(seconds: 1), () { | ||||||
|       _temporarySave(); |       _temporarySave(); | ||||||
| @@ -344,9 +348,10 @@ class PostWriteController extends ChangeNotifier { | |||||||
|           if (titleController.text.isNotEmpty) 'title': titleController.text, |           if (titleController.text.isNotEmpty) 'title': titleController.text, | ||||||
|           if (descriptionController.text.isNotEmpty) 'description': descriptionController.text, |           if (descriptionController.text.isNotEmpty) 'description': descriptionController.text, | ||||||
|           if (thumbnail != null && thumbnail!.attachment != null) 'thumbnail': thumbnail!.attachment!.toJson(), |           if (thumbnail != null && thumbnail!.attachment != null) 'thumbnail': thumbnail!.attachment!.toJson(), | ||||||
|           'attachments': attachments.where((e) => e.attachment != null).map((e) => e.attachment!.toJson()).toList(), |           'attachments': | ||||||
|           'tags': tags.map((ele) => {'alias': ele}).toList(), |               attachments.where((e) => e.attachment != null).map((e) => e.attachment!.toJson()).toList(growable: true), | ||||||
|           'categories': categories.map((ele) => {'alias': ele}).toList(), |           'tags': tags.map((ele) => {'alias': ele}).toList(growable: true), | ||||||
|  |           'categories': categories.map((ele) => {'alias': ele}).toList(growable: true), | ||||||
|           'visibility': visibility, |           'visibility': visibility, | ||||||
|           'visible_users_list': visibleUsers, |           'visible_users_list': visibleUsers, | ||||||
|           'invisible_users_list': invisibleUsers, |           'invisible_users_list': invisibleUsers, | ||||||
| @@ -622,13 +627,15 @@ class PostWriteController extends ChangeNotifier { | |||||||
|   void reset() { |   void reset() { | ||||||
|     publishedAt = null; |     publishedAt = null; | ||||||
|     publishedUntil = null; |     publishedUntil = null; | ||||||
|  |     thumbnail = null; | ||||||
|  |     visibility = 0; | ||||||
|     titleController.clear(); |     titleController.clear(); | ||||||
|     descriptionController.clear(); |     descriptionController.clear(); | ||||||
|     contentController.clear(); |     contentController.clear(); | ||||||
|     aliasController.clear(); |     aliasController.clear(); | ||||||
|     tags.clear(); |     tags = List.empty(growable: true); | ||||||
|     categories.clear(); |     categories = List.empty(growable: true); | ||||||
|     attachments.clear(); |     attachments = List.empty(growable: true); | ||||||
|     editingPost = null; |     editingPost = null; | ||||||
|     replyingPost = null; |     replyingPost = null; | ||||||
|     repostingPost = null; |     repostingPost = null; | ||||||
|   | |||||||
| @@ -10,7 +10,6 @@ import 'package:easy_localization_loader/easy_localization_loader.dart'; | |||||||
| import 'package:firebase_core/firebase_core.dart'; | import 'package:firebase_core/firebase_core.dart'; | ||||||
| import 'package:flutter/foundation.dart'; | import 'package:flutter/foundation.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.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:hive_flutter/hive_flutter.dart'; | ||||||
| import 'package:package_info_plus/package_info_plus.dart'; | import 'package:package_info_plus/package_info_plus.dart'; | ||||||
| @@ -18,7 +17,6 @@ import 'package:provider/provider.dart'; | |||||||
| import 'package:relative_time/relative_time.dart'; | import 'package:relative_time/relative_time.dart'; | ||||||
| import 'package:responsive_framework/responsive_framework.dart'; | import 'package:responsive_framework/responsive_framework.dart'; | ||||||
| import 'package:shared_preferences/shared_preferences.dart'; | import 'package:shared_preferences/shared_preferences.dart'; | ||||||
| import 'package:styled_widget/styled_widget.dart'; |  | ||||||
| import 'package:surface/firebase_options.dart'; | 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'; | ||||||
| @@ -30,6 +28,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_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'; | ||||||
| import 'package:surface/providers/user_directory.dart'; | import 'package:surface/providers/user_directory.dart'; | ||||||
| @@ -41,7 +40,6 @@ import 'package:surface/types/chat.dart'; | |||||||
| import 'package:surface/types/realm.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:surface/widgets/version_label.dart'; |  | ||||||
| import 'package:version/version.dart'; | import 'package:version/version.dart'; | ||||||
| import 'package:workmanager/workmanager.dart'; | import 'package:workmanager/workmanager.dart'; | ||||||
| import 'package:in_app_review/in_app_review.dart'; | import 'package:in_app_review/in_app_review.dart'; | ||||||
| @@ -144,6 +142,7 @@ class SolianApp extends StatelessWidget { | |||||||
|             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)), | ||||||
|  |             Provider(create: (ctx) => SnStickerProvider(ctx)), | ||||||
|             ChangeNotifierProvider(create: (ctx) => UserProvider(ctx)), |             ChangeNotifierProvider(create: (ctx) => UserProvider(ctx)), | ||||||
|             ChangeNotifierProvider(create: (ctx) => WebSocketProvider(ctx)), |             ChangeNotifierProvider(create: (ctx) => WebSocketProvider(ctx)), | ||||||
|             ChangeNotifierProvider(create: (ctx) => NotificationProvider(ctx)), |             ChangeNotifierProvider(create: (ctx) => NotificationProvider(ctx)), | ||||||
| @@ -208,8 +207,6 @@ class _AppSplashScreen extends StatefulWidget { | |||||||
| } | } | ||||||
|  |  | ||||||
| class _AppSplashScreenState extends State<_AppSplashScreen> { | class _AppSplashScreenState extends State<_AppSplashScreen> { | ||||||
|   bool _isReady = false; |  | ||||||
|  |  | ||||||
|   void _tryRequestRating() async { |   void _tryRequestRating() async { | ||||||
|     final prefs = await SharedPreferences.getInstance(); |     final prefs = await SharedPreferences.getInstance(); | ||||||
|     if (prefs.containsKey('first_boot_time')) { |     if (prefs.containsKey('first_boot_time')) { | ||||||
| @@ -261,6 +258,10 @@ class _AppSplashScreenState extends State<_AppSplashScreen> { | |||||||
|  |  | ||||||
|   Future<void> _initialize() async { |   Future<void> _initialize() async { | ||||||
|     try { |     try { | ||||||
|  |       final cfg = context.read<ConfigProvider>(); | ||||||
|  |       WidgetsBinding.instance.addPostFrameCallback((_) { | ||||||
|  |         cfg.calcDrawerSize(context); | ||||||
|  |       }); | ||||||
|       final home = context.read<HomeWidgetProvider>(); |       final home = context.read<HomeWidgetProvider>(); | ||||||
|       await home.initialize(); |       await home.initialize(); | ||||||
|       if (!mounted) return; |       if (!mounted) return; | ||||||
| @@ -278,12 +279,11 @@ class _AppSplashScreenState extends State<_AppSplashScreen> { | |||||||
|       await ws.tryConnect(); |       await ws.tryConnect(); | ||||||
|       if (!mounted) return; |       if (!mounted) return; | ||||||
|       final notify = context.read<NotificationProvider>(); |       final notify = context.read<NotificationProvider>(); | ||||||
|  |       notify.listen(); | ||||||
|       await notify.registerPushNotifications(); |       await notify.registerPushNotifications(); | ||||||
|     } catch (err) { |     } catch (err) { | ||||||
|       if (!mounted) return; |       if (!mounted) return; | ||||||
|       await context.showErrorDialog(err); |       await context.showErrorDialog(err); | ||||||
|     } finally { |  | ||||||
|       setState(() => _isReady = true); |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -303,32 +303,17 @@ class _AppSplashScreenState extends State<_AppSplashScreen> { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     if (!_isReady) { |     final cfg = context.read<ConfigProvider>(); | ||||||
|       return Scaffold( |     return NotificationListener<SizeChangedLayoutNotification>( | ||||||
|         backgroundColor: Theme.of(context).colorScheme.surface, |       onNotification: (notification) { | ||||||
|         body: Container( |         WidgetsBinding.instance.addPostFrameCallback((_) { | ||||||
|           constraints: const BoxConstraints(maxWidth: 180), |           cfg.calcDrawerSize(context); | ||||||
|           child: Column( |         }); | ||||||
|             mainAxisAlignment: MainAxisAlignment.center, |         return false; | ||||||
|             mainAxisSize: MainAxisSize.min, |       }, | ||||||
|             children: [ |       child: SizeChangedLayoutNotifier( | ||||||
|               if (MediaQuery.of(context).platformBrightness == Brightness.dark) |         child: widget.child, | ||||||
|                 Image.asset("assets/icon/icon-dark.png", width: 64, height: 64) |  | ||||||
|               else |  | ||||||
|                 Image.asset("assets/icon/icon.png", width: 64, height: 64), |  | ||||||
|               const Gap(6), |  | ||||||
|               LinearProgressIndicator( |  | ||||||
|                 backgroundColor: Theme.of(context).colorScheme.surfaceContainer, |  | ||||||
|       ), |       ), | ||||||
|               const Gap(20), |  | ||||||
|               Text('appInitializing'.tr(), textAlign: TextAlign.center), |  | ||||||
|               AppVersionLabel(), |  | ||||||
|             ], |  | ||||||
|           ), |  | ||||||
|         ).center(), |  | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|     return widget.child; |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:provider/provider.dart'; | import 'package:provider/provider.dart'; | ||||||
|  | import 'package:responsive_framework/responsive_framework.dart'; | ||||||
| import 'package:shared_preferences/shared_preferences.dart'; | import 'package:shared_preferences/shared_preferences.dart'; | ||||||
| import 'package:surface/providers/widget.dart'; | import 'package:surface/providers/widget.dart'; | ||||||
|  |  | ||||||
| @@ -12,6 +13,10 @@ const kNetworkServerStoreKey = 'app_server_url'; | |||||||
| const kAppbarTransparentStoreKey = 'app_bar_transparent'; | const kAppbarTransparentStoreKey = 'app_bar_transparent'; | ||||||
| const kAppBackgroundStoreKey = 'app_has_background'; | const kAppBackgroundStoreKey = 'app_has_background'; | ||||||
| const kAppColorSchemeStoreKey = 'app_color_scheme'; | const kAppColorSchemeStoreKey = 'app_color_scheme'; | ||||||
|  | const kAppDrawerPreferCollapse = 'app_drawer_prefer_collapse'; | ||||||
|  | const kAppNotifyWithHaptic = 'app_notify_with_haptic'; | ||||||
|  | const kAppExpandPostLink = 'app_expand_post_link'; | ||||||
|  | const kAppExpandChatLink = 'app_expand_chat_link'; | ||||||
|  |  | ||||||
| const Map<String, FilterQuality> kImageQualityLevel = { | const Map<String, FilterQuality> kImageQualityLevel = { | ||||||
|   'settingsImageQualityLowest': FilterQuality.none, |   'settingsImageQualityLowest': FilterQuality.none, | ||||||
| @@ -33,6 +38,24 @@ class ConfigProvider extends ChangeNotifier { | |||||||
|     prefs = await SharedPreferences.getInstance(); |     prefs = await SharedPreferences.getInstance(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   bool drawerIsCollapsed = false; | ||||||
|  |   bool drawerIsExpanded = false; | ||||||
|  |  | ||||||
|  |   void calcDrawerSize(BuildContext context) { | ||||||
|  |     final rpb = ResponsiveBreakpoints.of(context); | ||||||
|  |     final newDrawerIsCollapsed = rpb.smallerOrEqualTo(MOBILE); | ||||||
|  |     final newDrawerIsExpanded = rpb.largerThan(TABLET) | ||||||
|  |         ? (prefs.getBool(kAppDrawerPreferCollapse) ?? false) | ||||||
|  |             ? false | ||||||
|  |             : true | ||||||
|  |         : false; | ||||||
|  |     if (newDrawerIsExpanded != drawerIsExpanded || newDrawerIsCollapsed != drawerIsCollapsed) { | ||||||
|  |       drawerIsExpanded = newDrawerIsExpanded; | ||||||
|  |       drawerIsCollapsed = newDrawerIsCollapsed; | ||||||
|  |       notifyListeners(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|   FilterQuality get imageQuality { |   FilterQuality get imageQuality { | ||||||
|     return kImageQualityLevel.values.elementAtOrNull(prefs.getInt('app_image_quality') ?? 3) ?? FilterQuality.high; |     return kImageQualityLevel.values.elementAtOrNull(prefs.getInt('app_image_quality') ?? 3) ?? FilterQuality.high; | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -4,18 +4,26 @@ import 'dart:io'; | |||||||
| 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_udid/flutter_udid.dart'; | import 'package:flutter_udid/flutter_udid.dart'; | ||||||
| import 'package:provider/provider.dart'; | import 'package:provider/provider.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/providers/websocket.dart'; | ||||||
|  | import 'package:surface/types/notification.dart'; | ||||||
|  |  | ||||||
| class NotificationProvider extends ChangeNotifier { | class NotificationProvider extends ChangeNotifier { | ||||||
|   late final SnNetworkProvider _sn; |   late final SnNetworkProvider _sn; | ||||||
|   late final UserProvider _ua; |   late final UserProvider _ua; | ||||||
|  |   late final WebSocketProvider _ws; | ||||||
|  |   late final ConfigProvider _cfg; | ||||||
|  |  | ||||||
|   NotificationProvider(BuildContext context) { |   NotificationProvider(BuildContext context) { | ||||||
|     _sn = context.read<SnNetworkProvider>(); |     _sn = context.read<SnNetworkProvider>(); | ||||||
|     _ua = context.read<UserProvider>(); |     _ua = context.read<UserProvider>(); | ||||||
|  |     _ws = context.read<WebSocketProvider>(); | ||||||
|  |     _cfg = context.read<ConfigProvider>(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   Future<void> registerPushNotifications() async { |   Future<void> registerPushNotifications() async { | ||||||
| @@ -62,4 +70,23 @@ class NotificationProvider extends ChangeNotifier { | |||||||
|       }, |       }, | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   List<SnNotification> notifications = List.empty(growable: true); | ||||||
|  |  | ||||||
|  |   void listen() { | ||||||
|  |     _ws.stream.stream.listen((event) { | ||||||
|  |       if (event.method == 'notifications.new') { | ||||||
|  |         final notification = SnNotification.fromJson(event.payload!); | ||||||
|  |         notifications.add(notification); | ||||||
|  |         notifyListeners(); | ||||||
|  |         final doHaptic = _cfg.prefs.getBool(kAppNotifyWithHaptic) ?? true; | ||||||
|  |         if (doHaptic) HapticFeedback.lightImpact(); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void clear() { | ||||||
|  |     notifications.clear(); | ||||||
|  |     notifyListeners(); | ||||||
|  |   } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										38
									
								
								lib/providers/sn_sticker.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								lib/providers/sn_sticker.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | |||||||
|  | import 'dart:developer'; | ||||||
|  |  | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:provider/provider.dart'; | ||||||
|  | import 'package:surface/providers/sn_network.dart'; | ||||||
|  | import 'package:surface/types/attachment.dart'; | ||||||
|  |  | ||||||
|  | class SnStickerProvider { | ||||||
|  |   late final SnNetworkProvider _sn; | ||||||
|  |   final Map<String, SnSticker?> _cache = {}; | ||||||
|  |  | ||||||
|  |   SnStickerProvider(BuildContext context) { | ||||||
|  |     _sn = context.read<SnNetworkProvider>(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   bool hasNotSticker(String alias) { | ||||||
|  |     return _cache.containsKey(alias) && _cache[alias] == null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Future<SnSticker?> lookupSticker(String alias) async { | ||||||
|  |     if (_cache.containsKey(alias)) { | ||||||
|  |       return _cache[alias]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     try { | ||||||
|  |       final resp = await _sn.client.get('/cgi/uc/stickers/lookup/$alias'); | ||||||
|  |       final sticker = SnSticker.fromJson(resp.data); | ||||||
|  |       _cache[alias] = sticker; | ||||||
|  |  | ||||||
|  |       return sticker; | ||||||
|  |     } catch (err) { | ||||||
|  |       _cache[alias] = null; | ||||||
|  |       log('[Sticker] Failed to lookup sticker $alias: $err'); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -3,9 +3,12 @@ import 'package:provider/provider.dart'; | |||||||
| import 'package:surface/providers/userinfo.dart'; | import 'package:surface/providers/userinfo.dart'; | ||||||
|  |  | ||||||
| // Stored as key: month, day | // Stored as key: month, day | ||||||
| const Map<String, (int, int)> kSpecialDays = { | final Map<String, (int, int)> kSpecialDays = { | ||||||
|   // Birthday is dynamically generated according to the user's profile |   // Birthday is dynamically generated according to the user's profile | ||||||
|   'NewYear': (1, 1), |   'NewYear': (1, 1), | ||||||
|  |   'LunarNewYear': (lunarToGregorian(null, 1, 1).month, lunarToGregorian(null, 1, 1).day), | ||||||
|  |   'MidAutumn': (lunarToGregorian(null, 8, 15).month, lunarToGregorian(null, 8, 15).day), | ||||||
|  |   'DragonBoat': (lunarToGregorian(null, 5, 5).month, lunarToGregorian(null, 5, 5).day), | ||||||
|   'ValentineDay': (2, 14), |   'ValentineDay': (2, 14), | ||||||
|   'LaborDay': (5, 1), |   'LaborDay': (5, 1), | ||||||
|   'MotherDay': (5, 11), |   'MotherDay': (5, 11), | ||||||
| @@ -19,6 +22,9 @@ const Map<String, (int, int)> kSpecialDays = { | |||||||
| const Map<String, String> kSpecialDaysSymbol = { | const Map<String, String> kSpecialDaysSymbol = { | ||||||
|   'Birthday': '🎂', |   'Birthday': '🎂', | ||||||
|   'NewYear': '🎉', |   'NewYear': '🎉', | ||||||
|  |   'LunarNewYear': '🎉', | ||||||
|  |   'MidAutumn': '🥮', | ||||||
|  |   'DragonBoat': '🐲', | ||||||
|   'MerryXmas': '🎄', |   'MerryXmas': '🎄', | ||||||
|   'ValentineDay': '💑', |   'ValentineDay': '💑', | ||||||
|   'LaborDay': '🏋️', |   'LaborDay': '🏋️', | ||||||
| @@ -134,3 +140,45 @@ class SpecialDayProvider { | |||||||
|     return (elapsedDuration / totalDuration).clamp(0.0, 1.0); |     return (elapsedDuration / totalDuration).clamp(0.0, 1.0); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | final Map<int, LunarYear> lunarYearData = { | ||||||
|  |   2025: LunarYear( | ||||||
|  |     startDate: DateTime(2025, 1, 29), | ||||||
|  |     months: [29, 30, 30, 29, 30, 29, 29, 30, 30, 29, 30, 29], | ||||||
|  |     leapMonth: 0, | ||||||
|  |   ), | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class LunarYear { | ||||||
|  |   final DateTime startDate; | ||||||
|  |   final List<int> months; | ||||||
|  |   final int leapMonth; | ||||||
|  |  | ||||||
|  |   LunarYear({required this.startDate, required this.months, required this.leapMonth}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | DateTime lunarToGregorian(int? year, int month, int day, {bool isLeapMonth = false}) { | ||||||
|  |   year = year ?? DateTime.now().year; | ||||||
|  |   final lunarYear = lunarYearData[year]; | ||||||
|  |   if (lunarYear == null) { | ||||||
|  |     throw Exception('Lunar data for year $year not found'); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   int leapMonth = lunarYear.leapMonth; | ||||||
|  |   if (isLeapMonth && (leapMonth == 0 || leapMonth != month)) { | ||||||
|  |     throw Exception('Invalid leap month for year $year'); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   int daysFromStart = 0; | ||||||
|  |   for (int i = 0; i < month - 1; i++) { | ||||||
|  |     daysFromStart += lunarYear.months[i]; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (isLeapMonth) { | ||||||
|  |     daysFromStart += lunarYear.months[month - 1]; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   daysFromStart += day - 1; | ||||||
|  |  | ||||||
|  |   return lunarYear.startDate.add(Duration(days: daysFromStart)); | ||||||
|  | } | ||||||
|   | |||||||
| @@ -35,7 +35,7 @@ class WebSocketProvider extends ChangeNotifier { | |||||||
|  |  | ||||||
|   Future<void> connect({noRetry = false}) async { |   Future<void> connect({noRetry = false}) async { | ||||||
|     if (!_ua.isAuthorized) return; |     if (!_ua.isAuthorized) return; | ||||||
|     if (isConnected) { |     if (isConnected || conn != null) { | ||||||
|       disconnect(); |       disconnect(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -97,7 +97,7 @@ class WebSocketProvider extends ChangeNotifier { | |||||||
|       onError: (err) { |       onError: (err) { | ||||||
|         isConnected = false; |         isConnected = false; | ||||||
|         notifyListeners(); |         notifyListeners(); | ||||||
|         Future.delayed(const Duration(seconds: 11), () => connect()); |         Future.delayed(const Duration(seconds: 1), () => connect()); | ||||||
|       }, |       }, | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; | |||||||
| import 'package:go_router/go_router.dart'; | import 'package:go_router/go_router.dart'; | ||||||
| import 'package:surface/screens/abuse_report.dart'; | import 'package:surface/screens/abuse_report.dart'; | ||||||
| import 'package:surface/screens/account.dart'; | import 'package:surface/screens/account.dart'; | ||||||
| import 'package:surface/screens/account/pfp.dart'; | import 'package:surface/screens/account/profile_page.dart'; | ||||||
| import 'package:surface/screens/account/profile_edit.dart'; | import 'package:surface/screens/account/profile_edit.dart'; | ||||||
| import 'package:surface/screens/account/publishers/publisher_edit.dart'; | import 'package:surface/screens/account/publishers/publisher_edit.dart'; | ||||||
| import 'package:surface/screens/account/publishers/publisher_new.dart'; | import 'package:surface/screens/account/publishers/publisher_new.dart'; | ||||||
| @@ -36,10 +36,7 @@ import 'package:surface/widgets/navigation/app_scaffold.dart'; | |||||||
|  |  | ||||||
| final _appRoutes = [ | final _appRoutes = [ | ||||||
|   ShellRoute( |   ShellRoute( | ||||||
|     builder: (context, state, child) => AppPageScaffold( |     builder: (context, state, child) => child, | ||||||
|       body: child, |  | ||||||
|       showAppBar: false, |  | ||||||
|     ), |  | ||||||
|     routes: [ |     routes: [ | ||||||
|       GoRoute( |       GoRoute( | ||||||
|         path: '/', |         path: '/', | ||||||
| @@ -58,8 +55,7 @@ final _appRoutes = [ | |||||||
|           GoRoute( |           GoRoute( | ||||||
|             path: '/write/:mode', |             path: '/write/:mode', | ||||||
|             name: 'postEditor', |             name: 'postEditor', | ||||||
|             builder: (context, state) => AppBackground( |             builder: (context, state) => PostEditorScreen( | ||||||
|               child: PostEditorScreen( |  | ||||||
|               mode: state.pathParameters['mode']!, |               mode: state.pathParameters['mode']!, | ||||||
|               postEditId: int.tryParse( |               postEditId: int.tryParse( | ||||||
|                 state.uri.queryParameters['editing'] ?? '', |                 state.uri.queryParameters['editing'] ?? '', | ||||||
| @@ -73,40 +69,41 @@ final _appRoutes = [ | |||||||
|               extraProps: state.extra as PostEditorExtraProps?, |               extraProps: state.extra as PostEditorExtraProps?, | ||||||
|             ), |             ), | ||||||
|           ), |           ), | ||||||
|           ), |  | ||||||
|           GoRoute( |           GoRoute( | ||||||
|             path: '/search', |             path: '/search', | ||||||
|             name: 'postSearch', |             name: 'postSearch', | ||||||
|             builder: (context, state) => AppBackground( |             builder: (context, state) => PostSearchScreen( | ||||||
|               child: 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) => AppBackground( |             builder: (context, state) => PostPublisherScreen(name: state.pathParameters['name']!), | ||||||
|               child: PostPublisherScreen(name: state.pathParameters['name']!), |  | ||||||
|             ), |  | ||||||
|           ), |           ), | ||||||
|           GoRoute( |           GoRoute( | ||||||
|             path: '/:slug', |             path: '/:slug', | ||||||
|             name: 'postDetail', |             name: 'postDetail', | ||||||
|             builder: (context, state) => AppBackground( |             builder: (context, state) => PostDetailScreen( | ||||||
|               child: PostDetailScreen( |  | ||||||
|               slug: state.pathParameters['slug']!, |               slug: state.pathParameters['slug']!, | ||||||
|               preload: state.extra as SnPost?, |               preload: state.extra as SnPost?, | ||||||
|             ), |             ), | ||||||
|           ), |           ), | ||||||
|           ), |  | ||||||
|         ], |         ], | ||||||
|       ), |       ), | ||||||
|       GoRoute( |       GoRoute( | ||||||
|         path: '/account', |         path: '/account', | ||||||
|         name: 'account', |         name: 'account', | ||||||
|         pageBuilder: (context, state) => NoTransitionPage( |         pageBuilder: (context, state) => CustomTransitionPage( | ||||||
|  |           transitionsBuilder: (context, animation, secondaryAnimation, child) { | ||||||
|  |             return FadeThroughTransition( | ||||||
|  |               animation: animation, | ||||||
|  |               secondaryAnimation: secondaryAnimation, | ||||||
|  |               fillColor: Colors.transparent, | ||||||
|  |               child: child, | ||||||
|  |             ); | ||||||
|  |           }, | ||||||
|           child: const AccountScreen(), |           child: const AccountScreen(), | ||||||
|         ), |         ), | ||||||
|         routes: [], |         routes: [], | ||||||
| @@ -114,7 +111,15 @@ final _appRoutes = [ | |||||||
|       GoRoute( |       GoRoute( | ||||||
|         path: '/chat', |         path: '/chat', | ||||||
|         name: 'chat', |         name: 'chat', | ||||||
|         pageBuilder: (context, state) => NoTransitionPage( |         pageBuilder: (context, state) => CustomTransitionPage( | ||||||
|  |           transitionsBuilder: (context, animation, secondaryAnimation, child) { | ||||||
|  |             return FadeThroughTransition( | ||||||
|  |               animation: animation, | ||||||
|  |               secondaryAnimation: secondaryAnimation, | ||||||
|  |               fillColor: Colors.transparent, | ||||||
|  |               child: child, | ||||||
|  |             ); | ||||||
|  |           }, | ||||||
|           child: const ChatScreen(), |           child: const ChatScreen(), | ||||||
|         ), |         ), | ||||||
|         routes: [ |         routes: [ | ||||||
| @@ -228,59 +233,45 @@ final _appRoutes = [ | |||||||
|     ], |     ], | ||||||
|   ), |   ), | ||||||
|   ShellRoute( |   ShellRoute( | ||||||
|     builder: (context, state, child) => AppPageScaffold(body: child), |     builder: (context, state, child) => child, | ||||||
|     routes: [ |     routes: [ | ||||||
|       GoRoute( |       GoRoute( | ||||||
|         path: '/auth/login', |         path: '/auth/login', | ||||||
|         name: 'authLogin', |         name: 'authLogin', | ||||||
|         builder: (context, state) => const AppBackground( |         builder: (context, state) => LoginScreen(), | ||||||
|           child: LoginScreen(), |  | ||||||
|         ), |  | ||||||
|       ), |       ), | ||||||
|       GoRoute( |       GoRoute( | ||||||
|         path: '/auth/register', |         path: '/auth/register', | ||||||
|         name: 'authRegister', |         name: 'authRegister', | ||||||
|         builder: (context, state) => const AppBackground( |         builder: (context, state) => RegisterScreen(), | ||||||
|           child: RegisterScreen(), |  | ||||||
|         ), |  | ||||||
|       ), |       ), | ||||||
|       GoRoute( |       GoRoute( | ||||||
|         path: '/reports', |         path: '/reports', | ||||||
|         name: 'abuseReport', |         name: 'abuseReport', | ||||||
|         builder: (context, state) => const AppBackground( |         builder: (context, state) => AbuseReportScreen(), | ||||||
|           child: AbuseReportScreen(), |  | ||||||
|         ), |  | ||||||
|       ), |       ), | ||||||
|       GoRoute( |       GoRoute( | ||||||
|         path: '/account/profile/edit', |         path: '/account/profile/edit', | ||||||
|         name: 'accountProfileEdit', |         name: 'accountProfileEdit', | ||||||
|         builder: (context, state) => const AppBackground( |         builder: (context, state) => ProfileEditScreen(), | ||||||
|           child: ProfileEditScreen(), |  | ||||||
|         ), |  | ||||||
|       ), |       ), | ||||||
|       GoRoute( |       GoRoute( | ||||||
|         path: '/account/publishers', |         path: '/account/publishers', | ||||||
|         name: 'accountPublishers', |         name: 'accountPublishers', | ||||||
|         builder: (context, state) => const AppBackground( |         builder: (context, state) => PublisherScreen(), | ||||||
|           child: PublisherScreen(), |  | ||||||
|         ), |  | ||||||
|       ), |       ), | ||||||
|       GoRoute( |       GoRoute( | ||||||
|         path: '/account/publishers/new', |         path: '/account/publishers/new', | ||||||
|         name: 'accountPublisherNew', |         name: 'accountPublisherNew', | ||||||
|         builder: (context, state) => const AppBackground( |         builder: (context, state) => AccountPublisherNewScreen(), | ||||||
|           child: AccountPublisherNewScreen(), |  | ||||||
|         ), |  | ||||||
|       ), |       ), | ||||||
|       GoRoute( |       GoRoute( | ||||||
|         path: '/account/publishers/edit/:name', |         path: '/account/publishers/edit/:name', | ||||||
|         name: 'accountPublisherEdit', |         name: 'accountPublisherEdit', | ||||||
|         builder: (context, state) => AppBackground( |         builder: (context, state) => AccountPublisherEditScreen( | ||||||
|           child: AccountPublisherEditScreen( |  | ||||||
|           name: state.pathParameters['name']!, |           name: state.pathParameters['name']!, | ||||||
|         ), |         ), | ||||||
|       ), |       ), | ||||||
|       ), |  | ||||||
|     ], |     ], | ||||||
|   ), |   ), | ||||||
|   GoRoute( |   GoRoute( | ||||||
| @@ -291,26 +282,22 @@ final _appRoutes = [ | |||||||
|     ), |     ), | ||||||
|   ), |   ), | ||||||
|   ShellRoute( |   ShellRoute( | ||||||
|     builder: (context, state, child) => AppPageScaffold(body: child), |     builder: (context, state, child) => child, | ||||||
|     routes: [ |     routes: [ | ||||||
|       GoRoute( |       GoRoute( | ||||||
|         path: '/settings', |         path: '/settings', | ||||||
|         name: 'settings', |         name: 'settings', | ||||||
|         builder: (context, state) => const AppBackground( |         builder: (context, state) => SettingsScreen(), | ||||||
|           child: SettingsScreen(), |  | ||||||
|         ), |  | ||||||
|       ), |       ), | ||||||
|     ], |     ], | ||||||
|   ), |   ), | ||||||
|   ShellRoute( |   ShellRoute( | ||||||
|     builder: (context, state, child) => AppPageScaffold(body: child), |     builder: (context, state, child) => child, | ||||||
|     routes: [ |     routes: [ | ||||||
|       GoRoute( |       GoRoute( | ||||||
|         path: '/about', |         path: '/about', | ||||||
|         name: 'about', |         name: 'about', | ||||||
|         builder: (context, state) => const AppBackground( |         builder: (context, state) => AboutScreen(), | ||||||
|           child: AboutScreen(), |  | ||||||
|         ), |  | ||||||
|       ), |       ), | ||||||
|     ], |     ], | ||||||
|   ), |   ), | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ 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'; | ||||||
| import 'package:surface/widgets/dialog.dart'; | import 'package:surface/widgets/dialog.dart'; | ||||||
|  | import 'package:surface/widgets/navigation/app_scaffold.dart'; | ||||||
|  |  | ||||||
| import '../types/account.dart'; | import '../types/account.dart'; | ||||||
|  |  | ||||||
| @@ -56,7 +57,11 @@ class _AbuseReportScreenState extends State<AbuseReportScreen> { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return Scaffold( |     return AppScaffold( | ||||||
|  |       appBar: AppBar( | ||||||
|  |         leading: const PageBackButton(), | ||||||
|  |         title: Text('screenAbuseReport').tr(), | ||||||
|  |       ), | ||||||
|       body: Column( |       body: Column( | ||||||
|         children: [ |         children: [ | ||||||
|           ListTile( |           ListTile( | ||||||
| @@ -73,6 +78,7 @@ class _AbuseReportScreenState extends State<AbuseReportScreen> { | |||||||
|           else |           else | ||||||
|             Expanded( |             Expanded( | ||||||
|               child: ListView.builder( |               child: ListView.builder( | ||||||
|  |                 padding: EdgeInsets.only(top: 8), | ||||||
|                 itemCount: _reports.length, |                 itemCount: _reports.length, | ||||||
|                 itemBuilder: (context, idx) { |                 itemBuilder: (context, idx) { | ||||||
|                   return ListTile( |                   return ListTile( | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ import 'package:surface/providers/websocket.dart'; | |||||||
| import 'package:surface/widgets/account/account_image.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'; | ||||||
|  |  | ||||||
| class AccountScreen extends StatelessWidget { | class AccountScreen extends StatelessWidget { | ||||||
|   const AccountScreen({super.key}); |   const AccountScreen({super.key}); | ||||||
| @@ -20,7 +21,7 @@ class AccountScreen extends StatelessWidget { | |||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     final ua = context.watch<UserProvider>(); |     final ua = context.watch<UserProvider>(); | ||||||
|  |  | ||||||
|     return Scaffold( |     return AppScaffold( | ||||||
|       appBar: AppBar( |       appBar: AppBar( | ||||||
|         leading: AutoAppBarLeading(), |         leading: AutoAppBarLeading(), | ||||||
|         title: Text("screenAccount").tr(), |         title: Text("screenAccount").tr(), | ||||||
|   | |||||||
| @@ -18,6 +18,7 @@ import 'package:surface/providers/userinfo.dart'; | |||||||
| import 'package:surface/widgets/account/account_image.dart'; | 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/universal_image.dart'; | import 'package:surface/widgets/universal_image.dart'; | ||||||
|  |  | ||||||
| class ProfileEditScreen extends StatefulWidget { | class ProfileEditScreen extends StatefulWidget { | ||||||
| @@ -81,8 +82,7 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> { | |||||||
|             onDateTimeChanged: (DateTime newDate) { |             onDateTimeChanged: (DateTime newDate) { | ||||||
|               setState(() { |               setState(() { | ||||||
|                 _birthday = newDate; |                 _birthday = newDate; | ||||||
|                 _birthdayController.text = |                 _birthdayController.text = DateFormat(_kDateFormat).format(_birthday!); | ||||||
|                     DateFormat(_kDateFormat).format(_birthday!); |  | ||||||
|               }); |               }); | ||||||
|             }, |             }, | ||||||
|           ), |           ), | ||||||
| @@ -96,11 +96,9 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> { | |||||||
|     if (image == null) return; |     if (image == null) return; | ||||||
|     if (!mounted) return; |     if (!mounted) return; | ||||||
|  |  | ||||||
|     final ImageProvider imageProvider = |     final ImageProvider imageProvider = kIsWeb ? NetworkImage(image.path) : FileImage(File(image.path)); | ||||||
|         kIsWeb ? NetworkImage(image.path) : FileImage(File(image.path)); |     final aspectRatios = | ||||||
|     final aspectRatios = place == 'banner' |         place == 'banner' ? [CropAspectRatio(width: 16, height: 7)] : [CropAspectRatio(width: 1, height: 1)]; | ||||||
|         ? [CropAspectRatio(width: 16, height: 7)] |  | ||||||
|         : [CropAspectRatio(width: 1, height: 1)]; |  | ||||||
|     final result = (!kIsWeb && (Platform.isIOS || Platform.isMacOS)) |     final result = (!kIsWeb && (Platform.isIOS || Platform.isMacOS)) | ||||||
|         ? await showCupertinoImageCropper( |         ? await showCupertinoImageCropper( | ||||||
|             // ignore: use_build_context_synchronously |             // ignore: use_build_context_synchronously | ||||||
| @@ -122,10 +120,7 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> { | |||||||
|  |  | ||||||
|     setState(() => _isBusy = true); |     setState(() => _isBusy = true); | ||||||
|  |  | ||||||
|     final rawBytes = |     final rawBytes = (await result.uiImage.toByteData(format: ImageByteFormat.png))!.buffer.asUint8List(); | ||||||
|         (await result.uiImage.toByteData(format: ImageByteFormat.png))! |  | ||||||
|             .buffer |  | ||||||
|             .asUint8List(); |  | ||||||
|  |  | ||||||
|     try { |     try { | ||||||
|       final attachment = await attach.directUploadOne( |       final attachment = await attach.directUploadOne( | ||||||
| @@ -212,7 +207,12 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> { | |||||||
|  |  | ||||||
|     final sn = context.read<SnNetworkProvider>(); |     final sn = context.read<SnNetworkProvider>(); | ||||||
|  |  | ||||||
|     return SingleChildScrollView( |     return AppScaffold( | ||||||
|  |       appBar: AppBar( | ||||||
|  |         leading: const PageBackButton(), | ||||||
|  |         title: Text('screenAccountProfileEdit').tr(), | ||||||
|  |       ), | ||||||
|  |       body: SingleChildScrollView( | ||||||
|         child: Column( |         child: Column( | ||||||
|           crossAxisAlignment: CrossAxisAlignment.start, |           crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|           children: [ |           children: [ | ||||||
| @@ -229,8 +229,7 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> { | |||||||
|                       child: AspectRatio( |                       child: AspectRatio( | ||||||
|                         aspectRatio: 16 / 9, |                         aspectRatio: 16 / 9, | ||||||
|                         child: Container( |                         child: Container( | ||||||
|                         color: |                           color: Theme.of(context).colorScheme.surfaceContainerHigh, | ||||||
|                             Theme.of(context).colorScheme.surfaceContainerHigh, |  | ||||||
|                           child: _banner != null |                           child: _banner != null | ||||||
|                               ? AutoResizeUniversalImage( |                               ? AutoResizeUniversalImage( | ||||||
|                                   sn.getAttachmentUrl(_banner!), |                                   sn.getAttachmentUrl(_banner!), | ||||||
| @@ -343,6 +342,7 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> { | |||||||
|             ).padding(horizontal: padding), |             ).padding(horizontal: padding), | ||||||
|           ], |           ], | ||||||
|         ), |         ), | ||||||
|  |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -19,6 +19,7 @@ import 'package:surface/types/check_in.dart'; | |||||||
| import 'package:surface/types/post.dart'; | import 'package:surface/types/post.dart'; | ||||||
| import 'package:surface/widgets/account/account_image.dart'; | import 'package:surface/widgets/account/account_image.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/universal_image.dart'; | import 'package:surface/widgets/universal_image.dart'; | ||||||
| 
 | 
 | ||||||
| const Map<String, (String, IconData, Color)> kBadgesMeta = { | const Map<String, (String, IconData, Color)> kBadgesMeta = { | ||||||
| @@ -241,6 +242,7 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM | |||||||
|     final sn = context.read<SnNetworkProvider>(); |     final sn = context.read<SnNetworkProvider>(); | ||||||
| 
 | 
 | ||||||
|     return Scaffold( |     return Scaffold( | ||||||
|  |       backgroundColor: Colors.transparent, | ||||||
|       body: CustomScrollView( |       body: CustomScrollView( | ||||||
|         controller: _scrollController, |         controller: _scrollController, | ||||||
|         slivers: [ |         slivers: [ | ||||||
| @@ -18,6 +18,7 @@ import 'package:surface/types/post.dart'; | |||||||
| import 'package:surface/widgets/account/account_image.dart'; | 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/universal_image.dart'; | import 'package:surface/widgets/universal_image.dart'; | ||||||
|  |  | ||||||
| class AccountPublisherEditScreen extends StatefulWidget { | class AccountPublisherEditScreen extends StatefulWidget { | ||||||
| @@ -176,7 +177,7 @@ class _AccountPublisherEditScreenState extends State<AccountPublisherEditScreen> | |||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     final sn = context.read<SnNetworkProvider>(); |     final sn = context.read<SnNetworkProvider>(); | ||||||
|  |  | ||||||
|     return Scaffold( |     return AppScaffold( | ||||||
|       body: SingleChildScrollView( |       body: SingleChildScrollView( | ||||||
|         child: Column( |         child: Column( | ||||||
|           children: [ |           children: [ | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ 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/account/account_image.dart'; | ||||||
| import 'package:surface/widgets/dialog.dart'; | import 'package:surface/widgets/dialog.dart'; | ||||||
|  | import 'package:surface/widgets/navigation/app_scaffold.dart'; | ||||||
|  |  | ||||||
| class AccountPublisherNewScreen extends StatefulWidget { | class AccountPublisherNewScreen extends StatefulWidget { | ||||||
|   const AccountPublisherNewScreen({super.key}); |   const AccountPublisherNewScreen({super.key}); | ||||||
| @@ -24,7 +25,11 @@ class _AccountPublisherNewScreenState extends State<AccountPublisherNewScreen> { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return Scaffold( |     return  AppScaffold( | ||||||
|  |       appBar: AppBar( | ||||||
|  |         leading: const PageBackButton(), | ||||||
|  |         title: Text('screenAccountPublisherNew').tr(), | ||||||
|  |       ), | ||||||
|       body: SingleChildScrollView( |       body: SingleChildScrollView( | ||||||
|         child: Column( |         child: Column( | ||||||
|           children: [ |           children: [ | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ import 'package:surface/types/post.dart'; | |||||||
| import 'package:surface/widgets/account/account_image.dart'; | 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'; | ||||||
|  |  | ||||||
| class PublisherScreen extends StatefulWidget { | class PublisherScreen extends StatefulWidget { | ||||||
|   const PublisherScreen({super.key}); |   const PublisherScreen({super.key}); | ||||||
| @@ -32,8 +33,7 @@ class _PublisherScreenState extends State<PublisherScreen> { | |||||||
|  |  | ||||||
|     try { |     try { | ||||||
|       final resp = await sn.client.get('/cgi/co/publishers/me'); |       final resp = await sn.client.get('/cgi/co/publishers/me'); | ||||||
|       final List<SnPublisher> out = List<SnPublisher>.from( |       final List<SnPublisher> out = List<SnPublisher>.from(resp.data?.map((e) => SnPublisher.fromJson(e)) ?? []); | ||||||
|           resp.data?.map((e) => SnPublisher.fromJson(e)) ?? []); |  | ||||||
|  |  | ||||||
|       if (!mounted) return; |       if (!mounted) return; | ||||||
|  |  | ||||||
| @@ -53,7 +53,11 @@ class _PublisherScreenState extends State<PublisherScreen> { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return Scaffold( |     return AppScaffold( | ||||||
|  |       appBar: AppBar( | ||||||
|  |         leading: const PageBackButton(), | ||||||
|  |         title: Text('screenAccountPublishers').tr(), | ||||||
|  |       ), | ||||||
|       body: Column( |       body: Column( | ||||||
|         children: [ |         children: [ | ||||||
|           ListTile( |           ListTile( | ||||||
| @@ -62,9 +66,7 @@ class _PublisherScreenState extends State<PublisherScreen> { | |||||||
|             contentPadding: const EdgeInsets.symmetric(horizontal: 24), |             contentPadding: const EdgeInsets.symmetric(horizontal: 24), | ||||||
|             leading: const Icon(Symbols.add_circle), |             leading: const Icon(Symbols.add_circle), | ||||||
|             onTap: () { |             onTap: () { | ||||||
|               GoRouter.of(context) |               GoRouter.of(context).pushNamed('accountPublisherNew').then((value) { | ||||||
|                   .pushNamed('accountPublisherNew') |  | ||||||
|                   .then((value) { |  | ||||||
|                 if (value == true) { |                 if (value == true) { | ||||||
|                   _publishers.clear(); |                   _publishers.clear(); | ||||||
|                   _fetchPublishers(); |                   _fetchPublishers(); | ||||||
| @@ -75,6 +77,9 @@ class _PublisherScreenState extends State<PublisherScreen> { | |||||||
|           const Divider(height: 1), |           const Divider(height: 1), | ||||||
|           LoadingIndicator(isActive: _isBusy), |           LoadingIndicator(isActive: _isBusy), | ||||||
|           Expanded( |           Expanded( | ||||||
|  |             child: MediaQuery.removePadding( | ||||||
|  |               context: context, | ||||||
|  |               removeTop: true, | ||||||
|               child: RefreshIndicator( |               child: RefreshIndicator( | ||||||
|                 onRefresh: () { |                 onRefresh: () { | ||||||
|                   _publishers.clear(); |                   _publishers.clear(); | ||||||
| @@ -120,6 +125,7 @@ class _PublisherScreenState extends State<PublisherScreen> { | |||||||
|                 ), |                 ), | ||||||
|               ), |               ), | ||||||
|             ), |             ), | ||||||
|  |           ), | ||||||
|         ], |         ], | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ import 'package:surface/widgets/app_bar_leading.dart'; | |||||||
| import 'package:surface/widgets/attachment/attachment_zoom.dart'; | import 'package:surface/widgets/attachment/attachment_zoom.dart'; | ||||||
| import 'package:surface/widgets/attachment/attachment_item.dart'; | import 'package:surface/widgets/attachment/attachment_item.dart'; | ||||||
| import 'package:surface/widgets/dialog.dart'; | import 'package:surface/widgets/dialog.dart'; | ||||||
|  | import 'package:surface/widgets/navigation/app_scaffold.dart'; | ||||||
| import 'package:uuid/uuid.dart'; | import 'package:uuid/uuid.dart'; | ||||||
|  |  | ||||||
| class AlbumScreen extends StatefulWidget { | class AlbumScreen extends StatefulWidget { | ||||||
| @@ -82,7 +83,7 @@ class _AlbumScreenState extends State<AlbumScreen> { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return Scaffold( |     return AppScaffold( | ||||||
|       body: CustomScrollView( |       body: CustomScrollView( | ||||||
|         controller: _scrollController, |         controller: _scrollController, | ||||||
|         slivers: [ |         slivers: [ | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ import 'package:surface/providers/sn_network.dart'; | |||||||
| import 'package:surface/providers/userinfo.dart'; | import 'package:surface/providers/userinfo.dart'; | ||||||
| import 'package:surface/types/auth.dart'; | import 'package:surface/types/auth.dart'; | ||||||
| import 'package:surface/widgets/dialog.dart'; | import 'package:surface/widgets/dialog.dart'; | ||||||
|  | import 'package:surface/widgets/navigation/app_scaffold.dart'; | ||||||
| import 'package:url_launcher/url_launcher_string.dart'; | import 'package:url_launcher/url_launcher_string.dart'; | ||||||
|  |  | ||||||
| import '../../providers/websocket.dart'; | import '../../providers/websocket.dart'; | ||||||
| @@ -35,7 +36,12 @@ class _LoginScreenState extends State<LoginScreen> { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return Theme( |     return AppScaffold( | ||||||
|  |       appBar: AppBar( | ||||||
|  |         leading: const PageBackButton(), | ||||||
|  |         title: Text('screenAuthLogin').tr(), | ||||||
|  |       ), | ||||||
|  |       body: Theme( | ||||||
|         data: Theme.of(context).copyWith(canvasColor: Colors.transparent), |         data: Theme.of(context).copyWith(canvasColor: Colors.transparent), | ||||||
|         child: SingleChildScrollView( |         child: SingleChildScrollView( | ||||||
|           child: PageTransitionSwitcher( |           child: PageTransitionSwitcher( | ||||||
| @@ -96,6 +102,7 @@ class _LoginScreenState extends State<LoginScreen> { | |||||||
|             }, |             }, | ||||||
|           ).padding(all: 24), |           ).padding(all: 24), | ||||||
|         ).center(), |         ).center(), | ||||||
|  |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ 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'; | ||||||
| import 'package:surface/widgets/dialog.dart'; | import 'package:surface/widgets/dialog.dart'; | ||||||
|  | import 'package:surface/widgets/navigation/app_scaffold.dart'; | ||||||
| import 'package:url_launcher/url_launcher_string.dart'; | import 'package:url_launcher/url_launcher_string.dart'; | ||||||
|  |  | ||||||
| class RegisterScreen extends StatefulWidget { | class RegisterScreen extends StatefulWidget { | ||||||
| @@ -54,7 +55,12 @@ class _RegisterScreenState extends State<RegisterScreen> { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return StyledWidget(Container( |     return AppScaffold( | ||||||
|  |       appBar: AppBar( | ||||||
|  |         leading: const PageBackButton(), | ||||||
|  |         title: Text('screenAuthRegister').tr(), | ||||||
|  |       ), | ||||||
|  |       body: StyledWidget(Container( | ||||||
|         constraints: const BoxConstraints(maxWidth: 380), |         constraints: const BoxConstraints(maxWidth: 380), | ||||||
|         child: SingleChildScrollView( |         child: SingleChildScrollView( | ||||||
|           child: Column( |           child: Column( | ||||||
| @@ -180,10 +186,7 @@ class _RegisterScreenState extends State<RegisterScreen> { | |||||||
|                           'termAcceptNextWithAgree'.tr(), |                           'termAcceptNextWithAgree'.tr(), | ||||||
|                           textAlign: TextAlign.end, |                           textAlign: TextAlign.end, | ||||||
|                           style: Theme.of(context).textTheme.bodySmall!.copyWith( |                           style: Theme.of(context).textTheme.bodySmall!.copyWith( | ||||||
|                           color: Theme.of(context) |                                 color: Theme.of(context).colorScheme.onSurface.withAlpha((255 * 0.75).round()), | ||||||
|                               .colorScheme |  | ||||||
|                               .onSurface |  | ||||||
|                               .withAlpha((255 * 0.75).round()), |  | ||||||
|                               ), |                               ), | ||||||
|                         ), |                         ), | ||||||
|                         Material( |                         Material( | ||||||
| @@ -223,6 +226,7 @@ class _RegisterScreenState extends State<RegisterScreen> { | |||||||
|             ], |             ], | ||||||
|           ), |           ), | ||||||
|         ), |         ), | ||||||
|     )).padding(all: 24).center(); |       )).padding(all: 24).center(), | ||||||
|  |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -13,6 +13,7 @@ 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_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'; | ||||||
|  |  | ||||||
| @@ -120,7 +121,7 @@ class _ChatScreenState extends State<ChatScreen> { | |||||||
|     final ua = context.read<UserProvider>(); |     final ua = context.read<UserProvider>(); | ||||||
|  |  | ||||||
|     if (!ua.isAuthorized) { |     if (!ua.isAuthorized) { | ||||||
|       return Scaffold( |       return AppScaffold( | ||||||
|         appBar: AppBar( |         appBar: AppBar( | ||||||
|           leading: AutoAppBarLeading(), |           leading: AutoAppBarLeading(), | ||||||
|           title: Text('screenChat').tr(), |           title: Text('screenChat').tr(), | ||||||
| @@ -131,7 +132,7 @@ class _ChatScreenState extends State<ChatScreen> { | |||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return Scaffold( |     return AppScaffold( | ||||||
|       appBar: AppBar( |       appBar: AppBar( | ||||||
|         leading: AutoAppBarLeading(), |         leading: AutoAppBarLeading(), | ||||||
|         title: Text('screenChat').tr(), |         title: Text('screenChat').tr(), | ||||||
| @@ -195,6 +196,9 @@ class _ChatScreenState extends State<ChatScreen> { | |||||||
|         children: [ |         children: [ | ||||||
|           LoadingIndicator(isActive: _isBusy), |           LoadingIndicator(isActive: _isBusy), | ||||||
|           Expanded( |           Expanded( | ||||||
|  |             child: MediaQuery.removePadding( | ||||||
|  |               context: context, | ||||||
|  |               removeTop: true, | ||||||
|               child: RefreshIndicator( |               child: RefreshIndicator( | ||||||
|                 onRefresh: () => Future.sync(() => _refreshChannels()), |                 onRefresh: () => Future.sync(() => _refreshChannels()), | ||||||
|                 child: ListView.builder( |                 child: ListView.builder( | ||||||
| @@ -236,7 +240,7 @@ class _ChatScreenState extends State<ChatScreen> { | |||||||
|                               'alias': channel.alias, |                               'alias': channel.alias, | ||||||
|                             }, |                             }, | ||||||
|                           ).then((value) { |                           ).then((value) { | ||||||
|                           if (value == true) _refreshChannels(); |                             if (mounted) _refreshChannels(); | ||||||
|                           }); |                           }); | ||||||
|                         }, |                         }, | ||||||
|                       ); |                       ); | ||||||
| @@ -276,6 +280,7 @@ class _ChatScreenState extends State<ChatScreen> { | |||||||
|                 ), |                 ), | ||||||
|               ), |               ), | ||||||
|             ), |             ), | ||||||
|  |           ), | ||||||
|         ], |         ], | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   | |||||||
| @@ -9,10 +9,12 @@ import 'package:styled_widget/styled_widget.dart'; | |||||||
| import 'package:surface/providers/chat_call.dart'; | import 'package:surface/providers/chat_call.dart'; | ||||||
| import 'package:surface/widgets/chat/call/call_controls.dart'; | import 'package:surface/widgets/chat/call/call_controls.dart'; | ||||||
| import 'package:surface/widgets/chat/call/call_participant.dart'; | import 'package:surface/widgets/chat/call/call_participant.dart'; | ||||||
|  | import 'package:surface/widgets/navigation/app_scaffold.dart'; | ||||||
|  |  | ||||||
| class CallRoomScreen extends StatefulWidget { | class CallRoomScreen extends StatefulWidget { | ||||||
|   final String scope; |   final String scope; | ||||||
|   final String alias; |   final String alias; | ||||||
|  |  | ||||||
|   const CallRoomScreen({super.key, required this.scope, required this.alias}); |   const CallRoomScreen({super.key, required this.scope, required this.alias}); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
| @@ -35,8 +37,7 @@ class _CallRoomScreenState extends State<CallRoomScreen> { | |||||||
|     return Stack( |     return Stack( | ||||||
|       children: [ |       children: [ | ||||||
|         Container( |         Container( | ||||||
|           color: |           color: Theme.of(context).colorScheme.surfaceContainer.withOpacity(0.75), | ||||||
|               Theme.of(context).colorScheme.surfaceContainer.withOpacity(0.75), |  | ||||||
|           child: call.focusTrack != null |           child: call.focusTrack != null | ||||||
|               ? InteractiveParticipantWidget( |               ? InteractiveParticipantWidget( | ||||||
|                   isFixedAvatar: false, |                   isFixedAvatar: false, | ||||||
| @@ -71,8 +72,7 @@ class _CallRoomScreenState extends State<CallRoomScreen> { | |||||||
|                       color: Theme.of(context).cardColor, |                       color: Theme.of(context).cardColor, | ||||||
|                       participant: track, |                       participant: track, | ||||||
|                       onTap: () { |                       onTap: () { | ||||||
|                         if (track.participant.sid != |                         if (track.participant.sid != call.focusTrack?.participant.sid) { | ||||||
|                             call.focusTrack?.participant.sid) { |  | ||||||
|                           call.setFocusTrack(track); |                           call.setFocusTrack(track); | ||||||
|                         } |                         } | ||||||
|                       }, |                       }, | ||||||
| @@ -114,14 +114,10 @@ class _CallRoomScreenState extends State<CallRoomScreen> { | |||||||
|             child: ClipRRect( |             child: ClipRRect( | ||||||
|               borderRadius: const BorderRadius.all(Radius.circular(8)), |               borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||||
|               child: InteractiveParticipantWidget( |               child: InteractiveParticipantWidget( | ||||||
|                 color: Theme.of(context) |                 color: Theme.of(context).colorScheme.surfaceContainerHigh.withOpacity(0.75), | ||||||
|                     .colorScheme |  | ||||||
|                     .surfaceContainerHigh |  | ||||||
|                     .withOpacity(0.75), |  | ||||||
|                 participant: track, |                 participant: track, | ||||||
|                 onTap: () { |                 onTap: () { | ||||||
|                   if (track.participant.sid != |                   if (track.participant.sid != call.focusTrack?.participant.sid) { | ||||||
|                       call.focusTrack?.participant.sid) { |  | ||||||
|                     call.setFocusTrack(track); |                     call.setFocusTrack(track); | ||||||
|                   } |                   } | ||||||
|                 }, |                 }, | ||||||
| @@ -152,31 +148,24 @@ class _CallRoomScreenState extends State<CallRoomScreen> { | |||||||
|     return ListenableBuilder( |     return ListenableBuilder( | ||||||
|         listenable: call, |         listenable: call, | ||||||
|         builder: (context, _) { |         builder: (context, _) { | ||||||
|           return Scaffold( |           return AppScaffold( | ||||||
|             appBar: AppBar( |             appBar: AppBar( | ||||||
|               title: RichText( |               title: RichText( | ||||||
|                 textAlign: TextAlign.center, |                 textAlign: TextAlign.center, | ||||||
|                 text: TextSpan(children: [ |                 text: TextSpan(children: [ | ||||||
|                   TextSpan( |                   TextSpan( | ||||||
|                     text: 'call'.tr(), |                     text: 'call'.tr(), | ||||||
|                     style: Theme.of(context) |                     style: Theme.of(context).textTheme.titleLarge!.copyWith(color: Colors.white), | ||||||
|                         .textTheme |  | ||||||
|                         .titleLarge! |  | ||||||
|                         .copyWith(color: Colors.white), |  | ||||||
|                   ), |                   ), | ||||||
|                   const TextSpan(text: '\n'), |                   const TextSpan(text: '\n'), | ||||||
|                   TextSpan( |                   TextSpan( | ||||||
|                     text: call.lastDuration.toString(), |                     text: call.lastDuration.toString(), | ||||||
|                     style: Theme.of(context) |                     style: Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.white), | ||||||
|                         .textTheme |  | ||||||
|                         .bodySmall! |  | ||||||
|                         .copyWith(color: Colors.white), |  | ||||||
|                   ), |                   ), | ||||||
|                 ]), |                 ]), | ||||||
|               ), |               ), | ||||||
|             ), |             ), | ||||||
|             body: SafeArea( |             body: GestureDetector( | ||||||
|               child: GestureDetector( |  | ||||||
|               behavior: HitTestBehavior.translucent, |               behavior: HitTestBehavior.translucent, | ||||||
|               child: Column( |               child: Column( | ||||||
|                 children: [ |                 children: [ | ||||||
| @@ -190,8 +179,7 @@ class _CallRoomScreenState extends State<CallRoomScreen> { | |||||||
|                         Builder(builder: (context) { |                         Builder(builder: (context) { | ||||||
|                           final call = context.read<ChatCallProvider>(); |                           final call = context.read<ChatCallProvider>(); | ||||||
|                           final connectionQuality = |                           final connectionQuality = | ||||||
|                                 call.room.localParticipant?.connectionQuality ?? |                               call.room.localParticipant?.connectionQuality ?? livekit.ConnectionQuality.unknown; | ||||||
|                                     livekit.ConnectionQuality.unknown; |  | ||||||
|                           return Expanded( |                           return Expanded( | ||||||
|                             child: Column( |                             child: Column( | ||||||
|                               mainAxisSize: MainAxisSize.min, |                               mainAxisSize: MainAxisSize.min, | ||||||
| @@ -213,35 +201,24 @@ class _CallRoomScreenState extends State<CallRoomScreen> { | |||||||
|                                   children: [ |                                   children: [ | ||||||
|                                     Text( |                                     Text( | ||||||
|                                       { |                                       { | ||||||
|                                           livekit.ConnectionState.disconnected: |                                         livekit.ConnectionState.disconnected: 'callStatusDisconnected'.tr(), | ||||||
|                                               'callStatusDisconnected'.tr(), |                                         livekit.ConnectionState.connected: 'callStatusConnected'.tr(), | ||||||
|                                           livekit.ConnectionState.connected: |                                         livekit.ConnectionState.connecting: 'callStatusConnecting'.tr(), | ||||||
|                                               'callStatusConnected'.tr(), |                                         livekit.ConnectionState.reconnecting: 'callStatusReconnecting'.tr(), | ||||||
|                                           livekit.ConnectionState.connecting: |  | ||||||
|                                               'callStatusConnecting'.tr(), |  | ||||||
|                                           livekit.ConnectionState.reconnecting: |  | ||||||
|                                               'callStatusReconnecting'.tr(), |  | ||||||
|                                       }[call.room.connectionState]!, |                                       }[call.room.connectionState]!, | ||||||
|                                     ), |                                     ), | ||||||
|                                     const Gap(6), |                                     const Gap(6), | ||||||
|                                       if (connectionQuality != |                                     if (connectionQuality != livekit.ConnectionQuality.unknown) | ||||||
|                                           livekit.ConnectionQuality.unknown) |  | ||||||
|                                       Icon( |                                       Icon( | ||||||
|                                         { |                                         { | ||||||
|                                             livekit.ConnectionQuality.excellent: |                                           livekit.ConnectionQuality.excellent: Icons.signal_cellular_alt, | ||||||
|                                                 Icons.signal_cellular_alt, |                                           livekit.ConnectionQuality.good: Icons.signal_cellular_alt_2_bar, | ||||||
|                                             livekit.ConnectionQuality.good: |                                           livekit.ConnectionQuality.poor: Icons.signal_cellular_alt_1_bar, | ||||||
|                                                 Icons.signal_cellular_alt_2_bar, |  | ||||||
|                                             livekit.ConnectionQuality.poor: |  | ||||||
|                                                 Icons.signal_cellular_alt_1_bar, |  | ||||||
|                                         }[connectionQuality], |                                         }[connectionQuality], | ||||||
|                                         color: { |                                         color: { | ||||||
|                                             livekit.ConnectionQuality.excellent: |                                           livekit.ConnectionQuality.excellent: Colors.green, | ||||||
|                                                 Colors.green, |                                           livekit.ConnectionQuality.good: Colors.orange, | ||||||
|                                             livekit.ConnectionQuality.good: |                                           livekit.ConnectionQuality.poor: Colors.red, | ||||||
|                                                 Colors.orange, |  | ||||||
|                                             livekit.ConnectionQuality.poor: |  | ||||||
|                                                 Colors.red, |  | ||||||
|                                         }[connectionQuality], |                                         }[connectionQuality], | ||||||
|                                         size: 16, |                                         size: 16, | ||||||
|                                       ) |                                       ) | ||||||
| @@ -263,9 +240,7 @@ class _CallRoomScreenState extends State<CallRoomScreen> { | |||||||
|                         Row( |                         Row( | ||||||
|                           children: [ |                           children: [ | ||||||
|                             IconButton( |                             IconButton( | ||||||
|                                 icon: _layoutMode == 0 |                               icon: _layoutMode == 0 ? const Icon(Icons.view_list) : const Icon(Icons.grid_view), | ||||||
|                                     ? const Icon(Icons.view_list) |  | ||||||
|                                     : const Icon(Icons.grid_view), |  | ||||||
|                               onPressed: () { |                               onPressed: () { | ||||||
|                                 _switchLayout(); |                                 _switchLayout(); | ||||||
|                               }, |                               }, | ||||||
| @@ -277,8 +252,7 @@ class _CallRoomScreenState extends State<CallRoomScreen> { | |||||||
|                   ), |                   ), | ||||||
|                   Expanded( |                   Expanded( | ||||||
|                     child: Material( |                     child: Material( | ||||||
|                         color: |                       color: Theme.of(context).colorScheme.surfaceContainerLow, | ||||||
|                             Theme.of(context).colorScheme.surfaceContainerLow, |  | ||||||
|                       child: Builder( |                       child: Builder( | ||||||
|                         builder: (context) { |                         builder: (context) { | ||||||
|                           switch (_layoutMode) { |                           switch (_layoutMode) { | ||||||
| @@ -303,7 +277,6 @@ class _CallRoomScreenState extends State<CallRoomScreen> { | |||||||
|               ), |               ), | ||||||
|               onTap: () {}, |               onTap: () {}, | ||||||
|             ), |             ), | ||||||
|             ), |  | ||||||
|           ); |           ); | ||||||
|         }); |         }); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ 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/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:very_good_infinite_list/very_good_infinite_list.dart'; | import 'package:very_good_infinite_list/very_good_infinite_list.dart'; | ||||||
|  |  | ||||||
| class ChannelDetailScreen extends StatefulWidget { | class ChannelDetailScreen extends StatefulWidget { | ||||||
| @@ -189,7 +190,7 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> { | |||||||
|  |  | ||||||
|     final isOwned = ua.isAuthorized && _channel?.accountId == ua.user?.id; |     final isOwned = ua.isAuthorized && _channel?.accountId == ua.user?.id; | ||||||
|  |  | ||||||
|     return Scaffold( |     return AppScaffold( | ||||||
|       appBar: AppBar( |       appBar: AppBar( | ||||||
|         title: _channel != null ? Text(_channel!.name) : Text('loading').tr(), |         title: _channel != null ? Text(_channel!.name) : Text('loading').tr(), | ||||||
|       ), |       ), | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ 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/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:uuid/uuid.dart'; | import 'package:uuid/uuid.dart'; | ||||||
|  |  | ||||||
| class ChatManageScreen extends StatefulWidget { | class ChatManageScreen extends StatefulWidget { | ||||||
| @@ -87,7 +88,7 @@ class _ChatManageScreenState extends State<ChatManageScreen> { | |||||||
|     try { |     try { | ||||||
|       final resp = await sn.client.request( |       final resp = await sn.client.request( | ||||||
|         widget.editingChannelAlias != null |         widget.editingChannelAlias != null | ||||||
|             ? '/cgi/im/channels/$scope/${widget.editingChannelAlias}' |             ? '/cgi/im/channels/$scope/${_editingChannel!.id}' | ||||||
|             : '/cgi/im/channels/$scope', |             : '/cgi/im/channels/$scope', | ||||||
|         data: payload, |         data: payload, | ||||||
|         options: Options( |         options: Options( | ||||||
| @@ -121,7 +122,7 @@ class _ChatManageScreenState extends State<ChatManageScreen> { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return Scaffold( |     return AppScaffold( | ||||||
|       appBar: AppBar( |       appBar: AppBar( | ||||||
|         title: widget.editingChannelAlias != null |         title: widget.editingChannelAlias != null | ||||||
|             ? Text('screenChatManage').tr() |             ? Text('screenChatManage').tr() | ||||||
|   | |||||||
| @@ -17,8 +17,10 @@ import 'package:surface/types/chat.dart'; | |||||||
| import 'package:surface/widgets/chat/call/call_prejoin.dart'; | import 'package:surface/widgets/chat/call/call_prejoin.dart'; | ||||||
| import 'package:surface/widgets/chat/chat_message.dart'; | import 'package:surface/widgets/chat/chat_message.dart'; | ||||||
| import 'package:surface/widgets/chat/chat_message_input.dart'; | import 'package:surface/widgets/chat/chat_message_input.dart'; | ||||||
|  | import 'package:surface/widgets/chat/chat_typing_indicator.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:very_good_infinite_list/very_good_infinite_list.dart'; | import 'package:very_good_infinite_list/very_good_infinite_list.dart'; | ||||||
|  |  | ||||||
| import '../../providers/user_directory.dart'; | import '../../providers/user_directory.dart'; | ||||||
| @@ -210,7 +212,7 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> { | |||||||
|     final call = context.watch<ChatCallProvider>(); |     final call = context.watch<ChatCallProvider>(); | ||||||
|     final ud = context.read<UserDirectoryProvider>(); |     final ud = context.read<UserDirectoryProvider>(); | ||||||
|  |  | ||||||
|     return Scaffold( |     return AppScaffold( | ||||||
|       appBar: AppBar( |       appBar: AppBar( | ||||||
|         title: Text( |         title: Text( | ||||||
|           _channel?.type == 1 |           _channel?.type == 1 | ||||||
| @@ -280,11 +282,7 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> { | |||||||
|                 Expanded( |                 Expanded( | ||||||
|                   child: InfiniteList( |                   child: InfiniteList( | ||||||
|                     reverse: true, |                     reverse: true, | ||||||
|                     padding: const EdgeInsets.only( |                     padding: const EdgeInsets.only(top: 12), | ||||||
|                       left: 12, |  | ||||||
|                       right: 12, |  | ||||||
|                       top: 12, |  | ||||||
|                     ), |  | ||||||
|                     hasReachedMax: _messageController.isAllLoaded, |                     hasReachedMax: _messageController.isAllLoaded, | ||||||
|                     itemCount: _messageController.messages.length, |                     itemCount: _messageController.messages.length, | ||||||
|                     isLoading: _messageController.isLoading, |                     isLoading: _messageController.isLoading, | ||||||
| @@ -310,8 +308,6 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> { | |||||||
|  |  | ||||||
|                       return Align( |                       return Align( | ||||||
|                         alignment: Alignment.centerLeft, |                         alignment: Alignment.centerLeft, | ||||||
|                         child: Container( |  | ||||||
|                           constraints: BoxConstraints(maxWidth: 480), |  | ||||||
|                         child: ChatMessage( |                         child: ChatMessage( | ||||||
|                           data: message, |                           data: message, | ||||||
|                           isMerged: canMerge, |                           isMerged: canMerge, | ||||||
| @@ -327,7 +323,6 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> { | |||||||
|                             _inputGlobalKey.currentState?.deleteMessage(value); |                             _inputGlobalKey.currentState?.deleteMessage(value); | ||||||
|                           }, |                           }, | ||||||
|                         ), |                         ), | ||||||
|                         ), |  | ||||||
|                       ); |                       ); | ||||||
|                     }, |                     }, | ||||||
|                   ), |                   ), | ||||||
| @@ -335,11 +330,17 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> { | |||||||
|               if (!_messageController.isPending) |               if (!_messageController.isPending) | ||||||
|                 Material( |                 Material( | ||||||
|                   elevation: 2, |                   elevation: 2, | ||||||
|                   child: ChatMessageInput( |                   child: Column( | ||||||
|  |                     children: [ | ||||||
|  |                       ChatTypingIndicator(controller: _messageController), | ||||||
|  |                       ChatMessageInput( | ||||||
|                         key: _inputGlobalKey, |                         key: _inputGlobalKey, | ||||||
|                         otherMember: _otherMember, |                         otherMember: _otherMember, | ||||||
|                         controller: _messageController, |                         controller: _messageController, | ||||||
|                   ).padding(bottom: MediaQuery.of(context).padding.bottom), |                       ), | ||||||
|  |                       Gap(MediaQuery.of(context).padding.bottom), | ||||||
|  |                     ], | ||||||
|  |                   ), | ||||||
|                 ), |                 ), | ||||||
|             ], |             ], | ||||||
|           ); |           ); | ||||||
|   | |||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | import 'package:animations/animations.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,9 +9,11 @@ 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/screens/post/post_detail.dart'; | ||||||
| import 'package:surface/types/post.dart'; | import 'package:surface/types/post.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/post/post_item.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'; | ||||||
|  |  | ||||||
| @@ -93,7 +96,7 @@ class _ExploreScreenState extends State<ExploreScreen> { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return Scaffold( |     return AppScaffold( | ||||||
|       floatingActionButtonLocation: ExpandableFab.location, |       floatingActionButtonLocation: ExpandableFab.location, | ||||||
|       floatingActionButton: ExpandableFab( |       floatingActionButton: ExpandableFab( | ||||||
|         key: _fabKey, |         key: _fabKey, | ||||||
| @@ -210,6 +213,7 @@ class _ExploreScreenState extends State<ExploreScreen> { | |||||||
|                 ), |                 ), | ||||||
|               ), |               ), | ||||||
|             ), |             ), | ||||||
|  |             const SliverGap(12), | ||||||
|             SliverInfiniteList( |             SliverInfiniteList( | ||||||
|               itemCount: _posts.length, |               itemCount: _posts.length, | ||||||
|               isLoading: _isBusy, |               isLoading: _isBusy, | ||||||
| @@ -217,7 +221,10 @@ class _ExploreScreenState extends State<ExploreScreen> { | |||||||
|               hasReachedMax: _postCount != null && _posts.length >= _postCount!, |               hasReachedMax: _postCount != null && _posts.length >= _postCount!, | ||||||
|               onFetchData: _fetchPosts, |               onFetchData: _fetchPosts, | ||||||
|               itemBuilder: (context, idx) { |               itemBuilder: (context, idx) { | ||||||
|                 return GestureDetector( |                 return Center( | ||||||
|  |                   child: OpenContainer( | ||||||
|  |                     closedBuilder: (_, __) => Container( | ||||||
|  |                       constraints: const BoxConstraints(maxWidth: 640), | ||||||
|                       child: PostItem( |                       child: PostItem( | ||||||
|                         data: _posts[idx], |                         data: _posts[idx], | ||||||
|                         maxWidth: 640, |                         maxWidth: 640, | ||||||
| @@ -228,16 +235,23 @@ class _ExploreScreenState extends State<ExploreScreen> { | |||||||
|                           _refreshPosts(); |                           _refreshPosts(); | ||||||
|                         }, |                         }, | ||||||
|                       ), |                       ), | ||||||
|                   onTap: () { |                     ), | ||||||
|                     GoRouter.of(context).pushNamed( |                     openBuilder: (_, close) => PostDetailScreen( | ||||||
|                       'postDetail', |                       slug: _posts[idx].id.toString(), | ||||||
|                       pathParameters: {'slug': _posts[idx].id.toString()}, |                       preload: _posts[idx], | ||||||
|                       extra: _posts[idx], |                       onBack: close, | ||||||
|  |                     ), | ||||||
|  |                     openColor: Colors.transparent, | ||||||
|  |                     openElevation: 0, | ||||||
|  |                     closedColor: Theme.of(context).colorScheme.surfaceContainerLow.withOpacity(0.75), | ||||||
|  |                     transitionType: ContainerTransitionType.fade, | ||||||
|  |                     closedShape: const RoundedRectangleBorder( | ||||||
|  |                       borderRadius: BorderRadius.all(Radius.circular(16)), | ||||||
|  |                     ), | ||||||
|  |                   ), | ||||||
|                 ); |                 ); | ||||||
|               }, |               }, | ||||||
|                 ); |               separatorBuilder: (_, __) => const Gap(8), | ||||||
|               }, |  | ||||||
|               separatorBuilder: (context, index) => const Divider(height: 1), |  | ||||||
|             ), |             ), | ||||||
|           ], |           ], | ||||||
|         ), |         ), | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ 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 '../providers/userinfo.dart'; | import '../providers/userinfo.dart'; | ||||||
| import '../widgets/unauthorized_hint.dart'; | import '../widgets/unauthorized_hint.dart'; | ||||||
| @@ -180,7 +181,7 @@ class _FriendScreenState extends State<FriendScreen> { | |||||||
|     final ua = context.read<UserProvider>(); |     final ua = context.read<UserProvider>(); | ||||||
|  |  | ||||||
|     if (!ua.isAuthorized) { |     if (!ua.isAuthorized) { | ||||||
|       return Scaffold( |       return AppScaffold( | ||||||
|         appBar: AppBar( |         appBar: AppBar( | ||||||
|           leading: AutoAppBarLeading(), |           leading: AutoAppBarLeading(), | ||||||
|           title: Text('screenFriend').tr(), |           title: Text('screenFriend').tr(), | ||||||
| @@ -191,7 +192,7 @@ class _FriendScreenState extends State<FriendScreen> { | |||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return Scaffold( |     return AppScaffold( | ||||||
|       appBar: AppBar( |       appBar: AppBar( | ||||||
|         leading: AutoAppBarLeading(), |         leading: AutoAppBarLeading(), | ||||||
|         title: Text('screenFriend').tr(), |         title: Text('screenFriend').tr(), | ||||||
| @@ -233,6 +234,9 @@ class _FriendScreenState extends State<FriendScreen> { | |||||||
|           if (_requests.isNotEmpty || _blocks.isNotEmpty) |           if (_requests.isNotEmpty || _blocks.isNotEmpty) | ||||||
|             const Divider(height: 1), |             const Divider(height: 1), | ||||||
|           Expanded( |           Expanded( | ||||||
|  |             child: MediaQuery.removePadding( | ||||||
|  |               context: context, | ||||||
|  |               removeTop: true, | ||||||
|               child: RefreshIndicator( |               child: RefreshIndicator( | ||||||
|                 onRefresh: () => Future.wait([ |                 onRefresh: () => Future.wait([ | ||||||
|                   _fetchRelations(), |                   _fetchRelations(), | ||||||
| @@ -282,6 +286,7 @@ class _FriendScreenState extends State<FriendScreen> { | |||||||
|                 ), |                 ), | ||||||
|               ), |               ), | ||||||
|             ), |             ), | ||||||
|  |           ), | ||||||
|         ], |         ], | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   | |||||||
| @@ -25,6 +25,7 @@ import 'package:surface/types/check_in.dart'; | |||||||
| import 'package:surface/types/post.dart'; | import 'package:surface/types/post.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/post/post_item.dart'; | import 'package:surface/widgets/post/post_item.dart'; | ||||||
|  |  | ||||||
| class HomeScreenDashEntry { | class HomeScreenDashEntry { | ||||||
| @@ -67,7 +68,7 @@ class _HomeScreenState extends State<HomeScreen> { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return Scaffold( |     return AppScaffold( | ||||||
|       appBar: AppBar( |       appBar: AppBar( | ||||||
|         leading: AutoAppBarLeading(), |         leading: AutoAppBarLeading(), | ||||||
|         title: Text("screenHome").tr(), |         title: Text("screenHome").tr(), | ||||||
| @@ -153,9 +154,14 @@ class _HomeDashUpdateWidget extends StatelessWidget { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| class _HomeDashSpecialDayWidget extends StatelessWidget { | class _HomeDashSpecialDayWidget extends StatefulWidget { | ||||||
|   const _HomeDashSpecialDayWidget(); |   const _HomeDashSpecialDayWidget(); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   State<_HomeDashSpecialDayWidget> createState() => _HomeDashSpecialDayWidgetState(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _HomeDashSpecialDayWidgetState extends State<_HomeDashSpecialDayWidget> { | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     final ua = context.watch<UserProvider>(); |     final ua = context.watch<UserProvider>(); | ||||||
| @@ -165,7 +171,6 @@ class _HomeDashSpecialDayWidget extends StatelessWidget { | |||||||
|  |  | ||||||
|     if (days.isNotEmpty) { |     if (days.isNotEmpty) { | ||||||
|       return Column( |       return Column( | ||||||
|           spacing: 8, |  | ||||||
|           children: days.map((ele) { |           children: days.map((ele) { | ||||||
|         return Card( |         return Card( | ||||||
|           child: ListTile( |           child: ListTile( | ||||||
| @@ -173,8 +178,8 @@ class _HomeDashSpecialDayWidget extends StatelessWidget { | |||||||
|             title: Text('celebrate$ele').tr(args: [ua.user?.nick ?? 'user']), |             title: Text('celebrate$ele').tr(args: [ua.user?.nick ?? 'user']), | ||||||
|             subtitle: Text( |             subtitle: Text( | ||||||
|               DateFormat('y/M/d').format(DateTime.now().copyWith( |               DateFormat('y/M/d').format(DateTime.now().copyWith( | ||||||
|                     month: kSpecialDays[ele]!.$1, |                 month: kSpecialDays[ele]?.$1, | ||||||
|                     day: kSpecialDays[ele]!.$2, |                 day: kSpecialDays[ele]?.$2, | ||||||
|               )), |               )), | ||||||
|             ), |             ), | ||||||
|           ), |           ), | ||||||
| @@ -193,7 +198,7 @@ class _HomeDashSpecialDayWidget extends StatelessWidget { | |||||||
|       return Card( |       return Card( | ||||||
|         child: ListTile( |         child: ListTile( | ||||||
|           leading: Text(kSpecialDaysSymbol[name] ?? '🎉').fontSize(24), |           leading: Text(kSpecialDaysSymbol[name] ?? '🎉').fontSize(24), | ||||||
|           title: Text('pending$name').tr(args: [RelativeTime(context).format(date)]), |           title: Text('pending$name').tr(args: [RelativeTime(context).format(date).replaceFirst('in', '').trim()]), | ||||||
|           subtitle: Row( |           subtitle: Row( | ||||||
|             crossAxisAlignment: CrossAxisAlignment.center, |             crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|             children: [ |             children: [ | ||||||
| @@ -204,6 +209,9 @@ class _HomeDashSpecialDayWidget extends StatelessWidget { | |||||||
|                 separatorType: SeparatorType.symbol, |                 separatorType: SeparatorType.symbol, | ||||||
|                 decoration: BoxDecoration(), |                 decoration: BoxDecoration(), | ||||||
|                 padding: EdgeInsets.zero, |                 padding: EdgeInsets.zero, | ||||||
|  |                 onDone: () { | ||||||
|  |                   setState(() {}); | ||||||
|  |                 }, | ||||||
|               ), |               ), | ||||||
|               const Gap(12), |               const Gap(12), | ||||||
|               Expanded( |               Expanded( | ||||||
| @@ -380,6 +388,8 @@ class _HomeDashCheckInWidgetState extends State<_HomeDashCheckInWidget> { | |||||||
|                         Text( |                         Text( | ||||||
|                           'dailyCheckInNone', |                           'dailyCheckInNone', | ||||||
|                           style: Theme.of(context).textTheme.bodyLarge, |                           style: Theme.of(context).textTheme.bodyLarge, | ||||||
|  |                           maxLines: 2, | ||||||
|  |                           overflow: TextOverflow.ellipsis, | ||||||
|                         ).tr(), |                         ).tr(), | ||||||
|                       ], |                       ], | ||||||
|                     ) |                     ) | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ 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/markdown_content.dart'; | import 'package:surface/widgets/markdown_content.dart'; | ||||||
|  | import 'package:surface/widgets/navigation/app_scaffold.dart'; | ||||||
| import 'package:surface/widgets/post/post_item.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'; | ||||||
|  |  | ||||||
| @@ -82,24 +83,15 @@ class _NotificationScreenState extends State<NotificationScreen> { | |||||||
|     if (!mounted) return; |     if (!mounted) return; | ||||||
|     setState(() => _isSubmitting = true); |     setState(() => _isSubmitting = true); | ||||||
|  |  | ||||||
|     List<int> markList = List.empty(growable: true); |  | ||||||
|     for (final element in _notifications) { |  | ||||||
|       if (element.id <= 0) continue; |  | ||||||
|       if (element.readAt != null) continue; |  | ||||||
|       markList.add(element.id); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     try { |     try { | ||||||
|       final sn = context.read<SnNetworkProvider>(); |       final sn = context.read<SnNetworkProvider>(); | ||||||
|       await sn.client.put('/cgi/id/notifications/read', data: { |       final resp = await sn.client.put('/cgi/id/notifications/read/all'); | ||||||
|         'messages': markList, |  | ||||||
|       }); |  | ||||||
|       _notifications.clear(); |       _notifications.clear(); | ||||||
|       _fetchNotifications(); |       _fetchNotifications(); | ||||||
|  |  | ||||||
|       if (!mounted) return; |       if (!mounted) return; | ||||||
|       context.showSnackbar( |       context.showSnackbar( | ||||||
|         'notificationMarkAllReadPrompt'.plural(markList.length), |         'notificationMarkAllReadPrompt'.plural(resp.data['count']), | ||||||
|       ); |       ); | ||||||
|     } catch (err) { |     } catch (err) { | ||||||
|       if (!mounted) return; |       if (!mounted) return; | ||||||
| @@ -146,7 +138,7 @@ class _NotificationScreenState extends State<NotificationScreen> { | |||||||
|     final ua = context.read<UserProvider>(); |     final ua = context.read<UserProvider>(); | ||||||
|  |  | ||||||
|     if (!ua.isAuthorized) { |     if (!ua.isAuthorized) { | ||||||
|       return Scaffold( |       return AppScaffold( | ||||||
|         appBar: AppBar( |         appBar: AppBar( | ||||||
|           leading: AutoAppBarLeading(), |           leading: AutoAppBarLeading(), | ||||||
|           title: Text('screenNotification').tr(), |           title: Text('screenNotification').tr(), | ||||||
| @@ -157,7 +149,7 @@ class _NotificationScreenState extends State<NotificationScreen> { | |||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return Scaffold( |     return AppScaffold( | ||||||
|       appBar: AppBar( |       appBar: AppBar( | ||||||
|         leading: AutoAppBarLeading(), |         leading: AutoAppBarLeading(), | ||||||
|         title: Text('screenNotification').tr(), |         title: Text('screenNotification').tr(), | ||||||
| @@ -215,10 +207,11 @@ class _NotificationScreenState extends State<NotificationScreen> { | |||||||
|                                 style: Theme.of(context).textTheme.titleSmall, |                                 style: Theme.of(context).textTheme.titleSmall, | ||||||
|                               ), |                               ), | ||||||
|                             if (nty.subtitle != null) const Gap(4), |                             if (nty.subtitle != null) const Gap(4), | ||||||
|                             MarkdownTextContent( |                             SelectionArea( | ||||||
|  |                               child: MarkdownTextContent( | ||||||
|                                 content: nty.body, |                                 content: nty.body, | ||||||
|                                 isAutoWarp: true, |                                 isAutoWarp: true, | ||||||
|                               isSelectable: true, |                               ), | ||||||
|                             ), |                             ), | ||||||
|                             if ([ |                             if ([ | ||||||
|                                   'interactive.feedback', |                                   'interactive.feedback', | ||||||
|   | |||||||
| @@ -13,6 +13,8 @@ import 'package:surface/providers/userinfo.dart'; | |||||||
| import 'package:surface/types/post.dart'; | import 'package:surface/types/post.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/post/post_comment_list.dart'; | import 'package:surface/widgets/post/post_comment_list.dart'; | ||||||
| import 'package:surface/widgets/post/post_item.dart'; | import 'package:surface/widgets/post/post_item.dart'; | ||||||
| import 'package:surface/widgets/post/post_mini_editor.dart'; | import 'package:surface/widgets/post/post_mini_editor.dart'; | ||||||
| @@ -20,12 +22,9 @@ import 'package:surface/widgets/post/post_mini_editor.dart'; | |||||||
| class PostDetailScreen extends StatefulWidget { | class PostDetailScreen extends StatefulWidget { | ||||||
|   final String slug; |   final String slug; | ||||||
|   final SnPost? preload; |   final SnPost? preload; | ||||||
|  |   final Function? onBack; | ||||||
|  |  | ||||||
|   const PostDetailScreen({ |   const PostDetailScreen({super.key, required this.slug, this.preload, this.onBack}); | ||||||
|     super.key, |  | ||||||
|     required this.slug, |  | ||||||
|     this.preload, |  | ||||||
|   }); |  | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   State<PostDetailScreen> createState() => _PostDetailScreenState(); |   State<PostDetailScreen> createState() => _PostDetailScreenState(); | ||||||
| @@ -67,10 +66,15 @@ class _PostDetailScreenState extends State<PostDetailScreen> { | |||||||
|     final ua = context.watch<UserProvider>(); |     final ua = context.watch<UserProvider>(); | ||||||
|     final devicePixelRatio = MediaQuery.of(context).devicePixelRatio; |     final devicePixelRatio = MediaQuery.of(context).devicePixelRatio; | ||||||
|  |  | ||||||
|     return Scaffold( |     return AppBackground( | ||||||
|  |       isRoot: widget.onBack != null, | ||||||
|  |       child: AppScaffold( | ||||||
|         appBar: AppBar( |         appBar: AppBar( | ||||||
|           leading: BackButton( |           leading: BackButton( | ||||||
|             onPressed: () { |             onPressed: () { | ||||||
|  |               if (widget.onBack != null) { | ||||||
|  |                 widget.onBack!.call(); | ||||||
|  |               } | ||||||
|               if (GoRouter.of(context).canPop()) { |               if (GoRouter.of(context).canPop()) { | ||||||
|                 GoRouter.of(context).pop(context); |                 GoRouter.of(context).pop(context); | ||||||
|                 return; |                 return; | ||||||
| @@ -96,6 +100,8 @@ class _PostDetailScreenState extends State<PostDetailScreen> { | |||||||
|                           ), |                           ), | ||||||
|                     ), |                     ), | ||||||
|                   ]), |                   ]), | ||||||
|  |                   maxLines: 2, | ||||||
|  |                   overflow: TextOverflow.ellipsis, | ||||||
|                 ) |                 ) | ||||||
|               : Text('postDetail').tr(), |               : Text('postDetail').tr(), | ||||||
|         ), |         ), | ||||||
| @@ -183,6 +189,7 @@ class _PostDetailScreenState extends State<PostDetailScreen> { | |||||||
|             SliverGap(math.max(MediaQuery.of(context).padding.bottom, 16)), |             SliverGap(math.max(MediaQuery.of(context).padding.bottom, 16)), | ||||||
|           ], |           ], | ||||||
|         ), |         ), | ||||||
|  |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -13,6 +13,7 @@ import 'package:surface/providers/sn_network.dart'; | |||||||
| import 'package:surface/types/post.dart'; | import 'package:surface/types/post.dart'; | ||||||
| import 'package:surface/widgets/account/account_image.dart'; | import 'package:surface/widgets/account/account_image.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/post/post_item.dart'; | import 'package:surface/widgets/post/post_item.dart'; | ||||||
| import 'package:surface/widgets/post/post_media_pending_list.dart'; | import 'package:surface/widgets/post/post_media_pending_list.dart'; | ||||||
| import 'package:surface/widgets/post/post_meta_editor.dart'; | import 'package:surface/widgets/post/post_meta_editor.dart'; | ||||||
| @@ -54,7 +55,9 @@ class PostEditorScreen extends StatefulWidget { | |||||||
| } | } | ||||||
|  |  | ||||||
| class _PostEditorScreenState extends State<PostEditorScreen> { | class _PostEditorScreenState extends State<PostEditorScreen> { | ||||||
|   final PostWriteController _writeController = PostWriteController(); |   late final PostWriteController _writeController = PostWriteController( | ||||||
|  |     doLoadFromTemporary: widget.postEditId == null, | ||||||
|  |   ); | ||||||
|  |  | ||||||
|   bool _isFetching = false; |   bool _isFetching = false; | ||||||
|  |  | ||||||
| @@ -126,7 +129,7 @@ class _PostEditorScreenState extends State<PostEditorScreen> { | |||||||
|     return ListenableBuilder( |     return ListenableBuilder( | ||||||
|       listenable: _writeController, |       listenable: _writeController, | ||||||
|       builder: (context, _) { |       builder: (context, _) { | ||||||
|         return Scaffold( |         return AppScaffold( | ||||||
|           appBar: AppBar( |           appBar: AppBar( | ||||||
|             leading: BackButton( |             leading: BackButton( | ||||||
|               onPressed: () { |               onPressed: () { | ||||||
| @@ -301,7 +304,9 @@ class _PostEditorScreenState extends State<PostEditorScreen> { | |||||||
|                           ], |                           ], | ||||||
|                         ), |                         ), | ||||||
|                       // Content Input Area |                       // Content Input Area | ||||||
|                       TextField( |                       Container( | ||||||
|  |                         constraints: const BoxConstraints(maxWidth: 640), | ||||||
|  |                         child: TextField( | ||||||
|                           controller: _writeController.contentController, |                           controller: _writeController.contentController, | ||||||
|                           maxLines: null, |                           maxLines: null, | ||||||
|                           decoration: InputDecoration( |                           decoration: InputDecoration( | ||||||
| @@ -315,6 +320,7 @@ class _PostEditorScreenState extends State<PostEditorScreen> { | |||||||
|                           ), |                           ), | ||||||
|                           onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), |                           onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||||
|                         ), |                         ), | ||||||
|  |                       ), | ||||||
|                     ] |                     ] | ||||||
|                         .expandIndexed( |                         .expandIndexed( | ||||||
|                           (idx, ele) => [ |                           (idx, ele) => [ | ||||||
| @@ -364,7 +370,18 @@ class _PostEditorScreenState extends State<PostEditorScreen> { | |||||||
|                 child: Column( |                 child: Column( | ||||||
|                   crossAxisAlignment: CrossAxisAlignment.start, |                   crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|                   children: [ |                   children: [ | ||||||
|  |                     LoadingIndicator(isActive: _isLoading), | ||||||
|  |                     if (_writeController.isBusy && _writeController.progress != null) | ||||||
|  |                       TweenAnimationBuilder<double>( | ||||||
|  |                         tween: Tween(begin: 0, end: _writeController.progress), | ||||||
|  |                         duration: Duration(milliseconds: 300), | ||||||
|  |                         builder: (context, value, _) => LinearProgressIndicator(value: value, minHeight: 2), | ||||||
|  |                       ) | ||||||
|  |                     else if (_writeController.isBusy) | ||||||
|  |                       const LinearProgressIndicator(value: null, minHeight: 2), | ||||||
|                     Container( |                     Container( | ||||||
|  |                       child: _writeController.temporaryRestored | ||||||
|  |                           ? Container( | ||||||
|                               padding: const EdgeInsets.only(top: 4, bottom: 4, left: 28, right: 22), |                               padding: const EdgeInsets.only(top: 4, bottom: 4, left: 28, right: 22), | ||||||
|                               decoration: BoxDecoration( |                               decoration: BoxDecoration( | ||||||
|                                 border: Border( |                                 border: Border( | ||||||
| @@ -374,8 +391,7 @@ class _PostEditorScreenState extends State<PostEditorScreen> { | |||||||
|                                   ), |                                   ), | ||||||
|                                 ), |                                 ), | ||||||
|                               ), |                               ), | ||||||
|                       child: _writeController.temporaryRestored |                               child: Row( | ||||||
|                           ? Row( |  | ||||||
|                                 crossAxisAlignment: CrossAxisAlignment.center, |                                 crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|                                 children: [ |                                 children: [ | ||||||
|                                   const Icon(Icons.restore, size: 20), |                                   const Icon(Icons.restore, size: 20), | ||||||
| @@ -388,20 +404,11 @@ class _PostEditorScreenState extends State<PostEditorScreen> { | |||||||
|                                     }, |                                     }, | ||||||
|                                   ), |                                   ), | ||||||
|                                 ], |                                 ], | ||||||
|                             ) |                               )) | ||||||
|                           : const SizedBox.shrink(), |                           : const SizedBox.shrink(), | ||||||
|                     ) |                     ) | ||||||
|                         .height(_writeController.temporaryRestored ? 32 : 0, animate: true) |                         .height(_writeController.temporaryRestored ? 32 : 0, animate: true) | ||||||
|                         .animate(const Duration(milliseconds: 300), Curves.fastLinearToSlowEaseIn), |                         .animate(const Duration(milliseconds: 300), Curves.fastLinearToSlowEaseIn), | ||||||
|                     LoadingIndicator(isActive: _isLoading), |  | ||||||
|                     if (_writeController.isBusy && _writeController.progress != null) |  | ||||||
|                       TweenAnimationBuilder<double>( |  | ||||||
|                         tween: Tween(begin: 0, end: _writeController.progress), |  | ||||||
|                         duration: Duration(milliseconds: 300), |  | ||||||
|                         builder: (context, value, _) => LinearProgressIndicator(value: value, minHeight: 2), |  | ||||||
|                       ) |  | ||||||
|                     else if (_writeController.isBusy) |  | ||||||
|                       const LinearProgressIndicator(value: null, minHeight: 2), |  | ||||||
|                     Row( |                     Row( | ||||||
|                       mainAxisAlignment: MainAxisAlignment.spaceBetween, |                       mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||||||
|                       children: [ |                       children: [ | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ import 'package:styled_widget/styled_widget.dart'; | |||||||
| import 'package:surface/providers/post.dart'; | import 'package:surface/providers/post.dart'; | ||||||
| import 'package:surface/types/post.dart'; | import 'package:surface/types/post.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/post/post_item.dart'; | import 'package:surface/widgets/post/post_item.dart'; | ||||||
| import 'package:surface/widgets/post/post_tags_field.dart'; | import 'package:surface/widgets/post/post_tags_field.dart'; | ||||||
| import 'package:very_good_infinite_list/very_good_infinite_list.dart'; | import 'package:very_good_infinite_list/very_good_infinite_list.dart'; | ||||||
| @@ -119,7 +120,7 @@ class _PostSearchScreenState extends State<PostSearchScreen> { | |||||||
|       ), |       ), | ||||||
|     ]; |     ]; | ||||||
|  |  | ||||||
|     return Scaffold( |     return AppScaffold( | ||||||
|       appBar: AppBar( |       appBar: AppBar( | ||||||
|         title: Text('screenPostSearch').tr(), |         title: Text('screenPostSearch').tr(), | ||||||
|         actions: [ |         actions: [ | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ 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/dialog.dart'; | import 'package:surface/widgets/dialog.dart'; | ||||||
|  | import 'package:surface/widgets/navigation/app_scaffold.dart'; | ||||||
| import 'package:surface/widgets/post/post_item.dart'; | import 'package:surface/widgets/post/post_item.dart'; | ||||||
| import 'package:surface/widgets/universal_image.dart'; | import 'package:surface/widgets/universal_image.dart'; | ||||||
| import 'package:very_good_infinite_list/very_good_infinite_list.dart'; | import 'package:very_good_infinite_list/very_good_infinite_list.dart'; | ||||||
| @@ -274,7 +275,7 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> with SingleTi | |||||||
|  |  | ||||||
|     final sn = context.read<SnNetworkProvider>(); |     final sn = context.read<SnNetworkProvider>(); | ||||||
|  |  | ||||||
|     return Scaffold( |     return AppScaffold( | ||||||
|       body: NestedScrollView( |       body: NestedScrollView( | ||||||
|         controller: _scrollController, |         controller: _scrollController, | ||||||
|         headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { |         headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ 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/unauthorized_hint.dart'; | import 'package:surface/widgets/unauthorized_hint.dart'; | ||||||
| import 'package:surface/widgets/universal_image.dart'; | import 'package:surface/widgets/universal_image.dart'; | ||||||
|  |  | ||||||
| @@ -83,7 +84,7 @@ class _RealmScreenState extends State<RealmScreen> { | |||||||
|     final ua = context.read<UserProvider>(); |     final ua = context.read<UserProvider>(); | ||||||
|  |  | ||||||
|     if (!ua.isAuthorized) { |     if (!ua.isAuthorized) { | ||||||
|       return Scaffold( |       return AppScaffold( | ||||||
|         appBar: AppBar( |         appBar: AppBar( | ||||||
|           leading: AutoAppBarLeading(), |           leading: AutoAppBarLeading(), | ||||||
|           title: Text('screenRealm').tr(), |           title: Text('screenRealm').tr(), | ||||||
| @@ -94,7 +95,7 @@ class _RealmScreenState extends State<RealmScreen> { | |||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return Scaffold( |     return AppScaffold( | ||||||
|       appBar: AppBar( |       appBar: AppBar( | ||||||
|         leading: AutoAppBarLeading(), |         leading: AutoAppBarLeading(), | ||||||
|         title: Text('screenRealm').tr(), |         title: Text('screenRealm').tr(), | ||||||
| @@ -118,6 +119,9 @@ class _RealmScreenState extends State<RealmScreen> { | |||||||
|         children: [ |         children: [ | ||||||
|           LoadingIndicator(isActive: _isBusy), |           LoadingIndicator(isActive: _isBusy), | ||||||
|           Expanded( |           Expanded( | ||||||
|  |             child: MediaQuery.removePadding( | ||||||
|  |               context: context, | ||||||
|  |               removeTop: true, | ||||||
|               child: RefreshIndicator( |               child: RefreshIndicator( | ||||||
|                 onRefresh: _fetchRealms, |                 onRefresh: _fetchRealms, | ||||||
|                 child: ListView.builder( |                 child: ListView.builder( | ||||||
| @@ -196,7 +200,9 @@ class _RealmScreenState extends State<RealmScreen> { | |||||||
|                                   clipBehavior: Clip.none, |                                   clipBehavior: Clip.none, | ||||||
|                                   fit: StackFit.expand, |                                   fit: StackFit.expand, | ||||||
|                                   children: [ |                                   children: [ | ||||||
|                                   Container( |                                     ClipRRect( | ||||||
|  |                                       borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||||
|  |                                       child: Container( | ||||||
|                                         color: Theme.of(context).colorScheme.surfaceContainer, |                                         color: Theme.of(context).colorScheme.surfaceContainer, | ||||||
|                                         child: (realm.banner?.isEmpty ?? true) |                                         child: (realm.banner?.isEmpty ?? true) | ||||||
|                                             ? const SizedBox.shrink() |                                             ? const SizedBox.shrink() | ||||||
| @@ -205,6 +211,7 @@ class _RealmScreenState extends State<RealmScreen> { | |||||||
|                                                 fit: BoxFit.cover, |                                                 fit: BoxFit.cover, | ||||||
|                                               ), |                                               ), | ||||||
|                                       ), |                                       ), | ||||||
|  |                                     ), | ||||||
|                                     Positioned( |                                     Positioned( | ||||||
|                                       bottom: -30, |                                       bottom: -30, | ||||||
|                                       left: 18, |                                       left: 18, | ||||||
| @@ -240,6 +247,7 @@ class _RealmScreenState extends State<RealmScreen> { | |||||||
|                 ), |                 ), | ||||||
|               ), |               ), | ||||||
|             ), |             ), | ||||||
|  |           ), | ||||||
|         ], |         ], | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   | |||||||
| @@ -18,6 +18,7 @@ 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/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/universal_image.dart'; | import 'package:surface/widgets/universal_image.dart'; | ||||||
| import 'package:uuid/uuid.dart'; | import 'package:uuid/uuid.dart'; | ||||||
|  |  | ||||||
| @@ -179,7 +180,7 @@ class _RealmManageScreenState extends State<RealmManageScreen> { | |||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     final sn = context.read<SnNetworkProvider>(); |     final sn = context.read<SnNetworkProvider>(); | ||||||
|  |  | ||||||
|     return Scaffold( |     return AppScaffold( | ||||||
|       appBar: AppBar( |       appBar: AppBar( | ||||||
|         title: widget.editingRealmAlias != null |         title: widget.editingRealmAlias != null | ||||||
|             ? Text('screenRealmManage').tr() |             ? Text('screenRealmManage').tr() | ||||||
|   | |||||||
| @@ -8,13 +8,13 @@ import 'package:styled_widget/styled_widget.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/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/dialog.dart'; | import 'package:surface/widgets/dialog.dart'; | ||||||
|  | import 'package:surface/widgets/navigation/app_scaffold.dart'; | ||||||
| import 'package:very_good_infinite_list/very_good_infinite_list.dart'; | import 'package:very_good_infinite_list/very_good_infinite_list.dart'; | ||||||
|  |  | ||||||
| import '../../types/post.dart'; |  | ||||||
|  |  | ||||||
| class RealmDetailScreen extends StatefulWidget { | class RealmDetailScreen extends StatefulWidget { | ||||||
|   final String alias; |   final String alias; | ||||||
|  |  | ||||||
| @@ -70,27 +70,19 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> { | |||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return DefaultTabController( |     return DefaultTabController( | ||||||
|       length: 3, |       length: 3, | ||||||
|       child: Scaffold( |       child: AppScaffold( | ||||||
|         body: NestedScrollView( |         body: NestedScrollView( | ||||||
|           headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { |           headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { | ||||||
|             // These are the slivers that show up in the "outer" scroll view. |  | ||||||
|             return <Widget>[ |             return <Widget>[ | ||||||
|               SliverOverlapAbsorber( |               SliverOverlapAbsorber( | ||||||
|                 // This widget takes the overlapping behavior of the SliverAppBar, |  | ||||||
|                 // and redirects it to the SliverOverlapInjector below. If it is |  | ||||||
|                 // missing, then it is possible for the nested "inner" scroll view |  | ||||||
|                 // below to end up under the SliverAppBar even when the inner |  | ||||||
|                 // scroll view thinks it has not been scrolled. |  | ||||||
|                 // This is not necessary if the "headerSliverBuilder" only builds |  | ||||||
|                 // widgets that do not overlap the next sliver. |  | ||||||
|                 handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), |                 handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), | ||||||
|                 sliver: SliverAppBar( |                 sliver: SliverAppBar( | ||||||
|                   title: Text(_realm?.name ?? 'loading'.tr()), |                   title: Text(_realm?.name ?? 'loading'.tr()), | ||||||
|                   bottom: TabBar( |                   bottom: TabBar( | ||||||
|                     tabs: [ |                     tabs: [ | ||||||
|                       Tab(icon: const Icon(Symbols.home)), |                       Tab(icon: Icon(Symbols.home, color: Theme.of(context).appBarTheme.foregroundColor)), | ||||||
|                       Tab(icon: const Icon(Symbols.group)), |                       Tab(icon: Icon(Symbols.group, color: Theme.of(context).appBarTheme.foregroundColor)), | ||||||
|                       Tab(icon: const Icon(Symbols.settings)), |                       Tab(icon: Icon(Symbols.settings, color: Theme.of(context).appBarTheme.foregroundColor)), | ||||||
|                     ], |                     ], | ||||||
|                   ), |                   ), | ||||||
|                 ), |                 ), | ||||||
| @@ -428,7 +420,7 @@ class _RealmSettingsWidgetState extends State<_RealmSettingsWidget> { | |||||||
|  |  | ||||||
|     return Column( |     return Column( | ||||||
|       children: [ |       children: [ | ||||||
|         const Gap(16), |         const Gap(8), | ||||||
|         ListTile( |         ListTile( | ||||||
|           leading: const Icon(Symbols.edit), |           leading: const Icon(Symbols.edit), | ||||||
|           trailing: const Icon(Symbols.chevron_right), |           trailing: const Icon(Symbols.chevron_right), | ||||||
|   | |||||||
| @@ -18,6 +18,7 @@ import 'package:surface/providers/sn_network.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'; | ||||||
|  | import 'package:surface/widgets/navigation/app_scaffold.dart'; | ||||||
|  |  | ||||||
| const Map<String, Color> kColorSchemes = { | const Map<String, Color> kColorSchemes = { | ||||||
|   'colorSchemeIndigo': Colors.indigo, |   'colorSchemeIndigo': Colors.indigo, | ||||||
| @@ -67,7 +68,11 @@ class _SettingsScreenState extends State<SettingsScreen> { | |||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     final sn = context.read<SnNetworkProvider>(); |     final sn = context.read<SnNetworkProvider>(); | ||||||
|  |  | ||||||
|     return Scaffold( |     return AppScaffold( | ||||||
|  |       appBar: AppBar( | ||||||
|  |         leading: const PageBackButton(), | ||||||
|  |         title: Text('screenSettings').tr(), | ||||||
|  |       ), | ||||||
|       body: SingleChildScrollView( |       body: SingleChildScrollView( | ||||||
|         child: Column( |         child: Column( | ||||||
|           spacing: 16, |           spacing: 16, | ||||||
| @@ -120,7 +125,7 @@ class _SettingsScreenState extends State<SettingsScreen> { | |||||||
|                   subtitle: Text('settingsThemeMaterial3Description').tr(), |                   subtitle: Text('settingsThemeMaterial3Description').tr(), | ||||||
|                   contentPadding: const EdgeInsets.only(left: 24, right: 17), |                   contentPadding: const EdgeInsets.only(left: 24, right: 17), | ||||||
|                   secondary: const Icon(Symbols.new_releases), |                   secondary: const Icon(Symbols.new_releases), | ||||||
|                   value: _prefs.getBool(kMaterialYouToggleStoreKey) ?? false, |                   value: _prefs.getBool(kMaterialYouToggleStoreKey) ?? true, | ||||||
|                   onChanged: (value) { |                   onChanged: (value) { | ||||||
|                     setState(() { |                     setState(() { | ||||||
|                       _prefs.setBool( |                       _prefs.setBool( | ||||||
| @@ -240,6 +245,61 @@ class _SettingsScreenState extends State<SettingsScreen> { | |||||||
|                     setState(() {}); |                     setState(() {}); | ||||||
|                   }, |                   }, | ||||||
|                 ), |                 ), | ||||||
|  |                 CheckboxListTile( | ||||||
|  |                   secondary: const Icon(Symbols.left_panel_close), | ||||||
|  |                   title: Text('settingsDrawerPreferCollapse').tr(), | ||||||
|  |                   subtitle: Text('settingsDrawerPreferCollapseDescription').tr(), | ||||||
|  |                   contentPadding: const EdgeInsets.only(left: 24, right: 17), | ||||||
|  |                   value: _prefs.getBool(kAppDrawerPreferCollapse) ?? false, | ||||||
|  |                   onChanged: (value) { | ||||||
|  |                     _prefs.setBool(kAppDrawerPreferCollapse, value ?? false); | ||||||
|  |                     final cfg = context.read<ConfigProvider>(); | ||||||
|  |                     cfg.calcDrawerSize(context); | ||||||
|  |                     setState(() {}); | ||||||
|  |                   }, | ||||||
|  |                 ), | ||||||
|  |               ], | ||||||
|  |             ), | ||||||
|  |             Column( | ||||||
|  |               crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |               children: [ | ||||||
|  |                 Text('settingsFeatures').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4), | ||||||
|  |                 CheckboxListTile( | ||||||
|  |                   secondary: const Icon(Symbols.vibration), | ||||||
|  |                   contentPadding: const EdgeInsets.only(left: 24, right: 17), | ||||||
|  |                   title: Text('settingsNotifyWithHaptic').tr(), | ||||||
|  |                   subtitle: Text('settingsNotifyWithHapticDescription').tr(), | ||||||
|  |                   value: _prefs.getBool(kAppNotifyWithHaptic) ?? true, | ||||||
|  |                   onChanged: (value) { | ||||||
|  |                     setState(() { | ||||||
|  |                       _prefs.setBool(kAppNotifyWithHaptic, value ?? false); | ||||||
|  |                     }); | ||||||
|  |                   }, | ||||||
|  |                 ), | ||||||
|  |                 CheckboxListTile( | ||||||
|  |                   secondary: const Icon(Symbols.link), | ||||||
|  |                   title: Text('settingsExpandPostLink').tr(), | ||||||
|  |                   subtitle: Text('settingsExpandPostLinkDescription').tr(), | ||||||
|  |                   contentPadding: const EdgeInsets.only(left: 24, right: 17), | ||||||
|  |                   value: _prefs.getBool(kAppExpandPostLink) ?? true, | ||||||
|  |                   onChanged: (value) { | ||||||
|  |                     setState(() { | ||||||
|  |                       _prefs.setBool(kAppExpandPostLink, value ?? false); | ||||||
|  |                     }); | ||||||
|  |                   }, | ||||||
|  |                 ), | ||||||
|  |                 CheckboxListTile( | ||||||
|  |                   secondary: const Icon(Symbols.chat), | ||||||
|  |                   title: Text('settingsExpandChatLink').tr(), | ||||||
|  |                   subtitle: Text('settingsExpandChatLinkDescription').tr(), | ||||||
|  |                   contentPadding: const EdgeInsets.only(left: 24, right: 17), | ||||||
|  |                   value: _prefs.getBool(kAppExpandChatLink) ?? true, | ||||||
|  |                   onChanged: (value) { | ||||||
|  |                     setState(() { | ||||||
|  |                       _prefs.setBool(kAppExpandChatLink, value ?? false); | ||||||
|  |                     }); | ||||||
|  |                   }, | ||||||
|  |                 ), | ||||||
|               ], |               ], | ||||||
|             ), |             ), | ||||||
|             Column( |             Column( | ||||||
|   | |||||||
| @@ -34,9 +34,10 @@ Future<ThemeData> createAppTheme( | |||||||
|   ); |   ); | ||||||
|  |  | ||||||
|   final hasAppBarBlurry = prefs.getBool(kAppbarTransparentStoreKey) ?? false; |   final hasAppBarBlurry = prefs.getBool(kAppbarTransparentStoreKey) ?? false; | ||||||
|  |   final useM3 = useMaterial3 ?? (prefs.getBool(kMaterialYouToggleStoreKey) ?? true); | ||||||
|  |  | ||||||
|   return ThemeData( |   return ThemeData( | ||||||
|     useMaterial3: useMaterial3 ?? (prefs.getBool(kMaterialYouToggleStoreKey) ?? false), |     useMaterial3: useM3, | ||||||
|     colorScheme: colorScheme, |     colorScheme: colorScheme, | ||||||
|     brightness: brightness, |     brightness: brightness, | ||||||
|     iconTheme: IconThemeData( |     iconTheme: IconThemeData( | ||||||
| @@ -45,12 +46,24 @@ Future<ThemeData> createAppTheme( | |||||||
|       opticalSize: 20, |       opticalSize: 20, | ||||||
|       color: colorScheme.onSurface, |       color: colorScheme.onSurface, | ||||||
|     ), |     ), | ||||||
|  |     snackBarTheme: SnackBarThemeData( | ||||||
|  |       behavior: useM3 ? SnackBarBehavior.floating : SnackBarBehavior.fixed, | ||||||
|  |     ), | ||||||
|     appBarTheme: AppBarTheme( |     appBarTheme: AppBarTheme( | ||||||
|       centerTitle: true, |       centerTitle: true, | ||||||
|       elevation: hasAppBarBlurry ? 0 : null, |       elevation: hasAppBarBlurry ? 0 : null, | ||||||
|       backgroundColor: hasAppBarBlurry ? colorScheme.surfaceContainer.withAlpha(200) : colorScheme.primary, |       backgroundColor: hasAppBarBlurry ? colorScheme.primary.withOpacity(0.3) : colorScheme.primary, | ||||||
|       foregroundColor: hasAppBarBlurry ? colorScheme.onSurface : colorScheme.onPrimary, |       foregroundColor: hasAppBarBlurry ? colorScheme.onSurface : colorScheme.onPrimary, | ||||||
|     ), |     ), | ||||||
|     scaffoldBackgroundColor: Colors.transparent, |     pageTransitionsTheme: PageTransitionsTheme( | ||||||
|  |       builders: { | ||||||
|  |         TargetPlatform.android: PredictiveBackPageTransitionsBuilder(), | ||||||
|  |         TargetPlatform.iOS: CupertinoPageTransitionsBuilder(), | ||||||
|  |         TargetPlatform.macOS: ZoomPageTransitionsBuilder(), | ||||||
|  |         TargetPlatform.fuchsia: ZoomPageTransitionsBuilder(), | ||||||
|  |         TargetPlatform.linux: ZoomPageTransitionsBuilder(), | ||||||
|  |         TargetPlatform.windows: ZoomPageTransitionsBuilder(), | ||||||
|  |       }, | ||||||
|  |     ), | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -141,3 +141,39 @@ class SnAttachmentBoost with _$SnAttachmentBoost { | |||||||
|  |  | ||||||
|   factory SnAttachmentBoost.fromJson(Map<String, Object?> json) => _$SnAttachmentBoostFromJson(json); |   factory SnAttachmentBoost.fromJson(Map<String, Object?> json) => _$SnAttachmentBoostFromJson(json); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @freezed | ||||||
|  | class SnSticker with _$SnSticker { | ||||||
|  |   const factory SnSticker({ | ||||||
|  |     required int id, | ||||||
|  |     required DateTime createdAt, | ||||||
|  |     required DateTime updatedAt, | ||||||
|  |     required DateTime? deletedAt, | ||||||
|  |     required String alias, | ||||||
|  |     required String name, | ||||||
|  |     required int attachmentId, | ||||||
|  |     required SnAttachment attachment, | ||||||
|  |     required int packId, | ||||||
|  |     required SnStickerPack pack, | ||||||
|  |     required int accountId, | ||||||
|  |   }) = _SnSticker; | ||||||
|  |  | ||||||
|  |   factory SnSticker.fromJson(Map<String, Object?> json) => _$SnStickerFromJson(json); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @freezed | ||||||
|  | class SnStickerPack with _$SnStickerPack { | ||||||
|  |   const factory SnStickerPack({ | ||||||
|  |     required int id, | ||||||
|  |     required DateTime createdAt, | ||||||
|  |     required DateTime updatedAt, | ||||||
|  |     required DateTime? deletedAt, | ||||||
|  |     required String prefix, | ||||||
|  |     required String name, | ||||||
|  |     required String description, | ||||||
|  |     required List<SnSticker>? stickers, | ||||||
|  |     required int accountId, | ||||||
|  |   }) = _SnStickerPack; | ||||||
|  |  | ||||||
|  |   factory SnStickerPack.fromJson(Map<String, Object?> json) => _$SnStickerPackFromJson(json); | ||||||
|  | } | ||||||
|   | |||||||
| @@ -2272,3 +2272,738 @@ abstract class _SnAttachmentBoost implements SnAttachmentBoost { | |||||||
|   _$$SnAttachmentBoostImplCopyWith<_$SnAttachmentBoostImpl> get copyWith => |   _$$SnAttachmentBoostImplCopyWith<_$SnAttachmentBoostImpl> get copyWith => | ||||||
|       throw _privateConstructorUsedError; |       throw _privateConstructorUsedError; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | SnSticker _$SnStickerFromJson(Map<String, dynamic> json) { | ||||||
|  |   return _SnSticker.fromJson(json); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | mixin _$SnSticker { | ||||||
|  |   int get id => throw _privateConstructorUsedError; | ||||||
|  |   DateTime get createdAt => throw _privateConstructorUsedError; | ||||||
|  |   DateTime get updatedAt => throw _privateConstructorUsedError; | ||||||
|  |   DateTime? get deletedAt => throw _privateConstructorUsedError; | ||||||
|  |   String get alias => throw _privateConstructorUsedError; | ||||||
|  |   String get name => throw _privateConstructorUsedError; | ||||||
|  |   int get attachmentId => throw _privateConstructorUsedError; | ||||||
|  |   SnAttachment get attachment => throw _privateConstructorUsedError; | ||||||
|  |   int get packId => throw _privateConstructorUsedError; | ||||||
|  |   SnStickerPack get pack => throw _privateConstructorUsedError; | ||||||
|  |   int get accountId => throw _privateConstructorUsedError; | ||||||
|  |  | ||||||
|  |   /// Serializes this SnSticker to a JSON map. | ||||||
|  |   Map<String, dynamic> toJson() => throw _privateConstructorUsedError; | ||||||
|  |  | ||||||
|  |   /// Create a copy of SnSticker | ||||||
|  |   /// with the given fields replaced by the non-null parameter values. | ||||||
|  |   @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  |   $SnStickerCopyWith<SnSticker> get copyWith => | ||||||
|  |       throw _privateConstructorUsedError; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract class $SnStickerCopyWith<$Res> { | ||||||
|  |   factory $SnStickerCopyWith(SnSticker value, $Res Function(SnSticker) then) = | ||||||
|  |       _$SnStickerCopyWithImpl<$Res, SnSticker>; | ||||||
|  |   @useResult | ||||||
|  |   $Res call( | ||||||
|  |       {int id, | ||||||
|  |       DateTime createdAt, | ||||||
|  |       DateTime updatedAt, | ||||||
|  |       DateTime? deletedAt, | ||||||
|  |       String alias, | ||||||
|  |       String name, | ||||||
|  |       int attachmentId, | ||||||
|  |       SnAttachment attachment, | ||||||
|  |       int packId, | ||||||
|  |       SnStickerPack pack, | ||||||
|  |       int accountId}); | ||||||
|  |  | ||||||
|  |   $SnAttachmentCopyWith<$Res> get attachment; | ||||||
|  |   $SnStickerPackCopyWith<$Res> get pack; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | class _$SnStickerCopyWithImpl<$Res, $Val extends SnSticker> | ||||||
|  |     implements $SnStickerCopyWith<$Res> { | ||||||
|  |   _$SnStickerCopyWithImpl(this._value, this._then); | ||||||
|  |  | ||||||
|  |   // ignore: unused_field | ||||||
|  |   final $Val _value; | ||||||
|  |   // ignore: unused_field | ||||||
|  |   final $Res Function($Val) _then; | ||||||
|  |  | ||||||
|  |   /// Create a copy of SnSticker | ||||||
|  |   /// with the given fields replaced by the non-null parameter values. | ||||||
|  |   @pragma('vm:prefer-inline') | ||||||
|  |   @override | ||||||
|  |   $Res call({ | ||||||
|  |     Object? id = null, | ||||||
|  |     Object? createdAt = null, | ||||||
|  |     Object? updatedAt = null, | ||||||
|  |     Object? deletedAt = freezed, | ||||||
|  |     Object? alias = null, | ||||||
|  |     Object? name = null, | ||||||
|  |     Object? attachmentId = null, | ||||||
|  |     Object? attachment = null, | ||||||
|  |     Object? packId = null, | ||||||
|  |     Object? pack = null, | ||||||
|  |     Object? accountId = null, | ||||||
|  |   }) { | ||||||
|  |     return _then(_value.copyWith( | ||||||
|  |       id: null == id | ||||||
|  |           ? _value.id | ||||||
|  |           : id // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as int, | ||||||
|  |       createdAt: null == createdAt | ||||||
|  |           ? _value.createdAt | ||||||
|  |           : createdAt // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as DateTime, | ||||||
|  |       updatedAt: null == updatedAt | ||||||
|  |           ? _value.updatedAt | ||||||
|  |           : updatedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as DateTime, | ||||||
|  |       deletedAt: freezed == deletedAt | ||||||
|  |           ? _value.deletedAt | ||||||
|  |           : deletedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as DateTime?, | ||||||
|  |       alias: null == alias | ||||||
|  |           ? _value.alias | ||||||
|  |           : alias // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as String, | ||||||
|  |       name: null == name | ||||||
|  |           ? _value.name | ||||||
|  |           : name // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as String, | ||||||
|  |       attachmentId: null == attachmentId | ||||||
|  |           ? _value.attachmentId | ||||||
|  |           : attachmentId // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as int, | ||||||
|  |       attachment: null == attachment | ||||||
|  |           ? _value.attachment | ||||||
|  |           : attachment // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as SnAttachment, | ||||||
|  |       packId: null == packId | ||||||
|  |           ? _value.packId | ||||||
|  |           : packId // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as int, | ||||||
|  |       pack: null == pack | ||||||
|  |           ? _value.pack | ||||||
|  |           : pack // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as SnStickerPack, | ||||||
|  |       accountId: null == accountId | ||||||
|  |           ? _value.accountId | ||||||
|  |           : accountId // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as int, | ||||||
|  |     ) as $Val); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /// Create a copy of SnSticker | ||||||
|  |   /// with the given fields replaced by the non-null parameter values. | ||||||
|  |   @override | ||||||
|  |   @pragma('vm:prefer-inline') | ||||||
|  |   $SnAttachmentCopyWith<$Res> get attachment { | ||||||
|  |     return $SnAttachmentCopyWith<$Res>(_value.attachment, (value) { | ||||||
|  |       return _then(_value.copyWith(attachment: value) as $Val); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /// Create a copy of SnSticker | ||||||
|  |   /// with the given fields replaced by the non-null parameter values. | ||||||
|  |   @override | ||||||
|  |   @pragma('vm:prefer-inline') | ||||||
|  |   $SnStickerPackCopyWith<$Res> get pack { | ||||||
|  |     return $SnStickerPackCopyWith<$Res>(_value.pack, (value) { | ||||||
|  |       return _then(_value.copyWith(pack: value) as $Val); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract class _$$SnStickerImplCopyWith<$Res> | ||||||
|  |     implements $SnStickerCopyWith<$Res> { | ||||||
|  |   factory _$$SnStickerImplCopyWith( | ||||||
|  |           _$SnStickerImpl value, $Res Function(_$SnStickerImpl) then) = | ||||||
|  |       __$$SnStickerImplCopyWithImpl<$Res>; | ||||||
|  |   @override | ||||||
|  |   @useResult | ||||||
|  |   $Res call( | ||||||
|  |       {int id, | ||||||
|  |       DateTime createdAt, | ||||||
|  |       DateTime updatedAt, | ||||||
|  |       DateTime? deletedAt, | ||||||
|  |       String alias, | ||||||
|  |       String name, | ||||||
|  |       int attachmentId, | ||||||
|  |       SnAttachment attachment, | ||||||
|  |       int packId, | ||||||
|  |       SnStickerPack pack, | ||||||
|  |       int accountId}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   $SnAttachmentCopyWith<$Res> get attachment; | ||||||
|  |   @override | ||||||
|  |   $SnStickerPackCopyWith<$Res> get pack; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | class __$$SnStickerImplCopyWithImpl<$Res> | ||||||
|  |     extends _$SnStickerCopyWithImpl<$Res, _$SnStickerImpl> | ||||||
|  |     implements _$$SnStickerImplCopyWith<$Res> { | ||||||
|  |   __$$SnStickerImplCopyWithImpl( | ||||||
|  |       _$SnStickerImpl _value, $Res Function(_$SnStickerImpl) _then) | ||||||
|  |       : super(_value, _then); | ||||||
|  |  | ||||||
|  |   /// Create a copy of SnSticker | ||||||
|  |   /// with the given fields replaced by the non-null parameter values. | ||||||
|  |   @pragma('vm:prefer-inline') | ||||||
|  |   @override | ||||||
|  |   $Res call({ | ||||||
|  |     Object? id = null, | ||||||
|  |     Object? createdAt = null, | ||||||
|  |     Object? updatedAt = null, | ||||||
|  |     Object? deletedAt = freezed, | ||||||
|  |     Object? alias = null, | ||||||
|  |     Object? name = null, | ||||||
|  |     Object? attachmentId = null, | ||||||
|  |     Object? attachment = null, | ||||||
|  |     Object? packId = null, | ||||||
|  |     Object? pack = null, | ||||||
|  |     Object? accountId = null, | ||||||
|  |   }) { | ||||||
|  |     return _then(_$SnStickerImpl( | ||||||
|  |       id: null == id | ||||||
|  |           ? _value.id | ||||||
|  |           : id // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as int, | ||||||
|  |       createdAt: null == createdAt | ||||||
|  |           ? _value.createdAt | ||||||
|  |           : createdAt // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as DateTime, | ||||||
|  |       updatedAt: null == updatedAt | ||||||
|  |           ? _value.updatedAt | ||||||
|  |           : updatedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as DateTime, | ||||||
|  |       deletedAt: freezed == deletedAt | ||||||
|  |           ? _value.deletedAt | ||||||
|  |           : deletedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as DateTime?, | ||||||
|  |       alias: null == alias | ||||||
|  |           ? _value.alias | ||||||
|  |           : alias // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as String, | ||||||
|  |       name: null == name | ||||||
|  |           ? _value.name | ||||||
|  |           : name // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as String, | ||||||
|  |       attachmentId: null == attachmentId | ||||||
|  |           ? _value.attachmentId | ||||||
|  |           : attachmentId // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as int, | ||||||
|  |       attachment: null == attachment | ||||||
|  |           ? _value.attachment | ||||||
|  |           : attachment // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as SnAttachment, | ||||||
|  |       packId: null == packId | ||||||
|  |           ? _value.packId | ||||||
|  |           : packId // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as int, | ||||||
|  |       pack: null == pack | ||||||
|  |           ? _value.pack | ||||||
|  |           : pack // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as SnStickerPack, | ||||||
|  |       accountId: null == accountId | ||||||
|  |           ? _value.accountId | ||||||
|  |           : accountId // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as int, | ||||||
|  |     )); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | @JsonSerializable() | ||||||
|  | class _$SnStickerImpl implements _SnSticker { | ||||||
|  |   const _$SnStickerImpl( | ||||||
|  |       {required this.id, | ||||||
|  |       required this.createdAt, | ||||||
|  |       required this.updatedAt, | ||||||
|  |       required this.deletedAt, | ||||||
|  |       required this.alias, | ||||||
|  |       required this.name, | ||||||
|  |       required this.attachmentId, | ||||||
|  |       required this.attachment, | ||||||
|  |       required this.packId, | ||||||
|  |       required this.pack, | ||||||
|  |       required this.accountId}); | ||||||
|  |  | ||||||
|  |   factory _$SnStickerImpl.fromJson(Map<String, dynamic> json) => | ||||||
|  |       _$$SnStickerImplFromJson(json); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   final int id; | ||||||
|  |   @override | ||||||
|  |   final DateTime createdAt; | ||||||
|  |   @override | ||||||
|  |   final DateTime updatedAt; | ||||||
|  |   @override | ||||||
|  |   final DateTime? deletedAt; | ||||||
|  |   @override | ||||||
|  |   final String alias; | ||||||
|  |   @override | ||||||
|  |   final String name; | ||||||
|  |   @override | ||||||
|  |   final int attachmentId; | ||||||
|  |   @override | ||||||
|  |   final SnAttachment attachment; | ||||||
|  |   @override | ||||||
|  |   final int packId; | ||||||
|  |   @override | ||||||
|  |   final SnStickerPack pack; | ||||||
|  |   @override | ||||||
|  |   final int accountId; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String toString() { | ||||||
|  |     return 'SnSticker(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, alias: $alias, name: $name, attachmentId: $attachmentId, attachment: $attachment, packId: $packId, pack: $pack, accountId: $accountId)'; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     return identical(this, other) || | ||||||
|  |         (other.runtimeType == runtimeType && | ||||||
|  |             other is _$SnStickerImpl && | ||||||
|  |             (identical(other.id, id) || other.id == id) && | ||||||
|  |             (identical(other.createdAt, createdAt) || | ||||||
|  |                 other.createdAt == createdAt) && | ||||||
|  |             (identical(other.updatedAt, updatedAt) || | ||||||
|  |                 other.updatedAt == updatedAt) && | ||||||
|  |             (identical(other.deletedAt, deletedAt) || | ||||||
|  |                 other.deletedAt == deletedAt) && | ||||||
|  |             (identical(other.alias, alias) || other.alias == alias) && | ||||||
|  |             (identical(other.name, name) || other.name == name) && | ||||||
|  |             (identical(other.attachmentId, attachmentId) || | ||||||
|  |                 other.attachmentId == attachmentId) && | ||||||
|  |             (identical(other.attachment, attachment) || | ||||||
|  |                 other.attachment == attachment) && | ||||||
|  |             (identical(other.packId, packId) || other.packId == packId) && | ||||||
|  |             (identical(other.pack, pack) || other.pack == pack) && | ||||||
|  |             (identical(other.accountId, accountId) || | ||||||
|  |                 other.accountId == accountId)); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  |   @override | ||||||
|  |   int get hashCode => Object.hash( | ||||||
|  |       runtimeType, | ||||||
|  |       id, | ||||||
|  |       createdAt, | ||||||
|  |       updatedAt, | ||||||
|  |       deletedAt, | ||||||
|  |       alias, | ||||||
|  |       name, | ||||||
|  |       attachmentId, | ||||||
|  |       attachment, | ||||||
|  |       packId, | ||||||
|  |       pack, | ||||||
|  |       accountId); | ||||||
|  |  | ||||||
|  |   /// Create a copy of SnSticker | ||||||
|  |   /// with the given fields replaced by the non-null parameter values. | ||||||
|  |   @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  |   @override | ||||||
|  |   @pragma('vm:prefer-inline') | ||||||
|  |   _$$SnStickerImplCopyWith<_$SnStickerImpl> get copyWith => | ||||||
|  |       __$$SnStickerImplCopyWithImpl<_$SnStickerImpl>(this, _$identity); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Map<String, dynamic> toJson() { | ||||||
|  |     return _$$SnStickerImplToJson( | ||||||
|  |       this, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | abstract class _SnSticker implements SnSticker { | ||||||
|  |   const factory _SnSticker( | ||||||
|  |       {required final int id, | ||||||
|  |       required final DateTime createdAt, | ||||||
|  |       required final DateTime updatedAt, | ||||||
|  |       required final DateTime? deletedAt, | ||||||
|  |       required final String alias, | ||||||
|  |       required final String name, | ||||||
|  |       required final int attachmentId, | ||||||
|  |       required final SnAttachment attachment, | ||||||
|  |       required final int packId, | ||||||
|  |       required final SnStickerPack pack, | ||||||
|  |       required final int accountId}) = _$SnStickerImpl; | ||||||
|  |  | ||||||
|  |   factory _SnSticker.fromJson(Map<String, dynamic> json) = | ||||||
|  |       _$SnStickerImpl.fromJson; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get id; | ||||||
|  |   @override | ||||||
|  |   DateTime get createdAt; | ||||||
|  |   @override | ||||||
|  |   DateTime get updatedAt; | ||||||
|  |   @override | ||||||
|  |   DateTime? get deletedAt; | ||||||
|  |   @override | ||||||
|  |   String get alias; | ||||||
|  |   @override | ||||||
|  |   String get name; | ||||||
|  |   @override | ||||||
|  |   int get attachmentId; | ||||||
|  |   @override | ||||||
|  |   SnAttachment get attachment; | ||||||
|  |   @override | ||||||
|  |   int get packId; | ||||||
|  |   @override | ||||||
|  |   SnStickerPack get pack; | ||||||
|  |   @override | ||||||
|  |   int get accountId; | ||||||
|  |  | ||||||
|  |   /// Create a copy of SnSticker | ||||||
|  |   /// with the given fields replaced by the non-null parameter values. | ||||||
|  |   @override | ||||||
|  |   @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  |   _$$SnStickerImplCopyWith<_$SnStickerImpl> get copyWith => | ||||||
|  |       throw _privateConstructorUsedError; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | SnStickerPack _$SnStickerPackFromJson(Map<String, dynamic> json) { | ||||||
|  |   return _SnStickerPack.fromJson(json); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | mixin _$SnStickerPack { | ||||||
|  |   int get id => throw _privateConstructorUsedError; | ||||||
|  |   DateTime get createdAt => throw _privateConstructorUsedError; | ||||||
|  |   DateTime get updatedAt => throw _privateConstructorUsedError; | ||||||
|  |   DateTime? get deletedAt => throw _privateConstructorUsedError; | ||||||
|  |   String get prefix => throw _privateConstructorUsedError; | ||||||
|  |   String get name => throw _privateConstructorUsedError; | ||||||
|  |   String get description => throw _privateConstructorUsedError; | ||||||
|  |   List<SnSticker>? get stickers => throw _privateConstructorUsedError; | ||||||
|  |   int get accountId => throw _privateConstructorUsedError; | ||||||
|  |  | ||||||
|  |   /// Serializes this SnStickerPack to a JSON map. | ||||||
|  |   Map<String, dynamic> toJson() => throw _privateConstructorUsedError; | ||||||
|  |  | ||||||
|  |   /// Create a copy of SnStickerPack | ||||||
|  |   /// with the given fields replaced by the non-null parameter values. | ||||||
|  |   @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  |   $SnStickerPackCopyWith<SnStickerPack> get copyWith => | ||||||
|  |       throw _privateConstructorUsedError; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract class $SnStickerPackCopyWith<$Res> { | ||||||
|  |   factory $SnStickerPackCopyWith( | ||||||
|  |           SnStickerPack value, $Res Function(SnStickerPack) then) = | ||||||
|  |       _$SnStickerPackCopyWithImpl<$Res, SnStickerPack>; | ||||||
|  |   @useResult | ||||||
|  |   $Res call( | ||||||
|  |       {int id, | ||||||
|  |       DateTime createdAt, | ||||||
|  |       DateTime updatedAt, | ||||||
|  |       DateTime? deletedAt, | ||||||
|  |       String prefix, | ||||||
|  |       String name, | ||||||
|  |       String description, | ||||||
|  |       List<SnSticker>? stickers, | ||||||
|  |       int accountId}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | class _$SnStickerPackCopyWithImpl<$Res, $Val extends SnStickerPack> | ||||||
|  |     implements $SnStickerPackCopyWith<$Res> { | ||||||
|  |   _$SnStickerPackCopyWithImpl(this._value, this._then); | ||||||
|  |  | ||||||
|  |   // ignore: unused_field | ||||||
|  |   final $Val _value; | ||||||
|  |   // ignore: unused_field | ||||||
|  |   final $Res Function($Val) _then; | ||||||
|  |  | ||||||
|  |   /// Create a copy of SnStickerPack | ||||||
|  |   /// with the given fields replaced by the non-null parameter values. | ||||||
|  |   @pragma('vm:prefer-inline') | ||||||
|  |   @override | ||||||
|  |   $Res call({ | ||||||
|  |     Object? id = null, | ||||||
|  |     Object? createdAt = null, | ||||||
|  |     Object? updatedAt = null, | ||||||
|  |     Object? deletedAt = freezed, | ||||||
|  |     Object? prefix = null, | ||||||
|  |     Object? name = null, | ||||||
|  |     Object? description = null, | ||||||
|  |     Object? stickers = freezed, | ||||||
|  |     Object? accountId = null, | ||||||
|  |   }) { | ||||||
|  |     return _then(_value.copyWith( | ||||||
|  |       id: null == id | ||||||
|  |           ? _value.id | ||||||
|  |           : id // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as int, | ||||||
|  |       createdAt: null == createdAt | ||||||
|  |           ? _value.createdAt | ||||||
|  |           : createdAt // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as DateTime, | ||||||
|  |       updatedAt: null == updatedAt | ||||||
|  |           ? _value.updatedAt | ||||||
|  |           : updatedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as DateTime, | ||||||
|  |       deletedAt: freezed == deletedAt | ||||||
|  |           ? _value.deletedAt | ||||||
|  |           : deletedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as DateTime?, | ||||||
|  |       prefix: null == prefix | ||||||
|  |           ? _value.prefix | ||||||
|  |           : prefix // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as String, | ||||||
|  |       name: null == name | ||||||
|  |           ? _value.name | ||||||
|  |           : name // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as String, | ||||||
|  |       description: null == description | ||||||
|  |           ? _value.description | ||||||
|  |           : description // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as String, | ||||||
|  |       stickers: freezed == stickers | ||||||
|  |           ? _value.stickers | ||||||
|  |           : stickers // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as List<SnSticker>?, | ||||||
|  |       accountId: null == accountId | ||||||
|  |           ? _value.accountId | ||||||
|  |           : accountId // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as int, | ||||||
|  |     ) as $Val); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract class _$$SnStickerPackImplCopyWith<$Res> | ||||||
|  |     implements $SnStickerPackCopyWith<$Res> { | ||||||
|  |   factory _$$SnStickerPackImplCopyWith( | ||||||
|  |           _$SnStickerPackImpl value, $Res Function(_$SnStickerPackImpl) then) = | ||||||
|  |       __$$SnStickerPackImplCopyWithImpl<$Res>; | ||||||
|  |   @override | ||||||
|  |   @useResult | ||||||
|  |   $Res call( | ||||||
|  |       {int id, | ||||||
|  |       DateTime createdAt, | ||||||
|  |       DateTime updatedAt, | ||||||
|  |       DateTime? deletedAt, | ||||||
|  |       String prefix, | ||||||
|  |       String name, | ||||||
|  |       String description, | ||||||
|  |       List<SnSticker>? stickers, | ||||||
|  |       int accountId}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | class __$$SnStickerPackImplCopyWithImpl<$Res> | ||||||
|  |     extends _$SnStickerPackCopyWithImpl<$Res, _$SnStickerPackImpl> | ||||||
|  |     implements _$$SnStickerPackImplCopyWith<$Res> { | ||||||
|  |   __$$SnStickerPackImplCopyWithImpl( | ||||||
|  |       _$SnStickerPackImpl _value, $Res Function(_$SnStickerPackImpl) _then) | ||||||
|  |       : super(_value, _then); | ||||||
|  |  | ||||||
|  |   /// Create a copy of SnStickerPack | ||||||
|  |   /// with the given fields replaced by the non-null parameter values. | ||||||
|  |   @pragma('vm:prefer-inline') | ||||||
|  |   @override | ||||||
|  |   $Res call({ | ||||||
|  |     Object? id = null, | ||||||
|  |     Object? createdAt = null, | ||||||
|  |     Object? updatedAt = null, | ||||||
|  |     Object? deletedAt = freezed, | ||||||
|  |     Object? prefix = null, | ||||||
|  |     Object? name = null, | ||||||
|  |     Object? description = null, | ||||||
|  |     Object? stickers = freezed, | ||||||
|  |     Object? accountId = null, | ||||||
|  |   }) { | ||||||
|  |     return _then(_$SnStickerPackImpl( | ||||||
|  |       id: null == id | ||||||
|  |           ? _value.id | ||||||
|  |           : id // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as int, | ||||||
|  |       createdAt: null == createdAt | ||||||
|  |           ? _value.createdAt | ||||||
|  |           : createdAt // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as DateTime, | ||||||
|  |       updatedAt: null == updatedAt | ||||||
|  |           ? _value.updatedAt | ||||||
|  |           : updatedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as DateTime, | ||||||
|  |       deletedAt: freezed == deletedAt | ||||||
|  |           ? _value.deletedAt | ||||||
|  |           : deletedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as DateTime?, | ||||||
|  |       prefix: null == prefix | ||||||
|  |           ? _value.prefix | ||||||
|  |           : prefix // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as String, | ||||||
|  |       name: null == name | ||||||
|  |           ? _value.name | ||||||
|  |           : name // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as String, | ||||||
|  |       description: null == description | ||||||
|  |           ? _value.description | ||||||
|  |           : description // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as String, | ||||||
|  |       stickers: freezed == stickers | ||||||
|  |           ? _value._stickers | ||||||
|  |           : stickers // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as List<SnSticker>?, | ||||||
|  |       accountId: null == accountId | ||||||
|  |           ? _value.accountId | ||||||
|  |           : accountId // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as int, | ||||||
|  |     )); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | @JsonSerializable() | ||||||
|  | class _$SnStickerPackImpl implements _SnStickerPack { | ||||||
|  |   const _$SnStickerPackImpl( | ||||||
|  |       {required this.id, | ||||||
|  |       required this.createdAt, | ||||||
|  |       required this.updatedAt, | ||||||
|  |       required this.deletedAt, | ||||||
|  |       required this.prefix, | ||||||
|  |       required this.name, | ||||||
|  |       required this.description, | ||||||
|  |       required final List<SnSticker>? stickers, | ||||||
|  |       required this.accountId}) | ||||||
|  |       : _stickers = stickers; | ||||||
|  |  | ||||||
|  |   factory _$SnStickerPackImpl.fromJson(Map<String, dynamic> json) => | ||||||
|  |       _$$SnStickerPackImplFromJson(json); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   final int id; | ||||||
|  |   @override | ||||||
|  |   final DateTime createdAt; | ||||||
|  |   @override | ||||||
|  |   final DateTime updatedAt; | ||||||
|  |   @override | ||||||
|  |   final DateTime? deletedAt; | ||||||
|  |   @override | ||||||
|  |   final String prefix; | ||||||
|  |   @override | ||||||
|  |   final String name; | ||||||
|  |   @override | ||||||
|  |   final String description; | ||||||
|  |   final List<SnSticker>? _stickers; | ||||||
|  |   @override | ||||||
|  |   List<SnSticker>? get stickers { | ||||||
|  |     final value = _stickers; | ||||||
|  |     if (value == null) return null; | ||||||
|  |     if (_stickers is EqualUnmodifiableListView) return _stickers; | ||||||
|  |     // ignore: implicit_dynamic_type | ||||||
|  |     return EqualUnmodifiableListView(value); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   final int accountId; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String toString() { | ||||||
|  |     return 'SnStickerPack(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, prefix: $prefix, name: $name, description: $description, stickers: $stickers, accountId: $accountId)'; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     return identical(this, other) || | ||||||
|  |         (other.runtimeType == runtimeType && | ||||||
|  |             other is _$SnStickerPackImpl && | ||||||
|  |             (identical(other.id, id) || other.id == id) && | ||||||
|  |             (identical(other.createdAt, createdAt) || | ||||||
|  |                 other.createdAt == createdAt) && | ||||||
|  |             (identical(other.updatedAt, updatedAt) || | ||||||
|  |                 other.updatedAt == updatedAt) && | ||||||
|  |             (identical(other.deletedAt, deletedAt) || | ||||||
|  |                 other.deletedAt == deletedAt) && | ||||||
|  |             (identical(other.prefix, prefix) || other.prefix == prefix) && | ||||||
|  |             (identical(other.name, name) || other.name == name) && | ||||||
|  |             (identical(other.description, description) || | ||||||
|  |                 other.description == description) && | ||||||
|  |             const DeepCollectionEquality().equals(other._stickers, _stickers) && | ||||||
|  |             (identical(other.accountId, accountId) || | ||||||
|  |                 other.accountId == accountId)); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  |   @override | ||||||
|  |   int get hashCode => Object.hash( | ||||||
|  |       runtimeType, | ||||||
|  |       id, | ||||||
|  |       createdAt, | ||||||
|  |       updatedAt, | ||||||
|  |       deletedAt, | ||||||
|  |       prefix, | ||||||
|  |       name, | ||||||
|  |       description, | ||||||
|  |       const DeepCollectionEquality().hash(_stickers), | ||||||
|  |       accountId); | ||||||
|  |  | ||||||
|  |   /// Create a copy of SnStickerPack | ||||||
|  |   /// with the given fields replaced by the non-null parameter values. | ||||||
|  |   @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  |   @override | ||||||
|  |   @pragma('vm:prefer-inline') | ||||||
|  |   _$$SnStickerPackImplCopyWith<_$SnStickerPackImpl> get copyWith => | ||||||
|  |       __$$SnStickerPackImplCopyWithImpl<_$SnStickerPackImpl>(this, _$identity); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Map<String, dynamic> toJson() { | ||||||
|  |     return _$$SnStickerPackImplToJson( | ||||||
|  |       this, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | abstract class _SnStickerPack implements SnStickerPack { | ||||||
|  |   const factory _SnStickerPack( | ||||||
|  |       {required final int id, | ||||||
|  |       required final DateTime createdAt, | ||||||
|  |       required final DateTime updatedAt, | ||||||
|  |       required final DateTime? deletedAt, | ||||||
|  |       required final String prefix, | ||||||
|  |       required final String name, | ||||||
|  |       required final String description, | ||||||
|  |       required final List<SnSticker>? stickers, | ||||||
|  |       required final int accountId}) = _$SnStickerPackImpl; | ||||||
|  |  | ||||||
|  |   factory _SnStickerPack.fromJson(Map<String, dynamic> json) = | ||||||
|  |       _$SnStickerPackImpl.fromJson; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get id; | ||||||
|  |   @override | ||||||
|  |   DateTime get createdAt; | ||||||
|  |   @override | ||||||
|  |   DateTime get updatedAt; | ||||||
|  |   @override | ||||||
|  |   DateTime? get deletedAt; | ||||||
|  |   @override | ||||||
|  |   String get prefix; | ||||||
|  |   @override | ||||||
|  |   String get name; | ||||||
|  |   @override | ||||||
|  |   String get description; | ||||||
|  |   @override | ||||||
|  |   List<SnSticker>? get stickers; | ||||||
|  |   @override | ||||||
|  |   int get accountId; | ||||||
|  |  | ||||||
|  |   /// Create a copy of SnStickerPack | ||||||
|  |   /// with the given fields replaced by the non-null parameter values. | ||||||
|  |   @override | ||||||
|  |   @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  |   _$$SnStickerPackImplCopyWith<_$SnStickerPackImpl> get copyWith => | ||||||
|  |       throw _privateConstructorUsedError; | ||||||
|  | } | ||||||
|   | |||||||
| @@ -218,3 +218,66 @@ Map<String, dynamic> _$$SnAttachmentBoostImplToJson( | |||||||
|       'attachment': instance.attachment.toJson(), |       'attachment': instance.attachment.toJson(), | ||||||
|       'account': instance.account, |       'account': instance.account, | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|  | _$SnStickerImpl _$$SnStickerImplFromJson(Map<String, dynamic> json) => | ||||||
|  |     _$SnStickerImpl( | ||||||
|  |       id: (json['id'] as num).toInt(), | ||||||
|  |       createdAt: DateTime.parse(json['created_at'] as String), | ||||||
|  |       updatedAt: DateTime.parse(json['updated_at'] as String), | ||||||
|  |       deletedAt: json['deleted_at'] == null | ||||||
|  |           ? null | ||||||
|  |           : DateTime.parse(json['deleted_at'] as String), | ||||||
|  |       alias: json['alias'] as String, | ||||||
|  |       name: json['name'] as String, | ||||||
|  |       attachmentId: (json['attachment_id'] as num).toInt(), | ||||||
|  |       attachment: | ||||||
|  |           SnAttachment.fromJson(json['attachment'] as Map<String, dynamic>), | ||||||
|  |       packId: (json['pack_id'] as num).toInt(), | ||||||
|  |       pack: SnStickerPack.fromJson(json['pack'] as Map<String, dynamic>), | ||||||
|  |       accountId: (json['account_id'] as num).toInt(), | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  | Map<String, dynamic> _$$SnStickerImplToJson(_$SnStickerImpl instance) => | ||||||
|  |     <String, dynamic>{ | ||||||
|  |       'id': instance.id, | ||||||
|  |       'created_at': instance.createdAt.toIso8601String(), | ||||||
|  |       'updated_at': instance.updatedAt.toIso8601String(), | ||||||
|  |       'deleted_at': instance.deletedAt?.toIso8601String(), | ||||||
|  |       'alias': instance.alias, | ||||||
|  |       'name': instance.name, | ||||||
|  |       'attachment_id': instance.attachmentId, | ||||||
|  |       'attachment': instance.attachment.toJson(), | ||||||
|  |       'pack_id': instance.packId, | ||||||
|  |       'pack': instance.pack.toJson(), | ||||||
|  |       'account_id': instance.accountId, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  | _$SnStickerPackImpl _$$SnStickerPackImplFromJson(Map<String, dynamic> json) => | ||||||
|  |     _$SnStickerPackImpl( | ||||||
|  |       id: (json['id'] as num).toInt(), | ||||||
|  |       createdAt: DateTime.parse(json['created_at'] as String), | ||||||
|  |       updatedAt: DateTime.parse(json['updated_at'] as String), | ||||||
|  |       deletedAt: json['deleted_at'] == null | ||||||
|  |           ? null | ||||||
|  |           : DateTime.parse(json['deleted_at'] as String), | ||||||
|  |       prefix: json['prefix'] as String, | ||||||
|  |       name: json['name'] as String, | ||||||
|  |       description: json['description'] as String, | ||||||
|  |       stickers: (json['stickers'] as List<dynamic>?) | ||||||
|  |           ?.map((e) => SnSticker.fromJson(e as Map<String, dynamic>)) | ||||||
|  |           .toList(), | ||||||
|  |       accountId: (json['account_id'] as num).toInt(), | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  | Map<String, dynamic> _$$SnStickerPackImplToJson(_$SnStickerPackImpl instance) => | ||||||
|  |     <String, dynamic>{ | ||||||
|  |       'id': instance.id, | ||||||
|  |       'created_at': instance.createdAt.toIso8601String(), | ||||||
|  |       'updated_at': instance.updatedAt.toIso8601String(), | ||||||
|  |       'deleted_at': instance.deletedAt?.toIso8601String(), | ||||||
|  |       'prefix': instance.prefix, | ||||||
|  |       'name': instance.name, | ||||||
|  |       'description': instance.description, | ||||||
|  |       'stickers': instance.stickers?.map((e) => e.toJson()).toList(), | ||||||
|  |       'account_id': instance.accountId, | ||||||
|  |     }; | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; | |||||||
| import 'package:gap/gap.dart'; | import 'package:gap/gap.dart'; | ||||||
| import 'package:package_info_plus/package_info_plus.dart'; | import 'package:package_info_plus/package_info_plus.dart'; | ||||||
| import 'package:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  | import 'package:surface/widgets/navigation/app_scaffold.dart'; | ||||||
| import 'package:url_launcher/url_launcher_string.dart'; | import 'package:url_launcher/url_launcher_string.dart'; | ||||||
|  |  | ||||||
| class AboutScreen extends StatelessWidget { | class AboutScreen extends StatelessWidget { | ||||||
| @@ -12,7 +13,12 @@ class AboutScreen extends StatelessWidget { | |||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     const denseButtonStyle = ButtonStyle(visualDensity: VisualDensity(vertical: -4)); |     const denseButtonStyle = ButtonStyle(visualDensity: VisualDensity(vertical: -4)); | ||||||
|  |  | ||||||
|     return SizedBox( |     return AppScaffold( | ||||||
|  |       appBar: AppBar( | ||||||
|  |         leading: const PageBackButton(), | ||||||
|  |         title: Text('screenAbout').tr(), | ||||||
|  |       ), | ||||||
|  |       body: SizedBox( | ||||||
|         width: double.infinity, |         width: double.infinity, | ||||||
|         child: Column( |         child: Column( | ||||||
|           mainAxisAlignment: MainAxisAlignment.center, |           mainAxisAlignment: MainAxisAlignment.center, | ||||||
| @@ -104,6 +110,7 @@ class AboutScreen extends StatelessWidget { | |||||||
|             ), |             ), | ||||||
|           ], |           ], | ||||||
|         ), |         ), | ||||||
|  |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										164
									
								
								lib/widgets/account/account_popover.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										164
									
								
								lib/widgets/account/account_popover.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,164 @@ | |||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:gap/gap.dart'; | ||||||
|  | import 'package:go_router/go_router.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:provider/provider.dart'; | ||||||
|  | import 'package:relative_time/relative_time.dart'; | ||||||
|  | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  | import 'package:surface/providers/experience.dart'; | ||||||
|  | import 'package:surface/providers/sn_network.dart'; | ||||||
|  | import 'package:surface/screens/account/profile_page.dart'; | ||||||
|  | import 'package:surface/types/account.dart'; | ||||||
|  | import 'package:surface/widgets/account/account_image.dart'; | ||||||
|  | import 'package:surface/widgets/universal_image.dart'; | ||||||
|  |  | ||||||
|  | class AccountPopoverCard extends StatelessWidget { | ||||||
|  |   final SnAccount data; | ||||||
|  |  | ||||||
|  |   const AccountPopoverCard({super.key, required this.data}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     final sn = context.read<SnNetworkProvider>(); | ||||||
|  |  | ||||||
|  |     return Column( | ||||||
|  |       crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |       mainAxisSize: MainAxisSize.min, | ||||||
|  |       children: [ | ||||||
|  |         if (data.banner.isNotEmpty) | ||||||
|  |           Container( | ||||||
|  |             color: Theme.of(context).colorScheme.surfaceContainer, | ||||||
|  |             child: AspectRatio( | ||||||
|  |               aspectRatio: 16 / 7, | ||||||
|  |               child: AutoResizeUniversalImage( | ||||||
|  |                 sn.getAttachmentUrl(data.banner), | ||||||
|  |                 fit: BoxFit.cover, | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |         // Top padding | ||||||
|  |         Gap(16), | ||||||
|  |         Row( | ||||||
|  |           crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |           children: [ | ||||||
|  |             AccountImage( | ||||||
|  |               content: data.avatar, | ||||||
|  |               radius: 20, | ||||||
|  |             ), | ||||||
|  |             Gap(16), | ||||||
|  |             Expanded( | ||||||
|  |               child: Column( | ||||||
|  |                 crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |                 mainAxisSize: MainAxisSize.min, | ||||||
|  |                 children: [ | ||||||
|  |                   Text(data.nick).bold(), | ||||||
|  |                   Text('@${data.name}').fontSize(13).opacity(0.75), | ||||||
|  |                 ], | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |             IconButton( | ||||||
|  |               onPressed: () { | ||||||
|  |                 Navigator.pop(context); | ||||||
|  |                 GoRouter.of(context).pushNamed( | ||||||
|  |                   'accountProfilePage', | ||||||
|  |                   pathParameters: {'name': data.name}, | ||||||
|  |                 ); | ||||||
|  |               }, | ||||||
|  |               icon: const Icon(Symbols.chevron_right), | ||||||
|  |               padding: EdgeInsets.zero, | ||||||
|  |               visualDensity: const VisualDensity(horizontal: -4, vertical: -4), | ||||||
|  |             ), | ||||||
|  |             const Gap(8) | ||||||
|  |           ], | ||||||
|  |         ).padding(horizontal: 16), | ||||||
|  |         const Gap(16), | ||||||
|  |         Wrap( | ||||||
|  |           children: data.badges | ||||||
|  |               .map( | ||||||
|  |                 (ele) => Tooltip( | ||||||
|  |               richMessage: TextSpan( | ||||||
|  |                 children: [ | ||||||
|  |                   TextSpan(text: kBadgesMeta[ele.type]?.$1.tr() ?? 'unknown'.tr()), | ||||||
|  |                   if (ele.metadata['title'] != null) | ||||||
|  |                     TextSpan( | ||||||
|  |                       text: '\n${ele.metadata['title']}', | ||||||
|  |                       style: const TextStyle(fontWeight: FontWeight.bold), | ||||||
|  |                     ), | ||||||
|  |                   TextSpan(text: '\n'), | ||||||
|  |                   TextSpan( | ||||||
|  |                     text: DateFormat.yMEd().format(ele.createdAt), | ||||||
|  |                   ), | ||||||
|  |                 ], | ||||||
|  |               ), | ||||||
|  |               child: Icon( | ||||||
|  |                 kBadgesMeta[ele.type]?.$2 ?? Symbols.question_mark, | ||||||
|  |                 color: kBadgesMeta[ele.type]?.$3, | ||||||
|  |                 fill: 1, | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |           ) | ||||||
|  |               .toList(), | ||||||
|  |         ).padding(horizontal: 24), | ||||||
|  |         const Gap(8), | ||||||
|  |         Row( | ||||||
|  |           crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|  |           children: [ | ||||||
|  |             const Icon(Symbols.star), | ||||||
|  |             const Gap(8), | ||||||
|  |             Text('Lv${getLevelFromExp(data.profile?.experience ?? 0)}'), | ||||||
|  |             const Gap(8), | ||||||
|  |             Text(calcLevelUpProgressLevel(data.profile?.experience ?? 0)).fontSize(11).opacity(0.5), | ||||||
|  |             const Gap(8), | ||||||
|  |             Container( | ||||||
|  |               width: double.infinity, | ||||||
|  |               constraints: const BoxConstraints(maxWidth: 160), | ||||||
|  |               child: LinearProgressIndicator( | ||||||
|  |                 value: calcLevelUpProgress(data.profile?.experience ?? 0), | ||||||
|  |                 borderRadius: BorderRadius.circular(8), | ||||||
|  |                 backgroundColor: Theme.of(context).colorScheme.surfaceContainer, | ||||||
|  |               ).alignment(Alignment.centerLeft), | ||||||
|  |             ), | ||||||
|  |           ], | ||||||
|  |         ).padding(horizontal: 24), | ||||||
|  |         FutureBuilder( | ||||||
|  |           future: sn.client.get('/cgi/id/users/${data.name}/status'), | ||||||
|  |           builder: (context, snapshot) { | ||||||
|  |             final SnAccountStatusInfo? status = | ||||||
|  |                 snapshot.hasData ? SnAccountStatusInfo.fromJson(snapshot.data!.data) : null; | ||||||
|  |             return Row( | ||||||
|  |                 children: [ | ||||||
|  |                   Icon( | ||||||
|  |                     Symbols.circle, | ||||||
|  |                     fill: 1, | ||||||
|  |                     size: 16, | ||||||
|  |                     color: (status?.isOnline ?? false) ? Colors.green : Colors.grey, | ||||||
|  |                   ).padding(all: 4), | ||||||
|  |                   const Gap(8), | ||||||
|  |                   Text( | ||||||
|  |                     status != null | ||||||
|  |                         ? status.isOnline | ||||||
|  |                             ? 'accountStatusOnline'.tr() | ||||||
|  |                             : 'accountStatusOffline'.tr() | ||||||
|  |                         : 'loading'.tr(), | ||||||
|  |                   ), | ||||||
|  |                   if (status != null && !status.isOnline && status.lastSeenAt != null) | ||||||
|  |                     Text( | ||||||
|  |                       'accountStatusLastSeen'.tr(args: [ | ||||||
|  |                         status.lastSeenAt != null | ||||||
|  |                             ? RelativeTime(context).format( | ||||||
|  |                                 status.lastSeenAt!.toLocal(), | ||||||
|  |                               ) | ||||||
|  |                             : 'unknown', | ||||||
|  |                       ]), | ||||||
|  |                     ).padding(left: 6).opacity(0.75), | ||||||
|  |                 ], | ||||||
|  |               ).padding(horizontal: 24); | ||||||
|  |           }, | ||||||
|  |         ), | ||||||
|  |         // Bottom padding | ||||||
|  |         const Gap(16), | ||||||
|  |       ], | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -315,6 +315,7 @@ class _AttachmentItemContentVideoState extends State<_AttachmentItemContentVideo | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     return MaterialDesktopVideoControlsTheme( |     return MaterialDesktopVideoControlsTheme( | ||||||
|  |       key: Key('material-desktop-video-controls-theme-$_showOriginal'), | ||||||
|       normal: MaterialDesktopVideoControlsThemeData( |       normal: MaterialDesktopVideoControlsThemeData( | ||||||
|         buttonBarButtonSize: 24, |         buttonBarButtonSize: 24, | ||||||
|         buttonBarButtonColor: Colors.white, |         buttonBarButtonColor: Colors.white, | ||||||
| @@ -324,14 +325,16 @@ class _AttachmentItemContentVideoState extends State<_AttachmentItemContentVideo | |||||||
|           MaterialDesktopCustomButton( |           MaterialDesktopCustomButton( | ||||||
|             iconSize: 24, |             iconSize: 24, | ||||||
|             onPressed: _toggleOriginal, |             onPressed: _toggleOriginal, | ||||||
|             icon: Builder(builder: (context) { |             icon: Icon( | ||||||
|               return _showOriginal ? const Icon(Symbols.high_quality, size: 24) : const Icon(Symbols.sd, size: 24); |               _showOriginal ? Symbols.high_quality : Symbols.sd, | ||||||
|             }), |               size: 24, | ||||||
|  |             ), | ||||||
|           ), |           ), | ||||||
|         ], |         ], | ||||||
|       ), |       ), | ||||||
|       fullscreen: const MaterialDesktopVideoControlsThemeData(), |       fullscreen: const MaterialDesktopVideoControlsThemeData(), | ||||||
|       child: MaterialVideoControlsTheme( |       child: MaterialVideoControlsTheme( | ||||||
|  |         key: Key('material-video-controls-theme-$_showOriginal'), | ||||||
|         normal: MaterialVideoControlsThemeData( |         normal: MaterialVideoControlsThemeData( | ||||||
|           buttonBarButtonSize: 24, |           buttonBarButtonSize: 24, | ||||||
|           buttonBarButtonColor: Colors.white, |           buttonBarButtonColor: Colors.white, | ||||||
|   | |||||||
| @@ -15,10 +15,11 @@ class AttachmentList extends StatefulWidget { | |||||||
|   final List<SnAttachment?> data; |   final List<SnAttachment?> data; | ||||||
|   final bool bordered; |   final bool bordered; | ||||||
|   final bool gridded; |   final bool gridded; | ||||||
|   final bool noGrow; |   final bool columned; | ||||||
|   final BoxFit fit; |   final BoxFit fit; | ||||||
|   final double? maxHeight; |   final double? maxHeight; | ||||||
|   final double? minWidth; |   final double? minWidth; | ||||||
|  |   final double? maxWidth; | ||||||
|   final EdgeInsets? padding; |   final EdgeInsets? padding; | ||||||
|  |  | ||||||
|   const AttachmentList({ |   const AttachmentList({ | ||||||
| @@ -26,10 +27,11 @@ class AttachmentList extends StatefulWidget { | |||||||
|     required this.data, |     required this.data, | ||||||
|     this.bordered = false, |     this.bordered = false, | ||||||
|     this.gridded = false, |     this.gridded = false, | ||||||
|     this.noGrow = false, |     this.columned = false, | ||||||
|     this.fit = BoxFit.cover, |     this.fit = BoxFit.cover, | ||||||
|     this.maxHeight, |     this.maxHeight, | ||||||
|     this.minWidth, |     this.minWidth, | ||||||
|  |     this.maxWidth, | ||||||
|     this.padding, |     this.padding, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
| @@ -105,10 +107,12 @@ class _AttachmentListState extends State<AttachmentList> { | |||||||
|           ); |           ); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (widget.gridded) { |         final fullOfImage = | ||||||
|           return Padding( |             widget.data.where((ele) => ele?.mediaType == SnMediaType.image).length == widget.data.length; | ||||||
|             padding: widget.padding ?? EdgeInsets.zero, |  | ||||||
|             child: Container( |         if (widget.gridded && fullOfImage) { | ||||||
|  |           return Container( | ||||||
|  |             margin: widget.padding ?? EdgeInsets.zero, | ||||||
|             decoration: BoxDecoration( |             decoration: BoxDecoration( | ||||||
|               color: backgroundColor, |               color: backgroundColor, | ||||||
|               border: Border( |               border: Border( | ||||||
| @@ -151,22 +155,58 @@ class _AttachmentListState extends State<AttachmentList> { | |||||||
|                     .toList(), |                     .toList(), | ||||||
|               ), |               ), | ||||||
|             ), |             ), | ||||||
|  |           ); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if ((!fullOfImage && widget.gridded) || widget.columned) { | ||||||
|  |           return Container( | ||||||
|  |             margin: widget.padding ?? EdgeInsets.zero, | ||||||
|  |             decoration: BoxDecoration( | ||||||
|  |               color: backgroundColor, | ||||||
|  |               border: Border( | ||||||
|  |                 top: borderSide, | ||||||
|  |                 bottom: borderSide, | ||||||
|  |               ), | ||||||
|  |               borderRadius: AttachmentList.kDefaultRadius, | ||||||
|  |             ), | ||||||
|  |             child: ClipRRect( | ||||||
|  |               borderRadius: AttachmentList.kDefaultRadius, | ||||||
|  |               child: Column( | ||||||
|  |                 children: widget.data | ||||||
|  |                     .mapIndexed( | ||||||
|  |                       (idx, ele) => GestureDetector( | ||||||
|  |                         child: AspectRatio( | ||||||
|  |                           aspectRatio: ele?.data['ratio']?.toDouble() ?? 1, | ||||||
|  |                           child: Container( | ||||||
|  |                             constraints: constraints, | ||||||
|  |                             child: AttachmentItem( | ||||||
|  |                               data: ele, | ||||||
|  |                               heroTag: heroTags[idx], | ||||||
|  |                               fit: BoxFit.cover, | ||||||
|  |                             ), | ||||||
|  |                           ), | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|  |                     ) | ||||||
|  |                     .expand((ele) => [ele, const Divider(height: 1)]) | ||||||
|  |                     .toList() | ||||||
|  |                   ..removeLast(), | ||||||
|  |               ), | ||||||
|             ), |             ), | ||||||
|           ); |           ); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return AspectRatio( |         return Container( | ||||||
|           aspectRatio: (widget.data.firstOrNull?.data['ratio'] ?? 1).toDouble(), |  | ||||||
|           child: Container( |  | ||||||
|           constraints: BoxConstraints(maxHeight: constraints.maxHeight), |           constraints: BoxConstraints(maxHeight: constraints.maxHeight), | ||||||
|           child: ScrollConfiguration( |           child: ScrollConfiguration( | ||||||
|             behavior: _AttachmentListScrollBehavior(), |             behavior: _AttachmentListScrollBehavior(), | ||||||
|             child: ListView.separated( |             child: ListView.separated( | ||||||
|  |               padding: widget.padding, | ||||||
|               shrinkWrap: true, |               shrinkWrap: true, | ||||||
|               itemCount: widget.data.length, |               itemCount: widget.data.length, | ||||||
|               itemBuilder: (context, idx) { |               itemBuilder: (context, idx) { | ||||||
|                 return Container( |                 return Container( | ||||||
|                     constraints: constraints, |                   constraints: constraints.copyWith(maxWidth: widget.maxWidth), | ||||||
|                   child: AspectRatio( |                   child: AspectRatio( | ||||||
|                     aspectRatio: (widget.data[idx]?.data['ratio'] ?? 1).toDouble(), |                     aspectRatio: (widget.data[idx]?.data['ratio'] ?? 1).toDouble(), | ||||||
|                     child: GestureDetector( |                     child: GestureDetector( | ||||||
| @@ -174,8 +214,7 @@ class _AttachmentListState extends State<AttachmentList> { | |||||||
|                         if (widget.data[idx]?.mediaType != SnMediaType.image) return; |                         if (widget.data[idx]?.mediaType != SnMediaType.image) return; | ||||||
|                         context.pushTransparentRoute( |                         context.pushTransparentRoute( | ||||||
|                           AttachmentZoomView( |                           AttachmentZoomView( | ||||||
|                               data: |                             data: widget.data.where((ele) => ele != null && ele.mediaType == SnMediaType.image).cast(), | ||||||
|                                   widget.data.where((ele) => ele != null && ele.mediaType == SnMediaType.image).cast(), |  | ||||||
|                             initialIndex: idx, |                             initialIndex: idx, | ||||||
|                             heroTags: heroTags, |                             heroTags: heroTags, | ||||||
|                           ), |                           ), | ||||||
| @@ -217,12 +256,10 @@ class _AttachmentListState extends State<AttachmentList> { | |||||||
|                 ); |                 ); | ||||||
|               }, |               }, | ||||||
|               separatorBuilder: (context, index) => const Gap(8), |               separatorBuilder: (context, index) => const Gap(8), | ||||||
|                 padding: widget.padding, |  | ||||||
|               physics: const BouncingScrollPhysics(), |               physics: const BouncingScrollPhysics(), | ||||||
|               scrollDirection: Axis.horizontal, |               scrollDirection: Axis.horizontal, | ||||||
|             ), |             ), | ||||||
|           ), |           ), | ||||||
|           ), |  | ||||||
|         ); |         ); | ||||||
|       }, |       }, | ||||||
|     ); |     ); | ||||||
|   | |||||||
| @@ -129,6 +129,8 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> { | |||||||
|  |  | ||||||
|   Color get _unFocusColor => Theme.of(context).colorScheme.onSurface.withOpacity(0.75); |   Color get _unFocusColor => Theme.of(context).colorScheme.onSurface.withOpacity(0.75); | ||||||
|  |  | ||||||
|  |   bool _showDetail = false; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     final sn = context.read<SnNetworkProvider>(); |     final sn = context.read<SnNetworkProvider>(); | ||||||
| @@ -144,9 +146,11 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> { | |||||||
|       onDismissed: () { |       onDismissed: () { | ||||||
|         Navigator.of(context).pop(); |         Navigator.of(context).pop(); | ||||||
|       }, |       }, | ||||||
|       direction: DismissiblePageDismissDirection.down, |       direction: DismissiblePageDismissDirection.none, | ||||||
|       backgroundColor: Colors.transparent, |       backgroundColor: Colors.transparent, | ||||||
|       isFullScreen: true, |       isFullScreen: true, | ||||||
|  |       child: GestureDetector( | ||||||
|  |         behavior: HitTestBehavior.translucent, | ||||||
|         child: Scaffold( |         child: Scaffold( | ||||||
|           body: Stack( |           body: Stack( | ||||||
|             children: [ |             children: [ | ||||||
| @@ -231,7 +235,7 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> { | |||||||
|                             children: [ |                             children: [ | ||||||
|                               IgnorePointer( |                               IgnorePointer( | ||||||
|                                 child: AccountImage( |                                 child: AccountImage( | ||||||
|                                 content: account!.avatar, |                                   content: account?.avatar, | ||||||
|                                   radius: 19, |                                   radius: 19, | ||||||
|                                 ), |                                 ), | ||||||
|                               ), |                               ), | ||||||
| @@ -246,7 +250,7 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> { | |||||||
|                                         style: Theme.of(context).textTheme.bodySmall, |                                         style: Theme.of(context).textTheme.bodySmall, | ||||||
|                                       ), |                                       ), | ||||||
|                                       Text( |                                       Text( | ||||||
|                                       account.nick, |                                         account?.nick ?? 'unknown'.tr(), | ||||||
|                                         style: Theme.of(context).textTheme.bodyMedium, |                                         style: Theme.of(context).textTheme.bodyMedium, | ||||||
|                                       ), |                                       ), | ||||||
|                                     ], |                                     ], | ||||||
| @@ -264,7 +268,8 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> { | |||||||
|                                 borderRadius: const BorderRadius.all(Radius.circular(16)), |                                 borderRadius: const BorderRadius.all(Radius.circular(16)), | ||||||
|                                 onTap: _isDownloading |                                 onTap: _isDownloading | ||||||
|                                     ? null |                                     ? null | ||||||
|                                   : () => _saveToAlbum(widget.data.length > 1 ? _pageController.page?.round() ?? 0 : 0), |                                     : () => | ||||||
|  |                                         _saveToAlbum(widget.data.length > 1 ? _pageController.page?.round() ?? 0 : 0), | ||||||
|                                 child: Container( |                                 child: Container( | ||||||
|                                   padding: const EdgeInsets.all(6), |                                   padding: const EdgeInsets.all(6), | ||||||
|                                   child: !_isDownloading |                                   child: !_isDownloading | ||||||
| @@ -312,11 +317,6 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> { | |||||||
|                                   ]), |                                   ]), | ||||||
|                                   style: metaTextStyle, |                                   style: metaTextStyle, | ||||||
|                                 ).padding(right: 2), |                                 ).padding(right: 2), | ||||||
|                             if (item.metadata['exif']?['ShutterSpeed'] != null) |  | ||||||
|                               Text( |  | ||||||
|                                 item.metadata['exif']?['ShutterSpeed'], |  | ||||||
|                                 style: metaTextStyle, |  | ||||||
|                               ).padding(right: 2), |  | ||||||
|                               if (item.metadata['exif']?['ISO'] != null) |                               if (item.metadata['exif']?['ISO'] != null) | ||||||
|                                 Text( |                                 Text( | ||||||
|                                   'ISO${item.metadata['exif']?['ISO']}', |                                   'ISO${item.metadata['exif']?['ISO']}', | ||||||
| @@ -327,14 +327,15 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> { | |||||||
|                                   'f/${item.metadata['exif']?['Aperture']}', |                                   'f/${item.metadata['exif']?['Aperture']}', | ||||||
|                                   style: metaTextStyle, |                                   style: metaTextStyle, | ||||||
|                                 ).padding(right: 2), |                                 ).padding(right: 2), | ||||||
|                             if (item.metadata['exif']?['Megapixels'] != null && item.metadata['exif']?['Model'] != null) |                               if (item.metadata['exif']?['Megapixels'] != null && | ||||||
|  |                                   item.metadata['exif']?['Model'] != null) | ||||||
|                                 Text( |                                 Text( | ||||||
|                                   '${item.metadata['exif']?['Megapixels']}MP', |                                   '${item.metadata['exif']?['Megapixels']}MP', | ||||||
|                                   style: metaTextStyle, |                                   style: metaTextStyle, | ||||||
|                                 ) |                                 ) | ||||||
|                               else |                               else | ||||||
|                                 Text( |                                 Text( | ||||||
|                                 '${item.size} Bytes', |                                   item.size.formatBytes(), | ||||||
|                                   style: metaTextStyle, |                                   style: metaTextStyle, | ||||||
|                                 ), |                                 ), | ||||||
|                               if (item.metadata['width'] != null && item.metadata['height'] != null) |                               if (item.metadata['width'] != null && item.metadata['height'] != null) | ||||||
| @@ -362,6 +363,134 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> { | |||||||
|             ], |             ], | ||||||
|           ), |           ), | ||||||
|         ), |         ), | ||||||
|  |         onVerticalDragUpdate: (details) { | ||||||
|  |           if (_showDetail) return; | ||||||
|  |           if (details.delta.dy <= -40) { | ||||||
|  |             _showDetail = true; | ||||||
|  |             showModalBottomSheet( | ||||||
|  |               context: context, | ||||||
|  |               builder: (context) => _AttachmentZoomDetailPopup( | ||||||
|  |                 data: widget.data.elementAt(widget.data.length > 1 ? _pageController.page?.round() ?? 0 : 0), | ||||||
|  |               ), | ||||||
|  |             ).then((_) { | ||||||
|  |               _showDetail = false; | ||||||
|  |             }); | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         onTap: () { | ||||||
|  |           Navigator.of(context).pop(); | ||||||
|  |         }, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _AttachmentZoomDetailPopup extends StatelessWidget { | ||||||
|  |   final SnAttachment data; | ||||||
|  |  | ||||||
|  |   const _AttachmentZoomDetailPopup({required this.data}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     final ud = context.read<UserDirectoryProvider>(); | ||||||
|  |     final account = ud.getAccountFromCache(data.accountId); | ||||||
|  |  | ||||||
|  |     const tableGap = TableRow( | ||||||
|  |       children: [ | ||||||
|  |         TableCell(child: SizedBox(height: 16)), | ||||||
|  |         TableCell(child: SizedBox(height: 16)), | ||||||
|  |       ], | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     return SizedBox( | ||||||
|  |       child: Column( | ||||||
|  |         crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |         children: [ | ||||||
|  |           Row( | ||||||
|  |             crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|  |             children: [ | ||||||
|  |               const Icon(Symbols.info, size: 24), | ||||||
|  |               const Gap(16), | ||||||
|  |               Text('attachmentDetailInfo').tr().textStyle(Theme.of(context).textTheme.titleLarge!), | ||||||
|  |             ], | ||||||
|  |           ).padding(horizontal: 20, top: 16, bottom: 12), | ||||||
|  |           Expanded( | ||||||
|  |             child: SingleChildScrollView( | ||||||
|  |               child: Table( | ||||||
|  |                 columnWidths: { | ||||||
|  |                   0: IntrinsicColumnWidth(), | ||||||
|  |                   1: FlexColumnWidth(), | ||||||
|  |                 }, | ||||||
|  |                 children: [ | ||||||
|  |                   TableRow( | ||||||
|  |                     children: [ | ||||||
|  |                       TableCell( | ||||||
|  |                         child: Text('attachmentUploadBy').tr().padding(right: 16), | ||||||
|  |                       ), | ||||||
|  |                       TableCell( | ||||||
|  |                         child: Row( | ||||||
|  |                           children: [ | ||||||
|  |                             if (data.accountId > 0) | ||||||
|  |                               AccountImage( | ||||||
|  |                                 content: account?.avatar, | ||||||
|  |                                 radius: 8, | ||||||
|  |                               ), | ||||||
|  |                             const Gap(8), | ||||||
|  |                             Text(data.accountId > 0 ? account?.nick ?? 'unknown'.tr() : 'unknown'.tr()), | ||||||
|  |                             const Gap(8), | ||||||
|  |                             Text('#${data.accountId}', style: GoogleFonts.robotoMono()).opacity(0.75), | ||||||
|  |                           ], | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|  |                     ], | ||||||
|  |                   ), | ||||||
|  |                   tableGap, | ||||||
|  |                   TableRow( | ||||||
|  |                     children: [ | ||||||
|  |                       TableCell(child: Text('Mimetype').padding(right: 16)), | ||||||
|  |                       TableCell(child: Text(data.mimetype)), | ||||||
|  |                     ], | ||||||
|  |                   ), | ||||||
|  |                   TableRow( | ||||||
|  |                     children: [ | ||||||
|  |                       TableCell(child: Text('Size').padding(right: 16)), | ||||||
|  |                       TableCell( | ||||||
|  |                           child: Row( | ||||||
|  |                         children: [ | ||||||
|  |                           Text(data.size.formatBytes()), | ||||||
|  |                           const Gap(12), | ||||||
|  |                           Text('${data.size} Bytes', style: GoogleFonts.robotoMono()).opacity(0.75), | ||||||
|  |                         ], | ||||||
|  |                       )), | ||||||
|  |                     ], | ||||||
|  |                   ), | ||||||
|  |                   TableRow( | ||||||
|  |                     children: [ | ||||||
|  |                       TableCell(child: Text('Name').padding(right: 16)), | ||||||
|  |                       TableCell(child: Text(data.name)), | ||||||
|  |                     ], | ||||||
|  |                   ), | ||||||
|  |                   if (data.hash.isNotEmpty) | ||||||
|  |                     TableRow( | ||||||
|  |                       children: [ | ||||||
|  |                         TableCell(child: Text('Hash').padding(right: 16)), | ||||||
|  |                         TableCell(child: Text(data.hash, style: GoogleFonts.robotoMono(fontSize: 11)).opacity(0.9)), | ||||||
|  |                       ], | ||||||
|  |                     ), | ||||||
|  |                   tableGap, | ||||||
|  |                   ...(data.metadata['exif']?.keys.map((k) => TableRow( | ||||||
|  |                         children: [ | ||||||
|  |                           TableCell(child: Text(k).padding(right: 16)), | ||||||
|  |                           TableCell(child: Text(data.metadata['exif'][k].toString())), | ||||||
|  |                         ], | ||||||
|  |                       )) ?? | ||||||
|  |                       []), | ||||||
|  |                 ], | ||||||
|  |               ).padding(horizontal: 20, vertical: 8), | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										86
									
								
								lib/widgets/attachment/pending_attachment_alt.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								lib/widgets/attachment/pending_attachment_alt.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | |||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:provider/provider.dart'; | ||||||
|  | import 'package:surface/controllers/post_write_controller.dart'; | ||||||
|  | import 'package:surface/providers/sn_attachment.dart'; | ||||||
|  | import 'package:surface/widgets/dialog.dart'; | ||||||
|  |  | ||||||
|  | class PendingAttachmentAltDialog extends StatefulWidget { | ||||||
|  |   final PostWriteMedia media; | ||||||
|  |   const PendingAttachmentAltDialog({super.key, required this.media}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   State<PendingAttachmentAltDialog> createState() => _PendingAttachmentAltDialogState(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _PendingAttachmentAltDialogState extends State<PendingAttachmentAltDialog> { | ||||||
|  |   final _contentController = TextEditingController(); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void initState() { | ||||||
|  |     super.initState(); | ||||||
|  |     _contentController.text = widget.media.attachment!.alt; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   bool _isBusy = false; | ||||||
|  |  | ||||||
|  |   Future<void> _performAction() async { | ||||||
|  |     if (_isBusy) return; | ||||||
|  |  | ||||||
|  |     setState(() => _isBusy = true); | ||||||
|  |  | ||||||
|  |     try { | ||||||
|  |       final attach = context.read<SnAttachmentProvider>(); | ||||||
|  |       final result = await attach.updateOne( | ||||||
|  |         widget.media.attachment!, | ||||||
|  |         alt: _contentController.text, | ||||||
|  |       ); | ||||||
|  |       if (!mounted) return; | ||||||
|  |       attach.putCache([result]); | ||||||
|  |       Navigator.pop(context, result); | ||||||
|  |     } catch (err) { | ||||||
|  |       if (!mounted) return; | ||||||
|  |       context.showErrorDialog(err); | ||||||
|  |       setState(() => _isBusy = false); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void dispose() { | ||||||
|  |     _contentController.dispose(); | ||||||
|  |     super.dispose(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return AlertDialog( | ||||||
|  |       title: Text('attachmentSetAlt').tr(), | ||||||
|  |       content: Column( | ||||||
|  |         mainAxisSize: MainAxisSize.min, | ||||||
|  |         crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |         children: [ | ||||||
|  |           TextField( | ||||||
|  |             controller: _contentController, | ||||||
|  |             decoration: InputDecoration( | ||||||
|  |               labelText: 'fieldAttachmentAlt'.tr(), | ||||||
|  |               border: const UnderlineInputBorder(), | ||||||
|  |             ), | ||||||
|  |             onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||||
|  |           ), | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|  |       actions: [ | ||||||
|  |         TextButton( | ||||||
|  |           onPressed: _isBusy ? null : () { | ||||||
|  |             Navigator.pop(context); | ||||||
|  |           }, | ||||||
|  |           child: Text('dialogDismiss'.tr()), | ||||||
|  |         ), | ||||||
|  |         TextButton( | ||||||
|  |           onPressed: _isBusy ? null : () => _performAction(), | ||||||
|  |           child: Text('dialogConfirm'.tr()), | ||||||
|  |         ), | ||||||
|  |       ], | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -1,14 +1,19 @@ | |||||||
|  | import 'dart:math' as math; | ||||||
|  |  | ||||||
| import 'package:easy_localization/easy_localization.dart'; | import 'package:easy_localization/easy_localization.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:flutter_context_menu/flutter_context_menu.dart'; | import 'package:flutter_context_menu/flutter_context_menu.dart'; | ||||||
| import 'package:gap/gap.dart'; | import 'package:gap/gap.dart'; | ||||||
| import 'package:material_symbols_icons/symbols.dart'; | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:popover/popover.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/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/chat.dart'; | import 'package:surface/types/chat.dart'; | ||||||
| import 'package:surface/widgets/account/account_image.dart'; | import 'package:surface/widgets/account/account_image.dart'; | ||||||
|  | import 'package:surface/widgets/account/account_popover.dart'; | ||||||
| import 'package:surface/widgets/attachment/attachment_list.dart'; | import 'package:surface/widgets/attachment/attachment_list.dart'; | ||||||
| import 'package:surface/widgets/context_menu.dart'; | import 'package:surface/widgets/context_menu.dart'; | ||||||
| import 'package:surface/widgets/link_preview.dart'; | import 'package:surface/widgets/link_preview.dart'; | ||||||
| @@ -24,6 +29,7 @@ class ChatMessage extends StatelessWidget { | |||||||
|   final Function(SnChatMessage)? onReply; |   final Function(SnChatMessage)? onReply; | ||||||
|   final Function(SnChatMessage)? onEdit; |   final Function(SnChatMessage)? onEdit; | ||||||
|   final Function(SnChatMessage)? onDelete; |   final Function(SnChatMessage)? onDelete; | ||||||
|  |   final EdgeInsets padding; | ||||||
|  |  | ||||||
|   const ChatMessage({ |   const ChatMessage({ | ||||||
|     super.key, |     super.key, | ||||||
| @@ -35,6 +41,7 @@ class ChatMessage extends StatelessWidget { | |||||||
|     this.onReply, |     this.onReply, | ||||||
|     this.onEdit, |     this.onEdit, | ||||||
|     this.onDelete, |     this.onDelete, | ||||||
|  |     this.padding = const EdgeInsets.only(left: 12, right: 12), | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
| @@ -47,13 +54,15 @@ class ChatMessage extends StatelessWidget { | |||||||
|  |  | ||||||
|     final dateFormatter = DateFormat('MM/dd HH:mm'); |     final dateFormatter = DateFormat('MM/dd HH:mm'); | ||||||
|  |  | ||||||
|  |     final cfg = context.read<ConfigProvider>(); | ||||||
|  |  | ||||||
|     return SwipeTo( |     return SwipeTo( | ||||||
|       key: Key('chat-message-${data.id}'), |       key: Key('chat-message-${data.id}'), | ||||||
|       iconOnLeftSwipe: Symbols.reply, |       iconOnLeftSwipe: Symbols.reply, | ||||||
|       iconOnRightSwipe: Symbols.edit, |       iconOnRightSwipe: Symbols.edit, | ||||||
|       swipeSensitivity: 20, |       swipeSensitivity: 20, | ||||||
|       onLeftSwipe: onReply != null ? (_) => onReply!(data) : null, |       onLeftSwipe: onReply != null ? (_) => onReply!(data) : null, | ||||||
|       onRightSwipe: onEdit != null ? (_) => onEdit!(data) : null, |       onRightSwipe: (onEdit != null && isOwner) ? (_) => onEdit!(data) : null, | ||||||
|       child: ContextMenuArea( |       child: ContextMenuArea( | ||||||
|         contextMenu: ContextMenu( |         contextMenu: ContextMenu( | ||||||
|           entries: [ |           entries: [ | ||||||
| @@ -87,42 +96,68 @@ class ChatMessage extends StatelessWidget { | |||||||
|         child: Column( |         child: Column( | ||||||
|           crossAxisAlignment: CrossAxisAlignment.start, |           crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|           children: [ |           children: [ | ||||||
|             Row( |             Padding( | ||||||
|  |               padding: isCompact ? EdgeInsets.zero : padding, | ||||||
|  |               child: Row( | ||||||
|                 crossAxisAlignment: CrossAxisAlignment.start, |                 crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|                 children: [ |                 children: [ | ||||||
|                   if (!isMerged && !isCompact) |                   if (!isMerged && !isCompact) | ||||||
|                   AccountImage( |                     GestureDetector( | ||||||
|  |                       child: AccountImage( | ||||||
|                         content: user?.avatar, |                         content: user?.avatar, | ||||||
|  |                       ), | ||||||
|  |                       onTap: () { | ||||||
|  |                         if (user == null) return; | ||||||
|  |                         showPopover( | ||||||
|  |                           backgroundColor: Theme.of(context).colorScheme.surface, | ||||||
|  |                           context: context, | ||||||
|  |                           transition: PopoverTransition.other, | ||||||
|  |                           bodyBuilder: (context) => SizedBox( | ||||||
|  |                             width: math.min(400, MediaQuery.of(context).size.width - 10), | ||||||
|  |                             child: AccountPopoverCard( | ||||||
|  |                               data: user, | ||||||
|  |                             ), | ||||||
|  |                           ), | ||||||
|  |                           direction: PopoverDirection.bottom, | ||||||
|  |                           arrowHeight: 5, | ||||||
|  |                           arrowWidth: 15, | ||||||
|  |                           arrowDxOffset: -190, | ||||||
|  |                         ); | ||||||
|  |                       }, | ||||||
|                     ) |                     ) | ||||||
|                   else if (isMerged) |                   else if (isMerged) | ||||||
|                     const Gap(40), |                     const Gap(40), | ||||||
|                   const Gap(8), |                   const Gap(8), | ||||||
|                   Expanded( |                   Expanded( | ||||||
|  |                     child: Container( | ||||||
|  |                       constraints: BoxConstraints(maxWidth: 480), | ||||||
|                       child: Column( |                       child: Column( | ||||||
|                         crossAxisAlignment: CrossAxisAlignment.start, |                         crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|                         children: [ |                         children: [ | ||||||
|                           if (!isMerged) |                           if (!isMerged) | ||||||
|                             Row( |                             Row( | ||||||
|                           crossAxisAlignment: CrossAxisAlignment.baseline, |                               crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|                           textBaseline: TextBaseline.alphabetic, |  | ||||||
|                               children: [ |                               children: [ | ||||||
|                                 if (isCompact) |                                 if (isCompact) | ||||||
|                                   AccountImage( |                                   AccountImage( | ||||||
|                                     content: user?.avatar, |                                     content: user?.avatar, | ||||||
|                                     radius: 12, |                                     radius: 12, | ||||||
|                               ).padding(right: 6), |                                   ).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(6), |                                 const Gap(8), | ||||||
|                                 Text( |                                 Text( | ||||||
|                                   dateFormatter.format(data.createdAt.toLocal()), |                                   dateFormatter.format(data.createdAt.toLocal()), | ||||||
|                                 ).fontSize(13), |                                 ).fontSize(13), | ||||||
|                               ], |                               ], | ||||||
|                         ), |                             ).height(21), | ||||||
|                       if (isCompact) const Gap(4), |                           if (isCompact) const Gap(8), | ||||||
|                           if (data.preload?.quoteEvent != null) |                           if (data.preload?.quoteEvent != null) | ||||||
|                             StyledWidget(Container( |                             StyledWidget(Container( | ||||||
|  |                               constraints: BoxConstraints( | ||||||
|  |                                 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( | ||||||
| @@ -145,26 +180,36 @@ class ChatMessage extends StatelessWidget { | |||||||
|                               ), |                               ), | ||||||
|                             )).padding(bottom: 4, top: 4), |                             )).padding(bottom: 4, top: 4), | ||||||
|                           switch (data.type) { |                           switch (data.type) { | ||||||
|                         'messages.new' => _ChatMessageText(data: data), |                             'messages.new' => _ChatMessageText( | ||||||
|  |                                 data: data, | ||||||
|  |                                 onReply: onReply, | ||||||
|  |                                 onEdit: onEdit, | ||||||
|  |                                 onDelete: onDelete, | ||||||
|  |                               ), | ||||||
|                             _ => _ChatMessageSystemNotify(data: data), |                             _ => _ChatMessageSystemNotify(data: data), | ||||||
|                           }, |                           }, | ||||||
|                         ], |                         ], | ||||||
|                       ), |                       ), | ||||||
|  |                     ), | ||||||
|                   ) |                   ) | ||||||
|                 ], |                 ], | ||||||
|               ).opacity(isPending ? 0.5 : 1), |               ).opacity(isPending ? 0.5 : 1), | ||||||
|             if (data.body['text'] != null && (data.body['text']?.isNotEmpty ?? false)) |             ), | ||||||
|  |             if (data.body['text'] != null && | ||||||
|  |                 data.type == 'messages.new' && | ||||||
|  |                 (data.body['text']?.isNotEmpty ?? false) && | ||||||
|  |                 (cfg.prefs.getBool(kAppExpandChatLink) ?? true)) | ||||||
|               LinkPreviewWidget(text: data.body['text']!), |               LinkPreviewWidget(text: data.body['text']!), | ||||||
|             if (data.preload?.attachments?.isNotEmpty ?? false) |             if (data.preload?.attachments?.isNotEmpty ?? false) | ||||||
|               AttachmentList( |               AttachmentList( | ||||||
|                 data: data.preload!.attachments!, |                 data: data.preload!.attachments!, | ||||||
|                 bordered: true, |                 bordered: true, | ||||||
|                 gridded: true, |                 maxHeight: 560, | ||||||
|                 noGrow: true, |                 maxWidth: 480, | ||||||
|                 maxHeight: 520, |                 minWidth: 480, | ||||||
|                 padding: const EdgeInsets.only(top: 8), |                 padding: padding.copyWith(top: 8), | ||||||
|               ), |               ), | ||||||
|             if (!hasMerged && !isCompact) const Gap(12) else if (!isCompact) const Gap(6), |             if (!hasMerged && !isCompact) const Gap(12) else if (!isCompact) const Gap(8), | ||||||
|           ], |           ], | ||||||
|         ), |         ), | ||||||
|       ), |       ), | ||||||
| @@ -174,20 +219,76 @@ class ChatMessage extends StatelessWidget { | |||||||
|  |  | ||||||
| class _ChatMessageText extends StatelessWidget { | class _ChatMessageText extends StatelessWidget { | ||||||
|   final SnChatMessage data; |   final SnChatMessage data; | ||||||
|  |   final Function(SnChatMessage)? onReply; | ||||||
|  |   final Function(SnChatMessage)? onEdit; | ||||||
|  |   final Function(SnChatMessage)? onDelete; | ||||||
|  |  | ||||||
|   const _ChatMessageText({required this.data}); |   const _ChatMessageText({required this.data, this.onReply, this.onEdit, this.onDelete}); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|  |     final ua = context.read<UserProvider>(); | ||||||
|  |  | ||||||
|  |     final isOwner = ua.isAuthorized && data.sender.accountId == ua.user?.id; | ||||||
|  |  | ||||||
|     if (data.body['text'] != null && data.body['text'].isNotEmpty) { |     if (data.body['text'] != null && data.body['text'].isNotEmpty) { | ||||||
|       return Column( |       return Column( | ||||||
|         crossAxisAlignment: CrossAxisAlignment.start, |         crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|         children: [ |         children: [ | ||||||
|           MarkdownTextContent( |           SelectionArea( | ||||||
|  |             contextMenuBuilder: (context, editableTextState) { | ||||||
|  |               final List<ContextMenuButtonItem> items = editableTextState.contextMenuButtonItems; | ||||||
|  |  | ||||||
|  |               if (onReply != null) { | ||||||
|  |                 items.insert( | ||||||
|  |                   0, | ||||||
|  |                   ContextMenuButtonItem( | ||||||
|  |                     label: 'reply'.tr(), | ||||||
|  |                     onPressed: () { | ||||||
|  |                       ContextMenuController.removeAny(); | ||||||
|  |                       onReply?.call(data); | ||||||
|  |                     }, | ||||||
|  |                   ), | ||||||
|  |                 ); | ||||||
|  |               } | ||||||
|  |               if (isOwner && onEdit != null) { | ||||||
|  |                 items.insert( | ||||||
|  |                   1, | ||||||
|  |                   ContextMenuButtonItem( | ||||||
|  |                     label: 'edit'.tr(), | ||||||
|  |                     onPressed: () { | ||||||
|  |                       ContextMenuController.removeAny(); | ||||||
|  |                       onEdit?.call(data); | ||||||
|  |                     }, | ||||||
|  |                   ), | ||||||
|  |                 ); | ||||||
|  |               } | ||||||
|  |               if (isOwner && onDelete != null) { | ||||||
|  |                 items.insert( | ||||||
|  |                   2, | ||||||
|  |                   ContextMenuButtonItem( | ||||||
|  |                     label: 'delete'.tr(), | ||||||
|  |                     onPressed: () { | ||||||
|  |                       ContextMenuController.removeAny(); | ||||||
|  |                       onDelete?.call(data); | ||||||
|  |                     }, | ||||||
|  |                   ), | ||||||
|  |                 ); | ||||||
|  |               } | ||||||
|  |  | ||||||
|  |               return AdaptiveTextSelectionToolbar.buttonItems( | ||||||
|  |                 anchors: editableTextState.contextMenuAnchors, | ||||||
|  |                 buttonItems: items, | ||||||
|  |               ); | ||||||
|  |             }, | ||||||
|  |             child: Container( | ||||||
|  |               constraints: const BoxConstraints(maxWidth: 480), | ||||||
|  |               child: MarkdownTextContent( | ||||||
|                 content: data.body['text'], |                 content: data.body['text'], | ||||||
|             isSelectable: true, |  | ||||||
|                 isAutoWarp: true, |                 isAutoWarp: true, | ||||||
|               ), |               ), | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|           if (data.updatedAt != data.createdAt) |           if (data.updatedAt != data.createdAt) | ||||||
|             Text( |             Text( | ||||||
|               'messageEditedHint'.tr(), |               'messageEditedHint'.tr(), | ||||||
|   | |||||||
| @@ -11,7 +11,6 @@ import 'package:surface/providers/user_directory.dart'; | |||||||
| import 'package:surface/types/attachment.dart'; | import 'package:surface/types/attachment.dart'; | ||||||
| import 'package:surface/types/chat.dart'; | import 'package:surface/types/chat.dart'; | ||||||
| import 'package:surface/widgets/dialog.dart'; | import 'package:surface/widgets/dialog.dart'; | ||||||
| import 'package:surface/widgets/markdown_content.dart'; |  | ||||||
| import 'package:surface/widgets/post/post_media_pending_list.dart'; | import 'package:surface/widgets/post/post_media_pending_list.dart'; | ||||||
|  |  | ||||||
| class ChatMessageInput extends StatefulWidget { | class ChatMessageInput extends StatefulWidget { | ||||||
| @@ -33,12 +32,24 @@ class ChatMessageInputState extends State<ChatMessageInput> { | |||||||
|   final TextEditingController _contentController = TextEditingController(); |   final TextEditingController _contentController = TextEditingController(); | ||||||
|   final FocusNode _focusNode = FocusNode(); |   final FocusNode _focusNode = FocusNode(); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void initState() { | ||||||
|  |     super.initState(); | ||||||
|  |     _contentController.addListener(() { | ||||||
|  |       if (_contentController.text.isNotEmpty) { | ||||||
|  |         widget.controller.pingTypingStatus(); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   void setReply(SnChatMessage? value) { |   void setReply(SnChatMessage? value) { | ||||||
|     setState(() => _replyingMessage = value); |     setState(() => _replyingMessage = value); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   void setEdit(SnChatMessage? value) { |   void setEdit(SnChatMessage? value) { | ||||||
|     _contentController.text = value?.body['text'] ?? ''; |     _contentController.text = value?.body['text'] ?? ''; | ||||||
|  |     _attachments.clear(); | ||||||
|  |     _attachments.addAll(value?.preload?.attachments?.map((e) => PostWriteMedia(e)) ?? []); | ||||||
|     setState(() => _editingMessage = value); |     setState(() => _editingMessage = value); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -92,7 +103,9 @@ class ChatMessageInputState extends State<ChatMessageInput> { | |||||||
|           }, |           }, | ||||||
|         ); |         ); | ||||||
|  |  | ||||||
|  |         setState(() { | ||||||
|           _attachments[i] = PostWriteMedia(item); |           _attachments[i] = PostWriteMedia(item); | ||||||
|  |         }); | ||||||
|       } |       } | ||||||
|     } catch (err) { |     } catch (err) { | ||||||
|       if (!mounted) return; |       if (!mounted) return; | ||||||
| @@ -104,7 +117,7 @@ class ChatMessageInputState extends State<ChatMessageInput> { | |||||||
|     // Send the message |     // Send the message | ||||||
|     // NOTICE This future should not be awaited, so that the message can be sent in the background and the user can continue to type |     // NOTICE This future should not be awaited, so that the message can be sent in the background and the user can continue to type | ||||||
|     widget.controller.sendMessage( |     widget.controller.sendMessage( | ||||||
|       'messages.new', |       _editingMessage != null ? 'messages.edit' : 'messages.new', | ||||||
|       _contentController.text, |       _contentController.text, | ||||||
|       attachments: _attachments.where((e) => e.attachment != null).map((e) => e.attachment!.rid).toList(), |       attachments: _attachments.where((e) => e.attachment != null).map((e) => e.attachment!.rid).toList(), | ||||||
|       relatedId: _editingMessage?.id, |       relatedId: _editingMessage?.id, | ||||||
| @@ -161,75 +174,84 @@ class ChatMessageInputState extends State<ChatMessageInput> { | |||||||
|             .animate(const Duration(milliseconds: 300), Curves.fastEaseInToSlowEaseOut), |             .animate(const Duration(milliseconds: 300), Curves.fastEaseInToSlowEaseOut), | ||||||
|         SingleChildScrollView( |         SingleChildScrollView( | ||||||
|           physics: const NeverScrollableScrollPhysics(), |           physics: const NeverScrollableScrollPhysics(), | ||||||
|           child: Padding( |  | ||||||
|             padding: _replyingMessage != null ? const EdgeInsets.only(top: 8) : EdgeInsets.zero, |  | ||||||
|           child: _replyingMessage != null |           child: _replyingMessage != null | ||||||
|                 ? MaterialBanner( |               ? Container( | ||||||
|                     padding: const EdgeInsets.only(left: 16.0), |                   padding: const EdgeInsets.only(left: 16, right: 16), | ||||||
|                     leading: const Icon(Symbols.reply), |                   decoration: BoxDecoration( | ||||||
|                     backgroundColor: Colors.transparent, |                     border: Border( | ||||||
|                     content: SingleChildScrollView( |                       bottom: BorderSide( | ||||||
|                       physics: const NeverScrollableScrollPhysics(), |                         color: Theme.of(context).dividerColor, | ||||||
|                       child: Column( |                         width: 1 / MediaQuery.of(context).devicePixelRatio, | ||||||
|                         mainAxisAlignment: MainAxisAlignment.center, |                       ), | ||||||
|                         crossAxisAlignment: CrossAxisAlignment.start, |                     ), | ||||||
|  |                   ), | ||||||
|  |                   child: Row( | ||||||
|  |                     crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|                     children: [ |                     children: [ | ||||||
|                           if (_replyingMessage?.body['text'] != null) |                       const Icon(Symbols.reply, size: 20), | ||||||
|                             MarkdownTextContent( |                       const Gap(8), | ||||||
|                               content: _replyingMessage?.body['text'], |                       Expanded( | ||||||
|                             ), |                         child: Text( | ||||||
|                         ], |                           _replyingMessage?.body['text'] ?? '${_replyingMessage?.sender.nick}', | ||||||
|  |                           maxLines: 1, | ||||||
|  |                           overflow: TextOverflow.ellipsis, | ||||||
|                         ), |                         ), | ||||||
|                       ), |                       ), | ||||||
|                     actions: [ |                       const Gap(16), | ||||||
|                       TextButton( |                       InkWell( | ||||||
|                         child: Text('cancel'.tr()), |                         child: Text('cancel'.tr()), | ||||||
|                         onPressed: () { |                         onTap: () { | ||||||
|  |                           _attachments.clear(); | ||||||
|                           setState(() => _replyingMessage = null); |                           setState(() => _replyingMessage = null); | ||||||
|                         }, |                         }, | ||||||
|                       ), |                       ), | ||||||
|                     ], |                     ], | ||||||
|  |                   ).padding(vertical: 8), | ||||||
|                 ) |                 ) | ||||||
|               : const SizedBox.shrink(), |               : const SizedBox.shrink(), | ||||||
|           ), |  | ||||||
|         ) |         ) | ||||||
|             .height(_replyingMessage != null ? 54 + 8 : 0, animate: true) |             .height(_replyingMessage != null ? 38 : 0, animate: true) | ||||||
|             .animate(const Duration(milliseconds: 300), Curves.fastEaseInToSlowEaseOut), |             .animate(const Duration(milliseconds: 300), Curves.fastEaseInToSlowEaseOut), | ||||||
|         SingleChildScrollView( |         SingleChildScrollView( | ||||||
|           physics: const NeverScrollableScrollPhysics(), |           physics: const NeverScrollableScrollPhysics(), | ||||||
|           child: Padding( |  | ||||||
|             padding: _editingMessage != null ? const EdgeInsets.only(top: 8) : EdgeInsets.zero, |  | ||||||
|           child: _editingMessage != null |           child: _editingMessage != null | ||||||
|                 ? MaterialBanner( |               ? Container( | ||||||
|                     padding: const EdgeInsets.only(left: 16.0), |                   padding: const EdgeInsets.only(left: 16, right: 16), | ||||||
|                     leading: const Icon(Symbols.edit), |                   decoration: BoxDecoration( | ||||||
|                     backgroundColor: Colors.transparent, |                     border: Border( | ||||||
|                     content: SingleChildScrollView( |                       bottom: BorderSide( | ||||||
|                       physics: const NeverScrollableScrollPhysics(), |                         color: Theme.of(context).dividerColor, | ||||||
|                       child: Column( |                         width: 1 / MediaQuery.of(context).devicePixelRatio, | ||||||
|                         mainAxisAlignment: MainAxisAlignment.center, |                       ), | ||||||
|                         crossAxisAlignment: CrossAxisAlignment.start, |                     ), | ||||||
|  |                   ), | ||||||
|  |                   child: Row( | ||||||
|  |                     crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|                     children: [ |                     children: [ | ||||||
|                           if (_editingMessage?.body['text'] != null) |                       const Icon(Symbols.edit, size: 20), | ||||||
|                             MarkdownTextContent( |                       const Gap(8), | ||||||
|                               content: _editingMessage?.body['text'], |                       Expanded( | ||||||
|                             ), |                         child: Text( | ||||||
|                         ], |                           _editingMessage?.body['text'] ?? '${_editingMessage?.sender.nick}', | ||||||
|  |                           maxLines: 1, | ||||||
|  |                           overflow: TextOverflow.ellipsis, | ||||||
|                         ), |                         ), | ||||||
|                       ), |                       ), | ||||||
|                     actions: [ |                       const Gap(16), | ||||||
|                       TextButton( |                       InkWell( | ||||||
|                         child: Text('cancel'.tr()), |                         child: Text('cancel'.tr()), | ||||||
|                         onPressed: () { |                         onTap: () { | ||||||
|  |                           _attachments.clear(); | ||||||
|  |                           _contentController.clear(); | ||||||
|                           setState(() => _editingMessage = null); |                           setState(() => _editingMessage = null); | ||||||
|                         }, |                         }, | ||||||
|                       ), |                       ), | ||||||
|                     ], |                     ], | ||||||
|  |                   ).padding(vertical: 8), | ||||||
|                 ) |                 ) | ||||||
|               : const SizedBox.shrink(), |               : const SizedBox.shrink(), | ||||||
|           ), |  | ||||||
|         ) |         ) | ||||||
|             .height(_editingMessage != null ? 54 + 8 : 0, animate: true) |             .height(_editingMessage != null ? 38 : 0, animate: true) | ||||||
|             .animate(const Duration(milliseconds: 300), Curves.fastEaseInToSlowEaseOut), |             .animate(const Duration(milliseconds: 300), Curves.fastEaseInToSlowEaseOut), | ||||||
|         SizedBox( |         SizedBox( | ||||||
|           height: 56, |           height: 56, | ||||||
|   | |||||||
							
								
								
									
										53
									
								
								lib/widgets/chat/chat_typing_indicator.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								lib/widgets/chat/chat_typing_indicator.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | |||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:gap/gap.dart'; | ||||||
|  | import 'package:material_symbols_icons/material_symbols_icons.dart'; | ||||||
|  | import 'package:provider/provider.dart'; | ||||||
|  | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  | import 'package:surface/controllers/chat_message_controller.dart'; | ||||||
|  | import 'package:surface/providers/user_directory.dart'; | ||||||
|  |  | ||||||
|  | class ChatTypingIndicator extends StatelessWidget { | ||||||
|  |   final ChatMessageController controller; | ||||||
|  |  | ||||||
|  |   const ChatTypingIndicator({super.key, required this.controller}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     final ud = context.read<UserDirectoryProvider>(); | ||||||
|  |  | ||||||
|  |     return StyledWidget(controller.typingMembers.isEmpty | ||||||
|  |             ? const SizedBox.shrink() | ||||||
|  |             : Container( | ||||||
|  |                 padding: const EdgeInsets.only(left: 16, right: 16), | ||||||
|  |                 decoration: BoxDecoration( | ||||||
|  |                   border: Border( | ||||||
|  |                     bottom: BorderSide( | ||||||
|  |                       color: Theme.of(context).dividerColor, | ||||||
|  |                       width: 1 / MediaQuery.of(context).devicePixelRatio, | ||||||
|  |                     ), | ||||||
|  |                   ), | ||||||
|  |                 ), | ||||||
|  |                 child: Row( | ||||||
|  |                   children: [ | ||||||
|  |                     const Icon(Symbols.more_horiz, weight: 600, size: 20), | ||||||
|  |                     const Gap(8), | ||||||
|  |                     Text( | ||||||
|  |                       'messageTyping'.plural(controller.typingMembers.length, args: [ | ||||||
|  |                         controller.typingMembers | ||||||
|  |                             .map((ele) => (ele.nick?.isNotEmpty ?? false) | ||||||
|  |                                 ? ele.nick! | ||||||
|  |                                 : ud.getAccountFromCache(ele.accountId)?.name ?? 'unknown') | ||||||
|  |                             .join(', '), | ||||||
|  |                       ]), | ||||||
|  |                     ), | ||||||
|  |                   ], | ||||||
|  |                 ), | ||||||
|  |               )) | ||||||
|  |         .height(controller.typingMembers.isNotEmpty ? 38 : 0, animate: true) | ||||||
|  |         .animate( | ||||||
|  |           const Duration(milliseconds: 300), | ||||||
|  |           Curves.fastLinearToSlowEaseIn, | ||||||
|  |         ); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -1,5 +1,7 @@ | |||||||
| 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:gap/gap.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/userinfo.dart'; | import 'package:surface/providers/userinfo.dart'; | ||||||
| @@ -16,15 +18,14 @@ class ConnectionIndicator extends StatelessWidget { | |||||||
|       listenable: ws, |       listenable: ws, | ||||||
|       builder: (context, _) { |       builder: (context, _) { | ||||||
|         final ua = context.read<UserProvider>(); |         final ua = context.read<UserProvider>(); | ||||||
|  |         final show = (ws.isBusy || !ws.isConnected) && ua.isAuthorized; | ||||||
|  |  | ||||||
|         return GestureDetector( |         return IgnorePointer( | ||||||
|           child: Container( |           ignoring: !show, | ||||||
|             padding: EdgeInsets.only( |           child: GestureDetector( | ||||||
|               bottom: 8, |             child: Material( | ||||||
|               top: MediaQuery.of(context).padding.top + 8, |               elevation: 2, | ||||||
|               left: 24, |               shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))), | ||||||
|               right: 24, |  | ||||||
|             ), |  | ||||||
|               color: Theme.of(context).colorScheme.secondaryContainer, |               color: Theme.of(context).colorScheme.secondaryContainer, | ||||||
|               child: ua.isAuthorized |               child: ua.isAuthorized | ||||||
|                   ? Row( |                   ? Row( | ||||||
| @@ -32,21 +33,25 @@ class ConnectionIndicator extends StatelessWidget { | |||||||
|                       crossAxisAlignment: CrossAxisAlignment.center, |                       crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|                       children: [ |                       children: [ | ||||||
|                         if (ws.isBusy) |                         if (ws.isBusy) | ||||||
|                         Text('serverConnecting').tr().textColor( |                           Text('serverConnecting').tr().textColor(Theme.of(context).colorScheme.onSecondaryContainer) | ||||||
|                             Theme.of(context).colorScheme.onSecondaryContainer) |  | ||||||
|                         else if (!ws.isConnected) |                         else if (!ws.isConnected) | ||||||
|                         Text('serverDisconnected').tr().textColor( |                           Text('serverDisconnected').tr().textColor(Theme.of(context).colorScheme.onSecondaryContainer) | ||||||
|                             Theme.of(context).colorScheme.onSecondaryContainer), |                         else | ||||||
|  |                           Text('serverConnected').tr().textColor(Theme.of(context).colorScheme.onSecondaryContainer), | ||||||
|  |                         const Gap(8), | ||||||
|  |                         if (ws.isBusy) | ||||||
|  |                           const CircularProgressIndicator(strokeWidth: 2.5) | ||||||
|  |                               .width(12) | ||||||
|  |                               .height(12) | ||||||
|  |                               .padding(horizontal: 4, right: 4) | ||||||
|  |                         else if (!ws.isConnected) | ||||||
|  |                           const Icon(Symbols.power_off, size: 18) | ||||||
|  |                         else | ||||||
|  |                           const Icon(Symbols.power, size: 18), | ||||||
|                       ], |                       ], | ||||||
|                   ) |                     ).padding(horizontal: 8, vertical: 4) | ||||||
|                   : const SizedBox.shrink(), |                   : const SizedBox.shrink(), | ||||||
|           ) |             ).opacity(show ? 1 : 0, animate: true).animate( | ||||||
|               .height( |  | ||||||
|                   (ws.isBusy || !ws.isConnected) && ua.isAuthorized |  | ||||||
|                       ? MediaQuery.of(context).padding.top + 36 |  | ||||||
|                       : 0, |  | ||||||
|                   animate: true) |  | ||||||
|               .animate( |  | ||||||
|                   const Duration(milliseconds: 300), |                   const Duration(milliseconds: 300), | ||||||
|                   Curves.easeInOut, |                   Curves.easeInOut, | ||||||
|                 ), |                 ), | ||||||
| @@ -55,6 +60,7 @@ class ConnectionIndicator extends StatelessWidget { | |||||||
|                 ws.connect(); |                 ws.connect(); | ||||||
|               } |               } | ||||||
|             }, |             }, | ||||||
|  |           ), | ||||||
|         ); |         ); | ||||||
|       }, |       }, | ||||||
|     ); |     ); | ||||||
|   | |||||||
| @@ -1,7 +1,8 @@ | |||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:flutter_animate/flutter_animate.dart'; | import 'package:flutter_animate/flutter_animate.dart'; | ||||||
| import 'package:flutter_context_menu/flutter_context_menu.dart'; | import 'package:flutter_context_menu/flutter_context_menu.dart'; | ||||||
| import 'package:responsive_framework/responsive_framework.dart'; | import 'package:provider/provider.dart'; | ||||||
|  | import 'package:surface/providers/config.dart'; | ||||||
|  |  | ||||||
| class ContextMenuArea extends StatelessWidget { | class ContextMenuArea extends StatelessWidget { | ||||||
|   final ContextMenu contextMenu; |   final ContextMenu contextMenu; | ||||||
| @@ -22,11 +23,10 @@ class ContextMenuArea extends StatelessWidget { | |||||||
|     return Listener( |     return Listener( | ||||||
|       onPointerDown: (event) { |       onPointerDown: (event) { | ||||||
|         mousePosition = event.position; |         mousePosition = event.position; | ||||||
|         final isCollapseDrawer = ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE); |         final cfg = context.read<ConfigProvider>(); | ||||||
|         if (!isCollapseDrawer) { |         if (!cfg.drawerIsCollapsed) { | ||||||
|           final isExpandDrawer = ResponsiveBreakpoints.of(context).largerThan(TABLET); |  | ||||||
|           // Leave padding for side navigation |           // Leave padding for side navigation | ||||||
|           mousePosition = isExpandDrawer |           mousePosition = cfg.drawerIsExpanded | ||||||
|               ? mousePosition.copyWith(dx: mousePosition.dx - 304 * 2) |               ? mousePosition.copyWith(dx: mousePosition.dx - 304 * 2) | ||||||
|               : mousePosition.copyWith(dx: mousePosition.dx - 72 * 2); |               : mousePosition.copyWith(dx: mousePosition.dx - 72 * 2); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -81,8 +81,9 @@ class _LinkPreviewEntry extends StatelessWidget { | |||||||
|                   child: AspectRatio( |                   child: AspectRatio( | ||||||
|                     aspectRatio: 16 / 9, |                     aspectRatio: 16 / 9, | ||||||
|                     child: ClipRRect( |                     child: ClipRRect( | ||||||
|  |                       borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||||
|                       child: AutoResizeUniversalImage( |                       child: AutoResizeUniversalImage( | ||||||
|                         meta.image!, |                         meta.image!.startsWith('//') ? 'https:${meta.image}' : meta.image!, | ||||||
|                         fit: BoxFit.contain, |                         fit: BoxFit.contain, | ||||||
|                       ), |                       ), | ||||||
|                     ), |                     ), | ||||||
| @@ -94,11 +95,14 @@ class _LinkPreviewEntry extends StatelessWidget { | |||||||
|                   crossAxisAlignment: CrossAxisAlignment.center, |                   crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|                   children: [ |                   children: [ | ||||||
|                     if (meta.icon?.isNotEmpty ?? false) |                     if (meta.icon?.isNotEmpty ?? false) | ||||||
|                       StyledWidget( |                       SizedBox( | ||||||
|                         meta.icon!.endsWith('.svg') |                         width: 36, | ||||||
|                             ? SvgPicture.network(meta.icon!) |                         height: 36, | ||||||
|  |                         child: meta.icon!.endsWith('.svg') | ||||||
|  |                             ? SvgPicture.network(meta.icon!, width: 36, height: 36) | ||||||
|                             : UniversalImage( |                             : UniversalImage( | ||||||
|                                 meta.icon!, |                                 meta.icon!, | ||||||
|  |                                 noErrorWidget: true, | ||||||
|                                 width: 36, |                                 width: 36, | ||||||
|                                 height: 36, |                                 height: 36, | ||||||
|                                 cacheHeight: 36, |                                 cacheHeight: 36, | ||||||
|   | |||||||
| @@ -1,39 +1,38 @@ | |||||||
| import 'dart:ui'; |  | ||||||
|  |  | ||||||
| import 'package:dismissible_page/dismissible_page.dart'; | import 'package:dismissible_page/dismissible_page.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:flutter_markdown/flutter_markdown.dart'; | import 'package:flutter_markdown/flutter_markdown.dart'; | ||||||
| import 'package:go_router/go_router.dart'; | import 'package:go_router/go_router.dart'; | ||||||
| import 'package:google_fonts/google_fonts.dart'; | import 'package:google_fonts/google_fonts.dart'; | ||||||
| import 'package:markdown/markdown.dart' as markdown; | import 'package:markdown/markdown.dart' as markdown; | ||||||
| import 'package:styled_widget/styled_widget.dart'; | import 'package:provider/provider.dart'; | ||||||
|  | import 'package:surface/providers/sn_network.dart'; | ||||||
|  | import 'package:surface/providers/sn_sticker.dart'; | ||||||
| import 'package:surface/types/attachment.dart'; | import 'package:surface/types/attachment.dart'; | ||||||
| import 'package:surface/widgets/attachment/attachment_item.dart'; | import 'package:surface/widgets/attachment/attachment_item.dart'; | ||||||
| import 'package:surface/widgets/universal_image.dart'; | import 'package:surface/widgets/universal_image.dart'; | ||||||
| import 'package:syntax_highlight/syntax_highlight.dart'; |  | ||||||
| import 'package:url_launcher/url_launcher_string.dart'; | import 'package:url_launcher/url_launcher_string.dart'; | ||||||
| import 'package:path/path.dart'; |  | ||||||
| import 'package:uuid/uuid.dart'; | import 'package:uuid/uuid.dart'; | ||||||
|  |  | ||||||
| import 'attachment/attachment_zoom.dart'; | import 'attachment/attachment_zoom.dart'; | ||||||
|  |  | ||||||
| class MarkdownTextContent extends StatelessWidget { | class MarkdownTextContent extends StatelessWidget { | ||||||
|   final String content; |   final String content; | ||||||
|   final bool isSelectable; |  | ||||||
|   final bool isAutoWarp; |   final bool isAutoWarp; | ||||||
|  |   final bool isEnlargeSticker; | ||||||
|   final TextScaler? textScaler; |   final TextScaler? textScaler; | ||||||
|   final List<SnAttachment?>? attachments; |   final List<SnAttachment?>? attachments; | ||||||
|  |  | ||||||
|   const MarkdownTextContent({ |   const MarkdownTextContent({ | ||||||
|     super.key, |     super.key, | ||||||
|     required this.content, |     required this.content, | ||||||
|     this.isSelectable = false, |  | ||||||
|     this.isAutoWarp = false, |     this.isAutoWarp = false, | ||||||
|  |     this.isEnlargeSticker = false, | ||||||
|     this.textScaler, |     this.textScaler, | ||||||
|     this.attachments, |     this.attachments, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   Widget _buildContent(BuildContext context) { |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|     return Markdown( |     return Markdown( | ||||||
|       shrinkWrap: true, |       shrinkWrap: true, | ||||||
|       physics: const NeverScrollableScrollPhysics(), |       physics: const NeverScrollableScrollPhysics(), | ||||||
| @@ -65,10 +64,10 @@ class MarkdownTextContent extends StatelessWidget { | |||||||
|           ), |           ), | ||||||
|           borderRadius: const BorderRadius.all(Radius.circular(4)), |           borderRadius: const BorderRadius.all(Radius.circular(4)), | ||||||
|           color: Theme.of(context).colorScheme.surface.withOpacity(0.5), |           color: Theme.of(context).colorScheme.surface.withOpacity(0.5), | ||||||
|           )), |         ), | ||||||
|       builders: { |         code: GoogleFonts.robotoMono(height: 1), | ||||||
|         'code': _MarkdownTextCodeElement(), |       ), | ||||||
|       }, |       builders: {}, | ||||||
|       softLineBreak: true, |       softLineBreak: true, | ||||||
|       extensionSet: markdown.ExtensionSet( |       extensionSet: markdown.ExtensionSet( | ||||||
|         <markdown.BlockSyntax>[ |         <markdown.BlockSyntax>[ | ||||||
| @@ -78,6 +77,7 @@ class MarkdownTextContent extends StatelessWidget { | |||||||
|         <markdown.InlineSyntax>[ |         <markdown.InlineSyntax>[ | ||||||
|           if (isAutoWarp) markdown.LineBreakSyntax(), |           if (isAutoWarp) markdown.LineBreakSyntax(), | ||||||
|           _UserNameCardInlineSyntax(), |           _UserNameCardInlineSyntax(), | ||||||
|  |           _CustomEmoteInlineSyntax(context), | ||||||
|           markdown.AutolinkSyntax(), |           markdown.AutolinkSyntax(), | ||||||
|           markdown.AutolinkExtensionSyntax(), |           markdown.AutolinkExtensionSyntax(), | ||||||
|           markdown.CodeSyntax(), |           markdown.CodeSyntax(), | ||||||
| @@ -108,6 +108,38 @@ class MarkdownTextContent extends StatelessWidget { | |||||||
|         if (url.startsWith('solink://')) { |         if (url.startsWith('solink://')) { | ||||||
|           final segments = url.replaceFirst('solink://', '').split('/'); |           final segments = url.replaceFirst('solink://', '').split('/'); | ||||||
|           switch (segments[0]) { |           switch (segments[0]) { | ||||||
|  |             case 'stickers': | ||||||
|  |               final alias = segments[1]; | ||||||
|  |               final st = context.read<SnStickerProvider>(); | ||||||
|  |               final sn = context.read<SnNetworkProvider>(); | ||||||
|  |               final double size = isEnlargeSticker ? 128 : 32; | ||||||
|  |               return Container( | ||||||
|  |                 width: size, | ||||||
|  |                 height: size, | ||||||
|  |                 decoration: BoxDecoration( | ||||||
|  |                   borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||||
|  |                   color: Theme.of(context).colorScheme.surfaceContainerHigh, | ||||||
|  |                 ), | ||||||
|  |                 child: ClipRRect( | ||||||
|  |                   borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||||
|  |                   child: FutureBuilder<SnSticker?>( | ||||||
|  |                     future: st.lookupSticker(alias), | ||||||
|  |                     builder: (context, snapshot) { | ||||||
|  |                       if (snapshot.hasData) { | ||||||
|  |                         return UniversalImage( | ||||||
|  |                           sn.getAttachmentUrl(snapshot.data!.attachment.rid), | ||||||
|  |                           fit: BoxFit.cover, | ||||||
|  |                           width: size, | ||||||
|  |                           height: size, | ||||||
|  |                           cacheHeight: size, | ||||||
|  |                           cacheWidth: size, | ||||||
|  |                         ); | ||||||
|  |                       } | ||||||
|  |                       return const SizedBox.shrink(); | ||||||
|  |                     }, | ||||||
|  |                   ), | ||||||
|  |                 ), | ||||||
|  |               ); | ||||||
|             case 'attachments': |             case 'attachments': | ||||||
|               final attachment = attachments?.firstWhere( |               final attachment = attachments?.firstWhere( | ||||||
|                     (ele) => ele?.rid == segments[1], |                     (ele) => ele?.rid == segments[1], | ||||||
| @@ -168,14 +200,6 @@ class MarkdownTextContent extends StatelessWidget { | |||||||
|       }, |       }, | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   Widget build(BuildContext context) { |  | ||||||
|     if (isSelectable) { |  | ||||||
|       return SelectionArea(child: _buildContent(context)); |  | ||||||
|     } |  | ||||||
|     return _buildContent(context); |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| class _UserNameCardInlineSyntax extends markdown.InlineSyntax { | class _UserNameCardInlineSyntax extends markdown.InlineSyntax { | ||||||
| @@ -194,45 +218,24 @@ class _UserNameCardInlineSyntax extends markdown.InlineSyntax { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| class _MarkdownTextCodeElement extends MarkdownElementBuilder { | class _CustomEmoteInlineSyntax extends markdown.InlineSyntax { | ||||||
|   @override |   final BuildContext context; | ||||||
|   Widget? visitElementAfter( |  | ||||||
|     markdown.Element element, |  | ||||||
|     TextStyle? preferredStyle, |  | ||||||
|   ) { |  | ||||||
|     var language = ''; |  | ||||||
|  |  | ||||||
|     if (element.attributes['class'] != null) { |   _CustomEmoteInlineSyntax(this.context) : super(r':([-\w]+):'); | ||||||
|       String lg = element.attributes['class'] as String; |  | ||||||
|       language = lg.substring(9).trim(); |   @override | ||||||
|  |   bool onMatch(markdown.InlineParser parser, Match match) { | ||||||
|  |     final SnStickerProvider st = context.read<SnStickerProvider>(); | ||||||
|  |     final alias = match[1]!.toUpperCase(); | ||||||
|  |     if (st.hasNotSticker(alias)) { | ||||||
|  |       parser.advanceBy(1); | ||||||
|  |       return false; | ||||||
|     } |     } | ||||||
|     return SizedBox( |  | ||||||
|       child: FutureBuilder( |     final element = markdown.Element.empty('img'); | ||||||
|         future: (() async { |     element.attributes['src'] = 'solink://stickers/$alias'; | ||||||
|           final docPath = '../../../'; |     parser.addNode(element); | ||||||
|           final highlightingPath = join(docPath, 'assets/highlighting', language); |  | ||||||
|           await Highlighter.initialize([highlightingPath]); |     return true; | ||||||
|           return Highlighter( |  | ||||||
|             language: highlightingPath, |  | ||||||
|             theme: PlatformDispatcher.instance.platformBrightness == Brightness.light |  | ||||||
|                 ? await HighlighterTheme.loadLightTheme() |  | ||||||
|                 : await HighlighterTheme.loadDarkTheme(), |  | ||||||
|           ); |  | ||||||
|         })(), |  | ||||||
|         builder: (context, snapshot) { |  | ||||||
|           if (snapshot.hasData) { |  | ||||||
|             final highlighter = snapshot.data!; |  | ||||||
|             return Text.rich( |  | ||||||
|               highlighter.highlight(element.textContent.trim()), |  | ||||||
|               style: GoogleFonts.robotoMono(), |  | ||||||
|             ); |  | ||||||
|           } |  | ||||||
|           return Text( |  | ||||||
|             element.textContent.trim(), |  | ||||||
|             style: GoogleFonts.robotoMono(), |  | ||||||
|           ); |  | ||||||
|         }, |  | ||||||
|       ), |  | ||||||
|     ).padding(all: 8); |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,9 +1,13 @@ | |||||||
|  | import 'dart:io'; | ||||||
|  |  | ||||||
|  | import 'package:bitsdojo_window/bitsdojo_window.dart'; | ||||||
| import 'package:easy_localization/easy_localization.dart'; | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter/foundation.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:go_router/go_router.dart'; | import 'package:go_router/go_router.dart'; | ||||||
| import 'package:provider/provider.dart'; | import 'package:provider/provider.dart'; | ||||||
| import 'package:responsive_framework/responsive_framework.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/navigation.dart'; | import 'package:surface/providers/navigation.dart'; | ||||||
| import 'package:surface/widgets/version_label.dart'; | import 'package:surface/widgets/version_label.dart'; | ||||||
|  |  | ||||||
| @@ -28,8 +32,9 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer> { | |||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     final nav = context.watch<NavigationProvider>(); |     final nav = context.watch<NavigationProvider>(); | ||||||
|  |     final cfg = context.watch<ConfigProvider>(); | ||||||
|  |  | ||||||
|     final backgroundColor = ResponsiveBreakpoints.of(context).largerThan(TABLET) ? Colors.transparent : null; |     final backgroundColor = cfg.drawerIsExpanded ? Colors.transparent : null; | ||||||
|  |  | ||||||
|     return ListenableBuilder( |     return ListenableBuilder( | ||||||
|       listenable: nav, |       listenable: nav, | ||||||
| @@ -44,6 +49,18 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer> { | |||||||
|           backgroundColor: backgroundColor, |           backgroundColor: backgroundColor, | ||||||
|           selectedIndex: nav.currentIndex, |           selectedIndex: nav.currentIndex, | ||||||
|           children: [ |           children: [ | ||||||
|  |             if (!kIsWeb && (Platform.isWindows || Platform.isLinux || Platform.isMacOS) && !cfg.drawerIsExpanded) | ||||||
|  |               Container( | ||||||
|  |                 decoration: BoxDecoration( | ||||||
|  |                   border: Border( | ||||||
|  |                     bottom: BorderSide( | ||||||
|  |                       color: Theme.of(context).dividerColor, | ||||||
|  |                       width: 1 / MediaQuery.of(context).devicePixelRatio, | ||||||
|  |                     ), | ||||||
|  |                   ), | ||||||
|  |                 ), | ||||||
|  |                 child: WindowTitleBarBox(), | ||||||
|  |               ), | ||||||
|             Column( |             Column( | ||||||
|               mainAxisSize: MainAxisSize.min, |               mainAxisSize: MainAxisSize.min, | ||||||
|               crossAxisAlignment: CrossAxisAlignment.start, |               crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|   | |||||||
| @@ -18,9 +18,7 @@ class _AppRailNavigationState extends State<AppRailNavigation> { | |||||||
|   void initState() { |   void initState() { | ||||||
|     super.initState(); |     super.initState(); | ||||||
|     WidgetsBinding.instance.addPostFrameCallback((_) { |     WidgetsBinding.instance.addPostFrameCallback((_) { | ||||||
|       context |       context.read<NavigationProvider>().autoDetectIndex(GoRouter.maybeOf(context)); | ||||||
|           .read<NavigationProvider>() |  | ||||||
|           .autoDetectIndex(GoRouter.maybeOf(context)); |  | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -31,11 +29,11 @@ class _AppRailNavigationState extends State<AppRailNavigation> { | |||||||
|     return ListenableBuilder( |     return ListenableBuilder( | ||||||
|       listenable: nav, |       listenable: nav, | ||||||
|       builder: (context, _) { |       builder: (context, _) { | ||||||
|         final destinations = |         final destinations = nav.destinations.where((ele) => ele.isPinned).toList(); | ||||||
|             nav.destinations.where((ele) => ele.isPinned).toList(); |  | ||||||
|  |  | ||||||
|         return NavigationRail( |         return NavigationRail( | ||||||
|           selectedIndex: nav.currentIndex, |           selectedIndex: | ||||||
|  |               nav.currentIndex != null && nav.currentIndex! < nav.pinnedDestinationCount ? nav.currentIndex : null, | ||||||
|           destinations: [ |           destinations: [ | ||||||
|             ...destinations.where((ele) => ele.isPinned).map((ele) { |             ...destinations.where((ele) => ele.isPinned).map((ele) { | ||||||
|               return NavigationRailDestination( |               return NavigationRailDestination( | ||||||
|   | |||||||
| @@ -6,8 +6,10 @@ import 'package:flutter/foundation.dart'; | |||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:go_router/go_router.dart'; | import 'package:go_router/go_router.dart'; | ||||||
| import 'package:google_fonts/google_fonts.dart'; | import 'package:google_fonts/google_fonts.dart'; | ||||||
|  | import 'package:provider/provider.dart'; | ||||||
| import 'package:responsive_framework/responsive_framework.dart'; | import 'package:responsive_framework/responsive_framework.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/navigation.dart'; | import 'package:surface/providers/navigation.dart'; | ||||||
| import 'package:surface/widgets/connection_indicator.dart'; | import 'package:surface/widgets/connection_indicator.dart'; | ||||||
| import 'package:surface/widgets/dialog.dart'; | import 'package:surface/widgets/dialog.dart'; | ||||||
| @@ -15,37 +17,80 @@ import 'package:surface/widgets/navigation/app_background.dart'; | |||||||
| import 'package:surface/widgets/navigation/app_bottom_navigation.dart'; | import 'package:surface/widgets/navigation/app_bottom_navigation.dart'; | ||||||
| import 'package:surface/widgets/navigation/app_drawer_navigation.dart'; | import 'package:surface/widgets/navigation/app_drawer_navigation.dart'; | ||||||
| import 'package:surface/widgets/navigation/app_rail_navigation.dart'; | import 'package:surface/widgets/navigation/app_rail_navigation.dart'; | ||||||
|  | import 'package:surface/widgets/notify_indicator.dart'; | ||||||
|  |  | ||||||
| final globalRootScaffoldKey = GlobalKey<ScaffoldState>(); | final globalRootScaffoldKey = GlobalKey<ScaffoldState>(); | ||||||
|  |  | ||||||
| class AppPageScaffold extends StatelessWidget { | class AppScaffold extends StatelessWidget { | ||||||
|   final String? title; |  | ||||||
|   final Widget? body; |   final Widget? body; | ||||||
|   final bool showAppBar; |   final PreferredSizeWidget? bottomNavigationBar; | ||||||
|   final bool showBottomNavigation; |   final PreferredSizeWidget? bottomSheet; | ||||||
|  |   final Drawer? drawer; | ||||||
|  |   final Widget? endDrawer; | ||||||
|  |   final FloatingActionButtonAnimator? floatingActionButtonAnimator; | ||||||
|  |   final FloatingActionButtonLocation? floatingActionButtonLocation; | ||||||
|  |   final Widget? floatingActionButton; | ||||||
|  |   final AppBar? appBar; | ||||||
|  |   final DrawerCallback? onDrawerChanged; | ||||||
|  |   final DrawerCallback? onEndDrawerChanged; | ||||||
|  |  | ||||||
|   const AppPageScaffold({ |   const AppScaffold({ | ||||||
|     super.key, |     super.key, | ||||||
|     this.title, |     this.appBar, | ||||||
|     this.body, |     this.body, | ||||||
|     this.showAppBar = true, |     this.floatingActionButton, | ||||||
|     this.showBottomNavigation = false, |     this.floatingActionButtonLocation, | ||||||
|  |     this.floatingActionButtonAnimator, | ||||||
|  |     this.bottomNavigationBar, | ||||||
|  |     this.bottomSheet, | ||||||
|  |     this.drawer, | ||||||
|  |     this.endDrawer, | ||||||
|  |     this.onDrawerChanged, | ||||||
|  |     this.onEndDrawerChanged, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     final state = GoRouter.maybeOf(context); |     final appBarHeight = appBar?.preferredSize.height ?? 0; | ||||||
|     final routeName = state?.routerDelegate.currentConfiguration.last.route.name; |     final safeTop = MediaQuery.of(context).padding.top; | ||||||
|  |  | ||||||
|     final autoTitle = state != null ? 'screen${routeName?.capitalize()}' : 'screen'; |  | ||||||
|  |  | ||||||
|     return Scaffold( |     return Scaffold( | ||||||
|       appBar: showAppBar |       extendBody: true, | ||||||
|           ? AppBar( |       extendBodyBehindAppBar: true, | ||||||
|               title: Text(title ?? autoTitle.tr()), |       backgroundColor: Theme.of(context).scaffoldBackgroundColor, | ||||||
|             ) |       body: SizedBox.expand( | ||||||
|           : null, |         child: AppBackground( | ||||||
|       body: body, |           child: Column( | ||||||
|  |             children: [ | ||||||
|  |               IgnorePointer(child: SizedBox(height: appBar != null ? appBarHeight + safeTop : 0)), | ||||||
|  |               if (body != null) Expanded(child: body!), | ||||||
|  |             ], | ||||||
|  |           ), | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |       appBar: appBar, | ||||||
|  |       bottomNavigationBar: bottomNavigationBar, | ||||||
|  |       bottomSheet: bottomSheet, | ||||||
|  |       drawer: drawer, | ||||||
|  |       endDrawer: endDrawer, | ||||||
|  |       floatingActionButton: floatingActionButton, | ||||||
|  |       floatingActionButtonAnimator: floatingActionButtonAnimator, | ||||||
|  |       floatingActionButtonLocation: floatingActionButtonLocation, | ||||||
|  |       onDrawerChanged: onDrawerChanged, | ||||||
|  |       onEndDrawerChanged: onEndDrawerChanged, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class PageBackButton extends StatelessWidget { | ||||||
|  |   const PageBackButton({super.key}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return BackButton( | ||||||
|  |       onPressed: () { | ||||||
|  |         GoRouter.of(context).pop(); | ||||||
|  |       }, | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @@ -57,10 +102,11 @@ class AppRootScaffold extends StatelessWidget { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|  |     final cfg = context.watch<ConfigProvider>(); | ||||||
|     final devicePixelRatio = MediaQuery.of(context).devicePixelRatio; |     final devicePixelRatio = MediaQuery.of(context).devicePixelRatio; | ||||||
|  |  | ||||||
|     final isCollapseDrawer = ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE); |     final isCollapseDrawer = cfg.drawerIsCollapsed; | ||||||
|     final isExpandDrawer = ResponsiveBreakpoints.of(context).largerThan(TABLET); |     final isExpandedDrawer = cfg.drawerIsExpanded; | ||||||
|  |  | ||||||
|     final routeName = GoRouter.of(context).routerDelegate.currentConfiguration.last.route.name; |     final routeName = GoRouter.of(context).routerDelegate.currentConfiguration.last.route.name; | ||||||
|     final isShowBottomNavigation = NavigationProvider.kShowBottomNavScreen.contains(routeName) |     final isShowBottomNavigation = NavigationProvider.kShowBottomNavScreen.contains(routeName) | ||||||
| @@ -81,7 +127,7 @@ class AppRootScaffold extends StatelessWidget { | |||||||
|                     ), |                     ), | ||||||
|                   ), |                   ), | ||||||
|                 ), |                 ), | ||||||
|                 child: isExpandDrawer ? AppNavigationDrawer(elevation: 0) : AppRailNavigation(), |                 child: isExpandedDrawer ? AppNavigationDrawer(elevation: 0) : AppRailNavigation(), | ||||||
|               ), |               ), | ||||||
|               Expanded(child: body), |               Expanded(child: body), | ||||||
|             ], |             ], | ||||||
| @@ -95,14 +141,18 @@ class AppRootScaffold extends StatelessWidget { | |||||||
|       iconMouseDown: Theme.of(context).colorScheme.primary, |       iconMouseDown: Theme.of(context).colorScheme.primary, | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     return AppBackground( |     final safeTop = MediaQuery.of(context).padding.top; | ||||||
|       isRoot: true, |  | ||||||
|       child: Scaffold( |     return Scaffold( | ||||||
|       key: globalRootScaffoldKey, |       key: globalRootScaffoldKey, | ||||||
|         body: Column( |       backgroundColor: Theme.of(context).colorScheme.surface, | ||||||
|  |       body: Stack( | ||||||
|  |         children: [ | ||||||
|  |           Column( | ||||||
|             children: [ |             children: [ | ||||||
|               if (!kIsWeb && (Platform.isWindows || Platform.isLinux || Platform.isMacOS)) |               if (!kIsWeb && (Platform.isWindows || Platform.isLinux || Platform.isMacOS)) | ||||||
|               Container( |                 WindowTitleBarBox( | ||||||
|  |                   child: Container( | ||||||
|                     decoration: BoxDecoration( |                     decoration: BoxDecoration( | ||||||
|                       border: Border( |                       border: Border( | ||||||
|                         bottom: BorderSide( |                         bottom: BorderSide( | ||||||
| @@ -111,22 +161,18 @@ class AppRootScaffold extends StatelessWidget { | |||||||
|                         ), |                         ), | ||||||
|                       ), |                       ), | ||||||
|                     ), |                     ), | ||||||
|  |                     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: [ | ||||||
|                     WindowTitleBarBox( |                           Text( | ||||||
|                       child: MoveWindow( |  | ||||||
|                         child: Text( |  | ||||||
|                             'Solar Network', |                             'Solar Network', | ||||||
|                             style: GoogleFonts.spaceGrotesk(), |                             style: GoogleFonts.spaceGrotesk(), | ||||||
|                           ).padding(horizontal: 12, vertical: 5), |                           ).padding(horizontal: 12, vertical: 5), | ||||||
|                       ), |  | ||||||
|                     ), |  | ||||||
|                           if (!Platform.isMacOS) |                           if (!Platform.isMacOS) | ||||||
|                       Expanded( |                             Row( | ||||||
|                         child: WindowTitleBarBox( |                               mainAxisSize: MainAxisSize.min, | ||||||
|                           child: Row( |  | ||||||
|                               children: [ |                               children: [ | ||||||
|                                 Expanded(child: MoveWindow()), |                                 Expanded(child: MoveWindow()), | ||||||
|                                 Row( |                                 Row( | ||||||
| @@ -138,19 +184,21 @@ class AppRootScaffold extends StatelessWidget { | |||||||
|                                 ), |                                 ), | ||||||
|                               ], |                               ], | ||||||
|                             ), |                             ), | ||||||
|                         ), |  | ||||||
|                       ), |  | ||||||
|                         ], |                         ], | ||||||
|                       ), |                       ), | ||||||
|                     ), |                     ), | ||||||
|             ConnectionIndicator(), |                   ), | ||||||
|  |                 ), | ||||||
|               Expanded(child: innerWidget), |               Expanded(child: innerWidget), | ||||||
|             ], |             ], | ||||||
|           ), |           ), | ||||||
|         drawer: !isExpandDrawer ? AppNavigationDrawer() : null, |           Positioned(top: safeTop > 0 ? safeTop : 16, right: 8, child: NotifyIndicator()), | ||||||
|  |           Positioned(top: safeTop > 0 ? safeTop : 16, left: 8, child: ConnectionIndicator()), | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|  |       drawer: !isExpandedDrawer ? AppNavigationDrawer() : null, | ||||||
|       drawerEdgeDragWidth: isPopable ? 0 : null, |       drawerEdgeDragWidth: isPopable ? 0 : null, | ||||||
|       bottomNavigationBar: isShowBottomNavigation ? AppBottomNavigationBar() : null, |       bottomNavigationBar: isShowBottomNavigation ? AppBottomNavigationBar() : null, | ||||||
|       ), |  | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										63
									
								
								lib/widgets/notify_indicator.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								lib/widgets/notify_indicator.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | |||||||
|  | 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/notification.dart'; | ||||||
|  | import 'package:surface/providers/userinfo.dart'; | ||||||
|  |  | ||||||
|  | class NotifyIndicator extends StatelessWidget { | ||||||
|  |   const NotifyIndicator({super.key}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     final ua = context.read<UserProvider>(); | ||||||
|  |     final nty = context.watch<NotificationProvider>(); | ||||||
|  |  | ||||||
|  |     final show = nty.notifications.isNotEmpty && ua.isAuthorized; | ||||||
|  |  | ||||||
|  |     return ListenableBuilder( | ||||||
|  |         listenable: nty, | ||||||
|  |         builder: (context, _) { | ||||||
|  |           return IgnorePointer( | ||||||
|  |             ignoring: !show, | ||||||
|  |             child: GestureDetector( | ||||||
|  |               child: Material( | ||||||
|  |                 elevation: 2, | ||||||
|  |                 shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))), | ||||||
|  |                 color: Theme.of(context).colorScheme.secondaryContainer, | ||||||
|  |                 child: ua.isAuthorized | ||||||
|  |                     ? Row( | ||||||
|  |                         mainAxisAlignment: MainAxisAlignment.center, | ||||||
|  |                         crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|  |                         children: [ | ||||||
|  |                           Text( | ||||||
|  |                             nty.notifications.lastOrNull?.title ?? | ||||||
|  |                                 'notificationUnreadCount'.plural(nty.notifications.length), | ||||||
|  |                             maxLines: 1, | ||||||
|  |                             overflow: TextOverflow.ellipsis, | ||||||
|  |                           ), | ||||||
|  |                           if (nty.notifications.lastOrNull?.body != null) | ||||||
|  |                             Text( | ||||||
|  |                               nty.notifications.lastOrNull!.body, | ||||||
|  |                               maxLines: 1, | ||||||
|  |                               overflow: TextOverflow.ellipsis, | ||||||
|  |                             ).padding(left: 4), | ||||||
|  |                           const Gap(8), | ||||||
|  |                           const Icon(Symbols.notifications_unread, size: 18), | ||||||
|  |                         ], | ||||||
|  |                       ).padding(horizontal: 8, vertical: 4) | ||||||
|  |                     : const SizedBox.shrink(), | ||||||
|  |               ).opacity(show ? 1 : 0, animate: true).animate( | ||||||
|  |                     const Duration(milliseconds: 300), | ||||||
|  |                     Curves.easeInOut, | ||||||
|  |                   ), | ||||||
|  |               onTap: () { | ||||||
|  |                 nty.clear(); | ||||||
|  |               }, | ||||||
|  |             ), | ||||||
|  |           ); | ||||||
|  |         }); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -20,6 +20,7 @@ import 'package:styled_widget/styled_widget.dart'; | |||||||
| import 'package:surface/providers/config.dart'; | import 'package:surface/providers/config.dart'; | ||||||
| import 'package:surface/providers/sn_network.dart'; | import 'package:surface/providers/sn_network.dart'; | ||||||
| import 'package:surface/providers/userinfo.dart'; | import 'package:surface/providers/userinfo.dart'; | ||||||
|  | import 'package:surface/types/attachment.dart'; | ||||||
| import 'package:surface/types/post.dart'; | import 'package:surface/types/post.dart'; | ||||||
| import 'package:surface/types/reaction.dart'; | import 'package:surface/types/reaction.dart'; | ||||||
| import 'package:surface/widgets/account/account_image.dart'; | import 'package:surface/widgets/account/account_image.dart'; | ||||||
| @@ -112,7 +113,7 @@ class PostItem extends StatelessWidget { | |||||||
|         sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size, |         sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size, | ||||||
|       ); |       ); | ||||||
|     } else { |     } else { | ||||||
|       await FileSaver.instance.saveFile(name: 'Solar Network Post #${data.id}', file: imageFile); |       await FileSaver.instance.saveFile(name: 'Solar Network Post #${data.id}.png', file: imageFile); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     await imageFile.delete(); |     await imageFile.delete(); | ||||||
| @@ -198,6 +199,12 @@ class PostItem extends StatelessWidget { | |||||||
|       ).center(); |       ).center(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     final displayableAttachments = data.preload?.attachments | ||||||
|  |         ?.where((ele) => ele?.mediaType != SnMediaType.image || data.type != 'article') | ||||||
|  |         .toList(); | ||||||
|  |  | ||||||
|  |     final cfg = context.read<ConfigProvider>(); | ||||||
|  |  | ||||||
|     return Column( |     return Column( | ||||||
|       crossAxisAlignment: CrossAxisAlignment.center, |       crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|       children: [ |       children: [ | ||||||
| @@ -247,17 +254,16 @@ class PostItem extends StatelessWidget { | |||||||
|             ], |             ], | ||||||
|           ), |           ), | ||||||
|         ), |         ), | ||||||
|         if ((data.preload?.attachments?.isNotEmpty ?? false) && data.type != 'article') |         if (displayableAttachments?.isNotEmpty ?? false) | ||||||
|           AttachmentList( |           AttachmentList( | ||||||
|             data: data.preload!.attachments!, |             data: displayableAttachments!, | ||||||
|             bordered: true, |             bordered: true, | ||||||
|             gridded: true, |  | ||||||
|             maxHeight: showFullPost ? null : 480, |             maxHeight: showFullPost ? null : 480, | ||||||
|             minWidth: 640, |             maxWidth: MediaQuery.of(context).size.width - 20, | ||||||
|             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.body['content'] != null) |         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), | ||||||
| @@ -339,7 +345,7 @@ class PostShareImageWidget extends StatelessWidget { | |||||||
|           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!, | ||||||
|               gridded: true, |               columned: true, | ||||||
|             )).padding(horizontal: 16, bottom: 8), |             )).padding(horizontal: 16, bottom: 8), | ||||||
|           Column( |           Column( | ||||||
|             crossAxisAlignment: CrossAxisAlignment.start, |             crossAxisAlignment: CrossAxisAlignment.start, | ||||||
| @@ -874,12 +880,18 @@ class _PostContentBody extends StatelessWidget { | |||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     if (data.body['content'] == null) return const SizedBox.shrink(); |     if (data.body['content'] == null) return const SizedBox.shrink(); | ||||||
|     return MarkdownTextContent( |     final content = MarkdownTextContent( | ||||||
|       isSelectable: isSelectable, |       isAutoWarp: data.type == 'story', | ||||||
|  |       isEnlargeSticker: true, | ||||||
|       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, | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|  |     if (isSelectable) { | ||||||
|  |       return SelectionArea(child: content); | ||||||
|  |     } | ||||||
|  |     return content; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -21,6 +21,7 @@ import 'package:surface/providers/sn_network.dart'; | |||||||
| import 'package:surface/types/attachment.dart'; | import 'package:surface/types/attachment.dart'; | ||||||
| import 'package:surface/widgets/attachment/attachment_input.dart'; | import 'package:surface/widgets/attachment/attachment_input.dart'; | ||||||
| import 'package:surface/widgets/attachment/attachment_zoom.dart'; | import 'package:surface/widgets/attachment/attachment_zoom.dart'; | ||||||
|  | import 'package:surface/widgets/attachment/pending_attachment_alt.dart'; | ||||||
| import 'package:surface/widgets/attachment/pending_attachment_boost.dart'; | import 'package:surface/widgets/attachment/pending_attachment_boost.dart'; | ||||||
| import 'package:surface/widgets/context_menu.dart'; | import 'package:surface/widgets/context_menu.dart'; | ||||||
| import 'package:surface/widgets/dialog.dart'; | import 'package:surface/widgets/dialog.dart'; | ||||||
| @@ -157,6 +158,16 @@ class PostMediaPendingList extends StatelessWidget { | |||||||
|     onUpdate!(idx, result); |     onUpdate!(idx, result); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   Future<void> _setAlt(BuildContext context, int idx) async { | ||||||
|  |     final result = await showDialog<SnAttachment?>( | ||||||
|  |       context: context, | ||||||
|  |       builder: (context) => PendingAttachmentAltDialog(media: attachments[idx]), | ||||||
|  |     ); | ||||||
|  |     if (result == null) return; | ||||||
|  |  | ||||||
|  |     onUpdate!(idx, PostWriteMedia(result)); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   ContextMenu _createContextMenu(BuildContext context, int idx, PostWriteMedia media) { |   ContextMenu _createContextMenu(BuildContext context, int idx, PostWriteMedia media) { | ||||||
|     final canCompressVideo = !kIsWeb && (Platform.isAndroid || Platform.isIOS || Platform.isMacOS); |     final canCompressVideo = !kIsWeb && (Platform.isAndroid || Platform.isIOS || Platform.isMacOS); | ||||||
|     return ContextMenu( |     return ContextMenu( | ||||||
| @@ -169,6 +180,14 @@ class PostMediaPendingList extends StatelessWidget { | |||||||
|               _compressVideo(context, idx); |               _compressVideo(context, idx); | ||||||
|             }, |             }, | ||||||
|           ), |           ), | ||||||
|  |         if (media.attachment != null) | ||||||
|  |           MenuItem( | ||||||
|  |             label: 'attachmentSetAlt'.tr(), | ||||||
|  |             icon: Symbols.description, | ||||||
|  |             onSelected: () { | ||||||
|  |               _setAlt(context, idx); | ||||||
|  |             }, | ||||||
|  |           ), | ||||||
|         if (media.attachment != null) |         if (media.attachment != null) | ||||||
|           MenuItem( |           MenuItem( | ||||||
|             label: 'attachmentBoost'.tr(), |             label: 'attachmentBoost'.tr(), | ||||||
|   | |||||||
| @@ -25,7 +25,7 @@ class PostMiniEditor extends StatefulWidget { | |||||||
| } | } | ||||||
|  |  | ||||||
| class _PostMiniEditorState extends State<PostMiniEditor> { | class _PostMiniEditorState extends State<PostMiniEditor> { | ||||||
|   final PostWriteController _writeController = PostWriteController(); |   final PostWriteController _writeController = PostWriteController(doLoadFromTemporary: false); | ||||||
|  |  | ||||||
|   bool _isFetching = false; |   bool _isFetching = false; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| import 'package:easy_localization/easy_localization.dart'; | import 'package:easy_localization/easy_localization.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:flutter/services.dart'; | ||||||
| import 'package:gap/gap.dart'; | 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'; | ||||||
| @@ -12,6 +13,7 @@ import 'package:surface/widgets/dialog.dart'; | |||||||
| class PostReactionPopup extends StatefulWidget { | class PostReactionPopup extends StatefulWidget { | ||||||
|   final SnPost data; |   final SnPost data; | ||||||
|   final Function(Map<String, int> value, int attr, int delta)? onChanged; |   final Function(Map<String, int> value, int attr, int delta)? onChanged; | ||||||
|  |  | ||||||
|   const PostReactionPopup({super.key, required this.data, this.onChanged}); |   const PostReactionPopup({super.key, required this.data, this.onChanged}); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
| @@ -59,6 +61,7 @@ class _PostReactionPopupState extends State<PostReactionPopup> { | |||||||
|           ); |           ); | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|  |       HapticFeedback.mediumImpact(); | ||||||
|     } catch (err) { |     } catch (err) { | ||||||
|       // ignore: use_build_context_synchronously |       // ignore: use_build_context_synchronously | ||||||
|       if (context.mounted) context.showErrorDialog(err); |       if (context.mounted) context.showErrorDialog(err); | ||||||
| @@ -84,9 +87,7 @@ class _PostReactionPopupState extends State<PostReactionPopup> { | |||||||
|             children: [ |             children: [ | ||||||
|               const Icon(Symbols.mood, size: 24), |               const Icon(Symbols.mood, size: 24), | ||||||
|               const Gap(16), |               const Gap(16), | ||||||
|               Text('postReactions') |               Text('postReactions').tr().textStyle(Theme.of(context).textTheme.titleLarge!), | ||||||
|                   .tr() |  | ||||||
|                   .textStyle(Theme.of(context).textTheme.titleLarge!), |  | ||||||
|             ], |             ], | ||||||
|           ).padding(horizontal: 20, top: 16, bottom: 12), |           ).padding(horizontal: 20, top: 16, bottom: 12), | ||||||
|           Container( |           Container( | ||||||
| @@ -102,9 +103,7 @@ class _PostReactionPopupState extends State<PostReactionPopup> { | |||||||
|                 Text('postReactionDownvote').plural(widget.data.totalDownvote), |                 Text('postReactionDownvote').plural(widget.data.totalDownvote), | ||||||
|                 const Gap(24), |                 const Gap(24), | ||||||
|                 Icon( |                 Icon( | ||||||
|                   widget.data.totalUpvote >= widget.data.totalDownvote |                   widget.data.totalUpvote >= widget.data.totalDownvote ? Symbols.trending_up : Symbols.trending_down, | ||||||
|                       ? Symbols.trending_up |  | ||||||
|                       : Symbols.trending_down, |  | ||||||
|                   size: 16, |                   size: 16, | ||||||
|                 ), |                 ), | ||||||
|                 const Gap(8), |                 const Gap(8), | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| import 'package:extended_image/extended_image.dart'; | import 'package:cached_network_image/cached_network_image.dart'; | ||||||
| import 'package:flutter/foundation.dart'; | import 'package:flutter/foundation.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:material_symbols_icons/symbols.dart'; | import 'package:material_symbols_icons/symbols.dart'; | ||||||
| @@ -7,6 +7,7 @@ import 'package:styled_widget/styled_widget.dart'; | |||||||
| import 'package:flutter_animate/flutter_animate.dart'; | import 'package:flutter_animate/flutter_animate.dart'; | ||||||
|  |  | ||||||
| // Keep this import to make the web image render work | // Keep this import to make the web image render work | ||||||
|  | import 'package:cached_network_image_platform_interface/cached_network_image_platform_interface.dart'; | ||||||
| import 'package:surface/providers/config.dart'; | import 'package:surface/providers/config.dart'; | ||||||
|  |  | ||||||
| class UniversalImage extends StatelessWidget { | class UniversalImage extends StatelessWidget { | ||||||
| @@ -33,27 +34,48 @@ class UniversalImage extends StatelessWidget { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     final quality = filterQuality ?? context.read<ConfigProvider>().imageQuality; |     final devicePixelRatio = MediaQuery.of(context).devicePixelRatio; | ||||||
|  |     final double? resizeHeight = cacheHeight != null ? (cacheHeight! * devicePixelRatio) : null; | ||||||
|  |     final double? resizeWidth = cacheWidth != null ? (cacheWidth! * devicePixelRatio) : null; | ||||||
|  |  | ||||||
|     return ExtendedImage.network( |     return Image( | ||||||
|       url, |       filterQuality: filterQuality ?? context.read<ConfigProvider>().imageQuality, | ||||||
|  |       image: kIsWeb | ||||||
|  |           ? UniversalImage.provider(url) | ||||||
|  |           : ResizeImage( | ||||||
|  |               UniversalImage.provider(url), | ||||||
|  |               width: resizeWidth?.round(), | ||||||
|  |               height: resizeHeight?.round(), | ||||||
|  |               policy: ResizeImagePolicy.fit, | ||||||
|  |             ), | ||||||
|       width: width, |       width: width, | ||||||
|       height: height, |       height: height, | ||||||
|       fit: fit, |       fit: fit, | ||||||
|       cache: true, |       loadingBuilder: noProgressIndicator | ||||||
|       compressionRatio: kIsWeb ? 1 : switch(quality) { |           ? null | ||||||
|         FilterQuality.high => 1, |           : (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) { | ||||||
|         FilterQuality.medium => 0.75, |               if (loadingProgress == null) return child; | ||||||
|         FilterQuality.low => 0.5, |               return Container( | ||||||
|         FilterQuality.none => 0.25, |                 constraints: BoxConstraints(maxHeight: 80), | ||||||
|  |                 child: Center( | ||||||
|  |                   child: TweenAnimationBuilder( | ||||||
|  |                     tween: Tween( | ||||||
|  |                       begin: 0, | ||||||
|  |                       end: loadingProgress.expectedTotalBytes != null | ||||||
|  |                           ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes! | ||||||
|  |                           : 0, | ||||||
|  |                     ), | ||||||
|  |                     duration: const Duration(milliseconds: 300), | ||||||
|  |                     builder: (context, value, _) => CircularProgressIndicator( | ||||||
|  |                       value: loadingProgress.expectedTotalBytes != null ? value.toDouble() : null, | ||||||
|  |                     ), | ||||||
|  |                   ), | ||||||
|  |                 ), | ||||||
|  |               ); | ||||||
|             }, |             }, | ||||||
|       filterQuality: quality, |       errorBuilder: noErrorWidget | ||||||
|       enableLoadState: true, |           ? null | ||||||
|       retries: 3, |           : (context, error, stackTrace) { | ||||||
|       loadStateChanged: (ExtendedImageState state) { |  | ||||||
|         if (state.extendedImageLoadState == LoadState.completed) { |  | ||||||
|           return state.completedWidget; |  | ||||||
|         } else if (state.extendedImageLoadState == LoadState.failed) { |  | ||||||
|               return Material( |               return Material( | ||||||
|                 color: Theme.of(context).colorScheme.surface, |                 color: Theme.of(context).colorScheme.surface, | ||||||
|                 child: Container( |                 child: Container( | ||||||
| @@ -65,21 +87,13 @@ class UniversalImage extends StatelessWidget { | |||||||
|                           .animate(onPlay: (e) => e.repeat(reverse: true)) |                           .animate(onPlay: (e) => e.repeat(reverse: true)) | ||||||
|                           .fade(duration: 500.ms), |                           .fade(duration: 500.ms), | ||||||
|                       Text( |                       Text( | ||||||
|                     state.lastException.toString(), |                         error.toString(), | ||||||
|                         textAlign: TextAlign.center, |                         textAlign: TextAlign.center, | ||||||
|                       ), |                       ), | ||||||
|                     ], |                     ], | ||||||
|                   ).center(), |                   ).center(), | ||||||
|                 ), |                 ), | ||||||
|               ); |               ); | ||||||
|         } |  | ||||||
|         return Center( |  | ||||||
|           child: CircularProgressIndicator( |  | ||||||
|             value: state.loadingProgress != null |  | ||||||
|                 ? state.loadingProgress!.cumulativeBytesLoaded / state.loadingProgress!.expectedTotalBytes! |  | ||||||
|                 : null, |  | ||||||
|           ), |  | ||||||
|         ); |  | ||||||
|             }, |             }, | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| @@ -88,10 +102,9 @@ class UniversalImage extends StatelessWidget { | |||||||
|     // This place used to use network image or cached network image depending on the platform. |     // This place used to use network image or cached network image depending on the platform. | ||||||
|     // But now the cached network image is working on every platform. |     // But now the cached network image is working on every platform. | ||||||
|     // So we just use it now. |     // So we just use it now. | ||||||
|     return ExtendedNetworkImageProvider( |     return CachedNetworkImageProvider( | ||||||
|       url, |       url, | ||||||
|       cache: true, |       imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet, | ||||||
|       retries: 3, |  | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -26,6 +26,7 @@ import path_provider_foundation | |||||||
| import screen_brightness_macos | import screen_brightness_macos | ||||||
| import share_plus | import share_plus | ||||||
| import shared_preferences_foundation | import shared_preferences_foundation | ||||||
|  | import sqflite_darwin | ||||||
| import url_launcher_macos | import url_launcher_macos | ||||||
| import video_compress | import video_compress | ||||||
| import wakelock_plus | import wakelock_plus | ||||||
| @@ -52,6 +53,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { | |||||||
|   ScreenBrightnessMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenBrightnessMacosPlugin")) |   ScreenBrightnessMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenBrightnessMacosPlugin")) | ||||||
|   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")) | ||||||
|   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")) | ||||||
|   WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) |   WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) | ||||||
|   | |||||||
| @@ -12,59 +12,59 @@ PODS: | |||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|   - file_selector_macos (0.0.1): |   - file_selector_macos (0.0.1): | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|   - Firebase/Analytics (11.4.0): |   - Firebase/Analytics (11.6.0): | ||||||
|     - Firebase/Core |     - Firebase/Core | ||||||
|   - Firebase/Core (11.4.0): |   - Firebase/Core (11.6.0): | ||||||
|     - Firebase/CoreOnly |     - Firebase/CoreOnly | ||||||
|     - FirebaseAnalytics (~> 11.4.0) |     - FirebaseAnalytics (~> 11.6.0) | ||||||
|   - Firebase/CoreOnly (11.4.0): |   - Firebase/CoreOnly (11.6.0): | ||||||
|     - FirebaseCore (= 11.4.0) |     - FirebaseCore (~> 11.6.0) | ||||||
|   - Firebase/Messaging (11.4.0): |   - Firebase/Messaging (11.6.0): | ||||||
|     - Firebase/CoreOnly |     - Firebase/CoreOnly | ||||||
|     - FirebaseMessaging (~> 11.4.0) |     - FirebaseMessaging (~> 11.6.0) | ||||||
|   - firebase_analytics (11.3.6): |   - firebase_analytics (11.4.0): | ||||||
|     - Firebase/Analytics (= 11.4.0) |     - Firebase/Analytics (= 11.6.0) | ||||||
|     - firebase_core |     - firebase_core | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|   - firebase_core (3.9.0): |   - firebase_core (3.10.0): | ||||||
|     - Firebase/CoreOnly (~> 11.4.0) |     - Firebase/CoreOnly (~> 11.6.0) | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|   - firebase_messaging (15.1.6): |   - firebase_messaging (15.2.0): | ||||||
|     - Firebase/CoreOnly (~> 11.4.0) |     - Firebase/CoreOnly (~> 11.6.0) | ||||||
|     - Firebase/Messaging (~> 11.4.0) |     - Firebase/Messaging (~> 11.6.0) | ||||||
|     - firebase_core |     - firebase_core | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|   - FirebaseAnalytics (11.4.0): |   - FirebaseAnalytics (11.6.0): | ||||||
|     - FirebaseAnalytics/AdIdSupport (= 11.4.0) |     - FirebaseAnalytics/AdIdSupport (= 11.6.0) | ||||||
|     - FirebaseCore (~> 11.0) |     - FirebaseCore (~> 11.6.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.4.0): |   - FirebaseAnalytics/AdIdSupport (11.6.0): | ||||||
|     - FirebaseCore (~> 11.0) |     - FirebaseCore (~> 11.6.0) | ||||||
|     - FirebaseInstallations (~> 11.0) |     - FirebaseInstallations (~> 11.0) | ||||||
|     - GoogleAppMeasurement (= 11.4.0) |     - GoogleAppMeasurement (= 11.6.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.4.0): |   - FirebaseCore (11.6.0): | ||||||
|     - FirebaseCoreInternal (~> 11.0) |     - FirebaseCoreInternal (~> 11.6.0) | ||||||
|     - GoogleUtilities/Environment (~> 8.0) |     - GoogleUtilities/Environment (~> 8.0) | ||||||
|     - GoogleUtilities/Logger (~> 8.0) |     - GoogleUtilities/Logger (~> 8.0) | ||||||
|   - FirebaseCoreInternal (11.6.0): |   - FirebaseCoreInternal (11.6.0): | ||||||
|     - "GoogleUtilities/NSData+zlib (~> 8.0)" |     - "GoogleUtilities/NSData+zlib (~> 8.0)" | ||||||
|   - FirebaseInstallations (11.4.0): |   - FirebaseInstallations (11.6.0): | ||||||
|     - FirebaseCore (~> 11.0) |     - FirebaseCore (~> 11.6.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.4.0): |   - FirebaseMessaging (11.6.0): | ||||||
|     - FirebaseCore (~> 11.0) |     - FirebaseCore (~> 11.6.0) | ||||||
|     - FirebaseInstallations (~> 11.0) |     - FirebaseInstallations (~> 11.0) | ||||||
|     - GoogleDataTransport (~> 10.0) |     - GoogleDataTransport (~> 10.0) | ||||||
|     - GoogleUtilities/AppDelegateSwizzler (~> 8.0) |     - GoogleUtilities/AppDelegateSwizzler (~> 8.0) | ||||||
| @@ -75,28 +75,28 @@ PODS: | |||||||
|   - flutter_udid (0.0.1): |   - flutter_udid (0.0.1): | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|     - SAMKeychain |     - SAMKeychain | ||||||
|   - flutter_webrtc (0.12.2): |   - flutter_webrtc (0.12.6): | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|     - WebRTC-SDK (= 125.6422.06) |     - WebRTC-SDK (= 125.6422.06) | ||||||
|   - FlutterMacOS (1.0.0) |   - FlutterMacOS (1.0.0) | ||||||
|   - gal (1.0.0): |   - gal (1.0.0): | ||||||
|     - Flutter |     - Flutter | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|   - GoogleAppMeasurement (11.4.0): |   - GoogleAppMeasurement (11.6.0): | ||||||
|     - GoogleAppMeasurement/AdIdSupport (= 11.4.0) |     - GoogleAppMeasurement/AdIdSupport (= 11.6.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.4.0): |   - GoogleAppMeasurement/AdIdSupport (11.6.0): | ||||||
|     - GoogleAppMeasurement/WithoutAdIdSupport (= 11.4.0) |     - GoogleAppMeasurement/WithoutAdIdSupport (= 11.6.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.4.0): |   - GoogleAppMeasurement/WithoutAdIdSupport (11.6.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) | ||||||
| @@ -134,7 +134,7 @@ PODS: | |||||||
|     - GoogleUtilities/Privacy |     - GoogleUtilities/Privacy | ||||||
|   - in_app_review (2.0.0): |   - in_app_review (2.0.0): | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|   - livekit_client (2.3.4): |   - livekit_client (2.3.5): | ||||||
|     - flutter_webrtc |     - flutter_webrtc | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|     - WebRTC-SDK (= 125.6422.06) |     - WebRTC-SDK (= 125.6422.06) | ||||||
| @@ -165,6 +165,9 @@ PODS: | |||||||
|   - shared_preferences_foundation (0.0.1): |   - shared_preferences_foundation (0.0.1): | ||||||
|     - Flutter |     - Flutter | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|  |   - sqflite_darwin (0.0.4): | ||||||
|  |     - Flutter | ||||||
|  |     - FlutterMacOS | ||||||
|   - url_launcher_macos (0.0.1): |   - url_launcher_macos (0.0.1): | ||||||
|     - FlutterMacOS |     - FlutterMacOS | ||||||
|   - video_compress (0.3.0): |   - video_compress (0.3.0): | ||||||
| @@ -198,6 +201,7 @@ DEPENDENCIES: | |||||||
|   - screen_brightness_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_brightness_macos/macos`) |   - screen_brightness_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_brightness_macos/macos`) | ||||||
|   - 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`) | ||||||
|   - 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`) | ||||||
|   - wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`) |   - wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`) | ||||||
| @@ -267,6 +271,8 @@ EXTERNAL SOURCES: | |||||||
|     :path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos |     :path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos | ||||||
|   shared_preferences_foundation: |   shared_preferences_foundation: | ||||||
|     :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin |     :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin | ||||||
|  |   sqflite_darwin: | ||||||
|  |     :path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin | ||||||
|   url_launcher_macos: |   url_launcher_macos: | ||||||
|     :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos |     :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos | ||||||
|   video_compress: |   video_compress: | ||||||
| @@ -281,24 +287,24 @@ SPEC CHECKSUMS: | |||||||
|   device_info_plus: 1b14eed9bf95428983aed283a8d51cce3d8c4215 |   device_info_plus: 1b14eed9bf95428983aed283a8d51cce3d8c4215 | ||||||
|   file_saver: 44e6fbf666677faf097302460e214e977fdd977b |   file_saver: 44e6fbf666677faf097302460e214e977fdd977b | ||||||
|   file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d |   file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d | ||||||
|   Firebase: cf1b19f21410b029b6786a54e9764a0cacad3c99 |   Firebase: 374a441a91ead896215703a674d58cdb3e9d772b | ||||||
|   firebase_analytics: a80b3d6645f2f12d626fde928b61dae12e5ea2ef |   firebase_analytics: 5249f87da6fed852901581aab2602e0280ec2fdb | ||||||
|   firebase_core: 1dfe1f4d02ad78be0277e320aa3d8384cf46231f |   firebase_core: 6d9bb8b0ea817e8fe0d928177d42275b45fdba6f | ||||||
|   firebase_messaging: 61f678060b69a7ae1013e3a939ec8e1c56ef6fcf |   firebase_messaging: ae8e88b586e4d50abc7cac5bacf74d21967fd226 | ||||||
|   FirebaseAnalytics: 3feef9ae8733c567866342a1000691baaa7cad49 |   FirebaseAnalytics: 7114c698cac995602e3b1b96663473e50d54d6e7 | ||||||
|   FirebaseCore: e0510f1523bc0eb21653cac00792e1e2bd6f1771 |   FirebaseCore: 48b0dd707581cf9c1a1220da68223fb0a562afaa | ||||||
|   FirebaseCoreInternal: d98ab91e2d80a56d7b246856a8885443b302c0c2 |   FirebaseCoreInternal: d98ab91e2d80a56d7b246856a8885443b302c0c2 | ||||||
|   FirebaseInstallations: 6ef4a1c7eb2a61ee1f74727d7f6ce2e72acf1414 |   FirebaseInstallations: efc0946fc756e4d22d8113f7c761948120322e8c | ||||||
|   FirebaseMessaging: f8a160d99c2c2e5babbbcc90c4a3e15db036aee2 |   FirebaseMessaging: e1aca1fcc23e8b9eddb0e33f375ff90944623021 | ||||||
|   flutter_udid: 2e7b3da4b5fdfba86a396b97898f5fe8f4ec1a52 |   flutter_udid: 2e7b3da4b5fdfba86a396b97898f5fe8f4ec1a52 | ||||||
|   flutter_webrtc: 53c9e1285ab32dfb58afb1e1471416a877e23d7a |   flutter_webrtc: d55fd3f5c75b42940b6b4b2cf376a5797398d1f8 | ||||||
|   FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 |   FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 | ||||||
|   gal: 6a522c75909f1244732d4596d11d6a2f86ff37a5 |   gal: 6a522c75909f1244732d4596d11d6a2f86ff37a5 | ||||||
|   GoogleAppMeasurement: 987769c4ca6b968f2479fbcc9fe3ce34af454b8e |   GoogleAppMeasurement: 6a9e6317b6a6d810ad03d4a66564ca6c4c5818a3 | ||||||
|   GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 |   GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 | ||||||
|   GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d |   GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d | ||||||
|   in_app_review: a6a031b9acd03c7d103e341aa334adf2c493fb93 |   in_app_review: a6a031b9acd03c7d103e341aa334adf2c493fb93 | ||||||
|   livekit_client: b7ab91e79e657d7d40da16cb2f90d517cb72d406 |   livekit_client: 91c68237edede55f8891a166a28c1daec8a1e4b1 | ||||||
|   media_kit_libs_macos_video: b3e2bbec2eef97c285f2b1baa7963c67c753fb82 |   media_kit_libs_macos_video: b3e2bbec2eef97c285f2b1baa7963c67c753fb82 | ||||||
|   media_kit_native_event_loop: 81fd5b45192b72f8b5b69eaf5b540f45777eb8d5 |   media_kit_native_event_loop: 81fd5b45192b72f8b5b69eaf5b540f45777eb8d5 | ||||||
|   media_kit_video: c75b07f14d59706c775778e4dd47dd027de8d1e5 |   media_kit_video: c75b07f14d59706c775778e4dd47dd027de8d1e5 | ||||||
| @@ -311,6 +317,7 @@ SPEC CHECKSUMS: | |||||||
|   screen_brightness_macos: 2d6d3af2165592d9a55ffcd95b7550970e41ebda |   screen_brightness_macos: 2d6d3af2165592d9a55ffcd95b7550970e41ebda | ||||||
|   share_plus: 1fa619de8392a4398bfaf176d441853922614e89 |   share_plus: 1fa619de8392a4398bfaf176d441853922614e89 | ||||||
|   shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 |   shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 | ||||||
|  |   sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d | ||||||
|   url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404 |   url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404 | ||||||
|   video_compress: c896234f100791b5fef7f049afa38f6d2ef7b42f |   video_compress: c896234f100791b5fef7f049afa38f6d2ef7b42f | ||||||
|   wakelock_plus: 4783562c9a43d209c458cb9b30692134af456269 |   wakelock_plus: 4783562c9a43d209c458cb9b30692134af456269 | ||||||
|   | |||||||
							
								
								
									
										260
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										260
									
								
								pubspec.lock
									
									
									
									
									
								
							| @@ -13,10 +13,10 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: _flutterfire_internals |       name: _flutterfire_internals | ||||||
|       sha256: daa1d780fdecf8af925680c06c86563cdd445deea995d5c9176f1302a2b10bbe |       sha256: "27899c95f9e7ec06c8310e6e0eac967707714b9f1450c4a58fa00ca011a4a8ae" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.3.48" |     version: "1.3.49" | ||||||
|   _macros: |   _macros: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: dart |     description: dart | ||||||
| @@ -182,6 +182,30 @@ packages: | |||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "8.9.3" |     version: "8.9.3" | ||||||
|  |   cached_network_image: | ||||||
|  |     dependency: "direct main" | ||||||
|  |     description: | ||||||
|  |       name: cached_network_image | ||||||
|  |       sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916" | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "3.4.1" | ||||||
|  |   cached_network_image_platform_interface: | ||||||
|  |     dependency: transitive | ||||||
|  |     description: | ||||||
|  |       name: cached_network_image_platform_interface | ||||||
|  |       sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "4.1.1" | ||||||
|  |   cached_network_image_web: | ||||||
|  |     dependency: transitive | ||||||
|  |     description: | ||||||
|  |       name: cached_network_image_web | ||||||
|  |       sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062" | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "1.3.1" | ||||||
|   cassowary: |   cassowary: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -242,10 +266,10 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: connectivity_plus |       name: connectivity_plus | ||||||
|       sha256: e0817759ec6d2d8e57eb234e6e57d2173931367a865850c7acea40d4b4f9c27d |       sha256: "8a68739d3ee113e51ad35583fdf9ab82c55d09d693d3c39da1aebab87c938412" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "6.1.1" |     version: "6.1.2" | ||||||
|   connectivity_plus_platform_interface: |   connectivity_plus_platform_interface: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -266,10 +290,10 @@ packages: | |||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: croppy |       name: croppy | ||||||
|       sha256: "14bb40fd6c1771b093a907ddbf24df9aa49a4e6e379dd630602eb446e30ec629" |       sha256: bf99b00023df0d7d047e04d27d496d87cbefd968f578d0bd30f342ff75570a12 | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.3.1" |     version: "1.3.3" | ||||||
|   cross_file: |   cross_file: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
| @@ -330,18 +354,18 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: dbus |       name: dbus | ||||||
|       sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac" |       sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "0.7.10" |     version: "0.7.11" | ||||||
|   device_info_plus: |   device_info_plus: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: device_info_plus |       name: device_info_plus | ||||||
|       sha256: "4fa68e53e26ab17b70ca39f072c285562cfc1589df5bb1e9295db90f6645f431" |       sha256: b37d37c2f912ad4e8ec694187de87d05de2a3cb82b465ff1f65f65a2d05de544 | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "11.2.0" |     version: "11.2.1" | ||||||
|   device_info_plus_platform_interface: |   device_info_plus_platform_interface: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -430,22 +454,6 @@ packages: | |||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.0.7" |     version: "2.0.7" | ||||||
|   extended_image: |  | ||||||
|     dependency: "direct main" |  | ||||||
|     description: |  | ||||||
|       name: extended_image |  | ||||||
|       sha256: "93890a88d89ce017789f6c031c32ad8d2c685f1a5c25c169550746d973ca5e44" |  | ||||||
|       url: "https://pub.dev" |  | ||||||
|     source: hosted |  | ||||||
|     version: "9.0.9" |  | ||||||
|   extended_image_library: |  | ||||||
|     dependency: transitive |  | ||||||
|     description: |  | ||||||
|       name: extended_image_library |  | ||||||
|       sha256: "9a94ec9314aa206cfa35f16145c3cd6e2c924badcc670eaaca8a3a8063a68cd7" |  | ||||||
|       url: "https://pub.dev" |  | ||||||
|     source: hosted |  | ||||||
|     version: "4.0.5" |  | ||||||
|   fading_edge_scrollview: |   fading_edge_scrollview: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -530,34 +538,34 @@ packages: | |||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: firebase_analytics |       name: firebase_analytics | ||||||
|       sha256: "366140abb55418ea23060b779893fa997c2d8e1974a4d1cc4d9590933b65c5fd" |       sha256: "498c6cb8468e348a556709c745d92a52173ab3a9b906aa0593393f0787f201ea" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "11.3.6" |     version: "11.4.0" | ||||||
|   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: "8e987cf977c0c8f4ad02d9950a9b25b1a9606899f37b66a322a43af05be0246b" |       sha256: ccbb350554e98afdb4b59852689292d194d31232a2647b5012a66622b3711df9 | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "4.2.8" |     version: "4.3.0" | ||||||
|   firebase_analytics_web: |   firebase_analytics_web: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: firebase_analytics_web |       name: firebase_analytics_web | ||||||
|       sha256: "0b64ef9060d394bba3d3b4777f49ee098efeeea7b0afb04663c956de6a3da170" |       sha256: "68e1f18fc16482c211c658e739c25f015b202a260d9ad8249c6d3d7963b8105f" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "0.5.10+5" |     version: "0.5.10+6" | ||||||
|   firebase_core: |   firebase_core: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: firebase_core |       name: firebase_core | ||||||
|       sha256: "15d761b95dfa2906dfcc31b7fc6fe293188533d1a3ffe78389ba9e69bd7fdbde" |       sha256: "0307c1fde82e2b8b97e0be2dab93612aff9a72f31ebe9bfac66ed8b37ef7c568" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "3.9.0" |     version: "3.10.0" | ||||||
|   firebase_core_platform_interface: |   firebase_core_platform_interface: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -578,26 +586,26 @@ packages: | |||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: firebase_messaging |       name: firebase_messaging | ||||||
|       sha256: "151a3ee68736abf293aab66d1317ade53c88abe1db09c75a0460aebf7767bbdf" |       sha256: "48a8a59197c1c5174060ba9aa1e0036e9b5a0d28a0cc22d19c1fcabc67fafe3c" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "15.1.6" |     version: "15.2.0" | ||||||
|   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: f331ee51e40c243f90cc7bc059222dfec4e5df53125b08d31fb28961b00d2a9d |       sha256: "9770a8e91f54296829dcaa61ce9b7c2f9ae9abbf99976dd3103a60470d5264dd" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "4.5.49" |     version: "4.6.0" | ||||||
|   firebase_messaging_web: |   firebase_messaging_web: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: firebase_messaging_web |       name: firebase_messaging_web | ||||||
|       sha256: efaf3fdc54cd77e0eedb8e75f7f01c808828c64d052ddbf94d3009974e47d30f |       sha256: "329ca4ef45ec616abe6f1d5e58feed0934a50840a65aa327052354ad3c64ed77" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "3.9.5" |     version: "3.10.0" | ||||||
|   fixnum: |   fixnum: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -610,10 +618,10 @@ packages: | |||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: fl_chart |       name: fl_chart | ||||||
|       sha256: c724234b05e378383e958f3e82ca84a3e1e3c06a0898462044dd8a24b1ee9864 |       sha256: "5276944c6ffc975ae796569a826c38a62d2abcf264e26b88fa6f482e107f4237" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "0.70.0" |     version: "0.70.2" | ||||||
|   flutter: |   flutter: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: flutter |     description: flutter | ||||||
| @@ -635,6 +643,14 @@ packages: | |||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "3.2.2" |     version: "3.2.2" | ||||||
|  |   flutter_cache_manager: | ||||||
|  |     dependency: transitive | ||||||
|  |     description: | ||||||
|  |       name: flutter_cache_manager | ||||||
|  |       sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "3.4.1" | ||||||
|   flutter_colorpicker: |   flutter_colorpicker: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
| @@ -663,10 +679,10 @@ packages: | |||||||
|     dependency: "direct dev" |     dependency: "direct dev" | ||||||
|     description: |     description: | ||||||
|       name: flutter_launcher_icons |       name: flutter_launcher_icons | ||||||
|       sha256: "31cd0885738e87c72d6f055564d37fabcdacee743b396b78c7636c169cac64f5" |       sha256: bfa04787c85d80ecb3f8777bde5fc10c3de809240c48fa061a2c2bf15ea5211c | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "0.14.2" |     version: "0.14.3" | ||||||
|   flutter_lints: |   flutter_lints: | ||||||
|     dependency: "direct dev" |     dependency: "direct dev" | ||||||
|     description: |     description: | ||||||
| @@ -684,18 +700,18 @@ packages: | |||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: flutter_markdown |       name: flutter_markdown | ||||||
|       sha256: "255b00afa1a7bad19727da6a7780cf3db6c3c12e68d302d85e0ff1fdf173db9e" |       sha256: e37f4c69a07b07bb92622ef6b131a53c9aae48f64b176340af9e8e5238718487 | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "0.7.4+3" |     version: "0.7.5" | ||||||
|   flutter_native_splash: |   flutter_native_splash: | ||||||
|     dependency: "direct dev" |     dependency: "direct dev" | ||||||
|     description: |     description: | ||||||
|       name: flutter_native_splash |       name: flutter_native_splash | ||||||
|       sha256: "1152ab0067ca5a2ebeb862fe0a762057202cceb22b7e62692dcbabf6483891bb" |       sha256: "7062602e0dbd29141fb8eb19220b5871ca650be5197ab9c1f193a28b17537bc7" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.4.3" |     version: "2.4.4" | ||||||
|   flutter_plugin_android_lifecycle: |   flutter_plugin_android_lifecycle: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -724,10 +740,10 @@ packages: | |||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: flutter_svg |       name: flutter_svg | ||||||
|       sha256: "54900a1a1243f3c4a5506d853a2b5c2dbc38d5f27e52a52618a8054401431123" |       sha256: c200fd79c918a40c5cd50ea0877fa13f81bdaf6f0a5d3dbcc2a13e3285d6aa1b | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.0.16" |     version: "2.0.17" | ||||||
|   flutter_test: |   flutter_test: | ||||||
|     dependency: "direct dev" |     dependency: "direct dev" | ||||||
|     description: flutter |     description: flutter | ||||||
| @@ -750,10 +766,10 @@ packages: | |||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: flutter_webrtc |       name: flutter_webrtc | ||||||
|       sha256: "3efe9828f19a07d29a51a726759ad0c70a840d231548a1c7d0332075a94db1df" |       sha256: "188401cc3275bc4f1f965babdff6cac612a4b46572f1e49f49db8af5361d5712" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "0.12.5+hotfix.1" |     version: "0.12.6" | ||||||
|   freezed: |   freezed: | ||||||
|     dependency: "direct dev" |     dependency: "direct dev" | ||||||
|     description: |     description: | ||||||
| @@ -806,10 +822,10 @@ packages: | |||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: go_router |       name: go_router | ||||||
|       sha256: "2fd11229f59e23e967b0775df8d5948a519cd7e1e8b6e849729e010587b46539" |       sha256: "7c2d40b59890a929824f30d442e810116caf5088482629c894b9e4478c67472d" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "14.6.2" |     version: "14.6.3" | ||||||
|   google_fonts: |   google_fonts: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
| @@ -874,14 +890,6 @@ packages: | |||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.2.2" |     version: "1.2.2" | ||||||
|   http_client_helper: |  | ||||||
|     dependency: transitive |  | ||||||
|     description: |  | ||||||
|       name: http_client_helper |  | ||||||
|       sha256: "8a9127650734da86b5c73760de2b404494c968a3fd55602045ffec789dac3cb1" |  | ||||||
|       url: "https://pub.dev" |  | ||||||
|     source: hosted |  | ||||||
|     version: "3.0.0" |  | ||||||
|   http_multi_server: |   http_multi_server: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -894,10 +902,10 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: http_parser |       name: http_parser | ||||||
|       sha256: "76d306a1c3afb33fe82e2bbacad62a61f409b5634c915fceb0d799de1a913360" |       sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "4.1.1" |     version: "4.1.2" | ||||||
|   icons_launcher: |   icons_launcher: | ||||||
|     dependency: "direct dev" |     dependency: "direct dev" | ||||||
|     description: |     description: | ||||||
| @@ -926,10 +934,10 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: image_picker_android |       name: image_picker_android | ||||||
|       sha256: aa6f1280b670861ac45220cc95adc59bb6ae130259d36f980ccb62220dc5e59f |       sha256: b62d34a506e12bb965e824b6db4fbf709ee4589cf5d3e99b45ab2287b008ee0c | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "0.8.12+19" |     version: "0.8.12+20" | ||||||
|   image_picker_for_web: |   image_picker_for_web: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -942,10 +950,10 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: image_picker_ios |       name: image_picker_ios | ||||||
|       sha256: "4f0568120c6fcc0aaa04511cb9f9f4d29fc3d0139884b1d06be88dcec7641d6b" |       sha256: "05da758e67bc7839e886b3959848aa6b44ff123ab4b28f67891008afe8ef9100" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "0.8.12+1" |     version: "0.8.12+2" | ||||||
|   image_picker_linux: |   image_picker_linux: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -966,10 +974,10 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: image_picker_platform_interface |       name: image_picker_platform_interface | ||||||
|       sha256: "9ec26d410ff46f483c5519c29c02ef0e02e13a543f882b152d4bfd2f06802f80" |       sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.10.0" |     version: "2.10.1" | ||||||
|   image_picker_windows: |   image_picker_windows: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -1078,10 +1086,10 @@ packages: | |||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: livekit_client |       name: livekit_client | ||||||
|       sha256: "7cdeb3eaeec7fb70a4cf88d9caabccbef9e3bd5f0b23c086320bc5c9acb2770b" |       sha256: "02b4653d903852d0ae86b15fbe4324747606dae6410fe860d0c07a11c79988de" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.3.4" |     version: "2.3.5" | ||||||
|   logging: |   logging: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -1102,10 +1110,10 @@ packages: | |||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: markdown |       name: markdown | ||||||
|       sha256: ef2a1298144e3f985cc736b22e0ccdaf188b5b3970648f2d9dc13efd1d9df051 |       sha256: "935e23e1ff3bc02d390bad4d4be001208ee92cc217cb5b5a6c19bc14aaa318c1" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "7.2.2" |     version: "7.3.0" | ||||||
|   marquee: |   marquee: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
| @@ -1134,10 +1142,10 @@ packages: | |||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: material_symbols_icons |       name: material_symbols_icons | ||||||
|       sha256: "64404f47f8e0a9d20478468e5decef867a688660bad7173adcd20418d7f892c9" |       sha256: "89aac72d25dd49303f71b3b1e70f8374791846729365b25bebc2a2531e5b86cd" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "4.2801.0" |     version: "4.2801.1" | ||||||
|   media_kit: |   media_kit: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
| @@ -1242,6 +1250,14 @@ packages: | |||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "0.5.0" |     version: "0.5.0" | ||||||
|  |   octo_image: | ||||||
|  |     dependency: transitive | ||||||
|  |     description: | ||||||
|  |       name: octo_image | ||||||
|  |       sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "2.1.0" | ||||||
|   package_config: |   package_config: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -1254,10 +1270,10 @@ packages: | |||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: package_info_plus |       name: package_info_plus | ||||||
|       sha256: "70c421fe9d9cc1a9a7f3b05ae56befd469fe4f8daa3b484823141a55442d858d" |       sha256: "739e0a5c3c4055152520fa321d0645ee98e932718b4c8efeeb51451968fe0790" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "8.1.2" |     version: "8.1.3" | ||||||
|   package_info_plus_platform_interface: |   package_info_plus_platform_interface: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -1486,10 +1502,10 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: pubspec_parse |       name: pubspec_parse | ||||||
|       sha256: "81876843eb50dc2e1e5b151792c9a985c5ed2536914115ed04e9c8528f6647b0" |       sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.4.0" |     version: "1.5.0" | ||||||
|   qr: |   qr: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -1530,6 +1546,14 @@ packages: | |||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.5.1" |     version: "1.5.1" | ||||||
|  |   rxdart: | ||||||
|  |     dependency: transitive | ||||||
|  |     description: | ||||||
|  |       name: rxdart | ||||||
|  |       sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "0.28.0" | ||||||
|   safe_local_storage: |   safe_local_storage: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -1606,10 +1630,10 @@ packages: | |||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: share_plus |       name: share_plus | ||||||
|       sha256: "6327c3f233729374d0abaafd61f6846115b2a481b4feddd8534211dc10659400" |       sha256: fce43200aa03ea87b91ce4c3ac79f0cecd52e2a7a56c7a4185023c271fbfa6da | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "10.1.3" |     version: "10.1.4" | ||||||
|   share_plus_platform_interface: |   share_plus_platform_interface: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -1622,18 +1646,18 @@ packages: | |||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: shared_preferences |       name: shared_preferences | ||||||
|       sha256: "3c7e73920c694a436afaf65ab60ce3453d91f84208d761fbd83fc21182134d93" |       sha256: a752ce92ea7540fc35a0d19722816e04d0e72828a4200e83a98cf1a1eb524c9a | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.3.4" |     version: "2.3.5" | ||||||
|   shared_preferences_android: |   shared_preferences_android: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: shared_preferences_android |       name: shared_preferences_android | ||||||
|       sha256: "02a7d8a9ef346c9af715811b01fbd8e27845ad2c41148eefd31321471b41863d" |       sha256: "138b7bbbc7f59c56236e426c37afb8f78cbc57b094ac64c440e0bb90e380a4f5" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.4.0" |     version: "2.4.2" | ||||||
|   shared_preferences_foundation: |   shared_preferences_foundation: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -1743,6 +1767,46 @@ packages: | |||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "7.0.0" |     version: "7.0.0" | ||||||
|  |   sqflite: | ||||||
|  |     dependency: transitive | ||||||
|  |     description: | ||||||
|  |       name: sqflite | ||||||
|  |       sha256: "2d7299468485dca85efeeadf5d38986909c5eb0cd71fd3db2c2f000e6c9454bb" | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "2.4.1" | ||||||
|  |   sqflite_android: | ||||||
|  |     dependency: transitive | ||||||
|  |     description: | ||||||
|  |       name: sqflite_android | ||||||
|  |       sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3" | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "2.4.0" | ||||||
|  |   sqflite_common: | ||||||
|  |     dependency: transitive | ||||||
|  |     description: | ||||||
|  |       name: sqflite_common | ||||||
|  |       sha256: "761b9740ecbd4d3e66b8916d784e581861fd3c3553eda85e167bc49fdb68f709" | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "2.5.4+6" | ||||||
|  |   sqflite_darwin: | ||||||
|  |     dependency: transitive | ||||||
|  |     description: | ||||||
|  |       name: sqflite_darwin | ||||||
|  |       sha256: "22adfd9a2c7d634041e96d6241e6e1c8138ca6817018afc5d443fef91dcefa9c" | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "2.4.1+1" | ||||||
|  |   sqflite_platform_interface: | ||||||
|  |     dependency: transitive | ||||||
|  |     description: | ||||||
|  |       name: sqflite_platform_interface | ||||||
|  |       sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "2.4.0" | ||||||
|   stack_trace: |   stack_trace: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -1799,14 +1863,6 @@ packages: | |||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "3.3.0+3" |     version: "3.3.0+3" | ||||||
|   syntax_highlight: |  | ||||||
|     dependency: "direct main" |  | ||||||
|     description: |  | ||||||
|       name: syntax_highlight |  | ||||||
|       sha256: ee33b6aa82cc722bb9b40152a792181dee222353b486c0255fde666a3e3a4997 |  | ||||||
|       url: "https://pub.dev" |  | ||||||
|     source: hosted |  | ||||||
|     version: "0.4.0" |  | ||||||
|   term_glyph: |   term_glyph: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -1915,18 +1971,18 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: url_launcher_web |       name: url_launcher_web | ||||||
|       sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" |       sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.3.3" |     version: "2.4.0" | ||||||
|   url_launcher_windows: |   url_launcher_windows: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: url_launcher_windows |       name: url_launcher_windows | ||||||
|       sha256: "44cf3aabcedde30f2dba119a9dea3b0f2672fbe6fa96e85536251d678216b3c4" |       sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "3.1.3" |     version: "3.1.4" | ||||||
|   uuid: |   uuid: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
| @@ -1947,10 +2003,10 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: vector_graphics_codec |       name: vector_graphics_codec | ||||||
|       sha256: "2430b973a4ca3c4dbc9999b62b8c719a160100dcbae5c819bae0cacce32c9cdb" |       sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.1.12" |     version: "1.1.13" | ||||||
|   vector_graphics_compiler: |   vector_graphics_compiler: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -2067,10 +2123,10 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: win32 |       name: win32 | ||||||
|       sha256: "8b338d4486ab3fbc0ba0db9f9b4f5239b6697fcee427939a40e720cbb9ee0a69" |       sha256: "154360849a56b7b67331c21f09a386562d88903f90a1099c5987afc1912e1f29" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "5.9.0" |     version: "5.10.0" | ||||||
|   win32_registry: |   win32_registry: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|   | |||||||
| @@ -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.2.1+42 | version: 2.2.2+55 | ||||||
|  |  | ||||||
| environment: | environment: | ||||||
|   sdk: ^3.5.4 |   sdk: ^3.5.4 | ||||||
| @@ -54,7 +54,6 @@ dependencies: | |||||||
|   flutter_markdown: ^0.7.4+1 |   flutter_markdown: ^0.7.4+1 | ||||||
|   url_launcher: ^6.3.1 |   url_launcher: ^6.3.1 | ||||||
|   flutter_animate: ^4.5.0 |   flutter_animate: ^4.5.0 | ||||||
|   syntax_highlight: ^0.4.0 |  | ||||||
|   google_fonts: ^6.2.1 |   google_fonts: ^6.2.1 | ||||||
|   path: ^1.9.0 |   path: ^1.9.0 | ||||||
|   relative_time: ^5.0.0 |   relative_time: ^5.0.0 | ||||||
| @@ -115,7 +114,7 @@ dependencies: | |||||||
|   flutter_webrtc: ^0.12.5+hotfix.1 |   flutter_webrtc: ^0.12.5+hotfix.1 | ||||||
|   slide_countdown: ^2.0.2 |   slide_countdown: ^2.0.2 | ||||||
|   video_compress: ^3.1.3 |   video_compress: ^3.1.3 | ||||||
|   extended_image: ^9.0.9 |   cached_network_image: ^3.4.1 | ||||||
|  |  | ||||||
| dev_dependencies: | dev_dependencies: | ||||||
|   flutter_test: |   flutter_test: | ||||||
|   | |||||||
| @@ -1,9 +1,9 @@ | |||||||
| id = "solian-next" | id = "solian" | ||||||
|  |  | ||||||
| [[locations]] | [[locations]] | ||||||
| id = "solian-next" | id = "solian" | ||||||
| host = ["sn-next.solsynth.dev"] | hosts = ["sn.solsynth.dev"] | ||||||
| path = ["/"] | paths = ["/"] | ||||||
| [[locations.destinations]] | [[locations.destinations]] | ||||||
| id = "solian-next-web" | id = "solian-web" | ||||||
| uri = "files:///workdir/solian-next?fallback=index.html&index=index.html" | uri = "files:///workdir/solian?fallback=index.html&index=index.html" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user