Add WatchConnectivity
This commit is contained in:
parent
96901bba39
commit
31c1181f91
|
@ -1,6 +1,24 @@
|
|||
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")
|
||||
|
@ -9,6 +27,9 @@ struct ContentView: View {
|
|||
Text("Hello, world!")
|
||||
}
|
||||
.padding()
|
||||
.onAppear {
|
||||
viewModel.onAppear()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
import Foundation
|
||||
import WatchConnectivity
|
||||
|
||||
enum ConnectionError: Error {
|
||||
case needsUnlock
|
||||
case decoding(Error)
|
||||
}
|
||||
|
||||
protocol Connection {
|
||||
func sendMessage(completionHandler: @escaping (Result<WatchMessage, ConnectionError>) -> Void)
|
||||
}
|
||||
|
||||
final class WatchConnection: NSObject {
|
||||
private let session: WCSession
|
||||
|
||||
init(session: WCSession = .default) {
|
||||
self.session = session
|
||||
super.init()
|
||||
session.delegate = self
|
||||
session.activate()
|
||||
}
|
||||
|
||||
private func scheduledSendMessage(completionHandler: @escaping (Result<WatchMessage, ConnectionError>) -> Void) {
|
||||
Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { [weak self] _ in
|
||||
self?.sendMessage(completionHandler: completionHandler)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - WCSessionDelegate
|
||||
|
||||
extension WatchConnection: WCSessionDelegate {
|
||||
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Connection
|
||||
|
||||
extension WatchConnection: Connection {
|
||||
func sendMessage(completionHandler: @escaping (Result<WatchMessage, ConnectionError>) -> Void) {
|
||||
guard session.activationState == .activated else {
|
||||
scheduledSendMessage(completionHandler: completionHandler)
|
||||
return
|
||||
}
|
||||
|
||||
guard !session.iOSDeviceNeedsUnlockAfterRebootForReachability else {
|
||||
completionHandler(.failure(.needsUnlock))
|
||||
return
|
||||
}
|
||||
|
||||
guard session.isReachable else {
|
||||
scheduledSendMessage(completionHandler: completionHandler)
|
||||
return
|
||||
}
|
||||
|
||||
session.sendMessage([:]) { dictionary in
|
||||
do {
|
||||
let data = try JSONSerialization.data(withJSONObject: dictionary)
|
||||
let message = try JSONDecoder().decode(WatchMessage.self, from: data)
|
||||
|
||||
completionHandler(.success(message))
|
||||
} catch {
|
||||
completionHandler(.failure(.decoding(error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -79,6 +79,28 @@
|
|||
1ED038952B507B4D00C007D4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1ED038942B507B4D00C007D4 /* Assets.xcassets */; };
|
||||
1ED038982B507B4D00C007D4 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1ED038972B507B4D00C007D4 /* Preview Assets.xcassets */; };
|
||||
1ED0389B2B507B4D00C007D4 /* Rocket.Chat.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 1ED0388E2B507B4B00C007D4 /* Rocket.Chat.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
1ED038A12B508FE700C007D4 /* FileManager+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038A02B508FE700C007D4 /* FileManager+Extensions.swift */; };
|
||||
1ED038A22B508FE700C007D4 /* FileManager+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038A02B508FE700C007D4 /* FileManager+Extensions.swift */; };
|
||||
1ED038A32B508FE700C007D4 /* FileManager+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038A02B508FE700C007D4 /* FileManager+Extensions.swift */; };
|
||||
1ED038A52B50900800C007D4 /* Bundle+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038A42B50900800C007D4 /* Bundle+Extensions.swift */; };
|
||||
1ED038A62B50900800C007D4 /* Bundle+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038A42B50900800C007D4 /* Bundle+Extensions.swift */; };
|
||||
1ED038A72B50900800C007D4 /* Bundle+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038A42B50900800C007D4 /* Bundle+Extensions.swift */; };
|
||||
1ED038A92B5090AD00C007D4 /* MMKV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038A82B5090AD00C007D4 /* MMKV.swift */; };
|
||||
1ED038AA2B5090AD00C007D4 /* MMKV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038A82B5090AD00C007D4 /* MMKV.swift */; };
|
||||
1ED038AB2B5090AD00C007D4 /* MMKV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038A82B5090AD00C007D4 /* MMKV.swift */; };
|
||||
1ED038AD2B50927B00C007D4 /* WatermelonDB+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038AC2B50927B00C007D4 /* WatermelonDB+Extensions.swift */; };
|
||||
1ED038AE2B50927B00C007D4 /* WatermelonDB+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038AC2B50927B00C007D4 /* WatermelonDB+Extensions.swift */; };
|
||||
1ED038AF2B50927B00C007D4 /* WatermelonDB+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038AC2B50927B00C007D4 /* WatermelonDB+Extensions.swift */; };
|
||||
1ED038BA2B50A1B800C007D4 /* WatchConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038B92B50A1B800C007D4 /* WatchConnection.swift */; };
|
||||
1ED038BB2B50A1B800C007D4 /* WatchConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038B92B50A1B800C007D4 /* WatchConnection.swift */; };
|
||||
1ED038BE2B50A1D400C007D4 /* DBServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038BD2B50A1D400C007D4 /* DBServer.swift */; };
|
||||
1ED038BF2B50A1D400C007D4 /* DBServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038BD2B50A1D400C007D4 /* DBServer.swift */; };
|
||||
1ED038C12B50A1E400C007D4 /* DBUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038C02B50A1E400C007D4 /* DBUser.swift */; };
|
||||
1ED038C22B50A1E400C007D4 /* DBUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038C02B50A1E400C007D4 /* DBUser.swift */; };
|
||||
1ED038C42B50A1F500C007D4 /* WatchMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038C32B50A1F500C007D4 /* WatchMessage.swift */; };
|
||||
1ED038C52B50A1F500C007D4 /* WatchMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038C32B50A1F500C007D4 /* WatchMessage.swift */; };
|
||||
1ED038C62B50A21800C007D4 /* WatchMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038C32B50A1F500C007D4 /* WatchMessage.swift */; };
|
||||
1ED038CA2B50A58400C007D4 /* WatchConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038C92B50A58400C007D4 /* WatchConnection.swift */; };
|
||||
1ED59D4C22CBA77D00C54289 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 1ED59D4B22CBA77D00C54289 /* GoogleService-Info.plist */; };
|
||||
1EF5FBD1250C109E00614FEA /* Encryption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EF5FBD0250C109E00614FEA /* Encryption.swift */; };
|
||||
1EFEB5982493B6640072EDC0 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EFEB5972493B6640072EDC0 /* NotificationService.swift */; };
|
||||
|
@ -287,6 +309,15 @@
|
|||
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>"; };
|
||||
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>"; };
|
||||
1ED038A42B50900800C007D4 /* Bundle+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+Extensions.swift"; sourceTree = "<group>"; };
|
||||
1ED038A82B5090AD00C007D4 /* MMKV.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MMKV.swift; sourceTree = "<group>"; };
|
||||
1ED038AC2B50927B00C007D4 /* WatermelonDB+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WatermelonDB+Extensions.swift"; sourceTree = "<group>"; };
|
||||
1ED038B92B50A1B800C007D4 /* WatchConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchConnection.swift; sourceTree = "<group>"; };
|
||||
1ED038BD2B50A1D400C007D4 /* DBServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBServer.swift; sourceTree = "<group>"; };
|
||||
1ED038C02B50A1E400C007D4 /* DBUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBUser.swift; sourceTree = "<group>"; };
|
||||
1ED038C32B50A1F500C007D4 /* WatchMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchMessage.swift; sourceTree = "<group>"; };
|
||||
1ED038C92B50A58400C007D4 /* WatchConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchConnection.swift; sourceTree = "<group>"; };
|
||||
1ED59D4B22CBA77D00C54289 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = SOURCE_ROOT; };
|
||||
1EF5FBD0250C109E00614FEA /* Encryption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Encryption.swift; sourceTree = "<group>"; };
|
||||
1EFEB5952493B6640072EDC0 /* NotificationService.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotificationService.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
|
@ -462,6 +493,7 @@
|
|||
1E1C2F7F250FCB69005DCE7D /* Database.swift */,
|
||||
1E470E822513A71E00E3DD1D /* RocketChat.swift */,
|
||||
1EB8EF712510F1EE00F352B7 /* Storage.swift */,
|
||||
1ED038A82B5090AD00C007D4 /* MMKV.swift */,
|
||||
);
|
||||
path = RocketChat;
|
||||
sourceTree = "<group>";
|
||||
|
@ -473,6 +505,9 @@
|
|||
1E598AE32515057D002BDFBD /* Date+Extensions.swift */,
|
||||
1E598AE625150660002BDFBD /* Data+Extensions.swift */,
|
||||
1E67380324DC529B0009E081 /* String+Extensions.swift */,
|
||||
1ED038A02B508FE700C007D4 /* FileManager+Extensions.swift */,
|
||||
1ED038A42B50900800C007D4 /* Bundle+Extensions.swift */,
|
||||
1ED038AC2B50927B00C007D4 /* WatermelonDB+Extensions.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
|
@ -512,6 +547,7 @@
|
|||
1ED038922B507B4C00C007D4 /* ContentView.swift */,
|
||||
1ED038942B507B4D00C007D4 /* Assets.xcassets */,
|
||||
1ED038962B507B4D00C007D4 /* Preview Content */,
|
||||
1ED038C92B50A58400C007D4 /* WatchConnection.swift */,
|
||||
);
|
||||
path = "RocketChat Watch App";
|
||||
sourceTree = "<group>";
|
||||
|
@ -524,6 +560,25 @@
|
|||
path = "Preview Content";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1ED038B82B50A1A500C007D4 /* Watch */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1ED038BC2B50A1C700C007D4 /* Database */,
|
||||
1ED038B92B50A1B800C007D4 /* WatchConnection.swift */,
|
||||
1ED038C32B50A1F500C007D4 /* WatchMessage.swift */,
|
||||
);
|
||||
path = Watch;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1ED038BC2B50A1C700C007D4 /* Database */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1ED038BD2B50A1D400C007D4 /* DBServer.swift */,
|
||||
1ED038C02B50A1E400C007D4 /* DBUser.swift */,
|
||||
);
|
||||
path = Database;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1EFEB5962493B6640072EDC0 /* NotificationService */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -579,6 +634,7 @@
|
|||
83CBB9F61A601CBA00E9B192 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1ED038B82B50A1A500C007D4 /* Watch */,
|
||||
1E76CBC425152A7F0067298C /* Shared */,
|
||||
1E068CFB24FD2DAF00A0FFC1 /* AppGroup */,
|
||||
13B07FAE1A68108700A75B9A /* RocketChatRN */,
|
||||
|
@ -1438,6 +1494,7 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
1ED038A92B5090AD00C007D4 /* MMKV.swift in Sources */,
|
||||
1E76CBCB25152C250067298C /* Sender.swift in Sources */,
|
||||
1E76CBD825152C870067298C /* Request.swift in Sources */,
|
||||
1ED00BB12513E04400A1331F /* ReplyNotification.swift in Sources */,
|
||||
|
@ -1454,11 +1511,18 @@
|
|||
1E76CBD225152C730067298C /* Data+Extensions.swift in Sources */,
|
||||
1E76CBD125152C710067298C /* Date+Extensions.swift in Sources */,
|
||||
1E76CBD425152C790067298C /* Database.swift in Sources */,
|
||||
1ED038AD2B50927B00C007D4 /* WatermelonDB+Extensions.swift in Sources */,
|
||||
1ED038A52B50900800C007D4 /* Bundle+Extensions.swift in Sources */,
|
||||
1E76CBC325152A460067298C /* String+Extensions.swift in Sources */,
|
||||
1ED038BA2B50A1B800C007D4 /* WatchConnection.swift in Sources */,
|
||||
1ED038A12B508FE700C007D4 /* FileManager+Extensions.swift in Sources */,
|
||||
1E76CBCA25152C220067298C /* Notification.swift in Sources */,
|
||||
1ED038C12B50A1E400C007D4 /* DBUser.swift in Sources */,
|
||||
1E76CBD525152C7F0067298C /* API.swift in Sources */,
|
||||
1E76CBD625152C820067298C /* Response.swift in Sources */,
|
||||
1ED038BE2B50A1D400C007D4 /* DBServer.swift in Sources */,
|
||||
1E068D0124FD2E0500A0FFC1 /* AppGroup.m in Sources */,
|
||||
1ED038C42B50A1F500C007D4 /* WatchMessage.swift in Sources */,
|
||||
13B07FC11A68108700A75B9A /* main.m in Sources */,
|
||||
1E76CBD025152C6E0067298C /* URL+Extensions.swift in Sources */,
|
||||
1E068CFE24FD2DC700A0FFC1 /* AppGroup.swift in Sources */,
|
||||
|
@ -1486,6 +1550,8 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
1ED038932B507B4C00C007D4 /* ContentView.swift in Sources */,
|
||||
1ED038CA2B50A58400C007D4 /* WatchConnection.swift in Sources */,
|
||||
1ED038C62B50A21800C007D4 /* WatchMessage.swift in Sources */,
|
||||
1ED038912B507B4C00C007D4 /* RocketChatApp.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -1497,6 +1563,8 @@
|
|||
1E51D965251263D600DC95DE /* NotificationType.swift in Sources */,
|
||||
1EF5FBD1250C109E00614FEA /* Encryption.swift in Sources */,
|
||||
1E598AE42515057D002BDFBD /* Date+Extensions.swift in Sources */,
|
||||
1ED038A22B508FE700C007D4 /* FileManager+Extensions.swift in Sources */,
|
||||
1ED038AA2B5090AD00C007D4 /* MMKV.swift in Sources */,
|
||||
1E01C81C2511208400FEF824 /* URL+Extensions.swift in Sources */,
|
||||
1E470E832513A71E00E3DD1D /* RocketChat.swift in Sources */,
|
||||
1E2F615D25128FA300871711 /* Response.swift in Sources */,
|
||||
|
@ -1505,6 +1573,8 @@
|
|||
1E0426E7251A54B4008F022C /* RoomType.swift in Sources */,
|
||||
1E1C2F80250FCB69005DCE7D /* Database.swift in Sources */,
|
||||
1E67380424DC529B0009E081 /* String+Extensions.swift in Sources */,
|
||||
1ED038AE2B50927B00C007D4 /* WatermelonDB+Extensions.swift in Sources */,
|
||||
1ED038A62B50900800C007D4 /* Bundle+Extensions.swift in Sources */,
|
||||
1E01C8292511304100FEF824 /* Sender.swift in Sources */,
|
||||
1E51D962251263CD00DC95DE /* MessageType.swift in Sources */,
|
||||
1E01C82B2511335A00FEF824 /* Message.swift in Sources */,
|
||||
|
@ -1526,6 +1596,7 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
1ED038AB2B5090AD00C007D4 /* MMKV.swift in Sources */,
|
||||
7AAB3E15257E6A6E00707CF6 /* Sender.swift in Sources */,
|
||||
7AAB3E16257E6A6E00707CF6 /* Request.swift in Sources */,
|
||||
7AAB3E17257E6A6E00707CF6 /* ReplyNotification.swift in Sources */,
|
||||
|
@ -1542,11 +1613,18 @@
|
|||
7AAB3E23257E6A6E00707CF6 /* Data+Extensions.swift in Sources */,
|
||||
7AAB3E24257E6A6E00707CF6 /* Date+Extensions.swift in Sources */,
|
||||
7AAB3E25257E6A6E00707CF6 /* Database.swift in Sources */,
|
||||
1ED038AF2B50927B00C007D4 /* WatermelonDB+Extensions.swift in Sources */,
|
||||
1ED038A72B50900800C007D4 /* Bundle+Extensions.swift in Sources */,
|
||||
7AAB3E26257E6A6E00707CF6 /* String+Extensions.swift in Sources */,
|
||||
1ED038BB2B50A1B800C007D4 /* WatchConnection.swift in Sources */,
|
||||
1ED038A32B508FE700C007D4 /* FileManager+Extensions.swift in Sources */,
|
||||
7AAB3E27257E6A6E00707CF6 /* Notification.swift in Sources */,
|
||||
1ED038C22B50A1E400C007D4 /* DBUser.swift in Sources */,
|
||||
7AAB3E28257E6A6E00707CF6 /* API.swift in Sources */,
|
||||
7AAB3E29257E6A6E00707CF6 /* Response.swift in Sources */,
|
||||
1ED038BF2B50A1D400C007D4 /* DBServer.swift in Sources */,
|
||||
7AAB3E2A257E6A6E00707CF6 /* AppGroup.m in Sources */,
|
||||
1ED038C52B50A1F500C007D4 /* WatchMessage.swift in Sources */,
|
||||
7AAB3E2B257E6A6E00707CF6 /* main.m in Sources */,
|
||||
7AAB3E2C257E6A6E00707CF6 /* URL+Extensions.swift in Sources */,
|
||||
7AAB3E2D257E6A6E00707CF6 /* AppGroup.swift in Sources */,
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "1ED0388D2B507B4B00C007D4"
|
||||
BuildableName = "RocketChat Watch App.app"
|
||||
BuildableName = "Rocket.Chat.app"
|
||||
BlueprintName = "RocketChat Watch App"
|
||||
ReferencedContainer = "container:RocketChatRN.xcodeproj">
|
||||
</BuildableReference>
|
||||
|
@ -58,7 +58,7 @@
|
|||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "1ED0388D2B507B4B00C007D4"
|
||||
BuildableName = "RocketChat Watch App.app"
|
||||
BuildableName = "Rocket.Chat.app"
|
||||
BlueprintName = "RocketChat Watch App"
|
||||
ReferencedContainer = "container:RocketChatRN.xcodeproj">
|
||||
</BuildableReference>
|
||||
|
@ -75,7 +75,7 @@
|
|||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "1ED0388D2B507B4B00C007D4"
|
||||
BuildableName = "RocketChat Watch App.app"
|
||||
BuildableName = "Rocket.Chat.app"
|
||||
BlueprintName = "RocketChat Watch App"
|
||||
ReferencedContainer = "container:RocketChatRN.xcodeproj">
|
||||
</BuildableReference>
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#import <UIKit/UIKit.h>
|
||||
#import <React/RCTBridgeDelegate.h>
|
||||
#import <Expo/Expo.h>
|
||||
#import <WatchConnectivity/WatchConnectivity.h>
|
||||
// https://github.com/expo/expo/issues/17705#issuecomment-1196251146
|
||||
#import "ExpoModulesCore-Swift.h"
|
||||
#import "RocketChatRN-Swift.h"
|
||||
|
@ -17,5 +18,6 @@
|
|||
@interface AppDelegate : EXAppDelegateWrapper <UIApplicationDelegate, RCTBridgeDelegate>
|
||||
|
||||
@property (nonatomic, strong) UIWindow *window;
|
||||
@property (nonatomic, strong) WatchConnection *watchConnection;
|
||||
|
||||
@end
|
||||
|
|
|
@ -69,6 +69,8 @@
|
|||
[MMKV initializeMMKV:nil groupDir:groupDir logLevel:MMKVLogInfo];
|
||||
|
||||
[RNBootSplash initWithStoryboard:@"LaunchScreen" rootView:rootView];
|
||||
|
||||
self.watchConnection = [[WatchConnection alloc] initWithSession:[WCSession defaultSession]];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
import Foundation
|
||||
|
||||
extension Bundle {
|
||||
func bool(forKey key: String) -> Bool {
|
||||
object(forInfoDictionaryKey: key) as? Bool ?? false
|
||||
}
|
||||
|
||||
func string(forKey key: String) -> String {
|
||||
guard let string = object(forInfoDictionaryKey: key) as? String else {
|
||||
fatalError("Could not locate string for key \(key).")
|
||||
}
|
||||
|
||||
return string
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
import Foundation
|
||||
|
||||
extension FileManager {
|
||||
func groupDir() -> String {
|
||||
let applicationGroupIdentifier = Bundle.main.string(forKey: "AppGroup")
|
||||
|
||||
guard let path = containerURL(forSecurityApplicationGroupIdentifier: applicationGroupIdentifier)?.path else {
|
||||
return ""
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
import WatermelonDB
|
||||
|
||||
extension WatermelonDB.Database {
|
||||
convenience init(name: String) {
|
||||
let isOfficial = Bundle.main.bool(forKey: "IS_OFFICIAL")
|
||||
let groupDir = FileManager.default.groupDir()
|
||||
let path = "\(groupDir)/\(name)\(isOfficial ? "" : "-experimental").db"
|
||||
|
||||
self.init(path: path)
|
||||
}
|
||||
|
||||
func query<T: Codable>(raw: SQL, _ args: QueryArgs = []) -> [T] {
|
||||
guard let results = try? queryRaw(raw, args) else {
|
||||
return []
|
||||
}
|
||||
|
||||
return results.compactMap { result in
|
||||
guard let dictionary = result.resultDictionary else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let data = try? JSONSerialization.data(withJSONObject: dictionary) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let item = try? JSONDecoder().decode(T.self, from: data) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return item
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,38 +10,21 @@ import Foundation
|
|||
import WatermelonDB
|
||||
|
||||
final class Database {
|
||||
private final var database: WatermelonDB.Database? = nil
|
||||
|
||||
private var directory: String? {
|
||||
if let suiteName = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as? String {
|
||||
if let directory = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: suiteName) {
|
||||
return directory.path
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
private let database: WatermelonDB.Database
|
||||
|
||||
init(server: String) {
|
||||
if let url = URL(string: server) {
|
||||
if let domain = url.domain, let directory = directory {
|
||||
let isOfficial = Bundle.main.object(forInfoDictionaryKey: "IS_OFFICIAL") as? Bool ?? false
|
||||
self.database = WatermelonDB.Database(path: "\(directory)/\(domain)\(isOfficial ? "" : "-experimental").db")
|
||||
}
|
||||
}
|
||||
database = .init(name: server)
|
||||
}
|
||||
|
||||
func readRoomEncryptionKey(rid: String) -> String? {
|
||||
if let database = database {
|
||||
if let results = try? database.queryRaw("select * from subscriptions where id == ? limit 1", [rid]) {
|
||||
guard let record = results.next() else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if let room = record.resultDictionary as? [String: Any] {
|
||||
if let e2eKey = room["e2e_key"] as? String {
|
||||
return e2eKey
|
||||
}
|
||||
if let results = try? database.queryRaw("select * from subscriptions where id == ? limit 1", [rid]) {
|
||||
guard let record = results.next() else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if let room = record.resultDictionary as? [String: Any] {
|
||||
if let e2eKey = room["e2e_key"] as? String {
|
||||
return e2eKey
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,16 +33,14 @@ final class Database {
|
|||
}
|
||||
|
||||
func readRoomEncrypted(rid: String) -> Bool {
|
||||
if let database = database {
|
||||
if let results = try? database.queryRaw("select * from subscriptions where id == ? limit 1", [rid]) {
|
||||
guard let record = results.next() else {
|
||||
return false
|
||||
}
|
||||
|
||||
if let room = record.resultDictionary as? [String: Any] {
|
||||
if let encrypted = room["encrypted"] as? Bool {
|
||||
return encrypted
|
||||
}
|
||||
if let results = try? database.queryRaw("select * from subscriptions where id == ? limit 1", [rid]) {
|
||||
guard let record = results.next() else {
|
||||
return false
|
||||
}
|
||||
|
||||
if let room = record.resultDictionary as? [String: Any] {
|
||||
if let encrypted = room["encrypted"] as? Bool {
|
||||
return encrypted
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
import Foundation
|
||||
|
||||
extension MMKV {
|
||||
static func build() -> MMKV {
|
||||
let password = SecureStorage().getSecureKey("com.MMKV.default".toHex())
|
||||
let groupDir = FileManager.default.groupDir()
|
||||
|
||||
MMKV.initialize(rootDir: nil, groupDir: groupDir, logLevel: MMKVLogLevel.none)
|
||||
|
||||
guard let mmkv = MMKV(mmapID: "default", cryptKey: password?.data(using: .utf8), mode: MMKVMode.multiProcess) else {
|
||||
fatalError("Could not initialize MMKV instance.")
|
||||
}
|
||||
|
||||
return mmkv
|
||||
}
|
||||
|
||||
func userToken(for userId: String) -> String {
|
||||
guard let userToken = string(forKey: "reactnativemeteor_usertoken-\(userId)") else {
|
||||
fatalError("userToken is nil for userId \(userId)")
|
||||
}
|
||||
|
||||
return userToken
|
||||
}
|
||||
|
||||
func userId(for server: String) -> String {
|
||||
guard let userId = string(forKey: "reactnativemeteor_usertoken-\(server)") else {
|
||||
fatalError("userId is nil for server \(server)")
|
||||
}
|
||||
|
||||
return userId
|
||||
}
|
||||
|
||||
func privateKey(for server: String) -> String {
|
||||
guard let privateKey = string(forKey: "\(server)-RC_E2E_PRIVATE_KEY") else {
|
||||
fatalError("privateKey is nil for server \(server)")
|
||||
}
|
||||
|
||||
return privateKey
|
||||
}
|
||||
}
|
|
@ -1,11 +1,3 @@
|
|||
//
|
||||
// Storage.swift
|
||||
// NotificationService
|
||||
//
|
||||
// Created by Djorkaeff Alexandre Vilela Pereira on 9/15/20.
|
||||
// Copyright © 2020 Rocket.Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct Credentials {
|
||||
|
@ -13,48 +5,19 @@ struct Credentials {
|
|||
let userToken: String
|
||||
}
|
||||
|
||||
class Storage {
|
||||
final class Storage {
|
||||
static let shared = Storage()
|
||||
|
||||
final var mmkv: MMKV? = nil
|
||||
private let mmkv = MMKV.build()
|
||||
|
||||
init() {
|
||||
let mmapID = "default"
|
||||
let instanceID = "com.MMKV.\(mmapID)"
|
||||
let secureStorage = SecureStorage()
|
||||
|
||||
// get mmkv instance password from keychain
|
||||
var key: Data?
|
||||
if let password: String = secureStorage.getSecureKey(instanceID.toHex()) {
|
||||
key = password.data(using: .utf8)
|
||||
}
|
||||
|
||||
guard let cryptKey = key else {
|
||||
return
|
||||
}
|
||||
|
||||
// Get App Group directory
|
||||
let suiteName = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as! String
|
||||
guard let directory = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: suiteName) else {
|
||||
return
|
||||
}
|
||||
|
||||
// Set App Group dir
|
||||
MMKV.initialize(rootDir: nil, groupDir: directory.path, logLevel: MMKVLogLevel.none)
|
||||
self.mmkv = MMKV(mmapID: mmapID, cryptKey: cryptKey, mode: MMKVMode.multiProcess)
|
||||
func getCredentials(server: String) -> Credentials {
|
||||
let userId = mmkv.userId(for: server)
|
||||
let userToken = mmkv.userToken(for: userId)
|
||||
|
||||
return .init(userId: userId, userToken: userToken)
|
||||
}
|
||||
|
||||
func getCredentials(server: String) -> Credentials? {
|
||||
if let userId = self.mmkv?.string(forKey: "reactnativemeteor_usertoken-\(server)") {
|
||||
if let userToken = self.mmkv?.string(forKey: "reactnativemeteor_usertoken-\(userId)") {
|
||||
return Credentials(userId: userId, userToken: userToken)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getPrivateKey(server: String) -> String? {
|
||||
return self.mmkv?.string(forKey: "\(server)-RC_E2E_PRIVATE_KEY")
|
||||
func getPrivateKey(server: String) -> String {
|
||||
mmkv.privateKey(for: server)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
import Foundation
|
||||
|
||||
struct DBServer: Codable {
|
||||
let url: URL
|
||||
let name: String
|
||||
let useRealName: Int
|
||||
let iconURL: URL
|
||||
|
||||
var identifier: String {
|
||||
url.absoluteString
|
||||
}
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case url = "id"
|
||||
case name
|
||||
case useRealName = "use_real_name"
|
||||
case iconURL = "icon_url"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import Foundation
|
||||
|
||||
struct DBUser: Codable {
|
||||
let name: String
|
||||
let username: String
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
import Foundation
|
||||
import WatermelonDB
|
||||
import WatchConnectivity
|
||||
|
||||
@objc
|
||||
final class WatchConnection: NSObject {
|
||||
private let database = WatermelonDB.Database(name: "default")
|
||||
private let mmkv = MMKV.build()
|
||||
private let session: WCSession
|
||||
|
||||
@objc init(session: WCSession) {
|
||||
self.session = session
|
||||
super.init()
|
||||
|
||||
if WCSession.isSupported() {
|
||||
session.delegate = self
|
||||
session.activate()
|
||||
}
|
||||
}
|
||||
|
||||
private func getMessage() -> WatchMessage {
|
||||
let serversQuery = database.query(raw: "select * from servers") as [DBServer]
|
||||
|
||||
let servers = serversQuery.compactMap { item -> WatchMessage.Server? in
|
||||
let userId = mmkv.userId(for: item.identifier)
|
||||
let userToken = mmkv.userToken(for: userId)
|
||||
|
||||
let usersQuery = database.query(raw: "select * from users where token == ? limit 1", [userToken]) as [DBUser]
|
||||
|
||||
guard let user = usersQuery.first else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return WatchMessage.Server(
|
||||
url: item.url,
|
||||
name: item.name,
|
||||
iconURL: item.iconURL,
|
||||
useRealName: item.useRealName == 1 ? true : false,
|
||||
loggedUser: .init(
|
||||
id: userId,
|
||||
token: userToken,
|
||||
name: user.name,
|
||||
username: user.username
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return WatchMessage(servers: servers)
|
||||
}
|
||||
|
||||
private func encodedMessage() -> [String: Any] {
|
||||
do {
|
||||
let data = try JSONEncoder().encode(getMessage())
|
||||
|
||||
guard let dictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] else {
|
||||
fatalError("Could not serialize message: \(getMessage())")
|
||||
}
|
||||
|
||||
return dictionary
|
||||
} catch {
|
||||
fatalError("Could not encode message: \(getMessage())")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension WatchConnection: WCSessionDelegate {
|
||||
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
|
||||
|
||||
}
|
||||
|
||||
func sessionDidBecomeInactive(_ session: WCSession) {
|
||||
session.activate()
|
||||
}
|
||||
|
||||
func sessionDidDeactivate(_ session: WCSession) {
|
||||
session.activate()
|
||||
}
|
||||
|
||||
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) {
|
||||
replyHandler(encodedMessage())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import Foundation
|
||||
|
||||
struct WatchMessage: Codable {
|
||||
let servers: [Server]
|
||||
|
||||
struct Server: Codable {
|
||||
let url: URL
|
||||
let name: String
|
||||
let iconURL: URL
|
||||
let useRealName: Bool
|
||||
let loggedUser: LoggedUser
|
||||
|
||||
struct LoggedUser: Codable {
|
||||
let id: String
|
||||
let token: String
|
||||
let name: String
|
||||
let username: String
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue