diff --git a/NextcloudTalk/BaseChatViewController.swift b/NextcloudTalk/BaseChatViewController.swift index 3a853a78b..c10a87ce8 100644 --- a/NextcloudTalk/BaseChatViewController.swift +++ b/NextcloudTalk/BaseChatViewController.swift @@ -969,7 +969,8 @@ import QuickLook } func didPressTranslate(for message: NCChatMessage) { - let translateMessageVC = MessageTranslationViewController(message: message.parsedMessage().string, availableTranslations: NCSettingsController.sharedInstance().availableTranslations()) + let activeAccount = NCDatabaseManager.sharedInstance().activeAccount() + let translateMessageVC = MessageTranslationViewController(message: message.parsedMessage().string, availableTranslations: NCDatabaseManager.sharedInstance().availableTranslations(forAccountId: activeAccount.accountId)) self.presentWithNavigation(translateMessageVC, animated: true) } diff --git a/NextcloudTalk/ChatViewController.swift b/NextcloudTalk/ChatViewController.swift index 32d0a1a6c..539f43b45 100644 --- a/NextcloudTalk/ChatViewController.swift +++ b/NextcloudTalk/ChatViewController.swift @@ -1467,6 +1467,7 @@ import UIKit } var actions: [UIMenuElement] = [] + let activeAccount = NCDatabaseManager.sharedInstance().activeAccount() let hasChatPermissions = !NCDatabaseManager.sharedInstance().serverHasTalkCapability(kCapabilityChatPermission) || (self.room.permissions & NCPermission.chat.rawValue) != 0 // Reply option @@ -1477,7 +1478,6 @@ import UIKit } // Reply-privately option (only to other users and not in one-to-one) - let activeAccount = NCDatabaseManager.sharedInstance().activeAccount() if self.isMessageReplyable(message: message), self.room.type != kNCRoomTypeOneToOne, message.actorType == "users", message.actorId != activeAccount.userId { actions.append(UIAction(title: NSLocalizedString("Reply privately", comment: ""), image: .init(systemName: "person")) { _ in self.didPressReplyPrivately(for: message) @@ -1553,7 +1553,7 @@ import UIKit }) // Translate - if !self.offlineMode, !NCSettingsController.sharedInstance().availableTranslations().isEmpty { + if !self.offlineMode, NCDatabaseManager.sharedInstance().hasAvailableTranslations(forAccountId: activeAccount.accountId) { actions.append(UIAction(title: NSLocalizedString("Translate", comment: ""), image: .init(systemName: "character.book.closed")) { _ in self.didPressTranslate(for: message) }) diff --git a/NextcloudTalk/MessageTranslationViewController.swift b/NextcloudTalk/MessageTranslationViewController.swift index 833740aa2..05bcac504 100644 --- a/NextcloudTalk/MessageTranslationViewController.swift +++ b/NextcloudTalk/MessageTranslationViewController.swift @@ -103,13 +103,31 @@ import UIKit override func viewWillAppear(_ animated: Bool) { if !didTriggerInitialTranslation { + self.setTranslatingUI() + self.didTriggerInitialTranslation = true self.configureFromButton(title: NSLocalizedString("Detecting language", comment: ""), enabled: false) self.configureToButton(title: initialToLanguage(), enabled: false, fromLanguageCode: "") - self.adjustOriginalTextViewSizeToViewSize(size: self.view.bounds.size) - self.didTriggerInitialTranslation = true - self.translateOriginalText(from: "", to: userLanguageCode ?? "") + if NCDatabaseManager.sharedInstance().hasTranslationProviders(forAccountId: activeAccount?.accountId ?? "") { + NCAPIController.sharedInstance().getAvailableTranslations(for: activeAccount) { languages, languageDetection, error, _ in + if let translations = languages as? [NCTranslation], error == nil { + self.availableTranslations = translations + if languageDetection { + self.translateOriginalText(from: "", to: self.userLanguageCode ?? "") + } else { + self.configureFromButton(title: nil, enabled: true) + self.configureToButton(title: nil, enabled: false, fromLanguageCode: "") + self.removeTranslatingUI() + } + } else { + self.showTranslationError(message: NSLocalizedString("Could not get available languages", comment: "")) + self.removeTranslatingUI() + } + } + } else { + self.translateOriginalText(from: "", to: userLanguageCode ?? "") + } } } diff --git a/NextcloudTalk/NCAPIController.h b/NextcloudTalk/NCAPIController.h index c123c749c..8ac4e871b 100644 --- a/NextcloudTalk/NCAPIController.h +++ b/NextcloudTalk/NCAPIController.h @@ -73,6 +73,7 @@ typedef void (^ClearChatHistoryCompletionBlock)(NSDictionary *messageDict, NSErr typedef void (^GetSharedItemsOverviewCompletionBlock)(NSDictionary *sharedItemsOverview, NSError *error, NSInteger statusCode); typedef void (^GetSharedItemsCompletionBlock)(NSArray *sharedItems, NSInteger lastKnownMessage, NSError *error, NSInteger statusCode); +typedef void (^GetTranslationsCompletionBlock)(NSArray *languages, BOOL languageDetection, NSError *error, NSInteger statusCode); typedef void (^MessageTranslationCompletionBlock)(NSDictionary *translationDict, NSError *error, NSInteger statusCode); typedef void (^MessageReactionCompletionBlock)(NSDictionary *reactionsDict, NSError *error, NSInteger statusCode); @@ -225,6 +226,7 @@ extern NSInteger const kReceivedChatMessagesLimit; - (NSURLSessionDataTask *)getSharedItemsOfType:(NSString *)objectType fromLastMessageId:(NSInteger)messageId withLimit:(NSInteger)limit inRoom:(NSString *)token forAccount:(TalkAccount *)account withCompletionBlock:(GetSharedItemsCompletionBlock)block; // Translations +- (NSURLSessionDataTask *)getAvailableTranslationsForAccount:(TalkAccount *)account withCompletionBlock:(GetTranslationsCompletionBlock)block; - (NSURLSessionDataTask *)translateMessage:(NSString *)message from:(NSString *)from to:(NSString *)to forAccount:(TalkAccount *)account withCompletionBlock:(MessageTranslationCompletionBlock)block; // Reactions Controller diff --git a/NextcloudTalk/NCAPIController.m b/NextcloudTalk/NCAPIController.m index b292d72a9..25ae13c89 100644 --- a/NextcloudTalk/NCAPIController.m +++ b/NextcloudTalk/NCAPIController.m @@ -1638,6 +1638,30 @@ - (NSURLSessionDataTask *)getSharedItemsOfType:(NSString *)objectType fromLastMe #pragma mark - Translations Controller +- (NSURLSessionDataTask *)getAvailableTranslationsForAccount:(TalkAccount *)account withCompletionBlock:(GetTranslationsCompletionBlock)block +{ + NSString *URLString = [NSString stringWithFormat:@"%@/ocs/v2.php/translation/languages", account.server]; + NCAPISessionManager *apiSessionManager = [_apiSessionManagers objectForKey:account.accountId]; + NSURLSessionDataTask *task = [apiSessionManager GET:URLString parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { + NSDictionary *translationDict = [[responseObject objectForKey:@"ocs"] objectForKey:@"data"]; + if (block) { + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)task.response; + NSArray *translations = [[NCDatabaseManager sharedInstance] translationsFromTranslationsArray:[translationDict objectForKey:@"languages"]]; + BOOL languageDetection = [translationDict objectForKey:@"languageDetection"]; + block(translations, languageDetection, nil, httpResponse.statusCode); + } + } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { + NSInteger statusCode = [self getResponseStatusCode:task.response]; + [self checkResponseStatusCode:statusCode forAccount:account]; + NSDictionary *failureDict = [[[self getFailureResponseObjectFromError:error] objectForKey:@"ocs"] objectForKey:@"data"]; + if (block) { + block(@[], NO, error, statusCode); + } + }]; + + return task; +} + - (NSURLSessionDataTask *)translateMessage:(NSString *)message from:(NSString *)from to:(NSString *)to forAccount:(TalkAccount *)account withCompletionBlock:(MessageTranslationCompletionBlock)block { NSString *URLString = [NSString stringWithFormat:@"%@/ocs/v2.php/translation/translate", account.server]; diff --git a/NextcloudTalk/NCDatabaseManager.h b/NextcloudTalk/NCDatabaseManager.h index 5116e4f5b..8b388169c 100644 --- a/NextcloudTalk/NCDatabaseManager.h +++ b/NextcloudTalk/NCDatabaseManager.h @@ -84,6 +84,13 @@ extern NSString * const kNotificationsCapabilityExists; extern NSString * const kMinimumRequiredTalkCapability; +@interface NCTranslation : NSObject +@property (nonatomic, copy) NSString *from; +@property (nonatomic, copy) NSString *fromLabel; +@property (nonatomic, copy) NSString *to; +@property (nonatomic, copy) NSString *toLabel; +@end + @interface NCDatabaseManager : NSObject + (instancetype)sharedInstance; @@ -115,6 +122,11 @@ extern NSString * const kMinimumRequiredTalkCapability; - (BOOL)serverHasNotificationsCapability:(NSString *)capability forAccountId:(NSString *)accountId; - (void)setExternalSignalingServerVersion:(NSString *)version forAccountId:(NSString *)accountId; +- (BOOL)hasAvailableTranslationsForAccountId:(NSString *)accountId; +- (BOOL)hasTranslationProvidersForAccountId:(NSString *)accountId; +- (NSArray *)availableTranslationsForAccountId:(NSString *)accountId; +- (NSArray *)translationsFromTranslationsArray:(NSArray *)translations; + @end NS_ASSUME_NONNULL_END diff --git a/NextcloudTalk/NCDatabaseManager.m b/NextcloudTalk/NCDatabaseManager.m index 7d9e4e257..7aa49e26a 100644 --- a/NextcloudTalk/NCDatabaseManager.m +++ b/NextcloudTalk/NCDatabaseManager.m @@ -33,7 +33,7 @@ NSString *const kTalkDatabaseFolder = @"Library/Application Support/Talk"; NSString *const kTalkDatabaseFileName = @"talk.realm"; -uint64_t const kTalkDatabaseSchemaVersion = 54; +uint64_t const kTalkDatabaseSchemaVersion = 55; NSString * const kCapabilitySystemMessages = @"system-messages"; NSString * const kCapabilityNotificationLevels = @"notification-levels"; @@ -88,6 +88,10 @@ NSString * const kMinimumRequiredTalkCapability = kCapabilitySystemMessages; // Talk 4.0 is the minimum required version +@implementation NCTranslation + +@end + @implementation NCDatabaseManager + (NCDatabaseManager *)sharedInstance @@ -385,6 +389,7 @@ - (void)setServerCapabilities:(NSDictionary *)serverCapabilities forAccountId:(N capabilities.userStatus = [[userStatusCaps objectForKey:@"enabled"] boolValue]; capabilities.extendedSupport = [[version objectForKey:@"extendedSupport"] boolValue]; capabilities.talkCapabilities = [talkCaps objectForKey:@"features"]; + capabilities.hasTranslationProviders = [[[[talkCaps objectForKey:@"config"] objectForKey:@"chat"] objectForKey:@"has-translation-providers"] boolValue]; capabilities.attachmentsAllowed = [[[[talkCaps objectForKey:@"config"] objectForKey:@"attachments"] objectForKey:@"allowed"] boolValue]; capabilities.attachmentsFolder = [[[talkCaps objectForKey:@"config"] objectForKey:@"attachments"] objectForKey:@"folder"]; capabilities.accountPropertyScopesVersion2 = [[provisioningAPICaps objectForKey:@"AccountPropertyScopesVersion"] integerValue] == 2; @@ -502,4 +507,58 @@ - (void)setExternalSignalingServerVersion:(NSString *)version forAccountId:(NSSt }]; } +- (BOOL)hasAvailableTranslationsForAccountId:(NSString *)accountId +{ + return [self hasTranslationProvidersForAccountId:accountId] || [self availableTranslationsForAccountId:accountId].count > 0; +} + +- (BOOL)hasTranslationProvidersForAccountId:(NSString *)accountId +{ + ServerCapabilities *serverCapabilities = [self serverCapabilitiesForAccountId:accountId]; + + return serverCapabilities.hasTranslationProviders; +} + +- (NSArray *)availableTranslationsForAccountId:(NSString *)accountId +{ + ServerCapabilities *serverCapabilities = [self serverCapabilitiesForAccountId:accountId]; + if (serverCapabilities) { + NSArray *translations = [self translationsArrayFromTranslationsJSONString:serverCapabilities.translations]; + return [self translationsFromTranslationsArray:translations]; + } + return @[]; +} + +- (NSArray *)translationsArrayFromTranslationsJSONString:(NSString *)translations +{ + NSArray *translationsArray = @[]; + NSData *data = [translations dataUsingEncoding:NSUTF8StringEncoding]; + if (data) { + NSError* error; + NSArray* jsonData = [NSJSONSerialization JSONObjectWithData:data + options:0 + error:&error]; + if (jsonData) { + translationsArray = jsonData; + } else { + NSLog(@"Error retrieving translations JSON data: %@", error); + } + } + return translationsArray; +} + +- (NSArray *)translationsFromTranslationsArray:(NSArray *)translations +{ + NSMutableArray *availableTranslations = [NSMutableArray new]; + for (NSDictionary *translationDict in translations) { + NCTranslation *translation = [[NCTranslation alloc] init]; + translation.from = [translationDict objectForKey:@"from"]; + translation.fromLabel = [translationDict objectForKey:@"fromLabel"]; + translation.to = [translationDict objectForKey:@"to"]; + translation.toLabel = [translationDict objectForKey:@"toLabel"]; + [availableTranslations addObject:translation]; + } + return availableTranslations; +} + @end diff --git a/NextcloudTalk/NCSettingsController.h b/NextcloudTalk/NCSettingsController.h index d57900688..16b1a89c5 100644 --- a/NextcloudTalk/NCSettingsController.h +++ b/NextcloudTalk/NCSettingsController.h @@ -57,15 +57,6 @@ typedef enum NCPreferredFileSorting { NCModificationDateSorting } NCPreferredFileSorting; -@interface NCTranslation : NSObject - -@property (nonatomic, copy) NSString *from; -@property (nonatomic, copy) NSString *fromLabel; -@property (nonatomic, copy) NSString *to; -@property (nonatomic, copy) NSString *toLabel; - -@end - @class NCExternalSignalingController; @interface NCSettingsController : NSObject @@ -90,7 +81,6 @@ typedef enum NCPreferredFileSorting { - (void)disconnectAllExternalSignalingControllers; - (void)subscribeForPushNotificationsForAccountId:(NSString *)accountId withCompletionBlock:(SubscribeForPushNotificationsCompletionBlock)block; - (NSInteger)chatMaxLengthConfigCapability; -- (NSArray *)availableTranslations; - (BOOL)canCreateGroupAndPublicRooms; - (BOOL)callsEnabledCapability; - (BOOL)isGuestsAppEnabled; diff --git a/NextcloudTalk/NCSettingsController.m b/NextcloudTalk/NCSettingsController.m index a333e1f2e..85cdc9cdc 100644 --- a/NextcloudTalk/NCSettingsController.m +++ b/NextcloudTalk/NCSettingsController.m @@ -60,10 +60,6 @@ @implementation NCPushNotificationKeyPair @end -@implementation NCTranslation - -@end - @implementation NCSettingsController @@ -650,44 +646,6 @@ - (NSInteger)chatMaxLengthConfigCapability return kDefaultChatMaxLength; } -- (NSArray *)availableTranslations -{ - TalkAccount *activeAccount = [[NCDatabaseManager sharedInstance] activeAccount]; - ServerCapabilities *serverCapabilities = [[NCDatabaseManager sharedInstance] serverCapabilitiesForAccountId:activeAccount.accountId]; - NSMutableArray *availableTranslations = [NSMutableArray new]; - if (serverCapabilities) { - NSArray *translations = [self translationsArrayFromTranslations:serverCapabilities.translations]; - for (NSDictionary *translationDict in translations) { - NCTranslation *translation = [[NCTranslation alloc] init]; - translation.from = [translationDict objectForKey:@"from"]; - translation.fromLabel = [translationDict objectForKey:@"fromLabel"]; - translation.to = [translationDict objectForKey:@"to"]; - translation.toLabel = [translationDict objectForKey:@"toLabel"]; - [availableTranslations addObject:translation]; - } - return availableTranslations; - } - return @[]; -} - -- (NSArray *)translationsArrayFromTranslations:(NSString *)translations -{ - NSArray *translationsArray = @[]; - NSData *data = [translations dataUsingEncoding:NSUTF8StringEncoding]; - if (data) { - NSError* error; - NSArray* jsonData = [NSJSONSerialization JSONObjectWithData:data - options:0 - error:&error]; - if (jsonData) { - translationsArray = jsonData; - } else { - NSLog(@"Error retrieving reactions JSON data: %@", error); - } - } - return translationsArray; -} - - (BOOL)canCreateGroupAndPublicRooms { TalkAccount *activeAccount = [[NCDatabaseManager sharedInstance] activeAccount]; diff --git a/NextcloudTalk/ServerCapabilities.h b/NextcloudTalk/ServerCapabilities.h index ce16d7530..ab1cd3ebd 100644 --- a/NextcloudTalk/ServerCapabilities.h +++ b/NextcloudTalk/ServerCapabilities.h @@ -50,6 +50,7 @@ NS_ASSUME_NONNULL_BEGIN @property RLMArray *talkCapabilities; @property NSInteger chatMaxLength; @property NSString *translations; +@property BOOL hasTranslationProviders; @property BOOL canCreate; @property BOOL attachmentsAllowed; @property NSString *attachmentsFolder; diff --git a/NextcloudTalk/en.lproj/Localizable.strings b/NextcloudTalk/en.lproj/Localizable.strings index 3ad8c69ca..3a0d602d7 100644 --- a/NextcloudTalk/en.lproj/Localizable.strings +++ b/NextcloudTalk/en.lproj/Localizable.strings @@ -517,6 +517,9 @@ /* No comment provided by engineer. */ "Could not delete conversation" = "Could not delete conversation"; +/* No comment provided by engineer. */ +"Could not get available languages" = "Could not get available languages"; + /* No comment provided by engineer. */ "Could not join %@ call" = "Could not join %@ call";