aboutsummaryrefslogtreecommitdiff
path: root/Hotline
diff options
context:
space:
mode:
authorDustin Mierau <dustin@mierau.me>2023-12-04 09:12:58 -0800
committerDustin Mierau <dustin@mierau.me>2023-12-04 09:12:58 -0800
commit471546236b8991f61d26c0e3aa8ee48b83b983af (patch)
treeb74331a8c8ea61ee7e1eaedfc7bbd1686bbc4702 /Hotline
parent885119acdd27bd53ed3096285c3eca3e2d99ad70 (diff)
Starting to hash out a server view and tabs. Added research pdfs. etc.
Diffstat (limited to 'Hotline')
-rw-r--r--Hotline/Hotline/HotlineClient.swift (renamed from Hotline/Network/HotlineClient.swift)47
-rw-r--r--Hotline/Hotline/HotlineClientModel.swift10
-rw-r--r--Hotline/Hotline/HotlineProtocol.swift (renamed from Hotline/Network/HotlineProtocol.swift)20
-rw-r--r--Hotline/Hotline/HotlineTrackerClient.swift (renamed from Hotline/Network/HotlineTracker.swift)45
-rw-r--r--Hotline/HotlineApp.swift8
-rw-r--r--Hotline/TrackerView.swift100
-rw-r--r--Hotline/Views/AgreementView.swift35
-rw-r--r--Hotline/Views/ChatView.swift63
-rw-r--r--Hotline/Views/HotlineView.swift20
-rw-r--r--Hotline/Views/ServerView.swift62
-rw-r--r--Hotline/Views/TrackerServerView.swift47
-rw-r--r--Hotline/Views/TrackerView.swift73
12 files changed, 376 insertions, 154 deletions
diff --git a/Hotline/Network/HotlineClient.swift b/Hotline/Hotline/HotlineClient.swift
index b5092a0..b5ea1d9 100644
--- a/Hotline/Network/HotlineClient.swift
+++ b/Hotline/Hotline/HotlineClient.swift
@@ -9,8 +9,15 @@ enum HotlineClientStatus: Int {
case loggedIn
}
-class HotlineClient : ObservableObject {
- static let shared = HotlineClient()
+struct HotlineChat {
+ let message: String
+ let userID: UInt16?
+ let isTopic: Bool
+}
+
+@Observable
+class HotlineClient {
+// static let shared = HotlineClient()
static let handshakePacket = Data([
0x54, 0x52, 0x54, 0x50, // 'TRTP' protocol ID
@@ -19,18 +26,19 @@ class HotlineClient : ObservableObject {
0x00, 0x02, // Sub-version
])
- @Published var connectionStatus: HotlineClientStatus = .disconnected
- @Published var agreement: String?
- @Published var userList: [HotlineUser] = []
- @Published var chatMessages: [String] = []
+ var connectionStatus: HotlineClientStatus = .disconnected
+ var agreement: String?
+ var users: [UInt16:HotlineUser] = [:]
+ var userList: [UInt16] = []
+ var chatMessages: [HotlineChat] = []
var userName: String = "bolt"
var userIconID: UInt16 = 128
var serverVersion: UInt16 = 151
var server: HotlineServer?
- var connection: NWConnection?
- private var transactionLog: [UInt32:HotlineTransactionType] = [:]
+ @ObservationIgnored private var connection: NWConnection?
+ @ObservationIgnored private var transactionLog: [UInt32:HotlineTransactionType] = [:]
init() {
@@ -347,15 +355,19 @@ class HotlineClient : ObservableObject {
}
case .getUserNameList:
print("GOT USER LIST")
- var newUserList: [HotlineUser] = []
+ var newUsers: [UInt16:HotlineUser] = [:]
+ var newUserList: [UInt16] = []
for u in transaction.getFieldList(type: .userNameWithInfo) {
- let info = u.getUserInfo()
- print("USER: \(info)")
- let user = HotlineUser(id: info.id, iconID: info.iconID, status: info.flags, name: info.userName)
- newUserList.append(user)
+ let user = u.getUser()
+ newUsers[user.id] = user
+ newUserList.append(user.id)
}
DispatchQueue.main.async {
+ self.users = newUsers
self.userList = newUserList
+
+ print("HotlineClient got users:\n")
+ print("\(self.userList)\n\n")
}
default:
break
@@ -386,16 +398,9 @@ class HotlineClient : ObservableObject {
let userID = userIDParam.getUInt16() {
print("HotlineClient: \(userName):\(userID): \(chatText)")
DispatchQueue.main.async {
- self.chatMessages.append(chatText)
+ self.chatMessages.append(HotlineChat(message: chatText, userID: userID, isTopic: false))
}
}
- case .getUserNameList:
- print("HotlineClient: GOT USER NAME LIST!")
- let userList = transaction.getFieldList(type: .userNameWithInfo)
- for u in userList {
- let userInfo = u.getUserInfo()
- print("HotlineClient: user \(userInfo.userName)")
- }
case .notifyOfUserChange:
// print("HotlineClient: user changed")
if let p = transaction.getField(type: .userName),
diff --git a/Hotline/Hotline/HotlineClientModel.swift b/Hotline/Hotline/HotlineClientModel.swift
new file mode 100644
index 0000000..a45a5e2
--- /dev/null
+++ b/Hotline/Hotline/HotlineClientModel.swift
@@ -0,0 +1,10 @@
+import SwiftUI
+
+@Observable
+class HotlineClientModel {
+ var status: HotlineClientStatus = .disconnected
+ var server: HotlineServer?
+ var userlist: [HotlineUser]?
+ var chat: [(HotlineUser, String)]?
+ var agreement: String?
+}
diff --git a/Hotline/Network/HotlineProtocol.swift b/Hotline/Hotline/HotlineProtocol.swift
index 5372e78..200ac47 100644
--- a/Hotline/Network/HotlineProtocol.swift
+++ b/Hotline/Hotline/HotlineProtocol.swift
@@ -73,22 +73,6 @@ struct HotlineUser: Identifiable, Hashable {
}
}
-struct HotlineUserInfo {
- let id: UInt16
- let iconID: UInt16
- let flags: UInt16
- let userName: String
-
- init(data: Data) {
- self.id = data.readUInt16(at: 0)!
- self.iconID = data.readUInt16(at: 2)!
- self.flags = data.readUInt16(at: 4)!
-
- let userNameLength = data.readUInt16(at: 6)!
- self.userName = data.readString(at: 8, length: Int(userNameLength), encoding: .ascii)!
- }
-}
-
struct HotlineTransactionField {
let type: HotlineTransactionFieldType
let dataSize: UInt16
@@ -171,8 +155,8 @@ struct HotlineTransactionField {
return String(data: self.data, encoding: encoding)
}
- func getUserInfo() -> HotlineUserInfo {
- return HotlineUserInfo(data: self.data)
+ func getUser() -> HotlineUser {
+ return HotlineUser(from: self.data)
}
}
diff --git a/Hotline/Network/HotlineTracker.swift b/Hotline/Hotline/HotlineTrackerClient.swift
index 2c44bc8..fd4e97b 100644
--- a/Hotline/Network/HotlineTracker.swift
+++ b/Hotline/Hotline/HotlineTrackerClient.swift
@@ -7,26 +7,42 @@ enum HotlineTrackerStatus: Int {
case connected
}
-class HotlineTracker : ObservableObject {
- let serverAddress: NWEndpoint.Host
- let serverPort: NWEndpoint.Port = NWEndpoint.Port(rawValue: 5498)!
+struct HotlineTracker: Identifiable {
+ var address: String
+ var port: UInt16
+ var servers: [HotlineServer] = []
+ var expanded: Bool = false
+ var id: String { get { return self.address } }
+
+ init(_ address: String, port: UInt16 = 5498) {
+ self.address = address
+ self.port = port
+ }
+}
+
+@Observable
+class HotlineTrackerClient {
static let magicPacket = Data([
0x48, 0x54, 0x52, 0x4B, // 'HTRK'
0x00, 0x01 // Version
])
- var connection: NWConnection?
-
- var bytes = Data()
- var maxDataLength: Int = 0
- var serverCount: Int = 0
+ @ObservationIgnored private let serverAddress: NWEndpoint.Host
+ @ObservationIgnored private let serverPort: NWEndpoint.Port
+ @ObservationIgnored private var connection: NWConnection?
+ @ObservationIgnored private var bytes = Data()
+ @ObservationIgnored private var maxDataLength: Int = 0
+ @ObservationIgnored private var serverCount: Int = 0
- @Published var connectionStatus: HotlineTrackerStatus = .disconnected
- @Published var servers: [HotlineServer] = []
+ var tracker: HotlineTracker
+ var connectionStatus: HotlineTrackerStatus = .disconnected
+ var servers: [HotlineServer] = []
- init(address: String) {
- self.serverAddress = NWEndpoint.Host(address)
+ init(tracker: HotlineTracker) {
+ self.tracker = tracker
+ self.serverAddress = NWEndpoint.Host(tracker.address)
+ self.serverPort = NWEndpoint.Port(rawValue: tracker.port)!
}
func fetch() {
@@ -88,7 +104,7 @@ class HotlineTracker : ObservableObject {
// let packet: [UInt8] = [0x48, 0x54, 0x52, 0x4B, 0x00, self.serverVersion]
- c.send(content: HotlineTracker.magicPacket, completion: .contentProcessed { [weak self] (error) in
+ c.send(content: HotlineTrackerClient.magicPacket, completion: .contentProcessed { [weak self] (error) in
if let err = error {
print("HotlineTracker: sending magic failed \(err)")
return
@@ -112,7 +128,7 @@ class HotlineTracker : ObservableObject {
return
}
- if data.isEmpty || !data.elementsEqual(HotlineTracker.magicPacket) {
+ if data.isEmpty || !data.elementsEqual(HotlineTrackerClient.magicPacket) {
print("HotlineTracker: invalid magic response")
self.disconnect()
return
@@ -252,6 +268,7 @@ class HotlineTracker : ObservableObject {
}
DispatchQueue.main.async {
+ self.tracker.servers = foundServers
// print("CALLING CALLBACK")
self.servers = foundServers
}
diff --git a/Hotline/HotlineApp.swift b/Hotline/HotlineApp.swift
index da49fd0..238ca48 100644
--- a/Hotline/HotlineApp.swift
+++ b/Hotline/HotlineApp.swift
@@ -16,9 +16,15 @@ struct HotlineApp: App {
// }
// }()
+ @State private var hotline = HotlineClient()
+ @State private var tracker = HotlineTrackerClient(tracker: HotlineTracker("hltracker.com"))
+
var body: some Scene {
+
WindowGroup {
- TrackerView()
+ HotlineView()
+ .environment(hotline)
+ .environment(tracker)
}
// .modelContainer(sharedModelContainer)
}
diff --git a/Hotline/TrackerView.swift b/Hotline/TrackerView.swift
deleted file mode 100644
index 1b5cce5..0000000
--- a/Hotline/TrackerView.swift
+++ /dev/null
@@ -1,100 +0,0 @@
-import SwiftUI
-import SwiftData
-
-struct AgreementView: View {
- @Environment(\.dismiss) var dismiss
-
- let text: String
-
- var body: some View {
- Text(text)
- }
-}
-
-struct TrackerServerView: View {
- @Environment(\.dismiss) var dismiss
-
- let server: HotlineServer
-
- var body: some View {
- VStack(alignment: .leading) {
- HStack {
- Text("🌎")
- Text(server.name!).bold().dynamicTypeSize(.xxLarge)
- }
- .padding(EdgeInsets(top: 0, leading: 0, bottom: 8.0, trailing: 0))
- Text(server.description!).opacity(0.6).dynamicTypeSize(.xLarge).padding(EdgeInsets(top: 0, leading: 0, bottom: 8.0, trailing: 0))
- Text(server.address).opacity(0.3).dynamicTypeSize(.medium)
- Spacer()
- HStack(alignment: .center) {
- Button("Connect") {
- print("WHAT", "HELLO", server.address)
- HotlineClient.shared.connect(to: server)
- dismiss()
-// client = HotlineClient(server: selectedServer)
- }
- .bold()
- .padding(EdgeInsets(top: 16, leading: 24, bottom: 16, trailing: 24))
- .frame(maxWidth: .infinity)
- .foregroundColor(.black)
- .background(LinearGradient(gradient: Gradient(colors: [Color(white: 0.95), Color(white: 0.91)]), startPoint: .top, endPoint: .bottom))
- .
- overlay(
- RoundedRectangle(cornerRadius: 10.0).stroke(.black, lineWidth: 3).opacity(0.4)
- )
- .cornerRadius(10.0)
- }
- }
- .padding(EdgeInsets(top: 28.0, leading: 24.0, bottom: 24.0, trailing: 24.0))
- .presentationDetents([.fraction(0.4)])
- .presentationDragIndicator(.automatic)
- }
-}
-
-struct TrackerView: View {
-
- // @Environment(\.modelContext) private var modelContext
- // @Query private var items: [Item]
-
- @StateObject var tracker = HotlineTracker(address: "hltracker.com")
- @StateObject var client = HotlineClient.shared
-
- @State private var selectedServer: HotlineServer?
- @State private var showingAgreement = false
- @State private var showingConnectSheet = false
-
- var body: some View {
- List(selection: $selectedServer) {
- ForEach(tracker.servers) { server in
- HStack {
- Text("🌎")
- Text(server.name!).bold()
- Spacer()
- Text("\(server.users)").opacity(0.6)
- }
- .listRowBackground(Color(white: 0.96))
- .listRowInsets(EdgeInsets(top: 0, leading: 16.0, bottom: 0, trailing: 16.0))
- .listRowSeparator(.visible, edges: VerticalEdge.Set.all)
- .listRowSeparatorTint(Color(white: 1.0))
- .tag(server)
- }
- }
- .background(Color(white: 0.96))
- .listStyle(.plain)
- .frame(maxWidth: .infinity)
- .task {
- tracker.fetch()
- }
- .sheet(item: $selectedServer) { item in
- TrackerServerView(server: item)
- }
- .sheet(isPresented: Binding(get: { client.agreement != nil }, set: { _ in })) {
- AgreementView(text: client.agreement!)
- }
- }
-}
-
-#Preview {
- TrackerView()
- // .modelContainer(for: Item.self, inMemory: true)
-}
diff --git a/Hotline/Views/AgreementView.swift b/Hotline/Views/AgreementView.swift
new file mode 100644
index 0000000..c61c9d7
--- /dev/null
+++ b/Hotline/Views/AgreementView.swift
@@ -0,0 +1,35 @@
+import SwiftUI
+
+struct AgreementView: View {
+ @Environment(\.dismiss) var dismiss
+
+ let text: String
+
+ var body: some View {
+ VStack(alignment: .leading) {
+ ScrollView {
+ VStack(alignment: .leading) {
+ Text(text)
+ .fontDesign(.monospaced)
+ .padding()
+ .dynamicTypeSize(.small)
+ .textSelection(.enabled)
+ }
+ }
+ Button("OK") {
+ print("DONE")
+ }
+ .bold()
+ .padding(EdgeInsets(top: 16, leading: 24, bottom: 16, trailing: 24))
+ .frame(maxWidth: .infinity)
+ }
+ }
+}
+
+#Preview {
+ AgreementView(text: """
+Welcome!
+
+Take it on real one.
+""")
+}
diff --git a/Hotline/Views/ChatView.swift b/Hotline/Views/ChatView.swift
new file mode 100644
index 0000000..532a890
--- /dev/null
+++ b/Hotline/Views/ChatView.swift
@@ -0,0 +1,63 @@
+import SwiftUI
+
+struct ChatView: View {
+// @Binding private var input: String
+ @State var input: String = ""
+ @State private var scrollPos: Int?
+ @State private var contentHeight: CGFloat = 0
+
+ var body: some View {
+ VStack(spacing: 0) {
+ GeometryReader { geometry in
+ ScrollView(.vertical) {
+ ScrollViewReader { scrollReader in
+ VStack(alignment: .leading) {
+ Spacer()
+ LazyVStack(alignment: .leading, spacing: 10) {
+ HStack(alignment: .firstTextBaseline) {
+ Text("bolt:").bold().fontDesign(.monospaced).frame(minWidth: 60)
+ Text("hello!").fontDesign(.monospaced).textSelection(.enabled)
+ }
+ ForEach(0..<50) { i in
+ HStack(alignment: .firstTextBaseline) {
+ Text("mierau:").bold().fontDesign(.monospaced)
+ Text("g'day to you. what's going on this afternoon?")
+ .fontDesign(.monospaced)
+ .textSelection(.enabled)
+ }
+ }
+ Text("").font(.system(size: 0)).id("bottomScroll")
+ }
+ .padding()
+ }
+ // .frame(width: geometry.size.width)
+ .frame(minHeight: geometry.size.height)
+// .background(Color.red)
+ .onAppear() {
+ scrollReader.scrollTo("bottomScroll", anchor: .bottom)
+ }
+ // .onChange() {
+ // scrollReader.scrollTo(10000, anchor: .bottomTrailing)
+ // }
+ }
+ }
+ }
+
+ Divider()
+
+ HStack(alignment: .top) {
+ Image(systemName: "chevron.right")
+ TextField("This is a chat topic", text: $input, axis: .vertical)
+ .lineLimit(1...5)
+ .onSubmit {
+ // HotlineClient.shared.sendChat(message: self.input)
+ self.input = ""
+ }
+ }.padding()
+ }
+ }
+}
+
+#Preview {
+ ChatView()
+}
diff --git a/Hotline/Views/HotlineView.swift b/Hotline/Views/HotlineView.swift
new file mode 100644
index 0000000..67ef067
--- /dev/null
+++ b/Hotline/Views/HotlineView.swift
@@ -0,0 +1,20 @@
+import SwiftUI
+
+struct HotlineView: View {
+ @Environment(HotlineClient.self) private var hotline
+ @Environment(HotlineTrackerClient.self) private var tracker
+
+ @State private var isTrackerVisible = false
+
+ var body: some View {
+ NavigationStack {
+ TrackerView()
+ }
+ }
+}
+
+#Preview {
+ HotlineView()
+ .environment(HotlineClient())
+ .environment(HotlineTrackerClient(tracker: HotlineTracker("hltracker.com")))
+}
diff --git a/Hotline/Views/ServerView.swift b/Hotline/Views/ServerView.swift
new file mode 100644
index 0000000..1610aa7
--- /dev/null
+++ b/Hotline/Views/ServerView.swift
@@ -0,0 +1,62 @@
+import SwiftUI
+
+struct ServerView: View {
+ @Environment(HotlineClient.self) private var hotline
+ @Environment(HotlineTrackerClient.self) private var tracker
+
+ @State private var isTrackerVisible = false
+
+ var body: some View {
+ TabView {
+ NavigationView {
+ ChatView()
+ .navigationTitle("Badmoon")
+ .navigationBarItems(
+ leading: Button(action: {
+ withAnimation {
+ isTrackerVisible.toggle()
+ }
+ }) {
+ Image(systemName: "line.horizontal.3") // Hamburger icon or similar
+ .imageScale(.large)
+ }
+ )
+ .toolbarBackground(.visible, for: .navigationBar)
+ .toolbarBackground(.red, for: .navigationBar)
+ }
+ .navigationBarTitleDisplayMode(.inline)
+ .tabItem {
+ Image(systemName: "message")
+ }
+
+ Text("Users")
+ .tabItem {
+ Image(systemName: "person.fill")
+ }
+
+ Text("News")
+ .tabItem {
+ Image(systemName: "newspaper")
+ }
+
+ Text("Files")
+ .tabItem {
+ Image(systemName: "folder")
+ }
+
+ Text("Transfers")
+ .tabItem {
+ Image(systemName: "network")
+ }
+ }
+// .sheet(isPresented: Binding(get: { hotline.connectionStatus != .loggedIn }, set: { _ in })) {
+// TrackerView()
+// }
+ }
+}
+
+#Preview {
+ ServerView()
+ .environment(HotlineClient())
+ .environment(HotlineTrackerClient(tracker: HotlineTracker("hltracker.com")))
+}
diff --git a/Hotline/Views/TrackerServerView.swift b/Hotline/Views/TrackerServerView.swift
new file mode 100644
index 0000000..5be6f7a
--- /dev/null
+++ b/Hotline/Views/TrackerServerView.swift
@@ -0,0 +1,47 @@
+import SwiftUI
+
+struct TrackerServerView: View {
+ @Environment(\.dismiss) var dismiss
+
+ let server: HotlineServer
+
+ var body: some View {
+ VStack(alignment: .leading) {
+ HStack {
+ Text("🌎").dynamicTypeSize(.xxxLarge)
+ Text(server.name!).bold().dynamicTypeSize(.xxxLarge)
+ }
+ .padding(EdgeInsets(top: 0, leading: 0, bottom: 8.0, trailing: 0))
+
+ Text(server.description!).opacity(0.6).dynamicTypeSize(.xLarge).padding(EdgeInsets(top: 0, leading: 0, bottom: 8.0, trailing: 0)).textSelection(.enabled)
+ Text(server.address).opacity(0.3).dynamicTypeSize(.medium).textSelection(.enabled)
+
+ Spacer()
+
+ HStack(alignment: .center) {
+ Button("Connect") {
+ print("WHAT", "HELLO", server.address)
+// HotlineClient.shared.connect(to: server)
+ dismiss()
+ }
+ .bold()
+ .padding(EdgeInsets(top: 16, leading: 24, bottom: 16, trailing: 24))
+ .frame(maxWidth: .infinity)
+ .foregroundColor(.black)
+ .background(LinearGradient(gradient: Gradient(colors: [Color(white: 0.95), Color(white: 0.91)]), startPoint: .top, endPoint: .bottom))
+ .
+ overlay(
+ RoundedRectangle(cornerRadius: 10.0).stroke(.black, lineWidth: 3).opacity(0.4)
+ )
+ .cornerRadius(10.0)
+ }
+ }
+ .padding(EdgeInsets(top: 28.0, leading: 24.0, bottom: 24.0, trailing: 24.0))
+ .presentationDetents([.fraction(0.4)])
+ .presentationDragIndicator(.automatic)
+ }
+}
+
+#Preview {
+ TrackerServerView(server: HotlineServer(address: "192.168.1.1", port: 5050, users: 5, name: "Ye Olde Server", description: "This is a server"))
+}
diff --git a/Hotline/Views/TrackerView.swift b/Hotline/Views/TrackerView.swift
new file mode 100644
index 0000000..5d1afe2
--- /dev/null
+++ b/Hotline/Views/TrackerView.swift
@@ -0,0 +1,73 @@
+import SwiftUI
+
+struct TrackerView: View {
+
+ // @Environment(\.modelContext) private var modelContext
+ // @Query private var items: [Item]
+
+ @Environment(HotlineClient.self) private var hotline
+ @Environment(HotlineTrackerClient.self) private var tracker
+
+ // @StateObject var tracker = HotlineTrackerClient(address: "hltracker.com")
+ // @StateObject var client = HotlineClient.shared
+
+ @State private var selectedServer: HotlineServer?
+ @State private var showingAgreement = false
+ @State private var showingConnectSheet = false
+
+ // @Bindable var trackerTest = tracker
+
+ var body: some View {
+ List(selection: $selectedServer) {
+ ForEach(tracker.servers) { server in
+ HStack {
+ Text("🌎")
+ Text(server.name!).bold()
+ Spacer()
+ Text("\(server.users)").opacity(0.6)
+ }
+ .listRowBackground(Color(white: 0.96))
+ .listRowInsets(EdgeInsets(top: 0, leading: 16.0, bottom: 0, trailing: 16.0))
+ .listRowSeparator(.visible, edges: VerticalEdge.Set.all)
+ .listRowSeparatorTint(Color(white: 1.0))
+ .tag(server)
+ }
+ }
+ .background(Color(white: 0.96))
+ .listStyle(.plain)
+ .frame(maxWidth: .infinity)
+ .task {
+ tracker.fetch()
+ }
+ .sheet(item: $selectedServer) { item in
+ TrackerServerView(server: item)
+ }
+ .sheet(isPresented: Binding(get: { hotline.agreement != nil }, set: { _ in })) {
+ AgreementView(text: hotline.agreement!)
+ }
+ .navigationTitle("Tracker")
+ .navigationBarTitleDisplayMode(.inline)
+ .toolbar {
+ ToolbarItem(placement: .topBarTrailing) {
+ Image(systemName: "gearshape.fill")
+ }
+ // ToolbarItem(placement: .topBarTrailing) {
+ // Image(systemName: "camera.fill")
+ // }
+ // ToolbarItem(placement: .principal) {
+ // Text("Username")
+ // }
+ }
+ .refreshable {
+ tracker.fetch()
+ }
+ .interactiveDismissDisabled()
+ }
+}
+
+#Preview {
+ TrackerView()
+ .environment(HotlineClient())
+ .environment(HotlineTrackerClient(tracker: HotlineTracker("hltracker.com")))
+ // .modelContainer(for: Item.self, inMemory: true)
+}