Skip to content

Commit

Permalink
feat: WatchOS app (#5476)
Browse files Browse the repository at this point in the history
  • Loading branch information
djorkaeffalexandre committed Apr 26, 2024
1 parent e98ce32 commit 77d32f4
Show file tree
Hide file tree
Showing 124 changed files with 6,880 additions and 118 deletions.
4 changes: 2 additions & 2 deletions .detoxrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ module.exports = {
type: 'ios.app',
binaryPath: 'ios/build/Build/Products/Debug-iphonesimulator/Rocket.Chat Experimental.app',
build:
'xcodebuild -workspace ios/RocketChatRN.xcworkspace -scheme RocketChatRN -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build'
'xcodebuild -workspace ios/RocketChatRN.xcworkspace -scheme RocketChatRN -configuration Debug -destination \'generic/platform=iphonesimulator\' -derivedDataPath ios/build'
},
'ios.release': {
type: 'ios.app',
binaryPath: 'ios/build/Build/Products/Release-iphonesimulator/Rocket.Chat Experimental.app',
build:
'xcodebuild -workspace ios/RocketChatRN.xcworkspace -scheme RocketChatRN -configuration Release -sdk iphonesimulator -derivedDataPath ios/build'
'xcodebuild -workspace ios/RocketChatRN.xcworkspace -scheme RocketChatRN -configuration Release -destination \'generic/platform=iphonesimulator\' -derivedDataPath ios/build'
},
'android.debug': {
type: 'android.apk',
Expand Down
1 change: 1 addition & 0 deletions app/lib/methods/helpers/sslPinning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ const RCSSLPinning = Platform.select({
certificate = persistCertificate(name, certificate.password);
}
UserPreferences.setMap(extractHostname(server), certificate);
SSLPinning?.setCertificate(server, certificate.path, certificate.password);
}
}
},
Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions ios/Experimental.xcassets/AppIcon.appiconset/Contents.json
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,12 @@
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
},
{
"filename" : "1024 1.png",
"idiom" : "universal",
"platform" : "watchos",
"size" : "1024x1024"
}
],
"info" : {
Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions ios/Official.xcassets/AppIcon.appiconset/Contents.json
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,12 @@
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
},
{
"filename" : "1024 1.png",
"idiom" : "universal",
"platform" : "watchos",
"size" : "1024x1024"
}
],
"info" : {
Expand Down
39 changes: 39 additions & 0 deletions ios/RocketChat Watch App/ActionHandler/ErrorActionHandler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import Foundation

protocol ErrorActionHandling {
func handle(error: RocketChatError)
}

final class ErrorActionHandler {
@Dependency private var database: Database
@Dependency private var serversDB: ServersDatabase
@Dependency private var router: AppRouting

private let server: Server

init(server: Server) {
self.server = server
}

private func handleOnMain(error: RocketChatError) {
switch error {
case .server(let response):
router.present(error: response)
case .unauthorized:
router.route(to: [.loading, .serverList]) {
self.database.remove()
self.serversDB.remove(self.server)
}
case .unknown:
print("Unexpected error on Client.")
}
}
}

extension ErrorActionHandler: ErrorActionHandling {
func handle(error: RocketChatError) {
DispatchQueue.main.async {
self.handleOnMain(error: error)
}
}
}
83 changes: 83 additions & 0 deletions ios/RocketChat Watch App/AppRouter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import Foundation

protocol AppRouting {
func route(to route: Route)
func present(error: ErrorResponse)
func route(to routes: [Route], completion: (() -> Void)?)
}

final class AppRouter: ObservableObject {
@Published var error: ErrorResponse?

@Published var server: Server? {
didSet {
if server != oldValue, let server {
registerDependencies(in: server)
}
}
}

@Published var room: Room?

@Storage(.currentServer) private var currentURL: URL?

private func registerDependencies(in server: Server) {
Store.register(Database.self, factory: server.database)
Store.register(RocketChatClientProtocol.self, factory: RocketChatClient(server: server))
Store.register(MessageSending.self, factory: MessageSender(server: server))
Store.register(ErrorActionHandling.self, factory: ErrorActionHandler(server: server))
Store.register(MessagesLoading.self, factory: MessagesLoader())
Store.register(RoomsLoader.self, factory: RoomsLoader(server: server))
}
}

extension AppRouter: AppRouting {
func route(to route: Route) {
switch route {
case .roomList(let selectedServer):
currentURL = selectedServer.url
room = nil
server = selectedServer
case .room(let selectedServer, let selectedRoom):
currentURL = selectedServer.url
server = selectedServer
room = selectedRoom
case .serverList:
currentURL = nil
room = nil
server = nil
case .loading:
room = nil
server = nil
}
}

func present(error: ErrorResponse) {
guard self.error == nil else {
return
}

self.error = error
}
}

extension AppRouter {
func route(to routes: [Route], completion: (() -> Void)? = nil) {
guard let routeTo = routes.first else {
completion?()
return
}

DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.route(to: routeTo)
self.route(to: Array(routes[1..<routes.count]), completion: completion)
}
}
}

enum Route: Equatable {
case loading
case serverList
case roomList(Server)
case room(Server, Room)
}
39 changes: 39 additions & 0 deletions ios/RocketChat Watch App/AppView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import SwiftUI

struct AppView: View {
@Storage(.currentServer) private var currentURL: URL?

@Dependency private var database: ServersDatabase

@StateObject private var router: AppRouter

init(router: AppRouter) {
_router = StateObject(wrappedValue: router)
}

var body: some View {
NavigationView {
ServerListView()
.environmentObject(router)
.environment(\.managedObjectContext, database.viewContext)
}
.onAppear {
loadRoute()
}
.sheet(item: $router.error) { error in
Text(error.error)
.multilineTextAlignment(.center)
.padding()
}
}

private func loadRoute() {
if let currentURL, let server = database.server(url: currentURL) {
router.route(to: .roomList(server))
} else if database.servers().count == 1, let server = database.servers().first {
router.route(to: .roomList(server))
} else {
router.route(to: .serverList)
}
}
}
6 changes: 6 additions & 0 deletions ios/RocketChat Watch App/Assets.xcassets/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "channel-private.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "channel-public.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "discussions.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "message.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "teams-private.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "teams.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions ios/RocketChat Watch App/Client/Adapters/JSONAdapter.swift
Original file line number Diff line number Diff line change
@@ -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
}
}
12 changes: 12 additions & 0 deletions ios/RocketChat Watch App/Client/Adapters/RequestAdapter.swift
Original file line number Diff line number Diff line change
@@ -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
}
}
25 changes: 25 additions & 0 deletions ios/RocketChat Watch App/Client/Adapters/TokenAdapter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Foundation

struct TokenAdapter: RequestAdapter {
private let server: Server

init(server: Server) {
self.server = server
}

func adapt(_ url: URL) -> URL {
url.appending(
queryItems: [
URLQueryItem(name: "rc_token", value: server.loggedUser.token),
URLQueryItem(name: "rc_uid", value: server.loggedUser.id)
]
)
}

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
}
}
44 changes: 44 additions & 0 deletions ios/RocketChat Watch App/Client/DateCodingStrategy.swift
Original file line number Diff line number Diff line change
@@ -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 comments on commit 77d32f4

Please sign in to comment.