From 077686d0fd6e746d3bc1dda87ccebd687c88948f Mon Sep 17 00:00:00 2001 From: Djorkaeff Alexandre Date: Wed, 17 Jan 2024 16:57:50 -0300 Subject: [PATCH] Add ServerListView & RoomListView --- .../channel-private.imageset/Contents.json | 12 + .../channel-private.png | Bin 0 -> 371 bytes .../channel-public.imageset/Contents.json | 12 + .../channel-public.png | Bin 0 -> 259 bytes .../discussions.imageset/Contents.json | 12 + .../discussions.imageset/discussions.png | Bin 0 -> 517 bytes .../message.imageset/Contents.json | 12 + .../message.imageset/message.png | Bin 0 -> 479 bytes .../teams-private.imageset/Contents.json | 12 + .../teams-private.imageset/teams-private.png | Bin 0 -> 509 bytes .../teams.imageset/Contents.json | 12 + .../Assets.xcassets/teams.imageset/teams.png | Bin 0 -> 512 bytes .../Client/Adapters/JSONAdapter.swift | 10 + .../Client/Adapters/RequestAdapter.swift | 12 + .../Client/Adapters/TokenAdapter.swift | 29 ++ .../Client/DateCodingStrategy.swift | 44 +++ .../Client/Extensions/Data+Extensions.swift | 9 + .../Client/Extensions/String+Extensions.swift | 6 + .../Client/FailableDecodable.swift | 13 + .../Client/HTTP/HTTPMethod.swift | 4 + .../Client/HTTP/Request.swift | 24 ++ .../Client/Requests/HistoryRequest.swift | 43 +++ .../Client/Requests/MessagesRequest.swift | 15 + .../Client/Requests/ReadRequest.swift | 20 ++ .../Client/Requests/RoomsRequest.swift | 16 + .../Client/Requests/SendMessageRequest.swift | 29 ++ .../Requests/SubscriptionsRequest.swift | 16 + .../Client/Responses/AttachmentResponse.swift | 18 ++ .../Client/Responses/HistoryResponse.swift | 6 + .../Client/Responses/MessageResponse.swift | 12 + .../Client/Responses/MessagesResponse.swift | 11 + .../Client/Responses/ReadResponse.swift | 5 + .../Client/Responses/RoomsResponse.swift | 26 ++ .../Responses/SendMessageResponse.swift | 6 + .../Responses/SubscriptionsResponse.swift | 26 ++ .../Client/Responses/UserResponse.swift | 7 + .../Client/RocketChatClient.swift | 91 ++++++ .../Client/RocketChatError.swift | 7 + .../Client/RocketChatServer.swift | 21 ++ ios/RocketChat Watch App/ContentView.swift | 38 --- .../Database/Database.swift | 97 ++++++ .../Default.xcdatamodel/contents | 18 ++ .../Database/Models/LoggedUser.swift | 48 +++ .../Database/Models/Room.swift | 13 + .../Database/Models/Server.swift | 54 ++++ .../RocketChat.xcdatamodel/contents | 47 +++ .../Database/RocketChatDatabase.swift | 216 +++++++++++++ .../DependencyStore.swift | 29 ++ .../Localizable.xcstrings | 16 + ios/RocketChat Watch App/RocketChatApp.swift | 45 ++- .../RocketChatAppRouter.swift | 37 +++ ios/RocketChat Watch App/Storage.swift | 28 ++ .../ViewModels/RoomListViewModel.swift | 90 ++++++ .../ViewModels/RoomViewModel.swift | 142 +++++++++ .../ViewModels/ServerListViewModel.swift | 55 ++++ .../Views/RoomListView.swift | 39 +++ ios/RocketChat Watch App/Views/RoomView.swift | 51 +++ .../Views/ServerListView.swift | 54 ++++ .../Views/ServerView.swift | 17 + .../WatchConnection.swift | 2 +- ios/RocketChatRN.xcodeproj/project.pbxproj | 293 +++++++++++++++++- 61 files changed, 1980 insertions(+), 47 deletions(-) create mode 100644 ios/RocketChat Watch App/Assets.xcassets/channel-private.imageset/Contents.json create mode 100644 ios/RocketChat Watch App/Assets.xcassets/channel-private.imageset/channel-private.png create mode 100644 ios/RocketChat Watch App/Assets.xcassets/channel-public.imageset/Contents.json create mode 100644 ios/RocketChat Watch App/Assets.xcassets/channel-public.imageset/channel-public.png create mode 100644 ios/RocketChat Watch App/Assets.xcassets/discussions.imageset/Contents.json create mode 100644 ios/RocketChat Watch App/Assets.xcassets/discussions.imageset/discussions.png create mode 100644 ios/RocketChat Watch App/Assets.xcassets/message.imageset/Contents.json create mode 100644 ios/RocketChat Watch App/Assets.xcassets/message.imageset/message.png create mode 100644 ios/RocketChat Watch App/Assets.xcassets/teams-private.imageset/Contents.json create mode 100644 ios/RocketChat Watch App/Assets.xcassets/teams-private.imageset/teams-private.png create mode 100644 ios/RocketChat Watch App/Assets.xcassets/teams.imageset/Contents.json create mode 100644 ios/RocketChat Watch App/Assets.xcassets/teams.imageset/teams.png create mode 100644 ios/RocketChat Watch App/Client/Adapters/JSONAdapter.swift create mode 100644 ios/RocketChat Watch App/Client/Adapters/RequestAdapter.swift create mode 100644 ios/RocketChat Watch App/Client/Adapters/TokenAdapter.swift create mode 100644 ios/RocketChat Watch App/Client/DateCodingStrategy.swift create mode 100644 ios/RocketChat Watch App/Client/Extensions/Data+Extensions.swift create mode 100644 ios/RocketChat Watch App/Client/Extensions/String+Extensions.swift create mode 100644 ios/RocketChat Watch App/Client/FailableDecodable.swift create mode 100644 ios/RocketChat Watch App/Client/HTTP/HTTPMethod.swift create mode 100644 ios/RocketChat Watch App/Client/HTTP/Request.swift create mode 100644 ios/RocketChat Watch App/Client/Requests/HistoryRequest.swift create mode 100644 ios/RocketChat Watch App/Client/Requests/MessagesRequest.swift create mode 100644 ios/RocketChat Watch App/Client/Requests/ReadRequest.swift create mode 100644 ios/RocketChat Watch App/Client/Requests/RoomsRequest.swift create mode 100644 ios/RocketChat Watch App/Client/Requests/SendMessageRequest.swift create mode 100644 ios/RocketChat Watch App/Client/Requests/SubscriptionsRequest.swift create mode 100644 ios/RocketChat Watch App/Client/Responses/AttachmentResponse.swift create mode 100644 ios/RocketChat Watch App/Client/Responses/HistoryResponse.swift create mode 100644 ios/RocketChat Watch App/Client/Responses/MessageResponse.swift create mode 100644 ios/RocketChat Watch App/Client/Responses/MessagesResponse.swift create mode 100644 ios/RocketChat Watch App/Client/Responses/ReadResponse.swift create mode 100644 ios/RocketChat Watch App/Client/Responses/RoomsResponse.swift create mode 100644 ios/RocketChat Watch App/Client/Responses/SendMessageResponse.swift create mode 100644 ios/RocketChat Watch App/Client/Responses/SubscriptionsResponse.swift create mode 100644 ios/RocketChat Watch App/Client/Responses/UserResponse.swift create mode 100644 ios/RocketChat Watch App/Client/RocketChatClient.swift create mode 100644 ios/RocketChat Watch App/Client/RocketChatError.swift create mode 100644 ios/RocketChat Watch App/Client/RocketChatServer.swift delete mode 100644 ios/RocketChat Watch App/ContentView.swift create mode 100644 ios/RocketChat Watch App/Database/Database.swift create mode 100644 ios/RocketChat Watch App/Database/Default.xcdatamodeld/Default.xcdatamodel/contents create mode 100644 ios/RocketChat Watch App/Database/Models/LoggedUser.swift create mode 100644 ios/RocketChat Watch App/Database/Models/Room.swift create mode 100644 ios/RocketChat Watch App/Database/Models/Server.swift create mode 100644 ios/RocketChat Watch App/Database/RocketChat.xcdatamodeld/RocketChat.xcdatamodel/contents create mode 100644 ios/RocketChat Watch App/Database/RocketChatDatabase.swift create mode 100644 ios/RocketChat Watch App/DependencyStore.swift create mode 100644 ios/RocketChat Watch App/Localizable.xcstrings create mode 100644 ios/RocketChat Watch App/RocketChatAppRouter.swift create mode 100644 ios/RocketChat Watch App/Storage.swift create mode 100644 ios/RocketChat Watch App/ViewModels/RoomListViewModel.swift create mode 100644 ios/RocketChat Watch App/ViewModels/RoomViewModel.swift create mode 100644 ios/RocketChat Watch App/ViewModels/ServerListViewModel.swift create mode 100644 ios/RocketChat Watch App/Views/RoomListView.swift create mode 100644 ios/RocketChat Watch App/Views/RoomView.swift create mode 100644 ios/RocketChat Watch App/Views/ServerListView.swift create mode 100644 ios/RocketChat Watch App/Views/ServerView.swift diff --git a/ios/RocketChat Watch App/Assets.xcassets/channel-private.imageset/Contents.json b/ios/RocketChat Watch App/Assets.xcassets/channel-private.imageset/Contents.json new file mode 100644 index 000000000..7cdba7f83 --- /dev/null +++ b/ios/RocketChat Watch App/Assets.xcassets/channel-private.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "channel-private.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/RocketChat Watch App/Assets.xcassets/channel-private.imageset/channel-private.png b/ios/RocketChat Watch App/Assets.xcassets/channel-private.imageset/channel-private.png new file mode 100644 index 0000000000000000000000000000000000000000..11f863ed13087f3526b638c50e6f697c7a91a3bb GIT binary patch literal 371 zcmV-(0gV2MP)Px$ElET{R9HvtmP-zTFcd{!%fiI{Fp)Tsm>`M4iNOi`0T%`}E#J^Dr6vub<9<%> zyZ5ySJQ&CW2G}n|biIerG$|ky0wFaZ^ev10AyeH*i6~C!1}R{CF5k~uSHdVxh+s}8 z16cL-Lv+g{t^wMrN|+@|zwjp#WS zy&db?_7caS#cP$=8qxh2^H4%%_t`V8|7}kXnRf1Qrctx9&ZFVN1H!9v`~d#~G%)|0 RF!ule002ovPDHLkV1lGcoVNe~ literal 0 HcmV?d00001 diff --git a/ios/RocketChat Watch App/Assets.xcassets/channel-public.imageset/Contents.json b/ios/RocketChat Watch App/Assets.xcassets/channel-public.imageset/Contents.json new file mode 100644 index 000000000..6b5b77ce3 --- /dev/null +++ b/ios/RocketChat Watch App/Assets.xcassets/channel-public.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "channel-public.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/RocketChat Watch App/Assets.xcassets/channel-public.imageset/channel-public.png b/ios/RocketChat Watch App/Assets.xcassets/channel-public.imageset/channel-public.png new file mode 100644 index 0000000000000000000000000000000000000000..a99eda5845063d5f6d230c31d57de3f15df47970 GIT binary patch literal 259 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}dp%toLn7SY zPIKfsWFX-B|4CBs_mitiLRmzYXus6@ZWy;_@luT?E8nv4lp1c9Gnzkt=6U5eF}Ge9N`m``EgYsiFJ*?!YtGe{l$WS=I2rc-&; zn-cgGs@9|(cY9ycK1sEt^t9^Xliy=aiai~6J>6e)V1|KxvaNHegKPl%C51~G^IoXy z^*%Be<}&b}xTc5sa^}5wk-ff0CHE$&>3Di9(gPx$zez+vR9Hvtmd$R$Fbv0?@?ezr>1Ak_p-E5)vCG6ReIL39gG~9dQrAwra_EJe zN=f6N|8{H}Sb3S1?ZXRTkpkWOXA6%<3)XJIm=?om?dYF3n|}B{hv&-rH$b;PoB-fh zI~W{{0z?fEy}o6JduW<5_tfuk>wZ6G%Jjqa;-J~30wBhV2PP{-De)T}z}v?ydZaSW zK@8UmYpV*7doLYQ%7;t|7QPOUytj57e!h0qBq+aIiZw+x{0 zu~v;PsmM4{Q~=|@P<~9trvU`{Gz!#`m{lMqj*Ho;6h9R}s+@v~GJv&Pm-Pt%7A9dS z{?36J0A8C&On+;<#@L6Z70S2ZfD8bDg#%T{-f2Nl0rvqDhB*L}v6H&4>~c9E?Vx!* zP$}RJo3-Hx;tPz>`*CgvG@*qP73KgbZbH_I^FSI#d}d`s$~0E97B1ZaUpl44ne_d# zLGLjb^DE5+vI2oz-F^|O5E3qNCjd0rYTYjttf8DPx$nMp)JR9HvtmceeqFbsy1_Q6)(rx(yJph-{(!DV8Xz7O4lK}OsrB*BRt%9IPa zpo%vB;y<>3DGD!HIKFrTurttpel_BHZG=)EAe)p8{3rV#_pIlLZAUJC?9chy4JVhnHLjm02 z{pmo;6do}LA_kyf)2dqJUMd5_g?Iq4r&ns|e~t@E!_@yyCZked0JBf;Lba<6>=%>L z1F&g(!Rf6O_cBJa-~%kzXJ{}pv`<{fG5}DTT82ng(F{COda=t$$?5|T7v`CYC=Wo~ ztN+hHRv8i{lPdw0ewgY((=el#93mW;mQmL6PRJamL@OSC8XkB-(5n@0+i#C$@ zelnOdEVl!j`f1EjVKk~z{$d`H1>lvcfw9+kvghV`3T7TDW10<+cXaygH$d?W`~faj V8ZeuQI;#Kx002ovPDHLkV1ga=%Gv+` literal 0 HcmV?d00001 diff --git a/ios/RocketChat Watch App/Assets.xcassets/teams-private.imageset/Contents.json b/ios/RocketChat Watch App/Assets.xcassets/teams-private.imageset/Contents.json new file mode 100644 index 000000000..1345936f0 --- /dev/null +++ b/ios/RocketChat Watch App/Assets.xcassets/teams-private.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "teams-private.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/RocketChat Watch App/Assets.xcassets/teams-private.imageset/teams-private.png b/ios/RocketChat Watch App/Assets.xcassets/teams-private.imageset/teams-private.png new file mode 100644 index 0000000000000000000000000000000000000000..0374c85fa254ef925f816f4a88bf9b61883c27ba GIT binary patch literal 509 zcmVPx$w@E}nR9HvtR>5wHQV~!LeHr{!Rg4dLQqaaf~~D zGyrPMoKfuexa;$&3c%j5hw)1Gdthn_hB{$10DsSO!TmP`_X47K#sG+yQ$M||*^mtY z4Qi)g{7|7#vXV!0V5nY8d>^I23&2Z(B^3Apa%*HThOF1D00000NkvXXu0mjf`r6&< literal 0 HcmV?d00001 diff --git a/ios/RocketChat Watch App/Assets.xcassets/teams.imageset/Contents.json b/ios/RocketChat Watch App/Assets.xcassets/teams.imageset/Contents.json new file mode 100644 index 000000000..c77798bef --- /dev/null +++ b/ios/RocketChat Watch App/Assets.xcassets/teams.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "teams.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/RocketChat Watch App/Assets.xcassets/teams.imageset/teams.png b/ios/RocketChat Watch App/Assets.xcassets/teams.imageset/teams.png new file mode 100644 index 0000000000000000000000000000000000000000..6c19d984d9305527c0d3ff3fba0d75a715b224b9 GIT binary patch literal 512 zcmV+b0{{JqP)Px$x=BPqR9HvtR!wh%FbqxT4;%eITvqL}YSM~Sb(g8T@PEqw7@3^J!FE1K(IhT# zX(2IwetvmItBo(caelP}*cn)4pgWx}s%ggI`#MU&c5ff8nx;0p?qeY?6%Ws|0BmBV zK0{Jt#^LAdi1*#=`$K8nrq(^M2Ls$R#}tSX0OMcKD`Q@KvVOSVE-bk*ju{6=uFtWe z00fr=5GwFN(LGl0tD+ST_nYocXA2(w!>_!j%q%ki6(ZFL$R|(|0InlI7yD~l`3wUd zHP-;I$gOeu6(;!?4OkGMa=50ch2B%@N+Mi}t!hEdj95B`HSdTtkfhJO$ts*yCyy1x z;c(C0*!GoGM}EC5Nf&j39uxtRn)_u8bvcirqj zhm2ZKGN9D}q^QME%FhB)795+^0MN`bBYE+AprqhuHIUk042Hhs{v38wx3~zOAfBm-f9MFie9s42e31+IRk&d$$T)fxa2_q0000 URLRequest { + var request = urlRequest + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + request.addValue("application/json", forHTTPHeaderField: "Accept") + return request + } +} diff --git a/ios/RocketChat Watch App/Client/Adapters/RequestAdapter.swift b/ios/RocketChat Watch App/Client/Adapters/RequestAdapter.swift new file mode 100644 index 000000000..12faed61a --- /dev/null +++ b/ios/RocketChat Watch App/Client/Adapters/RequestAdapter.swift @@ -0,0 +1,12 @@ +import Foundation + +protocol RequestAdapter { + func adapt(_ urlRequest: URLRequest) -> URLRequest + func adapt(_ url: URL) -> URL +} + +extension RequestAdapter { + func adapt(_ url: URL) -> URL { + url + } +} diff --git a/ios/RocketChat Watch App/Client/Adapters/TokenAdapter.swift b/ios/RocketChat Watch App/Client/Adapters/TokenAdapter.swift new file mode 100644 index 000000000..d5c9bea93 --- /dev/null +++ b/ios/RocketChat Watch App/Client/Adapters/TokenAdapter.swift @@ -0,0 +1,29 @@ +import Foundation + +struct TokenAdapter: RequestAdapter { + private let server: Server + + init(server: Server) { + self.server = server + } + + func adapt(_ url: URL) -> URL { + var url = url + + url.append( + queryItems: [ + URLQueryItem(name: "rc_token", value: server.loggedUser.token), + URLQueryItem(name: "rc_uid", value: server.loggedUser.id) + ] + ) + + return url + } + + func adapt(_ urlRequest: URLRequest) -> URLRequest { + var request = urlRequest + request.addValue(server.loggedUser.id, forHTTPHeaderField: "x-user-id") + request.addValue(server.loggedUser.token, forHTTPHeaderField: "x-auth-token") + return request + } +} diff --git a/ios/RocketChat Watch App/Client/DateCodingStrategy.swift b/ios/RocketChat Watch App/Client/DateCodingStrategy.swift new file mode 100644 index 000000000..0c90b296e --- /dev/null +++ b/ios/RocketChat Watch App/Client/DateCodingStrategy.swift @@ -0,0 +1,44 @@ +// https://stackoverflow.com/a/28016692 + +import Foundation + +extension Date.ISO8601FormatStyle { + static let iso8601withFractionalSeconds: Self = .init(includingFractionalSeconds: true) +} + +extension ParseStrategy where Self == Date.ISO8601FormatStyle { + static var iso8601withFractionalSeconds: Date.ISO8601FormatStyle { .iso8601withFractionalSeconds } +} + +extension FormatStyle where Self == Date.ISO8601FormatStyle { + static var iso8601withFractionalSeconds: Date.ISO8601FormatStyle { .iso8601withFractionalSeconds } +} + +extension Date { + init(iso8601withFractionalSeconds parseInput: ParseStrategy.ParseInput) throws { + try self.init(parseInput, strategy: .iso8601withFractionalSeconds) + } + + var iso8601withFractionalSeconds: String { + formatted(.iso8601withFractionalSeconds) + } +} + +extension String { + func iso8601withFractionalSeconds() throws -> Date { + try .init(iso8601withFractionalSeconds: self) + } +} + +extension JSONDecoder.DateDecodingStrategy { + static let iso8601withFractionalSeconds = custom { + try .init(iso8601withFractionalSeconds: $0.singleValueContainer().decode(String.self)) + } +} + +extension JSONEncoder.DateEncodingStrategy { + static let iso8601withFractionalSeconds = custom { + var container = $1.singleValueContainer() + try container.encode($0.iso8601withFractionalSeconds) + } +} diff --git a/ios/RocketChat Watch App/Client/Extensions/Data+Extensions.swift b/ios/RocketChat Watch App/Client/Extensions/Data+Extensions.swift new file mode 100644 index 000000000..20061d852 --- /dev/null +++ b/ios/RocketChat Watch App/Client/Extensions/Data+Extensions.swift @@ -0,0 +1,9 @@ +import Foundation + +extension Data { + func decode(_ type: T.Type) throws -> T { + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .iso8601withFractionalSeconds + return try decoder.decode(T.self, from: self) + } +} diff --git a/ios/RocketChat Watch App/Client/Extensions/String+Extensions.swift b/ios/RocketChat Watch App/Client/Extensions/String+Extensions.swift new file mode 100644 index 000000000..408e6a2d6 --- /dev/null +++ b/ios/RocketChat Watch App/Client/Extensions/String+Extensions.swift @@ -0,0 +1,6 @@ +extension String { + static func random(_ count: Int) -> String { + let letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + return String((0..: Codable, Hashable { + let value: Value? + + init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + value = try? container.decode(Value.self) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(value) + } +} diff --git a/ios/RocketChat Watch App/Client/HTTP/HTTPMethod.swift b/ios/RocketChat Watch App/Client/HTTP/HTTPMethod.swift new file mode 100644 index 000000000..4c91929c7 --- /dev/null +++ b/ios/RocketChat Watch App/Client/HTTP/HTTPMethod.swift @@ -0,0 +1,4 @@ +enum HTTPMethod: String { + case get = "GET" + case post = "POST" +} diff --git a/ios/RocketChat Watch App/Client/HTTP/Request.swift b/ios/RocketChat Watch App/Client/HTTP/Request.swift new file mode 100644 index 000000000..f652bcc15 --- /dev/null +++ b/ios/RocketChat Watch App/Client/HTTP/Request.swift @@ -0,0 +1,24 @@ +import Foundation + +protocol Request { + associatedtype Response: Codable + + var path: String { get } + var method: HTTPMethod { get } + var body: Data? { get } + var queryItems: [URLQueryItem] { get } +} + +extension Request { + var method: HTTPMethod { + .get + } + + var body: Data? { + nil + } + + var queryItems: [URLQueryItem] { + [] + } +} diff --git a/ios/RocketChat Watch App/Client/Requests/HistoryRequest.swift b/ios/RocketChat Watch App/Client/Requests/HistoryRequest.swift new file mode 100644 index 000000000..c5384de03 --- /dev/null +++ b/ios/RocketChat Watch App/Client/Requests/HistoryRequest.swift @@ -0,0 +1,43 @@ +import Foundation + +struct HistoryRequest: Request { + typealias Response = HistoryResponse + + let path: String + let queryItems: [URLQueryItem] + + init(roomId: String, roomType: String?, latest: Date) { + path = "api/v1/\(RoomType.from(roomType).api).history" + + queryItems = [ + URLQueryItem(name: "roomId", value: roomId), + URLQueryItem(name: "latest", value: latest.iso8601withFractionalSeconds) + ] + } +} + +fileprivate enum RoomType: String { + case direct = "d" + case group = "p" + case channel = "c" + case livechat = "l" + + static func from(_ rawValue: String?) -> Self { + guard let rawValue, let type = RoomType(rawValue: rawValue) else { + return .channel + } + + return type + } + + var api: String { + switch self { + case .direct: + return "im" + case .group: + return "groups" + case .channel, .livechat: + return "channels" + } + } +} diff --git a/ios/RocketChat Watch App/Client/Requests/MessagesRequest.swift b/ios/RocketChat Watch App/Client/Requests/MessagesRequest.swift new file mode 100644 index 000000000..c08f36734 --- /dev/null +++ b/ios/RocketChat Watch App/Client/Requests/MessagesRequest.swift @@ -0,0 +1,15 @@ +import Foundation + +struct MessagesRequest: Request { + typealias Response = MessagesResponse + + let path: String = "api/v1/chat.syncMessages" + let queryItems: [URLQueryItem] + + init(lastUpdate: Date?, roomId: String) { + self.queryItems = [ + URLQueryItem(name: "roomId", value: roomId), + URLQueryItem(name: "lastUpdate", value: lastUpdate?.ISO8601Format()) + ] + } +} diff --git a/ios/RocketChat Watch App/Client/Requests/ReadRequest.swift b/ios/RocketChat Watch App/Client/Requests/ReadRequest.swift new file mode 100644 index 000000000..9cfe4963d --- /dev/null +++ b/ios/RocketChat Watch App/Client/Requests/ReadRequest.swift @@ -0,0 +1,20 @@ +import Foundation + +struct ReadRequest: Request { + typealias Response = ReadResponse + + let path: String = "api/v1/subscriptions.read" + let method: HTTPMethod = .post + + var body: Data? { + try? JSONSerialization.data(withJSONObject: [ + "rid": rid + ]) + } + + let rid: String + + init(rid: String) { + self.rid = rid + } +} diff --git a/ios/RocketChat Watch App/Client/Requests/RoomsRequest.swift b/ios/RocketChat Watch App/Client/Requests/RoomsRequest.swift new file mode 100644 index 000000000..22cff2039 --- /dev/null +++ b/ios/RocketChat Watch App/Client/Requests/RoomsRequest.swift @@ -0,0 +1,16 @@ +import Foundation + +struct RoomsRequest: Request { + typealias Response = RoomsResponse + + let path: String = "api/v1/rooms.get" + let queryItems: [URLQueryItem] + + init(updatedSince: Date?) { + if let updatedSince { + queryItems = [URLQueryItem(name: "updatedSince", value: updatedSince.iso8601withFractionalSeconds)] + } else { + queryItems = [] + } + } +} diff --git a/ios/RocketChat Watch App/Client/Requests/SendMessageRequest.swift b/ios/RocketChat Watch App/Client/Requests/SendMessageRequest.swift new file mode 100644 index 000000000..5aaeffbc6 --- /dev/null +++ b/ios/RocketChat Watch App/Client/Requests/SendMessageRequest.swift @@ -0,0 +1,29 @@ +import Foundation + +struct SendMessageRequest: Request { + typealias Response = SendMessageResponse + + let path: String = "api/v1/chat.sendMessage" + let method: HTTPMethod = .post + + var body: Data? { + try? JSONSerialization.data(withJSONObject: [ + "message": [ + "_id": id, + "rid": rid, + "msg": msg, + "tshow": false + ] + ]) + } + + let id: String + let rid: String + let msg: String + + init(id: String, rid: String, msg: String) { + self.id = id + self.rid = rid + self.msg = msg + } +} diff --git a/ios/RocketChat Watch App/Client/Requests/SubscriptionsRequest.swift b/ios/RocketChat Watch App/Client/Requests/SubscriptionsRequest.swift new file mode 100644 index 000000000..dc92801d4 --- /dev/null +++ b/ios/RocketChat Watch App/Client/Requests/SubscriptionsRequest.swift @@ -0,0 +1,16 @@ +import Foundation + +struct SubscriptionsRequest: Request { + typealias Response = SubscriptionsResponse + + let path: String = "api/v1/subscriptions.get" + let queryItems: [URLQueryItem] + + init(updatedSince: Date?) { + if let updatedSince { + queryItems = [URLQueryItem(name: "updatedSince", value: updatedSince.iso8601withFractionalSeconds)] + } else { + queryItems = [] + } + } +} diff --git a/ios/RocketChat Watch App/Client/Responses/AttachmentResponse.swift b/ios/RocketChat Watch App/Client/Responses/AttachmentResponse.swift new file mode 100644 index 000000000..7b376e67c --- /dev/null +++ b/ios/RocketChat Watch App/Client/Responses/AttachmentResponse.swift @@ -0,0 +1,18 @@ +import Foundation + +struct AttachmentResponse: Codable, Hashable { + let imageURL: URL? + let description: String? + let dimensions: Dimensions? + + enum CodingKeys: String, CodingKey { + case imageURL = "image_url" + case description + case dimensions = "image_dimensions" + } + + struct Dimensions: Codable, Hashable { + let width: Double + let height: Double + } +} diff --git a/ios/RocketChat Watch App/Client/Responses/HistoryResponse.swift b/ios/RocketChat Watch App/Client/Responses/HistoryResponse.swift new file mode 100644 index 000000000..e13f6396d --- /dev/null +++ b/ios/RocketChat Watch App/Client/Responses/HistoryResponse.swift @@ -0,0 +1,6 @@ +import Foundation + +struct HistoryResponse: Codable { + let messages: [MessageResponse] + let success: Bool +} diff --git a/ios/RocketChat Watch App/Client/Responses/MessageResponse.swift b/ios/RocketChat Watch App/Client/Responses/MessageResponse.swift new file mode 100644 index 000000000..7487b3d82 --- /dev/null +++ b/ios/RocketChat Watch App/Client/Responses/MessageResponse.swift @@ -0,0 +1,12 @@ +import Foundation + +struct MessageResponse: Codable, Hashable { + let _id: String + let rid: String + let msg: String + let u: UserResponse + let ts: Date + let attachments: [AttachmentResponse]? + let t: String? + let groupable: Bool? +} diff --git a/ios/RocketChat Watch App/Client/Responses/MessagesResponse.swift b/ios/RocketChat Watch App/Client/Responses/MessagesResponse.swift new file mode 100644 index 000000000..3187cb22f --- /dev/null +++ b/ios/RocketChat Watch App/Client/Responses/MessagesResponse.swift @@ -0,0 +1,11 @@ +import Foundation + +struct MessagesResponse: Codable { + let result: MessagesResult + let success: Bool + + struct MessagesResult: Codable { + let updated: [MessageResponse] + let deleted: [MessageResponse] + } +} diff --git a/ios/RocketChat Watch App/Client/Responses/ReadResponse.swift b/ios/RocketChat Watch App/Client/Responses/ReadResponse.swift new file mode 100644 index 000000000..c153dcffb --- /dev/null +++ b/ios/RocketChat Watch App/Client/Responses/ReadResponse.swift @@ -0,0 +1,5 @@ +import Foundation + +struct ReadResponse: Codable { + +} diff --git a/ios/RocketChat Watch App/Client/Responses/RoomsResponse.swift b/ios/RocketChat Watch App/Client/Responses/RoomsResponse.swift new file mode 100644 index 000000000..df52a5dfb --- /dev/null +++ b/ios/RocketChat Watch App/Client/Responses/RoomsResponse.swift @@ -0,0 +1,26 @@ +import Foundation + +struct RoomsResponse: Codable { + let update: Set + let remove: Set + let success: Bool + + struct Room: Codable, Hashable { + let _id: String + let name: String? + let fname: String? + let prid: String? + let t: String? + let ts: Date? + let ro: Bool? + let _updatedAt: Date? + let encrypted: Bool? + let usernames: [String]? + let uids: [String]? + let lastMessage: FailableDecodable? + let lm: Date? + let teamMain: Bool? + let archived: Bool? + let broadcast: Bool? + } +} diff --git a/ios/RocketChat Watch App/Client/Responses/SendMessageResponse.swift b/ios/RocketChat Watch App/Client/Responses/SendMessageResponse.swift new file mode 100644 index 000000000..09b82c6ba --- /dev/null +++ b/ios/RocketChat Watch App/Client/Responses/SendMessageResponse.swift @@ -0,0 +1,6 @@ +import Foundation + +struct SendMessageResponse: Codable { + let message: MessageResponse + let success: Bool +} diff --git a/ios/RocketChat Watch App/Client/Responses/SubscriptionsResponse.swift b/ios/RocketChat Watch App/Client/Responses/SubscriptionsResponse.swift new file mode 100644 index 000000000..fd3bff26f --- /dev/null +++ b/ios/RocketChat Watch App/Client/Responses/SubscriptionsResponse.swift @@ -0,0 +1,26 @@ +import Foundation + +struct SubscriptionsResponse: Codable { + let update: Set + let remove: Set + let success: Bool + + struct Subscription: Codable, Hashable { + let _id: String + let rid: String + let name: String? + let fname: String? + let t: String + let unread: Int + let alert: Bool + let lr: Date? + } +} + +extension Sequence where Element == SubscriptionsResponse.Subscription { + func find(withRoomID rid: String) -> SubscriptionsResponse.Subscription? { + first { subscription in + subscription.rid == rid + } + } +} diff --git a/ios/RocketChat Watch App/Client/Responses/UserResponse.swift b/ios/RocketChat Watch App/Client/Responses/UserResponse.swift new file mode 100644 index 000000000..023fe5583 --- /dev/null +++ b/ios/RocketChat Watch App/Client/Responses/UserResponse.swift @@ -0,0 +1,7 @@ +import Foundation + +struct UserResponse: Codable, Hashable { + let _id: String + let username: String + let name: String +} diff --git a/ios/RocketChat Watch App/Client/RocketChatClient.swift b/ios/RocketChat Watch App/Client/RocketChatClient.swift new file mode 100644 index 000000000..9a7fab72d --- /dev/null +++ b/ios/RocketChat Watch App/Client/RocketChatClient.swift @@ -0,0 +1,91 @@ +import Combine +import Foundation + +protocol RocketChatClientProtocol { + func authorizedURL(url: URL) -> URL + func getRooms(updatedSince: Date?) -> AnyPublisher + func getSubscriptions(updatedSince: Date?) -> AnyPublisher + func getHistory(rid: String, t: String, latest: Date) -> AnyPublisher + func syncMessages(rid: String, updatedSince: Date) -> AnyPublisher + func sendMessage(id: String, rid: String, msg: String) -> AnyPublisher + func sendRead(rid: String) -> AnyPublisher +} + +final class RocketChatClient { + private let server: Server + + init(server: Server) { + self.server = server + } + + private var adapters: [RequestAdapter] { + [ + TokenAdapter(server: server), + JSONAdapter() + ] + } + + private func dataTask(for request: T) -> AnyPublisher { + let url = server.url.appending(path: request.path).appending(queryItems: request.queryItems) + + var urlRequest = adapters.reduce(URLRequest(url: url), { $1.adapt($0) }) + urlRequest.httpMethod = request.method.rawValue + urlRequest.httpBody = request.body + + return URLSession.shared.dataTaskPublisher(for: urlRequest) + .tryMap { (data, response) in + guard let httpResponse = response as? HTTPURLResponse, 200...299 ~= httpResponse.statusCode else { + throw RocketChatError.unauthorized + } + + return try data.decode(T.Response.self) + } + .mapError { error in + if let error = error as? DecodingError { + return .decoding(error: error) + } + if let error = error as? RocketChatError { + return error + } + + return .unknown(error: error) + } + .eraseToAnyPublisher() + } +} + +extension RocketChatClient: RocketChatClientProtocol { + func authorizedURL(url: URL) -> URL { + adapters.reduce(server.url.appending(path: url.relativePath), { $1.adapt($0) }) + } + + func getRooms(updatedSince: Date?) -> AnyPublisher { + let request = RoomsRequest(updatedSince: updatedSince) + return dataTask(for: request) + } + + func getSubscriptions(updatedSince: Date?) -> AnyPublisher { + let request = SubscriptionsRequest(updatedSince: updatedSince) + return dataTask(for: request) + } + + func getHistory(rid: String, t: String, latest: Date) -> AnyPublisher { + let request = HistoryRequest(roomId: rid, roomType: t, latest: latest) + return dataTask(for: request) + } + + func syncMessages(rid: String, updatedSince: Date) -> AnyPublisher { + let request = MessagesRequest(lastUpdate: updatedSince, roomId: rid) + return dataTask(for: request) + } + + func sendMessage(id: String, rid: String, msg: String) -> AnyPublisher { + let request = SendMessageRequest(id: id, rid: rid, msg: msg) + return dataTask(for: request) + } + + func sendRead(rid: String) -> AnyPublisher { + let request = ReadRequest(rid: rid) + return dataTask(for: request) + } +} diff --git a/ios/RocketChat Watch App/Client/RocketChatError.swift b/ios/RocketChat Watch App/Client/RocketChatError.swift new file mode 100644 index 000000000..4d0732688 --- /dev/null +++ b/ios/RocketChat Watch App/Client/RocketChatError.swift @@ -0,0 +1,7 @@ +import Foundation + +enum RocketChatError: Error { + case decoding(error: Error) + case unknown(error: Error) + case unauthorized +} diff --git a/ios/RocketChat Watch App/Client/RocketChatServer.swift b/ios/RocketChat Watch App/Client/RocketChatServer.swift new file mode 100644 index 000000000..5f5424d8a --- /dev/null +++ b/ios/RocketChat Watch App/Client/RocketChatServer.swift @@ -0,0 +1,21 @@ +import Foundation + +struct RocketChatServer { + let url: URL +} + +extension RocketChatServer { + static var `default`: Self { + .init(url: .server("https://open.rocket.chat")) + } +} + +fileprivate extension URL { + static func server(_ string: String) -> URL { + guard let url = URL(string: string) else { + fatalError("Could not initialize an url from \(string).") + } + + return url + } +} diff --git a/ios/RocketChat Watch App/ContentView.swift b/ios/RocketChat Watch App/ContentView.swift deleted file mode 100644 index 8ea2ef6fe..000000000 --- a/ios/RocketChat Watch App/ContentView.swift +++ /dev/null @@ -1,38 +0,0 @@ -import SwiftUI - -final class ContentViewModel: ObservableObject { - private let connection: Connection - - init(connection: Connection) { - self.connection = connection - } - - func onAppear() { - connection.sendMessage { result in - print(result) - } - } -} - -struct ContentView: View { - @StateObject var viewModel = ContentViewModel( - connection: WatchConnection() - ) - - var body: some View { - VStack { - Image(systemName: "globe") - .imageScale(.large) - .foregroundStyle(.tint) - Text("Hello, world!") - } - .padding() - .onAppear { - viewModel.onAppear() - } - } -} - -#Preview { - ContentView() -} diff --git a/ios/RocketChat Watch App/Database/Database.swift b/ios/RocketChat Watch App/Database/Database.swift new file mode 100644 index 000000000..2e9d75f91 --- /dev/null +++ b/ios/RocketChat Watch App/Database/Database.swift @@ -0,0 +1,97 @@ +import CoreData +import Foundation + +final class Database { + private let container: NSPersistentContainer + + var viewContext: NSManagedObjectContext { + container.viewContext + } + + private static let model: NSManagedObjectModel = { + guard let url = Bundle.main.url(forResource: "Default", withExtension: "momd"), + let managedObjectModel = NSManagedObjectModel(contentsOf: url) else { + fatalError("Can't find Core Data Model") + } + + return managedObjectModel + }() + + init() { + container = NSPersistentContainer(name: "default", managedObjectModel: Self.model) + + container.loadPersistentStores { _, error in + if let error { fatalError("Can't load persistent stores: \(error)") } + } + + container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy + } + + func save() { + guard container.viewContext.hasChanges else { + return + } + + try? container.viewContext.save() + } + + func server(url: URL) -> Server? { + let request = Server.fetchRequest() + request.predicate = NSPredicate(format: "url == %@", url.absoluteString) + + return try? viewContext.fetch(request).first + } + + func user(id: String) -> LoggedUser? { + let request = LoggedUser.fetchRequest() + request.predicate = NSPredicate(format: "id == %@", id) + + return try? viewContext.fetch(request).first + } + + func servers() -> [Server] { + let request = Server.fetchRequest() + + return (try? viewContext.fetch(request)) ?? [] + } + + func process(updatedServer: WatchMessage.Server) { + if let server = server(url: updatedServer.url) { + server.url = updatedServer.url + server.name = updatedServer.name + server.iconURL = updatedServer.iconURL + server.useRealName = updatedServer.useRealName + server.loggedUser = user(from: updatedServer.loggedUser) + } else { + Server( + context: viewContext, + iconURL: updatedServer.iconURL, + name: updatedServer.name, + url: updatedServer.url, + useRealName: updatedServer.useRealName, + loggedUser: user(from: updatedServer.loggedUser) + ) + } + + save() + } + + private func user(from updatedUser: WatchMessage.Server.LoggedUser) -> LoggedUser { + if let user = user(id: updatedUser.id) { + user.id = updatedUser.id + user.name = updatedUser.name + user.username = updatedUser.username + user.token = updatedUser.token + + return user + } + + return LoggedUser( + context: viewContext, + id: updatedUser.id, + name: updatedUser.name, + token: updatedUser.token, + username: updatedUser.username + ) + } +} diff --git a/ios/RocketChat Watch App/Database/Default.xcdatamodeld/Default.xcdatamodel/contents b/ios/RocketChat Watch App/Database/Default.xcdatamodeld/Default.xcdatamodel/contents new file mode 100644 index 000000000..5e10ac99e --- /dev/null +++ b/ios/RocketChat Watch App/Database/Default.xcdatamodeld/Default.xcdatamodel/contents @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ios/RocketChat Watch App/Database/Models/LoggedUser.swift b/ios/RocketChat Watch App/Database/Models/LoggedUser.swift new file mode 100644 index 000000000..d602e80df --- /dev/null +++ b/ios/RocketChat Watch App/Database/Models/LoggedUser.swift @@ -0,0 +1,48 @@ +import CoreData + +@objc +public final class LoggedUser: NSManagedObject { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + NSFetchRequest(entityName: "LoggedUser") + } + + @NSManaged public var id: String + @NSManaged public var name: String + @NSManaged public var token: String + @NSManaged public var username: String + + @available(*, unavailable) + init() { + fatalError() + } + + @available(*, unavailable) + init(context: NSManagedObjectContext) { + fatalError() + } + + public override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) { + super.init(entity: entity, insertInto: context) + } + + @discardableResult + public init( + context: NSManagedObjectContext, + id: String, + name: String, + token: String, + username: String + ) { + let entity = NSEntityDescription.entity(forEntityName: "LoggedUser", in: context)! + super.init(entity: entity, insertInto: context) + self.id = id + self.name = name + self.token = token + self.username = username + } +} + +extension LoggedUser: Identifiable { + +} diff --git a/ios/RocketChat Watch App/Database/Models/Room.swift b/ios/RocketChat Watch App/Database/Models/Room.swift new file mode 100644 index 000000000..aac3eb16b --- /dev/null +++ b/ios/RocketChat Watch App/Database/Models/Room.swift @@ -0,0 +1,13 @@ +import CoreData + +extension Room { + var lastMessage: Message? { + let request = Message.fetchRequest() + + request.predicate = NSPredicate(format: "room == %@", self) + request.sortDescriptors = [NSSortDescriptor(keyPath: \Message.ts, ascending: false)] + request.fetchLimit = 1 + + return try? managedObjectContext?.fetch(request).first + } +} diff --git a/ios/RocketChat Watch App/Database/Models/Server.swift b/ios/RocketChat Watch App/Database/Models/Server.swift new file mode 100644 index 000000000..84be1dfba --- /dev/null +++ b/ios/RocketChat Watch App/Database/Models/Server.swift @@ -0,0 +1,54 @@ +import CoreData + +@objc +public final class Server: NSManagedObject { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + NSFetchRequest(entityName: "Server") + } + + @NSManaged public var iconURL: URL + @NSManaged public var name: String + @NSManaged public var updatedSince: Date? + @NSManaged public var url: URL + @NSManaged public var useRealName: Bool + @NSManaged public var loggedUser: LoggedUser + + @available(*, unavailable) + init() { + fatalError() + } + + @available(*, unavailable) + init(context: NSManagedObjectContext) { + fatalError() + } + + public override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) { + super.init(entity: entity, insertInto: context) + } + + @discardableResult + public init( + context: NSManagedObjectContext, + iconURL: URL, + name: String, + updatedSince: Date? = nil, + url: URL, + useRealName: Bool, + loggedUser: LoggedUser + ) { + let entity = NSEntityDescription.entity(forEntityName: "Server", in: context)! + super.init(entity: entity, insertInto: context) + self.iconURL = iconURL + self.name = name + self.updatedSince = updatedSince + self.url = url + self.useRealName = useRealName + self.loggedUser = loggedUser + } +} + +extension Server: Identifiable { + +} diff --git a/ios/RocketChat Watch App/Database/RocketChat.xcdatamodeld/RocketChat.xcdatamodel/contents b/ios/RocketChat Watch App/Database/RocketChat.xcdatamodeld/RocketChat.xcdatamodel/contents new file mode 100644 index 000000000..8040459ed --- /dev/null +++ b/ios/RocketChat Watch App/Database/RocketChat.xcdatamodeld/RocketChat.xcdatamodel/contents @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ios/RocketChat Watch App/Database/RocketChatDatabase.swift b/ios/RocketChat Watch App/Database/RocketChatDatabase.swift new file mode 100644 index 000000000..5f815f9d2 --- /dev/null +++ b/ios/RocketChat Watch App/Database/RocketChatDatabase.swift @@ -0,0 +1,216 @@ +import CoreData + +final class RocketChatDatabase { + private let container: NSPersistentContainer + + var viewContext: NSManagedObjectContext { + container.viewContext + } + + private static let model: NSManagedObjectModel = { + guard let url = Bundle.main.url(forResource: "RocketChat", withExtension: "momd"), + let managedObjectModel = NSManagedObjectModel(contentsOf: url) else { + fatalError("Can't find Core Data Model") + } + + return managedObjectModel + }() + + init(name: String) { + container = NSPersistentContainer(name: name, managedObjectModel: Self.model) + + container.loadPersistentStores { _, error in + if let error { fatalError("Can't load persistent stores: \(error)") } + } + + container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy + } + + func save() { + guard container.viewContext.hasChanges else { + return + } + + try? container.viewContext.save() + } + + func createUser(id: String) -> User { + let user = User(context: viewContext) + user.id = id + + return user + } + + func createRoom(id: String) -> Room { + let room = Room(context: viewContext) + room.id = id + + return room + } + + func createMessage(id: String) -> Message { + let message = Message(context: viewContext) + message.id = id + message.ts = Date() + + return message + } + + func createAttachment(url: String) -> Attachment { + let attachment = Attachment(context: viewContext) + attachment.imageURL = URL(string: url) + + return attachment + } + + func createTempMessage(msg: String, in room: Room, for loggedUser: LoggedUser) -> String { + let id = String.random(17) + let message = message(id: id) ?? createMessage(id: id) + + message.id = id + message.ts = Date() + message.room = room + message.status = "temp" // TODO: + message.msg = msg + + let user = user(id: loggedUser.id) ?? createUser(id: loggedUser.id) + user.username = loggedUser.username + user.name = loggedUser.name + message.user = user + + return id + } + + func user(id: String) -> User? { + let user = User(context: viewContext) + user.id = id + + return user + } + + func room(id: String) -> Room? { + let request = Room.fetchRequest() + request.predicate = NSPredicate(format: "id == %@", id) + + return try? viewContext.fetch(request).first + } + + func message(id: String) -> Message? { + let request = Message.fetchRequest() + request.predicate = NSPredicate(format: "id == %@", id) + + return try? viewContext.fetch(request).first + } + + func attachment(url: String) -> Attachment? { + let request = Attachment.fetchRequest() + request.predicate = NSPredicate(format: "imageURL == %@", url) + + return try? viewContext.fetch(request).first + } + + func rooms(ids: [String]) -> [Room] { + let request = Room.fetchRequest() + request.predicate = NSPredicate(format: "ANY id IN %@", ids) + + return (try? viewContext.fetch(request)) ?? [] + } + + func process(updatedMessage: MessageResponse, in room: Room) { + let message = message(id: updatedMessage._id) ?? createMessage(id: updatedMessage._id) + + let user = user(id: updatedMessage.u._id) ?? createUser(id: updatedMessage.u._id) + user.name = updatedMessage.u.name + user.username = updatedMessage.u.username + + message.status = "received" // TODO: + message.id = updatedMessage._id + message.msg = updatedMessage.msg + message.room = room + message.ts = updatedMessage.ts + message.user = user + message.t = updatedMessage.t + message.groupable = updatedMessage.groupable ?? true + + updatedMessage.attachments?.forEach { attachment in + process(updatedAttachment: attachment, in: message) + } + + save() + } + + func process(updatedAttachment: AttachmentResponse, in message: Message) { + guard let url = updatedAttachment.imageURL?.absoluteString else { + return + } + + let attachment = attachment(url: url) ?? createAttachment(url: url) + + attachment.msg = updatedAttachment.description + attachment.message = message + attachment.width = updatedAttachment.dimensions?.width ?? 0 + attachment.height = updatedAttachment.dimensions?.height ?? 0 + } + + func process(subscription: SubscriptionsResponse.Subscription?, in updatedRoom: RoomsResponse.Room) { + let room = room(id: updatedRoom._id) ?? createRoom(id: updatedRoom._id) + + room.name = updatedRoom.name + room.fname = updatedRoom.fname + room.updatedAt = updatedRoom._updatedAt + room.t = updatedRoom.t + room.usernames = updatedRoom.usernames + room.uids = updatedRoom.uids + room.prid = updatedRoom.prid + room.isReadOnly = updatedRoom.ro ?? false + room.encrypted = updatedRoom.encrypted ?? false + room.teamMain = updatedRoom.teamMain ?? false + room.archived = updatedRoom.archived ?? false + room.broadcast = updatedRoom.broadcast ?? false + + if let subscription { + room.alert = subscription.alert + room.name = room.name ?? subscription.name + room.fname = room.fname ?? subscription.fname + room.unread = Int32(subscription.unread) + } + + if let lastMessage = updatedRoom.lastMessage?.value { + process(updatedMessage: lastMessage, in: room) + } + + let lastRoomUpdate = updatedRoom.lm ?? updatedRoom.ts ?? updatedRoom._updatedAt + + if let lr = subscription?.lr, let lastRoomUpdate { + room.ts = max(lr, lastRoomUpdate) + } else { + room.ts = lastRoomUpdate + } + + save() + } + + func process(subscription: SubscriptionsResponse.Subscription) { + let room = room(id: subscription.rid) ?? createRoom(id: subscription.rid) + + room.alert = subscription.alert + room.name = room.name ?? subscription.name + room.fname = room.fname ?? subscription.fname + room.unread = Int32(subscription.unread) + + if let lr = subscription.lr, let lastRoomUpdate = room.ts { + room.ts = max(lr, lastRoomUpdate) + } + + save() + } + + func markRead(in roomID: String) { + let room = room(id: roomID) ?? createRoom(id: roomID) + + room.alert = false + room.unread = 0 + + save() + } +} diff --git a/ios/RocketChat Watch App/DependencyStore.swift b/ios/RocketChat Watch App/DependencyStore.swift new file mode 100644 index 000000000..09e17581b --- /dev/null +++ b/ios/RocketChat Watch App/DependencyStore.swift @@ -0,0 +1,29 @@ +final class DependencyStore { + func client(for server: Server) -> RocketChatClientProtocol { + RocketChatClient(server: server) + } + + let connection = WatchConnection(session: .default) + + let database = Database() + + private var activeDatabase: WeakRef? + + func database(for server: Server) -> RocketChatDatabase { + if let activeDatabase = activeDatabase?.value { + return activeDatabase + } + + let database = RocketChatDatabase(name: server.url.host ?? "server") + activeDatabase = WeakRef(value: database) + return database + } +} + +private final class WeakRef { + weak var value: T? + + init(value: T) { + self.value = value + } +} diff --git a/ios/RocketChat Watch App/Localizable.xcstrings b/ios/RocketChat Watch App/Localizable.xcstrings new file mode 100644 index 000000000..41e4458d7 --- /dev/null +++ b/ios/RocketChat Watch App/Localizable.xcstrings @@ -0,0 +1,16 @@ +{ + "sourceLanguage" : "en", + "strings" : { + "Rooms" : { + + }, + "Servers" : { + "comment" : "View title for ServerList.", + "extractionState" : "manual" + }, + "Try Again" : { + + } + }, + "version" : "1.0" +} \ No newline at end of file diff --git a/ios/RocketChat Watch App/RocketChatApp.swift b/ios/RocketChat Watch App/RocketChatApp.swift index 82663ac45..c14b67a87 100644 --- a/ios/RocketChat Watch App/RocketChatApp.swift +++ b/ios/RocketChat Watch App/RocketChatApp.swift @@ -2,9 +2,52 @@ import SwiftUI @main struct RocketChat_Watch_AppApp: App { + private let store: DependencyStore + + @StateObject var router: RocketChatAppRouter + + init() { + let store = DependencyStore() + + self.store = store + self._router = StateObject(wrappedValue: RocketChatAppRouter(database: store.database)) + } + + @ViewBuilder + private var serverListView: some View { + ServerListView( + dependencies: .init( + connection: store.connection, + database: store.database, + router: router + ) + ) + } + + @ViewBuilder + private func roomListView(for server: Server) -> some View { + RoomListView( + dependencies: .init( + client: store.client(for: server), + database: store.database(for: server), + router: router, + server: server + ) + ) + } + var body: some Scene { WindowGroup { - ContentView() + NavigationStack { + switch router.route { + case .roomList(let server): + roomListView(for: server) + .environment(\.managedObjectContext, store.database(for: server).viewContext) + case .serverList: + serverListView + .environment(\.managedObjectContext, store.database.viewContext) + } + } } } } diff --git a/ios/RocketChat Watch App/RocketChatAppRouter.swift b/ios/RocketChat Watch App/RocketChatAppRouter.swift new file mode 100644 index 000000000..1a476c836 --- /dev/null +++ b/ios/RocketChat Watch App/RocketChatAppRouter.swift @@ -0,0 +1,37 @@ +import Foundation + +final class RocketChatAppRouter: ObservableObject { + @Storage("current_server") var currentServer: URL? + + @Published var route: Route = .serverList + + private let database: Database + + init(database: Database) { + self.database = database + loadRoute() + } + + private func loadRoute() { + if let currentServer, let server = database.server(url: currentServer) { + route = .roomList(server) + } else if database.servers().count == 1, let server = database.servers().first { + route = .roomList(server) + } else { + route = .serverList + } + } + + func route(to route: Route) { + DispatchQueue.main.async { + self.route = route + } + } +} + +extension RocketChatAppRouter { + enum Route { + case roomList(Server) + case serverList + } +} diff --git a/ios/RocketChat Watch App/Storage.swift b/ios/RocketChat Watch App/Storage.swift new file mode 100644 index 000000000..8930f84e8 --- /dev/null +++ b/ios/RocketChat Watch App/Storage.swift @@ -0,0 +1,28 @@ +import Foundation + +@propertyWrapper +struct Storage { + private let key: String + private let defaultValue: T? + + init(_ key: String, defaultValue: T? = nil) { + self.key = key + self.defaultValue = defaultValue + } + + var wrappedValue: T? { + get { + guard let data = UserDefaults.standard.object(forKey: key) as? Data else { + return defaultValue + } + + let value = try? JSONDecoder().decode(T.self, from: data) + return value ?? defaultValue + } + set { + let data = try? JSONEncoder().encode(newValue) + + UserDefaults.standard.set(data, forKey: key) + } + } +} diff --git a/ios/RocketChat Watch App/ViewModels/RoomListViewModel.swift b/ios/RocketChat Watch App/ViewModels/RoomListViewModel.swift new file mode 100644 index 000000000..9a93dcbd9 --- /dev/null +++ b/ios/RocketChat Watch App/ViewModels/RoomListViewModel.swift @@ -0,0 +1,90 @@ +import Combine +import Foundation + +protocol RoomListViewModeling { + func viewModel(for room: Room) -> RoomViewModel + + func loadRooms() + func logout() +} + +final class RoomListViewModel: ObservableObject { + struct Dependencies { + let client: RocketChatClientProtocol + let database: RocketChatDatabase + let router: RocketChatAppRouter + let server: Server + } + + private let dependencies: Dependencies + + private var loadCancellable: AnyCancellable? + + init(dependencies: Dependencies) { + self.dependencies = dependencies + } + + private func scheduledLoadRooms() { + Timer.scheduledTimer(withTimeInterval: 5, repeats: false) { [weak self] _ in + self?.loadRooms() + } + } + + private func handleError(_ error: RocketChatError) { + switch error { + case .decoding(let error): + print(error) + case .unknown(let error): + print(error) + case .unauthorized: + logout() // TODO: Remove database and server entry + } + } +} + +// MARK: - RoomListViewModeling + +extension RoomListViewModel: RoomListViewModeling { + func viewModel(for room: Room) -> RoomViewModel { + RoomViewModel(room: room, server: dependencies.server) + } + + func loadRooms() { + let newUpdatedSince = Date() + + let updatedSince = dependencies.server.updatedSince + + let client = dependencies.client + + loadCancellable = Publishers.Zip( + client.getRooms(updatedSince: updatedSince), + client.getSubscriptions(updatedSince: updatedSince) + ) + .receive(on: DispatchQueue.main) + .sink { completion in + if case .failure(let error) = completion { + self.handleError(error) + } + } receiveValue: { (roomsResponse, subscriptionsResponse) in + let rooms = roomsResponse.update + let subscriptions = subscriptionsResponse.update + + for room in rooms { + let subscription = subscriptions.find(withRoomID: room._id) + + self.dependencies.database.process(subscription: subscription, in: room) + } + + for subscription in subscriptions { + self.dependencies.database.process(subscription: subscription) + } + + self.scheduledLoadRooms() + self.dependencies.server.updatedSince = newUpdatedSince + } + } + + func logout() { + dependencies.router.route(to: .serverList) + } +} diff --git a/ios/RocketChat Watch App/ViewModels/RoomViewModel.swift b/ios/RocketChat Watch App/ViewModels/RoomViewModel.swift new file mode 100644 index 000000000..4188ccc80 --- /dev/null +++ b/ios/RocketChat Watch App/ViewModels/RoomViewModel.swift @@ -0,0 +1,142 @@ +import Foundation + +final class RoomViewModel: ObservableObject { + @Published var room: Room + @Published var server: Server + + init(room: Room, server: Server) { + self.room = room + self.server = server + } + + var title: String? { + if isGroupChat, (room.name == nil || room.name?.isEmpty == true), let usernames = room.usernames { + return usernames + .filter { $0 == server.loggedUser.username } + .sorted() + .joined(separator: ", ") + } + + if room.t != "d" { + if let fname = room.fname { + return fname + } else if let name = room.name { + return name + } + } + + if room.prid != nil || server.useRealName { + return room.fname + } + + return room.name + } + + var iconName: String? { + if room.prid != nil { + return "discussions" + } else if room.teamMain == true, room.t == "p" { + return "teams-private" + } else if room.teamMain == true { + return "teams" + } else if room.t == "p" { + return "channel-private" + } else if room.t == "c" { + return "channel-public" + } else if room.t == "d", isGroupChat { + return "message" + } + + return nil + } + + var lastMessage: String { + guard let user = room.lastMessage?.user else { + return "No Message" + } + + let isLastMessageSentByMe = user.username == server.loggedUser.username + let username = isLastMessageSentByMe ? "You" : ((server.useRealName ? user.name : user.username) ?? "") + let message = room.lastMessage?.msg ?? "No message" + + if room.lastMessage?.t == "jitsi_call_started" { + return "Call started by: \(username)" + } + + if room.lastMessage?.attachments?.allObjects.isEmpty == false { + return "\(username) sent an attachment" + } + + if room.lastMessage?.t == "e2e" { + return "Encrypted message" + } + + if room.lastMessage?.t == "videoconf" { + return "Call started" + } + + if room.t == "d", !isLastMessageSentByMe { + return message + } + + return "\(username): \(message)" + } + + var isGroupChat: Bool { + if let uids = room.uids, uids.count > 2 { + return true + } + + if let usernames = room.usernames, usernames.count > 2 { + return true + } + + return false + } + + var updatedAt: String? { + guard let ts = room.ts else { + return nil + } + + let calendar = Calendar.current + let dateFormatter = DateFormatter() + dateFormatter.locale = Locale.current + dateFormatter.timeZone = TimeZone.current + + if calendar.isDateInYesterday(ts) { + return "Yesterday" + } + + if calendar.isDateInToday(ts) { + dateFormatter.timeStyle = .short + dateFormatter.dateStyle = .none + + return dateFormatter.string(from: ts) + } + + if isInPreviousWeek(date: ts) { + dateFormatter.dateFormat = "EEEE" + + return dateFormatter.string(from: ts) + } + + dateFormatter.timeStyle = .none + dateFormatter.dateStyle = .short + + return dateFormatter.string(from: ts) + } + + private func isInPreviousWeek(date: Date) -> Bool { + let oneDay = 24 * 60 * 60 + let calendar = Calendar.current + let currentDate = Date() + let lastWeekDate = currentDate.addingTimeInterval(TimeInterval(-7 * oneDay)) + + return calendar.isDate( + date, + equalTo: lastWeekDate, + toGranularity: .weekOfYear + ) + } +} diff --git a/ios/RocketChat Watch App/ViewModels/ServerListViewModel.swift b/ios/RocketChat Watch App/ViewModels/ServerListViewModel.swift new file mode 100644 index 000000000..0e006ce0b --- /dev/null +++ b/ios/RocketChat Watch App/ViewModels/ServerListViewModel.swift @@ -0,0 +1,55 @@ +import Foundation + +enum ServerListState { + case loading + case loaded + case error(ConnectionError) +} + +final class ServerListViewModel: ObservableObject { + struct Dependencies { + let connection: Connection + let database: Database + let router: RocketChatAppRouter + } + + private let dependencies: Dependencies + + @Published private(set) var state: ServerListState = .loading + + init(dependencies: Dependencies) { + self.dependencies = dependencies + } + + private func handleSuccess(message: WatchMessage) { + message.servers.forEach(dependencies.database.process(updatedServer:)) + state = .loaded + } + + private func handleFailure(error: Error) { + guard let connectionError = error as? ConnectionError else { + return + } + + state = .error(connectionError) + } + + func loadServers() { + dependencies.connection.sendMessage { [weak self] result in + guard let self else { + return + } + + switch result { + case .success(let message): + DispatchQueue.main.async { self.handleSuccess(message: message) } + case .failure(let error): + DispatchQueue.main.async { self.handleFailure(error: error) } + } + } + } + + func didTap(server: Server) { + dependencies.router.route(to: .roomList(server)) + } +} diff --git a/ios/RocketChat Watch App/Views/RoomListView.swift b/ios/RocketChat Watch App/Views/RoomListView.swift new file mode 100644 index 000000000..8f45d6000 --- /dev/null +++ b/ios/RocketChat Watch App/Views/RoomListView.swift @@ -0,0 +1,39 @@ +import SwiftUI + +struct RoomListView: View { + @StateObject var viewModel: RoomListViewModel + + @FetchRequest( + entity: Room.entity(), + sortDescriptors: [ + NSSortDescriptor(keyPath: \Room.ts, ascending: false) + ], + predicate: NSPredicate(format: "archived == false"), + animation: .default + ) + private var rooms: FetchedResults + + init(dependencies: RoomListViewModel.Dependencies) { + _viewModel = StateObject(wrappedValue: RoomListViewModel(dependencies: dependencies)) + } + + var body: some View { + List { + ForEach(rooms) { room in + RoomView(viewModel: viewModel.viewModel(for: room)) + } + } + .onAppear { + viewModel.loadRooms() + } + .navigationTitle("Rooms") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .automatic) { + Button("Servers") { + viewModel.logout() + } + } + } + } +} diff --git a/ios/RocketChat Watch App/Views/RoomView.swift b/ios/RocketChat Watch App/Views/RoomView.swift new file mode 100644 index 000000000..86c9cf37e --- /dev/null +++ b/ios/RocketChat Watch App/Views/RoomView.swift @@ -0,0 +1,51 @@ +import SwiftUI + +struct RoomView: View { + @ObservedObject var viewModel: RoomViewModel + + private var isUnread: Bool { + viewModel.room.unread > 0 || viewModel.room.alert + } + + var body: some View { + VStack(alignment: .leading) { + HStack { + if let iconName = viewModel.iconName { + Image(iconName) + .resizable() + .frame(width: 16, height: 16) + .scaledToFit() + } + Text(viewModel.title ?? "") + .lineLimit(1) + .font(.caption) + .fontWeight(isUnread ? .bold : .medium) + .foregroundStyle(.primary) + Spacer() + Text(viewModel.updatedAt ?? "") + .lineLimit(1) + .font(.footnote) + .fontWeight(isUnread ? .bold : .medium) + .foregroundStyle(isUnread ? .blue : .primary) + } + HStack(alignment: .top) { + Text(viewModel.lastMessage) + .lineLimit(2) + .font(.caption2) + .foregroundStyle(isUnread ? .primary : .secondary) + Spacer() + if isUnread, viewModel.room.unread > 0 { + Text(String(viewModel.room.unread)) + .font(.footnote) + .fontWeight(.bold) + .padding(6) + .background( + Circle() + .fill(.blue) + ) + .foregroundColor(.primary) + } + } + } + } +} diff --git a/ios/RocketChat Watch App/Views/ServerListView.swift b/ios/RocketChat Watch App/Views/ServerListView.swift new file mode 100644 index 000000000..428137cfb --- /dev/null +++ b/ios/RocketChat Watch App/Views/ServerListView.swift @@ -0,0 +1,54 @@ +import SwiftUI + +struct ServerListView: View { + @StateObject var viewModel: ServerListViewModel + + @FetchRequest(entity: Server.entity(), sortDescriptors: [], animation: .default) + private var servers: FetchedResults + + init(dependencies: ServerListViewModel.Dependencies) { + _viewModel = StateObject(wrappedValue: ServerListViewModel(dependencies: dependencies)) + } + + @ViewBuilder + private func errorView(_ text: String) -> some View { + VStack(alignment: .center) { + Text(text) + Button("Try Again") { + viewModel.loadServers() + } + } + } + + var body: some View { + VStack { + switch viewModel.state { + case .loading: + ProgressView() + case .loaded where servers.count > 0: + List { + ForEach(servers) { server in + ServerView(server: server) + .onTapGesture { + viewModel.didTap(server: server) + } + } + } + case .loaded: + errorView("There are no servers connected.") + case .error(let connectionError): + switch connectionError { + case .needsUnlock: + errorView("You need to unlock your iPhone.") + case .decoding: + errorView("We can't read servers information.") + } + } + } + .navigationTitle("Servers") + .padding() + .onAppear { + viewModel.loadServers() + } + } +} diff --git a/ios/RocketChat Watch App/Views/ServerView.swift b/ios/RocketChat Watch App/Views/ServerView.swift new file mode 100644 index 000000000..f76e5e12e --- /dev/null +++ b/ios/RocketChat Watch App/Views/ServerView.swift @@ -0,0 +1,17 @@ +import SwiftUI + +struct ServerView: View { + @ObservedObject var server: Server + + var body: some View { + VStack(alignment: .leading) { + Text(server.name) + .font(.caption) + .fontWeight(.bold) + .foregroundStyle(.primary) + Text(server.url.host ?? "") + .font(.caption) + .foregroundStyle(.primary) + } + } +} diff --git a/ios/RocketChat Watch App/WatchConnection.swift b/ios/RocketChat Watch App/WatchConnection.swift index 1777917eb..7e56808c8 100644 --- a/ios/RocketChat Watch App/WatchConnection.swift +++ b/ios/RocketChat Watch App/WatchConnection.swift @@ -13,7 +13,7 @@ protocol Connection { final class WatchConnection: NSObject { private let session: WCSession - init(session: WCSession = .default) { + init(session: WCSession) { self.session = session super.init() session.delegate = self diff --git a/ios/RocketChatRN.xcodeproj/project.pbxproj b/ios/RocketChatRN.xcodeproj/project.pbxproj index 905edd950..b1a43ac5f 100644 --- a/ios/RocketChatRN.xcodeproj/project.pbxproj +++ b/ios/RocketChatRN.xcodeproj/project.pbxproj @@ -34,6 +34,39 @@ 1E1EA8182326CD4B00E22452 /* libc.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E1EA8172326CD4B00E22452 /* libc.tbd */; }; 1E1EA81A2326CD5100E22452 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E1EA8192326CD5100E22452 /* libsqlite3.tbd */; }; 1E25743422CBA2CF005A877F /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7ACD4853222860DE00442C55 /* JavaScriptCore.framework */; }; + 1E29A2CC2B5857F50093C03C /* RoomListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A2CB2B5857F50093C03C /* RoomListView.swift */; }; + 1E29A2CE2B5857FC0093C03C /* RoomListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A2CD2B5857FC0093C03C /* RoomListViewModel.swift */; }; + 1E29A2D02B58582F0093C03C /* RoomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A2CF2B58582F0093C03C /* RoomView.swift */; }; + 1E29A2EF2B585B070093C03C /* RocketChatClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A2D22B585B070093C03C /* RocketChatClient.swift */; }; + 1E29A2F02B585B070093C03C /* AttachmentResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A2D42B585B070093C03C /* AttachmentResponse.swift */; }; + 1E29A2F12B585B070093C03C /* SendMessageResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A2D52B585B070093C03C /* SendMessageResponse.swift */; }; + 1E29A2F22B585B070093C03C /* HistoryResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A2D62B585B070093C03C /* HistoryResponse.swift */; }; + 1E29A2F32B585B070093C03C /* MessagesResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A2D72B585B070093C03C /* MessagesResponse.swift */; }; + 1E29A2F42B585B070093C03C /* SubscriptionsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A2D82B585B070093C03C /* SubscriptionsResponse.swift */; }; + 1E29A2F52B585B070093C03C /* RoomsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A2D92B585B070093C03C /* RoomsResponse.swift */; }; + 1E29A2F62B585B070093C03C /* UserResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A2DA2B585B070093C03C /* UserResponse.swift */; }; + 1E29A2F72B585B070093C03C /* ReadResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A2DB2B585B070093C03C /* ReadResponse.swift */; }; + 1E29A2F82B585B070093C03C /* MessageResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A2DC2B585B070093C03C /* MessageResponse.swift */; }; + 1E29A2F92B585B070093C03C /* SubscriptionsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A2DE2B585B070093C03C /* SubscriptionsRequest.swift */; }; + 1E29A2FA2B585B070093C03C /* HistoryRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A2DF2B585B070093C03C /* HistoryRequest.swift */; }; + 1E29A2FB2B585B070093C03C /* MessagesRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A2E02B585B070093C03C /* MessagesRequest.swift */; }; + 1E29A2FC2B585B070093C03C /* SendMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A2E12B585B070093C03C /* SendMessageRequest.swift */; }; + 1E29A2FD2B585B070093C03C /* RoomsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A2E22B585B070093C03C /* RoomsRequest.swift */; }; + 1E29A2FE2B585B070093C03C /* ReadRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A2E32B585B070093C03C /* ReadRequest.swift */; }; + 1E29A2FF2B585B070093C03C /* TokenAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A2E52B585B070093C03C /* TokenAdapter.swift */; }; + 1E29A3002B585B070093C03C /* JSONAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A2E62B585B070093C03C /* JSONAdapter.swift */; }; + 1E29A3012B585B070093C03C /* RequestAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A2E72B585B070093C03C /* RequestAdapter.swift */; }; + 1E29A3022B585B070093C03C /* DateCodingStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A2E82B585B070093C03C /* DateCodingStrategy.swift */; }; + 1E29A3032B585B070093C03C /* FailableDecodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A2E92B585B070093C03C /* FailableDecodable.swift */; }; + 1E29A3042B585B070093C03C /* HTTPMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A2EB2B585B070093C03C /* HTTPMethod.swift */; }; + 1E29A3052B585B070093C03C /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A2EC2B585B070093C03C /* Request.swift */; }; + 1E29A3062B585B070093C03C /* RocketChatServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A2ED2B585B070093C03C /* RocketChatServer.swift */; }; + 1E29A3072B585B070093C03C /* RocketChatError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A2EE2B585B070093C03C /* RocketChatError.swift */; }; + 1E29A30A2B585B370093C03C /* Data+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A3092B585B370093C03C /* Data+Extensions.swift */; }; + 1E29A30C2B585D1D0093C03C /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A30B2B585D1D0093C03C /* String+Extensions.swift */; }; + 1E29A30E2B58608C0093C03C /* LoggedUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A30D2B58608C0093C03C /* LoggedUser.swift */; }; + 1E29A3102B5865B80093C03C /* RoomViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A30F2B5865B80093C03C /* RoomViewModel.swift */; }; + 1E29A3122B5866090093C03C /* Room.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A3112B5866090093C03C /* Room.swift */; }; 1E2F615B25128F9A00871711 /* API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E2F615A25128F9A00871711 /* API.swift */; }; 1E2F615D25128FA300871711 /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E2F615C25128FA300871711 /* Response.swift */; }; 1E2F61642512955D00871711 /* HTTPMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E2F61632512955D00871711 /* HTTPMethod.swift */; }; @@ -69,13 +102,24 @@ 1E76CBD825152C870067298C /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E680ED82512990700C9257A /* Request.swift */; }; 1E76CBD925152C8C0067298C /* Push.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E2F61652512958900871711 /* Push.swift */; }; 1E76CBDA25152C8E0067298C /* SendMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E598AE825151A63002BDFBD /* SendMessage.swift */; }; + 1EB375892B55DBFB00AEC3D7 /* Server.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EB375882B55DBFB00AEC3D7 /* Server.swift */; }; 1EB8EF722510F1EE00F352B7 /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EB8EF712510F1EE00F352B7 /* Storage.swift */; }; 1EC6ACB722CB9FC300A41C61 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1EC6ACB522CB9FC300A41C61 /* MainInterface.storyboard */; }; 1EC6ACBB22CB9FC300A41C61 /* ShareRocketChatRN.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 1EC6ACB022CB9FC300A41C61 /* ShareRocketChatRN.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 1EC6ACF622CBA01500A41C61 /* ShareRocketChatRN.m in Sources */ = {isa = PBXBuildFile; fileRef = 1EC6ACF522CBA01500A41C61 /* ShareRocketChatRN.m */; }; 1ED00BB12513E04400A1331F /* ReplyNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED00BB02513E04400A1331F /* ReplyNotification.swift */; }; + 1ED033AE2B55B1CC004F4930 /* Default.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 1ED033AC2B55B1CC004F4930 /* Default.xcdatamodeld */; }; + 1ED033B02B55B25A004F4930 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED033AF2B55B25A004F4930 /* Database.swift */; }; + 1ED033B62B55B4A5004F4930 /* ServerListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED033B52B55B4A5004F4930 /* ServerListView.swift */; }; + 1ED033B82B55B4BE004F4930 /* ServerListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED033B72B55B4BE004F4930 /* ServerListViewModel.swift */; }; + 1ED033BA2B55B5F6004F4930 /* ServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED033B92B55B5F6004F4930 /* ServerView.swift */; }; + 1ED033BF2B55BF94004F4930 /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED033BE2B55BF94004F4930 /* Storage.swift */; }; + 1ED033C12B55C190004F4930 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 1ED033C02B55C190004F4930 /* Localizable.xcstrings */; }; + 1ED033C42B55C65C004F4930 /* RocketChatAppRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED033C32B55C65C004F4930 /* RocketChatAppRouter.swift */; }; + 1ED033C82B55CE78004F4930 /* DependencyStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED033C72B55CE78004F4930 /* DependencyStore.swift */; }; + 1ED033CB2B55D4F0004F4930 /* RocketChat.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 1ED033C92B55D4F0004F4930 /* RocketChat.xcdatamodeld */; }; + 1ED033CD2B55D671004F4930 /* RocketChatDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED033CC2B55D671004F4930 /* RocketChatDatabase.swift */; }; 1ED038912B507B4C00C007D4 /* RocketChatApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038902B507B4C00C007D4 /* RocketChatApp.swift */; }; - 1ED038932B507B4C00C007D4 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038922B507B4C00C007D4 /* ContentView.swift */; }; 1ED038952B507B4D00C007D4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1ED038942B507B4D00C007D4 /* Assets.xcassets */; }; 1ED038982B507B4D00C007D4 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1ED038972B507B4D00C007D4 /* Preview Assets.xcassets */; }; 1ED0389B2B507B4D00C007D4 /* Rocket.Chat.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 1ED0388E2B507B4B00C007D4 /* Rocket.Chat.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; @@ -284,6 +328,39 @@ 1E1EA8152326CD4500E22452 /* VideoToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = VideoToolbox.framework; path = System/Library/Frameworks/VideoToolbox.framework; sourceTree = SDKROOT; }; 1E1EA8172326CD4B00E22452 /* libc.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libc.tbd; path = usr/lib/libc.tbd; sourceTree = SDKROOT; }; 1E1EA8192326CD5100E22452 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; }; + 1E29A2CB2B5857F50093C03C /* RoomListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomListView.swift; sourceTree = ""; }; + 1E29A2CD2B5857FC0093C03C /* RoomListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomListViewModel.swift; sourceTree = ""; }; + 1E29A2CF2B58582F0093C03C /* RoomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomView.swift; sourceTree = ""; }; + 1E29A2D22B585B070093C03C /* RocketChatClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RocketChatClient.swift; sourceTree = ""; }; + 1E29A2D42B585B070093C03C /* AttachmentResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentResponse.swift; sourceTree = ""; }; + 1E29A2D52B585B070093C03C /* SendMessageResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendMessageResponse.swift; sourceTree = ""; }; + 1E29A2D62B585B070093C03C /* HistoryResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HistoryResponse.swift; sourceTree = ""; }; + 1E29A2D72B585B070093C03C /* MessagesResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesResponse.swift; sourceTree = ""; }; + 1E29A2D82B585B070093C03C /* SubscriptionsResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionsResponse.swift; sourceTree = ""; }; + 1E29A2D92B585B070093C03C /* RoomsResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomsResponse.swift; sourceTree = ""; }; + 1E29A2DA2B585B070093C03C /* UserResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserResponse.swift; sourceTree = ""; }; + 1E29A2DB2B585B070093C03C /* ReadResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadResponse.swift; sourceTree = ""; }; + 1E29A2DC2B585B070093C03C /* MessageResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageResponse.swift; sourceTree = ""; }; + 1E29A2DE2B585B070093C03C /* SubscriptionsRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionsRequest.swift; sourceTree = ""; }; + 1E29A2DF2B585B070093C03C /* HistoryRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HistoryRequest.swift; sourceTree = ""; }; + 1E29A2E02B585B070093C03C /* MessagesRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesRequest.swift; sourceTree = ""; }; + 1E29A2E12B585B070093C03C /* SendMessageRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendMessageRequest.swift; sourceTree = ""; }; + 1E29A2E22B585B070093C03C /* RoomsRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomsRequest.swift; sourceTree = ""; }; + 1E29A2E32B585B070093C03C /* ReadRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadRequest.swift; sourceTree = ""; }; + 1E29A2E52B585B070093C03C /* TokenAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenAdapter.swift; sourceTree = ""; }; + 1E29A2E62B585B070093C03C /* JSONAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONAdapter.swift; sourceTree = ""; }; + 1E29A2E72B585B070093C03C /* RequestAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestAdapter.swift; sourceTree = ""; }; + 1E29A2E82B585B070093C03C /* DateCodingStrategy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateCodingStrategy.swift; sourceTree = ""; }; + 1E29A2E92B585B070093C03C /* FailableDecodable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FailableDecodable.swift; sourceTree = ""; }; + 1E29A2EB2B585B070093C03C /* HTTPMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPMethod.swift; sourceTree = ""; }; + 1E29A2EC2B585B070093C03C /* Request.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Request.swift; sourceTree = ""; }; + 1E29A2ED2B585B070093C03C /* RocketChatServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RocketChatServer.swift; sourceTree = ""; }; + 1E29A2EE2B585B070093C03C /* RocketChatError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RocketChatError.swift; sourceTree = ""; }; + 1E29A3092B585B370093C03C /* Data+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Extensions.swift"; sourceTree = ""; }; + 1E29A30B2B585D1D0093C03C /* String+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = ""; }; + 1E29A30D2B58608C0093C03C /* LoggedUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggedUser.swift; sourceTree = ""; }; + 1E29A30F2B5865B80093C03C /* RoomViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomViewModel.swift; sourceTree = ""; }; + 1E29A3112B5866090093C03C /* Room.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Room.swift; sourceTree = ""; }; 1E2F615A25128F9A00871711 /* API.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = API.swift; sourceTree = ""; }; 1E2F615C25128FA300871711 /* Response.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Response.swift; sourceTree = ""; }; 1E2F61632512955D00871711 /* HTTPMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPMethod.swift; sourceTree = ""; }; @@ -297,6 +374,7 @@ 1E6737FF24DC52660009E081 /* NotificationService-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NotificationService-Bridging-Header.h"; sourceTree = ""; }; 1E67380324DC529B0009E081 /* String+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = ""; }; 1E680ED82512990700C9257A /* Request.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Request.swift; sourceTree = ""; }; + 1EB375882B55DBFB00AEC3D7 /* Server.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Server.swift; sourceTree = ""; }; 1EB8EF712510F1EE00F352B7 /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = ""; }; 1EC6ACB022CB9FC300A41C61 /* ShareRocketChatRN.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ShareRocketChatRN.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 1EC6ACB622CB9FC300A41C61 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; @@ -304,9 +382,19 @@ 1EC6ACF522CBA01500A41C61 /* ShareRocketChatRN.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ShareRocketChatRN.m; sourceTree = ""; }; 1EC6AD6022CBA20C00A41C61 /* ShareRocketChatRN.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ShareRocketChatRN.entitlements; sourceTree = ""; }; 1ED00BB02513E04400A1331F /* ReplyNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyNotification.swift; sourceTree = ""; }; + 1ED033AD2B55B1CC004F4930 /* Default.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Default.xcdatamodel; sourceTree = ""; }; + 1ED033AF2B55B25A004F4930 /* Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Database.swift; sourceTree = ""; }; + 1ED033B52B55B4A5004F4930 /* ServerListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerListView.swift; sourceTree = ""; }; + 1ED033B72B55B4BE004F4930 /* ServerListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerListViewModel.swift; sourceTree = ""; }; + 1ED033B92B55B5F6004F4930 /* ServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerView.swift; sourceTree = ""; }; + 1ED033BE2B55BF94004F4930 /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = ""; }; + 1ED033C02B55C190004F4930 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; + 1ED033C32B55C65C004F4930 /* RocketChatAppRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RocketChatAppRouter.swift; sourceTree = ""; }; + 1ED033C72B55CE78004F4930 /* DependencyStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DependencyStore.swift; sourceTree = ""; }; + 1ED033CA2B55D4F0004F4930 /* RocketChat.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = RocketChat.xcdatamodel; sourceTree = ""; }; + 1ED033CC2B55D671004F4930 /* RocketChatDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RocketChatDatabase.swift; sourceTree = ""; }; 1ED0388E2B507B4B00C007D4 /* Rocket.Chat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Rocket.Chat.app; sourceTree = BUILT_PRODUCTS_DIR; }; 1ED038902B507B4C00C007D4 /* RocketChatApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RocketChatApp.swift; sourceTree = ""; }; - 1ED038922B507B4C00C007D4 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 1ED038942B507B4D00C007D4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 1ED038972B507B4D00C007D4 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 1ED038A02B508FE700C007D4 /* FileManager+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileManager+Extensions.swift"; sourceTree = ""; }; @@ -466,6 +554,80 @@ path = API; sourceTree = ""; }; + 1E29A2D12B585B070093C03C /* Client */ = { + isa = PBXGroup; + children = ( + 1E29A3082B585B2F0093C03C /* Extensions */, + 1E29A2D22B585B070093C03C /* RocketChatClient.swift */, + 1E29A2D32B585B070093C03C /* Responses */, + 1E29A2DD2B585B070093C03C /* Requests */, + 1E29A2E42B585B070093C03C /* Adapters */, + 1E29A2E82B585B070093C03C /* DateCodingStrategy.swift */, + 1E29A2E92B585B070093C03C /* FailableDecodable.swift */, + 1E29A2EA2B585B070093C03C /* HTTP */, + 1E29A2ED2B585B070093C03C /* RocketChatServer.swift */, + 1E29A2EE2B585B070093C03C /* RocketChatError.swift */, + ); + path = Client; + sourceTree = ""; + }; + 1E29A2D32B585B070093C03C /* Responses */ = { + isa = PBXGroup; + children = ( + 1E29A2D42B585B070093C03C /* AttachmentResponse.swift */, + 1E29A2D52B585B070093C03C /* SendMessageResponse.swift */, + 1E29A2D62B585B070093C03C /* HistoryResponse.swift */, + 1E29A2D72B585B070093C03C /* MessagesResponse.swift */, + 1E29A2D82B585B070093C03C /* SubscriptionsResponse.swift */, + 1E29A2D92B585B070093C03C /* RoomsResponse.swift */, + 1E29A2DA2B585B070093C03C /* UserResponse.swift */, + 1E29A2DB2B585B070093C03C /* ReadResponse.swift */, + 1E29A2DC2B585B070093C03C /* MessageResponse.swift */, + ); + path = Responses; + sourceTree = ""; + }; + 1E29A2DD2B585B070093C03C /* Requests */ = { + isa = PBXGroup; + children = ( + 1E29A2DE2B585B070093C03C /* SubscriptionsRequest.swift */, + 1E29A2DF2B585B070093C03C /* HistoryRequest.swift */, + 1E29A2E02B585B070093C03C /* MessagesRequest.swift */, + 1E29A2E12B585B070093C03C /* SendMessageRequest.swift */, + 1E29A2E22B585B070093C03C /* RoomsRequest.swift */, + 1E29A2E32B585B070093C03C /* ReadRequest.swift */, + ); + path = Requests; + sourceTree = ""; + }; + 1E29A2E42B585B070093C03C /* Adapters */ = { + isa = PBXGroup; + children = ( + 1E29A2E52B585B070093C03C /* TokenAdapter.swift */, + 1E29A2E62B585B070093C03C /* JSONAdapter.swift */, + 1E29A2E72B585B070093C03C /* RequestAdapter.swift */, + ); + path = Adapters; + sourceTree = ""; + }; + 1E29A2EA2B585B070093C03C /* HTTP */ = { + isa = PBXGroup; + children = ( + 1E29A2EB2B585B070093C03C /* HTTPMethod.swift */, + 1E29A2EC2B585B070093C03C /* Request.swift */, + ); + path = HTTP; + sourceTree = ""; + }; + 1E29A3082B585B2F0093C03C /* Extensions */ = { + isa = PBXGroup; + children = ( + 1E29A3092B585B370093C03C /* Data+Extensions.swift */, + 1E29A30B2B585D1D0093C03C /* String+Extensions.swift */, + ); + path = Extensions; + sourceTree = ""; + }; 1E2F61622512954500871711 /* Requests */ = { isa = PBXGroup; children = ( @@ -528,6 +690,16 @@ path = Models; sourceTree = ""; }; + 1EB375872B55DBF400AEC3D7 /* Models */ = { + isa = PBXGroup; + children = ( + 1EB375882B55DBFB00AEC3D7 /* Server.swift */, + 1E29A30D2B58608C0093C03C /* LoggedUser.swift */, + 1E29A3112B5866090093C03C /* Room.swift */, + ); + path = Models; + sourceTree = ""; + }; 1EC6ACB122CB9FC300A41C61 /* ShareRocketChatRN */ = { isa = PBXGroup; children = ( @@ -540,14 +712,54 @@ path = ShareRocketChatRN; sourceTree = ""; }; + 1ED033AB2B55B1C2004F4930 /* Database */ = { + isa = PBXGroup; + children = ( + 1EB375872B55DBF400AEC3D7 /* Models */, + 1ED033AC2B55B1CC004F4930 /* Default.xcdatamodeld */, + 1ED033AF2B55B25A004F4930 /* Database.swift */, + 1ED033C92B55D4F0004F4930 /* RocketChat.xcdatamodeld */, + 1ED033CC2B55D671004F4930 /* RocketChatDatabase.swift */, + ); + path = Database; + sourceTree = ""; + }; + 1ED033B12B55B47F004F4930 /* Views */ = { + isa = PBXGroup; + children = ( + 1ED033B52B55B4A5004F4930 /* ServerListView.swift */, + 1ED033B92B55B5F6004F4930 /* ServerView.swift */, + 1E29A2CB2B5857F50093C03C /* RoomListView.swift */, + 1E29A2CF2B58582F0093C03C /* RoomView.swift */, + ); + path = Views; + sourceTree = ""; + }; + 1ED033B42B55B495004F4930 /* ViewModels */ = { + isa = PBXGroup; + children = ( + 1ED033B72B55B4BE004F4930 /* ServerListViewModel.swift */, + 1E29A2CD2B5857FC0093C03C /* RoomListViewModel.swift */, + 1E29A30F2B5865B80093C03C /* RoomViewModel.swift */, + ); + path = ViewModels; + sourceTree = ""; + }; 1ED0388F2B507B4C00C007D4 /* RocketChat Watch App */ = { isa = PBXGroup; children = ( + 1E29A2D12B585B070093C03C /* Client */, + 1ED033B42B55B495004F4930 /* ViewModels */, + 1ED033B12B55B47F004F4930 /* Views */, + 1ED033AB2B55B1C2004F4930 /* Database */, 1ED038902B507B4C00C007D4 /* RocketChatApp.swift */, - 1ED038922B507B4C00C007D4 /* ContentView.swift */, + 1ED033C32B55C65C004F4930 /* RocketChatAppRouter.swift */, + 1ED038C92B50A58400C007D4 /* WatchConnection.swift */, + 1ED033BE2B55BF94004F4930 /* Storage.swift */, 1ED038942B507B4D00C007D4 /* Assets.xcassets */, 1ED038962B507B4D00C007D4 /* Preview Content */, - 1ED038C92B50A58400C007D4 /* WatchConnection.swift */, + 1ED033C02B55C190004F4930 /* Localizable.xcstrings */, + 1ED033C72B55CE78004F4930 /* DependencyStore.swift */, ); path = "RocketChat Watch App"; sourceTree = ""; @@ -955,6 +1167,7 @@ files = ( 1ED038982B507B4D00C007D4 /* Preview Assets.xcassets in Resources */, 1ED038952B507B4D00C007D4 /* Assets.xcassets in Resources */, + 1ED033C12B55C190004F4930 /* Localizable.xcstrings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1549,10 +1762,53 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 1ED038932B507B4C00C007D4 /* ContentView.swift in Sources */, + 1EB375892B55DBFB00AEC3D7 /* Server.swift in Sources */, + 1E29A2F42B585B070093C03C /* SubscriptionsResponse.swift in Sources */, + 1E29A2F92B585B070093C03C /* SubscriptionsRequest.swift in Sources */, + 1E29A2F22B585B070093C03C /* HistoryResponse.swift in Sources */, + 1ED033BA2B55B5F6004F4930 /* ServerView.swift in Sources */, + 1E29A2D02B58582F0093C03C /* RoomView.swift in Sources */, + 1E29A2FD2B585B070093C03C /* RoomsRequest.swift in Sources */, 1ED038CA2B50A58400C007D4 /* WatchConnection.swift in Sources */, + 1E29A3002B585B070093C03C /* JSONAdapter.swift in Sources */, + 1E29A3022B585B070093C03C /* DateCodingStrategy.swift in Sources */, + 1ED033B62B55B4A5004F4930 /* ServerListView.swift in Sources */, + 1E29A3102B5865B80093C03C /* RoomViewModel.swift in Sources */, + 1E29A2FC2B585B070093C03C /* SendMessageRequest.swift in Sources */, + 1E29A30C2B585D1D0093C03C /* String+Extensions.swift in Sources */, + 1ED033CD2B55D671004F4930 /* RocketChatDatabase.swift in Sources */, + 1E29A3122B5866090093C03C /* Room.swift in Sources */, + 1E29A3032B585B070093C03C /* FailableDecodable.swift in Sources */, + 1E29A2FE2B585B070093C03C /* ReadRequest.swift in Sources */, + 1E29A3062B585B070093C03C /* RocketChatServer.swift in Sources */, + 1E29A3072B585B070093C03C /* RocketChatError.swift in Sources */, + 1E29A2F12B585B070093C03C /* SendMessageResponse.swift in Sources */, + 1E29A30E2B58608C0093C03C /* LoggedUser.swift in Sources */, + 1E29A2FF2B585B070093C03C /* TokenAdapter.swift in Sources */, + 1E29A3052B585B070093C03C /* Request.swift in Sources */, + 1E29A2EF2B585B070093C03C /* RocketChatClient.swift in Sources */, + 1E29A2FB2B585B070093C03C /* MessagesRequest.swift in Sources */, + 1E29A2F62B585B070093C03C /* UserResponse.swift in Sources */, + 1ED033AE2B55B1CC004F4930 /* Default.xcdatamodeld in Sources */, + 1ED033BF2B55BF94004F4930 /* Storage.swift in Sources */, + 1E29A2F82B585B070093C03C /* MessageResponse.swift in Sources */, + 1E29A3042B585B070093C03C /* HTTPMethod.swift in Sources */, + 1E29A3012B585B070093C03C /* RequestAdapter.swift in Sources */, + 1E29A2CE2B5857FC0093C03C /* RoomListViewModel.swift in Sources */, + 1E29A2F52B585B070093C03C /* RoomsResponse.swift in Sources */, + 1E29A2F32B585B070093C03C /* MessagesResponse.swift in Sources */, + 1E29A2FA2B585B070093C03C /* HistoryRequest.swift in Sources */, 1ED038C62B50A21800C007D4 /* WatchMessage.swift in Sources */, + 1E29A2F02B585B070093C03C /* AttachmentResponse.swift in Sources */, 1ED038912B507B4C00C007D4 /* RocketChatApp.swift in Sources */, + 1E29A2CC2B5857F50093C03C /* RoomListView.swift in Sources */, + 1ED033B82B55B4BE004F4930 /* ServerListViewModel.swift in Sources */, + 1ED033C42B55C65C004F4930 /* RocketChatAppRouter.swift in Sources */, + 1ED033B02B55B25A004F4930 /* Database.swift in Sources */, + 1ED033C82B55CE78004F4930 /* DependencyStore.swift in Sources */, + 1E29A30A2B585B370093C03C /* Data+Extensions.swift in Sources */, + 1E29A2F72B585B070093C03C /* ReadResponse.swift in Sources */, + 1ED033CB2B55D4F0004F4930 /* RocketChat.xcdatamodeld in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1966,7 +2222,7 @@ SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 4; - WATCHOS_DEPLOYMENT_TARGET = 8.0; + WATCHOS_DEPLOYMENT_TARGET = 9.0; }; name = Debug; }; @@ -2008,7 +2264,7 @@ SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 4; - WATCHOS_DEPLOYMENT_TARGET = 8.0; + WATCHOS_DEPLOYMENT_TARGET = 9.0; }; name = Release; }; @@ -2356,6 +2612,29 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCVersionGroup section */ + 1ED033AC2B55B1CC004F4930 /* Default.xcdatamodeld */ = { + isa = XCVersionGroup; + children = ( + 1ED033AD2B55B1CC004F4930 /* Default.xcdatamodel */, + ); + currentVersion = 1ED033AD2B55B1CC004F4930 /* Default.xcdatamodel */; + path = Default.xcdatamodeld; + sourceTree = ""; + versionGroupType = wrapper.xcdatamodel; + }; + 1ED033C92B55D4F0004F4930 /* RocketChat.xcdatamodeld */ = { + isa = XCVersionGroup; + children = ( + 1ED033CA2B55D4F0004F4930 /* RocketChat.xcdatamodel */, + ); + currentVersion = 1ED033CA2B55D4F0004F4930 /* RocketChat.xcdatamodel */; + path = RocketChat.xcdatamodeld; + sourceTree = ""; + versionGroupType = wrapper.xcdatamodel; + }; +/* End XCVersionGroup section */ }; rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; }