re-indent

This commit is contained in:
Djorkaeff Alexandre 2024-01-18 20:54:38 -03:00
parent 2fdf0e2c3b
commit 8f3bb1a99b
29 changed files with 369 additions and 394 deletions

View File

@ -1,10 +1,10 @@
import Foundation import Foundation
struct JSONAdapter: RequestAdapter { struct JSONAdapter: RequestAdapter {
func adapt(_ urlRequest: URLRequest) -> URLRequest { func adapt(_ urlRequest: URLRequest) -> URLRequest {
var request = urlRequest var request = urlRequest
request.addValue("application/json", forHTTPHeaderField: "Content-Type") request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept") request.addValue("application/json", forHTTPHeaderField: "Accept")
return request return request
} }
} }

View File

@ -1,12 +1,12 @@
import Foundation import Foundation
protocol RequestAdapter { protocol RequestAdapter {
func adapt(_ urlRequest: URLRequest) -> URLRequest func adapt(_ urlRequest: URLRequest) -> URLRequest
func adapt(_ url: URL) -> URL func adapt(_ url: URL) -> URL
} }
extension RequestAdapter { extension RequestAdapter {
func adapt(_ url: URL) -> URL { func adapt(_ url: URL) -> URL {
url url
} }
} }

View File

@ -1,29 +1,29 @@
import Foundation import Foundation
struct TokenAdapter: RequestAdapter { struct TokenAdapter: RequestAdapter {
private let server: Server private let server: Server
init(server: Server) { init(server: Server) {
self.server = server self.server = server
} }
func adapt(_ url: URL) -> URL { func adapt(_ url: URL) -> URL {
var url = url var url = url
url.append( url.append(
queryItems: [ queryItems: [
URLQueryItem(name: "rc_token", value: server.loggedUser.token), URLQueryItem(name: "rc_token", value: server.loggedUser.token),
URLQueryItem(name: "rc_uid", value: server.loggedUser.id) URLQueryItem(name: "rc_uid", value: server.loggedUser.id)
] ]
) )
return url return url
} }
func adapt(_ urlRequest: URLRequest) -> URLRequest { func adapt(_ urlRequest: URLRequest) -> URLRequest {
var request = urlRequest var request = urlRequest
request.addValue(server.loggedUser.id, forHTTPHeaderField: "x-user-id") request.addValue(server.loggedUser.id, forHTTPHeaderField: "x-user-id")
request.addValue(server.loggedUser.token, forHTTPHeaderField: "x-auth-token") request.addValue(server.loggedUser.token, forHTTPHeaderField: "x-auth-token")
return request return request
} }
} }

View File

@ -3,42 +3,42 @@
import Foundation import Foundation
extension Date.ISO8601FormatStyle { extension Date.ISO8601FormatStyle {
static let iso8601withFractionalSeconds: Self = .init(includingFractionalSeconds: true) static let iso8601withFractionalSeconds: Self = .init(includingFractionalSeconds: true)
} }
extension ParseStrategy where Self == Date.ISO8601FormatStyle { extension ParseStrategy where Self == Date.ISO8601FormatStyle {
static var iso8601withFractionalSeconds: Date.ISO8601FormatStyle { .iso8601withFractionalSeconds } static var iso8601withFractionalSeconds: Date.ISO8601FormatStyle { .iso8601withFractionalSeconds }
} }
extension FormatStyle where Self == Date.ISO8601FormatStyle { extension FormatStyle where Self == Date.ISO8601FormatStyle {
static var iso8601withFractionalSeconds: Date.ISO8601FormatStyle { .iso8601withFractionalSeconds } static var iso8601withFractionalSeconds: Date.ISO8601FormatStyle { .iso8601withFractionalSeconds }
} }
extension Date { extension Date {
init(iso8601withFractionalSeconds parseInput: ParseStrategy.ParseInput) throws { init(iso8601withFractionalSeconds parseInput: ParseStrategy.ParseInput) throws {
try self.init(parseInput, strategy: .iso8601withFractionalSeconds) try self.init(parseInput, strategy: .iso8601withFractionalSeconds)
} }
var iso8601withFractionalSeconds: String { var iso8601withFractionalSeconds: String {
formatted(.iso8601withFractionalSeconds) formatted(.iso8601withFractionalSeconds)
} }
} }
extension String { extension String {
func iso8601withFractionalSeconds() throws -> Date { func iso8601withFractionalSeconds() throws -> Date {
try .init(iso8601withFractionalSeconds: self) try .init(iso8601withFractionalSeconds: self)
} }
} }
extension JSONDecoder.DateDecodingStrategy { extension JSONDecoder.DateDecodingStrategy {
static let iso8601withFractionalSeconds = custom { static let iso8601withFractionalSeconds = custom {
try .init(iso8601withFractionalSeconds: $0.singleValueContainer().decode(String.self)) try .init(iso8601withFractionalSeconds: $0.singleValueContainer().decode(String.self))
} }
} }
extension JSONEncoder.DateEncodingStrategy { extension JSONEncoder.DateEncodingStrategy {
static let iso8601withFractionalSeconds = custom { static let iso8601withFractionalSeconds = custom {
var container = $1.singleValueContainer() var container = $1.singleValueContainer()
try container.encode($0.iso8601withFractionalSeconds) try container.encode($0.iso8601withFractionalSeconds)
} }
} }

