Various iOS 13 improvements

Dark Mode support
Share Suggestions support
Video playback position storing
Default browser selection
This commit is contained in:
Ilya Laktyushin 2019-10-08 21:23:31 +03:00
parent 221db9bc02
commit 03c7d2d99e
37 changed files with 4268 additions and 3797 deletions

View File

@ -24,6 +24,10 @@
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>IntentsSupported</key>
<array>
<string>INSendMessageIntent</string>
</array>
<key>NSExtensionActivationRule</key>
<string>SUBQUERY (
extensionItems,

View File

@ -257,6 +257,7 @@
<string>ddgQuickLink</string>
<string>moovit</string>
<string>alook</string>
<string>dgis</string>
</array>
<key>LSRequiresIPhoneOS</key>
<true/>

View File

@ -4906,3 +4906,11 @@ Any member of this group will be able to see messages in the channel.";
"Conversation.WalletRequiredSetup" = "Set Up";
"Wallet.CreateInvoice.Title" = "Create Invoice";
"AutoNightTheme.System" = "System";
"ChatSettings.OpenLinksIn" = "Open Links in";
"WebBrowser.Title" = "Web Browser";
"WebBrowser.DefaultBrowser" = "DEFAULT WEB BROWSER";
"WebBrowser.InAppSafari" = "In-App Safari";

View File

@ -9,8 +9,9 @@ public final class GalleryControllerActionInteraction {
public let openHashtag: (String?, String) -> Void
public let openBotCommand: (String) -> Void
public let addContact: (String) -> Void
public let storeMediaPlaybackState: (MessageId, Double?) -> Void
public init(openUrl: @escaping (String, Bool) -> Void, openUrlIn: @escaping (String) -> Void, openPeerMention: @escaping (String) -> Void, openPeer: @escaping (PeerId) -> Void, openHashtag: @escaping (String?, String) -> Void, openBotCommand: @escaping (String) -> Void, addContact: @escaping (String) -> Void) {
public init(openUrl: @escaping (String, Bool) -> Void, openUrlIn: @escaping (String) -> Void, openPeerMention: @escaping (String) -> Void, openPeer: @escaping (PeerId) -> Void, openHashtag: @escaping (String?, String) -> Void, openBotCommand: @escaping (String) -> Void, addContact: @escaping (String) -> Void, storeMediaPlaybackState: @escaping (MessageId, Double?) -> Void) {
self.openUrl = openUrl
self.openUrlIn = openUrlIn
self.openPeerMention = openPeerMention
@ -18,5 +19,6 @@ public final class GalleryControllerActionInteraction {
self.openHashtag = openHashtag
self.openBotCommand = openBotCommand
self.addContact = addContact
self.storeMediaPlaybackState = storeMediaPlaybackState
}
}

View File

@ -63,7 +63,7 @@ public func peerAvatarImageData(account: Account, peer: Peer, authorOfMessage: M
}
}
public func peerAvatarImage(account: Account, peer: Peer, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0), inset: CGFloat = 0.0, emptyColor: UIColor? = nil, synchronousLoad: Bool = false) -> Signal<UIImage?, NoError>? {
public func peerAvatarImage(account: Account, peer: Peer, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0), round: Bool = true, inset: CGFloat = 0.0, emptyColor: UIColor? = nil, synchronousLoad: Bool = false) -> Signal<UIImage?, NoError>? {
if let imageData = peerAvatarImageData(account: account, peer: peer, authorOfMessage: authorOfMessage, representation: representation, synchronousLoad: synchronousLoad) {
return imageData
|> mapToSignal { data -> Signal<UIImage?, NoError> in
@ -77,19 +77,29 @@ public func peerAvatarImage(account: Account, peer: Peer, authorOfMessage: Messa
context.clear(CGRect(origin: CGPoint(), size: displayDimensions))
context.setBlendMode(.copy)
context.draw(dataImage, in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
context.setBlendMode(.destinationOut)
context.draw(roundCorners.cgImage!, in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
if round {
context.setBlendMode(.destinationOut)
context.draw(roundCorners.cgImage!, in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
}
} else {
if let emptyColor = emptyColor {
context.clear(CGRect(origin: CGPoint(), size: displayDimensions))
context.setFillColor(emptyColor.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
if round {
context.fillEllipse(in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
} else {
context.fill(CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
}
}
}
} else if let emptyColor = emptyColor {
context.clear(CGRect(origin: CGPoint(), size: displayDimensions))
context.setFillColor(emptyColor.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
if round {
context.fillEllipse(in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
} else {
context.fill(CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
}
}
}))
}

View File

@ -65,7 +65,7 @@ public func childWindowHostView(parent: UIView) -> WindowHostView {
let hostView = WindowHostView(containerView: view, eventView: view, isRotating: {
return false
}, updateSupportedInterfaceOrientations: { orientations in
}, systemUserInterfaceStyle: .single(.light), updateSupportedInterfaceOrientations: { orientations in
}, updateDeferScreenEdgeGestures: { edges in
}, updatePrefersOnScreenNavigationHidden: { value in
})

View File

@ -12,6 +12,21 @@ private let defaultOrientations: UIInterfaceOrientationMask = {
}
}()
public enum WindowUserInterfaceStyle {
case light
case dark
@available(iOS 12.0, *)
fileprivate init(style: UIUserInterfaceStyle) {
switch style {
case .light, .unspecified:
self = .light
case .dark:
self = .dark
}
}
}
public final class PreviewingHostViewDelegate {
public let controllerForLocation: (UIView, CGPoint) -> (UIViewController, CGRect)?
public let commitController: (UIViewController) -> Void
@ -59,6 +74,11 @@ private final class WindowRootViewController: UIViewController, UIViewController
var presentController: ((UIViewController, PresentationSurfaceLevel, Bool, (() -> Void)?) -> Void)?
var transitionToSize: ((CGSize, Double) -> Void)?
private var _systemUserInterfaceStyle = ValuePromise<WindowUserInterfaceStyle>(ignoreRepeated: true)
var systemUserInterfaceStyle: Signal<WindowUserInterfaceStyle, NoError> {
return self._systemUserInterfaceStyle.get()
}
var orientations: UIInterfaceOrientationMask = defaultOrientations {
didSet {
if oldValue != self.orientations {
@ -106,6 +126,12 @@ private final class WindowRootViewController: UIViewController, UIViewController
return orientations
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
if #available(iOS 12.0, *) {
self._systemUserInterfaceStyle.set(WindowUserInterfaceStyle(style: self.traitCollection.userInterfaceStyle))
}
}
init() {
super.init(nibName: nil, bundle: nil)
@ -118,6 +144,12 @@ private final class WindowRootViewController: UIViewController, UIViewController
}
})
}
if #available(iOS 12.0, *) {
self._systemUserInterfaceStyle.set(WindowUserInterfaceStyle(style: self.traitCollection.userInterfaceStyle))
} else {
self._systemUserInterfaceStyle.set(.light)
}
}
required init?(coder aDecoder: NSCoder) {
@ -350,7 +382,7 @@ public func nativeWindowHostView() -> (UIWindow & WindowHost, WindowHostView) {
let hostView = WindowHostView(containerView: rootViewController.view, eventView: window, isRotating: {
return window.isRotating()
}, updateSupportedInterfaceOrientations: { orientations in
}, systemUserInterfaceStyle: rootViewController.systemUserInterfaceStyle, updateSupportedInterfaceOrientations: { orientations in
rootViewController.orientations = orientations
}, updateDeferScreenEdgeGestures: { edges in
rootViewController.gestureEdges = edges

View File

@ -16,6 +16,8 @@ public enum StatusBarStyle {
self = .White
case .blackOpaque:
self = .Black
case .darkContent:
self = .Black
}
}

View File

@ -203,6 +203,7 @@ public final class WindowHostView {
public let containerView: UIView
public let eventView: UIView
public let isRotating: () -> Bool
public let systemUserInterfaceStyle: Signal<WindowUserInterfaceStyle, NoError>
let updateSupportedInterfaceOrientations: (UIInterfaceOrientationMask) -> Void
let updateDeferScreenEdgeGestures: (UIRectEdge) -> Void
@ -223,10 +224,11 @@ public final class WindowHostView {
var forEachController: (((ContainableController) -> Void) -> Void)?
var getAccessibilityElements: (() -> [Any]?)?
init(containerView: UIView, eventView: UIView, isRotating: @escaping () -> Bool, updateSupportedInterfaceOrientations: @escaping (UIInterfaceOrientationMask) -> Void, updateDeferScreenEdgeGestures: @escaping (UIRectEdge) -> Void, updatePrefersOnScreenNavigationHidden: @escaping (Bool) -> Void) {
init(containerView: UIView, eventView: UIView, isRotating: @escaping () -> Bool, systemUserInterfaceStyle: Signal<WindowUserInterfaceStyle, NoError>, updateSupportedInterfaceOrientations: @escaping (UIInterfaceOrientationMask) -> Void, updateDeferScreenEdgeGestures: @escaping (UIRectEdge) -> Void, updatePrefersOnScreenNavigationHidden: @escaping (Bool) -> Void) {
self.containerView = containerView
self.eventView = eventView
self.isRotating = isRotating
self.systemUserInterfaceStyle = systemUserInterfaceStyle
self.updateSupportedInterfaceOrientations = updateSupportedInterfaceOrientations
self.updateDeferScreenEdgeGestures = updateDeferScreenEdgeGestures
self.updatePrefersOnScreenNavigationHidden = updatePrefersOnScreenNavigationHidden
@ -316,6 +318,8 @@ public class Window1 {
}
}
public let systemUserInterfaceStyle: Signal<WindowUserInterfaceStyle, NoError>
private var windowPanRecognizer: WindowPanRecognizer?
private let keyboardGestureRecognizerDelegate = WindowKeyboardGestureRecognizerDelegate()
private var keyboardGestureBeginLocation: CGPoint?
@ -330,6 +334,7 @@ public class Window1 {
public init(hostView: WindowHostView, statusBarHost: StatusBarHost?) {
self.hostView = hostView
self.systemUserInterfaceStyle = hostView.systemUserInterfaceStyle
//self.volumeControlStatusBar = VolumeControlStatusBar(frame: CGRect(origin: CGPoint(x: 0.0, y: -20.0), size: CGSize(width: 100.0, height: 20.0)), shouldBeVisible: statusBarHost?.handleVolumeControl ?? .single(false))
//self.volumeControlStatusBarNode = VolumeControlStatusBarNode()

View File

@ -139,7 +139,7 @@ private func galleryMessageCaptionText(_ message: Message) -> String {
return message.text
}
public func galleryItemForEntry(context: AccountContext, presentationData: PresentationData, entry: MessageHistoryEntry, isCentral: Bool = false, streamVideos: Bool, loopVideos: Bool = false, hideControls: Bool = false, fromPlayingVideo: Bool = false, landscape: Bool = false, timecode: Double? = nil, tempFilePath: String? = nil, playbackCompleted: @escaping () -> Void = {}, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void = { _ in }, openActionOptions: @escaping (GalleryControllerInteractionTapAction) -> Void = { _ in }) -> GalleryItem? {
public func galleryItemForEntry(context: AccountContext, presentationData: PresentationData, entry: MessageHistoryEntry, isCentral: Bool = false, streamVideos: Bool, loopVideos: Bool = false, hideControls: Bool = false, fromPlayingVideo: Bool = false, landscape: Bool = false, timecode: Double? = nil, tempFilePath: String? = nil, playbackCompleted: @escaping () -> Void = {}, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void = { _ in }, openActionOptions: @escaping (GalleryControllerInteractionTapAction) -> Void = { _ in }, storeMediaPlaybackState: @escaping (MessageId, Double?) -> Void = { _, _ in }) -> GalleryItem? {
let message = entry.message
let location = entry.location
if let (media, mediaImage) = mediaForMessage(message: message) {
@ -172,7 +172,7 @@ public func galleryItemForEntry(context: AccountContext, presentationData: Prese
}
let caption = galleryCaptionStringWithAppliedEntities(text, entities: entities)
return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: content, originData: GalleryItemOriginData(title: message.effectiveAuthor?.displayTitle, timestamp: message.timestamp), indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, contentInfo: .message(message), caption: caption, hideControls: hideControls, fromPlayingVideo: fromPlayingVideo, landscape: landscape, timecode: timecode, playbackCompleted: playbackCompleted, performAction: performAction, openActionOptions: openActionOptions)
return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: content, originData: GalleryItemOriginData(title: message.effectiveAuthor?.displayTitle, timestamp: message.timestamp), indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, contentInfo: .message(message), caption: caption, hideControls: hideControls, fromPlayingVideo: fromPlayingVideo, landscape: landscape, timecode: timecode, playbackCompleted: playbackCompleted, performAction: performAction, openActionOptions: openActionOptions, storeMediaPlaybackState: storeMediaPlaybackState)
} else {
if let fileName = file.fileName, (fileName as NSString).pathExtension.lowercased() == "json" {
return ChatAnimationGalleryItem(context: context, presentationData: presentationData, message: message, location: location)
@ -211,7 +211,7 @@ public func galleryItemForEntry(context: AccountContext, presentationData: Prese
}
}
if let content = content {
return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: content, originData: GalleryItemOriginData(title: message.effectiveAuthor?.displayTitle, timestamp: message.timestamp), indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, contentInfo: .message(message), caption: NSAttributedString(string: ""), fromPlayingVideo: fromPlayingVideo, landscape: landscape, timecode: timecode, performAction: performAction, openActionOptions: openActionOptions)
return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: content, originData: GalleryItemOriginData(title: message.effectiveAuthor?.displayTitle, timestamp: message.timestamp), indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, contentInfo: .message(message), caption: NSAttributedString(string: ""), fromPlayingVideo: fromPlayingVideo, landscape: landscape, timecode: timecode, performAction: performAction, openActionOptions: openActionOptions, storeMediaPlaybackState: storeMediaPlaybackState)
} else {
return nil
}
@ -434,7 +434,7 @@ public class GalleryController: ViewController, StandalonePresentableController
if entry.message.stableId == strongSelf.centralEntryStableId {
isCentral = true
}
if let item = galleryItemForEntry(context: context, presentationData: strongSelf.presentationData, entry: entry, isCentral: isCentral, streamVideos: streamSingleVideo, fromPlayingVideo: isCentral && fromPlayingVideo, landscape: isCentral && landscape, timecode: isCentral ? timecode : nil, performAction: strongSelf.performAction, openActionOptions: strongSelf.openActionOptions) {
if let item = galleryItemForEntry(context: context, presentationData: strongSelf.presentationData, entry: entry, isCentral: isCentral, streamVideos: streamSingleVideo, fromPlayingVideo: isCentral && fromPlayingVideo, landscape: isCentral && landscape, timecode: isCentral ? timecode : nil, performAction: strongSelf.performAction, openActionOptions: strongSelf.openActionOptions, storeMediaPlaybackState: strongSelf.actionInteraction?.storeMediaPlaybackState ?? { _, _ in }) {
if isCentral {
centralItemIndex = items.count
}
@ -856,7 +856,7 @@ public class GalleryController: ViewController, StandalonePresentableController
if entry.message.stableId == self.centralEntryStableId {
isCentral = true
}
if let item = galleryItemForEntry(context: self.context, presentationData: self.presentationData, entry: entry, streamVideos: self.streamVideos, fromPlayingVideo: isCentral && self.fromPlayingVideo, landscape: isCentral && self.landscape, timecode: isCentral ? self.timecode : nil, performAction: self.performAction, openActionOptions: self.openActionOptions) {
if let item = galleryItemForEntry(context: self.context, presentationData: self.presentationData, entry: entry, streamVideos: self.streamVideos, fromPlayingVideo: isCentral && self.fromPlayingVideo, landscape: isCentral && self.landscape, timecode: isCentral ? self.timecode : nil, performAction: self.performAction, openActionOptions: self.openActionOptions, storeMediaPlaybackState: self.actionInteraction?.storeMediaPlaybackState ?? { _, _ in }) {
if isCentral {
centralItemIndex = items.count
}

View File

@ -33,8 +33,9 @@ public class UniversalVideoGalleryItem: GalleryItem {
let playbackCompleted: () -> Void
let performAction: (GalleryControllerInteractionTapAction) -> Void
let openActionOptions: (GalleryControllerInteractionTapAction) -> Void
let storeMediaPlaybackState: (MessageId, Double?) -> Void
public init(context: AccountContext, presentationData: PresentationData, content: UniversalVideoContent, originData: GalleryItemOriginData?, indexData: GalleryItemIndexData?, contentInfo: UniversalVideoGalleryItemContentInfo?, caption: NSAttributedString, credit: NSAttributedString? = nil, hideControls: Bool = false, fromPlayingVideo: Bool = false, landscape: Bool = false, timecode: Double? = nil, playbackCompleted: @escaping () -> Void = {}, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void, openActionOptions: @escaping (GalleryControllerInteractionTapAction) -> Void) {
public init(context: AccountContext, presentationData: PresentationData, content: UniversalVideoContent, originData: GalleryItemOriginData?, indexData: GalleryItemIndexData?, contentInfo: UniversalVideoGalleryItemContentInfo?, caption: NSAttributedString, credit: NSAttributedString? = nil, hideControls: Bool = false, fromPlayingVideo: Bool = false, landscape: Bool = false, timecode: Double? = nil, playbackCompleted: @escaping () -> Void = {}, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void, openActionOptions: @escaping (GalleryControllerInteractionTapAction) -> Void, storeMediaPlaybackState: @escaping (MessageId, Double?) -> Void) {
self.context = context
self.presentationData = presentationData
self.content = content
@ -50,6 +51,7 @@ public class UniversalVideoGalleryItem: GalleryItem {
self.playbackCompleted = playbackCompleted
self.performAction = performAction
self.openActionOptions = openActionOptions
self.storeMediaPlaybackState = storeMediaPlaybackState
}
public func node() -> GalleryItemNode {
@ -178,6 +180,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
private var item: UniversalVideoGalleryItem?
private let statusDisposable = MetaDisposable()
private let mediaPlaybackStateDisposable = MetaDisposable()
private let fetchDisposable = MetaDisposable()
private var fetchStatus: MediaResourceStatus?
@ -304,6 +307,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
deinit {
self.statusDisposable.dispose()
self.scrubbingFrameDisposable?.dispose()
self.mediaPlaybackStateDisposable.dispose()
}
override func ready() -> Signal<Void, NoError> {
@ -400,6 +404,21 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
if let contentInfo = item.contentInfo, case let .message(message) = contentInfo {
if Namespaces.Message.allScheduled.contains(message.id.namespace) {
disablePictureInPicture = true
} else {
let throttledSignal = videoNode.status
|> mapToThrottled { next -> Signal<MediaPlayerStatus?, NoError> in
return .single(next) |> then(.complete() |> delay(4.0, queue: Queue.concurrentDefaultQueue()))
}
self.mediaPlaybackStateDisposable.set(throttledSignal.start(next: { status in
if let status = status, status.duration > 60.0 * 20.0 {
var timestamp: Double?
if status.timestamp > 5.0 && status.timestamp < status.duration - 5.0 {
timestamp = status.timestamp
}
item.storeMediaPlaybackState(message.id, timestamp)
}
}))
}
var file: TelegramMediaFile?

View File

@ -112,7 +112,7 @@ public struct InstantPageGalleryEntry: Equatable {
nativeId = .instantPage(self.pageId, file.fileId)
}
return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: NativeVideoContent(id: nativeId, fileReference: .webPage(webPage: WebpageReference(webPage), media: file), streamVideo: isMediaStreamable(media: file) ? .conservative : .none), originData: nil, indexData: indexData, contentInfo: .webPage(webPage, file), caption: caption, credit: credit, fromPlayingVideo: fromPlayingVideo, landscape: landscape, performAction: { _ in }, openActionOptions: { _ in })
return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: NativeVideoContent(id: nativeId, fileReference: .webPage(webPage: WebpageReference(webPage), media: file), streamVideo: isMediaStreamable(media: file) ? .conservative : .none), originData: nil, indexData: indexData, contentInfo: .webPage(webPage, file), caption: caption, credit: credit, fromPlayingVideo: fromPlayingVideo, landscape: landscape, performAction: { _ in }, openActionOptions: { _ in }, storeMediaPlaybackState: { _, _ in })
} else {
var representations: [TelegramMediaImageRepresentation] = []
representations.append(contentsOf: file.previewRepresentations)
@ -124,7 +124,7 @@ public struct InstantPageGalleryEntry: Equatable {
}
} else if let embedWebpage = self.media.media as? TelegramMediaWebpage, case let .Loaded(webpageContent) = embedWebpage.content {
if let content = WebEmbedVideoContent(webPage: embedWebpage, webpageContent: webpageContent) {
return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: content, originData: nil, indexData: nil, contentInfo: .webPage(webPage, embedWebpage), caption: NSAttributedString(string: ""), fromPlayingVideo: fromPlayingVideo, landscape: landscape, performAction: { _ in }, openActionOptions: { _ in })
return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: content, originData: nil, indexData: nil, contentInfo: .webPage(webPage, embedWebpage), caption: NSAttributedString(string: ""), fromPlayingVideo: fromPlayingVideo, landscape: landscape, performAction: { _ in }, openActionOptions: { _ in }, storeMediaPlaybackState: { _, _ in })
} else {
preconditionFailure()
}

View File

@ -285,10 +285,10 @@ func instantPageThemeTypeForSettingsAndTime(themeSettings: PresentationThemeSett
var fallback = true
if let themeSettings = themeSettings {
if case .none = themeSettings.automaticThemeSwitchSetting.trigger {
if case .explicitNone = themeSettings.automaticThemeSwitchSetting.trigger {
} else {
fallback = false
useDarkTheme = automaticThemeShouldSwitchNow(settings: themeSettings.automaticThemeSwitchSetting, currentTheme: themeSettings.theme)
useDarkTheme = automaticThemeShouldSwitchNow(settings: themeSettings.automaticThemeSwitchSetting, systemUserInterfaceStyle: .light)
}
}
if fallback, let time = time {

View File

@ -191,18 +191,20 @@ public class ItemListMultilineTextItemNode: ListViewItemNode {
var boldFont = titleBoldFont
var italicFont = titleItalicFont
var boldItalicFont = titleBoldItalicFont
var alignment = NSTextAlignment.natural
if case .monospace = item.font {
baseFont = Font.monospace(17.0)
linkFont = Font.monospace(17.0)
boldFont = Font.semiboldMonospace(17.0)
italicFont = Font.italicMonospace(17.0)
boldItalicFont = Font.semiboldItalicMonospace(17.0)
alignment = .justified
}
let entities = generateTextEntities(item.text, enabledTypes: item.enabledEntityTypes)
let string = stringWithAppliedEntities(item.text, entities: entities, baseColor: textColor, linkColor: item.theme.list.itemAccentColor, baseFont: baseFont, linkFont: linkFont, boldFont: boldFont, italicFont: italicFont, boldItalicFont: boldItalicFont, fixedFont: titleFixedFont, blockQuoteFont: titleFont)
let (titleLayout, titleApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: string, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (titleLayout, titleApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: string, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: alignment, cutout: nil, insets: UIEdgeInsets()))
let contentSize: CGSize
let insets: UIEdgeInsets

View File

@ -24,10 +24,12 @@ public enum OpenInAction {
}
public final class OpenInOption {
public let identifier: String
public let application: OpenInApplication
public let action: () -> OpenInAction
public init(application: OpenInApplication, action: @escaping () -> OpenInAction) {
public init(identifier: String, application: OpenInApplication, action: @escaping () -> OpenInAction) {
self.!identifier = identifier
self.application = application
self.action = action
}
@ -60,11 +62,11 @@ private func allOpenInOptions(context: AccountContext, item: OpenInItem) -> [Ope
var options: [OpenInOption] = []
switch item {
case let .url(url):
options.append(OpenInOption(application: .safari, action: {
options.append(OpenInOption(identifier: "safari", application: .safari, action: {
return .openUrl(url: url)
}))
options.append(OpenInOption(application: .other(title: "Chrome", identifier: 535886823, scheme: "googlechrome", store: nil), action: {
options.append(OpenInOption(identifier: "chrome", application: .other(title: "Chrome", identifier: 535886823, scheme: "googlechrome", store: nil), action: {
if let url = URL(string: url), var components = URLComponents(url: url, resolvingAgainstBaseURL: true) {
components.scheme = components.scheme == "https" ? "googlechromes" : "googlechrome"
if let url = components.string {
@ -74,21 +76,21 @@ private func allOpenInOptions(context: AccountContext, item: OpenInItem) -> [Ope
return .none
}))
options.append(OpenInOption(application: .other(title: "Firefox", identifier: 989804926, scheme: "firefox", store: nil), action: {
options.append(OpenInOption(identifier: "firefox", application: .other(title: "Firefox", identifier: 989804926, scheme: "firefox", store: nil), action: {
if let escapedUrl = url.addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) {
return .openUrl(url: "firefox://open-url?url=\(escapedUrl)")
}
return .none
}))
options.append(OpenInOption(application: .other(title: "Firefox Focus", identifier: 1055677337, scheme: "firefox-focus", store: nil), action: {
options.append(OpenInOption(identifier: "firefoxFocus", application: .other(title: "Firefox Focus", identifier: 1055677337, scheme: "firefox-focus", store: nil), action: {
if let escapedUrl = url.addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) {
return .openUrl(url: "firefox-focus://open-url?url=\(escapedUrl)")
}
return .none
}))
options.append(OpenInOption(application: .other(title: "Opera Mini", identifier: 363729560, scheme: "opera-http", store: "es"), action: {
options.append(OpenInOption(identifier: "operaMini", application: .other(title: "Opera Mini", identifier: 363729560, scheme: "opera-http", store: "es"), action: {
if let url = URL(string: url), var components = URLComponents(url: url, resolvingAgainstBaseURL: true) {
components.scheme = components.scheme == "https" ? "opera-https" : "opera-http"
if let url = components.string {
@ -98,7 +100,7 @@ private func allOpenInOptions(context: AccountContext, item: OpenInItem) -> [Ope
return .none
}))
options.append(OpenInOption(application: .other(title: "Opera Touch", identifier: 1411869974, scheme: "touch-http", store: nil), action: {
options.append(OpenInOption(identifier: "operaTouch", application: .other(title: "Opera Touch", identifier: 1411869974, scheme: "touch-http", store: nil), action: {
if let url = URL(string: url), var components = URLComponents(url: url, resolvingAgainstBaseURL: true) {
components.scheme = components.scheme == "https" ? "touch-https" : "touch-http"
if let url = components.string {
@ -108,18 +110,18 @@ private func allOpenInOptions(context: AccountContext, item: OpenInItem) -> [Ope
return .none
}))
options.append(OpenInOption(application: .other(title: "Yandex", identifier: 483693909, scheme: "yandexbrowser-open-url", store: nil), action: {
options.append(OpenInOption(identifier: "yandex", application: .other(title: "Yandex", identifier: 483693909, scheme: "yandexbrowser-open-url", store: nil), action: {
if let escapedUrl = url.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) {
return .openUrl(url: "yandexbrowser-open-url://\(escapedUrl)")
}
return .none
}))
options.append(OpenInOption(application: .other(title: "DuckDuckGo", identifier: 663592361, scheme: "ddgQuickLink", store: nil), action: {
options.append(OpenInOption(identifier: "duckDuckGo", application: .other(title: "DuckDuckGo", identifier: 663592361, scheme: "ddgQuickLink", store: nil), action: {
return .openUrl(url: "ddgQuickLink://\(url)")
}))
options.append(OpenInOption(application: .other(title: "Alook Browser", identifier: 1261944766, scheme: "alook", store: nil), action: {
options.append(OpenInOption(identifier: "alook", application: .other(title: "Alook Browser", identifier: 1261944766, scheme: "alook", store: nil), action: {
return .openUrl(url: "alook://\(url)")
}))
@ -129,17 +131,17 @@ private func allOpenInOptions(context: AccountContext, item: OpenInItem) -> [Ope
if !withDirections {
if let venue = location.venue, let venueId = venue.id, let provider = venue.provider, provider == "foursquare" {
options.append(OpenInOption(application: .other(title: "Foursquare", identifier: 306934924, scheme: "foursquare", store: nil), action: {
options.append(OpenInOption(identifier: "foursquare", application: .other(title: "Foursquare", identifier: 306934924, scheme: "foursquare", store: nil), action: {
return .openUrl(url: "foursquare://venues/\(venueId)")
}))
}
}
options.append(OpenInOption(application: .maps, action: {
options.append(OpenInOption(identifier: "appleMaps", application: .maps, action: {
return .openLocation(latitude: lat, longitude: lon, withDirections: withDirections)
}))
options.append(OpenInOption(application: .other(title: "Google Maps", identifier: 585027354, scheme: "comgooglemaps-x-callback", store: nil), action: {
options.append(OpenInOption(identifier: "googleMaps", application: .other(title: "Google Maps", identifier: 585027354, scheme: "comgooglemaps-x-callback", store: nil), action: {
let coordinates = "\(lat),\(lon)"
if withDirections {
return .openUrl(url: "comgooglemaps-x-callback://?daddr=\(coordinates)&directionsmode=driving&x-success=telegram://?resume=true&x-source=Telegram")
@ -148,7 +150,7 @@ private func allOpenInOptions(context: AccountContext, item: OpenInItem) -> [Ope
}
}))
options.append(OpenInOption(application: .other(title: "Yandex.Maps", identifier: 313877526, scheme: "yandexmaps", store: nil), action: {
options.append(OpenInOption(identifier: "yandexMaps", application: .other(title: "Yandex.Maps", identifier: 313877526, scheme: "yandexmaps", store: nil), action: {
if withDirections {
return .openUrl(url: "yandexmaps://build_route_on_map?lat_to=\(lat)&lon_to=\(lon)")
} else {
@ -156,7 +158,7 @@ private func allOpenInOptions(context: AccountContext, item: OpenInItem) -> [Ope
}
}))
options.append(OpenInOption(application: .other(title: "Uber", identifier: 368677368, scheme: "uber", store: nil), action: {
options.append(OpenInOption(identifier: "uber", application: .other(title: "Uber", identifier: 368677368, scheme: "uber", store: nil), action: {
let dropoffName: String
let dropoffAddress: String
if let title = location.venue?.title.addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed), title.count > 0 {
@ -172,12 +174,12 @@ private func allOpenInOptions(context: AccountContext, item: OpenInItem) -> [Ope
return .openUrl(url: "uber://?client_id=&action=setPickup&pickup=my_location&dropoff[latitude]=\(lat)&dropoff[longitude]=\(lon)&dropoff[nickname]=\(dropoffName)&dropoff[formatted_address]=\(dropoffAddress)")
}))
options.append(OpenInOption(application: .other(title: "Lyft", identifier: 529379082, scheme: "lyft", store: nil), action: {
options.append(OpenInOption(identifier: "lyft", application: .other(title: "Lyft", identifier: 529379082, scheme: "lyft", store: nil), action: {
return .openUrl(url: "lyft://ridetype?id=lyft&destination[latitude]=\(lat)&destination[longitude]=\(lon)")
}))
if withDirections {
options.append(OpenInOption(application: .other(title: "Citymapper", identifier: 469463298, scheme: "citymapper", store: nil), action: {
options.append(OpenInOption(identifier: "citymapper", application: .other(title: "Citymapper", identifier: 469463298, scheme: "citymapper", store: nil), action: {
let endName: String
let endAddress: String
if let title = location.venue?.title.addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed), title.count > 0 {
@ -192,13 +194,17 @@ private func allOpenInOptions(context: AccountContext, item: OpenInItem) -> [Ope
}
return .openUrl(url: "citymapper://directions?endcoord=\(lat),\(lon)&endname=\(endName)&endaddress=\(endAddress)")
}))
options.append(OpenInOption(identifier: "2gis", application: .other(title: "2GIS", identifier: 481627348, scheme: "dgis", store: nil), action: {
return .openUrl(url: "dgis://2gis.ru/routeSearch/rsType/car/to/\(lon),\(lat)")
}))
options.append(OpenInOption(application: .other(title: "Yandex.Navi", identifier: 474500851, scheme: "yandexnavi", store: nil), action: {
options.append(OpenInOption(identifier: "yandexNavi", application: .other(title: "Yandex.Navi", identifier: 474500851, scheme: "yandexnavi", store: nil), action: {
return .openUrl(url: "yandexnavi://build_route_on_map?lat_to=\(lat)&lon_to=\(lon)")
}))
}
options.append(OpenInOption(application: .other(title: "Moovit", identifier: 498477945, scheme: "moovit", store: nil), action: {
options.append(OpenInOption(identifier: "moovit", application: .other(title: "Moovit", identifier: 498477945, scheme: "moovit", store: nil), action: {
if withDirections {
let destName: String
if let title = location.venue?.title.addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed), title.count > 0 {
@ -213,12 +219,12 @@ private func allOpenInOptions(context: AccountContext, item: OpenInItem) -> [Ope
}))
if !withDirections {
options.append(OpenInOption(application: .other(title: "HERE Maps", identifier: 955837609, scheme: "here-location", store: nil), action: {
options.append(OpenInOption(identifier: "hereMaps", application: .other(title: "HERE Maps", identifier: 955837609, scheme: "here-location", store: nil), action: {
return .openUrl(url: "here-location://\(lat),\(lon)")
}))
}
options.append(OpenInOption(application: .other(title: "Waze", identifier: 323229106, scheme: "waze", store: nil), action: {
options.append(OpenInOption(identifier: "waze", application: .other(title: "Waze", identifier: 323229106, scheme: "waze", store: nil), action: {
let url = "waze://?ll=\(lat),\(lon)"
if withDirections {
return .openUrl(url: url.appending("&navigate=yes"))

View File

@ -39,6 +39,7 @@ private enum AutodownloadMediaCategorySection: Int32 {
case master
case dataUsage
case types
case lowDataMode
}
private enum AutodownloadMediaCategoryEntry: ItemListNodeEntry {
@ -50,6 +51,8 @@ private enum AutodownloadMediaCategoryEntry: ItemListNodeEntry {
case videos(PresentationTheme, String, String, Bool)
case files(PresentationTheme, String, String, Bool)
case voiceMessagesInfo(PresentationTheme, String)
case lowDataMode(PresentationTheme, String, Bool)
case lowDataModeInfo(PresentationTheme, String)
var section: ItemListSectionId {
switch self {
@ -59,6 +62,8 @@ private enum AutodownloadMediaCategoryEntry: ItemListNodeEntry {
return AutodownloadMediaCategorySection.dataUsage.rawValue
case .typesHeader, .photos, .videos, .files, .voiceMessagesInfo:
return AutodownloadMediaCategorySection.types.rawValue
case .lowDataMode, .lowDataModeInfo:
return AutodownloadMediaCategorySection.lowDataMode.rawValue
}
}
@ -80,6 +85,10 @@ private enum AutodownloadMediaCategoryEntry: ItemListNodeEntry {
return 6
case .voiceMessagesInfo:
return 7
case .lowDataMode:
return 8
case .lowDataModeInfo:
return 9
}
}
@ -133,6 +142,18 @@ private enum AutodownloadMediaCategoryEntry: ItemListNodeEntry {
} else {
return false
}
case let .lowDataMode(lhsTheme, lhsText, lhsValue):
if case let .lowDataMode(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .lowDataModeInfo(lhsTheme, lhsText):
if case let .lowDataModeInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
}
}
@ -169,6 +190,12 @@ private enum AutodownloadMediaCategoryEntry: ItemListNodeEntry {
})
case let .voiceMessagesInfo(theme, text):
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
case let .lowDataMode(theme, text, value):
return ItemListSwitchItem(theme: theme, title: text, value: value, enableInteractiveChanges: true, enabled: true, sectionId: self.section, style: .blocks, updated: { value in
})
case let .lowDataModeInfo(theme, text):
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
}
}
}
@ -272,6 +299,11 @@ private func autodownloadMediaConnectionTypeControllerEntries(presentationData:
entries.append(.files(presentationData.theme, presentationData.strings.AutoDownloadSettings_Files, stringForAutomaticDownloadPeers(strings: presentationData.strings, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator, peers: file, category: .file), master))
entries.append(.voiceMessagesInfo(presentationData.theme, presentationData.strings.AutoDownloadSettings_VoiceMessagesInfo))
if #available(iOSApplicationExtension 13.0, iOS 13.0, *) {
entries.append(.lowDataMode(presentationData.theme, "Follow Low Data Mode", true))
entries.append(.lowDataModeInfo(presentationData.theme, "The app won't download media automatically if Low Data Mode is active."))
}
return entries
}

View File

@ -9,6 +9,7 @@ import TelegramPresentationData
import TelegramUIPreferences
import ItemListUI
import AccountContext
import OpenInExternalAppUI
private final class DataAndStorageControllerArguments {
let openStorageUsage: () -> Void
@ -22,8 +23,9 @@ private final class DataAndStorageControllerArguments {
let toggleAutoplayGifs: (Bool) -> Void
let toggleAutoplayVideos: (Bool) -> Void
let toggleDownloadInBackground: (Bool) -> Void
let openBrowserSelection: () -> Void
init(openStorageUsage: @escaping () -> Void, openNetworkUsage: @escaping () -> Void, openProxy: @escaping () -> Void, openAutomaticDownloadConnectionType: @escaping (AutomaticDownloadConnectionType) -> Void, resetAutomaticDownload: @escaping () -> Void, openVoiceUseLessData: @escaping () -> Void, openSaveIncomingPhotos: @escaping () -> Void, toggleSaveEditedPhotos: @escaping (Bool) -> Void, toggleAutoplayGifs: @escaping (Bool) -> Void, toggleAutoplayVideos: @escaping (Bool) -> Void, toggleDownloadInBackground: @escaping (Bool) -> Void) {
init(openStorageUsage: @escaping () -> Void, openNetworkUsage: @escaping () -> Void, openProxy: @escaping () -> Void, openAutomaticDownloadConnectionType: @escaping (AutomaticDownloadConnectionType) -> Void, resetAutomaticDownload: @escaping () -> Void, openVoiceUseLessData: @escaping () -> Void, openSaveIncomingPhotos: @escaping () -> Void, toggleSaveEditedPhotos: @escaping (Bool) -> Void, toggleAutoplayGifs: @escaping (Bool) -> Void, toggleAutoplayVideos: @escaping (Bool) -> Void, toggleDownloadInBackground: @escaping (Bool) -> Void, openBrowserSelection: @escaping () -> Void) {
self.openStorageUsage = openStorageUsage
self.openNetworkUsage = openNetworkUsage
self.openProxy = openProxy
@ -35,6 +37,7 @@ private final class DataAndStorageControllerArguments {
self.toggleAutoplayGifs = toggleAutoplayGifs
self.toggleAutoplayVideos = toggleAutoplayVideos
self.toggleDownloadInBackground = toggleDownloadInBackground
self.openBrowserSelection = openBrowserSelection
}
}
@ -78,6 +81,7 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
case otherHeader(PresentationTheme, String)
case saveIncomingPhotos(PresentationTheme, String)
case saveEditedPhotos(PresentationTheme, String, Bool)
case openLinksIn(PresentationTheme, String, String)
case downloadInBackground(PresentationTheme, String, Bool)
case downloadInBackgroundInfo(PresentationTheme, String)
case connectionHeader(PresentationTheme, String)
@ -93,7 +97,7 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
return DataAndStorageSection.autoPlay.rawValue
case .voiceCallsHeader, .useLessVoiceData:
return DataAndStorageSection.voiceCalls.rawValue
case .otherHeader, .saveIncomingPhotos, .saveEditedPhotos, .downloadInBackground, .downloadInBackgroundInfo:
case .otherHeader, .saveIncomingPhotos, .saveEditedPhotos, .openLinksIn, .downloadInBackground, .downloadInBackgroundInfo:
return DataAndStorageSection.other.rawValue
case .connectionHeader, .connectionProxy:
return DataAndStorageSection.connection.rawValue
@ -130,14 +134,16 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
return 12
case .saveEditedPhotos:
return 13
case .downloadInBackground:
case .openLinksIn:
return 14
case .downloadInBackgroundInfo:
case .downloadInBackground:
return 15
case .connectionHeader:
case .downloadInBackgroundInfo:
return 16
case .connectionProxy:
case .connectionHeader:
return 17
case .connectionProxy:
return 18
}
}
@ -227,6 +233,12 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
} else {
return false
}
case let .openLinksIn(lhsTheme, lhsText, lhsValue):
if case let .openLinksIn(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .downloadInBackground(lhsTheme, lhsText, lhsValue):
if case let .downloadInBackground(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
@ -311,6 +323,10 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
return ItemListSwitchItem(theme: theme, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.toggleSaveEditedPhotos(value)
}, tag: DataAndStorageEntryTag.saveEditedPhotos)
case let .openLinksIn(theme, text, value):
return ItemListDisclosureItem(theme: theme, title: text, label: value, sectionId: self.section, style: .blocks, action: {
arguments.openBrowserSelection()
})
case let .downloadInBackground(theme, text, value):
return ItemListSwitchItem(theme: theme, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.toggleDownloadInBackground(value)
@ -413,7 +429,7 @@ private func stringForAutoDownloadSetting(strings: PresentationStrings, decimalS
}
}
private func dataAndStorageControllerEntries(state: DataAndStorageControllerState, data: DataAndStorageData, presentationData: PresentationData) -> [DataAndStorageEntry] {
private func dataAndStorageControllerEntries(state: DataAndStorageControllerState, data: DataAndStorageData, presentationData: PresentationData, defaultWebBrowser: String) -> [DataAndStorageEntry] {
var entries: [DataAndStorageEntry] = []
entries.append(.storageUsage(presentationData.theme, presentationData.strings.ChatSettings_Cache))
@ -437,6 +453,9 @@ private func dataAndStorageControllerEntries(state: DataAndStorageControllerStat
entries.append(.otherHeader(presentationData.theme, presentationData.strings.ChatSettings_Other))
entries.append(.saveIncomingPhotos(presentationData.theme, presentationData.strings.Settings_SaveIncomingPhotos))
entries.append(.saveEditedPhotos(presentationData.theme, presentationData.strings.Settings_SaveEditedPhotos, data.generatedMediaStoreSettings.storeEditedPhotos))
entries.append(.openLinksIn(presentationData.theme, presentationData.strings.ChatSettings_OpenLinksIn, defaultWebBrowser))
entries.append(.downloadInBackground(presentationData.theme, presentationData.strings.ChatSettings_DownloadInBackground, data.automaticMediaDownloadSettings.downloadInBackground))
entries.append(.downloadInBackgroundInfo(presentationData.theme, presentationData.strings.ChatSettings_DownloadInBackgroundInfo))
@ -556,19 +575,30 @@ func dataAndStorageController(context: AccountContext, focusOnItemTag: DataAndSt
settings.autoplayVideos = value
return settings
}).start()
}, toggleDownloadInBackground: { value in
}, toggleDownloadInBackground: { value in
let _ = updateMediaDownloadSettingsInteractively(accountManager: context.sharedContext.accountManager, { settings in
var settings = settings
settings.downloadInBackground = value
return settings
}).start()
}, openBrowserSelection: {
let controller = webBrowserSettingsController(context: context)
pushControllerImpl?(controller)
})
let signal = combineLatest(context.sharedContext.presentationData, statePromise.get(), dataAndStorageDataPromise.get()) |> deliverOnMainQueue
|> map { presentationData, state, dataAndStorageData -> (ItemListControllerState, (ItemListNodeState, Any)) in
let signal = combineLatest(context.sharedContext.presentationData, statePromise.get(), dataAndStorageDataPromise.get(), context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.webBrowserSettings])) |> deliverOnMainQueue
|> map { presentationData, state, dataAndStorageData, sharedData -> (ItemListControllerState, (ItemListNodeState, Any)) in
let webBrowserSettings = (sharedData.entries[ApplicationSpecificSharedDataKeys.webBrowserSettings] as? WebBrowserSettings) ?? WebBrowserSettings.defaultSettings
let options = availableOpenInOptions(context: context, item: .url(url: "http://telegram.org"))
let defaultWebBrowser: String
if let option = options.first(where: { $0.identifier == webBrowserSettings.defaultWebBrowser }) {
defaultWebBrowser = option.title
} else {
defaultWebBrowser = presentationData.strings.WebBrowser_InAppSafari
}
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.ChatSettings_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
let listState = ItemListNodeState(entries: dataAndStorageControllerEntries(state: state, data: dataAndStorageData, presentationData: presentationData), style: .blocks, ensureVisibleItemTag: focusOnItemTag, emptyStateItem: nil, animateChanges: false)
let listState = ItemListNodeState(entries: dataAndStorageControllerEntries(state: state, data: dataAndStorageData, presentationData: presentationData, defaultWebBrowser: defaultWebBrowser), style: .blocks, ensureVisibleItemTag: focusOnItemTag, emptyStateItem: nil, animateChanges: false)
return (controllerState, (listState, arguments))
} |> afterDisposed {

View File

@ -0,0 +1,125 @@
import Foundation
import UIKit
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
import TelegramPresentationData
import TelegramUIPreferences
import ItemListUI
import AccountContext
import OpenInExternalAppUI
private final class WebBrowserSettingsControllerArguments {
let updateDefaultBrowser: (String?) -> Void
init(updateDefaultBrowser: @escaping (String?) -> Void) {
self.updateDefaultBrowser = updateDefaultBrowser
}
}
private enum WebBrowserSettingsSection: Int32 {
case browsers
}
private enum WebBrowserSettingsControllerEntry: ItemListNodeEntry {
case browserHeader(PresentationTheme, String)
case browser(PresentationTheme, String, String?, Bool, Int32)
var section: ItemListSectionId {
switch self {
case .browserHeader, .browser:
return WebBrowserSettingsSection.browsers.rawValue
}
}
var stableId: Int32 {
switch self {
case .browserHeader:
return 0
case let .browser(_, _, _, _, index):
return 1 + index
}
}
static func ==(lhs: WebBrowserSettingsControllerEntry, rhs: WebBrowserSettingsControllerEntry) -> Bool {
switch lhs {
case let .browserHeader(lhsTheme, lhsText):
if case let .browserHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .browser(lhsTheme, lhsTitle, lhsIdentifier, lhsSelected, lhsIndex):
if case let .browser(rhsTheme, rhsTitle, rhsIdentifier, rhsSelected, rhsIndex) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsIdentifier == rhsIdentifier, lhsSelected == rhsSelected, lhsIndex == rhsIndex {
return true
} else {
return false
}
}
}
static func <(lhs: WebBrowserSettingsControllerEntry, rhs: WebBrowserSettingsControllerEntry) -> Bool {
return lhs.stableId < rhs.stableId
}
func item(_ arguments: Any) -> ListViewItem {
let arguments = arguments as! WebBrowserSettingsControllerArguments
switch self {
case let .browserHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .browser(theme, title, identifier, selected, _):
return ItemListCheckboxItem(theme: theme, title: title, style: .right, checked: selected, zeroSeparatorInsets: false, sectionId: self.section) {
arguments.updateDefaultBrowser(identifier)
}
}
}
}
private func webBrowserSettingsControllerEntries(context: AccountContext, presentationData: PresentationData, selectedBrowser: String?) -> [WebBrowserSettingsControllerEntry] {
var entries: [WebBrowserSettingsControllerEntry] = []
let options = availableOpenInOptions(context: context, item: .url(url: "http://telegram.org"))
entries.append(.browserHeader(presentationData.theme, presentationData.strings.WebBrowser_DefaultBrowser))
entries.append(.browser(presentationData.theme, presentationData.strings.WebBrowser_InAppSafari, nil, selectedBrowser == nil, 0))
var index: Int32 = 1
for option in options {
entries.append(.browser(presentationData.theme, option.title, option.identifier, option.identifier == selectedBrowser, index))
index += 1
}
return entries
}
public func webBrowserSettingsController(context: AccountContext) -> ViewController {
var pushControllerImpl: ((ViewController) -> Void)?
var presentControllerImpl: ((ViewController) -> Void)?
let updateDisposable = MetaDisposable()
let arguments = WebBrowserSettingsControllerArguments(updateDefaultBrowser: { identifier in
let _ = updateWebBrowserSettingsInteractively(accountManager: context.sharedContext.accountManager, { $0.withUpdatedDefaultWebBrowser(identifier) }).start()
})
let signal = combineLatest(context.sharedContext.presentationData, context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.webBrowserSettings]))
|> deliverOnMainQueue
|> map { presentationData, sharedData -> (ItemListControllerState, (ItemListNodeState, Any)) in
let settings = (sharedData.entries[ApplicationSpecificSharedDataKeys.webBrowserSettings] as? WebBrowserSettings) ?? WebBrowserSettings.defaultSettings
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.WebBrowser_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
let listState = ItemListNodeState(entries: webBrowserSettingsControllerEntries(context: context, presentationData: presentationData, selectedBrowser: settings.defaultWebBrowser), style: .blocks, animateChanges: false)
return (controllerState, (listState, arguments))
}
let controller = ItemListController(context: context, state: signal)
pushControllerImpl = { [weak controller] c in
(controller?.navigationController as? NavigationController)?.pushViewController(c)
}
presentControllerImpl = { [weak controller] c in
controller?.present(c, in: .window(.root))
}
return controller
}

View File

@ -14,6 +14,7 @@ import Geocoding
import WallpaperResources
private enum TriggerMode {
case system
case none
case timeBased
case brightness
@ -51,6 +52,7 @@ private enum ThemeAutoNightSettingsControllerSection: Int32 {
}
private enum ThemeAutoNightSettingsControllerEntry: ItemListNodeEntry {
case modeSystem(PresentationTheme, String, Bool)
case modeDisabled(PresentationTheme, String, Bool)
case modeTimeBased(PresentationTheme, String, Bool)
case modeBrightness(PresentationTheme, String, Bool)
@ -68,7 +70,7 @@ private enum ThemeAutoNightSettingsControllerEntry: ItemListNodeEntry {
var section: ItemListSectionId {
switch self {
case .modeDisabled, .modeTimeBased, .modeBrightness:
case .modeSystem, .modeDisabled, .modeTimeBased, .modeBrightness:
return ThemeAutoNightSettingsControllerSection.mode.rawValue
case .settingsHeader, .timeBasedAutomaticLocation, .timeBasedAutomaticLocationValue, .timeBasedManualFrom, .timeBasedManualTo, .brightnessValue, .settingInfo:
return ThemeAutoNightSettingsControllerSection.settings.rawValue
@ -79,35 +81,43 @@ private enum ThemeAutoNightSettingsControllerEntry: ItemListNodeEntry {
var stableId: Int32 {
switch self {
case .modeDisabled:
case .modeSystem:
return 0
case .modeTimeBased:
case .modeDisabled:
return 1
case .modeBrightness:
case .modeTimeBased:
return 2
case .settingsHeader:
case .modeBrightness:
return 3
case .timeBasedAutomaticLocation:
case .settingsHeader:
return 4
case .timeBasedAutomaticLocationValue:
case .timeBasedAutomaticLocation:
return 5
case .timeBasedManualFrom:
case .timeBasedAutomaticLocationValue:
return 6
case .timeBasedManualTo:
case .timeBasedManualFrom:
return 7
case .brightnessValue:
case .timeBasedManualTo:
return 8
case .settingInfo:
case .brightnessValue:
return 9
case .themeHeader:
case .settingInfo:
return 10
case .themeItem:
case .themeHeader:
return 11
case .themeItem:
return 12
}
}
static func ==(lhs: ThemeAutoNightSettingsControllerEntry, rhs: ThemeAutoNightSettingsControllerEntry) -> Bool {
switch lhs {
case let .modeSystem(lhsTheme, lhsTitle, lhsValue):
if case let .modeSystem(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue {
return true
} else {
return false
}
case let .modeDisabled(lhsTheme, lhsTitle, lhsValue):
if case let .modeDisabled(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue {
return true
@ -190,6 +200,10 @@ private enum ThemeAutoNightSettingsControllerEntry: ItemListNodeEntry {
func item(_ arguments: Any) -> ListViewItem {
let arguments = arguments as! ThemeAutoNightSettingsControllerArguments
switch self {
case let .modeSystem(theme, title, value):
return ItemListCheckboxItem(theme: theme, title: title, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.updateMode(.system)
})
case let .modeDisabled(theme, title, value):
return ItemListCheckboxItem(theme: theme, title: title, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.updateMode(.none)
@ -241,7 +255,13 @@ private func themeAutoNightSettingsControllerEntries(theme: PresentationTheme, s
let activeTriggerMode: TriggerMode
switch switchSetting.trigger {
case .none:
case .system:
if #available(iOSApplicationExtension 13.0, iOS 13.0, *) {
activeTriggerMode = .system
} else {
activeTriggerMode = .none
}
case .explicitNone:
activeTriggerMode = .none
case .timeBased:
activeTriggerMode = .timeBased
@ -249,12 +269,15 @@ private func themeAutoNightSettingsControllerEntries(theme: PresentationTheme, s
activeTriggerMode = .brightness
}
if #available(iOSApplicationExtension 13.0, iOS 13.0, *) {
entries.append(.modeSystem(theme, strings.AutoNightTheme_System, activeTriggerMode == .system))
}
entries.append(.modeDisabled(theme, strings.AutoNightTheme_Disabled, activeTriggerMode == .none))
entries.append(.modeTimeBased(theme, strings.AutoNightTheme_Scheduled, activeTriggerMode == .timeBased))
entries.append(.modeBrightness(theme, strings.AutoNightTheme_Automatic, activeTriggerMode == .brightness))
switch switchSetting.trigger {
case .none:
case .system, .explicitNone:
break
case let .timeBased(setting):
entries.append(.settingsHeader(theme, strings.AutoNightTheme_ScheduleSection))
@ -284,9 +307,9 @@ private func themeAutoNightSettingsControllerEntries(theme: PresentationTheme, s
}
switch switchSetting.trigger {
case .none:
case .explicitNone:
break
case .timeBased, .brightness:
case .system, .timeBased, .brightness:
entries.append(.themeHeader(theme, strings.AutoNightTheme_PreferredTheme))
entries.append(.themeItem(theme, strings, availableThemes, switchSetting.theme, settings.themeSpecificAccentColors))
}
@ -303,7 +326,7 @@ private func roundTimeToDay(_ timestamp: Int32) -> Int32 {
private func areSettingsValid(_ settings: AutomaticThemeSwitchSetting) -> Bool {
switch settings.trigger {
case .none:
case .system, .explicitNone, .brightness:
return true
case let .timeBased(setting):
switch setting {
@ -316,8 +339,6 @@ private func areSettingsValid(_ settings: AutomaticThemeSwitchSetting) -> Bool {
case .manual:
return true
}
case .brightness:
return true
}
}
@ -384,8 +405,10 @@ public func themeAutoNightSettingsController(context: AccountContext) -> ViewCon
updateSettings { settings in
var settings = settings
switch mode {
case .system:
settings.trigger = .system
case .none:
settings.trigger = .none
settings.trigger = .explicitNone
case .timeBased:
if case .timeBased = settings.trigger {
} else {

View File

@ -363,7 +363,13 @@ private func themeSettingsControllerEntries(presentationData: PresentationData,
let title: String
switch autoNightSettings.trigger {
case .none:
case .system:
if #available(iOSApplicationExtension 13.0, iOS 13.0, *) {
title = strings.AutoNightTheme_System
} else {
title = strings.AutoNightTheme_Disabled
}
case .explicitNone:
title = strings.AutoNightTheme_Disabled
case .timeBased:
title = strings.AutoNightTheme_Scheduled

View File

@ -386,7 +386,7 @@ public class WallpaperGalleryController: ViewController {
let _ = (updatePresentationThemeSettingsInteractively(accountManager: strongSelf.context.sharedContext.accountManager, { current in
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
var chatWallpaper = current.chatWallpaper
if automaticThemeShouldSwitchNow(settings: current.automaticThemeSwitchSetting, currentTheme: current.theme) {
if automaticThemeShouldSwitchNow(settings: current.automaticThemeSwitchSetting, systemUserInterfaceStyle: .light) {
themeSpecificChatWallpapers[current.automaticThemeSwitchSetting.theme.index] = wallpaper
} else {
themeSpecificChatWallpapers[current.theme.index] = wallpaper

View File

@ -281,7 +281,7 @@ public final class ShareController: ViewController {
private let peers = Promise<([(RenderedPeer, PeerPresence?)], Peer)>()
private let peersDisposable = MetaDisposable()
private let readyDisposable = MetaDisposable()
private let acountActiveDisposable = MetaDisposable()
private let accountActiveDisposable = MetaDisposable()
private var defaultAction: ShareControllerAction?
@ -411,7 +411,7 @@ public final class ShareController: ViewController {
deinit {
self.peersDisposable.dispose()
self.readyDisposable.dispose()
self.acountActiveDisposable.dispose()
self.accountActiveDisposable.dispose()
}
override public func loadDisplayNode() {
@ -787,7 +787,7 @@ public final class ShareController: ViewController {
private func switchToAccount(account: Account, animateIn: Bool) {
self.currentAccount = account
self.acountActiveDisposable.set(self.sharedContext.setAccountUserInterfaceInUse(account.id))
self.accountActiveDisposable.set(self.sharedContext.setAccountUserInterfaceInUse(account.id))
self.peers.set(combineLatest(
self.currentAccount.postbox.loadedPeerWithId(self.currentAccount.peerId)
@ -838,4 +838,8 @@ public final class ShareController: ViewController {
}
}))
}
public func send(peerId: PeerId) {
self.controllerNode.send(peerId: peerId)
}
}

View File

@ -498,7 +498,11 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
}
@objc func actionButtonPressed() {
if self.controllerInteraction!.selectedPeers.isEmpty {
}
func send(peerId: PeerId?) {
if peerId == nil && self.controllerInteraction!.selectedPeers.isEmpty {
if let defaultAction = self.defaultAction {
defaultAction.action()
}
@ -513,13 +517,20 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
}
self.inputFieldNode.deactivateInput()
let transition = ContainedViewLayoutTransition.animated(duration: 0.12, curve: .easeInOut)
let transition: ContainedViewLayoutTransition = peerId != nil ? .immediate : .animated(duration: 0.12, curve: .easeInOut)
transition.updateAlpha(node: self.actionButtonNode, alpha: 0.0)
transition.updateAlpha(node: self.inputFieldNode, alpha: 0.0)
transition.updateAlpha(node: self.actionSeparatorNode, alpha: 0.0)
transition.updateAlpha(node: self.actionsBackgroundNode, alpha: 0.0)
if let signal = self.share?(self.inputFieldNode.text, self.controllerInteraction!.selectedPeers.map { $0.peerId }) {
let peerIds: [PeerId]
if let peerId = peerId {
peerIds = [peerId]
} else {
peerIds = self.controllerInteraction!.selectedPeers.map { $0.peerId }
}
if let signal = self.share?(self.inputFieldNode.text, peerIds) {
self.transitionToContentNode(ShareLoadingContainerNode(theme: self.presentationData.theme, forceNativeAppearance: true), fastOut: true)
let timestamp = CACurrentMediaTime()
var wasDone = false

View File

@ -254,7 +254,7 @@ public func currentPresentationDataAndSettings(accountManager: AccountManager) -
var effectiveChatWallpaper: TelegramWallpaper = themeSettings.chatWallpaper
let parameters = AutomaticThemeSwitchParameters(settings: themeSettings.automaticThemeSwitchSetting)
if automaticThemeShouldSwitchNow(parameters, currentTheme: themeSettings.theme) {
if automaticThemeShouldSwitchNow(parameters, systemUserInterfaceStyle: .light) {
effectiveTheme = themeSettings.automaticThemeSwitchSetting.theme
} else {
effectiveTheme = themeSettings.theme
@ -295,7 +295,8 @@ private func roundTimeToDay(_ timestamp: Int32) -> Int32 {
}
private enum PreparedAutomaticThemeSwitchTrigger {
case none
case explicitNone
case system
case time(fromSeconds: Int32, toSeconds: Int32)
case brightness(threshold: Double)
}
@ -307,8 +308,10 @@ private struct AutomaticThemeSwitchParameters {
init(settings: AutomaticThemeSwitchSetting) {
let trigger: PreparedAutomaticThemeSwitchTrigger
switch settings.trigger {
case .none:
trigger = .none
case .system:
trigger = .system
case .explicitNone:
trigger = .explicitNone
case let .timeBased(setting):
let fromValue: Int32
let toValue: Int32
@ -330,10 +333,12 @@ private struct AutomaticThemeSwitchParameters {
}
}
private func automaticThemeShouldSwitchNow(_ parameters: AutomaticThemeSwitchParameters, currentTheme: PresentationThemeReference) -> Bool {
private func automaticThemeShouldSwitchNow(_ parameters: AutomaticThemeSwitchParameters, systemUserInterfaceStyle: WindowUserInterfaceStyle) -> Bool {
switch parameters.trigger {
case .none:
case .explicitNone:
return false
case .system:
return systemUserInterfaceStyle == .dark
case let .time(fromValue, toValue):
let roundedTimestamp = roundTimeToDay(Int32(Date().timeIntervalSince1970))
if roundedTimestamp >= fromValue || roundedTimestamp <= toValue {
@ -346,21 +351,21 @@ private func automaticThemeShouldSwitchNow(_ parameters: AutomaticThemeSwitchPar
}
}
public func automaticThemeShouldSwitchNow(settings: AutomaticThemeSwitchSetting, currentTheme: PresentationThemeReference) -> Bool {
public func automaticThemeShouldSwitchNow(settings: AutomaticThemeSwitchSetting, systemUserInterfaceStyle: WindowUserInterfaceStyle) -> Bool {
let parameters = AutomaticThemeSwitchParameters(settings: settings)
return automaticThemeShouldSwitchNow(parameters, currentTheme: currentTheme)
return automaticThemeShouldSwitchNow(parameters, systemUserInterfaceStyle: systemUserInterfaceStyle)
}
private func automaticThemeShouldSwitch(_ settings: AutomaticThemeSwitchSetting, currentTheme: PresentationThemeReference) -> Signal<Bool, NoError> {
if case .none = settings.trigger {
private func automaticThemeShouldSwitch(_ settings: AutomaticThemeSwitchSetting, systemUserInterfaceStyle: WindowUserInterfaceStyle) -> Signal<Bool, NoError> {
if case .explicitNone = settings.trigger {
return .single(false)
} else {
return Signal { subscriber in
let parameters = AutomaticThemeSwitchParameters(settings: settings)
subscriber.putNext(automaticThemeShouldSwitchNow(parameters, currentTheme: currentTheme))
subscriber.putNext(automaticThemeShouldSwitchNow(parameters, systemUserInterfaceStyle: systemUserInterfaceStyle))
let timer = SwiftSignalKit.Timer(timeout: 1.0, repeat: true, completion: {
subscriber.putNext(automaticThemeShouldSwitchNow(parameters, currentTheme: currentTheme))
subscriber.putNext(automaticThemeShouldSwitchNow(parameters, systemUserInterfaceStyle: systemUserInterfaceStyle))
}, queue: Queue.mainQueue())
timer.start()
@ -472,9 +477,9 @@ public func chatServiceBackgroundColor(wallpaper: TelegramWallpaper, mediaBox: M
}
}
public func updatedPresentationData(accountManager: AccountManager, applicationInForeground: Signal<Bool, NoError>) -> Signal<PresentationData, NoError> {
return accountManager.sharedData(keys: [SharedDataKeys.localizationSettings, ApplicationSpecificSharedDataKeys.presentationThemeSettings, ApplicationSpecificSharedDataKeys.contactSynchronizationSettings])
|> mapToSignal { sharedData -> Signal<PresentationData, NoError> in
public func updatedPresentationData(accountManager: AccountManager, applicationInForeground: Signal<Bool, NoError>, systemUserInterfaceStyle: Signal<WindowUserInterfaceStyle, NoError>) -> Signal<PresentationData, NoError> {
return combineLatest(accountManager.sharedData(keys: [SharedDataKeys.localizationSettings, ApplicationSpecificSharedDataKeys.presentationThemeSettings, ApplicationSpecificSharedDataKeys.contactSynchronizationSettings]), systemUserInterfaceStyle)
|> mapToSignal { sharedData, systemUserInterfaceStyle -> Signal<PresentationData, NoError> in
let themeSettings: PresentationThemeSettings
if let current = sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings] as? PresentationThemeSettings {
themeSettings = current
@ -497,7 +502,7 @@ public func updatedPresentationData(accountManager: AccountManager, applicationI
return applicationInForeground
|> mapToSignal { inForeground -> Signal<PresentationData, NoError> in
if inForeground {
return automaticThemeShouldSwitch(themeSettings.automaticThemeSwitchSetting, currentTheme: themeSettings.theme)
return automaticThemeShouldSwitch(themeSettings.automaticThemeSwitchSetting, systemUserInterfaceStyle: systemUserInterfaceStyle)
|> distinctUntilChanged
|> map { shouldSwitch in
var effectiveTheme: PresentationThemeReference

View File

@ -44,6 +44,7 @@ import PeerInfoUI
import RaiseToListen
import UrlHandling
import ReactionSelectionNode
import AvatarNode
import MessageReactionListUI
import AppBundle
import WalletUI
@ -513,6 +514,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let strongSelf = self {
strongSelf.controllerInteraction?.addContact(phoneNumber)
}
}, storeMediaPlaybackState: { [weak self] messageId, timestamp in
var storedState: MediaPlaybackStoredState?
if let timestamp = timestamp {
storedState = MediaPlaybackStoredState(timestamp: timestamp, playbackRate: .x1)
}
let _ = updateMediaPlaybackStoredStateInteractively(postbox: strongSelf.context.account.postbox, messageId: messageId, state: storedState).start()
})))
}, openPeer: { [weak self] id, navigation, fromMessage in
self?.openPeer(peerId: id, navigation: navigation, fromMessage: fromMessage)
@ -2746,7 +2753,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
})
strongSelf.donateIntent()
strongSelf.donateSendMessageIntent()
}
}
@ -5642,7 +5649,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
})
self.donateIntent()
self.donateSendMessageIntent()
} else {
let mode: ChatScheduleTimeControllerMode
if peerId == self.context.account.peerId {
@ -7681,17 +7688,32 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
})
}
private func donateIntent() {
private func donateSendMessageIntent() {
guard case let .peer(peerId) = self.chatLocation, peerId.namespace == Namespaces.Peer.CloudUser && peerId != context.account.peerId else {
return
}
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
let _ = (self.context.account.postbox.loadedPeerWithId(peerId)
|> deliverOnMainQueue).start(next: { peer in
|> mapToSignal { peer -> Signal<(Peer, UIImage?), NoError> in
let avatarImage = peerAvatarImage(account: self.context.account, peer: peer, authorOfMessage: nil, representation: peer.smallProfileImage, round: false) ?? .single(nil)
return avatarImage
|> map { avatarImage in
return (peer, avatarImage)
}
}
|> deliverOnMainQueue).start(next: { peer, avatarImage in
if let peer = peer as? TelegramUser {
let recipientHandle = INPersonHandle(value: "tg\(peerId.id)", type: .unknown)
let recipient = INPerson(personHandle: recipientHandle, nameComponents: nil, displayName: peer.displayTitle, image: nil, contactIdentifier: nil, customIdentifier: "tg\(peerId.id)")
let intent = INSendMessageIntent(recipients: [recipient], content: nil, groupName: nil, serviceName: nil, sender: nil)
var nameComponents = PersonNameComponents()
nameComponents.givenName = peer.firstName
nameComponents.familyName = peer.lastName
let recipient = INPerson(personHandle: recipientHandle, nameComponents: nameComponents, displayName: peer.displayTitle, image: nil, contactIdentifier: nil, customIdentifier: "tg\(peerId.id)")
let intent = INSendMessageIntent(recipients: [recipient], content: nil, speakableGroupName: INSpeakableString(spokenPhrase: peer.displayTitle), conversationIdentifier: "tg\(peerId.id)", serviceName: nil, sender: nil)
//let intent = INSendMessageIntent(recipients: [recipient], content: nil, groupName: INSpeakableString(spokenPhrase: peer.displayTitle), serviceName: nil, sender: nil)
if #available(iOS 12.0, *), let avatarImage = avatarImage, let avatarImageData = avatarImage.jpegData(compressionQuality: 0.8) {
intent.setImage(INImage(imageData: avatarImageData), forParameterNamed: \.groupName)
}
let interaction = INInteraction(intent: intent, response: nil)
interaction.direction = .outgoing
interaction.donate { error in

View File

@ -50,6 +50,7 @@ private var telegramUIDeclaredEncodables: Void = {
declareEncodable(VoipDerivedState.self, f: { VoipDerivedState(decoder: $0) })
declareEncodable(ChatArchiveSettings.self, f: { ChatArchiveSettings(decoder: $0) })
declareEncodable(MediaPlaybackStoredState.self, f: { MediaPlaybackStoredState(decoder: $0) })
declareEncodable(WebBrowserSettings.self, f: { WebBrowserSettings(decoder: $0) })
return
}()

View File

@ -26,7 +26,7 @@ private enum ChatMessageGalleryControllerData {
case stickerPack(StickerPackReference)
case audio(TelegramMediaFile)
case document(TelegramMediaFile)
case gallery(GalleryController)
case gallery(Signal<GalleryController, NoError>)
case secretGallery(SecretMediaPreviewController)
case chatAvatars(AvatarGalleryController, Media)
case theme(TelegramMediaFile)
@ -148,7 +148,7 @@ private func chatMessageGalleryControllerData(context: AccountContext, message:
let gallery = GalleryController(context: context, source: .peerMessagesAtId(message.id), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in
navigationController?.replaceTopController(controller, animated: false, ready: ready)
}, baseNavigationController: navigationController, actionInteraction: actionInteraction)
return .gallery(gallery)
return .gallery(.single(gallery))
}
}
#if DEBUG
@ -156,7 +156,7 @@ private func chatMessageGalleryControllerData(context: AccountContext, message:
let gallery = GalleryController(context: context, source: standalone ? .standaloneMessage(message) : .peerMessagesAtId(message.id), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in
navigationController?.replaceTopController(controller, animated: false, ready: ready)
}, baseNavigationController: navigationController, actionInteraction: actionInteraction)
return .gallery(gallery)
return .gallery(.single(gallery))
}
#endif
}
@ -165,7 +165,7 @@ private func chatMessageGalleryControllerData(context: AccountContext, message:
let gallery = GalleryController(context: context, source: .peerMessagesAtId(message.id), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in
navigationController?.replaceTopController(controller, animated: false, ready: ready)
}, baseNavigationController: navigationController, actionInteraction: actionInteraction)
return .gallery(gallery)
return .gallery(.single(gallery))
}
if !file.isVideo {
@ -177,11 +177,25 @@ private func chatMessageGalleryControllerData(context: AccountContext, message:
let gallery = SecretMediaPreviewController(context: context, messageId: message.id)
return .secretGallery(gallery)
} else {
let gallery = GalleryController(context: context, source: standalone ? .standaloneMessage(message) : .peerMessagesAtId(message.id), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in
navigationController?.replaceTopController(controller, animated: false, ready: ready)
let startTimecode: Signal<Double?, NoError>
if let timecode = timecode {
startTimecode = .single(timecode)
} else {
startTimecode = mediaPlaybackStoredState(postbox: context.account.postbox, messageId: message.id)
|> map { state in
return state?.timestamp
}
}
return .gallery(startTimecode
|> deliverOnMainQueue
|> map { timecode in
let gallery = GalleryController(context: context, source: standalone ? .standaloneMessage(message) : .peerMessagesAtId(message.id), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in
navigationController?.replaceTopController(controller, animated: false, ready: ready)
}, baseNavigationController: navigationController, actionInteraction: actionInteraction)
gallery.temporaryDoNotWaitForReady = autoplayingVideo
return .gallery(gallery)
gallery.temporaryDoNotWaitForReady = autoplayingVideo
return gallery
})
}
}
}
@ -201,7 +215,8 @@ func chatMessagePreviewControllerData(context: AccountContext, message: Message,
if let mediaData = chatMessageGalleryControllerData(context: context, message: message, navigationController: navigationController, standalone: standalone, reverseMessageGalleryOrder: reverseMessageGalleryOrder, mode: .default, synchronousLoad: true, actionInteraction: nil) {
switch mediaData {
case let .gallery(gallery):
return .gallery(gallery)
break
//return .gallery(gallery)
case let .instantPage(gallery, centralIndex, galleryMedia):
return .instantPage(gallery, centralIndex, galleryMedia)
default:
@ -309,13 +324,16 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
return true
case let .gallery(gallery):
params.dismissInput()
params.present(gallery, GalleryControllerPresentationArguments(transitionArguments: { messageId, media in
let selectedTransitionNode = params.transitionNode(messageId, media)
if let selectedTransitionNode = selectedTransitionNode {
return GalleryTransitionArguments(transitionNode: selectedTransitionNode, addToTransitionSurface: params.addToTransitionSurface)
}
return nil
}))
let _ = (gallery
|> deliverOnMainQueue).start(next: { gallery in
params.present(gallery, GalleryControllerPresentationArguments(transitionArguments: { messageId, media in
let selectedTransitionNode = params.transitionNode(messageId, media)
if let selectedTransitionNode = selectedTransitionNode {
return GalleryTransitionArguments(transitionNode: selectedTransitionNode, addToTransitionSurface: params.addToTransitionSurface)
}
return nil
}))
})
return true
case let .secretGallery(gallery):
params.dismissInput()

View File

@ -10,11 +10,13 @@ import MtProtoKit
import MtProtoKitDynamic
#endif
import TelegramPresentationData
import TelegramUIPreferences
import AccountContext
import UrlEscaping
import PassportUI
import UrlHandling
import WalletUI
import OpenInExternalAppUI
public struct ParsedSecureIdUrl {
public let peerId: PeerId
@ -640,20 +642,42 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
if parsedUrl.host == "t.me" || parsedUrl.host == "telegram.me" {
handleInternalUrl(parsedUrl.absoluteString)
} else {
if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
if let window = navigationController?.view.window {
let controller = SFSafariViewController(url: parsedUrl)
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
controller.preferredBarTintColor = presentationData.theme.rootController.navigationBar.backgroundColor
controller.preferredControlTintColor = presentationData.theme.rootController.navigationBar.accentTextColor
let settings = context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.webBrowserSettings])
|> take(1)
|> map { sharedData -> WebBrowserSettings in
if let current = sharedData.entries[ApplicationSpecificSharedDataKeys.webBrowserSettings] as? WebBrowserSettings {
return current
} else {
return WebBrowserSettings.defaultSettings
}
}
let _ = (settings
|> deliverOnMainQueue).start(next: { settings in
if settings.defaultWebBrowser == nil {
if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
if let window = navigationController?.view.window {
let controller = SFSafariViewController(url: parsedUrl)
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
controller.preferredBarTintColor = presentationData.theme.rootController.navigationBar.backgroundColor
controller.preferredControlTintColor = presentationData.theme.rootController.navigationBar.accentTextColor
}
window.rootViewController?.present(controller, animated: true)
} else {
context.sharedContext.applicationBindings.openUrl(parsedUrl.absoluteString)
}
} else {
context.sharedContext.applicationBindings.openUrl(url)
}
window.rootViewController?.present(controller, animated: true)
} else {
context.sharedContext.applicationBindings.openUrl(parsedUrl.absoluteString)
let openInOptions = availableOpenInOptions(context: context, item: .url(url: url))
if let option = openInOptions.first(where: { $0.identifier == settings.defaultWebBrowser }) {
if case let .openUrl(url) = option.action() {
context.sharedContext.applicationBindings.openUrl(url)
}
}
}
} else {
context.sharedContext.applicationBindings.openUrl(url)
}
})
}
} else {
context.sharedContext.applicationBindings.openUrl(url)

View File

@ -11,6 +11,7 @@ import LegacyUI
import PeerInfoUI
import ShareItems
import SettingsUI
import Intents
private let inForeground = ValuePromise<Bool>(false, ignoreRepeated: true)
@ -295,13 +296,12 @@ public class ShareRootControllerImpl {
shareController.dismissed = { _ in
self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil)
}
cancelImpl = { [weak shareController] in
shareController?.dismiss(completion: { [weak self] in
self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil)
})
}
if let strongSelf = self {
if let currentShareController = strongSelf.currentShareController {
currentShareController.dismiss()
@ -310,6 +310,15 @@ public class ShareRootControllerImpl {
strongSelf.mainWindow?.present(shareController, on: .root)
}
if #available(iOS 13.0, *), let sendMessageIntent = self?.getExtensionContext()?.intent as? INSendMessageIntent {
if let contact = sendMessageIntent.recipients?.first, let handle = contact.customIdentifier, handle.hasPrefix("tg") {
let string = handle.suffix(from: handle.index(handle.startIndex, offsetBy: 2))
if let userId = Int32(string) {
shareController.send(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId))
}
}
}
context.account.resetStateManagement()
}

View File

@ -178,7 +178,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
self._presentationData.set(.single(initialPresentationDataAndSettings.presentationData)
|> then(
updatedPresentationData(accountManager: self.accountManager, applicationInForeground: self.applicationBindings.applicationInForeground)
updatedPresentationData(accountManager: self.accountManager, applicationInForeground: self.applicationBindings.applicationInForeground, systemUserInterfaceStyle: mainWindow?.systemUserInterfaceStyle ?? .single(.light))
))
self._automaticMediaDownloadSettings.set(.single(initialPresentationDataAndSettings.automaticMediaDownloadSettings)
|> then(accountManager.sharedData(keys: [SharedDataKeys.autodownloadSettings, ApplicationSpecificSharedDataKeys.automaticMediaDownloadSettings])

View File

@ -66,7 +66,7 @@ final class ThemeUpdateManagerImpl: ThemeUpdateManager {
validIds.insert(themeSettings.theme.index)
themes[themeSettings.theme.index] = (themeSettings.theme, false)
}
if case .cloud = themeSettings.automaticThemeSwitchSetting.theme, themeSettings.automaticThemeSwitchSetting.trigger != .none {
if case .cloud = themeSettings.automaticThemeSwitchSetting.theme, themeSettings.automaticThemeSwitchSetting.trigger != .explicitNone {
validIds.insert(themeSettings.automaticThemeSwitchSetting.theme.index)
themes[themeSettings.automaticThemeSwitchSetting.theme.index] = (themeSettings.automaticThemeSwitchSetting.theme, true)
}

View File

@ -29,6 +29,7 @@ private enum ApplicationSpecificSharedDataKeyValues: Int32 {
case watchPresetSettings = 13
case webSearchSettings = 14
case contactSynchronizationSettings = 15
case webBrowserSettings = 16
}
public struct ApplicationSpecificSharedDataKeys {
@ -48,6 +49,7 @@ public struct ApplicationSpecificSharedDataKeys {
public static let watchPresetSettings = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.watchPresetSettings.rawValue)
public static let webSearchSettings = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.webSearchSettings.rawValue)
public static let contactSynchronizationSettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.contactSynchronizationSettings.rawValue)
public static let webBrowserSettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.webBrowserSettings.rawValue)
}
private enum ApplicationSpecificItemCacheCollectionIdValues: Int8 {

View File

@ -237,27 +237,30 @@ public enum AutomaticThemeSwitchTimeBasedSetting: PostboxCoding, Equatable {
}
public enum AutomaticThemeSwitchTrigger: PostboxCoding, Equatable {
case none
case system
case explicitNone
case timeBased(setting: AutomaticThemeSwitchTimeBasedSetting)
case brightness(threshold: Double)
public init(decoder: PostboxDecoder) {
switch decoder.decodeInt32ForKey("_t", orElse: 0) {
case 0:
self = .none
self = .system
case 1:
self = .timeBased(setting: decoder.decodeObjectForKey("setting", decoder: { AutomaticThemeSwitchTimeBasedSetting(decoder: $0) }) as! AutomaticThemeSwitchTimeBasedSetting)
case 2:
self = .brightness(threshold: decoder.decodeDoubleForKey("threshold", orElse: 0.2))
case 3:
self = .explicitNone
default:
assertionFailure()
self = .none
self = .system
}
}
public func encode(_ encoder: PostboxEncoder) {
switch self {
case .none:
case .system:
encoder.encodeInt32(0, forKey: "_t")
case let .timeBased(setting):
encoder.encodeInt32(1, forKey: "_t")
@ -265,6 +268,8 @@ public enum AutomaticThemeSwitchTrigger: PostboxCoding, Equatable {
case let .brightness(threshold):
encoder.encodeInt32(2, forKey: "_t")
encoder.encodeDouble(threshold, forKey: "threshold")
case .explicitNone:
encoder.encodeInt32(3, forKey: "_t")
}
}
}
@ -449,7 +454,7 @@ public struct PresentationThemeSettings: PreferencesEntry {
}
public static var defaultSettings: PresentationThemeSettings {
return PresentationThemeSettings(chatWallpaper: .builtin(WallpaperSettings()), theme: .builtin(.dayClassic), themeSpecificAccentColors: [:], themeSpecificChatWallpapers: [:], fontSize: .regular, automaticThemeSwitchSetting: AutomaticThemeSwitchSetting(trigger: .none, theme: .builtin(.nightAccent)), largeEmoji: true, disableAnimations: true)
return PresentationThemeSettings(chatWallpaper: .builtin(WallpaperSettings()), theme: .builtin(.dayClassic), themeSpecificAccentColors: [:], themeSpecificChatWallpapers: [:], fontSize: .regular, automaticThemeSwitchSetting: AutomaticThemeSwitchSetting(trigger: .system, theme: .builtin(.nightAccent)), largeEmoji: true, disableAnimations: true)
}
public init(chatWallpaper: TelegramWallpaper, theme: PresentationThemeReference, themeSpecificAccentColors: [Int64: PresentationThemeAccentColor], themeSpecificChatWallpapers: [Int64: TelegramWallpaper], fontSize: PresentationFontSize, automaticThemeSwitchSetting: AutomaticThemeSwitchSetting, largeEmoji: Bool, disableAnimations: Bool) {
@ -505,7 +510,7 @@ public struct PresentationThemeSettings: PreferencesEntry {
}
self.fontSize = PresentationFontSize(rawValue: decoder.decodeInt32ForKey("f", orElse: PresentationFontSize.regular.rawValue)) ?? .regular
self.automaticThemeSwitchSetting = (decoder.decodeObjectForKey("automaticThemeSwitchSetting", decoder: { AutomaticThemeSwitchSetting(decoder: $0) }) as? AutomaticThemeSwitchSetting) ?? AutomaticThemeSwitchSetting(trigger: .none, theme: .builtin(.nightAccent))
self.automaticThemeSwitchSetting = (decoder.decodeObjectForKey("automaticThemeSwitchSetting", decoder: { AutomaticThemeSwitchSetting(decoder: $0) }) as? AutomaticThemeSwitchSetting) ?? AutomaticThemeSwitchSetting(trigger: .system, theme: .builtin(.nightAccent))
self.largeEmoji = decoder.decodeBoolForKey("largeEmoji", orElse: true)
self.disableAnimations = decoder.decodeBoolForKey("disableAnimations", orElse: true)
}

View File

@ -0,0 +1,57 @@
import Foundation
import Postbox
import SwiftSignalKit
public struct WebBrowserSettings: PreferencesEntry, Equatable {
public let defaultWebBrowser: String?
public static var defaultSettings: WebBrowserSettings {
return WebBrowserSettings(defaultWebBrowser: nil)
}
public init(defaultWebBrowser: String?) {
self.defaultWebBrowser = defaultWebBrowser
}
public init(decoder: PostboxDecoder) {
self.defaultWebBrowser = decoder.decodeOptionalStringForKey("defaultWebBrowser")
}
public func encode(_ encoder: PostboxEncoder) {
if let defaultWebBrowser = self.defaultWebBrowser {
encoder.encodeString(defaultWebBrowser, forKey: "defaultWebBrowser")
} else {
encoder.encodeNil(forKey: "defaultWebBrowser")
}
}
public func isEqual(to: PreferencesEntry) -> Bool {
if let to = to as? WebBrowserSettings {
return self == to
} else {
return false
}
}
public static func ==(lhs: WebBrowserSettings, rhs: WebBrowserSettings) -> Bool {
return lhs.defaultWebBrowser == rhs.defaultWebBrowser
}
public func withUpdatedDefaultWebBrowser(_ defaultWebBrowser: String?) -> WebBrowserSettings {
return WebBrowserSettings(defaultWebBrowser: defaultWebBrowser)
}
}
public func updateWebBrowserSettingsInteractively(accountManager: AccountManager, _ f: @escaping (WebBrowserSettings) -> WebBrowserSettings) -> Signal<Void, NoError> {
return accountManager.transaction { transaction -> Void in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.webBrowserSettings, { entry in
let currentSettings: WebBrowserSettings
if let entry = entry as? WebBrowserSettings {
currentSettings = entry
} else {
currentSettings = WebBrowserSettings.defaultSettings
}
return f(currentSettings)
})
}
}

View File

@ -170,7 +170,8 @@ private func walletReceiveScreenEntries(presentationData: PresentationData, addr
entries.append(.addressCode(presentationData.theme, walletInvoiceUrl(address: address)))
entries.append(.addressHeader(presentationData.theme, presentationData.strings.Wallet_Receive_AddressHeader))
entries.append(.address(presentationData.theme, formatAddress(address), true))
let addressText = formatAddress(address)
entries.append(.address(presentationData.theme, addressText, true))
entries.append(.copyAddress(presentationData.theme, presentationData.strings.Wallet_Receive_CopyAddress))
entries.append(.shareAddressLink(presentationData.theme, presentationData.strings.Wallet_Receive_ShareAddress))
entries.append(.addressInfo(presentationData.theme, presentationData.strings.Wallet_Receive_ShareUrlInfo))