mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 16:10:20 +00:00
Notifications (#275)
This commit is contained in:
@@ -92,7 +92,7 @@ class Analytics {
|
||||
|
||||
// Catch and log crashes
|
||||
// MXLogger.logCrashes(true)
|
||||
// MXLogger.setBuildVersion(ElementInfoPlist.cfBundleShortVersionString)
|
||||
// MXLogger.setBuildVersion(Bundle.bundleShortVersionString)
|
||||
}
|
||||
|
||||
/// Use the analytics settings from the supplied user session to configure analytics.
|
||||
|
||||
@@ -61,7 +61,7 @@ class BugReportService: BugReportServiceProtocol {
|
||||
// also enable logging crashes, to send them with bug reports
|
||||
MXLogger.logCrashes(true)
|
||||
// set build version for logger
|
||||
MXLogger.buildVersion = ElementInfoPlist.cfBundleShortVersionString
|
||||
MXLogger.buildVersion = InfoPlistReader.target.bundleShortVersionString
|
||||
}
|
||||
|
||||
// MARK: - BugReportServiceProtocol
|
||||
@@ -151,8 +151,8 @@ class BugReportService: BugReportServiceProtocol {
|
||||
return [
|
||||
MultipartFormData(key: "user_agent", type: .text(value: "iOS")),
|
||||
MultipartFormData(key: "app", type: .text(value: applicationId)),
|
||||
MultipartFormData(key: "version", type: .text(value: ElementInfoPlist.cfBundleShortVersionString)),
|
||||
MultipartFormData(key: "build", type: .text(value: ElementInfoPlist.cfBundleVersion)),
|
||||
MultipartFormData(key: "version", type: .text(value: InfoPlistReader.target.bundleShortVersionString)),
|
||||
MultipartFormData(key: "build", type: .text(value: InfoPlistReader.target.bundleVersion)),
|
||||
MultipartFormData(key: "os", type: .text(value: os)),
|
||||
MultipartFormData(key: "resolved_language", type: .text(value: Bundle.preferredLanguages[0])),
|
||||
MultipartFormData(key: "user_language", type: .text(value: Bundle.elementLanguage ?? "null")),
|
||||
|
||||
@@ -32,11 +32,11 @@ class FileCache {
|
||||
private let fileManager = FileManager.default
|
||||
private let folder: URL
|
||||
|
||||
/// Default instance. Uses `FileCache` as the folder name.
|
||||
static let `default` = FileCache(folderName: "FileCache")
|
||||
/// Default instance. Uses `Files` as the folder name.
|
||||
static let `default` = FileCache(folderName: "Files")
|
||||
|
||||
init(folderName: String) {
|
||||
folder = fileManager.temporaryDirectory.appending(path: folderName, directoryHint: .isDirectory)
|
||||
folder = URL.cacheBaseDirectory.appending(path: folderName, directoryHint: .isDirectory)
|
||||
}
|
||||
|
||||
// MARK: Private
|
||||
@@ -44,21 +44,6 @@ class FileCache {
|
||||
private func filePath(forKey key: String, fileExtension: String) -> URL {
|
||||
folder.appending(path: key, directoryHint: .notDirectory).appendingPathExtension(fileExtension)
|
||||
}
|
||||
|
||||
private func folderExists() -> Bool {
|
||||
var isDirectory: ObjCBool = false
|
||||
guard fileManager.fileExists(atPath: folder.path(), isDirectory: &isDirectory) else {
|
||||
return false
|
||||
}
|
||||
return isDirectory.boolValue
|
||||
}
|
||||
|
||||
private func createFolderIfNeeded() throws {
|
||||
guard !folderExists() else {
|
||||
return
|
||||
}
|
||||
try fileManager.createDirectory(at: folder, withIntermediateDirectories: true)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - FileCacheProtocol
|
||||
@@ -70,7 +55,7 @@ extension FileCache: FileCacheProtocol {
|
||||
}
|
||||
|
||||
func store(_ data: Data, with fileExtension: String, forKey key: String) throws -> URL {
|
||||
try createFolderIfNeeded()
|
||||
try fileManager.createDirectoryIfNeeded(at: folder)
|
||||
let url = filePath(forKey: key, fileExtension: fileExtension)
|
||||
try data.write(to: url)
|
||||
return url
|
||||
@@ -81,7 +66,7 @@ extension FileCache: FileCacheProtocol {
|
||||
}
|
||||
|
||||
func removeAll() throws {
|
||||
guard folderExists() else {
|
||||
guard fileManager.directoryExists(at: folder) else {
|
||||
return
|
||||
}
|
||||
try fileManager.removeItem(at: folder)
|
||||
|
||||
@@ -52,6 +52,7 @@ class ClientProxy: ClientProxyProtocol {
|
||||
private let client: ClientProtocol
|
||||
private let backgroundTaskService: BackgroundTaskServiceProtocol
|
||||
private var sessionVerificationControllerProxy: SessionVerificationControllerProxy?
|
||||
private let mediaProxy: MediaProxyProtocol
|
||||
private let clientQueue: DispatchQueue
|
||||
|
||||
private var slidingSyncObserverToken: StoppableSpawn?
|
||||
@@ -75,6 +76,8 @@ class ClientProxy: ClientProxyProtocol {
|
||||
self.backgroundTaskService = backgroundTaskService
|
||||
clientQueue = .init(label: "ClientProxyQueue",
|
||||
attributes: .concurrent)
|
||||
mediaProxy = MediaProxy(client: client,
|
||||
clientQueue: clientQueue)
|
||||
|
||||
await Task.dispatch(on: clientQueue) {
|
||||
do {
|
||||
@@ -204,25 +207,7 @@ class ClientProxy: ClientProxyProtocol {
|
||||
.failure(.failedSettingAccountData)
|
||||
}
|
||||
}
|
||||
|
||||
func mediaSourceForURLString(_ urlString: String) -> MatrixRustSDK.MediaSource {
|
||||
MatrixRustSDK.mediaSourceFromUrl(url: urlString)
|
||||
}
|
||||
|
||||
func loadMediaContentForSource(_ source: MatrixRustSDK.MediaSource) async throws -> Data {
|
||||
try await Task.dispatch(on: clientQueue) {
|
||||
let bytes = try self.client.getMediaContent(source: source)
|
||||
return Data(bytes: bytes, count: bytes.count)
|
||||
}
|
||||
}
|
||||
|
||||
func loadMediaThumbnailForSource(_ source: MatrixRustSDK.MediaSource, width: UInt, height: UInt) async throws -> Data {
|
||||
try await Task.dispatch(on: clientQueue) {
|
||||
let bytes = try self.client.getMediaThumbnail(source: source, width: UInt64(width), height: UInt64(height))
|
||||
return Data(bytes: bytes, count: bytes.count)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func sessionVerificationControllerProxy() async -> Result<SessionVerificationControllerProxyProtocol, ClientProxyError> {
|
||||
await Task.dispatch(on: clientQueue) {
|
||||
do {
|
||||
@@ -243,6 +228,32 @@ class ClientProxy: ClientProxyProtocol {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// swiftlint:disable:next function_parameter_count
|
||||
func setPusher(pushkey: String,
|
||||
kind: PusherKind?,
|
||||
appId: String,
|
||||
appDisplayName: String,
|
||||
deviceDisplayName: String,
|
||||
profileTag: String?,
|
||||
lang: String,
|
||||
url: String?,
|
||||
format: PushFormat?,
|
||||
defaultPayload: [AnyHashable: Any]?) async throws {
|
||||
// let defaultPayloadString = jsonString(from: defaultPayload)
|
||||
// try await Task.dispatch(on: .global()) {
|
||||
// try self.client.setPusher(pushkey: pushkey,
|
||||
// kind: kind?.rustValue,
|
||||
// appId: appId,
|
||||
// appDisplayName: appDisplayName,
|
||||
// deviceDisplayName: deviceDisplayName,
|
||||
// profileTag: profileTag,
|
||||
// lang: lang,
|
||||
// url: url,
|
||||
// format: format?.rustValue,
|
||||
// defaultPayload: defaultPayloadString)
|
||||
// }
|
||||
}
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@@ -271,4 +282,29 @@ class ClientProxy: ClientProxyProtocol {
|
||||
|
||||
callbacks.send(.receivedSyncUpdate)
|
||||
}
|
||||
|
||||
/// Convenience method to get the json string of an Encodable
|
||||
private func jsonString(from dictionary: [AnyHashable: Any]?) -> String? {
|
||||
guard let dictionary,
|
||||
let data = try? JSONSerialization.data(withJSONObject: dictionary,
|
||||
options: [.fragmentsAllowed]) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return String(data: data, encoding: .utf8)
|
||||
}
|
||||
}
|
||||
|
||||
extension ClientProxy: MediaProxyProtocol {
|
||||
func mediaSourceForURLString(_ urlString: String) -> MediaSourceProxy {
|
||||
mediaProxy.mediaSourceForURLString(urlString)
|
||||
}
|
||||
|
||||
func loadMediaContentForSource(_ source: MediaSourceProxy) async throws -> Data {
|
||||
try await mediaProxy.loadMediaContentForSource(source)
|
||||
}
|
||||
|
||||
func loadMediaThumbnailForSource(_ source: MediaSourceProxy, width: UInt, height: UInt) async throws -> Data {
|
||||
try await mediaProxy.loadMediaThumbnailForSource(source, width: width, height: height)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,32 @@ enum ClientProxyError: Error {
|
||||
case failedLoadingMedia
|
||||
}
|
||||
|
||||
protocol ClientProxyProtocol {
|
||||
enum PusherKind {
|
||||
case http
|
||||
case email
|
||||
|
||||
// var rustValue: MatrixRustSDK.PusherKind {
|
||||
// switch self {
|
||||
// case .http:
|
||||
// return .http
|
||||
// case .email:
|
||||
// return .email
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
enum PushFormat {
|
||||
case eventIdOnly
|
||||
|
||||
// var rustValue: MatrixRustSDK.PushFormat {
|
||||
// switch self {
|
||||
// case .eventIdOnly:
|
||||
// return .eventIdOnly
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
protocol ClientProxyProtocol: MediaProxyProtocol {
|
||||
var callbacks: PassthroughSubject<ClientProxyCallback, Never> { get }
|
||||
|
||||
var userIdentifier: String { get }
|
||||
@@ -62,13 +87,19 @@ protocol ClientProxyProtocol {
|
||||
|
||||
func setAccountData<Content: Encodable>(content: Content, type: String) async -> Result<Void, ClientProxyError>
|
||||
|
||||
func mediaSourceForURLString(_ urlString: String) -> MatrixRustSDK.MediaSource
|
||||
|
||||
func loadMediaContentForSource(_ source: MatrixRustSDK.MediaSource) async throws -> Data
|
||||
|
||||
func loadMediaThumbnailForSource(_ source: MatrixRustSDK.MediaSource, width: UInt, height: UInt) async throws -> Data
|
||||
|
||||
func sessionVerificationControllerProxy() async -> Result<SessionVerificationControllerProxyProtocol, ClientProxyError>
|
||||
|
||||
func logout() async
|
||||
|
||||
// swiftlint:disable:next function_parameter_count
|
||||
func setPusher(pushkey: String,
|
||||
kind: PusherKind?,
|
||||
appId: String,
|
||||
appDisplayName: String,
|
||||
deviceDisplayName: String,
|
||||
profileTag: String?,
|
||||
lang: String,
|
||||
url: String?,
|
||||
format: PushFormat?,
|
||||
defaultPayload: [AnyHashable: Any]?) async throws
|
||||
}
|
||||
|
||||
@@ -53,15 +53,15 @@ struct MockClientProxy: ClientProxyProtocol {
|
||||
.failure(.failedSettingAccountData)
|
||||
}
|
||||
|
||||
func mediaSourceForURLString(_ urlString: String) -> MatrixRustSDK.MediaSource {
|
||||
MatrixRustSDK.mediaSourceFromUrl(url: urlString)
|
||||
func mediaSourceForURLString(_ urlString: String) -> MediaSourceProxy {
|
||||
.init(urlString: urlString)
|
||||
}
|
||||
|
||||
func loadMediaContentForSource(_ source: MatrixRustSDK.MediaSource) async throws -> Data {
|
||||
func loadMediaContentForSource(_ source: MediaSourceProxy) async throws -> Data {
|
||||
throw ClientProxyError.failedLoadingMedia
|
||||
}
|
||||
|
||||
func loadMediaThumbnailForSource(_ source: MatrixRustSDK.MediaSource, width: UInt, height: UInt) async throws -> Data {
|
||||
func loadMediaThumbnailForSource(_ source: MediaSourceProxy, width: UInt, height: UInt) async throws -> Data {
|
||||
throw ClientProxyError.failedLoadingMedia
|
||||
}
|
||||
|
||||
@@ -72,4 +72,18 @@ struct MockClientProxy: ClientProxyProtocol {
|
||||
func logout() async {
|
||||
// no-op
|
||||
}
|
||||
|
||||
// swiftlint:disable:next function_parameter_count
|
||||
func setPusher(pushkey: String,
|
||||
kind: PusherKind?,
|
||||
appId: String,
|
||||
appDisplayName: String,
|
||||
deviceDisplayName: String,
|
||||
profileTag: String?,
|
||||
lang: String,
|
||||
url: String?,
|
||||
format: PushFormat?,
|
||||
defaultPayload: [AnyHashable: Any]?) async throws {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
+21
-10
@@ -17,13 +17,24 @@
|
||||
import Foundation
|
||||
import KeychainAccess
|
||||
|
||||
enum KeychainControllerService: String {
|
||||
case sessions
|
||||
case tests
|
||||
|
||||
var identifier: String {
|
||||
InfoPlistReader.target.baseBundleIdentifier + "." + rawValue
|
||||
}
|
||||
}
|
||||
|
||||
class KeychainController: KeychainControllerProtocol {
|
||||
private let keychain: Keychain
|
||||
|
||||
init(identifier: String) {
|
||||
keychain = Keychain(service: identifier)
|
||||
|
||||
init(service: KeychainControllerService,
|
||||
accessGroup: String) {
|
||||
keychain = Keychain(service: service.identifier,
|
||||
accessGroup: accessGroup)
|
||||
}
|
||||
|
||||
|
||||
func setRestorationToken(_ restorationToken: RestorationToken, forUsername username: String) {
|
||||
do {
|
||||
let tokenData = try JSONEncoder().encode(restorationToken)
|
||||
@@ -32,7 +43,7 @@ class KeychainController: KeychainControllerProtocol {
|
||||
MXLog.error("Failed storing user restore token with error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func restorationTokenForUsername(_ username: String) -> RestorationToken? {
|
||||
do {
|
||||
guard let tokenData = try keychain.getData(username) else {
|
||||
@@ -49,24 +60,24 @@ class KeychainController: KeychainControllerProtocol {
|
||||
homeserverUrl: legacyRestorationToken.homeURL,
|
||||
isSoftLogout: legacyRestorationToken.isSoftLogout ?? false))
|
||||
}
|
||||
|
||||
|
||||
return try JSONDecoder().decode(RestorationToken.self, from: tokenData)
|
||||
} catch {
|
||||
MXLog.error("Failed retrieving user restore token")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func restorationTokens() -> [KeychainCredentials] {
|
||||
keychain.allKeys().compactMap { username in
|
||||
guard let restorationToken = restorationTokenForUsername(username) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
return KeychainCredentials(userID: username, restorationToken: restorationToken)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func removeRestorationTokenForUsername(_ username: String) {
|
||||
do {
|
||||
try keychain.remove(username)
|
||||
@@ -74,7 +85,7 @@ class KeychainController: KeychainControllerProtocol {
|
||||
MXLog.error("Failed removing restore token with error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func removeAllRestorationTokens() {
|
||||
do {
|
||||
try keychain.removeAll()
|
||||
@@ -18,26 +18,26 @@ import Kingfisher
|
||||
import UIKit
|
||||
|
||||
struct MediaProvider: MediaProviderProtocol {
|
||||
private let clientProxy: ClientProxyProtocol
|
||||
private let mediaProxy: MediaProxyProtocol
|
||||
private let imageCache: Kingfisher.ImageCache
|
||||
private let fileCache: FileCache
|
||||
private let backgroundTaskService: BackgroundTaskServiceProtocol
|
||||
private let backgroundTaskService: BackgroundTaskServiceProtocol?
|
||||
|
||||
init(clientProxy: ClientProxyProtocol,
|
||||
init(mediaProxy: MediaProxyProtocol,
|
||||
imageCache: Kingfisher.ImageCache,
|
||||
fileCache: FileCache,
|
||||
backgroundTaskService: BackgroundTaskServiceProtocol) {
|
||||
self.clientProxy = clientProxy
|
||||
backgroundTaskService: BackgroundTaskServiceProtocol?) {
|
||||
self.mediaProxy = mediaProxy
|
||||
self.imageCache = imageCache
|
||||
self.fileCache = fileCache
|
||||
self.backgroundTaskService = backgroundTaskService
|
||||
}
|
||||
|
||||
func imageFromSource(_ source: MediaSource?, avatarSize: AvatarSize?) -> UIImage? {
|
||||
func imageFromSource(_ source: MediaSourceProxy?, avatarSize: AvatarSize?) -> UIImage? {
|
||||
guard let source else {
|
||||
return nil
|
||||
}
|
||||
let cacheKey = cacheKeyForURLString(source.underlyingSource.url(), avatarSize: avatarSize)
|
||||
let cacheKey = cacheKeyForURLString(source.url, avatarSize: avatarSize)
|
||||
return imageCache.retrieveImageInMemoryCache(forKey: cacheKey, options: nil)
|
||||
}
|
||||
|
||||
@@ -46,19 +46,19 @@ struct MediaProvider: MediaProviderProtocol {
|
||||
return nil
|
||||
}
|
||||
|
||||
return imageFromSource(MediaSource(source: clientProxy.mediaSourceForURLString(urlString)), avatarSize: avatarSize)
|
||||
return imageFromSource(.init(urlString: urlString), avatarSize: avatarSize)
|
||||
}
|
||||
|
||||
func loadImageFromURLString(_ urlString: String, avatarSize: AvatarSize?) async -> Result<UIImage, MediaProviderError> {
|
||||
await loadImageFromSource(MediaSource(source: clientProxy.mediaSourceForURLString(urlString)), avatarSize: avatarSize)
|
||||
await loadImageFromSource(.init(urlString: urlString), avatarSize: avatarSize)
|
||||
}
|
||||
|
||||
func loadImageFromSource(_ source: MediaSource, avatarSize: AvatarSize?) async -> Result<UIImage, MediaProviderError> {
|
||||
func loadImageFromSource(_ source: MediaSourceProxy, avatarSize: AvatarSize?) async -> Result<UIImage, MediaProviderError> {
|
||||
if let image = imageFromSource(source, avatarSize: avatarSize) {
|
||||
return .success(image)
|
||||
}
|
||||
|
||||
let loadImageBgTask = await backgroundTaskService.startBackgroundTask(withName: "LoadImage: \(source.url.hashValue)")
|
||||
let loadImageBgTask = await backgroundTaskService?.startBackgroundTask(withName: "LoadImage: \(source.url.hashValue)")
|
||||
defer {
|
||||
loadImageBgTask?.stop()
|
||||
}
|
||||
@@ -73,9 +73,9 @@ struct MediaProvider: MediaProviderProtocol {
|
||||
do {
|
||||
let imageData: Data
|
||||
if let avatarSize {
|
||||
imageData = try await clientProxy.loadMediaThumbnailForSource(source.underlyingSource, width: UInt(avatarSize.scaledValue), height: UInt(avatarSize.scaledValue))
|
||||
imageData = try await mediaProxy.loadMediaThumbnailForSource(source, width: UInt(avatarSize.scaledValue), height: UInt(avatarSize.scaledValue))
|
||||
} else {
|
||||
imageData = try await clientProxy.loadMediaContentForSource(source.underlyingSource)
|
||||
imageData = try await mediaProxy.loadMediaContentForSource(source)
|
||||
}
|
||||
|
||||
guard let image = UIImage(data: imageData) else {
|
||||
@@ -92,20 +92,20 @@ struct MediaProvider: MediaProviderProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func fileFromSource(_ source: MediaSource?, fileExtension: String) -> URL? {
|
||||
func fileFromSource(_ source: MediaSourceProxy?, fileExtension: String) -> URL? {
|
||||
guard let source else {
|
||||
return nil
|
||||
}
|
||||
let cacheKey = fileCacheKeyForURLString(source.underlyingSource.url())
|
||||
let cacheKey = fileCacheKeyForURLString(source.url)
|
||||
return fileCache.file(forKey: cacheKey, fileExtension: fileExtension)
|
||||
}
|
||||
|
||||
@discardableResult func loadFileFromSource(_ source: MediaSource, fileExtension: String) async -> Result<URL, MediaProviderError> {
|
||||
@discardableResult func loadFileFromSource(_ source: MediaSourceProxy, fileExtension: String) async -> Result<URL, MediaProviderError> {
|
||||
if let url = fileFromSource(source, fileExtension: fileExtension) {
|
||||
return .success(url)
|
||||
}
|
||||
|
||||
let loadFileBgTask = await backgroundTaskService.startBackgroundTask(withName: "LoadFile: \(source.url.hashValue)")
|
||||
let loadFileBgTask = await backgroundTaskService?.startBackgroundTask(withName: "LoadFile: \(source.url.hashValue)")
|
||||
defer {
|
||||
loadFileBgTask?.stop()
|
||||
}
|
||||
@@ -113,7 +113,7 @@ struct MediaProvider: MediaProviderProtocol {
|
||||
let cacheKey = fileCacheKeyForURLString(source.url)
|
||||
|
||||
do {
|
||||
let data = try await clientProxy.loadMediaContentForSource(source.underlyingSource)
|
||||
let data = try await mediaProxy.loadMediaContentForSource(source)
|
||||
|
||||
let url = try fileCache.store(data, with: fileExtension, forKey: cacheKey)
|
||||
return .success(url)
|
||||
@@ -128,12 +128,12 @@ struct MediaProvider: MediaProviderProtocol {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fileFromSource(MediaSource(source: clientProxy.mediaSourceForURLString(urlString)),
|
||||
return fileFromSource(MediaSourceProxy(urlString: urlString),
|
||||
fileExtension: fileExtension)
|
||||
}
|
||||
|
||||
func loadFileFromURLString(_ urlString: String, fileExtension: String) async -> Result<URL, MediaProviderError> {
|
||||
await loadFileFromSource(MediaSource(source: clientProxy.mediaSourceForURLString(urlString)),
|
||||
await loadFileFromSource(MediaSourceProxy(urlString: urlString),
|
||||
fileExtension: fileExtension)
|
||||
}
|
||||
|
||||
|
||||
@@ -24,17 +24,17 @@ enum MediaProviderError: Error {
|
||||
}
|
||||
|
||||
protocol MediaProviderProtocol {
|
||||
func imageFromSource(_ source: MediaSource?, avatarSize: AvatarSize?) -> UIImage?
|
||||
func imageFromSource(_ source: MediaSourceProxy?, avatarSize: AvatarSize?) -> UIImage?
|
||||
|
||||
@discardableResult func loadImageFromSource(_ source: MediaSource, avatarSize: AvatarSize?) async -> Result<UIImage, MediaProviderError>
|
||||
@discardableResult func loadImageFromSource(_ source: MediaSourceProxy, avatarSize: AvatarSize?) async -> Result<UIImage, MediaProviderError>
|
||||
|
||||
func imageFromURLString(_ urlString: String?, avatarSize: AvatarSize?) -> UIImage?
|
||||
|
||||
@discardableResult func loadImageFromURLString(_ urlString: String, avatarSize: AvatarSize?) async -> Result<UIImage, MediaProviderError>
|
||||
|
||||
func fileFromSource(_ source: MediaSource?, fileExtension: String) -> URL?
|
||||
func fileFromSource(_ source: MediaSourceProxy?, fileExtension: String) -> URL?
|
||||
|
||||
@discardableResult func loadFileFromSource(_ source: MediaSource, fileExtension: String) async -> Result<URL, MediaProviderError>
|
||||
@discardableResult func loadFileFromSource(_ source: MediaSourceProxy, fileExtension: String) async -> Result<URL, MediaProviderError>
|
||||
|
||||
func fileFromURLString(_ urlString: String?, fileExtension: String) -> URL?
|
||||
|
||||
@@ -42,11 +42,11 @@ protocol MediaProviderProtocol {
|
||||
}
|
||||
|
||||
extension MediaProviderProtocol {
|
||||
func imageFromSource(_ source: MediaSource?) -> UIImage? {
|
||||
func imageFromSource(_ source: MediaSourceProxy?) -> UIImage? {
|
||||
imageFromSource(source, avatarSize: nil)
|
||||
}
|
||||
|
||||
@discardableResult func loadImageFromSource(_ source: MediaSource) async -> Result<UIImage, MediaProviderError> {
|
||||
@discardableResult func loadImageFromSource(_ source: MediaSourceProxy) async -> Result<UIImage, MediaProviderError> {
|
||||
await loadImageFromSource(source, avatarSize: nil)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
import UIKit
|
||||
|
||||
class MediaProxy: MediaProxyProtocol {
|
||||
private let client: ClientProtocol
|
||||
private let clientQueue: DispatchQueue
|
||||
|
||||
init(client: ClientProtocol,
|
||||
clientQueue: DispatchQueue = .global()) {
|
||||
self.client = client
|
||||
self.clientQueue = clientQueue
|
||||
}
|
||||
|
||||
func mediaSourceForURLString(_ urlString: String) -> MediaSourceProxy {
|
||||
.init(urlString: urlString)
|
||||
}
|
||||
|
||||
func loadMediaContentForSource(_ source: MediaSourceProxy) async throws -> Data {
|
||||
try await Task.dispatch(on: clientQueue) {
|
||||
let bytes = try self.client.getMediaContent(source: source.underlyingSource)
|
||||
return Data(bytes: bytes, count: bytes.count)
|
||||
}
|
||||
}
|
||||
|
||||
func loadMediaThumbnailForSource(_ source: MediaSourceProxy, width: UInt, height: UInt) async throws -> Data {
|
||||
try await Task.dispatch(on: clientQueue) {
|
||||
let bytes = try self.client.getMediaThumbnail(source: source.underlyingSource, width: UInt64(width), height: UInt64(height))
|
||||
return Data(bytes: bytes, count: bytes.count)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol MediaProxyProtocol {
|
||||
func mediaSourceForURLString(_ urlString: String) -> MediaSourceProxy
|
||||
|
||||
func loadMediaContentForSource(_ source: MediaSourceProxy) async throws -> Data
|
||||
|
||||
func loadMediaThumbnailForSource(_ source: MediaSourceProxy, width: UInt, height: UInt) async throws -> Data
|
||||
}
|
||||
+12
-12
@@ -17,24 +17,24 @@
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
struct MediaSource: Equatable {
|
||||
let underlyingSource: MatrixRustSDK.MediaSource
|
||||
struct MediaSourceProxy: Equatable {
|
||||
let underlyingSource: MediaSource
|
||||
|
||||
init(source: MediaSource) {
|
||||
underlyingSource = source
|
||||
}
|
||||
|
||||
init(urlString: String) {
|
||||
underlyingSource = mediaSourceFromUrl(url: urlString)
|
||||
}
|
||||
|
||||
var url: String {
|
||||
underlyingSource.url()
|
||||
}
|
||||
|
||||
init(source: MatrixRustSDK.MediaSource) {
|
||||
underlyingSource = source
|
||||
}
|
||||
|
||||
init(urlString: String) {
|
||||
underlyingSource = MatrixRustSDK.mediaSourceFromUrl(url: urlString)
|
||||
}
|
||||
|
||||
// MARK: - Equatable
|
||||
|
||||
static func == (lhs: MediaSource, rhs: MediaSource) -> Bool {
|
||||
lhs.underlyingSource.url() == rhs.underlyingSource.url()
|
||||
static func == (lhs: MediaSourceProxy, rhs: MediaSourceProxy) -> Bool {
|
||||
lhs.url == rhs.url
|
||||
}
|
||||
}
|
||||
@@ -18,11 +18,11 @@ import Foundation
|
||||
import UIKit
|
||||
|
||||
struct MockMediaProvider: MediaProviderProtocol {
|
||||
func imageFromSource(_ source: MediaSource?, avatarSize: AvatarSize?) -> UIImage? {
|
||||
func imageFromSource(_ source: MediaSourceProxy?, avatarSize: AvatarSize?) -> UIImage? {
|
||||
nil
|
||||
}
|
||||
|
||||
func loadImageFromSource(_ source: MediaSource, avatarSize: AvatarSize?) async -> Result<UIImage, MediaProviderError> {
|
||||
func loadImageFromSource(_ source: MediaSourceProxy, avatarSize: AvatarSize?) async -> Result<UIImage, MediaProviderError> {
|
||||
.failure(.failedRetrievingImage)
|
||||
}
|
||||
|
||||
@@ -50,11 +50,11 @@ struct MockMediaProvider: MediaProviderProtocol {
|
||||
return .success(image)
|
||||
}
|
||||
|
||||
func fileFromSource(_ source: MediaSource?, fileExtension: String) -> URL? {
|
||||
func fileFromSource(_ source: MediaSourceProxy?, fileExtension: String) -> URL? {
|
||||
nil
|
||||
}
|
||||
|
||||
@discardableResult func loadFileFromSource(_ source: MediaSource, fileExtension: String) async -> Result<URL, MediaProviderError> {
|
||||
@discardableResult func loadFileFromSource(_ source: MediaSourceProxy, fileExtension: String) async -> Result<URL, MediaProviderError> {
|
||||
.failure(.failedRetrievingFile)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class MockNotificationManager: NotificationManagerProtocol {
|
||||
// MARK: NotificationManagerProtocol
|
||||
|
||||
var isAvailable: Bool {
|
||||
false
|
||||
}
|
||||
|
||||
weak var delegate: NotificationManagerDelegate?
|
||||
|
||||
func start() {
|
||||
delegate?.authorizationStatusUpdated(self, granted: false)
|
||||
}
|
||||
|
||||
func register(with deviceToken: Data) { }
|
||||
|
||||
func registrationFailed(with error: Error) { }
|
||||
|
||||
func showLocalNotification(with title: String, subtitle: String?) { }
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import UserNotifications
|
||||
|
||||
class NotificationManager: NSObject, NotificationManagerProtocol {
|
||||
private let notificationCenter = UNUserNotificationCenter.current()
|
||||
private let clientProxy: ClientProxyProtocol
|
||||
|
||||
init(clientProxy: ClientProxyProtocol) {
|
||||
self.clientProxy = clientProxy
|
||||
super.init()
|
||||
}
|
||||
|
||||
// MARK: NotificationManagerProtocol
|
||||
|
||||
weak var delegate: NotificationManagerDelegate?
|
||||
|
||||
var isAvailable: Bool {
|
||||
true
|
||||
}
|
||||
|
||||
func start() {
|
||||
let replyAction = UNTextInputNotificationAction(identifier: NotificationConstants.Action.inlineReply,
|
||||
title: ElementL10n.actionQuickReply,
|
||||
options: [])
|
||||
let replyCategory = UNNotificationCategory(identifier: NotificationConstants.Category.reply,
|
||||
actions: [replyAction],
|
||||
intentIdentifiers: [],
|
||||
options: [])
|
||||
notificationCenter.setNotificationCategories([replyCategory])
|
||||
notificationCenter.delegate = self
|
||||
Task {
|
||||
do {
|
||||
let granted = try await notificationCenter.requestAuthorization(options: [.alert, .sound, .badge])
|
||||
MXLog.debug("[NotificationManager] permission granted: \(granted)")
|
||||
|
||||
await MainActor.run {
|
||||
delegate?.authorizationStatusUpdated(self, granted: granted)
|
||||
}
|
||||
} catch {
|
||||
MXLog.debug("[NotificationManager] request authorization failed: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func register(with deviceToken: Data) {
|
||||
setPusher(with: deviceToken, clientProxy: clientProxy)
|
||||
}
|
||||
|
||||
func registrationFailed(with error: Error) { }
|
||||
|
||||
func showLocalNotification(with title: String, subtitle: String?) {
|
||||
let content = UNMutableNotificationContent()
|
||||
content.title = title
|
||||
if let subtitle {
|
||||
content.subtitle = subtitle
|
||||
}
|
||||
let request = UNNotificationRequest(identifier: ProcessInfo.processInfo.globallyUniqueString,
|
||||
content: content,
|
||||
trigger: nil)
|
||||
Task {
|
||||
do {
|
||||
try await notificationCenter.add(request)
|
||||
MXLog.debug("[NotificationManager] show local notification succeeded")
|
||||
} catch {
|
||||
MXLog.debug("[NotificationManager] show local notification failed: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func setPusher(with deviceToken: Data, clientProxy: ClientProxyProtocol) {
|
||||
Task {
|
||||
do {
|
||||
try await clientProxy.setPusher(pushkey: deviceToken.base64EncodedString(),
|
||||
kind: .http,
|
||||
appId: BuildSettings.pusherAppId,
|
||||
appDisplayName: "\(InfoPlistReader.target.bundleDisplayName) (iOS)",
|
||||
deviceDisplayName: UIDevice.current.name,
|
||||
profileTag: pusherProfileTag(),
|
||||
lang: Bundle.preferredLanguages.first ?? "en",
|
||||
url: BuildSettings.pushGatewayBaseURL.absoluteString,
|
||||
format: .eventIdOnly,
|
||||
defaultPayload: [
|
||||
"aps": [
|
||||
"mutable-content": 1,
|
||||
"alert": [
|
||||
"loc-key": "Notification",
|
||||
"loc-args": []
|
||||
]
|
||||
]
|
||||
])
|
||||
MXLog.debug("[NotificationManager] set pusher succeeded")
|
||||
} catch {
|
||||
MXLog.debug("[NotificationManager] set pusher failed: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func pusherProfileTag() -> String {
|
||||
if let currentTag = ElementSettings.shared.pusherProfileTag {
|
||||
return currentTag
|
||||
}
|
||||
let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||
let newTag = (0..<16).map { _ in
|
||||
let offset = Int.random(in: 0..<chars.count)
|
||||
return String(chars[chars.index(chars.startIndex, offsetBy: offset)])
|
||||
}.joined()
|
||||
|
||||
ElementSettings.shared.pusherProfileTag = newTag
|
||||
return newTag
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UNUserNotificationCenterDelegate
|
||||
|
||||
extension NotificationManager: UNUserNotificationCenterDelegate {
|
||||
func userNotificationCenter(_ center: UNUserNotificationCenter,
|
||||
willPresent notification: UNNotification) async -> UNNotificationPresentationOptions {
|
||||
guard ElementSettings.shared.enableInAppNotifications else {
|
||||
return []
|
||||
}
|
||||
guard let delegate else {
|
||||
return [.badge, .sound, .list, .banner]
|
||||
}
|
||||
|
||||
guard delegate.shouldDisplayInAppNotification(self, content: notification.request.content) else {
|
||||
return []
|
||||
}
|
||||
|
||||
return [.badge, .sound, .list, .banner]
|
||||
}
|
||||
|
||||
func userNotificationCenter(_ center: UNUserNotificationCenter,
|
||||
didReceive response: UNNotificationResponse) async {
|
||||
switch response.actionIdentifier {
|
||||
case NotificationConstants.Action.inlineReply:
|
||||
guard let response = response as? UNTextInputNotificationResponse else {
|
||||
return
|
||||
}
|
||||
await delegate?.handleInlineReply(self,
|
||||
content: response.notification.request.content,
|
||||
replyText: response.userText)
|
||||
case UNNotificationDefaultActionIdentifier:
|
||||
await delegate?.notificationTapped(self,
|
||||
content: response.notification.request.content)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UserNotifications
|
||||
|
||||
protocol NotificationManagerDelegate: AnyObject {
|
||||
func authorizationStatusUpdated(_ service: NotificationManagerProtocol,
|
||||
granted: Bool)
|
||||
func shouldDisplayInAppNotification(_ service: NotificationManagerProtocol,
|
||||
content: UNNotificationContent) -> Bool
|
||||
func notificationTapped(_ service: NotificationManagerProtocol,
|
||||
content: UNNotificationContent) async
|
||||
func handleInlineReply(_ service: NotificationManagerProtocol,
|
||||
content: UNNotificationContent,
|
||||
replyText: String) async
|
||||
}
|
||||
|
||||
// MARK: - NotificationManagerProtocol
|
||||
|
||||
protocol NotificationManagerProtocol {
|
||||
var isAvailable: Bool { get }
|
||||
var delegate: NotificationManagerDelegate? { get set }
|
||||
|
||||
func start()
|
||||
func register(with deviceToken: Data)
|
||||
func registrationFailed(with error: Error)
|
||||
func showLocalNotification(with title: String, subtitle: String?)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum NotificationConstants {
|
||||
enum UserInfoKey {
|
||||
static let roomIdentifier = "room_id"
|
||||
static let eventIdentifier = "event_id"
|
||||
static let unreadCount = "unread_count"
|
||||
}
|
||||
|
||||
enum Category {
|
||||
static let discard = "discard"
|
||||
static let reply = "reply"
|
||||
}
|
||||
|
||||
enum Action {
|
||||
static let inlineReply = "inline-reply"
|
||||
}
|
||||
}
|
||||
+3
-4
@@ -16,9 +16,8 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
extension FileManager {
|
||||
/// The URL of the primary app group container.
|
||||
@objc var appGroupContainerURL: URL? {
|
||||
containerURL(forSecurityApplicationGroupIdentifier: ElementInfoPlist.appGroupIdentifier)
|
||||
class MockNotificationServiceProxy: NotificationServiceProxyProtocol {
|
||||
func notificationItem(roomId: String, eventId: String) async throws -> NotificationItemProxy? {
|
||||
nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
struct NotificationItemProxy {
|
||||
// let notificationItem: NotificationItem
|
||||
//
|
||||
// init(notificationItem: NotificationItem) {
|
||||
// self.notificationItem = notificationItem
|
||||
// }
|
||||
//
|
||||
// var timelineItemProxy: TimelineItemProxy {
|
||||
// .init(item: notificationItem.item)
|
||||
// }
|
||||
//
|
||||
// var title: String {
|
||||
// notificationItem.title
|
||||
// }
|
||||
//
|
||||
// var subtitle: String? {
|
||||
// notificationItem.subtitle
|
||||
// }
|
||||
//
|
||||
// var isNoisy: Bool {
|
||||
// notificationItem.isNoisy
|
||||
// }
|
||||
//
|
||||
// var avatarUrl: String? {
|
||||
// notificationItem.avatarUrl
|
||||
// }
|
||||
//
|
||||
// var avatarMediaSource: MediaSourceProxy? {
|
||||
// guard let avatarUrl else {
|
||||
// return nil
|
||||
// }
|
||||
// return .init(urlString: avatarUrl)
|
||||
// }
|
||||
|
||||
var title: String {
|
||||
"Title"
|
||||
}
|
||||
|
||||
var subtitle: String? {
|
||||
nil
|
||||
}
|
||||
|
||||
var isNoisy: Bool {
|
||||
true
|
||||
}
|
||||
|
||||
var avatarUrl: String? {
|
||||
nil
|
||||
}
|
||||
|
||||
var avatarMediaSource: MediaSourceProxy? {
|
||||
nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
class NotificationServiceProxy: NotificationServiceProxyProtocol {
|
||||
// private let service: NotificationServiceProtocol
|
||||
|
||||
init(basePath: String,
|
||||
userId: String) {
|
||||
// service = NotificationService(basePath: basePath, userId: userId)
|
||||
}
|
||||
|
||||
func notificationItem(roomId: String, eventId: String) async throws -> NotificationItemProxy? {
|
||||
nil
|
||||
// try await Task.dispatch(on: .global()) {
|
||||
// guard let item = try self.service.getNotificationItem(roomId: roomId, eventId: eventId) else {
|
||||
// return nil
|
||||
// }
|
||||
// return .init(notificationItem: item)
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol NotificationServiceProxyProtocol {
|
||||
func notificationItem(roomId: String, eventId: String) async throws -> NotificationItemProxy?
|
||||
}
|
||||
@@ -113,8 +113,8 @@ extension MatrixRustSDK.ImageMessageContent: MessageContentProtocol { }
|
||||
|
||||
/// A timeline item that represents an `m.room.message` event with a `msgtype` of `m.image`.
|
||||
extension MessageTimelineItem where Content == MatrixRustSDK.ImageMessageContent {
|
||||
var source: MediaSource {
|
||||
MediaSource(source: content.source)
|
||||
var source: MediaSourceProxy {
|
||||
.init(source: content.source)
|
||||
}
|
||||
|
||||
var width: CGFloat? {
|
||||
@@ -134,15 +134,15 @@ extension MatrixRustSDK.VideoMessageContent: MessageContentProtocol { }
|
||||
|
||||
/// A timeline item that represents an `m.room.message` event with a `msgtype` of `m.video`.
|
||||
extension MessageTimelineItem where Content == MatrixRustSDK.VideoMessageContent {
|
||||
var source: MediaSource {
|
||||
MediaSource(source: content.source)
|
||||
var source: MediaSourceProxy {
|
||||
.init(source: content.source)
|
||||
}
|
||||
|
||||
var thumbnailSource: MediaSource? {
|
||||
var thumbnailSource: MediaSourceProxy? {
|
||||
guard let src = content.info?.thumbnailSource else {
|
||||
return nil
|
||||
}
|
||||
return MediaSource(source: src)
|
||||
return .init(source: src)
|
||||
}
|
||||
|
||||
var duration: UInt64 {
|
||||
@@ -166,14 +166,14 @@ extension MatrixRustSDK.FileMessageContent: MessageContentProtocol { }
|
||||
|
||||
/// A timeline item that represents an `m.room.message` event with a `msgtype` of `m.file`.
|
||||
extension MessageTimelineItem where Content == MatrixRustSDK.FileMessageContent {
|
||||
var source: MediaSource {
|
||||
MediaSource(source: content.source)
|
||||
var source: MediaSourceProxy {
|
||||
.init(source: content.source)
|
||||
}
|
||||
|
||||
var thumbnailSource: MediaSource? {
|
||||
var thumbnailSource: MediaSourceProxy? {
|
||||
guard let src = content.info?.thumbnailSource else {
|
||||
return nil
|
||||
}
|
||||
return MediaSource(source: src)
|
||||
return .init(source: src)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,8 +29,8 @@ struct FileRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equat
|
||||
var senderDisplayName: String?
|
||||
var senderAvatar: UIImage?
|
||||
|
||||
let source: MediaSource?
|
||||
let thumbnailSource: MediaSource?
|
||||
let source: MediaSourceProxy?
|
||||
let thumbnailSource: MediaSourceProxy?
|
||||
var cachedFileURL: URL?
|
||||
|
||||
var properties = RoomTimelineItemProperties()
|
||||
|
||||
@@ -29,7 +29,7 @@ struct ImageRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equa
|
||||
var senderDisplayName: String?
|
||||
var senderAvatar: UIImage?
|
||||
|
||||
let source: MediaSource?
|
||||
let source: MediaSourceProxy?
|
||||
var image: UIImage?
|
||||
|
||||
var width: CGFloat?
|
||||
|
||||
@@ -30,8 +30,8 @@ struct VideoRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equa
|
||||
var senderAvatar: UIImage?
|
||||
|
||||
let duration: UInt64
|
||||
let source: MediaSource?
|
||||
let thumbnailSource: MediaSource?
|
||||
let source: MediaSourceProxy?
|
||||
let thumbnailSource: MediaSourceProxy?
|
||||
var image: UIImage?
|
||||
var cachedVideoURL: URL?
|
||||
|
||||
|
||||
@@ -42,7 +42,17 @@ class UserSessionFlowCoordinator: CoordinatorProtocol {
|
||||
func start() {
|
||||
stateMachine.processEvent(.start)
|
||||
}
|
||||
|
||||
|
||||
func stop() { }
|
||||
|
||||
func isDisplayingRoomScreen(withRoomId roomId: String) -> Bool {
|
||||
stateMachine.isDisplayingRoomScreen(withRoomId: roomId)
|
||||
}
|
||||
|
||||
func tryDisplayingRoomScreen(roomId: String) {
|
||||
stateMachine.processEvent(.showRoomScreen(roomId: roomId))
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
// swiftlint:disable:next cyclomatic_complexity
|
||||
|
||||
@@ -150,4 +150,14 @@ class UserSessionFlowCoordinatorStateMachine {
|
||||
func addErrorHandler(_ handler: @escaping StateMachine<State, Event>.Handler) {
|
||||
stateMachine.addErrorHandler(handler: handler)
|
||||
}
|
||||
|
||||
/// Flag indicating the machine is displaying room screen with given room identifier
|
||||
func isDisplayingRoomScreen(withRoomId roomId: String) -> Bool {
|
||||
switch stateMachine.state {
|
||||
case .roomScreen(let displayedRoomId):
|
||||
return roomId == displayedRoomId
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,26 +26,14 @@ class UserSessionStore: UserSessionStoreProtocol {
|
||||
var hasSessions: Bool { !keychainController.restorationTokens().isEmpty }
|
||||
|
||||
/// The base directory where all session data is stored.
|
||||
private(set) lazy var baseDirectory: URL = {
|
||||
guard let appGroupContainerURL = FileManager.default.appGroupContainerURL else {
|
||||
fatalError("Should always be able to retrieve the container directory")
|
||||
}
|
||||
|
||||
let url = appGroupContainerURL
|
||||
.appendingPathComponent("Library", isDirectory: true)
|
||||
.appendingPathComponent("Caches", isDirectory: true)
|
||||
.appendingPathComponent("Sessions", isDirectory: true)
|
||||
|
||||
try? FileManager.default.createDirectory(at: url, withIntermediateDirectories: false, attributes: nil)
|
||||
|
||||
MXLog.debug("Setup base directory at: \(url)")
|
||||
|
||||
return url
|
||||
}()
|
||||
let baseDirectory: URL
|
||||
|
||||
init(bundleIdentifier: String, backgroundTaskService: BackgroundTaskServiceProtocol) {
|
||||
keychainController = KeychainController(identifier: bundleIdentifier)
|
||||
init(backgroundTaskService: BackgroundTaskServiceProtocol) {
|
||||
keychainController = KeychainController(service: .sessions,
|
||||
accessGroup: InfoPlistReader.target.appGroupIdentifier)
|
||||
self.backgroundTaskService = backgroundTaskService
|
||||
baseDirectory = .sessionsBaseDirectory
|
||||
MXLog.debug("Setup base directory at: \(baseDirectory)")
|
||||
}
|
||||
|
||||
func restoreUserSession() async -> Result<UserSession, UserSessionStoreError> {
|
||||
@@ -58,7 +46,7 @@ class UserSessionStore: UserSessionStoreProtocol {
|
||||
switch await restorePreviousLogin(credentials) {
|
||||
case .success(let clientProxy):
|
||||
return .success(UserSession(clientProxy: clientProxy,
|
||||
mediaProvider: MediaProvider(clientProxy: clientProxy,
|
||||
mediaProvider: MediaProvider(mediaProxy: clientProxy,
|
||||
imageCache: .onlyInMemory,
|
||||
fileCache: .default,
|
||||
backgroundTaskService: backgroundTaskService)))
|
||||
@@ -77,7 +65,7 @@ class UserSessionStore: UserSessionStoreProtocol {
|
||||
switch await setupProxyForClient(client) {
|
||||
case .success(let clientProxy):
|
||||
return .success(UserSession(clientProxy: clientProxy,
|
||||
mediaProvider: MediaProvider(clientProxy: clientProxy,
|
||||
mediaProvider: MediaProvider(mediaProxy: clientProxy,
|
||||
imageCache: .onlyInMemory,
|
||||
fileCache: .default,
|
||||
backgroundTaskService: backgroundTaskService)))
|
||||
|
||||
Reference in New Issue
Block a user