View File

@ -1,9 +1,9 @@
import Foundation import Foundation
extension Data { extension Data {
func decode<T: Decodable>(_ type: T.Type) throws -> T { func decode<T: Decodable>(_ type: T.Type) throws -> T {
let decoder = JSONDecoder() let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601withFractionalSeconds decoder.dateDecodingStrategy = .iso8601withFractionalSeconds
return try decoder.decode(T.self, from: self) return try decoder.decode(T.self, from: self)
} }
} }

View File

@ -1,6 +1,6 @@
extension String { extension String {
static func random(_ count: Int) -> String { static func random(_ count: Int) -> String {
let letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" let letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
return String((0..<count).compactMap { _ in letters.randomElement() }) return String((0..<count).compactMap { _ in letters.randomElement() })
} }
} }

View File

@ -1,13 +1,13 @@
struct FailableDecodable<Value: Codable & Hashable>: Codable, Hashable { struct FailableDecodable<Value: Codable & Hashable>: Codable, Hashable {
let value: Value? let value: Value?
init(from decoder: Decoder) throws { init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer() let container = try decoder.singleValueContainer()
value = try? container.decode(Value.self) value = try? container.decode(Value.self)
} }
func encode(to encoder: Encoder) throws { func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer() var container = encoder.singleValueContainer()
try container.encode(value) try container.encode(value)
} }
} }

View File

@ -1,4 +1,4 @@
enum HTTPMethod: String { enum HTTPMethod: String {
case get = "GET" case get = "GET"
case post = "POST" case post = "POST"
} }

View File

@ -1,24 +1,24 @@
import Foundation import Foundation
protocol Request<Response> { protocol Request<Response> {
associatedtype Response: Codable associatedtype Response: Codable
var path: String { get } var path: String { get }
var method: HTTPMethod { get } var method: HTTPMethod { get }
var body: Data? { get } var body: Data? { get }
var queryItems: [URLQueryItem] { get } var queryItems: [URLQueryItem] { get }
} }
extension Request { extension Request {
var method: HTTPMethod { var method: HTTPMethod {
.get .get
} }
var body: Data? { var body: Data? {
nil nil
} }
var queryItems: [URLQueryItem] { var queryItems: [URLQueryItem] {
[] []
} }
} }

View File

@ -3,44 +3,44 @@ import Foundation
let HISTORY_MESSAGE_COUNT = 50 let HISTORY_MESSAGE_COUNT = 50
struct HistoryRequest: Request { struct HistoryRequest: Request {
typealias Response = HistoryResponse typealias Response = HistoryResponse
let path: String let path: String
let queryItems: [URLQueryItem] let queryItems: [URLQueryItem]
init(roomId: String, roomType: String?, latest: Date) { init(roomId: String, roomType: String?, latest: Date) {
path = "api/v1/\(RoomType.from(roomType).api).history" path = "api/v1/\(RoomType.from(roomType).api).history"
queryItems = [ queryItems = [
URLQueryItem(name: "roomId", value: roomId), URLQueryItem(name: "roomId", value: roomId),
URLQueryItem(name: "count", value: String(HISTORY_MESSAGE_COUNT)), URLQueryItem(name: "count", value: String(HISTORY_MESSAGE_COUNT)),
URLQueryItem(name: "latest", value: latest.iso8601withFractionalSeconds) URLQueryItem(name: "latest", value: latest.iso8601withFractionalSeconds)
] ]
} }
} }
fileprivate enum RoomType: String { fileprivate enum RoomType: String {
case direct = "d" case direct = "d"
case group = "p" case group = "p"
case channel = "c" case channel = "c"
case livechat = "l" case livechat = "l"
static func from(_ rawValue: String?) -> Self { static func from(_ rawValue: String?) -> Self {
guard let rawValue, let type = RoomType(rawValue: rawValue) else { guard let rawValue, let type = RoomType(rawValue: rawValue) else {
return .channel return .channel
} }
return type return type
} }
var api: String { var api: String {
switch self { switch self {
case .direct: case .direct:
return "im" return "im"
case .group: case .group:
return "groups" return "groups"
case .channel, .livechat: case .channel, .livechat:
return "channels" return "channels"
} }
} }
} }

