Various Improvements

This commit is contained in:
Ilya Laktyushin 2021-12-21 05:20:53 +04:00
parent 55e914abae
commit fc8787d732
9 changed files with 389 additions and 25 deletions

View File

@ -7149,3 +7149,14 @@ Sorry for the inconvenience.";
"Conversation.ContextMenuTranslate" = "Translate";
"ClearCache.ClearDescription" = "All media will stay in the Telegram cloud and can be re-downloaded if you need it again.";
"Localization.TranslateMessages" = "Translate Messages";
"Localization.ShowTranslate" = "Show Translate Button";
"Localization.ShowTranslateInfo" = "Show 'Translate' button in the message action menu.";
"Localization.DoNotTranslate" = "Do Not Translate";
"Localization.DoNotTranslateInfo" = "Do not show 'Translate' button in the message action menu for this language";
"Localization.DoNotTranslateManyInfo" = "Do not show 'Translate' button in the message action menu for this languages";
"Localization.InterfaceLanguage" = "Interface Language";
"DoNotTranslate.Title" = "Do Not Translate";

View File

@ -92,7 +92,6 @@ public class LocalizationListController: ViewController {
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
self.controllerNode.updatePresentationData(self.presentationData)
let editItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.editPressed))
let doneItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed))
if self.navigationItem.rightBarButtonItem === self.editItem {
@ -124,6 +123,8 @@ public class LocalizationListController: ViewController {
}
}, present: { [weak self] c, a in
self?.present(c, in: .window(.root), with: a)
}, push: { [weak self] c in
self?.push(c)
})
self.controllerNode.listNode.visibleContentOffsetChanged = { [weak self] offset in

View File

@ -14,14 +14,18 @@ import ShareController
import SearchBarNode
import SearchUI
import UndoUI
import TelegramUIPreferences
private enum LanguageListSection: ItemListSectionId {
case translate
case official
case unofficial
}
private enum LanguageListEntryId: Hashable {
case search
case translate(Int)
case localizationTitle
case localization(String)
}
@ -31,10 +35,26 @@ private enum LanguageListEntryType {
}
private enum LanguageListEntry: Comparable, Identifiable {
case translateTitle(text: String)
case translate(text: String, value: Bool)
case doNotTranslate(text: String, value: String)
case translateInfo(text: String)
case localizationTitle(text: String, section: ItemListSectionId)
case localization(index: Int, info: LocalizationInfo?, type: LanguageListEntryType, selected: Bool, activity: Bool, revealed: Bool, editing: Bool)
var stableId: LanguageListEntryId {
switch self {
case .translateTitle:
return .translate(0)
case .translate:
return .translate(1)
case .doNotTranslate:
return .translate(2)
case .translateInfo:
return .translate(3)
case .localizationTitle:
return .localizationTitle
case let .localization(index, info, _, _, _, _, _):
return .localization(info?.languageCode ?? "\(index)")
}
@ -42,8 +62,18 @@ private enum LanguageListEntry: Comparable, Identifiable {
private func index() -> Int {
switch self {
case .translateTitle:
return 0
case .translate:
return 1
case .doNotTranslate:
return 2
case .translateInfo:
return 3
case .localizationTitle:
return 1000
case let .localization(index, _, _, _, _, _, _):
return index
return 1001 + index
}
}
@ -51,8 +81,22 @@ private enum LanguageListEntry: Comparable, Identifiable {
return lhs.index() < rhs.index()
}
func item(presentationData: PresentationData, searchMode: Bool, openSearch: @escaping () -> Void, selectLocalization: @escaping (LocalizationInfo) -> Void, setItemWithRevealedOptions: @escaping (String?, String?) -> Void, removeItem: @escaping (String) -> Void) -> ListViewItem {
func item(presentationData: PresentationData, searchMode: Bool, openSearch: @escaping () -> Void, toggleShowTranslate: @escaping (Bool) -> Void, openDoNotTranslate: @escaping () -> Void, selectLocalization: @escaping (LocalizationInfo) -> Void, setItemWithRevealedOptions: @escaping (String?, String?) -> Void, removeItem: @escaping (String) -> Void) -> ListViewItem {
switch self {
case let .translateTitle(text):
return ItemListSectionHeaderItem(presentationData: ItemListPresentationData(presentationData), text: text, sectionId: LanguageListSection.translate.rawValue)
case let .translate(text, value):
return ItemListSwitchItem(presentationData: ItemListPresentationData(presentationData), title: text, value: value, sectionId: LanguageListSection.translate.rawValue, style: .blocks, updated: { value in
toggleShowTranslate(value)
})
case let .doNotTranslate(text, value):
return ItemListDisclosureItem(presentationData: ItemListPresentationData(presentationData), title: text, label: value, sectionId: LanguageListSection.translate.rawValue, style: .blocks, action: {
openDoNotTranslate()
})
case let .translateInfo(text):
return ItemListTextItem(presentationData: ItemListPresentationData(presentationData), text: .plain(text), sectionId: LanguageListSection.translate.rawValue)
case let .localizationTitle(text, section):
return ItemListSectionHeaderItem(presentationData: ItemListPresentationData(presentationData), text: text, sectionId: section)
case let .localization(_, info, type, selected, activity, revealed, editing):
return LocalizationListItem(presentationData: ItemListPresentationData(presentationData), id: info?.languageCode ?? "", title: info?.title ?? " ", subtitle: info?.localizedTitle ?? " ", checked: selected, activity: activity, loading: info == nil, editing: LocalizationListItemEditing(editable: !selected && !searchMode && !(info?.isOfficial ?? true), editing: editing, revealed: !selected && revealed, reorderable: false), sectionId: type == .official ? LanguageListSection.official.rawValue : LanguageListSection.unofficial.rawValue, alwaysPlain: searchMode, action: {
if let info = info {
@ -74,8 +118,8 @@ private func preparedLanguageListSearchContainerTransition(presentationData: Pre
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries, allUpdated: forceUpdate)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(presentationData: presentationData, searchMode: true, openSearch: {}, selectLocalization: selectLocalization, setItemWithRevealedOptions: { _, _ in }, removeItem: { _ in }), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(presentationData: presentationData, searchMode: true, openSearch: {}, selectLocalization: selectLocalization, setItemWithRevealedOptions: { _, _ in }, removeItem: { _ in }), directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(presentationData: presentationData, searchMode: true, openSearch: {}, toggleShowTranslate: { _ in }, openDoNotTranslate: {}, selectLocalization: selectLocalization, setItemWithRevealedOptions: { _, _ in }, removeItem: { _ in }), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(presentationData: presentationData, searchMode: true, openSearch: {}, toggleShowTranslate: { _ in }, openDoNotTranslate: {}, selectLocalization: selectLocalization, setItemWithRevealedOptions: { _, _ in }, removeItem: { _ in }), directionHint: nil) }
return LocalizationListSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, isSearching: isSearching)
}
@ -262,12 +306,12 @@ private struct LanguageListNodeTransition {
let crossfade: Bool
}
private func preparedLanguageListNodeTransition(presentationData: PresentationData, from fromEntries: [LanguageListEntry], to toEntries: [LanguageListEntry], openSearch: @escaping () -> Void, selectLocalization: @escaping (LocalizationInfo) -> Void, setItemWithRevealedOptions: @escaping (String?, String?) -> Void, removeItem: @escaping (String) -> Void, firstTime: Bool, isLoading: Bool, forceUpdate: Bool, animated: Bool, crossfade: Bool) -> LanguageListNodeTransition {
private func preparedLanguageListNodeTransition(presentationData: PresentationData, from fromEntries: [LanguageListEntry], to toEntries: [LanguageListEntry], openSearch: @escaping () -> Void, toggleShowTranslate: @escaping (Bool) -> Void, openDoNotTranslate: @escaping () -> Void, selectLocalization: @escaping (LocalizationInfo) -> Void, setItemWithRevealedOptions: @escaping (String?, String?) -> Void, removeItem: @escaping (String) -> Void, firstTime: Bool, isLoading: Bool, forceUpdate: Bool, animated: Bool, crossfade: Bool) -> LanguageListNodeTransition {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries, allUpdated: forceUpdate)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(presentationData: presentationData, searchMode: false, openSearch: openSearch, selectLocalization: selectLocalization, setItemWithRevealedOptions: setItemWithRevealedOptions, removeItem: removeItem), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(presentationData: presentationData, searchMode: false, openSearch: openSearch, selectLocalization: selectLocalization, setItemWithRevealedOptions: setItemWithRevealedOptions, removeItem: removeItem), directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(presentationData: presentationData, searchMode: false, openSearch: openSearch, toggleShowTranslate: toggleShowTranslate, openDoNotTranslate: openDoNotTranslate, selectLocalization: selectLocalization, setItemWithRevealedOptions: setItemWithRevealedOptions, removeItem: removeItem), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(presentationData: presentationData, searchMode: false, openSearch: openSearch, toggleShowTranslate: toggleShowTranslate, openDoNotTranslate: openDoNotTranslate, selectLocalization: selectLocalization, setItemWithRevealedOptions: setItemWithRevealedOptions, removeItem: removeItem), directionHint: nil) }
return LanguageListNodeTransition(deletions: deletions, insertions: insertions, updates: updates, firstTime: firstTime, isLoading: isLoading, animated: animated, crossfade: crossfade)
}
@ -279,6 +323,7 @@ final class LocalizationListControllerNode: ViewControllerTracingNode {
private let requestActivateSearch: () -> Void
private let requestDeactivateSearch: () -> Void
private let present: (ViewController, Any?) -> Void
private let push: (ViewController) -> Void
private var didSetReady = false
let _ready = ValuePromise<Bool>()
@ -304,7 +349,7 @@ final class LocalizationListControllerNode: ViewControllerTracingNode {
}
}
init(context: AccountContext, presentationData: PresentationData, navigationBar: NavigationBar, requestActivateSearch: @escaping () -> Void, requestDeactivateSearch: @escaping () -> Void, updateCanStartEditing: @escaping (Bool?) -> Void, present: @escaping (ViewController, Any?) -> Void) {
init(context: AccountContext, presentationData: PresentationData, navigationBar: NavigationBar, requestActivateSearch: @escaping () -> Void, requestDeactivateSearch: @escaping () -> Void, updateCanStartEditing: @escaping (Bool?) -> Void, present: @escaping (ViewController, Any?) -> Void, push: @escaping (ViewController) -> Void) {
self.context = context
self.presentationData = presentationData
self.presentationDataValue.set(.single(presentationData))
@ -312,6 +357,7 @@ final class LocalizationListControllerNode: ViewControllerTracingNode {
self.requestActivateSearch = requestActivateSearch
self.requestDeactivateSearch = requestDeactivateSearch
self.present = present
self.push = push
self.listNode = ListView()
self.listNode.keepTopItemOverscrollBackground = ListViewKeepTopItemOverscrollBackground(color: presentationData.theme.list.blocksBackgroundColor, direction: true)
@ -373,7 +419,7 @@ final class LocalizationListControllerNode: ViewControllerTracingNode {
let preferencesKey: PostboxViewKey = .preferences(keys: Set([PreferencesKeys.localizationListState]))
let previousState = Atomic<LocalizationListState?>(value: nil)
let previousEntriesHolder = Atomic<([LanguageListEntry], PresentationTheme, PresentationStrings)?>(value: nil)
self.listDisposable = combineLatest(queue: .mainQueue(), context.account.postbox.combinedView(keys: [preferencesKey]), context.sharedContext.accountManager.sharedData(keys: [SharedDataKeys.localizationSettings]), self.presentationDataValue.get(), self.applyingCode.get(), revealedCode.get(), self.isEditing.get()).start(next: { [weak self] view, sharedData, presentationData, applyingCode, revealedCode, isEditing in
self.listDisposable = combineLatest(queue: .mainQueue(), context.account.postbox.combinedView(keys: [preferencesKey]), context.sharedContext.accountManager.sharedData(keys: [SharedDataKeys.localizationSettings, ApplicationSpecificSharedDataKeys.translationSettings]), self.presentationDataValue.get(), self.applyingCode.get(), revealedCode.get(), self.isEditing.get()).start(next: { [weak self] view, sharedData, presentationData, applyingCode, revealedCode, isEditing in
guard let strongSelf = self else {
return
}
@ -385,10 +431,26 @@ final class LocalizationListControllerNode: ViewControllerTracingNode {
}
var existingIds = Set<String>()
var showTranslate = true
if let translationSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.translationSettings]?.get(TranslationSettings.self) {
showTranslate = translationSettings.showTranslate
}
let localizationListState = (view.views[preferencesKey] as? PreferencesView)?.values[PreferencesKeys.localizationListState]?.get(LocalizationListState.self)
if let localizationListState = localizationListState, !localizationListState.availableOfficialLocalizations.isEmpty {
strongSelf.currentListState = localizationListState
if #available(iOS 15.0, *) {
entries.append(.translateTitle(text: presentationData.strings.Localization_TranslateMessages.uppercased()))
entries.append(.translate(text: presentationData.strings.Localization_ShowTranslate, value: showTranslate))
if showTranslate {
entries.append(.doNotTranslate(text: presentationData.strings.Localization_DoNotTranslate, value: ""))
entries.append(.translateInfo(text: presentationData.strings.Localization_DoNotTranslateInfo))
} else {
entries.append(.translateInfo(text: presentationData.strings.Localization_ShowTranslateInfo))
}
}
let availableSavedLocalizations = localizationListState.availableSavedLocalizations.filter({ info in !localizationListState.availableOfficialLocalizations.contains(where: { $0.languageCode == info.languageCode }) })
if availableSavedLocalizations.isEmpty {
updateCanStartEditing(nil)
@ -396,6 +458,7 @@ final class LocalizationListControllerNode: ViewControllerTracingNode {
updateCanStartEditing(isEditing)
}
if !availableSavedLocalizations.isEmpty {
entries.append(.localizationTitle(text: presentationData.strings.Localization_InterfaceLanguage.uppercased(), section: LanguageListSection.unofficial.rawValue))
for info in availableSavedLocalizations {
if existingIds.contains(info.languageCode) {
continue
@ -403,6 +466,8 @@ final class LocalizationListControllerNode: ViewControllerTracingNode {
existingIds.insert(info.languageCode)
entries.append(.localization(index: entries.count, info: info, type: .unofficial, selected: info.languageCode == activeLanguageCode, activity: applyingCode == info.languageCode, revealed: revealedCode == info.languageCode, editing: isEditing))
}
} else {
entries.append(.localizationTitle(text: presentationData.strings.Localization_InterfaceLanguage.uppercased(), section: LanguageListSection.official.rawValue))
}
for info in localizationListState.availableOfficialLocalizations {
if existingIds.contains(info.languageCode) {
@ -420,7 +485,19 @@ final class LocalizationListControllerNode: ViewControllerTracingNode {
let previousState = previousState.swap(localizationListState)
let previousEntriesAndPresentationData = previousEntriesHolder.swap((entries, presentationData.theme, presentationData.strings))
let transition = preparedLanguageListNodeTransition(presentationData: presentationData, from: previousEntriesAndPresentationData?.0 ?? [], to: entries, openSearch: openSearch, selectLocalization: { [weak self] info in self?.selectLocalization(info) }, setItemWithRevealedOptions: setItemWithRevealedOptions, removeItem: removeItem, firstTime: previousEntriesAndPresentationData == nil, isLoading: entries.isEmpty, forceUpdate: previousEntriesAndPresentationData?.1 !== presentationData.theme || previousEntriesAndPresentationData?.2 !== presentationData.strings, animated: (previousEntriesAndPresentationData?.0.count ?? 0) >= entries.count, crossfade: (previousState == nil) != (localizationListState == nil))
let transition = preparedLanguageListNodeTransition(presentationData: presentationData, from: previousEntriesAndPresentationData?.0 ?? [], to: entries, openSearch: openSearch, toggleShowTranslate: { value in
let _ = updateTranslationSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
var updated = current.withUpdatedShowTranslate(value)
if !value {
updated = updated.withUpdatedIgnoredLanguages(nil)
}
return updated
}).start()
}, openDoNotTranslate: { [weak self] in
if let strongSelf = self {
strongSelf.push(translationSettingsController(context: strongSelf.context))
}
}, selectLocalization: { [weak self] info in self?.selectLocalization(info) }, setItemWithRevealedOptions: setItemWithRevealedOptions, removeItem: removeItem, firstTime: previousEntriesAndPresentationData == nil, isLoading: entries.isEmpty, forceUpdate: previousEntriesAndPresentationData?.1 !== presentationData.theme || previousEntriesAndPresentationData?.2 !== presentationData.strings, animated: (previousEntriesAndPresentationData?.0.count ?? 0) >= entries.count, crossfade: (previousState == nil) != (localizationListState == nil))
strongSelf.enqueueTransition(transition)
})
self.updatedDisposable = context.engine.localization.synchronizedLocalizationListState().start()

View File

@ -0,0 +1,147 @@
import Foundation
import UIKit
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
import TelegramPresentationData
import TelegramUIPreferences
import ItemListUI
import PresentationDataUtils
import TelegramStringFormatting
import AccountContext
import Translate
private final class TranslationSettingsControllerArguments {
let context: AccountContext
let updateLanguageSelected: (String, Bool) -> Void
init(context: AccountContext, updateLanguageSelected: @escaping (String, Bool) -> Void) {
self.context = context
self.updateLanguageSelected = updateLanguageSelected
}
}
private enum TranslationSettingsControllerSection: Int32 {
case languages
}
private enum TranslationSettingsControllerEntry: ItemListNodeEntry {
case language(Int32, PresentationTheme, String, String, Bool, String)
var section: ItemListSectionId {
switch self {
case .language:
return TranslationSettingsControllerSection.languages.rawValue
}
}
var stableId: Int32 {
switch self {
case let .language(index, _, _, _, _, _):
return index
}
}
static func ==(lhs: TranslationSettingsControllerEntry, rhs: TranslationSettingsControllerEntry) -> Bool {
switch lhs {
case let .language(lhsIndex, lhsTheme, lhsTitle, lhsSubtitle, lhsValue, lhsCode):
if case let .language(rhsIndex, rhsTheme, rhsTitle, rhsSubtitle, rhsValue, rhsCode) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle, lhsValue == rhsValue, lhsCode == rhsCode {
return true
} else {
return false
}
}
}
static func <(lhs: TranslationSettingsControllerEntry, rhs: TranslationSettingsControllerEntry) -> Bool {
return lhs.stableId < rhs.stableId
}
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
let arguments = arguments as! TranslationSettingsControllerArguments
switch self {
case let .language(_, _, title, subtitle, value, code):
return LocalizationListItem(presentationData: presentationData, id: code, title: title, subtitle: subtitle, checked: value, activity: false, loading: false, editing: LocalizationListItemEditing(editable: false, editing: false, revealed: false, reorderable: false), sectionId: self.section, alwaysPlain: false, action: {
arguments.updateLanguageSelected(code, !value)
}, setItemWithRevealedOptions: { _, _ in }, removeItem: { _ in })
}
}
}
private func translationSettingsControllerEntries(theme: PresentationTheme, strings: PresentationStrings, settings: TranslationSettings, languages: [(String, String, String)]) -> [TranslationSettingsControllerEntry] {
var entries: [TranslationSettingsControllerEntry] = []
var index: Int32 = 0
var selectedLanguages: Set<String>
if let ignoredLanguages = settings.ignoredLanguages {
selectedLanguages = Set(ignoredLanguages)
} else {
selectedLanguages = Set([strings.baseLanguageCode])
}
for (code, title, subtitle) in languages {
entries.append(.language(index, theme, title, subtitle, selectedLanguages.contains(code), code))
index += 1
}
return entries
}
public func translationSettingsController(context: AccountContext) -> ViewController {
let actionsDisposable = DisposableSet()
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let interfaceLanguageCode = presentationData.strings.baseLanguageCode
let arguments = TranslationSettingsControllerArguments(context: context, updateLanguageSelected: { code, value in
let _ = updateTranslationSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
var updated = current
var updatedIgnoredLanguages = updated.ignoredLanguages ?? []
if value {
if current.ignoredLanguages == nil {
updatedIgnoredLanguages.append(interfaceLanguageCode)
}
if !updatedIgnoredLanguages.contains(code) {
updatedIgnoredLanguages.append(code)
}
} else {
updatedIgnoredLanguages.removeAll(where: { $0 == code })
}
updated = updated.withUpdatedIgnoredLanguages(updatedIgnoredLanguages)
return updated
}).start()
})
let enLocale = Locale(identifier: "en")
var languages: [(String, String, String)] = []
for code in supportedTranslationLanguages {
if let title = enLocale.localizedString(forLanguageCode: code) {
let languageLocale = Locale(identifier: code)
let subtitle = languageLocale.localizedString(forLanguageCode: code) ?? title
let value = (code, title.capitalized, subtitle.capitalized)
if code == interfaceLanguageCode {
languages.insert(value, at: 0)
} else {
languages.append(value)
}
}
}
let sharedData = context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.translationSettings])
let signal = combineLatest(queue: Queue.mainQueue(), context.sharedContext.presentationData, sharedData)
|> map { presentationData, sharedData -> (ItemListControllerState, (ItemListNodeState, Any)) in
let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.translationSettings]?.get(TranslationSettings.self) ?? TranslationSettings.defaultSettings
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.DoNotTranslate_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: translationSettingsControllerEntries(theme: presentationData.theme, strings: presentationData.strings, settings: settings, languages: languages), style: .blocks, animateChanges: false)
return (controllerState, (listState, arguments))
}
|> afterDisposed {
actionsDisposable.dispose()
}
let controller = ItemListController(context: context, state: signal)
controller.alwaysSynchronous = true
return controller
}

View File

@ -24,6 +24,8 @@ import AvatarNode
import AdUI
import TelegramNotices
import ReactionListContextMenuContent
import TelegramUIPreferences
import Translate
private struct MessageContextMenuData {
let starStatus: Bool?
@ -543,7 +545,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
return transaction.getCombinedPeerReadState(messages[0].id.peerId)
}
let dataSignal: Signal<(MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], CachedPeerData?, AppConfiguration, Bool, Int32, AvailableReactions?), NoError> = combineLatest(
let dataSignal: Signal<(MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], CachedPeerData?, AppConfiguration, Bool, Int32, AvailableReactions?, TranslationSettings), NoError> = combineLatest(
loadLimits,
loadStickerSaveStatusSignal,
loadResourceStatusSignal,
@ -553,9 +555,10 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
cachedData,
readState,
ApplicationSpecificNotice.getMessageViewsPrivacyTips(accountManager: context.sharedContext.accountManager),
context.engine.stickers.availableReactions()
context.engine.stickers.availableReactions(),
context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.translationSettings])
)
|> map { limitsAndAppConfig, stickerSaveStatus, resourceStatus, messageActions, updatingMessageMedia, cachedData, readState, messageViewsPrivacyTips, availableReactions -> (MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], CachedPeerData?, AppConfiguration, Bool, Int32, AvailableReactions?) in
|> map { limitsAndAppConfig, stickerSaveStatus, resourceStatus, messageActions, updatingMessageMedia, cachedData, readState, messageViewsPrivacyTips, availableReactions, sharedData -> (MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], CachedPeerData?, AppConfiguration, Bool, Int32, AvailableReactions?, TranslationSettings) in
let (limitsConfiguration, appConfig) = limitsAndAppConfig
var canEdit = false
if !isAction {
@ -568,12 +571,19 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
isMessageRead = readState.isOutgoingMessageIndexRead(message.index)
}
return (MessageContextMenuData(starStatus: stickerSaveStatus, canReply: canReply, canPin: canPin, canEdit: canEdit, canSelect: canSelect, resourceStatus: resourceStatus, messageActions: messageActions), updatingMessageMedia, cachedData, appConfig, isMessageRead, messageViewsPrivacyTips, availableReactions)
let translationSettings: TranslationSettings
if let current = sharedData.entries[ApplicationSpecificSharedDataKeys.translationSettings]?.get(TranslationSettings.self) {
translationSettings = current
} else {
translationSettings = TranslationSettings.defaultSettings
}
return (MessageContextMenuData(starStatus: stickerSaveStatus, canReply: canReply, canPin: canPin, canEdit: canEdit, canSelect: canSelect, resourceStatus: resourceStatus, messageActions: messageActions), updatingMessageMedia, cachedData, appConfig, isMessageRead, messageViewsPrivacyTips, availableReactions, translationSettings)
}
return dataSignal
|> deliverOnMainQueue
|> map { data, updatingMessageMedia, cachedData, appConfig, isMessageRead, messageViewsPrivacyTips, availableReactions -> ContextController.Items in
|> map { data, updatingMessageMedia, cachedData, appConfig, isMessageRead, messageViewsPrivacyTips, availableReactions, translationSettings -> ContextController.Items in
var actions: [ContextMenuItem] = []
var isPinnedMessages = false
@ -760,7 +770,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
f(.default)
})))
if #available(iOS 15.0, *), !message.text.isEmpty {
if canTranslateText(context: context, text: message.text, showTranslate: translationSettings.showTranslate, ignoredLanguages: translationSettings.ignoredLanguages) {
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuTranslate, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Translate"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in

View File

@ -1248,14 +1248,20 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
let animationProgress: CGFloat = (currentValue - initialHeight) / (targetHeight - initialHeight)
let scaleProgress: CGFloat
var effectiveAvatarInset = avatarInset
if currentValue < targetHeight {
initialSize = displaySize
targetSize = maximumDisplaySize
scaleProgress = animationProgress
} else if currentValue > targetHeight {
initialSize = maximumDisplaySize
targetSize = displaySize
scaleProgress = 1.0 - animationProgress
if abs(targetHeight - initialHeight) > 100.0 {
if currentValue < targetHeight {
initialSize = displaySize
targetSize = maximumDisplaySize
scaleProgress = animationProgress
} else if currentValue > targetHeight {
initialSize = maximumDisplaySize
targetSize = displaySize
scaleProgress = 1.0 - animationProgress
} else {
initialSize = isPlaying ? maximumDisplaySize : displaySize
targetSize = initialSize
scaleProgress = isPlaying ? 1.0 : 0.0
}
} else {
initialSize = isPlaying ? maximumDisplaySize : displaySize
targetSize = initialSize
@ -1324,6 +1330,12 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
let actionButtonsFrame = CGRect(origin: CGPoint(x: videoFrame.minX, y: videoFrame.maxY), size: actionButtonsSize)
actionButtonsNode.frame = actionButtonsFrame
}
if let reactionButtonsNode = self.reactionButtonsNode {
let reactionButtonsSize = reactionButtonsNode.frame.size
let reactionButtonsFrame = CGRect(origin: CGPoint(x: videoFrame.minX, y: videoFrame.maxY + 6.0), size: reactionButtonsSize)
reactionButtonsNode.frame = reactionButtonsFrame
}
}
override func openMessageContextMenu() {

View File

@ -35,6 +35,7 @@ private enum ApplicationSpecificSharedDataKeyValues: Int32 {
case contactSynchronizationSettings = 15
case webBrowserSettings = 16
case intentsSettings = 17
case translationSettings = 18
}
public struct ApplicationSpecificSharedDataKeys {
@ -56,6 +57,7 @@ public struct ApplicationSpecificSharedDataKeys {
public static let contactSynchronizationSettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.contactSynchronizationSettings.rawValue)
public static let webBrowserSettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.webBrowserSettings.rawValue)
public static let intentsSettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.intentsSettings.rawValue)
public static let translationSettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.translationSettings.rawValue)
}
private enum ApplicationSpecificItemCacheCollectionIdValues: Int8 {

View File

@ -0,0 +1,58 @@
import Foundation
import Postbox
import TelegramCore
import SwiftSignalKit
public struct TranslationSettings: Codable, Equatable {
public var showTranslate: Bool
public var ignoredLanguages: [String]?
public static var defaultSettings: TranslationSettings {
return TranslationSettings(showTranslate: true, ignoredLanguages: nil)
}
init(showTranslate: Bool, ignoredLanguages: [String]?) {
self.showTranslate = showTranslate
self.ignoredLanguages = ignoredLanguages
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: StringCodingKey.self)
self.showTranslate = try container.decodeIfPresent(Bool.self, forKey: "showTranslate") ?? true
self.ignoredLanguages = try container.decodeIfPresent([String].self, forKey: "ignoredLanguages")
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: StringCodingKey.self)
try container.encode(self.showTranslate, forKey: "showTranslate")
try container.encodeIfPresent(self.ignoredLanguages, forKey: "ignoredLanguages")
}
public static func ==(lhs: TranslationSettings, rhs: TranslationSettings) -> Bool {
return lhs.showTranslate == rhs.showTranslate && lhs.ignoredLanguages == rhs.ignoredLanguages
}
public func withUpdatedShowTranslate(_ showTranslate: Bool) -> TranslationSettings {
return TranslationSettings(showTranslate: showTranslate, ignoredLanguages: self.ignoredLanguages)
}
public func withUpdatedIgnoredLanguages(_ ignoredLanguages: [String]?) -> TranslationSettings {
return TranslationSettings(showTranslate: self.showTranslate, ignoredLanguages: ignoredLanguages)
}
}
public func updateTranslationSettingsInteractively(accountManager: AccountManager<TelegramAccountManagerTypes>, _ f: @escaping (TranslationSettings) -> TranslationSettings) -> Signal<Void, NoError> {
return accountManager.transaction { transaction -> Void in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.translationSettings, { entry in
let currentSettings: TranslationSettings
if let entry = entry?.get(TranslationSettings.self) {
currentSettings = entry
} else {
currentSettings = TranslationSettings.defaultSettings
}
return PreferencesEntry(f(currentSettings))
})
}
}

