mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Various Improvements
This commit is contained in:
parent
55e914abae
commit
fc8787d732
@ -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";
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
}
|
@ -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
|
||||
|
@ -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() {
|
||||
|
@ -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 {
|
||||
|
@ -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))
|
||||
})
|
||||
}
|
||||
}
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user