View File

@ -1,15 +1,15 @@
import Foundation import Foundation
struct MessagesRequest: Request { struct MessagesRequest: Request {
typealias Response = MessagesResponse typealias Response = MessagesResponse
let path: String = "api/v1/chat.syncMessages" let path: String = "api/v1/chat.syncMessages"
let queryItems: [URLQueryItem] let queryItems: [URLQueryItem]
init(lastUpdate: Date?, roomId: String) { init(lastUpdate: Date?, roomId: String) {
self.queryItems = [ self.queryItems = [
URLQueryItem(name: "roomId", value: roomId), URLQueryItem(name: "roomId", value: roomId),
URLQueryItem(name: "lastUpdate", value: lastUpdate?.ISO8601Format()) URLQueryItem(name: "lastUpdate", value: lastUpdate?.ISO8601Format())
] ]
} }
} }

View File

@ -1,20 +1,20 @@
import Foundation import Foundation
struct ReadRequest: Request { struct ReadRequest: Request {
typealias Response = ReadResponse typealias Response = ReadResponse
let path: String = "api/v1/subscriptions.read" let path: String = "api/v1/subscriptions.read"
let method: HTTPMethod = .post let method: HTTPMethod = .post
var body: Data? { var body: Data? {
try? JSONSerialization.data(withJSONObject: [ try? JSONSerialization.data(withJSONObject: [
"rid": rid "rid": rid
]) ])
} }
let rid: String let rid: String
init(rid: String) { init(rid: String) {
self.rid = rid self.rid = rid
} }
} }

View File

@ -1,16 +1,16 @@
import Foundation import Foundation
struct RoomsRequest: Request { struct RoomsRequest: Request {
typealias Response = RoomsResponse typealias Response = RoomsResponse
let path: String = "api/v1/rooms.get" let path: String = "api/v1/rooms.get"
let queryItems: [URLQueryItem] let queryItems: [URLQueryItem]
init(updatedSince: Date?) { init(updatedSince: Date?) {
if let updatedSince { if let updatedSince {
queryItems = [URLQueryItem(name: "updatedSince", value: updatedSince.iso8601withFractionalSeconds)] queryItems = [URLQueryItem(name: "updatedSince", value: updatedSince.iso8601withFractionalSeconds)]
} else { } else {
queryItems = [] queryItems = []
} }
} }
} }

View File

@ -1,29 +1,29 @@
import Foundation import Foundation
struct SendMessageRequest: Request { struct SendMessageRequest: Request {
typealias Response = SendMessageResponse typealias Response = SendMessageResponse
let path: String = "api/v1/chat.sendMessage" let path: String = "api/v1/chat.sendMessage"
let method: HTTPMethod = .post let method: HTTPMethod = .post
var body: Data? { var body: Data? {
try? JSONSerialization.data(withJSONObject: [ try? JSONSerialization.data(withJSONObject: [
"message": [ "message": [
"_id": id, "_id": id,
"rid": rid, "rid": rid,
"msg": msg, "msg": msg,
"tshow": false "tshow": false
] ]
]) ])
} }
let id: String let id: String
let rid: String let rid: String
let msg: String let msg: String
init(id: String, rid: String, msg: String) { init(id: String, rid: String, msg: String) {
self.id = id self.id = id
self.rid = rid self.rid = rid
self.msg = msg self.msg = msg
} }
} }