View File

@ -2,11 +2,57 @@ import Foundation
import UIKit
import Display
import AccountContext
import NaturalLanguage
// Incuding at least one Objective-C class in a swift file ensures that it doesn't get stripped by the linker
private final class LinkHelperClass: NSObject {
}
public var supportedTranslationLanguages = [
"en",
"ar",
"zh",
"fr",
"de",
"it",
"jp",
"ko",
"pt",
"ru",
"es"
]
@available(iOS 12.0, *)
private let languageRecognizer = NLLanguageRecognizer()
public func canTranslateText(context: AccountContext, text: String, showTranslate: Bool, ignoredLanguages: [String]?) -> Bool {
guard showTranslate, text.count > 0 else {
return false
}
if #available(iOS 15.0, *) {
var dontTranslateLanguages: [String] = []
if let ignoredLanguages = ignoredLanguages {
dontTranslateLanguages = ignoredLanguages
} else {
dontTranslateLanguages = [context.sharedContext.currentPresentationData.with { $0 }.strings.baseLanguageCode]
}
let text = String(text.prefix(64))
languageRecognizer.processString(text)
let hypotheses = languageRecognizer.languageHypotheses(withMaximum: 2)
languageRecognizer.reset()
if let language = hypotheses.first(where: { supportedTranslationLanguages.contains($0.key.rawValue) }) {
return !dontTranslateLanguages.contains(language.key.rawValue)
} else {
return false
}
} else {
return false
}
}
public func translateText(context: AccountContext, text: String) {
guard !text.isEmpty else {
return