diff --git a/Images.xcassets/Settings/MenuIcons/EditProfile.imageset/Contents.json b/Images.xcassets/Settings/MenuIcons/EditProfile.imageset/Contents.json new file mode 100644 index 0000000000..ccf2341872 --- /dev/null +++ b/Images.xcassets/Settings/MenuIcons/EditProfile.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "EditProfile@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "EditProfile@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Images.xcassets/Settings/MenuIcons/EditProfile.imageset/EditProfile@2x.png b/Images.xcassets/Settings/MenuIcons/EditProfile.imageset/EditProfile@2x.png new file mode 100644 index 0000000000..c48581d6a3 Binary files /dev/null and b/Images.xcassets/Settings/MenuIcons/EditProfile.imageset/EditProfile@2x.png differ diff --git a/Images.xcassets/Settings/MenuIcons/EditProfile.imageset/EditProfile@3x.png b/Images.xcassets/Settings/MenuIcons/EditProfile.imageset/EditProfile@3x.png new file mode 100644 index 0000000000..dbc486f392 Binary files /dev/null and b/Images.xcassets/Settings/MenuIcons/EditProfile.imageset/EditProfile@3x.png differ diff --git a/TelegramUI/CachedFaqInstantPage.swift b/TelegramUI/CachedFaqInstantPage.swift index a69c3346f5..befd263111 100644 --- a/TelegramUI/CachedFaqInstantPage.swift +++ b/TelegramUI/CachedFaqInstantPage.swift @@ -71,6 +71,7 @@ func faqSearchableItems(context: AccountContext) -> Signal<[SettingsSearchableIt return cachedFaqInstantPage(context: context) |> map { resolvedUrl -> [SettingsSearchableItem] in var results: [SettingsSearchableItem] = [] + var nextIndex: Int = 1 if case let .instantView(webPage, _) = resolvedUrl { if case let .Loaded(content) = webPage.content, let instantPage = content.instantPage { var processingQuestions = false @@ -83,14 +84,15 @@ func faqSearchableItems(context: AccountContext) -> Signal<[SettingsSearchableIt if results.isEmpty { processingQuestions = true } - case let .anchor(anchor): - currentAnchor = anchor - case let .header(text): - if let anchor = currentAnchor { - results.append(SettingsSearchableItem(id: .faq(results.count + 1), title: text.plainText, alternate: [], icon: .faq, breadcrumbs: [strings.SettingsSearch_FAQ], present: { context, _, present in - present(.push, InstantPageController(context: context, webPage: webPage, sourcePeerType: .channel, anchor: anchor)) - })) - } +// case let .anchor(anchor): +// currentAnchor = anchor +// case let .header(text): +// if let anchor = currentAnchor { +// results.append(SettingsSearchableItem(id: .faq(nextIndex), title: text.plainText, alternate: [], icon: .faq, breadcrumbs: [strings.SettingsSearch_FAQ], present: { context, _, present in +// present(.push, InstantPageController(context: context, webPage: webPage, sourcePeerType: .channel, anchor: anchor)) +// })) +// nextIndex += 1 +// } default: break } @@ -107,9 +109,20 @@ func faqSearchableItems(context: AccountContext) -> Signal<[SettingsSearchableIt for item in items { if case let .text(itemText, _) = item, case let .url(text, url, _) = itemText { let (_, anchor) = extractAnchor(string: url) - results.append(SettingsSearchableItem(id: .faq(results.count + 1), title: text.plainText, alternate: [], icon: .faq, breadcrumbs: [strings.SettingsSearch_FAQ, currentSection], present: { context, _, present in + var index = nextIndex + if anchor?.contains("delete-my-account") ?? false { + index = 0 + } else { + nextIndex += 1 + } + let item = SettingsSearchableItem(id: .faq(index), title: text.plainText, alternate: [], icon: .faq, breadcrumbs: [strings.SettingsSearch_FAQ, currentSection], present: { context, _, present in present(.push, InstantPageController(context: context, webPage: webPage, sourcePeerType: .channel, anchor: anchor)) - })) + }) + if index == 0 { + results.insert(item, at: 0) + } else { + results.append(item) + } } } } diff --git a/TelegramUI/ChatMessageInteractiveInstantVideoNode.swift b/TelegramUI/ChatMessageInteractiveInstantVideoNode.swift index 8131324e9d..b604bd5ca8 100644 --- a/TelegramUI/ChatMessageInteractiveInstantVideoNode.swift +++ b/TelegramUI/ChatMessageInteractiveInstantVideoNode.swift @@ -352,7 +352,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } durationNode.defaultDuration = telegramFile.duration.flatMap(Double.init) - let streamVideo = automaticDownload && isMediaStreamable(message: item.message, media: telegramFile) + let streamVideo = automaticDownload && isMediaStreamable(message: item.message, media: telegramFile) && telegramFile.id?.namespace != Namespaces.Media.LocalFile if let videoNode = strongSelf.videoNode { videoNode.layer.allowsGroupOpacity = true videoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.5, delay: 0.2, removeOnCompletion: false, completion: { [weak videoNode] _ in @@ -500,6 +500,15 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } } + var isBuffering: Bool? + if let message = self.item?.message, let media = self.media, let size = media.size, (isMediaStreamable(message: message, media: media) || size <= 256 * 1024) && (self.automaticDownload ?? false) { + if let playerStatus = self.playerStatus, case .buffering = playerStatus.status { + isBuffering = true + } else { + isBuffering = false + } + } + var progressRequired = false if case let .fetchStatus(fetchStatus) = status.mediaStatus { if case .Local = fetchStatus { @@ -511,6 +520,8 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } else { progressRequired = true } + } else if isBuffering ?? false { + progressRequired = true } if progressRequired { @@ -530,15 +541,6 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } } - var isBuffering: Bool? - if let message = self.item?.message, let media = self.media, let size = media.size, (isMediaStreamable(message: message, media: media) || size <= 256 * 1024) && (self.automaticDownload ?? false) { - if let playerStatus = self.playerStatus, case .buffering = playerStatus.status { - isBuffering = true - } else { - isBuffering = false - } - } - var state: RadialStatusNodeState switch status.mediaStatus { case var .fetchStatus(fetchStatus): @@ -550,7 +552,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { case let .Fetching(_, progress): if let isBuffering = isBuffering { if isBuffering { - state = .progress(color: bubbleTheme.mediaOverlayControlForegroundColor, lineWidth: nil, value: nil, cancelEnabled: false) + state = .progress(color: bubbleTheme.mediaOverlayControlForegroundColor, lineWidth: nil, value: nil, cancelEnabled: true) } else { state = .none } @@ -573,7 +575,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } default: if isBuffering ?? false { - state = .progress(color: bubbleTheme.mediaOverlayControlForegroundColor, lineWidth: nil, value: nil, cancelEnabled: false) + state = .progress(color: bubbleTheme.mediaOverlayControlForegroundColor, lineWidth: nil, value: nil, cancelEnabled: true) } else { state = .none } diff --git a/TelegramUI/ChatMessageInteractiveMediaNode.swift b/TelegramUI/ChatMessageInteractiveMediaNode.swift index ec5b2862da..7e464768b9 100644 --- a/TelegramUI/ChatMessageInteractiveMediaNode.swift +++ b/TelegramUI/ChatMessageInteractiveMediaNode.swift @@ -178,8 +178,8 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { let point = recognizer.location(in: self.imageNode.view) if let fetchStatus = self.fetchStatus, case .Local = fetchStatus { var videoContentMatch = true - if let content = self.videoContent, case let .message(id, _, _) = content.nativeId { - videoContentMatch = self.message?.id == id + if let content = self.videoContent, case let .message(id, _, mediaId) = content.nativeId { + videoContentMatch = self.message?.id == id && self.media?.id == mediaId } self.activateLocalContent((self.automaticPlayback ?? false) && videoContentMatch ? .automaticPlayback : .default) } else { @@ -698,26 +698,20 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { } else if let image = media as? TelegramMediaWebFile { strongSelf.fetchDisposable.set(chatMessageWebFileInteractiveFetched(account: context.account, image: image).start()) } else if let file = media as? TelegramMediaFile { - if automaticPlayback || !file.isAnimated { - let fetchSignal = messageMediaFileInteractiveFetched(context: context, message: message, file: file, userInitiated: false) - if !file.isAnimated { - let visibilityAwareFetchSignal = strongSelf.visibilityPromise.get() - |> mapToSignal { visibility -> Signal in - switch visibility { - case .visible: - return fetchSignal - |> mapToSignal { _ -> Signal in - return .complete() - } - case .none: - return .complete() + let fetchSignal = messageMediaFileInteractiveFetched(context: context, message: message, file: file, userInitiated: false) + let visibilityAwareFetchSignal = strongSelf.visibilityPromise.get() + |> mapToSignal { visibility -> Signal in + switch visibility { + case .visible: + return fetchSignal + |> mapToSignal { _ -> Signal in + return .complete() } + case .none: + return .complete() } - strongSelf.fetchDisposable.set(visibilityAwareFetchSignal.start()) - } else { - strongSelf.fetchDisposable.set(fetchSignal.start()) - } } + strongSelf.fetchDisposable.set(visibilityAwareFetchSignal.start()) } } else if case .prefetch = automaticDownload, message.id.namespace != Namespaces.Message.SecretIncoming { if let file = media as? TelegramMediaFile { @@ -927,7 +921,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { if wideLayout { if let size = file.size { let sizeString = "\(dataSizeString(Int(Float(size) * progress), forceDecimal: true, decimalSeparator: decimalSeparator)) / \(dataSizeString(size, forceDecimal: true, decimalSeparator: decimalSeparator))" - if file.isAnimated && !automaticDownload { + if file.isAnimated && (!automaticDownload || !automaticPlayback) { badgeContent = .mediaDownload(backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, duration: "GIF " + sizeString, size: nil, muted: false, active: false) } else if let duration = file.duration, !message.flags.contains(.Unsent) { @@ -1031,7 +1025,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { case .Remote: state = .download(bubbleTheme.mediaOverlayControlForegroundColor) if let file = self.media as? TelegramMediaFile { - if file.isAnimated && !automaticDownload { + if file.isAnimated && (!automaticDownload || !automaticPlayback) { let string = "GIF " + dataSizeString(file.size ?? 0, decimalSeparator: decimalSeparator) badgeContent = .mediaDownload(backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, duration: string, size: nil, muted: false, active: false) } else { diff --git a/TelegramUI/EditSettingsController.swift b/TelegramUI/EditSettingsController.swift index 5029595eda..aa0fea3075 100644 --- a/TelegramUI/EditSettingsController.swift +++ b/TelegramUI/EditSettingsController.swift @@ -400,22 +400,22 @@ func editSettingsController(context: AccountContext, currentName: ItemListAvatar let peerView = context.account.viewTracker.peerView(context.account.peerId) let signal = combineLatest(context.sharedContext.presentationData, statePromise.get(), peerView) - |> map { presentationData, state, view -> (ItemListControllerState, (ItemListNodeState, SettingsEntry.ItemGenerationArguments)) in - let rightNavigationButton: ItemListNavigationButton - if state.updatingName != nil || state.updatingBioText { - rightNavigationButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {}) - } else { - rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: { - arguments.saveEditingState() - }) - } - - let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.EditProfile_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back)) - let listState = ItemListNodeState(entries: editSettingsEntries(presentationData: presentationData, state: state, view: view, canAddAccounts: canAddAccounts), style: .blocks, ensureVisibleItemTag: focusOnItemTag) - - return (controllerState, (listState, arguments)) - } |> afterDisposed { - actionsDisposable.dispose() + |> map { presentationData, state, view -> (ItemListControllerState, (ItemListNodeState, SettingsEntry.ItemGenerationArguments)) in + let rightNavigationButton: ItemListNavigationButton + if state.updatingName != nil || state.updatingBioText { + rightNavigationButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {}) + } else { + rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: { + arguments.saveEditingState() + }) + } + + let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.EditProfile_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back)) + let listState = ItemListNodeState(entries: editSettingsEntries(presentationData: presentationData, state: state, view: view, canAddAccounts: canAddAccounts), style: .blocks, ensureVisibleItemTag: focusOnItemTag) + + return (controllerState, (listState, arguments)) + } |> afterDisposed { + actionsDisposable.dispose() } let controller = ItemListController(context: context, state: signal, tabBarItem: nil) @@ -527,12 +527,12 @@ func editSettingsController(context: AccountContext, currentName: ItemListAvatar return mapResourceToAvatarSizes(postbox: context.account.postbox, resource: resource, representations: representations) }) |> deliverOnMainQueue).start(next: { result in switch result { - case .complete: - updateState { - $0.withUpdatedUpdatingAvatar(nil) - } - case .progress: - break + case .complete: + updateState { + $0.withUpdatedUpdatingAvatar(nil) + } + case .progress: + break } })) } diff --git a/TelegramUI/GridMessageItem.swift b/TelegramUI/GridMessageItem.swift index 101d086da9..1d933f9eaf 100644 --- a/TelegramUI/GridMessageItem.swift +++ b/TelegramUI/GridMessageItem.swift @@ -267,7 +267,7 @@ final class GridMessageItemNode: GridItemNode { badgeContent = .text(inset: 0.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: durationString)) } - strongSelf.mediaBadgeNode.update(theme: item.theme, content: badgeContent, mediaDownloadState: mediaDownloadState, alignment: .right, animated: false) + strongSelf.mediaBadgeNode.update(theme: item.theme, content: badgeContent, mediaDownloadState: mediaDownloadState, alignment: .right, animated: false, badgeAnimated: false) } } })) diff --git a/TelegramUI/InstantPageControllerNode.swift b/TelegramUI/InstantPageControllerNode.swift index 122e95513e..bd55f75905 100644 --- a/TelegramUI/InstantPageControllerNode.swift +++ b/TelegramUI/InstantPageControllerNode.swift @@ -338,23 +338,29 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { shouldUpdateVisibleItems = true self.updateNavigationBar() } + var didSetScrollOffset = false if resetOffset { var contentOffset = CGPoint(x: 0.0, y: -self.scrollNode.view.contentInset.top) if let state = self.initialState { - self.setupScrollOffsetOnLayout = false + didSetScrollOffset = true contentOffset = CGPoint(x: 0.0, y: CGFloat(state.contentOffset)) } else if let anchor = self.initialAnchor, !anchor.isEmpty { if let items = self.currentLayout?.items { - self.setupScrollOffsetOnLayout = false + didSetScrollOffset = true if let (item, lineOffset, _, _) = self.findAnchorItem(anchor, items: items) { contentOffset = CGPoint(x: 0.0, y: item.frame.minY + lineOffset - self.scrollNode.view.contentInset.top) } } } else { - self.setupScrollOffsetOnLayout = false + didSetScrollOffset = true } self.scrollNode.view.contentOffset = contentOffset + if didSetScrollOffset { + self.previousContentOffset = contentOffset + self.updateNavigationBar() + self.setupScrollOffsetOnLayout = false + } } if shouldUpdateVisibleItems { self.updateVisibleItems(visibleBounds: self.scrollNode.view.bounds) @@ -668,7 +674,9 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { } let delta: CGFloat - if let previousContentOffset = self.previousContentOffset { + if self.setupScrollOffsetOnLayout { + delta = 0.0 + } else if let previousContentOffset = self.previousContentOffset { delta = contentOffset.y - previousContentOffset.y } else { delta = 0.0 @@ -700,6 +708,10 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { navigationBarFrame.size.height = max(minBarHeight, min(maxBarHeight, navigationBarFrame.size.height)) } + if self.setupScrollOffsetOnLayout { + navigationBarFrame.size.height = maxBarHeight + } + let transitionFactor = (navigationBarFrame.size.height - minBarHeight) / (maxBarHeight - minBarHeight) if containerLayout.safeInsets.top.isZero { diff --git a/TelegramUI/ItemListActionItem.swift b/TelegramUI/ItemListActionItem.swift index 6d62a57cf2..a32a7c1c2e 100644 --- a/TelegramUI/ItemListActionItem.swift +++ b/TelegramUI/ItemListActionItem.swift @@ -92,7 +92,7 @@ class ItemListActionItemNode: ListViewItemNode, ItemListItemNode { private var item: ItemListActionItem? var tag: ItemListItemTag? { - return self.item?.tag + return self.item?.tag as? ItemListItemTag } init() { diff --git a/TelegramUI/LogoutOptionsController.swift b/TelegramUI/LogoutOptionsController.swift index d1adc9d0b6..e4104397ec 100644 --- a/TelegramUI/LogoutOptionsController.swift +++ b/TelegramUI/LogoutOptionsController.swift @@ -5,14 +5,6 @@ import Postbox import TelegramCore import LegacyComponents -private final class LogoutOptionsItemIcons { - static let addAccount = UIImage(bundleImageName: "Settings/MenuIcons/AddAccount")?.precomposed() - static let setPasscode = UIImage(bundleImageName: "Settings/MenuIcons/SetPasscode")?.precomposed() - static let clearCache = UIImage(bundleImageName: "Settings/MenuIcons/ClearCache")?.precomposed() - static let changePhoneNumber = UIImage(bundleImageName: "Settings/MenuIcons/ChangePhoneNumber")?.precomposed() - static let contactSupport = UIImage(bundleImageName: "Settings/MenuIcons/Support")?.precomposed() -} - private struct LogoutOptionsItemArguments { let addAccount: () -> Void let setPasscode: () -> Void @@ -76,23 +68,23 @@ private enum LogoutOptionsEntry: ItemListNodeEntry, Equatable { case let .alternativeHeader(theme, title): return ItemListSectionHeaderItem(theme: theme, text: title, sectionId: self.section) case let .addAccount(theme, title, text): - return ItemListDisclosureItem(theme: theme, icon: LogoutOptionsItemIcons.addAccount, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { + return ItemListDisclosureItem(theme: theme, icon: PresentationResourcesSettings.addAccount, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { arguments.addAccount() }) case let .setPasscode(theme, title, text): - return ItemListDisclosureItem(theme: theme, icon: LogoutOptionsItemIcons.setPasscode, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { + return ItemListDisclosureItem(theme: theme, icon: PresentationResourcesSettings.setPasscode, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { arguments.setPasscode() }) case let .clearCache(theme, title, text): - return ItemListDisclosureItem(theme: theme, icon: LogoutOptionsItemIcons.clearCache, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { + return ItemListDisclosureItem(theme: theme, icon: PresentationResourcesSettings.clearCache, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { arguments.clearCache() }) case let .changePhoneNumber(theme, title, text): - return ItemListDisclosureItem(theme: theme, icon: LogoutOptionsItemIcons.changePhoneNumber, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { + return ItemListDisclosureItem(theme: theme, icon: PresentationResourcesSettings.changePhoneNumber, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { arguments.changePhoneNumber() }) case let .contactSupport(theme, title, text): - return ItemListDisclosureItem(theme: theme, icon: LogoutOptionsItemIcons.contactSupport, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { + return ItemListDisclosureItem(theme: theme, icon: PresentationResourcesSettings.support, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { arguments.contactSupport() }) case let .logout(theme, title): diff --git a/TelegramUI/PhotoResources.swift b/TelegramUI/PhotoResources.swift index f39e513573..f067de1c0f 100644 --- a/TelegramUI/PhotoResources.swift +++ b/TelegramUI/PhotoResources.swift @@ -312,7 +312,6 @@ private func chatMessageVideoDatas(postbox: Postbox, fileReference: FileMediaRef |> mapToSignal { maybeData -> Signal<(Data?, (Data, String)?, Bool), NoError> in if maybeData.complete { let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) - return .single((nil, loadedData == nil ? nil : (loadedData!, maybeData.path), true)) } else { let thumbnail: Signal diff --git a/TelegramUI/PresentationResourcesSettings.swift b/TelegramUI/PresentationResourcesSettings.swift index db421bcca5..74d8f21d7f 100644 --- a/TelegramUI/PresentationResourcesSettings.swift +++ b/TelegramUI/PresentationResourcesSettings.swift @@ -2,6 +2,7 @@ import Foundation import Display struct PresentationResourcesSettings { + static let editProfile = UIImage(bundleImageName: "Settings/MenuIcons/EditProfile")?.precomposed() static let proxy = UIImage(bundleImageName: "Settings/MenuIcons/Proxy")?.precomposed() static let savedMessages = UIImage(bundleImageName: "Settings/MenuIcons/SavedMessages")?.precomposed() static let recentCalls = UIImage(bundleImageName: "Settings/MenuIcons/RecentCalls")?.precomposed() @@ -18,4 +19,9 @@ struct PresentationResourcesSettings { static let support = UIImage(bundleImageName: "Settings/MenuIcons/Support")?.precomposed() static let faq = UIImage(bundleImageName: "Settings/MenuIcons/Faq")?.precomposed() + + static let addAccount = UIImage(bundleImageName: "Settings/MenuIcons/AddAccount")?.precomposed() + static let setPasscode = UIImage(bundleImageName: "Settings/MenuIcons/SetPasscode")?.precomposed() + static let clearCache = UIImage(bundleImageName: "Settings/MenuIcons/ClearCache")?.precomposed() + static let changePhoneNumber = UIImage(bundleImageName: "Settings/MenuIcons/ChangePhoneNumber")?.precomposed() } diff --git a/TelegramUI/SelectivePrivacySettingsController.swift b/TelegramUI/SelectivePrivacySettingsController.swift index a3421b6067..6cf937cdbe 100644 --- a/TelegramUI/SelectivePrivacySettingsController.swift +++ b/TelegramUI/SelectivePrivacySettingsController.swift @@ -598,7 +598,6 @@ func selectivePrivacySettingsController(context: AccountContext, kind: Selective let actionsDisposable = DisposableSet() let updateSettingsDisposable = MetaDisposable() - actionsDisposable.add(updateSettingsDisposable) let arguments = SelectivePrivacySettingsControllerArguments(context: context, updateType: { type in updateState { @@ -721,71 +720,81 @@ func selectivePrivacySettingsController(context: AccountContext, kind: Selective actionsDisposable.dispose() } - let controller = ItemListController(context: context, state: signal) - controller.didDisappear = { [weak controller] _ in - if let controller = controller, controller.navigationController?.viewControllers.firstIndex(of: controller) == nil { - var wasSaving = false - var settings: SelectivePrivacySettings? - var callP2PSettings: SelectivePrivacySettings? - var callDataSaving: VoiceCallDataSaving? - var callIntegrationEnabled: Bool? - updateState { state in - wasSaving = state.saving - callDataSaving = state.callDataSaving - callIntegrationEnabled = state.callIntegrationEnabled - switch state.setting { - case .everybody: - settings = SelectivePrivacySettings.enableEveryone(disableFor: state.disableFor) - case .contacts: - settings = SelectivePrivacySettings.enableContacts(enableFor: state.enableFor, disableFor: state.disableFor) - case .nobody: - settings = SelectivePrivacySettings.disableEveryone(enableFor: state.enableFor) - } - - if case .voiceCalls = kind, let callP2PMode = state.callP2PMode, let disableFor = state.callP2PDisableFor, let enableFor = state.callP2PEnableFor { - switch callP2PMode { - case .everybody: - callP2PSettings = SelectivePrivacySettings.enableEveryone(disableFor: disableFor) - case .contacts: - callP2PSettings = SelectivePrivacySettings.enableContacts(enableFor: enableFor, disableFor: disableFor) - case .nobody: - callP2PSettings = SelectivePrivacySettings.disableEveryone(enableFor: enableFor) - } - } - - return state.withUpdatedSaving(true) + + let update: (Bool) -> Void = { save in + var wasSaving = false + var settings: SelectivePrivacySettings? + var callP2PSettings: SelectivePrivacySettings? + var callDataSaving: VoiceCallDataSaving? + var callIntegrationEnabled: Bool? + updateState { state in + wasSaving = state.saving + callDataSaving = state.callDataSaving + callIntegrationEnabled = state.callIntegrationEnabled + switch state.setting { + case .everybody: + settings = SelectivePrivacySettings.enableEveryone(disableFor: state.disableFor) + case .contacts: + settings = SelectivePrivacySettings.enableContacts(enableFor: state.enableFor, disableFor: state.disableFor) + case .nobody: + settings = SelectivePrivacySettings.disableEveryone(enableFor: state.enableFor) } - if let settings = settings, !wasSaving { - let type: UpdateSelectiveAccountPrivacySettingsType - switch kind { - case .presence: - type = .presence - case .groupInvitations: - type = .groupInvitations - case .voiceCalls: - type = .voiceCalls - case .profilePhoto: - type = .profilePhoto - case .forwards: - type = .forwards - } - - let updateSettingsSignal = updateSelectiveAccountPrivacySettings(account: context.account, type: type, settings: settings) - var updateCallP2PSettingsSignal: Signal = Signal.complete() - if let callP2PSettings = callP2PSettings { - updateCallP2PSettingsSignal = updateSelectiveAccountPrivacySettings(account: context.account, type: .voiceCallsP2P, settings: callP2PSettings) - } - - updateSettingsDisposable.set((combineLatest(updateSettingsSignal, updateCallP2PSettingsSignal) |> deliverOnMainQueue).start(completed: { - })) - - if case .voiceCalls = kind, let dataSaving = callDataSaving, let callP2PSettings = callP2PSettings, let systemIntegrationEnabled = callIntegrationEnabled { - updated(settings, (callP2PSettings, VoiceCallSettings(dataSaving: dataSaving, enableSystemIntegration: systemIntegrationEnabled))) - } else { - updated(settings, nil) + if case .voiceCalls = kind, let callP2PMode = state.callP2PMode, let disableFor = state.callP2PDisableFor, let enableFor = state.callP2PEnableFor { + switch callP2PMode { + case .everybody: + callP2PSettings = SelectivePrivacySettings.enableEveryone(disableFor: disableFor) + case .contacts: + callP2PSettings = SelectivePrivacySettings.enableContacts(enableFor: enableFor, disableFor: disableFor) + case .nobody: + callP2PSettings = SelectivePrivacySettings.disableEveryone(enableFor: enableFor) } } + + return state.withUpdatedSaving(true) + } + + if let settings = settings, !wasSaving { + let type: UpdateSelectiveAccountPrivacySettingsType + switch kind { + case .presence: + type = .presence + case .groupInvitations: + type = .groupInvitations + case .voiceCalls: + type = .voiceCalls + case .profilePhoto: + type = .profilePhoto + case .forwards: + type = .forwards + } + + let updateSettingsSignal = updateSelectiveAccountPrivacySettings(account: context.account, type: type, settings: settings) + var updateCallP2PSettingsSignal: Signal = Signal.complete() + if let callP2PSettings = callP2PSettings { + updateCallP2PSettingsSignal = updateSelectiveAccountPrivacySettings(account: context.account, type: .voiceCallsP2P, settings: callP2PSettings) + } + + updateSettingsDisposable.set((combineLatest(updateSettingsSignal, updateCallP2PSettingsSignal) |> deliverOnMainQueue).start(completed: { + })) + + if case .voiceCalls = kind, let dataSaving = callDataSaving, let callP2PSettings = callP2PSettings, let systemIntegrationEnabled = callIntegrationEnabled { + updated(settings, (callP2PSettings, VoiceCallSettings(dataSaving: dataSaving, enableSystemIntegration: systemIntegrationEnabled))) + } else { + updated(settings, nil) + } + } + } + + let controller = ItemListController(context: context, state: signal) + controller.willDisappear = { [weak controller] _ in + if let controller = controller, controller.navigationController?.viewControllers.firstIndex(of: controller) == nil { + update(false) + } + } + controller.didDisappear = { [weak controller] _ in + if let controller = controller, controller.navigationController?.viewControllers.firstIndex(of: controller) == nil { + update(true) } } diff --git a/TelegramUI/SettingsController.swift b/TelegramUI/SettingsController.swift index 60a7c83037..1e9752a192 100644 --- a/TelegramUI/SettingsController.swift +++ b/TelegramUI/SettingsController.swift @@ -1078,7 +1078,7 @@ public func settingsController(context: AccountContext, accountManager: AccountM presentControllerImpl?(v, a) }, pushController: { v in pushControllerImpl?(v) - }, getNavigationController: getNavigationControllerImpl) + }, getNavigationController: getNavigationControllerImpl, exceptionsList: notifyExceptions.get()) let (hasPassport, hasWatchApp) = hasPassportAndWatch let listState = ItemListNodeState(entries: settingsEntries(account: context.account, presentationData: presentationData, state: state, view: view, proxySettings: proxySettings, notifyExceptions: preferencesAndExceptions.1, notificationsAuthorizationStatus: preferencesAndExceptions.2, notificationsWarningSuppressed: preferencesAndExceptions.3, unreadTrendingStickerPacks: unreadTrendingStickerPacks, archivedPacks: featuredAndArchived.1, hasPassport: hasPassport, hasWatchApp: hasWatchApp, accountsAndPeers: accountsAndPeers.1, inAppNotificationSettings: inAppNotificationSettings), style: .blocks, searchItem: searchItem, initialScrollToItem: ListViewScrollToItem(index: 0, position: .top(-navigationBarSearchContentHeight), animated: false, curve: .Default(duration: 0.0), directionHint: .Up)) diff --git a/TelegramUI/SettingsSearchItem.swift b/TelegramUI/SettingsSearchItem.swift index 3a4d0aaebb..fa40ec9e20 100644 --- a/TelegramUI/SettingsSearchItem.swift +++ b/TelegramUI/SettingsSearchItem.swift @@ -19,6 +19,8 @@ extension NavigationBarSearchContentNode: ItemListControllerSearchNavigationCont extension SettingsSearchableItemIcon { func image() -> UIImage? { switch self { + case .profile: + return PresentationResourcesSettings.editProfile case .proxy: return PresentationResourcesSettings.proxy case .savedMessages: @@ -58,12 +60,13 @@ final class SettingsSearchItem: ItemListControllerSearch { let presentController: (ViewController, Any?) -> Void let pushController: (ViewController) -> Void let getNavigationController: (() -> NavigationController?)? + let exceptionsList: Signal private var updateActivity: ((Bool) -> Void)? private var activity: ValuePromise = ValuePromise(ignoreRepeated: false) private let activityDisposable = MetaDisposable() - init(context: AccountContext, theme: PresentationTheme, placeholder: String, activated: Bool, updateActivated: @escaping (Bool) -> Void, presentController: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, getNavigationController: (() -> NavigationController?)?) { + init(context: AccountContext, theme: PresentationTheme, placeholder: String, activated: Bool, updateActivated: @escaping (Bool) -> Void, presentController: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, getNavigationController: (() -> NavigationController?)?, exceptionsList: Signal) { self.context = context self.theme = theme self.placeholder = placeholder @@ -72,6 +75,7 @@ final class SettingsSearchItem: ItemListControllerSearch { self.presentController = presentController self.pushController = pushController self.getNavigationController = getNavigationController + self.exceptionsList = exceptionsList self.activityDisposable.set((activity.get() |> mapToSignal { value -> Signal in if value { return .single(value) |> delay(0.2, queue: Queue.mainQueue()) @@ -135,7 +139,7 @@ final class SettingsSearchItem: ItemListControllerSearch { pushController(c) }, presentController: { c, a in presentController(c, a) - }, getNavigationController: self.getNavigationController) + }, getNavigationController: self.getNavigationController, exceptionsList: self.exceptionsList) } } } @@ -217,7 +221,7 @@ private final class SettingsSearchContainerNode: SearchDisplayControllerContentN private let themeAndStringsPromise: Promise<(PresentationTheme, PresentationStrings)> - init(context: AccountContext, listState: LocalizationListState, openResult: @escaping (SettingsSearchableItem) -> Void) { + init(context: AccountContext, openResult: @escaping (SettingsSearchableItem) -> Void, exceptionsList: Signal) { self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.themeAndStringsPromise = Promise((self.presentationData.theme, self.presentationData.strings)) @@ -235,14 +239,20 @@ private final class SettingsSearchContainerNode: SearchDisplayControllerContentN self.addSubnode(self.dimNode) self.addSubnode(self.listNode) - let queryAndFoundItems = combineLatest(settingsSearchableItems(context: context), faqSearchableItems(context: context)) + let queryAndFoundItems = combineLatest(settingsSearchableItems(context: context, exceptionsList: exceptionsList), faqSearchableItems(context: context)) |> mapToSignal { searchableItems, faqSearchableItems -> Signal<(String, [SettingsSearchableItem])?, NoError> in return self.searchQuery.get() |> mapToSignal { query -> Signal<(String, [SettingsSearchableItem])?, NoError> in if let query = query, !query.isEmpty { - let result = searchSettingsItems(items: searchableItems, query: query) + let results = searchSettingsItems(items: searchableItems, query: query) let faqResults = searchSettingsItems(items: faqSearchableItems, query: query) - return .single((query, result + faqResults)) + let finalResults: [SettingsSearchableItem] + if faqResults.first?.id == .faq(0) { + finalResults = faqResults + results + } else { + finalResults = results + faqResults + } + return .single((query, finalResults)) } else { return .single(nil) } @@ -404,16 +414,18 @@ private final class SettingsSearchItemNode: ItemListControllerSearchNode { let pushController: (ViewController) -> Void let presentController: (ViewController, Any?) -> Void let getNavigationController: (() -> NavigationController?)? + let exceptionsList: Signal var cancel: () -> Void - init(context: AccountContext, cancel: @escaping () -> Void, updateActivity: @escaping(Bool) -> Void, pushController: @escaping (ViewController) -> Void, presentController: @escaping (ViewController, Any?) -> Void, getNavigationController: (() -> NavigationController?)?) { + init(context: AccountContext, cancel: @escaping () -> Void, updateActivity: @escaping(Bool) -> Void, pushController: @escaping (ViewController) -> Void, presentController: @escaping (ViewController, Any?) -> Void, getNavigationController: (() -> NavigationController?)?, exceptionsList: Signal) { self.context = context self.presentationData = context.sharedContext.currentPresentationData.with { $0 } + self.cancel = cancel self.pushController = pushController self.presentController = presentController self.getNavigationController = getNavigationController - self.cancel = cancel + self.exceptionsList = exceptionsList super.init() } @@ -428,7 +440,7 @@ private final class SettingsSearchItemNode: ItemListControllerSearchNode { return } - self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: SettingsSearchContainerNode(context: self.context, listState: LocalizationListState.defaultSettings, openResult: { [weak self] result in + self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: SettingsSearchContainerNode(context: self.context, openResult: { [weak self] result in if let strongSelf = self { result.present(strongSelf.context, strongSelf.getNavigationController?(), { [weak self] mode, controller in if let strongSelf = self { @@ -445,7 +457,7 @@ private final class SettingsSearchItemNode: ItemListControllerSearchNode { } }) } - }), cancel: { [weak self] in + }, exceptionsList: self.exceptionsList), cancel: { [weak self] in self?.cancel() }) diff --git a/TelegramUI/SettingsSearchableItems.swift b/TelegramUI/SettingsSearchableItems.swift index acbb4208ae..87db888065 100644 --- a/TelegramUI/SettingsSearchableItems.swift +++ b/TelegramUI/SettingsSearchableItems.swift @@ -7,6 +7,7 @@ import TelegramCore private let maximumNumberOfAccounts = 3 enum SettingsSearchableItemIcon { + case profile case proxy case savedMessages case calls @@ -55,7 +56,7 @@ struct SettingsSearchableItem { } private func profileSearchableItems(context: AccountContext, canAddAccount: Bool) -> [SettingsSearchableItem] { - let icon: SettingsSearchableItemIcon = .calls + let icon: SettingsSearchableItemIcon = .profile let strings = context.sharedContext.currentPresentationData.with { $0 }.strings let presentProfileSettings: (AccountContext, @escaping (SettingsSearchableItemPresentation, ViewController) -> Void, EditSettingsEntryTag?) -> Void = { context, present, itemTag in @@ -87,7 +88,7 @@ private func profileSearchableItems(context: AccountContext, canAddAccount: Bool })) if canAddAccount { items.append(SettingsSearchableItem(id: .profile(4), title: strings.Settings_AddAccount, alternate: [], icon: icon, breadcrumbs: [strings.EditProfile_Title], present: { context, _, present in - let isTestingEnvironment = context.account.testingEnvironment + let isTestingEnvironment = context.account.testingEnvironment context.sharedContext.beginNewAuth(testingEnvironment: isTestingEnvironment) })) } @@ -155,12 +156,12 @@ private func stickerSearchableItems(context: AccountContext) -> [SettingsSearcha ] } -private func notificationSearchableItems(context: AccountContext, notifyExceptions: Signal) -> [SettingsSearchableItem] { +private func notificationSearchableItems(context: AccountContext, exceptionsList: NotificationExceptionsList?) -> [SettingsSearchableItem] { let icon: SettingsSearchableItemIcon = .notifications let strings = context.sharedContext.currentPresentationData.with { $0 }.strings let presentNotificationSettings: (AccountContext, (SettingsSearchableItemPresentation, ViewController) -> Void, NotificationsAndSoundsEntryTag?) -> Void = { context, present, itemTag in - present(.push, notificationsAndSoundsController(context: context, exceptionsList: nil, focusOnItemTag: itemTag)) + present(.push, notificationsAndSoundsController(context: context, exceptionsList: exceptionsList, focusOnItemTag: itemTag)) } return [ @@ -475,7 +476,7 @@ private func appearanceSearchableItems(context: AccountContext) -> [SettingsSear ] } -func settingsSearchableItems(context: AccountContext) -> Signal<[SettingsSearchableItem], NoError> { +func settingsSearchableItems(context: AccountContext, exceptionsList: Signal) -> Signal<[SettingsSearchableItem], NoError> { let watchAppInstalled = (context.watchManager?.watchAppInstalled ?? .single(false)) |> take(1) let canAddAccount = activeAccountsAndPeers(context: context) @@ -483,8 +484,8 @@ func settingsSearchableItems(context: AccountContext) -> Signal<[SettingsSearcha |> map { accountsAndPeers -> Bool in return accountsAndPeers.1.count + 1 < maximumNumberOfAccounts } - return combineLatest(watchAppInstalled, canAddAccount) - |> map { watchAppInstalled, canAddAccount in + return combineLatest(watchAppInstalled, canAddAccount, exceptionsList) + |> map { watchAppInstalled, canAddAccount, exceptionsList in let strings = context.sharedContext.currentPresentationData.with { $0 }.strings var allItems: [SettingsSearchableItem] = [] @@ -503,7 +504,7 @@ func settingsSearchableItems(context: AccountContext) -> Signal<[SettingsSearcha let stickerItems = stickerSearchableItems(context: context) allItems.append(contentsOf: stickerItems) - let notificationItems = notificationSearchableItems(context: context, notifyExceptions: .complete()) + let notificationItems = notificationSearchableItems(context: context, exceptionsList: exceptionsList) allItems.append(contentsOf: notificationItems) let privacyItems = privacySearchableItems(context: context) @@ -535,13 +536,25 @@ func settingsSearchableItems(context: AccountContext) -> Signal<[SettingsSearcha }) allItems.append(passport) - let support = SettingsSearchableItem(id: .support(0), title: strings.Settings_Support, alternate: ["Support"], icon: .support, breadcrumbs: [], present: { context, _, present in - //return .push(ChatController(context: context, chatLocation: .peer(context.account.peerId))) + let support = SettingsSearchableItem(id: .support(0), title: strings.Settings_Support, alternate: [], icon: .support, breadcrumbs: [], present: { context, _, present in + let _ = (supportPeerId(account: context.account) + |> deliverOnMainQueue).start(next: { peerId in + if let peerId = peerId { + present(.push, ChatController(context: context, chatLocation: .peer(peerId))) + } + }) }) allItems.append(support) - let faq = SettingsSearchableItem(id: .faq(0), title: strings.Settings_FAQ, alternate: [], icon: .faq, breadcrumbs: [], present: { context, _, present in - //return .push(ChatController(context: context, chatLocation: .peer(context.account.peerId))) + let faq = SettingsSearchableItem(id: .faq(0), title: strings.Settings_FAQ, alternate: [], icon: .faq, breadcrumbs: [], present: { context, navigationController, present in + + let _ = (cachedFaqInstantPage(context: context) + |> deliverOnMainQueue).start(next: { resolvedUrl in + openResolvedUrl(resolvedUrl, context: context, navigationController: navigationController, openPeer: { peer, navigation in + }, present: { controller, arguments in + present(.push, controller) + }, dismissInput: {}) + }) }) allItems.append(faq) diff --git a/TelegramUI/SoftwareVideoThumbnailLayer.swift b/TelegramUI/SoftwareVideoThumbnailLayer.swift index 0829247779..d92cf36d4c 100644 --- a/TelegramUI/SoftwareVideoThumbnailLayer.swift +++ b/TelegramUI/SoftwareVideoThumbnailLayer.swift @@ -10,7 +10,7 @@ private final class SoftwareVideoThumbnailLayerNullAction: NSObject, CAAction { } final class SoftwareVideoThumbnailLayer: CALayer { - var disposable: Disposable? + var disposable = MetaDisposable() var ready: (() -> Void)? { didSet { @@ -28,8 +28,7 @@ final class SoftwareVideoThumbnailLayer: CALayer { self.masksToBounds = true if let dimensions = fileReference.media.dimensions { - self.disposable = (mediaGridMessageVideo(postbox: account.postbox, videoReference: fileReference) - |> deliverOn(Queue.concurrentDefaultQueue())).start(next: { [weak self] transform in + self.disposable.set((mediaGridMessageVideo(postbox: account.postbox, videoReference: fileReference)).start(next: { [weak self] transform in var boundingSize = dimensions.aspectFilled(CGSize(width: 93.0, height: 93.0)) let imageSize = boundingSize boundingSize.width = min(200.0, boundingSize.width) @@ -42,7 +41,7 @@ final class SoftwareVideoThumbnailLayer: CALayer { } } } - }) + })) } } @@ -51,7 +50,7 @@ final class SoftwareVideoThumbnailLayer: CALayer { } deinit { - self.disposable?.dispose() + self.disposable.dispose() } override func action(forKey event: String) -> CAAction? {