View File

@ -1,16 +1,16 @@
import Foundation import Foundation
struct SubscriptionsRequest: Request { struct SubscriptionsRequest: Request {
typealias Response = SubscriptionsResponse typealias Response = SubscriptionsResponse
let path: String = "api/v1/subscriptions.get" let path: String = "api/v1/subscriptions.get"
let queryItems: [URLQueryItem] let queryItems: [URLQueryItem]
init(updatedSince: Date?) { init(updatedSince: Date?) {
if let updatedSince { if let updatedSince {
queryItems = [URLQueryItem(name: "updatedSince", value: updatedSince.iso8601withFractionalSeconds)] queryItems = [URLQueryItem(name: "updatedSince", value: updatedSince.iso8601withFractionalSeconds)]
} else { } else {
queryItems = [] queryItems = []
} }
} }
} }

View File

@ -1,18 +1,18 @@
import Foundation import Foundation
struct AttachmentResponse: Codable, Hashable { struct AttachmentResponse: Codable, Hashable {
let imageURL: URL? let imageURL: URL?
let description: String? let description: String?
let dimensions: Dimensions? let dimensions: Dimensions?
enum CodingKeys: String, CodingKey { enum CodingKeys: String, CodingKey {
case imageURL = "image_url" case imageURL = "image_url"
case description case description
case dimensions = "image_dimensions" case dimensions = "image_dimensions"
} }
struct Dimensions: Codable, Hashable { struct Dimensions: Codable, Hashable {
let width: Double let width: Double
let height: Double let height: Double
} }
} }

View File

@ -1,6 +1,6 @@
import Foundation import Foundation
struct HistoryResponse: Codable { struct HistoryResponse: Codable {
let messages: [MessageResponse] let messages: [MessageResponse]
let success: Bool let success: Bool
} }

View File

@ -1,12 +1,12 @@
import Foundation import Foundation
struct MessageResponse: Codable, Hashable { struct MessageResponse: Codable, Hashable {
let _id: String let _id: String
let rid: String let rid: String
let msg: String let msg: String
let u: UserResponse let u: UserResponse
let ts: Date let ts: Date
let attachments: [AttachmentResponse]? let attachments: [AttachmentResponse]?
let t: String? let t: String?
let groupable: Bool? let groupable: Bool?
} }

View File

@ -1,11 +1,11 @@
import Foundation import Foundation
struct MessagesResponse: Codable { struct MessagesResponse: Codable {
let result: MessagesResult let result: MessagesResult
let success: Bool let success: Bool
struct MessagesResult: Codable { struct MessagesResult: Codable {
let updated: [MessageResponse] let updated: [MessageResponse]
let deleted: [MessageResponse] let deleted: [MessageResponse]
} }
} }

View File

@ -1,26 +1,26 @@
import Foundation import Foundation
struct RoomsResponse: Codable { struct RoomsResponse: Codable {
let update: Set<Room> let update: Set<Room>
let remove: Set<Room> let remove: Set<Room>
let success: Bool let success: Bool
struct Room: Codable, Hashable { struct Room: Codable, Hashable {
let _id: String let _id: String
let name: String? let name: String?
let fname: String? let fname: String?
let prid: String? let prid: String?
let t: String? let t: String?
let ts: Date? let ts: Date?
let ro: Bool? let ro: Bool?
let _updatedAt: Date? let _updatedAt: Date?
let encrypted: Bool? let encrypted: Bool?
let usernames: [String]? let usernames: [String]?
let uids: [String]? let uids: [String]?
let lastMessage: FailableDecodable<MessageResponse>? let lastMessage: FailableDecodable<MessageResponse>?
let lm: Date? let lm: Date?
let teamMain: Bool? let teamMain: Bool?
let archived: Bool? let archived: Bool?
let broadcast: Bool? let broadcast: Bool?
} }
} }

View File

@ -1,6 +1,6 @@
import Foundation import Foundation
struct SendMessageResponse: Codable { struct SendMessageResponse: Codable {
let message: MessageResponse let message: MessageResponse
let success: Bool let success: Bool
} }

View File

