From eb5a2ffe05bf23ed1c1182100e28dd7130d29418 Mon Sep 17 00:00:00 2001 From: Lakr <25259084+Lakr233@users.noreply.github.com> Date: Fri, 20 Jun 2025 13:09:33 +0800 Subject: [PATCH] chore: basic setup of v2 AI (#12864) Co-authored-by: Hwang --- .../ios/App/App.xcodeproj/project.pbxproj | 14 +- .../xcshareddata/swiftpm/Package.resolved | 59 --- .../xcshareddata/swiftpm/Package.resolved | 57 ++- .../App/AffineViewController+AIButton.swift | 119 +---- .../ios/App/App/AffineViewController.swift | 20 +- packages/frontend/apps/ios/App/App/Info.plist | 2 +- .../Plugins/Cookie/IntelligentsPlugin.swift | 66 +-- .../App/App/uniffi/affine_mobile_native.swift | 3 + .../uniffi/affine_mobile_nativeFFI.modulemap | 3 + .../Packages/AffineGraphQL/Package.resolved | 14 - .../App/Packages/AffineGraphQL/Package.swift | 2 +- .../GetCopilotHistoriesQuery.graphql.swift | 32 +- .../GetCopilotHistoryIdsQuery.graphql.swift | 4 +- .../GetCopilotSessionQuery.graphql.swift | 6 +- .../GetCopilotSessionsQuery.graphql.swift | 6 +- .../GetRecentlyUpdatedDocsQuery.graphql.swift | 138 ++++++ .../CreateChatSessionInput.graphql.swift | 11 +- .../UpdateChatSessionInput.graphql.swift | 20 +- .../Schema/Objects/StreamObject.graphql.swift | 12 + .../Schema/SchemaMetadata.graphql.swift | 1 + .../Packages/Intelligents/Package.resolved | 59 --- .../App/Packages/Intelligents/Package.swift | 21 +- .../Backend/Constant/Constant.swift | 15 - .../Backend/Constant/UnableTo.swift | 45 -- .../Intelligents/Backend/Model/Chat.swift | 27 -- .../Intelligents/Backend/Model/Prompt.swift | 53 --- .../Extension/Ext+EventHandler.swift | 37 -- .../Intelligents/Extension/Ext+String.swift | 19 - .../Intelligents/Extension/Ext+UIColor.swift | 27 -- .../Intelligents/Extension/Ext+UIFont.swift | 33 -- .../Intelligents/Extension/Ext+UIView.swift | 46 -- .../Extension/Ext+UIViewController.swift | 52 --- .../Intelligents/Extension/Ext+print.swift | 14 - .../Sources/Intelligents/Intelligents.swift | 28 +- .../BlurTransition.swift | 158 +++++++ .../IntelligentsController.swift | 27 ++ .../MainViewController+Header.swift | 22 + .../MainViewController+Input.swift | 123 ++++++ .../MainViewController.swift | 59 +++ .../Effect/ParticleView+Removal.swift | 45 ++ .../Effect/ParticleView+Renderer.swift | 331 ++++++++++++++ .../Interface/Effect/ParticleView.swift | 79 ++++ .../Effect/UIView+createViewSnapshot.swift | 22 + .../InputEditView/AttachmentBannerView.swift | 163 ------- .../InputEditView/InputEditView+Camera.swift | 63 --- .../InputEditView/InputEditView+Photo.swift | 38 -- .../InputEditView+ViewModel.swift | 48 -- .../InputEditView/InputEditView.swift | 131 ------ .../InputEditView/PlainTextEditView.swift | 69 --- .../InputEditView/TextEditControlBanner.swift | 62 --- .../IntelligentsChatController+Chat.swift | 372 ---------------- .../IntelligentsChatController+Header.swift | 124 ------ .../IntelligentsChatController+InputBox.swift | 64 --- .../IntelligentsChatController.swift | 150 ------- .../Cells/MessageListView+AssistantCell.swift | 150 ------- .../Cells/MessageListView+BaseCell.swift | 143 ------ .../Cells/MessageListView+HintCell.swift | 92 ---- .../Cells/MessageListView+SpacerCell.swift | 47 -- .../Cells/MessageListView+UserCell.swift | 167 ------- .../MessageListView+DataElement.swift | 61 --- .../TableView/MessageListView+Delegate.swift | 68 --- .../MessageListView+LayoutEngine.swift | 143 ------ .../TableView/MessageListView+Update.swift | 148 ------- .../TableView/MessageListView.swift | 72 --- .../EphemeralAction.swift | 48 -- .../ImageRotatedPreview.swift | 60 --- ...ligentsEphemeralActionController+API.swift | 143 ------ ...sEphemeralActionController+ActionBar.swift | 80 ---- ...entsEphemeralActionController+Header.swift | 115 ----- ...ntelligentsEphemeralActionController.swift | 297 ------------- ...ntelligentsFocusApertureView+Capture.swift | 30 -- ...telligentsFocusApertureView+Delegate.swift | 22 - ...IntelligentsFocusApertureView+Layout.swift | 89 ---- .../IntelligentsFocusApertureView+Panel.swift | 115 ----- .../IntelligentsFocusApertureView.swift | 133 ------ .../Interface/Supplement/AccentColor.swift | 12 + .../Interface/Supplement/Animation.swift | 23 + .../SupplementView/UIHostingView.swift | 54 --- .../Interface/View/InputBox/InputBox.swift | 287 ++++++++++++ .../InputBoxBackground.colorset/Contents.json | 38 ++ .../View/InputBox/InputBoxDelegate.swift | 35 ++ .../View/InputBox/InputBoxFunctionBar.swift | 204 +++++++++ .../View/InputBox/InputBoxImageBar.swift | 180 ++++++++ .../View/InputBox/InputBoxViewModel.swift | 164 +++++++ .../IntelligentsButton+Control.swift | 31 +- .../IntelligentsButton+Delegate.swift | 0 .../IntelligentsButton.swift | 98 +++-- .../View/MainHeaderView/MainHeaderView.swift | 121 ++++++ .../SupplementView/CircleImageView.swift | 0 .../SupplementView/DarkActionButton.swift | 0 .../SupplementView/DeleteButtonView.swift | 56 +++ .../Model/IntelligentContext.swift | 44 ++ .../Intelligents/Model/UIColor+Affine.swift | 78 ++++ .../Intelligents/Model/UIImage+Affine.swift | 93 ++++ .../Resources/Colors.xcassets/Contents.json | 6 + .../Contents.json | 38 ++ .../Contents.json | 38 ++ .../Contents.json | 38 ++ .../Contents.json | 38 ++ .../Contents.json | 38 ++ .../Contents.json | 38 ++ .../Contents.json | 38 ++ .../Contents.json | 38 ++ .../affine.text.link.colorset/Contents.json | 38 ++ .../Contents.json | 38 ++ .../Contents.json | 38 ++ .../Contents.json | 38 ++ .../Contents.json | 38 ++ .../Contents.json | 38 ++ .../Contents.json | 38 ++ .../ArrowDown.imageset/Contents.json | 12 + .../ArrowDown.imageset/More Options Icon.pdf | Bin 0 -> 3979 bytes .../ArrowUpBig.imageset/ArrowUpBig.pdf | Bin 0 -> 4201 bytes .../ArrowUpBig.imageset/Contents.json | 12 + .../Icons.xcassets/Box.imageset/Contents.json | 12 + .../Box.imageset/Left icon-5.pdf | Bin 0 -> 4407 bytes .../Icons.xcassets/Broom.imageset/Broom.pdf | Bin 0 -> 4134 bytes .../Broom.imageset/Contents.json | 12 + .../Icons.xcassets/Bubble.imageset/Bubble.pdf | Bin 0 -> 3972 bytes .../Bubble.imageset/Contents.json | 12 + .../Calendar.imageset/Contents.json | 12 + .../Calendar.imageset/Left icon-1.pdf | Bin 0 -> 4472 bytes .../Icons.xcassets/Camera.imageset/Camera.pdf | Bin 0 -> 4219 bytes .../Camera.imageset/Contents.json | 12 + .../CheckCircle.imageset/Contents.json | 12 + .../CheckCircle.imageset/Left icon.pdf | Bin 0 -> 3895 bytes .../Icons.xcassets/Close.imageset/Close.pdf | Bin 0 -> 4250 bytes .../Close.imageset/Contents.json | 12 + .../Resources/Icons.xcassets/Contents.json | 6 + .../Image.imageset/Contents.json | 12 + .../Icons.xcassets/Image.imageset/Image.pdf | Bin 0 -> 4052 bytes .../More.imageset/Contents.json | 12 + .../More.imageset/More Options Icon-1.pdf | Bin 0 -> 4350 bytes .../Page.imageset/Contents.json | 12 + .../Icons.xcassets/Page.imageset/Page.pdf | Bin 0 -> 3972 bytes .../Plus.imageset/Contents.json | 12 + .../Icons.xcassets/Plus.imageset/Plus.pdf | Bin 0 -> 4235 bytes .../Settings.imageset/Contents.json | 12 + .../Settings.imageset/Settings.pdf | Bin 0 -> 4551 bytes .../Think.imageset/Contents.json | 12 + .../Icons.xcassets/Think.imageset/icon-2.pdf | Bin 0 -> 5008 bytes .../Tools.imageset/Contents.json | 12 + .../Icons.xcassets/Tools.imageset/icon.pdf | Bin 0 -> 4437 bytes .../Upload.imageset/Contents.json | 12 + .../Icons.xcassets/Upload.imageset/Upload.pdf | Bin 0 -> 4059 bytes .../Icons.xcassets/Web.imageset/Contents.json | 12 + .../Icons.xcassets/Web.imageset/icon-1.pdf | Bin 0 -> 4275 bytes .../Resources/en.lproj/Localizable.strings | 17 - .../Sources/Intelligents/Resources/main.metal | 87 ++++ .../zh-Hans.lproj/Localizable.strings | 18 - .../ios/App/Packages/MarkdownView/.gitignore | 8 - .../xcschemes/FlowMarkdownView.xcscheme | 67 --- .../Example/Example.xcodeproj/project.pbxproj | 411 ------------------ .../contents.xcworkspacedata | 7 - .../contents.xcworkspacedata | 10 - .../MarkdownView/Example/Example/App.swift | 180 -------- .../App/Packages/MarkdownView/Package.swift | 29 -- .../BlockNode/BlockNode+Rewrite.swift | 104 ----- .../MarkdownParser/BlockNode/BlockNode.swift | 85 ---- .../InlineNode/InlineNode+Collect.swift | 13 - .../InlineNode/InlineNode+Rewrite.swift | 15 - .../InlineNode/InlineNode.swift | 52 --- .../MarkdownParser+Delegate.swift | 14 - .../MarkdownParser/MarkdownParser+Node.swift | 22 - .../MarkdownParser/MarkdownParser+Setup.swift | 25 -- .../MarkdownParser/MarkdownParser.swift | 34 -- .../MarkdownParser/Utils/Converters.swift | 289 ------------ .../MarkdownParser/Utils/Ext+Array.swift | 15 - .../MarkdownBlockView/BlockquoteView.swift | 111 ----- .../MarkdownBlockView/BulletedItemView.swift | 118 ----- .../MarkdownBlockView/BulletedListView.swift | 106 ----- .../MarkdownBlockView/CodeBlockView.swift | 197 --------- .../MarkdownBlockView/GroupBlockView.swift | 142 ------ .../MarkdownBlockView/HTMLBlockView.swift | 90 ---- .../MarkdownBlockView/HeadingView.swift | 87 ---- .../MarkdownBlockView/NumberedItemView.swift | 138 ------ .../MarkdownBlockView/NumberedListView.swift | 110 ----- .../MarkdownBlockView/ParagraphView.swift | 74 ---- .../Supplements/BlockManifest.swift | 55 --- .../BlockView+DiffableUpdate.swift | 49 --- .../Supplements/BlockView.swift | 84 ---- .../MarkdownBlockView/TableView.swift | 205 --------- .../MarkdownBlockView/TaskItemView.swift | 119 ----- .../MarkdownBlockView/TaskListView.swift | 107 ----- .../MarkdownBlockView/ThematicBreakView.swift | 63 --- .../MarkdownView/MarkdownView.swift | 45 -- .../MarkdownView/Supplements/CircleView.swift | 25 -- .../MarkdownView/Supplements/Ext+Array.swift | 51 --- .../Supplements/Ext+BlockNode.swift | 43 -- .../Supplements/Ext+DispatchQueue.swift | 17 - .../Supplements/Ext+InlineNode.swift | 121 ------ .../Supplements/Ext+NSAttributedString.swift | 83 ---- .../Supplements/Ext+UIColor.swift | 18 - .../MarkdownView/Supplements/Ext+UIFont.swift | 29 -- .../MarkdownView/Supplements/GridView.swift | 64 --- .../MarkdownView/Supplements/TextLabel.swift | 53 --- .../MarkdownView/Supplements/Theme.swift | 121 ------ .../FlowMarkdownViewTests.swift | 6 - packages/frontend/apps/ios/codegen.ts | 2 +- 199 files changed, 3478 insertions(+), 8311 deletions(-) delete mode 100644 packages/frontend/apps/ios/App/App.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved delete mode 100644 packages/frontend/apps/ios/App/Packages/AffineGraphQL/Package.resolved create mode 100644 packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Operations/Queries/GetRecentlyUpdatedDocsQuery.graphql.swift create mode 100644 packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/Objects/StreamObject.graphql.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Package.resolved delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Backend/Constant/Constant.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Backend/Constant/UnableTo.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Backend/Model/Chat.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Backend/Model/Prompt.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Extension/Ext+EventHandler.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Extension/Ext+String.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Extension/Ext+UIColor.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Extension/Ext+UIFont.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Extension/Ext+UIView.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Extension/Ext+UIViewController.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Extension/Ext+print.swift create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Controller/IntelligentsController/BlurTransition.swift create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Controller/IntelligentsController/IntelligentsController.swift create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Controller/MainViewController/MainViewController+Header.swift create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Controller/MainViewController/MainViewController+Input.swift create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Controller/MainViewController/MainViewController.swift create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Effect/ParticleView+Removal.swift create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Effect/ParticleView+Renderer.swift create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Effect/ParticleView.swift create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Effect/UIView+createViewSnapshot.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/InputEditView/AttachmentBannerView.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/InputEditView/InputEditView+Camera.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/InputEditView/InputEditView+Photo.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/InputEditView/InputEditView+ViewModel.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/InputEditView/InputEditView.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/InputEditView/PlainTextEditView.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/InputEditView/TextEditControlBanner.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/IntelligentsChatController+Chat.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/IntelligentsChatController+Header.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/IntelligentsChatController+InputBox.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/IntelligentsChatController.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/Cells/MessageListView+AssistantCell.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/Cells/MessageListView+BaseCell.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/Cells/MessageListView+HintCell.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/Cells/MessageListView+SpacerCell.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/Cells/MessageListView+UserCell.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/TableView/MessageListView+DataElement.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/TableView/MessageListView+Delegate.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/TableView/MessageListView+LayoutEngine.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/TableView/MessageListView+Update.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/TableView/MessageListView.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsEphemeralActionController/EphemeralAction.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsEphemeralActionController/ImageRotatedPreview.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsEphemeralActionController/IntelligentsEphemeralActionController+API.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsEphemeralActionController/IntelligentsEphemeralActionController+ActionBar.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsEphemeralActionController/IntelligentsEphemeralActionController+Header.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsEphemeralActionController/IntelligentsEphemeralActionController.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsFocusApertureView/IntelligentsFocusApertureView+Capture.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsFocusApertureView/IntelligentsFocusApertureView+Delegate.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsFocusApertureView/IntelligentsFocusApertureView+Layout.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsFocusApertureView/IntelligentsFocusApertureView+Panel.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsFocusApertureView/IntelligentsFocusApertureView.swift create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Supplement/AccentColor.swift create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Supplement/Animation.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/SupplementView/UIHostingView.swift create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.swift create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.xcassets/InputBoxBackground.colorset/Contents.json create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBoxDelegate.swift create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBoxFunctionBar.swift create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBoxImageBar.swift create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBoxViewModel.swift rename packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/{ => View}/IntelligentsButton/IntelligentsButton+Control.swift (70%) rename packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/{ => View}/IntelligentsButton/IntelligentsButton+Delegate.swift (100%) rename packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/{ => View}/IntelligentsButton/IntelligentsButton.swift (51%) create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/MainHeaderView/MainHeaderView.swift rename packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/{ => View}/SupplementView/CircleImageView.swift (100%) rename packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/{ => View}/SupplementView/DarkActionButton.swift (100%) create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/SupplementView/DeleteButtonView.swift create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Model/IntelligentContext.swift create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Model/UIColor+Affine.swift create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Model/UIImage+Affine.swift create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/Contents.json create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.button.primary.colorset/Contents.json create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.icon.activated.colorset/Contents.json create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.icon.primary.colorset/Contents.json create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.layer.background.primary.colorset/Contents.json create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.layer.background.secondary.colorset/Contents.json create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.layer.border.colorset/Contents.json create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.layer.pureWhite.colorset/Contents.json create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.text.emphasis.colorset/Contents.json create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.text.link.colorset/Contents.json create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.text.listDotAndNumber.colorset/Contents.json create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.text.placeholder.colorset/Contents.json create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.text.primary.colorset/Contents.json create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.text.pureWhite.colorset/Contents.json create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.text.secondary.colorset/Contents.json create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.text.tertiary.colorset/Contents.json create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/ArrowDown.imageset/Contents.json create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/ArrowDown.imageset/More Options Icon.pdf create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/ArrowUpBig.imageset/ArrowUpBig.pdf create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/ArrowUpBig.imageset/Contents.json create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Box.imageset/Contents.json create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Box.imageset/Left icon-5.pdf create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Broom.imageset/Broom.pdf create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Broom.imageset/Contents.json create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Bubble.imageset/Bubble.pdf create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Bubble.imageset/Contents.json create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Calendar.imageset/Contents.json create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Calendar.imageset/Left icon-1.pdf create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Camera.imageset/Camera.pdf create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Camera.imageset/Contents.json create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/CheckCircle.imageset/Contents.json create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/CheckCircle.imageset/Left icon.pdf create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Close.imageset/Close.pdf create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Close.imageset/Contents.json create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Contents.json create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Image.imageset/Contents.json create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Image.imageset/Image.pdf create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/More.imageset/Contents.json create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/More.imageset/More Options Icon-1.pdf create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Page.imageset/Contents.json create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Page.imageset/Page.pdf create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Plus.imageset/Contents.json create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Plus.imageset/Plus.pdf create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Settings.imageset/Contents.json create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Settings.imageset/Settings.pdf create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Think.imageset/Contents.json create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Think.imageset/icon-2.pdf create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Tools.imageset/Contents.json create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Tools.imageset/icon.pdf create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Upload.imageset/Contents.json create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Upload.imageset/Upload.pdf create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Web.imageset/Contents.json create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Web.imageset/icon-1.pdf delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/en.lproj/Localizable.strings create mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/main.metal delete mode 100644 packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/zh-Hans.lproj/Localizable.strings delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/.gitignore delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/.swiftpm/xcode/xcshareddata/xcschemes/FlowMarkdownView.xcscheme delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Example/Example.xcodeproj/project.pbxproj delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Example/Example.xcworkspace/contents.xcworkspacedata delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Example/Example/App.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Package.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/BlockNode/BlockNode+Rewrite.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/BlockNode/BlockNode.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/InlineNode/InlineNode+Collect.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/InlineNode/InlineNode+Rewrite.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/InlineNode/InlineNode.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/MarkdownParser/MarkdownParser+Delegate.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/MarkdownParser/MarkdownParser+Node.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/MarkdownParser/MarkdownParser+Setup.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/MarkdownParser/MarkdownParser.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/Utils/Converters.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/Utils/Ext+Array.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/BlockquoteView.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/BulletedItemView.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/BulletedListView.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/CodeBlockView.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/GroupBlockView.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/HTMLBlockView.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/HeadingView.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/NumberedItemView.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/NumberedListView.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/ParagraphView.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/Supplements/BlockManifest.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/Supplements/BlockView+DiffableUpdate.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/Supplements/BlockView.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/TableView.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/TaskItemView.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/TaskListView.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/ThematicBreakView.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownView/MarkdownView.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/CircleView.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/Ext+Array.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/Ext+BlockNode.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/Ext+DispatchQueue.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/Ext+InlineNode.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/Ext+NSAttributedString.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/Ext+UIColor.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/Ext+UIFont.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/GridView.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/TextLabel.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/Theme.swift delete mode 100644 packages/frontend/apps/ios/App/Packages/MarkdownView/Tests/FlowMarkdownViewTests/FlowMarkdownViewTests.swift diff --git a/packages/frontend/apps/ios/App/App.xcodeproj/project.pbxproj b/packages/frontend/apps/ios/App/App.xcodeproj/project.pbxproj index 5870e8c65..b12a2b139 100644 --- a/packages/frontend/apps/ios/App/App.xcodeproj/project.pbxproj +++ b/packages/frontend/apps/ios/App/App.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 77; + objectVersion = 56; objects = { /* Begin PBXBuildFile section */ @@ -13,7 +13,6 @@ 50802D612D112F8700694021 /* Intelligents in Frameworks */ = {isa = PBXBuildFile; productRef = 50802D602D112F8700694021 /* Intelligents */; }; 50A285D72D112A5E000D5A6D /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 50A285D62D112A5E000D5A6D /* Localizable.xcstrings */; }; 50A285D82D112A5E000D5A6D /* InfoPlist.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 50A285D52D112A5E000D5A6D /* InfoPlist.xcstrings */; }; - 50A285DC2D112B24000D5A6D /* Intelligents in Frameworks */ = {isa = PBXBuildFile; productRef = 50A285DB2D112B24000D5A6D /* Intelligents */; }; 50FF428A2D2E757E0050AA83 /* ApplicationBridgedWindowScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50FF42892D2E757E0050AA83 /* ApplicationBridgedWindowScript.swift */; }; 50FF428C2D2E77CC0050AA83 /* AffineViewController+AIButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50FF428B2D2E77CC0050AA83 /* AffineViewController+AIButton.swift */; }; 9D52FC432D26CDBF00105D0A /* JSValueContainerExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D52FC422D26CDB600105D0A /* JSValueContainerExt.swift */; }; @@ -78,6 +77,8 @@ /* Begin PBXFileSystemSynchronizedRootGroup section */ C45499AB2D140B5000E21978 /* NBStore */ = { isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + ); path = NBStore; sourceTree = ""; }; @@ -88,7 +89,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 50A285DC2D112B24000D5A6D /* Intelligents in Frameworks */, 9DFCD1462D27D1D70028C92B /* libaffine_mobile_native.a in Frameworks */, 50802D612D112F8700694021 /* Intelligents in Frameworks */, 2E0DD47B57B994A104B25EED /* Pods_AFFiNE.framework in Frameworks */, @@ -521,7 +521,7 @@ "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 73YMMDVT2M; INFOPLIST_FILE = App/Info.plist; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity"; - IPHONEOS_DEPLOYMENT_TARGET = 16.4; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -559,7 +559,7 @@ "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 73YMMDVT2M; INFOPLIST_FILE = App/Info.plist; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity"; - IPHONEOS_DEPLOYMENT_TARGET = 16.4; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -613,10 +613,6 @@ isa = XCSwiftPackageProductDependency; productName = Intelligents; }; - 50A285DB2D112B24000D5A6D /* Intelligents */ = { - isa = XCSwiftPackageProductDependency; - productName = Intelligents; - }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 504EC2FC1FED79650016851F /* Project object */; diff --git a/packages/frontend/apps/ios/App/App.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/packages/frontend/apps/ios/App/App.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index 63f227d74..000000000 --- a/packages/frontend/apps/ios/App/App.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,59 +0,0 @@ -{ - "pins" : [ - { - "identity" : "apollo-ios", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apollographql/apollo-ios", - "state" : { - "revision" : "39fea7617346c0731be25f61afd537e7032fb562", - "version" : "1.22.0" - } - }, - { - "identity" : "chidorimenu", - "kind" : "remoteSourceControl", - "location" : "https://github.com/Lakr233/ChidoriMenu", - "state" : { - "revision" : "3bb4323fe0f7f8f435d15656c3eeffcbb7c9c605", - "version" : "3.0.0" - } - }, - { - "identity" : "splash", - "kind" : "remoteSourceControl", - "location" : "https://github.com/JohnSundell/Splash", - "state" : { - "revision" : "7f4df436eb78fe64fe2c32c58006e9949fa28ad8", - "version" : "0.16.0" - } - }, - { - "identity" : "swift-cmark", - "kind" : "remoteSourceControl", - "location" : "https://github.com/swiftlang/swift-cmark", - "state" : { - "revision" : "b022b08312decdc46585e0b3440d97f6f22ef703", - "version" : "0.6.0" - } - }, - { - "identity" : "swift-collections", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-collections", - "state" : { - "revision" : "c1805596154bb3a265fd91b8ac0c4433b4348fb0", - "version" : "1.2.0" - } - }, - { - "identity" : "swift-eventsource", - "kind" : "remoteSourceControl", - "location" : "https://github.com/LaunchDarkly/swift-eventsource.git", - "state" : { - "revision" : "57051701c58a93603ffa2051f8e9cf0c8cff7814", - "version" : "3.3.0" - } - } - ], - "version" : 2 -} diff --git a/packages/frontend/apps/ios/App/App.xcworkspace/xcshareddata/swiftpm/Package.resolved b/packages/frontend/apps/ios/App/App.xcworkspace/xcshareddata/swiftpm/Package.resolved index a0763ea43..0973b9f21 100644 --- a/packages/frontend/apps/ios/App/App.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/packages/frontend/apps/ios/App/App.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -3,37 +3,28 @@ { "identity" : "apollo-ios", "kind" : "remoteSourceControl", - "location" : "https://github.com/apollographql/apollo-ios.git", + "location" : "https://github.com/apollographql/apollo-ios", "state" : { - "revision" : "39fea7617346c0731be25f61afd537e7032fb562", - "version" : "1.22.0" + "revision" : "9aa748d6f0526a744d49d59a2383dc7fdf9d645b", + "version" : "1.18.0" } }, { - "identity" : "chidorimenu", + "identity" : "snapkit", "kind" : "remoteSourceControl", - "location" : "https://github.com/Lakr233/ChidoriMenu", + "location" : "https://github.com/SnapKit/SnapKit.git", "state" : { - "revision" : "3bb4323fe0f7f8f435d15656c3eeffcbb7c9c605", - "version" : "3.0.0" + "revision" : "2842e6e84e82eb9a8dac0100ca90d9444b0307f4", + "version" : "5.7.1" } }, { - "identity" : "splash", + "identity" : "sqlite.swift", "kind" : "remoteSourceControl", - "location" : "https://github.com/JohnSundell/Splash", + "location" : "https://github.com/stephencelis/SQLite.swift.git", "state" : { - "revision" : "7f4df436eb78fe64fe2c32c58006e9949fa28ad8", - "version" : "0.16.0" - } - }, - { - "identity" : "swift-cmark", - "kind" : "remoteSourceControl", - "location" : "https://github.com/swiftlang/swift-cmark", - "state" : { - "revision" : "b022b08312decdc46585e0b3440d97f6f22ef703", - "version" : "0.6.0" + "revision" : "392dd6058624d9f6c5b4c769d165ddd8c7293394", + "version" : "0.15.4" } }, { @@ -46,12 +37,30 @@ } }, { - "identity" : "swift-eventsource", + "identity" : "swift-toolchain-sqlite", "kind" : "remoteSourceControl", - "location" : "https://github.com/LaunchDarkly/swift-eventsource.git", + "location" : "https://github.com/swiftlang/swift-toolchain-sqlite", "state" : { - "revision" : "57051701c58a93603ffa2051f8e9cf0c8cff7814", - "version" : "3.3.0" + "revision" : "b626d3002773b1a1304166643e7f118f724b2132", + "version" : "1.0.4" + } + }, + { + "identity" : "swifterswift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SwifterSwift/SwifterSwift.git", + "state" : { + "revision" : "39fa28c90a3ebe3d53f80289304fd880cf2c42d0", + "version" : "6.2.0" + } + }, + { + "identity" : "then", + "kind" : "remoteSourceControl", + "location" : "https://github.com/devxoul/Then", + "state" : { + "revision" : "d41ef523faef0f911369f79c0b96815d9dbb6d7a", + "version" : "3.0.0" } } ], diff --git a/packages/frontend/apps/ios/App/App/AffineViewController+AIButton.swift b/packages/frontend/apps/ios/App/App/AffineViewController+AIButton.swift index e34d2da57..eedfaaa68 100644 --- a/packages/frontend/apps/ios/App/App/AffineViewController+AIButton.swift +++ b/packages/frontend/apps/ios/App/App/AffineViewController+AIButton.swift @@ -5,123 +5,18 @@ // Created by 秋星桥 on 2025/1/8. // -import ChidoriMenu import Intelligents import UIKit -extension AFFiNEViewController: IntelligentsButtonDelegate, IntelligentsFocusApertureViewDelegate { +extension AFFiNEViewController: IntelligentsButtonDelegate { func onIntelligentsButtonTapped(_ button: IntelligentsButton) { - guard let webView else { - assertionFailure() // ? wdym ? - return - } - + IntelligentContext.shared.webView = webView! button.beginProgress() - - let group = DispatchGroup() - - group.enter() - webView.evaluateScript(.getCurrentServerBaseUrl) { result in - self.baseUrl = result as? String - print("[*] setting baseUrl: \(self.baseUrl ?? "")") - group.leave() - } - - group.enter() - webView.evaluateScript(.getCurrentDocId) { result in - self.documentID = result as? String - print("[*] setting documentID: \(self.documentID ?? "")") - group.leave() - } - - group.enter() - webView.evaluateScript(.getCurrentWorkspaceId) { result in - self.workspaceID = result as? String - print("[*] setting workspaceID: \(self.workspaceID ?? "")") - group.leave() - } - - group.enter() - webView.evaluateScript(.getCurrentDocContentInMarkdown) { input in - self.documentContent = input as? String - print("[*] setting documentContent: \(self.documentContent?.count ?? 0) chars") - group.leave() - } - - DispatchQueue.global().asyncAfter(deadline: .now()) { - group.wait() - DispatchQueue.main.async { - button.stopProgress() - webView.resignFirstResponder() - self.openIntelligentsSheet() - } - } - } - - @discardableResult - func openIntelligentsSheet() -> IntelligentsFocusApertureView? { - dismissIntelligentsButton() - view.resignFirstResponder() - // stop scroll on webview - if let contentOffset = webView?.scrollView.contentOffset { - webView?.scrollView.contentOffset = contentOffset - } - let focus = IntelligentsFocusApertureView() - focus.prepareAnimationWith( - capturingTargetContentView: webView ?? .init(), - coveringRootViewController: self - ) - focus.delegate = self - focus.executeAnimationKickIn() - dismissIntelligentsButton() - return focus - } - - func openSimpleChat() { - let targetController = IntelligentsChatController() - presentIntoCurrentContext(withTargetController: targetController) - } - - func focusApertureRequestAction( - from view: IntelligentsFocusApertureView, - actionType: IntelligentsFocusApertureViewActionType - ) { - switch actionType { - case .translateTo: - var actions: [UIAction] = [] - for lang in IntelligentsEphemeralActionController.EphemeralAction.Language.allCases { - actions.append(.init(title: lang.rawValue) { [weak self] _ in - guard let self else { return } - let controller = IntelligentsEphemeralActionController( - action: .translate(to: lang) - ) - controller.workspaceID = workspaceID ?? "" - controller.documentID = documentID ?? "" - controller.documentContent = documentContent ?? "" - controller.configure(previewImage: view.capturedImage ?? .init()) - presentIntoCurrentContext(withTargetController: controller) - }) - } - view.present(menu: .init(children: actions)) { controller in - controller.overrideUserInterfaceStyle = .dark - } controllerDidPresent: { _ in } - case .summary: - let controller = IntelligentsEphemeralActionController( - action: .summarize - ) - controller.configure(previewImage: view.capturedImage ?? .init()) - controller.workspaceID = workspaceID ?? "" - controller.documentID = documentID ?? "" - controller.documentContent = documentContent ?? "" - presentIntoCurrentContext(withTargetController: controller) - case .chatWithAI: - let controller = IntelligentsChatController() - controller.metadata[.documentID] = documentID - controller.metadata[.workspaceID] = workspaceID - controller.metadata[.content] = documentContent - presentIntoCurrentContext(withTargetController: controller) - case .dismiss: - presentIntelligentsButton() + + IntelligentContext.shared.preparePresent() { + button.stopProgress() + let controller = IntelligentsController() + self.present(controller, animated: true) } } } diff --git a/packages/frontend/apps/ios/App/App/AffineViewController.swift b/packages/frontend/apps/ios/App/App/AffineViewController.swift index b17db0815..22227845a 100644 --- a/packages/frontend/apps/ios/App/App/AffineViewController.swift +++ b/packages/frontend/apps/ios/App/App/AffineViewController.swift @@ -3,13 +3,6 @@ import Intelligents import UIKit class AFFiNEViewController: CAPBridgeViewController { - var baseUrl: String? { - didSet { Intelligents.setUpstreamEndpoint(baseUrl ?? "") } - } - var documentID: String? - var workspaceID: String? - var documentContent: String? - override func viewDidLoad() { super.viewDidLoad() webView?.allowsBackForwardNavigationGestures = true @@ -36,7 +29,7 @@ class AFFiNEViewController: CAPBridgeViewController { CookiePlugin(), HashcashPlugin(), NavigationGesturePlugin(), - IntelligentsPlugin(representController: self), + // IntelligentsPlugin(representController: self), // no longer put in use NbStorePlugin(), ] plugins.forEach { bridge?.registerPluginInstance($0) } @@ -45,16 +38,11 @@ class AFFiNEViewController: CAPBridgeViewController { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) navigationController?.setNavigationBarHidden(false, animated: animated) - } - -#if DEBUG - override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) { - super.motionEnded(motion, with: event) - if motion == .motionShake { - presentIntelligentsButton() + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + self.presentIntelligentsButton() } } -#endif } + diff --git a/packages/frontend/apps/ios/App/App/Info.plist b/packages/frontend/apps/ios/App/App/Info.plist index ff1e14a21..7bc4ac16e 100644 --- a/packages/frontend/apps/ios/App/App/Info.plist +++ b/packages/frontend/apps/ios/App/App/Info.plist @@ -42,7 +42,7 @@ NSPhotoLibraryUsageDescription AFFiNE requires access to select photos from your photo library and insert them into your documents NSUserTrackingUsageDescription - Rest assured, enabling this permission won't access your private info on other sites. It's only used to identify your device and improve security and product experience. + Rest assured, enabling this permission won't access your private info on other sites. It's only used to identify your device and improve security and product experience. UILaunchScreen UIImageName diff --git a/packages/frontend/apps/ios/App/App/Plugins/Cookie/IntelligentsPlugin.swift b/packages/frontend/apps/ios/App/App/Plugins/Cookie/IntelligentsPlugin.swift index f348b0629..cff51cf72 100644 --- a/packages/frontend/apps/ios/App/App/Plugins/Cookie/IntelligentsPlugin.swift +++ b/packages/frontend/apps/ios/App/App/Plugins/Cookie/IntelligentsPlugin.swift @@ -1,36 +1,36 @@ import Capacitor import Foundation -@objc(IntelligentsPlugin) -public class IntelligentsPlugin: CAPPlugin, CAPBridgedPlugin { - public let identifier = "IntelligentsPlugin" - public let jsName = "Intelligents" - public let pluginMethods: [CAPPluginMethod] = [ - CAPPluginMethod(name: "presentIntelligentsButton", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "dismissIntelligentsButton", returnType: CAPPluginReturnPromise), - ] - public private(set) weak var representController: UIViewController? - - init(representController: UIViewController) { - self.representController = representController - super.init() - } - - deinit { - representController = nil - } - - @objc public func presentIntelligentsButton(_ call: CAPPluginCall) { - DispatchQueue.main.async { - self.representController?.presentIntelligentsButton() - call.resolve() - } - } - - @objc public func dismissIntelligentsButton(_ call: CAPPluginCall) { - DispatchQueue.main.async { - self.representController?.dismissIntelligentsButton() - call.resolve() - } - } -} +//@objc(IntelligentsPlugin) +//public class IntelligentsPlugin: CAPPlugin, CAPBridgedPlugin { +// public let identifier = "IntelligentsPlugin" +// public let jsName = "Intelligents" +// public let pluginMethods: [CAPPluginMethod] = [ +// CAPPluginMethod(name: "presentIntelligentsButton", returnType: CAPPluginReturnPromise), +// CAPPluginMethod(name: "dismissIntelligentsButton", returnType: CAPPluginReturnPromise), +// ] +// public private(set) weak var representController: UIViewController? +// +// init(representController: UIViewController) { +// self.representController = representController +// super.init() +// } +// +// deinit { +// representController = nil +// } +// +// @objc public func presentIntelligentsButton(_ call: CAPPluginCall) { +// DispatchQueue.main.async { +// self.representController?.presentIntelligentsButton() +// call.resolve() +// } +// } +// +// @objc public func dismissIntelligentsButton(_ call: CAPPluginCall) { +// DispatchQueue.main.async { +// self.representController?.dismissIntelligentsButton() +// call.resolve() +// } +// } +//} diff --git a/packages/frontend/apps/ios/App/App/uniffi/affine_mobile_native.swift b/packages/frontend/apps/ios/App/App/uniffi/affine_mobile_native.swift index 45d68f4dc..9e015b0b5 100644 --- a/packages/frontend/apps/ios/App/App/uniffi/affine_mobile_native.swift +++ b/packages/frontend/apps/ios/App/App/uniffi/affine_mobile_native.swift @@ -1711,6 +1711,7 @@ extension UniffiError: Equatable, Hashable {} + extension UniffiError: Foundation.LocalizedError { public var errorDescription: String? { String(reflecting: self) @@ -1718,6 +1719,8 @@ extension UniffiError: Foundation.LocalizedError { } + + #if swift(>=5.8) @_documentation(visibility: private) #endif diff --git a/packages/frontend/apps/ios/App/App/uniffi/affine_mobile_nativeFFI.modulemap b/packages/frontend/apps/ios/App/App/uniffi/affine_mobile_nativeFFI.modulemap index a7925044f..45fbfa8fb 100644 --- a/packages/frontend/apps/ios/App/App/uniffi/affine_mobile_nativeFFI.modulemap +++ b/packages/frontend/apps/ios/App/App/uniffi/affine_mobile_nativeFFI.modulemap @@ -1,4 +1,7 @@ module affine_mobile_nativeFFI { header "affine_mobile_nativeFFI.h" export * + use "Darwin" + use "_Builtin_stdbool" + use "_Builtin_stdint" } \ No newline at end of file diff --git a/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Package.resolved b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Package.resolved deleted file mode 100644 index 20f317d36..000000000 --- a/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Package.resolved +++ /dev/null @@ -1,14 +0,0 @@ -{ - "pins" : [ - { - "identity" : "apollo-ios", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apollographql/apollo-ios.git", - "state" : { - "revision" : "6ecc75281dab2fa231cb0d5fed3a3713826fecae", - "version" : "1.21.0" - } - } - ], - "version" : 2 -} diff --git a/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Package.swift b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Package.swift index 14a8270da..c87014c63 100644 --- a/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Package.swift +++ b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Package.swift @@ -14,7 +14,7 @@ let package = Package( .library(name: "AffineGraphQL", targets: ["AffineGraphQL"]), ], dependencies: [ - .package(url: "https://github.com/apollographql/apollo-ios", exact: "1.22.0"), + .package(url: "https://github.com/apollographql/apollo-ios", exact: "1.18.0"), ], targets: [ .target( diff --git a/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Operations/Queries/GetCopilotHistoriesQuery.graphql.swift b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Operations/Queries/GetCopilotHistoriesQuery.graphql.swift index 4624ed7aa..cda9e34e9 100644 --- a/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Operations/Queries/GetCopilotHistoriesQuery.graphql.swift +++ b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Operations/Queries/GetCopilotHistoriesQuery.graphql.swift @@ -7,7 +7,7 @@ public class GetCopilotHistoriesQuery: GraphQLQuery { public static let operationName: String = "getCopilotHistories" public static let operationDocument: ApolloAPI.OperationDocument = .init( definition: .init( - #"query getCopilotHistories($workspaceId: String!, $docId: String, $options: QueryChatHistoriesInput) { currentUser { __typename copilot(workspaceId: $workspaceId) { __typename histories(docId: $docId, options: $options) { __typename sessionId tokens action createdAt messages { __typename id role content attachments createdAt } } } } }"# + #"query getCopilotHistories($workspaceId: String!, $docId: String, $options: QueryChatHistoriesInput) { currentUser { __typename copilot(workspaceId: $workspaceId) { __typename histories(docId: $docId, options: $options) { __typename sessionId pinned tokens action createdAt messages { __typename id role content streamObjects { __typename type textDelta toolCallId toolName args result } attachments createdAt } } } } }"# )) public var workspaceId: String @@ -86,6 +86,7 @@ public class GetCopilotHistoriesQuery: GraphQLQuery { public static var __selections: [ApolloAPI.Selection] { [ .field("__typename", String.self), .field("sessionId", String.self), + .field("pinned", Bool.self), .field("tokens", Int.self), .field("action", String?.self), .field("createdAt", AffineGraphQL.DateTime.self), @@ -93,6 +94,7 @@ public class GetCopilotHistoriesQuery: GraphQLQuery { ] } public var sessionId: String { __data["sessionId"] } + public var pinned: Bool { __data["pinned"] } /// The number of tokens used in the session public var tokens: Int { __data["tokens"] } /// An mark identifying which view to use to display the session @@ -113,6 +115,7 @@ public class GetCopilotHistoriesQuery: GraphQLQuery { .field("id", AffineGraphQL.ID?.self), .field("role", String.self), .field("content", String.self), + .field("streamObjects", [StreamObject]?.self), .field("attachments", [String]?.self), .field("createdAt", AffineGraphQL.DateTime.self), ] } @@ -120,8 +123,35 @@ public class GetCopilotHistoriesQuery: GraphQLQuery { public var id: AffineGraphQL.ID? { __data["id"] } public var role: String { __data["role"] } public var content: String { __data["content"] } + public var streamObjects: [StreamObject]? { __data["streamObjects"] } public var attachments: [String]? { __data["attachments"] } public var createdAt: AffineGraphQL.DateTime { __data["createdAt"] } + + /// CurrentUser.Copilot.History.Message.StreamObject + /// + /// Parent Type: `StreamObject` + public struct StreamObject: AffineGraphQL.SelectionSet { + public let __data: DataDict + public init(_dataDict: DataDict) { __data = _dataDict } + + public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.StreamObject } + public static var __selections: [ApolloAPI.Selection] { [ + .field("__typename", String.self), + .field("type", String.self), + .field("textDelta", String?.self), + .field("toolCallId", String?.self), + .field("toolName", String?.self), + .field("args", AffineGraphQL.JSON?.self), + .field("result", AffineGraphQL.JSON?.self), + ] } + + public var type: String { __data["type"] } + public var textDelta: String? { __data["textDelta"] } + public var toolCallId: String? { __data["toolCallId"] } + public var toolName: String? { __data["toolName"] } + public var args: AffineGraphQL.JSON? { __data["args"] } + public var result: AffineGraphQL.JSON? { __data["result"] } + } } } } diff --git a/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Operations/Queries/GetCopilotHistoryIdsQuery.graphql.swift b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Operations/Queries/GetCopilotHistoryIdsQuery.graphql.swift index 9f3ad7e87..adf2aaf2f 100644 --- a/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Operations/Queries/GetCopilotHistoryIdsQuery.graphql.swift +++ b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Operations/Queries/GetCopilotHistoryIdsQuery.graphql.swift @@ -7,7 +7,7 @@ public class GetCopilotHistoryIdsQuery: GraphQLQuery { public static let operationName: String = "getCopilotHistoryIds" public static let operationDocument: ApolloAPI.OperationDocument = .init( definition: .init( - #"query getCopilotHistoryIds($workspaceId: String!, $docId: String, $options: QueryChatHistoriesInput) { currentUser { __typename copilot(workspaceId: $workspaceId) { __typename histories(docId: $docId, options: $options) { __typename sessionId messages { __typename id role createdAt } } } } }"# + #"query getCopilotHistoryIds($workspaceId: String!, $docId: String, $options: QueryChatHistoriesInput) { currentUser { __typename copilot(workspaceId: $workspaceId) { __typename histories(docId: $docId, options: $options) { __typename sessionId pinned messages { __typename id role createdAt } } } } }"# )) public var workspaceId: String @@ -86,10 +86,12 @@ public class GetCopilotHistoryIdsQuery: GraphQLQuery { public static var __selections: [ApolloAPI.Selection] { [ .field("__typename", String.self), .field("sessionId", String.self), + .field("pinned", Bool.self), .field("messages", [Message].self), ] } public var sessionId: String { __data["sessionId"] } + public var pinned: Bool { __data["pinned"] } public var messages: [Message] { __data["messages"] } /// CurrentUser.Copilot.History.Message diff --git a/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Operations/Queries/GetCopilotSessionQuery.graphql.swift b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Operations/Queries/GetCopilotSessionQuery.graphql.swift index 97ff8d721..5affd6246 100644 --- a/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Operations/Queries/GetCopilotSessionQuery.graphql.swift +++ b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Operations/Queries/GetCopilotSessionQuery.graphql.swift @@ -7,7 +7,7 @@ public class GetCopilotSessionQuery: GraphQLQuery { public static let operationName: String = "getCopilotSession" public static let operationDocument: ApolloAPI.OperationDocument = .init( definition: .init( - #"query getCopilotSession($workspaceId: String!, $sessionId: String!) { currentUser { __typename copilot(workspaceId: $workspaceId) { __typename session(sessionId: $sessionId) { __typename id parentSessionId promptName model optionalModels } } } }"# + #"query getCopilotSession($workspaceId: String!, $sessionId: String!) { currentUser { __typename copilot(workspaceId: $workspaceId) { __typename session(sessionId: $sessionId) { __typename id parentSessionId docId pinned promptName model optionalModels } } } }"# )) public var workspaceId: String @@ -81,6 +81,8 @@ public class GetCopilotSessionQuery: GraphQLQuery { .field("__typename", String.self), .field("id", AffineGraphQL.ID.self), .field("parentSessionId", AffineGraphQL.ID?.self), + .field("docId", String?.self), + .field("pinned", Bool.self), .field("promptName", String.self), .field("model", String.self), .field("optionalModels", [String].self), @@ -88,6 +90,8 @@ public class GetCopilotSessionQuery: GraphQLQuery { public var id: AffineGraphQL.ID { __data["id"] } public var parentSessionId: AffineGraphQL.ID? { __data["parentSessionId"] } + public var docId: String? { __data["docId"] } + public var pinned: Bool { __data["pinned"] } public var promptName: String { __data["promptName"] } public var model: String { __data["model"] } public var optionalModels: [String] { __data["optionalModels"] } diff --git a/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Operations/Queries/GetCopilotSessionsQuery.graphql.swift b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Operations/Queries/GetCopilotSessionsQuery.graphql.swift index 95d53c26e..54f3f6a1b 100644 --- a/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Operations/Queries/GetCopilotSessionsQuery.graphql.swift +++ b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Operations/Queries/GetCopilotSessionsQuery.graphql.swift @@ -7,7 +7,7 @@ public class GetCopilotSessionsQuery: GraphQLQuery { public static let operationName: String = "getCopilotSessions" public static let operationDocument: ApolloAPI.OperationDocument = .init( definition: .init( - #"query getCopilotSessions($workspaceId: String!, $docId: String, $options: QueryChatSessionsInput) { currentUser { __typename copilot(workspaceId: $workspaceId) { __typename sessions(docId: $docId, options: $options) { __typename id parentSessionId promptName model optionalModels } } } }"# + #"query getCopilotSessions($workspaceId: String!, $docId: String, $options: QueryChatSessionsInput) { currentUser { __typename copilot(workspaceId: $workspaceId) { __typename sessions(docId: $docId, options: $options) { __typename id parentSessionId docId pinned promptName model optionalModels } } } }"# )) public var workspaceId: String @@ -88,6 +88,8 @@ public class GetCopilotSessionsQuery: GraphQLQuery { .field("__typename", String.self), .field("id", AffineGraphQL.ID.self), .field("parentSessionId", AffineGraphQL.ID?.self), + .field("docId", String?.self), + .field("pinned", Bool.self), .field("promptName", String.self), .field("model", String.self), .field("optionalModels", [String].self), @@ -95,6 +97,8 @@ public class GetCopilotSessionsQuery: GraphQLQuery { public var id: AffineGraphQL.ID { __data["id"] } public var parentSessionId: AffineGraphQL.ID? { __data["parentSessionId"] } + public var docId: String? { __data["docId"] } + public var pinned: Bool { __data["pinned"] } public var promptName: String { __data["promptName"] } public var model: String { __data["model"] } public var optionalModels: [String] { __data["optionalModels"] } diff --git a/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Operations/Queries/GetRecentlyUpdatedDocsQuery.graphql.swift b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Operations/Queries/GetRecentlyUpdatedDocsQuery.graphql.swift new file mode 100644 index 000000000..8c09ace04 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Operations/Queries/GetRecentlyUpdatedDocsQuery.graphql.swift @@ -0,0 +1,138 @@ +// @generated +// This file was automatically generated and should not be edited. + +@_exported import ApolloAPI + +public class GetRecentlyUpdatedDocsQuery: GraphQLQuery { + public static let operationName: String = "getRecentlyUpdatedDocs" + public static let operationDocument: ApolloAPI.OperationDocument = .init( + definition: .init( + #"query getRecentlyUpdatedDocs($workspaceId: String!, $pagination: PaginationInput!) { workspace(id: $workspaceId) { __typename recentlyUpdatedDocs(pagination: $pagination) { __typename totalCount pageInfo { __typename endCursor hasNextPage } edges { __typename node { __typename id title createdAt updatedAt creatorId lastUpdaterId } } } } }"# + )) + + public var workspaceId: String + public var pagination: PaginationInput + + public init( + workspaceId: String, + pagination: PaginationInput + ) { + self.workspaceId = workspaceId + self.pagination = pagination + } + + public var __variables: Variables? { [ + "workspaceId": workspaceId, + "pagination": pagination + ] } + + public struct Data: AffineGraphQL.SelectionSet { + public let __data: DataDict + public init(_dataDict: DataDict) { __data = _dataDict } + + public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Query } + public static var __selections: [ApolloAPI.Selection] { [ + .field("workspace", Workspace.self, arguments: ["id": .variable("workspaceId")]), + ] } + + /// Get workspace by id + public var workspace: Workspace { __data["workspace"] } + + /// Workspace + /// + /// Parent Type: `WorkspaceType` + public struct Workspace: AffineGraphQL.SelectionSet { + public let __data: DataDict + public init(_dataDict: DataDict) { __data = _dataDict } + + public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.WorkspaceType } + public static var __selections: [ApolloAPI.Selection] { [ + .field("__typename", String.self), + .field("recentlyUpdatedDocs", RecentlyUpdatedDocs.self, arguments: ["pagination": .variable("pagination")]), + ] } + + /// Get recently updated docs of a workspace + public var recentlyUpdatedDocs: RecentlyUpdatedDocs { __data["recentlyUpdatedDocs"] } + + /// Workspace.RecentlyUpdatedDocs + /// + /// Parent Type: `PaginatedDocType` + public struct RecentlyUpdatedDocs: AffineGraphQL.SelectionSet { + public let __data: DataDict + public init(_dataDict: DataDict) { __data = _dataDict } + + public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.PaginatedDocType } + public static var __selections: [ApolloAPI.Selection] { [ + .field("__typename", String.self), + .field("totalCount", Int.self), + .field("pageInfo", PageInfo.self), + .field("edges", [Edge].self), + ] } + + public var totalCount: Int { __data["totalCount"] } + public var pageInfo: PageInfo { __data["pageInfo"] } + public var edges: [Edge] { __data["edges"] } + + /// Workspace.RecentlyUpdatedDocs.PageInfo + /// + /// Parent Type: `PageInfo` + public struct PageInfo: AffineGraphQL.SelectionSet { + public let __data: DataDict + public init(_dataDict: DataDict) { __data = _dataDict } + + public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.PageInfo } + public static var __selections: [ApolloAPI.Selection] { [ + .field("__typename", String.self), + .field("endCursor", String?.self), + .field("hasNextPage", Bool.self), + ] } + + public var endCursor: String? { __data["endCursor"] } + public var hasNextPage: Bool { __data["hasNextPage"] } + } + + /// Workspace.RecentlyUpdatedDocs.Edge + /// + /// Parent Type: `DocTypeEdge` + public struct Edge: AffineGraphQL.SelectionSet { + public let __data: DataDict + public init(_dataDict: DataDict) { __data = _dataDict } + + public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.DocTypeEdge } + public static var __selections: [ApolloAPI.Selection] { [ + .field("__typename", String.self), + .field("node", Node.self), + ] } + + public var node: Node { __data["node"] } + + /// Workspace.RecentlyUpdatedDocs.Edge.Node + /// + /// Parent Type: `DocType` + public struct Node: AffineGraphQL.SelectionSet { + public let __data: DataDict + public init(_dataDict: DataDict) { __data = _dataDict } + + public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.DocType } + public static var __selections: [ApolloAPI.Selection] { [ + .field("__typename", String.self), + .field("id", String.self), + .field("title", String?.self), + .field("createdAt", AffineGraphQL.DateTime?.self), + .field("updatedAt", AffineGraphQL.DateTime?.self), + .field("creatorId", String?.self), + .field("lastUpdaterId", String?.self), + ] } + + public var id: String { __data["id"] } + public var title: String? { __data["title"] } + public var createdAt: AffineGraphQL.DateTime? { __data["createdAt"] } + public var updatedAt: AffineGraphQL.DateTime? { __data["updatedAt"] } + public var creatorId: String? { __data["creatorId"] } + public var lastUpdaterId: String? { __data["lastUpdaterId"] } + } + } + } + } + } +} diff --git a/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/InputObjects/CreateChatSessionInput.graphql.swift b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/InputObjects/CreateChatSessionInput.graphql.swift index f92474a65..c8c8fc915 100644 --- a/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/InputObjects/CreateChatSessionInput.graphql.swift +++ b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/InputObjects/CreateChatSessionInput.graphql.swift @@ -11,22 +11,29 @@ public struct CreateChatSessionInput: InputObject { } public init( - docId: String, + docId: GraphQLNullable = nil, + pinned: GraphQLNullable = nil, promptName: String, workspaceId: String ) { __data = InputDict([ "docId": docId, + "pinned": pinned, "promptName": promptName, "workspaceId": workspaceId ]) } - public var docId: String { + public var docId: GraphQLNullable { get { __data["docId"] } set { __data["docId"] = newValue } } + public var pinned: GraphQLNullable { + get { __data["pinned"] } + set { __data["pinned"] = newValue } + } + /// The prompt name to use for the session public var promptName: String { get { __data["promptName"] } diff --git a/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/InputObjects/UpdateChatSessionInput.graphql.swift b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/InputObjects/UpdateChatSessionInput.graphql.swift index afe008716..bb2bf1f04 100644 --- a/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/InputObjects/UpdateChatSessionInput.graphql.swift +++ b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/InputObjects/UpdateChatSessionInput.graphql.swift @@ -11,17 +11,33 @@ public struct UpdateChatSessionInput: InputObject { } public init( - promptName: String, + docId: GraphQLNullable = nil, + pinned: GraphQLNullable = nil, + promptName: GraphQLNullable = nil, sessionId: String ) { __data = InputDict([ + "docId": docId, + "pinned": pinned, "promptName": promptName, "sessionId": sessionId ]) } + /// The workspace id of the session + public var docId: GraphQLNullable { + get { __data["docId"] } + set { __data["docId"] = newValue } + } + + /// Whether to pin the session + public var pinned: GraphQLNullable { + get { __data["pinned"] } + set { __data["pinned"] = newValue } + } + /// The prompt name to use for the session - public var promptName: String { + public var promptName: GraphQLNullable { get { __data["promptName"] } set { __data["promptName"] = newValue } } diff --git a/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/Objects/StreamObject.graphql.swift b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/Objects/StreamObject.graphql.swift new file mode 100644 index 000000000..7857b6d3a --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/Objects/StreamObject.graphql.swift @@ -0,0 +1,12 @@ +// @generated +// This file was automatically generated and should not be edited. + +import ApolloAPI + +public extension Objects { + static let StreamObject = ApolloAPI.Object( + typename: "StreamObject", + implementedInterfaces: [], + keyFields: nil + ) +} \ No newline at end of file diff --git a/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/SchemaMetadata.graphql.swift b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/SchemaMetadata.graphql.swift index 2d0a2df16..414967d89 100644 --- a/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/SchemaMetadata.graphql.swift +++ b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/SchemaMetadata.graphql.swift @@ -81,6 +81,7 @@ public enum SchemaMetadata: ApolloAPI.SchemaMetadata { case "SearchResultObjectType": return AffineGraphQL.Objects.SearchResultObjectType case "SearchResultPagination": return AffineGraphQL.Objects.SearchResultPagination case "ServerConfigType": return AffineGraphQL.Objects.ServerConfigType + case "StreamObject": return AffineGraphQL.Objects.StreamObject case "SubscriptionPrice": return AffineGraphQL.Objects.SubscriptionPrice case "SubscriptionType": return AffineGraphQL.Objects.SubscriptionType case "TranscriptionItemType": return AffineGraphQL.Objects.TranscriptionItemType diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Package.resolved b/packages/frontend/apps/ios/App/Packages/Intelligents/Package.resolved deleted file mode 100644 index a0763ea43..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Package.resolved +++ /dev/null @@ -1,59 +0,0 @@ -{ - "pins" : [ - { - "identity" : "apollo-ios", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apollographql/apollo-ios.git", - "state" : { - "revision" : "39fea7617346c0731be25f61afd537e7032fb562", - "version" : "1.22.0" - } - }, - { - "identity" : "chidorimenu", - "kind" : "remoteSourceControl", - "location" : "https://github.com/Lakr233/ChidoriMenu", - "state" : { - "revision" : "3bb4323fe0f7f8f435d15656c3eeffcbb7c9c605", - "version" : "3.0.0" - } - }, - { - "identity" : "splash", - "kind" : "remoteSourceControl", - "location" : "https://github.com/JohnSundell/Splash", - "state" : { - "revision" : "7f4df436eb78fe64fe2c32c58006e9949fa28ad8", - "version" : "0.16.0" - } - }, - { - "identity" : "swift-cmark", - "kind" : "remoteSourceControl", - "location" : "https://github.com/swiftlang/swift-cmark", - "state" : { - "revision" : "b022b08312decdc46585e0b3440d97f6f22ef703", - "version" : "0.6.0" - } - }, - { - "identity" : "swift-collections", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-collections", - "state" : { - "revision" : "c1805596154bb3a265fd91b8ac0c4433b4348fb0", - "version" : "1.2.0" - } - }, - { - "identity" : "swift-eventsource", - "kind" : "remoteSourceControl", - "location" : "https://github.com/LaunchDarkly/swift-eventsource.git", - "state" : { - "revision" : "57051701c58a93603ffa2051f8e9cf0c8cff7814", - "version" : "3.3.0" - } - } - ], - "version" : 2 -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Package.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Package.swift index 1f7fef2c5..e8104d16e 100644 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Package.swift +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Package.swift @@ -7,29 +7,30 @@ let package = Package( name: "Intelligents", defaultLocalization: "en", platforms: [ - .iOS(.v15), - .macCatalyst(.v15), + .iOS(.v16), ], products: [ .library(name: "Intelligents", targets: ["Intelligents"]), ], dependencies: [ .package(path: "../AffineGraphQL"), - .package(path: "../MarkdownView"), - .package(url: "https://github.com/apollographql/apollo-ios.git", from: "1.22.0"), - .package(url: "https://github.com/LaunchDarkly/swift-eventsource.git", from: "3.3.0"), + .package(url: "https://github.com/apollographql/apollo-ios.git", from: "1.18.0"), .package(url: "https://github.com/apple/swift-collections", from: "1.2.0"), - .package(url: "https://github.com/Lakr233/ChidoriMenu", from: "3.0.0"), + .package(url: "https://github.com/devxoul/Then", from: "3.0.0"), + .package(url: "https://github.com/SnapKit/SnapKit.git", from: "5.0.1"), + .package(url: "https://github.com/SwifterSwift/SwifterSwift.git", from: "6.0.0"), ], targets: [ .target(name: "Intelligents", dependencies: [ "AffineGraphQL", - "ChidoriMenu", - "MarkdownView", - "ChidoriMenu", + "SnapKit", + "Then", + "SwifterSwift", .product(name: "Apollo", package: "apollo-ios"), - .product(name: "LDSwiftEventSource", package: "swift-eventsource"), .product(name: "OrderedCollections", package: "swift-collections"), + ], resources: [ + .process("Resources/main.metal"), + .process("Interface/View/InputBox/InputBox.xcassets"), ]), ] ) diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Backend/Constant/Constant.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Backend/Constant/Constant.swift deleted file mode 100644 index e5f639f6f..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Backend/Constant/Constant.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// Constant.swift -// Intelligents -// -// Created by 秋星桥 on 2024/11/18. -// - -import UIKit - -enum Constant { - static let affineTabbarHeight: CGFloat = 44 - static let affineTintColor: UIColor = .init(red: 30 / 255, green: 150 / 255, blue: 235 / 255, alpha: 1.0) - - static var affineUpstreamURL = URL(string: "https://app.affine.pro/")! -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Backend/Constant/UnableTo.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Backend/Constant/UnableTo.swift deleted file mode 100644 index bb7c80943..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Backend/Constant/UnableTo.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// UnableTo.swift -// Intelligents -// -// Created by 秋星桥 on 4/1/25. -// - -import Foundation - -private let domain = "Intelligents" - -enum UnableTo { - static let identifyDocumentOrWorkspace = - NSError( - domain: domain, - code: -1, - userInfo: [NSLocalizedDescriptionKey: "Unable to identify the document or workspace"] - ) - - static let createSession = NSError( - domain: domain, - code: -1, - userInfo: [NSLocalizedDescriptionKey: "Unable to create a session"] - ) - - static let createMessage = NSError( - domain: domain, - code: -1, - userInfo: [NSLocalizedDescriptionKey: "Unable to create a message"] - ) - - static let compressImage = NSError( - domain: domain, - code: -1, - userInfo: [ - NSLocalizedDescriptionKey: "Failed to compress image data", - ] - ) - - static let clearHistory = NSError( - domain: domain, - code: -1, - userInfo: [NSLocalizedDescriptionKey: "Unable to clear history"] - ) -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Backend/Model/Chat.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Backend/Model/Chat.swift deleted file mode 100644 index 85fba639b..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Backend/Model/Chat.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// Chat.swift -// Intelligents -// -// Created by 秋星桥 on 2024/11/18. -// - -import Foundation - -struct Chat: Codable { - enum ParticipantType: String, Codable, Equatable { - case user - case bot - } - - var participant: ParticipantType - - typealias MarkdownDocument = String - var content: MarkdownDocument - var date: Date - - init(participant: ParticipantType, content: MarkdownDocument, date: Date = .init()) { - self.participant = participant - self.content = content - self.date = date - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Backend/Model/Prompt.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Backend/Model/Prompt.swift deleted file mode 100644 index 6bbac1b90..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Backend/Model/Prompt.swift +++ /dev/null @@ -1,53 +0,0 @@ -// -// Prompt.swift -// Intelligents -// -// Created by 秋星桥 on 2024/12/26. -// - -import Foundation - -enum Prompt: String { - #if DEBUG - case debug_action_dalle3 = "debug:action:dalle3" - case debug_action_fal_sd15 = "debug:action:fal-sd15" - case debug_action_fal_upscaler = "debug:action:fal-upscaler" - case debug_action_fal_remove_bg = "debug:action:fal-remove-bg" - case debug_action_fal_face_to_sticker = "debug:action:fal-face-to-sticker" - #endif - - case general_Chat_With_AFFiNE_AI = "Chat With AFFiNE AI" - case general_Summary = "Summary" - case general_Generate_a_caption = "Generate a caption" - case general_Summary_the_webpage = "Summary the webpage" - case general_Explain_this = "Explain this" - case general_Explain_this_image = "Explain this image" - case general_Explain_this_code = "Explain this code" - case general_Translate_to = "Translate to" - case general_Write_an_article_about_this = "Write an article about this" - case general_Write_a_twitter_about_this = "Write a twitter about this" - case general_Write_a_poem_about_this = "Write a poem about this" - case general_Write_a_blog_post_about_this = "Write a blog post about this" - case general_Write_outline = "Write outline" - case general_Change_tone_to = "Change tone to" - case general_Brainstorm_ideas_about_this = "Brainstorm ideas about this" - case general_Expand_mind_map = "Expand mind map" - case general_Improve_writing_for_it = "Improve writing for it" - case general_Improve_grammar_for_it = "Improve grammar for it" - case general_Fix_spelling_for_it = "Fix spelling for it" - case general_Find_action_items_from_it = "Find action items from it" - case general_Check_code_error = "Check code error" - case general_Create_headings = "Create headings" - case general_Make_it_real = "Make it real" - case general_Make_it_real_with_text = "Make it real with text" - case general_Make_it_longer = "Make it longer" - case general_Make_it_shorter = "Make it shorter" - case general_Continue_writing = "Continue writing" - - case workflow_presentation = "workflow:presentation" - case workflow_brainstorm = "workflow:brainstorm" - case workflow_image_sketch = "workflow:image-sketch" - case workflow_image_clay = "workflow:image-clay" - case workflow_image_anime = "workflow:image-anime" - case workflow_image_pixel = "workflow:image-pixel" -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Extension/Ext+EventHandler.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Extension/Ext+EventHandler.swift deleted file mode 100644 index f7ee027bb..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Extension/Ext+EventHandler.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// Ext+EventHandler.swift -// Intelligents -// -// Created by 秋星桥 on 2024/12/26. -// - -import Foundation -import LDSwiftEventSource - -class BlockEventHandler: EventHandler { - var onOpenedBlock: (() -> Void)? - var onClosedBlock: (() -> Void)? - var onMessageBlock: ((String, LDSwiftEventSource.MessageEvent) -> Void)? - var onCommentBlock: ((String) -> Void)? - var onErrorBlock: ((Error) -> Void)? - - public func onOpened() { - onOpenedBlock?() - } - - public func onClosed() { - onClosedBlock?() - } - - public func onMessage(eventType: String, messageEvent: LDSwiftEventSource.MessageEvent) { - onMessageBlock?(eventType, messageEvent) - } - - public func onComment(comment: String) { - onCommentBlock?(comment) - } - - public func onError(error: any Error) { - onErrorBlock?(error) - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Extension/Ext+String.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Extension/Ext+String.swift deleted file mode 100644 index 7feddb601..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Extension/Ext+String.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// Ext+String.swift -// Intelligents -// -// Created by 秋星桥 on 2024/11/18. -// - -import Foundation - -extension String { - func localized() -> String { - let ans = NSLocalizedString(self, bundle: Bundle.module, comment: "") - guard !ans.isEmpty else { - assertionFailure() - return self - } - return ans - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Extension/Ext+UIColor.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Extension/Ext+UIColor.swift deleted file mode 100644 index 932f5f3eb..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Extension/Ext+UIColor.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// Ext+UIColor.swift -// Intelligents -// -// Created by 秋星桥 on 2024/12/13. -// - -import UIKit - -extension UIColor { - static var accent: UIColor { - Constant.affineTintColor - } - - convenience init(light: UIColor, dark: UIColor) { - self.init(dynamicProvider: { traitCollection in - switch traitCollection.userInterfaceStyle { - case .light: - light - case .dark: - dark - default: - light - } - }) - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Extension/Ext+UIFont.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Extension/Ext+UIFont.swift deleted file mode 100644 index b94160a5c..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Extension/Ext+UIFont.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// Ext+UIFont.swift -// Intelligents -// -// Created by 秋星桥 on 2024/11/21. -// - -import UIKit - -extension UIFont { - static func preferredFont(for style: TextStyle, weight: Weight, italic: Bool = false) -> UIFont { - // Get the style's default pointSize - let traits = UITraitCollection(preferredContentSizeCategory: .large) - let desc = UIFontDescriptor.preferredFontDescriptor(withTextStyle: style, compatibleWith: traits) - - // Get the font at the default size and preferred weight - var font = UIFont.systemFont(ofSize: desc.pointSize, weight: weight) - if italic == true { - font = font.with([.traitItalic]) - } - - // Setup the font to be auto-scalable - let metrics = UIFontMetrics(forTextStyle: style) - return metrics.scaledFont(for: font) - } - - private func with(_ traits: UIFontDescriptor.SymbolicTraits...) -> UIFont { - guard let descriptor = fontDescriptor.withSymbolicTraits(UIFontDescriptor.SymbolicTraits(traits).union(fontDescriptor.symbolicTraits)) else { - return self - } - return UIFont(descriptor: descriptor, size: 0) - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Extension/Ext+UIView.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Extension/Ext+UIView.swift deleted file mode 100644 index 1da7e3b99..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Extension/Ext+UIView.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// Ext+UIView.swift -// Intelligents -// -// Created by 秋星桥 on 2024/11/18. -// - -import UIKit - -extension UIView { - var parentViewController: UIViewController? { - var responder: UIResponder? = self - while responder != nil { - if let responder = responder as? UIViewController { - return responder - } - responder = responder?.next - } - return nil - } - - func removeEveryAutoResizingMasks() { - var views: [UIView] = [self] - while let view = views.first { - views.removeFirst() - view.translatesAutoresizingMaskIntoConstraints = false - view.subviews.forEach { views.append($0) } - } - } - - #if DEBUG - func debugFrame() { - layer.borderWidth = 1 - layer.borderColor = [ - UIColor.red, - .green, - .blue, - .yellow, - .cyan, - .magenta, - .orange, - ].map(\.cgColor).randomElement() - subviews.forEach { $0.debugFrame() } - } - #endif -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Extension/Ext+UIViewController.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Extension/Ext+UIViewController.swift deleted file mode 100644 index eb58f6849..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Extension/Ext+UIViewController.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// Ext+UIViewController.swift -// Intelligents -// -// Created by 秋星桥 on 2024/11/18. -// - -import UIKit - -public extension UIViewController { - func presentIntoCurrentContext(withTargetController targetController: UIViewController, animated: Bool = true) { - if let nav = self as? UINavigationController { - nav.pushViewController(targetController, animated: animated) - } else if let nav = navigationController { - nav.pushViewController(targetController, animated: animated) - } else { - present(targetController, animated: animated, completion: nil) - } - } - - func dismissInContext() { - if let nav = navigationController { - nav.popViewController(animated: true) - } else { - dismiss(animated: true, completion: nil) - } - } - - func hideKeyboardWhenTappedAround() { - let tap = UITapGestureRecognizer(target: self, action: #selector(UIViewController.dismissKeyboard)) - tap.cancelsTouchesInView = false - view.addGestureRecognizer(tap) - } - - @objc func dismissKeyboard() { - view.endEditing(true) - } - - func presentError(_ error: Error, onDismiss: @escaping () -> Void = {}) { - DispatchQueue.main.async { [self] in - let alert = UIAlertController( - title: "Error".localized(), - message: error.localizedDescription, - preferredStyle: .alert - ) - alert.addAction(UIAlertAction(title: "OK".localized(), style: .default) { _ in - onDismiss() - }) - present(alert, animated: true) - } - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Extension/Ext+print.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Extension/Ext+print.swift deleted file mode 100644 index 1959304e7..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Extension/Ext+print.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// Ext+print.swift -// Intelligents -// -// Created by 秋星桥 on 2024/11/18. -// - -import Foundation - -public func print(_ items: Any..., separator: String = " ", terminator: String = "\n") { - #if DEBUG - Swift.print(items, separator: separator, terminator: terminator) - #endif -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Intelligents.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Intelligents.swift index b16a00f45..f07f81b7e 100644 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Intelligents.swift +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Intelligents.swift @@ -5,19 +5,7 @@ import AffineGraphQL import Apollo import Foundation -public enum Intelligents { - public private(set) static var qlClient: ApolloClient = createQlClient() - - public static func setUpstreamEndpoint(_ upstream: String) { - guard let url = URL(string: upstream) else { - assertionFailure() - return - } - print("[*] setting up upstream endpoint to \(url.absoluteString)") - Constant.affineUpstreamURL = url - qlClient = createQlClient() - } -} +public enum Intelligents {} private extension Intelligents { private final class URLSessionCookieClient: URLSessionClient { @@ -29,18 +17,4 @@ private extension Intelligents { } } } - - static func createQlClient() -> ApolloClient { - let store = ApolloStore(cache: InMemoryNormalizedCache()) - let provider = DefaultInterceptorProvider( - client: URLSessionCookieClient(), - shouldInvalidateClientOnDeinit: true, - store: store - ) - let transport = RequestChainNetworkTransport( - interceptorProvider: provider, - endpointURL: Constant.affineUpstreamURL.appendingPathComponent("graphql") - ) - return .init(networkTransport: transport, store: store) - } } diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Controller/IntelligentsController/BlurTransition.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Controller/IntelligentsController/BlurTransition.swift new file mode 100644 index 000000000..f2929ea94 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Controller/IntelligentsController/BlurTransition.swift @@ -0,0 +1,158 @@ +// +// BlurTransition.swift +// BlurTransition +// +// Created by 秋星桥 on 6/16/23. +// + +import UIKit + +extension UIViewController { + func presentWithFullScreenBlurTransition(_ viewController: UIViewController) { + viewController.modalPresentationStyle = .custom + viewController.transitioningDelegate = BlurTransitioningDelegate.shared + present(viewController, animated: true) + } +} + +class BlurTransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate { + static let shared = BlurTransitioningDelegate() + + func animationController( + forPresented _: UIViewController, + presenting _: UIViewController, + source _: UIViewController + ) -> UIViewControllerAnimatedTransitioning? { + BlurTransitionAnimator(presenting: true) + } + + func animationController( + forDismissed _: UIViewController + ) -> UIViewControllerAnimatedTransitioning? { + BlurTransitionAnimator(presenting: false) + } +} + +class BlurTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning { + private let presenting: Bool + + private let snapshotViewTag = "snapshotView".hashValue + private let blurViewTag = "blurView".hashValue + + init(presenting: Bool) { + self.presenting = presenting + super.init() + } + + func transitionDuration(using _: UIViewControllerContextTransitioning?) -> TimeInterval { + 0.5 + } + + func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { + if presenting { + animatePresentation(using: transitionContext) + } else { + animateDismissal(using: transitionContext) + } + } + + private func animatePresentation(using transitionContext: UIViewControllerContextTransitioning) { + guard let toViewController = transitionContext.viewController(forKey: .to), + let fromViewController = transitionContext.viewController(forKey: .from) + else { + transitionContext.completeTransition(false) + assertionFailure() + return + } + + let toView = toViewController.view! + let fromView = fromViewController.view! + + let containerView = transitionContext.containerView + + guard let fromViewSnapshot = fromView.snapshotView(afterScreenUpdates: false) else { + transitionContext.completeTransition(false) + assertionFailure() + return + } + fromViewSnapshot.frame = fromView.frame + fromViewSnapshot.tag = snapshotViewTag + containerView.addSubview(fromViewSnapshot) + fromView.isHidden = true + + let blurEffectView = UIVisualEffectView() + blurEffectView.frame = containerView.bounds + blurEffectView.tag = blurViewTag + containerView.addSubview(blurEffectView) + + toView.frame = containerView.bounds + toView.alpha = 0 + toView.transform = CGAffineTransform(scaleX: 1.05, y: 1.05) + containerView.addSubview(toView) + + toView.layoutIfNeeded() + + performWithAnimation(animations: { + blurEffectView.effect = UIBlurEffect(style: .systemMaterial) + fromViewSnapshot.transform = CGAffineTransform(scaleX: 0.95, y: 0.95) + toView.alpha = 1 + toView.transform = .identity + fromView.layoutIfNeeded() + toView.layoutIfNeeded() + }) { _ in + let success = !transitionContext.transitionWasCancelled + if !success { + assertionFailure() + fromView.isHidden = false + fromViewSnapshot.removeFromSuperview() + blurEffectView.removeFromSuperview() + toView.removeFromSuperview() + } + transitionContext.completeTransition(success) + } + } + + private func animateDismissal(using transitionContext: UIViewControllerContextTransitioning) { + guard let fromViewController = transitionContext.viewController(forKey: .from), + let toViewController = transitionContext.viewController(forKey: .to) + else { + transitionContext.completeTransition(false) + assertionFailure() + return + } + + let fromView = fromViewController.view! + let toView = toViewController.view! + let containerView = transitionContext.containerView + + guard let fromViewSnapshot = containerView.viewWithTag(snapshotViewTag), + let blurEffectView = containerView.viewWithTag(blurViewTag) as? UIVisualEffectView + else { + toView.isHidden = false + assertionFailure() + transitionContext.completeTransition(true) + return + } + + performWithAnimation(animations: { + fromViewSnapshot.transform = .identity + blurEffectView.effect = nil + fromView.transform = CGAffineTransform(scaleX: 1.05, y: 1.05) + fromView.alpha = 0 + }) { _ in + let success = !transitionContext.transitionWasCancelled + if success { + toView.isHidden = false + fromViewSnapshot.removeFromSuperview() + blurEffectView.removeFromSuperview() + fromView.layoutIfNeeded() + toView.layoutIfNeeded() + } else { + assertionFailure() + fromView.transform = .identity + fromView.alpha = 1 + } + transitionContext.completeTransition(success) + } + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Controller/IntelligentsController/IntelligentsController.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Controller/IntelligentsController/IntelligentsController.swift new file mode 100644 index 000000000..bca2bf4c1 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Controller/IntelligentsController/IntelligentsController.swift @@ -0,0 +1,27 @@ +// +// IntelligentsController.swift +// Intelligents +// +// Created by 秋星桥 on 6/17/25. +// + +import UIKit + +public class IntelligentsController: UINavigationController { + public init() { + super.init(rootViewController: MainViewController()) + modalPresentationStyle = .custom + transitioningDelegate = BlurTransitioningDelegate.shared + setNavigationBarHidden(true, animated: false) + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError() + } + + override public func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .systemBackground + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Controller/MainViewController/MainViewController+Header.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Controller/MainViewController/MainViewController+Header.swift new file mode 100644 index 000000000..36fb59af9 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Controller/MainViewController/MainViewController+Header.swift @@ -0,0 +1,22 @@ +// +// MainViewController+Header.swift +// Intelligents +// +// Created by 秋星桥 on 6/19/25. +// + +import UIKit + +extension MainViewController: MainHeaderViewDelegate { + func mainHeaderViewDidTapClose() { + dismiss(animated: true) + } + + func mainHeaderViewDidTapDropdown() { + print(#function) + } + + func mainHeaderViewDidTapMenu() { + print(#function) + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Controller/MainViewController/MainViewController+Input.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Controller/MainViewController/MainViewController+Input.swift new file mode 100644 index 000000000..c1304562d --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Controller/MainViewController/MainViewController+Input.swift @@ -0,0 +1,123 @@ +// +// MainViewController+Input.swift +// Intelligents +// +// Created by 秋星桥 on 6/19/25. +// + +import PhotosUI +import UIKit +import UniformTypeIdentifiers + +extension MainViewController: InputBoxDelegate { + func inputBoxDidSelectTakePhoto(_: InputBox) { + let imagePickerController = UIImagePickerController() + imagePickerController.delegate = self + imagePickerController.sourceType = .camera + imagePickerController.allowsEditing = false + present(imagePickerController, animated: true) + } + + func inputBoxDidSelectPhotoLibrary(_: InputBox) { + var configuration = PHPickerConfiguration() + configuration.filter = .images + configuration.selectionLimit = 0 // 0 means no limit + + let picker = PHPickerViewController(configuration: configuration) + picker.delegate = self + present(picker, animated: true) + } + + func inputBoxDidSelectAttachFiles(_: InputBox) { + let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [ + .pdf, .plainText, .commaSeparatedText, .data, + ]) + documentPicker.delegate = self + documentPicker.allowsMultipleSelection = false + present(documentPicker, animated: true) + } + + func inputBoxDidSelectEmbedDocs(_ inputBox: InputBox) { + print(#function, inputBox) + } + + func inputBoxDidSelectAttachment(_ inputBox: InputBox) { + print(#function, inputBox) + } + + func inputBoxDidSend(_ inputBox: InputBox) { + print(#function, inputBox, inputBox.viewModel) + } + + func inputBoxTextDidChange(_ text: String) { + print(#function, text) + } +} + +// MARK: - UIImagePickerControllerDelegate + +extension MainViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { + func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) { + defer { picker.dismiss(animated: true) } + + guard let image = info[.originalImage] as? UIImage else { return } + inputBox.addImageAttachment(image) + } + + func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { + picker.dismiss(animated: true) + } +} + +// MARK: - PHPickerViewControllerDelegate + +extension MainViewController: PHPickerViewControllerDelegate { + func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { + defer { picker.dismiss(animated: true) } + + for result in results { + if result.itemProvider.canLoadObject(ofClass: UIImage.self) { + result.itemProvider.loadObject(ofClass: UIImage.self) { [weak self] object, error in + guard let image = object as? UIImage, error == nil else { return } + + DispatchQueue.main.async { + self?.inputBox.addImageAttachment(image) + } + } + } + } + } +} + +// MARK: - UIDocumentPickerDelegate + +extension MainViewController: UIDocumentPickerDelegate { + func documentPicker(_: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { + for url in urls { + // Start accessing security-scoped resource + guard url.startAccessingSecurityScopedResource() else { continue } + defer { url.stopAccessingSecurityScopedResource() } + + // Copy file to temporary directory + let context = IntelligentContext.shared + context.prepareTemporaryDirectory() + + let tempURL = context.temporaryDirectory.appendingPathComponent(url.lastPathComponent) + + do { + // Remove existing file if it exists + if FileManager.default.fileExists(atPath: tempURL.path) { + try FileManager.default.removeItem(at: tempURL) + } + + // Copy file to temporary directory + try FileManager.default.copyItem(at: url, to: tempURL) + + // Add file attachment using the temporary URL + inputBox.addFileAttachment(tempURL) + } catch { + print("Failed to copy file: \(error)") + } + } + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Controller/MainViewController/MainViewController.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Controller/MainViewController/MainViewController.swift new file mode 100644 index 000000000..87afcd687 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Controller/MainViewController/MainViewController.swift @@ -0,0 +1,59 @@ +import Combine +import SnapKit +import Then +import UIKit + +class MainViewController: UIViewController { + // MARK: - UI Components + + lazy var headerView = MainHeaderView().then { + $0.delegate = self + } + + lazy var inputBox = InputBox().then { + $0.delegate = self + } + + // MARK: - Properties + + private var cancellables = Set() + private let intelligentContext = IntelligentContext.shared + + // MARK: - Lifecycle + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .affineLayerBackgroundPrimary + + let inputBox = InputBox().then { + $0.delegate = self + } + self.inputBox = inputBox + + view.addSubview(headerView) + view.addSubview(inputBox) + + headerView.snp.makeConstraints { make in + make.top.equalTo(view.safeAreaLayoutGuide) + make.leading.trailing.equalToSuperview() + } + + inputBox.snp.makeConstraints { make in + make.leading.trailing.equalToSuperview() + make.bottom.equalTo(view.keyboardLayoutGuide.snp.top) + } + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + navigationController!.setNavigationBarHidden(true, animated: animated) + DispatchQueue.main.async { + self.inputBox.textView.becomeFirstResponder() + } + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + navigationController!.setNavigationBarHidden(false, animated: animated) + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Effect/ParticleView+Removal.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Effect/ParticleView+Removal.swift new file mode 100644 index 000000000..51e1e621c --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Effect/ParticleView+Removal.swift @@ -0,0 +1,45 @@ +// +// ParticleView+Removal.swift +// UIEffectKit +// +// Created by 秋星桥 on 6/13/25. +// + +import UIKit + +public extension UIView { + func removeFromSuperviewWithExplodeEffect() { + guard let superview else { return } + guard let window else { + removeFromSuperview() + return + } + guard MTLCreateSystemDefaultDevice() != nil else { + removeFromSuperview() + return + } + + let image = createViewSnapshot() + guard let cgImage = image.cgImage else { + removeFromSuperview() + return + } + + let frameInWindow = superview.convert(frame, to: window) + let particleView = ParticleView(frame: frameInWindow) + + window.addSubview(particleView) + particleView.layer.zPosition = 1000 + particleView.frame = frameInWindow + particleView.setNeedsLayout() + particleView.layoutIfNeeded() + + particleView.beginWith(cgImage, targetFrame: frameInWindow, onComplete: { + particleView.removeFromSuperview() + }, onFirstFrameRendered: { [weak self] in + DispatchQueue.main.async { + self?.removeFromSuperview() + } + }) + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Effect/ParticleView+Renderer.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Effect/ParticleView+Renderer.swift new file mode 100644 index 000000000..4e7e1bf67 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Effect/ParticleView+Renderer.swift @@ -0,0 +1,331 @@ +// +// ParticleView+Renderer.swift +// UIEffectKit +// +// Created by 秋星桥 on 6/13/25. +// + +import MetalKit + +extension ParticleView { + class Renderer: NSObject, MTKViewDelegate { + private struct Particle { + var position: simd_float2 + var velocity: simd_float2 + var life: simd_float1 + var duration: simd_float1 + } + + private struct Vertex { + var position: simd_float4 + var uv: simd_float2 + var opacity: simd_float1 + } + + private var isPrepared = false + private var renderPipeline: MTLRenderPipelineState! + private var computePipeline: MTLComputePipelineState! + private var vertexBuffer: MTLBuffer! + private var particleBuffer: MTLBuffer! + private var particleCount: Int = 0 + private var texture: MTLTexture! + private var targetFrameSize: simd_float2 = .zero + private var stepSize: Float = 0 + private var commandQueue: MTLCommandQueue! + private var maxLife: Float = 0 + private var onComplete: (() -> Void)? + private var onFirstFrameRendered: (() -> Void)? + private var hasRenderedFirstFrame = false + private var device: MTLDevice! + + func prepareResources( + with device: MTLDevice, + image: CGImage, + targetFrame: CGRect, + onComplete: @escaping () -> Void, + onFirstFrameRendered: @escaping () -> Void + ) { + guard !isPrepared else { return } + + self.device = device + self.onComplete = onComplete + self.onFirstFrameRendered = onFirstFrameRendered + let integralTargetFrame = targetFrame.integral + + DispatchQueue.global(qos: .userInitiated).async { [weak self] in + guard let self else { return } + + setupPipelineStates(with: device) + setupVertexBuffer(with: device) + setupParticleSystem(targetFrame: integralTargetFrame, device: device) + setupTexture(from: image, device: device) + finalizeSetup(targetFrame: integralTargetFrame, device: device) + + DispatchQueue.main.async { self.isPrepared = true } + } + } + + private func setupPipelineStates(with device: MTLDevice) { + let library = try! device.makeDefaultLibrary(bundle: .module) + + let particleVertexFunction = library.makeFunction(name: "PTS_ParticleVertex")! + let particleFragmentFunction = library.makeFunction(name: "PTS_ParticleFragment")! + let updateParticlesFunction = library.makeFunction(name: "PTS_UpdateParticles")! + + let renderPipelineDescriptor = createRenderPipelineDescriptor( + vertexFunction: particleVertexFunction, + fragmentFunction: particleFragmentFunction + ) + + do { + renderPipeline = try device.makeRenderPipelineState(descriptor: renderPipelineDescriptor) + computePipeline = try device.makeComputePipelineState(function: updateParticlesFunction) + } catch { + fatalError("failed to create pipeline states: \(error)") + } + } + + private func createRenderPipelineDescriptor( + vertexFunction: MTLFunction, + fragmentFunction: MTLFunction + ) -> MTLRenderPipelineDescriptor { + let descriptor = MTLRenderPipelineDescriptor() + descriptor.colorAttachments[0].pixelFormat = .bgra8Unorm + descriptor.colorAttachments[0].isBlendingEnabled = true + descriptor.colorAttachments[0].rgbBlendOperation = .add + descriptor.colorAttachments[0].alphaBlendOperation = .add + descriptor.colorAttachments[0].sourceRGBBlendFactor = .sourceAlpha + descriptor.colorAttachments[0].sourceAlphaBlendFactor = .sourceAlpha + descriptor.colorAttachments[0].destinationRGBBlendFactor = .oneMinusSourceAlpha + descriptor.colorAttachments[0].destinationAlphaBlendFactor = .oneMinusSourceAlpha + descriptor.vertexFunction = vertexFunction + descriptor.fragmentFunction = fragmentFunction + return descriptor + } + + func mtkView(_: MTKView, drawableSizeWillChange _: CGSize) { + // No-op since view is not subject to resize + } + + func draw(in view: MTKView) { + guard isPrepared else { return } + + updateParticles() + + if checkAllParticlesDead() { + DispatchQueue.main.async { [weak self] in + self?.onComplete?() + } + return + } + + renderParticles(in: view) + + if !hasRenderedFirstFrame { + hasRenderedFirstFrame = true + DispatchQueue.main.async { [weak self] in + self?.onFirstFrameRendered?() + } + } + } + + private func updateParticles() { + let maxThreadsPerThreadgroup = computePipeline.maxTotalThreadsPerThreadgroup + let threadgroupSize = min(maxThreadsPerThreadgroup, 2048) + let threadgroupCount = (particleCount + threadgroupSize - 1) / threadgroupSize + + let computeCommandBuffer = commandQueue.makeCommandBuffer()! + + let computeCommandEncoder = computeCommandBuffer.makeComputeCommandEncoder()! + computeCommandEncoder.setComputePipelineState(computePipeline) + computeCommandEncoder.setBuffer(particleBuffer, offset: 0, index: 0) + computeCommandEncoder.dispatchThreadgroups( + .init(width: threadgroupCount, height: 1, depth: 1), + threadsPerThreadgroup: .init(width: threadgroupSize, height: 1, depth: 1) + ) + computeCommandEncoder.endEncoding() + + computeCommandBuffer.commit() + } + + private func checkAllParticlesDead() -> Bool { + let particleData = particleBuffer + .contents() + .bindMemory(to: Particle.self, capacity: particleCount) + + for i in 0 ..< particleCount { + if particleData[i].life >= 0 { + return false + } + } + return true + } + + private func renderParticles(in view: MTKView) { + let viewCGSize = view.bounds.size + var viewSize = simd_float2(Float(viewCGSize.width), Float(viewCGSize.height)) + + let renderCommandBuffer = commandQueue.makeCommandBuffer()! + + guard let renderPassDescriptor = view.currentRenderPassDescriptor else { return } + renderPassDescriptor.colorAttachments[0].loadAction = .clear + renderPassDescriptor.colorAttachments[0].clearColor = .init(red: 0, green: 0, blue: 0, alpha: 0) + + let renderCommandEncoder = renderCommandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor)! + renderCommandEncoder.setRenderPipelineState(renderPipeline) + renderCommandEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0) + + withUnsafeBytes(of: &viewSize) { pointer in + renderCommandEncoder.setVertexBytes( + pointer.baseAddress!, + length: MemoryLayout.size, + index: 1 + ) + } + renderCommandEncoder.setVertexBuffer(particleBuffer, offset: 0, index: 2) + withUnsafeBytes(of: &targetFrameSize) { pointer in + renderCommandEncoder.setVertexBytes( + pointer.baseAddress!, + length: MemoryLayout.size, + index: 3 + ) + } + withUnsafeBytes(of: &stepSize) { pointer in + renderCommandEncoder.setVertexBytes( + pointer.baseAddress!, + length: MemoryLayout.size, + index: 4 + ) + } + renderCommandEncoder.setFragmentTexture(texture, index: 0) + + setupSampler(renderCommandEncoder: renderCommandEncoder) + + renderCommandEncoder.drawPrimitives( + type: .triangleStrip, + vertexStart: 0, + vertexCount: 4, + instanceCount: particleCount + ) + renderCommandEncoder.endEncoding() + + renderCommandBuffer.present(view.currentDrawable!) + renderCommandBuffer.commit() + } + + private func setupSampler(renderCommandEncoder: MTLRenderCommandEncoder) { + let samplerDescriptor = MTLSamplerDescriptor() + samplerDescriptor.minFilter = .linear + samplerDescriptor.magFilter = .linear + samplerDescriptor.mipFilter = .notMipmapped + samplerDescriptor.sAddressMode = .clampToEdge + samplerDescriptor.tAddressMode = .clampToEdge + let samplerState = device.makeSamplerState(descriptor: samplerDescriptor) + renderCommandEncoder.setFragmentSamplerState(samplerState, index: 0) + } + } +} + +extension ParticleView.Renderer { + private func setupVertexBuffer(with device: MTLDevice) { + let vertices: [Vertex] = [ + .init(position: .init(0, 0, 0, 1), uv: .init(0, 0), opacity: .zero), + .init(position: .init(1, 0, 0, 1), uv: .init(1, 0), opacity: .zero), + .init(position: .init(0, 1, 0, 1), uv: .init(0, 1), opacity: .zero), + .init(position: .init(1, 1, 0, 1), uv: .init(1, 1), opacity: .zero), + ] + let vertexBuffer = vertices.withUnsafeBytes { pointer in + device.makeBuffer( + bytes: pointer.baseAddress!, + length: MemoryLayout.stride * vertices.count, + options: .storageModeShared + ) + } + self.vertexBuffer = vertexBuffer! + } + + private func setupParticleSystem(targetFrame: CGRect, device: MTLDevice) { + var particles = [Particle]() + let targetFrameHeight = Float(targetFrame.height) + let targetFrameWidth = Float(targetFrame.width) + let particleStep = 1 + + let estimatedParticleCount = 1 + * Int(targetFrameWidth / Float(particleStep)) + * Int(targetFrameHeight / Float(particleStep)) + let pixelMultiplier = 1 + particles.reserveCapacity(estimatedParticleCount * pixelMultiplier) + + for y in stride(from: 0, to: Int(targetFrameHeight), by: particleStep) { + for x in stride(from: 0, to: Int(targetFrameWidth), by: particleStep) { + let particle = createParticle(x: x, y: y, step: particleStep) + for _ in 0 ..< pixelMultiplier { + particles.append(particle) + } + } + } + + particleCount = particles.count + let particleBuffer = particles.withUnsafeBytes { pointer in + device.makeBuffer( + bytes: pointer.baseAddress!, + length: MemoryLayout.stride * particles.count, + options: .storageModeShared + ) + } + self.particleBuffer = particleBuffer! + stepSize = Float(particleStep) + } + + private func createParticle(x: Int, y: Int, step: Int) -> Particle { + let particleDuration: Float = .random(in: 20 ... 60) + let initialX = Float(x) + Float(step) / 2.0 + let initialY = Float(y) + Float(step) / 2.0 + return .init( + position: .init(initialX, initialY), + velocity: .init( + cos(Float.random(in: 0 ... (2 * Float.pi))) * Float.random(in: 1 ... 4), + sin(Float.random(in: 0 ... (2 * Float.pi))) * Float.random(in: 1 ... 4) - 2.5 + ), + life: simd_float1(particleDuration), + duration: simd_float1(particleDuration) + ) + } + + private func setupTexture(from image: CGImage, device: MTLDevice) { + let colorSpace = CGColorSpace(name: CGColorSpace.sRGB)! + let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue) + + guard let context = CGContext( + data: nil, + width: image.width, + height: image.height, + bitsPerComponent: 8, + bytesPerRow: 0, + space: colorSpace, + bitmapInfo: bitmapInfo.rawValue + ) else { return } + context.draw(image, in: CGRect(x: 0, y: 0, width: image.width, height: image.height)) + + guard let convertedImage = context.makeImage() else { return } + + let textureLoader = MTKTextureLoader(device: device) + let textureLoaderOptions: [MTKTextureLoader.Option: Any] = [ + .textureStorageMode: MTLStorageMode.private.rawValue, + .SRGB: false, + ] + guard let texture = try? textureLoader.newTexture( + cgImage: convertedImage, + options: textureLoaderOptions + ) else { return } + + self.texture = texture + } + + private func finalizeSetup(targetFrame: CGRect, device: MTLDevice) { + let targetFrameWidth = Float(targetFrame.width) + let targetFrameHeight = Float(targetFrame.height) + targetFrameSize = .init(targetFrameWidth, targetFrameHeight) + commandQueue = device.makeCommandQueue()! + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Effect/ParticleView.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Effect/ParticleView.swift new file mode 100644 index 000000000..f2c8b666e --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Effect/ParticleView.swift @@ -0,0 +1,79 @@ +// +// ParticleView.swift +// TrollNFC +// +// Created by 砍砍 on 6/8/25. +// + +import MetalKit +import simd + +import UIKit + +class ParticleView: UIView { + private var device: MTLDevice! + private var metalView: MTKView! + private var renderer = Renderer() + + override init(frame: CGRect) { + super.init(frame: frame) + setupMetalDevice() + setupMetalView() + setupViewProperties() + } + + private func setupMetalDevice() { + guard let device = Self.createSystemDefaultDevice() else { + fatalError("failed to create Metal device") + } + self.device = device + } + + private func setupMetalView() { + metalView = MTKView(frame: .zero, device: device) + configureMetalView() + addSubview(metalView) + } + + private func configureMetalView() { + metalView.layer.isOpaque = false + metalView.backgroundColor = UIColor.clear + metalView.delegate = renderer + } + + private func setupViewProperties() { + clipsToBounds = false + metalView.clipsToBounds = false + } + + private static func createSystemDefaultDevice() -> MTLDevice? { + MTLCreateSystemDefaultDevice() + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError() + } + + func beginWith( + _ image: CGImage, + targetFrame: CGRect, + onComplete: @escaping () -> Void, + onFirstFrameRendered: @escaping () -> Void + ) { + renderer.prepareResources( + with: device, + image: image, + targetFrame: targetFrame, + onComplete: onComplete, + onFirstFrameRendered: onFirstFrameRendered + ) + metalView.draw() + } + + override func layoutSubviews() { + super.layoutSubviews() + let expandedBounds = bounds.insetBy(dx: -bounds.width, dy: -bounds.height) + metalView.frame = expandedBounds + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Effect/UIView+createViewSnapshot.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Effect/UIView+createViewSnapshot.swift new file mode 100644 index 000000000..a36639178 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Effect/UIView+createViewSnapshot.swift @@ -0,0 +1,22 @@ +// +// UIView+createViewSnapshot.swift +// Intelligents +// +// Created by 秋星桥 on 6/19/25. +// + +import UIKit + +public extension UIView { + func createViewSnapshot() -> UIImage { + let renderer = UIGraphicsImageRenderer(bounds: bounds) + return renderer.image { context in + // clear the background + context.cgContext.setFillColor(UIColor.clear.cgColor) + context.cgContext.fill(bounds) + + // MUST USE DRAW HIERARCHY TO RENDER VISUAL EFFECT VIEW + self.drawHierarchy(in: bounds, afterScreenUpdates: false) + } + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/InputEditView/AttachmentBannerView.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/InputEditView/AttachmentBannerView.swift deleted file mode 100644 index e659ff48d..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/InputEditView/AttachmentBannerView.swift +++ /dev/null @@ -1,163 +0,0 @@ -// -// AttachmentBannerView.swift -// Intelligents -// -// Created by 秋星桥 on 2024/11/18. -// - -import UIKit - -private let attachmentSize: CGFloat = 100 -private let attachmentSpacing: CGFloat = 16 - -class AttachmentBannerView: UIScrollView { - var readAttachments: (() -> ([UIImage]))? - var onAttachmentsDelete: ((Int) -> Void)? - var attachments: [UIImage] { - get { readAttachments?() ?? [] } - set { assertionFailure() } - } - - override var intrinsicContentSize: CGSize { - if attachments.isEmpty { return .zero } - return .init( - width: (attachmentSize + attachmentSize) * CGFloat(attachments.count) - - attachmentSpacing, - height: attachmentSize - ) - } - - let stackView = UIStackView() - - init() { - super.init(frame: .zero) - - translatesAutoresizingMaskIntoConstraints = false - - showsHorizontalScrollIndicator = false - showsVerticalScrollIndicator = false - - stackView.axis = .horizontal - stackView.spacing = attachmentSpacing - stackView.alignment = .center - stackView.distribution = .fill - stackView.translatesAutoresizingMaskIntoConstraints = false - addSubview(stackView) - [ - stackView.topAnchor.constraint(equalTo: topAnchor), - stackView.leadingAnchor.constraint(equalTo: leadingAnchor), - stackView.trailingAnchor.constraint(equalTo: trailingAnchor), - stackView.bottomAnchor.constraint(equalTo: bottomAnchor), - ].forEach { $0.isActive = true } - - rebuildViews() - } - - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError() - } - - var reusableViews = [AttachmentPreviewView]() - - func rebuildViews() { - let attachments = attachments - - if reusableViews.count > attachments.count { - for index in attachments.count ..< reusableViews.count { - reusableViews[index].removeFromSuperview() - } - reusableViews.removeLast(reusableViews.count - attachments.count) - } - if reusableViews.count < attachments.count { - for _ in reusableViews.count ..< attachments.count { - let view = AttachmentPreviewView() - view.alpha = 0 - reusableViews.append(view) - } - } - - assert(reusableViews.count == attachments.count) - - for (index, attachment) in attachments.enumerated() { - let view = reusableViews[index] - view.imageView.image = attachment - stackView.addArrangedSubview(view) - view.deleteButtonAction = { [weak self] in - self?.onAttachmentsDelete?(index) - } - } - - invalidateIntrinsicContentSize() - contentSize = intrinsicContentSize - UIView.performWithoutAnimation { - self.layoutIfNeeded() - } - - UIView.animate(withDuration: 0.3) { - for view in self.reusableViews { - view.alpha = 1 - } - } - } -} - -extension AttachmentBannerView { - class AttachmentPreviewView: UIView { - let imageView = UIImageView() - let deleteButton = UIButton() - - var deleteButtonAction: (() -> Void)? - - override var intrinsicContentSize: CGSize { - .init(width: attachmentSize, height: attachmentSize) - } - - init() { - super.init(frame: .zero) - addSubview(imageView) - addSubview(deleteButton) - - layer.cornerRadius = 8 - clipsToBounds = true - - imageView.contentMode = .scaleAspectFill - imageView.clipsToBounds = true - imageView.translatesAutoresizingMaskIntoConstraints = false - [ - imageView.topAnchor.constraint(equalTo: topAnchor), - imageView.leadingAnchor.constraint(equalTo: leadingAnchor), - imageView.trailingAnchor.constraint(equalTo: trailingAnchor), - imageView.bottomAnchor.constraint(equalTo: bottomAnchor), - ].forEach { $0.isActive = true } - - deleteButton.setImage(.init(named: "close", in: .module, with: nil), for: .normal) - deleteButton.imageView?.contentMode = .scaleAspectFit - deleteButton.tintColor = .white - deleteButton.translatesAutoresizingMaskIntoConstraints = false - [ - deleteButton.topAnchor.constraint(equalTo: topAnchor, constant: 4), - deleteButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -4), - deleteButton.widthAnchor.constraint(equalToConstant: 32), - deleteButton.heightAnchor.constraint(equalToConstant: 32), - ].forEach { $0.isActive = true } - - deleteButton.addTarget(self, action: #selector(deleteButtonTapped), for: .touchUpInside) - - [ - widthAnchor.constraint(equalToConstant: attachmentSize), - heightAnchor.constraint(equalToConstant: attachmentSize), - ].forEach { $0.isActive = true } - } - - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError() - } - - @objc func deleteButtonTapped() { - deleteButtonAction?() - deleteButtonAction = nil - } - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/InputEditView/InputEditView+Camera.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/InputEditView/InputEditView+Camera.swift deleted file mode 100644 index 54babfff8..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/InputEditView/InputEditView+Camera.swift +++ /dev/null @@ -1,63 +0,0 @@ -// -// InputEditView+Camera.swift -// Intelligents -// -// Created by 秋星桥 on 2024/12/6. -// - -import AVKit -import UIKit - -extension InputEditView: UIImagePickerControllerDelegate, UINavigationControllerDelegate { - @objc func takePhoto() { - AVCaptureDevice.requestAccess(for: .video) { _ in - DispatchQueue.main.async { - let ctrl = UIImagePickerController() - ctrl.allowsEditing = false - ctrl.sourceType = .camera - ctrl.mediaTypes = [UTType.movie.identifier, UTType.image.identifier] - ctrl.cameraCaptureMode = .photo - ctrl.delegate = self - self.parentViewController?.present(ctrl, animated: true) - } - } - } - - private func processJPEGImageData(_ image: UIImage) throws -> Data? { - guard let data = image.jpegData(compressionQuality: 0.75) else { - throw UnableTo.compressImage - } - return data - } - - func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) { - picker.dismiss(animated: true) { - var itemUrl: URL? - - if itemUrl == nil, - let image = info[.editedImage] as? UIImage ?? info[.originalImage] as? UIImage - { - let tempDir = URL(fileURLWithPath: NSTemporaryDirectory()) - .appendingPathComponent("Camera") - try? FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true) - let tempFile = tempDir - .appendingPathComponent(UUID().uuidString) - .appendingPathExtension("jpeg") - try? self.processJPEGImageData(image)?.write(to: tempFile) - itemUrl = tempFile - } - if itemUrl == nil, - let url = info[.mediaURL] as? URL - { - itemUrl = url - } - - guard let url = itemUrl, FileManager.default.fileExists(atPath: url.path) else { - return - } - guard let image = UIImage(contentsOfFile: url.path) else { return } - try? FileManager.default.removeItem(at: url) - self.viewModel.attachments.append(image) - } - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/InputEditView/InputEditView+Photo.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/InputEditView/InputEditView+Photo.swift deleted file mode 100644 index 73fca1899..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/InputEditView/InputEditView+Photo.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// InputEditView+Photo.swift -// Intelligents -// -// Created by 秋星桥 on 2024/12/6. -// - -import PhotosUI -import UIKit - -extension InputEditView: PHPickerViewControllerDelegate { - @objc func selectPhoto() { - var config = PHPickerConfiguration(photoLibrary: .shared()) - config.filter = .images - config.selectionLimit = 9 - let picker = PHPickerViewController(configuration: config) - picker.modalPresentationStyle = .formSheet - picker.delegate = self - parentViewController?.present(picker, animated: true, completion: nil) - } - - func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { - picker.dismiss(animated: true) - loadPNG(from: results) - } - - private func loadPNG(from results: [PHPickerResult]) { - for result in results { - result.itemProvider.loadObject(ofClass: UIImage.self) { [weak self] image, _ in - if let image = image as? UIImage { - DispatchQueue.main.async { - self?.viewModel.attachments.append(image) - } - } - } - } - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/InputEditView/InputEditView+ViewModel.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/InputEditView/InputEditView+ViewModel.swift deleted file mode 100644 index 9634cff63..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/InputEditView/InputEditView+ViewModel.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// InputEditView+ViewModel.swift -// Intelligents -// -// Created by 秋星桥 on 2024/11/18. -// - -import Combine -import UIKit - -extension InputEditView { - class ViewModel: ObservableObject { - var cancellables: Set = [] - - @Published var text: String = "" - @Published var attachments: [UIImage] = [] - - init() {} - - deinit { - cancellables.forEach { $0.cancel() } - cancellables.removeAll() - } - - func reset() { - text = "" - attachments = [] - } - - func duplicate() -> ViewModel { - let ans = ViewModel() - ans.text = text - ans.attachments = attachments - return ans - } - } -} - -extension InputEditView.ViewModel: Hashable, Equatable { - func hash(into hasher: inout Hasher) { - hasher.combine(text) - hasher.combine(attachments) - } - - static func == (lhs: InputEditView.ViewModel, rhs: InputEditView.ViewModel) -> Bool { - lhs.hashValue == rhs.hashValue - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/InputEditView/InputEditView.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/InputEditView/InputEditView.swift deleted file mode 100644 index 27fe21a14..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/InputEditView/InputEditView.swift +++ /dev/null @@ -1,131 +0,0 @@ -// -// InputEditView.swift -// Intelligents -// -// Created by 秋星桥 on 2024/11/18. -// - -import Combine -import UIKit - -class InputEditView: UIView { - let mainStack = UIStackView() - let attachmentsEditor = AttachmentBannerView() - let textEditor = PlainTextEditView() - let placeholderLabel = UILabel() - let controlBanner = TextEditControlBanner() - - let viewModel = ViewModel() - var placeholderText: String = "" { - didSet { - placeholderLabel.text = placeholderText - } - } - - var submitAction: (() -> Void) = {} - - init() { - super.init(frame: .zero) - - addSubview(mainStack) - mainStack.translatesAutoresizingMaskIntoConstraints = false - mainStack.axis = .vertical - mainStack.spacing = 16 - mainStack.alignment = .fill - mainStack.distribution = .equalSpacing - [ - mainStack.topAnchor.constraint(equalTo: topAnchor), - mainStack.leadingAnchor.constraint(equalTo: leadingAnchor), - mainStack.trailingAnchor.constraint(equalTo: trailingAnchor), - mainStack.bottomAnchor.constraint(equalTo: bottomAnchor), - ].forEach { $0.isActive = true } - - textEditor.heightAnchor.constraint(greaterThanOrEqualToConstant: 64).isActive = true - - [ - attachmentsEditor, textEditor, controlBanner, - ].forEach { - $0.translatesAutoresizingMaskIntoConstraints = false - mainStack.addArrangedSubview($0) - [ - $0.leadingAnchor.constraint(equalTo: mainStack.leadingAnchor), - $0.trailingAnchor.constraint(equalTo: mainStack.trailingAnchor), - ].forEach { $0.isActive = true } - } - - attachmentsEditor.readAttachments = { [weak self] in - self?.viewModel.attachments ?? [] - } - attachmentsEditor.onAttachmentsDelete = { [weak self] index in - self?.viewModel.attachments.remove(at: index) - } - - controlBanner.cameraButton.addTarget( - self, - action: #selector(takePhoto), - for: .touchUpInside - ) - controlBanner.photoButton.addTarget( - self, - action: #selector(selectPhoto), - for: .touchUpInside - ) - - textEditor.returnKeyType = .send - textEditor.addSubview(placeholderLabel) - placeholderLabel.textColor = .label.withAlphaComponent(0.25) - placeholderLabel.font = textEditor.font - placeholderLabel.translatesAutoresizingMaskIntoConstraints = false - [ - placeholderLabel.leadingAnchor.constraint(equalTo: textEditor.leadingAnchor, constant: 2), - placeholderLabel.trailingAnchor.constraint(equalTo: textEditor.trailingAnchor, constant: -2), - placeholderLabel.topAnchor.constraint(equalTo: textEditor.topAnchor, constant: 0), - ].forEach { $0.isActive = true } - - viewModel.objectWillChange - .receive(on: DispatchQueue.main) - .sink { [weak self] _ in - self?.updateValues() - } - .store(in: &viewModel.cancellables) - - updateValues() - - textEditor.textDidChange = { [weak self] text in - self?.viewModel.text = text - self?.updatePlaceholderVisibility() - } - - textEditor.textDidReturn = { [weak self] in - self?.submitAction() - } - } - - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError() - } - - func updatePlaceholderVisibility() { - let visible = viewModel.text.isEmpty && !textEditor.isFirstResponder - UIView.animate(withDuration: 0.25) { - self.placeholderLabel.alpha = visible ? 1 : 0 - } - } - - func updateValues() { - UIView.animate( - withDuration: 0.5, - delay: 0, - usingSpringWithDamping: 1.0, - initialSpringVelocity: 0.8 - ) { [self] in - if textEditor.text != viewModel.text { - textEditor.text = viewModel.text - } - attachmentsEditor.rebuildViews() - parentViewController?.view.layoutIfNeeded() - updatePlaceholderVisibility() - } - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/InputEditView/PlainTextEditView.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/InputEditView/PlainTextEditView.swift deleted file mode 100644 index 181161a3b..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/InputEditView/PlainTextEditView.swift +++ /dev/null @@ -1,69 +0,0 @@ -// -// PlainTextEditView.swift -// Intelligents -// -// Created by 秋星桥 on 2024/11/18. -// - -import UIKit - -class PlainTextEditView: UITextView, UITextViewDelegate { - var textDidChange: ((String) -> Void) = { _ in } - var textDidReturn: (() -> Void) = {} - - init() { - super.init(frame: .zero, textContainer: nil) - - delegate = self - tintColor = .accent - - linkTextAttributes = [:] - showsVerticalScrollIndicator = false - showsHorizontalScrollIndicator = false - textContainer.lineFragmentPadding = .zero - textAlignment = .natural - backgroundColor = .clear - textContainerInset = .zero - textContainer.lineBreakMode = .byTruncatingTail - isScrollEnabled = false - clipsToBounds = false - - isEditable = true - isSelectable = true - isScrollEnabled = false - } - - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError() - } - - func textViewDidChange(_ textView: UITextView) { - textDidChange(textView.text) - } - - func textViewDidBeginEditing(_ textView: UITextView) { - textDidChange(textView.text) - } - - func textViewDidEndEditing(_ textView: UITextView) { - textDidChange(textView.text) - } - - func textView(_: UITextView, editMenuForTextIn _: NSRange, suggestedActions: [UIMenuElement]) -> UIMenu? { - .init(children: suggestedActions + [ - UIAction(title: "Insert Newline") { [weak self] _ in - self?.insertText("\n") - }, - ]) - } - - func textView(_: UITextView, shouldChangeTextIn _: NSRange, replacementText text: String) -> Bool { - if text == "\n" { - textDidReturn() - return false - } else { - return true - } - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/InputEditView/TextEditControlBanner.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/InputEditView/TextEditControlBanner.swift deleted file mode 100644 index 7b28e6ba4..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/InputEditView/TextEditControlBanner.swift +++ /dev/null @@ -1,62 +0,0 @@ -// -// TextEditControlBanner.swift -// Intelligents -// -// Created by 秋星桥 on 2024/11/18. -// - -import UIKit - -class TextEditControlBanner: UIStackView { - static let height: CGFloat = 32 - - let cameraButton = UIButton() - let photoButton = UIButton() - - let spacer = UIView() - - let sendButton = UIButton() - - init() { - super.init(frame: .zero) - - axis = .horizontal - spacing = 16 - alignment = .center - distribution = .fill - - [ - heightAnchor.constraint(equalToConstant: Self.height), - ].forEach { $0.isActive = true } - - [ - cameraButton, photoButton, - sendButton, - ].forEach { - $0.widthAnchor.constraint(equalToConstant: Self.height).isActive = true - $0.heightAnchor.constraint(equalToConstant: Self.height).isActive = true - } - - [ - cameraButton, photoButton, - spacer, - sendButton, - ].forEach { - $0.translatesAutoresizingMaskIntoConstraints = false - addArrangedSubview($0) - } - - cameraButton.setImage(.init(systemName: "camera"), for: .normal) - cameraButton.tintColor = .label - photoButton.setImage(.init(systemName: "photo"), for: .normal) - photoButton.tintColor = .label - - sendButton.setImage(.init(systemName: "paperplane.fill"), for: .normal) - sendButton.tintColor = .label - } - - @available(*, unavailable) - required init(coder _: NSCoder) { - fatalError() - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/IntelligentsChatController+Chat.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/IntelligentsChatController+Chat.swift deleted file mode 100644 index c61f54856..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/IntelligentsChatController+Chat.swift +++ /dev/null @@ -1,372 +0,0 @@ -// -// IntelligentsChatController+Chat.swift -// Intelligents -// -// Created by 秋星桥 on 2024/12/26. -// - -import AffineGraphQL -import LDSwiftEventSource -import MarkdownParser -import UIKit - -extension IntelligentsChatController { - @objc func chat_onLoad() { - beginProgress() - chat_createSession { session in - self.sessionID = session ?? "" - self.chat_retrieveHistories { - self.dispatchToMain { - self.endProgress() - } - } - } onFailure: { error in - self.presentError(error) { - if let nav = self.navigationController { - nav.popViewController(animated: true) - } else { - self.dismiss(animated: true) - } - } - } - } - - @objc func chat_onSend() { - beginProgress() - let viewModel = inputBox.editor.viewModel.duplicate() - viewModel.text = viewModel.text.trimmingCharacters(in: .whitespacesAndNewlines) - inputBox.editor.viewModel.reset() - inputBox.editor.updateValues() - DispatchQueue.global().async { - self.chat_onSendExecute(viewModel: viewModel) - self.endProgress() - } - } - - func chat_clearHistory() { - beginProgress() - Intelligents.qlClient.perform(mutation: CleanupCopilotSessionMutation(input: .init( - docId: metadata[.documentID] ?? "", - sessionIds: [sessionID], - workspaceId: metadata[.workspaceID] ?? "" - ))) { result in - self.dispatchToMain { - self.endProgress() - if case let .success(value) = result, - let sessions = value.data?.cleanupCopilotSession, - sessions.contains(self.sessionID) - { - self.simpleChatContents.removeAll() - return - } - self.presentError(UnableTo.clearHistory) - } - } - } - - func chat_retrieveHistories(_ completion: @escaping () -> Void) { - Intelligents.qlClient.fetch(query: GetCopilotHistoriesQuery( - workspaceId: metadata[.workspaceID] ?? "", - docId: .init(stringLiteral: metadata[.documentID] ?? ""), - options: .some(.init( - action: false, - fork: false, - limit: .init(nilLiteral: ()), - messageOrder: .some(.case(.asc)), - sessionId: .init(stringLiteral: sessionID), - sessionOrder: .some(.case(.desc)), - skip: .init(nilLiteral: ()), - withPrompt: .init(booleanLiteral: false) - )) - )) { [weak self] result in - if let self, - case let .success(value) = result, - let object = value.data, - let currentUser = object.__data._data["currentUser"] as? DataDict, - let copilot = currentUser._data["copilot"] as? DataDict, - let histories = copilot._data["histories"] as? [DataDict], - let mostRecent = histories.first, - let messages = mostRecent._data["messages"] as? [DataDict], - !messages.isEmpty - { - print("[*] retrieved \(messages.count) messages") - tableView.scrollToBottomOnNextUpdate = true - tableView.alpha = 0 - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.8) { - self.tableView.alpha = 1 - } - } - for message in messages { - guard let role = message._data["role"] as? String, - let content = message._data["content"] as? String - // TODO: ATTACHMENTS - else { continue } - switch role { - case "assistant": - simpleChatContents.updateValue( - .assistant(document: content), - forKey: UUID() - ) - case "user": - simpleChatContents.updateValue( - .user(document: content), - forKey: UUID() - ) - default: - assertionFailure() - } - } - } - completion() - } - } -} - -private extension IntelligentsChatController { - func dispatchToMain(_ block: @escaping () -> Void) { - if Thread.isMainThread { - block() - } else { - DispatchQueue.main.async(execute: block) - } - } - - func beginProgress() { - dispatchToMain { [self] in - header.isUserInteractionEnabled = false - inputBox.isUserInteractionEnabled = false - progressView.isHidden = false - progressView.alpha = 0 - progressView.startAnimating() - UIView.animate(withDuration: 0.25) { - self.inputBox.editor.alpha = 0 - self.progressView.alpha = 1 - } - } - } - - func endProgress() { - dispatchToMain { [self] in - UIView.animate(withDuration: 0.3) { - self.inputBox.editor.alpha = 1 - self.progressView.alpha = 0 - self.header.isUserInteractionEnabled = true - } completion: { _ in - self.inputBox.isUserInteractionEnabled = true - self.progressView.stopAnimating() - } - } - } -} - -private extension IntelligentsChatController { - func chat_onError(_ error: Error) { - print("[*] chat error", error) - dispatchToMain { - let key = UUID() - let content = ChatContent.error(text: error.localizedDescription) - self.simpleChatContents.updateValue(content, forKey: key) - } - } - - func chat_createSession( - forceCreateNewSession: Bool = false, - onSuccess: @escaping (String?) -> Void, - onFailure: @escaping (Error) -> Void - ) { - if !forceCreateNewSession, - let doc = metadata[.documentID], - !doc.isEmpty - { - Intelligents.qlClient.fetch(query: GetCopilotSessionsQuery( - workspaceId: .init(stringLiteral: metadata[.workspaceID] ?? ""), - docId: .init(stringLiteral: doc), - options: .some(QueryChatSessionsInput(InputDict([ - "action": false, - ]))) - )) { result in - switch result { - case let .success(value): - if let result = value.data, - let currentUser = result.__data._data["currentUser"] as? DataDict, - let copilot = currentUser._data["copilot"] as? DataDict, - let sessions = copilot._data["sessions"] as? [DataDict], - let mostRecent = sessions.last, - let sessionID = mostRecent._data["id"] as? String - { - print("[*] using existing session", sessionID) - self.dispatchToMain { onSuccess(sessionID) } - return - } - self.chat_createSession( - forceCreateNewSession: true, - onSuccess: onSuccess, - onFailure: onFailure - ) - case let .failure(error): - self.dispatchToMain { onFailure(error) } - } - } - } - Intelligents.qlClient.perform( - mutation: CreateCopilotSessionMutation(options: .init( - docId: metadata[.documentID] ?? "", - promptName: Prompt.general_Chat_With_AFFiNE_AI.rawValue, - workspaceId: metadata[.workspaceID] ?? "" - )), - queue: .global() - ) { result in - switch result { - case let .success(value): - if let session = value.data?.createCopilotSession { - self.dispatchToMain { onSuccess(session) } - } else { - self.dispatchToMain { - onFailure(UnableTo.createSession) - } - } - case let .failure(error): - self.dispatchToMain { onFailure(error) } - } - } - } - - func chat_onSendExecute(viewModel: InputEditView.ViewModel) { - let text = viewModel.text - // let images = viewModel.attachments - - let assistantContentID = UUID() - dispatchToMain { - let content = ChatContent.user(document: text) - self.simpleChatContents.updateValue(content, forKey: .init()) - self.simpleChatContents.updateValue( - .assistant(document: "..."), - forKey: assistantContentID - ) - self.tableView.scrollToBottomOnNextUpdate = true - } - - let sem = DispatchSemaphore(value: 0) - let sessionID = sessionID - Intelligents.qlClient.perform( - mutation: CreateCopilotMessageMutation(options: .init( - content: .init(stringLiteral: text), - params: .some(.dictionary([ - "docs": [ - "docId": metadata[.documentID] ?? "", - "docContent": metadata[.content] ?? "", - ], - ])), - sessionId: sessionID - )), - queue: .global() - ) { result in - defer { sem.signal() } - switch result { - case let .success(value): - if let messageID = value.data?.createCopilotMessage { - print("[*] messageID", messageID) - self.chat_processWithMessageID( - sessionID: sessionID, - messageID: messageID, - cellID: assistantContentID - ) - } else { - self.chat_onError(UnableTo.createMessage) - } - case let .failure(error): - self.chat_onError(error) - } - } - - sem.wait() - } - - func chat_processWithMessageID(sessionID: String, messageID: String, cellID: UUID) { - let url = Constant.affineUpstreamURL - .appendingPathComponent("api") - .appendingPathComponent("copilot") - .appendingPathComponent("chat") - .appendingPathComponent(sessionID) - .appendingPathComponent("stream") - var comps = URLComponents(url: url, resolvingAgainstBaseURL: false) - comps?.queryItems = [URLQueryItem(name: "messageId", value: messageID)] - - guard let url = comps?.url else { - assertionFailure() - chat_onError(UnableTo.createMessage) - return - } - - dispatchToMain { - self.simpleChatContents.updateValue( - .assistant(document: "..."), - forKey: cellID - ) - } - - let sem = DispatchSemaphore(value: 0) - - let eventHandler = BlockEventHandler() - eventHandler.onOpenedBlock = { - print("[*] chat opened") - } - eventHandler.onClosedBlock = { - sem.signal() - self.chatTask?.stop() - self.chatTask = nil - } - eventHandler.onErrorBlock = { error in - self.chat_onError(error) - } - - var document = "" - eventHandler.onMessageBlock = { _, message in - self.dispatchToMain { - document += message.data - let content = ChatContent.assistant(document: document) - self.simpleChatContents.updateValue(content, forKey: cellID) - } - } - let eventSource = EventSource(config: .init(handler: eventHandler, url: url)) - chatTask = eventSource - eventSource.start() - - sem.wait() - } -} - -extension IntelligentsChatController { - func updateContentToPublisher() { - assert(Thread.isMainThread) - let copy = simpleChatContents - let input: [MessageListView.Element] = copy.map { key, value in - switch value { - case let .assistant(document): - let nodes = MarkdownParser().feed(document) - return .init( - id: key, - cell: .assistant, - viewModel: MessageListView.AssistantCell.ViewModel(blocks: nodes), - object: nil - ) - case let .user(document): - return .init( - id: key, - cell: .user, - viewModel: MessageListView.UserCell.ViewModel(text: document), - object: nil - ) - case let .error(text): - return .init( - id: key, - cell: .hint, - viewModel: MessageListView.HintCell.ViewModel(hint: text), - object: nil - ) - } - } - publisher.send(input) - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/IntelligentsChatController+Header.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/IntelligentsChatController+Header.swift deleted file mode 100644 index 203ac3da7..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/IntelligentsChatController+Header.swift +++ /dev/null @@ -1,124 +0,0 @@ -// -// IntelligentsChatController+Header.swift -// Intelligents -// -// Created by 秋星桥 on 2024/11/18. -// - -import UIKit - -extension IntelligentsChatController { - class Header: UIView { - static let height: CGFloat = 44 - - let contentView = UIView() - let titleLabel = UILabel() - let dropMenu = UIButton() - let backButton = UIButton() - let rightBarItemsStack = UIStackView() - let moreMenu = UIButton() - - init() { - super.init(frame: .zero) - setupLayout() - } - - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError() - } - - override var isUserInteractionEnabled: Bool { - didSet { updateAvailabilityStyles() } - } - - func updateAvailabilityStyles() { - if isUserInteractionEnabled { - backButton.isEnabled = true - dropMenu.isEnabled = true - moreMenu.isEnabled = true - } else { - backButton.isEnabled = false - dropMenu.isEnabled = false - moreMenu.isEnabled = false - } - } - - @objc func navigateActionBack() { - parentViewController?.dismissInContext() - } - } -} - -private extension IntelligentsChatController.Header { - func setupLayout() { - contentView.translatesAutoresizingMaskIntoConstraints = false - addSubview(contentView) - [ - contentView.leadingAnchor.constraint(equalTo: leadingAnchor), - contentView.trailingAnchor.constraint(equalTo: trailingAnchor), - contentView.bottomAnchor.constraint(equalTo: bottomAnchor), - contentView.heightAnchor.constraint(equalToConstant: Self.height), - ].forEach { $0.isActive = true } - - titleLabel.textColor = .label - titleLabel.font = .systemFont( - ofSize: UIFont.labelFontSize, - weight: .semibold - ) - - backButton.setImage( - UIImage(systemName: "chevron.left"), - for: .normal - ) - backButton.tintColor = .accent - backButton.addTarget(self, action: #selector(navigateActionBack), for: .touchUpInside) - - dropMenu.setImage( - .init(systemName: "chevron.down")?.withRenderingMode(.alwaysTemplate), - for: .normal - ) - dropMenu.tintColor = .gray.withAlphaComponent(0.5) - - contentView.addSubview(titleLabel) - contentView.addSubview(backButton) - contentView.addSubview(dropMenu) - contentView.addSubview(rightBarItemsStack) - titleLabel.translatesAutoresizingMaskIntoConstraints = false - backButton.translatesAutoresizingMaskIntoConstraints = false - dropMenu.translatesAutoresizingMaskIntoConstraints = false - rightBarItemsStack.translatesAutoresizingMaskIntoConstraints = false - - rightBarItemsStack.axis = .horizontal - rightBarItemsStack.spacing = 10 - rightBarItemsStack.alignment = .center - rightBarItemsStack.distribution = .equalSpacing - - [ - backButton.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), - backButton.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10), - backButton.widthAnchor.constraint(equalToConstant: 44), - backButton.heightAnchor.constraint(equalToConstant: 44), - - rightBarItemsStack.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), - rightBarItemsStack.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10), - rightBarItemsStack.heightAnchor.constraint(equalToConstant: 44), - - titleLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), - titleLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), - titleLabel.leadingAnchor.constraint(greaterThanOrEqualTo: backButton.trailingAnchor, constant: 10), - - dropMenu.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), - dropMenu.widthAnchor.constraint(equalToConstant: 44), - dropMenu.heightAnchor.constraint(equalToConstant: 44), - titleLabel.trailingAnchor.constraint(lessThanOrEqualTo: dropMenu.leadingAnchor, constant: -10), - ].forEach { $0.isActive = true } - - rightBarItemsStack.addArrangedSubview(moreMenu) - moreMenu.setImage( - .init(systemName: "ellipsis.circle"), - for: .normal - ) - moreMenu.tintColor = .accent - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/IntelligentsChatController+InputBox.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/IntelligentsChatController+InputBox.swift deleted file mode 100644 index 298430a7e..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/IntelligentsChatController+InputBox.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// IntelligentsChatController+InputBox.swift -// Intelligents -// -// Created by 秋星桥 on 2024/11/18. -// - -import UIKit - -extension IntelligentsChatController { - class InputBox: UIView { - let backgroundView = UIView() - let editor = InputEditView() - - init() { - super.init(frame: .zero) - - setupLayout() - - editor.textEditor.font = UIFont.systemFont(ofSize: UIFont.labelFontSize) - editor.placeholderText = "Summarize this article for me...".localized() - - backgroundView.backgroundColor = .init( - light: .init(white: 1, alpha: 1), - dark: .init(white: 0.15, alpha: 1) - ) - backgroundView.layer.cornerRadius = 16 - backgroundView.layer.shadowColor = UIColor.black.withAlphaComponent(0.25).cgColor - backgroundView.layer.shadowOffset = .init(width: 0, height: 0) - backgroundView.layer.shadowRadius = 8 - } - - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError() - } - } -} - -private extension IntelligentsChatController.InputBox { - func setupLayout() { - addSubview(backgroundView) - backgroundView.translatesAutoresizingMaskIntoConstraints = false - - addSubview(editor) - editor.translatesAutoresizingMaskIntoConstraints = false - - let inset: CGFloat = 16 - - [ - editor.leadingAnchor.constraint(equalTo: leadingAnchor, constant: inset), - editor.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -inset), - editor.topAnchor.constraint(equalTo: topAnchor, constant: inset), - editor.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -inset), - ].forEach { $0.isActive = true } - - [ - backgroundView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0), - backgroundView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0), - backgroundView.topAnchor.constraint(equalTo: topAnchor, constant: 0), - backgroundView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 128), - ].forEach { $0.isActive = true } - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/IntelligentsChatController.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/IntelligentsChatController.swift deleted file mode 100644 index 4a991cbfa..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/IntelligentsChatController.swift +++ /dev/null @@ -1,150 +0,0 @@ -// -// IntelligentsChatController.swift -// -// -// Created by 秋星桥 on 2024/11/18. -// - -import Combine -import LDSwiftEventSource -import OrderedCollections -import UIKit - -public class IntelligentsChatController: UIViewController { - let header = Header() - let inputBox = InputBox() - let progressView = UIActivityIndicatorView() - - let publisher = PassthroughSubject() - lazy var tableView = MessageListView(dataPublisher: publisher.eraseToAnyPublisher()) - - var inputBoxKeyboardAdapterHeightConstraint = NSLayoutConstraint() - - enum ChatContent { - case user(document: String) - case assistant(document: String) - case error(text: String) - } - - var simpleChatContents: OrderedDictionary = [:] { - didSet { updateContentToPublisher() } - } - - var sessionID: String = "" - - public enum MetadataKey: String { - case documentID - case workspaceID - case content - } - - public var metadata: [MetadataKey: String] = [:] - - var chatTask: EventSource? - - override public var title: String? { - set { - super.title = newValue - header.titleLabel.text = newValue - } - get { - super.title - } - } - - public init() { - super.init(nibName: nil, bundle: nil) - title = "Chat with AI".localized() - - overrideUserInterfaceStyle = .dark - } - - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError() - } - - deinit { - chatTask?.stop() - chatTask = nil - } - - override public func viewDidLoad() { - super.viewDidLoad() - assert(navigationController != nil) - view.backgroundColor = .secondarySystemBackground - - hideKeyboardWhenTappedAround() - - view.addSubview(header) - view.addSubview(tableView) - view.addSubview(inputBox) - view.addSubview(progressView) - setupLayout() - - header.moreMenu.showsMenuAsPrimaryAction = true - header.moreMenu.menu = .init(children: [ - UIAction(title: "Clear History".localized(), image: UIImage(systemName: "eraser")) { [weak self] _ in - self?.chat_clearHistory() - }, - ]) - - // TODO: IMPL - header.dropMenu.isHidden = true - inputBox.editor.controlBanner.cameraButton.isHidden = true - inputBox.editor.controlBanner.photoButton.isHidden = true - - updateContentToPublisher() - chat_onLoad() - } - - override public func viewDidDisappear(_ animated: Bool) { - super.viewDidDisappear(animated) - chatTask?.stop() - chatTask = nil - } - - func setupLayout() { - header.translatesAutoresizingMaskIntoConstraints = false - [ - header.topAnchor.constraint(equalTo: view.topAnchor), - header.leadingAnchor.constraint(equalTo: view.leadingAnchor), - header.trailingAnchor.constraint(equalTo: view.trailingAnchor), - header.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 44), - ].forEach { $0.isActive = true } - - inputBox.translatesAutoresizingMaskIntoConstraints = false - [ - inputBox.leadingAnchor.constraint(equalTo: view.leadingAnchor), - inputBox.trailingAnchor.constraint(equalTo: view.trailingAnchor), - inputBox.bottomAnchor.constraint(equalTo: view.keyboardLayoutGuide.topAnchor), - ].forEach { $0.isActive = true } - - tableView.translatesAutoresizingMaskIntoConstraints = false - [ - tableView.topAnchor.constraint(equalTo: header.bottomAnchor), - tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - tableView.bottomAnchor.constraint(equalTo: inputBox.topAnchor), - ].forEach { $0.isActive = true } - - inputBox.editor.controlBanner.sendButton.addTarget( - self, - action: #selector(chat_onSend), - for: .touchUpInside - ) - inputBox.editor.submitAction = { [weak self] in - guard let self else { return } - chat_onSend() - } - - progressView.hidesWhenStopped = true - progressView.stopAnimating() - progressView.translatesAutoresizingMaskIntoConstraints = false - [ - progressView.centerXAnchor.constraint(equalTo: inputBox.centerXAnchor), - progressView.centerYAnchor.constraint(equalTo: inputBox.centerYAnchor), - ].forEach { $0.isActive = true } - progressView.style = .large - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/Cells/MessageListView+AssistantCell.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/Cells/MessageListView+AssistantCell.swift deleted file mode 100644 index 4eb2b4012..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/Cells/MessageListView+AssistantCell.swift +++ /dev/null @@ -1,150 +0,0 @@ -// -// MessageListView+AssistantCell.swift -// FlowDown -// -// Created by 秋星桥 on 2025/1/2. -// - -import Combine -import MarkdownParser -import MarkdownView -import UIKit - -extension MessageListView { - class AssistantCell: BaseCell { - let avatarView = UIImageView() - let usernameView = UILabel() - let markdownView = MarkdownView() - - override func initializeContent() { - super.initializeContent() - - avatarView.contentMode = .scaleAspectFit - avatarView.image = UIImage(named: "spark", in: .module, with: nil) - usernameView.text = "AFFiNE AI" - usernameView.font = .preferredFont(forTextStyle: .body).bold - usernameView.textColor = .label - - containerView.addSubview(avatarView) - containerView.addSubview(usernameView) - containerView.addSubview(markdownView) - } - - override func prepareForReuse() { - super.prepareForReuse() - markdownView.prepareForReuse() - } - - override func updateContent( - object: any MessageListView.Element.ViewModel, - originalObject _: Element.UserObject? - ) { - guard let object = object as? ViewModel else { - assertionFailure() - return - } - _ = object - } - - override func layoutContent(cache: any MessageListView.TableLayoutEngine.LayoutCache) { - super.layoutContent(cache: cache) - guard let cache = cache as? LayoutCache else { - assertionFailure() - return - } - avatarView.frame = cache.avatarRect - usernameView.frame = cache.usernameRect - markdownView.frame = cache.markdownFrame - - UIView.performWithoutAnimation { - markdownView.updateContentViews(cache.manifests) - } - } - - override class func layoutInsideContainer( - containerWidth: CGFloat, - object: any MessageListView.Element.ViewModel - ) -> any MessageListView.TableLayoutEngine.LayoutCache { - guard let object = object as? ViewModel else { - assertionFailure() - return LayoutCache() - } - let cache = LayoutCache() - cache.width = containerWidth - - let inset: CGFloat = 8 - let bubbleInset = UIEdgeInsets(top: inset, left: inset, bottom: inset, right: inset) - - let avatarRect = CGRect(x: bubbleInset.left, y: bubbleInset.top, width: 24, height: 24) - let usernameRect = CGRect( - x: avatarRect.maxX + bubbleInset.right, - y: bubbleInset.top, - width: containerWidth - avatarRect.maxX - bubbleInset.right, - height: 24 - ) - - let textWidth = containerWidth - bubbleInset.left - bubbleInset.right - - var height: CGFloat = 0 - let manifests = object.blocks.map { - let ret = $0.manifest(theme: object.theme) - ret.setLayoutTheme(.default) - ret.setLayoutWidth(textWidth) - ret.layoutIfNeeded() - height += ret.size.height + Theme.default.spacings.final - return ret - } - if height > 0 { height -= Theme.default.spacings.final } - let textRect = CGRect( - x: bubbleInset.left, - y: usernameRect.maxY + bubbleInset.bottom, - width: textWidth, - height: height - ) - cache.markdownFrame = textRect - cache.avatarRect = avatarRect - cache.usernameRect = usernameRect - cache.manifests = manifests - cache.height = textRect.maxY + bubbleInset.bottom - - return cache - } - } -} - -extension MessageListView.AssistantCell { - class ViewModel: MessageListView.Element.ViewModel { - var theme: Theme - var blocks: [BlockNode] - - enum GroupLocation { - case begin - case center - case end - } - - var groupLocation: GroupLocation = .center - - init(theme: Theme = .default, blocks: [BlockNode]) { - self.theme = theme - self.blocks = blocks - } - - func contentIdentifier(hasher: inout Hasher) { - hasher.combine(blocks) - } - } -} - -extension MessageListView.AssistantCell { - class LayoutCache: MessageListView.TableLayoutEngine.LayoutCache { - var width: CGFloat = 0 - var height: CGFloat = 0 - - var avatarRect: CGRect = .zero - var usernameRect: CGRect = .zero - - var markdownFrame: CGRect = .zero - var manifests: [AnyBlockManifest] = [] - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/Cells/MessageListView+BaseCell.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/Cells/MessageListView+BaseCell.swift deleted file mode 100644 index ea84f2770..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/Cells/MessageListView+BaseCell.swift +++ /dev/null @@ -1,143 +0,0 @@ -// -// MessageListView+BaseCell.swift -// FlowDown -// -// Created by 秋星桥 on 2025/1/2. -// - -import Combine -import UIKit - -extension MessageListView { - class BaseCell: UITableViewCell, MessageListView.TableLayoutEngine.LayoutableCell { - var associatedObject: Element? = nil - var cancellable: Set = [] - let containerView: UIView = .init() - - var layoutEngine: MessageListView.TableLayoutEngine? = nil - - func layoutCache() -> MessageListView.TableLayoutEngine.LayoutCache { - guard let associatedObject, let engine = layoutEngine else { - return MessageListView.TableLayoutEngine.ZeroLayoutCache() - } - let cache = engine.requestLayoutCacheFromCell( - forElement: associatedObject, - atWidth: bounds.width - ) - return cache - } - - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - commitInit() - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - commitInit() - } - - private func commitInit() { - selectionStyle = .none - separatorInset = .zero - contentView.addSubview(containerView) - contentView.clipsToBounds = false - clipsToBounds = false - initializeContent() - } - - override func layoutSubviews() { - super.layoutSubviews() - guard let cache = layoutCache() as? LayoutCache else { - assertionFailure() - return - } - containerView.frame = cache.containerRect - layoutContent(cache: cache.containerLayoutCache) - } - - func registerViewModel(element: Element) { - removeViewModelObject() - associatedObject = element - updateContent(object: element.viewModel, originalObject: element.object) - setNeedsLayout() - } - - func removeViewModelObject() { - associatedObject = nil - cancellable.forEach { $0.cancel() } - cancellable.removeAll() - } - - func initializeContent() {} - func updateContent(object: any Element.ViewModel, originalObject: Element.UserObject?) { - _ = object - _ = originalObject - } - - func layoutContent(cache: MessageListView.TableLayoutEngine.LayoutCache) { - _ = cache - } - - class func layoutInsideContainer( - containerWidth: CGFloat, - object: any Element.ViewModel - ) -> MessageListView.TableLayoutEngine.LayoutCache { - _ = containerWidth - _ = object - assertionFailure("must override") - return MessageListView.TableLayoutEngine.ZeroLayoutCache() - } - - class func containerInset() -> UIEdgeInsets { - let inset: CGFloat = 16 - let containerInset = UIEdgeInsets(top: inset / 2, left: inset, bottom: inset / 2, right: inset) - return containerInset - } - } -} - -extension MessageListView.BaseCell { - class LayoutCache: MessageListView.TableLayoutEngine.LayoutCache { - var width: CGFloat - var height: CGFloat - var containerRect: CGRect - var containerLayoutCache: any MessageListView.TableLayoutEngine.LayoutCache - - init( - width: CGFloat, - height: CGFloat, - containerRect: CGRect, - containerLayoutCache: any MessageListView.TableLayoutEngine.LayoutCache - ) { - self.width = width - self.height = height - self.containerRect = containerRect - self.containerLayoutCache = containerLayoutCache - } - } - - class func resolveLayout( - dataElement element: MessageListView.Element, - contentWidth width: CGFloat - ) -> any MessageListView.TableLayoutEngine.LayoutCache { - let object = element.viewModel - let containerInset = MessageListView.BaseCell.containerInset() - let containerWidth = width - containerInset.left - containerInset.right - let containerCache = Self.layoutInsideContainer(containerWidth: containerWidth, object: object) - let cellHeight = containerCache.height + containerInset.top + containerInset.bottom - let containerRect = CGRect( - x: containerInset.left, - y: containerInset.top, - width: containerWidth, - height: containerCache.height - ) - let cache = LayoutCache( - width: width, - height: cellHeight, - containerRect: containerRect, - containerLayoutCache: containerCache - ) - return cache - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/Cells/MessageListView+HintCell.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/Cells/MessageListView+HintCell.swift deleted file mode 100644 index c21bdbeb3..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/Cells/MessageListView+HintCell.swift +++ /dev/null @@ -1,92 +0,0 @@ -// -// MessageListView+HintCell.swift -// FlowDown -// -// Created by 秋星桥 on 2025/1/2. -// - -import Combine -import UIKit - -extension MessageListView { - class HintCell: BaseCell { - let label = UILabel() - - override func initializeContent() { - super.initializeContent() - label.font = .preferredFont(forTextStyle: .footnote) - label.alpha = 0.5 - label.numberOfLines = 0 - containerView.addSubview(label) - } - - override func updateContent( - object: any MessageListView.Element.ViewModel, - originalObject: Element.UserObject? - ) { - super.updateContent(object: object, originalObject: originalObject) - guard let object = object as? ViewModel else { return } - label.attributedText = object.hint - } - - override func layoutContent(cache: any MessageListView.TableLayoutEngine.LayoutCache) { - super.layoutContent(cache: cache) - guard let cache = cache as? LayoutCache else { - assertionFailure() - return - } - label.frame = cache.labelFrame - } - - override class func layoutInsideContainer( - containerWidth: CGFloat, - object: any MessageListView.Element.ViewModel - ) -> any MessageListView.TableLayoutEngine.LayoutCache { - guard let object = object as? ViewModel else { - assertionFailure() - return LayoutCache() - } - let cache = LayoutCache() - cache.width = containerWidth - cache.height = object.hint.measureHeight(usingWidth: containerWidth) - cache.labelFrame = .init(x: 0, y: 0, width: containerWidth, height: cache.height) - return cache - } - } -} - -extension MessageListView.HintCell { - class ViewModel: MessageListView.Element.ViewModel { - var hint: NSAttributedString = .init() - - init(hint: NSAttributedString) { - self.hint = hint - } - - convenience init(hint: String) { - let paragraphStyle = NSMutableParagraphStyle() - paragraphStyle.alignment = .center - let attributes: [NSAttributedString.Key: Any] = [ - .font: UIFont.preferredFont(forTextStyle: .footnote), - .originalFont: UIFont.preferredFont(forTextStyle: .footnote), - .foregroundColor: UIColor.label, - .paragraphStyle: paragraphStyle, - ] - let text = NSMutableAttributedString(string: hint, attributes: attributes) - self.init(hint: text) - } - - func contentIdentifier(hasher: inout Hasher) { - hasher.combine(hint) - } - } -} - -extension MessageListView.HintCell { - class LayoutCache: MessageListView.TableLayoutEngine.LayoutCache { - var width: CGFloat = 0 - var height: CGFloat = 0 - - var labelFrame: CGRect = .zero - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/Cells/MessageListView+SpacerCell.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/Cells/MessageListView+SpacerCell.swift deleted file mode 100644 index 370a9fea0..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/Cells/MessageListView+SpacerCell.swift +++ /dev/null @@ -1,47 +0,0 @@ -// -// MessageListView+SpacerCell.swift -// FlowDown -// -// Created by 秋星桥 on 2025/1/12. -// - -import Combine -import UIKit - -extension MessageListView { - class SpacerCell: BaseCell { - override class func layoutInsideContainer( - containerWidth: CGFloat, - object: any MessageListView.Element.ViewModel - ) -> any MessageListView.TableLayoutEngine.LayoutCache { - guard let object = object as? ViewModel else { - assertionFailure() - return LayoutCache() - } - let cache = LayoutCache() - cache.width = containerWidth - cache.height = object.height - return cache - } - } -} - -extension MessageListView.SpacerCell { - class ViewModel: MessageListView.Element.ViewModel { - var height: CGFloat - init(height: CGFloat) { - self.height = height - } - - func contentIdentifier(hasher: inout Hasher) { - hasher.combine(height) - } - } -} - -extension MessageListView.SpacerCell { - class LayoutCache: MessageListView.TableLayoutEngine.LayoutCache { - var width: CGFloat = 0 - var height: CGFloat = 0 - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/Cells/MessageListView+UserCell.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/Cells/MessageListView+UserCell.swift deleted file mode 100644 index bc494d6f6..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/Cells/MessageListView+UserCell.swift +++ /dev/null @@ -1,167 +0,0 @@ -// -// MessageListView+UserCell.swift -// FlowDown -// -// Created by 秋星桥 on 2025/1/2. -// - -import Combine -import UIKit - -extension MessageListView { - class UserCell: BaseCell { - let avatarView = UIImageView() - let usernameView = UILabel() - let bubbleView = UIView() - let textView = UITextView() - - override func initializeContent() { - super.initializeContent() - - textView.isSelectable = true - textView.isScrollEnabled = true - textView.isEditable = false - textView.showsVerticalScrollIndicator = false - textView.showsHorizontalScrollIndicator = false - textView.textColor = .label - textView.textContainer.lineFragmentPadding = .zero - textView.textAlignment = .natural - textView.backgroundColor = .clear - textView.textContainerInset = .zero - textView.textContainer.lineBreakMode = .byTruncatingTail - - avatarView.contentMode = .scaleAspectFit - avatarView.image = UIImage(systemName: "person.fill") - usernameView.text = "You" - usernameView.font = .preferredFont(forTextStyle: .body).bold - usernameView.textColor = .label - - bubbleView.layer.cornerRadius = 8 - bubbleView.backgroundColor = .gray.withAlphaComponent(0.1) - - containerView.addSubview(bubbleView) - containerView.addSubview(avatarView) - containerView.addSubview(usernameView) - containerView.addSubview(textView) - } - - override func updateContent( - object: any MessageListView.Element.ViewModel, - originalObject: Element.UserObject? - ) { - super.updateContent(object: object, originalObject: originalObject) - guard let object = object as? ViewModel else { - assertionFailure() - return - } - textView.attributedText = object.text - } - - override func layoutContent(cache: any MessageListView.TableLayoutEngine.LayoutCache) { - super.layoutContent(cache: cache) - guard let cache = cache as? LayoutCache else { - assertionFailure() - return - } - bubbleView.frame = cache.bubbleFrame - avatarView.frame = cache.avatarFrame - usernameView.frame = cache.usernameFrame - textView.frame = cache.labelFrame - } - - override class func layoutInsideContainer( - containerWidth: CGFloat, - object: any MessageListView.Element.ViewModel - ) -> any MessageListView.TableLayoutEngine.LayoutCache { - guard let object = object as? ViewModel else { - assertionFailure() - return LayoutCache() - } - let cache = LayoutCache() - cache.width = containerWidth - - let inset: CGFloat = 8 - let bubbleInset = UIEdgeInsets(top: inset, left: inset, bottom: inset, right: inset) - - let avatarRect = CGRect( - x: bubbleInset.left, - y: bubbleInset.top, - width: 24, - height: 24 - ) - let usernameFrame = CGRect( - x: avatarRect.maxX + inset, - y: bubbleInset.top, - width: containerWidth - avatarRect.maxX - bubbleInset.right, - height: 24 - ) - - let textWidth = min( - object.text.measureWidth(), - containerWidth - inset * 2 - ) - let textHeight = object.text.measureHeight(usingWidth: textWidth) - let textRect = CGRect( - x: bubbleInset.left, - y: avatarRect.maxY + bubbleInset.top, - width: textWidth, - height: textHeight - ) - let bubbleRect = CGRect( - x: 0, - y: 0, - width: containerWidth, - height: textRect.maxY + bubbleInset.bottom - ) - cache.bubbleFrame = bubbleRect - cache.avatarFrame = avatarRect - cache.usernameFrame = usernameFrame - cache.labelFrame = textRect - cache.height = bubbleRect.maxY - return cache - } - } -} - -extension MessageListView.UserCell { - class ViewModel: MessageListView.Element.ViewModel { - var text: NSAttributedString = .init() - - init(text: NSAttributedString) { - self.text = text - } - - convenience init(text: String) { - let paragraphStyle = NSMutableParagraphStyle() - paragraphStyle.alignment = .natural - let attributes: [NSAttributedString.Key: Any] = [ - .font: UIFont.preferredFont(forTextStyle: .body), - .originalFont: UIFont.preferredFont(forTextStyle: .body), - .foregroundColor: UIColor.label, - .paragraphStyle: paragraphStyle, - ] - var text = text - while text.contains("\n\n\n") { - text = text.replacingOccurrences(of: "\n\n\n", with: "\n\n") - } - print(text) - self.init(text: NSMutableAttributedString(string: text, attributes: attributes)) - } - - func contentIdentifier(hasher: inout Hasher) { - hasher.combine(text) - } - } -} - -extension MessageListView.UserCell { - class LayoutCache: MessageListView.TableLayoutEngine.LayoutCache { - var width: CGFloat = 0 - var height: CGFloat = 0 - - var bubbleFrame: CGRect = .zero - var labelFrame: CGRect = .zero - var avatarFrame: CGRect = .zero - var usernameFrame: CGRect = .zero - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/TableView/MessageListView+DataElement.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/TableView/MessageListView+DataElement.swift deleted file mode 100644 index b5a3a750a..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/TableView/MessageListView+DataElement.swift +++ /dev/null @@ -1,61 +0,0 @@ -// -// MessageListView+DataElement.swift -// FlowDown -// -// Created by 秋星桥 on 2025/1/2. -// - -import Combine -import Foundation -import UIKit - -extension MessageListView { - struct Element: Identifiable { - let id: AnyHashable // equals to message id if applicable - - enum Cell: String, CaseIterable { - case base - case hint - case user - case assistant - case spacer - } - - let cell: Cell - let viewModel: any ViewModel - - typealias UserObject = any(Identifiable & Hashable) - let object: UserObject? - - init(id: AnyHashable, cell: Cell, viewModel: any ViewModel, object: UserObject?) { - assert(cell != .base) - self.id = id - self.cell = cell - self.viewModel = viewModel - self.object = object - } - } -} - -extension MessageListView.Element.Cell { - var cellClass: MessageListView.BaseCell.Type { - switch self { - case .base: - MessageListView.BaseCell.self - case .hint: - MessageListView.HintCell.self - case .user: - MessageListView.UserCell.self - case .assistant: - MessageListView.AssistantCell.self - case .spacer: - MessageListView.SpacerCell.self - } - } -} - -extension MessageListView.Element { - protocol ViewModel { - func contentIdentifier(hasher: inout Hasher) - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/TableView/MessageListView+Delegate.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/TableView/MessageListView+Delegate.swift deleted file mode 100644 index dae48e388..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/TableView/MessageListView+Delegate.swift +++ /dev/null @@ -1,68 +0,0 @@ -// -// MessageListView+Delegate.swift -// FlowDown -// -// Created by 秋星桥 on 2025/1/6. -// - -import UIKit - -extension MessageListView: UITableViewDelegate, UITableViewDataSource { - func item(forIndexPath indexPath: IndexPath) -> Element? { - guard indexPath.row < elements.count else { - return nil - } - guard indexPath.row >= 0 else { - return nil - } - return elements.values[indexPath.row] - } - - func numberOfSections(in _: UITableView) -> Int { - 1 - } - - func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int { - elements.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let item = item(forIndexPath: indexPath) else { - assertionFailure() - return UITableViewCell() - } - let cell = tableView.dequeueReusableCell(withIdentifier: item.cell.rawValue, for: indexPath) - if let cell = cell as? BaseCell { - cell.layoutEngine = layoutEngine - cell.registerViewModel(element: item) - } - cell.backgroundColor = .clear - return cell - } - - func tableView(_: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - guard let item = item(forIndexPath: indexPath) else { - return 0 - } - if let height = layoutEngine.height(forElement: item) { - heightKeeper[item.id] = height - return height - } - let ret = layoutEngine.resolveLayoutNow(item).height - heightKeeper[item.id] = ret - return ret - } - - func tableView(_: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { - guard let item = item(forIndexPath: indexPath) else { - return 0 - } - if let height = layoutEngine.height(forElement: item) { - return height - } - if let height = heightKeeper[item.id] { - return height - } - return UITableView.automaticDimension - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/TableView/MessageListView+LayoutEngine.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/TableView/MessageListView+LayoutEngine.swift deleted file mode 100644 index f2877e4f9..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/TableView/MessageListView+LayoutEngine.swift +++ /dev/null @@ -1,143 +0,0 @@ -// -// MessageListView+LayoutEngine.swift -// FlowDown -// -// Created by 秋星桥 on 2025/1/2. -// - -import Foundation - -extension MessageListView { - class TableLayoutEngine { - private let lock = NSLock() - struct LayoutCacheBox { - var cache: LayoutCache - var contentIdentifier: Int - } - - private var layoutCache: [Element.ID: LayoutCacheBox] = [:] - private(set) var contentWidth: CGFloat = .zero - var layoutSession: UUID = .init() - - func setContentWidth(_ width: CGFloat) { - accessLayoutCache { _ in - contentWidth = width - layoutSession = .init() - } - } - - func createSession() -> UUID { - let session = UUID() - layoutSession = session - return session - } - - @discardableResult - func accessLayoutCache(_ block: (inout [Element.ID: LayoutCacheBox]) -> T) -> T { - lock.lock() - defer { lock.unlock() } - return block(&layoutCache) - } - - func contentIdentifier(forElement dataElement: Element) -> Int? { - accessLayoutCache { pool in - guard let box = pool[dataElement.id] else { return nil } - return box.contentIdentifier - } - } - } - - func viewCallingUpdateLayoutEngineWidth() { - guard layoutEngine.contentWidth != tableView.bounds.width else { return } - layoutEngine.setContentWidth(tableView.bounds.width) - reconfigure(enforceReload: false) - NSObject.cancelPreviousPerformRequests( - withTarget: self, - selector: #selector(resolveAllLayoutInBackground), - object: nil - ) - perform(#selector(resolveAllLayoutInBackground), with: nil, afterDelay: 0.1) - } - - @objc private func resolveAllLayoutInBackground() { - let items = Array(elements.values) - let date = Date() - DispatchQueue.global().async { - let session = self.layoutEngine.createSession() - for element in items { - guard self.layoutEngine.layoutSession == session else { continue } - self.layoutEngine.resolveLayoutNow(element) - } - DispatchQueue.main.async { - self.reconfigure(enforceReload: true) - print("[*] layout engine updated \(items.count) items in \(Date().timeIntervalSince(date)) seconds") - } - } - } -} - -extension MessageListView.TableLayoutEngine { - protocol LayoutableCell: AnyObject { - static func resolveLayout( - dataElement: MessageListView.Element, - contentWidth: CGFloat - ) -> LayoutCache - } - - protocol LayoutCache: AnyObject { - var width: CGFloat { get } - var height: CGFloat { get } - } - - class ZeroLayoutCache: LayoutCache { - var width: CGFloat = 0 - var height: CGFloat = 0 - } -} - -extension MessageListView.TableLayoutEngine { - @discardableResult - func resolveLayoutNow(_ element: MessageListView.Element) -> LayoutCache { - var hasher = Hasher() - element.viewModel.contentIdentifier(hasher: &hasher) - let contentIdentifier = hasher.finalize() - - if let cacheBox = accessLayoutCache({ $0[element.id] }) { - if cacheBox.cache.width == contentWidth, - cacheBox.contentIdentifier == contentIdentifier - { return cacheBox.cache } - } - - let target = element.cell.cellClass.self - let cache = target.resolveLayout(dataElement: element, contentWidth: contentWidth) - let cacheBox = LayoutCacheBox(cache: cache, contentIdentifier: contentIdentifier) - accessLayoutCache { $0[element.id] = cacheBox } - return cache - } - - func requestLayoutCacheFromCell( - forElement dataElement: MessageListView.Element, - atWidth width: CGFloat - ) -> LayoutCache { - let cache = accessLayoutCache { pool -> LayoutCache? in - guard let box = pool[dataElement.id] else { return nil } - guard box.contentIdentifier == dataElement.object?.hashValue else { return nil } - guard box.cache.width == contentWidth else { return nil } - guard box.cache.width == width else { return nil } - return box.cache - } - if let cache { return cache } - return resolveLayoutNow(dataElement) - } -} - -extension MessageListView.TableLayoutEngine { - func height(forElement dataElement: MessageListView.Element) -> CGFloat? { - accessLayoutCache { pool in - guard let box = pool[dataElement.id] else { return nil } - guard box.contentIdentifier == dataElement.object?.hashValue else { return nil } - guard box.cache.width == contentWidth else { return nil } - return box.cache.height - } - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/TableView/MessageListView+Update.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/TableView/MessageListView+Update.swift deleted file mode 100644 index 315e8cb3d..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/TableView/MessageListView+Update.swift +++ /dev/null @@ -1,148 +0,0 @@ -// -// MessageListView+Update.swift -// FlowDown -// -// Created by 秋星桥 on 2025/1/2. -// - -import Combine -import Foundation -import OrderedCollections -import UIKit - -extension MessageListView { - func setupPublishers(dataPublisher: AnyPublisher<[Element], Never>) { - // process input from data source where we transform those to view model - let publisher = dataPublisher - .map { input -> [Element] in input + [Element( - id: "spacer", - cell: .spacer, - viewModel: MessageListView.SpacerCell.ViewModel(height: 32), - object: nil - )] } - .map { output in - OrderedDictionary( - uniqueKeysWithValues: output.map { ($0.id, $0) } - ) - } - .eraseToAnyPublisher() - - // after so, limit the refresh rate so we can handle them better - let updateQueue = DispatchQueue(label: "affine.message-list-update-queue", qos: .userInteractive) - let inQueuePublisher = publisher - .throttle(for: .seconds(1 / 5), scheduler: updateQueue, latest: true) - .eraseToAnyPublisher() - - // finally before sending to display, call layout engine to process those items - inQueuePublisher - .sink { [weak self] output in self?.prepare(forNewElements: output) } - .store(in: &cancellables) - } - - func prepare(forNewElements elements: Elements) { - print("[*] received \(elements.count) for update at \(Date())") - elementUpdateProcessLock.lock() - distributedPendingUpdateElements = elements - elementUpdateProcessLock.unlock() - performSelector(onMainThread: #selector(elementsUpdateExecute), with: nil, waitUntilDone: false) - } - - private func pickupElementsPair() -> (oldValue: Elements, newValue: Elements)? { - #if DEBUG // just make sure assert is not called in release mode - assert(!elementUpdateProcessLock.try(), "should not call this method without lock") - #endif - guard let distributedPendingUpdateElements else { return nil } - - let oldValue = elements - elements = distributedPendingUpdateElements - self.distributedPendingUpdateElements = nil - print("[*] pikup is sending \(elements.count) for update at \(Date())") - return (oldValue, distributedPendingUpdateElements) - } - - @objc private func elementsUpdateExecute() { - assert(Thread.isMainThread) - elementUpdateProcessLock.lock() - defer { elementUpdateProcessLock.unlock() } - let pickup = pickupElementsPair() - guard let (oldValue, newValue) = pickup else { return } - guard window != nil else { return } - for value in heightKeeper.keys where !newValue.keys.contains(value) { - heightKeeper.removeValue(forKey: value) - } - - let shouldRealodTableView = newValue.count != oldValue.count - let contentOffset = tableView.contentOffset - UIView.performWithoutAnimation { - self.reconfigure(enforceReload: shouldRealodTableView) - self.tableView.layoutIfNeeded() - } - tableView.contentOffset = contentOffset - - if scrollToBottomOnNextUpdate { - scrollToBottomOnNextUpdate = false - scrollToBottom(useTableViewAnimation: false) - } - } - - func reconfigure(enforceReload: Bool) { - if enforceReload || tableView(tableView, numberOfRowsInSection: 0) != elements.count { - tableView.reloadData() - return - } - var requiresReload = [IndexPath]() - for indexPath in tableView.indexPathsForVisibleRows ?? [] { - guard let item = item(forIndexPath: indexPath) else { continue } - guard let cell = tableView.cellForRow(at: indexPath) as? BaseCell else { continue } - guard type(of: cell) == item.cell.cellClass else { - requiresReload.append(indexPath) - continue - } - layoutEngine.resolveLayoutNow(item) - cell.registerViewModel(element: item) - } - tableView.beginUpdates() - tableView.reloadRows(at: requiresReload, with: .none) - tableView.endUpdates() - } -} - -extension MessageListView { - func scrollToBottom(useTableViewAnimation: Bool = false) { - guard elements.count > 0 else { return } - guard tableView.contentSize.height > tableView.frame.height else { return } - let targetIndexPath = IndexPath(row: elements.count - 1, section: 0) - let cellRect = tableView.rectForRow(at: targetIndexPath) - if tableView.contentOffset.y + tableView.frame.height >= cellRect.origin.y + cellRect.height { return } - UIView.animate(withDuration: 0.35, delay: 0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.8) { - self.tableView.scrollToRow( - at: targetIndexPath, - at: .bottom, - animated: useTableViewAnimation - ) - self.tableView.layoutIfNeeded() - } - NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(finishAutomaticScroll), object: nil) - isAutomaticScrollAnimating = true - perform(#selector(finishAutomaticScroll), with: nil, afterDelay: 0.5) - } - -// func scrollLastCellToTop(useTableViewAnimation: Bool = false) { -// guard elements.count > 1 else { return } -// guard tableView.contentSize.height > tableView.frame.height else { return } -// UIView.animate(withDuration: 0.35, delay: 0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.8) { -// self.tableView.scrollToRow( -// at: IndexPath(row: self.elements.count - 1, section: 0), -// at: .top, -// animated: useTableViewAnimation -// ) -// } -// NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(finishAutomaticScroll), object: nil) -// isAutomaticScrollAnimating = true -// perform(#selector(finishAutomaticScroll), with: nil, afterDelay: 0.5) -// } - - @objc private func finishAutomaticScroll() { - isAutomaticScrollAnimating = false - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/TableView/MessageListView.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/TableView/MessageListView.swift deleted file mode 100644 index 045596f19..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsChatController/MessageListView/TableView/MessageListView.swift +++ /dev/null @@ -1,72 +0,0 @@ -// -// MessageListView.swift -// FlowDown -// -// Created by 秋星桥 on 2025/1/2. -// - -import Combine -import OrderedCollections -import UIKit - -class MessageListView: UIView { - typealias ElementPublisher = AnyPublisher<[Element], Never> - typealias Elements = OrderedDictionary - var elements: Elements = .init() - - var cancellables: Set = [] - - let tableView: UITableView = .init(frame: .zero, style: .plain) - - let layoutEngine = TableLayoutEngine() - var heightKeeper: [Element.ID: CGFloat] = [:] - let elementUpdateProcessLock = NSLock() - var distributedPendingUpdateElements: Elements? = nil - var isAutomaticScrollAnimating: Bool = false - var scrollToBottomOnNextUpdate = false - - let footerView = UIView(frame: .init(x: 0, y: 0, width: 0, height: 200)) - - init(dataPublisher: AnyPublisher<[Element], Never>) { - super.init(frame: .zero) - - tableView.delegate = self - tableView.dataSource = self - tableView.allowsSelection = false - tableView.allowsMultipleSelection = false - tableView.allowsFocus = false - tableView.selectionFollowsFocus = true - tableView.separatorColor = .clear - tableView.backgroundColor = .clear - for cellIdentifier in Element.Cell.allCases { - tableView.register(cellIdentifier.cellClass, forCellReuseIdentifier: cellIdentifier.rawValue) - } - addSubview(tableView) - - tableView.tableFooterView = footerView - tableView.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: topAnchor), - tableView.bottomAnchor.constraint(equalTo: bottomAnchor), - tableView.leadingAnchor.constraint(equalTo: leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: trailingAnchor), - ]) - - setupPublishers(dataPublisher: dataPublisher) - } - - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError() - } - - deinit { - cancellables.forEach { $0.cancel() } - cancellables.removeAll() - } - - override func layoutSubviews() { - super.layoutSubviews() - viewCallingUpdateLayoutEngineWidth() - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsEphemeralActionController/EphemeralAction.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsEphemeralActionController/EphemeralAction.swift deleted file mode 100644 index f9562f9b5..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsEphemeralActionController/EphemeralAction.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// EphemeralAction.swift -// Intelligents -// -// Created by 秋星桥 on 2025/1/8. -// - -import Foundation - -public extension IntelligentsEphemeralActionController { - enum EphemeralAction { - public enum Language: String, CaseIterable { - case langEnglish = "English" - case langSpanish = "Spanish" - case langGerman = "German" - case langFrench = "French" - case langItalian = "Italian" - case langSimplifiedChinese = "Simplified Chinese" - case langTraditionalChinese = "Traditional Chinese" - case langJapanese = "Japanese" - case langRussian = "Russian" - case langKorean = "Korean" - } - - case translate(to: Language) - case summarize - } -} - -extension IntelligentsEphemeralActionController.EphemeralAction { - var title: String { - switch self { - case let .translate(to): - String(format: NSLocalizedString("Translate to %@", comment: ""), to.rawValue) - case .summarize: - NSLocalizedString("Summarize", comment: "") - } - } - - var prompt: Prompt { - switch self { - case .translate: - .general_Translate_to - case .summarize: - .general_Summary - } - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsEphemeralActionController/ImageRotatedPreview.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsEphemeralActionController/ImageRotatedPreview.swift deleted file mode 100644 index 9ba608100..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsEphemeralActionController/ImageRotatedPreview.swift +++ /dev/null @@ -1,60 +0,0 @@ -// -// ImageRotatedPreview.swift -// Intelligents -// -// Created by 秋星桥 on 2025/1/8. -// - -import UIKit - -public class RotatedImagePreview: UIView { - let imageView = UIImageView() - let rotationDegree: CGFloat = 5 - - public init() { - super.init(frame: .zero) - imageView.contentMode = .scaleAspectFill - imageView.layer.cornerRadius = 16 - imageView.clipsToBounds = true - addSubview(imageView) - - clipsToBounds = false - - heightAnchor.constraint(equalToConstant: 300).isActive = true - imageView.transform = CGAffineTransform(rotationAngle: rotationDegree * CGFloat.pi / 180) - } - - @available(*, unavailable) - public required init?(coder _: NSCoder) { - fatalError() - } - - public func configure(previewImage: UIImage) { - imageView.image = previewImage - setNeedsLayout() - } - - override public func layoutSubviews() { - super.layoutSubviews() - - guard let image = imageView.image else { - imageView.frame = .zero - return - } - - let viewHeight = bounds.height // limiter - guard bounds.height > 0 else { return } - - // fit in side - let imageAspectRatio = image.size.width / image.size.height - let imageHeight = viewHeight - let imageWidth = imageHeight * imageAspectRatio - - imageView.frame = CGRect( - x: (bounds.width - imageWidth) / 2, - y: (bounds.height - imageHeight) / 2, - width: imageWidth, - height: imageHeight - ) - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsEphemeralActionController/IntelligentsEphemeralActionController+API.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsEphemeralActionController/IntelligentsEphemeralActionController+API.swift deleted file mode 100644 index fae017aff..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsEphemeralActionController/IntelligentsEphemeralActionController+API.swift +++ /dev/null @@ -1,143 +0,0 @@ -// -// IntelligentsEphemeralActionController+API.swift -// Intelligents -// -// Created by 秋星桥 on 2025/1/15. -// - -import AffineGraphQL -import Foundation -import LDSwiftEventSource - -extension IntelligentsEphemeralActionController { - func beginAction() { - print("[*] begin ephemeral action for did \(documentID) wid \(workspaceID)") - chatTask?.stop() - chatTask = nil - copilotDocumentStorage = "" - sessionID = "" - messageID = "" - chat_createSession( - documentIdentifier: documentID, - workspaceIdentifier: workspaceID - ) { session in - self.sessionID = session - self.beginThisRound() - } onFailure: { error in - self.presentError(error) { - self.close() - } - } - } - - func chat_createSession( - documentIdentifier: String, - workspaceIdentifier: String, - onSuccess: @escaping (String) -> Void, - onFailure: @escaping (Error) -> Void - ) { - if documentIdentifier.isEmpty || workspaceIdentifier.isEmpty { - onFailure(UnableTo.identifyDocumentOrWorkspace) - } - Intelligents.qlClient.perform( - mutation: CreateCopilotSessionMutation(options: .init( - docId: documentIdentifier, - promptName: action.prompt.rawValue, - workspaceId: workspaceIdentifier - )), - queue: .global() - ) { result in - switch result { - case let .success(value): - if let session = value.data?.createCopilotSession, !session.isEmpty { - DispatchQueue.main.async { onSuccess(session) } - } else { - DispatchQueue.main.async { - onFailure(UnableTo.createSession) - } - } - case let .failure(error): - DispatchQueue.main.async { onFailure(error) } - } - } - } - - func beginThisRound() { - let parms: [String: AnyHashable] = switch action { - case let .translate(lang): - ["language": lang.rawValue] - case .summarize: - [:] - } - let json = try! CustomJSON(_jsonValue: parms) - Intelligents.qlClient.perform( - mutation: CreateCopilotMessageMutation(options: .init( - content: .init(stringLiteral: "\(documentContent)"), - params: .some(json), - sessionId: sessionID - )), - queue: .global() - ) { result in - switch result { - case let .success(value): - if let messageID = value.data?.createCopilotMessage { - self.messageID = messageID - self.chat_processWithMessageID(sessionID: self.sessionID, messageID: messageID) - } else { - self.presentError(UnableTo.createMessage) { - self.close() - } - } - case let .failure(error): - self.presentError(error) { - self.close() - } - } - } - } - - func chat_processWithMessageID(sessionID: String, messageID: String) { - let url = Constant.affineUpstreamURL - .appendingPathComponent("api") - .appendingPathComponent("copilot") - .appendingPathComponent("chat") - .appendingPathComponent(sessionID) - .appendingPathComponent("stream") - var comps = URLComponents(url: url, resolvingAgainstBaseURL: false) - comps?.queryItems = [URLQueryItem(name: "messageId", value: messageID)] - - guard let url = comps?.url else { - assertionFailure() - presentError(UnableTo.createMessage) - return - } - - let eventHandler = BlockEventHandler() - eventHandler.onOpenedBlock = { - print("[*] chat opened") - } - eventHandler.onErrorBlock = { error in - self.presentError(error) { self.close() } - } - eventHandler.onMessageBlock = { _, message in - self.chat_onEvent(message.data) - } - eventHandler.onClosedBlock = { - self.chatTask?.stop() - self.chatTask = nil - } - let eventSource = EventSource(config: .init(handler: eventHandler, url: url)) - eventSource.start() - chatTask = eventSource - } - - func chat_onEvent(_ data: String) { - if Thread.isMainThread { - copilotDocumentStorage += data - } else { - DispatchQueue.main.asyncAndWait { - self.copilotDocumentStorage += data - } - } - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsEphemeralActionController/IntelligentsEphemeralActionController+ActionBar.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsEphemeralActionController/IntelligentsEphemeralActionController+ActionBar.swift deleted file mode 100644 index 29baa8ef1..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsEphemeralActionController/IntelligentsEphemeralActionController+ActionBar.swift +++ /dev/null @@ -1,80 +0,0 @@ -// -// IntelligentsEphemeralActionController+ActionBar.swift -// Intelligents -// -// Created by 秋星桥 on 2025/1/15. -// - -import UIKit - -extension IntelligentsEphemeralActionController { - class ActionBar: UIView { - let retryButton = DarkActionButton() - let continueToChat = DarkActionButton() - let createNewDoc = DarkActionButton() - - init() { - super.init(frame: .zero) - defer { removeEveryAutoResizingMasks() } - - let contentSpacing: CGFloat = 16 - let buttonGroupHeight: CGFloat = 55 - - let firstButtonSectionGroup = UIView() - addSubview(firstButtonSectionGroup) - [ - firstButtonSectionGroup.topAnchor.constraint(equalTo: topAnchor, constant: contentSpacing), - firstButtonSectionGroup.leadingAnchor.constraint(equalTo: leadingAnchor), - firstButtonSectionGroup.trailingAnchor.constraint(equalTo: trailingAnchor), - firstButtonSectionGroup.heightAnchor.constraint(equalToConstant: buttonGroupHeight), - ].forEach { $0.isActive = true } - - retryButton.title = NSLocalizedString("Retry", comment: "") - retryButton.iconSystemName = "arrow.clockwise" - continueToChat.title = NSLocalizedString("Continue to Chat", comment: "") - continueToChat.iconSystemName = "paperplane" - firstButtonSectionGroup.addSubview(retryButton) - firstButtonSectionGroup.addSubview(continueToChat) - [ - retryButton.topAnchor.constraint(equalTo: firstButtonSectionGroup.topAnchor), - retryButton.leadingAnchor.constraint(equalTo: firstButtonSectionGroup.leadingAnchor), - retryButton.bottomAnchor.constraint(equalTo: firstButtonSectionGroup.bottomAnchor), - - continueToChat.topAnchor.constraint(equalTo: firstButtonSectionGroup.topAnchor), - continueToChat.trailingAnchor.constraint(equalTo: firstButtonSectionGroup.trailingAnchor), - continueToChat.bottomAnchor.constraint(equalTo: firstButtonSectionGroup.bottomAnchor), - - retryButton.widthAnchor.constraint(equalTo: continueToChat.widthAnchor), - retryButton.trailingAnchor.constraint(equalTo: continueToChat.leadingAnchor, constant: -contentSpacing), - ].forEach { $0.isActive = true } - - let secondButtonSectionGroup = UIView() - addSubview(secondButtonSectionGroup) - [ - secondButtonSectionGroup.topAnchor.constraint(equalTo: firstButtonSectionGroup.bottomAnchor, constant: contentSpacing), - secondButtonSectionGroup.leadingAnchor.constraint(equalTo: leadingAnchor), - secondButtonSectionGroup.trailingAnchor.constraint(equalTo: trailingAnchor), - secondButtonSectionGroup.heightAnchor.constraint(equalToConstant: buttonGroupHeight), - ].forEach { $0.isActive = true } - - secondButtonSectionGroup.addSubview(createNewDoc) - createNewDoc.title = NSLocalizedString("Create New Doc", comment: "") - createNewDoc.iconSystemName = "doc.badge.plus" - [ - createNewDoc.topAnchor.constraint(equalTo: secondButtonSectionGroup.topAnchor), - createNewDoc.leadingAnchor.constraint(equalTo: secondButtonSectionGroup.leadingAnchor), - createNewDoc.bottomAnchor.constraint(equalTo: secondButtonSectionGroup.bottomAnchor), - createNewDoc.trailingAnchor.constraint(equalTo: secondButtonSectionGroup.trailingAnchor), - ].forEach { $0.isActive = true } - - [ - secondButtonSectionGroup.bottomAnchor.constraint(equalTo: bottomAnchor), - ].forEach { $0.isActive = true } - } - - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError() - } - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsEphemeralActionController/IntelligentsEphemeralActionController+Header.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsEphemeralActionController/IntelligentsEphemeralActionController+Header.swift deleted file mode 100644 index cabbb3e6f..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsEphemeralActionController/IntelligentsEphemeralActionController+Header.swift +++ /dev/null @@ -1,115 +0,0 @@ -// -// IntelligentsEphemeralActionController+Header.swift -// Intelligents -// -// Created by 秋星桥 on 2025/1/8. -// - -// -// IntelligentsChatController+Header.swift -// Intelligents -// -// Created by 秋星桥 on 2024/11/18. -// - -import UIKit - -extension IntelligentsEphemeralActionController { - class Header: UIView { - static let height: CGFloat = 44 - - let contentView = UIView() - let titleLabel = UILabel() - let dropMenu = UIButton() - let backButton = UIButton() - let rightBarItemsStack = UIStackView() - let moreMenu = UIButton() - - init() { - super.init(frame: .zero) - setupLayout() - } - - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError() - } - - @objc func navigateActionBack() { - parentViewController?.dismissInContext() - } - } -} - -private extension IntelligentsEphemeralActionController.Header { - func setupLayout() { - contentView.translatesAutoresizingMaskIntoConstraints = false - addSubview(contentView) - [ - contentView.leadingAnchor.constraint(equalTo: leadingAnchor), - contentView.trailingAnchor.constraint(equalTo: trailingAnchor), - contentView.bottomAnchor.constraint(equalTo: bottomAnchor), - contentView.heightAnchor.constraint(equalToConstant: Self.height), - ].forEach { $0.isActive = true } - - titleLabel.textColor = .label - titleLabel.font = .systemFont( - ofSize: UIFont.labelFontSize, - weight: .semibold - ) - - backButton.setImage( - UIImage(systemName: "chevron.left"), - for: .normal - ) - backButton.tintColor = .accent - backButton.addTarget(self, action: #selector(navigateActionBack), for: .touchUpInside) - - dropMenu.setImage( - .init(systemName: "chevron.down")?.withRenderingMode(.alwaysTemplate), - for: .normal - ) - dropMenu.tintColor = .gray.withAlphaComponent(0.5) - - contentView.addSubview(titleLabel) - contentView.addSubview(backButton) - contentView.addSubview(dropMenu) - contentView.addSubview(rightBarItemsStack) - titleLabel.translatesAutoresizingMaskIntoConstraints = false - backButton.translatesAutoresizingMaskIntoConstraints = false - dropMenu.translatesAutoresizingMaskIntoConstraints = false - rightBarItemsStack.translatesAutoresizingMaskIntoConstraints = false - - rightBarItemsStack.axis = .horizontal - rightBarItemsStack.spacing = 10 - rightBarItemsStack.alignment = .center - rightBarItemsStack.distribution = .equalSpacing - - [ - backButton.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), - backButton.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10), - backButton.widthAnchor.constraint(equalToConstant: 44), - backButton.heightAnchor.constraint(equalToConstant: 44), - - rightBarItemsStack.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), - rightBarItemsStack.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10), - rightBarItemsStack.heightAnchor.constraint(equalToConstant: 44), - - titleLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), - titleLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), - titleLabel.leadingAnchor.constraint(greaterThanOrEqualTo: backButton.trailingAnchor, constant: 10), - - dropMenu.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), - dropMenu.widthAnchor.constraint(equalToConstant: 44), - dropMenu.heightAnchor.constraint(equalToConstant: 44), - titleLabel.trailingAnchor.constraint(lessThanOrEqualTo: dropMenu.leadingAnchor, constant: -10), - ].forEach { $0.isActive = true } - - rightBarItemsStack.addArrangedSubview(moreMenu) - moreMenu.setImage( - .init(systemName: "ellipsis.circle"), - for: .normal - ) - moreMenu.tintColor = .accent - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsEphemeralActionController/IntelligentsEphemeralActionController.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsEphemeralActionController/IntelligentsEphemeralActionController.swift deleted file mode 100644 index 6794ffe43..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsEphemeralActionController/IntelligentsEphemeralActionController.swift +++ /dev/null @@ -1,297 +0,0 @@ -// -// IntelligentsEphemeralActionController.swift -// Intelligents -// -// Created by 秋星桥 on 2025/1/8. -// - -import LDSwiftEventSource -import MarkdownParser -import MarkdownView -import UIKit - -public class IntelligentsEphemeralActionController: UIViewController { - let action: EphemeralAction - let scrollView = UIScrollView() - let stackView = UIStackView() - - let header = Header() - let preview = RotatedImagePreview() - - let markdownView = MarkdownView() - let indicator = UIActivityIndicatorView(style: .large) - var responseContainer: UIView = .init() - var responseHeightAnchor: NSLayoutConstraint? - - let actionBar = ActionBar() - - public var documentID: String = "" - public var workspaceID: String = "" - public var documentContent: String = "" - public internal(set) var sessionID: String = "" { - didSet { print(#fileID, #function, sessionID) } - } - - public internal(set) var messageID: String = "" { - didSet { print(#fileID, #function, messageID) } - } - - var chatTask: EventSource? - var copilotDocumentStorage: String = "" { - didSet { - updateDocumentPresentationView() - scrollToBottom() - } - } - - public init(action: EphemeralAction) { - self.action = action - super.init(nibName: nil, bundle: nil) - title = action.title - } - - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError() - } - - override public func viewDidLoad() { - super.viewDidLoad() - - overrideUserInterfaceStyle = .dark - hideKeyboardWhenTappedAround() - view.backgroundColor = .systemBackground - - header.titleLabel.text = title - header.dropMenu.isHidden = true - header.moreMenu.isHidden = true - view.addSubview(header) - header.translatesAutoresizingMaskIntoConstraints = false - [ - header.topAnchor.constraint(equalTo: view.topAnchor), - header.leadingAnchor.constraint(equalTo: view.leadingAnchor), - header.trailingAnchor.constraint(equalTo: view.trailingAnchor), - header.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 44), - ].forEach { $0.isActive = true } - - view.addSubview(actionBar) - actionBar.translatesAutoresizingMaskIntoConstraints = false - [ - actionBar.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 8), - actionBar.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -8), - actionBar.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), - ].forEach { $0.isActive = true } - - scrollView.clipsToBounds = true - scrollView.alwaysBounceVertical = true - scrollView.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(scrollView) - scrollView.translatesAutoresizingMaskIntoConstraints = false - [ - scrollView.topAnchor.constraint(equalTo: header.bottomAnchor), - scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - scrollView.bottomAnchor.constraint(equalTo: actionBar.topAnchor), - ].forEach { $0.isActive = true } - - let contentView = UIView() - scrollView.addSubview(contentView) - contentView.translatesAutoresizingMaskIntoConstraints = false - [ - contentView.topAnchor.constraint(equalTo: scrollView.topAnchor), - contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor), - contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor), - contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor), - contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor), - contentView.heightAnchor.constraint(greaterThanOrEqualTo: scrollView.heightAnchor), - ].forEach { $0.isActive = true } - - contentView.addSubview(stackView) - stackView.translatesAutoresizingMaskIntoConstraints = false - stackView.axis = .vertical - stackView.spacing = 16 - stackView.alignment = .fill - stackView.distribution = .fill - contentView.addSubview(stackView) - - let stackViewInset: CGFloat = 8 - [ - stackView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: stackViewInset), - stackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: stackViewInset), - stackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: -stackViewInset), - stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: -stackViewInset), - ].forEach { $0.isActive = true } - - setupContentViews() - - actionBar.retryButton.action = { [weak self] in - self?.beginAction() - } - actionBar.continueToChat.action = { [weak self] in - guard let self else { return } - continueToChat() - } - } - - func setupContentViews() { - defer { stackView.addArrangedSubview(UIView()) } - - preview.layer.cornerRadius = 16 - preview.clipsToBounds = true - preview.contentMode = .scaleAspectFill - preview.translatesAutoresizingMaskIntoConstraints = false - stackView.addArrangedSubview(preview) - - let headerGroup = UIView() - headerGroup.translatesAutoresizingMaskIntoConstraints = false - stackView.addArrangedSubview(headerGroup) - - let headerLabel = UILabel() - let headerIcon = UIImageView() - - headerLabel.translatesAutoresizingMaskIntoConstraints = false - headerLabel.text = NSLocalizedString("AFFiNE AI", comment: "") - headerLabel.font = .preferredFont(for: .title3, weight: .bold) - headerLabel.textColor = .white - headerLabel.textAlignment = .left - headerIcon.translatesAutoresizingMaskIntoConstraints = false - headerIcon.image = .init(named: "spark", in: .module, with: nil) - headerIcon.contentMode = .scaleAspectFit - headerIcon.tintColor = .accent - headerGroup.addSubview(headerLabel) - headerGroup.addSubview(headerIcon) - [ - headerIcon.leadingAnchor.constraint(equalTo: headerGroup.leadingAnchor), - headerIcon.centerYAnchor.constraint(equalTo: headerGroup.centerYAnchor), - headerIcon.widthAnchor.constraint(equalToConstant: 32), - - headerLabel.leadingAnchor.constraint(equalTo: headerIcon.trailingAnchor, constant: 16), - headerLabel.topAnchor.constraint(equalTo: headerGroup.topAnchor), - headerLabel.bottomAnchor.constraint(equalTo: headerGroup.bottomAnchor), - ].forEach { $0.isActive = true } - - responseContainer.translatesAutoresizingMaskIntoConstraints = false - responseContainer.setContentHuggingPriority(.required, for: .vertical) - responseContainer.setContentCompressionResistancePriority(.required, for: .vertical) - responseContainer.heightAnchor.constraint(greaterThanOrEqualToConstant: 350).isActive = true - stackView.addArrangedSubview(responseContainer) - - responseContainer.addSubview(markdownView) - - markdownView.translatesAutoresizingMaskIntoConstraints = false - [ - markdownView.topAnchor.constraint(equalTo: responseContainer.topAnchor), - markdownView.leadingAnchor.constraint(equalTo: responseContainer.leadingAnchor), - markdownView.trailingAnchor.constraint(equalTo: responseContainer.trailingAnchor), - markdownView.bottomAnchor.constraint(equalTo: responseContainer.bottomAnchor), - ].forEach { - $0.isActive = true - } - - indicator.startAnimating() - indicator.translatesAutoresizingMaskIntoConstraints = false - responseContainer.addSubview(indicator) - [ - indicator.centerXAnchor.constraint(equalTo: responseContainer.centerXAnchor), - indicator.centerYAnchor.constraint(equalTo: responseContainer.centerYAnchor), - indicator.heightAnchor.constraint(equalToConstant: 200), - ].forEach { - $0.isActive = true - } - - updateDocumentPresentationView() - } - - public func configure(previewImage: UIImage) { - preview.configure(previewImage: previewImage) - } - - private var isFirstAppear: Bool = true - override public func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - guard isFirstAppear else { return } - isFirstAppear = false - onFirstAppear() - } - - func onFirstAppear() { - beginAction() - } - - func close() { - if let navigationController { - navigationController.popViewController(animated: true) - } else { - dismiss(animated: true) - } - } - - private var previousLayoutWidth: CGFloat = 0 - - override public func viewWillLayoutSubviews() { - super.viewWillLayoutSubviews() - - if previousLayoutWidth != view.bounds.width { - previousLayoutWidth = view.bounds.width - updateDocumentPresentationView() - } - } - - func updateDocumentPresentationView() { - assert(Thread.isMainThread) - - responseHeightAnchor?.isActive = false - responseHeightAnchor = nil - - if copilotDocumentStorage.isEmpty { - indicator.isHidden = false - indicator.startAnimating() - responseHeightAnchor = responseContainer.heightAnchor.constraint(equalToConstant: 200) - responseHeightAnchor?.isActive = true - markdownView.updateContentViews([]) - return - } - - indicator.isHidden = true - indicator.stopAnimating() - - let document = MarkdownParser().feed(copilotDocumentStorage) - var height: CGFloat = 0 - let manifests = document.map { - let ret = $0.manifest(theme: .default) - ret.setLayoutWidth(responseContainer.bounds.width) - ret.layoutIfNeeded() - height += ret.size.height - height += Theme.default.spacings.final - return ret - } - markdownView.updateContentViews(manifests) - if height > 0 { height -= Theme.default.spacings.final } - responseHeightAnchor = responseContainer.heightAnchor.constraint(equalToConstant: height) - responseHeightAnchor?.isActive = true - } - - func scrollToBottom() { - guard !copilotDocumentStorage.isEmpty else { return } - let bottomOffset = CGPoint( - x: 0, - y: max(0, scrollView.contentSize.height - scrollView.bounds.size.height) - ) - UIView.animate( - withDuration: 0.5, - delay: 0, - usingSpringWithDamping: 1.0, - initialSpringVelocity: 0.8 - ) { self.scrollView.setContentOffset(bottomOffset, animated: false) } - } -} - -extension IntelligentsEphemeralActionController { - func continueToChat() { - let chatController = IntelligentsChatController() - chatController.metadata[.documentID] = documentID - chatController.metadata[.workspaceID] = workspaceID - chatController.metadata[.content] = documentContent - navigationController?.pushViewController(chatController, animated: true) - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsFocusApertureView/IntelligentsFocusApertureView+Capture.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsFocusApertureView/IntelligentsFocusApertureView+Capture.swift deleted file mode 100644 index e257762f6..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsFocusApertureView/IntelligentsFocusApertureView+Capture.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// IntelligentsFocusApertureView+Capture.swift -// Intelligents -// -// Created by 秋星桥 on 2024/11/21. -// - -import UIKit - -extension IntelligentsFocusApertureView { - func captureImageBuffer(_ targetContentView: UIView) { - let contentSize = targetContentView.frame.size - - let renderer = UIGraphicsImageRenderer(size: contentSize) - let image = renderer.image { _ in - let drawRect = CGRect( - x: 0, - y: 0, - width: contentSize.width, - height: contentSize.height - ) - - targetContentView.drawHierarchy( - in: drawRect, - afterScreenUpdates: true - ) - } - capturedImage = image - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsFocusApertureView/IntelligentsFocusApertureView+Delegate.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsFocusApertureView/IntelligentsFocusApertureView+Delegate.swift deleted file mode 100644 index eb13eff51..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsFocusApertureView/IntelligentsFocusApertureView+Delegate.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// IntelligentsFocusApertureView+Delegate.swift -// Intelligents -// -// Created by 秋星桥 on 2024/11/21. -// - -import Foundation - -public enum IntelligentsFocusApertureViewActionType: String { - case translateTo - case summary - case chatWithAI - case dismiss -} - -public protocol IntelligentsFocusApertureViewDelegate: AnyObject { - func focusApertureRequestAction( - from: IntelligentsFocusApertureView, - actionType: IntelligentsFocusApertureViewActionType - ) -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsFocusApertureView/IntelligentsFocusApertureView+Layout.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsFocusApertureView/IntelligentsFocusApertureView+Layout.swift deleted file mode 100644 index 3ba0b0622..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsFocusApertureView/IntelligentsFocusApertureView+Layout.swift +++ /dev/null @@ -1,89 +0,0 @@ -// -// IntelligentsFocusApertureView+Layout.swift -// Intelligents -// -// Created by 秋星桥 on 2024/11/21. -// - -import UIKit - -extension IntelligentsFocusApertureView { - func prepareFrameLayout() { - guard let viewController = targetViewController, - let view = viewController.view - else { - assertionFailure() - return - } - let safeLayout = viewController.view.safeAreaLayoutGuide - - frameConstraints = [ - // use safe area to layout content views - leadingAnchor.constraint(equalTo: safeLayout.leadingAnchor), - trailingAnchor.constraint(equalTo: safeLayout.trailingAnchor), - topAnchor.constraint(equalTo: safeLayout.topAnchor), - bottomAnchor.constraint(equalTo: safeLayout.bottomAnchor), - // cover all safe area so use constraints over view - backgroundView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - backgroundView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - backgroundView.topAnchor.constraint(equalTo: view.topAnchor), - backgroundView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ] - } - - func prepareContentLayouts() { - guard let targetView else { - assertionFailure() - return - } - - contentBeginConstraints = [ - snapshotImageView.leftAnchor.constraint(equalTo: targetView.leftAnchor), - snapshotImageView.rightAnchor.constraint(equalTo: targetView.rightAnchor), - snapshotImageView.topAnchor.constraint(equalTo: targetView.topAnchor), - snapshotImageView.bottomAnchor.constraint(equalTo: targetView.bottomAnchor), - - controlButtonsPanel.leftAnchor.constraint(equalTo: leftAnchor), - controlButtonsPanel.rightAnchor.constraint(equalTo: rightAnchor), - controlButtonsPanel.topAnchor.constraint(equalTo: bottomAnchor), - ] - - let sharedInset: CGFloat = 32 - contentFinalConstraints = [ - snapshotImageView.leftAnchor.constraint(equalTo: leftAnchor, constant: sharedInset), - snapshotImageView.rightAnchor.constraint(equalTo: rightAnchor, constant: -sharedInset), - snapshotImageView.topAnchor.constraint(equalTo: topAnchor), - snapshotImageView.bottomAnchor.constraint(equalTo: controlButtonsPanel.topAnchor, constant: -sharedInset / 2), - - controlButtonsPanel.leftAnchor.constraint(equalTo: leftAnchor, constant: sharedInset), - controlButtonsPanel.rightAnchor.constraint(equalTo: rightAnchor, constant: -sharedInset), - controlButtonsPanel.bottomAnchor.constraint(equalTo: bottomAnchor), - ] - } - - enum LayoutType { - case begin - case complete - } - - func activateLayoutForAnimation(_ type: LayoutType) { - NSLayoutConstraint.activate(frameConstraints) - switch type { - case .begin: - NSLayoutConstraint.deactivate(contentFinalConstraints) - NSLayoutConstraint.activate(contentBeginConstraints) - - snapshotImageView.layer.cornerRadius = 0 - case .complete: - NSLayoutConstraint.deactivate(contentBeginConstraints) - NSLayoutConstraint.activate(contentFinalConstraints) - - snapshotImageView.layer.cornerRadius = 32 - } - let effectiveView = superview ?? self - effectiveView.setNeedsUpdateConstraints() - effectiveView.setNeedsLayout() - updateConstraints() - layoutIfNeeded() - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsFocusApertureView/IntelligentsFocusApertureView+Panel.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsFocusApertureView/IntelligentsFocusApertureView+Panel.swift deleted file mode 100644 index 9216be4a3..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsFocusApertureView/IntelligentsFocusApertureView+Panel.swift +++ /dev/null @@ -1,115 +0,0 @@ -// -// IntelligentsFocusApertureView+Panel.swift -// Intelligents -// -// Created by 秋星桥 on 2024/11/21. -// - -import UIKit - -extension IntelligentsFocusApertureView { - class ControlButtonsPanel: UIView { - let headerLabel = UILabel() - let headerIcon = UIImageView() - - let translateButton = DarkActionButton() - let summaryButton = DarkActionButton() - let chatWithAIButton = DarkActionButton() - - init() { - super.init(frame: .zero) - defer { removeEveryAutoResizingMasks() } - - let contentSpacing: CGFloat = 16 - let buttonGroupHeight: CGFloat = 55 - - let headerGroup = UIView() - addSubview(headerGroup) - [ - headerGroup.topAnchor.constraint(equalTo: topAnchor), - headerGroup.leadingAnchor.constraint(equalTo: leadingAnchor), - headerGroup.trailingAnchor.constraint(equalTo: trailingAnchor), - ].forEach { $0.isActive = true } - - headerLabel.text = NSLocalizedString("AFFiNE AI", comment: "") // TODO: FREE TRAIL??? - // title 3 with bold - headerLabel.font = .preferredFont(for: .title3, weight: .bold) - headerLabel.textColor = .white - headerLabel.textAlignment = .left - headerIcon.image = .init(named: "spark", in: .module, with: nil) - headerIcon.contentMode = .scaleAspectFit - headerIcon.tintColor = .accent - headerGroup.addSubview(headerLabel) - headerGroup.addSubview(headerIcon) - [ - headerLabel.topAnchor.constraint(equalTo: headerGroup.topAnchor), - headerLabel.leadingAnchor.constraint(equalTo: headerGroup.leadingAnchor), - headerLabel.bottomAnchor.constraint(equalTo: headerGroup.bottomAnchor), - - headerIcon.topAnchor.constraint(equalTo: headerGroup.topAnchor), - headerIcon.trailingAnchor.constraint(equalTo: headerGroup.trailingAnchor), - headerIcon.bottomAnchor.constraint(equalTo: headerGroup.bottomAnchor), - - headerIcon.widthAnchor.constraint(equalToConstant: 32), - headerIcon.trailingAnchor.constraint(equalTo: headerGroup.trailingAnchor), - headerIcon.leadingAnchor.constraint(equalTo: headerLabel.trailingAnchor, constant: contentSpacing), - ].forEach { $0.isActive = true } - - let firstButtonSectionGroup = UIView() - addSubview(firstButtonSectionGroup) - [ - firstButtonSectionGroup.topAnchor.constraint(equalTo: headerGroup.bottomAnchor, constant: contentSpacing), - firstButtonSectionGroup.leadingAnchor.constraint(equalTo: leadingAnchor), - firstButtonSectionGroup.trailingAnchor.constraint(equalTo: trailingAnchor), - firstButtonSectionGroup.heightAnchor.constraint(equalToConstant: buttonGroupHeight), - ].forEach { $0.isActive = true } - - translateButton.title = NSLocalizedString("Translate", comment: "") - translateButton.iconSystemName = "textformat" - summaryButton.title = NSLocalizedString("Summary", comment: "") - summaryButton.iconSystemName = "doc.text" - firstButtonSectionGroup.addSubview(translateButton) - firstButtonSectionGroup.addSubview(summaryButton) - [ - translateButton.topAnchor.constraint(equalTo: firstButtonSectionGroup.topAnchor), - translateButton.leadingAnchor.constraint(equalTo: firstButtonSectionGroup.leadingAnchor), - translateButton.bottomAnchor.constraint(equalTo: firstButtonSectionGroup.bottomAnchor), - - summaryButton.topAnchor.constraint(equalTo: firstButtonSectionGroup.topAnchor), - summaryButton.trailingAnchor.constraint(equalTo: firstButtonSectionGroup.trailingAnchor), - summaryButton.bottomAnchor.constraint(equalTo: firstButtonSectionGroup.bottomAnchor), - - translateButton.widthAnchor.constraint(equalTo: summaryButton.widthAnchor), - translateButton.trailingAnchor.constraint(equalTo: summaryButton.leadingAnchor, constant: -contentSpacing), - ].forEach { $0.isActive = true } - - let secondButtonSectionGroup = UIView() - addSubview(secondButtonSectionGroup) - [ - secondButtonSectionGroup.topAnchor.constraint(equalTo: firstButtonSectionGroup.bottomAnchor, constant: contentSpacing), - secondButtonSectionGroup.leadingAnchor.constraint(equalTo: leadingAnchor), - secondButtonSectionGroup.trailingAnchor.constraint(equalTo: trailingAnchor), - secondButtonSectionGroup.heightAnchor.constraint(equalToConstant: buttonGroupHeight), - ].forEach { $0.isActive = true } - - secondButtonSectionGroup.addSubview(chatWithAIButton) - chatWithAIButton.title = NSLocalizedString("Chat with AI", comment: "") - chatWithAIButton.iconSystemName = "paperplane" - [ - chatWithAIButton.topAnchor.constraint(equalTo: secondButtonSectionGroup.topAnchor), - chatWithAIButton.leadingAnchor.constraint(equalTo: secondButtonSectionGroup.leadingAnchor), - chatWithAIButton.bottomAnchor.constraint(equalTo: secondButtonSectionGroup.bottomAnchor), - chatWithAIButton.trailingAnchor.constraint(equalTo: secondButtonSectionGroup.trailingAnchor), - ].forEach { $0.isActive = true } - - [ - secondButtonSectionGroup.bottomAnchor.constraint(equalTo: bottomAnchor), - ].forEach { $0.isActive = true } - } - - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError() - } - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsFocusApertureView/IntelligentsFocusApertureView.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsFocusApertureView/IntelligentsFocusApertureView.swift deleted file mode 100644 index ee0a881d0..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsFocusApertureView/IntelligentsFocusApertureView.swift +++ /dev/null @@ -1,133 +0,0 @@ -// -// IntelligentsFocusApertureView.swift -// Intelligents -// -// Created by 秋星桥 on 2024/11/21. -// - -import UIKit - -public class IntelligentsFocusApertureView: UIView { - public let backgroundView = UIView() - public let snapshotImageView = UIImageView() - let controlButtonsPanel = ControlButtonsPanel() - - public var animationDuration: TimeInterval = 0.75 - - public internal(set) weak var targetView: UIView? - public internal(set) weak var targetViewController: UIViewController? - public internal(set) weak var capturedImage: UIImage? { - get { snapshotImageView.image } - set { snapshotImageView.image = newValue } - } - - var frameConstraints: [NSLayoutConstraint] = [] - var contentBeginConstraints: [NSLayoutConstraint] = [] - var contentFinalConstraints: [NSLayoutConstraint] = [] - - public weak var delegate: (any IntelligentsFocusApertureViewDelegate)? - - public init() { - super.init(frame: .zero) - - backgroundView.backgroundColor = .black - backgroundView.isUserInteractionEnabled = true - let tap = UITapGestureRecognizer( - target: self, - action: #selector(dismissFocus) - ) - tap.cancelsTouchesInView = true - backgroundView.addGestureRecognizer(tap) - - snapshotImageView.setContentHuggingPriority(.defaultLow, for: .vertical) - snapshotImageView.setContentCompressionResistancePriority(.defaultLow, for: .vertical) - snapshotImageView.layer.contentsGravity = .top - snapshotImageView.layer.masksToBounds = true - snapshotImageView.contentMode = .scaleAspectFill - snapshotImageView.isUserInteractionEnabled = true - snapshotImageView.addGestureRecognizer(UITapGestureRecognizer( - target: self, - action: #selector(dismissFocus) - )) - - addSubview(backgroundView) - addSubview(controlButtonsPanel) - addSubview(snapshotImageView) - bringSubviewToFront(snapshotImageView) - - controlButtonsPanel.translateButton.action = { [weak self] in - guard let self else { return } - delegate?.focusApertureRequestAction(from: self, actionType: .translateTo) - } - controlButtonsPanel.summaryButton.action = { [weak self] in - guard let self else { return } - delegate?.focusApertureRequestAction(from: self, actionType: .summary) - } - controlButtonsPanel.chatWithAIButton.action = { [weak self] in - guard let self else { return } - delegate?.focusApertureRequestAction(from: self, actionType: .chatWithAI) - } - removeEveryAutoResizingMasks() - } - - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError() - } - - public func prepareAnimationWith( - capturingTargetContentView targetContentView: UIView, - coveringRootViewController viewController: UIViewController - ) { - captureImageBuffer(targetContentView) - - targetView = targetContentView - targetViewController = viewController - - viewController.view.addSubview(self) - - prepareFrameLayout() - prepareContentLayouts() - activateLayoutForAnimation(.begin) - } - - public func executeAnimationKickIn(_ completion: @escaping () -> Void = {}) { - activateLayoutForAnimation(.begin) - isUserInteractionEnabled = false - UIView.animate( - withDuration: animationDuration, - delay: 0, - usingSpringWithDamping: 1.0, - initialSpringVelocity: 0.8 - ) { - self.activateLayoutForAnimation(.complete) - } completion: { _ in - self.isUserInteractionEnabled = true - completion() - } - } - - public func executeAnimationDismiss(_ completion: @escaping () -> Void = {}) { - activateLayoutForAnimation(.complete) - isUserInteractionEnabled = false - UIView.animate( - withDuration: animationDuration, - delay: 0, - usingSpringWithDamping: 1.0, - initialSpringVelocity: 0.8 - ) { - self.activateLayoutForAnimation(.begin) - } completion: { _ in - self.isUserInteractionEnabled = true - completion() - } - } - - @objc func dismissFocus() { - isUserInteractionEnabled = false - executeAnimationDismiss { - self.removeFromSuperview() - self.delegate?.focusApertureRequestAction(from: self, actionType: .dismiss) - } - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Supplement/AccentColor.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Supplement/AccentColor.swift new file mode 100644 index 000000000..eec75f6f0 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Supplement/AccentColor.swift @@ -0,0 +1,12 @@ +// +// AccentColor.swift +// Intelligents +// +// Created by 秋星桥 on 6/19/25. +// + +import UIKit + +extension UIColor { + static let accent: UIColor = .init(red: 30 / 255, green: 150 / 255, blue: 235 / 255, alpha: 1.0) +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Supplement/Animation.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Supplement/Animation.swift new file mode 100644 index 000000000..bac7c40b8 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Supplement/Animation.swift @@ -0,0 +1,23 @@ +// +// Animation.swift +// Intelligents +// +// Created by 秋星桥 on 6/19/25. +// + +import UIKit + +func performWithAnimation( + animations: @escaping () -> Void, + completion: @escaping (Bool) -> Void = { _ in } +) { + UIView.animate( + withDuration: 0.5, + delay: 0, + usingSpringWithDamping: 0.8, + initialSpringVelocity: 0.8, + options: [.beginFromCurrentState, .allowAnimatedContent, .curveEaseInOut], + animations: animations, + completion: completion + ) +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/SupplementView/UIHostingView.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/SupplementView/UIHostingView.swift deleted file mode 100644 index 86422f9ec..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/SupplementView/UIHostingView.swift +++ /dev/null @@ -1,54 +0,0 @@ -// -// UIHostingView.swift -// Intelligents -// -// Created by 秋星桥 on 2024/12/13. -// - -import SwiftUI -import UIKit - -class UIHostingView: UIView { - private let hostingViewController: UIHostingController - - var rootView: Content { - get { hostingViewController.rootView } - set { hostingViewController.rootView = newValue } - } - - override var intrinsicContentSize: CGSize { - hostingViewController.view.intrinsicContentSize - } - - init(rootView: Content) { - hostingViewController = UIHostingController(rootView: rootView) - hostingViewController.edgesForExtendedLayout = [] - hostingViewController.extendedLayoutIncludesOpaqueBars = false - super.init(frame: .zero) - - hostingViewController.view?.translatesAutoresizingMaskIntoConstraints = false - addSubview(hostingViewController.view) - if let view = hostingViewController.view { - view.removeFromSuperview() - view.backgroundColor = .clear - view.isOpaque = false - addSubview(view) - let constraints = [ - view.topAnchor.constraint(equalTo: topAnchor), - view.bottomAnchor.constraint(equalTo: bottomAnchor), - view.leftAnchor.constraint(equalTo: leftAnchor), - view.rightAnchor.constraint(equalTo: rightAnchor), - ] - NSLayoutConstraint.activate(constraints) - } - } - - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func sizeThatFits(_ size: CGSize) -> CGSize { - hostingViewController.sizeThatFits(in: size) - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.swift new file mode 100644 index 000000000..3b9745259 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.swift @@ -0,0 +1,287 @@ +import Combine +import SnapKit +import Then +import UIKit + +class InputBox: UIView { + weak var delegate: InputBoxDelegate? + + public let viewModel = InputBoxViewModel() + var cancellables = Set() + + lazy var containerView = UIView().then { + $0.backgroundColor = UIColor.affineLayerBackgroundPrimary + $0.layer.cornerRadius = 16 + $0.layer.cornerCurve = .continuous + $0.layer.borderWidth = 1 + $0.layer.shadowColor = UIColor.black.cgColor + $0.layer.shadowOffset = CGSize(width: 0, height: 0) + $0.layer.shadowRadius = 12 + $0.layer.shadowOpacity = 0.075 + $0.clipsToBounds = false + } + + lazy var textView = UITextView().then { + $0.backgroundColor = .clear + $0.font = .systemFont(ofSize: 16) + $0.textColor = UIColor.affineTextPrimary + $0.isScrollEnabled = false + $0.textContainer.lineFragmentPadding = 0 + $0.textContainerInset = .zero + $0.delegate = self + $0.text = "" + } + + lazy var placeholderLabel = UILabel().then { + $0.text = "Write your message..." + $0.font = .systemFont(ofSize: 16) + $0.textColor = UIColor.affineTextPlaceholder + $0.isHidden = true + } + + lazy var functionBar = InputBoxFunctionBar().then { + $0.delegate = self + } + + lazy var imageBar = InputBoxImageBar().then { + $0.imageBarDelegate = self + } + + lazy var mainStackView = UIStackView().then { + $0.axis = .vertical + $0.spacing = 16 + $0.alignment = .fill + $0.clipsToBounds = true + $0.addArrangedSubview(imageBar) + $0.addArrangedSubview(textView) + $0.addArrangedSubview(functionBar) + } + + var textViewHeightConstraint: Constraint? + let minTextViewHeight: CGFloat = 22 + let maxTextViewHeight: CGFloat = 100 + + var text: String { + get { textView.text ?? "" } + set { + textView.text = newValue + updatePlaceholderVisibility() + updateTextViewHeight() + } + } + + override init(frame: CGRect = .zero) { + super.init(frame: frame) + + backgroundColor = .clear + addSubview(containerView) + containerView.addSubview(mainStackView) + containerView.addSubview(placeholderLabel) + imageBar.isHidden = true + + containerView.snp.makeConstraints { make in + make.edges.equalToSuperview().inset(16) + } + + mainStackView.snp.makeConstraints { make in + make.edges.equalToSuperview().inset(16) + } + + imageBar.snp.makeConstraints { make in + make.left.right.equalToSuperview() + } + + textView.snp.makeConstraints { make in + textViewHeightConstraint = make.height.equalTo(minTextViewHeight).constraint + } + + placeholderLabel.snp.makeConstraints { make in + make.left.right.equalTo(textView) + make.top.equalTo(textView) + } + + setupBindings() + updatePlaceholderVisibility() + updateColors() + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + if traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) { + updateColors() + } + } + + func setupBindings() { + // 绑定 ViewModel 到 UI + viewModel.$inputText + .removeDuplicates() + .sink { [weak self] text in + if self?.textView.text != text { + self?.textView.text = text + self?.updatePlaceholderVisibility() + self?.updateTextViewHeight() + } + } + .store(in: &cancellables) + + viewModel.$isToolEnabled + .removeDuplicates() + .sink { [weak self] enabled in + self?.functionBar.updateToolState(isEnabled: enabled) + } + .store(in: &cancellables) + + viewModel.$isNetworkEnabled + .removeDuplicates() + .sink { [weak self] enabled in + self?.functionBar.updateNetworkState(isEnabled: enabled) + } + .store(in: &cancellables) + + viewModel.$isDeepThinkingEnabled + .removeDuplicates() + .sink { [weak self] enabled in + self?.functionBar.updateDeepThinkingState(isEnabled: enabled) + } + .store(in: &cancellables) + + viewModel.$canSend + .removeDuplicates() + .sink { [weak self] canSend in + self?.functionBar.updateSendState(canSend: canSend) + } + .store(in: &cancellables) + + viewModel.$hasAttachments + .dropFirst() // for view setup + .removeDuplicates() + .sink { [weak self] hasAttachments in + performWithAnimation { + self?.updateImageBarVisibility(hasAttachments) + self?.layoutIfNeeded() + } + } + .store(in: &cancellables) + + viewModel.$attachments + .removeDuplicates() + .sink { [weak self] attachments in + self?.updateImageBarContent(attachments) + } + .store(in: &cancellables) + } + + func updateTextViewHeight() { + let size = textView.sizeThatFits(CGSize(width: textView.frame.width, height: CGFloat.greatestFiniteMagnitude)) + let newHeight = max(minTextViewHeight, min(maxTextViewHeight, size.height)) + + let height = textView.frame.height + guard height != newHeight else { return } + + textViewHeightConstraint?.update(offset: newHeight) + textView.isScrollEnabled = size.height > maxTextViewHeight + + if height == 0 || superview == nil || window == nil || isHidden { return } + + performWithAnimation { + self.layoutIfNeeded() + self.superview?.layoutIfNeeded() + } + } + + func updatePlaceholderVisibility() { + placeholderLabel.isHidden = !textView.text.isEmpty + } + + func updateImageBarVisibility(_ hasAttachments: Bool) { + imageBar.isHidden = !hasAttachments + } + + func updateImageBarContent(_ attachments: [InputAttachment]) { + imageBar.updateImageBarContent(attachments) + } + + func updateColors() { + containerView.layer.borderColor = UIColor.affineLayerBorder.cgColor + } + + // MARK: - Public Methods + + public func addImageAttachment(_ image: UIImage) { + guard let imageData = image.jpegData(compressionQuality: 0.8) else { return } + + let attachment = InputAttachment( + type: .image, + data: imageData, + name: "image.jpg", + size: Int64(imageData.count) + ) + + performWithAnimation { [self] in + viewModel.addAttachment(attachment) + layoutIfNeeded() + } + } + + public func addFileAttachment(_ url: URL) { + guard let fileData = try? Data(contentsOf: url) else { return } + + let attachment = InputAttachment( + type: .file, + data: fileData, + name: url.lastPathComponent, + size: Int64(fileData.count) + ) + + performWithAnimation { [self] in + viewModel.addAttachment(attachment) + layoutIfNeeded() + } + } + + public var inputBoxData: InputBoxData { + viewModel.prepareSendData() + } +} + +// MARK: - InputBoxFunctionBarDelegate + +extension InputBox: InputBoxFunctionBarDelegate { + func functionBarDidTapTakePhoto(_: InputBoxFunctionBar) { + delegate?.inputBoxDidSelectTakePhoto(self) + } + + func functionBarDidTapPhotoLibrary(_: InputBoxFunctionBar) { + delegate?.inputBoxDidSelectPhotoLibrary(self) + } + + func functionBarDidTapAttachFiles(_: InputBoxFunctionBar) { + delegate?.inputBoxDidSelectAttachFiles(self) + } + + func functionBarDidTapEmbedDocs(_: InputBoxFunctionBar) { + delegate?.inputBoxDidSelectEmbedDocs(self) + } + + func functionBarDidTapTool(_: InputBoxFunctionBar) { + viewModel.toggleTool() + } + + func functionBarDidTapNetwork(_: InputBoxFunctionBar) { + viewModel.toggleNetwork() + } + + func functionBarDidTapDeepThinking(_: InputBoxFunctionBar) { + viewModel.toggleDeepThinking() + } + + func functionBarDidTapSend(_: InputBoxFunctionBar) { + delegate?.inputBoxDidSend(self) + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.xcassets/InputBoxBackground.colorset/Contents.json b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.xcassets/InputBoxBackground.colorset/Contents.json new file mode 100644 index 000000000..0cba25aa6 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.xcassets/InputBoxBackground.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "255", + "green" : "255", + "red" : "255" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "32", + "green" : "32", + "red" : "32" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBoxDelegate.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBoxDelegate.swift new file mode 100644 index 000000000..b89494913 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBoxDelegate.swift @@ -0,0 +1,35 @@ +// +// InputBoxDelegate.swift +// Intelligents +// +// Created by 秋星桥 on 6/18/25. +// + +import UIKit + +protocol InputBoxDelegate: AnyObject { + func inputBoxDidSelectTakePhoto(_ inputBox: InputBox) + func inputBoxDidSelectPhotoLibrary(_ inputBox: InputBox) + func inputBoxDidSelectAttachFiles(_ inputBox: InputBox) + func inputBoxDidSelectEmbedDocs(_ inputBox: InputBox) + func inputBoxDidSend(_ inputBox: InputBox) + func inputBoxTextDidChange(_ text: String) +} + +extension InputBox: InputBoxImageBarDelegate { + func inputBoxImageBar(_: InputBoxImageBar, didRemoveImageWithId id: InputAttachment.ID) { + performWithAnimation { [self] in + viewModel.removeAttachment(withId: id) + layoutIfNeeded() + } + } +} + +extension InputBox: UITextViewDelegate { + func textViewDidChange(_ textView: UITextView) { + viewModel.updateText(textView.text ?? "") + delegate?.inputBoxTextDidChange(textView.text ?? "") + updatePlaceholderVisibility() + updateTextViewHeight() + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBoxFunctionBar.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBoxFunctionBar.swift new file mode 100644 index 000000000..cdd917670 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBoxFunctionBar.swift @@ -0,0 +1,204 @@ +import SnapKit +import Then +import UIKit + +protocol InputBoxFunctionBarDelegate: AnyObject { + func functionBarDidTapTakePhoto(_ functionBar: InputBoxFunctionBar) + func functionBarDidTapPhotoLibrary(_ functionBar: InputBoxFunctionBar) + func functionBarDidTapAttachFiles(_ functionBar: InputBoxFunctionBar) + func functionBarDidTapEmbedDocs(_ functionBar: InputBoxFunctionBar) + func functionBarDidTapTool(_ functionBar: InputBoxFunctionBar) + func functionBarDidTapNetwork(_ functionBar: InputBoxFunctionBar) + func functionBarDidTapDeepThinking(_ functionBar: InputBoxFunctionBar) + func functionBarDidTapSend(_ functionBar: InputBoxFunctionBar) +} + +private let unselectedColor: UIColor = UIColor.affineIconPrimary +private let selectedColor: UIColor = UIColor.affineIconActivated + +class InputBoxFunctionBar: UIView { + weak var delegate: InputBoxFunctionBarDelegate? + + lazy var attachmentButton = UIButton(type: .system).then { + $0.setImage(UIImage.affinePlus, for: .normal) + $0.tintColor = unselectedColor + $0.layer.borderWidth = 1 + $0.layer.cornerRadius = 4 + $0.imageView?.contentMode = .scaleAspectFit + $0.showsMenuAsPrimaryAction = true + $0.menu = createAttachmentMenu() + } + + lazy var toolButton = UIButton(type: .system).then { + $0.setImage(UIImage.affineTools, for: .normal) + $0.tintColor = unselectedColor + $0.imageView?.contentMode = .scaleAspectFit + $0.addTarget(self, action: #selector(toolButtonTapped), for: .touchUpInside) + } + + lazy var networkButton = UIButton(type: .system).then { + $0.setImage(UIImage.affineWeb, for: .normal) + $0.tintColor = unselectedColor + $0.imageView?.contentMode = .scaleAspectFit + $0.addTarget(self, action: #selector(networkButtonTapped), for: .touchUpInside) + } + + lazy var deepThinkingButton = UIButton(type: .system).then { + $0.setImage(UIImage.affineThink, for: .normal) + $0.tintColor = unselectedColor + $0.imageView?.contentMode = .scaleAspectFit + $0.addTarget(self, action: #selector(deepThinkingButtonTapped), for: .touchUpInside) + } + + lazy var sendButton = UIButton(type: .system).then { + $0.setImage(UIImage.affineArrowUpBig, for: .normal) + $0.tintColor = UIColor.affineTextPureWhite + $0.backgroundColor = UIColor.affineButtonPrimary + $0.imageView?.contentMode = .scaleAspectFit + $0.addTarget(self, action: #selector(sendButtonTapped), for: .touchUpInside) + $0.clipsToBounds = true + } + + lazy var leftButtonsStackView = UIStackView().then { + $0.axis = .horizontal + $0.spacing = 16 + $0.alignment = .center + $0.addArrangedSubview(attachmentButton) + } + + lazy var rightButtonsStackView = UIStackView().then { + $0.axis = .horizontal + $0.spacing = 16 + $0.alignment = .center + $0.addArrangedSubview(toolButton) + $0.addArrangedSubview(networkButton) + $0.addArrangedSubview(deepThinkingButton) + $0.addArrangedSubview(sendButton) + } + + lazy var stackView = UIStackView().then { + $0.axis = .horizontal + $0.spacing = 12 + $0.alignment = .center + $0.addArrangedSubview(leftButtonsStackView) + $0.addArrangedSubview(UIView()) // spacer + $0.addArrangedSubview(rightButtonsStackView) + } + + override init(frame: CGRect) { + super.init(frame: frame) + addSubview(stackView) + stackView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + for button in [attachmentButton, toolButton, networkButton, deepThinkingButton, sendButton] { + button.snp.makeConstraints { make in + make.width.height.equalTo(32) + } + } + updateColors() + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + sendButton.layer.cornerRadius = sendButton.bounds.height / 2 + for button in [toolButton, networkButton, deepThinkingButton] { + button.layer.cornerRadius = button.bounds.height / 2 + } + } + + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + if traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) { + updateColors() + } + } + + // MARK: - Public Methods + + func updateToolState(isEnabled: Bool) { + toolButton.tintColor = isEnabled ? selectedColor : unselectedColor + } + + func updateNetworkState(isEnabled: Bool) { + networkButton.tintColor = isEnabled ? selectedColor : unselectedColor + } + + func updateDeepThinkingState(isEnabled: Bool) { + deepThinkingButton.tintColor = isEnabled ? selectedColor : unselectedColor + } + + func updateSendState(canSend: Bool) { + sendButton.isEnabled = canSend + sendButton.alpha = canSend ? 1.0 : 0.5 + } + + // MARK: - Private Methods + + private func updateColors() { + attachmentButton.layer.borderColor = UIColor.affineLayerBorder.cgColor + } + + private func createAttachmentMenu() -> UIMenu { + let takePhotoAction = UIAction( + title: "Take Photo or Video", + image: UIImage.affineCamera + ) { [weak self] _ in + guard let self else { return } + delegate?.functionBarDidTapTakePhoto(self) + } + + let photoLibraryAction = UIAction( + title: "Photo Library", + image: UIImage.affineImage + ) { [weak self] _ in + guard let self else { return } + delegate?.functionBarDidTapPhotoLibrary(self) + } + + let attachFilesAction = UIAction( + title: "Attach Files (pdf, txt, csv)", + image: UIImage.affineUpload + ) { [weak self] _ in + guard let self else { return } + delegate?.functionBarDidTapAttachFiles(self) + } + + let embedDocsAction = UIAction( + title: "Embed AFFINE Docs", + image: UIImage.affinePage + ) { [weak self] _ in + guard let self else { return } + delegate?.functionBarDidTapEmbedDocs(self) + } + + return UIMenu( + options: [.displayInline], + children: [takePhotoAction, photoLibraryAction, attachFilesAction, embedDocsAction].reversed() + ) + } + + // MARK: - Actions + + @objc private func toolButtonTapped() { + delegate?.functionBarDidTapTool(self) + } + + @objc private func networkButtonTapped() { + delegate?.functionBarDidTapNetwork(self) + } + + @objc private func deepThinkingButtonTapped() { + delegate?.functionBarDidTapDeepThinking(self) + } + + @objc private func sendButtonTapped() { + delegate?.functionBarDidTapSend(self) + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBoxImageBar.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBoxImageBar.swift new file mode 100644 index 000000000..2afaf18fe --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBoxImageBar.swift @@ -0,0 +1,180 @@ +// +// InputBoxImageBar.swift +// Intelligents +// +// Created by 秋星桥 on 6/18/25. +// + +import SnapKit +import Then +import UIKit + +protocol InputBoxImageBarDelegate: AnyObject { + func inputBoxImageBar(_ imageBar: InputBoxImageBar, didRemoveImageWithId id: UUID) +} + +private class AttachmentViewModel { + let attachment: InputAttachment + let imageCell: InputBoxImageBar.ImageCell + + init(attachment: InputAttachment, imageCell: InputBoxImageBar.ImageCell) { + self.attachment = attachment + self.imageCell = imageCell + } +} + +class InputBoxImageBar: UIScrollView { + weak var imageBarDelegate: InputBoxImageBarDelegate? + + private var attachmentViewModels: [AttachmentViewModel] = [] + private let cellSpacing: CGFloat = 8 + private let constantHeight: CGFloat = 80 + + override init(frame: CGRect = .zero) { + super.init(frame: frame) + showsHorizontalScrollIndicator = false + showsVerticalScrollIndicator = false + + snp.makeConstraints { make in + make.height.equalTo(constantHeight) + } + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError() + } + + func updateImageBarContent(_ attachments: [InputAttachment]) { + let currentIds = Set(attachmentViewModels.map(\.attachment.id)) + let imageAttachments = attachments.filter { $0.type == .image } + let newIds = Set(imageAttachments.map(\.id)) + + // 移除不再存在的附件 + let idsToRemove = currentIds.subtracting(newIds) + for id in idsToRemove { + if let index = attachmentViewModels.firstIndex(where: { $0.attachment.id == id }) { + let viewModel = attachmentViewModels.remove(at: index) + viewModel.imageCell.removeFromSuperview() + } + } + + // 添加新的附件 + let idsToAdd = newIds.subtracting(currentIds) + var initialXOffset = attachmentViewModels.reduce(0) { $0 + $1.imageCell.frame.width + cellSpacing } + for attachment in imageAttachments { + if idsToAdd.contains(attachment.id), + let data = attachment.data, + let image = UIImage(data: data) + { + let imageCell = ImageCell( + // for animation to work + frame: .init(x: initialXOffset, y: 0, width: constantHeight, height: constantHeight), + image: image, + attachmentId: attachment.id + ) + initialXOffset += constantHeight + cellSpacing + imageCell.onRemove = { [weak self] cell in + self?.removeImageCell(cell) + } + imageCell.alpha = 0 + DispatchQueue.main.async { + performWithAnimation { imageCell.alpha = 1 } + } + + let viewModel = AttachmentViewModel(attachment: attachment, imageCell: imageCell) + attachmentViewModels.append(viewModel) + addSubview(imageCell) + } + } + + layoutImageCells() + } + + func removeImageCell(_ cell: ImageCell) { + if let index = attachmentViewModels.firstIndex(where: { $0.imageCell === cell }) { + let viewModel = attachmentViewModels.remove(at: index) + viewModel.imageCell.removeFromSuperviewWithExplodeEffect() + imageBarDelegate?.inputBoxImageBar(self, didRemoveImageWithId: cell.attachmentId) + layoutImageCells() + } + } + + func clear() { + for viewModel in attachmentViewModels { + viewModel.imageCell.removeFromSuperview() + } + attachmentViewModels.removeAll() + contentSize = .zero + } + + private func layoutImageCells() { + var xOffset: CGFloat = 0 + + for viewModel in attachmentViewModels { + viewModel.imageCell.frame = CGRect(x: xOffset, y: 0, width: constantHeight, height: constantHeight) + xOffset += constantHeight + cellSpacing + } + + // Update content size + let totalWidth = max(0, xOffset - cellSpacing) + contentSize = CGSize(width: totalWidth, height: constantHeight) + } +} + +extension InputBoxImageBar { + class ImageCell: UIView { + let attachmentId: UUID + var onRemove: ((ImageCell) -> Void)? + + private lazy var imageView = UIImageView(frame: bounds).then { + $0.contentMode = .scaleAspectFill + $0.clipsToBounds = true + $0.layer.cornerRadius = 12 + $0.layer.cornerCurve = .continuous + $0.backgroundColor = .systemGray6 + } + + private lazy var removeButton = DeleteButtonView(frame: removeButtonFrame).then { + $0.onTapped = { [weak self] in + self?.removeButtonTapped() + } + } + + init(frame: CGRect, image: UIImage, attachmentId: UUID) { + self.attachmentId = attachmentId + super.init(frame: frame) + addSubview(imageView) + addSubview(removeButton) + imageView.image = image + } + + var removeButtonFrame: CGRect { + let buttonSize: CGFloat = 18 + let buttonInset: CGFloat = 6 + return CGRect( + x: bounds.width - buttonSize - buttonInset, + y: buttonInset, + width: buttonSize, + height: buttonSize + ) + } + + override func layoutSubviews() { + super.layoutSubviews() + + imageView.frame = bounds + + removeButton.frame = removeButtonFrame + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError() + } + + @objc private func removeButtonTapped() { + onRemove?(self) + } + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBoxViewModel.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBoxViewModel.swift new file mode 100644 index 000000000..9a3a24912 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBoxViewModel.swift @@ -0,0 +1,164 @@ +// +// InputBoxViewModel.swift +// Intelligents +// +// Created by AI Assistant on 6/17/25. +// + +import Combine +import Foundation + +// MARK: - Data Models + +public struct InputAttachment: Identifiable, Equatable, Hashable, Codable { + public var id: UUID = .init() + public var type: AttachmentType + public var data: Data? + public var url: URL? + public var name: String + public var size: Int64 + + public enum AttachmentType: String, Equatable, Hashable, Codable { + case image + case document + case file + } + + public init( + type: AttachmentType, + data: Data? = nil, + url: URL? = nil, + name: String, + size: Int64 = 0 + ) { + self.type = type + self.data = data + self.url = url + self.name = name + self.size = size + } +} + +public struct InputBoxData { + public var text: String + public var attachments: [InputAttachment] + public var isToolEnabled: Bool + public var isNetworkEnabled: Bool + public var isDeepThinkingEnabled: Bool + + public init( + text: String, + attachments: [InputAttachment], + isToolEnabled: Bool, + isNetworkEnabled: Bool, + isDeepThinkingEnabled: Bool + ) { + self.text = text + self.attachments = attachments + self.isToolEnabled = isToolEnabled + self.isNetworkEnabled = isNetworkEnabled + self.isDeepThinkingEnabled = isDeepThinkingEnabled + } +} + +// MARK: - View Model + +public class InputBoxViewModel: ObservableObject { + // MARK: - Published Properties + + @Published public var inputText: String = "" + @Published public var isToolEnabled: Bool = false + @Published public var isNetworkEnabled: Bool = false + @Published public var isDeepThinkingEnabled: Bool = false + @Published public var hasAttachments: Bool = false + @Published public var attachments: [InputAttachment] = [] + @Published public var canSend: Bool = false + + // MARK: - Private Properties + + private var cancellables = Set() + + // MARK: - Initialization + + public init() { + setupBindings() + } + + // MARK: - Private Methods + + private func setupBindings() { + // 监听文本变化,自动更新发送按钮状态 + $inputText + .map { !$0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty } + .assign(to: \.canSend, on: self) + .store(in: &cancellables) + + // 监听附件变化 + $attachments + .map { !$0.isEmpty } + .assign(to: \.hasAttachments, on: self) + .store(in: &cancellables) + } +} + +// MARK: - Text Management + +public extension InputBoxViewModel { + func updateText(_ text: String) { + inputText = text + } +} + +// MARK: - Feature Toggles + +public extension InputBoxViewModel { + func toggleTool() { + isToolEnabled.toggle() + } + + func toggleNetwork() { + isNetworkEnabled.toggle() + } + + func toggleDeepThinking() { + isDeepThinkingEnabled.toggle() + } +} + +// MARK: - Attachment Management + +public extension InputBoxViewModel { + func addAttachment(_ attachment: InputAttachment) { + attachments.append(attachment) + } + + func removeAttachment(withId id: UUID) { + attachments.removeAll { $0.id == id } + } + + func clearAttachments() { + attachments.removeAll() + } +} + +// MARK: - Send Management + +public extension InputBoxViewModel { + func prepareSendData() -> InputBoxData { + InputBoxData( + text: inputText.trimmingCharacters(in: .whitespacesAndNewlines), + attachments: attachments, + isToolEnabled: isToolEnabled, + isNetworkEnabled: isNetworkEnabled, + isDeepThinkingEnabled: isDeepThinkingEnabled + ) + } + + func resetInput() { + inputText = "" + attachments.removeAll() + isToolEnabled = false + isNetworkEnabled = false + isDeepThinkingEnabled = false + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsButton/IntelligentsButton+Control.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/IntelligentsButton/IntelligentsButton+Control.swift similarity index 70% rename from packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsButton/IntelligentsButton+Control.swift rename to packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/IntelligentsButton/IntelligentsButton+Control.swift index 117c2c594..d389a056b 100644 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsButton/IntelligentsButton+Control.swift +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/IntelligentsButton/IntelligentsButton+Control.swift @@ -5,6 +5,7 @@ // Created by 秋星桥 on 2024/11/18. // +import SnapKit import UIKit public extension UIViewController { @@ -16,15 +17,15 @@ public extension UIViewController { let button = IntelligentsButton() view.addSubview(button) view.bringSubviewToFront(button) - button.translatesAutoresizingMaskIntoConstraints = false - [ - button.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20), - button.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20 - Constant.affineTabbarHeight), - button.widthAnchor.constraint(equalToConstant: 50), - button.heightAnchor.constraint(equalToConstant: 50), - ].forEach { $0.isActive = true } + button.snp.makeConstraints { make in + make.trailing.equalTo(view.safeAreaLayoutGuide).offset(-20) + make.bottom.equalTo(view.safeAreaLayoutGuide).offset(-20 - 44) + make.width.height.equalTo(50) + } button.transform = .init(scaleX: 0, y: 0) - view.layoutIfNeeded() + if view.frame != .zero { + view.layoutIfNeeded() + } return button } @@ -47,12 +48,7 @@ public extension UIViewController { button.stopProgress() view.layoutIfNeeded() - UIView.animate( - withDuration: 0.5, - delay: 0, - usingSpringWithDamping: 1.0, - initialSpringVelocity: 0.8 - ) { + performWithAnimation { button.alpha = 1 button.transform = .identity button.setNeedsLayout() @@ -77,12 +73,7 @@ public extension UIViewController { button.stopProgress() button.setNeedsLayout() view.layoutIfNeeded() - UIView.animate( - withDuration: 0.5, - delay: 0, - usingSpringWithDamping: 1.0, - initialSpringVelocity: 0.8 - ) { + performWithAnimation { button.alpha = 0 button.transform = .init(scaleX: 0, y: 0) button.setNeedsLayout() diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsButton/IntelligentsButton+Delegate.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/IntelligentsButton/IntelligentsButton+Delegate.swift similarity index 100% rename from packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsButton/IntelligentsButton+Delegate.swift rename to packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/IntelligentsButton/IntelligentsButton+Delegate.swift diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsButton/IntelligentsButton.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/IntelligentsButton/IntelligentsButton.swift similarity index 51% rename from packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsButton/IntelligentsButton.swift rename to packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/IntelligentsButton/IntelligentsButton.swift index 60572c95b..a633a5c12 100644 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/IntelligentsButton/IntelligentsButton.swift +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/IntelligentsButton/IntelligentsButton.swift @@ -5,13 +5,26 @@ // Created by 秋星桥 on 2024/11/18. // +import SnapKit +import SwifterSwift +import Then import UIKit // floating button to open intelligent panel public class IntelligentsButton: UIView { - let image = UIImageView() - let background = UIView() - let activityIndicator = UIActivityIndicatorView() + lazy var image = UIImageView().then { + $0.image = .init(named: "spark", in: .module, with: .none) + $0.contentMode = .scaleAspectFit + } + + lazy var background = UIView().then { + $0.backgroundColor = .init( + light: .systemBackground, + dark: .darkGray.withAlphaComponent(0.25) + ) + } + + lazy var activityIndicator = UIActivityIndicatorView() public weak var delegate: (any IntelligentsButtonDelegate)? = nil { didSet { assert(Thread.isMainThread) } @@ -19,45 +32,10 @@ public class IntelligentsButton: UIView { public init() { super.init(frame: .zero) - - background.backgroundColor = .white - addSubview(background) - background.translatesAutoresizingMaskIntoConstraints = false - [ - background.leadingAnchor.constraint(equalTo: leadingAnchor), - background.trailingAnchor.constraint(equalTo: trailingAnchor), - background.topAnchor.constraint(equalTo: topAnchor), - background.bottomAnchor.constraint(equalTo: bottomAnchor), - ].forEach { $0.isActive = true } - - image.image = .init(named: "spark", in: .module, with: .none) - image.contentMode = .scaleAspectFit - image.tintColor = .accent - addSubview(image) - let imageInsetValue: CGFloat = 12 - image.translatesAutoresizingMaskIntoConstraints = false - [ - image.leadingAnchor.constraint(equalTo: leadingAnchor, constant: imageInsetValue), - image.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -imageInsetValue), - image.topAnchor.constraint(equalTo: topAnchor, constant: imageInsetValue), - image.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -imageInsetValue), - ].forEach { $0.isActive = true } - - activityIndicator.translatesAutoresizingMaskIntoConstraints = false - addSubview(activityIndicator) - [ - activityIndicator.centerXAnchor.constraint(equalTo: centerXAnchor), - activityIndicator.centerYAnchor.constraint(equalTo: centerYAnchor), - ].forEach { $0.isActive = true } - - clipsToBounds = true - layer.borderWidth = 2 - layer.borderColor = UIColor.gray.withAlphaComponent(0.1).cgColor - - let tap = UITapGestureRecognizer(target: self, action: #selector(tapped)) - addGestureRecognizer(tap) - isUserInteractionEnabled = true - + setupViews() + setupConstraints() + setupGesture() + setupAppearance() stopProgress() } @@ -97,3 +75,39 @@ public class IntelligentsButton: UIView { image.isHidden = false } } + +// MARK: - Setup Methods + +private extension IntelligentsButton { + func setupViews() { + addSubview(background) + addSubview(image) + addSubview(activityIndicator) + } + + func setupConstraints() { + background.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + image.snp.makeConstraints { make in + make.edges.equalToSuperview().inset(12) + } + + activityIndicator.snp.makeConstraints { make in + make.center.equalToSuperview() + } + } + + func setupGesture() { + let tap = UITapGestureRecognizer(target: self, action: #selector(tapped)) + addGestureRecognizer(tap) + isUserInteractionEnabled = true + } + + func setupAppearance() { + clipsToBounds = true + layer.borderWidth = 2 + layer.borderColor = UIColor.gray.withAlphaComponent(0.1).cgColor + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/MainHeaderView/MainHeaderView.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/MainHeaderView/MainHeaderView.swift new file mode 100644 index 000000000..c9859acd1 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/MainHeaderView/MainHeaderView.swift @@ -0,0 +1,121 @@ +import SnapKit +import Then +import UIKit + +protocol MainHeaderViewDelegate: AnyObject { + func mainHeaderViewDidTapClose() + func mainHeaderViewDidTapDropdown() + func mainHeaderViewDidTapMenu() +} + +class MainHeaderView: UIView { + weak var delegate: MainHeaderViewDelegate? + + private lazy var closeButton = UIButton(type: .system).then { + $0.imageView?.contentMode = .scaleAspectFit + $0.setImage(UIImage.affineClose, for: .normal) + $0.tintColor = UIColor.affineIconPrimary + $0.backgroundColor = UIColor.affineLayerBackgroundSecondary + $0.layer.cornerRadius = 8 + $0.addTarget(self, action: #selector(closeButtonTapped), for: .touchUpInside) + $0.setContentHuggingPriority(.required, for: .horizontal) + } + + private lazy var titleLabel = UILabel().then { + $0.text = "AFFiNE AI" + $0.font = .systemFont(ofSize: 16, weight: .medium) + $0.textColor = UIColor.affineTextPrimary + $0.textAlignment = .center + } + + private lazy var dropdownButton = UIButton(type: .system).then { + $0.imageView?.contentMode = .scaleAspectFit + $0.setImage(UIImage.affineArrowDown, for: .normal) + $0.tintColor = UIColor.affineIconPrimary + $0.addTarget(self, action: #selector(dropdownButtonTapped), for: .touchUpInside) + } + + private lazy var centerStackView = UIStackView().then { + $0.axis = .horizontal + $0.spacing = 8 + $0.alignment = .center + $0.addArrangedSubview(titleLabel) + $0.addArrangedSubview(dropdownButton) + } + + private lazy var menuButton = UIButton(type: .system).then { + $0.imageView?.contentMode = .scaleAspectFit + $0.setImage(UIImage.affineMore, for: .normal) + $0.tintColor = UIColor.affineIconPrimary + $0.backgroundColor = UIColor.affineLayerBackgroundSecondary + $0.layer.cornerRadius = 8 + $0.addTarget(self, action: #selector(menuButtonTapped), for: .touchUpInside) + $0.setContentHuggingPriority(.required, for: .horizontal) + } + + private lazy var leftSpacerView = UIView() + + private lazy var rightSpacerView = UIView() + + private lazy var mainStackView = UIStackView().then { + $0.axis = .horizontal + $0.alignment = .center + $0.distribution = .fill + $0.spacing = 16 + $0.addArrangedSubview(closeButton) + $0.addArrangedSubview(leftSpacerView) + $0.addArrangedSubview(centerStackView) + $0.addArrangedSubview(rightSpacerView) + $0.addArrangedSubview(menuButton) + } + + init() { + super.init(frame: .zero) + + backgroundColor = UIColor.affineLayerBackgroundPrimary + addSubview(mainStackView) + + mainStackView.snp.makeConstraints { make in + make.left.right.equalToSuperview().inset(16) + make.centerY.equalToSuperview() + } + + closeButton.snp.makeConstraints { make in + make.size.equalTo(titleLabel.font.pointSize + 16) + } + + menuButton.snp.makeConstraints { make in + make.size.equalTo(titleLabel.font.pointSize + 16) + } + + dropdownButton.snp.makeConstraints { make in + make.size.equalTo(titleLabel.font.pointSize + 16) + } + + // ensure center stack to be center + leftSpacerView.snp.makeConstraints { make in + make.width.equalTo(rightSpacerView) + } + + snp.makeConstraints { make in + make.height.equalTo(52) + } + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + fatalError() + } + + @objc private func closeButtonTapped() { + delegate?.mainHeaderViewDidTapClose() + } + + @objc private func dropdownButtonTapped() { + delegate?.mainHeaderViewDidTapDropdown() + } + + @objc private func menuButtonTapped() { + delegate?.mainHeaderViewDidTapMenu() + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/SupplementView/CircleImageView.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/SupplementView/CircleImageView.swift similarity index 100% rename from packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/SupplementView/CircleImageView.swift rename to packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/SupplementView/CircleImageView.swift diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/SupplementView/DarkActionButton.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/SupplementView/DarkActionButton.swift similarity index 100% rename from packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/SupplementView/DarkActionButton.swift rename to packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/SupplementView/DarkActionButton.swift diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/SupplementView/DeleteButtonView.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/SupplementView/DeleteButtonView.swift new file mode 100644 index 000000000..33fbf1f29 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/SupplementView/DeleteButtonView.swift @@ -0,0 +1,56 @@ +import UIKit + +class DeleteButtonView: UIView { + let imageView = UIImageView(image: .init(systemName: "xmark")).then { + $0.tintColor = .white + $0.contentMode = .scaleAspectFit + } + + let blur = UIVisualEffectView( + effect: UIBlurEffect(style: .systemUltraThinMaterialDark) + ).then { + $0.clipsToBounds = true + } + + var onTapped: () -> Void = {} + + override init(frame: CGRect) { + super.init(frame: frame) + + isUserInteractionEnabled = true + + addSubview(blur) + addSubview(imageView) + + blur.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + imageView.snp.makeConstraints { make in + make.edges.equalToSuperview().inset(2) + } + + let gesture = UITapGestureRecognizer(target: self, action: #selector(tapped)) + addGestureRecognizer(gesture) + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + if bounds.width < 50 || bounds.height < 50 { + return bounds.insetBy(dx: -20, dy: -20).contains(point) + } + return super.point(inside: point, with: event) + } + + override func layoutSubviews() { + super.layoutSubviews() + blur.layer.cornerRadius = min(bounds.width, bounds.height) / 2 + } + + @objc func tapped() { + onTapped() + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Model/IntelligentContext.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Model/IntelligentContext.swift new file mode 100644 index 000000000..605ff9261 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Model/IntelligentContext.swift @@ -0,0 +1,44 @@ +// +// IntelligentContext.swift +// Intelligents +// +// Created by 秋星桥 on 6/17/25. +// + +import Combine +import Foundation +import WebKit + +public class IntelligentContext { + // shared across the app, we expect our app to have a single context and webview + public static let shared = IntelligentContext() + + public var webView: WKWebView! + + public lazy var temporaryDirectory: URL = { + let tempDir = FileManager.default.temporaryDirectory + return tempDir.appendingPathComponent("IntelligentContext") + }() + + private init() {} + + public func preparePresent(_ completion: @escaping () -> Void) { + DispatchQueue.global(qos: .userInitiated).async { [self] in + prepareTemporaryDirectory() + // TODO: used to gathering information, populate content from webview, etc. + DispatchQueue.main.async { + completion() + } + } + } + + func prepareTemporaryDirectory() { + if FileManager.default.fileExists(atPath: temporaryDirectory.path) { + try? FileManager.default.removeItem(at: temporaryDirectory) + } + try? FileManager.default.createDirectory( + at: temporaryDirectory, + withIntermediateDirectories: true + ) + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Model/UIColor+Affine.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Model/UIColor+Affine.swift new file mode 100644 index 000000000..9b1594604 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Model/UIColor+Affine.swift @@ -0,0 +1,78 @@ +import UIKit + +extension UIColor { + /// Primary icon color + static var affineIconPrimary: UIColor { + UIColor(named: "affine.icon.primary", in: .module, compatibleWith: nil) ?? .black + } + + /// Primary background layer color + static var affineLayerBackgroundPrimary: UIColor { + UIColor(named: "affine.layer.background.primary", in: .module, compatibleWith: nil) ?? .white + } + + /// Secondary background layer color + static var affineLayerBackgroundSecondary: UIColor { + UIColor(named: "affine.layer.background.secondary", in: .module, compatibleWith: nil) ?? .systemGray6 + } + + /// Border layer color + static var affineLayerBorder: UIColor { + UIColor(named: "affine.layer.border", in: .module, compatibleWith: nil) ?? .gray + } + + /// Pure white layer color + static var affineLayerPureWhite: UIColor { + UIColor(named: "affine.layer.pureWhite", in: .module, compatibleWith: nil) ?? .white + } + + /// Primary button color + static var affineButtonPrimary: UIColor { + UIColor(named: "affine.button.primary", in: .module, compatibleWith: nil) ?? .blue + } + + /// Activated icon color + static var affineIconActivated: UIColor { + UIColor(named: "affine.icon.activated", in: .module, compatibleWith: nil) ?? .blue + } + + /// Text emphasis color + static var affineTextEmphasis: UIColor { + UIColor(named: "affine.text.emphasis", in: .module, compatibleWith: nil) ?? .blue + } + + /// Text link color + static var affineTextLink: UIColor { + UIColor(named: "affine.text.link", in: .module, compatibleWith: nil) ?? .blue + } + + /// List dot and number color + static var affineTextListDotAndNumber: UIColor { + UIColor(named: "affine.text.listDotAndNumber", in: .module, compatibleWith: nil) ?? .blue + } + + /// Placeholder text color + static var affineTextPlaceholder: UIColor { + UIColor(named: "affine.text.placeholder", in: .module, compatibleWith: nil) ?? .gray + } + + /// Primary text color + static var affineTextPrimary: UIColor { + UIColor(named: "affine.text.primary", in: .module, compatibleWith: nil) ?? .black + } + + /// Pure white text color + static var affineTextPureWhite: UIColor { + UIColor(named: "affine.text.pureWhite", in: .module, compatibleWith: nil) ?? .white + } + + /// Secondary text color + static var affineTextSecondary: UIColor { + UIColor(named: "affine.text.secondary", in: .module, compatibleWith: nil) ?? .gray + } + + /// Tertiary text color + static var affineTextTertiary: UIColor { + UIColor(named: "affine.text.tertiary", in: .module, compatibleWith: nil) ?? .gray + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Model/UIImage+Affine.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Model/UIImage+Affine.swift new file mode 100644 index 000000000..1468143f0 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Model/UIImage+Affine.swift @@ -0,0 +1,93 @@ +import UIKit + +extension UIImage { + /// Check circle icon + static var affineCheckCircle: UIImage { + UIImage(named: "CheckCircle", in: .module, compatibleWith: nil) ?? UIImage() + } + + /// More options icon + static var affineMore: UIImage { + UIImage(named: "More", in: .module, compatibleWith: nil) ?? UIImage() + } + + /// Arrow down icon + static var affineArrowDown: UIImage { + UIImage(named: "ArrowDown", in: .module, compatibleWith: nil) ?? UIImage() + } + + /// Tools icon + static var affineTools: UIImage { + UIImage(named: "Tools", in: .module, compatibleWith: nil) ?? UIImage() + } + + /// Box icon + static var affineBox: UIImage { + UIImage(named: "Box", in: .module, compatibleWith: nil) ?? UIImage() + } + + /// Think icon + static var affineThink: UIImage { + UIImage(named: "Think", in: .module, compatibleWith: nil) ?? UIImage() + } + + /// Web icon + static var affineWeb: UIImage { + UIImage(named: "Web", in: .module, compatibleWith: nil) ?? UIImage() + } + + /// Calendar icon + static var affineCalendar: UIImage { + UIImage(named: "Calendar", in: .module, compatibleWith: nil) ?? UIImage() + } + + /// Camera icon + static var affineCamera: UIImage { + UIImage(named: "Camera", in: .module, compatibleWith: nil) ?? UIImage() + } + + /// Close icon + static var affineClose: UIImage { + UIImage(named: "Close", in: .module, compatibleWith: nil) ?? UIImage() + } + + /// Big arrow up icon + static var affineArrowUpBig: UIImage { + UIImage(named: "ArrowUpBig", in: .module, compatibleWith: nil) ?? UIImage() + } + + /// Broom icon + static var affineBroom: UIImage { + UIImage(named: "Broom", in: .module, compatibleWith: nil) ?? UIImage() + } + + /// Bubble icon + static var affineBubble: UIImage { + UIImage(named: "Bubble", in: .module, compatibleWith: nil) ?? UIImage() + } + + /// Image icon + static var affineImage: UIImage { + UIImage(named: "Image", in: .module, compatibleWith: nil) ?? UIImage() + } + + /// Page icon + static var affinePage: UIImage { + UIImage(named: "Page", in: .module, compatibleWith: nil) ?? UIImage() + } + + /// Plus icon + static var affinePlus: UIImage { + UIImage(named: "Plus", in: .module, compatibleWith: nil) ?? UIImage() + } + + /// Settings icon + static var affineSettings: UIImage { + UIImage(named: "Settings", in: .module, compatibleWith: nil) ?? UIImage() + } + + /// Upload icon + static var affineUpload: UIImage { + UIImage(named: "Upload", in: .module, compatibleWith: nil) ?? UIImage() + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/Contents.json b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.button.primary.colorset/Contents.json b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.button.primary.colorset/Contents.json new file mode 100644 index 000000000..5cf53db92 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.button.primary.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xEB", + "green" : "0x96", + "red" : "0x1E" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xEB", + "green" : "0x96", + "red" : "0x1E" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.icon.activated.colorset/Contents.json b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.icon.activated.colorset/Contents.json new file mode 100644 index 000000000..5cf53db92 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.icon.activated.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xEB", + "green" : "0x96", + "red" : "0x1E" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xEB", + "green" : "0x96", + "red" : "0x1E" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.icon.primary.colorset/Contents.json b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.icon.primary.colorset/Contents.json new file mode 100644 index 000000000..097a07d04 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.icon.primary.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x7A", + "green" : "0x7A", + "red" : "0x7A" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF3", + "green" : "0xF3", + "red" : "0xF3" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.layer.background.primary.colorset/Contents.json b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.layer.background.primary.colorset/Contents.json new file mode 100644 index 000000000..ee2f44f71 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.layer.background.primary.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFF" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x14", + "green" : "0x14", + "red" : "0x14" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.layer.background.secondary.colorset/Contents.json b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.layer.background.secondary.colorset/Contents.json new file mode 100644 index 000000000..d0a7f7fbb --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.layer.background.secondary.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF5", + "green" : "0xF5", + "red" : "0xF5" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x25", + "green" : "0x25", + "red" : "0x25" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.layer.border.colorset/Contents.json b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.layer.border.colorset/Contents.json new file mode 100644 index 000000000..5867cb4b8 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.layer.border.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xE6", + "green" : "0xE6", + "red" : "0xE6" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x41", + "green" : "0x41", + "red" : "0x41" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.layer.pureWhite.colorset/Contents.json b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.layer.pureWhite.colorset/Contents.json new file mode 100644 index 000000000..b8b76883a --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.layer.pureWhite.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFF" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "255", + "green" : "255", + "red" : "255" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.text.emphasis.colorset/Contents.json b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.text.emphasis.colorset/Contents.json new file mode 100644 index 000000000..6fd68daf9 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.text.emphasis.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xEB", + "green" : "0x96", + "red" : "0x1E" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xEB", + "green" : "0x96", + "red" : "0x1E" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.text.link.colorset/Contents.json b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.text.link.colorset/Contents.json new file mode 100644 index 000000000..38c33058a --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.text.link.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x9F", + "green" : "0x5F", + "red" : "0x03" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x9F", + "green" : "0x5F", + "red" : "0x03" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.text.listDotAndNumber.colorset/Contents.json b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.text.listDotAndNumber.colorset/Contents.json new file mode 100644 index 000000000..e651740bf --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.text.listDotAndNumber.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xCB", + "green" : "0x77", + "red" : "0x00" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xCB", + "green" : "0x77", + "red" : "0x00" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.text.placeholder.colorset/Contents.json b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.text.placeholder.colorset/Contents.json new file mode 100644 index 000000000..3e6e1103e --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.text.placeholder.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x92", + "green" : "0x92", + "red" : "0x92" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x7A", + "green" : "0x7A", + "red" : "0x7A" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.text.primary.colorset/Contents.json b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.text.primary.colorset/Contents.json new file mode 100644 index 000000000..8fd8401b2 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.text.primary.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x14", + "green" : "0x14", + "red" : "0x14" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xE6", + "green" : "0xE6", + "red" : "0xE6" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.text.pureWhite.colorset/Contents.json b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.text.pureWhite.colorset/Contents.json new file mode 100644 index 000000000..70a39895a --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.text.pureWhite.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFF" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.text.secondary.colorset/Contents.json b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.text.secondary.colorset/Contents.json new file mode 100644 index 000000000..df415be47 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.text.secondary.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x7A", + "green" : "0x7A", + "red" : "0x7A" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x92", + "green" : "0x92", + "red" : "0x92" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.text.tertiary.colorset/Contents.json b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.text.tertiary.colorset/Contents.json new file mode 100644 index 000000000..3e6e1103e --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Colors.xcassets/affine.text.tertiary.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x92", + "green" : "0x92", + "red" : "0x92" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x7A", + "green" : "0x7A", + "red" : "0x7A" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/ArrowDown.imageset/Contents.json b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/ArrowDown.imageset/Contents.json new file mode 100644 index 000000000..65640bae4 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/ArrowDown.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "More Options Icon.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/ArrowDown.imageset/More Options Icon.pdf b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/ArrowDown.imageset/More Options Icon.pdf new file mode 100644 index 0000000000000000000000000000000000000000..06088d67906380b6f9772f3e29297d765ed19a73 GIT binary patch literal 3979 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 literal 0 HcmV?d00001 diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/ArrowUpBig.imageset/ArrowUpBig.pdf b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/ArrowUpBig.imageset/ArrowUpBig.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f47faca5936a23fc4b222656516bb293c7abe7d8 GIT binary patch literal 4201 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 RnpFY^VhD(>TeS}D|3BDgq`&|G literal 0 HcmV?d00001 diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Broom.imageset/Broom.pdf b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Broom.imageset/Broom.pdf new file mode 100644 index 0000000000000000000000000000000000000000..cb873ca1860eab221e72930fa099699f27a76dfc GIT binary patch literal 4134 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 literal 0 HcmV?d00001 diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Broom.imageset/Contents.json b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Broom.imageset/Contents.json new file mode 100644 index 000000000..eb6e250f2 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Broom.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Broom.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Bubble.imageset/Bubble.pdf b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Bubble.imageset/Bubble.pdf new file mode 100644 index 0000000000000000000000000000000000000000..63a63af104640b43b3754731c67330eb7e5655f2 GIT binary patch literal 3972 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 S<9}2_5{`g^&z@DecJ)8JA=<_O literal 0 HcmV?d00001 diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Bubble.imageset/Contents.json b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Bubble.imageset/Contents.json new file mode 100644 index 000000000..36cb25248 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Bubble.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Bubble.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Calendar.imageset/Contents.json b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Calendar.imageset/Contents.json new file mode 100644 index 000000000..3ace4add8 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Calendar.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Left icon-1.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Calendar.imageset/Left icon-1.pdf b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Calendar.imageset/Left icon-1.pdf new file mode 100644 index 0000000000000000000000000000000000000000..087a564ce9ab77d7347467142649a1e180f9bd10 GIT binary patch literal 4472 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 literal 0 HcmV?d00001 diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Close.imageset/Close.pdf b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Close.imageset/Close.pdf new file mode 100644 index 0000000000000000000000000000000000000000..1cebcc33bee5e34cce2a4992be65a730951bc842 GIT binary patch literal 4250 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 Ct^tSu literal 0 HcmV?d00001 diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/More.imageset/Contents.json b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/More.imageset/Contents.json new file mode 100644 index 000000000..c4324cb95 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/More.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "More Options Icon-1.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/More.imageset/More Options Icon-1.pdf b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/More.imageset/More Options Icon-1.pdf new file mode 100644 index 0000000000000000000000000000000000000000..3fc56638fde7c1824a848642407567702393af1d GIT binary patch literal 4350 zcmZu#c|6qJ_ct-oC(}yKaB1=P(iZsT^2xFOHvV-9Ki|>wV98pXJUyuXAS`MSH&r8bJgAG>8J3*7g7h?%4w( zH5^!US0^fq4kESb?hG2;jC;yQp&Wyq`nL+Asm?iq&f$ zS*|hmU!$WfWTC!XXOlC_+-CI}>>YBmGr49 zlh`aSPM^VXchOxMy?-&Jf~Pu#0!!Cy zKYeggSJs#iC40>Jn3|$yB-e0V4M~bB?6gd{My?rjPl&;Yc5H-)Q>C+3$W%t6){XlKc2hX(H;Jcn2f8gdM2qD|x#``ulk(nzoBh(h z>vN>xl&v>_gThbM(Z;0lBp@thyYZGrbKH8v1K5Ta zcKb#F+=hg!Zvqk8QK^S+#x-_*QvI}MRAPG9#q*7iqN(vH2Lqw)o5L^EaBo=KqYa~L zqhWO$Kgdc3A9*ZImHqcztFD8*``*IUULqZrzCIbJ9>{%3+GHel`l8!o_&X8_jS%-( z?E!eeTCT}(eIA8>Z|BIAiK_v~Cm*}@hjt9fPM(`_n-SIUKc((0FSM2uSP&?$DV7}K z9E{o@EvK|Lny#ix884sF;?EW!ir@@;54Uo&E(_4 zj`i8)1(wBm$9L*g96G0!Oc7Nays`72=)5?S1I5~B4W_^Y5%)uBwL^V`+9TEY+OgX5 zupQMh)n3&(kybmblzh#8Mb6w>rtz5KpZ3VO^%f=B*@&u3J$^mYE_-7}bc%Tf$7Cq> zUC~;j8cNNH%jq}nvJCpens%<&X->_{u=q@{7aPN{uUt%%YHZEdJ`Vf3<2cgs;4!^6 zGqM5sM6u>nS)M68%{rsfOwUZwgq(`W7-sl?SauCQ122`tDSxv1ev+M>S*m1(w<<7Z ztB-|CTRuObqUV@%Jh)wfonLZi+I38MjQcsNdB=t($=%34M4c7KwntFRoNKCJPAh+g z4vCX~8GAgoB9;*=j7Ktz?C;nWpV?*~VaK*Jv^!j2SpaX}*1jR%sv)9(^QeudRC%0Q zT557{xU+!ugXo_oUd2N;XP~+jrS%27LKSt;& z=SMfIy33QVQ;T|5aV5Df3P%(Q8+WIArUpHepb>jnz4pDO+s)VA)(fkV3-7r7@0|q3 zdB%l)6>MwW8-r8s)minMXoGeLTn=H2b*R+lD+c@b(w=BOwZ5!#RVVoAE`6WcowenW zdn2u~zhs4F+h<+Oj>!6%?UvP%HJgVn=xH}??X=WB)_31-B;~bEj^lHOslp(~Z*9h3 zBiq0JJCOIo`r9dF9`oNM=LU~UE*Y$i)=kkGb($ZJ9JrF2-fLUsUUkiA+{ii|Yvj8d z8y_BjX!kWF;dq^u#eL2zot6u|Chz6hX{FDKHnSs2`iopTGrJN?28u^I&vYeuMRT@# z#t(DeGu|r>I}Cf*yIMQiusxH@yvv8nJB_Ki1=U7olz5i?vcV)?D3KWJ#> z_l^PLlh-zGZR{_KzQVI1zq(+3exZKDe(CxKQI{g~RW3wLBfB1q_Fw|&m2?$^OET`3GKrDJt>tb_NljzdggRKbX9Ig>_&PzM>J-BInrWwH^=Ct z&$*Bp=@+s0(b3W0qr=fjWdC^k7c6DfLX7)AweL1}SM(K+9OSI?-f#{*dp2I*!ljc_ zrax0Ld0FT|U{jzTOrL__Bz%hEY#s73In^|DtF7;Q-!lpCg9iVRlfzJYtqGYCu?g)_ zwvmxh+YLPqzD-iw2CQzjRhPPpIsO#acfj?qwQ?Ims)qGEt9mAMeI%Q30(~Od$1S_R z-SSLQ&d9NeUe8g_!RdQqyJZTUHnx4r&9z^bujqcb#kTx;aa>j5q!T$eq6p6zV3N(q zPsz*&7no;io9nv5R7<)%ynGTZ5_dA~f<~??s5Yv?RlPh^+S$%6E#4=8jV-%-zUh4Z zh0e->ede>2aqgGFqONkCah;%vQxhT+qJ!)XuN3oKj^)s&inJfG`k%HI=D_CBnd+I`duHW@H^*yTdR+Q1cdzSWuVS-z;Eg1kxnp6| zmk&3lJI(fu9X=zNnw)8zmfMXZsgQrZ`xu=hTrU*gzq@u%$M@jJ$Oyw%w`XiK&N_~m z>T3q)#m^SsBYZ5pZKZN<@0)usPr96(@-`OERd_%fuPj;lGJ%m0zM&U+)jhjk zx9JI_{Z+)u4&|(p%SB}|Y`|p&G&RlA#ex5FODCj_OD$=)p^5=;8AojoeO&{Cq z2c9`T>)7GF-RYtEl=T;Sdu>cj**=Nc3~&0(=cs*Mvt{Jcaw` z4!kVx=cMI-r7;szTVAQfPgc*gjwsH&9Er)QFRG_o8C2x^h)qsA6X@>as^Zp<$88(G zX3Kv4Dn$@IQ?g;-RRulCm-H7`BVyx~)--B0ku1~_G@@c6X5$WB-YcSB>n>or#XDFI zi1n@W$|GxZC&7J>dTcDVIeDh)fw?_ippGFx@xllVxuMORcm6CTqs~vcc(1X#LnA z_7lBCRoUxVRaZ=}n}d4jbOocPReo2BbO>(x8a7W9Xo+<%D$qg#N0&W{N@;70$!u#w zRx4Au0<43-;2|Q73IN^7W_}`sB;M2sVEE(b-1xWvWBKq25UHo7rAc+8+km`T)daNU z(aag+_*%T#Rg3AsbUo}$rP1d`SuHfkV=;loP~IfEV1xg|W`1-f%quR;t4PRL>oCCk zqPYPs02+asTOevdP~l7bJsbl6TM3OqUVRCy+7Vf?EPOuTNeA(EtpBKq3%uge4#rfg_;tSkgQPM8-$> zE@=QCn`c0S^8#@}9lPjpUKtD0pit)n<752b`AmFdi9b7lxKORdlpFy90*}L?F-!Br zBZyEf=ju0K3_h}ikB{-a@~QZd@jdbp8bCte@OTn&NtR>;hIbC+g#w^KKC;9gANySZ zsH%LX#eDb<7ad}ygxKDZbipKURuMDR0 z{69T`cN>2{Nl8RJ|Kbw)=-flq$%)Bw15a4azc@|LIzw+(3ib5DqtuXY!=P$1Ip7JX zc>Mb`FJN#B(4w@!WI}givR!F(H;~s}Oy>Eum`*IZ6XcQCKNdg=)fK`(=x)tHOS%jX zT6|UOGi;zxL0$&D_VxQct;J+Rw&=g&cY}~6Oyob9cnP}k9rB*#zZ2J@vZxNs(|?+B zT{9+#e)z&ztAXfP6DA`G`FDE1qI4mix3g>mlhEZ<@l!-=`SuU z5o&^eYGHXj?*Bk!EOg|5aN$r(xLB@Kh6CLd;GMgkHdM%+ZcInIBDj-bqxid=NE0TL k1@cNYcQnYu3{N^Q@40^C=GGYlnrWN9o(mNXcQn86Gt`&LLKvTq@4WC_`~vA&cg zvWuul$-X7NncnLCy58@)zWa~oIoIz#=RD_}`#kscJExKAni5b+IS>d6mIC9f9YJ95 z`SW0iiW33thD8z3V2C=}17m|WyrfFrA}@$0hO$R~JCV25(Ka|+^v@k#G}fNr0G5Wq z<$faiU>UNcqy)kf+|Vdz5HYnf9)n~bN%%Y{_a6$f}`EYoSc1al(aQYRkfEFi8gl8e0T0g8q`1zsDz5LL)%EALBsiLto>QZhw4DDGrt*&^f+j8)4(;n##Mz~ho`yD z^16u0q)w)9#|Xx4d|O;>*BcIz-x@fG`;3z1smEI(FxW}y?O`edPZyPQQIYc@DgE$P z_bc1aj%guZ{!OAQkp%%wx z;1Mt|Oqpwxsw(I_l4>j9`{NTQ0IdY%oBI@ySibdRn6y{=`5q z1HA!Q>A*Cj)X; zbcnn_W8yu5$#z0`eRF+fkl1%?A zhl?8^+2d(Z(;<@T3E5X3rwmLjC@!#WaO|DC8#K_Ih)R`m(xp9fGWJe4>5;V~R4=h7 z5zx#1fuAelY6~xlztnG3%Sp)NLKTZQ!$jDZwk_19ve)t_^qK7M;#+|4(bL`4Ek*}Uv2?S~TIHR*FvdX{Xw~yavR0+JP>>@>!anPCiIY?9rfh>DFEhZ!F@t9Ax>6xPs>CoCFc6P zLwQJjicjjKcH}$_F56`W@(sANyfsaHVqxn$Q0h! z$!5uo$(Uq%7zCs5Sm01|>$GE>gS&&CgHferC2;)o`0)y>{jOc@+`V^!Fs(sbi65YQ5UiN5L5{J3+b zA@d=sdiEHJi{vVNRk&(EA=@iEw1dM&ZjLbLI9GSZls#8Frdu#}BDb_44HJa9vnU}m z+B>a#)1#}X?}W{=gQRQ;T);Y-=j44|%?mbds_oX{8c`Y%?dNoSd(QSW#9xTFD*jv) zQ|wrDw>YlodojLfqUf+3S~)vzJUVHqetrJA!+O>mjS}aVPPyV z;AXv67SD-88Y6e+4Brd6=hSsnpLCC_U95JUESyTOU8-51yfu~Kok%?8mAXcJk9jY; z=CtP1=Vt9}>+Y3V@6)i>;MEXUe~p;neQ|4GGkm*XJ87+Q-SX?iV(9A8m5C*}wl}u; zG561+{`7~D2UCFgfarj=fIJ=Dgs}Jui8~2Q$PvS*=-)*}2!eN+z4!#K$ESAYLwU1BIWjj+i_x(ZAvA7rD>-D)~7y zF>xg^7Mg(wOm%!k5Lc{{_PE#c?&M74e9ii0BD>FVKj`7@R2>V~Nn*Xue&bF!?Te7X z5N&`CQj(aqkU%`O>T7s&aP`U9{K|X>htFl*zYv)*Qre?wg>lJg;|X^0@d;=2JTJe^ zP&y4_8MagAc`9wzE<1n8&B$7OOp>RY@Up0DKbkY%9c~UaPxQqXFOFN@$|zaCzCGu) z;kCT?j7fp73d=pVP*ULopXG-B8AvL>aoY6pubi>roVmT(9YL^}fa;tuXs&v5Tl zjAHGSr?+pqMfzEsL+E;xu;PFsP|@2{V%*(jWW?vj!6x<7puwQNJCm(T7flb5Tcpp+ z)l&@`TNE+|{NPCXG-xww zlhm0$95TGI-?d-%%%q{}@m7!Ptm|U<4EvP(F?V+nm_FAqX)|Uo+-M*VdpN&obc-fC zv(O-?Y(`dI0`dLb$HWZ!KHAhpg`V>hD-kV_IK83VeY^bI&g;fHsu2~i^2)ENhc(aO zAFFb$B>XN+KYM+{^~SD`0ezY93!AOh+M}=2Fln*J=O9rY#f!X+9IlJl?X~6U9za6L zjnx{rg3j!ZtydGCei=!9mR9$we%g(6pt-JBm!j7+_WjM!t+x|CJ+mfD&Ycs^z7gLP z_NMkA)2p@k_mbb1BLmSizQO0{zA|YrYkyr`j(mHi@o-P&LvYfp1T&O*T|o4_m~YW) z^)tY$&O#(vmH*ULP?E1{QavLXI`JKxt$mu3EXKKOCGZ>ht%V?xB|47SO1*ZMPh zyeFx<{vyX=z7Kl;Q^Lim!+HcK6t-oy;k8xLD(8rcYO3^|fbq+I-?99wE@^vLU`Q!- zr)z(7U3CBTdQwqebsySFx3R*PX=l#`j`rA6WVUYEvK#nP%>U&J51jEIkFi#ZV!=rD<33P!pH4-|G-kLA$O#tX5%8Fx}=pHio%}-;ds=E(2fHI?k}@ z&-ePUalL8DJmYPBSz?An`jQOHxmb%O1DyN(j&-9}*F*O! z`h&*;?pJHT@j5EDZNfI`y{{Ufv{cuqn-j9e#*zxh#vondND>v{@&PcCVL%v!#@ha5 zgk*`rIziIE>E{pn_zRZ#4V!}@+G=X5C_LI0OyR1AU`vXdAI7r3wJ6+G4d;Y&Gjc)M zpnp(VH7JMTS%CAS7Ay(OqW;zAPG8*RmYXiGc7qBt z0*p6j)C<=R4e84jbz)sm>x?qbQ{)9cR;M;i(pP!ln2X;4vcf_F$KBD=g`m6+Zu;aW9HH~Hu;iLjz%)4d zQs!qWBUZF{x_|-I*dxIQ%}OSI%DDy`Wp){ZCZ27=w(QDf^RV0_dT^N&LYyBp0WjJV zN3|rm9kC;xy)yvQWh~tlGb*O{d(7c(csic3q0z(|woOU(_8DK({L&ebguS9to51j@ z&7SSIzy$JEgdc0wwf`(dm(jiz3KMfBC#X({hU@=1vce0y1arm&xb^k ztT$A`%?46@ky)A@bs91;PGbGBs7`7=>M1-2kQ|13RP08{A(;!Pq%j0`l=)%1tnJv@ zn0n)*F@aC4b!1O3GMz6!FyHgTU)x?hpt6=w3i}_ONjdqy2P0WH;&=PV{YHO`OIR$9 zfCrmf{+xS^Z@Z8uCnU=L*Jz}Nw#A@SaYV2=SV~gr$NEQ*mi-0&BmMGZh{ogG-E7c! zFr~>U9Y~qM)Noh=8cY5nrHg-oNR%5H1Izz}{;St1&_A=C4#t)oDwvW1Y$AO}iW|3Grm|I(6!k#qc0Oa5QJWMtsvo8S*E85wzU3IBv(|MEq) zBi~OywFqt~j1$@oL^*eDb#fuk;&IMsQSe!et?1w7gc#y*1TdvkKaK`sgz-XC^8PUg UqudBTDj@?$KtZQZYh2O%55N-IV*mgE literal 0 HcmV?d00001 diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Plus.imageset/Contents.json b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Plus.imageset/Contents.json new file mode 100644 index 000000000..406918a05 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Plus.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Plus.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Plus.imageset/Plus.pdf b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Plus.imageset/Plus.pdf new file mode 100644 index 0000000000000000000000000000000000000000..09bafbb4a0864ff65b6c5948a280b41e50d45877 GIT binary patch literal 4235 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 ajc4S&`gZ`Ih$ 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 literal 0 HcmV?d00001 diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Think.imageset/Contents.json b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Think.imageset/Contents.json new file mode 100644 index 000000000..2e3822aa8 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Think.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "icon-2.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Think.imageset/icon-2.pdf b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Think.imageset/icon-2.pdf new file mode 100644 index 0000000000000000000000000000000000000000..089429e05fc419074382f4abb18996f4b97262dd GIT binary patch literal 5008 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 Xb0-FMLHz85kf?wth?Q0T@x%WCWoy@J literal 0 HcmV?d00001 diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Tools.imageset/Contents.json b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Tools.imageset/Contents.json new file mode 100644 index 000000000..dd9e55cfc --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Tools.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "icon.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Tools.imageset/icon.pdf b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Tools.imageset/icon.pdf new file mode 100644 index 0000000000000000000000000000000000000000..107a7743c8b3274f48f00eef71edcba216cbe8e5 GIT binary patch literal 4437 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 V#|GR3{o^Dgzz`^aheugkjw literal 0 HcmV?d00001 diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Upload.imageset/Contents.json b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Upload.imageset/Contents.json new file mode 100644 index 000000000..859f0a5b1 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Upload.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Upload.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Upload.imageset/Upload.pdf b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Upload.imageset/Upload.pdf new file mode 100644 index 0000000000000000000000000000000000000000..b0b940d7b6d15ead3313e5b45e9c135a28c18050 GIT binary patch literal 4059 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 I(@^XG09sfARsaA1 literal 0 HcmV?d00001 diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Web.imageset/Contents.json b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Web.imageset/Contents.json new file mode 100644 index 000000000..f2124a908 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Web.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "icon-1.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Web.imageset/icon-1.pdf b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/Icons.xcassets/Web.imageset/icon-1.pdf new file mode 100644 index 0000000000000000000000000000000000000000..fa4b9ab6a52921ae9038ed2362c80cd8f90da912 GIT binary patch literal 4275 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 V1GE?6$4ST`k@64`5lthl{{g?Jb8i3u literal 0 HcmV?d00001 diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/en.lproj/Localizable.strings b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/en.lproj/Localizable.strings deleted file mode 100644 index 88701da42..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/en.lproj/Localizable.strings +++ /dev/null @@ -1,17 +0,0 @@ -/* - Localizable.strings - Intelligents - - Created by 秋星桥 on 2024/11/18. - -*/ - -"Chat with AI" = "Chat with AI"; -"AFFiNE AI" = "AFFiNE AI"; -"Translate" = "Translate"; -"Summary" = "Summary"; -"Summarize this article for me..." = "Summarize this article for me..."; -"System" = "System"; -"AFFiNE AI" = "AFFiNE AI"; -"You" = "You"; -"Error" = "Error"; diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/main.metal b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/main.metal new file mode 100644 index 000000000..d79a65f82 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/main.metal @@ -0,0 +1,87 @@ +#include + +using namespace metal; + +namespace ParticleTransitionSystem { + + struct TrollParticle { + float2 position; + float2 velocity; + float life; + float duration; + }; + + struct TrollVertex { + float4 position [[position]]; + float2 uv; + float opacity; + }; + +} + +// 顶点着色器 负责将粒子数据转换为顶点数据 +vertex ParticleTransitionSystem::TrollVertex PTS_ParticleVertex(const device ParticleTransitionSystem::TrollVertex *vertices [[buffer(0)]], + const device float2 &resolution [[buffer(1)]], + const device ParticleTransitionSystem::TrollParticle *particles [[buffer(2)]], + const device float2 &targetFrameSize [[buffer(3)]], + const device float &stepSize [[buffer(4)]], + unsigned int vid [[vertex_id]], + unsigned int particleId [[instance_id]]) { + ParticleTransitionSystem::TrollVertex v = vertices[vid]; + ParticleTransitionSystem::TrollParticle p = particles[particleId]; + + int particlesPerRow = int(targetFrameSize.x / stepSize); + int row = particleId / particlesPerRow; + int col = particleId % particlesPerRow; + + float2 originalPos = float2(col * stepSize + stepSize / 2.0, row * stepSize + stepSize / 2.0); + float2 currentPos = p.position; + + // 计算目标帧在屏幕中的居中偏移 + float2 offset = (resolution - targetFrameSize) / 2.0; + + // 设置UV坐标用于纹理采样 + v.uv.x = originalPos.x / targetFrameSize.x; + v.uv.y = originalPos.y / targetFrameSize.y; + + // 计算最终的屏幕位置 + float particleSize = stepSize; + float2 worldPos = v.position.xy * particleSize + currentPos + offset; + + // 转换到NDC坐标系 (-1到1) + v.position.x = (worldPos.x / resolution.x) * 2.0 - 1.0; + v.position.y = 1.0 - (worldPos.y / resolution.y) * 2.0; + + // 逐渐消失 + v.opacity = p.life / p.duration; + + return v; +} + +// 片段着色器 负责将顶点数据转换为像素颜色 +fragment float4 PTS_ParticleFragment(ParticleTransitionSystem::TrollVertex in [[stage_in]], + const texture2d texture [[texture(0)]], + const sampler textureSampler [[sampler(0)]]) { + constexpr sampler samplr; + float4 color = texture.sample(samplr, in.uv); + float a = color.a * in.opacity; // apply texture alpha + color *= in.opacity; // apply opacity from vertex shader aka pre-multiplied alpha + color.a = a; + return color; +} + +// 计算粒子位置和速度的计算着色器 负责更新粒子的位置和速度 +kernel void PTS_UpdateParticles(device ParticleTransitionSystem::TrollParticle *particles [[buffer(0)]], + unsigned int index [[thread_position_in_grid]]) { + if (particles[index].life >= 0) { + particles[index].position += particles[index].velocity; + + // 模拟空气阻力,降低速度 x, y 分量 + particles[index].velocity.x *= 0.99; + particles[index].velocity.y *= 0.99; + + // 模拟重力影响,增加 y 分量 + particles[index].velocity.y += 0.1; + } + particles[index].life -= 1.0; +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/zh-Hans.lproj/Localizable.strings b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/zh-Hans.lproj/Localizable.strings deleted file mode 100644 index d89c4a6ce..000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Resources/zh-Hans.lproj/Localizable.strings +++ /dev/null @@ -1,18 +0,0 @@ -/* - Localizable.strings - Intelligents - - Created by 秋星桥 on 2024/11/18. - -*/ - -"Chat with AI" = "与 AI 聊天"; -"AFFiNE AI" = "AFFiNE 人工智能"; -"Translate" = "翻译"; -"Summary" = "总结"; -"Summarize this article for me..." = "请为我总结这份文档..."; -"System" = "系统"; -"AFFiNE AI" = "AFFiNE AI"; -"You" = "你"; -"Error" = "错误"; -"OK" = "确定"; diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/.gitignore b/packages/frontend/apps/ios/App/Packages/MarkdownView/.gitignore deleted file mode 100644 index 0023a5340..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -.DS_Store -/.build -/Packages -xcuserdata/ -DerivedData/ -.swiftpm/configuration/registries.json -.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata -.netrc diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/.swiftpm/xcode/xcshareddata/xcschemes/FlowMarkdownView.xcscheme b/packages/frontend/apps/ios/App/Packages/MarkdownView/.swiftpm/xcode/xcshareddata/xcschemes/FlowMarkdownView.xcscheme deleted file mode 100644 index de651b9d8..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/.swiftpm/xcode/xcshareddata/xcschemes/FlowMarkdownView.xcscheme +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Example/Example.xcodeproj/project.pbxproj b/packages/frontend/apps/ios/App/Packages/MarkdownView/Example/Example.xcodeproj/project.pbxproj deleted file mode 100644 index 65782460a..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Example/Example.xcodeproj/project.pbxproj +++ /dev/null @@ -1,411 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 77; - objects = { - -/* Begin PBXBuildFile section */ - 50219C662D3E2304006CB93C /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50219C652D3E2304006CB93C /* App.swift */; }; - 507C16722D2719F100B478D2 /* MarkdownView in Frameworks */ = {isa = PBXBuildFile; productRef = 507C16712D2719F100B478D2 /* MarkdownView */; }; - 5084C6742D281A41007310F0 /* LookinServer in Frameworks */ = {isa = PBXBuildFile; productRef = 5084C6732D281A41007310F0 /* LookinServer */; }; -/* End PBXBuildFile section */ - -/* Begin PBXFileReference section */ - 50219C652D3E2304006CB93C /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; - 505E99EA2D26D8380014A6D3 /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 505E99E72D26D8380014A6D3 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 507C16722D2719F100B478D2 /* MarkdownView in Frameworks */, - 5084C6742D281A41007310F0 /* LookinServer in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 5015F6D32D26DCFB005FA7D2 /* Frameworks */ = { - isa = PBXGroup; - children = ( - ); - name = Frameworks; - sourceTree = ""; - }; - 50219C642D3E22FB006CB93C /* Example */ = { - isa = PBXGroup; - children = ( - 50219C652D3E2304006CB93C /* App.swift */, - ); - path = Example; - sourceTree = ""; - }; - 505E99E12D26D8380014A6D3 = { - isa = PBXGroup; - children = ( - 50219C642D3E22FB006CB93C /* Example */, - 5015F6D32D26DCFB005FA7D2 /* Frameworks */, - 505E99EB2D26D8380014A6D3 /* Products */, - ); - sourceTree = ""; - }; - 505E99EB2D26D8380014A6D3 /* Products */ = { - isa = PBXGroup; - children = ( - 505E99EA2D26D8380014A6D3 /* Example.app */, - ); - name = Products; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 505E99E92D26D8380014A6D3 /* Example */ = { - isa = PBXNativeTarget; - buildConfigurationList = 505E99F82D26D8390014A6D3 /* Build configuration list for PBXNativeTarget "Example" */; - buildPhases = ( - 5015F6CD2D26DB1B005FA7D2 /* Format Source */, - 505E99E62D26D8380014A6D3 /* Sources */, - 505E99E72D26D8380014A6D3 /* Frameworks */, - 505E99E82D26D8380014A6D3 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = Example; - packageProductDependencies = ( - 507C16712D2719F100B478D2 /* MarkdownView */, - 5084C6732D281A41007310F0 /* LookinServer */, - ); - productName = Example; - productReference = 505E99EA2D26D8380014A6D3 /* Example.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 505E99E22D26D8380014A6D3 /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1620; - LastUpgradeCheck = 1620; - TargetAttributes = { - 505E99E92D26D8380014A6D3 = { - CreatedOnToolsVersion = 16.2; - LastSwiftMigration = 1620; - }; - }; - }; - buildConfigurationList = 505E99E52D26D8380014A6D3 /* Build configuration list for PBXProject "Example" */; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 505E99E12D26D8380014A6D3; - minimizedProjectReferenceProxies = 1; - packageReferences = ( - 5084C6722D281A38007310F0 /* XCRemoteSwiftPackageReference "LookinServer" */, - ); - preferredProjectObjectVersion = 77; - productRefGroup = 505E99EB2D26D8380014A6D3 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 505E99E92D26D8380014A6D3 /* Example */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 505E99E82D26D8380014A6D3 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 5015F6CD2D26DB1B005FA7D2 /* Format Source */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Format Source"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/opt/homebrew/bin/swiftformat . --swiftversion 6.0\n\n"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 505E99E62D26D8380014A6D3 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 50219C662D3E2304006CB93C /* App.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - 505E99F62D26D8390014A6D3 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - ENABLE_USER_SCRIPT_SANDBOXING = YES; - GCC_C_LANGUAGE_STANDARD = gnu17; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 18.2; - LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - }; - name = Debug; - }; - 505E99F72D26D8390014A6D3 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = YES; - GCC_C_LANGUAGE_STANDARD = gnu17; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 18.2; - LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - SDKROOT = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 505E99F92D26D8390014A6D3 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CLANG_ENABLE_MODULES = YES; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 964G86XT2P; - ENABLE_PREVIEWS = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; - INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchScreen_Generation = YES; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = wiki.qaq.Example; - PRODUCT_NAME = "$(TARGET_NAME)"; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; - SUPPORTS_MACCATALYST = YES; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; - SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 505E99FA2D26D8390014A6D3 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CLANG_ENABLE_MODULES = YES; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 964G86XT2P; - ENABLE_PREVIEWS = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; - INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchScreen_Generation = YES; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = wiki.qaq.Example; - PRODUCT_NAME = "$(TARGET_NAME)"; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; - SUPPORTS_MACCATALYST = YES; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; - SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 505E99E52D26D8380014A6D3 /* Build configuration list for PBXProject "Example" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 505E99F62D26D8390014A6D3 /* Debug */, - 505E99F72D26D8390014A6D3 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 505E99F82D26D8390014A6D3 /* Build configuration list for PBXNativeTarget "Example" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 505E99F92D26D8390014A6D3 /* Debug */, - 505E99FA2D26D8390014A6D3 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - -/* Begin XCRemoteSwiftPackageReference section */ - 5084C6722D281A38007310F0 /* XCRemoteSwiftPackageReference "LookinServer" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/QMUI/LookinServer/"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 1.2.8; - }; - }; -/* End XCRemoteSwiftPackageReference section */ - -/* Begin XCSwiftPackageProductDependency section */ - 507C16712D2719F100B478D2 /* MarkdownView */ = { - isa = XCSwiftPackageProductDependency; - productName = MarkdownView; - }; - 5084C6732D281A41007310F0 /* LookinServer */ = { - isa = XCSwiftPackageProductDependency; - package = 5084C6722D281A38007310F0 /* XCRemoteSwiftPackageReference "LookinServer" */; - productName = LookinServer; - }; -/* End XCSwiftPackageProductDependency section */ - }; - rootObject = 505E99E22D26D8380014A6D3 /* Project object */; -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/frontend/apps/ios/App/Packages/MarkdownView/Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a62..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Example/Example.xcworkspace/contents.xcworkspacedata b/packages/frontend/apps/ios/App/Packages/MarkdownView/Example/Example.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 684b9e8df..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Example/Example.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Example/Example/App.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Example/Example/App.swift deleted file mode 100644 index c972267ba..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Example/Example/App.swift +++ /dev/null @@ -1,180 +0,0 @@ -// -// App.swift -// Example -// -// Created by 秋星桥 on 1/20/25. -// - -import SwiftUI - -@main -struct TheApp: App { - var body: some Scene { - WindowGroup { - NavigationView { - Content() - .navigationTitle("MarkdownView") - .navigationBarTitleDisplayMode(.inline) - } - .navigationViewStyle(.stack) - } - } -} - -import MarkdownParser -import MarkdownView - -class ContentController: UIViewController { - let document = MarkdownParser().feed(testDocument) - let scrollView = UIScrollView() - let markdownView = MarkdownView(theme: .default) - - override func viewDidLoad() { - super.viewDidLoad() - view.addSubview(scrollView) - scrollView.addSubview(markdownView) - } - - override func viewWillLayoutSubviews() { - super.viewWillLayoutSubviews() - scrollView.frame = view.bounds - let width = view.bounds.width - 32 - let manifest = document.map { - let manifest = $0.manifest(theme: markdownView.theme) - manifest.setLayoutWidth(width) - manifest.layoutIfNeeded() - return manifest - } - markdownView.updateContentViews(manifest) - markdownView.frame = .init( - x: 16, - y: 16, - width: width, - height: markdownView.height - ) - scrollView.contentSize = .init( - width: width, - height: markdownView.height + 100 - ) - } -} - -struct Content: UIViewControllerRepresentable { - func makeUIViewController(context _: Context) -> ContentController { - ContentController() - } - - func updateUIViewController(_: ContentController, context _: Context) {} -} - -let testDocument = ###""" -# Markdown 测试文稿 - -这是一篇用于测试渲染引擎性能的 Markdown 文档,包含多种格式和元素。以下是不同 Markdown 语法的示例: - -## 标题 - -### 三级标题 - -#### 四级标题 - -##### 五级标题 - -###### 六级标题 - -## 段落与文本格式 - -这是一个普通段落。**这是加粗的文字**,*这是斜体的文字*,***这是加粗且斜体的文字***。~~这是删除线~~。 - -这是`行内代码`的示例。 - -## 列表 - -### 无序列表 - -- 项目 1 -- 项目 2 - - 子项目 2.1 - - 子项目 2.2 -- 项目 3 - -### 有序列表 - -1. 第一项 -2. 第二项 - 1. 子项 2.1 - 2. 子项 2.2 -3. 第三项 - -## 引用 - -> 这是一个引用块。引用块可以包含多行文字,甚至可以包含其他 Markdown 元素,比如**加粗**或`代码`。 - -## 代码块 - -```python -def hello_world(): - print("Hello, World!") -``` - -```javascript -function helloWorld() { - console.log("Hello, World!"); -} -``` - -## 表格 - -| 序号 | 名称 | 描述 | -| ---- | ---------- | ------------- | -| 1 | 项目 A | 这是项目 A | -| 2 | 项目 B | 这是项目 B | -| 3 | 项目 C | 这是项目 C | - -## 链接与图片 - -这是一个[短链接](https://example.com)的示例。 - -![图片描述](https://via.placeholder.com/150) - -## HTML 嵌入 - -

这是一个红色的段落,使用 HTML 标签实现。

- -这是一个短链接 - -## 分隔线 - ---- - -## 脚注 - -这是一个脚注的示例[^1]。 - -[^1]: 这是脚注的内容。 - -## 内嵌 HTML - -
- 这是一个带有边框的 HTML 块。 -
- -## 数学公式(如果支持) - -这是一个行内公式:$E = mc^2$。 - -这是一个块级公式: -$$ -\int_{a}^{b} x^2 dx -$$ - -## 任务列表 - -- [x] 完成任务 1 -- [ ] 完成任务 2 -- [ ] 完成任务 3 - -## 结束语 - -这篇文档包含了多种 Markdown 格式和 HTML 元素,适合用于测试渲染引擎的性能和兼容性。希望它能帮助你完成测试! -"""### diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Package.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Package.swift deleted file mode 100644 index bb620e0c3..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Package.swift +++ /dev/null @@ -1,29 +0,0 @@ -// swift-tools-version: 5.9 -// The swift-tools-version declares the minimum version of Swift required to build this package. - -import PackageDescription - -let package = Package( - name: "MarkdownView", - platforms: [ - .iOS(.v14), - .macCatalyst(.v14), - ], - products: [ - .library(name: "MarkdownView", targets: ["MarkdownView"]), - ], - dependencies: [ - .package(url: "https://github.com/JohnSundell/Splash", from: "0.16.0"), - .package(url: "https://github.com/swiftlang/swift-cmark", from: "0.6.0"), - ], - targets: [ - .target(name: "MarkdownView", dependencies: [ - "MarkdownParser", - "Splash", - ]), - .target(name: "MarkdownParser", dependencies: [ - .product(name: "cmark-gfm", package: "swift-cmark"), - .product(name: "cmark-gfm-extensions", package: "swift-cmark"), - ]), - ] -) diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/BlockNode/BlockNode+Rewrite.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/BlockNode/BlockNode+Rewrite.swift deleted file mode 100644 index 03990f4a4..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/BlockNode/BlockNode+Rewrite.swift +++ /dev/null @@ -1,104 +0,0 @@ -import Foundation - -extension Sequence { - func rewrite(_ r: (BlockNode) throws -> [BlockNode]) rethrows -> [BlockNode] { - try flatMap { try $0.rewrite(r) } - } - - func rewrite(_ r: (InlineNode) throws -> [InlineNode]) rethrows -> [BlockNode] { - try flatMap { try $0.rewrite(r) } - } -} - -extension BlockNode { - func rewrite(_ r: (BlockNode) throws -> [BlockNode]) rethrows -> [BlockNode] { - switch self { - case let .blockquote(children): - try r(.blockquote(children: children.rewrite(r))) - case let .bulletedList(isTight, items): - try r( - .bulletedList( - isTight: isTight, - items: items.map { - try RawListItem(children: $0.children.rewrite(r)) - } - ) - ) - case let .numberedList(isTight, start, items): - try r( - .numberedList( - isTight: isTight, - start: start, - items: items.map { - try RawListItem(children: $0.children.rewrite(r)) - } - ) - ) - case let .taskList(isTight, items): - try r( - .taskList( - isTight: isTight, - items: items.map { - try RawTaskListItem(isCompleted: $0.isCompleted, children: $0.children.rewrite(r)) - } - ) - ) - default: - try r(self) - } - } - - func rewrite(_ r: (InlineNode) throws -> [InlineNode]) rethrows -> [BlockNode] { - switch self { - case let .blockquote(children): - try [.blockquote(children: children.rewrite(r))] - case let .bulletedList(isTight, items): - try [ - .bulletedList( - isTight: isTight, - items: items.map { - try RawListItem(children: $0.children.rewrite(r)) - } - ), - ] - case let .numberedList(isTight, start, items): - try [ - .numberedList( - isTight: isTight, - start: start, - items: items.map { - try RawListItem(children: $0.children.rewrite(r)) - } - ), - ] - case let .taskList(isTight, items): - try [ - .taskList( - isTight: isTight, - items: items.map { - try RawTaskListItem(isCompleted: $0.isCompleted, children: $0.children.rewrite(r)) - } - ), - ] - case let .paragraph(content): - try [.paragraph(content: content.rewrite(r))] - case let .heading(level, content): - try [.heading(level: level, content: content.rewrite(r))] - case let .table(columnAlignments, rows): - try [ - .table( - columnAlignments: columnAlignments, - rows: rows.map { - try RawTableRow( - cells: $0.cells.map { - try RawTableCell(content: $0.content.rewrite(r)) - } - ) - } - ), - ] - default: - [self] - } - } -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/BlockNode/BlockNode.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/BlockNode/BlockNode.swift deleted file mode 100644 index 1115c3c85..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/BlockNode/BlockNode.swift +++ /dev/null @@ -1,85 +0,0 @@ -import Foundation - -public enum BlockNode: Hashable, Equatable, Codable { - case blockquote(children: [BlockNode]) - case bulletedList(isTight: Bool, items: [RawListItem]) - case numberedList(isTight: Bool, start: Int, items: [RawListItem]) - case taskList(isTight: Bool, items: [RawTaskListItem]) - case codeBlock(fenceInfo: String?, content: String) -// case htmlBlock(content: String) - case paragraph(content: [InlineNode]) - case heading(level: Int, content: [InlineNode]) - case table(columnAlignments: [RawTableColumnAlignment], rows: [RawTableRow]) - case thematicBreak -// -// public var typeIdentifier: String { -// switch self { -// case .blockquote: -// "blockquote" -// case .bulletedList: -// "bulletedList" -// case .numberedList: -// "numberedList" -// case .taskList: -// "taskList" -// case .codeBlock: -// "codeBlock" -// case .htmlBlock: -// "htmlBlock" -// case .paragraph: -// "paragraph" -// case .heading: -// "heading" -// case .table: -// "table" -// case .thematicBreak: -// "thematicBreak" -// } -// } -} - -extension BlockNode { - var children: [BlockNode] { - switch self { - case let .blockquote(children): - children - case let .bulletedList(_, items): - items.map(\.children).flatMap(\.self) - case let .numberedList(_, _, items): - items.map(\.children).flatMap(\.self) - case let .taskList(_, items): - items.map(\.children).flatMap(\.self) - default: - [] - } - } - - var isParagraph: Bool { - guard case .paragraph = self else { return false } - return true - } -} - -public struct RawListItem: Hashable, Equatable, Codable { - public let children: [BlockNode] -} - -public struct RawTaskListItem: Hashable, Equatable, Codable { - public let isCompleted: Bool - public let children: [BlockNode] -} - -public enum RawTableColumnAlignment: Character, Equatable, Codable { - case none = "\0" - case left = "l" - case center = "c" - case right = "r" -} - -public struct RawTableRow: Hashable, Equatable, Codable { - public let cells: [RawTableCell] -} - -public struct RawTableCell: Hashable, Equatable, Codable { - public let content: [InlineNode] -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/InlineNode/InlineNode+Collect.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/InlineNode/InlineNode+Collect.swift deleted file mode 100644 index 07d6fa484..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/InlineNode/InlineNode+Collect.swift +++ /dev/null @@ -1,13 +0,0 @@ -import Foundation - -extension Sequence { - func collect(_ c: (InlineNode) throws -> [Result]) rethrows -> [Result] { - try flatMap { try $0.collect(c) } - } -} - -extension InlineNode { - func collect(_ c: (InlineNode) throws -> [Result]) rethrows -> [Result] { - try children.collect(c) + c(self) - } -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/InlineNode/InlineNode+Rewrite.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/InlineNode/InlineNode+Rewrite.swift deleted file mode 100644 index 6b7589798..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/InlineNode/InlineNode+Rewrite.swift +++ /dev/null @@ -1,15 +0,0 @@ -import Foundation - -extension Sequence { - func rewrite(_ r: (InlineNode) throws -> [InlineNode]) rethrows -> [InlineNode] { - try flatMap { try $0.rewrite(r) } - } -} - -extension InlineNode { - func rewrite(_ r: (InlineNode) throws -> [InlineNode]) rethrows -> [InlineNode] { - var inline = self - inline.children = try children.rewrite(r) - return try r(inline) - } -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/InlineNode/InlineNode.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/InlineNode/InlineNode.swift deleted file mode 100644 index 7352ae564..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/InlineNode/InlineNode.swift +++ /dev/null @@ -1,52 +0,0 @@ -import Foundation - -public enum InlineNode: Hashable, Sendable, Equatable, Codable { - case text(String) - case softBreak - case lineBreak - case code(String) - case html(String) - case emphasis(children: [InlineNode]) - case strong(children: [InlineNode]) - case strikethrough(children: [InlineNode]) - case link(destination: String, children: [InlineNode]) - case image(source: String, children: [InlineNode]) -} - -extension InlineNode { - var children: [InlineNode] { - get { - switch self { - case let .emphasis(children): - children - case let .strong(children): - children - case let .strikethrough(children): - children - case let .link(_, children): - children - case let .image(_, children): - children - default: - [] - } - } - - set { - switch self { - case .emphasis: - self = .emphasis(children: newValue) - case .strong: - self = .strong(children: newValue) - case .strikethrough: - self = .strikethrough(children: newValue) - case let .link(destination, _): - self = .link(destination: destination, children: newValue) - case let .image(source, _): - self = .image(source: source, children: newValue) - default: - break - } - } - } -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/MarkdownParser/MarkdownParser+Delegate.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/MarkdownParser/MarkdownParser+Delegate.swift deleted file mode 100644 index 89cb30a86..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/MarkdownParser/MarkdownParser+Delegate.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// MarkdownParser+Delegate.swift -// FlowMarkdownView -// -// Created by 秋星桥 on 2025/1/3. -// - -import Foundation - -public extension MarkdownParser { - protocol Delegate: AnyObject { - func updateBlockNodes(_ blockNodes: [BlockNode]) - } -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/MarkdownParser/MarkdownParser+Node.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/MarkdownParser/MarkdownParser+Node.swift deleted file mode 100644 index fe5eedde2..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/MarkdownParser/MarkdownParser+Node.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// MarkdownParser+Node.swift -// FlowMarkdownView -// -// Created by 秋星桥 on 2025/1/3. -// - -import cmark_gfm -import cmark_gfm_extensions -import Foundation - -extension MarkdownParser { - func dumpBlocks(root: UnsafeNode?) { - guard let root else { - assertionFailure() - return - } - assert(root.pointee.type == CMARK_NODE_DOCUMENT.rawValue) - - blocks = root.children.compactMap(BlockNode.init(unsafeNode:)) - } -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/MarkdownParser/MarkdownParser+Setup.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/MarkdownParser/MarkdownParser+Setup.swift deleted file mode 100644 index adf584d21..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/MarkdownParser/MarkdownParser+Setup.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// MarkdownParser+Setup.swift -// FlowMarkdownView -// -// Created by 秋星桥 on 2025/1/3. -// - -import cmark_gfm -import cmark_gfm_extensions - -import Foundation - -extension MarkdownParser { - func setupExtensions(parser: UnsafeMutablePointer) { - cmark_gfm_core_extensions_ensure_registered() - let extensionNames = ["autolink", "strikethrough", "tagfilter", "tasklist", "table"] - for extensionName in extensionNames { - guard let syntaxExtension = cmark_find_syntax_extension(extensionName) else { - assertionFailure() - continue - } - cmark_parser_attach_syntax_extension(parser, syntaxExtension) - } - } -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/MarkdownParser/MarkdownParser.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/MarkdownParser/MarkdownParser.swift deleted file mode 100644 index 08de046f8..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/MarkdownParser/MarkdownParser.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// MarkdownParser.swift -// FlowMarkdownView -// -// Created by 秋星桥 on 2025/1/2. -// - -import cmark_gfm -import cmark_gfm_extensions -import Foundation - -public class MarkdownParser { - public internal(set) var blocks: [BlockNode] = [] { - didSet { delegate?.updateBlockNodes(blocks) } - } - - public weak var delegate: Delegate? - var currentDoc = String() - - public init() {} - - @discardableResult - public func feed(_ text: String) -> [BlockNode] { - currentDoc += text - let parser = cmark_parser_new(CMARK_OPT_DEFAULT)! - defer { cmark_parser_free(parser) } - setupExtensions(parser: parser) - cmark_parser_feed(parser, currentDoc, currentDoc.utf8.count) - let node = cmark_parser_finish(parser) - defer { cmark_node_free(node) } - dumpBlocks(root: node) - return blocks - } -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/Utils/Converters.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/Utils/Converters.swift deleted file mode 100644 index 56cdb687d..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/Utils/Converters.swift +++ /dev/null @@ -1,289 +0,0 @@ -import cmark_gfm -import cmark_gfm_extensions -import Foundation - -typealias UnsafeNode = UnsafeMutablePointer - -extension BlockNode { - init?(unsafeNode: UnsafeNode) { - switch unsafeNode.nodeType { - case .blockquote: - self = .blockquote(children: unsafeNode.children.compactMap(BlockNode.init(unsafeNode:))) - case .list: - if unsafeNode.children.contains(where: \.isTaskListItem) { - self = .taskList( - isTight: unsafeNode.isTightList, - items: unsafeNode.children.map(RawTaskListItem.init(unsafeNode:)) - ) - } else { - switch unsafeNode.listType { - case CMARK_BULLET_LIST: - self = .bulletedList( - isTight: unsafeNode.isTightList, - items: unsafeNode.children.map(RawListItem.init(unsafeNode:)) - ) - case CMARK_ORDERED_LIST: - self = .numberedList( - isTight: unsafeNode.isTightList, - start: unsafeNode.listStart, - items: unsafeNode.children.map(RawListItem.init(unsafeNode:)) - ) - default: - fatalError("cmark reported a list node without a list type.") - } - } - case .codeBlock: - self = .codeBlock(fenceInfo: unsafeNode.fenceInfo, content: unsafeNode.literal ?? "") - case .htmlBlock: -// self = .htmlBlock(content: unsafeNode.literal ?? "") - self = .codeBlock(fenceInfo: "html", content: unsafeNode.literal ?? "") - case .paragraph: - self = .paragraph(content: unsafeNode.children.compactMap(InlineNode.init(unsafeNode:))) - case .heading: - self = .heading( - level: unsafeNode.headingLevel, - content: unsafeNode.children.compactMap(InlineNode.init(unsafeNode:)) - ) - case .table: - self = .table( - columnAlignments: unsafeNode.tableAlignments, - rows: unsafeNode.children.map(RawTableRow.init(unsafeNode:)) - ) - case .thematicBreak: - self = .thematicBreak - default: - assertionFailure("Unhandled node type '\(unsafeNode.nodeType)' in BlockNode.") - return nil - } - } -} - -extension RawListItem { - init(unsafeNode: UnsafeNode) { - guard unsafeNode.nodeType == .item else { - fatalError("Expected a list item but got a '\(unsafeNode.nodeType)' instead.") - } - self.init(children: unsafeNode.children.compactMap(BlockNode.init(unsafeNode:))) - } -} - -extension RawTaskListItem { - init(unsafeNode: UnsafeNode) { - guard unsafeNode.nodeType == .taskListItem || unsafeNode.nodeType == .item else { - fatalError("Expected a list item but got a '\(unsafeNode.nodeType)' instead.") - } - self.init( - isCompleted: unsafeNode.isTaskListItemChecked, - children: unsafeNode.children.compactMap(BlockNode.init(unsafeNode:)) - ) - } -} - -extension RawTableRow { - init(unsafeNode: UnsafeNode) { - guard unsafeNode.nodeType == .tableRow || unsafeNode.nodeType == .tableHead else { - fatalError("Expected a table row but got a '\(unsafeNode.nodeType)' instead.") - } - self.init(cells: unsafeNode.children.map(RawTableCell.init(unsafeNode:))) - } -} - -extension RawTableCell { - init(unsafeNode: UnsafeNode) { - guard unsafeNode.nodeType == .tableCell else { - fatalError("Expected a table cell but got a '\(unsafeNode.nodeType)' instead.") - } - self.init(content: unsafeNode.children.compactMap(InlineNode.init(unsafeNode:))) - } -} - -extension InlineNode { - init?(unsafeNode: UnsafeNode) { - switch unsafeNode.nodeType { - case .text: - self = .text(unsafeNode.literal ?? "") - case .softBreak: - self = .softBreak - case .lineBreak: - self = .lineBreak - case .code: - self = .code(unsafeNode.literal ?? "") - case .html: - self = .html(unsafeNode.literal ?? "") - case .emphasis: - self = .emphasis(children: unsafeNode.children.compactMap(InlineNode.init(unsafeNode:))) - case .strong: - self = .strong(children: unsafeNode.children.compactMap(InlineNode.init(unsafeNode:))) - case .strikethrough: - self = .strikethrough(children: unsafeNode.children.compactMap(InlineNode.init(unsafeNode:))) - case .link: - self = .link( - destination: unsafeNode.url ?? "", - children: unsafeNode.children.compactMap(InlineNode.init(unsafeNode:)) - ) - case .image: - self = .image( - source: unsafeNode.url ?? "", - children: unsafeNode.children.compactMap(InlineNode.init(unsafeNode:)) - ) - default: - assertionFailure("Unhandled node type '\(unsafeNode.nodeType)' in InlineNode.") - return nil - } - } -} - -extension UnsafeNode { - var nodeType: NodeType { - let typeString = String(cString: cmark_node_get_type_string(self)) - guard let nodeType = NodeType(rawValue: typeString) else { - fatalError("Unknown node type '\(typeString)' found.") - } - return nodeType - } - - var children: UnsafeNodeSequence { - .init(cmark_node_first_child(self)) - } - - var literal: String? { - cmark_node_get_literal(self).map(String.init(cString:)) - } - - var url: String? { - cmark_node_get_url(self).map(String.init(cString:)) - } - - var isTaskListItem: Bool { - nodeType == .taskListItem - } - - var listType: cmark_list_type { - cmark_node_get_list_type(self) - } - - var listStart: Int { - Int(cmark_node_get_list_start(self)) - } - - var isTaskListItemChecked: Bool { - cmark_gfm_extensions_get_tasklist_item_checked(self) - } - - var isTightList: Bool { - cmark_node_get_list_tight(self) != 0 - } - - var fenceInfo: String? { - cmark_node_get_fence_info(self).map(String.init(cString:)) - } - - var headingLevel: Int { - Int(cmark_node_get_heading_level(self)) - } - - var tableColumns: Int { - Int(cmark_gfm_extensions_get_table_columns(self)) - } - - var tableAlignments: [RawTableColumnAlignment] { - (0 ..< tableColumns).map { column in - let ascii = cmark_gfm_extensions_get_table_alignments(self)[column] - let scalar = UnicodeScalar(ascii) - let character = Character(scalar) - return .init(rawValue: character) ?? .none - } - } -} - -enum NodeType: String { - case document - case blockquote = "block_quote" - case list - case item - case codeBlock = "code_block" - case htmlBlock = "html_block" - case customBlock = "custom_block" - case paragraph - case heading - case thematicBreak = "thematic_break" - case text - case softBreak = "softbreak" - case lineBreak = "linebreak" - case code - case html = "html_inline" - case customInline = "custom_inline" - case emphasis = "emph" - case strong - case link - case image - case inlineAttributes = "attribute" - case none = "NONE" - case unknown = "" - - // Extensions - - case strikethrough - case table - case tableHead = "table_header" - case tableRow = "table_row" - case tableCell = "table_cell" - case taskListItem = "tasklist" -} - -struct UnsafeNodeSequence: Sequence { - struct Iterator: IteratorProtocol { - var node: UnsafeNode? - - init(_ node: UnsafeNode?) { - self.node = node - } - - mutating func next() -> UnsafeNode? { - guard let node else { return nil } - defer { self.node = cmark_node_next(node) } - return node - } - } - - let node: UnsafeNode? - - init(_ node: UnsafeNode?) { - self.node = node - } - - func makeIterator() -> Iterator { - .init(node) - } -} - -// Extension node types are not exported in `cmark_gfm_extensions`, -// so we need to look for them in the symbol table -struct ExtensionNodeTypes { - let CMARK_NODE_TABLE: cmark_node_type - let CMARK_NODE_TABLE_ROW: cmark_node_type - let CMARK_NODE_TABLE_CELL: cmark_node_type - let CMARK_NODE_STRIKETHROUGH: cmark_node_type - - static let shared = ExtensionNodeTypes() - - init() { - func findNodeType(_ name: String, in handle: UnsafeMutableRawPointer!) -> cmark_node_type? { - guard let symbol = dlsym(handle, name) else { - return nil - } - return symbol.assumingMemoryBound(to: cmark_node_type.self).pointee - } - - let handle = dlopen(nil, RTLD_LAZY) - - CMARK_NODE_TABLE = findNodeType("CMARK_NODE_TABLE", in: handle) ?? CMARK_NODE_NONE - CMARK_NODE_TABLE_ROW = findNodeType("CMARK_NODE_TABLE_ROW", in: handle) ?? CMARK_NODE_NONE - CMARK_NODE_TABLE_CELL = - findNodeType("CMARK_NODE_TABLE_CELL", in: handle) ?? CMARK_NODE_NONE - CMARK_NODE_STRIKETHROUGH = - findNodeType("CMARK_NODE_STRIKETHROUGH", in: handle) ?? CMARK_NODE_NONE - - dlclose(handle) - } -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/Utils/Ext+Array.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/Utils/Ext+Array.swift deleted file mode 100644 index ff19cb197..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownParser/Utils/Ext+Array.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// Ext+Array.swift -// MarkdownView -// -// Created by 秋星桥 on 2025/1/3. -// - -import Foundation - -extension Array { - subscript(safe index: Int) -> Element? { - guard index >= 0, index < count else { return nil } - return self[index] - } -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/BlockquoteView.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/BlockquoteView.swift deleted file mode 100644 index 4ee0be808..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/BlockquoteView.swift +++ /dev/null @@ -1,111 +0,0 @@ -// -// BlockquoteView.swift -// MarkdownView -// -// Created by 秋星桥 on 2025/1/3. -// - -import MarkdownParser -import UIKit - -class BlockquoteView: BlockView { - var manifest: Manifest { _manifest as! Manifest } - - let backgroundView = UIView() - let childrenContainer = UIView() - var childrenViews: [BlockView] = [] - - override func viewDidLoad() { - super.viewDidLoad() - addSubview(backgroundView) - backgroundView.backgroundColor = .gray.withAlphaComponent(0.05) - backgroundView.layer.cornerRadius = 8 - backgroundView.clipsToBounds = true - backgroundView.layer.masksToBounds = true - addSubview(childrenContainer) - } - - override func viewDidLayout() { - super.viewDidLayout() - backgroundView.frame = bounds - childrenViews.first?.frame = manifest.childrenGroupRect - childrenContainer.frame = bounds - } - - override func viewDidUpdate() { - super.viewDidUpdate() - childrenContainer.diffableUpdate( - reusingViews: &childrenViews, - manifests: [manifest.childrenGroup] - ) - } - - override func accept(_ manifest: AnyBlockManifest) -> Bool { - manifest is Manifest - } -} - -extension BlockquoteView { - class Manifest: BlockManifest { - var size: CGSize = .zero - var theme: Theme = .default - var dirty: Bool = true - - var childrenGroupWidth: CGFloat { - max(0, size.width - spacings.general * 2) - } - - var intrinsicWidth: CGFloat { - childrenGroup.intrinsicWidth + spacings.general * 2 - } - - let childrenGroup: GroupBlockView.Manifest = .init() - var childrenGroupRect: CGRect = .zero - - required init() {} - - var block: BlockNode? = nil - func load(block: BlockNode) { - guard self.block != block else { return } - dirty = true - self.block = block - guard case let .blockquote(children) = block else { - assertionFailure() - return - } - childrenGroup.setChildren(nodes: children) - } - - func setLayoutWidth(_ width: CGFloat) { - guard size.width != width else { return } - assert(width >= 0) - dirty = true - size.width = width - childrenGroup.setLayoutWidth(childrenGroupWidth) - } - - func setLayoutTheme(_ theme: Theme) { - guard self.theme != theme else { return } - dirty = true - self.theme = theme - childrenGroup.setLayoutTheme(theme) - } - - func layoutIfNeeded() { - guard dirty, size.width > 0 else { return } - defer { dirty = false } - childrenGroup.layoutIfNeeded() - size.height = childrenGroup.size.height + spacings.general * 2 - childrenGroupRect = CGRect( - x: spacings.general, - y: spacings.general, - width: childrenGroup.size.width, - height: childrenGroup.size.height - ) - } - - func determineViewType() -> BlockView.Type { - BlockquoteView.self - } - } -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/BulletedItemView.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/BulletedItemView.swift deleted file mode 100644 index c65dd58c3..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/BulletedItemView.swift +++ /dev/null @@ -1,118 +0,0 @@ -// -// BulletedItemView.swift -// MarkdownView -// -// Created by 秋星桥 on 2025/1/3. -// - -import MarkdownParser -import UIKit - -class BulletedItemView: BlockView { - let bulletedIcon = CircleView() - var manifest: Manifest { _manifest as! Manifest } - let childrenContainer = UIView() - var childrenViews: [BlockView] = [] - - override func viewDidLoad() { - super.viewDidLoad() - addSubview(bulletedIcon) - addSubview(childrenContainer) - } - - override func viewDidLayout() { - super.viewDidLayout() - bulletedIcon.frame = manifest.iconRect - childrenViews.first?.frame = manifest.childrenRect - childrenContainer.frame = bounds - } - - override func viewDidUpdate() { - super.viewDidUpdate() - childrenContainer.diffableUpdate( - reusingViews: &childrenViews, - manifests: [manifest.childrenGroup] - ) - } - - override func accept(_ manifest: AnyBlockManifest) -> Bool { - manifest is Manifest - } -} - -extension BulletedItemView { - class Manifest: BlockManifest { - var size: CGSize = .zero - var theme: Theme = .default - var dirty: Bool = true - - var intrinsicWidth: CGFloat { - childrenGroup.intrinsicWidth + iconLayoutGuideRect.width - } - - var iconLayoutGuideRect: CGRect { - .init(x: 0, y: 0, width: sizes.bullet + spacings.list, height: fonts.body.baseLineHeight) - } - - var iconRect: CGRect = .zero - let childrenGroup: GroupBlockView.Manifest = .init() - var childrenRect: CGRect = .zero - - var childrenGroupWidth: CGFloat { - max(0, size.width - iconLayoutGuideRect.width) - } - - required init() { - childrenGroup.overrideGroupSpacing = 0 - } - - func load(block _: BlockNode) { - assertionFailure("should not be called") - } - - func setItems(_ items: RawListItem) { - dirty = true - childrenGroup.setChildren(nodes: items.children) - } - - func setLayoutWidth(_ width: CGFloat) { - guard size.width != width else { return } - assert(width >= 0) - dirty = true - size.width = width - childrenGroup.setLayoutWidth(childrenGroupWidth) - } - - func setLayoutTheme(_ theme: Theme) { - guard self.theme != theme else { return } - dirty = true - self.theme = theme - childrenGroup.setLayoutTheme(theme) - } - - func layoutIfNeeded() { - guard dirty, size.width > 0 else { return } - defer { dirty = false } - let iconLayoutGuideRect = iconLayoutGuideRect - - childrenGroup.layoutIfNeeded() - size.height = max(iconLayoutGuideRect.height, childrenGroup.size.height) - iconRect = CGRect( - x: 0, - y: (iconLayoutGuideRect.height - sizes.bullet) / 2, - width: sizes.bullet, - height: sizes.bullet - ) - childrenRect = CGRect( - x: iconLayoutGuideRect.maxX, - y: 0, - width: childrenGroup.size.width, - height: childrenGroup.size.height - ) - } - - func determineViewType() -> BlockView.Type { - BulletedItemView.self - } - } -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/BulletedListView.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/BulletedListView.swift deleted file mode 100644 index 371755465..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/BulletedListView.swift +++ /dev/null @@ -1,106 +0,0 @@ -// -// BulletedListView.swift -// MarkdownView -// -// Created by 秋星桥 on 2025/1/3. -// - -import MarkdownParser -import UIKit - -class BulletedListView: BlockView { - var manifest: Manifest { _manifest as! Manifest } - let childrenContainer = UIView() - var childrenViews: [BlockView] = [] - - override func viewDidLoad() { - super.viewDidLoad() - addSubview(childrenContainer) - } - - override func viewDidLayout() { - super.viewDidLayout() - childrenViews.first?.frame = manifest.childrenGroupRect - childrenContainer.frame = bounds - } - - override func viewDidUpdate() { - super.viewDidUpdate() - childrenContainer.diffableUpdate( - reusingViews: &childrenViews, - manifests: [manifest.childrenGroup] - ) - } - - override func accept(_ manifest: AnyBlockManifest) -> Bool { - manifest is Manifest - } -} - -extension BulletedListView { - class Manifest: BlockManifest { - var size: CGSize = .zero - var theme: Theme = .default - var dirty: Bool = true - - var intrinsicWidth: CGFloat { - childrenGroup.intrinsicWidth - } - - required init() { - childrenGroup.overrideGroupSpacing = 0 - } - - let childrenGroup: GroupBlockView.Manifest = .init() - var childrenGroupRect: CGRect = .zero - - var block: BlockNode? = nil - func load(block: BlockNode) { - guard self.block != block else { return } - dirty = true - self.block = block - guard case let .bulletedList(_, items) = block else { - assertionFailure() - return - } - childrenGroup.setChildren(manifests: items.map { listItem in - let manifest = BulletedItemView.Manifest() - manifest.setItems(listItem) - return manifest - }) - childrenGroup.setLayoutWidth(size.width) - } - - func setLayoutWidth(_ width: CGFloat) { - guard size.width != width else { return } - assert(width >= 0) - dirty = true - size.width = width - childrenGroup.setLayoutWidth(width) - } - - func setLayoutTheme(_ theme: Theme) { - guard self.theme != theme else { return } - dirty = true - self.theme = theme - childrenGroup.setLayoutTheme(theme) - } - - func layoutIfNeeded() { - guard dirty, size.width > 0 else { return } - defer { dirty = false } - childrenGroup.layoutIfNeeded() - size.height = childrenGroup.size.height - childrenGroupRect = CGRect( - x: 0, - y: 0, - width: size.width, - height: size.height - ) - } - - func determineViewType() -> BlockView.Type { - BulletedListView.self - } - } -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/CodeBlockView.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/CodeBlockView.swift deleted file mode 100644 index b3cbcf1c1..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/CodeBlockView.swift +++ /dev/null @@ -1,197 +0,0 @@ -// -// CodeBlockView.swift -// MarkdownView -// -// Created by 秋星桥 on 2025/1/3. -// - -import MarkdownParser -import Splash -import UIKit - -class CodeBlockView: BlockView { - let backgroundView = UIView() - let fenceView = UIView() - let fenceLabel = TextLabel() - let fenceCopyButton = UIButton() - let scrollView = UIScrollView() - let codeTextView = TextLabel() - - var manifest: Manifest { _manifest as! Manifest } - - override func viewDidLoad() { - super.viewDidLoad() - clipsToBounds = true - layer.cornerRadius = 8 - layer.masksToBounds = true - - addSubview(backgroundView) - backgroundView.backgroundColor = .gray.withAlphaComponent(0.05) - - addSubview(fenceView) - fenceView.backgroundColor = .gray.withAlphaComponent(0.05) - - addSubview(fenceCopyButton) - fenceCopyButton.setImage(UIImage(systemName: "doc.on.doc"), for: .normal) - fenceCopyButton.tintColor = .label - fenceCopyButton.addTarget(self, action: #selector(copyButtonTapped), for: .touchUpInside) - fenceCopyButton.imageView?.contentMode = .scaleAspectFit - - addSubview(fenceLabel) - fenceLabel.isSelectable = false - fenceLabel.textColor = .label - fenceLabel.font = .preferredFont(forTextStyle: .caption1) - fenceLabel.textAlignment = .left - - addSubview(scrollView) - scrollView.showsVerticalScrollIndicator = false - scrollView.showsHorizontalScrollIndicator = false - scrollView.addSubview(codeTextView) - - scrollView.clipsToBounds = false - scrollView.layer.masksToBounds = false - scrollView.bringSubviewToFront(codeTextView) - } - - override func viewDidLayout() { - super.viewDidLayout() - backgroundView.frame = bounds - fenceView.frame = manifest.fenceRect - fenceLabel.frame = manifest.fenceLabelRect - fenceCopyButton.frame = manifest.fenceCopyButtonRect - scrollView.frame = manifest.scrollRect - codeTextView.frame = manifest.codeContentRect - scrollView.contentSize = manifest.scrollableContentSize - } - - override func viewDidUpdate() { - super.viewDidUpdate() - fenceLabel.attributedText = manifest.fenceInfo - codeTextView.attributedText = manifest.content - } - - @objc func copyButtonTapped() { - UIPasteboard.general.string = manifest.content.string - fenceCopyButton.setImage(UIImage(systemName: "checkmark"), for: .normal) - NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(setButtonImageBack), object: nil) - perform(#selector(setButtonImageBack), with: nil, afterDelay: 1) - } - - @objc func setButtonImageBack() { - fenceCopyButton.setImage(UIImage(systemName: "doc.on.doc"), for: .normal) - } - - override func accept(_ manifest: AnyBlockManifest) -> Bool { - manifest is Manifest - } -} - -extension CodeBlockView { - class Manifest: BlockManifest { - var size: CGSize = .zero - var theme: Theme = .default - var dirty: Bool = true - - var intrinsicWidth: CGFloat { - content.measureWidth() + spacings.general * 4 - } - - var fenceInfo: NSAttributedString = .init() - var content: NSAttributedString = .init() - - var fenceRect: CGRect = .zero - var fenceCopyButtonRect: CGRect = .zero - var fenceLabelRect: CGRect = .zero - var scrollRect: CGRect = .zero - var codeContentRect: CGRect = .zero - var scrollableContentSize: CGSize = .zero - - required init() {} - - var block: BlockNode? = nil - func load(block: BlockNode) { - guard self.block != block else { return } - dirty = true - self.block = block - guard case let .codeBlock(info, content) = block else { - assertionFailure() - return - } - var infoText: String = info ?? "" - infoText = infoText.trimmingCharacters(in: .whitespacesAndNewlines) - if infoText.isEmpty { infoText = "#" } - fenceInfo = NSAttributedString(string: infoText, attributes: [ - .font: theme.fonts.code, - .foregroundColor: theme.colors.body, - .originalFont: theme.fonts.code, - ]) - let code = content.trimmingCharacters(in: .whitespacesAndNewlines) - let codeTheme = theme.codeTheme(withFont: theme.fonts.code) - let output = AttributedStringOutputFormat(theme: codeTheme) - let result: NSMutableAttributedString? - switch info?.lowercased() { - case "swift": - let splash = SyntaxHighlighter(format: output, grammar: SwiftGrammar()) - result = splash.highlight(code).mutableCopy() as? NSMutableAttributedString - default: - let splash = SyntaxHighlighter(format: output) - result = splash.highlight(code).mutableCopy() as? NSMutableAttributedString - } - let defaultAttrs: [NSAttributedString.Key: Any] = [ - .font: theme.fonts.code, - .originalFont: theme.fonts.code, - ] - result?.addAttributes(defaultAttrs, range: NSRange(location: 0, length: result?.length ?? 0)) - self.content = result ?? .init(string: code, attributes: defaultAttrs) - } - - func layoutIfNeeded() { - guard dirty, size.width > 0 else { return } - defer { dirty = false } - let fenceLabelHeight = fenceInfo.measureHeight(usingWidth: .greatestFiniteMagnitude) - let fenceHeight = fenceLabelHeight + spacings.general * 2 - let fenceCopyButtonSize = fenceLabelHeight - fenceRect = CGRect( - x: 0, - y: 0, - width: size.width, - height: fenceHeight - ) - fenceLabelRect = .init( - x: spacings.general, - y: spacings.general, - width: size.width - fenceCopyButtonSize - spacings.general * 2, - height: fenceLabelHeight - ) - fenceCopyButtonRect = .init( - x: size.width - fenceCopyButtonSize - spacings.general, - y: fenceLabelRect.minY, - width: fenceCopyButtonSize, - height: fenceCopyButtonSize - ) - let contentWidth = content.measureWidth() - let contentHeight = content.measureHeight(usingWidth: .greatestFiniteMagnitude) - scrollRect = .init( - x: 0, - y: fenceRect.maxY, - width: size.width, - height: contentHeight + spacings.general * 2 - ) - size.height = scrollRect.maxY - codeContentRect = .init( - x: spacings.general, - y: spacings.general, - width: contentWidth + spacings.general * 2, - height: contentHeight - ) - scrollableContentSize = .init( - width: codeContentRect.maxX + spacings.general, - height: codeContentRect.maxY + spacings.general - ) - } - - func determineViewType() -> BlockView.Type { - CodeBlockView.self - } - } -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/GroupBlockView.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/GroupBlockView.swift deleted file mode 100644 index 9d7f1a797..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/GroupBlockView.swift +++ /dev/null @@ -1,142 +0,0 @@ -// -// GroupBlockView.swift -// MarkdownView -// -// Created by 秋星桥 on 2025/1/3. -// - -import MarkdownParser -import UIKit - -class GroupBlockView: BlockView { - var manifest: Manifest { _manifest as! Manifest } - - let childrenContainer = UIView() - var childrenViews: [BlockView] = [] - - override func viewDidLoad() { - super.viewDidLoad() - addSubview(childrenContainer) - } - - override func viewDidLayout() { - super.viewDidLayout() - childrenContainer.frame = bounds - for (index, view) in childrenViews.enumerated() { - view.frame = manifest.children[index].rect - } - } - - override func viewDidUpdate() { - childrenContainer.diffableUpdate( - reusingViews: &childrenViews, - manifests: manifest.children.map(\.manifest) - ) - } - - override func accept(_ manifest: AnyBlockManifest) -> Bool { - manifest is Manifest - } -} - -extension GroupBlockView { - class Manifest: BlockManifest { - var size: CGSize = .zero - var theme: Theme = .default - var dirty: Bool = true - - var intrinsicWidth: CGFloat { - children.map(\.manifest.intrinsicWidth).max() ?? 0 - } - - var overrideGroupSpacing: CGFloat? = nil { - didSet { dirty = true } - } - - var spacing: CGFloat { - if let overrideGroupSpacing { return overrideGroupSpacing } - return theme.spacings.general - } - - required init() {} - - func load(block _: BlockNode) { - assertionFailure("should not be called") - } - - var children: [Child] = [] { - didSet { dirty = true } - } - - func setLayoutWidth(_ width: CGFloat) { - guard size.width != width else { return } - assert(width >= 0) - dirty = true - size.width = width - children.forEach { $0.manifest.setLayoutWidth(width) } - } - - func setChildren(manifests: [AnyBlockManifest]) { - dirty = true - children = manifests.map { Child(manifest: $0) } - } - - func setChildren(nodes: [BlockNode]) { - setChildren(manifests: nodes.map { $0.manifest(theme: theme) }) - } - - func setLayoutTheme(_ theme: Theme) { - guard self.theme != theme else { return } - dirty = true - self.theme = theme - children.forEach { $0.manifest.setLayoutTheme(theme) } - } - - func layoutIfNeeded() { - guard dirty, size.width > 0 else { return } - defer { dirty = false } - var anchor: CGFloat = 0 - for child in children { - child.manifest.setLayoutWidth(size.width) - child.manifest.layoutIfNeeded() - child.rect = CGRect( - x: 0, - y: anchor + spacing, - width: child.manifest.size.width, - height: child.manifest.size.height - ) - anchor = child.rect.maxY - } - size.height = anchor - } - - func determineViewType() -> BlockView.Type { - GroupBlockView.self - } - } -} - -extension GroupBlockView.Manifest { - class Child { - let manifest: AnyBlockManifest - var rect: CGRect - - init(manifest: AnyBlockManifest, rect: CGRect = .zero) { - self.manifest = manifest - self.rect = rect - } - } - - func build(reusingChildren: [Child], forManifests manifests: [AnyBlockManifest]) -> [Child] { - var ans = [Child]() - for idx in manifests.indices { - let manifest = manifests[idx] - if let child = reusingChildren[safe: idx], type(of: child.manifest) == type(of: manifest) { - ans.append(child) - } else { - ans.append(Child(manifest: manifest)) - } - } - return ans - } -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/HTMLBlockView.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/HTMLBlockView.swift deleted file mode 100644 index 4c4e2cec4..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/HTMLBlockView.swift +++ /dev/null @@ -1,90 +0,0 @@ -//// -//// HTMLBlockView.swift -//// MarkdownView -//// -//// Created by 秋星桥 on 2025/1/3. -//// -// -// import Foundation -// import MarkdownParser -// import UIKit -// -// class HTMLBlockView: BlockView { -// let text = TextView() -// var manifest: Manifest { _manifest as! Manifest } -// -// override func viewDidLoad() { -// super.viewDidLoad() -// addSubview(text) -// text.isEditable = false -// text.isSelectable = true -// text.isScrollEnabled = false -// } -// -// override func viewDidLayout() { -// super.viewDidLayout() -// text.frame = manifest.contentRect -// } -// -// override func viewDidUpdate() { -// super.viewDidUpdate() -// text.attributedText = manifest.content -// } -// -// override func accept(_ manifest: AnyBlockManifest) -> Bool { -// manifest is Manifest -// } -// } -// -// extension HTMLBlockView { -// class Manifest: BlockManifest { -// var size: CGSize = .zero -// var theme: Theme = .default -// var dirty: Bool = true -// -// var intrinsicWidth: CGFloat { -// size.width -// } -// -// var content: NSMutableAttributedString = .init() -// var contentRect: CGRect = .zero -// -// required init() {} -// -// var block: BlockNode? = nil -// func load(block: BlockNode) { -// guard self.block != block else { return } -// dirty = true -// self.block = block -// guard case let .htmlBlock(contents) = block else { -// assertionFailure() -// return -// } -// let htmlData = NSString(string: contents).data(using: String.Encoding.unicode.rawValue) -// let options = [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.html] -// let ans = try? NSMutableAttributedString( -// data: htmlData ?? Data(), -// options: options, -// documentAttributes: nil -// ) -// let content = ans ?? .init() -// content.addAttributes( -// [.originalFont: theme.fonts.body], -// range: .init(location: 0, length: content.length) -// ) -// self.content = content -// } -// -// func layoutIfNeeded() { -// guard dirty, size.width > 0 else { return } -// defer { dirty = false } -// let textHeight = content.measureHeight(usingWidth: size.width) -// contentRect = .init(x: 0, y: 0, width: size.width, height: textHeight) -// size.height = textHeight -// } -// -// func determineViewType() -> BlockView.Type { -// HTMLBlockView.self -// } -// } -// } diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/HeadingView.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/HeadingView.swift deleted file mode 100644 index ac057c6b7..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/HeadingView.swift +++ /dev/null @@ -1,87 +0,0 @@ -// -// HeadingView.swift -// MarkdownView -// -// Created by 秋星桥 on 2025/1/3. -// - -import MarkdownParser -import UIKit - -class HeadingView: BlockView { - let text = TextLabel() - var manifest: Manifest { _manifest as! Manifest } - - override func viewDidLoad() { - super.viewDidLoad() - addSubview(text) - } - - override func viewDidLayout() { - super.viewDidLayout() - text.frame = manifest.contentRect - } - - override func viewDidUpdate() { - super.viewDidUpdate() - text.attributedText = manifest.content - } - - override func accept(_ manifest: AnyBlockManifest) -> Bool { - manifest is Manifest - } -} - -extension HeadingView { - class Manifest: BlockManifest { - var size: CGSize = .zero - var theme: Theme = .default - var dirty: Bool = true - - var intrinsicWidth: CGFloat { - size.width - } - - var content: NSMutableAttributedString = .init() - var contentRect: CGRect = .zero - - required init() {} - - var block: BlockNode? = nil - func load(block: BlockNode) { - guard self.block != block else { return } - dirty = true - self.block = block - guard case let .heading(level, inlines) = block else { - assertionFailure() - return - } - let attrText = inlines.render(theme: theme) - var supposeFont: UIFont = theme.fonts.title - if level <= 1 { - supposeFont = theme.fonts.largeTitle - } - attrText.addAttributes( - [ - .font: supposeFont, - .originalFont: supposeFont, - .foregroundColor: theme.colors.body, - ], - range: .init(location: 0, length: attrText.length) - ) - content = attrText - } - - func layoutIfNeeded() { - guard dirty, size.width > 0 else { return } - defer { dirty = false } - let textHeight = content.measureHeight(usingWidth: size.width) - contentRect = .init(x: 0, y: 0, width: size.width, height: textHeight) - size.height = textHeight - } - - func determineViewType() -> BlockView.Type { - HeadingView.self - } - } -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/NumberedItemView.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/NumberedItemView.swift deleted file mode 100644 index 4a6dabc22..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/NumberedItemView.swift +++ /dev/null @@ -1,138 +0,0 @@ -// -// BulletedItemView 2.swift -// MarkdownView -// -// Created by 秋星桥 on 2025/1/3. -// - -import MarkdownParser -import UIKit - -class NumberedItemView: BlockView { - let numberView = TextLabel() - var manifest: Manifest { _manifest as! Manifest } - - let childrenContainer = UIView() - var childrenViews: [BlockView] = [] - - override func viewDidLoad() { - super.viewDidLoad() - numberView.textAlignment = .left - numberView.font = .preferredFont(forTextStyle: .body) - numberView.textColor = .label - addSubview(numberView) - addSubview(childrenContainer) - } - - override func viewDidLayout() { - super.viewDidLayout() - childrenContainer.frame = bounds - numberView.frame = manifest.iconRect - childrenViews.first?.frame = manifest.childrenRect - } - - override func viewDidUpdate() { - super.viewDidUpdate() - numberView.attributedText = manifest.number - childrenContainer.diffableUpdate( - reusingViews: &childrenViews, - manifests: [manifest.childrenGroup] - ) - } - - override func accept(_ manifest: AnyBlockManifest) -> Bool { - manifest is Manifest - } -} - -extension NumberedItemView { - class Manifest: BlockManifest { - var size: CGSize = .zero - var theme: Theme = .default - var dirty: Bool = true - - var intrinsicWidth: CGFloat { - childrenGroup.intrinsicWidth + iconLayoutGuideRect.width - } - - var number: NSAttributedString = .init() - var iconRect: CGRect = .zero - let childrenGroup: GroupBlockView.Manifest = .init() - var childrenRect: CGRect = .zero - - var iconWidth: CGFloat { - "99.".size(withAttributes: [.font: theme.fonts.body]).width - } - - var iconLayoutGuideRect: CGRect { - .init(x: 0, y: 0, width: iconWidth + spacings.list, height: fonts.body.baseLineHeight) - } - - var childrenGroupWidth: CGFloat { - max(0, size.width - iconLayoutGuideRect.width) - } - - required init() { - childrenGroup.overrideGroupSpacing = 0 - } - - func load(block _: BlockNode) { - assertionFailure("should not be called") - } - - func setNumber(_ number: Int) { - dirty = true - self.number = NSMutableAttributedString(string: "\(number).", attributes: [ - .font: theme.fonts.body, - .originalFont: theme.fonts.body, - .foregroundColor: UIColor.label, - ]) - } - - func setItems(_ items: RawListItem) { - dirty = true - childrenGroup.setChildren(nodes: items.children) - } - - func setLayoutWidth(_ width: CGFloat) { - guard size.width != width else { return } - assert(width >= 0) - dirty = true - size.width = width - childrenGroup.setLayoutWidth(childrenGroupWidth) - } - - func setLayoutTheme(_ theme: Theme) { - guard self.theme != theme else { return } - dirty = true - self.theme = theme - childrenGroup.setLayoutTheme(theme) - } - - func layoutIfNeeded() { - guard dirty, size.width > 0 else { return } - defer { dirty = false } - let iconLayoutGuideRect = iconLayoutGuideRect - - childrenGroup.setLayoutWidth(childrenGroupWidth) - childrenGroup.layoutIfNeeded() - size.height = max(iconLayoutGuideRect.height, childrenGroup.size.height) - iconRect = CGRect( - x: 0, - y: (iconLayoutGuideRect.height - iconLayoutGuideRect.height) / 2, - width: iconWidth, - height: iconLayoutGuideRect.height - ) - childrenRect = CGRect( - x: iconLayoutGuideRect.maxX, - y: 0, - width: childrenGroup.size.width, - height: childrenGroup.size.height - ) - } - - func determineViewType() -> BlockView.Type { - NumberedItemView.self - } - } -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/NumberedListView.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/NumberedListView.swift deleted file mode 100644 index d94a0479c..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/NumberedListView.swift +++ /dev/null @@ -1,110 +0,0 @@ -// -// NumberedListView.swift -// MarkdownView -// -// Created by 秋星桥 on 2025/1/3. -// - -import MarkdownParser -import UIKit - -class NumberedListView: BlockView { - var manifest: Manifest { _manifest as! Manifest } - - let childrenContainer = UIView() - var childrenViews: [BlockView] = [] - - override func viewDidLoad() { - super.viewDidLoad() - addSubview(childrenContainer) - } - - override func viewDidLayout() { - super.viewDidLayout() - childrenContainer.frame = bounds - childrenViews.first?.frame = manifest.childrenGroupRect - } - - override func viewDidUpdate() { - super.viewDidUpdate() - childrenContainer.diffableUpdate( - reusingViews: &childrenViews, - manifests: [manifest.childrenGroup] - ) - } - - override func accept(_ manifest: AnyBlockManifest) -> Bool { - manifest is Manifest - } -} - -extension NumberedListView { - class Manifest: BlockManifest { - var size: CGSize = .zero - var theme: Theme = .default - var dirty: Bool = true - - var intrinsicWidth: CGFloat { - childrenGroup.intrinsicWidth - } - - required init() { - childrenGroup.overrideGroupSpacing = 0 - } - - let childrenGroup: GroupBlockView.Manifest = .init() - var childrenGroupRect: CGRect = .zero - - var block: BlockNode? = nil - func load(block: BlockNode) { - guard self.block != block else { return } - dirty = true - self.block = block - guard case let .numberedList(_, start, items) = block else { - assertionFailure() - return - } - var number = start - childrenGroup.setChildren(manifests: items.map { listItem in - defer { number += 1 } - let manifest = NumberedItemView.Manifest() - manifest.setNumber(number) - manifest.setItems(listItem) - return manifest - }) - childrenGroup.setLayoutWidth(size.width) - } - - func setLayoutWidth(_ width: CGFloat) { - guard size.width != width else { return } - assert(width >= 0) - dirty = true - size.width = width - childrenGroup.setLayoutWidth(width) - } - - func setLayoutTheme(_ theme: Theme) { - guard self.theme != theme else { return } - dirty = true - self.theme = theme - childrenGroup.setLayoutTheme(theme) - } - - func layoutIfNeeded() { - guard dirty, size.width > 0 else { return } - defer { dirty = false } - childrenGroup.layoutIfNeeded() - size.height = childrenGroup.size.height - childrenGroupRect = CGRect( - x: 0, - y: 0, - width: size.width, - height: size.height - ) - } - - func determineViewType() -> BlockView.Type { - NumberedListView.self - } - } -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/ParagraphView.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/ParagraphView.swift deleted file mode 100644 index 8c4074802..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/ParagraphView.swift +++ /dev/null @@ -1,74 +0,0 @@ -// -// ParagraphView.swift -// MarkdownView -// -// Created by 秋星桥 on 2025/1/3. -// - -import MarkdownParser -import UIKit - -class ParagraphView: BlockView { - let text = TextLabel() - var manifest: Manifest { _manifest as! Manifest } - - override func viewDidLoad() { - super.viewDidLoad() - addSubview(text) - } - - override func viewDidLayout() { - super.viewDidLayout() - text.frame = manifest.contentRect - } - - override func viewDidUpdate() { - super.viewDidUpdate() - text.attributedText = manifest.content - } - - override func accept(_ manifest: AnyBlockManifest) -> Bool { - manifest is Manifest - } -} - -extension ParagraphView { - class Manifest: BlockManifest { - var size: CGSize = .zero - var theme: Theme = .default - var dirty: Bool = true - - var intrinsicWidth: CGFloat { - content.measureWidth() - } - - var content: NSMutableAttributedString = .init() - var contentRect: CGRect = .zero - - required init() {} - - var block: BlockNode? = nil - func load(block: BlockNode) { - guard self.block != block else { return } - dirty = true - self.block = block - guard case let .paragraph(contents) = block else { - assertionFailure() - return - } - content = contents.render(theme: theme) - } - - func layoutIfNeeded() { - guard dirty, size.width > 0 else { return } - defer { dirty = false } - let textHeight = content.measureHeight(usingWidth: size.width) - contentRect = .init(x: 0, y: 0, width: size.width, height: textHeight) - size.height = textHeight - } - - func determineViewType() -> BlockView.Type { - ParagraphView.self - } - } -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/Supplements/BlockManifest.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/Supplements/BlockManifest.swift deleted file mode 100644 index 945e304b9..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/Supplements/BlockManifest.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// BlockManifest.swift -// MarkdownView -// -// Created by 秋星桥 on 2025/1/3. -// - -import MarkdownParser -import UIKit - -public typealias AnyBlockManifest = any BlockManifest - -public protocol BlockManifest: AnyObject { - var size: CGSize { get set } - var theme: Theme { get set } - var dirty: Bool { get set } - - var intrinsicWidth: CGFloat { get } - - init() - - func setLayoutWidth(_ width: CGFloat) - func setLayoutTheme(_ theme: Theme) - func load(block: BlockNode) - func layoutIfNeeded() - - @inline(__always) func determineViewType() -> BlockView.Type -} - -public extension BlockManifest { - func setLayoutWidth(_ width: CGFloat) { - guard size.width != width else { return } - assert(width >= 0) - size.width = width - size.height = .zero - dirty = true - } - - func setLayoutTheme(_ theme: Theme) { - guard self.theme != theme else { return } - self.theme = theme - dirty = true - } - - func layoutIfNeeded() { - dirty = false - } -} - -extension BlockManifest { - var fonts: Theme.Fonts { theme.fonts } - var colors: Theme.Colors { theme.colors } - var spacings: Theme.Spacings { theme.spacings } - var sizes: Theme.Sizes { theme.sizes } -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/Supplements/BlockView+DiffableUpdate.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/Supplements/BlockView+DiffableUpdate.swift deleted file mode 100644 index 1720a51b7..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/Supplements/BlockView+DiffableUpdate.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// BlockView+DiffableUpdate.swift -// MarkdownView -// -// Created by 秋星桥 on 2025/1/3. -// - -import Foundation -import UIKit - -extension UIView { - func diffableUpdate(reusingViews blockViews: inout [BlockView], manifests: [AnyBlockManifest]) { - defer { - assert(blockViews.count == manifests.count, "blockViews count must match manifests count") - } - var shouldRemovedIndices: [Int] = [] - defer { - for index in shouldRemovedIndices.sorted(by: >) { - blockViews.remove(at: index) - } - } - for idx in 0 ..< max(blockViews.count, manifests.count) { - guard let manifest = manifests[safe: idx] else { - if let view = blockViews[safe: idx] { - view.removeFromSuperview() - shouldRemovedIndices.append(idx) - } - continue - } - lazy var view = { - let view = manifest.determineViewType().init(manifest: manifest) - addSubview(view) - return view - }() - if let currentView = blockViews[safe: idx] { - if currentView.accept(manifest) { - currentView.set(manifest) - continue - } else { - currentView.removeFromSuperview() - blockViews[idx] = view - } - } else { - addSubview(view) - blockViews.insert(view, at: idx) - } - } - } -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/Supplements/BlockView.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/Supplements/BlockView.swift deleted file mode 100644 index 2b4951b94..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/Supplements/BlockView.swift +++ /dev/null @@ -1,84 +0,0 @@ -// -// BlockView.swift -// FlowMarkdownView -// -// Created by 秋星桥 on 2025/1/3. -// - -import Foundation -import MarkdownParser -import UIKit - -public class BlockView: UIView { - private(set) var _manifest: AnyBlockManifest - required init(manifest: AnyBlockManifest) { - _manifest = manifest - super.init(frame: .zero) - backgroundColor = .clear - -// NSLog("[*] \(type(of: self)) was initialized at \(Date()) \(debugDescription)") - - viewDidLoad() - viewDidUpdate() - } - - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError() - } - - override public func action(for _: CALayer, forKey _: String) -> (any CAAction)? { - nil - } - - override public func layoutSubviews() { - super.layoutSubviews() - assert(_manifest.size.width >= 0, "\(type(of: self))'s manifest has invalid size") - assert(_manifest.size.height >= 0, "\(type(of: self))'s manifest has invalid size") - viewDidLayout() - } - - func viewDidLoad() { - assert(Thread.isMainThread) - } - - func viewDidUpdate() { - assert(Thread.isMainThread) - } - - func viewDidLayout() { - assert(Thread.isMainThread) - } - - func accept(_ manifest: AnyBlockManifest) -> Bool { - manifest is Manifest - } - - func set(_ manifest: AnyBlockManifest) { - _manifest = manifest - viewDidUpdate() - setNeedsLayout() - } -} - -public extension BlockView { - class Manifest: BlockManifest { - public var size: CGSize = .zero - public var theme: Theme = .default - public var dirty: Bool = true - - public var intrinsicWidth: CGFloat { 0 } - - public required init() {} - - public func load(block _: BlockNode) {} - - public func layoutIfNeeded() { - dirty = false - } - - public func determineViewType() -> BlockView.Type { - BlockView.self - } - } -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/TableView.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/TableView.swift deleted file mode 100644 index 577d517b8..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/TableView.swift +++ /dev/null @@ -1,205 +0,0 @@ -// -// TableView.swift -// MarkdownView -// -// Created by 秋星桥 on 2025/1/3. -// - -import MarkdownParser -import UIKit - -class TableView: BlockView { - var manifest: Manifest { _manifest as! Manifest } - var childrenViews: [TextLabel] = [] - let scrollView = UIScrollView() - let gridView = GridView() - - override func viewDidLoad() { - super.viewDidLoad() - scrollView.isScrollEnabled = true - scrollView.contentInset = .zero - scrollView.showsVerticalScrollIndicator = false - scrollView.showsHorizontalScrollIndicator = false - scrollView.alwaysBounceVertical = false - scrollView.alwaysBounceHorizontal = false - scrollView.clipsToBounds = true - scrollView.backgroundColor = .clear - addSubview(scrollView) - scrollView.addSubview(gridView) - } - - override func viewDidLayout() { - super.viewDidLayout() - scrollView.frame = bounds - - let flatCells = manifest.cells.flatMap(\.self) - for (index, view) in childrenViews.enumerated() { - view.frame = flatCells[index].contentRect - } - gridView.frame = .init( - x: 0, - y: 0, - width: scrollView.contentSize.width, - height: scrollView.contentSize.height - ) - - scrollView.contentSize = manifest.contentSize - } - - override func viewDidUpdate() { - super.viewDidUpdate() - let currentCells = childrenViews - let targetCells = manifest.cells.flatMap(\.self) - for idx in 0 ..< max(currentCells.count, targetCells.count) { - if let target = targetCells[safe: idx] { - if let current = currentCells[safe: idx] { - current.attributedText = target.content - current.frame = target.rect - } else { - let view = TextLabel(frame: target.rect) - view.attributedText = target.content - scrollView.addSubview(view) - childrenViews.append(view) - } - } else { - currentCells[safe: idx]?.removeFromSuperview() - } - } - gridView.lines = manifest.drawLine.map { start, end in - .init(start: start, end: end) - } - scrollView.contentSize = manifest.contentSize - } - - override func accept(_ manifest: AnyBlockManifest) -> Bool { - manifest is Manifest - } -} - -extension TableView { - class Manifest: BlockManifest { - var size: CGSize = .zero - var theme: Theme = .default - var dirty: Bool = true - - var intrinsicWidth: CGFloat { - contentSize.width - } - - var cells: [[Cell]] = [] - var drawLine: [(CGPoint, CGPoint)] = [] - - var contentSize: CGSize { - let rect = cells.last?.last?.rect ?? .zero - return .init(width: rect.maxX, height: rect.maxY) - } - - required init() {} - - var block: BlockNode? = nil - func load(block: BlockNode) { - guard self.block != block else { return } - dirty = true - self.block = block - guard case let .table(_, rawCells) = block else { - assertionFailure() - return - } - - cells = rawCells.map { row in - row.cells.map { cell in - let content = cell.content.render(theme: theme) - return Cell(content: content, rect: .zero) - } - } - } - - func layoutIfNeeded() { - guard dirty, size.width > 0 else { return } - defer { dirty = false } - let cols = cells.first?.count ?? 0 - let rows = cells.count - drawLine.removeAll() - - guard rows > 0, cols > 0 else { - size.height = 0 - return - } - - // first pass calculate intrinsic width of each column to get the width - var colWidths: [Int: CGFloat] = [:] - var rowHeights: [Int: CGFloat] = [:] - for col in 0 ..< cols { - for row in 0 ..< rows { - let cell = cells[row][col] - colWidths[col] = max(colWidths[col] ?? 0, cell.intrinsicSize.width + 32) - rowHeights[row] = max(rowHeights[row] ?? 0, cell.intrinsicSize.height + 16) - } - } - - // now calculate the rects and points - var anchorY: CGFloat = 0 - var linePoints: [CGFloat] = [0] - var colPoints: [CGFloat] = [0] - for rowIdx in 0 ..< rows { - var anchorX: CGFloat = 0 - let rowHeight = rowHeights[rowIdx] ?? 0 - linePoints.append(anchorY) - for colIdx in 0 ..< cols { // column - let colWidth = colWidths[colIdx] ?? 0 - if rowIdx == 0 { colPoints.append(anchorX) } - let rect = CGRect(x: anchorX, y: anchorY, width: colWidth, height: rowHeight) - cells[rowIdx][colIdx].rect = rect - anchorX = rect.maxX - } - colPoints.append(anchorX + spacings.general) - anchorY += rowHeight - } - linePoints.append(anchorY) - - for x in colPoints { - drawLine.append((CGPoint(x: x, y: 0), CGPoint(x: x, y: linePoints.last ?? 0))) - } - - for y in linePoints { - drawLine.append((CGPoint(x: 0, y: y), CGPoint(x: colPoints.last ?? 0, y: y))) - } - - size.height = contentSize.height - } - - func determineViewType() -> BlockView.Type { - TableView.self - } - } -} - -extension TableView.Manifest { - class Cell { - let content: NSMutableAttributedString - var rect: CGRect { didSet { updateContentRect() } } - let intrinsicSize: CGSize - var contentRect: CGRect - init( - content: NSMutableAttributedString, - rect: CGRect = .zero - ) { - self.content = content - self.rect = rect - intrinsicSize = .init( - width: content.measureWidth(), - height: content.measureHeight(usingWidth: .greatestFiniteMagnitude) - ) - contentRect = .zero - } - - func updateContentRect() { - contentRect = .init( - x: rect.minX + (rect.width - intrinsicSize.width) / 2, - y: rect.minY + (rect.height - intrinsicSize.height) / 2, - width: intrinsicSize.width, - height: intrinsicSize.height - ) - } - } -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/TaskItemView.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/TaskItemView.swift deleted file mode 100644 index 350a0dc87..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/TaskItemView.swift +++ /dev/null @@ -1,119 +0,0 @@ -// -// TaskItemView.swift -// MarkdownView -// -// Created by 秋星桥 on 2025/1/3. -// - -import MarkdownParser -import UIKit - -class TaskItemView: BlockView { - let bulletedIcon = CircleView() - var manifest: Manifest { _manifest as! Manifest } - - let childrenContainer = UIView() - var childrenViews: [BlockView] = [] - - override func viewDidLoad() { - super.viewDidLoad() - addSubview(bulletedIcon) - addSubview(childrenContainer) - } - - override func viewDidLayout() { - super.viewDidLayout() - childrenContainer.frame = bounds - bulletedIcon.frame = manifest.iconRect - childrenViews.first?.frame = manifest.childrenRect - } - - override func viewDidUpdate() { - super.viewDidUpdate() - childrenContainer.diffableUpdate( - reusingViews: &childrenViews, - manifests: [manifest.childrenGroup] - ) - } - - override func accept(_ manifest: AnyBlockManifest) -> Bool { - manifest is Manifest - } -} - -extension TaskItemView { - class Manifest: BlockManifest { - var size: CGSize = .zero - var theme: Theme = .default - var dirty: Bool = true - - var intrinsicWidth: CGFloat { - childrenGroup.intrinsicWidth + iconLayoutGuideRect.width - } - - var iconLayoutGuideRect: CGRect { - .init(x: 0, y: 0, width: sizes.bullet + spacings.list, height: fonts.body.baseLineHeight) - } - - var iconRect: CGRect = .zero - let childrenGroup: GroupBlockView.Manifest = .init() - var childrenRect: CGRect = .zero - - var childrenGroupWidth: CGFloat { - max(0, size.width - iconLayoutGuideRect.width) - } - - required init() { - childrenGroup.overrideGroupSpacing = 0 - } - - func load(block _: BlockNode) { - assertionFailure("should not be called") - } - - func setItems(_ items: RawTaskListItem) { - dirty = true - childrenGroup.setChildren(nodes: items.children) - } - - func setLayoutWidth(_ width: CGFloat) { - guard size.width != width else { return } - assert(width >= 0) - dirty = true - size.width = width - childrenGroup.setLayoutWidth(childrenGroupWidth) - } - - func setLayoutTheme(_ theme: Theme) { - guard self.theme != theme else { return } - dirty = true - self.theme = theme - childrenGroup.setLayoutTheme(theme) - } - - func layoutIfNeeded() { - guard dirty, size.width > 0 else { return } - defer { dirty = false } - let iconLayoutGuideRect = iconLayoutGuideRect - - childrenGroup.layoutIfNeeded() - size.height = max(iconLayoutGuideRect.height, childrenGroup.size.height) - iconRect = CGRect( - x: 0, - y: (iconLayoutGuideRect.height - sizes.bullet) / 2, - width: sizes.bullet, - height: sizes.bullet - ) - childrenRect = CGRect( - x: iconLayoutGuideRect.maxX, - y: 0, - width: childrenGroup.size.width, - height: childrenGroup.size.height - ) - } - - func determineViewType() -> BlockView.Type { - TaskItemView.self - } - } -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/TaskListView.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/TaskListView.swift deleted file mode 100644 index e33c976fc..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/TaskListView.swift +++ /dev/null @@ -1,107 +0,0 @@ -// -// TaskListView.swift -// MarkdownView -// -// Created by 秋星桥 on 2025/1/3. -// - -import MarkdownParser -import UIKit - -class TaskListView: BlockView { - var manifest: Manifest { _manifest as! Manifest } - - let childrenContainer = UIView() - var childrenViews: [BlockView] = [] - - override func viewDidLoad() { - super.viewDidLoad() - addSubview(childrenContainer) - } - - override func viewDidLayout() { - super.viewDidLayout() - childrenContainer.frame = bounds - childrenViews.first?.frame = manifest.childrenGroupRect - } - - override func viewDidUpdate() { - super.viewDidUpdate() - childrenContainer.diffableUpdate( - reusingViews: &childrenViews, - manifests: [manifest.childrenGroup] - ) - } - - override func accept(_ manifest: AnyBlockManifest) -> Bool { - manifest is Manifest - } -} - -extension TaskListView { - class Manifest: BlockManifest { - var size: CGSize = .zero - var theme: Theme = .default - var dirty: Bool = true - - var intrinsicWidth: CGFloat { - childrenGroup.intrinsicWidth - } - - required init() { - childrenGroup.overrideGroupSpacing = 0 - } - - let childrenGroup: GroupBlockView.Manifest = .init() - var childrenGroupRect: CGRect = .zero - - var block: BlockNode? = nil - func load(block: BlockNode) { - guard self.block != block else { return } - dirty = true - self.block = block - guard case let .taskList(_, items) = block else { - assertionFailure() - return - } - childrenGroup.setChildren(manifests: items.map { listItem in - let manifest = TaskItemView.Manifest() - manifest.setItems(listItem) - return manifest - }) - childrenGroup.setLayoutWidth(size.width) - } - - func setLayoutWidth(_ width: CGFloat) { - guard size.width != width else { return } - assert(width >= 0) - dirty = true - size.width = width - childrenGroup.setLayoutWidth(width) - } - - func setLayoutTheme(_ theme: Theme) { - guard self.theme != theme else { return } - dirty = true - self.theme = theme - childrenGroup.setLayoutTheme(theme) - } - - func layoutIfNeeded() { - guard dirty, size.width > 0 else { return } - defer { dirty = false } - childrenGroup.layoutIfNeeded() - size.height = childrenGroup.size.height - childrenGroupRect = CGRect( - x: 0, - y: 0, - width: size.width, - height: size.height - ) - } - - func determineViewType() -> BlockView.Type { - TaskListView.self - } - } -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/ThematicBreakView.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/ThematicBreakView.swift deleted file mode 100644 index 0c7d70864..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownBlockView/ThematicBreakView.swift +++ /dev/null @@ -1,63 +0,0 @@ -// -// ThematicBreakView.swift -// MarkdownView -// -// Created by 秋星桥 on 2025/1/3. -// - -import MarkdownParser -import UIKit - -class ThematicBreakView: BlockView { - let separateView = UIView() - - override func viewDidLoad() { - super.viewDidLoad() - addSubview(separateView) - separateView.backgroundColor = .label.withAlphaComponent(0.1) - } - - override func viewDidLayout() { - super.viewDidLayout() - separateView.frame = bounds - } - - override func accept(_ manifest: AnyBlockManifest) -> Bool { - manifest is Manifest - } -} - -extension ThematicBreakView { - class Manifest: BlockManifest { - var size: CGSize = .zero - var theme: Theme = .default - var dirty: Bool = true - - var intrinsicWidth: CGFloat { - 32 - } - - required init() {} - - var block: BlockNode? = nil - func load(block: BlockNode) { - guard self.block != block else { return } - dirty = true - self.block = block - guard case .thematicBreak = block else { - assertionFailure() - return - } - } - - func layoutIfNeeded() { - guard dirty, size.width > 0 else { return } - defer { dirty = false } - size.height = 1 - } - - func determineViewType() -> BlockView.Type { - ThematicBreakView.self - } - } -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownView/MarkdownView.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownView/MarkdownView.swift deleted file mode 100644 index 120475eb5..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/MarkdownView/MarkdownView.swift +++ /dev/null @@ -1,45 +0,0 @@ -// The Swift Programming Language -// https://docs.swift.org/swift-book - -import MarkdownParser -import UIKit - -public class MarkdownView: UIView { - public var height: CGFloat = 0 - - var blockViews: [BlockView] = [] - - public var theme: Theme - public init(theme: Theme = .default) { - self.theme = theme - super.init(frame: .zero) - clipsToBounds = true - } - - @available(*, unavailable) - public required init?(coder _: NSCoder) { - fatalError() - } - - public func prepareForReuse() { - blockViews.forEach { $0.removeFromSuperview() } - blockViews.removeAll() - } - - public func updateContentViews(_ manifest: [AnyBlockManifest]) { - assert(Thread.isMainThread) - diffableUpdate(reusingViews: &blockViews, manifests: manifest) - var anchorY: CGFloat = 0 - for view in blockViews { - view.frame = CGRect( - x: 0, - y: anchorY, - width: view._manifest.size.width, - height: view._manifest.size.height - ) - anchorY = view.frame.maxY + theme.spacings.final - } - height = blockViews.map(\.frame.maxY).max() ?? 0 - assert(subviews.count == blockViews.count) - } -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/CircleView.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/CircleView.swift deleted file mode 100644 index 164831d90..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/CircleView.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// CircleView.swift -// MarkdownView -// -// Created by 秋星桥 on 2025/1/3. -// - -import UIKit - -class CircleView: UIView { - init() { - super.init(frame: .zero) - backgroundColor = .label - } - - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError() - } - - override func layoutSubviews() { - super.layoutSubviews() - layer.cornerRadius = (frame.height + frame.width) / 4 - } -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/Ext+Array.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/Ext+Array.swift deleted file mode 100644 index f8892a85f..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/Ext+Array.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// Ext+Array.swift -// MarkdownView -// -// Created by 秋星桥 on 2025/1/3. -// - -import Foundation - -extension Array { - subscript(safe index: Int) -> Element? { - guard index >= 0, index < count else { return nil } - return self[index] - } -} - -private let maxConcurrentDefaultValue: Int = max(1, ProcessInfo.processInfo.processorCount) - -extension Array { - @inline(__always) - func splitInSubArrays(into size: Int) -> [[Element]] { - (0 ..< size).map { - stride(from: $0, to: count, by: size).map { self[$0] } - } - } - - func forParallelEach( - maxConcurrent: Int = maxConcurrentDefaultValue, - block: @escaping (Element) -> Void - ) { - assert(maxConcurrent > 0) - if count < maxConcurrent || maxConcurrent <= 1 { - for element in self { - block(element) - } - return - } - - let cuts = splitInSubArrays(into: maxConcurrent) - - let group = DispatchGroup() - for cut in cuts { - group.enter() - DispatchQueue.global().async { - cut.forEach(block) - group.leave() - } - } - group.wait() - } -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/Ext+BlockNode.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/Ext+BlockNode.swift deleted file mode 100644 index 7767ad2af..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/Ext+BlockNode.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// Ext+BlockNode.swift -// MarkdownView -// -// Created by 秋星桥 on 2025/1/3. -// - -import Foundation -import MarkdownParser - -public extension BlockNode { - var manifestType: BlockManifest.Type { - switch self { - case .blockquote: - BlockquoteView.Manifest.self - case .bulletedList: - BulletedListView.Manifest.self - case .numberedList: - NumberedListView.Manifest.self - case .taskList: - TaskListView.Manifest.self - case .codeBlock: - CodeBlockView.Manifest.self -// case .htmlBlock: -// HTMLBlockView.Manifest.self - case .paragraph: - ParagraphView.Manifest.self - case .heading: - HeadingView.Manifest.self - case .table: - TableView.Manifest.self - case .thematicBreak: - ThematicBreakView.Manifest.self - } - } - - func manifest(theme: Theme) -> AnyBlockManifest { - let object = manifestType.init() - object.setLayoutTheme(theme) - object.load(block: self) - return object - } -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/Ext+DispatchQueue.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/Ext+DispatchQueue.swift deleted file mode 100644 index 92fe2432b..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/Ext+DispatchQueue.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// Ext+DispatchQueue.swift -// MarkdownView -// -// Created by 秋星桥 on 2025/1/3. -// - -import Foundation - -public extension DispatchQueue { - static func isCurrent(_ queue: DispatchQueue) -> Bool { - let key = DispatchSpecificKey() - queue.setSpecific(key: key, value: ()) - defer { queue.setSpecific(key: key, value: nil) } - return DispatchQueue.getSpecific(key: key) != nil - } -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/Ext+InlineNode.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/Ext+InlineNode.swift deleted file mode 100644 index b7701bb36..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/Ext+InlineNode.swift +++ /dev/null @@ -1,121 +0,0 @@ -// -// Ext+InlineNode.swift -// MarkdownView -// -// Created by 秋星桥 on 2025/1/3. -// - -import Foundation -import MarkdownParser -import UIKit - -extension [InlineNode] { - func render(theme: Theme) -> NSMutableAttributedString { - let result = NSMutableAttributedString() - for node in self { - result.append(node.render(theme: theme)) - } - return result - } -} - -extension InlineNode { - func render(theme: Theme) -> NSAttributedString { - switch self { - case let .text(string): - return NSAttributedString( - string: string, - attributes: [ - .font: theme.fonts.body, - .foregroundColor: theme.colors.body, - .originalFont: theme.fonts.body, - ] - ) - case .softBreak: - return NSAttributedString(string: " ", attributes: [ - .font: theme.fonts.body, - .foregroundColor: theme.colors.body, - .originalFont: theme.fonts.body, - ]) - case .lineBreak: - return NSAttributedString(string: "\n", attributes: [ - .font: theme.fonts.body, - .foregroundColor: theme.colors.body, - .originalFont: theme.fonts.body, - ]) - case let .code(string): - return NSAttributedString( - string: "\(string)", - attributes: [ - .font: theme.fonts.codeInline, - .originalFont: theme.fonts.codeInline, - .foregroundColor: theme.colors.code, - .backgroundColor: theme.colors.codeBackground.withAlphaComponent(0.05), - ] - ) - case let .html(content): - return NSAttributedString( - string: "\(content)", - attributes: [ - .font: theme.fonts.codeInline, - .originalFont: theme.fonts.codeInline, - .foregroundColor: theme.colors.code, - .backgroundColor: theme.colors.codeBackground.withAlphaComponent(0.05), - ] - ) -// let htmlData = NSString(string: content).data(using: String.Encoding.unicode.rawValue) -// let options = [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.html] -// let ans = try? NSMutableAttributedString( -// data: htmlData ?? Data(), -// options: options, -// documentAttributes: nil -// ) -// return ans ?? .init() - case let .emphasis(children): - let ans = NSMutableAttributedString() - children.map { $0.render(theme: theme) }.forEach { ans.append($0) } - ans.addAttributes( - [ - .underlineStyle: NSUnderlineStyle.thick.rawValue, - .underlineColor: theme.colors.emphasis, - ], - range: NSRange(location: 0, length: ans.length) - ) - return ans - case let .strong(children): - let ans = NSMutableAttributedString() - children.map { $0.render(theme: theme) }.forEach { ans.append($0) } - ans.addAttributes( - [.font: theme.fonts.bold], - range: NSRange(location: 0, length: ans.length) - ) - return ans - case let .strikethrough(children): - let ans = NSMutableAttributedString() - children.map { $0.render(theme: theme) }.forEach { ans.append($0) } - ans.addAttributes( - [.strikethroughStyle: NSUnderlineStyle.thick.rawValue], - range: NSRange(location: 0, length: ans.length) - ) - return ans - case let .link(destination, children): - let ans = NSMutableAttributedString() - children.map { $0.render(theme: theme) }.forEach { ans.append($0) } - ans.addAttributes( - [.link: destination], - range: NSRange(location: 0, length: ans.length) - ) - return ans - case let .image(source, _): // children => alternative text can be ignored? - return NSAttributedString( - string: source, - attributes: [ - .link: source, - .font: theme.fonts.body, - .originalFont: theme.fonts.body, - .foregroundColor: theme.colors.body, - ] - ) - } - } -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/Ext+NSAttributedString.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/Ext+NSAttributedString.swift deleted file mode 100644 index 26f77bc7b..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/Ext+NSAttributedString.swift +++ /dev/null @@ -1,83 +0,0 @@ -// -// Ext+NSAttributedString.swift -// FlowDown -// -// Created by 秋星桥 on 2025/1/2. -// - -import UIKit - -class TextMeasurementHelper { - static let shared = TextMeasurementHelper() - - private var textStorage: NSTextStorage - private var textContainer: NSTextContainer - private var layoutManager: NSLayoutManager - - private let lock = NSLock() - - init() { - textStorage = NSTextStorage() - textContainer = NSTextContainer(size: CGSize(width: CGFloat.infinity, height: CGFloat.infinity)) - layoutManager = NSLayoutManager() - layoutManager.addTextContainer(textContainer) - textStorage.addLayoutManager(layoutManager) - textContainer.lineFragmentPadding = 0 - } - - func measureSize( - of attributedString: NSAttributedString, - usingWidth width: CGFloat, - lineLimit: Int = 0, - lineBreakMode: NSLineBreakMode = .byTruncatingTail - ) -> CGSize { - lock.lock() - defer { lock.unlock() } - - textContainer.size = CGSize(width: width, height: .infinity) - textContainer.maximumNumberOfLines = lineLimit - textContainer.lineBreakMode = lineBreakMode - textStorage.beginEditing() - textStorage.setAttributedString(attributedString) - textStorage.endEditing() - - let size = layoutManager.usedRect(for: textContainer).size - return .init(width: ceil(size.width), height: ceil(size.height)) - } -} - -extension NSAttributedString: @unchecked @retroactive Sendable {} - -public extension NSAttributedString { - func measureWidth() -> CGFloat { - if string.trimmingCharacters(in: .whitespacesAndNewlines).count <= 0 { - return 0 - } - return TextMeasurementHelper.shared.measureSize( - of: self, - usingWidth: .infinity - ).width - } - - func measureHeight( - usingWidth width: CGFloat, - lineLimit: Int = 0, - lineBreakMode: NSLineBreakMode = .byTruncatingTail - ) -> CGFloat { - if string.trimmingCharacters(in: .whitespacesAndNewlines).count <= 0 { - return 0 - } - - return TextMeasurementHelper.shared.measureSize( - of: self, - usingWidth: width, - lineLimit: lineLimit, - lineBreakMode: lineBreakMode - ).height - } -} - -public extension NSAttributedString.Key { - @inline(__always) static let coreTextRunDelegate = NSAttributedString.Key(rawValue: kCTRunDelegateAttributeName as String) - @inline(__always) static let originalFont = NSAttributedString.Key(rawValue: "NSOriginalFont") -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/Ext+UIColor.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/Ext+UIColor.swift deleted file mode 100644 index 8dc4bf622..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/Ext+UIColor.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// Ext+UIColor.swift -// MarkdownView -// -// Created by 秋星桥 on 2025/1/7. -// - -import UIKit - -extension UIColor { - convenience init(light: UIColor, dark: UIColor) { - if #available(iOS 13.0, tvOS 13.0, *) { - self.init(dynamicProvider: { $0.userInterfaceStyle == .dark ? dark : light }) - } else { - self.init(cgColor: light.cgColor) - } - } -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/Ext+UIFont.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/Ext+UIFont.swift deleted file mode 100644 index 574acc045..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/Ext+UIFont.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// Ext+UIFont.swift -// MarkdownView -// -// Created by 秋星桥 on 2025/1/3. -// - -import UIKit - -public extension UIFont { - var bold: UIFont { - UIFont(descriptor: fontDescriptor.withSymbolicTraits(.traitBold)!, size: 0) - } - - var italic: UIFont { - UIFont(descriptor: fontDescriptor.withSymbolicTraits(.traitItalic)!, size: 0) - } - - var monospaced: UIFont { - let settings = [[ - UIFontDescriptor.FeatureKey.featureIdentifier: kNumberSpacingType, - UIFontDescriptor.FeatureKey.typeIdentifier: kMonospacedNumbersSelector, - ]] - - let attributes = [UIFontDescriptor.AttributeName.featureSettings: settings] - let newDescriptor = fontDescriptor.addingAttributes(attributes) - return UIFont(descriptor: newDescriptor, size: 0) - } -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/GridView.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/GridView.swift deleted file mode 100644 index f1df4a5b0..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/GridView.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// GridView.swift -// MarkdownView -// -// Created by 秋星桥 on 2025/1/3. -// - -import UIKit - -class GridView: UIView { - var stokeColor: UIColor = .label.withAlphaComponent(0.25) { - didSet { - layer.borderWidth = 1 - layer.borderColor = stokeColor.cgColor - setNeedsDisplay() - } - } - - var lines: [CGPointPair] = [] { - didSet { setNeedsDisplay() } - } - - init() { - super.init(frame: .zero) - backgroundColor = .clear - layer.borderWidth = 1 - layer.borderColor = stokeColor.cgColor - layer.contentsGravity = .top - } - - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError() - } - - override var frame: CGRect { - set { UIView.performWithoutAnimation { super.frame = newValue } } - get { super.frame } - } - - override func draw(_ rect: CGRect) { - super.draw(rect) - - guard let context = UIGraphicsGetCurrentContext() else { return } - - context.setStrokeColor(stokeColor.cgColor) - context.setLineWidth(1) - - for pathPair in lines { - let adjustedStart = CGPoint(x: pathPair.start.x, y: pathPair.start.y) - let adjustedEnd = CGPoint(x: pathPair.end.x, y: pathPair.end.y) - context.move(to: adjustedStart) - context.addLine(to: adjustedEnd) - } - context.strokePath() - } -} - -extension GridView { - struct CGPointPair: Equatable { - let start: CGPoint - let end: CGPoint - } -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/TextLabel.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/TextLabel.swift deleted file mode 100644 index cf252773a..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/TextLabel.swift +++ /dev/null @@ -1,53 +0,0 @@ -// -// TextLabel.swift -// MarkdownView -// -// Created by 秋星桥 on 2025/1/12. -// - -import UIKit - -class TextLabel: UITextView { - #if DEBUG - private var setupCompleted: Bool = false - #endif - - override required init(frame: CGRect, textContainer: NSTextContainer?) { - super.init(frame: frame, textContainer: textContainer) - commitInit() - } - - convenience init(frame: CGRect = .zero) { - if #available(iOS 16.0, macCatalyst 16.0, *) { - self.init(usingTextLayoutManager: false) - } else { - self.init(frame: frame, textContainer: nil) - _ = layoutManager.textContainers - } - commitInit() - } - - func commitInit() { - #if DEBUG - assert(!setupCompleted) - setupCompleted = true - #endif - showsVerticalScrollIndicator = false - showsHorizontalScrollIndicator = false - textColor = .label - textContainer.lineFragmentPadding = .zero - textAlignment = .natural - backgroundColor = .clear - textContainerInset = .zero - textContainer.lineBreakMode = .byTruncatingTail - clipsToBounds = false - isSelectable = true - isScrollEnabled = false - isEditable = false - } - - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/Theme.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/Theme.swift deleted file mode 100644 index 1eddf7898..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Sources/MarkdownView/Supplements/Theme.swift +++ /dev/null @@ -1,121 +0,0 @@ -// -// Theme.swift -// MarkdownView -// -// Created by 秋星桥 on 2025/1/3. -// - -import Foundation -import Splash -import UIKit - -public extension Theme { - static var `default`: Theme = .init() -} - -public struct Theme: Equatable { - public struct Fonts: Equatable { - public var body = UIFont.preferredFont(forTextStyle: .body) - public var codeInline = UIFont.monospacedSystemFont( - ofSize: UIFont.preferredFont(forTextStyle: .body).pointSize, - weight: .regular - ) - public var bold = UIFont.preferredFont(forTextStyle: .body).bold - public var italic = UIFont.preferredFont(forTextStyle: .body).italic - public var code = UIFont.monospacedSystemFont( - ofSize: UIFont.preferredFont(forTextStyle: .body).pointSize * 0.85, - weight: .regular - ) - public var largeTitle = UIFont.preferredFont(forTextStyle: .body).bold - public var title = UIFont.preferredFont(forTextStyle: .body).bold - } - - public var fonts: Fonts = .init() - - public struct Colors: Equatable { - public var body = UIColor.label - public var emphasis = UIColor.systemOrange - public var code = UIColor.label - public var codeBackground = UIColor.gray.withAlphaComponent(0.25) - } - - public var colors: Colors = .init() - - public struct Spacings: Equatable { - public var final: CGFloat = 16 - public var general: CGFloat = 8 - public var list: CGFloat = 12 - public var cell: CGFloat = 32 - } - - public var spacings: Spacings = .init() - - public struct Sizes: Equatable { - public var bullet: CGFloat = 4 - } - - public var sizes: Sizes = .init() - - public init() {} -} - -extension UIFont { - var baseLineHeight: CGFloat { - NSAttributedString(string: "88", attributes: [ - .font: self, - .originalFont: self, - ]).measureHeight(usingWidth: .greatestFiniteMagnitude) - } -} - -private let codeThemeTemplate: Splash.Theme = .init( - font: .init(size: Double(0)), - plainTextColor: .label, - tokenColors: [ - .keyword: Color( - light: Color(red: 0.948, green: 0.140, blue: 0.547, alpha: 1), - dark: Color(red: 0.948, green: 0.140, blue: 0.547, alpha: 1) - ), - .string: Color( - light: Color(red: 0.988, green: 0.273, blue: 0.317, alpha: 1), - dark: Color(red: 0.988, green: 0.273, blue: 0.317, alpha: 1) - ), - .type: Color( - light: Color(red: 0.384, green: 0.698, blue: 0.161, alpha: 1), - dark: Color(red: 0.584, green: 0.898, blue: 0.361, alpha: 1) - ), - .call: Color( - light: Color(red: 0.384, green: 0.698, blue: 0.161, alpha: 1), - dark: Color(red: 0.584, green: 0.898, blue: 0.361, alpha: 1) - ), - .number: Color( - light: Color(red: 0.387, green: 0.317, blue: 0.774, alpha: 1), - dark: Color(red: 0.587, green: 0.517, blue: 0.974, alpha: 1) - ), - .comment: Color( - light: Color(red: 0.424, green: 0.475, blue: 0.529, alpha: 1), - dark: Color(red: 0.424, green: 0.475, blue: 0.529, alpha: 1) - ), - .property: Color( - light: Color(red: 0.384, green: 0.698, blue: 0.161, alpha: 1), - dark: Color(red: 0.584, green: 0.898, blue: 0.361, alpha: 1) - ), - .dotAccess: Color( - light: Color(red: 0.384, green: 0.698, blue: 0.161, alpha: 1), - dark: Color(red: 0.584, green: 0.898, blue: 0.361, alpha: 1) - ), - .preprocessing: Color( - light: Color(red: 0.752, green: 0.326, blue: 0.12, alpha: 19), - dark: Color(red: 0.952, green: 0.526, blue: 0.22, alpha: 19) - ), - ], - backgroundColor: .clear -) - -public extension Theme { - func codeTheme(withFont font: UIFont) -> Splash.Theme { - var ret = codeThemeTemplate - ret.font = .init(size: Double(font.pointSize)) - return ret - } -} diff --git a/packages/frontend/apps/ios/App/Packages/MarkdownView/Tests/FlowMarkdownViewTests/FlowMarkdownViewTests.swift b/packages/frontend/apps/ios/App/Packages/MarkdownView/Tests/FlowMarkdownViewTests/FlowMarkdownViewTests.swift deleted file mode 100644 index 451d56e58..000000000 --- a/packages/frontend/apps/ios/App/Packages/MarkdownView/Tests/FlowMarkdownViewTests/FlowMarkdownViewTests.swift +++ /dev/null @@ -1,6 +0,0 @@ -@testable import FlowMarkdownView -import Testing - -@Test func example() async throws { - // Write your test here and use APIs like `#expect(...)` to check expected conditions. -} diff --git a/packages/frontend/apps/ios/codegen.ts b/packages/frontend/apps/ios/codegen.ts index 6cadbb169..c470578ae 100644 --- a/packages/frontend/apps/ios/codegen.ts +++ b/packages/frontend/apps/ios/codegen.ts @@ -11,7 +11,7 @@ console.log('[*] PackageRoot', PackageRoot); console.log('[*] graphql...'); execSync( - `${PackageRoot}/App/Packages/AffineGraphQL/apollo-ios-cli generate --path ${PackageRoot}/apollo-codegen-config.json`, + `${PackageRoot}/App/Packages/AffineGraphQL/apollo-ios-cli generate --path ${PackageRoot}/apollo-codegen-config.json --ignore-version-mismatch`, { stdio: 'inherit' } );