forked from intitni/CopilotForXcode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathChatTab.swift
More file actions
154 lines (132 loc) · 4.16 KB
/
Copy pathChatTab.swift
File metadata and controls
154 lines (132 loc) · 4.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import ComposableArchitecture
import Foundation
import SwiftUI
public struct ChatTabInfo: Identifiable, Equatable {
public var id: String
public var title: String
public init(id: String, title: String) {
self.id = id
self.title = title
}
}
public struct ChatTabInfoPreferenceKey: PreferenceKey {
public static var defaultValue: [ChatTabInfo] = []
public static func reduce(value: inout [ChatTabInfo], nextValue: () -> [ChatTabInfo]) {
value.append(contentsOf: nextValue())
}
}
/// Every chat tab should conform to this type.
public typealias ChatTab = BaseChatTab & ChatTabType
/// The base class for all chat tabs.
open class BaseChatTab: Equatable {
/// To support dynamic update of title in view.
final class InfoObservable: ObservableObject {
@Published var id: String
@Published var title: String
init(id: String, title: String) {
self.title = title
self.id = id
}
}
/// A wrapper to support dynamic update of title in view.
struct ContentView: View {
@ObservedObject var info: InfoObservable
var buildView: () -> any View
var body: some View {
AnyView(buildView())
.preference(
key: ChatTabInfoPreferenceKey.self,
value: [ChatTabInfo(
id: info.id,
title: info.title
)]
)
}
}
public let id: String
public var title: String {
didSet { info.title = title }
}
let info: InfoObservable
public init(id: String, title: String) {
self.id = id
self.title = title
info = InfoObservable(id: id, title: title)
}
/// The view for this chat tab.
@ViewBuilder
public var body: some View {
let id = "ChatTabBody\(info.id)"
if let tab = self as? (any ChatTabType) {
ContentView(info: info, buildView: tab.buildView).id(id)
} else {
EmptyView().id(id)
}
}
/// The menu for this chat tab.
@ViewBuilder
public var menu: some View {
let id = "ChatTabMenu\(info.id)"
if let tab = self as? (any ChatTabType) {
ContentView(info: info, buildView: tab.buildMenu).id(id)
} else {
EmptyView().id(id)
}
}
public static func == (lhs: BaseChatTab, rhs: BaseChatTab) -> Bool {
lhs.id == rhs.id
}
}
/// A factory of a chat tab.
public protocol ChatTabBuilder {
/// A visible title for user.
var title: String { get }
/// Build the chat tab.
func build() -> any ChatTab
}
public protocol ChatTabType {
/// The type of the external dependency required by this chat tab.
associatedtype ExternalDependency
/// Build the view for this chat tab.
@ViewBuilder
func buildView() -> any View
/// Build the menu for this chat tab.
@ViewBuilder
func buildMenu() -> any View
/// The name of this chat tab.
static var name: String { get }
/// Available builders for this chat tab.
/// It's used to generate a list of tab types for user to create.
static func chatBuilders(externalDependency: ExternalDependency) -> [ChatTabBuilder]
}
public extension ChatTabType where ExternalDependency == Void {
/// Available builders for this chat tab.
/// It's used to generate a list of tab types for user to create.
static func chatBuilders() -> [ChatTabBuilder] {
chatBuilders(externalDependency: ())
}
}
public class EmptyChatTab: ChatTab {
public static var name: String { "Empty" }
struct Builder: ChatTabBuilder {
let title: String
func build() -> any ChatTab {
EmptyChatTab()
}
}
public static func chatBuilders(externalDependency: Void) -> [ChatTabBuilder] {
[Builder(title: "Empty")]
}
public func buildView() -> any View {
VStack {
Text("Empty-\(id)")
}
.background(Color.blue)
}
public func buildMenu() -> any View {
EmptyView()
}
public init(id: String = UUID().uuidString) {
super.init(id: id, title: "Empty")
}
}