@ -1,26 +1,26 @@
import Foundation import Foundation
struct SubscriptionsResponse: Codable { struct SubscriptionsResponse: Codable {
let update: Set<Subscription> let update: Set<Subscription>
let remove: Set<Subscription> let remove: Set<Subscription>
let success: Bool let success: Bool
struct Subscription: Codable, Hashable { struct Subscription: Codable, Hashable {
let _id: String let _id: String
let rid: String let rid: String
let name: String? let name: String?
let fname: String? let fname: String?
let t: String let t: String
let unread: Int let unread: Int
let alert: Bool let alert: Bool
let lr: Date? let lr: Date?
} }
} }
extension Sequence where Element == SubscriptionsResponse.Subscription { extension Sequence where Element == SubscriptionsResponse.Subscription {
func find(withRoomID rid: String) -> SubscriptionsResponse.Subscription? { func find(withRoomID rid: String) -> SubscriptionsResponse.Subscription? {
first { subscription in first { subscription in
subscription.rid == rid subscription.rid == rid
} }
} }
} }

View File

@ -1,7 +1,7 @@
import Foundation import Foundation
struct UserResponse: Codable, Hashable { struct UserResponse: Codable, Hashable {
let _id: String let _id: String
let username: String let username: String
let name: String let name: String
} }

View File

