Add ServerListView & RoomListView
This commit is contained in:
parent
31c1181f91
commit
077686d0fd
12
ios/RocketChat Watch App/Assets.xcassets/channel-private.imageset/Contents.json
vendored
Normal file
12
ios/RocketChat Watch App/Assets.xcassets/channel-private.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "channel-private.png",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
ios/RocketChat Watch App/Assets.xcassets/channel-private.imageset/channel-private.png
vendored
Normal file
BIN
ios/RocketChat Watch App/Assets.xcassets/channel-private.imageset/channel-private.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 371 B |
12
ios/RocketChat Watch App/Assets.xcassets/channel-public.imageset/Contents.json
vendored
Normal file
12
ios/RocketChat Watch App/Assets.xcassets/channel-public.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "channel-public.png",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
ios/RocketChat Watch App/Assets.xcassets/channel-public.imageset/channel-public.png
vendored
Normal file
BIN
ios/RocketChat Watch App/Assets.xcassets/channel-public.imageset/channel-public.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 259 B |
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "discussions.png",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
ios/RocketChat Watch App/Assets.xcassets/discussions.imageset/discussions.png
vendored
Normal file
BIN
ios/RocketChat Watch App/Assets.xcassets/discussions.imageset/discussions.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 517 B |
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "message.png",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 479 B |
12
ios/RocketChat Watch App/Assets.xcassets/teams-private.imageset/Contents.json
vendored
Normal file
12
ios/RocketChat Watch App/Assets.xcassets/teams-private.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "teams-private.png",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
ios/RocketChat Watch App/Assets.xcassets/teams-private.imageset/teams-private.png
vendored
Normal file
BIN
ios/RocketChat Watch App/Assets.xcassets/teams-private.imageset/teams-private.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 509 B |
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "teams.png",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 512 B |
|
@ -0,0 +1,10 @@
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct JSONAdapter: RequestAdapter {
|
||||||
|
func adapt(_ urlRequest: URLRequest) -> URLRequest {
|
||||||
|
var request = urlRequest
|
||||||
|
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||||
|
request.addValue("application/json", forHTTPHeaderField: "Accept")
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension Data {
|
||||||
|
func decode<T: Decodable>(_ type: T.Type) throws -> T {
|
||||||
|
let decoder = JSONDecoder()
|
||||||
|
decoder.dateDecodingStrategy = .iso8601withFractionalSeconds
|
||||||
|
return try decoder.decode(T.self, from: self)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
extension String {
|
||||||
|
static func random(_ count: Int) -> String {
|
||||||
|
let letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||||
|
return String((0..<count).compactMap { _ in letters.randomElement() })
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
struct FailableDecodable<Value: Codable & Hashable>: 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
enum HTTPMethod: String {
|
||||||
|
case get = "GET"
|
||||||
|
case post = "POST"
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
protocol Request<Response> {
|
||||||
|
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] {
|
||||||
|
[]
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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())
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct HistoryResponse: Codable {
|
||||||
|
let messages: [MessageResponse]
|
||||||
|
let success: Bool
|
||||||
|
}
|
|
@ -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?
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct MessagesResponse: Codable {
|
||||||
|
let result: MessagesResult
|
||||||
|
let success: Bool
|
||||||
|
|
||||||
|
struct MessagesResult: Codable {
|
||||||
|
let updated: [MessageResponse]
|
||||||
|
let deleted: [MessageResponse]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct ReadResponse: Codable {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct RoomsResponse: Codable {
|
||||||
|
let update: Set<Room>
|
||||||
|
let remove: Set<Room>
|
||||||
|
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<MessageResponse>?
|
||||||
|
let lm: Date?
|
||||||
|
let teamMain: Bool?
|
||||||
|
let archived: Bool?
|
||||||
|
let broadcast: Bool?
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct SendMessageResponse: Codable {
|
||||||
|
let message: MessageResponse
|
||||||
|
let success: Bool
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct SubscriptionsResponse: Codable {
|
||||||
|
let update: Set<Subscription>
|
||||||
|
let remove: Set<Subscription>
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct UserResponse: Codable, Hashable {
|
||||||
|
let _id: String
|
||||||
|
let username: String
|
||||||
|
let name: String
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
import Combine
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
protocol RocketChatClientProtocol {
|
||||||
|
func authorizedURL(url: URL) -> URL
|
||||||
|
func getRooms(updatedSince: Date?) -> AnyPublisher<RoomsResponse, RocketChatError>
|
||||||
|
func getSubscriptions(updatedSince: Date?) -> AnyPublisher<SubscriptionsResponse, RocketChatError>
|
||||||
|
func getHistory(rid: String, t: String, latest: Date) -> AnyPublisher<HistoryResponse, RocketChatError>
|
||||||
|
func syncMessages(rid: String, updatedSince: Date) -> AnyPublisher<MessagesResponse, RocketChatError>
|
||||||
|
func sendMessage(id: String, rid: String, msg: String) -> AnyPublisher<SendMessageResponse, RocketChatError>
|
||||||
|
func sendRead(rid: String) -> AnyPublisher<ReadResponse, RocketChatError>
|
||||||
|
}
|
||||||
|
|
||||||
|
final class RocketChatClient {
|
||||||
|
private let server: Server
|
||||||
|
|
||||||
|
init(server: Server) {
|
||||||
|
self.server = server
|
||||||
|
}
|
||||||
|
|
||||||
|
private var adapters: [RequestAdapter] {
|
||||||
|
[
|
||||||
|
TokenAdapter(server: server),
|
||||||
|
JSONAdapter()
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
private func dataTask<T: Request>(for request: T) -> AnyPublisher<T.Response, RocketChatError> {
|
||||||
|
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<RoomsResponse, RocketChatError> {
|
||||||
|
let request = RoomsRequest(updatedSince: updatedSince)
|
||||||
|
return dataTask(for: request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSubscriptions(updatedSince: Date?) -> AnyPublisher<SubscriptionsResponse, RocketChatError> {
|
||||||
|
let request = SubscriptionsRequest(updatedSince: updatedSince)
|
||||||
|
return dataTask(for: request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHistory(rid: String, t: String, latest: Date) -> AnyPublisher<HistoryResponse, RocketChatError> {
|
||||||
|
let request = HistoryRequest(roomId: rid, roomType: t, latest: latest)
|
||||||
|
return dataTask(for: request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func syncMessages(rid: String, updatedSince: Date) -> AnyPublisher<MessagesResponse, RocketChatError> {
|
||||||
|
let request = MessagesRequest(lastUpdate: updatedSince, roomId: rid)
|
||||||
|
return dataTask(for: request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendMessage(id: String, rid: String, msg: String) -> AnyPublisher<SendMessageResponse, RocketChatError> {
|
||||||
|
let request = SendMessageRequest(id: id, rid: rid, msg: msg)
|
||||||
|
return dataTask(for: request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendRead(rid: String) -> AnyPublisher<ReadResponse, RocketChatError> {
|
||||||
|
let request = ReadRequest(rid: rid)
|
||||||
|
return dataTask(for: request)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum RocketChatError: Error {
|
||||||
|
case decoding(error: Error)
|
||||||
|
case unknown(error: Error)
|
||||||
|
case unauthorized
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22222" systemVersion="22G120" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithSwiftData="YES" userDefinedModelVersionIdentifier="">
|
||||||
|
<entity name="LoggedUser" representedClassName=".LoggedUser" syncable="YES">
|
||||||
|
<attribute name="id" optional="YES" attributeType="String"/>
|
||||||
|
<attribute name="name" optional="YES" attributeType="String"/>
|
||||||
|
<attribute name="token" optional="YES" attributeType="String"/>
|
||||||
|
<attribute name="username" optional="YES" attributeType="String"/>
|
||||||
|
<relationship name="server" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Server" inverseName="loggedUser" inverseEntity="Server"/>
|
||||||
|
</entity>
|
||||||
|
<entity name="Server" representedClassName=".Server" syncable="YES">
|
||||||
|
<attribute name="iconURL" optional="YES" attributeType="URI"/>
|
||||||
|
<attribute name="name" optional="YES" attributeType="String"/>
|
||||||
|
<attribute name="updatedSince" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
|
<attribute name="url" optional="YES" attributeType="URI"/>
|
||||||
|
<attribute name="useRealName" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||||
|
<relationship name="loggedUser" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="LoggedUser" inverseName="server" inverseEntity="LoggedUser"/>
|
||||||
|
</entity>
|
||||||
|
</model>
|
|
@ -0,0 +1,48 @@
|
||||||
|
import CoreData
|
||||||
|
|
||||||
|
@objc
|
||||||
|
public final class LoggedUser: NSManagedObject {
|
||||||
|
|
||||||
|
@nonobjc public class func fetchRequest() -> NSFetchRequest<LoggedUser> {
|
||||||
|
NSFetchRequest<LoggedUser>(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 {
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
import CoreData
|
||||||
|
|
||||||
|
@objc
|
||||||
|
public final class Server: NSManagedObject {
|
||||||
|
|
||||||
|
@nonobjc public class func fetchRequest() -> NSFetchRequest<Server> {
|
||||||
|
NSFetchRequest<Server>(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 {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22222" systemVersion="22G120" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithSwiftData="YES" userDefinedModelVersionIdentifier="">
|
||||||
|
<entity name="Attachment" representedClassName="Attachment" syncable="YES" codeGenerationType="class">
|
||||||
|
<attribute name="height" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||||
|
<attribute name="imageURL" optional="YES" attributeType="URI"/>
|
||||||
|
<attribute name="msg" optional="YES" attributeType="String"/>
|
||||||
|
<attribute name="width" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||||
|
<relationship name="message" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Message" inverseName="attachments" inverseEntity="Message"/>
|
||||||
|
</entity>
|
||||||
|
<entity name="Message" representedClassName="Message" syncable="YES" codeGenerationType="class">
|
||||||
|
<attribute name="groupable" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||||
|
<attribute name="id" optional="YES" attributeType="String"/>
|
||||||
|
<attribute name="msg" optional="YES" attributeType="String"/>
|
||||||
|
<attribute name="status" optional="YES" attributeType="String"/>
|
||||||
|
<attribute name="t" optional="YES" attributeType="String"/>
|
||||||
|
<attribute name="ts" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
|
<relationship name="attachments" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Attachment" inverseName="message" inverseEntity="Attachment"/>
|
||||||
|
<relationship name="room" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Room" inverseName="messages" inverseEntity="Room"/>
|
||||||
|
<relationship name="user" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="User" inverseName="messages" inverseEntity="User"/>
|
||||||
|
</entity>
|
||||||
|
<entity name="Room" representedClassName="Room" syncable="YES" codeGenerationType="class">
|
||||||
|
<attribute name="alert" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||||
|
<attribute name="archived" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||||
|
<attribute name="broadcast" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||||
|
<attribute name="encrypted" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||||
|
<attribute name="fname" optional="YES" attributeType="String"/>
|
||||||
|
<attribute name="id" optional="YES" attributeType="String"/>
|
||||||
|
<attribute name="isReadOnly" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||||
|
<attribute name="name" optional="YES" attributeType="String"/>
|
||||||
|
<attribute name="prid" optional="YES" attributeType="String"/>
|
||||||
|
<attribute name="t" optional="YES" attributeType="String"/>
|
||||||
|
<attribute name="teamMain" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||||
|
<attribute name="ts" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
|
<attribute name="uids" optional="YES" attributeType="Transformable" valueTransformerName="NSSecureUnarchiveFromDataTransformer" customClassName="[String]"/>
|
||||||
|
<attribute name="unread" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||||
|
<attribute name="updatedAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
|
<attribute name="updatedSince" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
|
<attribute name="usernames" optional="YES" attributeType="Transformable" valueTransformerName="NSSecureUnarchiveFromDataTransformer" customClassName="[String]"/>
|
||||||
|
<relationship name="messages" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Message" inverseName="room" inverseEntity="Message"/>
|
||||||
|
</entity>
|
||||||
|
<entity name="User" representedClassName="User" syncable="YES" codeGenerationType="class">
|
||||||
|
<attribute name="id" optional="YES" attributeType="String"/>
|
||||||
|
<attribute name="name" optional="YES" attributeType="String"/>
|
||||||
|
<attribute name="username" optional="YES" attributeType="String"/>
|
||||||
|
<relationship name="messages" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Message" inverseName="user" inverseEntity="Message"/>
|
||||||
|
</entity>
|
||||||
|
</model>
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<RocketChatDatabase>?
|
||||||
|
|
||||||
|
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<T: AnyObject> {
|
||||||
|
weak var value: T?
|
||||||
|
|
||||||
|
init(value: T) {
|
||||||
|
self.value = value
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"sourceLanguage" : "en",
|
||||||
|
"strings" : {
|
||||||
|
"Rooms" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Servers" : {
|
||||||
|
"comment" : "View title for ServerList.",
|
||||||
|
"extractionState" : "manual"
|
||||||
|
},
|
||||||
|
"Try Again" : {
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"version" : "1.0"
|
||||||
|
}
|
|
@ -2,9 +2,52 @@ import SwiftUI
|
||||||
|
|
||||||
@main
|
@main
|
||||||
struct RocketChat_Watch_AppApp: App {
|
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 {
|
var body: some Scene {
|
||||||
WindowGroup {
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
@propertyWrapper
|
||||||
|
struct Storage<T: Codable> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Room>
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ServerListView: View {
|
||||||
|
@StateObject var viewModel: ServerListViewModel
|
||||||
|
|
||||||
|
@FetchRequest(entity: Server.entity(), sortDescriptors: [], animation: .default)
|
||||||
|
private var servers: FetchedResults<Server>
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,7 +13,7 @@ protocol Connection {
|
||||||
final class WatchConnection: NSObject {
|
final class WatchConnection: NSObject {
|
||||||
private let session: WCSession
|
private let session: WCSession
|
||||||
|
|
||||||
init(session: WCSession = .default) {
|
init(session: WCSession) {
|
||||||
self.session = session
|
self.session = session
|
||||||
super.init()
|
super.init()
|
||||||
session.delegate = self
|
session.delegate = self
|
||||||
|
|
|
@ -34,6 +34,39 @@
|
||||||
1E1EA8182326CD4B00E22452 /* libc.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E1EA8172326CD4B00E22452 /* libc.tbd */; };
|
1E1EA8182326CD4B00E22452 /* libc.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E1EA8172326CD4B00E22452 /* libc.tbd */; };
|
||||||
1E1EA81A2326CD5100E22452 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E1EA8192326CD5100E22452 /* libsqlite3.tbd */; };
|
1E1EA81A2326CD5100E22452 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E1EA8192326CD5100E22452 /* libsqlite3.tbd */; };
|
||||||
1E25743422CBA2CF005A877F /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7ACD4853222860DE00442C55 /* JavaScriptCore.framework */; };
|
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 */; };
|
1E2F615B25128F9A00871711 /* API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E2F615A25128F9A00871711 /* API.swift */; };
|
||||||
1E2F615D25128FA300871711 /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E2F615C25128FA300871711 /* Response.swift */; };
|
1E2F615D25128FA300871711 /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E2F615C25128FA300871711 /* Response.swift */; };
|
||||||
1E2F61642512955D00871711 /* HTTPMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E2F61632512955D00871711 /* HTTPMethod.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 */; };
|
1E76CBD825152C870067298C /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E680ED82512990700C9257A /* Request.swift */; };
|
||||||
1E76CBD925152C8C0067298C /* Push.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E2F61652512958900871711 /* Push.swift */; };
|
1E76CBD925152C8C0067298C /* Push.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E2F61652512958900871711 /* Push.swift */; };
|
||||||
1E76CBDA25152C8E0067298C /* SendMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E598AE825151A63002BDFBD /* SendMessage.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 */; };
|
1EB8EF722510F1EE00F352B7 /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EB8EF712510F1EE00F352B7 /* Storage.swift */; };
|
||||||
1EC6ACB722CB9FC300A41C61 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1EC6ACB522CB9FC300A41C61 /* MainInterface.storyboard */; };
|
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, ); }; };
|
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 */; };
|
1EC6ACF622CBA01500A41C61 /* ShareRocketChatRN.m in Sources */ = {isa = PBXBuildFile; fileRef = 1EC6ACF522CBA01500A41C61 /* ShareRocketChatRN.m */; };
|
||||||
1ED00BB12513E04400A1331F /* ReplyNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED00BB02513E04400A1331F /* ReplyNotification.swift */; };
|
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 */; };
|
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 */; };
|
1ED038952B507B4D00C007D4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1ED038942B507B4D00C007D4 /* Assets.xcassets */; };
|
||||||
1ED038982B507B4D00C007D4 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1ED038972B507B4D00C007D4 /* Preview 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, ); }; };
|
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; };
|
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; };
|
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; };
|
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 = "<group>"; };
|
||||||
|
1E29A2CD2B5857FC0093C03C /* RoomListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomListViewModel.swift; sourceTree = "<group>"; };
|
||||||
|
1E29A2CF2B58582F0093C03C /* RoomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomView.swift; sourceTree = "<group>"; };
|
||||||
|
1E29A2D22B585B070093C03C /* RocketChatClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RocketChatClient.swift; sourceTree = "<group>"; };
|
||||||
|
1E29A2D42B585B070093C03C /* AttachmentResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentResponse.swift; sourceTree = "<group>"; };
|
||||||
|
1E29A2D52B585B070093C03C /* SendMessageResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendMessageResponse.swift; sourceTree = "<group>"; };
|
||||||
|
1E29A2D62B585B070093C03C /* HistoryResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HistoryResponse.swift; sourceTree = "<group>"; };
|
||||||
|
1E29A2D72B585B070093C03C /* MessagesResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesResponse.swift; sourceTree = "<group>"; };
|
||||||
|
1E29A2D82B585B070093C03C /* SubscriptionsResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionsResponse.swift; sourceTree = "<group>"; };
|
||||||
|
1E29A2D92B585B070093C03C /* RoomsResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomsResponse.swift; sourceTree = "<group>"; };
|
||||||
|
1E29A2DA2B585B070093C03C /* UserResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserResponse.swift; sourceTree = "<group>"; };
|
||||||
|
1E29A2DB2B585B070093C03C /* ReadResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadResponse.swift; sourceTree = "<group>"; };
|
||||||
|
1E29A2DC2B585B070093C03C /* MessageResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageResponse.swift; sourceTree = "<group>"; };
|
||||||
|
1E29A2DE2B585B070093C03C /* SubscriptionsRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionsRequest.swift; sourceTree = "<group>"; };
|
||||||
|
1E29A2DF2B585B070093C03C /* HistoryRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HistoryRequest.swift; sourceTree = "<group>"; };
|
||||||
|
1E29A2E02B585B070093C03C /* MessagesRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesRequest.swift; sourceTree = "<group>"; };
|
||||||
|
1E29A2E12B585B070093C03C /* SendMessageRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendMessageRequest.swift; sourceTree = "<group>"; };
|
||||||
|
1E29A2E22B585B070093C03C /* RoomsRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomsRequest.swift; sourceTree = "<group>"; };
|
||||||
|
1E29A2E32B585B070093C03C /* ReadRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadRequest.swift; sourceTree = "<group>"; };
|
||||||
|
1E29A2E52B585B070093C03C /* TokenAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenAdapter.swift; sourceTree = "<group>"; };
|
||||||
|
1E29A2E62B585B070093C03C /* JSONAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONAdapter.swift; sourceTree = "<group>"; };
|
||||||
|
1E29A2E72B585B070093C03C /* RequestAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestAdapter.swift; sourceTree = "<group>"; };
|
||||||
|
1E29A2E82B585B070093C03C /* DateCodingStrategy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateCodingStrategy.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>"; };
|
||||||
|
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>"; };
|
||||||
|
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>"; };
|
||||||
|
1E29A30D2B58608C0093C03C /* LoggedUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggedUser.swift; sourceTree = "<group>"; };
|
||||||
|
1E29A30F2B5865B80093C03C /* RoomViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomViewModel.swift; sourceTree = "<group>"; };
|
||||||
|
1E29A3112B5866090093C03C /* Room.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Room.swift; sourceTree = "<group>"; };
|
||||||
1E2F615A25128F9A00871711 /* API.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = API.swift; sourceTree = "<group>"; };
|
1E2F615A25128F9A00871711 /* API.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = API.swift; sourceTree = "<group>"; };
|
||||||
1E2F615C25128FA300871711 /* Response.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Response.swift; sourceTree = "<group>"; };
|
1E2F615C25128FA300871711 /* Response.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Response.swift; sourceTree = "<group>"; };
|
||||||
1E2F61632512955D00871711 /* HTTPMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPMethod.swift; sourceTree = "<group>"; };
|
1E2F61632512955D00871711 /* HTTPMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPMethod.swift; sourceTree = "<group>"; };
|
||||||
|
@ -297,6 +374,7 @@
|
||||||
1E6737FF24DC52660009E081 /* NotificationService-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NotificationService-Bridging-Header.h"; sourceTree = "<group>"; };
|
1E6737FF24DC52660009E081 /* NotificationService-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NotificationService-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
1E67380324DC529B0009E081 /* String+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = "<group>"; };
|
1E67380324DC529B0009E081 /* String+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
1E680ED82512990700C9257A /* Request.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Request.swift; sourceTree = "<group>"; };
|
1E680ED82512990700C9257A /* Request.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Request.swift; sourceTree = "<group>"; };
|
||||||
|
1EB375882B55DBFB00AEC3D7 /* Server.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Server.swift; sourceTree = "<group>"; };
|
||||||
1EB8EF712510F1EE00F352B7 /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = "<group>"; };
|
1EB8EF712510F1EE00F352B7 /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = "<group>"; };
|
||||||
1EC6ACB022CB9FC300A41C61 /* ShareRocketChatRN.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ShareRocketChatRN.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
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 = "<group>"; };
|
1EC6ACB622CB9FC300A41C61 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = "<group>"; };
|
||||||
|
@ -304,9 +382,19 @@
|
||||||
1EC6ACF522CBA01500A41C61 /* ShareRocketChatRN.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ShareRocketChatRN.m; sourceTree = "<group>"; };
|
1EC6ACF522CBA01500A41C61 /* ShareRocketChatRN.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ShareRocketChatRN.m; sourceTree = "<group>"; };
|
||||||
1EC6AD6022CBA20C00A41C61 /* ShareRocketChatRN.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ShareRocketChatRN.entitlements; sourceTree = "<group>"; };
|
1EC6AD6022CBA20C00A41C61 /* ShareRocketChatRN.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ShareRocketChatRN.entitlements; sourceTree = "<group>"; };
|
||||||
1ED00BB02513E04400A1331F /* ReplyNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyNotification.swift; sourceTree = "<group>"; };
|
1ED00BB02513E04400A1331F /* ReplyNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyNotification.swift; sourceTree = "<group>"; };
|
||||||
|
1ED033AD2B55B1CC004F4930 /* Default.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Default.xcdatamodel; sourceTree = "<group>"; };
|
||||||
|
1ED033AF2B55B25A004F4930 /* Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Database.swift; sourceTree = "<group>"; };
|
||||||
|
1ED033B52B55B4A5004F4930 /* ServerListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerListView.swift; sourceTree = "<group>"; };
|
||||||
|
1ED033B72B55B4BE004F4930 /* ServerListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerListViewModel.swift; sourceTree = "<group>"; };
|
||||||
|
1ED033B92B55B5F6004F4930 /* ServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerView.swift; sourceTree = "<group>"; };
|
||||||
|
1ED033BE2B55BF94004F4930 /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = "<group>"; };
|
||||||
|
1ED033C02B55C190004F4930 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
|
||||||
|
1ED033C32B55C65C004F4930 /* RocketChatAppRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RocketChatAppRouter.swift; sourceTree = "<group>"; };
|
||||||
|
1ED033C72B55CE78004F4930 /* DependencyStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DependencyStore.swift; sourceTree = "<group>"; };
|
||||||
|
1ED033CA2B55D4F0004F4930 /* RocketChat.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = RocketChat.xcdatamodel; sourceTree = "<group>"; };
|
||||||
|
1ED033CC2B55D671004F4930 /* RocketChatDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RocketChatDatabase.swift; sourceTree = "<group>"; };
|
||||||
1ED0388E2B507B4B00C007D4 /* Rocket.Chat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Rocket.Chat.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
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 = "<group>"; };
|
1ED038902B507B4C00C007D4 /* RocketChatApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RocketChatApp.swift; sourceTree = "<group>"; };
|
||||||
1ED038922B507B4C00C007D4 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
|
||||||
1ED038942B507B4D00C007D4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
1ED038942B507B4D00C007D4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
1ED038972B507B4D00C007D4 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
1ED038972B507B4D00C007D4 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||||
1ED038A02B508FE700C007D4 /* FileManager+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileManager+Extensions.swift"; sourceTree = "<group>"; };
|
1ED038A02B508FE700C007D4 /* FileManager+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileManager+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
|
@ -466,6 +554,80 @@
|
||||||
path = API;
|
path = API;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
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 = "<group>";
|
||||||
|
};
|
||||||
|
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 = "<group>";
|
||||||
|
};
|
||||||
|
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 = "<group>";
|
||||||
|
};
|
||||||
|
1E29A2E42B585B070093C03C /* Adapters */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
1E29A2E52B585B070093C03C /* TokenAdapter.swift */,
|
||||||
|
1E29A2E62B585B070093C03C /* JSONAdapter.swift */,
|
||||||
|
1E29A2E72B585B070093C03C /* RequestAdapter.swift */,
|
||||||
|
);
|
||||||
|
path = Adapters;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
1E29A2EA2B585B070093C03C /* HTTP */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
1E29A2EB2B585B070093C03C /* HTTPMethod.swift */,
|
||||||
|
1E29A2EC2B585B070093C03C /* Request.swift */,
|
||||||
|
);
|
||||||
|
path = HTTP;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
1E29A3082B585B2F0093C03C /* Extensions */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
1E29A3092B585B370093C03C /* Data+Extensions.swift */,
|
||||||
|
1E29A30B2B585D1D0093C03C /* String+Extensions.swift */,
|
||||||
|
);
|
||||||
|
path = Extensions;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
1E2F61622512954500871711 /* Requests */ = {
|
1E2F61622512954500871711 /* Requests */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -528,6 +690,16 @@
|
||||||
path = Models;
|
path = Models;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
1EB375872B55DBF400AEC3D7 /* Models */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
1EB375882B55DBFB00AEC3D7 /* Server.swift */,
|
||||||
|
1E29A30D2B58608C0093C03C /* LoggedUser.swift */,
|
||||||
|
1E29A3112B5866090093C03C /* Room.swift */,
|
||||||
|
);
|
||||||
|
path = Models;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
1EC6ACB122CB9FC300A41C61 /* ShareRocketChatRN */ = {
|
1EC6ACB122CB9FC300A41C61 /* ShareRocketChatRN */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -540,14 +712,54 @@
|
||||||
path = ShareRocketChatRN;
|
path = ShareRocketChatRN;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
1ED033AB2B55B1C2004F4930 /* Database */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
1EB375872B55DBF400AEC3D7 /* Models */,
|
||||||
|
1ED033AC2B55B1CC004F4930 /* Default.xcdatamodeld */,
|
||||||
|
1ED033AF2B55B25A004F4930 /* Database.swift */,
|
||||||
|
1ED033C92B55D4F0004F4930 /* RocketChat.xcdatamodeld */,
|
||||||
|
1ED033CC2B55D671004F4930 /* RocketChatDatabase.swift */,
|
||||||
|
);
|
||||||
|
path = Database;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
1ED033B12B55B47F004F4930 /* Views */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
1ED033B52B55B4A5004F4930 /* ServerListView.swift */,
|
||||||
|
1ED033B92B55B5F6004F4930 /* ServerView.swift */,
|
||||||
|
1E29A2CB2B5857F50093C03C /* RoomListView.swift */,
|
||||||
|
1E29A2CF2B58582F0093C03C /* RoomView.swift */,
|
||||||
|
);
|
||||||
|
path = Views;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
1ED033B42B55B495004F4930 /* ViewModels */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
1ED033B72B55B4BE004F4930 /* ServerListViewModel.swift */,
|
||||||
|
1E29A2CD2B5857FC0093C03C /* RoomListViewModel.swift */,
|
||||||
|
1E29A30F2B5865B80093C03C /* RoomViewModel.swift */,
|
||||||
|
);
|
||||||
|
path = ViewModels;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
1ED0388F2B507B4C00C007D4 /* RocketChat Watch App */ = {
|
1ED0388F2B507B4C00C007D4 /* RocketChat Watch App */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
1E29A2D12B585B070093C03C /* Client */,
|
||||||
|
1ED033B42B55B495004F4930 /* ViewModels */,
|
||||||
|
1ED033B12B55B47F004F4930 /* Views */,
|
||||||
|
1ED033AB2B55B1C2004F4930 /* Database */,
|
||||||
1ED038902B507B4C00C007D4 /* RocketChatApp.swift */,
|
1ED038902B507B4C00C007D4 /* RocketChatApp.swift */,
|
||||||
1ED038922B507B4C00C007D4 /* ContentView.swift */,
|
1ED033C32B55C65C004F4930 /* RocketChatAppRouter.swift */,
|
||||||
|
1ED038C92B50A58400C007D4 /* WatchConnection.swift */,
|
||||||
|
1ED033BE2B55BF94004F4930 /* Storage.swift */,
|
||||||
1ED038942B507B4D00C007D4 /* Assets.xcassets */,
|
1ED038942B507B4D00C007D4 /* Assets.xcassets */,
|
||||||
1ED038962B507B4D00C007D4 /* Preview Content */,
|
1ED038962B507B4D00C007D4 /* Preview Content */,
|
||||||
1ED038C92B50A58400C007D4 /* WatchConnection.swift */,
|
1ED033C02B55C190004F4930 /* Localizable.xcstrings */,
|
||||||
|
1ED033C72B55CE78004F4930 /* DependencyStore.swift */,
|
||||||
);
|
);
|
||||||
path = "RocketChat Watch App";
|
path = "RocketChat Watch App";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -955,6 +1167,7 @@
|
||||||
files = (
|
files = (
|
||||||
1ED038982B507B4D00C007D4 /* Preview Assets.xcassets in Resources */,
|
1ED038982B507B4D00C007D4 /* Preview Assets.xcassets in Resources */,
|
||||||
1ED038952B507B4D00C007D4 /* Assets.xcassets in Resources */,
|
1ED038952B507B4D00C007D4 /* Assets.xcassets in Resources */,
|
||||||
|
1ED033C12B55C190004F4930 /* Localizable.xcstrings in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -1549,10 +1762,53 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
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 */,
|
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 */,
|
1ED038C62B50A21800C007D4 /* WatchMessage.swift in Sources */,
|
||||||
|
1E29A2F02B585B070093C03C /* AttachmentResponse.swift in Sources */,
|
||||||
1ED038912B507B4C00C007D4 /* RocketChatApp.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;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -1966,7 +2222,7 @@
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = 4;
|
TARGETED_DEVICE_FAMILY = 4;
|
||||||
WATCHOS_DEPLOYMENT_TARGET = 8.0;
|
WATCHOS_DEPLOYMENT_TARGET = 9.0;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
|
@ -2008,7 +2264,7 @@
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = 4;
|
TARGETED_DEVICE_FAMILY = 4;
|
||||||
WATCHOS_DEPLOYMENT_TARGET = 8.0;
|
WATCHOS_DEPLOYMENT_TARGET = 9.0;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
@ -2356,6 +2612,29 @@
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
|
/* Begin XCVersionGroup section */
|
||||||
|
1ED033AC2B55B1CC004F4930 /* Default.xcdatamodeld */ = {
|
||||||
|
isa = XCVersionGroup;
|
||||||
|
children = (
|
||||||
|
1ED033AD2B55B1CC004F4930 /* Default.xcdatamodel */,
|
||||||
|
);
|
||||||
|
currentVersion = 1ED033AD2B55B1CC004F4930 /* Default.xcdatamodel */;
|
||||||
|
path = Default.xcdatamodeld;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
versionGroupType = wrapper.xcdatamodel;
|
||||||
|
};
|
||||||
|
1ED033C92B55D4F0004F4930 /* RocketChat.xcdatamodeld */ = {
|
||||||
|
isa = XCVersionGroup;
|
||||||
|
children = (
|
||||||
|
1ED033CA2B55D4F0004F4930 /* RocketChat.xcdatamodel */,
|
||||||
|
);
|
||||||
|
currentVersion = 1ED033CA2B55D4F0004F4930 /* RocketChat.xcdatamodel */;
|
||||||
|
path = RocketChat.xcdatamodeld;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
versionGroupType = wrapper.xcdatamodel;
|
||||||
|
};
|
||||||
|
/* End XCVersionGroup section */
|
||||||
};
|
};
|
||||||
rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;
|
rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue