mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-17 01:56:27 +08:00
WIP: 打个框架先
This commit is contained in:
@@ -7,10 +7,12 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
505B0A342CEB3FB10092FC35 /* Intelligents in Frameworks */ = {isa = PBXBuildFile; productRef = 505B0A332CEB3FB10092FC35 /* Intelligents */; };
|
||||
505B0A362CEB48B10092FC35 /* RootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505B0A352CEB48B10092FC35 /* RootViewController.swift */; };
|
||||
9D6A85332CCF6DA700DAB35F /* HashcashPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D6A85322CCF6DA700DAB35F /* HashcashPlugin.swift */; };
|
||||
9D90BE252CCB9876006677DB /* CookieManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D90BE172CCB9876006677DB /* CookieManager.swift */; };
|
||||
9D90BE262CCB9876006677DB /* CookiePlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D90BE182CCB9876006677DB /* CookiePlugin.swift */; };
|
||||
9D90BE272CCB9876006677DB /* AFFiNEViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D90BE1B2CCB9876006677DB /* AFFiNEViewController.swift */; };
|
||||
9D90BE272CCB9876006677DB /* AffineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D90BE1B2CCB9876006677DB /* AffineViewController.swift */; };
|
||||
9D90BE282CCB9876006677DB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D90BE1C2CCB9876006677DB /* AppDelegate.swift */; };
|
||||
9D90BE292CCB9876006677DB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9D90BE1D2CCB9876006677DB /* Assets.xcassets */; };
|
||||
9D90BE2A2CCB9876006677DB /* capacitor.config.json in Resources */ = {isa = PBXBuildFile; fileRef = 9D90BE1E2CCB9876006677DB /* capacitor.config.json */; };
|
||||
@@ -22,10 +24,12 @@
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
504EC3041FED79650016851F /* App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = App.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
505B0A312CEB3FAB0092FC35 /* Intelligents */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Intelligents; sourceTree = "<group>"; };
|
||||
505B0A352CEB48B10092FC35 /* RootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootViewController.swift; sourceTree = "<group>"; };
|
||||
9D6A85322CCF6DA700DAB35F /* HashcashPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashcashPlugin.swift; sourceTree = "<group>"; };
|
||||
9D90BE172CCB9876006677DB /* CookieManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CookieManager.swift; sourceTree = "<group>"; };
|
||||
9D90BE182CCB9876006677DB /* CookiePlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CookiePlugin.swift; sourceTree = "<group>"; };
|
||||
9D90BE1B2CCB9876006677DB /* AFFiNEViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AFFiNEViewController.swift; sourceTree = "<group>"; };
|
||||
9D90BE1B2CCB9876006677DB /* AffineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AffineViewController.swift; sourceTree = "<group>"; };
|
||||
9D90BE1C2CCB9876006677DB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
9D90BE1D2CCB9876006677DB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
9D90BE1E2CCB9876006677DB /* capacitor.config.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = capacitor.config.json; sourceTree = "<group>"; };
|
||||
@@ -43,6 +47,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
505B0A342CEB3FB10092FC35 /* Intelligents in Frameworks */,
|
||||
C4C413792CBE705D00337889 /* Pods_App.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -65,9 +70,7 @@
|
||||
504EC3051FED79650016851F /* Products */,
|
||||
7F8756D8B27F46E3366F6CEA /* Pods */,
|
||||
27E2DDA53C4D2A4D1A88CE4A /* Frameworks */,
|
||||
9D6A85312CCF6D6B00DAB35F /* Recovered References */,
|
||||
);
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
tabWidth = 2;
|
||||
};
|
||||
@@ -79,6 +82,14 @@
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
505B0A322CEB3FAB0092FC35 /* Packages */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
505B0A312CEB3FAB0092FC35 /* Intelligents */,
|
||||
);
|
||||
path = Packages;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7F8756D8B27F46E3366F6CEA /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -98,19 +109,21 @@
|
||||
path = Cookie;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9D90BE1A2CCB9876006677DB /* plugins */ = {
|
||||
9D90BE1A2CCB9876006677DB /* Plugins */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9D90BE192CCB9876006677DB /* Cookie */,
|
||||
);
|
||||
path = plugins;
|
||||
path = Plugins;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9D90BE242CCB9876006677DB /* App */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9D90BE1A2CCB9876006677DB /* plugins */,
|
||||
9D90BE1B2CCB9876006677DB /* AFFiNEViewController.swift */,
|
||||
505B0A322CEB3FAB0092FC35 /* Packages */,
|
||||
9D90BE1A2CCB9876006677DB /* Plugins */,
|
||||
9D90BE1B2CCB9876006677DB /* AffineViewController.swift */,
|
||||
505B0A352CEB48B10092FC35 /* RootViewController.swift */,
|
||||
9D90BE1C2CCB9876006677DB /* AppDelegate.swift */,
|
||||
9D90BE1D2CCB9876006677DB /* Assets.xcassets */,
|
||||
9D90BE1E2CCB9876006677DB /* capacitor.config.json */,
|
||||
@@ -236,8 +249,9 @@
|
||||
files = (
|
||||
9D90BE252CCB9876006677DB /* CookieManager.swift in Sources */,
|
||||
9D90BE262CCB9876006677DB /* CookiePlugin.swift in Sources */,
|
||||
505B0A362CEB48B10092FC35 /* RootViewController.swift in Sources */,
|
||||
9D6A85332CCF6DA700DAB35F /* HashcashPlugin.swift in Sources */,
|
||||
9D90BE272CCB9876006677DB /* AFFiNEViewController.swift in Sources */,
|
||||
9D90BE272CCB9876006677DB /* AffineViewController.swift in Sources */,
|
||||
9D90BE282CCB9876006677DB /* AppDelegate.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -384,7 +398,7 @@
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 73YMMDVT2M;
|
||||
INFOPLIST_FILE = App/Info.plist;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@@ -414,7 +428,7 @@
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 73YMMDVT2M;
|
||||
INFOPLIST_FILE = App/Info.plist;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@@ -454,6 +468,13 @@
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
505B0A332CEB3FB10092FC35 /* Intelligents */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Intelligents;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = 504EC2FC1FED79650016851F /* Project object */;
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import UIKit
|
||||
import Capacitor
|
||||
|
||||
class AFFiNEViewController: CAPBridgeViewController {
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
webView?.allowsBackForwardNavigationGestures = true
|
||||
}
|
||||
|
||||
override func capacitorDidLoad() {
|
||||
bridge?.registerPluginInstance(CookiePlugin())
|
||||
bridge?.registerPluginInstance(HashcashPlugin())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import UIKit
|
||||
import Capacitor
|
||||
import Intelligents
|
||||
|
||||
class AFFiNEViewController: CAPBridgeViewController {
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
webView?.allowsBackForwardNavigationGestures = true
|
||||
self.navigationController?.navigationBar.isHidden = true
|
||||
installIntelligentsButton()
|
||||
}
|
||||
|
||||
override func capacitorDidLoad() {
|
||||
bridge?.registerPluginInstance(CookiePlugin())
|
||||
bridge?.registerPluginInstance(HashcashPlugin())
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
navigationController?.setNavigationBarHidden(false, animated: animated)
|
||||
self.presentIntelligentsButton()
|
||||
}
|
||||
|
||||
override func viewDidDisappear(_ animated: Bool) {
|
||||
super.viewDidDisappear(animated)
|
||||
dismissIntelligentsButton()
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23094" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23084"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--FiNE View Controller-->
|
||||
<!--Root View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="AFFiNEViewController" customModule="App" customModuleProvider="target" sceneMemberID="viewController"/>
|
||||
<viewController id="BYZ-38-t0r" customClass="RootViewController" customModule="App" customModuleProvider="target" sceneMemberID="viewController"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="140" y="-1"/>
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleAllowMixedLocalizations</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/configuration/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
||||
@@ -0,0 +1,19 @@
|
||||
// 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: "Intelligents",
|
||||
defaultLocalization: "en",
|
||||
platforms: [
|
||||
.iOS(.v14),
|
||||
.macCatalyst(.v14),
|
||||
],
|
||||
products: [
|
||||
.library(name: "Intelligents", targets: ["Intelligents"]),
|
||||
],
|
||||
targets: [
|
||||
.target(name: "Intelligents"),
|
||||
]
|
||||
)
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// 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
|
||||
}
|
||||
|
||||
#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
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// Ext+UIViewController.swift
|
||||
// Intelligents
|
||||
//
|
||||
// Created by 秋星桥 on 2024/11/18.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// 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
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
// The Swift Programming Language
|
||||
// https://docs.swift.org/swift-book
|
||||
|
||||
enum Intelligents {}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
//
|
||||
// IntelligentsButton+Control.swift
|
||||
// Intelligents
|
||||
//
|
||||
// Created by 秋星桥 on 2024/11/18.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
public extension UIViewController {
|
||||
@discardableResult
|
||||
func installIntelligentsButton() -> IntelligentsButton {
|
||||
print("[*] \(#function)")
|
||||
if let button = findIntelligentsButton() { return button }
|
||||
|
||||
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.transform = .init(scaleX: 0, y: 0)
|
||||
view.layoutIfNeeded()
|
||||
return button
|
||||
}
|
||||
|
||||
private func findIntelligentsButton() -> IntelligentsButton? {
|
||||
for subview in view.subviews { // for for depth 1
|
||||
if let button = subview as? IntelligentsButton {
|
||||
return button
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func presentIntelligentsButton() {
|
||||
guard let button = findIntelligentsButton() else { return }
|
||||
print("[*] \(button) is calling \(#function)")
|
||||
|
||||
button.alpha = 0
|
||||
button.isHidden = false
|
||||
button.setNeedsLayout()
|
||||
view.layoutIfNeeded()
|
||||
|
||||
UIView.animate(
|
||||
withDuration: 0.5,
|
||||
delay: 0,
|
||||
usingSpringWithDamping: 1.0,
|
||||
initialSpringVelocity: 0.8
|
||||
) {
|
||||
button.alpha = 1
|
||||
button.transform = .identity
|
||||
button.setNeedsLayout()
|
||||
self.view.layoutIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
func dismissIntelligentsButton() {
|
||||
guard let button = findIntelligentsButton() else { return }
|
||||
print("[*] \(button) is calling \(#function)")
|
||||
|
||||
button.setNeedsLayout()
|
||||
view.layoutIfNeeded()
|
||||
UIView.animate(
|
||||
withDuration: 0.5,
|
||||
delay: 0,
|
||||
usingSpringWithDamping: 1.0,
|
||||
initialSpringVelocity: 0.8
|
||||
) {
|
||||
button.alpha = 0
|
||||
button.transform = .init(scaleX: 0, y: 0)
|
||||
button.setNeedsLayout()
|
||||
self.view.layoutIfNeeded()
|
||||
} completion: { _ in
|
||||
button.isHidden = true
|
||||
}
|
||||
}
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
//
|
||||
// IntelligentsButton.swift
|
||||
//
|
||||
//
|
||||
// Created by 秋星桥 on 2024/11/18.
|
||||
//
|
||||
|
||||
import ColorfulX
|
||||
import UIKit
|
||||
|
||||
// floating button to open intelligent panel
|
||||
public class IntelligentsButton: UIView {
|
||||
let image = UIImageView()
|
||||
let background = 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 = Constant.affineTintColor
|
||||
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 }
|
||||
|
||||
// layer.shadowColor = UIColor.black.withAlphaComponent(0.1).cgColor
|
||||
// layer.shadowOffset = CGSize(width: 0, height: 0)
|
||||
// layer.shadowRadius = 8
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder _: NSCoder) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
override public func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
layer.cornerRadius = bounds.width / 2
|
||||
}
|
||||
|
||||
@objc func tapped() {
|
||||
guard let controller = parentViewController else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
let targetController = IntelligentsChatController()
|
||||
controller.presentIntoCurrentContext(withTargetController: targetController)
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// ChatTableView+BaseCell.swift
|
||||
// Intelligents
|
||||
//
|
||||
// Created by 秋星桥 on 2024/11/18.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension ChatTableView {
|
||||
class BaseCell: UITableViewCell {
|
||||
var inset: UIEdgeInsets = .zero {
|
||||
didSet { setNeedsLayout() }
|
||||
}
|
||||
|
||||
let containerView = UIView()
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
selectionStyle = .none
|
||||
backgroundColor = .clear
|
||||
contentView.addSubview(containerView)
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder _: NSCoder) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
containerView.frame = contentView.bounds.inset(by: inset)
|
||||
}
|
||||
|
||||
func update(via _: AnyObject?) {
|
||||
assertionFailure() // "should be override"
|
||||
}
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// ChatTableView+Data.swift
|
||||
// Intelligents
|
||||
//
|
||||
// Created by 秋星桥 on 2024/11/18.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension ChatTableView {
|
||||
struct DataElement {
|
||||
enum CellType: String, CaseIterable {
|
||||
case base
|
||||
}
|
||||
|
||||
let type: CellType
|
||||
let object: AnyObject?
|
||||
|
||||
init(type: CellType, object: AnyObject?) {
|
||||
self.type = type
|
||||
self.object = object
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ChatTableView.DataElement.CellType {
|
||||
var cellClassType: ChatTableView.BaseCell.Type {
|
||||
switch self {
|
||||
case .base:
|
||||
ChatTableView.BaseCell.self
|
||||
}
|
||||
}
|
||||
|
||||
var cellIdentifier: String {
|
||||
NSStringFromClass(cellClassType)
|
||||
}
|
||||
}
|
||||
|
||||
extension ChatTableView: UITableViewDelegate, UITableViewDataSource {
|
||||
func numberOfSections(in _: UITableView) -> Int {
|
||||
1
|
||||
}
|
||||
|
||||
func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
|
||||
dataSource.count
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: dataSource[indexPath.row].type.cellIdentifier, for: indexPath) as! BaseCell
|
||||
let object = dataSource[indexPath.row].object
|
||||
cell.update(via: object)
|
||||
return cell
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// ChatTableView.swift
|
||||
// Intelligents
|
||||
//
|
||||
// Created by 秋星桥 on 2024/11/18.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class ChatTableView: UIView {
|
||||
let tableView = UITableView()
|
||||
|
||||
var dataSource: [DataElement] = []
|
||||
|
||||
init() {
|
||||
super.init(frame: .zero)
|
||||
|
||||
for eachCase in DataElement.CellType.allCases {
|
||||
let cellClass = eachCase.cellClassType
|
||||
tableView.register(cellClass, forCellReuseIdentifier: eachCase.cellIdentifier)
|
||||
}
|
||||
|
||||
tableView.backgroundColor = .clear
|
||||
|
||||
tableView.delegate = self
|
||||
tableView.dataSource = self
|
||||
addSubview(tableView)
|
||||
|
||||
tableView.translatesAutoresizingMaskIntoConstraints = false
|
||||
[
|
||||
tableView.topAnchor.constraint(equalTo: topAnchor),
|
||||
tableView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
tableView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
tableView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||
].forEach { $0.isActive = true }
|
||||
|
||||
let foot = UIView()
|
||||
foot.translatesAutoresizingMaskIntoConstraints = false
|
||||
foot.heightAnchor.constraint(equalToConstant: 200).isActive = true
|
||||
foot.widthAnchor.constraint(equalToConstant: 200).isActive = true
|
||||
tableView.tableFooterView = foot
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder _: NSCoder) {
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
//
|
||||
// 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 attachments: [UIImage] = [] {
|
||||
didSet {
|
||||
rebuildViews()
|
||||
}
|
||||
}
|
||||
|
||||
override var intrinsicContentSize: CGSize {
|
||||
if attachments.isEmpty { return .zero }
|
||||
return .init(
|
||||
width: (attachmentSize + attachmentSize) * CGFloat(attachments.count)
|
||||
- attachmentSpacing,
|
||||
height: attachmentSize
|
||||
)
|
||||
}
|
||||
|
||||
init() {
|
||||
super.init(frame: .zero)
|
||||
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
showsHorizontalScrollIndicator = false
|
||||
showsVerticalScrollIndicator = false
|
||||
|
||||
rebuildViews()
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder _: NSCoder) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func rebuildViews() {
|
||||
subviews.forEach { $0.removeFromSuperview() }
|
||||
for (index, attachment) in attachments.enumerated() {
|
||||
let view = AttachmentPreviewView()
|
||||
view.imageView.image = attachment
|
||||
addSubview(view)
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.frame = .init(
|
||||
origin: .init(
|
||||
x: (attachmentSize + attachmentSpacing) * CGFloat(index),
|
||||
y: 0
|
||||
),
|
||||
size: .init(width: attachmentSize, height: attachmentSize)
|
||||
)
|
||||
}
|
||||
invalidateIntrinsicContentSize()
|
||||
contentSize = intrinsicContentSize
|
||||
}
|
||||
}
|
||||
|
||||
extension AttachmentBannerView {
|
||||
class AttachmentPreviewView: UIView {
|
||||
let imageView = UIImageView()
|
||||
let deleteButton = UIButton()
|
||||
|
||||
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 }
|
||||
|
||||
[
|
||||
widthAnchor.constraint(equalToConstant: attachmentSize),
|
||||
heightAnchor.constraint(equalToConstant: attachmentSize),
|
||||
].forEach { $0.isActive = true }
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder _: NSCoder) {
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// File.swift
|
||||
// Intelligents
|
||||
//
|
||||
// Created by 秋星桥 on 2024/11/18.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Combine
|
||||
|
||||
extension InputEditView {
|
||||
class ViewModel: ObservableObject {
|
||||
var cancellables: Set<AnyCancellable> = []
|
||||
|
||||
@Published var text: String = ""
|
||||
@Published var attachments: [UIImage] = []
|
||||
|
||||
init() {
|
||||
|
||||
}
|
||||
|
||||
deinit {
|
||||
cancellables.forEach { $0.cancel() }
|
||||
cancellables.removeAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
//
|
||||
// InputEditView.swift
|
||||
// Intelligents
|
||||
//
|
||||
// Created by 秋星桥 on 2024/11/18.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Combine
|
||||
|
||||
class InputEditView: UIView, UITextViewDelegate {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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.delegate = self
|
||||
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 }
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder _: NSCoder) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func textViewDidChange(_ textView: UITextView) {
|
||||
viewModel.text = textView.text
|
||||
}
|
||||
|
||||
func textViewDidBeginEditing(_ textView: UITextView) {
|
||||
updatePlaceholderVisibility()
|
||||
}
|
||||
|
||||
func textViewDidEndEditing(_ textView: UITextView) {
|
||||
updatePlaceholderVisibility()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
if attachmentsEditor.attachments != viewModel.attachments {
|
||||
attachmentsEditor.attachments = viewModel.attachments
|
||||
}
|
||||
self.parentViewController?.view.layoutIfNeeded()
|
||||
}
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// TextEditView.swift
|
||||
// Intelligents
|
||||
//
|
||||
// Created by 秋星桥 on 2024/11/18.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class PlainTextEditView: UITextView, UITextViewDelegate {
|
||||
init() {
|
||||
super.init(frame: .zero, textContainer: nil)
|
||||
|
||||
delegate = self
|
||||
tintColor = Constant.affineTintColor
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
//
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
//
|
||||
// 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()
|
||||
}
|
||||
|
||||
@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 = Constant.affineTintColor
|
||||
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 = Constant.affineTintColor
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
//
|
||||
// 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 = .systemBackground
|
||||
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 }
|
||||
}
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// IntelligentsChatController.swift
|
||||
//
|
||||
//
|
||||
// Created by 秋星桥 on 2024/11/18.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class IntelligentsChatController: UIViewController {
|
||||
let header = Header()
|
||||
let inputBox = InputBox()
|
||||
let tableView = ChatTableView()
|
||||
|
||||
override var title: String? {
|
||||
set {
|
||||
super.title = newValue
|
||||
header.titleLabel.text = newValue
|
||||
}
|
||||
get {
|
||||
super.title
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
title = "Chat with AI".localized()
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder _: NSCoder) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
assert(navigationController != nil)
|
||||
view.backgroundColor = .secondarySystemBackground
|
||||
|
||||
hideKeyboardWhenTappedAround()
|
||||
setupLayout()
|
||||
}
|
||||
|
||||
func setupLayout() {
|
||||
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(inputBox)
|
||||
inputBox.translatesAutoresizingMaskIntoConstraints = false
|
||||
[
|
||||
inputBox.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||
inputBox.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||
inputBox.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
|
||||
].forEach { $0.isActive = true }
|
||||
|
||||
view.addSubview(tableView)
|
||||
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 }
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "close.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<svg width="19" height="18" viewBox="0 0 19 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="0.922363" y="0.5" width="17" height="17" rx="8.5" fill="#2A2A2A"/>
|
||||
<rect x="0.922363" y="0.5" width="17" height="17" rx="8.5" stroke="#E6E6E6"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.1572 5.73483C6.30364 5.58839 6.54108 5.58839 6.68753 5.73483L9.42236 8.46967L12.1572 5.73484C12.3036 5.58839 12.5411 5.58839 12.6875 5.73484C12.834 5.88128 12.834 6.11872 12.6875 6.26517L9.95269 9L12.6875 11.7348C12.834 11.8813 12.834 12.1187 12.6875 12.2652C12.5411 12.4116 12.3036 12.4116 12.1572 12.2652L9.42236 9.53033L6.68753 12.2652C6.54108 12.4116 6.30364 12.4116 6.1572 12.2652C6.01075 12.1187 6.01075 11.8813 6.1572 11.7348L8.89203 9L6.1572 6.26516C6.01075 6.11872 6.01075 5.88128 6.1572 5.73483Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 831 B |
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "spark.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M12.1738 5.49104C12.1329 5.13024 11.8278 4.85751 11.4647 4.85714C11.1016 4.85677 10.796 5.12887 10.7544 5.48959C10.4839 7.83515 9.78261 9.48448 8.65126 10.6158C7.51992 11.7472 5.87058 12.4485 3.52502 12.719C3.16431 12.7606 2.89221 13.0662 2.89258 13.4293C2.89295 13.7924 3.16568 14.0975 3.52647 14.1383C5.83327 14.3996 7.51766 15.1006 8.67586 16.2379C9.82971 17.3709 10.5456 19.0198 10.7525 21.3489C10.7853 21.7178 11.0945 22.0004 11.4648 22C11.8351 21.9996 12.1437 21.7162 12.1756 21.3473C12.3739 19.0565 13.0892 17.3729 14.2487 16.2133C15.4083 15.0537 17.092 14.3385 19.3827 14.1402C19.7517 14.1083 20.035 13.7997 20.0354 13.4294C20.0359 13.0591 19.7532 12.7499 19.3843 12.7171C17.0553 12.5102 15.4063 11.7943 14.2733 10.6404C13.136 9.48222 12.435 7.79783 12.1738 5.49104Z"
|
||||
fill="#1E96EB" />
|
||||
<path
|
||||
d="M19.8353 2.24651C19.8194 2.1062 19.7007 2.00014 19.5595 2C19.4183 1.99986 19.2995 2.10567 19.2833 2.24595C19.1781 3.15811 18.9054 3.79952 18.4654 4.23949C18.0254 4.67946 17.384 4.95218 16.4719 5.05739C16.3316 5.07356 16.2258 5.19241 16.2259 5.33362C16.2261 5.47482 16.3321 5.59345 16.4724 5.60935C17.3695 5.71096 18.0246 5.98357 18.475 6.42584C18.9237 6.86644 19.2021 7.50771 19.2826 8.41347C19.2953 8.55691 19.4156 8.66683 19.5596 8.66667C19.7036 8.6665 19.8236 8.55632 19.836 8.41284C19.9131 7.52199 20.1913 6.86723 20.6422 6.41629C21.0931 5.96534 21.7479 5.68719 22.6388 5.61008C22.7822 5.59766 22.8924 5.47765 22.8926 5.33365C22.8927 5.18964 22.7828 5.06939 22.6394 5.05664C21.7336 4.97619 21.0924 4.69777 20.6517 4.24905C20.2095 3.79864 19.9369 3.1436 19.8353 2.24651Z"
|
||||
fill="#1E96EB" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
+13
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
Intelligents
|
||||
|
||||
Created by 秋星桥 on 2024/11/18.
|
||||
|
||||
*/
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Chat with AI" = "Chat with AI";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Summarize this article for me..." = "Summarize this article for me...";
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
Intelligents
|
||||
|
||||
Created by 秋星桥 on 2024/11/18.
|
||||
|
||||
*/
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Chat with AI" = "与 AI 聊天";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Summarize this article for me..." = "请为我总结这份文档...";
|
||||
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// RootViewController.swift
|
||||
// App
|
||||
//
|
||||
// Created by 秋星桥 on 2024/11/18.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@objc
|
||||
class RootViewController: UINavigationController {
|
||||
override init(rootViewController: UIViewController) {
|
||||
fatalError() // "you are not allowed to call this"
|
||||
}
|
||||
|
||||
override init(navigationBarClass: AnyClass?, toolbarClass: AnyClass?) {
|
||||
fatalError() // "you are not allowed to call this"
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
commitInit()
|
||||
}
|
||||
|
||||
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
|
||||
fatalError() // "you are not allowed to call this"
|
||||
}
|
||||
|
||||
func commitInit() {
|
||||
assert(viewControllers.isEmpty)
|
||||
viewControllers = [AFFiNEViewController()]
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
view.backgroundColor = .systemBackground
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/configuration/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
||||
@@ -0,0 +1,14 @@
|
||||
// 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: "Intelligents",
|
||||
products: [
|
||||
.library(name: "Intelligents", targets: ["Intelligents"]),
|
||||
],
|
||||
targets: [
|
||||
.target(name: "Intelligents"),
|
||||
]
|
||||
)
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
// The Swift Programming Language
|
||||
// https://docs.swift.org/swift-book
|
||||
|
||||
class Intelligents {}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// IntelligentsButton.swift
|
||||
//
|
||||
//
|
||||
// Created by 秋星桥 on 2024/11/18.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
// floating button to open intelligent panel
|
||||
class IntelligentsButton: UIView {
|
||||
let image = UIImageView()
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// IntelligentsChatController.swift
|
||||
//
|
||||
//
|
||||
// Created by 秋星桥 on 2024/11/18.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class IntelligentsChatController: UIViewController {
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user