mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-02 00:17:02 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
a3874367c8
@ -243,6 +243,7 @@
|
||||
"Common.Search" = "Search";
|
||||
"Common.More" = "More";
|
||||
"Common.Select" = "Select";
|
||||
"Items.NOfM" = "%1$@ of %2$@";
|
||||
|
||||
// State
|
||||
"State.Connecting" = "Connecting...";
|
||||
@ -309,6 +310,7 @@
|
||||
"LastSeen.HoursAgo_0" = "last seen %@ hours ago";
|
||||
"LastSeen.YesterdayAt" = "last seen yesterday at %@";
|
||||
"LastSeen.AtDate" = "last seen %@";
|
||||
"LastSeen.TodayAt" = "last seen today at %@";
|
||||
"LastSeen.Lately" = "last seen recently";
|
||||
"LastSeen.WithinAWeek" = "last seen within a week";
|
||||
"LastSeen.WithinAMonth" = "last seen within a month";
|
||||
@ -4679,7 +4681,8 @@ Any member of this group will be able to see messages in the channel.";
|
||||
"EditTheme.Preview" = "CHAT PREVIEW";
|
||||
"EditTheme.UploadNewTheme" = "Create from File...";
|
||||
"EditTheme.UploadEditedTheme" = "Update from File...";
|
||||
"EditTheme.ThemeTemplateAlert" = "New Theme Added\n\nPress and hold on your theme to edit it or get a sharing link. Users who install your theme will get automatic updates each time you change it.\n\nFor advanced editing purposes, you can find a file with your theme in Saved Messages.";
|
||||
"EditTheme.ThemeTemplateAlertTitle" = "New Theme Added";
|
||||
"EditTheme.ThemeTemplateAlertText" = "Press and hold on your theme to edit it or get a sharing link. Users who install your theme will get automatic updates each time you change it.\n\nFor advanced editing purposes, you can find a file with your theme in Saved Messages.";
|
||||
"EditTheme.FileReadError" = "Invalid theme file";
|
||||
|
||||
"EditTheme.Create.TopInfo" = "The theme will be based on your currently selected colors and wallpaper.";
|
||||
@ -4707,6 +4710,7 @@ Any member of this group will be able to see messages in the channel.";
|
||||
"EditTheme.Edit.Preview.OutgoingText" = "Or upload a theme file";
|
||||
|
||||
"EditTheme.ErrorLinkTaken" = "Sorry, this link is already taken";
|
||||
"EditTheme.ErrorInvalidCharacters" = "Sorry, this link is invalid.";
|
||||
|
||||
"Wallpaper.ErrorNotFound" = "Sorry, this chat background doesn't seem to exist.";
|
||||
"Theme.ErrorNotFound" = "Sorry, this color theme doesn't seem to exist.";
|
||||
|
@ -115,7 +115,7 @@ public class ChatListSearchItemNode: ListViewItemNode {
|
||||
let baseWidth = params.width - params.leftInset - params.rightInset
|
||||
|
||||
let backgroundColor = nextIsPinned ? item.theme.chatList.pinnedItemBackgroundColor : item.theme.chatList.itemBackgroundColor
|
||||
let placeholderColor = item.theme.rootController.navigationSearchBar.inputPlaceholderTextColor
|
||||
let placeholderColor = item.theme.list.itemSecondaryTextColor
|
||||
|
||||
let (_, searchBarApply) = searchBarNodeLayout(NSAttributedString(string: placeholder ?? "", font: searchBarFont, textColor: placeholderColor), CGSize(width: baseWidth - 20.0, height: 36.0), 1.0, placeholderColor, nextIsPinned ? item.theme.chatList.pinnedSearchBarColor : item.theme.chatList.regularSearchBarColor, backgroundColor, .immediate)
|
||||
|
||||
|
@ -83,12 +83,6 @@ public struct ContainerViewLayout: Equatable {
|
||||
public extension ContainerViewLayout {
|
||||
func insets(options: ContainerViewLayoutInsetOptions) -> UIEdgeInsets {
|
||||
var insets = self.intrinsicInsets
|
||||
if self.inSlideOver {
|
||||
let onScreenNavigationHeight = self.deviceMetrics.onScreenNavigationHeight(inLandscape: false) ?? 0.0
|
||||
if insets.bottom > 0.0 && abs(insets.bottom - onScreenNavigationHeight) < 0.1 {
|
||||
insets.bottom = 0.0
|
||||
}
|
||||
}
|
||||
if let statusBarHeight = self.statusBarHeight, options.contains(.statusBar) {
|
||||
insets.top += statusBarHeight
|
||||
}
|
||||
|
@ -25,6 +25,11 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
||||
}
|
||||
|
||||
public init(screenSize: CGSize, statusBarHeight: CGFloat, onScreenNavigationHeight: CGFloat?) {
|
||||
var screenSize = screenSize
|
||||
if screenSize.width > screenSize.height {
|
||||
screenSize = CGSize(width: screenSize.height, height: screenSize.width)
|
||||
}
|
||||
|
||||
let additionalSize = CGSize(width: screenSize.width, height: screenSize.height + 20.0)
|
||||
for device in DeviceMetrics.allCases {
|
||||
if let _ = onScreenNavigationHeight, device.onScreenNavigationHeight(inLandscape: false) == nil {
|
||||
@ -33,7 +38,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
||||
|
||||
let width = device.screenSize.width
|
||||
let height = device.screenSize.height
|
||||
if ((screenSize.width.isEqual(to: width) && screenSize.height.isEqual(to: height)) || screenSize.height.isEqual(to: width) && screenSize.width.isEqual(to: height)) || ((additionalSize.width.isEqual(to: width) && additionalSize.height.isEqual(to: height)) || additionalSize.height.isEqual(to: width) && additionalSize.width.isEqual(to: height)) {
|
||||
if ((screenSize.width.isEqual(to: width) && screenSize.height.isEqual(to: height)) || (additionalSize.width.isEqual(to: width) && additionalSize.height.isEqual(to: height))) {
|
||||
self = device
|
||||
return
|
||||
}
|
||||
|
@ -15,6 +15,22 @@ public class EditableTextNode: ASEditableTextNode {
|
||||
self.textView.reloadInputViews()
|
||||
}
|
||||
}
|
||||
|
||||
public var isRTL: Bool {
|
||||
if let text = self.textView.text, !text.isEmpty {
|
||||
let tagger = NSLinguisticTagger(tagSchemes: [.language], options: 0)
|
||||
tagger.string = text
|
||||
|
||||
let lang = tagger.tag(at: 0, scheme: .language, tokenRange: nil, sentenceRange: nil)
|
||||
if let lang = lang?.rawValue, lang.contains("he") || lang.contains("ar") || lang.contains("fa") {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension UITextView {
|
||||
|
@ -85,6 +85,7 @@ private struct UpdatingLayout {
|
||||
}
|
||||
}
|
||||
|
||||
private let defaultStatusBarHeight: CGFloat = 20.0
|
||||
private let statusBarHiddenInLandscape: Bool = UIDevice.current.userInterfaceIdiom == .phone
|
||||
|
||||
private func inputHeightOffsetForLayout(_ layout: WindowLayout) -> CGFloat {
|
||||
@ -280,7 +281,7 @@ public final class WindowKeyboardGestureRecognizerDelegate: NSObject, UIGestureR
|
||||
public class Window1 {
|
||||
public let hostView: WindowHostView
|
||||
|
||||
private let deviceMetrics: DeviceMetrics
|
||||
private var deviceMetrics: DeviceMetrics
|
||||
|
||||
private let statusBarHost: StatusBarHost?
|
||||
private let statusBarManager: StatusBarManager?
|
||||
@ -342,7 +343,7 @@ public class Window1 {
|
||||
self.volumeControlStatusBarNode.isHidden = true
|
||||
|
||||
let boundsSize = self.hostView.eventView.bounds.size
|
||||
self.deviceMetrics = DeviceMetrics(screenSize: UIScreen.main.bounds.size, statusBarHeight: statusBarHost?.statusBarFrame.height ?? 20.0, onScreenNavigationHeight: self.hostView.onScreenNavigationHeight)
|
||||
self.deviceMetrics = DeviceMetrics(screenSize: UIScreen.main.bounds.size, statusBarHeight: statusBarHost?.statusBarFrame.height ?? defaultStatusBarHeight, onScreenNavigationHeight: self.hostView.onScreenNavigationHeight)
|
||||
|
||||
self.statusBarHost = statusBarHost
|
||||
let statusBarHeight: CGFloat
|
||||
@ -439,7 +440,7 @@ public class Window1 {
|
||||
|
||||
self.statusBarChangeObserver = NotificationCenter.default.addObserver(forName: UIApplication.willChangeStatusBarFrameNotification, object: nil, queue: OperationQueue.main, using: { [weak self] notification in
|
||||
if let strongSelf = self {
|
||||
let statusBarHeight: CGFloat = max(20.0, (notification.userInfo?[UIApplication.statusBarFrameUserInfoKey] as? NSValue)?.cgRectValue.height ?? 20.0)
|
||||
let statusBarHeight: CGFloat = max(defaultStatusBarHeight, (notification.userInfo?[UIApplication.statusBarFrameUserInfoKey] as? NSValue)?.cgRectValue.height ?? defaultStatusBarHeight)
|
||||
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.35, curve: .easeInOut)
|
||||
strongSelf.updateLayout { $0.update(statusBarHeight: statusBarHeight, transition: transition, overrideTransition: false) }
|
||||
@ -947,6 +948,11 @@ public class Window1 {
|
||||
} else {
|
||||
statusBarHeight = self.deviceMetrics.statusBarHeight
|
||||
}
|
||||
|
||||
if self.deviceMetrics.type == .tablet, let onScreenNavigationHeight = self.hostView.onScreenNavigationHeight, onScreenNavigationHeight != self.deviceMetrics.onScreenNavigationHeight(inLandscape: false) {
|
||||
self.deviceMetrics = DeviceMetrics(screenSize: UIScreen.main.bounds.size, statusBarHeight: statusBarHeight ?? defaultStatusBarHeight, onScreenNavigationHeight: onScreenNavigationHeight)
|
||||
}
|
||||
|
||||
let statusBarWasHidden = self.statusBarHidden
|
||||
if statusBarHiddenInLandscape && isLandscape {
|
||||
statusBarHeight = nil
|
||||
|
@ -39,7 +39,7 @@ class ChatDocumentGalleryItem: GalleryItem {
|
||||
}
|
||||
|
||||
if let location = self.location {
|
||||
node._title.set(.single("\(location.index + 1) \(self.presentationData.strings.Common_of) \(location.count)"))
|
||||
node._title.set(.single(self.presentationData.strings.Items_NOfM("\(location.index + 1)", "\(location.count)").0))
|
||||
}
|
||||
node.setMessage(self.message)
|
||||
|
||||
@ -48,7 +48,7 @@ class ChatDocumentGalleryItem: GalleryItem {
|
||||
|
||||
func updateNode(node: GalleryItemNode) {
|
||||
if let node = node as? ChatDocumentGalleryItemNode, let location = self.location {
|
||||
node._title.set(.single("\(location.index + 1) \(self.presentationData.strings.Common_of) \(location.count)"))
|
||||
node._title.set(.single(self.presentationData.strings.Items_NOfM("\(location.index + 1)", "\(location.count)").0))
|
||||
node.setMessage(self.message)
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ class ChatExternalFileGalleryItem: GalleryItem {
|
||||
}
|
||||
|
||||
if let location = self.location {
|
||||
node._title.set(.single("\(location.index + 1) \(self.presentationData.strings.Common_of) \(location.count)"))
|
||||
node._title.set(.single(self.presentationData.strings.Items_NOfM("\(location.index + 1)", "\(location.count)").0))
|
||||
}
|
||||
node.setMessage(self.message)
|
||||
|
||||
@ -49,7 +49,7 @@ class ChatExternalFileGalleryItem: GalleryItem {
|
||||
|
||||
func updateNode(node: GalleryItemNode) {
|
||||
if let node = node as? ChatExternalFileGalleryItemNode, let location = self.location {
|
||||
node._title.set(.single("\(location.index + 1) \(self.presentationData.strings.Common_of) \(location.count)"))
|
||||
node._title.set(.single(self.presentationData.strings.Items_NOfM("\(location.index + 1)", "\(location.count)").0))
|
||||
node.setMessage(self.message)
|
||||
}
|
||||
}
|
||||
|
@ -112,7 +112,7 @@ class ChatImageGalleryItem: GalleryItem {
|
||||
}
|
||||
|
||||
if let location = self.location {
|
||||
node._title.set(.single("\(location.index + 1) \(self.presentationData.strings.Common_of) \(location.count)"))
|
||||
node._title.set(.single(self.presentationData.strings.Items_NOfM("\(location.index + 1)", "\(location.count)").0))
|
||||
}
|
||||
|
||||
node.setMessage(self.message)
|
||||
@ -122,7 +122,7 @@ class ChatImageGalleryItem: GalleryItem {
|
||||
|
||||
func updateNode(node: GalleryItemNode) {
|
||||
if let node = node as? ChatImageGalleryItemNode, let location = self.location {
|
||||
node._title.set(.single("\(location.index + 1) \(self.presentationData.strings.Common_of) \(location.count)"))
|
||||
node._title.set(.single(self.presentationData.strings.Items_NOfM("\(location.index + 1)", "\(location.count)").0))
|
||||
|
||||
node.setMessage(self.message)
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ public class UniversalVideoGalleryItem: GalleryItem {
|
||||
let node = UniversalVideoGalleryItemNode(context: self.context, presentationData: self.presentationData, performAction: self.performAction, openActionOptions: self.openActionOptions)
|
||||
|
||||
if let indexData = self.indexData {
|
||||
node._title.set(.single("\(indexData.position + 1) \(self.presentationData.strings.Common_of) \(indexData.totalCount)"))
|
||||
node._title.set(.single(self.presentationData.strings.Items_NOfM("\(indexData.position + 1)", "\(indexData.totalCount)").0))
|
||||
}
|
||||
|
||||
node.setupItem(self)
|
||||
@ -66,7 +66,7 @@ public class UniversalVideoGalleryItem: GalleryItem {
|
||||
public func updateNode(node: GalleryItemNode) {
|
||||
if let node = node as? UniversalVideoGalleryItemNode {
|
||||
if let indexData = self.indexData {
|
||||
node._title.set(.single("\(indexData.position + 1) \(self.presentationData.strings.Common_of) \(indexData.totalCount)"))
|
||||
node._title.set(.single(self.presentationData.strings.Items_NOfM("\(indexData.position + 1)", "\(indexData.totalCount)").0))
|
||||
}
|
||||
|
||||
node.setupItem(self)
|
||||
|
@ -60,7 +60,7 @@ class InstantImageGalleryItem: GalleryItem {
|
||||
node.setImage(imageReference: self.imageReference)
|
||||
|
||||
if let location = self.location {
|
||||
node._title.set(.single("\(location.position + 1) \(self.presentationData.strings.Common_of) \(location.totalCount)"))
|
||||
node._title.set(.single(self.presentationData.strings.Items_NOfM("\(location.position + 1)", "\(location.totalCount)").0))
|
||||
}
|
||||
|
||||
node.setCaption(self.caption, credit: self.credit)
|
||||
@ -71,7 +71,7 @@ class InstantImageGalleryItem: GalleryItem {
|
||||
func updateNode(node: GalleryItemNode) {
|
||||
if let node = node as? InstantImageGalleryItemNode {
|
||||
if let location = self.location {
|
||||
node._title.set(.single("\(location.position + 1) \(self.presentationData.strings.Common_of) \(location.totalCount)"))
|
||||
node._title.set(.single(self.presentationData.strings.Items_NOfM("\(location.position + 1)", "\(location.totalCount)").0))
|
||||
}
|
||||
|
||||
node.setCaption(self.caption, credit: self.credit)
|
||||
|
@ -215,11 +215,11 @@ func importLegacyPreferences(accountManager: AccountManager, account: TemporaryA
|
||||
}
|
||||
switch autoNightPreferences.mode {
|
||||
case TGPresentationAutoNightModeSunsetSunrise:
|
||||
settings.automaticThemeSwitchSetting = AutomaticThemeSwitchSetting(trigger: .timeBased(setting: .automatic(latitude: Double(autoNightPreferences.latitude), longitude: Double(autoNightPreferences.longitude), localizedName: autoNightPreferences.cachedLocationName)), theme: nightTheme)
|
||||
settings.automaticThemeSwitchSetting = AutomaticThemeSwitchSetting(trigger: .timeBased(setting: .automatic(latitude: Double(autoNightPreferences.latitude), longitude: Double(autoNightPreferences.longitude), localizedName: autoNightPreferences.cachedLocationName)), theme: .builtin(nightTheme))
|
||||
case TGPresentationAutoNightModeScheduled:
|
||||
settings.automaticThemeSwitchSetting = AutomaticThemeSwitchSetting(trigger: .timeBased(setting: .manual(fromSeconds: autoNightPreferences.scheduleStart, toSeconds: autoNightPreferences.scheduleEnd)), theme: nightTheme)
|
||||
settings.automaticThemeSwitchSetting = AutomaticThemeSwitchSetting(trigger: .timeBased(setting: .manual(fromSeconds: autoNightPreferences.scheduleStart, toSeconds: autoNightPreferences.scheduleEnd)), theme: .builtin(nightTheme))
|
||||
case TGPresentationAutoNightModeBrightness:
|
||||
settings.automaticThemeSwitchSetting = AutomaticThemeSwitchSetting(trigger: .brightness(threshold: Double(autoNightPreferences.brightnessThreshold)), theme: nightTheme)
|
||||
settings.automaticThemeSwitchSetting = AutomaticThemeSwitchSetting(trigger: .brightness(threshold: Double(autoNightPreferences.brightnessThreshold)), theme: .builtin(nightTheme))
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
@ -340,8 +340,9 @@ private final class LegacyComponentsGlobalsProviderImpl: NSObject, LegacyCompone
|
||||
|
||||
let theme = presentationTheme.list
|
||||
let navigationBar = presentationTheme.rootController.navigationBar
|
||||
let tabBar = presentationTheme.rootController.tabBar
|
||||
|
||||
return TGMediaAssetsPallete(dark: presentationTheme.overallDarkAppearance, backgroundColor: theme.plainBackgroundColor, selectionColor: theme.itemHighlightedBackgroundColor, separatorColor: theme.itemPlainSeparatorColor, textColor: theme.itemPrimaryTextColor, secondaryTextColor: theme.controlSecondaryColor, accentColor: theme.itemAccentColor, barBackgroundColor: navigationBar.backgroundColor, barSeparatorColor: navigationBar.separatorColor, navigationTitleColor: navigationBar.primaryTextColor, badge: generateStretchableFilledCircleImage(diameter: 22.0, color: navigationBar.accentTextColor), badgeTextColor: navigationBar.backgroundColor, sendIconImage: PresentationResourcesChat.chatInputPanelSendButtonImage(presentationTheme), maybeAccentColor: navigationBar.accentTextColor)
|
||||
return TGMediaAssetsPallete(dark: presentationTheme.overallDarkAppearance, backgroundColor: theme.plainBackgroundColor, selectionColor: theme.itemHighlightedBackgroundColor, separatorColor: theme.itemPlainSeparatorColor, textColor: theme.itemPrimaryTextColor, secondaryTextColor: theme.controlSecondaryColor, accentColor: theme.itemAccentColor, barBackgroundColor: tabBar.backgroundColor, barSeparatorColor: tabBar.separatorColor, navigationTitleColor: navigationBar.primaryTextColor, badge: generateStretchableFilledCircleImage(diameter: 22.0, color: navigationBar.accentTextColor), badgeTextColor: navigationBar.backgroundColor, sendIconImage: PresentationResourcesChat.chatInputPanelSendButtonImage(presentationTheme), maybeAccentColor: navigationBar.accentTextColor)
|
||||
}
|
||||
|
||||
func checkButtonPallete() -> TGCheckButtonPallete! {
|
||||
|
@ -740,12 +740,6 @@ NSString *suffix = @"";
|
||||
if ([platform hasPrefix:@"iPad"]) return UIDeviceUnknowniPad;
|
||||
if ([platform hasPrefix:@"AppleTV"]) return UIDeviceUnknownAppleTV;
|
||||
|
||||
#define IPAD_PRO_3G_NAMESTRING @"iPad Pro 12.9 (3rd gen)"
|
||||
#define IPAD_PRO_11_NAMESTRING @"iPad Pro 11"
|
||||
#define IPAD_PRO_6G_NAMESTRING @"iPad (6th gen)"
|
||||
#define IPAD_PRO_10_5_NAMESTRING @"iPad Pro 10.5"
|
||||
#define IPAD_PRO_12_9_NAMESTRING @"iPad Pro 12.9"
|
||||
|
||||
// Simulator thanks Jordan Breeding
|
||||
if ([platform hasSuffix:@"86"] || [platform isEqual:@"x86_64"])
|
||||
{
|
||||
|
@ -36,7 +36,7 @@ class SecureIdDocumentGalleryItem: GalleryItem {
|
||||
|
||||
node.setResource(secureIdContext: self.secureIdContext, resource: self.resource)
|
||||
|
||||
node._title.set(.single("\(self.location.position + 1) \(self.strings.Common_of) \(self.location.totalCount)"))
|
||||
node._title.set(.single(self.strings.Items_NOfM("\(self.location.position + 1)", "\(self.location.totalCount)").0))
|
||||
|
||||
node.setCaption(self.caption)
|
||||
node.delete = self.delete
|
||||
@ -46,7 +46,7 @@ class SecureIdDocumentGalleryItem: GalleryItem {
|
||||
|
||||
func updateNode(node: GalleryItemNode) {
|
||||
if let node = node as? SecureIdDocumentGalleryItemNode {
|
||||
node._title.set(.single("\(self.location.position + 1) \(self.strings.Common_of) \(self.location.totalCount)"))
|
||||
node._title.set(.single(self.strings.Items_NOfM("\(self.location.position + 1)", "\(self.location.totalCount)").0))
|
||||
|
||||
node.setCaption(self.caption)
|
||||
node.delete = self.delete
|
||||
|
@ -59,7 +59,7 @@ class PeerAvatarImageGalleryItem: GalleryItem {
|
||||
let node = PeerAvatarImageGalleryItemNode(context: self.context, presentationData: self.presentationData, peer: self.peer)
|
||||
|
||||
if let indexData = self.entry.indexData {
|
||||
node._title.set(.single("\(indexData.position + 1) \(self.presentationData.strings.Common_of) \(indexData.totalCount)"))
|
||||
node._title.set(.single(self.presentationData.strings.Items_NOfM("\(indexData.position + 1)", "\(indexData.totalCount)").0))
|
||||
}
|
||||
|
||||
node.setEntry(self.entry)
|
||||
@ -71,7 +71,7 @@ class PeerAvatarImageGalleryItem: GalleryItem {
|
||||
func updateNode(node: GalleryItemNode) {
|
||||
if let node = node as? PeerAvatarImageGalleryItemNode {
|
||||
if let indexData = self.entry.indexData {
|
||||
node._title.set(.single("\(indexData.position + 1) \(self.presentationData.strings.Common_of) \(indexData.totalCount)"))
|
||||
node._title.set(.single(self.presentationData.strings.Items_NOfM("\(indexData.position + 1)", "\(indexData.totalCount)").0))
|
||||
}
|
||||
|
||||
node.setEntry(self.entry)
|
||||
|
@ -379,7 +379,7 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
|
||||
let _ = enqueueMessages(account: context.account, peerId: context.account.peerId, messages: [message]).start()
|
||||
|
||||
if let navigateToChat = navigateToChat {
|
||||
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.EditTheme_ThemeTemplateAlert, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Settings_SavedMessages, action: {
|
||||
presentControllerImpl?(textAlertController(context: context, title: presentationData.strings.EditTheme_ThemeTemplateAlertTitle, text: presentationData.strings.EditTheme_ThemeTemplateAlertText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Settings_SavedMessages, action: {
|
||||
completion()
|
||||
navigateToChat(context.account.peerId)
|
||||
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {
|
||||
@ -524,8 +524,18 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
|
||||
return state
|
||||
}
|
||||
|
||||
if case .slugOccupied = error {
|
||||
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.EditTheme_ErrorLinkTaken, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||
var errorText: String?
|
||||
switch error {
|
||||
case .slugOccupied:
|
||||
errorText = presentationData.strings.EditTheme_ErrorLinkTaken
|
||||
case .slugInvalid:
|
||||
errorText = presentationData.strings.EditTheme_ErrorInvalidCharacters
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if let errorText = errorText {
|
||||
presentControllerImpl?(textAlertController(context: context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import TelegramStringFormatting
|
||||
import AccountContext
|
||||
import DeviceLocationManager
|
||||
import Geocoding
|
||||
import WallpaperResources
|
||||
|
||||
private enum TriggerMode {
|
||||
case none
|
||||
@ -24,14 +25,16 @@ private enum TimeBasedManualField {
|
||||
}
|
||||
|
||||
private final class ThemeAutoNightSettingsControllerArguments {
|
||||
let context: AccountContext
|
||||
let updateMode: (TriggerMode) -> Void
|
||||
let updateTimeBasedAutomatic: (Bool) -> Void
|
||||
let openTimeBasedManual: (TimeBasedManualField) -> Void
|
||||
let updateTimeBasedAutomaticLocation: () -> Void
|
||||
let updateAutomaticBrightness: (Double) -> Void
|
||||
let updateTheme: (PresentationBuiltinThemeReference) -> Void
|
||||
let updateTheme: (PresentationThemeReference) -> Void
|
||||
|
||||
init(updateMode: @escaping (TriggerMode) -> Void, updateTimeBasedAutomatic: @escaping (Bool) -> Void, openTimeBasedManual: @escaping (TimeBasedManualField) -> Void, updateTimeBasedAutomaticLocation: @escaping () -> Void, updateAutomaticBrightness: @escaping (Double) -> Void, updateTheme: @escaping (PresentationBuiltinThemeReference) -> Void) {
|
||||
init(context: AccountContext, updateMode: @escaping (TriggerMode) -> Void, updateTimeBasedAutomatic: @escaping (Bool) -> Void, openTimeBasedManual: @escaping (TimeBasedManualField) -> Void, updateTimeBasedAutomaticLocation: @escaping () -> Void, updateAutomaticBrightness: @escaping (Double) -> Void, updateTheme: @escaping (PresentationThemeReference) -> Void) {
|
||||
self.context = context
|
||||
self.updateMode = updateMode
|
||||
self.updateTimeBasedAutomatic = updateTimeBasedAutomatic
|
||||
self.openTimeBasedManual = openTimeBasedManual
|
||||
@ -61,8 +64,7 @@ private enum ThemeAutoNightSettingsControllerEntry: ItemListNodeEntry {
|
||||
case settingInfo(PresentationTheme, String)
|
||||
|
||||
case themeHeader(PresentationTheme, String)
|
||||
case themeNightBlue(PresentationTheme, String, Bool)
|
||||
case themeNight(PresentationTheme, String, Bool)
|
||||
case themeItem(PresentationTheme, PresentationStrings, [PresentationThemeReference], PresentationThemeReference, [Int64: PresentationThemeAccentColor])
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
@ -70,7 +72,7 @@ private enum ThemeAutoNightSettingsControllerEntry: ItemListNodeEntry {
|
||||
return ThemeAutoNightSettingsControllerSection.mode.rawValue
|
||||
case .settingsHeader, .timeBasedAutomaticLocation, .timeBasedAutomaticLocationValue, .timeBasedManualFrom, .timeBasedManualTo, .brightnessValue, .settingInfo:
|
||||
return ThemeAutoNightSettingsControllerSection.settings.rawValue
|
||||
case .themeHeader, .themeNightBlue, .themeNight:
|
||||
case .themeHeader, .themeItem:
|
||||
return ThemeAutoNightSettingsControllerSection.theme.rawValue
|
||||
}
|
||||
}
|
||||
@ -99,10 +101,8 @@ private enum ThemeAutoNightSettingsControllerEntry: ItemListNodeEntry {
|
||||
return 9
|
||||
case .themeHeader:
|
||||
return 10
|
||||
case .themeNightBlue:
|
||||
case .themeItem:
|
||||
return 11
|
||||
case .themeNight:
|
||||
return 12
|
||||
}
|
||||
}
|
||||
|
||||
@ -174,14 +174,8 @@ private enum ThemeAutoNightSettingsControllerEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .themeNightBlue(lhsTheme, lhsTitle, lhsValue):
|
||||
if case let .themeNightBlue(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .themeNight(lhsTheme, lhsTitle, lhsValue):
|
||||
if case let .themeNight(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue {
|
||||
case let .themeItem(lhsTheme, lhsStrings, lhsThemes, lhsCurrentTheme, lhsThemeAccentColors):
|
||||
if case let .themeItem(rhsTheme, rhsStrings, rhsThemes, rhsCurrentTheme, rhsThemeAccentColors) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsThemes == rhsThemes, lhsCurrentTheme == rhsCurrentTheme, lhsThemeAccentColors == rhsThemeAccentColors {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -233,19 +227,16 @@ private enum ThemeAutoNightSettingsControllerEntry: ItemListNodeEntry {
|
||||
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
|
||||
case let .themeHeader(theme, title):
|
||||
return ItemListSectionHeaderItem(theme: theme, text: title, sectionId: self.section)
|
||||
case let .themeNightBlue(theme, title, value):
|
||||
return ItemListCheckboxItem(theme: theme, title: title, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: {
|
||||
arguments.updateTheme(.nightAccent)
|
||||
})
|
||||
case let .themeNight(theme, title, value):
|
||||
return ItemListCheckboxItem(theme: theme, title: title, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: {
|
||||
arguments.updateTheme(.night)
|
||||
case let .themeItem(theme, strings, themes, currentTheme, themeSpecificAccentColors):
|
||||
return ThemeSettingsThemeItem(context: arguments.context, theme: theme, strings: strings, sectionId: self.section, themes: themes, themeSpecificAccentColors: themeSpecificAccentColors, currentTheme: currentTheme, updatedTheme: { theme in
|
||||
arguments.updateTheme(theme)
|
||||
}, longTapped: { _ in
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func themeAutoNightSettingsControllerEntries(theme: PresentationTheme, strings: PresentationStrings, switchSetting: AutomaticThemeSwitchSetting, dateTimeFormat: PresentationDateTimeFormat) -> [ThemeAutoNightSettingsControllerEntry] {
|
||||
private func themeAutoNightSettingsControllerEntries(theme: PresentationTheme, strings: PresentationStrings, settings: PresentationThemeSettings, switchSetting: AutomaticThemeSwitchSetting, availableThemes: [PresentationThemeReference], dateTimeFormat: PresentationDateTimeFormat) -> [ThemeAutoNightSettingsControllerEntry] {
|
||||
var entries: [ThemeAutoNightSettingsControllerEntry] = []
|
||||
|
||||
let activeTriggerMode: TriggerMode
|
||||
@ -297,8 +288,7 @@ private func themeAutoNightSettingsControllerEntries(theme: PresentationTheme, s
|
||||
break
|
||||
case .timeBased, .brightness:
|
||||
entries.append(.themeHeader(theme, strings.AutoNightTheme_PreferredTheme))
|
||||
entries.append(.themeNightBlue(theme, strings.Appearance_ThemeCarouselTintedNight, switchSetting.theme == .nightAccent))
|
||||
entries.append(.themeNight(theme, strings.Appearance_ThemeCarouselNewNight, switchSetting.theme == .night))
|
||||
entries.append(.themeItem(theme, strings, availableThemes, switchSetting.theme, settings.themeSpecificAccentColors))
|
||||
}
|
||||
|
||||
return entries
|
||||
@ -389,7 +379,7 @@ public func themeAutoNightSettingsController(context: AccountContext) -> ViewCon
|
||||
updateLocationDisposable.set(disposable)
|
||||
}
|
||||
|
||||
let arguments = ThemeAutoNightSettingsControllerArguments(updateMode: { mode in
|
||||
let arguments = ThemeAutoNightSettingsControllerArguments(context: context, updateMode: { mode in
|
||||
var updateLocation = false
|
||||
updateSettings { settings in
|
||||
var settings = settings
|
||||
@ -508,19 +498,51 @@ public func themeAutoNightSettingsController(context: AccountContext) -> ViewCon
|
||||
}
|
||||
}))
|
||||
}, updateTheme: { theme in
|
||||
updateSettings { settings in
|
||||
var settings = settings
|
||||
settings.theme = theme
|
||||
return settings
|
||||
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, settings: file.settings)
|
||||
|> map { wallpaper -> TelegramWallpaper? in
|
||||
return wallpaper?.wallpaper
|
||||
}
|
||||
} else {
|
||||
resolvedWallpaper = .single(nil)
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
updateSettings { settings in
|
||||
var settings = settings
|
||||
settings.theme = updatedTheme
|
||||
return settings
|
||||
}
|
||||
|
||||
return .complete()
|
||||
}).start()
|
||||
})
|
||||
|
||||
let signal = combineLatest(context.sharedContext.presentationData |> deliverOnMainQueue, sharedData |> deliverOnMainQueue, stagingSettingsPromise.get() |> deliverOnMainQueue)
|
||||
|> map { presentationData, sharedData, stagingSettings -> (ItemListControllerState, (ItemListNodeState<ThemeAutoNightSettingsControllerEntry>, ThemeAutoNightSettingsControllerEntry.ItemGenerationArguments)) in
|
||||
let cloudThemes = Promise<[TelegramTheme]>()
|
||||
let updatedCloudThemes = telegramThemes(postbox: context.account.postbox, network: context.account.network, accountManager: context.sharedContext.accountManager)
|
||||
cloudThemes.set(updatedCloudThemes)
|
||||
|
||||
let signal = combineLatest(context.sharedContext.presentationData |> deliverOnMainQueue, sharedData |> deliverOnMainQueue, cloudThemes.get() |> deliverOnMainQueue, stagingSettingsPromise.get() |> deliverOnMainQueue)
|
||||
|> map { presentationData, sharedData, cloudThemes, stagingSettings -> (ItemListControllerState, (ItemListNodeState<ThemeAutoNightSettingsControllerEntry>, ThemeAutoNightSettingsControllerEntry.ItemGenerationArguments)) in
|
||||
let settings = (sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings] as? PresentationThemeSettings) ?? PresentationThemeSettings.defaultSettings
|
||||
|
||||
let defaultThemes: [PresentationThemeReference] = [.builtin(.night), .builtin(.nightAccent)]
|
||||
let cloudThemes: [PresentationThemeReference] = cloudThemes.map { .cloud(PresentationCloudTheme(theme: $0, resolvedWallpaper: nil)) }
|
||||
|
||||
var availableThemes = defaultThemes
|
||||
availableThemes.append(contentsOf: cloudThemes)
|
||||
|
||||
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.AutoNightTheme_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
|
||||
let listState = ItemListNodeState(entries: themeAutoNightSettingsControllerEntries(theme: presentationData.theme, strings: presentationData.strings, switchSetting: stagingSettings ?? settings.automaticThemeSwitchSetting, dateTimeFormat: presentationData.dateTimeFormat), style: .blocks, animateChanges: false)
|
||||
let listState = ItemListNodeState(entries: themeAutoNightSettingsControllerEntries(theme: presentationData.theme, strings: presentationData.strings, settings: settings, switchSetting: stagingSettings ?? settings.automaticThemeSwitchSetting, availableThemes: availableThemes, dateTimeFormat: presentationData.dateTimeFormat), style: .blocks, animateChanges: false)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|
@ -101,7 +101,7 @@ private final class ThemeAutoNightTimeSelectionActionSheetItemNode: ActionSheetI
|
||||
self.pickerView.datePickerMode = .time
|
||||
self.pickerView.timeZone = TimeZone(secondsFromGMT: 0)
|
||||
self.pickerView.date = Date(timeIntervalSince1970: Double(currentValue))
|
||||
self.pickerView.locale = localeWithStrings(strings)
|
||||
self.pickerView.locale = Locale.current
|
||||
|
||||
self.pickerView.setValue(theme.primaryTextColor, forKey: "textColor")
|
||||
|
||||
|
@ -98,11 +98,11 @@ private let colors: [UInt32: String] = [
|
||||
0x80b3c4: "Glacier",
|
||||
0xfebaad: "Melon",
|
||||
0xc54b8c: "Mulberry",
|
||||
0xa9c6c2: "Opal"
|
||||
0xa9c6c2: "Opal",
|
||||
0x54a5f8: "Blue"
|
||||
]
|
||||
|
||||
private let adjectives = [
|
||||
"Always",
|
||||
"Ancient",
|
||||
"Antique",
|
||||
"Autumn",
|
||||
@ -148,7 +148,6 @@ private let adjectives = [
|
||||
"Frosty",
|
||||
"Frozen",
|
||||
"Gentle",
|
||||
"Golden",
|
||||
"Heavenly",
|
||||
"Hyper",
|
||||
"Icy",
|
||||
@ -202,7 +201,6 @@ private let adjectives = [
|
||||
"Twinkling",
|
||||
"Ultimate",
|
||||
"Ultra",
|
||||
"Uptown",
|
||||
"Velvety",
|
||||
"Vibrant",
|
||||
"Vintage",
|
||||
@ -322,7 +320,6 @@ private extension UIColor {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func generateThemeName(accentColor: UIColor) -> String {
|
||||
var nearest: (color: UInt32, distance: Int32)?
|
||||
for (color, _) in colors {
|
||||
@ -340,11 +337,7 @@ func generateThemeName(accentColor: UIColor) -> String {
|
||||
if arc4random() % 2 == 0 {
|
||||
return "\(adjectives[Int(arc4random()) % adjectives.count].capitalized) \(colorName)"
|
||||
} else {
|
||||
if false, arc4random() % 3 == 0 {
|
||||
return "\(adjectives[Int(arc4random()) % adjectives.count].capitalized) \(colorName) \(subjectives[Int(arc4random()) % subjectives.count].capitalized)"
|
||||
} else {
|
||||
return "\(colorName) \(subjectives[Int(arc4random()) % subjectives.count].capitalized)"
|
||||
}
|
||||
return "\(colorName) \(subjectives[Int(arc4random()) % subjectives.count].capitalized)"
|
||||
}
|
||||
} else {
|
||||
return ""
|
||||
|
@ -24,13 +24,12 @@ public final class ThemePreviewController: ViewController {
|
||||
private let previewTheme: PresentationTheme
|
||||
private let source: ThemePreviewSource
|
||||
private let theme = Promise<TelegramTheme?>()
|
||||
private let presentationTheme = Promise<PresentationTheme>()
|
||||
|
||||
private var controllerNode: ThemePreviewControllerNode {
|
||||
return self.displayNode as! ThemePreviewControllerNode
|
||||
}
|
||||
|
||||
private let titleView: CounterContollerTitleView
|
||||
|
||||
|
||||
private var didPlayPresentationAnimation = false
|
||||
|
||||
private var presentationData: PresentationData
|
||||
@ -45,8 +44,7 @@ public final class ThemePreviewController: ViewController {
|
||||
self.source = source
|
||||
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
self.titleView = CounterContollerTitleView(theme: self.previewTheme)
|
||||
self.presentationTheme.set(.single(previewTheme))
|
||||
|
||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationTheme: self.previewTheme, presentationStrings: self.presentationData.strings))
|
||||
|
||||
@ -61,24 +59,45 @@ public final class ThemePreviewController: ViewController {
|
||||
return .single(nil)
|
||||
})
|
||||
themeName = previewTheme.name.string
|
||||
|
||||
self.presentationTheme.set(.single(self.previewTheme)
|
||||
|> then(
|
||||
self.theme.get()
|
||||
|> mapToSignal { theme in
|
||||
if let file = theme?.file {
|
||||
return telegramThemeData(account: context.account, accountManager: context.sharedContext.accountManager, resource: file.resource)
|
||||
|> mapToSignal { data -> Signal<PresentationTheme, NoError> in
|
||||
guard let data = data, let presentationTheme = makePresentationTheme(data: data) else {
|
||||
return .complete()
|
||||
}
|
||||
return .single(presentationTheme)
|
||||
}
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
))
|
||||
} else {
|
||||
self.theme.set(.single(nil))
|
||||
themeName = previewTheme.name.string
|
||||
}
|
||||
|
||||
self.titleView.title = CounterContollerTitle(title: themeName, counter: " ")
|
||||
let titleView = CounterContollerTitleView(theme: self.previewTheme)
|
||||
titleView.title = CounterContollerTitle(title: themeName, counter: " ")
|
||||
self.navigationItem.titleView = titleView
|
||||
|
||||
|
||||
self.statusBar.statusBarStyle = self.previewTheme.rootController.statusBarStyle.style
|
||||
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: self.previewTheme.rootController.navigationBar.accentTextColor), style: .plain, target: self, action: #selector(self.actionPressed))
|
||||
|
||||
self.disposable = (self.theme.get()
|
||||
|> deliverOnMainQueue).start(next: { [weak self] theme in
|
||||
self.disposable = (combineLatest(self.theme.get(), self.presentationTheme.get())
|
||||
|> deliverOnMainQueue).start(next: { [weak self] theme, presentationTheme in
|
||||
if let strongSelf = self, let theme = theme {
|
||||
strongSelf.titleView.title = CounterContollerTitle(title: themeName, counter: strongSelf.presentationData.strings.Theme_UsersCount(max(1, theme.installCount)))
|
||||
let titleView = CounterContollerTitleView(theme: strongSelf.previewTheme)
|
||||
titleView.title = CounterContollerTitle(title: themeName, counter: strongSelf.presentationData.strings.Theme_UsersCount(max(1, theme.installCount)))
|
||||
strongSelf.navigationItem.titleView = titleView
|
||||
strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationTheme: presentationTheme, presentationStrings: strongSelf.presentationData.strings))
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -29,7 +29,7 @@ private func generateMaskImage(color: UIColor) -> UIImage? {
|
||||
|
||||
final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
private let context: AccountContext
|
||||
private let previewTheme: PresentationTheme
|
||||
private var previewTheme: PresentationTheme
|
||||
private var presentationData: PresentationData
|
||||
|
||||
public let wallpaperPromise = Promise<TelegramWallpaper>()
|
||||
@ -87,12 +87,15 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.instantChatBackgroundNode.displaysAsynchronously = false
|
||||
self.instantChatBackgroundNode.image = chatControllerBackgroundImage(theme: previewTheme, wallpaper: previewTheme.chat.defaultWallpaper, mediaBox: context.sharedContext.accountManager.mediaBox, knockoutMode: context.sharedContext.immediateExperimentalUISettings.knockoutWallpaper)
|
||||
self.instantChatBackgroundNode.motionEnabled = previewTheme.chat.defaultWallpaper.settings?.motion ?? false
|
||||
self.instantChatBackgroundNode.view.contentMode = .scaleAspectFill
|
||||
|
||||
self.remoteChatBackgroundNode = TransformImageNode()
|
||||
self.remoteChatBackgroundNode.backgroundColor = previewTheme.chatList.backgroundColor
|
||||
self.remoteChatBackgroundNode.view.contentMode = .scaleAspectFill
|
||||
|
||||
self.blurredNode = BlurredImageNode()
|
||||
self.blurredNode.clipsToBounds = true
|
||||
self.blurredNode.blurView.contentMode = .scaleAspectFill
|
||||
|
||||
self.toolbarNode = WallpaperGalleryToolbarNode(theme: self.previewTheme, strings: self.presentationData.strings)
|
||||
|
||||
@ -263,6 +266,27 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.pageControlNode.setPage(0.0)
|
||||
}
|
||||
|
||||
func updateTheme(_ theme: PresentationTheme) {
|
||||
self.previewTheme = theme
|
||||
|
||||
self.backgroundColor = self.previewTheme.list.plainBackgroundColor
|
||||
|
||||
self.pageControlNode.dotColor = self.previewTheme.chatList.unreadBadgeActiveBackgroundColor
|
||||
self.pageControlNode.inactiveDotColor = self.previewTheme.list.pageIndicatorInactiveColor
|
||||
|
||||
self.chatListBackgroundNode.backgroundColor = self.previewTheme.chatList.backgroundColor
|
||||
self.maskNode.image = generateMaskImage(color: self.previewTheme.chatList.backgroundColor)
|
||||
if case let .color(value) = self.previewTheme.chat.defaultWallpaper {
|
||||
self.instantChatBackgroundNode.backgroundColor = UIColor(rgb: UInt32(bitPattern: value))
|
||||
}
|
||||
|
||||
self.toolbarNode.updateThemeAndStrings(theme: self.previewTheme, strings: self.presentationData.strings)
|
||||
|
||||
if let (layout, navigationBarHeight) = self.validLayout {
|
||||
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
let bounds = scrollView.bounds
|
||||
if !bounds.width.isZero {
|
||||
|
@ -36,7 +36,7 @@ func themeDisplayName(strings: PresentationStrings, reference: PresentationTheme
|
||||
|
||||
private final class ThemeSettingsControllerArguments {
|
||||
let context: AccountContext
|
||||
let selectTheme: (PresentationThemeReference) -> Void
|
||||
let updateTheme: (PresentationThemeReference) -> Void
|
||||
let selectFontSize: (PresentationFontSize) -> Void
|
||||
let openWallpaperSettings: () -> Void
|
||||
let selectAccentColor: (PresentationThemeAccentColor) -> Void
|
||||
@ -48,9 +48,9 @@ private final class ThemeSettingsControllerArguments {
|
||||
let presentThemeMenu: (PresentationThemeReference, Bool) -> Void
|
||||
let editTheme: (PresentationCloudTheme) -> Void
|
||||
|
||||
init(context: AccountContext, selectTheme: @escaping (PresentationThemeReference) -> Void, selectFontSize: @escaping (PresentationFontSize) -> Void, openWallpaperSettings: @escaping () -> Void, selectAccentColor: @escaping (PresentationThemeAccentColor) -> Void, openAccentColorPicker: @escaping (PresentationThemeReference, PresentationThemeAccentColor?) -> Void, openAutoNightTheme: @escaping () -> Void, toggleLargeEmoji: @escaping (Bool) -> Void, disableAnimations: @escaping (Bool) -> Void, selectAppIcon: @escaping (String) -> Void, presentThemeMenu: @escaping (PresentationThemeReference, Bool) -> Void, editTheme: @escaping (PresentationCloudTheme) -> Void) {
|
||||
init(context: AccountContext, updateTheme: @escaping (PresentationThemeReference) -> Void, selectFontSize: @escaping (PresentationFontSize) -> Void, openWallpaperSettings: @escaping () -> Void, selectAccentColor: @escaping (PresentationThemeAccentColor) -> Void, openAccentColorPicker: @escaping (PresentationThemeReference, PresentationThemeAccentColor?) -> Void, openAutoNightTheme: @escaping () -> Void, toggleLargeEmoji: @escaping (Bool) -> Void, disableAnimations: @escaping (Bool) -> Void, selectAppIcon: @escaping (String) -> Void, presentThemeMenu: @escaping (PresentationThemeReference, Bool) -> Void, editTheme: @escaping (PresentationCloudTheme) -> Void) {
|
||||
self.context = context
|
||||
self.selectTheme = selectTheme
|
||||
self.updateTheme = updateTheme
|
||||
self.selectFontSize = selectFontSize
|
||||
self.openWallpaperSettings = openWallpaperSettings
|
||||
self.selectAccentColor = selectAccentColor
|
||||
@ -290,14 +290,14 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
||||
})
|
||||
case let .themeListHeader(theme, text):
|
||||
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||
case let .themeItem(theme, strings, themes, currentTheme, themeSpecificAccentColors, currentColor):
|
||||
case let .themeItem(theme, strings, themes, currentTheme, themeSpecificAccentColors, _):
|
||||
return ThemeSettingsThemeItem(context: arguments.context, theme: theme, strings: strings, sectionId: self.section, themes: themes, themeSpecificAccentColors: themeSpecificAccentColors, currentTheme: currentTheme, updatedTheme: { theme in
|
||||
if case let .cloud(theme) = theme, theme.theme.file == nil {
|
||||
if theme.theme.isCreator {
|
||||
arguments.editTheme(theme)
|
||||
}
|
||||
} else {
|
||||
arguments.selectTheme(theme)
|
||||
arguments.updateTheme(theme)
|
||||
}
|
||||
}, longTapped: { theme in
|
||||
arguments.presentThemeMenu(theme, theme.index == currentTheme.index)
|
||||
@ -324,11 +324,6 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
||||
}
|
||||
}
|
||||
|
||||
private struct ThemeSettingsState: Equatable {
|
||||
init() {
|
||||
}
|
||||
}
|
||||
|
||||
private func themeSettingsControllerEntries(presentationData: PresentationData, theme: PresentationTheme, themeReference: PresentationThemeReference, themeSpecificAccentColors: [Int64: PresentationThemeAccentColor], availableThemes: [PresentationThemeReference], autoNightSettings: AutomaticThemeSwitchSetting, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, dateTimeFormat: PresentationDateTimeFormat, largeEmoji: Bool, disableAnimations: Bool, availableAppIcons: [PresentationAppIcon], currentAppIconName: String?) -> [ThemeSettingsControllerEntry] {
|
||||
var entries: [ThemeSettingsControllerEntry] = []
|
||||
|
||||
@ -342,19 +337,17 @@ private func themeSettingsControllerEntries(presentationData: PresentationData,
|
||||
}
|
||||
|
||||
entries.append(.wallpaper(presentationData.theme, strings.Settings_ChatBackground))
|
||||
|
||||
if theme.name == .builtin(.day) || theme.name == .builtin(.dayClassic) {
|
||||
let title: String
|
||||
switch autoNightSettings.trigger {
|
||||
case .none:
|
||||
title = strings.AutoNightTheme_Disabled
|
||||
case .timeBased:
|
||||
title = strings.AutoNightTheme_Scheduled
|
||||
case .brightness:
|
||||
title = strings.AutoNightTheme_Automatic
|
||||
}
|
||||
entries.append(.autoNightTheme(presentationData.theme, strings.Appearance_AutoNightTheme, title))
|
||||
|
||||
let title: String
|
||||
switch autoNightSettings.trigger {
|
||||
case .none:
|
||||
title = strings.AutoNightTheme_Disabled
|
||||
case .timeBased:
|
||||
title = strings.AutoNightTheme_Scheduled
|
||||
case .brightness:
|
||||
title = strings.AutoNightTheme_Automatic
|
||||
}
|
||||
entries.append(.autoNightTheme(presentationData.theme, strings.Appearance_AutoNightTheme, title))
|
||||
|
||||
entries.append(.fontSizeHeader(presentationData.theme, strings.Appearance_TextSize.uppercased()))
|
||||
entries.append(.fontSize(presentationData.theme, fontSize))
|
||||
@ -373,18 +366,11 @@ private func themeSettingsControllerEntries(presentationData: PresentationData,
|
||||
}
|
||||
|
||||
public func themeSettingsController(context: AccountContext, focusOnItemTag: ThemeSettingsEntryTag? = nil) -> ViewController {
|
||||
let initialState = ThemeSettingsState()
|
||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: initialState)
|
||||
let updateState: ((ThemeSettingsState) -> ThemeSettingsState) -> Void = { f in
|
||||
statePromise.set(stateValue.modify { f($0) })
|
||||
}
|
||||
|
||||
var pushControllerImpl: ((ViewController) -> Void)?
|
||||
var presentControllerImpl: ((ViewController, Any?) -> Void)?
|
||||
var getNavigationControllerImpl: (() -> NavigationController?)?
|
||||
|
||||
var selectThemeImpl: ((PresentationThemeReference) -> Void)?
|
||||
var updateThemeImpl: ((PresentationThemeReference) -> Void)?
|
||||
var moreImpl: (() -> Void)?
|
||||
|
||||
let _ = telegramWallpapers(postbox: context.account.postbox, network: context.account.network).start()
|
||||
@ -405,8 +391,8 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
||||
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
|
||||
selectThemeImpl?(theme)
|
||||
let arguments = ThemeSettingsControllerArguments(context: context, updateTheme: { theme in
|
||||
updateThemeImpl?(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)
|
||||
@ -488,7 +474,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
||||
} else {
|
||||
newTheme = .builtin(.nightAccent)
|
||||
}
|
||||
selectThemeImpl?(newTheme)
|
||||
updateThemeImpl?(newTheme)
|
||||
}
|
||||
|
||||
let _ = deleteThemeInteractively(account: context.account, accountManager: context.sharedContext.accountManager, theme: theme.theme).start()
|
||||
@ -516,8 +502,8 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
||||
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
})
|
||||
|
||||
let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings]), cloudThemes.get(), availableAppIcons, currentAppIconName.get(), statePromise.get())
|
||||
|> map { presentationData, sharedData, cloudThemes, availableAppIcons, currentAppIconName, state -> (ItemListControllerState, (ItemListNodeState<ThemeSettingsControllerEntry>, ThemeSettingsControllerEntry.ItemGenerationArguments)) in
|
||||
let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings]), cloudThemes.get(), availableAppIcons, currentAppIconName.get())
|
||||
|> map { presentationData, sharedData, cloudThemes, availableAppIcons, currentAppIconName -> (ItemListControllerState, (ItemListNodeState<ThemeSettingsControllerEntry>, ThemeSettingsControllerEntry.ItemGenerationArguments)) in
|
||||
let settings = (sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings] as? PresentationThemeSettings) ?? PresentationThemeSettings.defaultSettings
|
||||
|
||||
let fontSize = settings.fontSize
|
||||
@ -565,7 +551,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
||||
getNavigationControllerImpl = { [weak controller] in
|
||||
return controller?.navigationController as? NavigationController
|
||||
}
|
||||
selectThemeImpl = { theme in
|
||||
updateThemeImpl = { theme in
|
||||
let presentationTheme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: theme, accentColor: nil, serviceBackgroundColor: .black, baseColor: nil)
|
||||
|
||||
let resolvedWallpaper: Signal<TelegramWallpaper?, NoError>
|
||||
@ -590,7 +576,6 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
||||
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
|
||||
|
@ -160,19 +160,12 @@ final class WallpaperColorPanelNode: ASDisplayNode, UITextFieldDelegate {
|
||||
}
|
||||
|
||||
@objc internal func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||
if string.count > 1 {
|
||||
if string.count <= 6 {
|
||||
var updated = textField.text ?? ""
|
||||
updated.replaceSubrange(updated.index(updated.startIndex, offsetBy: range.lowerBound) ..< updated.index(updated.startIndex, offsetBy: range.upperBound), with: string)
|
||||
if updated.count <= 6 && updated.rangeOfCharacter(from: CharacterSet(charactersIn: "0123456789abcdefABCDEF").inverted) == nil {
|
||||
textField.text = updated.uppercased()
|
||||
}
|
||||
}
|
||||
return false
|
||||
} else if string.count == 1 {
|
||||
return (textField.text ?? "").count < 6 && string.rangeOfCharacter(from: CharacterSet(charactersIn: "0123456789abcdefABCDEF").inverted) == nil
|
||||
var updated = textField.text ?? ""
|
||||
updated.replaceSubrange(updated.index(updated.startIndex, offsetBy: range.lowerBound) ..< updated.index(updated.startIndex, offsetBy: range.upperBound), with: string)
|
||||
if updated.count <= 6 && updated.rangeOfCharacter(from: CharacterSet(charactersIn: "0123456789abcdefABCDEF").inverted) == nil {
|
||||
textField.text = updated.uppercased()
|
||||
}
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
@objc func textFieldTextChanged(_ sender: UITextField) {
|
||||
|
@ -66,8 +66,8 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode {
|
||||
self.separatorNode.backgroundColor = theme.rootController.tabBar.separatorColor
|
||||
self.topSeparatorNode.backgroundColor = theme.rootController.tabBar.separatorColor
|
||||
|
||||
self.cancelButton.setTitle(strings.Common_Cancel, with: Font.regular(17.0), with: theme.rootController.navigationBar.primaryTextColor, for: [])
|
||||
self.doneButton.setTitle(strings.Wallpaper_Set, with: Font.regular(17.0), with: theme.rootController.navigationBar.primaryTextColor, for: [])
|
||||
self.cancelButton.setTitle(strings.Common_Cancel, with: Font.regular(17.0), with: theme.list.itemPrimaryTextColor, for: [])
|
||||
self.doneButton.setTitle(strings.Wallpaper_Set, with: Font.regular(17.0), with: theme.list.itemPrimaryTextColor, for: [])
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize, layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
|
@ -455,7 +455,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
||||
|
||||
if let (item, previousItem, nextItem, order, type, _) = self.playlistStateAndType, !mediaAccessoryPanelHidden {
|
||||
let panelHeight = MediaNavigationAccessoryHeaderNode.minimizedHeight
|
||||
let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationHeight.isZero ? -panelHeight : (navigationHeight + additionalHeight + UIScreenPixel)), size: CGSize(width: layout.size.width, height: panelHeight))
|
||||
let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationHeight.isZero ? -panelHeight : (navigationHeight + additionalHeight)), size: CGSize(width: layout.size.width, height: panelHeight))
|
||||
if let (mediaAccessoryPanel, mediaType) = self.mediaAccessoryPanel, mediaType == type {
|
||||
transition.updateFrame(layer: mediaAccessoryPanel.layer, frame: panelFrame)
|
||||
mediaAccessoryPanel.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: transition)
|
||||
@ -615,7 +615,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
||||
if let dismissingPanel = self.dismissingPanel {
|
||||
self.displayNode.insertSubnode(mediaAccessoryPanel, aboveSubnode: dismissingPanel)
|
||||
} else if let navigationBar = self.navigationBar {
|
||||
self.displayNode.insertSubnode(mediaAccessoryPanel, aboveSubnode: navigationBar)
|
||||
self.displayNode.insertSubnode(mediaAccessoryPanel, belowSubnode: navigationBar)
|
||||
} else {
|
||||
self.displayNode.addSubnode(mediaAccessoryPanel)
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ public enum AddressNameAvailability: Equatable {
|
||||
public enum AddressNameDomain {
|
||||
case account
|
||||
case peer(PeerId)
|
||||
case theme(TelegramTheme)
|
||||
}
|
||||
|
||||
public func checkAddressNameFormat(_ value: String, canEmpty: Bool = false) -> AddressNameFormatError? {
|
||||
@ -106,6 +107,20 @@ public func addressNameAvailability(account: Account, domain: AddressNameDomain,
|
||||
} else {
|
||||
return .single(.invalid)
|
||||
}
|
||||
case .theme:
|
||||
return account.network.request(Api.functions.account.createTheme(slug: name, title: "", document: .inputDocumentEmpty))
|
||||
|> map { _ -> AddressNameAvailability in
|
||||
return .available
|
||||
}
|
||||
|> `catch` { error -> Signal<AddressNameAvailability, NoError> in
|
||||
if error.errorDescription == "THEME_SLUG_OCCUPIED" {
|
||||
return .single(.taken)
|
||||
} else if error.errorDescription == "THEME_SLUG_INVALID" {
|
||||
return .single(.invalid)
|
||||
} else {
|
||||
return .single(.available)
|
||||
}
|
||||
}
|
||||
}
|
||||
} |> switchToLatest
|
||||
}
|
||||
@ -154,6 +169,15 @@ public func updateAddressName(account: Account, domain: AddressNameDomain, name:
|
||||
} else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
case let .theme(theme):
|
||||
let flags: Int32 = 1 << 0
|
||||
return account.network.request(Api.functions.account.updateTheme(flags: flags, format: telegramThemeFormat, theme: .inputTheme(id: theme.id, accessHash: theme.accessHash), slug: nil, title: nil, document: nil))
|
||||
|> mapError { _ -> UpdateAddressNameError in
|
||||
return .generic
|
||||
}
|
||||
|> map { _ in
|
||||
return Void()
|
||||
}
|
||||
}
|
||||
} |> mapError { _ -> UpdateAddressNameError in return .generic } |> switchToLatest
|
||||
}
|
||||
|
@ -26,16 +26,16 @@ final class CachedThemesConfiguration: PostboxCoding {
|
||||
}
|
||||
|
||||
#if os(macOS)
|
||||
private let themeFormat = "macos"
|
||||
private let themeFileExtension = "palette"
|
||||
let telegramThemeFormat = "macos"
|
||||
let telegramThemeFileExtension = "palette"
|
||||
#else
|
||||
private let themeFormat = "ios"
|
||||
private let themeFileExtension = "tgios-theme"
|
||||
let telegramThemeFormat = "ios"
|
||||
let telegramThemeFileExtension = "tgios-theme"
|
||||
#endif
|
||||
|
||||
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))
|
||||
network.request(Api.functions.account.getThemes(format: telegramThemeFormat, hash: hash ?? 0))
|
||||
|> retryRequest
|
||||
|> mapToSignal { result -> Signal<([TelegramTheme], Int32), NoError> in
|
||||
switch result {
|
||||
@ -109,7 +109,7 @@ public enum GetThemeError {
|
||||
}
|
||||
|
||||
public func getTheme(account: Account, slug: String) -> Signal<TelegramTheme, GetThemeError> {
|
||||
return account.network.request(Api.functions.account.getTheme(format: themeFormat, theme: .inputThemeSlug(slug: slug), documentId: 0))
|
||||
return account.network.request(Api.functions.account.getTheme(format: telegramThemeFormat, theme: .inputThemeSlug(slug: slug), documentId: 0))
|
||||
|> mapError { error -> GetThemeError in
|
||||
if error.errorDescription == "THEME_FORMAT_INVALID" {
|
||||
return .unsupported
|
||||
@ -137,7 +137,7 @@ private func checkThemeUpdated(network: Network, theme: TelegramTheme) -> Signal
|
||||
guard let file = theme.file, let fileId = file.id?.id else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
return network.request(Api.functions.account.getTheme(format: themeFormat, theme: .inputTheme(id: theme.id, accessHash: theme.accessHash), documentId: fileId))
|
||||
return network.request(Api.functions.account.getTheme(format: telegramThemeFormat, theme: .inputTheme(id: theme.id, accessHash: theme.accessHash), documentId: fileId))
|
||||
|> mapError { _ -> GetThemeError in return .generic }
|
||||
|> map { theme -> ThemeUpdatedResult in
|
||||
if let theme = TelegramTheme(apiTheme: theme) {
|
||||
@ -192,7 +192,7 @@ private func installTheme(account: Account, theme: TelegramTheme?, autoNight: Bo
|
||||
inputTheme = nil
|
||||
}
|
||||
|
||||
return account.network.request(Api.functions.account.installTheme(flags: flags, format: themeFormat, theme: inputTheme))
|
||||
return account.network.request(Api.functions.account.installTheme(flags: flags, format: telegramThemeFormat, theme: inputTheme))
|
||||
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
||||
return .complete()
|
||||
}
|
||||
@ -240,8 +240,8 @@ private func uploadedThemeThumbnail(postbox: Postbox, network: Network, data: Da
|
||||
}
|
||||
|
||||
private func uploadTheme(account: Account, resource: MediaResource, thumbnailData: Data? = nil) -> Signal<UploadThemeResult, UploadThemeError> {
|
||||
let fileName = "theme.\(themeFileExtension)"
|
||||
let mimeType = "application/x-tgtheme-\(themeFormat)"
|
||||
let fileName = "theme.\(telegramThemeFileExtension)"
|
||||
let mimeType = "application/x-tgtheme-\(telegramThemeFormat)"
|
||||
|
||||
let uploadedThumbnail: Signal<UploadedThemeData?, UploadThemeError>
|
||||
if let thumbnailData = thumbnailData {
|
||||
@ -387,7 +387,7 @@ public func updateTheme(account: Account, accountManager: AccountManager, theme:
|
||||
inputDocument = nil
|
||||
}
|
||||
|
||||
return account.network.request(Api.functions.account.updateTheme(flags: flags, format: themeFormat, theme: .inputTheme(id: theme.id, accessHash: theme.accessHash), slug: slug, title: title, document: inputDocument))
|
||||
return account.network.request(Api.functions.account.updateTheme(flags: flags, format: telegramThemeFormat, theme: .inputTheme(id: theme.id, accessHash: theme.accessHash), slug: slug, title: title, document: inputDocument))
|
||||
|> mapError { error in
|
||||
if error.errorDescription == "THEME_SLUG_INVALID" {
|
||||
return .slugInvalid
|
||||
|
@ -100,6 +100,7 @@ private func makeDarkPresentationTheme(accentColor: UIColor, baseColor: Presenta
|
||||
)
|
||||
|
||||
let intro = PresentationThemeIntro(
|
||||
statusBarStyle: .white,
|
||||
startButtonColor: accentColor,
|
||||
dotColor: UIColor(rgb: 0x5e5e5e)
|
||||
)
|
||||
|
@ -76,6 +76,7 @@ private func makeDarkPresentationTheme(accentColor: UIColor, baseColor: Presenta
|
||||
)
|
||||
|
||||
let intro = PresentationThemeIntro(
|
||||
statusBarStyle: .white,
|
||||
startButtonColor: accentColor,
|
||||
dotColor: mainSecondaryColor
|
||||
)
|
||||
|
@ -83,6 +83,7 @@ private func makeDefaultDayPresentationTheme(accentColor: UIColor, serviceBackgr
|
||||
)
|
||||
|
||||
let intro = PresentationThemeIntro(
|
||||
statusBarStyle: .black,
|
||||
startButtonColor: UIColor(rgb: 0x2ca5e0),
|
||||
dotColor: UIColor(rgb: 0xd9d9d9)
|
||||
)
|
||||
@ -294,8 +295,8 @@ private func makeDefaultDayPresentationTheme(accentColor: UIColor, serviceBackgr
|
||||
)
|
||||
|
||||
let historyNavigation = PresentationThemeChatHistoryNavigation(
|
||||
fillColor: .white,
|
||||
strokeColor: UIColor(rgb: 0x000000, alpha: 0.15),
|
||||
fillColor: UIColor(rgb: 0xf7f7f7),
|
||||
strokeColor: UIColor(rgb: 0xb1b1b1),
|
||||
foregroundColor: UIColor(rgb: 0x88888d),
|
||||
badgeBackgroundColor: accentColor,
|
||||
badgeStrokeColor: accentColor,
|
||||
|
@ -254,7 +254,7 @@ public func currentPresentationDataAndSettings(accountManager: AccountManager) -
|
||||
|
||||
let parameters = AutomaticThemeSwitchParameters(settings: themeSettings.automaticThemeSwitchSetting)
|
||||
if automaticThemeShouldSwitchNow(parameters, currentTheme: themeSettings.theme) {
|
||||
effectiveTheme = .builtin(themeSettings.automaticThemeSwitchSetting.theme)
|
||||
effectiveTheme = themeSettings.automaticThemeSwitchSetting.theme
|
||||
} else {
|
||||
effectiveTheme = themeSettings.theme
|
||||
}
|
||||
@ -301,7 +301,7 @@ private enum PreparedAutomaticThemeSwitchTrigger {
|
||||
|
||||
private struct AutomaticThemeSwitchParameters {
|
||||
let trigger: PreparedAutomaticThemeSwitchTrigger
|
||||
let theme: PresentationBuiltinThemeReference
|
||||
let theme: PresentationThemeReference
|
||||
|
||||
init(settings: AutomaticThemeSwitchSetting) {
|
||||
let trigger: PreparedAutomaticThemeSwitchTrigger
|
||||
@ -330,17 +330,6 @@ private struct AutomaticThemeSwitchParameters {
|
||||
}
|
||||
|
||||
private func automaticThemeShouldSwitchNow(_ parameters: AutomaticThemeSwitchParameters, currentTheme: PresentationThemeReference) -> Bool {
|
||||
switch currentTheme {
|
||||
case let .builtin(builtin):
|
||||
switch builtin {
|
||||
case .nightAccent, .night:
|
||||
return false
|
||||
default:
|
||||
break
|
||||
}
|
||||
default:
|
||||
return false
|
||||
}
|
||||
switch parameters.trigger {
|
||||
case .none:
|
||||
return false
|
||||
@ -514,7 +503,7 @@ public func updatedPresentationData(accountManager: AccountManager, applicationI
|
||||
var effectiveChatWallpaper: TelegramWallpaper = currentWallpaper
|
||||
|
||||
if shouldSwitch {
|
||||
let automaticTheme: PresentationThemeReference = .builtin(themeSettings.automaticThemeSwitchSetting.theme)
|
||||
let automaticTheme = themeSettings.automaticThemeSwitchSetting.theme
|
||||
if let themeSpecificWallpaper = themeSettings.themeSpecificChatWallpapers[automaticTheme.index] {
|
||||
effectiveChatWallpaper = themeSpecificWallpaper
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -19,10 +19,12 @@ public final class PresentationThemeGradientColors {
|
||||
}
|
||||
|
||||
public final class PresentationThemeIntro {
|
||||
public let statusBarStyle: PresentationThemeStatusBarStyle
|
||||
public let startButtonColor: UIColor
|
||||
public let dotColor: UIColor
|
||||
|
||||
public init(startButtonColor: UIColor, dotColor: UIColor) {
|
||||
public init(statusBarStyle: PresentationThemeStatusBarStyle, startButtonColor: UIColor, dotColor: UIColor) {
|
||||
self.statusBarStyle = statusBarStyle
|
||||
self.startButtonColor = startButtonColor
|
||||
self.dotColor = dotColor
|
||||
}
|
||||
|
@ -237,18 +237,21 @@ extension PresentationThemeGradientColors: Codable {
|
||||
|
||||
extension PresentationThemeIntro: Codable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case statusBar
|
||||
case startButton
|
||||
case dot
|
||||
}
|
||||
|
||||
public convenience init(from decoder: Decoder) throws {
|
||||
let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.init(startButtonColor: try decodeColor(values, .startButton),
|
||||
self.init(statusBarStyle: try values.decode(PresentationThemeStatusBarStyle.self, forKey: .statusBar),
|
||||
startButtonColor: try decodeColor(values, .startButton),
|
||||
dotColor: try decodeColor(values, .dot))
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var values = encoder.container(keyedBy: CodingKeys.self)
|
||||
try values.encode(self.statusBarStyle, forKey: .statusBar)
|
||||
try encodeColor(&values, self.startButtonColor, .startButton)
|
||||
try encodeColor(&values, self.dotColor, .dot)
|
||||
}
|
||||
|
@ -2,18 +2,36 @@ import Foundation
|
||||
|
||||
public enum ArabicNumeralStringType {
|
||||
case western
|
||||
case eastern
|
||||
case arabic
|
||||
case persian
|
||||
}
|
||||
|
||||
public func normalizeArabicNumeralString(_ string: String, type: ArabicNumeralStringType) -> String {
|
||||
var string = string
|
||||
let numerals = ["٠": "0", "١": "1", "٢": "2", "٣": "3", "٤": "4", "٥": "5", "٦": "6", "٧": "7", "٨": "8", "٩": "9"]
|
||||
for (easternNumeral, westernNumeral) in numerals {
|
||||
|
||||
let numerals = [
|
||||
("0", "٠", "۰"),
|
||||
("1", "١", "۱"),
|
||||
("2", "٢", "۲"),
|
||||
("3", "٣", "۳"),
|
||||
("4", "٤", "۴"),
|
||||
("5", "٥", "۵"),
|
||||
("6", "٦", "۶"),
|
||||
("7", "٧", "۷"),
|
||||
("8", "٨", "۸"),
|
||||
("9", "٩", "۹"),
|
||||
]
|
||||
for (western, arabic, persian) in numerals {
|
||||
switch type {
|
||||
case .western:
|
||||
string = string.replacingOccurrences(of: easternNumeral, with: westernNumeral)
|
||||
case .eastern:
|
||||
string = string.replacingOccurrences(of: westernNumeral, with: easternNumeral)
|
||||
string = string.replacingOccurrences(of: arabic, with: western)
|
||||
string = string.replacingOccurrences(of: persian, with: western)
|
||||
case .arabic:
|
||||
string = string.replacingOccurrences(of: western, with: arabic)
|
||||
string = string.replacingOccurrences(of: persian, with: arabic)
|
||||
case .persian:
|
||||
string = string.replacingOccurrences(of: western, with: persian)
|
||||
string = string.replacingOccurrences(of: arabic, with: persian)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -114,7 +114,7 @@ public func stringForUserPresence(strings: PresentationStrings, day: RelativeTim
|
||||
let dayString: String
|
||||
switch day {
|
||||
case .today:
|
||||
dayString = strings.LastSeen_AtDate(strings.Time_TodayAt(stringForShortTimestamp(hours: hours, minutes: minutes, dateTimeFormat: dateTimeFormat)).0).0
|
||||
dayString = strings.LastSeen_TodayAt(stringForShortTimestamp(hours: hours, minutes: minutes, dateTimeFormat: dateTimeFormat)).0
|
||||
case .yesterday:
|
||||
dayString = strings.LastSeen_YesterdayAt(stringForShortTimestamp(hours: hours, minutes: minutes, dateTimeFormat: dateTimeFormat)).0
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ final class AuthorizationSequenceAwaitingAccountResetController: ViewController
|
||||
|
||||
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||
|
||||
self.statusBar.statusBarStyle = theme.rootController.statusBarStyle.style
|
||||
self.statusBar.statusBarStyle = theme.intro.statusBarStyle.style
|
||||
|
||||
self.attemptNavigation = { _ in
|
||||
return false
|
||||
|
@ -47,7 +47,7 @@ final class AuthorizationSequenceCodeEntryController: ViewController {
|
||||
|
||||
self.hasActiveInput = true
|
||||
|
||||
self.statusBar.statusBarStyle = theme.rootController.statusBarStyle.style
|
||||
self.statusBar.statusBarStyle = theme.intro.statusBarStyle.style
|
||||
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.strings.Common_Next, style: .done, target: self, action: #selector(self.nextPressed))
|
||||
|
||||
|
@ -54,7 +54,7 @@ final class AuthorizationSequencePasswordEntryController: ViewController {
|
||||
|
||||
self.hasActiveInput = true
|
||||
|
||||
self.statusBar.statusBarStyle = theme.rootController.statusBarStyle.style
|
||||
self.statusBar.statusBarStyle = theme.intro.statusBarStyle.style
|
||||
|
||||
self.attemptNavigation = { _ in
|
||||
return false
|
||||
|
@ -42,7 +42,7 @@ final class AuthorizationSequencePasswordRecoveryController: ViewController {
|
||||
|
||||
self.hasActiveInput = true
|
||||
|
||||
self.statusBar.statusBarStyle = theme.rootController.statusBarStyle.style
|
||||
self.statusBar.statusBarStyle = theme.intro.statusBarStyle.style
|
||||
|
||||
self.attemptNavigation = { _ in
|
||||
return false
|
||||
|
@ -61,7 +61,7 @@ final class AuthorizationSequencePhoneEntryController: ViewController {
|
||||
|
||||
self.hasActiveInput = true
|
||||
|
||||
self.statusBar.statusBarStyle = theme.rootController.statusBarStyle.style
|
||||
self.statusBar.statusBarStyle = theme.intro.statusBarStyle.style
|
||||
self.attemptNavigation = { _ in
|
||||
return false
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ final class AuthorizationSequenceSignUpController: ViewController {
|
||||
|
||||
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||
|
||||
self.statusBar.statusBarStyle = self.theme.rootController.statusBarStyle.style
|
||||
self.statusBar.statusBarStyle = theme.intro.statusBarStyle.style
|
||||
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.strings.Common_Next, style: .done, target: self, action: #selector(self.nextPressed))
|
||||
|
||||
|
@ -74,7 +74,7 @@ final class AuthorizationSequenceSplashController: ViewController {
|
||||
|
||||
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||
|
||||
self.statusBar.statusBarStyle = theme.rootController.statusBarStyle.style
|
||||
self.statusBar.statusBarStyle = theme.intro.statusBarStyle.style
|
||||
|
||||
self.controller.startMessaging = { [weak self] in
|
||||
self?.activateLocalization("en")
|
||||
|
@ -526,6 +526,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
if let messages = strongSelf.chatDisplayNode.historyNode.messageGroupInCurrentHistoryView(message.id) {
|
||||
(strongSelf.view.window as? WindowHost)?.cancelInteractiveKeyboardGestures()
|
||||
strongSelf.chatDisplayNode.cancelInteractiveKeyboardGestures()
|
||||
var updatedMessages = messages
|
||||
for i in 0 ..< updatedMessages.count {
|
||||
if updatedMessages[i].id == message.id {
|
||||
@ -1553,10 +1554,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|
||||
let controller = ChatScheduleTimeController(context: strongSelf.context, mode: mode, minimalTime: strongSelf.presentationInterfaceState.slowmodeState?.timeout, completion: { [weak self] scheduleTime in
|
||||
if let strongSelf = self {
|
||||
strongSelf.chatDisplayNode.sendCurrentMessage(scheduleTime: scheduleTime)
|
||||
if !strongSelf.presentationInterfaceState.isScheduledMessages {
|
||||
strongSelf.openScheduledMessages()
|
||||
}
|
||||
strongSelf.chatDisplayNode.sendCurrentMessage(scheduleTime: scheduleTime, completion: { [weak self] in
|
||||
if let strongSelf = self, !strongSelf.presentationInterfaceState.isScheduledMessages {
|
||||
strongSelf.openScheduledMessages()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
strongSelf.chatDisplayNode.dismissInput()
|
||||
|
@ -2057,7 +2057,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func sendCurrentMessage(silentPosting: Bool? = nil, scheduleTime: Int32? = nil) {
|
||||
func sendCurrentMessage(silentPosting: Bool? = nil, scheduleTime: Int32? = nil, completion: @escaping () -> Void = {}) {
|
||||
if let textInputPanelNode = self.inputPanelNode as? ChatTextInputPanelNode {
|
||||
if textInputPanelNode.textInputNode?.isFirstResponder() ?? false {
|
||||
Keyboard.applyAutocorrection()
|
||||
@ -2114,6 +2114,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
textInputPanelNode.text = ""
|
||||
strongSelf.requestUpdateChatInterfaceState(false, true, { $0.withUpdatedReplyMessageId(nil).withUpdatedForwardMessageIds(nil).withUpdatedComposeDisableUrlPreview(nil) })
|
||||
strongSelf.ignoreUpdateHeight = false
|
||||
completion()
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -145,8 +145,8 @@ final class ChatInfoTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
let panelHeight: CGFloat = 55.0
|
||||
|
||||
if themeUpdated {
|
||||
self.separatorNode.backgroundColor = interfaceState.theme.rootController.navigationBar.separatorColor
|
||||
self.backgroundColor = interfaceState.theme.rootController.navigationBar.backgroundColor
|
||||
self.backgroundColor = interfaceState.theme.chat.historyNavigation.fillColor
|
||||
self.separatorNode.backgroundColor = interfaceState.theme.chat.historyNavigation.strokeColor
|
||||
}
|
||||
|
||||
let updatedButtons: [ChatInfoTitleButton]
|
||||
@ -157,8 +157,6 @@ final class ChatInfoTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
} else {
|
||||
updatedButtons = []
|
||||
}
|
||||
/*case .group:
|
||||
updatedButtons = groupButtons()*/
|
||||
}
|
||||
|
||||
var buttonsUpdated = false
|
||||
@ -182,7 +180,7 @@ final class ChatInfoTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
let buttonNode = ChatInfoTitlePanelButtonNode()
|
||||
buttonNode.laysOutHorizontally = false
|
||||
|
||||
buttonNode.setup(text: button.title(interfaceState.strings), color: interfaceState.theme.rootController.navigationBar.accentTextColor, icon: button.icon(interfaceState.theme))
|
||||
buttonNode.setup(text: button.title(interfaceState.strings), color: interfaceState.theme.chat.inputPanel.panelControlAccentColor, icon: button.icon(interfaceState.theme))
|
||||
|
||||
buttonNode.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: [.touchUpInside])
|
||||
self.addSubnode(buttonNode)
|
||||
|
@ -373,6 +373,24 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
|
||||
})))
|
||||
}
|
||||
|
||||
if data.messageActions.options.contains(.sendScheduledNow) {
|
||||
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.ScheduledMessages_SendNow, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { _, f in
|
||||
controllerInteraction.sendScheduledMessagesNow(selectAll ? messages.map { $0.id } : [message.id])
|
||||
f(.dismissWithoutContent)
|
||||
})))
|
||||
}
|
||||
|
||||
if data.messageActions.options.contains(.editScheduledTime) {
|
||||
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.ScheduledMessages_EditTime, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Schedule"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { _, f in
|
||||
controllerInteraction.editScheduledMessagesTime(selectAll ? messages.map { $0.id } : [message.id])
|
||||
f(.dismissWithoutContent)
|
||||
})))
|
||||
}
|
||||
|
||||
let resourceAvailable: Bool
|
||||
if let resourceStatus = data.resourceStatus, case .Local = resourceStatus {
|
||||
resourceAvailable = true
|
||||
@ -425,24 +443,6 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
|
||||
})))
|
||||
}
|
||||
|
||||
if data.messageActions.options.contains(.sendScheduledNow) {
|
||||
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.ScheduledMessages_SendNow, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { _, f in
|
||||
controllerInteraction.sendScheduledMessagesNow(selectAll ? messages.map { $0.id } : [message.id])
|
||||
f(.dismissWithoutContent)
|
||||
})))
|
||||
}
|
||||
|
||||
if data.messageActions.options.contains(.editScheduledTime) {
|
||||
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.ScheduledMessages_EditTime, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Schedule"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { _, f in
|
||||
controllerInteraction.editScheduledMessagesTime(selectAll ? messages.map { $0.id } : [message.id])
|
||||
f(.dismissWithoutContent)
|
||||
})))
|
||||
}
|
||||
|
||||
if data.canEdit {
|
||||
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_MessageDialogEdit, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.actionSheet.primaryTextColor)
|
||||
@ -738,8 +738,14 @@ func chatAvailableMessageActionsImpl(postbox: Postbox, accountPeerId: PeerId, me
|
||||
}
|
||||
if id.namespace == Namespaces.Message.ScheduledCloud {
|
||||
optionsMap[id]!.insert(.sendScheduledNow)
|
||||
optionsMap[id]!.insert(.editScheduledTime)
|
||||
optionsMap[id]!.insert(.deleteLocally)
|
||||
if let peer = transaction.getPeer(id.peerId), let channel = peer as? TelegramChannel, !channel.hasPermission(.editAllMessages) {
|
||||
} else {
|
||||
optionsMap[id]!.insert(.editScheduledTime)
|
||||
}
|
||||
if let peer = transaction.getPeer(id.peerId), let channel = peer as? TelegramChannel, !channel.hasPermission(.deleteAllMessages) {
|
||||
} else {
|
||||
optionsMap[id]!.insert(.deleteLocally)
|
||||
}
|
||||
} else if id.peerId == accountPeerId {
|
||||
if !(message.flags.isSending || message.flags.contains(.Failed)) {
|
||||
optionsMap[id]!.insert(.forward)
|
||||
|
@ -190,6 +190,15 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
}
|
||||
return .waitForSingleTap
|
||||
}
|
||||
recognizer.longTap = { [weak self] point, recognizer in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
//strongSelf.reactionRecognizer?.cancel()
|
||||
if strongSelf.gestureRecognized(gesture: .longTap, location: point, recognizer: recognizer) {
|
||||
recognizer.cancel()
|
||||
}
|
||||
}
|
||||
self.view.addGestureRecognizer(recognizer)
|
||||
|
||||
let replyRecognizer = ChatSwipeToReplyRecognizer(target: self, action: #selector(self.swipeToReplyGesture(_:)))
|
||||
@ -736,129 +745,135 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
switch recognizer.state {
|
||||
case .ended:
|
||||
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
|
||||
switch gesture {
|
||||
case .tap:
|
||||
if let avatarNode = self.accessoryItemNode as? ChatMessageAvatarAccessoryItemNode, avatarNode.frame.contains(location) {
|
||||
if let item = self.item, let author = item.content.firstMessage.author {
|
||||
var openPeerId = item.effectiveAuthorId ?? author.id
|
||||
var navigate: ChatControllerInteractionNavigateToPeer
|
||||
|
||||
if item.content.firstMessage.id.peerId == item.context.account.peerId {
|
||||
navigate = .chat(textInputState: nil, subject: nil)
|
||||
} else {
|
||||
navigate = .info
|
||||
}
|
||||
|
||||
for attribute in item.content.firstMessage.attributes {
|
||||
if let attribute = attribute as? SourceReferenceMessageAttribute {
|
||||
openPeerId = attribute.messageId.peerId
|
||||
navigate = .chat(textInputState: nil, subject: .message(attribute.messageId))
|
||||
}
|
||||
}
|
||||
|
||||
if item.effectiveAuthorId?.namespace == Namespaces.Peer.Empty {
|
||||
item.controllerInteraction.displayMessageTooltip(item.content.firstMessage.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, self, avatarNode.frame)
|
||||
} else {
|
||||
if let channel = item.content.firstMessage.forwardInfo?.author as? TelegramChannel, channel.username == nil {
|
||||
if case .member = channel.participationStatus {
|
||||
} else {
|
||||
item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, self, avatarNode.frame)
|
||||
return
|
||||
}
|
||||
}
|
||||
item.controllerInteraction.openPeer(openPeerId, navigate, item.message)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if let viaBotNode = self.viaBotNode, viaBotNode.frame.contains(location) {
|
||||
if let item = self.item {
|
||||
for attribute in item.message.attributes {
|
||||
if let attribute = attribute as? InlineBotMessageAttribute {
|
||||
var botAddressName: String?
|
||||
if let peerId = attribute.peerId, let botPeer = item.message.peers[peerId], let addressName = botPeer.addressName {
|
||||
botAddressName = addressName
|
||||
} else {
|
||||
botAddressName = attribute.title
|
||||
}
|
||||
|
||||
if let botAddressName = botAddressName {
|
||||
item.controllerInteraction.updateInputState { textInputState in
|
||||
return ChatTextInputState(inputText: NSAttributedString(string: "@" + botAddressName + " "))
|
||||
}
|
||||
item.controllerInteraction.updateInputMode { _ in
|
||||
return .text
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let replyInfoNode = self.replyInfoNode, replyInfoNode.frame.contains(location) {
|
||||
if let item = self.item {
|
||||
for attribute in item.message.attributes {
|
||||
if let attribute = attribute as? ReplyMessageAttribute {
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let item = self.item, self.imageNode.frame.contains(location) {
|
||||
if self.telegramFile != nil {
|
||||
let _ = item.controllerInteraction.openMessage(item.message, .default)
|
||||
} else if let _ = self.emojiFile {
|
||||
var startTime: Signal<Double, NoError>
|
||||
if self.animationNode.playIfNeeded() {
|
||||
startTime = .single(0.0)
|
||||
} else {
|
||||
startTime = self.animationNode.status
|
||||
|> map { $0.timestamp }
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue
|
||||
}
|
||||
|
||||
if let text = self.item?.message.text, let firstScalar = text.unicodeScalars.first, firstScalar.value == 0x2764 {
|
||||
let _ = startTime.start(next: { [weak self] time in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let heartbeatHaptic: ChatMessageHeartbeatHaptic
|
||||
if let current = strongSelf.heartbeatHaptic {
|
||||
heartbeatHaptic = current
|
||||
} else {
|
||||
heartbeatHaptic = ChatMessageHeartbeatHaptic()
|
||||
heartbeatHaptic.enabled = true
|
||||
strongSelf.heartbeatHaptic = heartbeatHaptic
|
||||
}
|
||||
if !heartbeatHaptic.active {
|
||||
heartbeatHaptic.start(time: time)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
self.item?.controllerInteraction.clickThroughMessage()
|
||||
case .longTap, .doubleTap:
|
||||
if let item = self.item, self.imageNode.frame.contains(location) {
|
||||
item.controllerInteraction.openMessageContextMenu(item.message, false, self, self.imageNode.frame, nil)
|
||||
}
|
||||
case .hold:
|
||||
break
|
||||
}
|
||||
let _ = self.gestureRecognized(gesture: gesture, location: location, recognizer: nil)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
private func gestureRecognized(gesture: TapLongTapOrDoubleTapGesture, location: CGPoint, recognizer: TapLongTapOrDoubleTapGestureRecognizer?) -> Bool {
|
||||
switch gesture {
|
||||
case .tap:
|
||||
if let avatarNode = self.accessoryItemNode as? ChatMessageAvatarAccessoryItemNode, avatarNode.frame.contains(location) {
|
||||
if let item = self.item, let author = item.content.firstMessage.author {
|
||||
var openPeerId = item.effectiveAuthorId ?? author.id
|
||||
var navigate: ChatControllerInteractionNavigateToPeer
|
||||
|
||||
if item.content.firstMessage.id.peerId == item.context.account.peerId {
|
||||
navigate = .chat(textInputState: nil, subject: nil)
|
||||
} else {
|
||||
navigate = .info
|
||||
}
|
||||
|
||||
for attribute in item.content.firstMessage.attributes {
|
||||
if let attribute = attribute as? SourceReferenceMessageAttribute {
|
||||
openPeerId = attribute.messageId.peerId
|
||||
navigate = .chat(textInputState: nil, subject: .message(attribute.messageId))
|
||||
}
|
||||
}
|
||||
|
||||
if item.effectiveAuthorId?.namespace == Namespaces.Peer.Empty {
|
||||
item.controllerInteraction.displayMessageTooltip(item.content.firstMessage.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, self, avatarNode.frame)
|
||||
} else {
|
||||
if let channel = item.content.firstMessage.forwardInfo?.author as? TelegramChannel, channel.username == nil {
|
||||
if case .member = channel.participationStatus {
|
||||
} else {
|
||||
item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, self, avatarNode.frame)
|
||||
return true
|
||||
}
|
||||
}
|
||||
item.controllerInteraction.openPeer(openPeerId, navigate, item.message)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if let viaBotNode = self.viaBotNode, viaBotNode.frame.contains(location) {
|
||||
if let item = self.item {
|
||||
for attribute in item.message.attributes {
|
||||
if let attribute = attribute as? InlineBotMessageAttribute {
|
||||
var botAddressName: String?
|
||||
if let peerId = attribute.peerId, let botPeer = item.message.peers[peerId], let addressName = botPeer.addressName {
|
||||
botAddressName = addressName
|
||||
} else {
|
||||
botAddressName = attribute.title
|
||||
}
|
||||
|
||||
if let botAddressName = botAddressName {
|
||||
item.controllerInteraction.updateInputState { textInputState in
|
||||
return ChatTextInputState(inputText: NSAttributedString(string: "@" + botAddressName + " "))
|
||||
}
|
||||
item.controllerInteraction.updateInputMode { _ in
|
||||
return .text
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let replyInfoNode = self.replyInfoNode, replyInfoNode.frame.contains(location) {
|
||||
if let item = self.item {
|
||||
for attribute in item.message.attributes {
|
||||
if let attribute = attribute as? ReplyMessageAttribute {
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let item = self.item, self.imageNode.frame.contains(location) {
|
||||
if self.telegramFile != nil {
|
||||
let _ = item.controllerInteraction.openMessage(item.message, .default)
|
||||
} else if let _ = self.emojiFile {
|
||||
var startTime: Signal<Double, NoError>
|
||||
if self.animationNode.playIfNeeded() {
|
||||
startTime = .single(0.0)
|
||||
} else {
|
||||
startTime = self.animationNode.status
|
||||
|> map { $0.timestamp }
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue
|
||||
}
|
||||
|
||||
if let text = self.item?.message.text, let firstScalar = text.unicodeScalars.first, firstScalar.value == 0x2764 {
|
||||
let _ = startTime.start(next: { [weak self] time in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let heartbeatHaptic: ChatMessageHeartbeatHaptic
|
||||
if let current = strongSelf.heartbeatHaptic {
|
||||
heartbeatHaptic = current
|
||||
} else {
|
||||
heartbeatHaptic = ChatMessageHeartbeatHaptic()
|
||||
heartbeatHaptic.enabled = true
|
||||
strongSelf.heartbeatHaptic = heartbeatHaptic
|
||||
}
|
||||
if !heartbeatHaptic.active {
|
||||
heartbeatHaptic.start(time: time)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
self.item?.controllerInteraction.clickThroughMessage()
|
||||
case .longTap, .doubleTap:
|
||||
if let item = self.item, self.imageNode.frame.contains(location) {
|
||||
item.controllerInteraction.openMessageContextMenu(item.message, false, self, self.imageNode.frame, nil)
|
||||
return false
|
||||
}
|
||||
case .hold:
|
||||
break
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@objc func shareButtonPressed() {
|
||||
if let item = self.item {
|
||||
if item.content.firstMessage.id.peerId == item.context.account.peerId {
|
||||
|
@ -402,8 +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 = 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)
|
||||
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData.theme.theme, presentationData.strings, presentationData.dateTimeFormat, message, file, .full, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
|
||||
initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right
|
||||
refineContentImageLayout = refineLayout
|
||||
} else if file.isInstantVideo {
|
||||
@ -521,7 +520,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
||||
|
||||
|
||||
if let count = webpageGalleryMediaCount {
|
||||
additionalImageBadgeContent = .text(inset: 0.0, backgroundColor: presentationData.theme.theme.chat.message.mediaDateAndStatusFillColor, foregroundColor: presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor, text: NSAttributedString(string: "1 \(presentationData.strings.Common_of) \(count)"))
|
||||
additionalImageBadgeContent = .text(inset: 0.0, backgroundColor: presentationData.theme.theme.chat.message.mediaDateAndStatusFillColor, foregroundColor: presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor, text: NSAttributedString(string: presentationData.strings.Items_NOfM("1", "\(count)").0))
|
||||
skipStandardStatus = imageMode
|
||||
} else if let mediaBadge = mediaBadge {
|
||||
additionalImageBadgeContent = .text(inset: 0.0, backgroundColor: presentationData.theme.theme.chat.message.mediaDateAndStatusFillColor, foregroundColor: presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor, text: NSAttributedString(string: mediaBadge))
|
||||
|
@ -832,7 +832,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
||||
}
|
||||
strongSelf.fetchDisposable.set(visibilityAwareFetchSignal.start())
|
||||
}
|
||||
} else if case .prefetch = automaticDownload, message.id.namespace != Namespaces.Message.SecretIncoming {
|
||||
} else if case .prefetch = automaticDownload, message.id.namespace != Namespaces.Message.SecretIncoming && message.id.namespace != Namespaces.Message.Local {
|
||||
if let file = media as? TelegramMediaFile {
|
||||
let fetchSignal = preloadVideoResource(postbox: context.account.postbox, resourceReference: AnyMediaReference.message(message: MessageReference(message), media: file).resourceReference(file.resource), duration: 4.0)
|
||||
let visibilityAwareFetchSignal = strongSelf.visibilityPromise.get()
|
||||
|
@ -80,6 +80,15 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
}
|
||||
return .waitForSingleTap
|
||||
}
|
||||
recognizer.longTap = { [weak self] point, recognizer in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
//strongSelf.reactionRecognizer?.cancel()
|
||||
if strongSelf.gestureRecognized(gesture: .longTap, location: point, recognizer: recognizer) {
|
||||
recognizer.cancel()
|
||||
}
|
||||
}
|
||||
self.view.addGestureRecognizer(recognizer)
|
||||
|
||||
let replyRecognizer = ChatSwipeToReplyRecognizer(target: self, action: #selector(self.swipeToReplyGesture(_:)))
|
||||
@ -577,97 +586,104 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
|
||||
@objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
|
||||
switch recognizer.state {
|
||||
case .ended:
|
||||
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
|
||||
switch gesture {
|
||||
case .tap:
|
||||
if let avatarNode = self.accessoryItemNode as? ChatMessageAvatarAccessoryItemNode, avatarNode.frame.contains(location) {
|
||||
if let item = self.item, let author = item.content.firstMessage.author {
|
||||
var openPeerId = item.effectiveAuthorId ?? author.id
|
||||
var navigate: ChatControllerInteractionNavigateToPeer
|
||||
|
||||
if item.content.firstMessage.id.peerId == item.context.account.peerId {
|
||||
navigate = .chat(textInputState: nil, subject: nil)
|
||||
} else {
|
||||
navigate = .info
|
||||
}
|
||||
|
||||
for attribute in item.content.firstMessage.attributes {
|
||||
if let attribute = attribute as? SourceReferenceMessageAttribute {
|
||||
openPeerId = attribute.messageId.peerId
|
||||
navigate = .chat(textInputState: nil, subject: .message(attribute.messageId))
|
||||
}
|
||||
}
|
||||
|
||||
if item.effectiveAuthorId?.namespace == Namespaces.Peer.Empty {
|
||||
item.controllerInteraction.displayMessageTooltip(item.content.firstMessage.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, self, avatarNode.frame)
|
||||
} else {
|
||||
if let channel = item.content.firstMessage.forwardInfo?.author as? TelegramChannel, channel.username == nil {
|
||||
if case .member = channel.participationStatus {
|
||||
} else {
|
||||
item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, self, avatarNode.frame)
|
||||
return
|
||||
}
|
||||
}
|
||||
item.controllerInteraction.openPeer(openPeerId, navigate, item.message)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if let viaBotNode = self.viaBotNode, viaBotNode.frame.contains(location) {
|
||||
if let item = self.item {
|
||||
for attribute in item.message.attributes {
|
||||
if let attribute = attribute as? InlineBotMessageAttribute {
|
||||
var botAddressName: String?
|
||||
if let peerId = attribute.peerId, let botPeer = item.message.peers[peerId], let addressName = botPeer.addressName {
|
||||
botAddressName = addressName
|
||||
} else {
|
||||
botAddressName = attribute.title
|
||||
}
|
||||
|
||||
if let botAddressName = botAddressName {
|
||||
item.controllerInteraction.updateInputState { textInputState in
|
||||
return ChatTextInputState(inputText: NSAttributedString(string: "@" + botAddressName + " "))
|
||||
}
|
||||
item.controllerInteraction.updateInputMode { _ in
|
||||
return .text
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let replyInfoNode = self.replyInfoNode, replyInfoNode.frame.contains(location) {
|
||||
if let item = self.item {
|
||||
for attribute in item.message.attributes {
|
||||
if let attribute = attribute as? ReplyMessageAttribute {
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let item = self.item, self.imageNode.frame.contains(location) {
|
||||
let _ = item.controllerInteraction.openMessage(item.message, .default)
|
||||
return
|
||||
}
|
||||
case .ended:
|
||||
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
|
||||
let _ = self.gestureRecognized(gesture: gesture, location: location, recognizer: nil)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
private func gestureRecognized(gesture: TapLongTapOrDoubleTapGesture, location: CGPoint, recognizer: TapLongTapOrDoubleTapGestureRecognizer?) -> Bool {
|
||||
switch gesture {
|
||||
case .tap:
|
||||
if let avatarNode = self.accessoryItemNode as? ChatMessageAvatarAccessoryItemNode, avatarNode.frame.contains(location) {
|
||||
if let item = self.item, let author = item.content.firstMessage.author {
|
||||
var openPeerId = item.effectiveAuthorId ?? author.id
|
||||
var navigate: ChatControllerInteractionNavigateToPeer
|
||||
|
||||
self.item?.controllerInteraction.clickThroughMessage()
|
||||
case .longTap, .doubleTap:
|
||||
if let item = self.item, self.imageNode.frame.contains(location) {
|
||||
item.controllerInteraction.openMessageContextMenu(item.message, false, self, self.imageNode.frame, nil)
|
||||
if item.content.firstMessage.id.peerId == item.context.account.peerId {
|
||||
navigate = .chat(textInputState: nil, subject: nil)
|
||||
} else {
|
||||
navigate = .info
|
||||
}
|
||||
|
||||
for attribute in item.content.firstMessage.attributes {
|
||||
if let attribute = attribute as? SourceReferenceMessageAttribute {
|
||||
openPeerId = attribute.messageId.peerId
|
||||
navigate = .chat(textInputState: nil, subject: .message(attribute.messageId))
|
||||
}
|
||||
case .hold:
|
||||
break
|
||||
}
|
||||
|
||||
if item.effectiveAuthorId?.namespace == Namespaces.Peer.Empty {
|
||||
item.controllerInteraction.displayMessageTooltip(item.content.firstMessage.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, self, avatarNode.frame)
|
||||
} else {
|
||||
if let channel = item.content.firstMessage.forwardInfo?.author as? TelegramChannel, channel.username == nil {
|
||||
if case .member = channel.participationStatus {
|
||||
} else {
|
||||
item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, self, avatarNode.frame)
|
||||
return true
|
||||
}
|
||||
}
|
||||
item.controllerInteraction.openPeer(openPeerId, navigate, item.message)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if let viaBotNode = self.viaBotNode, viaBotNode.frame.contains(location) {
|
||||
if let item = self.item {
|
||||
for attribute in item.message.attributes {
|
||||
if let attribute = attribute as? InlineBotMessageAttribute {
|
||||
var botAddressName: String?
|
||||
if let peerId = attribute.peerId, let botPeer = item.message.peers[peerId], let addressName = botPeer.addressName {
|
||||
botAddressName = addressName
|
||||
} else {
|
||||
botAddressName = attribute.title
|
||||
}
|
||||
|
||||
if let botAddressName = botAddressName {
|
||||
item.controllerInteraction.updateInputState { textInputState in
|
||||
return ChatTextInputState(inputText: NSAttributedString(string: "@" + botAddressName + " "))
|
||||
}
|
||||
item.controllerInteraction.updateInputMode { _ in
|
||||
return .text
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
|
||||
if let replyInfoNode = self.replyInfoNode, replyInfoNode.frame.contains(location) {
|
||||
if let item = self.item {
|
||||
for attribute in item.message.attributes {
|
||||
if let attribute = attribute as? ReplyMessageAttribute {
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let item = self.item, self.imageNode.frame.contains(location) {
|
||||
let _ = item.controllerInteraction.openMessage(item.message, .default)
|
||||
return true
|
||||
}
|
||||
|
||||
self.item?.controllerInteraction.clickThroughMessage()
|
||||
case .longTap, .doubleTap:
|
||||
if let item = self.item, self.imageNode.frame.contains(location) {
|
||||
item.controllerInteraction.openMessageContextMenu(item.message, false, self, self.imageNode.frame, recognizer)
|
||||
return false
|
||||
}
|
||||
case .hold:
|
||||
break
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@objc func shareButtonPressed() {
|
||||
|
@ -110,8 +110,8 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
self.theme = interfaceState.theme
|
||||
self.closeButton.setImage(PresentationResourcesChat.chatInputPanelCloseIconImage(interfaceState.theme), for: [])
|
||||
self.lineNode.image = PresentationResourcesChat.chatInputPanelVerticalSeparatorLineImage(interfaceState.theme)
|
||||
self.backgroundColor = interfaceState.theme.rootController.navigationBar.backgroundColor
|
||||
self.separatorNode.backgroundColor = interfaceState.theme.rootController.navigationBar.separatorColor
|
||||
self.backgroundColor = interfaceState.theme.chat.historyNavigation.fillColor
|
||||
self.separatorNode.backgroundColor = interfaceState.theme.chat.historyNavigation.strokeColor
|
||||
}
|
||||
|
||||
var messageUpdated = false
|
||||
|
@ -92,8 +92,8 @@ final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
self.theme = interfaceState.theme
|
||||
|
||||
self.closeButton.setImage(PresentationResourcesChat.chatInputPanelEncircledCloseIconImage(interfaceState.theme), for: [])
|
||||
self.backgroundColor = interfaceState.theme.rootController.navigationBar.backgroundColor
|
||||
self.separatorNode.backgroundColor = interfaceState.theme.rootController.navigationBar.separatorColor
|
||||
self.backgroundColor = interfaceState.theme.chat.historyNavigation.fillColor
|
||||
self.separatorNode.backgroundColor = interfaceState.theme.chat.historyNavigation.strokeColor
|
||||
}
|
||||
|
||||
let panelHeight: CGFloat = 40.0
|
||||
|
@ -34,8 +34,8 @@ final class ChatRequestInProgressTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
if interfaceState.theme !== self.theme {
|
||||
self.theme = interfaceState.theme
|
||||
|
||||
self.backgroundColor = interfaceState.theme.rootController.navigationBar.backgroundColor
|
||||
self.separatorNode.backgroundColor = interfaceState.theme.rootController.navigationBar.separatorColor
|
||||
self.backgroundColor = interfaceState.theme.chat.historyNavigation.fillColor
|
||||
self.separatorNode.backgroundColor = interfaceState.theme.chat.historyNavigation.strokeColor
|
||||
}
|
||||
|
||||
let panelHeight: CGFloat = 40.0
|
||||
|
@ -125,7 +125,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel
|
||||
let pickerView = UIDatePicker()
|
||||
pickerView.timeZone = TimeZone(secondsFromGMT: 0)
|
||||
pickerView.datePickerMode = .dateAndTime
|
||||
pickerView.locale = localeWithStrings(self.presentationData.strings)
|
||||
pickerView.locale = Locale.current
|
||||
pickerView.timeZone = TimeZone.current
|
||||
pickerView.minuteInterval = 1
|
||||
pickerView.setValue(self.presentationData.theme.actionSheet.primaryTextColor, forKey: "textColor")
|
||||
|
@ -128,7 +128,7 @@ final class ChatSearchInputPanelNode: ChatInputPanelNode {
|
||||
if let currentId = results.currentId, let index = results.messageIndices.firstIndex(where: { $0.id == currentId }) {
|
||||
let adjustedIndex = results.messageIndices.count - 1 - index
|
||||
resultIndex = index
|
||||
resultsText = NSAttributedString(string: "\(adjustedIndex + 1) \(interfaceState.strings.Common_of) \(displayTotalCount)", font: labelFont, textColor: interfaceState.theme.chat.inputPanel.primaryTextColor)
|
||||
resultsText = NSAttributedString(string: interfaceState.strings.Items_NOfM("\(adjustedIndex + 1)", "\(displayTotalCount)").0, font: labelFont, textColor: interfaceState.theme.chat.inputPanel.primaryTextColor)
|
||||
} else {
|
||||
resultsText = NSAttributedString(string: interfaceState.strings.Conversation_SearchNoResults, font: labelFont, textColor: interfaceState.theme.chat.inputPanel.primaryTextColor)
|
||||
}
|
||||
|
@ -384,9 +384,14 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
|
||||
self.messageBackgroundNode.layer.animateBounds(from: fromFrame, to: self.messageBackgroundNode.bounds, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
self.messageBackgroundNode.layer.animatePosition(from: CGPoint(x: (initialWidth - self.messageClipNode.bounds.width) / 2.0, y: delta), to: CGPoint(), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
|
||||
let textOffset = self.textInputNode.textView.contentSize.height - self.textInputNode.textView.contentOffset.y - self.textInputNode.textView.frame.height
|
||||
self.fromMessageTextNode.layer.animatePosition(from: CGPoint(x: 0.0, y: delta * 2.0 + textOffset), to: CGPoint(), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
self.toMessageTextNode.layer.animatePosition(from: CGPoint(x: 0.0, y: delta * 2.0 + textOffset), to: CGPoint(), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
|
||||
var textXOffset: CGFloat = 0.0
|
||||
let textYOffset = self.textInputNode.textView.contentSize.height - self.textInputNode.textView.contentOffset.y - self.textInputNode.textView.frame.height
|
||||
if self.textInputNode.textView.numberOfLines == 1 && self.textInputNode.isRTL {
|
||||
textXOffset = initialWidth - self.messageClipNode.bounds.width
|
||||
}
|
||||
self.fromMessageTextNode.layer.animatePosition(from: CGPoint(x: textXOffset, y: delta * 2.0 + textYOffset), to: CGPoint(), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
self.toMessageTextNode.layer.animatePosition(from: CGPoint(x: textXOffset, y: delta * 2.0 + textYOffset), to: CGPoint(), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
|
||||
let springDuration: Double = 0.42
|
||||
let springDamping: CGFloat = 104.0
|
||||
@ -480,9 +485,13 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
|
||||
self.messageBackgroundNode.layer.animateBounds(from: self.messageBackgroundNode.bounds, to: toFrame, duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||
self.messageBackgroundNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: (initialWidth - self.messageClipNode.bounds.width) / 2.0, y: delta), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
|
||||
|
||||
let textOffset = self.textInputNode.textView.contentSize.height - self.textInputNode.textView.contentOffset.y - self.textInputNode.textView.frame.height
|
||||
self.fromMessageTextNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: delta * 2.0 + textOffset), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
|
||||
self.toMessageTextNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: delta * 2.0 + textOffset), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
|
||||
var textXOffset: CGFloat = 0.0
|
||||
let textYOffset = self.textInputNode.textView.contentSize.height - self.textInputNode.textView.contentOffset.y - self.textInputNode.textView.frame.height
|
||||
if self.textInputNode.textView.numberOfLines == 1 && self.textInputNode.isRTL {
|
||||
textXOffset = initialWidth - self.messageClipNode.bounds.width
|
||||
}
|
||||
self.fromMessageTextNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: textXOffset, y: delta * 2.0 + textYOffset), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
|
||||
self.toMessageTextNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: textXOffset, y: delta * 2.0 + textYOffset), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
|
||||
} else {
|
||||
completedBubble = true
|
||||
}
|
||||
@ -560,9 +569,11 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
|
||||
messageFrame.size.width = ceil(layout.size.width - messageFrame.origin.x - sendButtonFrame.width - layout.safeInsets.left - layout.safeInsets.right + 8.0)
|
||||
}
|
||||
|
||||
var messageOriginDelta: CGFloat = 0.0
|
||||
if self.textInputNode.textView.numberOfLines == 1 || self.textInputNode.textView.attributedText.string.isEmpty {
|
||||
let textWidth = min(self.toMessageTextNode.textView.sizeThatFits(layout.size).width + 36.0, messageFrame.width)
|
||||
messageFrame.origin.x += messageFrame.width - textWidth
|
||||
messageOriginDelta = messageFrame.width - textWidth
|
||||
messageFrame.origin.x += messageOriginDelta
|
||||
messageFrame.size.width = textWidth
|
||||
}
|
||||
|
||||
@ -586,6 +597,11 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
|
||||
var textFrame = self.textFieldFrame
|
||||
textFrame.origin = CGPoint(x: 13.0, y: 6.0 - UIScreenPixel)
|
||||
textFrame.size.height = self.textInputNode.textView.contentSize.height
|
||||
|
||||
if self.textInputNode.isRTL {
|
||||
textFrame.origin.x -= messageOriginDelta
|
||||
}
|
||||
|
||||
self.fromMessageTextNode.frame = textFrame
|
||||
self.toMessageTextNode.frame = textFrame
|
||||
|
||||
|
@ -42,8 +42,8 @@ final class ChatToastAlertPanelNode: ChatTitleAccessoryPanelNode {
|
||||
let panelHeight: CGFloat = 40.0
|
||||
|
||||
self.textColor = interfaceState.theme.rootController.navigationBar.primaryTextColor
|
||||
self.backgroundColor = interfaceState.theme.rootController.navigationBar.backgroundColor
|
||||
self.separatorNode.backgroundColor = interfaceState.theme.rootController.navigationBar.separatorColor
|
||||
self.backgroundColor = interfaceState.theme.chat.historyNavigation.fillColor
|
||||
self.separatorNode.backgroundColor = interfaceState.theme.chat.historyNavigation.strokeColor
|
||||
|
||||
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelHeight - UIScreenPixel), size: CGSize(width: width, height: UIScreenPixel)))
|
||||
|
||||
|
@ -282,7 +282,32 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
||||
let controller = OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .loading(cancelled: nil))
|
||||
present(controller, nil)
|
||||
|
||||
let _ = (signal
|
||||
var cancelImpl: (() -> Void)?
|
||||
let progressSignal = Signal<Never, NoError> { subscriber in
|
||||
let controller = OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .loading(cancelled: {
|
||||
cancelImpl?()
|
||||
}))
|
||||
present(controller, nil)
|
||||
return ActionDisposable { [weak controller] in
|
||||
Queue.mainQueue().async() {
|
||||
controller?.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|> runOn(Queue.mainQueue())
|
||||
|> delay(0.35, queue: Queue.mainQueue())
|
||||
|
||||
let disposable = MetaDisposable()
|
||||
let progressDisposable = progressSignal.start()
|
||||
cancelImpl = {
|
||||
disposable.set(nil)
|
||||
}
|
||||
disposable.set((signal
|
||||
|> afterDisposed {
|
||||
Queue.mainQueue().async {
|
||||
progressDisposable.dispose()
|
||||
}
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak controller] dataAndTheme in
|
||||
controller?.dismiss()
|
||||
|
||||
@ -300,7 +325,7 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
||||
}
|
||||
present(textAlertController(context: context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||
controller?.dismiss()
|
||||
})
|
||||
}))
|
||||
dismissInput()
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
@ -48,18 +48,15 @@ public final class TelegramRootController: NavigationController {
|
||||
if presentationData.chatWallpaper != strongSelf.presentationData.chatWallpaper {
|
||||
let navigationDetailsBackgroundMode: NavigationEmptyDetailsBackgoundMode?
|
||||
switch presentationData.chatWallpaper {
|
||||
case .color:
|
||||
let image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/EmptyMasterDetailIcon"), color: presentationData.theme.chatList.messageTextColor.withAlphaComponent(0.2))
|
||||
navigationDetailsBackgroundMode = image != nil ? .image(image!) : nil
|
||||
default:
|
||||
let image = chatControllerBackgroundImage(theme: presentationData.theme, wallpaper: presentationData.chatWallpaper, mediaBox: strongSelf.context.account.postbox.mediaBox, knockoutMode: strongSelf.context.sharedContext.immediateExperimentalUISettings.knockoutWallpaper)
|
||||
navigationDetailsBackgroundMode = image != nil ? .wallpaper(image!) : nil
|
||||
case .color:
|
||||
let image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/EmptyMasterDetailIcon"), color: presentationData.theme.chatList.messageTextColor.withAlphaComponent(0.2))
|
||||
navigationDetailsBackgroundMode = image != nil ? .image(image!) : nil
|
||||
default:
|
||||
navigationDetailsBackgroundMode = chatControllerBackgroundImage(theme: presentationData.theme, wallpaper: presentationData.chatWallpaper, mediaBox: strongSelf.context.sharedContext.accountManager.mediaBox, knockoutMode: strongSelf.context.sharedContext.immediateExperimentalUISettings.knockoutWallpaper).flatMap(NavigationEmptyDetailsBackgoundMode.wallpaper)
|
||||
}
|
||||
strongSelf.updateBackgroundDetailsMode(navigationDetailsBackgroundMode, transition: .immediate)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
let previousTheme = strongSelf.presentationData.theme
|
||||
strongSelf.presentationData = presentationData
|
||||
if previousTheme !== presentationData.theme {
|
||||
|
@ -66,6 +66,10 @@ final class ThemeUpdateManagerImpl: ThemeUpdateManager {
|
||||
validIds.insert(themeSettings.theme.index)
|
||||
themes[themeSettings.theme.index] = (themeSettings.theme, false)
|
||||
}
|
||||
if case .cloud = themeSettings.automaticThemeSwitchSetting.theme, themeSettings.automaticThemeSwitchSetting.trigger != .none {
|
||||
validIds.insert(themeSettings.automaticThemeSwitchSetting.theme.index)
|
||||
themes[themeSettings.automaticThemeSwitchSetting.theme.index] = (themeSettings.automaticThemeSwitchSetting.theme, true)
|
||||
}
|
||||
|
||||
if previousIds != validIds {
|
||||
for id in validIds {
|
||||
@ -126,20 +130,25 @@ final class ThemeUpdateManagerImpl: ThemeUpdateManager {
|
||||
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
|
||||
}
|
||||
var chatWallpaper = current.chatWallpaper
|
||||
var automaticThemeSwitchSetting = current.automaticThemeSwitchSetting
|
||||
if isAutoNight {
|
||||
automaticThemeSwitchSetting.theme = updatedTheme
|
||||
} else {
|
||||
chatWallpaper = current.chatWallpaper
|
||||
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)
|
||||
return PresentationThemeSettings(chatWallpaper: chatWallpaper, theme: updatedTheme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
|
||||
})
|
||||
}).start()
|
||||
}
|
||||
|
@ -271,21 +271,27 @@ public enum AutomaticThemeSwitchTrigger: PostboxCoding, Equatable {
|
||||
|
||||
public struct AutomaticThemeSwitchSetting: PostboxCoding, Equatable {
|
||||
public var trigger: AutomaticThemeSwitchTrigger
|
||||
public var theme: PresentationBuiltinThemeReference
|
||||
public var theme: PresentationThemeReference
|
||||
|
||||
public init(trigger: AutomaticThemeSwitchTrigger, theme: PresentationBuiltinThemeReference) {
|
||||
public init(trigger: AutomaticThemeSwitchTrigger, theme: PresentationThemeReference) {
|
||||
self.trigger = trigger
|
||||
self.theme = theme
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.trigger = decoder.decodeObjectForKey("trigger", decoder: { AutomaticThemeSwitchTrigger(decoder: $0) }) as! AutomaticThemeSwitchTrigger
|
||||
self.theme = PresentationBuiltinThemeReference(rawValue: decoder.decodeInt32ForKey("theme", orElse: 0))!
|
||||
if let theme = decoder.decodeObjectForKey("theme_v2", decoder: { PresentationThemeReference(decoder: $0) }) as? PresentationThemeReference {
|
||||
self.theme = theme
|
||||
} else if let legacyValue = decoder.decodeOptionalInt32ForKey("theme") {
|
||||
self.theme = .builtin(PresentationBuiltinThemeReference(rawValue: legacyValue) ?? .nightAccent)
|
||||
} else {
|
||||
self.theme = .builtin(.nightAccent)
|
||||
}
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeObject(self.trigger, forKey: "trigger")
|
||||
encoder.encodeInt32(self.theme.rawValue, forKey: "theme")
|
||||
encoder.encodeObject(self.theme, forKey: "theme_v2")
|
||||
}
|
||||
}
|
||||
|
||||
@ -443,7 +449,7 @@ public struct PresentationThemeSettings: PreferencesEntry {
|
||||
}
|
||||
|
||||
public static var defaultSettings: PresentationThemeSettings {
|
||||
return PresentationThemeSettings(chatWallpaper: .builtin(WallpaperSettings()), theme: .builtin(.dayClassic), themeSpecificAccentColors: [:], themeSpecificChatWallpapers: [:], fontSize: .regular, automaticThemeSwitchSetting: AutomaticThemeSwitchSetting(trigger: .none, theme: .nightAccent), largeEmoji: true, disableAnimations: true)
|
||||
return PresentationThemeSettings(chatWallpaper: .builtin(WallpaperSettings()), theme: .builtin(.dayClassic), themeSpecificAccentColors: [:], themeSpecificChatWallpapers: [:], fontSize: .regular, automaticThemeSwitchSetting: AutomaticThemeSwitchSetting(trigger: .none, theme: .builtin(.nightAccent)), largeEmoji: true, disableAnimations: true)
|
||||
}
|
||||
|
||||
public init(chatWallpaper: TelegramWallpaper, theme: PresentationThemeReference, themeSpecificAccentColors: [Int64: PresentationThemeAccentColor], themeSpecificChatWallpapers: [Int64: TelegramWallpaper], fontSize: PresentationFontSize, automaticThemeSwitchSetting: AutomaticThemeSwitchSetting, largeEmoji: Bool, disableAnimations: Bool) {
|
||||
@ -499,7 +505,7 @@ public struct PresentationThemeSettings: PreferencesEntry {
|
||||
}
|
||||
|
||||
self.fontSize = PresentationFontSize(rawValue: decoder.decodeInt32ForKey("f", orElse: PresentationFontSize.regular.rawValue)) ?? .regular
|
||||
self.automaticThemeSwitchSetting = (decoder.decodeObjectForKey("automaticThemeSwitchSetting", decoder: { AutomaticThemeSwitchSetting(decoder: $0) }) as? AutomaticThemeSwitchSetting) ?? AutomaticThemeSwitchSetting(trigger: .none, theme: .nightAccent)
|
||||
self.automaticThemeSwitchSetting = (decoder.decodeObjectForKey("automaticThemeSwitchSetting", decoder: { AutomaticThemeSwitchSetting(decoder: $0) }) as? AutomaticThemeSwitchSetting) ?? AutomaticThemeSwitchSetting(trigger: .none, theme: .builtin(.nightAccent))
|
||||
self.largeEmoji = decoder.decodeBoolForKey("largeEmoji", orElse: true)
|
||||
self.disableAnimations = decoder.decodeBoolForKey("disableAnimations", orElse: true)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user