diff --git a/App/Assets/Base.lproj/Localizable.strings b/App/Assets/Base.lproj/Localizable.strings index fc66336c..a22a9e40 100644 --- a/App/Assets/Base.lproj/Localizable.strings +++ b/App/Assets/Base.lproj/Localizable.strings @@ -139,6 +139,6 @@ "link.huggingChat.privacy" = "https://huggingface.co/chat/privacy"; "link.openai.privacy" = "https://openai.com/policies/privacy-policy"; "link.openai.tos" = "https://openai.com/policies/terms-of-use"; -"link.openai.api-keys" = "https://platform.openai.com/account/api-keys"; +"link.openai.api-keys" = "https://platform.openai.com/api-keys"; "link.openai.known-proxy" = ""; "link.feedback" = "https://github.com/b9software/B9ChatAI/discussions"; diff --git a/App/Assets/Generated/L10n.swift b/App/Assets/Generated/L10n.swift index fec6b594..d29b7de6 100644 --- a/App/Assets/Generated/L10n.swift +++ b/App/Assets/Generated/L10n.swift @@ -167,8 +167,8 @@ internal enum L10n { internal static let privacy = L10n.tr("Localizable", "link.huggingChat.privacy", fallback: "https://huggingface.co/chat/privacy") } internal enum Openai { - /// https://platform.openai.com/account/api-keys - internal static let apiKeys = L10n.tr("Localizable", "link.openai.api-keys", fallback: "https://platform.openai.com/account/api-keys") + /// https://platform.openai.com/api-keys + internal static let apiKeys = L10n.tr("Localizable", "link.openai.api-keys", fallback: "https://platform.openai.com/api-keys") /// internal static let knownProxy = L10n.tr("Localizable", "link.openai.known-proxy", fallback: "") /// https://openai.com/policies/privacy-policy diff --git a/App/General/List/MBCollectionViewColumnLayout.swift b/App/General/List/MBCollectionViewColumnLayout.swift new file mode 100644 index 00000000..5fba65fe --- /dev/null +++ b/App/General/List/MBCollectionViewColumnLayout.swift @@ -0,0 +1,116 @@ +/* + MBCollectionViewColumnLayout + + Copyright © 2018-2021 BB9z. + Copyright © 2014-2015 Beijing ZhiYun ZhiYuan Information Technology Co., Ltd. + https://github.com/BB9z/iOS-Project-Template + + The MIT License + https://opensource.org/licenses/MIT + */ + +/** + 按列布局,根据列数等比例调整 cell 的大小并保持 cell 的间距不变 + + 可以指定一个固定列数或一个 cell 的参考大小自动调整列数 + + 不支持通过 delegate 设置 itemSize。如果 delegate 返回了 itemSize,则列设置将失效。 + 多 section 将根据第一个 section 的尺寸进行布局,不支持多 section 有不同的 itemSize。 + */ +class MBCollectionViewColumnLayout: UICollectionViewFlowLayout { + + /** + 根据宽度自适应调整列数 + + 开启时,外部设置的 columnCount 失效,将根据 itemSize 决定列数,能显示几列就显示几列。 + 但跟原始的 UICollectionViewFlowLayout 不同之处在于会等比拉大 cell 保持最小间隙。 + + 默认 NO + */ + @IBInspectable var autoColumnDecideOnItemMinimumWidth: Bool = false + + /// 列数量,默认 3 + @IBInspectable var columnCount: Int = 3 { + didSet { + if oldValue == columnCount { return } + if !autoColumnDecideOnItemMinimumWidth { + invalidateLayout() + } + } + } + + /** + 布局的参考 item size,这个类会修改实际 itemSize + + 从 nib 里载入后会吧 itemSize 复制给这个属性,如果手动更新该属性需要手动调用重新布局的方法 + */ + @IBInspectable var referenceItemSize: CGSize = .zero + + /** + 仅宽度自适应,保持高度 + */ + @IBInspectable var onlyAdjustWidth: Bool = false + + override func awakeFromNib() { + super.awakeFromNib() + referenceItemSize = itemSize + } + + override func prepare() { + super.prepare() + guard let list = collectionView else { return } + let layoutChanged = updateLayout(bounds: list.bounds) + if layoutChanged { + list.invalidateIntrinsicContentSize() + } + } + + override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { + if newBounds.width != collectionView?.bounds.width { + updateLayout(bounds: newBounds) + return true + } + return super.shouldInvalidateLayout(forBoundsChange: newBounds) + } + + /// 返回是否更新了布局 + @discardableResult private func updateLayout(bounds: CGRect) -> Bool { + if (collectionView?.numberOfSections ?? 0) == 0 { return false } + let size = itemSize(for: bounds) + if itemSize == size { return false } + itemSize = size + return true + } + + // MARK: - 布局计算 + + private var delegateRefrence: UICollectionViewDelegateFlowLayout? { + collectionView?.delegate as? UICollectionViewDelegateFlowLayout + } + + private func itemSize(for bounds: CGRect) -> CGSize { + let reference = referenceItemSize + if autoColumnDecideOnItemMinimumWidth { + let width = innerLayoutWidth(section: 0, bounds: bounds) + columnCount = Int(width / reference.width) + } + let width = itemWidth(section: 0, bounds: bounds) + let height = onlyAdjustWidth ? reference.height : width / reference.width * reference.height + return CGSize(width: width, height: height) + } + + private func itemWidth(section: Int, bounds: CGRect) -> CGFloat { + let width = innerLayoutWidth(section: section, bounds: bounds) + let column = CGFloat(max(1, columnCount)) + return ((width - (column - 1) * minimumInteritemSpacing) / column).rounded(.down) + } + + private func innerLayoutWidth(section: Int, bounds: CGRect) -> CGFloat { + var inset = sectionInset + if let list = collectionView, + let dInset = delegateRefrence?.collectionView?(list, layout: self, insetForSectionAt: section) { + inset = dInset + } + return bounds.width - inset.left - inset.right + } +} diff --git a/App/General/UIKit+IBInspectable/UICollectionViewFlowLayout+IBInspectable.h b/App/General/UIKit+IBInspectable/UICollectionViewFlowLayout+IBInspectable.h deleted file mode 100644 index 21ada214..00000000 --- a/App/General/UIKit+IBInspectable/UICollectionViewFlowLayout+IBInspectable.h +++ /dev/null @@ -1,18 +0,0 @@ -/* - UICollectionViewFlowLayout+IBSelection - - Copyright © 2018 RFUI. - https://github.com/BB9z/iOS-Project-Template - - The MIT License - https://opensource.org/licenses/MIT - */ - -#import "UIKit+IBInspectable.h" - -/** - Interface Builder 中没提供这项的开关 - */ -@interface UICollectionViewFlowLayout (IBInspectable) -@property (nonatomic) IBInspectable CGSize estimatedItemSize; -@end diff --git a/App/Model/AppError.swift b/App/Model/AppError.swift index 4235275a..e81cb147 100644 --- a/App/Model/AppError.swift +++ b/App/Model/AppError.swift @@ -3,42 +3,6 @@ // App // -/// App 用错误 -enum AppError: LocalizedError { - - /// 包含一条信息的错误 - case message(_ message: String) - - /// Operation is cancelled - case cancel - - var errorDescription: String? { - switch self { - case let .message(text): - return text - case .cancel: - return "Operation is cancelled" - } - } - - /// Conveniently determine if an object is `AppError.cancel` - static func isCancel(_ err: Error?) -> Bool { - if let err = err as? AppError { - if case .cancel = err { - return true - } - return false - } - if err is CancellationError { - return true - } - if let urlError = err as? URLError { - return urlError.code == .cancelled - } - return false - } -} - enum ModelError: LocalizedError { /// 模型验证失败 case invalid(String) diff --git a/App/Model/CoreData/CDEngine+.swift b/App/Model/CoreData/CDEngine+.swift index 9503103c..c82815fe 100644 --- a/App/Model/CoreData/CDEngine+.swift +++ b/App/Model/CoreData/CDEngine+.swift @@ -5,6 +5,7 @@ // Copyright © 2023 B9Software. All rights reserved. // +import AppFramework import CoreData extension CDEngine { diff --git a/App/Model/CoreData/CDMessage+.swift b/App/Model/CoreData/CDMessage+.swift index 12e2671d..93467aa0 100644 --- a/App/Model/CoreData/CDMessage+.swift +++ b/App/Model/CoreData/CDMessage+.swift @@ -5,6 +5,7 @@ // Copyright © 2023 B9Software. All rights reserved. // +import AppFramework import CoreData public extension CDMessage { diff --git a/App/Model/Items/Conversation.swift b/App/Model/Items/Conversation.swift index 66c6e01a..616de8c4 100644 --- a/App/Model/Items/Conversation.swift +++ b/App/Model/Items/Conversation.swift @@ -5,6 +5,7 @@ // Copyright © 2023 B9Software. All rights reserved. // +import AppFramework import B9Action import B9MulticastDelegate import CoreData diff --git a/App/Model/Items/Engine.swift b/App/Model/Items/Engine.swift index 2e317cb2..bf7cbed2 100644 --- a/App/Model/Items/Engine.swift +++ b/App/Model/Items/Engine.swift @@ -5,6 +5,7 @@ // Copyright © 2023 B9Software. All rights reserved. // +import AppFramework import CoreData import Logging diff --git a/App/Model/Items/Message.swift b/App/Model/Items/Message.swift index 8c03dfb9..d7996a04 100644 --- a/App/Model/Items/Message.swift +++ b/App/Model/Items/Message.swift @@ -5,6 +5,7 @@ // Copyright © 2023 B9Software. All rights reserved. // +import AppFramework import B9Action import B9MulticastDelegate import CoreData diff --git a/App/Model/OpenAI API/OANetwork.swift b/App/Model/OpenAI API/OANetwork.swift index 1dad7aac..d31315d8 100644 --- a/App/Model/OpenAI API/OANetwork.swift +++ b/App/Model/OpenAI API/OANetwork.swift @@ -6,6 +6,7 @@ // Copyright © 2023 B9Software. All rights reserved. // +import AppFramework import Foundation class OANetwork { diff --git a/App/Scene/Base.lproj/Conversation.storyboard b/App/Scene/Base.lproj/Conversation.storyboard index ff91324e..2ab07530 100644 --- a/App/Scene/Base.lproj/Conversation.storyboard +++ b/App/Scene/Base.lproj/Conversation.storyboard @@ -1,9 +1,9 @@ - + - + @@ -300,7 +300,7 @@ - + @@ -1773,7 +1773,7 @@ - + @@ -1796,7 +1796,7 @@ - + diff --git a/App/Scene/Base.lproj/Setting.storyboard b/App/Scene/Base.lproj/Setting.storyboard index 77d14738..272afe40 100644 --- a/App/Scene/Base.lproj/Setting.storyboard +++ b/App/Scene/Base.lproj/Setting.storyboard @@ -1,9 +1,9 @@ - + - + @@ -18,20 +18,20 @@ - + - + - + - + - + - + @@ -151,7 +151,7 @@ - - - - - - - - - - - + - + - + - + - + @@ -1220,13 +1201,13 @@ Try it now by pressing the Tab (+shift) key and arrow keys. - + - + @@ -1245,16 +1226,16 @@ Try it now by pressing the Tab (+shift) key and arrow keys. - + - +