Skip to content

Commit

Permalink
feat: WatchOS app (#5476)
Browse files Browse the repository at this point in the history
  • Loading branch information
djorkaeffalexandre authored 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
Loading
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
Loading
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
}
}
Loading
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
}
}
Loading
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
}
}
Loading
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
}
}
Loading
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
}
}
Loading
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
}
}
Loading
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)
}
}
Loading

0 comments on commit 77d32f4

Please sign in to comment.