@ -2,90 +2,90 @@ import Combine
import Foundation import Foundation
protocol RocketChatClientProtocol { protocol RocketChatClientProtocol {
func authorizedURL(url: URL) -> URL func authorizedURL(url: URL) -> URL
func getRooms(updatedSince: Date?) -> AnyPublisher<RoomsResponse, RocketChatError> func getRooms(updatedSince: Date?) -> AnyPublisher<RoomsResponse, RocketChatError>
func getSubscriptions(updatedSince: Date?) -> AnyPublisher<SubscriptionsResponse, RocketChatError> func getSubscriptions(updatedSince: Date?) -> AnyPublisher<SubscriptionsResponse, RocketChatError>
func getHistory(rid: String, t: String, latest: Date) -> AnyPublisher<HistoryResponse, RocketChatError> func getHistory(rid: String, t: String, latest: Date) -> AnyPublisher<HistoryResponse, RocketChatError>
func syncMessages(rid: String, updatedSince: Date) -> AnyPublisher<MessagesResponse, RocketChatError> func syncMessages(rid: String, updatedSince: Date) -> AnyPublisher<MessagesResponse, RocketChatError>
func sendMessage(id: String, rid: String, msg: String) -> AnyPublisher<SendMessageResponse, RocketChatError> func sendMessage(id: String, rid: String, msg: String) -> AnyPublisher<SendMessageResponse, RocketChatError>
func sendRead(rid: String) -> AnyPublisher<ReadResponse, RocketChatError> func sendRead(rid: String) -> AnyPublisher<ReadResponse, RocketChatError>
} }
final class RocketChatClient { final class RocketChatClient {
private let server: Server private let server: Server
init(server: Server) { init(server: Server) {
self.server = server self.server = server
} }
private var adapters: [RequestAdapter] { private var adapters: [RequestAdapter] {
[ [
TokenAdapter(server: server), TokenAdapter(server: server),
JSONAdapter() JSONAdapter()
] ]
} }
private func dataTask<T: Request>(for request: T) -> AnyPublisher<T.Response, RocketChatError> { private func dataTask<T: Request>(for request: T) -> AnyPublisher<T.Response, RocketChatError> {
let url = server.url.appending(path: request.path).appending(queryItems: request.queryItems) let url = server.url.appending(path: request.path).appending(queryItems: request.queryItems)
var urlRequest = adapters.reduce(URLRequest(url: url), { $1.adapt($0) }) var urlRequest = adapters.reduce(URLRequest(url: url), { $1.adapt($0) })
urlRequest.httpMethod = request.method.rawValue urlRequest.httpMethod = request.method.rawValue
urlRequest.httpBody = request.body urlRequest.httpBody = request.body
return URLSession.shared.dataTaskPublisher(for: urlRequest) return URLSession.shared.dataTaskPublisher(for: urlRequest)
.tryMap { (data, response) in .tryMap { (data, response) in
guard let httpResponse = response as? HTTPURLResponse, 200...299 ~= httpResponse.statusCode else { guard let httpResponse = response as? HTTPURLResponse, 200...299 ~= httpResponse.statusCode else {
throw RocketChatError.unauthorized throw RocketChatError.unauthorized
} }
return try data.decode(T.Response.self) return try data.decode(T.Response.self)
} }
.mapError { error in .mapError { error in
if let error = error as? DecodingError { if let error = error as? DecodingError {
return .decoding(error: error) return .decoding(error: error)
} }
if let error = error as? RocketChatError { if let error = error as? RocketChatError {
return error return error
} }
return .unknown(error: error) return .unknown(error: error)
} }
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
} }
extension RocketChatClient: RocketChatClientProtocol { extension RocketChatClient: RocketChatClientProtocol {
func authorizedURL(url: URL) -> URL { func authorizedURL(url: URL) -> URL {
adapters.reduce(server.url.appending(path: url.relativePath), { $1.adapt($0) }) adapters.reduce(server.url.appending(path: url.relativePath), { $1.adapt($0) })
} }
func getRooms(updatedSince: Date?) -> AnyPublisher<RoomsResponse, RocketChatError> { func getRooms(updatedSince: Date?) -> AnyPublisher<RoomsResponse, RocketChatError> {
let request = RoomsRequest(updatedSince: updatedSince) let request = RoomsRequest(updatedSince: updatedSince)
return dataTask(for: request) return dataTask(for: request)
} }
func getSubscriptions(updatedSince: Date?) -> AnyPublisher<SubscriptionsResponse, RocketChatError> { func getSubscriptions(updatedSince: Date?) -> AnyPublisher<SubscriptionsResponse, RocketChatError> {
let request = SubscriptionsRequest(updatedSince: updatedSince) let request = SubscriptionsRequest(updatedSince: updatedSince)
return dataTask(for: request) return dataTask(for: request)
} }
func getHistory(rid: String, t: String, latest: Date) -> AnyPublisher<HistoryResponse, RocketChatError> { func getHistory(rid: String, t: String, latest: Date) -> AnyPublisher<HistoryResponse, RocketChatError> {
let request = HistoryRequest(roomId: rid, roomType: t, latest: latest) let request = HistoryRequest(roomId: rid, roomType: t, latest: latest)
return dataTask(for: request) return dataTask(for: request)
} }
func syncMessages(rid: String, updatedSince: Date) -> AnyPublisher<MessagesResponse, RocketChatError> { func syncMessages(rid: String, updatedSince: Date) -> AnyPublisher<MessagesResponse, RocketChatError> {
let request = MessagesRequest(lastUpdate: updatedSince, roomId: rid) let request = MessagesRequest(lastUpdate: updatedSince, roomId: rid)
return dataTask(for: request) return dataTask(for: request)
} }
func sendMessage(id: String, rid: String, msg: String) -> AnyPublisher<SendMessageResponse, RocketChatError> { func sendMessage(id: String, rid: String, msg: String) -> AnyPublisher<SendMessageResponse, RocketChatError> {
let request = SendMessageRequest(id: id, rid: rid, msg: msg) let request = SendMessageRequest(id: id, rid: rid, msg: msg)
return dataTask(for: request) return dataTask(for: request)
} }
func sendRead(rid: String) -> AnyPublisher<ReadResponse, RocketChatError> { func sendRead(rid: String) -> AnyPublisher<ReadResponse, RocketChatError> {
let request = ReadRequest(rid: rid) let request = ReadRequest(rid: rid)
return dataTask(for: request) return dataTask(for: request)
} }
} }

View File

@ -1,7 +1,7 @@
import Foundation import Foundation
enum RocketChatError: Error { enum RocketChatError: Error {
case decoding(error: Error) case decoding(error: Error)
case unknown(error: Error) case unknown(error: Error)
case unauthorized case unauthorized
} }

View File

@ -1,21 +0,0 @@
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
}
}

View File

