diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Package.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Package.swift index 9cdb8cc65d..76dd137cf9 100644 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Package.swift +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Package.swift @@ -26,6 +26,8 @@ let package = Package( "Then", .product(name: "Apollo", package: "apollo-ios"), .product(name: "OrderedCollections", package: "swift-collections"), + ], resources: [ + .process("Interface/View/InputBox/InputBox.xcassets") ]), ] ) 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 index c95de6cfd8..0698ce2c02 100644 --- 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 @@ -9,6 +9,10 @@ class MainViewController: UIViewController { $0.delegate = self } + private lazy var inputBox = InputBox().then { + $0.delegate = self + } + // MARK: - Lifecycle override func viewDidLoad() { @@ -32,11 +36,17 @@ class MainViewController: UIViewController { view.backgroundColor = .systemBackground 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) + } } } @@ -57,3 +67,32 @@ extension MainViewController: MainHeaderViewDelegate { print("Menu tapped") } } + +// MARK: - InputBoxDelegate + +extension MainViewController: InputBoxDelegate { + func inputBoxDidTapAddAttachment() { + + } + + func inputBoxDidTapTool() { + + } + + func inputBoxDidTapNetwork() { + + } + + func inputBoxDidTapDeepThinking() { + + } + + func inputBoxDidTapSend() { + + } + + func inputBoxTextDidChange(_ text: String) { + + } + +} 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 0000000000..dbb20ae53f --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.swift @@ -0,0 +1,239 @@ +import SnapKit +import Then +import UIKit + +protocol InputBoxDelegate: AnyObject { + func inputBoxDidTapAddAttachment() + func inputBoxDidTapTool() + func inputBoxDidTapNetwork() + func inputBoxDidTapDeepThinking() + func inputBoxDidTapSend() + func inputBoxTextDidChange(_ text: String) +} + +class InputBox: UIView { + weak var delegate: InputBoxDelegate? + + private lazy var containerView = UIView().then { + $0.backgroundColor = .systemBackground + $0.layer.cornerRadius = 12 + $0.layer.borderWidth = 0.5 + $0.layer.borderColor = UIColor.systemGray4.cgColor + $0.layer.shadowColor = UIColor.black.cgColor + $0.layer.shadowOffset = CGSize(width: 0, height: 2) + $0.layer.shadowRadius = 6 + $0.layer.shadowOpacity = 0.04 + $0.clipsToBounds = false + } + + private lazy var textView = UITextView().then { + $0.backgroundColor = .clear + $0.font = .systemFont(ofSize: 16) + $0.textColor = .label + $0.isScrollEnabled = false + $0.textContainer.lineFragmentPadding = 0 + $0.textContainerInset = .zero + $0.delegate = self + $0.text = "This is AFFiNE AI" + } + + private lazy var placeholderLabel = UILabel().then { + $0.text = "Write your message..." + $0.font = .systemFont(ofSize: 16) + $0.textColor = .systemGray3 + $0.isHidden = true + } + + private lazy var addButton = UIButton(type: .system).then { + $0.backgroundColor = .systemBackground + $0.layer.cornerRadius = 6 + $0.layer.borderWidth = 0.5 + $0.layer.borderColor = UIColor.systemGray4.cgColor + $0.setImage(UIImage(named: "inputbox.add.attachment", in: .module, with: nil), for: .normal) + $0.tintColor = .secondaryLabel + $0.imageView?.contentMode = .scaleAspectFit + $0.addTarget(self, action: #selector(addButtonTapped), for: .touchUpInside) + } + + private lazy var toolButton = UIButton(type: .system).then { + $0.setImage(UIImage(named: "inputbox.tool", in: .module, with: nil), for: .normal) + $0.tintColor = .secondaryLabel + $0.imageView?.contentMode = .scaleAspectFit + $0.addTarget(self, action: #selector(toolButtonTapped), for: .touchUpInside) + } + + private lazy var webButton = UIButton(type: .system).then { + $0.setImage(UIImage(named: "inputbox.network", in: .module, with: nil), for: .normal) + $0.tintColor = .secondaryLabel + $0.imageView?.contentMode = .scaleAspectFit + $0.addTarget(self, action: #selector(webButtonTapped), for: .touchUpInside) + } + + private lazy var reactButton = UIButton(type: .system).then { + $0.setImage(UIImage(named: "inputbox.deep.thinking", in: .module, with: nil), for: .normal) + $0.tintColor = .secondaryLabel + $0.imageView?.contentMode = .scaleAspectFit + $0.addTarget(self, action: #selector(reactButtonTapped), for: .touchUpInside) + } + + private lazy var sendButton = UIButton(type: .system).then { + $0.backgroundColor = UIColor.systemBlue + $0.layer.cornerRadius = 19 + $0.setImage(UIImage(named: "inputbox.send", in: .module, with: nil), for: .normal) + $0.tintColor = .white + $0.imageView?.contentMode = .scaleAspectFit + $0.addTarget(self, action: #selector(sendButtonTapped), for: .touchUpInside) + } + + private lazy var leftButtonsStackView = UIStackView().then { + $0.axis = .horizontal + $0.spacing = 16 + $0.alignment = .center + $0.addArrangedSubview(addButton) + } + + private lazy var rightButtonsStackView = UIStackView().then { + $0.axis = .horizontal + $0.spacing = 16 + $0.alignment = .center + $0.addArrangedSubview(toolButton) + $0.addArrangedSubview(webButton) + $0.addArrangedSubview(reactButton) + $0.addArrangedSubview(sendButton) + } + + private lazy var functionsStackView = UIStackView().then { + $0.axis = .horizontal + $0.spacing = 12 + $0.alignment = .center + $0.addArrangedSubview(leftButtonsStackView) + $0.addArrangedSubview(UIView()) // spacer + $0.addArrangedSubview(rightButtonsStackView) + } + + private lazy var mainStackView = UIStackView().then { + $0.axis = .vertical + $0.spacing = 16 + $0.alignment = .fill + $0.addArrangedSubview(textView) + $0.addArrangedSubview(functionsStackView) + } + + private var textViewHeightConstraint: Constraint? + private let minTextViewHeight: CGFloat = 22 + private let maxTextViewHeight: CGFloat = 100 + + var text: String { + get { textView.text ?? "" } + set { + textView.text = newValue + updatePlaceholderVisibility() + updateTextViewHeight() + } + } + + init() { + super.init(frame: .zero) + setupViews() + setupConstraints() + updatePlaceholderVisibility() + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupViews() { + backgroundColor = .clear + addSubview(containerView) + containerView.addSubview(mainStackView) + containerView.addSubview(placeholderLabel) + } + + private func setupConstraints() { + containerView.snp.makeConstraints { make in + make.edges.equalToSuperview().inset(16) + } + + mainStackView.snp.makeConstraints { make in + make.edges.equalToSuperview().inset(16) + } + + addButton.snp.makeConstraints { make in + make.size.equalTo(38) + } + + for button in [toolButton, webButton, reactButton] { + button.snp.makeConstraints { make in + make.size.equalTo(24) + } + } + + sendButton.snp.makeConstraints { make in + make.size.equalTo(38) + } + + 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) + } + } + + private func updateTextViewHeight() { + let size = textView.sizeThatFits(CGSize(width: textView.frame.width, height: CGFloat.greatestFiniteMagnitude)) + let newHeight = max(minTextViewHeight, min(maxTextViewHeight, size.height)) + + textViewHeightConstraint?.update(offset: newHeight) + textView.isScrollEnabled = size.height > maxTextViewHeight + + UIView.animate( + withDuration: 0.5, + delay: 0, + usingSpringWithDamping: 0.8, + initialSpringVelocity: 1.0, + options: [.curveEaseInOut] + ) { + self.layoutIfNeeded() + self.superview?.layoutIfNeeded() + } + } + + private func updatePlaceholderVisibility() { + placeholderLabel.isHidden = !textView.text.isEmpty + } + + @objc private func addButtonTapped() { + delegate?.inputBoxDidTapAddAttachment() + } + + @objc private func toolButtonTapped() { + delegate?.inputBoxDidTapTool() + } + + @objc private func webButtonTapped() { + delegate?.inputBoxDidTapNetwork() + } + + @objc private func reactButtonTapped() { + delegate?.inputBoxDidTapDeepThinking() + } + + @objc private func sendButtonTapped() { + delegate?.inputBoxDidTapSend() + } +} + +// MARK: - UITextViewDelegate + +extension InputBox: UITextViewDelegate { + func textViewDidChange(_ textView: UITextView) { + updatePlaceholderVisibility() + updateTextViewHeight() + delegate?.inputBoxTextDidChange(textView.text) + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.xcassets/Contents.json b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.xcassets/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.xcassets/inputbox.add.attachment.imageset/Contents.json b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.xcassets/inputbox.add.attachment.imageset/Contents.json new file mode 100644 index 0000000000..a56be13406 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.xcassets/inputbox.add.attachment.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "inputbox.add.attachment.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.xcassets/inputbox.add.attachment.imageset/inputbox.add.attachment.png b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.xcassets/inputbox.add.attachment.imageset/inputbox.add.attachment.png new file mode 100644 index 0000000000..7140de8b25 Binary files /dev/null and b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.xcassets/inputbox.add.attachment.imageset/inputbox.add.attachment.png differ diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.xcassets/inputbox.deep.thinking.imageset/Contents.json b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.xcassets/inputbox.deep.thinking.imageset/Contents.json new file mode 100644 index 0000000000..a6380ac93e --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.xcassets/inputbox.deep.thinking.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "inputbox.deep.thinking.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.xcassets/inputbox.deep.thinking.imageset/inputbox.deep.thinking.png b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.xcassets/inputbox.deep.thinking.imageset/inputbox.deep.thinking.png new file mode 100644 index 0000000000..a785290da4 Binary files /dev/null and b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.xcassets/inputbox.deep.thinking.imageset/inputbox.deep.thinking.png differ diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.xcassets/inputbox.network.imageset/Contents.json b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.xcassets/inputbox.network.imageset/Contents.json new file mode 100644 index 0000000000..27d93a81ab --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.xcassets/inputbox.network.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "inputbox.network.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.xcassets/inputbox.network.imageset/inputbox.network.png b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.xcassets/inputbox.network.imageset/inputbox.network.png new file mode 100644 index 0000000000..9b9c847776 Binary files /dev/null and b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.xcassets/inputbox.network.imageset/inputbox.network.png differ diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.xcassets/inputbox.send.imageset/Contents.json b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.xcassets/inputbox.send.imageset/Contents.json new file mode 100644 index 0000000000..4ca7e2cd22 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.xcassets/inputbox.send.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "inputbox.send.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.xcassets/inputbox.send.imageset/inputbox.send.png b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.xcassets/inputbox.send.imageset/inputbox.send.png new file mode 100644 index 0000000000..163362c45b Binary files /dev/null and b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.xcassets/inputbox.send.imageset/inputbox.send.png differ diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.xcassets/inputbox.tool.imageset/Contents.json b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.xcassets/inputbox.tool.imageset/Contents.json new file mode 100644 index 0000000000..423f9747c8 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.xcassets/inputbox.tool.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "inputbox.tool.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.xcassets/inputbox.tool.imageset/inputbox.tool.png b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.xcassets/inputbox.tool.imageset/inputbox.tool.png new file mode 100644 index 0000000000..470799177b Binary files /dev/null and b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.xcassets/inputbox.tool.imageset/inputbox.tool.png differ 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 index a911c6121b..195cfd666e 100644 --- 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 @@ -9,9 +9,8 @@ protocol MainHeaderViewDelegate: AnyObject { } class MainHeaderView: UIView { - weak var delegate: MainHeaderViewDelegate? - + private lazy var closeButton = UIButton(type: .system).then { $0.imageView?.contentMode = .scaleAspectFit $0.setImage(UIImage(systemName: "xmark"), for: .normal) @@ -19,21 +18,21 @@ class MainHeaderView: UIView { $0.addTarget(self, action: #selector(closeButtonTapped), for: .touchUpInside) $0.setContentHuggingPriority(.required, for: .horizontal) } - + private lazy var titleLabel = UILabel().then { $0.text = "AFFiNE" $0.font = .systemFont(ofSize: 16, weight: .medium) $0.textColor = .black $0.textAlignment = .center } - + private lazy var dropdownButton = UIButton(type: .system).then { $0.imageView?.contentMode = .scaleAspectFit $0.setImage(UIImage(systemName: "chevron.down"), for: .normal) $0.tintColor = .systemGray $0.addTarget(self, action: #selector(dropdownButtonTapped), for: .touchUpInside) } - + private lazy var centerStackView = UIStackView().then { $0.axis = .horizontal $0.spacing = 8 @@ -41,7 +40,7 @@ class MainHeaderView: UIView { $0.addArrangedSubview(titleLabel) $0.addArrangedSubview(dropdownButton) } - + private lazy var menuButton = UIButton(type: .system).then { $0.imageView?.contentMode = .scaleAspectFit $0.setImage(UIImage(systemName: "ellipsis"), for: .normal) @@ -49,11 +48,11 @@ class MainHeaderView: UIView { $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 @@ -65,53 +64,53 @@ class MainHeaderView: UIView { $0.addArrangedSubview(rightSpacerView) $0.addArrangedSubview(menuButton) } - + init() { super.init(frame: .zero) - + backgroundColor = .white 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) } - + menuButton.snp.makeConstraints { make in make.size.equalTo(titleLabel.font.pointSize) } - + dropdownButton.snp.makeConstraints { make in make.size.equalTo(titleLabel.font.pointSize) } - + // 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() }