mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' into buck-test
This commit is contained in:
commit
ee8deffc76
@ -8,11 +8,6 @@
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>${APP_NAME}</string>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict/>
|
||||
<dict/>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIcons</key>
|
||||
|
@ -4637,7 +4637,7 @@ Any member of this group will be able to see messages in the channel.";
|
||||
"AccentColor.Title" = "Accent Color";
|
||||
|
||||
"Appearance.ThemePreview.ChatList.1.Name" = "Alicia Torreaux";
|
||||
"Appearance.ThemePreview.ChatList.1.Text" = "Bob says hi.";
|
||||
"Appearance.ThemePreview.ChatList.1.Text" = "Bob says hi. 😊 ❤️ 😱";
|
||||
"Appearance.ThemePreview.ChatList.2.Name" = "Roberto";
|
||||
"Appearance.ThemePreview.ChatList.2.Text" = "Say hello to Alice 👋";
|
||||
"Appearance.ThemePreview.ChatList.3.Name" = "Campus Public Chat";
|
||||
|
@ -7,6 +7,7 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
095214F12318D4F5008CDD87 /* ThemeUpdateManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 095214F02318D4F5008CDD87 /* ThemeUpdateManager.swift */; };
|
||||
D03E4228230565790049C28B /* ChatListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E4227230565790049C28B /* ChatListController.swift */; };
|
||||
D03E422A230567900049C28B /* ContactMultiselectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E4229230567900049C28B /* ContactMultiselectionController.swift */; };
|
||||
D03E487C230770000049C28B /* PeerSelectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E487B230770000049C28B /* PeerSelectionController.swift */; };
|
||||
@ -49,6 +50,7 @@
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
095214F02318D4F5008CDD87 /* ThemeUpdateManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeUpdateManager.swift; sourceTree = "<group>"; };
|
||||
D03E4227230565790049C28B /* ChatListController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListController.swift; sourceTree = "<group>"; };
|
||||
D03E4229230567900049C28B /* ContactMultiselectionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactMultiselectionController.swift; sourceTree = "<group>"; };
|
||||
D03E487B230770000049C28B /* PeerSelectionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerSelectionController.swift; sourceTree = "<group>"; };
|
||||
@ -155,6 +157,7 @@
|
||||
D0879B6A22F7B04400C4D6B3 /* DeviceContactData.swift */,
|
||||
D0879B6E22F83F6600C4D6B3 /* DownloadedMediaStoreManager.swift */,
|
||||
D0879B7022F8425B00C4D6B3 /* WallpaperUploadManager.swift */,
|
||||
095214F02318D4F5008CDD87 /* ThemeUpdateManager.swift */,
|
||||
D0879B7222F842FF00C4D6B3 /* WatchManager.swift */,
|
||||
D0C9C12222FE414700FAB518 /* PresentationSurfaceLevels.swift */,
|
||||
D0C9C3D62300CC2400FAB518 /* FetchMediaUtils.swift */,
|
||||
@ -271,6 +274,7 @@
|
||||
D0879B6122F7A7A600C4D6B3 /* UniversalVideoNode.swift in Sources */,
|
||||
D0879A4E22F65B2A00C4D6B3 /* MediaManager.swift in Sources */,
|
||||
D0879A4A22F6584B00C4D6B3 /* SharedMediaPlayer.swift in Sources */,
|
||||
095214F12318D4F5008CDD87 /* ThemeUpdateManager.swift in Sources */,
|
||||
D0879B7122F8425B00C4D6B3 /* WallpaperUploadManager.swift in Sources */,
|
||||
D03E422A230567900049C28B /* ContactMultiselectionController.swift in Sources */,
|
||||
D03E487C230770000049C28B /* PeerSelectionController.swift in Sources */,
|
||||
|
@ -0,0 +1,7 @@
|
||||
import Foundation
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
|
||||
public protocol ThemeUpdateManager: class {
|
||||
}
|
@ -495,12 +495,12 @@ public final class TextNodeLayout: NSObject {
|
||||
return result
|
||||
}
|
||||
|
||||
public func attributeSubstring(name: String, index: Int) -> String? {
|
||||
public func attributeSubstring(name: String, index: Int) -> (String, String)? {
|
||||
if let attributedString = self.attributedString {
|
||||
var range = NSRange()
|
||||
let _ = attributedString.attribute(NSAttributedString.Key(rawValue: name), at: index, effectiveRange: &range)
|
||||
if range.length != 0 {
|
||||
return (attributedString.string as NSString).substring(with: range)
|
||||
return ((attributedString.string as NSString).substring(with: range), attributedString.string)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@ -755,7 +755,7 @@ public class TextNode: ASDisplayNode {
|
||||
return self.cachedLayout?.textRangesRects(text: text) ?? []
|
||||
}
|
||||
|
||||
public func attributeSubstring(name: String, index: Int) -> String? {
|
||||
public func attributeSubstring(name: String, index: Int) -> (String, String)? {
|
||||
return self.cachedLayout?.attributeSubstring(name: name, index: index)
|
||||
}
|
||||
|
||||
|
@ -17,11 +17,12 @@ final class RadialStatusBackgroundNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
init(color: UIColor) {
|
||||
init(color: UIColor, synchronous: Bool) {
|
||||
self.color = color
|
||||
|
||||
super.init()
|
||||
|
||||
self.displaysAsynchronously = !synchronous
|
||||
self.isLayerBacked = true
|
||||
self.isOpaque = false
|
||||
}
|
||||
|
@ -187,7 +187,7 @@ public enum RadialStatusNodeState: Equatable {
|
||||
public final class RadialStatusNode: ASControlNode {
|
||||
public var backgroundNodeColor: UIColor {
|
||||
didSet {
|
||||
self.transitionToBackgroundColor(state.backgroundColor(color: self.backgroundNodeColor), previousContentNode: nil, animated: false, completion: {})
|
||||
self.transitionToBackgroundColor(self.state.backgroundColor(color: self.backgroundNodeColor), previousContentNode: nil, animated: false, synchronous: false, completion: {})
|
||||
}
|
||||
}
|
||||
|
||||
@ -215,16 +215,16 @@ public final class RadialStatusNode: ASControlNode {
|
||||
|
||||
let contentNode = state.contentNode(current: self.contentNode, synchronous: synchronous)
|
||||
if contentNode !== self.contentNode {
|
||||
self.transitionToContentNode(contentNode, state: state, fromState: fromState, backgroundColor: state.backgroundColor(color: self.backgroundNodeColor), animated: animated, completion: completion)
|
||||
self.transitionToContentNode(contentNode, state: state, fromState: fromState, backgroundColor: state.backgroundColor(color: self.backgroundNodeColor), animated: animated, synchronous: synchronous, completion: completion)
|
||||
} else {
|
||||
self.transitionToBackgroundColor(state.backgroundColor(color: self.backgroundNodeColor), previousContentNode: nil, animated: animated, completion: completion)
|
||||
self.transitionToBackgroundColor(state.backgroundColor(color: self.backgroundNodeColor), previousContentNode: nil, animated: animated, synchronous: synchronous, completion: completion)
|
||||
}
|
||||
} else {
|
||||
completion()
|
||||
}
|
||||
}
|
||||
|
||||
private func transitionToContentNode(_ node: RadialStatusContentNode?, state: RadialStatusNodeState, fromState: RadialStatusNodeState, backgroundColor: UIColor?, animated: Bool, completion: @escaping () -> Void) {
|
||||
private func transitionToContentNode(_ node: RadialStatusContentNode?, state: RadialStatusNodeState, fromState: RadialStatusNodeState, backgroundColor: UIColor?, animated: Bool, synchronous: Bool = false, completion: @escaping () -> Void) {
|
||||
if let contentNode = self.contentNode {
|
||||
self.nextContentNode = node
|
||||
contentNode.enqueueReadyForTransition { [weak contentNode, weak self] in
|
||||
@ -242,7 +242,7 @@ public final class RadialStatusNode: ASControlNode {
|
||||
contentNode.animateIn(from: fromState, delay: delay)
|
||||
}
|
||||
}
|
||||
strongSelf.transitionToBackgroundColor(strongSelf.contentNode != nil ? backgroundColor : nil, previousContentNode: previousContentNode, animated: animated, completion: completion)
|
||||
strongSelf.transitionToBackgroundColor(strongSelf.contentNode != nil ? backgroundColor : nil, previousContentNode: previousContentNode, animated: animated, synchronous: synchronous, completion: completion)
|
||||
})
|
||||
previousContentNode.animateOut(to: state, completion: { [weak contentNode] in
|
||||
if let strongSelf = self, let contentNode = contentNode {
|
||||
@ -262,7 +262,7 @@ public final class RadialStatusNode: ASControlNode {
|
||||
contentNode.layout()
|
||||
}
|
||||
}
|
||||
strongSelf.transitionToBackgroundColor(backgroundColor, previousContentNode: nil, animated: animated, completion: completion)
|
||||
strongSelf.transitionToBackgroundColor(backgroundColor, previousContentNode: nil, animated: animated, synchronous: synchronous, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -282,11 +282,11 @@ public final class RadialStatusNode: ASControlNode {
|
||||
}
|
||||
}
|
||||
}
|
||||
self.transitionToBackgroundColor(backgroundColor, previousContentNode: nil, animated: animated, completion: completion)
|
||||
self.transitionToBackgroundColor(backgroundColor, previousContentNode: nil, animated: animated, synchronous: synchronous, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
private func transitionToBackgroundColor(_ color: UIColor?, previousContentNode: RadialStatusContentNode?, animated: Bool, completion: @escaping () -> Void) {
|
||||
private func transitionToBackgroundColor(_ color: UIColor?, previousContentNode: RadialStatusContentNode?, animated: Bool, synchronous: Bool, completion: @escaping () -> Void) {
|
||||
let currentColor = self.backgroundNode?.color
|
||||
|
||||
var updated = false
|
||||
@ -302,7 +302,7 @@ public final class RadialStatusNode: ASControlNode {
|
||||
backgroundNode.color = color
|
||||
completion()
|
||||
} else {
|
||||
let backgroundNode = RadialStatusBackgroundNode(color: color)
|
||||
let backgroundNode = RadialStatusBackgroundNode(color: color, synchronous: synchronous)
|
||||
backgroundNode.frame = self.bounds
|
||||
self.backgroundNode = backgroundNode
|
||||
self.insertSubnode(backgroundNode, at: 0)
|
||||
|
@ -108,6 +108,7 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
|
||||
case phoneDiscoveryHeader(PresentationTheme, String)
|
||||
case phoneDiscoveryEverybody(PresentationTheme, String, Bool)
|
||||
case phoneDiscoveryMyContacts(PresentationTheme, String, Bool)
|
||||
case phoneDiscoveryInfo(PresentationTheme, String)
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
@ -123,7 +124,7 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
|
||||
return SelectivePrivacySettingsSection.callsP2PPeers.rawValue
|
||||
case .callsIntegrationEnabled, .callsIntegrationInfo:
|
||||
return SelectivePrivacySettingsSection.callsIntegrationEnabled.rawValue
|
||||
case .phoneDiscoveryHeader, .phoneDiscoveryEverybody, .phoneDiscoveryMyContacts:
|
||||
case .phoneDiscoveryHeader, .phoneDiscoveryEverybody, .phoneDiscoveryMyContacts, .phoneDiscoveryInfo:
|
||||
return SelectivePrivacySettingsSection.phoneDiscovery.rawValue
|
||||
}
|
||||
}
|
||||
@ -150,34 +151,36 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
|
||||
return 8
|
||||
case .phoneDiscoveryMyContacts:
|
||||
return 9
|
||||
case .exceptionsHeader:
|
||||
case .phoneDiscoveryInfo:
|
||||
return 10
|
||||
case .disableFor:
|
||||
case .exceptionsHeader:
|
||||
return 11
|
||||
case .enableFor:
|
||||
case .disableFor:
|
||||
return 12
|
||||
case .peersInfo:
|
||||
case .enableFor:
|
||||
return 13
|
||||
case .callsP2PHeader:
|
||||
case .peersInfo:
|
||||
return 14
|
||||
case .callsP2PAlways:
|
||||
case .callsP2PHeader:
|
||||
return 15
|
||||
case .callsP2PContacts:
|
||||
case .callsP2PAlways:
|
||||
return 16
|
||||
case .callsP2PNever:
|
||||
case .callsP2PContacts:
|
||||
return 17
|
||||
case .callsP2PInfo:
|
||||
case .callsP2PNever:
|
||||
return 18
|
||||
case .callsP2PDisableFor:
|
||||
case .callsP2PInfo:
|
||||
return 19
|
||||
case .callsP2PEnableFor:
|
||||
case .callsP2PDisableFor:
|
||||
return 20
|
||||
case .callsP2PPeersInfo:
|
||||
case .callsP2PEnableFor:
|
||||
return 21
|
||||
case .callsIntegrationEnabled:
|
||||
case .callsP2PPeersInfo:
|
||||
return 22
|
||||
case .callsIntegrationInfo:
|
||||
case .callsIntegrationEnabled:
|
||||
return 23
|
||||
case .callsIntegrationInfo:
|
||||
return 24
|
||||
}
|
||||
}
|
||||
|
||||
@ -327,6 +330,12 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .phoneDiscoveryInfo(lhsTheme, lhsText):
|
||||
if case let .phoneDiscoveryInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -383,7 +392,7 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
|
||||
arguments.updateCallP2PMode?(.nobody)
|
||||
})
|
||||
case let .callsP2PInfo(theme, text):
|
||||
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
|
||||
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
|
||||
case let .callsP2PDisableFor(theme, title, value):
|
||||
return ItemListDisclosureItem(theme: theme, title: title, label: value, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openSelective(.callP2P, false)
|
||||
@ -410,6 +419,8 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
|
||||
return ItemListCheckboxItem(theme: theme, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: {
|
||||
arguments.updatePhoneDiscovery?(false)
|
||||
})
|
||||
case let .phoneDiscoveryInfo(theme, text):
|
||||
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -522,7 +533,7 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present
|
||||
var entries: [SelectivePrivacySettingsEntry] = []
|
||||
|
||||
let settingTitle: String
|
||||
let settingInfoText: String
|
||||
let settingInfoText: String?
|
||||
let disableForText: String
|
||||
let enableForText: String
|
||||
switch kind {
|
||||
@ -554,7 +565,7 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present
|
||||
case .phoneNumber:
|
||||
settingTitle = presentationData.strings.PrivacyPhoneNumberSettings_WhoCanSeeMyPhoneNumber
|
||||
if state.setting == .nobody, state.phoneDiscoveryEnabled == false {
|
||||
settingInfoText = presentationData.strings.PrivacyPhoneNumberSettings_CustomDisabledHelp
|
||||
settingInfoText = nil
|
||||
} else {
|
||||
settingInfoText = presentationData.strings.PrivacyPhoneNumberSettings_CustomHelp
|
||||
}
|
||||
@ -590,12 +601,15 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present
|
||||
case .groupInvitations, .profilePhoto:
|
||||
break
|
||||
}
|
||||
entries.append(.settingInfo(presentationData.theme, settingInfoText))
|
||||
if let settingInfoText = settingInfoText {
|
||||
entries.append(.settingInfo(presentationData.theme, settingInfoText))
|
||||
}
|
||||
|
||||
if case .phoneNumber = kind, state.setting == .nobody {
|
||||
entries.append(.phoneDiscoveryHeader(presentationData.theme, presentationData.strings.PrivacyPhoneNumberSettings_DiscoveryHeader))
|
||||
entries.append(.phoneDiscoveryEverybody(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenEverybody, state.phoneDiscoveryEnabled != false))
|
||||
entries.append(.phoneDiscoveryMyContacts(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenContacts, state.phoneDiscoveryEnabled == false))
|
||||
entries.append(.phoneDiscoveryInfo(presentationData.theme, presentationData.strings.PrivacyPhoneNumberSettings_CustomDisabledHelp))
|
||||
}
|
||||
|
||||
entries.append(.exceptionsHeader(presentationData.theme, presentationData.strings.GroupInfo_Permissions_Exceptions))
|
||||
|
@ -132,7 +132,7 @@ private enum EditThemeControllerEntry: ItemListNodeEntry {
|
||||
func item(_ arguments: EditThemeControllerArguments) -> ListViewItem {
|
||||
switch self {
|
||||
case let .title(theme, strings, title, text, done):
|
||||
return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(), text: text, placeholder: title, type: .regular(capitalization: true, autocorrection: false), returnKeyType: done ? .done : .next, clearType: .onFocus, tag: EditThemeEntryTag.title, sectionId: self.section, textUpdated: { value in
|
||||
return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(), text: text, placeholder: title, type: .regular(capitalization: true, autocorrection: false), returnKeyType: .default, clearType: .onFocus, tag: EditThemeEntryTag.title, sectionId: self.section, textUpdated: { value in
|
||||
arguments.updateState { current in
|
||||
var state = current
|
||||
state.title = value
|
||||
@ -267,10 +267,14 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
|
||||
|> mapToSignal { wallpaper -> Signal<TelegramWallpaper?, NoError> in
|
||||
if let wallpaper = wallpaper, case let .file(file) = wallpaper.wallpaper {
|
||||
var convertedRepresentations: [ImageRepresentationWithReference] = []
|
||||
convertedRepresentations.append(ImageRepresentationWithReference(representation: TelegramMediaImageRepresentation(dimensions: CGSize(width: 100.0, height: 100.0), resource: file.file.resource), reference: .wallpaper(resource: file.file.resource)))
|
||||
return wallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, fileReference: .standalone(media: file.file), representations: convertedRepresentations, alwaysShowThumbnailFirst: false, thumbnail: false, onlyFullSize: true, autoFetchFullSize: true, synchronousLoad: false)
|
||||
|> map { _ -> TelegramWallpaper? in
|
||||
return wallpaper.wallpaper
|
||||
convertedRepresentations.append(ImageRepresentationWithReference(representation: TelegramMediaImageRepresentation(dimensions: CGSize(width: 100.0, height: 100.0), resource: file.file.resource), reference: .media(media: .standalone(media: file.file), resource: file.file.resource)))
|
||||
return wallpaperDatas(account: context.account, accountManager: context.sharedContext.accountManager, fileReference: .standalone(media: file.file), representations: convertedRepresentations, alwaysShowThumbnailFirst: false, thumbnail: false, onlyFullSize: true, autoFetchFullSize: true, synchronousLoad: false)
|
||||
|> mapToSignal { _, fullSizeData, complete -> Signal<TelegramWallpaper?, NoError> in
|
||||
guard complete, let fullSizeData = fullSizeData else {
|
||||
return .complete()
|
||||
}
|
||||
context.sharedContext.accountManager.mediaBox.storeResourceData(file.file.resource.id, data: fullSizeData)
|
||||
return .single(wallpaper.wallpaper)
|
||||
}
|
||||
} else {
|
||||
return .single(nil)
|
||||
@ -422,11 +426,11 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
|
||||
context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data)
|
||||
}
|
||||
let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: resolvedWallpaper))
|
||||
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
|
||||
var chatWallpaper = current.chatWallpaper
|
||||
if let theme = theme {
|
||||
themeSpecificChatWallpapers[themeReference.index] = theme.chat.defaultWallpaper
|
||||
chatWallpaper = resolvedWallpaper ?? theme.chat.defaultWallpaper
|
||||
}
|
||||
return PresentationThemeSettings(chatWallpaper: theme?.chat.defaultWallpaper ?? current.chatWallpaper, theme: themeReference, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
|
||||
return PresentationThemeSettings(chatWallpaper: chatWallpaper, theme: themeReference, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
|
||||
})
|
||||
} |> deliverOnMainQueue).start(completed: {
|
||||
if !hasCustomFile {
|
||||
@ -447,7 +451,7 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
|
||||
})
|
||||
}
|
||||
case let .edit(info):
|
||||
let _ = (updateTheme(account: context.account, theme: info.theme, title: state.title, slug: state.slug, resource: themeResource)
|
||||
let _ = (updateTheme(account: context.account, accountManager: context.sharedContext.accountManager, theme: info.theme, title: state.title, slug: state.slug, resource: themeResource)
|
||||
|> deliverOnMainQueue).start(next: { next in
|
||||
if case let .result(resultTheme) = next {
|
||||
let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start()
|
||||
@ -459,18 +463,15 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
|
||||
} else {
|
||||
current = PresentationThemeSettings.defaultSettings
|
||||
}
|
||||
|
||||
if let resource = resultTheme.file?.resource, let data = themeData {
|
||||
context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data)
|
||||
}
|
||||
|
||||
let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: resolvedWallpaper))
|
||||
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
|
||||
var chatWallpaper = current.chatWallpaper
|
||||
if let theme = theme {
|
||||
themeSpecificChatWallpapers[themeReference.index] = theme.chat.defaultWallpaper
|
||||
chatWallpaper = resolvedWallpaper ?? theme.chat.defaultWallpaper
|
||||
}
|
||||
|
||||
return PresentationThemeSettings(chatWallpaper: theme?.chat.defaultWallpaper ?? current.chatWallpaper, theme: themeReference, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
|
||||
return PresentationThemeSettings(chatWallpaper: chatWallpaper, theme: themeReference, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
|
||||
})
|
||||
} |> deliverOnMainQueue).start(completed: {
|
||||
if let themeResource = themeResource, !hasCustomFile {
|
||||
|
@ -11,7 +11,7 @@ import ChatListUI
|
||||
import AccountContext
|
||||
|
||||
private func generateMaskImage(color: UIColor) -> UIImage? {
|
||||
return generateImage(CGSize(width: 1.0, height: 60.0), opaque: false, rotatedContext: { size, context in
|
||||
return generateImage(CGSize(width: 1.0, height: 80.0), opaque: false, rotatedContext: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.clear(bounds)
|
||||
|
||||
@ -21,7 +21,7 @@ private func generateMaskImage(color: UIColor) -> UIImage? {
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
|
||||
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: 60.0), options: CGGradientDrawingOptions())
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: 80.0), options: CGGradientDrawingOptions())
|
||||
})
|
||||
}
|
||||
|
||||
@ -82,8 +82,11 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
|
||||
self.chatListBackgroundNode = ASDisplayNode()
|
||||
self.chatBackgroundNode = WallpaperBackgroundNode()
|
||||
self.chatBackgroundNode.displaysAsynchronously = false
|
||||
self.chatBackgroundNode.image = chatControllerBackgroundImage(theme: theme, wallpaper: self.presentationData.chatWallpaper, mediaBox: context.sharedContext.accountManager.mediaBox, knockoutMode: context.sharedContext.immediateExperimentalUISettings.knockoutWallpaper)
|
||||
self.chatBackgroundNode.motionEnabled = self.presentationData.chatWallpaper.settings?.motion ?? false
|
||||
if case .color = self.presentationData.chatWallpaper {
|
||||
} else {
|
||||
self.chatBackgroundNode.image = chatControllerBackgroundImage(theme: theme, wallpaper: self.presentationData.chatWallpaper, mediaBox: context.sharedContext.accountManager.mediaBox, knockoutMode: context.sharedContext.immediateExperimentalUISettings.knockoutWallpaper)
|
||||
self.chatBackgroundNode.motionEnabled = self.presentationData.chatWallpaper.settings?.motion ?? false
|
||||
}
|
||||
|
||||
self.colorPanelNode = WallpaperColorPanelNode(theme: self.theme, strings: self.presentationData.strings)
|
||||
self.colorPanelNode.color = color
|
||||
@ -233,8 +236,9 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
|
||||
let timestamp1 = timestamp + 120
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: selfPeer, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
|
||||
let presenceTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + 60 * 60)
|
||||
let timestamp2 = timestamp + 3660
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_2_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer2), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_2_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer2), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), notificationSettings: nil, presence: TelegramUserPresence(status: .present(until: presenceTimestamp), lastActivity: presenceTimestamp), summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
|
||||
let timestamp3 = timestamp + 3200
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer3.id, namespace: 0, id: 0), timestamp: timestamp3)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer3.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp3, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer3Author, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer3), combinedReadState: nil, notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
@ -305,7 +309,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
|
||||
let voiceAttributes: [TelegramMediaFileAttribute] = [.Audio(isVoice: true, duration: 23, title: nil, performer: nil, waveform: MemoryBuffer(data: Data(base64Encoded: waveformBase64)!))]
|
||||
let voiceMedia = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: voiceAttributes)
|
||||
|
||||
let message3 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: [])
|
||||
let message3 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: [])
|
||||
items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message3, theme: self.theme, strings: self.presentationData.strings, wallpaper: self.theme.chat.defaultWallpaper, fontSize: self.presentationData.fontSize, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: FileMediaResourceStatus(mediaStatus: .playbackStatus(.paused), fetchStatus: .Local)))
|
||||
|
||||
let message4 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])
|
||||
@ -388,6 +392,6 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
|
||||
self.pageControlNode.frame = pageControlFrame
|
||||
self.pageControlBackgroundNode.frame = CGRect(x: pageControlFrame.minX - 11.0, y: pageControlFrame.minY - 12.0, width: pageControlFrame.width + 22.0, height: 30.0)
|
||||
|
||||
transition.updateFrame(node: self.maskNode, frame: CGRect(x: 0.0, y: layout.size.height - bottomInset - 60.0, width: bounds.width, height: 60.0))
|
||||
transition.updateFrame(node: self.maskNode, frame: CGRect(x: 0.0, y: layout.size.height - bottomInset - 80.0, width: bounds.width, height: 80.0))
|
||||
}
|
||||
}
|
||||
|
@ -177,23 +177,14 @@ final class ThemeGridController: ViewController {
|
||||
}
|
||||
for wallpaper in wallpapers {
|
||||
if wallpaper == strongSelf.presentationData.chatWallpaper {
|
||||
let presentationData = strongSelf.presentationData
|
||||
let _ = (updatePresentationThemeSettingsInteractively(accountManager: strongSelf.context.sharedContext.accountManager, { current in
|
||||
var fallbackWallpaper: TelegramWallpaper = .builtin(WallpaperSettings())
|
||||
if case let .builtin(theme) = current.theme {
|
||||
switch theme {
|
||||
case .day:
|
||||
fallbackWallpaper = .color(0xffffff)
|
||||
case .night:
|
||||
fallbackWallpaper = .color(0x000000)
|
||||
case .nightAccent:
|
||||
fallbackWallpaper = .color(0x18222d)
|
||||
default:
|
||||
fallbackWallpaper = .builtin(WallpaperSettings())
|
||||
}
|
||||
var fallbackWallpaper = presentationData.theme.chat.defaultWallpaper
|
||||
if case let .cloud(info) = current.theme, let resolvedWallpaper = info.resolvedWallpaper {
|
||||
fallbackWallpaper = resolvedWallpaper
|
||||
}
|
||||
|
||||
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
|
||||
themeSpecificChatWallpapers[current.theme.index] = fallbackWallpaper
|
||||
themeSpecificChatWallpapers[current.theme.index] = nil
|
||||
return PresentationThemeSettings(chatWallpaper: fallbackWallpaper, theme: current.theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
|
||||
})).start()
|
||||
break
|
||||
@ -243,6 +234,7 @@ final class ThemeGridController: ViewController {
|
||||
strongSelf.present(controller, in: .window(.root))
|
||||
|
||||
let _ = resetWallpapers(account: strongSelf.context.account).start(completed: { [weak self, weak controller] in
|
||||
let presentationData = strongSelf.presentationData
|
||||
let _ = (strongSelf.context.sharedContext.accountManager.transaction { transaction -> Void in
|
||||
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings, { entry in
|
||||
let current: PresentationThemeSettings
|
||||
@ -251,22 +243,13 @@ final class ThemeGridController: ViewController {
|
||||
} else {
|
||||
current = PresentationThemeSettings.defaultSettings
|
||||
}
|
||||
let wallpaper: TelegramWallpaper
|
||||
if case let .builtin(theme) = current.theme {
|
||||
switch theme {
|
||||
case .day:
|
||||
wallpaper = .color(0xffffff)
|
||||
case .night:
|
||||
wallpaper = .color(0x000000)
|
||||
case .nightAccent:
|
||||
wallpaper = .color(0x18222d)
|
||||
default:
|
||||
wallpaper = .builtin(WallpaperSettings())
|
||||
}
|
||||
} else {
|
||||
wallpaper = .builtin(WallpaperSettings())
|
||||
var fallbackWallpaper = presentationData.theme.chat.defaultWallpaper
|
||||
if case let .cloud(info) = current.theme, let resolvedWallpaper = info.resolvedWallpaper {
|
||||
fallbackWallpaper = resolvedWallpaper
|
||||
}
|
||||
return PresentationThemeSettings(chatWallpaper: wallpaper, theme: current.theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: [:], fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
|
||||
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
|
||||
themeSpecificChatWallpapers[current.theme.index] = nil
|
||||
return PresentationThemeSettings(chatWallpaper: fallbackWallpaper, theme: current.theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: [:], fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
|
||||
})
|
||||
}).start()
|
||||
|
||||
|
@ -366,6 +366,16 @@ final class ThemeGridControllerNode: ASDisplayNode {
|
||||
|
||||
entries.insert(ThemeGridControllerEntry(index: 0, wallpaper: presentationData.chatWallpaper, selected: true), at: 0)
|
||||
|
||||
var defaultWallpaper: TelegramWallpaper?
|
||||
if !areWallpapersEqual(presentationData.chatWallpaper, presentationData.theme.chat.defaultWallpaper) {
|
||||
if case .builtin = presentationData.theme.chat.defaultWallpaper {
|
||||
} else {
|
||||
defaultWallpaper = presentationData.theme.chat.defaultWallpaper
|
||||
entries.insert(ThemeGridControllerEntry(index: 1, wallpaper: presentationData.theme.chat.defaultWallpaper, selected: false), at: 1)
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
|
||||
var sortedWallpapers: [TelegramWallpaper] = []
|
||||
if presentationData.theme.overallDarkAppearance {
|
||||
var darkWallpapers: [TelegramWallpaper] = []
|
||||
@ -386,7 +396,11 @@ final class ThemeGridControllerNode: ASDisplayNode {
|
||||
continue
|
||||
}
|
||||
let selected = areWallpapersEqual(presentationData.chatWallpaper, wallpaper)
|
||||
if !selected {
|
||||
var isDefault = false
|
||||
if let defaultWallpaper = defaultWallpaper, areWallpapersEqual(defaultWallpaper, wallpaper) {
|
||||
isDefault = true
|
||||
}
|
||||
if !selected && !isDefault {
|
||||
entries.append(ThemeGridControllerEntry(index: index, wallpaper: wallpaper, selected: false))
|
||||
}
|
||||
index += 1
|
||||
|
@ -11,6 +11,7 @@ import AccountContext
|
||||
import ShareController
|
||||
import CounterContollerTitleView
|
||||
import WallpaperResources
|
||||
import OverlayStatusController
|
||||
|
||||
public enum ThemePreviewSource {
|
||||
case theme(TelegramTheme)
|
||||
@ -32,6 +33,8 @@ public final class ThemePreviewController: ViewController {
|
||||
|
||||
private var presentationData: PresentationData
|
||||
private var presentationDataDisposable: Disposable?
|
||||
|
||||
private var applyDisposable = MetaDisposable()
|
||||
|
||||
public init(context: AccountContext, previewTheme: PresentationTheme, source: ThemePreviewSource) {
|
||||
self.context = context
|
||||
@ -75,7 +78,6 @@ public final class ThemePreviewController: ViewController {
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||
if let strongSelf = self {
|
||||
strongSelf.presentationData = presentationData
|
||||
strongSelf.updateStrings()
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -86,6 +88,7 @@ public final class ThemePreviewController: ViewController {
|
||||
|
||||
deinit {
|
||||
self.presentationDataDisposable?.dispose()
|
||||
self.applyDisposable.dispose()
|
||||
}
|
||||
|
||||
override public func viewDidAppear(_ animated: Bool) {
|
||||
@ -108,63 +111,7 @@ public final class ThemePreviewController: ViewController {
|
||||
}
|
||||
}, apply: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
let previewTheme = strongSelf.previewTheme
|
||||
let theme: Signal<PresentationThemeReference?, NoError>
|
||||
|
||||
switch strongSelf.source {
|
||||
case .theme, .slug:
|
||||
theme = combineLatest(strongSelf.theme.get() |> take(1), strongSelf.controllerNode.wallpaperPromise.get() |> take(1))
|
||||
|> map { theme, wallpaper in
|
||||
if let theme = theme {
|
||||
if case let .file(file) = wallpaper, file.id != 0 {
|
||||
return .cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: wallpaper))
|
||||
} else {
|
||||
return .cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil))
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
case .media:
|
||||
if let strings = encodePresentationTheme(previewTheme), let data = strings.data(using: .utf8) {
|
||||
let resource = LocalFileMediaResource(fileId: arc4random64())
|
||||
strongSelf.context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data)
|
||||
|
||||
theme = .single(.local(PresentationLocalTheme(title: previewTheme.name.string, resource: resource)))
|
||||
} else {
|
||||
theme = .single(.builtin(.dayClassic))
|
||||
}
|
||||
}
|
||||
|
||||
let signal = theme
|
||||
|> mapToSignal { theme -> Signal<Void, NoError> in
|
||||
guard let theme = theme else {
|
||||
return .complete()
|
||||
}
|
||||
return strongSelf.context.sharedContext.accountManager.transaction { transaction -> Void in
|
||||
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings, { entry in
|
||||
let current: PresentationThemeSettings
|
||||
if let entry = entry as? PresentationThemeSettings {
|
||||
current = entry
|
||||
} else {
|
||||
current = PresentationThemeSettings.defaultSettings
|
||||
}
|
||||
return PresentationThemeSettings(chatWallpaper: previewTheme.chat.defaultWallpaper, theme: theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// let resolvedWallpaper: TelegramWallpaper?
|
||||
// if let theme = theme, case let .file(file) = theme.chat.defaultWallpaper, file.id != 0 {
|
||||
// resolvedWallpaper = theme.chat.defaultWallpaper
|
||||
// updateCachedWallpaper(account: context.account, wallpaper: theme.chat.defaultWallpaper)
|
||||
// }
|
||||
|
||||
let _ = (signal |> deliverOnMainQueue).start(completed: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.dismiss()
|
||||
}
|
||||
})
|
||||
strongSelf.apply()
|
||||
}
|
||||
})
|
||||
self.displayNodeDidLoad()
|
||||
@ -180,8 +127,165 @@ public final class ThemePreviewController: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
private func updateStrings() {
|
||||
|
||||
private func apply() {
|
||||
let previewTheme = self.previewTheme
|
||||
let theme: Signal<PresentationThemeReference?, NoError>
|
||||
let context = self.context
|
||||
let wallpaperPromise = self.controllerNode.wallpaperPromise
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let disposable = self.applyDisposable
|
||||
|
||||
switch self.source {
|
||||
case .theme, .slug:
|
||||
theme = combineLatest(self.theme.get() |> take(1), wallpaperPromise.get() |> take(1))
|
||||
|> mapToSignal { theme, wallpaper -> Signal<PresentationThemeReference?, NoError> in
|
||||
if let theme = theme {
|
||||
if case let .file(file) = wallpaper, file.id != 0 {
|
||||
return .single(.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: wallpaper)))
|
||||
} else {
|
||||
return .single(.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil)))
|
||||
}
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
case .media:
|
||||
if let strings = encodePresentationTheme(previewTheme), let data = strings.data(using: .utf8) {
|
||||
let resource = LocalFileMediaResource(fileId: arc4random64())
|
||||
context.account.postbox.mediaBox.storeResourceData(resource.id, data: data)
|
||||
context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data)
|
||||
theme = .single(.local(PresentationLocalTheme(title: previewTheme.name.string, resource: resource, resolvedWallpaper: nil)))
|
||||
} else {
|
||||
theme = .single(.builtin(.dayClassic))
|
||||
}
|
||||
}
|
||||
|
||||
var resolvedWallpaper: TelegramWallpaper?
|
||||
|
||||
let signal = theme
|
||||
|> mapToSignal { theme -> Signal<PresentationThemeReference, NoError> in
|
||||
guard let theme = theme else {
|
||||
return .complete()
|
||||
}
|
||||
switch theme {
|
||||
case let .cloud(info):
|
||||
resolvedWallpaper = info.resolvedWallpaper
|
||||
return .single(theme)
|
||||
case let .local(info):
|
||||
return wallpaperPromise.get()
|
||||
|> take(1)
|
||||
|> mapToSignal { currentWallpaper -> Signal<PresentationThemeReference, NoError> in
|
||||
if case let .file(file) = currentWallpaper, file.id != 0 {
|
||||
resolvedWallpaper = currentWallpaper
|
||||
}
|
||||
|
||||
var wallpaperImage: UIImage?
|
||||
if case .file = currentWallpaper {
|
||||
wallpaperImage = chatControllerBackgroundImage(theme: previewTheme, wallpaper: currentWallpaper, mediaBox: context.sharedContext.accountManager.mediaBox, knockoutMode: false)
|
||||
}
|
||||
let themeThumbnail = generateImage(CGSize(width: 213, height: 320.0), contextGenerator: { size, context in
|
||||
if let image = generateImage(CGSize(width: 194.0, height: 291.0), contextGenerator: { size, c in
|
||||
drawThemeImage(context: c, theme: previewTheme, wallpaperImage: wallpaperImage, size: size)
|
||||
})?.cgImage {
|
||||
context.draw(image, in: CGRect(origin: CGPoint(), size: size))
|
||||
}
|
||||
}, scale: 1.0)
|
||||
let themeThumbnailData = themeThumbnail?.jpegData(compressionQuality: 0.6)
|
||||
|
||||
return telegramThemes(postbox: context.account.postbox, network: context.account.network, accountManager: context.sharedContext.accountManager)
|
||||
|> take(1)
|
||||
|> mapToSignal { themes -> Signal<PresentationThemeReference, NoError> in
|
||||
let similarTheme = themes.filter { $0.isCreator && $0.title == info.title }.first
|
||||
if let similarTheme = similarTheme {
|
||||
return updateTheme(account: context.account, accountManager: context.sharedContext.accountManager, theme: similarTheme, title: nil, slug: nil, resource: info.resource, thumbnailData: themeThumbnailData)
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<CreateThemeResult?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { result -> Signal<PresentationThemeReference, NoError> in
|
||||
guard let result = result else {
|
||||
let updatedTheme = PresentationLocalTheme(title: info.title, resource: info.resource, resolvedWallpaper: resolvedWallpaper)
|
||||
return .single(.local(updatedTheme))
|
||||
}
|
||||
if case let .result(theme) = result, let file = theme.file {
|
||||
context.sharedContext.accountManager.mediaBox.moveResourceData(from: info.resource.id, to: file.resource.id)
|
||||
return .single(.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: resolvedWallpaper)))
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
return createTheme(account: context.account, title: info.title, resource: info.resource, thumbnailData: themeThumbnailData)
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<CreateThemeResult?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { result -> Signal<PresentationThemeReference, NoError> in
|
||||
guard let result = result else {
|
||||
let updatedTheme = PresentationLocalTheme(title: info.title, resource: info.resource, resolvedWallpaper: resolvedWallpaper)
|
||||
return .single(.local(updatedTheme))
|
||||
}
|
||||
if case let .result(updatedTheme) = result, let file = updatedTheme.file {
|
||||
context.sharedContext.accountManager.mediaBox.moveResourceData(from: info.resource.id, to: file.resource.id)
|
||||
return .single(.cloud(PresentationCloudTheme(theme: updatedTheme, resolvedWallpaper: resolvedWallpaper)))
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case .builtin:
|
||||
return .single(theme)
|
||||
}
|
||||
}
|
||||
|> mapToSignal { theme -> Signal<Void, NoError> in
|
||||
if case let .cloud(info) = theme {
|
||||
let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: info.theme).start()
|
||||
let _ = saveThemeInteractively(account: context.account, accountManager: context.sharedContext.accountManager, theme: info.theme).start()
|
||||
}
|
||||
return context.sharedContext.accountManager.transaction { transaction -> Void in
|
||||
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings, { entry in
|
||||
let current = entry as? PresentationThemeSettings ?? PresentationThemeSettings.defaultSettings
|
||||
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
|
||||
themeSpecificChatWallpapers[theme.index] = nil
|
||||
return PresentationThemeSettings(chatWallpaper: resolvedWallpaper ?? previewTheme.chat.defaultWallpaper, theme: theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var cancelImpl: (() -> Void)?
|
||||
let progressSignal = Signal<Never, NoError> { [weak self] subscriber in
|
||||
let controller = OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .loading(cancelled: {
|
||||
cancelImpl?()
|
||||
}))
|
||||
self?.present(controller, in: .window(.root))
|
||||
return ActionDisposable { [weak controller] in
|
||||
Queue.mainQueue().async() {
|
||||
controller?.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|> runOn(Queue.mainQueue())
|
||||
|> delay(0.7, queue: Queue.mainQueue())
|
||||
|
||||
let progressDisposable = progressSignal.start()
|
||||
cancelImpl = {
|
||||
disposable.set(nil)
|
||||
}
|
||||
disposable.set((signal
|
||||
|> afterDisposed {
|
||||
Queue.mainQueue().async {
|
||||
progressDisposable.dispose()
|
||||
}
|
||||
}
|
||||
|> deliverOnMainQueue).start(completed: {[weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.dismiss()
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
override public func dismiss(completion: (() -> Void)? = nil) {
|
||||
|
@ -12,7 +12,7 @@ import ChatListUI
|
||||
import WallpaperResources
|
||||
|
||||
private func generateMaskImage(color: UIColor) -> UIImage? {
|
||||
return generateImage(CGSize(width: 1.0, height: 60.0), opaque: false, rotatedContext: { size, context in
|
||||
return generateImage(CGSize(width: 1.0, height: 80.0), opaque: false, rotatedContext: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.clear(bounds)
|
||||
|
||||
@ -22,7 +22,7 @@ private func generateMaskImage(color: UIColor) -> UIImage? {
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
|
||||
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: 60.0), options: CGGradientDrawingOptions())
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: 80.0), options: CGGradientDrawingOptions())
|
||||
})
|
||||
}
|
||||
|
||||
@ -55,6 +55,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
private var wallpaperDisposable: Disposable?
|
||||
private var colorDisposable: Disposable?
|
||||
private var statusDisposable: Disposable?
|
||||
private var fetchDisposable = MetaDisposable()
|
||||
|
||||
init(context: AccountContext, previewTheme: PresentationTheme, dismiss: @escaping () -> Void, apply: @escaping () -> Void) {
|
||||
self.context = context
|
||||
@ -140,8 +141,12 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
|
||||
self.colorDisposable = (self.wallpaperPromise.get()
|
||||
|> mapToSignal { wallpaper in
|
||||
return chatServiceBackgroundColor(wallpaper: wallpaper, mediaBox: context.account.postbox.mediaBox)
|
||||
|> mapToSignal { wallpaper -> Signal<UIColor, NoError> in
|
||||
if case let .file(file) = wallpaper, file.id == 0 {
|
||||
return .complete()
|
||||
} else {
|
||||
return chatServiceBackgroundColor(wallpaper: wallpaper, mediaBox: context.account.postbox.mediaBox)
|
||||
}
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] color in
|
||||
if let strongSelf = self {
|
||||
@ -161,16 +166,24 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
if case let .file(file) = wallpaper {
|
||||
let dimensions = file.file.dimensions ?? CGSize(width: 100.0, height: 100.0)
|
||||
let displaySize = dimensions.dividedByScreenScale().integralFloor
|
||||
|
||||
|
||||
var convertedRepresentations: [ImageRepresentationWithReference] = []
|
||||
for representation in file.file.previewRepresentations {
|
||||
convertedRepresentations.append(ImageRepresentationWithReference(representation: representation, reference: .wallpaper(resource: representation.resource)))
|
||||
convertedRepresentations.append(ImageRepresentationWithReference(representation: representation, reference: MediaResourceReference.media(media: .standalone(media: file.file), resource: representation.resource)))
|
||||
}
|
||||
convertedRepresentations.append(ImageRepresentationWithReference(representation: .init(dimensions: dimensions, resource: file.file.resource), reference: .wallpaper(resource: file.file.resource)))
|
||||
convertedRepresentations.append(ImageRepresentationWithReference(representation: .init(dimensions: dimensions, resource: file.file.resource), reference: .media(media: .standalone(media: file.file), resource: file.file.resource)))
|
||||
|
||||
let fileReference = FileMediaReference.standalone(media: file.file)
|
||||
let signal = wallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, fileReference: fileReference, representations: convertedRepresentations, alwaysShowThumbnailFirst: true, autoFetchFullSize: false)
|
||||
let signal = wallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, fileReference: fileReference, representations: convertedRepresentations, alwaysShowThumbnailFirst: false, autoFetchFullSize: false)
|
||||
|> afterNext { next in
|
||||
if let _ = context.sharedContext.accountManager.mediaBox.completedResourcePath(file.file.resource) {
|
||||
} else if let path = context.account.postbox.mediaBox.completedResourcePath(file.file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path)) {
|
||||
context.sharedContext.accountManager.mediaBox.storeResourceData(file.file.resource.id, data: data)
|
||||
}
|
||||
}
|
||||
strongSelf.remoteChatBackgroundNode.setSignal(signal)
|
||||
|
||||
strongSelf.fetchDisposable.set(freeMediaFileInteractiveFetched(account: context.account, fileReference: .standalone(media: file.file)).start())
|
||||
|
||||
let account = strongSelf.context.account
|
||||
let statusSignal = strongSelf.context.sharedContext.accountManager.mediaBox.resourceStatus(file.file.resource)
|
||||
@ -199,6 +212,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.colorDisposable?.dispose()
|
||||
self.wallpaperDisposable?.dispose()
|
||||
self.statusDisposable?.dispose()
|
||||
self.fetchDisposable.dispose()
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
@ -250,8 +264,9 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
let timestamp1 = timestamp + 120
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: selfPeer, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
|
||||
let presenceTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + 60 * 60)
|
||||
let timestamp2 = timestamp + 3660
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_2_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer2), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: "", attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer2), combinedReadState: nil, notificationSettings: nil, presence: TelegramUserPresence(status: .present(until: presenceTimestamp), lastActivity: presenceTimestamp), summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: [(peer2, .typingText)], isAd: false, ignoreUnreadBadge: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
|
||||
let timestamp3 = timestamp + 3200
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer3.id, namespace: 0, id: 0), timestamp: timestamp3)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer3.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp3, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer3Author, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer3), combinedReadState: nil, notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
@ -262,7 +277,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
let timestamp5 = timestamp + 1000
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer5.id, namespace: 0, id: 0), timestamp: timestamp5)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp5, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer5, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer5), combinedReadState: nil, notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer6.id, namespace: 0, id: 0), timestamp: timestamp - 360)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer6.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp - 360, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer6, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer6), combinedReadState: nil, notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer6.id, namespace: 0, id: 0), timestamp: timestamp - 360)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer6.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp - 360, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer6, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer6), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer7.id, namespace: 0, id: 0), timestamp: timestamp - 420)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer7.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp - 420, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer6, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_7_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer7), combinedReadState: nil, notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
|
||||
@ -329,7 +344,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
let voiceAttributes: [TelegramMediaFileAttribute] = [.Audio(isVoice: true, duration: 23, title: nil, performer: nil, waveform: MemoryBuffer(data: Data(base64Encoded: waveformBase64)!))]
|
||||
let voiceMedia = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: voiceAttributes)
|
||||
|
||||
let message3 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: [])
|
||||
let message3 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: [])
|
||||
items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message3, theme: self.previewTheme, strings: self.presentationData.strings, wallpaper: self.previewTheme.chat.defaultWallpaper, fontSize: self.presentationData.fontSize, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: FileMediaResourceStatus(mediaStatus: .playbackStatus(.paused), fetchStatus: .Local)))
|
||||
|
||||
let message4 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])
|
||||
@ -400,6 +415,6 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
let pageControlFrame = CGRect(origin: CGPoint(x: floor((bounds.width - pageControlSize.width) / 2.0), y: layout.size.height - toolbarHeight - 42.0), size: pageControlSize)
|
||||
self.pageControlNode.frame = pageControlFrame
|
||||
self.pageControlBackgroundNode.frame = CGRect(x: pageControlFrame.minX - 11.0, y: pageControlFrame.minY - 12.0, width: pageControlFrame.width + 22.0, height: 30.0)
|
||||
transition.updateFrame(node: self.maskNode, frame: CGRect(x: 0.0, y: layout.size.height - toolbarHeight - 60.0, width: bounds.width, height: 60.0))
|
||||
transition.updateFrame(node: self.maskNode, frame: CGRect(x: 0.0, y: layout.size.height - toolbarHeight - 80.0, width: bounds.width, height: 80.0))
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import ItemListUI
|
||||
import AlertUI
|
||||
import WallpaperResources
|
||||
import ShareController
|
||||
import AccountContext
|
||||
|
||||
@ -299,7 +300,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
||||
arguments.selectTheme(theme)
|
||||
}
|
||||
}, longTapped: { theme in
|
||||
arguments.presentThemeMenu(theme, theme == currentTheme)
|
||||
arguments.presentThemeMenu(theme, theme.index == currentTheme.index)
|
||||
})
|
||||
case let .iconHeader(theme, text):
|
||||
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||
@ -401,31 +402,11 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
||||
currentAppIconName.set(currentAppIcon?.name ?? "Blue")
|
||||
|
||||
let cloudThemes = Promise<[TelegramTheme]>()
|
||||
let updatedCloudThemes = telegramThemes(postbox: context.account.postbox, network: context.account.network)
|
||||
let updatedCloudThemes = telegramThemes(postbox: context.account.postbox, network: context.account.network, accountManager: context.sharedContext.accountManager)
|
||||
cloudThemes.set(updatedCloudThemes)
|
||||
|
||||
let arguments = ThemeSettingsControllerArguments(context: context, selectTheme: { theme in
|
||||
let _ = (context.sharedContext.accountManager.transaction { transaction -> Void in
|
||||
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings, { entry in
|
||||
let current: PresentationThemeSettings
|
||||
if let entry = entry as? PresentationThemeSettings {
|
||||
current = entry
|
||||
} else {
|
||||
current = PresentationThemeSettings.defaultSettings
|
||||
}
|
||||
|
||||
let chatWallpaper: TelegramWallpaper
|
||||
if let themeSpecificWallpaper = current.themeSpecificChatWallpapers[theme.index] {
|
||||
chatWallpaper = themeSpecificWallpaper
|
||||
} else {
|
||||
let accentColor = current.themeSpecificAccentColors[theme.index]?.color
|
||||
let theme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: theme, accentColor: accentColor, serviceBackgroundColor: defaultServiceBackgroundColor, baseColor: current.themeSpecificAccentColors[theme.index]?.baseColor ?? .blue)
|
||||
chatWallpaper = theme.chat.defaultWallpaper
|
||||
}
|
||||
|
||||
return PresentationThemeSettings(chatWallpaper: chatWallpaper, theme: theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
|
||||
})
|
||||
}).start()
|
||||
selectThemeImpl?(theme)
|
||||
}, selectFontSize: { size in
|
||||
let _ = updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
|
||||
return PresentationThemeSettings(chatWallpaper: current.chatWallpaper, theme: current.theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, fontSize: size, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
|
||||
@ -510,7 +491,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
||||
selectThemeImpl?(newTheme)
|
||||
}
|
||||
|
||||
let _ = deleteThemeInteractively(account: context.account, theme: theme.theme).start()
|
||||
let _ = deleteThemeInteractively(account: context.account, accountManager: context.sharedContext.accountManager, theme: theme.theme).start()
|
||||
})
|
||||
}))
|
||||
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
|
||||
@ -585,7 +566,52 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
||||
return controller?.navigationController as? NavigationController
|
||||
}
|
||||
selectThemeImpl = { theme in
|
||||
arguments.selectTheme(theme)
|
||||
let presentationTheme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: theme, accentColor: nil, serviceBackgroundColor: .black, baseColor: nil)
|
||||
|
||||
let resolvedWallpaper: Signal<TelegramWallpaper?, NoError>
|
||||
if case let .file(file) = presentationTheme.chat.defaultWallpaper, file.id == 0 {
|
||||
resolvedWallpaper = cachedWallpaper(account: context.account, slug: file.slug)
|
||||
|> map { wallpaper -> TelegramWallpaper? in
|
||||
return wallpaper?.wallpaper
|
||||
}
|
||||
} else {
|
||||
resolvedWallpaper = .single(nil)
|
||||
}
|
||||
|
||||
var cloudTheme: TelegramTheme?
|
||||
if case let .cloud(theme) = theme {
|
||||
cloudTheme = theme.theme
|
||||
}
|
||||
let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: cloudTheme).start()
|
||||
|
||||
let _ = (resolvedWallpaper
|
||||
|> mapToSignal { resolvedWallpaper -> Signal<Void, NoError> in
|
||||
var updatedTheme = theme
|
||||
if case let .cloud(info) = theme {
|
||||
updatedTheme = .cloud(PresentationCloudTheme(theme: info.theme, resolvedWallpaper: resolvedWallpaper))
|
||||
}
|
||||
|
||||
return (context.sharedContext.accountManager.transaction { transaction -> Void in
|
||||
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings, { entry in
|
||||
let current: PresentationThemeSettings
|
||||
if let entry = entry as? PresentationThemeSettings {
|
||||
current = entry
|
||||
} else {
|
||||
current = PresentationThemeSettings.defaultSettings
|
||||
}
|
||||
|
||||
let chatWallpaper: TelegramWallpaper
|
||||
if let themeSpecificWallpaper = current.themeSpecificChatWallpapers[updatedTheme.index] {
|
||||
chatWallpaper = themeSpecificWallpaper
|
||||
} else {
|
||||
let presentationTheme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: updatedTheme, accentColor: current.themeSpecificAccentColors[updatedTheme.index]?.color, serviceBackgroundColor: .black, baseColor: nil)
|
||||
chatWallpaper = resolvedWallpaper ?? presentationTheme.chat.defaultWallpaper
|
||||
}
|
||||
|
||||
return PresentationThemeSettings(chatWallpaper: chatWallpaper, theme: updatedTheme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
|
||||
})
|
||||
})
|
||||
}).start()
|
||||
}
|
||||
moreImpl = {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
@ -615,7 +641,7 @@ public final class ThemeSettingsCrossfadeController: ViewController {
|
||||
private let snapshotView: UIView?
|
||||
|
||||
public init() {
|
||||
self.snapshotView = UIScreen.main.snapshotView(afterScreenUpdates: false)
|
||||
self.snapshotView = UIScreen.main.snapshotView(afterScreenUpdates: true)
|
||||
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
|
||||
@ -631,6 +657,7 @@ public final class ThemeSettingsCrossfadeController: ViewController {
|
||||
|
||||
self.displayNode.backgroundColor = nil
|
||||
self.displayNode.isOpaque = false
|
||||
self.displayNode.isUserInteractionEnabled = false
|
||||
if let snapshotView = self.snapshotView {
|
||||
self.displayNode.view.addSubview(snapshotView)
|
||||
}
|
||||
@ -639,7 +666,7 @@ public final class ThemeSettingsCrossfadeController: ViewController {
|
||||
override public func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
self.displayNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak self] _ in
|
||||
self.displayNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak self] _ in
|
||||
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||
})
|
||||
}
|
||||
|
@ -11,34 +11,43 @@ import ItemListUI
|
||||
import WallpaperResources
|
||||
import AccountContext
|
||||
|
||||
private var borderImages: [String: UIImage] = [:]
|
||||
|
||||
private func generateBorderImage(theme: PresentationTheme, bordered: Bool, selected: Bool) -> UIImage? {
|
||||
return generateImage(CGSize(width: 30.0, height: 30.0), rotatedContext: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.setFillColor(theme.list.itemBlocksBackgroundColor.cgColor)
|
||||
context.fill(bounds)
|
||||
|
||||
context.setBlendMode(.clear)
|
||||
context.fillEllipse(in: bounds)
|
||||
context.setBlendMode(.normal)
|
||||
|
||||
let lineWidth: CGFloat
|
||||
if selected {
|
||||
var accentColor = theme.list.itemAccentColor
|
||||
if accentColor.rgb == 0xffffff {
|
||||
accentColor = UIColor(rgb: 0x999999)
|
||||
let key = "\(theme.list.itemBlocksBackgroundColor.hexString)_\(selected ? "s" + theme.list.itemAccentColor.hexString : theme.list.disclosureArrowColor.hexString)"
|
||||
if let image = borderImages[key] {
|
||||
return image
|
||||
} else {
|
||||
let image = generateImage(CGSize(width: 30.0, height: 30.0), rotatedContext: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.setFillColor(theme.list.itemBlocksBackgroundColor.cgColor)
|
||||
context.fill(bounds)
|
||||
|
||||
context.setBlendMode(.clear)
|
||||
context.fillEllipse(in: bounds)
|
||||
context.setBlendMode(.normal)
|
||||
|
||||
let lineWidth: CGFloat
|
||||
if selected {
|
||||
var accentColor = theme.list.itemAccentColor
|
||||
if accentColor.rgb == 0xffffff {
|
||||
accentColor = UIColor(rgb: 0x999999)
|
||||
}
|
||||
context.setStrokeColor(accentColor.cgColor)
|
||||
lineWidth = 2.0
|
||||
} else {
|
||||
context.setStrokeColor(theme.list.disclosureArrowColor.withAlphaComponent(0.4).cgColor)
|
||||
lineWidth = 1.0
|
||||
}
|
||||
context.setStrokeColor(accentColor.cgColor)
|
||||
lineWidth = 2.0
|
||||
} else {
|
||||
context.setStrokeColor(theme.list.disclosureArrowColor.withAlphaComponent(0.4).cgColor)
|
||||
lineWidth = 1.0
|
||||
}
|
||||
|
||||
if bordered || selected {
|
||||
context.setLineWidth(lineWidth)
|
||||
context.strokeEllipse(in: bounds.insetBy(dx: lineWidth / 2.0, dy: lineWidth / 2.0))
|
||||
}
|
||||
})?.stretchableImage(withLeftCapWidth: 15, topCapHeight: 15)
|
||||
|
||||
if bordered || selected {
|
||||
context.setLineWidth(lineWidth)
|
||||
context.strokeEllipse(in: bounds.insetBy(dx: lineWidth / 2.0, dy: lineWidth / 2.0))
|
||||
}
|
||||
})?.stretchableImage(withLeftCapWidth: 15, topCapHeight: 15)
|
||||
borderImages[key] = image
|
||||
return image
|
||||
}
|
||||
}
|
||||
|
||||
private func createThemeImage(theme: PresentationTheme) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
||||
@ -66,78 +75,6 @@ private func createThemeImage(theme: PresentationTheme) -> Signal<(TransformImag
|
||||
}
|
||||
}
|
||||
|
||||
private func themeIconImage(context: AccountContext, theme: PresentationThemeReference, accentColor: UIColor?) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
||||
let signal: Signal<(UIColor, UIColor, UIColor), NoError>
|
||||
if case let .builtin(theme) = theme {
|
||||
switch theme {
|
||||
case .dayClassic:
|
||||
signal = .single((UIColor(rgb: 0xd6e2ee), UIColor(rgb: 0xffffff), UIColor(rgb: 0xe1ffc7)))
|
||||
case .day:
|
||||
signal = .single((.white, UIColor(rgb: 0xd5dde6), accentColor ?? UIColor(rgb: 0x007aff)))
|
||||
case .night:
|
||||
signal = .single((.black, UIColor(rgb: 0x1f1f1f), accentColor ?? UIColor(rgb: 0x313131)))
|
||||
case .nightAccent:
|
||||
let accentColor = accentColor ?? UIColor(rgb: 0x007aff)
|
||||
signal = .single((accentColor.withMultiplied(hue: 1.024, saturation: 0.573, brightness: 0.18), accentColor.withMultiplied(hue: 1.024, saturation: 0.585, brightness: 0.25), accentColor.withMultiplied(hue: 1.019, saturation: 0.731, brightness: 0.59)))
|
||||
}
|
||||
} else {
|
||||
var resource: MediaResource?
|
||||
if case let .local(theme) = theme {
|
||||
resource = theme.resource
|
||||
} else if case let .cloud(theme) = theme {
|
||||
resource = theme.theme.file?.resource
|
||||
}
|
||||
if let resource = resource {
|
||||
signal = telegramThemeData(account: context.account, accountManager: context.sharedContext.accountManager, resource: resource, synchronousLoad: false)
|
||||
|> mapToSignal { data -> Signal<(UIColor, UIColor, UIColor), NoError> in
|
||||
if let data = data, let theme = makePresentationTheme(data: data) {
|
||||
let backgroundColor: UIColor
|
||||
switch theme.chat.defaultWallpaper {
|
||||
case .builtin:
|
||||
backgroundColor = UIColor(rgb: 0xd6e2ee)
|
||||
case let .color(color):
|
||||
backgroundColor = UIColor(rgb: UInt32(bitPattern: color))
|
||||
default:
|
||||
backgroundColor = theme.chatList.backgroundColor
|
||||
}
|
||||
return .single((backgroundColor, theme.chat.message.incoming.bubble.withoutWallpaper.fill ,theme.chat.message.outgoing.bubble.withoutWallpaper.fill))
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
signal = .never()
|
||||
}
|
||||
}
|
||||
return signal
|
||||
|> map { colors in
|
||||
return { arguments in
|
||||
let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: arguments.emptyColor == nil)
|
||||
let drawingRect = arguments.drawingRect
|
||||
|
||||
context.withContext { c in
|
||||
c.setFillColor(colors.0.cgColor)
|
||||
c.fill(drawingRect)
|
||||
|
||||
let incoming = generateTintedImage(image: UIImage(bundleImageName: "Settings/ThemeBubble"), color: colors.1)
|
||||
let outgoing = generateTintedImage(image: UIImage(bundleImageName: "Settings/ThemeBubble"), color: colors.2)
|
||||
|
||||
c.translateBy(x: drawingRect.width / 2.0, y: drawingRect.height / 2.0)
|
||||
c.scaleBy(x: 1.0, y: -1.0)
|
||||
c.translateBy(x: -drawingRect.width / 2.0, y: -drawingRect.height / 2.0)
|
||||
|
||||
c.draw(incoming!.cgImage!, in: CGRect(x: 9.0, y: 34.0, width: 57.0, height: 16.0))
|
||||
|
||||
c.translateBy(x: drawingRect.width / 2.0, y: drawingRect.height / 2.0)
|
||||
c.scaleBy(x: -1.0, y: 1.0)
|
||||
c.translateBy(x: -drawingRect.width / 2.0, y: -drawingRect.height / 2.0)
|
||||
c.draw(outgoing!.cgImage!, in: CGRect(x: 9.0, y: 12.0, width: 57.0, height: 16.0))
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ThemeSettingsThemeItem: ListViewItem, ItemListItem {
|
||||
var sectionId: ItemListSectionId
|
||||
@ -206,6 +143,11 @@ private final class ThemeSettingsThemeItemIconNode : ASDisplayNode {
|
||||
private var action: (() -> Void)?
|
||||
private var longTapAction: (() -> Void)?
|
||||
|
||||
private var theme: PresentationThemeReference?
|
||||
private var currentTheme: PresentationTheme?
|
||||
private var bordered: Bool?
|
||||
private var selected: Bool?
|
||||
|
||||
override init() {
|
||||
self.imageNode = TransformImageNode()
|
||||
self.imageNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 98.0, height: 62.0))
|
||||
@ -228,12 +170,23 @@ private final class ThemeSettingsThemeItemIconNode : ASDisplayNode {
|
||||
|
||||
func setup(context: AccountContext, theme: PresentationThemeReference, accentColor: UIColor?, currentTheme: PresentationTheme, title: NSAttributedString, bordered: Bool, selected: Bool, action: @escaping () -> Void, longTapAction: @escaping () -> Void) {
|
||||
if case let .cloud(theme) = theme, theme.theme.file == nil {
|
||||
self.imageNode.setSignal(createThemeImage(theme: currentTheme))
|
||||
if self.currentTheme == nil || currentTheme !== self.currentTheme! {
|
||||
self.imageNode.setSignal(createThemeImage(theme: currentTheme))
|
||||
self.currentTheme = currentTheme
|
||||
}
|
||||
} else {
|
||||
self.imageNode.setSignal(themeIconImage(context: context, theme: theme, accentColor: accentColor))
|
||||
if theme != self.theme {
|
||||
self.imageNode.setSignal(themeIconImage(account: context.account, accountManager: context.sharedContext.accountManager, theme: theme, accentColor: accentColor))
|
||||
self.theme = theme
|
||||
}
|
||||
}
|
||||
if self.currentTheme == nil || currentTheme !== self.currentTheme! || bordered != self.bordered || selected != self.selected {
|
||||
self.overlayNode.image = generateBorderImage(theme: currentTheme, bordered: bordered, selected: selected)
|
||||
self.currentTheme = currentTheme
|
||||
self.bordered = bordered
|
||||
self.selected = selected
|
||||
}
|
||||
self.textNode.attributedText = title
|
||||
self.overlayNode.image = generateBorderImage(theme: currentTheme, bordered: bordered, selected: selected)
|
||||
self.action = {
|
||||
action()
|
||||
}
|
||||
|
@ -2955,14 +2955,14 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
|
||||
updatedEntries.append(OrderedItemListEntry(id: id, contents: theme))
|
||||
}
|
||||
transaction.replaceOrderedItemListItems(collectionId: Namespaces.OrderedItemList.CloudThemes, items: updatedEntries)
|
||||
accountManager.transaction { transaction in
|
||||
let _ = accountManager.transaction { transaction in
|
||||
transaction.updateSharedData(SharedDataKeys.themeSettings, { current in
|
||||
if let current = current as? ThemeSettings, let theme = current.currentTheme, let updatedTheme = updatedThemes[theme.id] {
|
||||
return ThemeSettings(currentTheme: updatedTheme)
|
||||
}
|
||||
return current
|
||||
})
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
addedIncomingMessageIds.append(contentsOf: addedSecretMessageIds)
|
||||
|
@ -33,7 +33,7 @@ private let themeFormat = "ios"
|
||||
private let themeFileExtension = "tgios-theme"
|
||||
#endif
|
||||
|
||||
public func telegramThemes(postbox: Postbox, network: Network, forceUpdate: Bool = false) -> Signal<[TelegramTheme], NoError> {
|
||||
public func telegramThemes(postbox: Postbox, network: Network, accountManager: AccountManager, forceUpdate: Bool = false) -> Signal<[TelegramTheme], NoError> {
|
||||
let fetch: ([TelegramTheme]?, Int32?) -> Signal<[TelegramTheme], NoError> = { current, hash in
|
||||
network.request(Api.functions.account.getThemes(format: themeFormat, hash: hash ?? 0))
|
||||
|> retryRequest
|
||||
@ -51,6 +51,19 @@ public func telegramThemes(postbox: Postbox, network: Network, forceUpdate: Bool
|
||||
}
|
||||
}
|
||||
|> mapToSignal { items, hash -> Signal<[TelegramTheme], NoError> in
|
||||
let _ = accountManager.transaction { transaction in
|
||||
transaction.updateSharedData(SharedDataKeys.themeSettings, { current in
|
||||
var updated = current as? ThemeSettings ?? ThemeSettings(currentTheme: nil)
|
||||
for theme in items {
|
||||
if theme.id == updated.currentTheme?.id {
|
||||
updated = ThemeSettings(currentTheme: theme)
|
||||
break
|
||||
}
|
||||
}
|
||||
return updated
|
||||
})
|
||||
}.start()
|
||||
|
||||
return postbox.transaction { transaction -> [TelegramTheme] in
|
||||
var entries: [OrderedItemListEntry] = []
|
||||
for item in items {
|
||||
@ -135,7 +148,7 @@ private func checkThemeUpdated(network: Network, theme: TelegramTheme) -> Signal
|
||||
}
|
||||
}
|
||||
|
||||
private func saveUnsaveTheme(account: Account, theme: TelegramTheme, unsave: Bool) -> Signal<Void, NoError> {
|
||||
private func saveUnsaveTheme(account: Account, accountManager: AccountManager, theme: TelegramTheme, unsave: Bool) -> Signal<Void, NoError> {
|
||||
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
|
||||
let entries = transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudThemes)
|
||||
var items = entries.map { $0.contents as! TelegramTheme }
|
||||
@ -156,7 +169,7 @@ private func saveUnsaveTheme(account: Account, theme: TelegramTheme, unsave: Boo
|
||||
return .complete()
|
||||
}
|
||||
|> mapToSignal { _ -> Signal<Void, NoError> in
|
||||
return telegramThemes(postbox: account.postbox, network: account.network, forceUpdate: true)
|
||||
return telegramThemes(postbox: account.postbox, network: account.network, accountManager: accountManager, forceUpdate: true)
|
||||
|> take(1)
|
||||
|> mapToSignal { _ -> Signal<Void, NoError> in
|
||||
return .complete()
|
||||
@ -316,7 +329,7 @@ public func createTheme(account: Account, title: String, resource: MediaResource
|
||||
}
|
||||
}
|
||||
|
||||
public func updateTheme(account: Account, theme: TelegramTheme, title: String?, slug: String?, resource: MediaResource?, thumbnailData: Data? = nil) -> Signal<CreateThemeResult, CreateThemeError> {
|
||||
public func updateTheme(account: Account, accountManager: AccountManager, theme: TelegramTheme, title: String?, slug: String?, resource: MediaResource?, thumbnailData: Data? = nil) -> Signal<CreateThemeResult, CreateThemeError> {
|
||||
guard title != nil || slug != nil || resource != nil else {
|
||||
return .complete()
|
||||
}
|
||||
@ -366,13 +379,22 @@ public func updateTheme(account: Account, theme: TelegramTheme, title: String?,
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { apiTheme -> Signal<CreateThemeResult, CreateThemeError> in
|
||||
if let result = TelegramTheme(apiTheme: apiTheme) {
|
||||
if let updatedTheme = TelegramTheme(apiTheme: apiTheme) {
|
||||
let _ = accountManager.transaction { transaction in
|
||||
transaction.updateSharedData(SharedDataKeys.themeSettings, { current in
|
||||
var updated = current as? ThemeSettings ?? ThemeSettings(currentTheme: nil)
|
||||
if updatedTheme.id == updated.currentTheme?.id {
|
||||
updated = ThemeSettings(currentTheme: updatedTheme)
|
||||
}
|
||||
return updated
|
||||
})
|
||||
}.start()
|
||||
return account.postbox.transaction { transaction -> CreateThemeResult in
|
||||
let entries = transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudThemes)
|
||||
let items = entries.map { entry -> TelegramTheme in
|
||||
let theme = entry.contents as! TelegramTheme
|
||||
if theme.id == result.id {
|
||||
return result
|
||||
if theme.id == updatedTheme.id {
|
||||
return updatedTheme
|
||||
} else {
|
||||
return theme
|
||||
}
|
||||
@ -384,7 +406,7 @@ public func updateTheme(account: Account, theme: TelegramTheme, title: String?,
|
||||
updatedEntries.append(OrderedItemListEntry(id: id, contents: item))
|
||||
}
|
||||
transaction.replaceOrderedItemListItems(collectionId: Namespaces.OrderedItemList.CloudThemes, items: updatedEntries)
|
||||
return .result(result)
|
||||
return .result(updatedTheme)
|
||||
}
|
||||
|> introduceError(CreateThemeError.self)
|
||||
} else {
|
||||
@ -426,12 +448,12 @@ public final class ThemeSettings: PreferencesEntry, Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
public func saveThemeInteractively(account: Account, theme: TelegramTheme) -> Signal<Void, NoError> {
|
||||
return saveUnsaveTheme(account: account, theme: theme, unsave: false)
|
||||
public func saveThemeInteractively(account: Account, accountManager: AccountManager, theme: TelegramTheme) -> Signal<Void, NoError> {
|
||||
return saveUnsaveTheme(account: account, accountManager: accountManager, theme: theme, unsave: false)
|
||||
}
|
||||
|
||||
public func deleteThemeInteractively(account: Account, theme: TelegramTheme) -> Signal<Void, NoError> {
|
||||
return saveUnsaveTheme(account: account, theme: theme, unsave: true)
|
||||
public func deleteThemeInteractively(account: Account, accountManager: AccountManager, theme: TelegramTheme) -> Signal<Void, NoError> {
|
||||
return saveUnsaveTheme(account: account, accountManager: accountManager, theme: theme, unsave: true)
|
||||
}
|
||||
|
||||
public func applyTheme(accountManager: AccountManager, account: Account, theme: TelegramTheme?) -> Signal<Never, NoError> {
|
||||
@ -450,46 +472,104 @@ public func applyTheme(accountManager: AccountManager, account: Account, theme:
|
||||
}
|
||||
|
||||
func managedThemesUpdates(accountManager: AccountManager, postbox: Postbox, network: Network) -> Signal<Void, NoError> {
|
||||
let currentTheme = Atomic<TelegramTheme?>(value: nil)
|
||||
return accountManager.sharedData(keys: [SharedDataKeys.themeSettings])
|
||||
|> mapToSignal { sharedData -> Signal<Void, NoError> in
|
||||
|> map { sharedData -> TelegramTheme? in
|
||||
let themeSettings = (sharedData.entries[SharedDataKeys.themeSettings] as? ThemeSettings) ?? ThemeSettings(currentTheme: nil)
|
||||
if let currentTheme = themeSettings.currentTheme {
|
||||
return themeSettings.currentTheme
|
||||
}
|
||||
|> filter { theme in
|
||||
return theme?.id != currentTheme.with({ $0 })?.id
|
||||
}
|
||||
|> mapToSignal { theme -> Signal<Void, NoError> in
|
||||
let _ = currentTheme.swap(theme)
|
||||
if let theme = theme {
|
||||
let poll = Signal<Void, NoError> { subscriber in
|
||||
return checkThemeUpdated(network: network, theme: currentTheme).start(next: { result in
|
||||
let actualTheme = currentTheme.with { $0 } ?? theme
|
||||
return checkThemeUpdated(network: network, theme: actualTheme).start(next: { result in
|
||||
if case let .updated(updatedTheme) = result {
|
||||
let _ = currentTheme.swap(theme)
|
||||
let _ = accountManager.transaction { transaction in
|
||||
transaction.updateSharedData(SharedDataKeys.themeSettings, { _ in
|
||||
return ThemeSettings(currentTheme: updatedTheme)
|
||||
})
|
||||
}.start()
|
||||
let _ = postbox.transaction { transaction in
|
||||
|
||||
}
|
||||
let entries = transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudThemes)
|
||||
let items = entries.map { entry -> TelegramTheme in
|
||||
let theme = entry.contents as! TelegramTheme
|
||||
if theme.id == updatedTheme.id {
|
||||
return updatedTheme
|
||||
} else {
|
||||
return theme
|
||||
}
|
||||
}
|
||||
var updatedEntries: [OrderedItemListEntry] = []
|
||||
for item in items {
|
||||
var intValue = Int32(updatedEntries.count)
|
||||
let id = MemoryBuffer(data: Data(bytes: &intValue, count: 4))
|
||||
updatedEntries.append(OrderedItemListEntry(id: id, contents: item))
|
||||
}
|
||||
transaction.replaceOrderedItemListItems(collectionId: Namespaces.OrderedItemList.CloudThemes, items: updatedEntries)
|
||||
}.start()
|
||||
}
|
||||
subscriber.putCompletion()
|
||||
})
|
||||
}
|
||||
return ((.complete() |> suspendAwareDelay(1.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue())) |> then(poll)) |> restart
|
||||
return (poll |> then(.complete() |> suspendAwareDelay(1.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func actualizedTheme(accountManager: AccountManager, theme: TelegramTheme) -> Signal<TelegramTheme, NoError> {
|
||||
private func areThemesEqual(_ lhs: TelegramTheme, _ rhs: TelegramTheme) -> Bool {
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
if lhs.slug != rhs.slug {
|
||||
return false
|
||||
}
|
||||
if lhs.file?.id != rhs.file?.id {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public func actualizedTheme(account: Account, accountManager: AccountManager, theme: TelegramTheme) -> Signal<TelegramTheme, NoError> {
|
||||
var currentTheme = theme
|
||||
return accountManager.sharedData(keys: [SharedDataKeys.themeSettings])
|
||||
|> map { sharedData -> TelegramTheme in
|
||||
|> mapToSignal { sharedData -> Signal<TelegramTheme, NoError> in
|
||||
let themeSettings = (sharedData.entries[SharedDataKeys.themeSettings] as? ThemeSettings) ?? ThemeSettings(currentTheme: nil)
|
||||
if let updatedTheme = themeSettings.currentTheme, updatedTheme.id == currentTheme.id {
|
||||
if updatedTheme != currentTheme {
|
||||
if let updatedTheme = themeSettings.currentTheme, updatedTheme.id == theme.id {
|
||||
if !areThemesEqual(updatedTheme, currentTheme) {
|
||||
currentTheme = updatedTheme
|
||||
return updatedTheme
|
||||
return .single(updatedTheme)
|
||||
} else {
|
||||
return currentTheme
|
||||
return .single(currentTheme)
|
||||
}
|
||||
} else {
|
||||
return theme
|
||||
return account.postbox.combinedView(keys: [PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudThemes)])
|
||||
|> map { view -> [TelegramTheme] in
|
||||
if let view = view.views[.orderedItemList(id: Namespaces.OrderedItemList.CloudThemes)] as? OrderedItemListView {
|
||||
return view.items.compactMap { $0.contents as? TelegramTheme }
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|> map { themes -> TelegramTheme in
|
||||
let updatedTheme = themes.filter { $0.id == theme.id }.first
|
||||
if let updatedTheme = updatedTheme {
|
||||
if !areThemesEqual(updatedTheme, currentTheme) {
|
||||
currentTheme = updatedTheme
|
||||
return updatedTheme
|
||||
} else {
|
||||
return currentTheme
|
||||
}
|
||||
} else {
|
||||
return currentTheme
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,14 @@ private func makeDarkPresentationTheme(accentColor: UIColor, baseColor: Presenta
|
||||
let hsv = accentColor.hsv
|
||||
accentColor = UIColor(hue: hsv.0, saturation: hsv.1, brightness: max(hsv.2, 0.18), alpha: 1.0)
|
||||
|
||||
let secondaryBadgeTextColor: UIColor
|
||||
let lightness = accentColor.lightness
|
||||
if lightness > 0.7 {
|
||||
secondaryBadgeTextColor = .black
|
||||
} else {
|
||||
secondaryBadgeTextColor = .white
|
||||
}
|
||||
|
||||
let mainBackgroundColor = accentColor.withMultiplied(hue: 1.024, saturation: 0.585, brightness: 0.25)
|
||||
let mainSelectionColor = accentColor.withMultiplied(hue: 1.03, saturation: 0.585, brightness: 0.12)
|
||||
let additionalBackgroundColor = accentColor.withMultiplied(hue: 1.024, saturation: 0.573, brightness: 0.18)
|
||||
@ -126,7 +134,7 @@ private func makeDarkPresentationTheme(accentColor: UIColor, baseColor: Presenta
|
||||
itemCheckColors: PresentationThemeFillStrokeForeground(
|
||||
fillColor: accentColor,
|
||||
strokeColor: mainSecondaryTextColor.withAlphaComponent(0.5),
|
||||
foregroundColor: .white
|
||||
foregroundColor: secondaryBadgeTextColor
|
||||
),
|
||||
controlSecondaryColor: mainSecondaryTextColor.withAlphaComponent(0.5),
|
||||
freeInputField: PresentationInputFieldTheme(
|
||||
@ -161,7 +169,7 @@ private func makeDarkPresentationTheme(accentColor: UIColor, baseColor: Presenta
|
||||
failedForegroundColor: .white,
|
||||
muteIconColor: mainSecondaryTextColor.withAlphaComponent(0.4),
|
||||
unreadBadgeActiveBackgroundColor: accentColor,
|
||||
unreadBadgeActiveTextColor: UIColor(rgb: 0xffffff),
|
||||
unreadBadgeActiveTextColor: secondaryBadgeTextColor,
|
||||
unreadBadgeInactiveBackgroundColor: mainSecondaryTextColor.withAlphaComponent(0.4),
|
||||
unreadBadgeInactiveTextColor: additionalBackgroundColor,
|
||||
pinnedBadgeColor: mainSecondaryTextColor.withAlphaComponent(0.5),
|
||||
@ -293,7 +301,7 @@ private func makeDarkPresentationTheme(accentColor: UIColor, baseColor: Presenta
|
||||
inputPlaceholderColor: mainSecondaryColor,
|
||||
inputTextColor: .white,
|
||||
inputClearButtonColor: mainSecondaryColor,
|
||||
checkContentColor: .white
|
||||
checkContentColor: secondaryBadgeTextColor
|
||||
)
|
||||
|
||||
let contextMenu = PresentationThemeContextMenu(
|
||||
|
@ -24,7 +24,7 @@ public func makePresentationTheme(mediaBox: MediaBox, themeReference: Presentati
|
||||
case let .builtin(reference):
|
||||
theme = makeDefaultPresentationTheme(reference: reference, accentColor: accentColor, serviceBackgroundColor: serviceBackgroundColor, baseColor: baseColor, preview: preview)
|
||||
case let .local(info):
|
||||
if let path = mediaBox.completedResourcePath(info.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead), let loadedTheme = makePresentationTheme(data: data) {
|
||||
if let path = mediaBox.completedResourcePath(info.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead), let loadedTheme = makePresentationTheme(data: data, resolvedWallpaper: info.resolvedWallpaper) {
|
||||
theme = loadedTheme
|
||||
} else {
|
||||
theme = makeDefaultPresentationTheme(reference: .dayClassic, accentColor: nil, serviceBackgroundColor: serviceBackgroundColor, baseColor: baseColor, preview: preview)
|
||||
|
@ -465,14 +465,12 @@ public func chatServiceBackgroundColor(wallpaper: TelegramWallpaper, mediaBox: M
|
||||
}
|
||||
} else {
|
||||
return Signal<UIColor, NoError> { subscriber in
|
||||
let fetch = mediaBox.fetchedResource(file.file.resource, parameters: nil).start()
|
||||
let data = serviceColor(for: mediaBox.resourceData(file.file.resource)).start(next: { next in
|
||||
subscriber.putNext(next)
|
||||
}, completed: {
|
||||
subscriber.putCompletion()
|
||||
})
|
||||
return ActionDisposable {
|
||||
fetch.dispose()
|
||||
data.dispose()
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ extension TelegramWallpaper: Codable {
|
||||
case "builtin":
|
||||
self = .builtin(WallpaperSettings())
|
||||
default:
|
||||
if value.count == 6, let color = UIColor(hexString: value) {
|
||||
if [6,7].contains(value.count), let color = UIColor(hexString: value) {
|
||||
self = .color(Int32(bitPattern: color.rgb))
|
||||
} else {
|
||||
self = .file(id: 0, accessHash: 0, isCreator: false, isDefault: false, isPattern: false, isDark: false, slug: value, file: TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], immediateThumbnailData: nil, mimeType: "", size: nil, attributes: []), settings: WallpaperSettings(blur: false, motion: false, color: nil, intensity: nil))
|
||||
|
@ -225,13 +225,13 @@ public final class PrincipalThemeEssentialGraphics {
|
||||
self.checkBubblePartialImage = generateCheckImage(partial: true, color: theme.message.outgoingCheckColor)!
|
||||
self.chatMessageBackgroundIncomingHighlightedImage = emptyImage
|
||||
self.chatMessageBackgroundIncomingMergedTopMaskImage = emptyImage
|
||||
self.chatMessageBackgroundIncomingMergedTopImage = emptyImage
|
||||
self.chatMessageBackgroundIncomingMergedTopImage = messageBubbleImage(incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout)
|
||||
self.chatMessageBackgroundIncomingMergedTopHighlightedImage = emptyImage
|
||||
self.chatMessageBackgroundIncomingMergedTopSideMaskImage = emptyImage
|
||||
self.chatMessageBackgroundIncomingMergedTopSideImage = emptyImage
|
||||
self.chatMessageBackgroundIncomingMergedTopSideHighlightedImage = emptyImage
|
||||
self.chatMessageBackgroundIncomingMergedBottomMaskImage = emptyImage
|
||||
self.chatMessageBackgroundIncomingMergedBottomImage = emptyImage
|
||||
self.chatMessageBackgroundIncomingMergedBottomImage = messageBubbleImage(incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout)
|
||||
self.chatMessageBackgroundIncomingMergedBottomHighlightedImage = emptyImage
|
||||
self.chatMessageBackgroundIncomingMergedBothMaskImage = emptyImage
|
||||
self.chatMessageBackgroundIncomingMergedBothImage = emptyImage
|
||||
|
@ -111,6 +111,7 @@ public final class AccountContextImpl: AccountContext {
|
||||
|
||||
public let liveLocationManager: LiveLocationManager?
|
||||
public let wallpaperUploadManager: WallpaperUploadManager?
|
||||
private let themeUpdateManager: ThemeUpdateManager?
|
||||
|
||||
public let peerChannelMemberCategoriesContextsManager = PeerChannelMemberCategoriesContextsManager()
|
||||
|
||||
@ -143,9 +144,11 @@ public final class AccountContextImpl: AccountContext {
|
||||
if sharedContext.applicationBindings.isMainApp {
|
||||
self.prefetchManager = PrefetchManager(sharedContext: sharedContext, account: account, fetchManager: self.fetchManager)
|
||||
self.wallpaperUploadManager = WallpaperUploadManagerImpl(sharedContext: sharedContext, account: account, presentationData: sharedContext.presentationData)
|
||||
self.themeUpdateManager = ThemeUpdateManagerImpl(sharedContext: sharedContext, account: account)
|
||||
} else {
|
||||
self.prefetchManager = nil
|
||||
self.wallpaperUploadManager = nil
|
||||
self.themeUpdateManager = nil
|
||||
}
|
||||
|
||||
let updatedLimitsConfiguration = account.postbox.preferencesView(keys: [PreferencesKeys.limitsConfiguration])
|
||||
|
@ -268,8 +268,8 @@ final class ChatBotInfoItemNode: ListViewItemNode {
|
||||
if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - self.offsetContainer.frame.minX - textNodeFrame.minX, y: point.y - self.offsetContainer.frame.minY - textNodeFrame.minY)) {
|
||||
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||
var concealed = true
|
||||
if let attributeText = self.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
|
||||
concealed = !doesUrlMatchText(url: url, text: attributeText)
|
||||
if let (attributeText, fullText) = self.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
|
||||
concealed = !doesUrlMatchText(url: url, text: attributeText, fullText: fullText)
|
||||
}
|
||||
return .url(url: url, concealed: concealed)
|
||||
} else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
|
||||
|
@ -1428,6 +1428,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if let strongSelf = self {
|
||||
let _ = deleteMessagesInteractively(postbox: strongSelf.context.account.postbox, messageIds: [id], type: .forLocalPeer).start()
|
||||
}
|
||||
f(.dismissWithoutContent)
|
||||
})))
|
||||
|
||||
let controller = ContextController(account: strongSelf.context.account, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, source: ChatMessageContextControllerContentSource(chatNode: strongSelf.chatDisplayNode, message: message), items: actions, reactionItems: [], recognizer: nil)
|
||||
@ -6875,11 +6876,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
|
||||
if concealed, let parsedUrlValue = parsedUrlValue, (parsedUrlValue.scheme == "http" || parsedUrlValue.scheme == "https"), !isConcealedUrlWhitelisted(parsedUrlValue) {
|
||||
var displayUrl = url
|
||||
var rawDisplayUrl = url
|
||||
let maxLength = 180
|
||||
if displayUrl.count > maxLength {
|
||||
displayUrl = String(displayUrl[..<displayUrl.index(displayUrl.startIndex, offsetBy: maxLength - 2)]) + "..."
|
||||
if rawDisplayUrl.count > maxLength {
|
||||
rawDisplayUrl = String(rawDisplayUrl[..<rawDisplayUrl.index(rawDisplayUrl.startIndex, offsetBy: maxLength - 2)]) + "..."
|
||||
}
|
||||
var displayUrl = rawDisplayUrl
|
||||
displayUrl.replacingOccurrences(of: "\u{202e}", with: "")
|
||||
self.present(textAlertController(context: self.context, title: nil, text: self.presentationData.strings.Generic_OpenHiddenLinkAlert(displayUrl).0, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_No, action: {}), TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_Yes, action: {
|
||||
openImpl()
|
||||
})]), in: .window(.root))
|
||||
|
@ -248,8 +248,8 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
if let (index, attributes) = self.labelNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY - 10.0)), gesture == .tap {
|
||||
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||
var concealed = true
|
||||
if let attributeText = self.labelNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
|
||||
concealed = !doesUrlMatchText(url: url, text: attributeText)
|
||||
if let (attributeText, fullText) = self.labelNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
|
||||
concealed = !doesUrlMatchText(url: url, text: attributeText, fullText: fullText)
|
||||
}
|
||||
return .url(url: url, concealed: concealed)
|
||||
} else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
|
||||
|
@ -402,7 +402,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
||||
if let (media, flags) = mediaAndFlags {
|
||||
if let file = media as? TelegramMediaFile {
|
||||
if file.mimeType == "application/x-tgtheme-ios", let size = file.size, size < 16 * 1024 {
|
||||
let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, authorPeerId: message.author?.id, contactsPeerIds: associatedData.contactsPeerIds, media: file)
|
||||
let automaticDownload = true
|
||||
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData.theme.theme, presentationData.strings, presentationData.dateTimeFormat, message, file, automaticDownload ? .full : .none, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
|
||||
initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right
|
||||
refineContentImageLayout = refineLayout
|
||||
@ -981,8 +981,8 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
||||
if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) {
|
||||
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||
var concealed = true
|
||||
if let attributeText = self.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
|
||||
concealed = !doesUrlMatchText(url: url, text: attributeText)
|
||||
if let (attributeText, fullText) = self.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
|
||||
concealed = !doesUrlMatchText(url: url, text: attributeText, fullText: fullText)
|
||||
}
|
||||
return .url(url: url, concealed: concealed)
|
||||
} else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
|
||||
|
@ -627,6 +627,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
}))
|
||||
}
|
||||
|
||||
strongSelf.waveformNode.displaysAsynchronously = !presentationData.isPreview
|
||||
strongSelf.statusNode?.frame = progressFrame
|
||||
strongSelf.progressFrame = progressFrame
|
||||
strongSelf.streamingCacheStatusFrame = streamingCacheStatusFrame
|
||||
|
@ -924,8 +924,8 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) {
|
||||
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||
var concealed = true
|
||||
if let attributeText = self.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
|
||||
concealed = !doesUrlMatchText(url: url, text: attributeText)
|
||||
if let (attributeText, fullText) = self.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
|
||||
concealed = !doesUrlMatchText(url: url, text: attributeText, fullText: fullText)
|
||||
}
|
||||
return .url(url: url, concealed: concealed)
|
||||
} else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
|
||||
|
@ -407,8 +407,8 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) {
|
||||
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||
var concealed = true
|
||||
if let attributeText = self.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
|
||||
concealed = !doesUrlMatchText(url: url, text: attributeText)
|
||||
if let (attributeText, fullText) = self.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
|
||||
concealed = !doesUrlMatchText(url: url, text: attributeText, fullText: fullText)
|
||||
}
|
||||
return .url(url: url, concealed: concealed)
|
||||
} else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
|
||||
@ -486,8 +486,8 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
rect = rect.union(rects[i])
|
||||
}
|
||||
var concealed = true
|
||||
if let attributeText = self.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
|
||||
concealed = !doesUrlMatchText(url: value, text: attributeText)
|
||||
if let (attributeText, fullText) = self.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
|
||||
concealed = !doesUrlMatchText(url: value, text: attributeText, fullText: fullText)
|
||||
}
|
||||
return (item.message, .url(self, rect, value, concealed))
|
||||
}
|
||||
|
@ -246,7 +246,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
mainMedia = webpage.file ?? webpage.image
|
||||
}
|
||||
|
||||
if let file = mainMedia as? TelegramMediaFile {
|
||||
if let file = mainMedia as? TelegramMediaFile, webpage.type != "telegram_theme" {
|
||||
if let embedUrl = webpage.embedUrl, !embedUrl.isEmpty {
|
||||
if automaticPlayback {
|
||||
mediaAndFlags = (file, [.preferMediaBeforeText])
|
||||
@ -293,9 +293,18 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
mediaAndFlags = (media, ChatMessageAttachedContentNodeMediaFlags())
|
||||
}
|
||||
}
|
||||
} else if type == "telegram_theme", let files = webpage.files, let file = files.first {
|
||||
let media = WallpaperPreviewMedia(content: .file(file, nil, true))
|
||||
mediaAndFlags = (media, ChatMessageAttachedContentNodeMediaFlags())
|
||||
} else if type == "telegram_theme" {
|
||||
var file: TelegramMediaFile?
|
||||
let mimeType = "application/x-tgtheme-ios"
|
||||
if let contentFiles = webpage.files, let filteredFile = contentFiles.filter({ $0.mimeType == mimeType }).first {
|
||||
file = filteredFile
|
||||
} else if let contentFile = webpage.file, contentFile.mimeType == mimeType {
|
||||
file = contentFile
|
||||
}
|
||||
if let file = file {
|
||||
let media = WallpaperPreviewMedia(content: .file(file, nil, true))
|
||||
mediaAndFlags = (media, ChatMessageAttachedContentNodeMediaFlags())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -323,7 +332,9 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
case "telegram_theme":
|
||||
title = item.presentationData.strings.Conversation_Theme
|
||||
text = nil
|
||||
actionTitle = item.presentationData.strings.Conversation_ViewTheme
|
||||
if mediaAndFlags != nil {
|
||||
actionTitle = item.presentationData.strings.Conversation_ViewTheme
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
@ -463,8 +463,15 @@ func openChatTheme(context: AccountContext, message: Message, present: @escaping
|
||||
if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
|
||||
let _ = (context.sharedContext.resolveUrl(account: context.account, url: content.url)
|
||||
|> deliverOnMainQueue).start(next: { resolvedUrl in
|
||||
if case let .theme(slug) = resolvedUrl, let files = content.files {
|
||||
if let file = files.filter({ $0.mimeType == "application/x-tgtheme-ios" }).first, let path = context.sharedContext.accountManager.mediaBox.completedResourcePath(file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead), let theme = makePresentationTheme(data: data) {
|
||||
var file: TelegramMediaFile?
|
||||
let mimeType = "application/x-tgtheme-ios"
|
||||
if let contentFiles = content.files, let filteredFile = contentFiles.filter({ $0.mimeType == mimeType }).first {
|
||||
file = filteredFile
|
||||
} else if let contentFile = content.file, contentFile.mimeType == mimeType {
|
||||
file = contentFile
|
||||
}
|
||||
if case let .theme(slug) = resolvedUrl, let file = file {
|
||||
if let path = context.sharedContext.accountManager.mediaBox.completedResourcePath(file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead), let theme = makePresentationTheme(data: data) {
|
||||
let controller = ThemePreviewController(context: context, previewTheme: theme, source: .slug(slug, file))
|
||||
present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}
|
||||
|
@ -45,7 +45,6 @@ public final class TelegramRootController: NavigationController {
|
||||
self.presentationDataDisposable = (context.sharedContext.presentationData
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||
if let strongSelf = self {
|
||||
|
||||
if presentationData.chatWallpaper != strongSelf.presentationData.chatWallpaper {
|
||||
let navigationDetailsBackgroundMode: NavigationEmptyDetailsBackgoundMode?
|
||||
switch presentationData.chatWallpaper {
|
||||
|
158
submodules/TelegramUI/TelegramUI/ThemeUpdateManager.swift
Normal file
158
submodules/TelegramUI/TelegramUI/ThemeUpdateManager.swift
Normal file
@ -0,0 +1,158 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import SwiftSignalKit
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import MediaResources
|
||||
import WallpaperResources
|
||||
import AccountContext
|
||||
|
||||
private final class ThemeUpdateManagerContext {
|
||||
let themeReference: PresentationThemeReference
|
||||
private let disposable: Disposable
|
||||
let isAutoNight: Bool
|
||||
|
||||
init(themeReference: PresentationThemeReference, disposable: Disposable, isAutoNight: Bool) {
|
||||
self.themeReference = themeReference
|
||||
self.disposable = disposable
|
||||
self.isAutoNight = isAutoNight
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
final class ThemeUpdateManagerImpl: ThemeUpdateManager {
|
||||
private let sharedContext: SharedAccountContext
|
||||
private let account: Account
|
||||
private var contexts: [Int64: ThemeUpdateManagerContext] = [:]
|
||||
private let queue = Queue()
|
||||
|
||||
private var disposable: Disposable?
|
||||
private var currentThemeSettings: PresentationThemeSettings?
|
||||
|
||||
init(sharedContext: SharedAccountContext, account: Account) {
|
||||
self.sharedContext = sharedContext
|
||||
self.account = account
|
||||
|
||||
self.disposable = (sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings])
|
||||
|> map { sharedData -> PresentationThemeSettings in
|
||||
return (sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings] as? PresentationThemeSettings) ?? PresentationThemeSettings.defaultSettings
|
||||
}
|
||||
|> deliverOn(queue)).start(next: { [weak self] themeSettings in
|
||||
self?.presentationThemeSettingsUpdated(themeSettings)
|
||||
})
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable?.dispose()
|
||||
}
|
||||
|
||||
private func presentationThemeSettingsUpdated(_ themeSettings: PresentationThemeSettings) {
|
||||
let previousThemeSettings = self.currentThemeSettings
|
||||
self.currentThemeSettings = themeSettings
|
||||
|
||||
var previousIds = Set<Int64>()
|
||||
if let previousThemeSettings = previousThemeSettings {
|
||||
previousIds.insert(previousThemeSettings.theme.index)
|
||||
}
|
||||
|
||||
var validIds = Set<Int64>()
|
||||
var themes: [Int64: (PresentationThemeReference, Bool)] = [:]
|
||||
if case .cloud = themeSettings.theme {
|
||||
validIds.insert(themeSettings.theme.index)
|
||||
themes[themeSettings.theme.index] = (themeSettings.theme, false)
|
||||
}
|
||||
|
||||
if previousIds != validIds {
|
||||
for id in validIds {
|
||||
if let _ = self.contexts[id] {
|
||||
} else if let (theme, isAutoNight) = themes[id], case let .cloud(info) = theme {
|
||||
var currentTheme = theme
|
||||
let account = self.account
|
||||
let accountManager = self.sharedContext.accountManager
|
||||
let disposable = (actualizedTheme(account: account, accountManager: accountManager, theme: info.theme)
|
||||
|> mapToSignal { theme -> Signal<(PresentationThemeReference, PresentationTheme?), NoError> in
|
||||
guard let file = theme.file else {
|
||||
return .complete()
|
||||
}
|
||||
return telegramThemeData(account: account, accountManager: accountManager, resource: file.resource)
|
||||
|> mapToSignal { data -> Signal<(PresentationThemeReference, PresentationTheme?), NoError> in
|
||||
guard let data = data, let presentationTheme = makePresentationTheme(data: data) else {
|
||||
return .complete()
|
||||
}
|
||||
|
||||
let resolvedWallpaper: Signal<TelegramWallpaper?, NoError>
|
||||
if case let .file(file) = presentationTheme.chat.defaultWallpaper, file.id == 0 {
|
||||
resolvedWallpaper = cachedWallpaper(account: account, slug: file.slug)
|
||||
|> map { wallpaper in
|
||||
return wallpaper?.wallpaper
|
||||
}
|
||||
} else {
|
||||
resolvedWallpaper = .single(nil)
|
||||
}
|
||||
|
||||
return resolvedWallpaper
|
||||
|> mapToSignal { wallpaper -> Signal<(PresentationThemeReference, PresentationTheme?), NoError> in
|
||||
if let wallpaper = wallpaper, case let .file(file) = wallpaper {
|
||||
var convertedRepresentations: [ImageRepresentationWithReference] = []
|
||||
convertedRepresentations.append(ImageRepresentationWithReference(representation: TelegramMediaImageRepresentation(dimensions: CGSize(width: 100.0, height: 100.0), resource: file.file.resource), reference: .media(media: .standalone(media: file.file), resource: file.file.resource)))
|
||||
return wallpaperDatas(account: account, accountManager: accountManager, fileReference: .standalone(media: file.file), representations: convertedRepresentations, alwaysShowThumbnailFirst: false, thumbnail: false, onlyFullSize: true, autoFetchFullSize: true, synchronousLoad: false)
|
||||
|> mapToSignal { _, fullSizeData, complete -> Signal<(PresentationThemeReference, PresentationTheme?), NoError> in
|
||||
guard complete, let fullSizeData = fullSizeData else {
|
||||
return .complete()
|
||||
}
|
||||
accountManager.mediaBox.storeResourceData(file.file.resource.id, data: fullSizeData)
|
||||
return .single((.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: wallpaper)), presentationTheme))
|
||||
}
|
||||
} else {
|
||||
return .single((.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil)), presentationTheme))
|
||||
}
|
||||
}
|
||||
}
|
||||
}).start(next: { updatedTheme, presentationTheme in
|
||||
if updatedTheme != currentTheme {
|
||||
currentTheme = updatedTheme
|
||||
|
||||
let _ = (accountManager.transaction { transaction -> Void in
|
||||
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings, { entry in
|
||||
let current: PresentationThemeSettings
|
||||
if let entry = entry as? PresentationThemeSettings {
|
||||
current = entry
|
||||
} else {
|
||||
current = PresentationThemeSettings.defaultSettings
|
||||
}
|
||||
|
||||
let chatWallpaper: TelegramWallpaper
|
||||
if let themeSpecificWallpaper = current.themeSpecificChatWallpapers[updatedTheme.index] {
|
||||
chatWallpaper = themeSpecificWallpaper
|
||||
} else if let presentationTheme = presentationTheme {
|
||||
if case let .cloud(info) = updatedTheme, let resolvedWallpaper = info.resolvedWallpaper {
|
||||
chatWallpaper = resolvedWallpaper
|
||||
} else {
|
||||
chatWallpaper = presentationTheme.chat.defaultWallpaper
|
||||
}
|
||||
} else {
|
||||
chatWallpaper = current.chatWallpaper
|
||||
}
|
||||
|
||||
return PresentationThemeSettings(chatWallpaper: chatWallpaper, theme: updatedTheme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
|
||||
})
|
||||
}).start()
|
||||
}
|
||||
})
|
||||
self.contexts[id] = ThemeUpdateManagerContext(themeReference: theme, disposable: disposable, isAutoNight: isAutoNight)
|
||||
}
|
||||
}
|
||||
|
||||
for id in previousIds {
|
||||
if !validIds.contains(id) {
|
||||
self.contexts[id] = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -31,6 +31,7 @@
|
||||
094735192277483C00EA2312 /* anim_infotip.json in Resources */ = {isa = PBXBuildFile; fileRef = 094735182277483B00EA2312 /* anim_infotip.json */; };
|
||||
09510B1322F96E5B0078CAB7 /* ChatScheduleTimeController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09510B1222F96E5B0078CAB7 /* ChatScheduleTimeController.swift */; };
|
||||
09510B1522F96E6C0078CAB7 /* ChatScheduleTimeControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09510B1422F96E6C0078CAB7 /* ChatScheduleTimeControllerNode.swift */; };
|
||||
095214EF2318D4D3008CDD87 /* ThemeUpdateManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 095214EE2318D4D3008CDD87 /* ThemeUpdateManager.swift */; };
|
||||
0962E67921B67A9800245FD9 /* ChatMessageAnimatedStickerItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0962E67821B67A9800245FD9 /* ChatMessageAnimatedStickerItemNode.swift */; };
|
||||
09749BC521F0E024008FDDE9 /* StickersChatInputContextPanelItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09749BC421F0E024008FDDE9 /* StickersChatInputContextPanelItem.swift */; };
|
||||
09874E4F21078FA100E190B8 /* Generic.html in Resources */ = {isa = PBXBuildFile; fileRef = 0979788321065F8C0077D77F /* Generic.html */; };
|
||||
@ -596,6 +597,7 @@
|
||||
094735182277483B00EA2312 /* anim_infotip.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = anim_infotip.json; sourceTree = "<group>"; };
|
||||
09510B1222F96E5B0078CAB7 /* ChatScheduleTimeController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatScheduleTimeController.swift; sourceTree = "<group>"; };
|
||||
09510B1422F96E6C0078CAB7 /* ChatScheduleTimeControllerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatScheduleTimeControllerNode.swift; sourceTree = "<group>"; };
|
||||
095214EE2318D4D3008CDD87 /* ThemeUpdateManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeUpdateManager.swift; sourceTree = "<group>"; };
|
||||
0962E67821B67A9800245FD9 /* ChatMessageAnimatedStickerItemNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessageAnimatedStickerItemNode.swift; sourceTree = "<group>"; };
|
||||
09749BC421F0E024008FDDE9 /* StickersChatInputContextPanelItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickersChatInputContextPanelItem.swift; sourceTree = "<group>"; };
|
||||
0979788021065F8B0077D77F /* VimeoUserScript.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = VimeoUserScript.js; sourceTree = "<group>"; };
|
||||
@ -2540,6 +2542,7 @@
|
||||
D0F3A8AA1E82D83E00B4C64C /* TelegramAccountAuxiliaryMethods.swift */,
|
||||
D01DBA9A209CC6AD00C64E64 /* ChatLinkPreview.swift */,
|
||||
090B48C72200BCA8005083FA /* WallpaperUploadManager.swift */,
|
||||
095214EE2318D4D3008CDD87 /* ThemeUpdateManager.swift */,
|
||||
09D96898221DE92600B1458A /* ID3ArtworkReader.swift */,
|
||||
D099E21F229405BB00561B75 /* Weak.swift */,
|
||||
);
|
||||
@ -3053,6 +3056,7 @@
|
||||
D0EC6DEC1EB9F58900EBF1C3 /* ChatToastAlertPanelNode.swift in Sources */,
|
||||
D0EC6DED1EB9F58900EBF1C3 /* ChatHistoryNavigationButtonNode.swift in Sources */,
|
||||
D0EC6DF51EB9F58900EBF1C3 /* PeerMediaCollectionController.swift in Sources */,
|
||||
095214EF2318D4D3008CDD87 /* ThemeUpdateManager.swift in Sources */,
|
||||
D0EC6DF61EB9F58900EBF1C3 /* PeerMediaCollectionControllerNode.swift in Sources */,
|
||||
D0EC6DF81EB9F58900EBF1C3 /* PeerMediaCollectionInterfaceState.swift in Sources */,
|
||||
D0EC6DF91EB9F58900EBF1C3 /* PeerMediaCollectionInterfaceStateButtons.swift in Sources */,
|
||||
|
@ -29,20 +29,28 @@ public struct WallpaperPresentationOptions: OptionSet {
|
||||
public struct PresentationLocalTheme: PostboxCoding, Equatable {
|
||||
public let title: String
|
||||
public let resource: LocalFileMediaResource
|
||||
public let resolvedWallpaper: TelegramWallpaper?
|
||||
|
||||
public init(title: String, resource: LocalFileMediaResource) {
|
||||
public init(title: String, resource: LocalFileMediaResource, resolvedWallpaper: TelegramWallpaper?) {
|
||||
self.title = title
|
||||
self.resource = resource
|
||||
self.resolvedWallpaper = resolvedWallpaper
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.title = decoder.decodeStringForKey("title", orElse: "")
|
||||
self.resource = decoder.decodeObjectForKey("resource", decoder: { LocalFileMediaResource(decoder: $0) }) as! LocalFileMediaResource
|
||||
self.resolvedWallpaper = decoder.decodeObjectForKey("wallpaper", decoder: { TelegramWallpaper(decoder: $0) }) as? TelegramWallpaper
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeString(self.title, forKey: "title")
|
||||
encoder.encodeObject(self.resource, forKey: "resource")
|
||||
if let resolvedWallpaper = self.resolvedWallpaper {
|
||||
encoder.encodeObject(resolvedWallpaper, forKey: "wallpaper")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "wallpaper")
|
||||
}
|
||||
}
|
||||
|
||||
public static func ==(lhs: PresentationLocalTheme, rhs: PresentationLocalTheme) -> Bool {
|
||||
@ -52,6 +60,9 @@ public struct PresentationLocalTheme: PostboxCoding, Equatable {
|
||||
if !lhs.resource.isEqual(to: rhs.resource) {
|
||||
return false
|
||||
}
|
||||
if lhs.resolvedWallpaper != rhs.resolvedWallpaper {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -74,6 +85,8 @@ public struct PresentationCloudTheme: PostboxCoding, Equatable {
|
||||
encoder.encodeObject(self.theme, forKey: "theme")
|
||||
if let resolvedWallpaper = self.resolvedWallpaper {
|
||||
encoder.encodeObject(resolvedWallpaper, forKey: "wallpaper")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "wallpaper")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,8 @@
|
||||
import Foundation
|
||||
|
||||
public func doesUrlMatchText(url: String, text: String) -> Bool {
|
||||
for c in url {
|
||||
if !c.isASCII {
|
||||
return false
|
||||
}
|
||||
public func doesUrlMatchText(url: String, text: String, fullText: String) -> Bool {
|
||||
if fullText.range(of: "\u{202e}") != nil {
|
||||
return false
|
||||
}
|
||||
if url == text {
|
||||
return true
|
||||
|
@ -10,8 +10,9 @@ import TinyThumbnail
|
||||
import PhotoResources
|
||||
import LocalMediaResources
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
|
||||
private func wallpaperDatas(account: Account, accountManager: AccountManager, fileReference: FileMediaReference? = nil, representations: [ImageRepresentationWithReference], alwaysShowThumbnailFirst: Bool = false, thumbnail: Bool = false, onlyFullSize: Bool = false, autoFetchFullSize: Bool = false, synchronousLoad: Bool = false) -> Signal<(Data?, Data?, Bool), NoError> {
|
||||
public func wallpaperDatas(account: Account, accountManager: AccountManager, fileReference: FileMediaReference? = nil, representations: [ImageRepresentationWithReference], alwaysShowThumbnailFirst: Bool = false, thumbnail: Bool = false, onlyFullSize: Bool = false, autoFetchFullSize: Bool = false, synchronousLoad: Bool = false) -> Signal<(Data?, Data?, Bool), NoError> {
|
||||
if let smallestRepresentation = smallestImageRepresentation(representations.map({ $0.representation })), let largestRepresentation = largestImageRepresentation(representations.map({ $0.representation })), let smallestIndex = representations.firstIndex(where: { $0.representation == smallestRepresentation }), let largestIndex = representations.firstIndex(where: { $0.representation == largestRepresentation }) {
|
||||
|
||||
let maybeFullSize: Signal<MediaResourceData, NoError>
|
||||
@ -51,7 +52,13 @@ private func wallpaperDatas(account: Account, accountManager: AccountManager, fi
|
||||
} else {
|
||||
maybeFullSize = combineLatest(accountManager.mediaBox.resourceData(largestRepresentation.resource), account.postbox.mediaBox.resourceData(largestRepresentation.resource))
|
||||
|> map { sharedData, data -> MediaResourceData in
|
||||
if sharedData.complete {
|
||||
if sharedData.complete && data.complete {
|
||||
if sharedData.size > data.size {
|
||||
return sharedData
|
||||
} else {
|
||||
return data
|
||||
}
|
||||
} else if sharedData.complete {
|
||||
return sharedData
|
||||
} else {
|
||||
return data
|
||||
@ -168,9 +175,6 @@ public func wallpaperImage(account: Account, accountManager: AccountManager, fil
|
||||
|
||||
return signal
|
||||
|> map { (thumbnailData, fullSizeData, fullSizeComplete) in
|
||||
if let fullSizeData = fullSizeData, let fileReference = fileReference {
|
||||
accountManager.mediaBox.storeResourceData(fileReference.media.resource.id, data: fullSizeData)
|
||||
}
|
||||
return { arguments in
|
||||
let drawingRect = arguments.drawingRect
|
||||
var fittedSize = arguments.imageSize
|
||||
@ -259,7 +263,6 @@ public func wallpaperImage(account: Account, accountManager: AccountManager, fil
|
||||
}
|
||||
|
||||
let context = DrawingContext(size: arguments.drawingSize, clear: true)
|
||||
|
||||
context.withFlippedContext { c in
|
||||
c.setBlendMode(.copy)
|
||||
if arguments.imageSize.width < arguments.boundingSize.width || arguments.imageSize.height < arguments.boundingSize.height {
|
||||
@ -278,9 +281,7 @@ public func wallpaperImage(account: Account, accountManager: AccountManager, fil
|
||||
drawImage(context: c, image: fullSizeImage, orientation: imageOrientation, in: fittedRect)
|
||||
}
|
||||
}
|
||||
|
||||
addCorners(context, arguments: arguments)
|
||||
|
||||
return context
|
||||
}
|
||||
}
|
||||
@ -596,7 +597,7 @@ public func photoWallpaper(postbox: Postbox, photoLibraryResource: PhotoLibraryM
|
||||
}
|
||||
}
|
||||
|
||||
public func telegramThemeData(account: Account, accountManager: AccountManager, resource: MediaResource, synchronousLoad: Bool) -> Signal<Data?, NoError> {
|
||||
public func telegramThemeData(account: Account, accountManager: AccountManager, resource: MediaResource, synchronousLoad: Bool = false) -> Signal<Data?, NoError> {
|
||||
let maybeFetched = accountManager.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad)
|
||||
return maybeFetched
|
||||
|> take(1)
|
||||
@ -653,7 +654,7 @@ public func drawThemeImage(context c: CGContext, theme: PresentationTheme, wallp
|
||||
case let .color(value):
|
||||
c.setFillColor(UIColor(rgb: UInt32(bitPattern: value)).cgColor)
|
||||
c.fill(drawingRect)
|
||||
case let .file(file):
|
||||
case .file:
|
||||
c.setFillColor(theme.chatList.backgroundColor.cgColor)
|
||||
c.fill(drawingRect)
|
||||
|
||||
@ -803,13 +804,18 @@ public func themeImage(account: Account, accountManager: AccountManager, fileRef
|
||||
|> mapToSignal { wallpaper -> Signal<(PresentationTheme?, UIImage?, Data?), NoError> in
|
||||
if let wallpaper = wallpaper, case let .file(file) = wallpaper.wallpaper {
|
||||
var convertedRepresentations: [ImageRepresentationWithReference] = []
|
||||
convertedRepresentations.append(ImageRepresentationWithReference(representation: TelegramMediaImageRepresentation(dimensions: CGSize(width: 100.0, height: 100.0), resource: file.file.resource), reference: .wallpaper(resource: file.file.resource)))
|
||||
return wallpaperImage(account: account, accountManager: accountManager, fileReference: .standalone(media: file.file), representations: convertedRepresentations, alwaysShowThumbnailFirst: false, thumbnail: false, onlyFullSize: true, autoFetchFullSize: true, synchronousLoad: false)
|
||||
|> map { _ -> (PresentationTheme?, UIImage?, Data?) in
|
||||
if let path = accountManager.mediaBox.completedResourcePath(file.file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path)), let image = UIImage(data: data) {
|
||||
return (theme, image, thumbnailData)
|
||||
convertedRepresentations.append(ImageRepresentationWithReference(representation: TelegramMediaImageRepresentation(dimensions: CGSize(width: 100.0, height: 100.0), resource: file.file.resource), reference: .media(media: .standalone(media: file.file), resource: file.file.resource)))
|
||||
return wallpaperDatas(account: account, accountManager: accountManager, fileReference: .standalone(media: file.file), representations: convertedRepresentations, alwaysShowThumbnailFirst: false, thumbnail: false, onlyFullSize: true, autoFetchFullSize: true, synchronousLoad: false)
|
||||
|> mapToSignal { _, fullSizeData, complete -> Signal<(PresentationTheme?, UIImage?, Data?), NoError> in
|
||||
guard complete, let fullSizeData = fullSizeData else {
|
||||
return .complete()
|
||||
}
|
||||
accountManager.mediaBox.storeResourceData(file.file.resource.id, data: fullSizeData)
|
||||
|
||||
if let image = UIImage(data: fullSizeData) {
|
||||
return .single((theme, image, thumbnailData))
|
||||
} else {
|
||||
return (theme, nil, thumbnailData)
|
||||
return .single((theme, nil, thumbnailData))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -845,7 +851,7 @@ public func themeImage(account: Account, accountManager: AccountManager, fileRef
|
||||
c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize))
|
||||
}
|
||||
telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes)
|
||||
|
||||
telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes)
|
||||
var thumbnailContextFittingSize = CGSize(width: floor(arguments.drawingSize.width * 0.5), height: floor(arguments.drawingSize.width * 0.5))
|
||||
if thumbnailContextFittingSize.width < 150.0 || thumbnailContextFittingSize.height < 150.0 {
|
||||
thumbnailContextFittingSize = thumbnailContextFittingSize.aspectFilled(CGSize(width: 150.0, height: 150.0))
|
||||
@ -890,3 +896,124 @@ public func themeImage(account: Account, accountManager: AccountManager, fileRef
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func themeIconImage(account: Account, accountManager: AccountManager, theme: PresentationThemeReference, accentColor: UIColor?) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
||||
let signal: Signal<(UIColor, UIColor, UIColor, UIImage?), NoError>
|
||||
if case let .builtin(theme) = theme {
|
||||
switch theme {
|
||||
case .dayClassic:
|
||||
signal = .single((UIColor(rgb: 0xd6e2ee), UIColor(rgb: 0xffffff), UIColor(rgb: 0xe1ffc7), nil))
|
||||
case .day:
|
||||
signal = .single((.white, UIColor(rgb: 0xd5dde6), accentColor ?? UIColor(rgb: 0x007aff), nil))
|
||||
case .night:
|
||||
signal = .single((.black, UIColor(rgb: 0x1f1f1f), accentColor ?? UIColor(rgb: 0x313131), nil))
|
||||
case .nightAccent:
|
||||
let accentColor = accentColor ?? UIColor(rgb: 0x007aff)
|
||||
signal = .single((accentColor.withMultiplied(hue: 1.024, saturation: 0.573, brightness: 0.18), accentColor.withMultiplied(hue: 1.024, saturation: 0.585, brightness: 0.25), accentColor.withMultiplied(hue: 1.019, saturation: 0.731, brightness: 0.59), nil))
|
||||
}
|
||||
} else {
|
||||
var resource: MediaResource?
|
||||
if case let .local(theme) = theme {
|
||||
resource = theme.resource
|
||||
} else if case let .cloud(theme) = theme {
|
||||
resource = theme.theme.file?.resource
|
||||
}
|
||||
if let resource = resource {
|
||||
signal = telegramThemeData(account: account, accountManager: accountManager, resource: resource, synchronousLoad: false)
|
||||
|> mapToSignal { data -> Signal<(UIColor, UIColor, UIColor, UIImage?), NoError> in
|
||||
if let data = data, let theme = makePresentationTheme(data: data) {
|
||||
var wallpaperSignal: Signal<(UIColor, UIColor, UIColor, UIImage?), NoError> = .complete()
|
||||
let backgroundColor: UIColor
|
||||
let incomingColor = theme.chat.message.incoming.bubble.withoutWallpaper.fill
|
||||
let outgoingColor = theme.chat.message.outgoing.bubble.withoutWallpaper.fill
|
||||
switch theme.chat.defaultWallpaper {
|
||||
case .builtin:
|
||||
backgroundColor = UIColor(rgb: 0xd6e2ee)
|
||||
case let .color(color):
|
||||
backgroundColor = UIColor(rgb: UInt32(bitPattern: color))
|
||||
case .image:
|
||||
backgroundColor = .black
|
||||
case let .file(file):
|
||||
backgroundColor = theme.chatList.backgroundColor
|
||||
wallpaperSignal = cachedWallpaper(account: account, slug: file.slug)
|
||||
|> mapToSignal { wallpaper in
|
||||
if let wallpaper = wallpaper, case let .file(file) = wallpaper.wallpaper {
|
||||
var convertedRepresentations: [ImageRepresentationWithReference] = []
|
||||
convertedRepresentations.append(ImageRepresentationWithReference(representation: TelegramMediaImageRepresentation(dimensions: CGSize(width: 100.0, height: 100.0), resource: file.file.resource), reference: .media(media: .standalone(media: file.file), resource: file.file.resource)))
|
||||
return wallpaperDatas(account: account, accountManager: accountManager, fileReference: .standalone(media: file.file), representations: convertedRepresentations, alwaysShowThumbnailFirst: false, thumbnail: false, onlyFullSize: true, autoFetchFullSize: true, synchronousLoad: false)
|
||||
|> mapToSignal { _, fullSizeData, complete -> Signal<(UIColor, UIColor, UIColor, UIImage?), NoError> in
|
||||
guard complete, let fullSizeData = fullSizeData else {
|
||||
return .complete()
|
||||
}
|
||||
accountManager.mediaBox.storeResourceData(file.file.resource.id, data: fullSizeData)
|
||||
|
||||
if let image = UIImage(data: fullSizeData) {
|
||||
return .single((backgroundColor, incomingColor, outgoingColor, image))
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
}
|
||||
return .single((backgroundColor, incomingColor, outgoingColor, nil))
|
||||
|> then(wallpaperSignal)
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
signal = .never()
|
||||
}
|
||||
}
|
||||
return signal
|
||||
|> map { colors in
|
||||
return { arguments in
|
||||
let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: arguments.emptyColor == nil)
|
||||
let drawingRect = arguments.drawingRect
|
||||
|
||||
context.withContext { c in
|
||||
c.setFillColor(colors.0.cgColor)
|
||||
c.fill(drawingRect)
|
||||
|
||||
if let image = colors.3 {
|
||||
let initialThumbnailContextFittingSize = arguments.imageSize.fitted(CGSize(width: 90.0, height: 90.0))
|
||||
let thumbnailContextSize = image.size.aspectFilled(initialThumbnailContextFittingSize)
|
||||
let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0)
|
||||
thumbnailContext.withFlippedContext { c in
|
||||
c.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: thumbnailContextSize))
|
||||
}
|
||||
telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes)
|
||||
|
||||
if let blurredThumbnailImage = thumbnailContext.generateImage(), let cgImage = blurredThumbnailImage.cgImage {
|
||||
let fittedSize = thumbnailContext.size.aspectFilled(CGSize(width: drawingRect.size.width + 1.0, height: drawingRect.size.height + 1.0))
|
||||
c.saveGState()
|
||||
c.translateBy(x: drawingRect.width / 2.0, y: drawingRect.height / 2.0)
|
||||
c.scaleBy(x: 1.0, y: -1.0)
|
||||
c.translateBy(x: -drawingRect.width / 2.0, y: -drawingRect.height / 2.0)
|
||||
c.draw(cgImage, in: CGRect(origin: CGPoint(x: (drawingRect.size.width - fittedSize.width) / 2.0, y: (drawingRect.size.height - fittedSize.height) / 2.0), size: fittedSize))
|
||||
c.restoreGState()
|
||||
}
|
||||
}
|
||||
|
||||
let incoming = generateTintedImage(image: UIImage(bundleImageName: "Settings/ThemeBubble"), color: colors.1)
|
||||
let outgoing = generateTintedImage(image: UIImage(bundleImageName: "Settings/ThemeBubble"), color: colors.2)
|
||||
|
||||
c.translateBy(x: drawingRect.width / 2.0, y: drawingRect.height / 2.0)
|
||||
c.scaleBy(x: 1.0, y: -1.0)
|
||||
c.translateBy(x: -drawingRect.width / 2.0, y: -drawingRect.height / 2.0)
|
||||
|
||||
c.draw(incoming!.cgImage!, in: CGRect(x: 9.0, y: 34.0, width: 57.0, height: 16.0))
|
||||
|
||||
c.translateBy(x: drawingRect.width / 2.0, y: drawingRect.height / 2.0)
|
||||
c.scaleBy(x: -1.0, y: 1.0)
|
||||
c.translateBy(x: -drawingRect.width / 2.0, y: -drawingRect.height / 2.0)
|
||||
c.draw(outgoing!.cgImage!, in: CGRect(x: 9.0, y: 12.0, width: 57.0, height: 16.0))
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user