@ -38,7 +38,7 @@ struct MessageListView: View {
var body: some View { var body: some View {
ScrollViewReader { reader in ScrollViewReader { reader in
ScrollView { ScrollView {
VStack(alignment: .leading, spacing: 8) { LazyVStack(alignment: .leading, spacing: 8) {
if room.hasMoreMessages { if room.hasMoreMessages {
Button("Load More...") { Button("Load More...") {
guard let oldestMessage = room.firstMessage?.ts else { return } guard let oldestMessage = room.firstMessage?.ts else { return }

View File

@ -59,7 +59,6 @@
1E29A3032B585B070093C03C /* FailableDecodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A2E92B585B070093C03C /* FailableDecodable.swift */; }; 1E29A3032B585B070093C03C /* FailableDecodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A2E92B585B070093C03C /* FailableDecodable.swift */; };
1E29A3042B585B070093C03C /* HTTPMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A2EB2B585B070093C03C /* HTTPMethod.swift */; }; 1E29A3042B585B070093C03C /* HTTPMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A2EB2B585B070093C03C /* HTTPMethod.swift */; };
1E29A3052B585B070093C03C /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A2EC2B585B070093C03C /* Request.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 */; }; 1E29A3072B585B070093C03C /* RocketChatError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A2EE2B585B070093C03C /* RocketChatError.swift */; };
1E29A30A2B585B370093C03C /* Data+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A3092B585B370093C03C /* Data+Extensions.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 */; }; 1E29A30C2B585D1D0093C03C /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A30B2B585D1D0093C03C /* String+Extensions.swift */; };
@ -367,7 +366,6 @@
1E29A2E92B585B070093C03C /* FailableDecodable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FailableDecodable.swift; sourceTree = "<group>"; }; 1E29A2E92B585B070093C03C /* FailableDecodable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FailableDecodable.swift; sourceTree = "<group>"; };
1E29A2EB2B585B070093C03C /* HTTPMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPMethod.swift; sourceTree = "<group>"; }; 1E29A2EB2B585B070093C03C /* HTTPMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPMethod.swift; sourceTree = "<group>"; };
1E29A2EC2B585B070093C03C /* Request.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Request.swift; sourceTree = "<group>"; }; 1E29A2EC2B585B070093C03C /* Request.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Request.swift; sourceTree = "<group>"; };
1E29A2ED2B585B070093C03C /* RocketChatServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RocketChatServer.swift; sourceTree = "<group>"; };
1E29A2EE2B585B070093C03C /* RocketChatError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RocketChatError.swift; sourceTree = "<group>"; }; 1E29A2EE2B585B070093C03C /* RocketChatError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RocketChatError.swift; sourceTree = "<group>"; };
1E29A3092B585B370093C03C /* Data+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Extensions.swift"; sourceTree = "<group>"; }; 1E29A3092B585B370093C03C /* Data+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Extensions.swift"; sourceTree = "<group>"; };
1E29A30B2B585D1D0093C03C /* String+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = "<group>"; }; 1E29A30B2B585D1D0093C03C /* String+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = "<group>"; };
@ -594,7 +592,6 @@
1E29A2E82B585B070093C03C /* DateCodingStrategy.swift */, 1E29A2E82B585B070093C03C /* DateCodingStrategy.swift */,
1E29A2E92B585B070093C03C /* FailableDecodable.swift */, 1E29A2E92B585B070093C03C /* FailableDecodable.swift */,
1E29A2EA2B585B070093C03C /* HTTP */, 1E29A2EA2B585B070093C03C /* HTTP */,
1E29A2ED2B585B070093C03C /* RocketChatServer.swift */,
1E29A2EE2B585B070093C03C /* RocketChatError.swift */, 1E29A2EE2B585B070093C03C /* RocketChatError.swift */,
); );
path = Client; path = Client;
@ -1863,7 +1860,6 @@
1E29A3032B585B070093C03C /* FailableDecodable.swift in Sources */, 1E29A3032B585B070093C03C /* FailableDecodable.swift in Sources */,
1E29A2FE2B585B070093C03C /* ReadRequest.swift in Sources */, 1E29A2FE2B585B070093C03C /* ReadRequest.swift in Sources */,
1E9A71692B59B6E100477BA2 /* MessageSender.swift in Sources */, 1E9A71692B59B6E100477BA2 /* MessageSender.swift in Sources */,
1E29A3062B585B070093C03C /* RocketChatServer.swift in Sources */,
1E29A3072B585B070093C03C /* RocketChatError.swift in Sources */, 1E29A3072B585B070093C03C /* RocketChatError.swift in Sources */,
1E9A71672B599E6300477BA2 /* NotificationController.swift in Sources */, 1E9A71672B599E6300477BA2 /* NotificationController.swift in Sources */,
1EDFD1082B58AA77002FEE5F /* RoomsLoader.swift in Sources */, 1EDFD1082B58AA77002FEE5F /* RoomsLoader.swift in Sources */,