mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-25 10:22:55 +08:00
WIP: 嵌入式框架优化调整
This commit is contained in:
@@ -33,7 +33,7 @@ class AFFiNEViewController: CAPBridgeViewController {
|
||||
}
|
||||
}
|
||||
|
||||
extension AFFiNEViewController: IntelligentsButtonDelegate {
|
||||
extension AFFiNEViewController: IntelligentsButtonDelegate, IntelligentsFocusApertureViewDelegate {
|
||||
func onIntelligentsButtonTapped(_ button: IntelligentsButton) {
|
||||
guard let webView else {
|
||||
assertionFailure() // ? wdym ?
|
||||
@@ -56,37 +56,55 @@ extension AFFiNEViewController: IntelligentsButtonDelegate {
|
||||
print("[?] \(self) script error: \(error.localizedDescription)")
|
||||
}
|
||||
|
||||
if case let .success(content) = result,
|
||||
let res = content as? String
|
||||
{
|
||||
print("[*] \(self) received document with \(res.count) characters")
|
||||
DispatchQueue.main.async {
|
||||
self.openIntelligentsSheet(withContext: res)
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
self.openSimpleChat()
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
if case let .success(content) = result,
|
||||
let res = content as? String
|
||||
{
|
||||
print("[*] \(self) received document with \(res.count) characters")
|
||||
DispatchQueue.main.async {
|
||||
self.openIntelligentsSheet(withContext: res)
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
self.openSimpleChat()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func openIntelligentsSheet(withContext context: String) {
|
||||
guard let view = webView else {
|
||||
guard let view = webView?.subviews.first else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
assert(view is UIScrollView)
|
||||
_ = context
|
||||
let focus = IntelligentsFocusApertureView()
|
||||
focus.prepareAnimationWith(
|
||||
capturingTargetContentView: view,
|
||||
coveringRootViewController: self
|
||||
)
|
||||
focus.delegate = self
|
||||
focus.executeAnimationKickIn()
|
||||
dismissIntelligentsButton()
|
||||
}
|
||||
|
||||
func openSimpleChat() {
|
||||
let targetController = IntelligentsChatController()
|
||||
presentIntoCurrentContext(withTargetController: targetController)
|
||||
}
|
||||
|
||||
func focusApertureRequestAction(actionType: IntelligentsFocusApertureViewActionType) {
|
||||
switch actionType {
|
||||
case .translateTo:
|
||||
fatalError("not implemented")
|
||||
case .summary:
|
||||
fatalError("not implemented")
|
||||
case .chatWithAI:
|
||||
fatalError("not implemented")
|
||||
case .dismiss:
|
||||
presentIntelligentsButton()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,15 @@ extension UIView {
|
||||
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
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// IntelligentsFocusApertureView+ActionButton.swift
|
||||
// Intelligents
|
||||
//
|
||||
// Created by 秋星桥 on 2024/11/21.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension IntelligentsFocusApertureView.ControlButtonsPanel {
|
||||
class DarkActionButton: UIView {
|
||||
var iconSystemName: String {
|
||||
set { iconView.image = UIImage(systemName: newValue) }
|
||||
get { fatalError() }
|
||||
}
|
||||
|
||||
var title: String {
|
||||
set { titleLabel.text = newValue }
|
||||
get { titleLabel.text ?? "" }
|
||||
}
|
||||
|
||||
let titleLabel = UILabel()
|
||||
let iconView = UIImageView()
|
||||
var action: (() -> Void)? = nil
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
backgroundColor = .white.withAlphaComponent(0.25)
|
||||
layer.cornerRadius = 12
|
||||
|
||||
let layoutGuide = UILayoutGuide()
|
||||
addLayoutGuide(layoutGuide)
|
||||
|
||||
titleLabel.textAlignment = .center
|
||||
titleLabel.font = .systemFont(ofSize: UIFont.labelFontSize, weight: .semibold)
|
||||
titleLabel.textColor = .white
|
||||
addSubview(titleLabel)
|
||||
|
||||
iconView.contentMode = .scaleAspectFit
|
||||
iconView.tintColor = .white
|
||||
addSubview(iconView)
|
||||
|
||||
[
|
||||
layoutGuide.centerXAnchor.constraint(equalTo: centerXAnchor),
|
||||
layoutGuide.centerYAnchor.constraint(equalTo: centerYAnchor),
|
||||
|
||||
iconView.topAnchor.constraint(greaterThanOrEqualTo: layoutGuide.topAnchor),
|
||||
iconView.leadingAnchor.constraint(equalTo: layoutGuide.leadingAnchor),
|
||||
iconView.bottomAnchor.constraint(lessThanOrEqualTo: layoutGuide.bottomAnchor),
|
||||
iconView.centerYAnchor.constraint(equalTo: layoutGuide.centerYAnchor),
|
||||
|
||||
titleLabel.topAnchor.constraint(greaterThanOrEqualTo: layoutGuide.topAnchor),
|
||||
titleLabel.trailingAnchor.constraint(equalTo: layoutGuide.trailingAnchor),
|
||||
titleLabel.bottomAnchor.constraint(lessThanOrEqualTo: layoutGuide.bottomAnchor),
|
||||
titleLabel.centerYAnchor.constraint(equalTo: layoutGuide.centerYAnchor),
|
||||
|
||||
titleLabel.leadingAnchor.constraint(equalTo: iconView.trailingAnchor, constant: 8),
|
||||
].forEach { $0.isActive = true }
|
||||
|
||||
isUserInteractionEnabled = true
|
||||
addGestureRecognizer(UITapGestureRecognizer(
|
||||
target: self,
|
||||
action: #selector(onTapped)
|
||||
))
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder _: NSCoder) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
@objc func onTapped() {
|
||||
action?()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// 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(actionType: IntelligentsFocusApertureViewActionType)
|
||||
}
|
||||
@@ -9,11 +9,102 @@ 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)
|
||||
backgroundColor = .red
|
||||
defer { removeEveryAutoResizingMasks() }
|
||||
|
||||
heightAnchor.constraint(equalToConstant: 256).isActive = true
|
||||
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 = Constant.affineTintColor
|
||||
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)
|
||||
|
||||
@@ -25,34 +25,44 @@ public class IntelligentsFocusApertureView: UIView {
|
||||
var contentBeginConstraints: [NSLayoutConstraint] = []
|
||||
var contentFinalConstraints: [NSLayoutConstraint] = []
|
||||
|
||||
public weak var delegate: (any IntelligentsFocusApertureViewDelegate)?
|
||||
|
||||
public init() {
|
||||
super.init(frame: .zero)
|
||||
|
||||
let tap = UITapGestureRecognizer(
|
||||
target: self,
|
||||
action: #selector(dismissFocus)
|
||||
)
|
||||
|
||||
backgroundView.backgroundColor = .black
|
||||
backgroundView.isUserInteractionEnabled = true
|
||||
backgroundView.addGestureRecognizer(tap)
|
||||
backgroundView.addGestureRecognizer(UITapGestureRecognizer(
|
||||
target: self,
|
||||
action: #selector(dismissFocus)
|
||||
))
|
||||
|
||||
snapshotView.setContentHuggingPriority(.defaultLow, for: .vertical)
|
||||
snapshotView.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
|
||||
snapshotView.layer.contentsGravity = .top
|
||||
snapshotView.layer.masksToBounds = true
|
||||
snapshotView.contentMode = .scaleAspectFill
|
||||
snapshotView.isUserInteractionEnabled = true
|
||||
snapshotView.addGestureRecognizer(tap)
|
||||
snapshotView.addGestureRecognizer(UITapGestureRecognizer(
|
||||
target: self,
|
||||
action: #selector(dismissFocus)
|
||||
))
|
||||
|
||||
addSubview(backgroundView)
|
||||
addSubview(controlButtonsPanel)
|
||||
addSubview(snapshotView)
|
||||
bringSubviewToFront(snapshotView)
|
||||
|
||||
var views: [UIView] = [self]
|
||||
while let view = views.first {
|
||||
views.removeFirst()
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.subviews.forEach { views.append($0) }
|
||||
controlButtonsPanel.translateButton.action = { [weak self] in
|
||||
self?.delegate?.focusApertureRequestAction(actionType: .translateTo)
|
||||
}
|
||||
controlButtonsPanel.summaryButton.action = { [weak self] in
|
||||
self?.delegate?.focusApertureRequestAction(actionType: .summary)
|
||||
}
|
||||
controlButtonsPanel.chatWithAIButton.action = { [weak self] in
|
||||
self?.delegate?.focusApertureRequestAction(actionType: .chatWithAI)
|
||||
}
|
||||
removeEveryAutoResizingMasks()
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
@@ -112,6 +122,7 @@ public class IntelligentsFocusApertureView: UIView {
|
||||
isUserInteractionEnabled = false
|
||||
executeAnimationDismiss {
|
||||
self.removeFromSuperview()
|
||||
self.delegate?.focusApertureRequestAction(actionType: .dismiss)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,5 +9,14 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Chat with AI" = "Chat with AI";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"AFFiNE AI" = "AFFiNE AI";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Translate" = "Translate";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Summary" = "Summary";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Summarize this article for me..." = "Summarize this article for me...";
|
||||
|
||||
@@ -9,5 +9,14 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Chat with AI" = "与 AI 聊天";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"AFFiNE AI" = "AFFiNE 人工智能";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Translate" = "翻译";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Summary" = "总结";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Summarize this article for me..." = "请为我总结这份文档...";
|
||||
|
||||
Reference in New Issue
Block a user