diff --git a/Telegram-iOS.xcodeproj/project.pbxproj b/Telegram-iOS.xcodeproj/project.pbxproj index 15b3dff1e7..94321e98e9 100644 --- a/Telegram-iOS.xcodeproj/project.pbxproj +++ b/Telegram-iOS.xcodeproj/project.pbxproj @@ -198,6 +198,7 @@ 09EBE2AF22B004EA00F670AB /* BlackIconIpad@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 09EBE2A322B004E900F670AB /* BlackIconIpad@2x.png */; }; 09EBE2B022B004EA00F670AB /* BlueFilledIconLargeIpad@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 09EBE2A422B004E900F670AB /* BlueFilledIconLargeIpad@2x.png */; }; 09EC0DE322C6339700E7185B /* IntentMessages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09EC0DE222C6339700E7185B /* IntentMessages.swift */; }; + 09EC5CDA22CBBF9600292E42 /* telegram_plane1@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 09EC5CD922CBBF9600292E42 /* telegram_plane1@2x.png */; }; 09FDAEE62140477F00BF856F /* MtProtoKitDynamic.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09FDAEE52140477F00BF856F /* MtProtoKitDynamic.framework */; }; D000CACF21FB6E380011B15D /* NotificationService.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = D000CAC821FB6E370011B15D /* NotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; D001D5AA1F878DA300DF975A /* PhoneCountries.txt in Resources */ = {isa = PBXBuildFile; fileRef = D001D5A91F878DA300DF975A /* PhoneCountries.txt */; }; @@ -316,7 +317,6 @@ D08DB0BC213F4D1D00F2ADBF /* start_arrow@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D050F2311E49DEDE00988324 /* start_arrow@2x.png */; }; D08DB0BD213F4D1D00F2ADBF /* start_arrow_ipad.png in Resources */ = {isa = PBXBuildFile; fileRef = D050F2321E49DEDE00988324 /* start_arrow_ipad.png */; }; D08DB0BE213F4D1D00F2ADBF /* start_arrow_ipad@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D050F2331E49DEDE00988324 /* start_arrow_ipad@2x.png */; }; - D08DB0BF213F4D1D00F2ADBF /* telegram_plane1@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D050F2341E49DEDE00988324 /* telegram_plane1@2x.png */; }; D08DB0C0213F4D1D00F2ADBF /* telegram_sphere@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D050F2351E49DEDE00988324 /* telegram_sphere@2x.png */; }; D096C2BE1CC3C021006D814E /* Display.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D096C2BD1CC3C021006D814E /* Display.framework */; }; D096C2BF1CC3C021006D814E /* Display.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D096C2BD1CC3C021006D814E /* Display.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -841,6 +841,7 @@ 09EBE2A322B004E900F670AB /* BlackIconIpad@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "BlackIconIpad@2x.png"; sourceTree = ""; }; 09EBE2A422B004E900F670AB /* BlueFilledIconLargeIpad@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "BlueFilledIconLargeIpad@2x.png"; sourceTree = ""; }; 09EC0DE222C6339700E7185B /* IntentMessages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentMessages.swift; sourceTree = ""; }; + 09EC5CD922CBBF9600292E42 /* telegram_plane1@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "telegram_plane1@2x.png"; sourceTree = ""; }; 09FDAEE52140477F00BF856F /* MtProtoKitDynamic.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MtProtoKitDynamic.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D000CAC021FB6E160011B15D /* NotificationService-HockeyApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "NotificationService-HockeyApp.entitlements"; sourceTree = ""; }; D000CAC121FB6E160011B15D /* NotificationService-Fork.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "NotificationService-Fork.entitlements"; sourceTree = ""; }; @@ -960,7 +961,6 @@ D050F2311E49DEDE00988324 /* start_arrow@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "start_arrow@2x.png"; sourceTree = ""; }; D050F2321E49DEDE00988324 /* start_arrow_ipad.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = start_arrow_ipad.png; sourceTree = ""; }; D050F2331E49DEDE00988324 /* start_arrow_ipad@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "start_arrow_ipad@2x.png"; sourceTree = ""; }; - D050F2341E49DEDE00988324 /* telegram_plane1@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "telegram_plane1@2x.png"; sourceTree = ""; }; D050F2351E49DEDE00988324 /* telegram_sphere@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "telegram_sphere@2x.png"; sourceTree = ""; }; D051DB0C215E5E2300F30F92 /* NotificationContent.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NotificationContent.entitlements; sourceTree = ""; }; D052974422B0073E004ABAF6 /* WhiteFilledIcon@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "WhiteFilledIcon@3x.png"; sourceTree = ""; }; @@ -2173,6 +2173,7 @@ D050F21B1E49DEDE00988324 /* intro */ = { isa = PBXGroup; children = ( + 09EC5CD922CBBF9600292E42 /* telegram_plane1@2x.png */, D050F21C1E49DEDE00988324 /* fast_arrow@2x.png */, D050F21D1E49DEDE00988324 /* fast_arrow_shadow@2x.png */, D050F21E1E49DEDE00988324 /* fast_body@2x.png */, @@ -2197,7 +2198,6 @@ D050F2311E49DEDE00988324 /* start_arrow@2x.png */, D050F2321E49DEDE00988324 /* start_arrow_ipad.png */, D050F2331E49DEDE00988324 /* start_arrow_ipad@2x.png */, - D050F2341E49DEDE00988324 /* telegram_plane1@2x.png */, D050F2351E49DEDE00988324 /* telegram_sphere@2x.png */, ); name = intro; @@ -2750,6 +2750,7 @@ D0CE6F66213EDA4400BCD44B /* AppIntentVocabulary.plist in Resources */, D08DB0B8213F4D1D00F2ADBF /* powerful_mask@2x.png in Resources */, D08DB0B4213F4D1D00F2ADBF /* knot_down@2x.png in Resources */, + 09EC5CDA22CBBF9600292E42 /* telegram_plane1@2x.png in Resources */, D08DB0BC213F4D1D00F2ADBF /* start_arrow@2x.png in Resources */, D08DB0B6213F4D1D00F2ADBF /* powerful_infinity@2x.png in Resources */, 09EBE2AA22B004EA00F670AB /* BlueIconLargeIpad@2x.png in Resources */, @@ -2834,7 +2835,6 @@ D021D4D9219CAEDD0064BEBA /* Config-Fork.xcconfig in Resources */, D08DB0AD213F4D1D00F2ADBF /* ic_cam@2x.png in Resources */, 090E777622A6945900CD99F5 /* BlackClassicIcon@3x.png in Resources */, - D08DB0BF213F4D1D00F2ADBF /* telegram_plane1@2x.png in Resources */, D0E8B8B02044496C00605593 /* voip_ringback.caf in Resources */, 09E9600822C23FF200B13673 /* BlackNotificationIcon.png in Resources */, 09EBE2A922B004EA00F670AB /* BlackFilledIconLargeIpad@2x.png in Resources */, diff --git a/Telegram-iOS/Resources/intro/telegram_plane1@2x.png b/Telegram-iOS/Resources/intro/telegram_plane1@2x.png index f56072cb17..7a5a342bc9 100644 Binary files a/Telegram-iOS/Resources/intro/telegram_plane1@2x.png and b/Telegram-iOS/Resources/intro/telegram_plane1@2x.png differ diff --git a/Telegram-iOS/Resources/intro/telegram_sphere@2x.png b/Telegram-iOS/Resources/intro/telegram_sphere@2x.png index 15cc5f5277..85e805a00f 100644 Binary files a/Telegram-iOS/Resources/intro/telegram_sphere@2x.png and b/Telegram-iOS/Resources/intro/telegram_sphere@2x.png differ diff --git a/Watch/App/Assets.xcassets/AppIcon.appiconset/CompanionIcon@2x.png b/Watch/App/Assets.xcassets/AppIcon.appiconset/CompanionIcon@2x.png deleted file mode 100644 index e90a582eee..0000000000 Binary files a/Watch/App/Assets.xcassets/AppIcon.appiconset/CompanionIcon@2x.png and /dev/null differ diff --git a/Watch/App/Assets.xcassets/AppIcon.appiconset/CompanionIcon@3x.png b/Watch/App/Assets.xcassets/AppIcon.appiconset/CompanionIcon@3x.png deleted file mode 100644 index fef48bc05e..0000000000 Binary files a/Watch/App/Assets.xcassets/AppIcon.appiconset/CompanionIcon@3x.png and /dev/null differ diff --git a/Watch/App/Assets.xcassets/AppIcon.appiconset/Contents.json b/Watch/App/Assets.xcassets/AppIcon.appiconset/Contents.json index 9409bb80cc..b46b3e5abb 100644 --- a/Watch/App/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Watch/App/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -3,7 +3,7 @@ { "size" : "24x24", "idiom" : "watch", - "filename" : "NotificationIcon38@2x.png", + "filename" : "Watch48@2x.png", "scale" : "2x", "role" : "notificationCenter", "subtype" : "38mm" @@ -11,7 +11,7 @@ { "size" : "27.5x27.5", "idiom" : "watch", - "filename" : "NotificationIcon42@2x.png", + "filename" : "Watch55@2x.png", "scale" : "2x", "role" : "notificationCenter", "subtype" : "42mm" @@ -19,21 +19,21 @@ { "size" : "29x29", "idiom" : "watch", - "filename" : "CompanionIcon@2x.png", + "filename" : "Simple@58x58.png", "role" : "companionSettings", "scale" : "2x" }, { "size" : "29x29", "idiom" : "watch", - "filename" : "CompanionIcon@3x.png", + "filename" : "Simple@87x87.png", "role" : "companionSettings", "scale" : "3x" }, { "size" : "40x40", "idiom" : "watch", - "filename" : "Icon@2x.png", + "filename" : "Simple@80x80.png", "scale" : "2x", "role" : "appLauncher", "subtype" : "38mm" @@ -41,7 +41,7 @@ { "size" : "44x44", "idiom" : "watch", - "filename" : "Home88@2x.png", + "filename" : "Watch88@2x.png", "scale" : "2x", "role" : "appLauncher", "subtype" : "40mm" @@ -49,7 +49,7 @@ { "size" : "50x50", "idiom" : "watch", - "filename" : "Home100@2x.png", + "filename" : "Watch100@2x.png", "scale" : "2x", "role" : "appLauncher", "subtype" : "44mm" @@ -57,7 +57,7 @@ { "size" : "86x86", "idiom" : "watch", - "filename" : "ShortLook38@2x.png", + "filename" : "Watch172@2x.png", "scale" : "2x", "role" : "quickLook", "subtype" : "38mm" @@ -65,7 +65,7 @@ { "size" : "98x98", "idiom" : "watch", - "filename" : "ShortLook42@2x.png", + "filename" : "Watch196@2x.png", "scale" : "2x", "role" : "quickLook", "subtype" : "42mm" @@ -73,7 +73,7 @@ { "size" : "108x108", "idiom" : "watch", - "filename" : "ShortLook44@2x.png", + "filename" : "Watch216@2x.png", "scale" : "2x", "role" : "quickLook", "subtype" : "44mm" @@ -81,7 +81,7 @@ { "size" : "1024x1024", "idiom" : "watch-marketing", - "filename" : "T-Ipad_1024.png", + "filename" : "Simple-iTunesArtwork.png", "scale" : "1x" } ], diff --git a/Watch/App/Assets.xcassets/AppIcon.appiconset/Home100@2x.png b/Watch/App/Assets.xcassets/AppIcon.appiconset/Home100@2x.png deleted file mode 100644 index 9a05846398..0000000000 Binary files a/Watch/App/Assets.xcassets/AppIcon.appiconset/Home100@2x.png and /dev/null differ diff --git a/Watch/App/Assets.xcassets/AppIcon.appiconset/Home88@2x.png b/Watch/App/Assets.xcassets/AppIcon.appiconset/Home88@2x.png deleted file mode 100644 index 3d6be85c5f..0000000000 Binary files a/Watch/App/Assets.xcassets/AppIcon.appiconset/Home88@2x.png and /dev/null differ diff --git a/Watch/App/Assets.xcassets/AppIcon.appiconset/Icon@2x.png b/Watch/App/Assets.xcassets/AppIcon.appiconset/Icon@2x.png deleted file mode 100644 index 9c3c5f0da3..0000000000 Binary files a/Watch/App/Assets.xcassets/AppIcon.appiconset/Icon@2x.png and /dev/null differ diff --git a/Watch/App/Assets.xcassets/AppIcon.appiconset/NotificationIcon38@2x.png b/Watch/App/Assets.xcassets/AppIcon.appiconset/NotificationIcon38@2x.png deleted file mode 100644 index 519faf6972..0000000000 Binary files a/Watch/App/Assets.xcassets/AppIcon.appiconset/NotificationIcon38@2x.png and /dev/null differ diff --git a/Watch/App/Assets.xcassets/AppIcon.appiconset/NotificationIcon42@2x.png b/Watch/App/Assets.xcassets/AppIcon.appiconset/NotificationIcon42@2x.png deleted file mode 100644 index aba7a5cea2..0000000000 Binary files a/Watch/App/Assets.xcassets/AppIcon.appiconset/NotificationIcon42@2x.png and /dev/null differ diff --git a/Watch/App/Assets.xcassets/AppIcon.appiconset/ShortLook38@2x.png b/Watch/App/Assets.xcassets/AppIcon.appiconset/ShortLook38@2x.png deleted file mode 100644 index f2b13939cc..0000000000 Binary files a/Watch/App/Assets.xcassets/AppIcon.appiconset/ShortLook38@2x.png and /dev/null differ diff --git a/Watch/App/Assets.xcassets/AppIcon.appiconset/ShortLook42@2x.png b/Watch/App/Assets.xcassets/AppIcon.appiconset/ShortLook42@2x.png deleted file mode 100644 index a9077f67e3..0000000000 Binary files a/Watch/App/Assets.xcassets/AppIcon.appiconset/ShortLook42@2x.png and /dev/null differ diff --git a/Watch/App/Assets.xcassets/AppIcon.appiconset/ShortLook44@2x.png b/Watch/App/Assets.xcassets/AppIcon.appiconset/ShortLook44@2x.png deleted file mode 100644 index 62fbc1ed28..0000000000 Binary files a/Watch/App/Assets.xcassets/AppIcon.appiconset/ShortLook44@2x.png and /dev/null differ diff --git a/Watch/App/Assets.xcassets/AppIcon.appiconset/Simple-iTunesArtwork.png b/Watch/App/Assets.xcassets/AppIcon.appiconset/Simple-iTunesArtwork.png new file mode 100644 index 0000000000..f00a2857f0 Binary files /dev/null and b/Watch/App/Assets.xcassets/AppIcon.appiconset/Simple-iTunesArtwork.png differ diff --git a/Watch/App/Assets.xcassets/AppIcon.appiconset/Simple@58x58.png b/Watch/App/Assets.xcassets/AppIcon.appiconset/Simple@58x58.png new file mode 100644 index 0000000000..aa6a4a442e Binary files /dev/null and b/Watch/App/Assets.xcassets/AppIcon.appiconset/Simple@58x58.png differ diff --git a/Watch/App/Assets.xcassets/AppIcon.appiconset/Simple@80x80.png b/Watch/App/Assets.xcassets/AppIcon.appiconset/Simple@80x80.png new file mode 100644 index 0000000000..385bc474b2 Binary files /dev/null and b/Watch/App/Assets.xcassets/AppIcon.appiconset/Simple@80x80.png differ diff --git a/Watch/App/Assets.xcassets/AppIcon.appiconset/Simple@87x87.png b/Watch/App/Assets.xcassets/AppIcon.appiconset/Simple@87x87.png new file mode 100644 index 0000000000..c0a9ce9319 Binary files /dev/null and b/Watch/App/Assets.xcassets/AppIcon.appiconset/Simple@87x87.png differ diff --git a/Watch/App/Assets.xcassets/AppIcon.appiconset/T-Ipad_1024.png b/Watch/App/Assets.xcassets/AppIcon.appiconset/T-Ipad_1024.png deleted file mode 100644 index 31cfe6b631..0000000000 Binary files a/Watch/App/Assets.xcassets/AppIcon.appiconset/T-Ipad_1024.png and /dev/null differ diff --git a/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch100@2x.png b/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch100@2x.png new file mode 100644 index 0000000000..5c93a66260 Binary files /dev/null and b/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch100@2x.png differ diff --git a/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch172@2x.png b/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch172@2x.png new file mode 100644 index 0000000000..0708facadf Binary files /dev/null and b/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch172@2x.png differ diff --git a/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch196@2x.png b/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch196@2x.png new file mode 100644 index 0000000000..d399392477 Binary files /dev/null and b/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch196@2x.png differ diff --git a/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch216@2x.png b/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch216@2x.png new file mode 100644 index 0000000000..b88255eaa8 Binary files /dev/null and b/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch216@2x.png differ diff --git a/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch48@2x.png b/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch48@2x.png new file mode 100644 index 0000000000..5fd09d4461 Binary files /dev/null and b/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch48@2x.png differ diff --git a/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch55@2x.png b/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch55@2x.png new file mode 100644 index 0000000000..1de47d40c7 Binary files /dev/null and b/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch55@2x.png differ diff --git a/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch88@2x.png b/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch88@2x.png new file mode 100644 index 0000000000..71aaf3fdf0 Binary files /dev/null and b/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch88@2x.png differ diff --git a/submodules/DeviceAccess/Sources/DeviceAccess.swift b/submodules/DeviceAccess/Sources/DeviceAccess.swift index b27ef642e5..6a63829cef 100644 --- a/submodules/DeviceAccess/Sources/DeviceAccess.swift +++ b/submodules/DeviceAccess/Sources/DeviceAccess.swift @@ -179,7 +179,7 @@ public final class DeviceAccess { func statusForCellularState(_ state: CTCellularDataRestrictedState) -> AccessType? { switch state { case .restricted: - return .allowed + return .denied case .notRestricted: return .allowed default: diff --git a/submodules/Display/Display/ListView.swift b/submodules/Display/Display/ListView.swift index 14212245b4..b1b42d9011 100644 --- a/submodules/Display/Display/ListView.swift +++ b/submodules/Display/Display/ListView.swift @@ -1177,10 +1177,10 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture var bottomItemEdge: CGFloat = 0.0 for i in 0 ..< self.itemNodes.count { - if let index = itemNodes[i].index { + if let index = self.itemNodes[i].index { if index == 0 { topItemFound = true - topItemEdge = itemNodes[0].apparentFrame.origin.y + topItemEdge = self.itemNodes[0].apparentFrame.origin.y break } } @@ -1194,9 +1194,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture var completeHeight = effectiveInsets.top + effectiveInsets.bottom - if let index = itemNodes[itemNodes.count - 1].index, index == self.items.count - 1 { + if let index = self.itemNodes[self.itemNodes.count - 1].index, index == self.items.count - 1 { bottomItemFound = true - bottomItemEdge = itemNodes[itemNodes.count - 1].apparentFrame.maxY + bottomItemEdge = self.itemNodes[self.itemNodes.count - 1].apparentFrame.maxY } topItemEdge -= effectiveInsets.top @@ -1223,12 +1223,12 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture let wasIgnoringScrollingEvents = self.ignoreScrollingEvents self.ignoreScrollingEvents = true if topItemFound && bottomItemFound { + self.scroller.contentSize = CGSize(width: self.visibleSize.width, height: completeHeight) if self.stackFromBottom { self.lastContentOffset = CGPoint(x: 0.0, y: -topItemEdge) } else { self.lastContentOffset = CGPoint(x: 0.0, y: -topItemEdge) } - self.scroller.contentSize = CGSize(width: self.visibleSize.width, height: completeHeight) self.scroller.contentOffset = self.lastContentOffset } else if topItemFound { self.scroller.contentSize = CGSize(width: self.visibleSize.width, height: infiniteScrollSize * 2.0) @@ -1346,7 +1346,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture private func deleteAndInsertItemsTransaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem?, additionalScrollDistance: CGFloat, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemRange: (Int, Int)?, updateOpaqueState: Any?, completion: @escaping () -> Void) { if deleteIndices.isEmpty && insertIndicesAndItems.isEmpty && updateIndicesAndItems.isEmpty && scrollToItem == nil { - if let updateSizeAndInsets = updateSizeAndInsets , (self.items.count == 0 || (updateSizeAndInsets.size == self.visibleSize && updateSizeAndInsets.insets == self.insets)) { + if let updateSizeAndInsets = updateSizeAndInsets, (self.items.count == 0 || (updateSizeAndInsets.size == self.visibleSize && updateSizeAndInsets.insets == self.insets)) { self.visibleSize = updateSizeAndInsets.size self.insets = updateSizeAndInsets.insets self.headerInsets = updateSizeAndInsets.headerInsets ?? self.insets @@ -2457,7 +2457,6 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture var headerNodesTransition: (ContainedViewLayoutTransition, Bool, CGFloat) = (.immediate, false, 0.0) var deferredUpdateVisible = false - var insetTransitionOffset: CGFloat = 0.0 if let updateSizeAndInsets = updateSizeAndInsets { if self.insets != updateSizeAndInsets.insets || self.headerInsets != updateSizeAndInsets.headerInsets || !self.visibleSize.height.isEqual(to: updateSizeAndInsets.size.height) { @@ -2602,8 +2601,6 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture let (snappedTopInset, snapToBoundsOffset) = self.snapToBounds(snapTopItem: scrollToItem != nil, stackFromBottom: self.stackFromBottom, updateSizeAndInsets: updateSizeAndInsets, scrollToItem: scrollToItem) if !snappedTopInset.isZero && previousApparentFrames.isEmpty { - let offsetFix = snappedTopInset - for itemNode in self.itemNodes { let position = itemNode.position itemNode.position = CGPoint(x: position.x, y: position.y + snappedTopInset) diff --git a/submodules/Display/Display/ViewController.swift b/submodules/Display/Display/ViewController.swift index 9f3c415f7a..6f5ea48045 100644 --- a/submodules/Display/Display/ViewController.swift +++ b/submodules/Display/Display/ViewController.swift @@ -257,7 +257,9 @@ open class ViewControllerPresentationArguments { } } self.navigationBar?.requestContainerLayout = { [weak self] transition in - self?.requestLayout(transition: transition) + if let strongSelf = self, strongSelf.isNodeLoaded, let validLayout = strongSelf.validLayout { + strongSelf.updateNavigationBarLayout(validLayout, transition: transition) + } } self.navigationBar?.item = self.navigationItem self.automaticallyAdjustsScrollViewInsets = false @@ -275,17 +277,7 @@ open class ViewControllerPresentationArguments { } - open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - self.validLayout = layout - - if !self.isViewLoaded { - self.loadView() - } - transition.updateFrame(node: self.displayNode, frame: CGRect(origin: self.view.frame.origin, size: layout.size)) - if let _ = layout.statusBarHeight { - self.statusBar.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: 40.0)) - } - + private func updateNavigationBarLayout(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { let statusBarHeight: CGFloat = layout.statusBarHeight ?? 0.0 let navigationBarHeight: CGFloat = max(20.0, statusBarHeight) + (self.navigationBar?.contentHeight ?? 44.0) let navigationBarOffset: CGFloat @@ -318,6 +310,20 @@ open class ViewControllerPresentationArguments { transition.updateFrame(node: navigationBar, frame: navigationBarFrame) navigationBar.setHidden(!self.displayNavigationBar, animated: transition.isAnimated) } + } + + open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + self.validLayout = layout + + if !self.isViewLoaded { + self.loadView() + } + transition.updateFrame(node: self.displayNode, frame: CGRect(origin: self.view.frame.origin, size: layout.size)) + if let _ = layout.statusBarHeight { + self.statusBar.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: 40.0)) + } + + self.updateNavigationBarLayout(layout, transition: transition) self.presentationContext.containerLayoutUpdated(layout, transition: transition) diff --git a/submodules/RMIntro/RMIntro_Xcode.xcodeproj/project.pbxproj b/submodules/RMIntro/RMIntro_Xcode.xcodeproj/project.pbxproj index db058d16f8..198185de4f 100644 --- a/submodules/RMIntro/RMIntro_Xcode.xcodeproj/project.pbxproj +++ b/submodules/RMIntro/RMIntro_Xcode.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 09EC5CD822CBB86B00292E42 /* telegram_plane1@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 09EC5CD722CBB86A00292E42 /* telegram_plane1@2x.png */; }; D0A25B8922B5C0CF00AD4DE8 /* LegacyComponents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0A25B8822B5C0CF00AD4DE8 /* LegacyComponents.framework */; }; D0A25B8A22B5C0CF00AD4DE8 /* LegacyComponents.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D0A25B8822B5C0CF00AD4DE8 /* LegacyComponents.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; D0AE304922B1D77E0058D3BC /* RMIntro.h in Headers */ = {isa = PBXBuildFile; fileRef = D0AE304722B1D77E0058D3BC /* RMIntro.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -56,7 +57,6 @@ D0AE30C022B1D8ED0058D3BC /* powerful_infinity_white@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D0AE308422B1D8ED0058D3BC /* powerful_infinity_white@2x.png */; }; D0AE30C122B1D8ED0058D3BC /* private_screw@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D0AE308522B1D8ED0058D3BC /* private_screw@2x.png */; }; D0AE30C222B1D8ED0058D3BC /* powerful_infinity@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D0AE308622B1D8ED0058D3BC /* powerful_infinity@2x.png */; }; - D0AE30C322B1D8ED0058D3BC /* telegram_plane@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D0AE308722B1D8ED0058D3BC /* telegram_plane@2x.png */; }; D0AE30C422B1D8ED0058D3BC /* ic_smile@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D0AE308822B1D8ED0058D3BC /* ic_smile@2x.png */; }; D0AE30C522B1D8ED0058D3BC /* RMGeometry.m in Sources */ = {isa = PBXBuildFile; fileRef = D0AE308922B1D8ED0058D3BC /* RMGeometry.m */; }; D0AE30C622B1D8ED0058D3BC /* RMIntroViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = D0AE308A22B1D8ED0058D3BC /* RMIntroViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -84,6 +84,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 09EC5CD722CBB86A00292E42 /* telegram_plane1@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "telegram_plane1@2x.png"; sourceTree = ""; }; D0A25B8822B5C0CF00AD4DE8 /* LegacyComponents.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = LegacyComponents.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D0AE304422B1D77E0058D3BC /* RMIntro.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RMIntro.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D0AE304722B1D77E0058D3BC /* RMIntro.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RMIntro.h; sourceTree = ""; }; @@ -134,7 +135,6 @@ D0AE308422B1D8ED0058D3BC /* powerful_infinity_white@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "powerful_infinity_white@2x.png"; sourceTree = ""; }; D0AE308522B1D8ED0058D3BC /* private_screw@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "private_screw@2x.png"; sourceTree = ""; }; D0AE308622B1D8ED0058D3BC /* powerful_infinity@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "powerful_infinity@2x.png"; sourceTree = ""; }; - D0AE308722B1D8ED0058D3BC /* telegram_plane@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "telegram_plane@2x.png"; sourceTree = ""; }; D0AE308822B1D8ED0058D3BC /* ic_smile@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_smile@2x.png"; sourceTree = ""; }; D0AE308922B1D8ED0058D3BC /* RMGeometry.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMGeometry.m; sourceTree = ""; }; D0AE308A22B1D8ED0058D3BC /* RMIntroViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMIntroViewController.h; sourceTree = ""; }; @@ -249,6 +249,7 @@ D0AE306E22B1D8ED0058D3BC /* Resources */ = { isa = PBXGroup; children = ( + 09EC5CD722CBB86A00292E42 /* telegram_plane1@2x.png */, D0AE306F22B1D8ED0058D3BC /* ic_bubble_dot@2x.png */, D0AE307022B1D8ED0058D3BC /* powerful_mask@2x.png */, D0AE307122B1D8ED0058D3BC /* telegram_sphere@2x.png */, @@ -273,7 +274,6 @@ D0AE308422B1D8ED0058D3BC /* powerful_infinity_white@2x.png */, D0AE308522B1D8ED0058D3BC /* private_screw@2x.png */, D0AE308622B1D8ED0058D3BC /* powerful_infinity@2x.png */, - D0AE308722B1D8ED0058D3BC /* telegram_plane@2x.png */, D0AE308822B1D8ED0058D3BC /* ic_smile@2x.png */, ); path = Resources; @@ -402,6 +402,7 @@ D0AE30B522B1D8ED0058D3BC /* start_arrow_ipad@2x.png in Resources */, D0AE30BD22B1D8ED0058D3BC /* fast_spiral@2x.png in Resources */, D0AE30CD22B1D8ED0058D3BC /* .gitignore in Resources */, + 09EC5CD822CBB86B00292E42 /* telegram_plane1@2x.png in Resources */, D0AE30B822B1D8ED0058D3BC /* fast_arrow@2x.png in Resources */, D0AE30AC22B1D8ED0058D3BC /* powerful_mask@2x.png in Resources */, D0AE30B322B1D8ED0058D3BC /* fast_arrow_shadow@2x.png in Resources */, @@ -414,7 +415,6 @@ D0AE30B222B1D8ED0058D3BC /* start_arrow_ipad.png in Resources */, D0AE30C422B1D8ED0058D3BC /* ic_smile@2x.png in Resources */, D0AE30B922B1D8ED0058D3BC /* ic_pencil@2x.png in Resources */, - D0AE30C322B1D8ED0058D3BC /* telegram_plane@2x.png in Resources */, D0AE30B422B1D8ED0058D3BC /* ic_cam@2x.png in Resources */, D0AE30B622B1D8ED0058D3BC /* fast_body@2x.png in Resources */, D0AE30BC22B1D8ED0058D3BC /* ic_pin@2x.png in Resources */, diff --git a/submodules/RMIntro/Sources/platform/ios/Resources/telegram_plane1@2x.png b/submodules/RMIntro/Sources/platform/ios/Resources/telegram_plane1@2x.png new file mode 100644 index 0000000000..7a5a342bc9 Binary files /dev/null and b/submodules/RMIntro/Sources/platform/ios/Resources/telegram_plane1@2x.png differ diff --git a/submodules/RMIntro/Sources/platform/ios/Resources/telegram_plane@2x.png b/submodules/RMIntro/Sources/platform/ios/Resources/telegram_plane@2x.png deleted file mode 100644 index f56072cb17..0000000000 Binary files a/submodules/RMIntro/Sources/platform/ios/Resources/telegram_plane@2x.png and /dev/null differ diff --git a/submodules/RMIntro/Sources/platform/ios/Resources/telegram_sphere@2x.png b/submodules/RMIntro/Sources/platform/ios/Resources/telegram_sphere@2x.png index 1c8a870dcb..85e805a00f 100644 Binary files a/submodules/RMIntro/Sources/platform/ios/Resources/telegram_sphere@2x.png and b/submodules/RMIntro/Sources/platform/ios/Resources/telegram_sphere@2x.png differ diff --git a/submodules/TelegramCallsUI/Sources/CallKitIntegration.swift b/submodules/TelegramCallsUI/Sources/CallKitIntegration.swift index 1ade559300..c44866c743 100644 --- a/submodules/TelegramCallsUI/Sources/CallKitIntegration.swift +++ b/submodules/TelegramCallsUI/Sources/CallKitIntegration.swift @@ -1,6 +1,7 @@ import Foundation import UIKit import CallKit +import Intents import AVFoundation import Postbox import TelegramCore @@ -49,6 +50,7 @@ public final class CallKitIntegration { func startCall(account: Account, peerId: PeerId, displayTitle: String) { if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { (sharedProviderDelegate as? CallKitProviderDelegate)?.startCall(account: account, peerId: peerId, displayTitle: displayTitle) + self.donateIntent(peerId: peerId, displayTitle: displayTitle) } } @@ -75,6 +77,20 @@ public final class CallKitIntegration { (sharedProviderDelegate as? CallKitProviderDelegate)?.reportOutgoingCallConnected(uuid: uuid, at: date) } } + + private func donateIntent(peerId: PeerId, displayTitle: String) { + if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { + let handle = INPersonHandle(value: "tg\(peerId.id)", type: .unknown) + let contact = INPerson(personHandle: handle, nameComponents: nil, displayName: displayTitle, image: nil, contactIdentifier: nil, customIdentifier: "tg\(peerId.id)") + + let intent = INStartAudioCallIntent(contacts: [contact]) + + let interaction = INInteraction(intent: intent, response: nil) + interaction.direction = .outgoing + interaction.donate { _ in + } + } + } } @available(iOSApplicationExtension 10.0, iOS 10.0, *) diff --git a/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift b/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift index 56588c0dbd..e9e19294e1 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift @@ -68,8 +68,6 @@ public final class PresentationCallManager { private var callSettings: VoiceCallSettings? private var callSettingsDisposable: Disposable? - public var callRequested: ((_ accountPeerId: PeerId, _ peerId: PeerId) -> Void)? - public static var voipMaxLayer: Int32 { return OngoingCallContext.maxLayer } @@ -316,8 +314,6 @@ public final class PresentationCallManager { } else { begin() } - - self.callRequested?(account.peerId, peerId) } else { let begin: () -> Void = { [weak self] in guard let strongSelf = self else { @@ -333,7 +329,6 @@ public final class PresentationCallManager { } else { begin() } - self.callRequested?(account.peerId, peerId) } return .requested } diff --git a/submodules/TelegramCore/TelegramCore/SearchStickers.swift b/submodules/TelegramCore/TelegramCore/SearchStickers.swift index 60b866388f..494337b030 100644 --- a/submodules/TelegramCore/TelegramCore/SearchStickers.swift +++ b/submodules/TelegramCore/TelegramCore/SearchStickers.swift @@ -107,6 +107,7 @@ public func searchStickers(account: Account, query: String, scope: SearchSticker let currentItems = Set(result.map { $0.file.fileId }) var recentItems: [TelegramMediaFile] = [] + var recentAnimatedItems: [TelegramMediaFile] = [] var recentItemsIds = Set() var matchingRecentItemsIds = Set() @@ -118,14 +119,19 @@ public func searchStickers(account: Account, query: String, scope: SearchSticker matchingRecentItemsIds.insert(file.fileId) } recentItemsIds.insert(file.fileId) - recentItems.append(file) + if file.isAnimatedSticker { + recentAnimatedItems.append(file) + } else { + recentItems.append(file) + } break } } } } - var installed: [FoundStickerItem] = [] + var installedItems: [FoundStickerItem] = [] + var installedAnimatedItems: [FoundStickerItem] = [] for item in transaction.searchItemCollection(namespace: Namespaces.ItemCollection.CloudStickerPacks, query: .exact(ValueBoxKey(query))) { if let item = item as? StickerPackItem { if !currentItems.contains(item.file.fileId) { @@ -138,7 +144,11 @@ public func searchStickers(account: Account, query: String, scope: SearchSticker } } if !recentItemsIds.contains(item.file.fileId) { - installed.append(FoundStickerItem(file: item.file, stringRepresentations: stringRepresentations)) + if item.file.isAnimatedSticker { + installedAnimatedItems.append(FoundStickerItem(file: item.file, stringRepresentations: stringRepresentations)) + } else { + installedItems.append(FoundStickerItem(file: item.file, stringRepresentations: stringRepresentations)) + } } else { matchingRecentItemsIds.insert(item.file.fileId) } @@ -146,13 +156,20 @@ public func searchStickers(account: Account, query: String, scope: SearchSticker } } + for file in recentAnimatedItems { + if matchingRecentItemsIds.contains(file.fileId) { + result.append(FoundStickerItem(file: file, stringRepresentations: [query])) + } + } + for file in recentItems { if matchingRecentItemsIds.contains(file.fileId) { result.append(FoundStickerItem(file: file, stringRepresentations: [query])) } } - result.append(contentsOf: installed) + result.append(contentsOf: installedAnimatedItems) + result.append(contentsOf: installedItems) } let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerQueryResults, key: CachedStickerQueryResult.cacheKey(query))) as? CachedStickerQueryResult diff --git a/submodules/TelegramUI/TelegramUI/AppDelegate.swift b/submodules/TelegramUI/TelegramUI/AppDelegate.swift index 4634e88b57..cb03970e0d 100644 --- a/submodules/TelegramUI/TelegramUI/AppDelegate.swift +++ b/submodules/TelegramUI/TelegramUI/AppDelegate.swift @@ -586,8 +586,8 @@ final class SharedApplicationContext { self.window?.rootViewController?.dismiss(animated: true, completion: nil) }, getAvailableAlternateIcons: { if #available(iOS 10.3, *) { - var icons = [PresentationAppIcon(name: "Blue", imageName: "BlueIcon", isDefault: false), - PresentationAppIcon(name: "Black", imageName: "BlackIcon", isDefault: false), + var icons = [PresentationAppIcon(name: "Blue", imageName: "BlueIcon", isDefault: buildConfig.isAppStoreBuild), + PresentationAppIcon(name: "Black", imageName: "BlackIcon", isDefault: buildConfig.isInternalBuild), PresentationAppIcon(name: "BlueClassic", imageName: "BlueClassicIcon", isDefault: false), PresentationAppIcon(name: "BlackClassic", imageName: "BlackClassicIcon", isDefault: false), PresentationAppIcon(name: "BlueFilled", imageName: "BlueFilledIcon", isDefault: false), diff --git a/submodules/TelegramUI/TelegramUI/ChannelInfoController.swift b/submodules/TelegramUI/TelegramUI/ChannelInfoController.swift index 1a5a85d5dd..2a3beaf74f 100644 --- a/submodules/TelegramUI/TelegramUI/ChannelInfoController.swift +++ b/submodules/TelegramUI/TelegramUI/ChannelInfoController.swift @@ -312,13 +312,13 @@ private enum ChannelInfoEntry: ItemListNodeEntry { arguments.tapAvatarAction() }, context: arguments.avatarAndNameInfoContext, updatingImage: updatingAvatar) case let .about(theme, text, value): - return ItemListTextWithLabelItem(theme: theme, label: text, text: foldMultipleLineBreaks(value), enabledEntitiyTypes: [.url, .mention, .hashtag], multiline: true, sectionId: self.section, action: nil, longTapAction: { + return ItemListTextWithLabelItem(theme: theme, label: text, text: foldMultipleLineBreaks(value), enabledEntityTypes: [.url, .mention, .hashtag], multiline: true, sectionId: self.section, action: nil, longTapAction: { arguments.displayContextMenu(ChannelInfoEntryTag.about, value) }, linkItemAction: { action, itemLink in arguments.aboutLinkAction(action, itemLink) }, tag: ChannelInfoEntryTag.about) case let .addressName(theme, text, value): - return ItemListTextWithLabelItem(theme: theme, label: text, text: "https://t.me/\(value)", textColor: .accent, enabledEntitiyTypes: [], multiline: false, sectionId: self.section, action: { + return ItemListTextWithLabelItem(theme: theme, label: text, text: "https://t.me/\(value)", textColor: .accent, enabledEntityTypes: [], multiline: false, sectionId: self.section, action: { arguments.displayAddressNameContextMenu("https://t.me/\(value)") }, longTapAction: { arguments.displayContextMenu(ChannelInfoEntryTag.link, "https://t.me/\(value)") @@ -1157,7 +1157,7 @@ public func channelInfoController(context: AccountContext, peerId: PeerId) -> Vi } aboutLinkActionImpl = { [weak controller] action, itemLink in if let controller = controller { - handlePeerInfoAboutTextAction(context: context, peerId: peerId, navigateDisposable: navigateDisposable, controller: controller, action: action, itemLink: itemLink) + handleTextLinkAction(context: context, peerId: peerId, navigateDisposable: navigateDisposable, controller: controller, action: action, itemLink: itemLink) } } endEditingImpl = { diff --git a/submodules/TelegramUI/TelegramUI/ChatController.swift b/submodules/TelegramUI/TelegramUI/ChatController.swift index 8cd2274128..4f1efc337f 100644 --- a/submodules/TelegramUI/TelegramUI/ChatController.swift +++ b/submodules/TelegramUI/TelegramUI/ChatController.swift @@ -2954,7 +2954,7 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget, self?.deleteMediaRecording() }, sendRecordedMedia: { [weak self] in self?.sendMediaRecording() - }, displayRestrictedInfo: { [weak self] subject in + }, displayRestrictedInfo: { [weak self] subject, displayType in guard let strongSelf = self else { return } @@ -3006,37 +3006,42 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget, strongSelf.recordingModeFeedback?.error() - var rect: CGRect? - let isStickers: Bool = subject == .stickers - switch subject { - case .stickers: - rect = strongSelf.chatDisplayNode.frameForStickersButton() - if var rectValue = rect, let actionRect = strongSelf.chatDisplayNode.frameForInputActionButton() { - rectValue.origin.y = actionRect.minY - rect = rectValue + switch displayType { + case .tooltip: + var rect: CGRect? + let isStickers: Bool = subject == .stickers + switch subject { + case .stickers: + rect = strongSelf.chatDisplayNode.frameForStickersButton() + if var rectValue = rect, let actionRect = strongSelf.chatDisplayNode.frameForInputActionButton() { + rectValue.origin.y = actionRect.minY + rect = rectValue + } + case .mediaRecording: + rect = strongSelf.chatDisplayNode.frameForInputActionButton() } - case .mediaRecording: - rect = strongSelf.chatDisplayNode.frameForInputActionButton() - } - - if let tooltipController = strongSelf.mediaRestrictedTooltipController, strongSelf.mediaRestrictedTooltipControllerMode == isStickers { - tooltipController.content = .text(banDescription) - } else if let rect = rect { - strongSelf.mediaRestrictedTooltipController?.dismiss() - let tooltipController = TooltipController(content: .text(banDescription)) - strongSelf.mediaRestrictedTooltipController = tooltipController - strongSelf.mediaRestrictedTooltipControllerMode = isStickers - tooltipController.dismissed = { [weak tooltipController] in - if let strongSelf = self, let tooltipController = tooltipController, strongSelf.mediaRestrictedTooltipController === tooltipController { - strongSelf.mediaRestrictedTooltipController = nil + + if let tooltipController = strongSelf.mediaRestrictedTooltipController, strongSelf.mediaRestrictedTooltipControllerMode == isStickers { + tooltipController.content = .text(banDescription) + } else if let rect = rect { + strongSelf.mediaRestrictedTooltipController?.dismiss() + let tooltipController = TooltipController(content: .text(banDescription)) + strongSelf.mediaRestrictedTooltipController = tooltipController + strongSelf.mediaRestrictedTooltipControllerMode = isStickers + tooltipController.dismissed = { [weak tooltipController] in + if let strongSelf = self, let tooltipController = tooltipController, strongSelf.mediaRestrictedTooltipController === tooltipController { + strongSelf.mediaRestrictedTooltipController = nil + } + } + strongSelf.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: { + if let strongSelf = self { + return (strongSelf.chatDisplayNode, rect) + } + return nil + })) } - } - strongSelf.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: { - if let strongSelf = self { - return (strongSelf.chatDisplayNode, rect) - } - return nil - })) + case .alert: + strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: banDescription, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) } } }, displayVideoUnmuteTip: { [weak self] location in @@ -3607,6 +3612,8 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget, self.failedMessageEventsDisposable.set((self.context.account.pendingMessageManager.failedMessageEvents(peerId: peerId) |> deliverOnMainQueue).start(next: { [weak self] reason in if let strongSelf = self { + let subjectFlags: TelegramChatBannedRightsFlags = .banSendMedia + let text: String let moreInfo: Bool switch reason { @@ -3617,8 +3624,8 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget, text = strongSelf.presentationData.strings.Conversation_SendMessageErrorGroupRestricted moreInfo = true case .mediaRestricted: - text = strongSelf.presentationData.strings.Conversation_DefaultRestrictedMedia - moreInfo = false + strongSelf.interfaceInteraction?.displayRestrictedInfo(.mediaRecording, .alert) + return } let actions: [TextAlertAction] if moreInfo { @@ -3631,27 +3638,6 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget, strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: actions), in: .window(.root)) } })) - /*case let .group(groupId): - let unreadCountsKey: PostboxViewKey = .unreadCounts(items: [.group(groupId), .total(nil)]) - self.chatUnreadCountDisposable = (self.context.account.postbox.combinedView(keys: [unreadCountsKey]) |> deliverOnMainQueue).start(next: { [weak self] views in - if let strongSelf = self { - var unreadCount: Int32 = 0 - var totalCount: Int32 = 0 - - if let view = views.views[unreadCountsKey] as? UnreadMessageCountsView { - if let count = view.count(for: .group(groupId)) { - unreadCount = count - } - if let (_, state) = view.total() { - let inAppSettings = strongSelf.context.sharedContext.currentInAppNotificationSettings.with { $0 } - let (count, _) = renderedTotalUnreadCount(inAppSettings: inAppSettings, totalUnreadState: state) - totalCount = count - } - } - - strongSelf.chatDisplayNode.navigateButtons.unreadCount = unreadCount - } - })*/ } self.interfaceInteraction = interfaceInteraction diff --git a/submodules/TelegramUI/TelegramUI/ChatListController.swift b/submodules/TelegramUI/TelegramUI/ChatListController.swift index 4506692cc0..da54ca1e5c 100644 --- a/submodules/TelegramUI/TelegramUI/ChatListController.swift +++ b/submodules/TelegramUI/TelegramUI/ChatListController.swift @@ -837,21 +837,6 @@ public class ChatListController: TelegramController, UIViewControllerPreviewingD } } - /*self.chatListDisplayNode.chatListNode.contentOffsetChanged = { [weak self] offset in - if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode { - var progress: CGFloat = 0.0 - switch offset { - case let .known(offset): - progress = max(0.0, (searchContentNode.nominalHeight - max(0.0, offset - 76.0))) / searchContentNode.nominalHeight - case .none: - progress = 1.0 - default: - break - } - searchContentNode.updateExpansionProgress(progress) - } - }*/ - self.chatListDisplayNode.chatListNode.contentScrollingEnded = { [weak self] listView in if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode { return fixListNodeScrolling(listView, searchNode: searchContentNode) diff --git a/submodules/TelegramUI/TelegramUI/ChatMediaInputStickerPackItem.swift b/submodules/TelegramUI/TelegramUI/ChatMediaInputStickerPackItem.swift index 4b46f19319..26aeb93446 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMediaInputStickerPackItem.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMediaInputStickerPackItem.swift @@ -67,7 +67,7 @@ private let boundingImageSize = CGSize(width: 28.0, height: 28.0) private let highlightSize = CGSize(width: 35.0, height: 35.0) private let verticalOffset: CGFloat = 3.0 -private enum StickerPackThumbnailItem: Equatable { +enum StickerPackThumbnailItem: Equatable { case still(TelegramMediaImageRepresentation) case animated(MediaResource) diff --git a/submodules/TelegramUI/TelegramUI/ChatPanelInterfaceInteraction.swift b/submodules/TelegramUI/TelegramUI/ChatPanelInterfaceInteraction.swift index 2366c0e45a..4850f3a742 100644 --- a/submodules/TelegramUI/TelegramUI/ChatPanelInterfaceInteraction.swift +++ b/submodules/TelegramUI/TelegramUI/ChatPanelInterfaceInteraction.swift @@ -37,6 +37,11 @@ enum ChatPanelRestrictionInfoSubject { case stickers } +enum ChatPanelRestrictionInfoDisplayType { + case tooltip + case alert +} + final class ChatPanelInterfaceInteraction { let setupReplyMessage: (MessageId) -> Void let setupEditMessage: (MessageId?) -> Void @@ -73,7 +78,7 @@ final class ChatPanelInterfaceInteraction { let lockMediaRecording: () -> Void let deleteRecordedMedia: () -> Void let sendRecordedMedia: () -> Void - let displayRestrictedInfo: (ChatPanelRestrictionInfoSubject) -> Void + let displayRestrictedInfo: (ChatPanelRestrictionInfoSubject, ChatPanelRestrictionInfoDisplayType) -> Void let displayVideoUnmuteTip: (CGPoint?) -> Void let switchMediaRecordingMode: () -> Void let setupMessageAutoremoveTimeout: () -> Void @@ -102,7 +107,7 @@ final class ChatPanelInterfaceInteraction { let reportPeerIrrelevantGeoLocation: () -> Void let statuses: ChatPanelInterfaceInteractionStatuses? - init(setupReplyMessage: @escaping (MessageId) -> Void, setupEditMessage: @escaping (MessageId?) -> Void, beginMessageSelection: @escaping ([MessageId]) -> Void, deleteSelectedMessages: @escaping () -> Void, reportSelectedMessages: @escaping () -> Void, reportMessages: @escaping ([Message]) -> Void, deleteMessages: @escaping ([Message]) -> Void, forwardSelectedMessages: @escaping () -> Void, forwardCurrentForwardMessages: @escaping () -> Void, forwardMessages: @escaping ([Message]) -> Void, shareSelectedMessages: @escaping () -> Void, updateTextInputStateAndMode: @escaping ((ChatTextInputState, ChatInputMode) -> (ChatTextInputState, ChatInputMode)) -> Void, updateInputModeAndDismissedButtonKeyboardMessageId: @escaping ((ChatPresentationInterfaceState) -> (ChatInputMode, MessageId?)) -> Void, openStickers: @escaping () -> Void, editMessage: @escaping () -> Void, beginMessageSearch: @escaping (ChatSearchDomain, String) -> Void, dismissMessageSearch: @escaping () -> Void, updateMessageSearch: @escaping (String) -> Void, navigateMessageSearch: @escaping (ChatPanelSearchNavigationAction) -> Void, openCalendarSearch: @escaping () -> Void, toggleMembersSearch: @escaping (Bool) -> Void, navigateToMessage: @escaping (MessageId) -> Void, navigateToChat: @escaping (PeerId) -> Void, openPeerInfo: @escaping () -> Void, togglePeerNotifications: @escaping () -> Void, sendContextResult: @escaping (ChatContextResultCollection, ChatContextResult) -> Void, sendBotCommand: @escaping (Peer, String) -> Void, sendBotStart: @escaping (String?) -> Void, botSwitchChatWithPayload: @escaping (PeerId, String) -> Void, beginMediaRecording: @escaping (Bool) -> Void, finishMediaRecording: @escaping (ChatFinishMediaRecordingAction) -> Void, stopMediaRecording: @escaping () -> Void, lockMediaRecording: @escaping () -> Void, deleteRecordedMedia: @escaping () -> Void, sendRecordedMedia: @escaping () -> Void, displayRestrictedInfo: @escaping (ChatPanelRestrictionInfoSubject) -> Void, displayVideoUnmuteTip: @escaping (CGPoint?) -> Void, switchMediaRecordingMode: @escaping () -> Void, setupMessageAutoremoveTimeout: @escaping () -> Void, sendSticker: @escaping (FileMediaReference) -> Void, unblockPeer: @escaping () -> Void, pinMessage: @escaping (MessageId) -> Void, unpinMessage: @escaping () -> Void, shareAccountContact: @escaping () -> Void, reportPeer: @escaping () -> Void, presentPeerContact: @escaping () -> Void, dismissReportPeer: @escaping () -> Void, deleteChat: @escaping () -> Void, beginCall: @escaping () -> Void, toggleMessageStickerStarred: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, getNavigationController: @escaping () -> NavigationController?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, navigateFeed: @escaping () -> Void, openGrouping: @escaping () -> Void, toggleSilentPost: @escaping () -> Void, requestUnvoteInMessage: @escaping (MessageId) -> Void, requestStopPollInMessage: @escaping (MessageId) -> Void, updateInputLanguage: @escaping ((String?) -> String?) -> Void, unarchiveChat: @escaping () -> Void, openLinkEditing: @escaping () -> Void, reportPeerIrrelevantGeoLocation: @escaping () -> Void, statuses: ChatPanelInterfaceInteractionStatuses?) { + init(setupReplyMessage: @escaping (MessageId) -> Void, setupEditMessage: @escaping (MessageId?) -> Void, beginMessageSelection: @escaping ([MessageId]) -> Void, deleteSelectedMessages: @escaping () -> Void, reportSelectedMessages: @escaping () -> Void, reportMessages: @escaping ([Message]) -> Void, deleteMessages: @escaping ([Message]) -> Void, forwardSelectedMessages: @escaping () -> Void, forwardCurrentForwardMessages: @escaping () -> Void, forwardMessages: @escaping ([Message]) -> Void, shareSelectedMessages: @escaping () -> Void, updateTextInputStateAndMode: @escaping ((ChatTextInputState, ChatInputMode) -> (ChatTextInputState, ChatInputMode)) -> Void, updateInputModeAndDismissedButtonKeyboardMessageId: @escaping ((ChatPresentationInterfaceState) -> (ChatInputMode, MessageId?)) -> Void, openStickers: @escaping () -> Void, editMessage: @escaping () -> Void, beginMessageSearch: @escaping (ChatSearchDomain, String) -> Void, dismissMessageSearch: @escaping () -> Void, updateMessageSearch: @escaping (String) -> Void, navigateMessageSearch: @escaping (ChatPanelSearchNavigationAction) -> Void, openCalendarSearch: @escaping () -> Void, toggleMembersSearch: @escaping (Bool) -> Void, navigateToMessage: @escaping (MessageId) -> Void, navigateToChat: @escaping (PeerId) -> Void, openPeerInfo: @escaping () -> Void, togglePeerNotifications: @escaping () -> Void, sendContextResult: @escaping (ChatContextResultCollection, ChatContextResult) -> Void, sendBotCommand: @escaping (Peer, String) -> Void, sendBotStart: @escaping (String?) -> Void, botSwitchChatWithPayload: @escaping (PeerId, String) -> Void, beginMediaRecording: @escaping (Bool) -> Void, finishMediaRecording: @escaping (ChatFinishMediaRecordingAction) -> Void, stopMediaRecording: @escaping () -> Void, lockMediaRecording: @escaping () -> Void, deleteRecordedMedia: @escaping () -> Void, sendRecordedMedia: @escaping () -> Void, displayRestrictedInfo: @escaping (ChatPanelRestrictionInfoSubject, ChatPanelRestrictionInfoDisplayType) -> Void, displayVideoUnmuteTip: @escaping (CGPoint?) -> Void, switchMediaRecordingMode: @escaping () -> Void, setupMessageAutoremoveTimeout: @escaping () -> Void, sendSticker: @escaping (FileMediaReference) -> Void, unblockPeer: @escaping () -> Void, pinMessage: @escaping (MessageId) -> Void, unpinMessage: @escaping () -> Void, shareAccountContact: @escaping () -> Void, reportPeer: @escaping () -> Void, presentPeerContact: @escaping () -> Void, dismissReportPeer: @escaping () -> Void, deleteChat: @escaping () -> Void, beginCall: @escaping () -> Void, toggleMessageStickerStarred: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, getNavigationController: @escaping () -> NavigationController?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, navigateFeed: @escaping () -> Void, openGrouping: @escaping () -> Void, toggleSilentPost: @escaping () -> Void, requestUnvoteInMessage: @escaping (MessageId) -> Void, requestStopPollInMessage: @escaping (MessageId) -> Void, updateInputLanguage: @escaping ((String?) -> String?) -> Void, unarchiveChat: @escaping () -> Void, openLinkEditing: @escaping () -> Void, reportPeerIrrelevantGeoLocation: @escaping () -> Void, statuses: ChatPanelInterfaceInteractionStatuses?) { self.setupReplyMessage = setupReplyMessage self.setupEditMessage = setupEditMessage self.beginMessageSelection = beginMessageSelection diff --git a/submodules/TelegramUI/TelegramUI/ChatRecentActionsController.swift b/submodules/TelegramUI/TelegramUI/ChatRecentActionsController.swift index 809879414c..9c3c294a57 100644 --- a/submodules/TelegramUI/TelegramUI/ChatRecentActionsController.swift +++ b/submodules/TelegramUI/TelegramUI/ChatRecentActionsController.swift @@ -74,7 +74,7 @@ final class ChatRecentActionsController: TelegramController { }, lockMediaRecording: { }, deleteRecordedMedia: { }, sendRecordedMedia: { - }, displayRestrictedInfo: { _ in + }, displayRestrictedInfo: { _, _ in }, displayVideoUnmuteTip: { _ in }, switchMediaRecordingMode: { }, setupMessageAutoremoveTimeout: { diff --git a/submodules/TelegramUI/TelegramUI/ChatTextInputPanelNode.swift b/submodules/TelegramUI/TelegramUI/ChatTextInputPanelNode.swift index a034a11d85..8a3daa8ffd 100644 --- a/submodules/TelegramUI/TelegramUI/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatTextInputPanelNode.swift @@ -348,7 +348,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { self.addSubnode(self.actionButtons) self.actionButtons.micButton.recordingDisabled = { [weak self] in - self?.interfaceInteraction?.displayRestrictedInfo(.mediaRecording) + self?.interfaceInteraction?.displayRestrictedInfo(.mediaRecording, .tooltip) } self.actionButtons.micButton.beginRecording = { [weak self] in @@ -1578,7 +1578,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { if enabled { self.interfaceInteraction?.openStickers() } else { - self.interfaceInteraction?.displayRestrictedInfo(.stickers) + self.interfaceInteraction?.displayRestrictedInfo(.stickers, .tooltip) } case .keyboard: self.interfaceInteraction?.updateInputModeAndDismissedButtonKeyboardMessageId({ state in diff --git a/submodules/TelegramUI/TelegramUI/DeviceContactInfoController.swift b/submodules/TelegramUI/TelegramUI/DeviceContactInfoController.swift index bfecc5f606..6ba3381665 100644 --- a/submodules/TelegramUI/TelegramUI/DeviceContactInfoController.swift +++ b/submodules/TelegramUI/TelegramUI/DeviceContactInfoController.swift @@ -387,10 +387,10 @@ private enum DeviceContactInfoEntry: ItemListNodeEntry { arguments.performAction(.addToExisting) }) case let .company(_, theme, title, value, selected): - return ItemListTextWithLabelItem(theme: theme, label: title, text: value, style: arguments.isPlain ? .plain : .blocks, enabledEntitiyTypes: [], multiline: true, selected: selected, sectionId: self.section, action: { + return ItemListTextWithLabelItem(theme: theme, label: title, text: value, style: arguments.isPlain ? .plain : .blocks, enabledEntityTypes: [], multiline: true, selected: selected, sectionId: self.section, action: { }, tag: nil) case let .phoneNumber(_, index, theme, title, label, value, selected, isInteractionEnabled): - return ItemListTextWithLabelItem(theme: theme, label: title, text: value, style: arguments.isPlain ? .plain : .blocks, textColor: .accent, enabledEntitiyTypes: [], multiline: false, selected: selected, sectionId: self.section, action: isInteractionEnabled ? { + return ItemListTextWithLabelItem(theme: theme, label: title, text: value, style: arguments.isPlain ? .plain : .blocks, textColor: .accent, enabledEntityTypes: [], multiline: false, selected: selected, sectionId: self.section, action: isInteractionEnabled ? { if selected != nil { arguments.toggleSelection(.phoneNumber(label, value)) } else { @@ -424,7 +424,7 @@ private enum DeviceContactInfoEntry: ItemListNodeEntry { arguments.addPhoneNumber() }) case let .email(_, index, theme, title, label, value, selected): - return ItemListTextWithLabelItem(theme: theme, label: title, text: value, style: arguments.isPlain ? .plain : .blocks, textColor: .accent, enabledEntitiyTypes: [], multiline: false, selected: selected, sectionId: self.section, action: { + return ItemListTextWithLabelItem(theme: theme, label: title, text: value, style: arguments.isPlain ? .plain : .blocks, textColor: .accent, enabledEntityTypes: [], multiline: false, selected: selected, sectionId: self.section, action: { if selected != nil { arguments.toggleSelection(.email(label, value)) } else { @@ -436,7 +436,7 @@ private enum DeviceContactInfoEntry: ItemListNodeEntry { } }, tag: DeviceContactInfoEntryTag.info(index)) case let .url(_, index, theme, title, label, value, selected): - return ItemListTextWithLabelItem(theme: theme, label: title, text: value, style: arguments.isPlain ? .plain : .blocks, textColor: .accent, enabledEntitiyTypes: [], multiline: false, selected: selected, sectionId: self.section, action: { + return ItemListTextWithLabelItem(theme: theme, label: title, text: value, style: arguments.isPlain ? .plain : .blocks, textColor: .accent, enabledEntityTypes: [], multiline: false, selected: selected, sectionId: self.section, action: { if selected != nil { arguments.toggleSelection(.url(label, value)) } else { @@ -475,7 +475,7 @@ private enum DeviceContactInfoEntry: ItemListNodeEntry { } }, tag: DeviceContactInfoEntryTag.info(index)) case let .birthday(_, theme, title, value, text, selected): - return ItemListTextWithLabelItem(theme: theme, label: title, text: text, style: arguments.isPlain ? .plain : .blocks, textColor: .accent, enabledEntitiyTypes: [], multiline: true, selected: selected, sectionId: self.section, action: { + return ItemListTextWithLabelItem(theme: theme, label: title, text: text, style: arguments.isPlain ? .plain : .blocks, textColor: .accent, enabledEntityTypes: [], multiline: true, selected: selected, sectionId: self.section, action: { if selected != nil { arguments.toggleSelection(.birthday) } else { @@ -504,7 +504,7 @@ private enum DeviceContactInfoEntry: ItemListNodeEntry { } }, tag: DeviceContactInfoEntryTag.birthday) case let .socialProfile(_, index, theme, title, value, text, selected): - return ItemListTextWithLabelItem(theme: theme, label: title, text: text, style: arguments.isPlain ? .plain : .blocks, textColor: .accent, enabledEntitiyTypes: [], multiline: true, selected: selected, sectionId: self.section, action: { + return ItemListTextWithLabelItem(theme: theme, label: title, text: text, style: arguments.isPlain ? .plain : .blocks, textColor: .accent, enabledEntityTypes: [], multiline: true, selected: selected, sectionId: self.section, action: { if selected != nil { arguments.toggleSelection(.socialProfile(value)) } else if value.url.count > 0 { @@ -516,7 +516,7 @@ private enum DeviceContactInfoEntry: ItemListNodeEntry { } }, tag: DeviceContactInfoEntryTag.info(index)) case let .instantMessenger(_, index, theme, title, value, text, selected): - return ItemListTextWithLabelItem(theme: theme, label: title, text: text, style: arguments.isPlain ? .plain : .blocks, textColor: .accent, enabledEntitiyTypes: [], multiline: true, selected: selected, sectionId: self.section, action: { + return ItemListTextWithLabelItem(theme: theme, label: title, text: text, style: arguments.isPlain ? .plain : .blocks, textColor: .accent, enabledEntityTypes: [], multiline: true, selected: selected, sectionId: self.section, action: { if selected != nil { arguments.toggleSelection(.instantMessenger(value)) } diff --git a/submodules/TelegramUI/TelegramUI/GroupInfoController.swift b/submodules/TelegramUI/TelegramUI/GroupInfoController.swift index fc5207af2c..0dee989c88 100644 --- a/submodules/TelegramUI/TelegramUI/GroupInfoController.swift +++ b/submodules/TelegramUI/TelegramUI/GroupInfoController.swift @@ -461,7 +461,7 @@ private enum GroupInfoEntry: ItemListNodeEntry { arguments.changeProfilePhoto() }) case let .about(theme, text): - return ItemListMultilineTextItem(theme: theme, text: foldMultipleLineBreaks(text), enabledEntitiyTypes: [.url, .mention, .hashtag], sectionId: self.section, style: .blocks, longTapAction: { + return ItemListMultilineTextItem(theme: theme, text: foldMultipleLineBreaks(text), enabledEntityTypes: [.url, .mention, .hashtag], sectionId: self.section, style: .blocks, longTapAction: { arguments.displayAboutContextMenu(text) }, linkItemAction: { action, itemLink in arguments.aboutLinkAction(action, itemLink) @@ -2362,7 +2362,7 @@ public func groupInfoController(context: AccountContext, peerId originalPeerId: |> take(1) |> deliverOnMainQueue).start(next: { peerView in if let controller = controller { - handlePeerInfoAboutTextAction(context: context, peerId: peerView.peerId, navigateDisposable: navigateDisposable, controller: controller, action: action, itemLink: itemLink) + handleTextLinkAction(context: context, peerId: peerView.peerId, navigateDisposable: navigateDisposable, controller: controller, action: action, itemLink: itemLink) } }) } @@ -2416,149 +2416,3 @@ public func groupInfoController(context: AccountContext, peerId originalPeerId: } return controller } - -func handlePeerInfoAboutTextAction(context: AccountContext, peerId: PeerId, navigateDisposable: MetaDisposable, controller: ViewController, action: TextLinkItemActionType, itemLink: TextLinkItem) { - let presentImpl: (ViewController, Any?) -> Void = { controllerToPresent, _ in - controller.present(controllerToPresent, in: .window(.root)) - } - - let openResolvedPeerImpl: (PeerId?, ChatControllerInteractionNavigateToPeer) -> Void = { [weak controller] peerId, navigation in - openResolvedUrl(.peer(peerId, navigation), context: context, navigationController: (controller?.navigationController as? NavigationController), openPeer: { (peerId, navigation) in - switch navigation { - case let .chat(_, messageId): - if let navigationController = controller?.navigationController as? NavigationController { - navigateToChatController(navigationController: navigationController, context: context, chatLocation: .peer(peerId), messageId: messageId, keepStack: .always) - } - case .info: - let peerSignal: Signal - peerSignal = context.account.postbox.loadedPeerWithId(peerId) |> map(Optional.init) - navigateDisposable.set((peerSignal |> take(1) |> deliverOnMainQueue).start(next: { peer in - if let controller = controller, let peer = peer { - if let infoController = peerInfoController(context: context, peer: peer) { - (controller.navigationController as? NavigationController)?.pushViewController(infoController) - } - } - })) - default: - break - } - }, present: presentImpl, dismissInput: {}) - } - - let openLinkImpl: (String) -> Void = { [weak controller] url in - navigateDisposable.set((resolveUrl(account: context.account, url: url) |> deliverOnMainQueue).start(next: { result in - if let controller = controller { - switch result { - case let .externalUrl(url): - context.sharedContext.applicationBindings.openUrl(url) - case let .peer(peerId, _): - openResolvedPeerImpl(peerId, .default) - case let .channelMessage(peerId, messageId): - if let navigationController = controller.navigationController as? NavigationController { - navigateToChatController(navigationController: navigationController, context: context, chatLocation: .peer(peerId), messageId: messageId) - } - case let .stickerPack(name): - controller.present(StickerPackPreviewController(context: context, stickerPack: .name(name), parentNavigationController: controller.navigationController as? NavigationController), in: .window(.root)) - case let .instantView(webpage, anchor): - (controller.navigationController as? NavigationController)?.pushViewController(InstantPageController(context: context, webPage: webpage, sourcePeerType: .group, anchor: anchor)) - case let .join(link): - controller.present(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peerId in - openResolvedPeerImpl(peerId, .chat(textInputState: nil, messageId: nil)) - }), in: .window(.root)) - default: - break - } - } - })) - } - - let openPeerMentionImpl: (String) -> Void = { mention in - navigateDisposable.set((resolvePeerByName(account: context.account, name: mention, ageLimit: 10) |> take(1) |> deliverOnMainQueue).start(next: { peerId in - openResolvedPeerImpl(peerId, .default) - })) - } - - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - switch action { - case .tap: - switch itemLink { - case let .url(url): - openLinkImpl(url) - case let .mention(mention): - openPeerMentionImpl(mention) - case let .hashtag(_, hashtag): - let peerSignal = context.account.postbox.loadedPeerWithId(peerId) - let _ = (peerSignal - |> deliverOnMainQueue).start(next: { peer in - let searchController = HashtagSearchController(context: context, peer: peer, query: hashtag) - (controller.navigationController as? NavigationController)?.pushViewController(searchController) - }) - } - case .longTap: - switch itemLink { - case let .url(url): - let canOpenIn = availableOpenInOptions(context: context, item: .url(url: url)).count > 1 - let openText = canOpenIn ? presentationData.strings.Conversation_FileOpenIn : presentationData.strings.Conversation_LinkDialogOpen - let actionSheet = ActionSheetController(presentationTheme: presentationData.theme) - actionSheet.setItemGroups([ActionSheetItemGroup(items: [ - ActionSheetTextItem(title: url), - ActionSheetButtonItem(title: openText, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - openLinkImpl(url) - }), - ActionSheetButtonItem(title: presentationData.strings.ShareMenu_CopyShareLink, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - UIPasteboard.general.string = url - }), - ActionSheetButtonItem(title: presentationData.strings.Conversation_AddToReadingList, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - if let link = URL(string: url) { - let _ = try? SSReadingList.default()?.addItem(with: link, title: nil, previewText: nil) - } - }) - ]), ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - }) - ])]) - controller.present(actionSheet, in: .window(.root)) - case let .mention(mention): - let actionSheet = ActionSheetController(presentationTheme: presentationData.theme) - actionSheet.setItemGroups([ActionSheetItemGroup(items: [ - ActionSheetTextItem(title: mention), - ActionSheetButtonItem(title: presentationData.strings.Conversation_LinkDialogOpen, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - openPeerMentionImpl(mention) - }), - ActionSheetButtonItem(title: presentationData.strings.Conversation_LinkDialogCopy, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - UIPasteboard.general.string = mention - }) - ]), ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - }) - ])]) - controller.present(actionSheet, in: .window(.root)) - case let .hashtag(_, hashtag): - let actionSheet = ActionSheetController(presentationTheme: presentationData.theme) - actionSheet.setItemGroups([ActionSheetItemGroup(items: [ - ActionSheetTextItem(title: hashtag), - ActionSheetButtonItem(title: presentationData.strings.Conversation_LinkDialogOpen, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - let searchController = HashtagSearchController(context: context, peer: nil, query: hashtag) - (controller.navigationController as? NavigationController)?.pushViewController(searchController) - }), - ActionSheetButtonItem(title: presentationData.strings.Conversation_LinkDialogCopy, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - UIPasteboard.general.string = hashtag - }) - ]), ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - }) - ])]) - controller.present(actionSheet, in: .window(.root)) - } - } -} diff --git a/submodules/TelegramUI/TelegramUI/HorizontalListContextResultsChatInputPanelItem.swift b/submodules/TelegramUI/TelegramUI/HorizontalListContextResultsChatInputPanelItem.swift index b8709c87b1..7b856965f1 100644 --- a/submodules/TelegramUI/TelegramUI/HorizontalListContextResultsChatInputPanelItem.swift +++ b/submodules/TelegramUI/TelegramUI/HorizontalListContextResultsChatInputPanelItem.swift @@ -80,9 +80,11 @@ private let iconTextBackgroundImage = generateStretchableFilledCircleImage(radiu final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode { private let imageNodeBackground: ASDisplayNode private let imageNode: TransformImageNode + private var animationNode: AnimatedStickerNode? private var videoLayer: (SoftwareVideoThumbnailLayer, SoftwareVideoLayerFrameManager, SampleBufferLayer)? private var currentImageResource: TelegramMediaResource? private var currentVideoFile: TelegramMediaFile? + private var currentAnimatedStickerFile: TelegramMediaFile? private var resourceStatus: MediaResourceStatus? private(set) var item: HorizontalListContextResultsChatInputPanelItem? private var statusDisposable = MetaDisposable() @@ -188,6 +190,7 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode let imageLayout = self.imageNode.asyncLayout() let currentImageResource = self.currentImageResource let currentVideoFile = self.currentVideoFile + let currentAnimatedStickerFile = self.currentAnimatedStickerFile return { [weak self] item, params, mergedTop, mergedBottom in let height = params.width @@ -199,6 +202,7 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode var imageResource: TelegramMediaResource? var stickerFile: TelegramMediaFile? + var animatedStickerFile: TelegramMediaFile? var videoFile: TelegramMediaFile? var imageDimensions: CGSize? switch item.result { @@ -231,7 +235,10 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode } else if let largestRepresentation = largestImageRepresentation(file.previewRepresentations) { imageDimensions = largestRepresentation.dimensions } - if file.isSticker { + if file.isAnimatedSticker { + animatedStickerFile = file + imageResource = smallestImageRepresentation(file.previewRepresentations)?.resource + } else if file.isSticker { stickerFile = file imageResource = file.resource } else { @@ -286,6 +293,15 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode updatedVideoFile = true } + var updatedAnimatedStickerFile = false + if let currentAnimatedStickerFile = currentAnimatedStickerFile, let animatedStickerFile = animatedStickerFile { + if !currentAnimatedStickerFile.isEqual(to: animatedStickerFile) { + updatedAnimatedStickerFile = true + } + } else if (currentAnimatedStickerFile != nil) != (animatedStickerFile != nil) { + updatedAnimatedStickerFile = true + } + if updatedImageResource { if let imageResource = imageResource { if let stickerFile = stickerFile { @@ -300,7 +316,6 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode } } - let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: height, height: croppedImageDimensions.width + sideInset), insets: UIEdgeInsets()) return (nodeLayout, { _ in @@ -348,6 +363,30 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode } } + if updatedAnimatedStickerFile { + if let animationNode = strongSelf.animationNode { + strongSelf.animationNode = nil + animationNode.removeFromSupernode() + } + + if let animatedStickerFile = animatedStickerFile { + let animationNode: AnimatedStickerNode + if let currentAnimationNode = strongSelf.animationNode { + animationNode = currentAnimationNode + } else { + animationNode = AnimatedStickerNode() + animationNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0) + animationNode.visibility = true + strongSelf.addSubnode(animationNode) + strongSelf.animationNode = animationNode + } + animationNode.started = { [weak self] in + self?.imageNode.alpha = 0.0 + } + animationNode.setup(account: item.account, resource: animatedStickerFile.resource, width: 140, height: 140, mode: .cached) + } + } + let progressSize = CGSize(width: 24.0, height: 24.0) let progressFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((nodeLayout.contentSize.width - progressSize.width) / 2.0), y: floorToScreenPixels((nodeLayout.contentSize.height - progressSize.height) / 2.0)), size: progressSize) @@ -363,20 +402,18 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode if let strongSelf = strongSelf { strongSelf.resourceStatus = status - let state: RadialStatusNodeState let statusForegroundColor: UIColor = .white switch status { - case let .Fetching(_, progress): - state = .progress(color: statusForegroundColor, lineWidth: nil, value: CGFloat(max(progress, 0.2)), cancelEnabled: false) - case .Remote: - state = .download(statusForegroundColor) - case .Local: - state = .none + case let .Fetching(_, progress): + state = .progress(color: statusForegroundColor, lineWidth: nil, value: CGFloat(max(progress, 0.2)), cancelEnabled: false) + case .Remote: + state = .download(statusForegroundColor) + case .Local: + state = .none } - strongSelf.statusNode.transitionToState(state, completion: { }) } } @@ -391,6 +428,12 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode layer.layer.bounds = CGRect(origin: CGPoint(), size: CGSize(width: croppedImageDimensions.width, height: croppedImageDimensions.height)) layer.layer.position = CGPoint(x: height / 2.0, y: (nodeLayout.contentSize.height - sideInset) / 2.0 + sideInset) } + + if let animationNode = strongSelf.animationNode { + animationNode.bounds = CGRect(origin: CGPoint(), size: CGSize(width: croppedImageDimensions.width, height: croppedImageDimensions.height)) + animationNode.position = CGPoint(x: height / 2.0, y: (nodeLayout.contentSize.height - sideInset) / 2.0 + sideInset) + animationNode.updateLayout(size: croppedImageDimensions) + } } }) } diff --git a/submodules/TelegramUI/TelegramUI/HorizontalStickersChatContextPanelNode.swift b/submodules/TelegramUI/TelegramUI/HorizontalStickersChatContextPanelNode.swift index 2232b9eae9..fe6a9b8c3b 100755 --- a/submodules/TelegramUI/TelegramUI/HorizontalStickersChatContextPanelNode.swift +++ b/submodules/TelegramUI/TelegramUI/HorizontalStickersChatContextPanelNode.swift @@ -302,9 +302,6 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode { self.backgroundNode.image = backgroundCenterImage(theme) self.backgroundLeftNode.image = backgroundLeftImage(theme) self.backgroundRightNode.image = backgroundLeftImage(theme) - // if let currentEntries = self.currentEntries { - // self.updateToEntries(entries: currentEntries, forceUpdate: true) - // } } } diff --git a/submodules/TelegramUI/TelegramUI/InviteContactsControllerNode.swift b/submodules/TelegramUI/TelegramUI/InviteContactsControllerNode.swift index 2ca1f37427..0ee253c743 100644 --- a/submodules/TelegramUI/TelegramUI/InviteContactsControllerNode.swift +++ b/submodules/TelegramUI/TelegramUI/InviteContactsControllerNode.swift @@ -191,14 +191,14 @@ private func inviteContactsEntries(accountPeer: Peer?, sortedContacts: [(DeviceC return entries } -private func preparedInviteContactsTransition(account: Account, from fromEntries: [InviteContactsEntry], to toEntries: [InviteContactsEntry], sortedContacts: [(DeviceContactStableId, DeviceContactBasicData, Int32)]?, interaction: InviteContactsInteraction, firstTime: Bool, isLoading: Bool, animated: Bool) -> InviteContactsTransition { +private func preparedInviteContactsTransition(account: Account, from fromEntries: [InviteContactsEntry], to toEntries: [InviteContactsEntry], sortedContacts: [(DeviceContactStableId, DeviceContactBasicData, Int32)]?, interaction: InviteContactsInteraction, isLoading: Bool, firstTime: Bool, crossfade: Bool) -> InviteContactsTransition { let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, interaction: interaction), directionHint: nil) } let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, interaction: interaction), directionHint: nil) } - return InviteContactsTransition(deletions: deletions, insertions: insertions, updates: updates, sortedContacts: sortedContacts, firstTime: firstTime, isLoading: isLoading, animated: animated) + return InviteContactsTransition(deletions: deletions, insertions: insertions, updates: updates, sortedContacts: sortedContacts, isLoading: isLoading, firstTime: firstTime, crossfade: crossfade) } private struct InviteContactsTransition { @@ -206,9 +206,9 @@ private struct InviteContactsTransition { let insertions: [ListViewInsertItem] let updates: [ListViewUpdateItem] let sortedContacts: [(DeviceContactStableId, DeviceContactBasicData, Int32)]? - let firstTime: Bool let isLoading: Bool - let animated: Bool + let firstTime: Bool + let crossfade: Bool } final class InviteContactsControllerNode: ASDisplayNode { @@ -229,7 +229,7 @@ final class InviteContactsControllerNode: ASDisplayNode { var requestShareTelegram: (() -> Void)? var requestShare: (([(DeviceContactBasicData, Int32)]) -> Void)? - let currentSortedContacts = Atomic<[(DeviceContactStableId, DeviceContactBasicData, Int32)]>(value: []) + let currentSortedContacts = Atomic<[(DeviceContactStableId, DeviceContactBasicData, Int32)]?>(value: nil) var selectionState = InviteContactsGroupSelectionState() { didSet { @@ -290,6 +290,7 @@ final class InviteContactsControllerNode: ASDisplayNode { }) self.backgroundColor = self.presentationData.theme.chatList.backgroundColor + self.listNode.backgroundColor = self.presentationData.theme.chatList.backgroundColor self.addSubnode(self.listNode) self.addSubnode(self.countPanelNode) @@ -310,7 +311,6 @@ final class InviteContactsControllerNode: ASDisplayNode { }) let account = self.context.account - var firstTime: Int32 = 1 let selectionStateSignal = self.selectionStatePromise.get() let transition: Signal let themeAndStringsPromise = self.themeAndStringsPromise @@ -387,36 +387,24 @@ final class InviteContactsControllerNode: ASDisplayNode { return result } } - |> beforeNext { sortedContacts in - if let sortedContacts = sortedContacts { - let _ = currentSortedContacts.swap(sortedContacts) - } - } let processingQueue = Queue() - transition = (combineLatest(.single(nil) |> then(sortedContacts), selectionStateSignal, themeAndStringsPromise.get()) - |> mapToQueue { sortedContacts, selectionState, themeAndStrings -> Signal in + transition = (combineLatest(.single(nil) |> then(sortedContacts), selectionStateSignal, themeAndStringsPromise.get(), .single(true) |> delay(0.2, queue: Queue.mainQueue())) + |> mapToQueue { sortedContacts, selectionState, themeAndStrings, ready -> Signal in + guard sortedContacts != nil || ready else { + return .never() + } + let signal = deferred { () -> Signal in let entries = inviteContactsEntries(accountPeer: nil, sortedContacts: sortedContacts, selectionState: selectionState, theme: themeAndStrings.0, strings: themeAndStrings.1, nameSortOrder: themeAndStrings.2, nameDisplayOrder: themeAndStrings.3, interaction: interaction) let previous = previousEntries.swap(entries) - let animated: Bool - if let previous = previous { - animated = (entries.count - previous.count) < 20 - } else { - animated = false - } - return .single(preparedInviteContactsTransition(account: context.account, from: previous ?? [], to: entries, sortedContacts: sortedContacts, interaction: interaction, firstTime: previous == nil, isLoading: false, animated: animated)) + let previousContacts = currentSortedContacts.with { $0 } + let crossfade = previous != nil && previousContacts == nil + + return .single(preparedInviteContactsTransition(account: context.account, from: previous ?? [], to: entries, sortedContacts: sortedContacts, interaction: interaction, isLoading: sortedContacts == nil, firstTime: previous == nil, crossfade: crossfade)) } return signal |> runOn(processingQueue) - -// if OSAtomicCompareAndSwap32(1, 0, &firstTime) { -// return signal -// |> runOn(Queue.mainQueue()) -// } else { -// return signal -// |> runOn(processingQueue) -// } }) |> deliverOnMainQueue @@ -424,12 +412,10 @@ final class InviteContactsControllerNode: ASDisplayNode { self?.enqueueTransition(transition) }) - self.enqueueTransition(InviteContactsTransition(deletions: [], insertions: [], updates: [], sortedContacts: [], firstTime: true, isLoading: true, animated: false)) - shareImpl = { [weak self] in if let strongSelf = self { var result: [(DeviceContactBasicData, Int32)] = [] - for contact in (strongSelf.currentSortedContacts.with { $0 }) { + for contact in (strongSelf.currentSortedContacts.with { $0 } ?? []) { if strongSelf.selectionState.selectedContactIndices[contact.0] != nil { result.append((contact.1, contact.2)) } @@ -448,6 +434,7 @@ final class InviteContactsControllerNode: ASDisplayNode { private func updateThemeAndStrings() { self.backgroundColor = self.presentationData.theme.chatList.backgroundColor + self.listNode.backgroundColor = self.presentationData.theme.chatList.backgroundColor self.searchDisplayController?.updatePresentationData(self.presentationData) } @@ -571,8 +558,8 @@ final class InviteContactsControllerNode: ASDisplayNode { if transition.firstTime { options.insert(.Synchronous) options.insert(.LowLatency) - } else if transition.animated { - options.insert(.AnimateInsertion) + } else if transition.crossfade { + options.insert(.AnimateCrossfade) } self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateOpaqueState: nil, completion: { [weak self] _ in if let strongSelf = self { @@ -582,10 +569,16 @@ final class InviteContactsControllerNode: ASDisplayNode { let activityIndicator = ActivityIndicator(type: .custom(strongSelf.presentationData.theme.list.itemAccentColor, 22.0, 1.0, false)) strongSelf.activityIndicator = activityIndicator strongSelf.insertSubnode(activityIndicator, aboveSubnode: strongSelf.listNode) + + if let (layout, navigationHeight, actualNavigationBarHeight) = strongSelf.validLayout { + strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, actualNavigationBarHeight: actualNavigationBarHeight, transition: .immediate) + } } else if !transition.isLoading, let activityIndicator = strongSelf.activityIndicator { strongSelf.activityIndicator = nil activityIndicator.removeFromSupernode() } + + let _ = strongSelf.currentSortedContacts.swap(transition.sortedContacts) } }) } @@ -593,7 +586,7 @@ final class InviteContactsControllerNode: ASDisplayNode { } func selectAll() { - let ids = self.currentSortedContacts.with { $0 }.map { $0.0 } + let ids = self.currentSortedContacts.with { $0 }?.map { $0.0 } ?? [] var allSelected = true for id in ids { if self.selectionState.selectedContactIndices[id] == nil { diff --git a/submodules/TelegramUI/TelegramUI/ItemListMultilineTextItem.swift b/submodules/TelegramUI/TelegramUI/ItemListMultilineTextItem.swift index 868c59b8ab..8a0dd83357 100644 --- a/submodules/TelegramUI/TelegramUI/ItemListMultilineTextItem.swift +++ b/submodules/TelegramUI/TelegramUI/ItemListMultilineTextItem.swift @@ -19,7 +19,7 @@ enum TextLinkItem { class ItemListMultilineTextItem: ListViewItem, ItemListItem { let theme: PresentationTheme let text: String - let enabledEntitiyTypes: EnabledEntityTypes + let enabledEntityTypes: EnabledEntityTypes let sectionId: ItemListSectionId let style: ItemListStyle let action: (() -> Void)? @@ -30,10 +30,10 @@ class ItemListMultilineTextItem: ListViewItem, ItemListItem { let selectable: Bool - init(theme: PresentationTheme, text: String, enabledEntitiyTypes: EnabledEntityTypes, sectionId: ItemListSectionId, style: ItemListStyle, action: (() -> Void)? = nil, longTapAction: (() -> Void)? = nil, linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? = nil, tag: Any? = nil) { + init(theme: PresentationTheme, text: String, enabledEntityTypes: EnabledEntityTypes, sectionId: ItemListSectionId, style: ItemListStyle, action: (() -> Void)? = nil, longTapAction: (() -> Void)? = nil, linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? = nil, tag: Any? = nil) { self.theme = theme self.text = text - self.enabledEntitiyTypes = enabledEntitiyTypes + self.enabledEntityTypes = enabledEntityTypes self.sectionId = sectionId self.style = style self.action = action @@ -184,7 +184,7 @@ class ItemListMultilineTextItemNode: ListViewItemNode { leftInset = 16.0 + params.rightInset } - let entities = generateTextEntities(item.text, enabledTypes: item.enabledEntitiyTypes) + let entities = generateTextEntities(item.text, enabledTypes: item.enabledEntityTypes) let string = stringWithAppliedEntities(item.text, entities: entities, baseColor: textColor, linkColor: item.theme.list.itemAccentColor, baseFont: titleFont, linkFont: titleFont, boldFont: titleBoldFont, italicFont: titleItalicFont, boldItalicFont: titleBoldItalicFont, fixedFont: titleFixedFont) let (titleLayout, titleApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: string, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) diff --git a/submodules/TelegramUI/TelegramUI/ItemListStickerPackItem.swift b/submodules/TelegramUI/TelegramUI/ItemListStickerPackItem.swift index 3c42a2f593..08589d6e87 100644 --- a/submodules/TelegramUI/TelegramUI/ItemListStickerPackItem.swift +++ b/submodules/TelegramUI/TelegramUI/ItemListStickerPackItem.swift @@ -121,6 +121,8 @@ private let titleFont = Font.bold(15.0) private let statusFont = Font.regular(14.0) class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { + private var currentThumbnailItem: StickerPackThumbnailItem? + private let backgroundNode: ASDisplayNode private let topStripeNode: ASDisplayNode private let bottomStripeNode: ASDisplayNode @@ -128,6 +130,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { private var disabledOverlayNode: ASDisplayNode? fileprivate let imageNode: TransformImageNode + private var animationNode: AnimatedStickerNode? private let unreadNode: ASImageNode private let titleNode: TextNode private let statusNode: TextNode @@ -153,6 +156,17 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { } } + override var visibility: ListViewItemNodeVisibility { + didSet { + let wasVisible = oldValue != .none + let isVisible = self.visibility != .none + + if wasVisible != isVisible { + self.animationNode?.visibility = isVisible + } + } + } + init() { self.backgroundNode = ASDisplayNode() self.backgroundNode.isLayerBacked = true @@ -230,13 +244,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { let editableControlLayout = ItemListEditableControlNode.asyncLayout(self.editableControlNode) let reorderControlLayout = ItemListEditableReorderControlNode.asyncLayout(self.reorderControlNode) - var previousThumbnailItem: TelegramMediaImageRepresentation? - if let thumbnail = self.layoutParams?.0.packInfo.thumbnail { - previousThumbnailItem = thumbnail - } else if let item = self.layoutParams?.0.topItem, let dimensions = item.file.dimensions, let resource = chatMessageStickerResource(file: item.file, small: true) as? TelegramMediaResource { - previousThumbnailItem = TelegramMediaImageRepresentation(dimensions: dimensions, resource: resource) - } - + var previousThumbnailItem = self.currentThumbnailItem var currentDisabledOverlayNode = self.disabledOverlayNode let currentItem = self.layoutParams?.0 @@ -324,31 +332,47 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { currentDisabledOverlayNode = nil } - var thumbnailItem: TelegramMediaImageRepresentation? + var thumbnailItem: StickerPackThumbnailItem? var resourceReference: MediaResourceReference? if let thumbnail = item.packInfo.thumbnail { - thumbnailItem = thumbnail - resourceReference = MediaResourceReference.stickerPackThumbnail(stickerPack: .id(id: item.packInfo.id.id, accessHash: item.packInfo.accessHash), resource: thumbnail.resource) - } else if let item = item.topItem, let dimensions = item.file.dimensions, let resource = chatMessageStickerResource(file: item.file, small: true) as? TelegramMediaResource { - thumbnailItem = TelegramMediaImageRepresentation(dimensions: dimensions, resource: resource) - resourceReference = MediaResourceReference.media(media: .standalone(media: item.file), resource: resource) - } - - let fileUpdated = thumbnailItem != previousThumbnailItem - - var imageApply: (() -> Void)? - var imageSize: CGSize = CGSize(width: 34.0, height: 34.0) - if let thumbnailItem = thumbnailItem { - let imageBoundingSize = CGSize(width: 34.0, height: 34.0) - imageSize = thumbnailItem.dimensions.aspectFitted(imageBoundingSize) - imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets())) + if item.packInfo.flags.contains(.isAnimated) { + thumbnailItem = .animated(thumbnail.resource) + resourceReference = MediaResourceReference.stickerPackThumbnail(stickerPack: .id(id: item.packInfo.id.id, accessHash: item.packInfo.accessHash), resource: thumbnail.resource) + } else { + thumbnailItem = .still(thumbnail) + resourceReference = MediaResourceReference.stickerPackThumbnail(stickerPack: .id(id: item.packInfo.id.id, accessHash: item.packInfo.accessHash), resource: thumbnail.resource) + } + } else if let item = item.topItem { + if item.file.isAnimatedSticker { + thumbnailItem = .animated(item.file.resource) + resourceReference = MediaResourceReference.media(media: .standalone(media: item.file), resource: item.file.resource) + } else if let dimensions = item.file.dimensions, let resource = chatMessageStickerResource(file: item.file, small: true) as? TelegramMediaResource { + thumbnailItem = .still(TelegramMediaImageRepresentation(dimensions: dimensions, resource: resource)) + resourceReference = MediaResourceReference.media(media: .standalone(media: item.file), resource: resource) + } } var updatedImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? var updatedFetchSignal: Signal? + + let imageBoundingSize = CGSize(width: 34.0, height: 34.0) + var imageApply: (() -> Void)? + let fileUpdated = thumbnailItem != previousThumbnailItem + + var imageSize: CGSize? + if fileUpdated { if let thumbnailItem = thumbnailItem { - updatedImageSignal = chatMessageStickerPackThumbnail(postbox: item.account.postbox, representation: thumbnailItem) + switch thumbnailItem { + case let .still(representation): + let stillImageSize = representation.dimensions.aspectFitted(imageBoundingSize) + imageSize = stillImageSize + + imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: stillImageSize, boundingSize: stillImageSize, intrinsicInsets: UIEdgeInsets())) + updatedImageSignal = chatMessageStickerPackThumbnail(postbox: item.account.postbox, representation: representation) + case .animated: + imageSize = imageBoundingSize + } if let resourceReference = resourceReference { updatedFetchSignal = fetchedMediaResource(postbox: item.account.postbox, reference: resourceReference) } @@ -361,6 +385,10 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { return (layout, { [weak self] animated in if let strongSelf = self { strongSelf.layoutParams = (item, params, neighbors) + + if fileUpdated { + strongSelf.currentThumbnailItem = thumbnailItem + } if let _ = updatedTheme { strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor @@ -516,7 +544,27 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { transition.updateFrame(node: strongSelf.statusNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 32.0), size: statusLayout.size)) let boundingSize = CGSize(width: 34.0, height: 34.0) - transition.updateFrame(node: strongSelf.imageNode, frame: CGRect(origin: CGPoint(x: params.leftInset + revealOffset + editingOffset + 15.0 + floor((boundingSize.width - imageSize.width) / 2.0), y: 11.0 + floor((boundingSize.height - imageSize.height) / 2.0)), size: imageSize)) + if let thumbnailItem = thumbnailItem, let imageSize = imageSize { + let imageFrame = CGRect(origin: CGPoint(x: params.leftInset + revealOffset + editingOffset + 15.0 + floor((boundingSize.width - imageSize.width) / 2.0), y: 11.0 + floor((boundingSize.height - imageSize.height) / 2.0)), size: imageSize) + switch thumbnailItem { + case .still: + transition.updateFrame(node: strongSelf.imageNode, frame: imageFrame) + case let .animated(resource): + let animationNode: AnimatedStickerNode + if let current = strongSelf.animationNode { + animationNode = current + } else { + animationNode = AnimatedStickerNode() + strongSelf.animationNode = animationNode + strongSelf.addSubnode(animationNode) + animationNode.setup(account: item.account, resource: resource, width: 80, height: 80, mode: .cached) + animationNode.visibility = strongSelf.visibility != .none + } + if let animationNode = strongSelf.animationNode { + transition.updateFrame(node: animationNode, frame: imageFrame) + } + } + } if let updatedImageSignal = updatedImageSignal { strongSelf.imageNode.setSignal(updatedImageSignal) diff --git a/submodules/TelegramUI/TelegramUI/ItemListTextWithLabelItem.swift b/submodules/TelegramUI/TelegramUI/ItemListTextWithLabelItem.swift index 43ba6a026c..774113f7eb 100644 --- a/submodules/TelegramUI/TelegramUI/ItemListTextWithLabelItem.swift +++ b/submodules/TelegramUI/TelegramUI/ItemListTextWithLabelItem.swift @@ -18,7 +18,7 @@ final class ItemListTextWithLabelItem: ListViewItem, ItemListItem { let style: ItemListStyle let labelColor: ItemListTextWithLabelItemTextColor let textColor: ItemListTextWithLabelItemTextColor - let enabledEntitiyTypes: EnabledEntityTypes + let enabledEntityTypes: EnabledEntityTypes let multiline: Bool let selected: Bool? let sectionId: ItemListSectionId @@ -28,14 +28,14 @@ final class ItemListTextWithLabelItem: ListViewItem, ItemListItem { let tag: Any? - init(theme: PresentationTheme, label: String, text: String, style: ItemListStyle = .plain, labelColor: ItemListTextWithLabelItemTextColor = .primary, textColor: ItemListTextWithLabelItemTextColor = .primary, enabledEntitiyTypes: EnabledEntityTypes, multiline: Bool, selected: Bool? = nil, sectionId: ItemListSectionId, action: (() -> Void)?, longTapAction: (() -> Void)? = nil, linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? = nil, tag: Any? = nil) { + init(theme: PresentationTheme, label: String, text: String, style: ItemListStyle = .plain, labelColor: ItemListTextWithLabelItemTextColor = .primary, textColor: ItemListTextWithLabelItemTextColor = .primary, enabledEntityTypes: EnabledEntityTypes, multiline: Bool, selected: Bool? = nil, sectionId: ItemListSectionId, action: (() -> Void)?, longTapAction: (() -> Void)? = nil, linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? = nil, tag: Any? = nil) { self.theme = theme self.label = label self.text = text self.style = style self.labelColor = labelColor self.textColor = textColor - self.enabledEntitiyTypes = enabledEntitiyTypes + self.enabledEntityTypes = enabledEntityTypes self.multiline = multiline self.selected = selected self.sectionId = sectionId @@ -199,7 +199,7 @@ class ItemListTextWithLabelItemNode: ListViewItemNode { } let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.label, font: labelFont, textColor: labelColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftOffset - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - let entities = generateTextEntities(item.text, enabledTypes: item.enabledEntitiyTypes) + let entities = generateTextEntities(item.text, enabledTypes: item.enabledEntityTypes) let baseColor: UIColor switch item.textColor { case .primary: diff --git a/submodules/TelegramUI/TelegramUI/MediaInputPaneTrendingItem.swift b/submodules/TelegramUI/TelegramUI/MediaInputPaneTrendingItem.swift index 64d2cef179..04cf0a09bf 100644 --- a/submodules/TelegramUI/TelegramUI/MediaInputPaneTrendingItem.swift +++ b/submodules/TelegramUI/TelegramUI/MediaInputPaneTrendingItem.swift @@ -66,7 +66,7 @@ private let titleFont = Font.bold(16.0) private let statusFont = Font.regular(15.0) private let buttonFont = Font.medium(13.0) -private final class TrendingTopItemNode: ASDisplayNode { +final class TrendingTopItemNode: ASDisplayNode { private let imageNode: TransformImageNode private var animationNode: AnimatedStickerNode? public private(set) var file: TelegramMediaFile? = nil diff --git a/submodules/TelegramUI/TelegramUI/PeerMediaCollectionController.swift b/submodules/TelegramUI/TelegramUI/PeerMediaCollectionController.swift index 0d7893b84a..16fc139bfc 100644 --- a/submodules/TelegramUI/TelegramUI/PeerMediaCollectionController.swift +++ b/submodules/TelegramUI/TelegramUI/PeerMediaCollectionController.swift @@ -346,7 +346,7 @@ public class PeerMediaCollectionController: TelegramController { }, lockMediaRecording: { }, deleteRecordedMedia: { }, sendRecordedMedia: { - }, displayRestrictedInfo: { _ in + }, displayRestrictedInfo: { _, _ in }, displayVideoUnmuteTip: { _ in }, switchMediaRecordingMode: { }, setupMessageAutoremoveTimeout: { diff --git a/submodules/TelegramUI/TelegramUI/StickerPaneSearchGlobaltem.swift b/submodules/TelegramUI/TelegramUI/StickerPaneSearchGlobaltem.swift index d40ab5790f..65bce49a26 100644 --- a/submodules/TelegramUI/TelegramUI/StickerPaneSearchGlobaltem.swift +++ b/submodules/TelegramUI/TelegramUI/StickerPaneSearchGlobaltem.swift @@ -78,30 +78,6 @@ private let titleFont = Font.bold(16.0) private let statusFont = Font.regular(15.0) private let buttonFont = Font.medium(13.0) -private final class TrendingTopItemNode: TransformImageNode { - var file: TelegramMediaFile? = nil - let loadDisposable = MetaDisposable() - - var currentIsPreviewing = false - - func updatePreviewing(animated: Bool, isPreviewing: Bool) { - if self.currentIsPreviewing != isPreviewing { - self.currentIsPreviewing = isPreviewing - - if isPreviewing { - if animated { - self.layer.animateSpring(from: 1.0 as NSNumber, to: 0.8 as NSNumber, keyPath: "transform.scale", duration: 0.4, removeOnCompletion: false) - } - } else { - self.layer.removeAnimation(forKey: "transform.scale") - if animated { - self.layer.animateSpring(from: 0.8 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5) - } - } - } - } -} - class StickerPaneSearchGlobalItemNode: GridItemNode { private let titleNode: TextNode private let descriptionNode: TextNode @@ -115,6 +91,16 @@ class StickerPaneSearchGlobalItemNode: GridItemNode { private var appliedItem: StickerPaneSearchGlobalItem? private let preloadDisposable = MetaDisposable() + override var isVisibleInGrid: Bool { + didSet { + if oldValue != self.isVisibleInGrid { + for node in self.itemNodes { + node.visibility = self.isVisibleInGrid + } + } + } + } + override init() { self.titleNode = TextNode() self.titleNode.isUserInteractionEnabled = false @@ -270,9 +256,14 @@ class StickerPaneSearchGlobalItemNode: GridItemNode { strongSelf.unreadNode.frame = CGRect(origin: CGPoint(x: titleFrame.maxX + 2.0, y: titleFrame.minY + 7.0), size: image.size) } - var offset: CGFloat = params.leftInset + leftInset - let itemSize = CGSize(width: 68.0, height: 68.0) - + let sideInset: CGFloat = 2.0 + let availableWidth = params.width - params.leftInset - params.rightInset - sideInset * 2.0 + var itemSide: CGFloat = floor(availableWidth / 5.0) + itemSide = min(itemSide, 75.0) + let itemSize = CGSize(width: itemSide, height: itemSide) + var offset = sideInset + let itemSpacing = (max(0, availableWidth - 5.0 * itemSide - sideInset * 2.0)) / 4.0 + for i in 0 ..< topItems.count { let file = topItems[i].file let node: TrendingTopItemNode @@ -280,20 +271,17 @@ class StickerPaneSearchGlobalItemNode: GridItemNode { node = strongSelf.itemNodes[i] } else { node = TrendingTopItemNode() - node.contentAnimations = [.subsequentUpdates] + node.visibility = strongSelf.isVisibleInGrid strongSelf.itemNodes.append(node) strongSelf.addSubnode(node) } if file.fileId != node.file?.fileId { - node.file = file - node.setSignal(chatMessageSticker(account: item.account, file: file, small: true)) - node.loadDisposable.set(freeMediaFileResourceInteractiveFetched(account: item.account, fileReference: stickerPackFileReference(file), resource: chatMessageStickerResource(file: file, small: true)).start()) + node.setup(account: item.account, item: topItems[i], itemSize: itemSize, synchronousLoads: false) } if let dimensions = file.dimensions { let imageSize = dimensions.aspectFitted(itemSize) - node.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))() node.frame = CGRect(origin: CGPoint(x: offset, y: 48.0 + topOffset), size: imageSize) - offset += imageSize.width + 4.0 + offset += itemSize.width + itemSpacing } } diff --git a/submodules/TelegramUI/TelegramUI/TextLinkHandling.swift b/submodules/TelegramUI/TelegramUI/TextLinkHandling.swift new file mode 100644 index 0000000000..9cf383f43d --- /dev/null +++ b/submodules/TelegramUI/TelegramUI/TextLinkHandling.swift @@ -0,0 +1,157 @@ +import Foundation +import UIKit +import TelegramCore +import Postbox +import Display +import SwiftSignalKit +import TelegramUIPreferences + +import SafariServices + +func handleTextLinkAction(context: AccountContext, peerId: PeerId?, navigateDisposable: MetaDisposable, controller: ViewController, action: TextLinkItemActionType, itemLink: TextLinkItem) { + let presentImpl: (ViewController, Any?) -> Void = { controllerToPresent, _ in + controller.present(controllerToPresent, in: .window(.root)) + } + + let openResolvedPeerImpl: (PeerId?, ChatControllerInteractionNavigateToPeer) -> Void = { [weak controller] peerId, navigation in + openResolvedUrl(.peer(peerId, navigation), context: context, navigationController: (controller?.navigationController as? NavigationController), openPeer: { (peerId, navigation) in + switch navigation { + case let .chat(_, messageId): + if let navigationController = controller?.navigationController as? NavigationController { + navigateToChatController(navigationController: navigationController, context: context, chatLocation: .peer(peerId), messageId: messageId, keepStack: .always) + } + case .info: + let peerSignal: Signal + peerSignal = context.account.postbox.loadedPeerWithId(peerId) |> map(Optional.init) + navigateDisposable.set((peerSignal |> take(1) |> deliverOnMainQueue).start(next: { peer in + if let controller = controller, let peer = peer { + if let infoController = peerInfoController(context: context, peer: peer) { + (controller.navigationController as? NavigationController)?.pushViewController(infoController) + } + } + })) + default: + break + } + }, present: presentImpl, dismissInput: {}) + } + + let openLinkImpl: (String) -> Void = { [weak controller] url in + navigateDisposable.set((resolveUrl(account: context.account, url: url) |> deliverOnMainQueue).start(next: { result in + if let controller = controller { + switch result { + case let .externalUrl(url): + context.sharedContext.applicationBindings.openUrl(url) + case let .peer(peerId, _): + openResolvedPeerImpl(peerId, .default) + case let .channelMessage(peerId, messageId): + if let navigationController = controller.navigationController as? NavigationController { + navigateToChatController(navigationController: navigationController, context: context, chatLocation: .peer(peerId), messageId: messageId) + } + case let .stickerPack(name): + controller.present(StickerPackPreviewController(context: context, stickerPack: .name(name), parentNavigationController: controller.navigationController as? NavigationController), in: .window(.root)) + case let .instantView(webpage, anchor): + (controller.navigationController as? NavigationController)?.pushViewController(InstantPageController(context: context, webPage: webpage, sourcePeerType: .group, anchor: anchor)) + case let .join(link): + controller.present(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peerId in + openResolvedPeerImpl(peerId, .chat(textInputState: nil, messageId: nil)) + }), in: .window(.root)) + default: + break + } + } + })) + } + + let openPeerMentionImpl: (String) -> Void = { mention in + navigateDisposable.set((resolvePeerByName(account: context.account, name: mention, ageLimit: 10) |> take(1) |> deliverOnMainQueue).start(next: { peerId in + openResolvedPeerImpl(peerId, .default) + })) + } + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + switch action { + case .tap: + switch itemLink { + case let .url(url): + openLinkImpl(url) + case let .mention(mention): + openPeerMentionImpl(mention) + case let .hashtag(_, hashtag): + if let peerId = peerId { + let peerSignal = context.account.postbox.loadedPeerWithId(peerId) + let _ = (peerSignal + |> deliverOnMainQueue).start(next: { peer in + let searchController = HashtagSearchController(context: context, peer: peer, query: hashtag) + (controller.navigationController as? NavigationController)?.pushViewController(searchController) + }) + } + } + case .longTap: + switch itemLink { + case let .url(url): + let canOpenIn = availableOpenInOptions(context: context, item: .url(url: url)).count > 1 + let openText = canOpenIn ? presentationData.strings.Conversation_FileOpenIn : presentationData.strings.Conversation_LinkDialogOpen + let actionSheet = ActionSheetController(presentationTheme: presentationData.theme) + actionSheet.setItemGroups([ActionSheetItemGroup(items: [ + ActionSheetTextItem(title: url), + ActionSheetButtonItem(title: openText, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + openLinkImpl(url) + }), + ActionSheetButtonItem(title: presentationData.strings.ShareMenu_CopyShareLink, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + UIPasteboard.general.string = url + }), + ActionSheetButtonItem(title: presentationData.strings.Conversation_AddToReadingList, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + if let link = URL(string: url) { + let _ = try? SSReadingList.default()?.addItem(with: link, title: nil, previewText: nil) + } + }) + ]), ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ])]) + controller.present(actionSheet, in: .window(.root)) + case let .mention(mention): + let actionSheet = ActionSheetController(presentationTheme: presentationData.theme) + actionSheet.setItemGroups([ActionSheetItemGroup(items: [ + ActionSheetTextItem(title: mention), + ActionSheetButtonItem(title: presentationData.strings.Conversation_LinkDialogOpen, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + openPeerMentionImpl(mention) + }), + ActionSheetButtonItem(title: presentationData.strings.Conversation_LinkDialogCopy, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + UIPasteboard.general.string = mention + }) + ]), ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ])]) + controller.present(actionSheet, in: .window(.root)) + case let .hashtag(_, hashtag): + let actionSheet = ActionSheetController(presentationTheme: presentationData.theme) + actionSheet.setItemGroups([ActionSheetItemGroup(items: [ + ActionSheetTextItem(title: hashtag), + ActionSheetButtonItem(title: presentationData.strings.Conversation_LinkDialogOpen, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + let searchController = HashtagSearchController(context: context, peer: nil, query: hashtag) + (controller.navigationController as? NavigationController)?.pushViewController(searchController) + }), + ActionSheetButtonItem(title: presentationData.strings.Conversation_LinkDialogCopy, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + UIPasteboard.general.string = hashtag + }) + ]), ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ])]) + controller.present(actionSheet, in: .window(.root)) + } + } +} diff --git a/submodules/TelegramUI/TelegramUI/ThemeSettingsController.swift b/submodules/TelegramUI/TelegramUI/ThemeSettingsController.swift index 5813018150..6ecb190d68 100644 --- a/submodules/TelegramUI/TelegramUI/ThemeSettingsController.swift +++ b/submodules/TelegramUI/TelegramUI/ThemeSettingsController.swift @@ -322,9 +322,17 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The let _ = telegramWallpapers(postbox: context.account.postbox, network: context.account.network).start() - let availableAppIcons: Signal<[PresentationAppIcon], NoError> = .single(context.sharedContext.applicationBindings.getAvailableAlternateIcons()) + let currentAppIcon: PresentationAppIcon? + let appIcons = context.sharedContext.applicationBindings.getAvailableAlternateIcons() + if let alternateIconName = context.sharedContext.applicationBindings.getAlternateIconName() { + currentAppIcon = appIcons.filter { $0.name == alternateIconName }.first + } else { + currentAppIcon = appIcons.filter { $0.isDefault }.first + } + + let availableAppIcons: Signal<[PresentationAppIcon], NoError> = .single(appIcons) let currentAppIconName = ValuePromise() - currentAppIconName.set(context.sharedContext.applicationBindings.getAlternateIconName() ?? "Blue") + currentAppIconName.set(currentAppIcon?.name ?? "Blue") let arguments = ThemeSettingsControllerArguments(context: context, selectTheme: { theme in let _ = (context.sharedContext.accountManager.transaction { transaction -> Void in diff --git a/submodules/TelegramUI/TelegramUI/UpdateInfoController.swift b/submodules/TelegramUI/TelegramUI/UpdateInfoController.swift index 2c43d29f14..02bea114a0 100644 --- a/submodules/TelegramUI/TelegramUI/UpdateInfoController.swift +++ b/submodules/TelegramUI/TelegramUI/UpdateInfoController.swift @@ -8,9 +8,11 @@ import TelegramPresentationData private final class UpdateInfoControllerArguments { let openAppStorePage: () -> Void + let linkAction: (TextLinkItemActionType, TextLinkItem) -> Void - init(openAppStorePage: @escaping () -> Void) { + init(openAppStorePage: @escaping () -> Void, linkAction: @escaping (TextLinkItemActionType, TextLinkItem) -> Void) { self.openAppStorePage = openAppStorePage + self.linkAction = linkAction } } @@ -20,7 +22,7 @@ private enum UpdateInfoControllerSection: Int32 { } private enum UpdateInfoControllerEntry: ItemListNodeEntry { - case info(PresentationTheme, String, String, [MessageTextEntity]) + case info(PresentationTheme, PresentationAppIcon?, String, String, [MessageTextEntity]) case update(PresentationTheme, String) var section: ItemListSectionId { @@ -43,8 +45,8 @@ private enum UpdateInfoControllerEntry: ItemListNodeEntry { static func ==(lhs: UpdateInfoControllerEntry, rhs: UpdateInfoControllerEntry) -> Bool { switch lhs { - case let .info(lhsTheme, lhsTitle, lhsText, lhsEntities): - if case let .info(rhsTheme, rhsTitle, rhsText, rhsEntities) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsText == rhsText, lhsEntities == rhsEntities { + case let .info(lhsTheme, lhsIcon, lhsTitle, lhsText, lhsEntities): + if case let .info(rhsTheme, rhsIcon, rhsTitle, rhsText, rhsEntities) = rhs, lhsTheme === rhsTheme, lhsIcon == rhsIcon, lhsTitle == rhsTitle, lhsText == rhsText, lhsEntities == rhsEntities { return true } else { return false @@ -64,9 +66,10 @@ private enum UpdateInfoControllerEntry: ItemListNodeEntry { func item(_ arguments: UpdateInfoControllerArguments) -> ListViewItem { switch self { - case let .info(theme, title, text, entities): - let text = stringWithAppliedEntities(text, entities: entities, baseColor: theme.list.itemPrimaryTextColor, linkColor: theme.list.itemAccentColor, baseFont: Font.regular(14.0), linkFont: Font.regular(14.0), boldFont: Font.bold(14.0), italicFont: Font.italic(14.0), boldItalicFont: Font.semiboldItalic(14.0), fixedFont: Font.monospace(14.0)) - return ItemListSectionHeaderItem(theme: theme, text: text.string, sectionId: self.section) + case let .info(theme, icon, title, text, entities): + return UpdateInfoItem(theme: theme, appIcon: icon, title: title, text: text, entities: entities, sectionId: self.section, style: .blocks, linkItemAction: { action, itemLink in + arguments.linkAction(action, itemLink) + }) case let .update(theme, title): return ItemListActionItem(theme: theme, title: title, kind: .generic, alignment: .center, sectionId: self.section, style: .blocks, action: { arguments.openAppStorePage() @@ -75,10 +78,10 @@ private enum UpdateInfoControllerEntry: ItemListNodeEntry { } } -private func updateInfoControllerEntries(theme: PresentationTheme, strings: PresentationStrings, appUpdateInfo: AppUpdateInfo) -> [UpdateInfoControllerEntry] { +private func updateInfoControllerEntries(theme: PresentationTheme, strings: PresentationStrings, appIcon: PresentationAppIcon?, appUpdateInfo: AppUpdateInfo) -> [UpdateInfoControllerEntry] { var entries: [UpdateInfoControllerEntry] = [] - entries.append(.info(theme, strings.Update_AppVersion(appUpdateInfo.version).0, appUpdateInfo.text, appUpdateInfo.entities)) + entries.append(.info(theme, appIcon, strings.Update_AppVersion(appUpdateInfo.version).0, appUpdateInfo.text, appUpdateInfo.entities)) entries.append(.update(theme, strings.Update_UpdateApp)) return entries @@ -86,24 +89,48 @@ private func updateInfoControllerEntries(theme: PresentationTheme, strings: Pres public func updateInfoController(context: AccountContext, appUpdateInfo: AppUpdateInfo) -> ViewController { var dismissImpl: (() -> Void)? + var linkActionImpl: ((TextLinkItemActionType, TextLinkItem) -> Void)? + + let actionsDisposable = DisposableSet() + + let navigateDisposable = MetaDisposable() + actionsDisposable.add(navigateDisposable) let arguments = UpdateInfoControllerArguments(openAppStorePage: { context.sharedContext.applicationBindings.openAppStorePage() + }, linkAction: { action, itemLink in + linkActionImpl?(action, itemLink) }) let signal = context.sharedContext.presentationData |> deliverOnMainQueue |> map { presentationData -> (ItemListControllerState, (ItemListNodeState, UpdateInfoControllerEntry.ItemGenerationArguments)) in - let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Update_Skip), style: .regular, enabled: true, action: { + let appIcon: PresentationAppIcon? + let appIcons = context.sharedContext.applicationBindings.getAvailableAlternateIcons() + if let alternateIconName = context.sharedContext.applicationBindings.getAlternateIconName() { + appIcon = appIcons.filter { $0.name == alternateIconName }.first + } else { + appIcon = appIcons.filter { $0.isDefault }.first + } + + let leftNavigationButton = appUpdateInfo.popup ? ItemListNavigationButton(content: .text(presentationData.strings.Update_Skip), style: .regular, enabled: true, action: { dismissImpl?() - }) + }) : nil let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Update_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back)) - let listState = ItemListNodeState(entries: updateInfoControllerEntries(theme: presentationData.theme, strings: presentationData.strings, appUpdateInfo: appUpdateInfo), style: .blocks, animateChanges: false) + let listState = ItemListNodeState(entries: updateInfoControllerEntries(theme: presentationData.theme, strings: presentationData.strings, appIcon: appIcon, appUpdateInfo: appUpdateInfo), style: .blocks, animateChanges: false) return (controllerState, (listState, arguments)) } + |> afterDisposed { + actionsDisposable.dispose() + } let controller = ItemListController(sharedContext: context.sharedContext, state: signal) + linkActionImpl = { [weak controller] action, itemLink in + if let strongController = controller { + handleTextLinkAction(context: context, peerId: nil, navigateDisposable: navigateDisposable, controller: strongController, action: action, itemLink: itemLink) + } + } dismissImpl = { [weak controller] in controller?.view.endEditing(true) controller?.dismiss() diff --git a/submodules/TelegramUI/TelegramUI/UpdateInfoItem.swift b/submodules/TelegramUI/TelegramUI/UpdateInfoItem.swift new file mode 100644 index 0000000000..e5cf15cb24 --- /dev/null +++ b/submodules/TelegramUI/TelegramUI/UpdateInfoItem.swift @@ -0,0 +1,443 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import SwiftSignalKit +import TelegramCore +import TelegramPresentationData + +private func generateBorderImage(theme: PresentationTheme, bordered: Bool) -> UIImage? { + return generateImage(CGSize(width: 30.0, height: 30.0), rotatedContext: { size, context in + let bounds = CGRect(origin: CGPoint(), size: size) + context.setFillColor(theme.list.itemBlocksBackgroundColor.cgColor) + context.fill(bounds) + + context.setBlendMode(.clear) + context.fillEllipse(in: bounds) + context.setBlendMode(.normal) + + if bordered { + let lineWidth: CGFloat = 1.0 + context.setLineWidth(lineWidth) + context.setStrokeColor(theme.list.disclosureArrowColor.withAlphaComponent(0.4).cgColor) + context.strokeEllipse(in: bounds.insetBy(dx: lineWidth / 2.0, dy: lineWidth / 2.0)) + } + })?.stretchableImage(withLeftCapWidth: 15, topCapHeight: 15) +} + +class UpdateInfoItem: ListViewItem, ItemListItem { + let theme: PresentationTheme + let appIcon: PresentationAppIcon? + let title: String + let text: String + let entities: [MessageTextEntity] + let sectionId: ItemListSectionId + let style: ItemListStyle + let linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? + + let tag: Any? + + let selectable: Bool = false + + init(theme: PresentationTheme, appIcon: PresentationAppIcon?, title: String, text: String, entities: [MessageTextEntity], sectionId: ItemListSectionId, style: ItemListStyle, linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? = nil, tag: Any? = nil) { + self.theme = theme + self.appIcon = appIcon + self.title = title + self.text = text + self.entities = entities + self.sectionId = sectionId + self.style = style + self.linkItemAction = linkItemAction + self.tag = tag + } + + func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { + async { + let node = UpdateInfoItemNode() + let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem)) + + node.contentSize = layout.contentSize + node.insets = layout.insets + + Queue.mainQueue().async { + completion(node, { + return (nil, { _ in apply() }) + }) + } + } + } + + func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { + Queue.mainQueue().async { + if let nodeValue = node() as? UpdateInfoItemNode { + let makeLayout = nodeValue.asyncLayout() + + async { + let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem)) + Queue.mainQueue().async { + completion(layout, { _ in + apply() + }) + } + } + } + } + } +} + +private let titleFont = Font.bold(17.0) +private let textFont = Font.regular(16.0) +private let textBoldFont = Font.medium(16.0) +private let textItalicFont = Font.italic(16.0) +private let textBoldItalicFont = Font.semiboldItalic(16.0) +private let textFixedFont = Font.regular(16.0) + +class UpdateInfoItemNode: ListViewItemNode { + private let backgroundNode: ASDisplayNode + private let topStripeNode: ASDisplayNode + private let bottomStripeNode: ASDisplayNode + private let highlightedBackgroundNode: ASDisplayNode + private var linkHighlightingNode: LinkHighlightingNode? + + private let iconNode: ASImageNode + private let overlayNode: ASImageNode + private let titleNode: TextNode + private let textNode: TextNode + + private let activateArea: AccessibilityAreaNode + + private var item: UpdateInfoItem? + + var tag: Any? { + return self.item?.tag + } + + init() { + self.backgroundNode = ASDisplayNode() + self.backgroundNode.isLayerBacked = true + self.backgroundNode.backgroundColor = .white + + self.topStripeNode = ASDisplayNode() + self.topStripeNode.isLayerBacked = true + + self.bottomStripeNode = ASDisplayNode() + self.bottomStripeNode.isLayerBacked = true + + self.iconNode = ASImageNode() + self.iconNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 62.0, height: 62.0)) + self.iconNode.isLayerBacked = true + + self.overlayNode = ASImageNode() + self.overlayNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 62.0, height: 62.0)) + self.overlayNode.isLayerBacked = true + + self.titleNode = TextNode() + self.titleNode.isUserInteractionEnabled = false + + self.textNode = TextNode() + self.textNode.isUserInteractionEnabled = false + self.textNode.contentMode = .left + self.textNode.contentsScale = UIScreen.main.scale + + self.highlightedBackgroundNode = ASDisplayNode() + self.highlightedBackgroundNode.isLayerBacked = true + + self.activateArea = AccessibilityAreaNode() + + super.init(layerBacked: false, dynamicBounce: false) + + self.addSubnode(self.iconNode) + self.addSubnode(self.overlayNode) + self.addSubnode(self.titleNode) + self.addSubnode(self.textNode) + self.addSubnode(self.activateArea) + } + + override func didLoad() { + super.didLoad() + + let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:))) + recognizer.tapActionAtPoint = { [weak self] point in + if let strongSelf = self, strongSelf.linkItemAtPoint(point) != nil { + return .waitForSingleTap + } + return .fail + } + recognizer.highlight = { [weak self] point in + if let strongSelf = self { + strongSelf.updateTouchesAtPoint(point) + } + } + self.view.addGestureRecognizer(recognizer) + } + + func asyncLayout() -> (_ item: UpdateInfoItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { + let makeTitleLayout = TextNode.asyncLayout(self.titleNode) + let makeTextLayout = TextNode.asyncLayout(self.textNode) + + let currentItem = self.item + + return { item, params, neighbors in + var updatedTheme: PresentationTheme? + var updatedAppIcon: PresentationAppIcon? + + if currentItem?.theme !== item.theme { + updatedTheme = item.theme + } + + if currentItem?.appIcon != item.appIcon { + updatedAppIcon = item.appIcon + } + + let textColor: UIColor = item.theme.list.itemPrimaryTextColor + + let inset: CGFloat + let itemBackgroundColor: UIColor + let itemSeparatorColor: UIColor + + switch item.style { + case .plain: + itemBackgroundColor = item.theme.list.plainBackgroundColor + itemSeparatorColor = item.theme.list.itemPlainSeparatorColor + inset = 14.0 + params.leftInset + case .blocks: + itemBackgroundColor = item.theme.list.itemBlocksBackgroundColor + itemSeparatorColor = item.theme.list.itemBlocksSeparatorColor + inset = 14.0 + params.rightInset + } + + let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: textColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 88.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + let string = stringWithAppliedEntities(item.text, entities: item.entities, baseColor: textColor, linkColor: item.theme.list.itemAccentColor, baseFont: textFont, linkFont: textFont, boldFont: textBoldFont, italicFont: textItalicFont, boldItalicFont: textBoldItalicFont, fixedFont: textFixedFont) + let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: string, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 28.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + let contentSize: CGSize + let insets: UIEdgeInsets + let separatorHeight = UIScreenPixel + + switch item.style { + case .plain: + contentSize = CGSize(width: params.width, height: 88.0 + textLayout.size.height + inset) + insets = itemListNeighborsPlainInsets(neighbors) + case .blocks: + contentSize = CGSize(width: params.width, height: 88.0 + textLayout.size.height + inset) + insets = itemListNeighborsGroupedInsets(neighbors) + } + + let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) + let layoutSize = layout.size + + return (layout, { [weak self] in + if let strongSelf = self { + strongSelf.item = item + + strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: CGSize(width: params.width - params.leftInset - params.rightInset, height: layout.contentSize.height)) + strongSelf.activateArea.accessibilityLabel = item.text + + if let _ = updatedTheme { + strongSelf.topStripeNode.backgroundColor = itemSeparatorColor + strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor + strongSelf.backgroundNode.backgroundColor = itemBackgroundColor + strongSelf.highlightedBackgroundNode.backgroundColor = item.theme.list.itemHighlightedBackgroundColor + + var bordered = true + if let appIcon = item.appIcon { + switch appIcon.name { + case "BlueFilled": + bordered = false + case "BlackFilled": + bordered = false + default: + break + } + } + + strongSelf.overlayNode.image = generateBorderImage(theme: item.theme, bordered: bordered) + } + + if let appIcon = updatedAppIcon, let image = UIImage(named: appIcon.imageName, in: Bundle.main, compatibleWith: nil) { + strongSelf.iconNode.image = image + } + + let _ = titleApply() + let _ = textApply() + + switch item.style { + case .plain: + if strongSelf.backgroundNode.supernode != nil { + strongSelf.backgroundNode.removeFromSupernode() + } + if strongSelf.topStripeNode.supernode != nil { + strongSelf.topStripeNode.removeFromSupernode() + } + if strongSelf.bottomStripeNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 0) + } + + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: inset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - inset, height: separatorHeight)) + case .blocks: + if strongSelf.backgroundNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0) + } + if strongSelf.topStripeNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1) + } + if strongSelf.bottomStripeNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2) + } + switch neighbors.top { + case .sameSection(false): + strongSelf.topStripeNode.isHidden = true + default: + strongSelf.topStripeNode.isHidden = false + } + let bottomStripeInset: CGFloat + let bottomStripeOffset: CGFloat + switch neighbors.bottom { + case .sameSection(false): + bottomStripeInset = 16.0 + bottomStripeOffset = -separatorHeight + default: + bottomStripeInset = 0.0 + bottomStripeOffset = 0.0 + } + strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) + strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)) + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)) + } + + let iconFrame = CGRect(origin: CGPoint(x: inset, y: inset), size: CGSize(width: 62.0, height: 62.0)) + strongSelf.iconNode.frame = iconFrame + strongSelf.overlayNode.frame = iconFrame + + strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: iconFrame.maxX + inset, y: iconFrame.minY + ceil((iconFrame.height - titleLayout.size.height) / 2.0)), size: titleLayout.size) + strongSelf.textNode.frame = CGRect(origin: CGPoint(x: inset, y: iconFrame.maxY + inset), size: textLayout.size) + + strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: layout.contentSize.height + UIScreenPixel + UIScreenPixel)) + } + }) + } + } + + override func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) { + super.setHighlighted(highlighted, at: point, animated: animated) + + if highlighted && self.linkItemAtPoint(point) == nil { + self.highlightedBackgroundNode.alpha = 1.0 + if self.highlightedBackgroundNode.supernode == nil { + var anchorNode: ASDisplayNode? + if self.bottomStripeNode.supernode != nil { + anchorNode = self.bottomStripeNode + } else if self.topStripeNode.supernode != nil { + anchorNode = self.topStripeNode + } else if self.backgroundNode.supernode != nil { + anchorNode = self.backgroundNode + } + if let anchorNode = anchorNode { + self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: anchorNode) + } else { + self.addSubnode(self.highlightedBackgroundNode) + } + } + } else { + if self.highlightedBackgroundNode.supernode != nil { + if animated { + self.highlightedBackgroundNode.layer.animateAlpha(from: self.highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak self] completed in + if let strongSelf = self { + if completed { + strongSelf.highlightedBackgroundNode.removeFromSupernode() + } + } + }) + self.highlightedBackgroundNode.alpha = 0.0 + } else { + self.highlightedBackgroundNode.removeFromSupernode() + } + } + } + } + + override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4) + } + + override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) + } + + @objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { + switch recognizer.state { + case .ended: + if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation { + switch gesture { + case .tap, .longTap: + if let item = self.item, let linkItem = self.linkItemAtPoint(location) { + item.linkItemAction?(gesture == .tap ? .tap : .longTap, linkItem) + } + default: + break + } + } + default: + break + } + } + + private func linkItemAtPoint(_ point: CGPoint) -> TextLinkItem? { + let textNodeFrame = self.textNode.frame + if let (_, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) { + if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? String { + return .url(url) + } else if let peerName = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.PeerTextMention)] as? String { + return .mention(peerName) + } else if let hashtag = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.Hashtag)] as? TelegramHashtag { + return .hashtag(hashtag.peerName, hashtag.hashtag) + } else { + return nil + } + } + return nil + } + + private func updateTouchesAtPoint(_ point: CGPoint?) { + if let item = self.item { + var rects: [CGRect]? + if let point = point { + let textNodeFrame = self.textNode.frame + if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) { + let possibleNames: [String] = [ + TelegramTextAttributes.URL, + TelegramTextAttributes.PeerMention, + TelegramTextAttributes.PeerTextMention, + TelegramTextAttributes.BotCommand, + TelegramTextAttributes.Hashtag + ] + for name in possibleNames { + if let _ = attributes[NSAttributedStringKey(rawValue: name)] { + rects = self.textNode.attributeRects(name: name, at: index) + break + } + } + } + } + + if let rects = rects { + let linkHighlightingNode: LinkHighlightingNode + if let current = self.linkHighlightingNode { + linkHighlightingNode = current + } else { + linkHighlightingNode = LinkHighlightingNode(color: item.theme.list.itemAccentColor.withAlphaComponent(0.5)) + self.linkHighlightingNode = linkHighlightingNode + self.insertSubnode(linkHighlightingNode, belowSubnode: self.textNode) + } + linkHighlightingNode.frame = self.textNode.frame + linkHighlightingNode.updateRects(rects) + } else if let linkHighlightingNode = self.linkHighlightingNode { + self.linkHighlightingNode = nil + linkHighlightingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak linkHighlightingNode] _ in + linkHighlightingNode?.removeFromSupernode() + }) + } + } + } +} diff --git a/submodules/TelegramUI/TelegramUI/UserInfoController.swift b/submodules/TelegramUI/TelegramUI/UserInfoController.swift index 3af7a14cdf..6b383bff36 100644 --- a/submodules/TelegramUI/TelegramUI/UserInfoController.swift +++ b/submodules/TelegramUI/TelegramUI/UserInfoController.swift @@ -386,27 +386,27 @@ private enum UserInfoEntry: ItemListNodeEntry { case let .calls(theme, strings, dateTimeFormat, messages): return ItemListCallListItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, messages: messages, sectionId: self.section, style: .plain) case let .about(theme, peer, text, value): - var enabledEntitiyTypes: EnabledEntityTypes = [] + var enabledEntityTypes: EnabledEntityTypes = [] if let peer = peer as? TelegramUser, let _ = peer.botInfo { - enabledEntitiyTypes = [.url, .mention, .hashtag] + enabledEntityTypes = [.url, .mention, .hashtag] } - return ItemListTextWithLabelItem(theme: theme, label: text, text: foldMultipleLineBreaks(value), enabledEntitiyTypes: enabledEntitiyTypes, multiline: true, sectionId: self.section, action: nil, longTapAction: { + return ItemListTextWithLabelItem(theme: theme, label: text, text: foldMultipleLineBreaks(value), enabledEntityTypes: enabledEntityTypes, multiline: true, sectionId: self.section, action: nil, longTapAction: { arguments.displayAboutContextMenu(value) }, linkItemAction: { action, itemLink in arguments.aboutLinkAction(action, itemLink) }, tag: UserInfoEntryTag.about) case let .phoneNumber(theme, _, label, value, isMain): - return ItemListTextWithLabelItem(theme: theme, label: label, text: value, textColor: isMain ? .highlighted : .accent, enabledEntitiyTypes: [], multiline: false, sectionId: self.section, action: { + return ItemListTextWithLabelItem(theme: theme, label: label, text: value, textColor: isMain ? .highlighted : .accent, enabledEntityTypes: [], multiline: false, sectionId: self.section, action: { arguments.openCallMenu(value) }, longTapAction: { arguments.displayCopyContextMenu(.phoneNumber, value) }, tag: UserInfoEntryTag.phoneNumber) case let .requestPhoneNumber(theme, label, value): - return ItemListTextWithLabelItem(theme: theme, label: label, text: value, textColor: .accent, enabledEntitiyTypes: [], multiline: false, sectionId: self.section, action: { + return ItemListTextWithLabelItem(theme: theme, label: label, text: value, textColor: .accent, enabledEntityTypes: [], multiline: false, sectionId: self.section, action: { arguments.requestPhoneNumber() }) case let .userName(theme, text, value): - return ItemListTextWithLabelItem(theme: theme, label: text, text: "@\(value)", textColor: .accent, enabledEntitiyTypes: [], multiline: false, sectionId: self.section, action: { + return ItemListTextWithLabelItem(theme: theme, label: text, text: "@\(value)", textColor: .accent, enabledEntityTypes: [], multiline: false, sectionId: self.section, action: { arguments.displayUsernameContextMenu("@\(value)") }, longTapAction: { arguments.displayCopyContextMenu(.username, "@\(value)") @@ -1178,113 +1178,114 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Us let globalNotificationsKey: PostboxViewKey = .preferences(keys: Set([PreferencesKeys.globalNotifications])) let signal = combineLatest(context.sharedContext.presentationData, statePromise.get(), peerView.get(), deviceContacts, context.account.postbox.combinedView(keys: [.peerChatState(peerId: peerId), globalNotificationsKey])) - |> map { presentationData, state, view, deviceContacts, combinedView -> (ItemListControllerState, (ItemListNodeState, UserInfoEntry.ItemGenerationArguments)) in - let peer = peerViewMainPeer(view.0) - - var globalNotificationSettings: GlobalNotificationSettings = .defaultSettings - if let preferencesView = combinedView.views[globalNotificationsKey] as? PreferencesView { - if let settings = preferencesView.values[PreferencesKeys.globalNotifications] as? GlobalNotificationSettings { - globalNotificationSettings = settings - } + |> map { presentationData, state, view, deviceContacts, combinedView -> (ItemListControllerState, (ItemListNodeState, UserInfoEntry.ItemGenerationArguments)) in + let peer = peerViewMainPeer(view.0) + + var globalNotificationSettings: GlobalNotificationSettings = .defaultSettings + if let preferencesView = combinedView.views[globalNotificationsKey] as? PreferencesView { + if let settings = preferencesView.values[PreferencesKeys.globalNotifications] as? GlobalNotificationSettings { + globalNotificationSettings = settings } - - if let peer = peer { - let _ = cachedAvatarEntries.modify { value in - if value != nil { - return value - } else { - let promise = Promise<[AvatarGalleryEntry]>() - promise.set(fetchedAvatarGalleryEntries(account: context.account, peer: peer)) - return promise - } - } - } - var leftNavigationButton: ItemListNavigationButton? - let rightNavigationButton: ItemListNavigationButton - if let editingState = state.editingState { - leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { - updateState { - $0.withUpdatedEditingState(nil) - } - }) - - var doneEnabled = true - if let editingName = editingState.editingName, editingName.isEmpty { - doneEnabled = false - } - - if state.savingData { - rightNavigationButton = ItemListNavigationButton(content: .none, style: .activity, enabled: doneEnabled, action: {}) + } + + if let peer = peer { + let _ = cachedAvatarEntries.modify { value in + if value != nil { + return value } else { - rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: doneEnabled, action: { - var updateName: ItemListAvatarAndNameInfoItemName? - updateState { state in - if let editingState = state.editingState, let editingName = editingState.editingName { - if let user = peer { - if ItemListAvatarAndNameInfoItemName(user) != editingName { - updateName = editingName - } + let promise = Promise<[AvatarGalleryEntry]>() + promise.set(fetchedAvatarGalleryEntries(account: context.account, peer: peer)) + return promise + } + } + } + var leftNavigationButton: ItemListNavigationButton? + let rightNavigationButton: ItemListNavigationButton + if let editingState = state.editingState { + leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { + updateState { + $0.withUpdatedEditingState(nil) + } + }) + + var doneEnabled = true + if let editingName = editingState.editingName, editingName.isEmpty { + doneEnabled = false + } + + if state.savingData { + rightNavigationButton = ItemListNavigationButton(content: .none, style: .activity, enabled: doneEnabled, action: {}) + } else { + rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: doneEnabled, action: { + var updateName: ItemListAvatarAndNameInfoItemName? + updateState { state in + if let editingState = state.editingState, let editingName = editingState.editingName { + if let user = peer { + if ItemListAvatarAndNameInfoItemName(user) != editingName { + updateName = editingName } } - if updateName != nil { - return state.withUpdatedSavingData(true) - } else { - return state.withUpdatedEditingState(nil) - } } - - if let updateName = updateName, case let .personName(firstName, lastName) = updateName { - updatePeerNameDisposable.set((updateContactName(account: context.account, peerId: peerId, firstName: firstName, lastName: lastName) - |> deliverOnMainQueue).start(error: { _ in - updateState { state in - return state.withUpdatedSavingData(false) + if updateName != nil { + return state.withUpdatedSavingData(true) + } else { + return state.withUpdatedEditingState(nil) + } + } + + if let updateName = updateName, case let .personName(firstName, lastName) = updateName { + updatePeerNameDisposable.set((updateContactName(account: context.account, peerId: peerId, firstName: firstName, lastName: lastName) + |> deliverOnMainQueue).start(error: { _ in + updateState { state in + return state.withUpdatedSavingData(false) + } + }, completed: { + updateState { state in + return state.withUpdatedSavingData(false).withUpdatedEditingState(nil) + } + + let _ = (getUserPeer(postbox: context.account.postbox, peerId: peerId) + |> mapToSignal { peer, _ -> Signal in + guard let peer = peer as? TelegramUser, let phone = peer.phone, !phone.isEmpty else { + return .complete() } - }, completed: { - updateState { state in - return state.withUpdatedSavingData(false).withUpdatedEditingState(nil) - } - - let _ = (getUserPeer(postbox: context.account.postbox, peerId: peerId) - |> mapToSignal { peer, _ -> Signal in - guard let peer = peer as? TelegramUser, let phone = peer.phone, !phone.isEmpty else { + return (context.sharedContext.contactDataManager?.basicDataForNormalizedPhoneNumber(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone))) ?? .single([])) + |> take(1) + |> mapToSignal { records -> Signal in + var signals: [Signal] = [] + if let contactDataManager = context.sharedContext.contactDataManager { + for (id, basicData) in records { + signals.append(contactDataManager.appendContactData(DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: firstName, lastName: lastName, phoneNumbers: basicData.phoneNumbers), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: []), to: id)) + } + } + return combineLatest(signals) + |> mapToSignal { _ -> Signal in return .complete() } - return (context.sharedContext.contactDataManager?.basicDataForNormalizedPhoneNumber(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone))) ?? .single([])) - |> take(1) - |> mapToSignal { records -> Signal in - var signals: [Signal] = [] - if let contactDataManager = context.sharedContext.contactDataManager { - for (id, basicData) in records { - signals.append(contactDataManager.appendContactData(DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: firstName, lastName: lastName, phoneNumbers: basicData.phoneNumbers), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: []), to: id)) - } - } - return combineLatest(signals) - |> mapToSignal { _ -> Signal in - return .complete() - } - } - }).start() - })) - } - }) - } - } else { - rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Edit), style: .regular, enabled: true, action: { - if let user = peer { - updateState { state in - return state.withUpdatedEditingState(UserInfoEditingState(editingName: ItemListAvatarAndNameInfoItemName(user))) - } + } + }).start() + })) } }) } - - let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.UserInfo_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: nil) - let listState = ItemListNodeState(entries: userInfoEntries(account: context.account, presentationData: presentationData, view: view.0, cachedPeerData: view.1, deviceContacts: deviceContacts, mode: mode, state: state, peerChatState: (combinedView.views[.peerChatState(peerId: peerId)] as? PeerChatStateView)?.chatState, globalNotificationSettings: globalNotificationSettings), style: .plain) - - return (controllerState, (listState, arguments)) - } |> afterDisposed { - actionsDisposable.dispose() + } else { + rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Edit), style: .regular, enabled: true, action: { + if let user = peer { + updateState { state in + return state.withUpdatedEditingState(UserInfoEditingState(editingName: ItemListAvatarAndNameInfoItemName(user))) + } + } + }) } + + let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.UserInfo_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: nil) + let listState = ItemListNodeState(entries: userInfoEntries(account: context.account, presentationData: presentationData, view: view.0, cachedPeerData: view.1, deviceContacts: deviceContacts, mode: mode, state: state, peerChatState: (combinedView.views[.peerChatState(peerId: peerId)] as? PeerChatStateView)?.chatState, globalNotificationSettings: globalNotificationSettings), style: .plain) + + return (controllerState, (listState, arguments)) + } + |> afterDisposed { + actionsDisposable.dispose() + } let controller = ItemListController(context: context, state: signal) @@ -1449,7 +1450,7 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Us } aboutLinkActionImpl = { [weak controller] action, itemLink in if let controller = controller { - handlePeerInfoAboutTextAction(context: context, peerId: peerId, navigateDisposable: navigateDisposable, controller: controller, action: action, itemLink: itemLink) + handleTextLinkAction(context: context, peerId: peerId, navigateDisposable: navigateDisposable, controller: controller, action: action, itemLink: itemLink) } } displayAboutContextMenuImpl = { [weak controller] text in diff --git a/submodules/TelegramUI/TelegramUI_Xcode.xcodeproj/project.pbxproj b/submodules/TelegramUI/TelegramUI_Xcode.xcodeproj/project.pbxproj index 8851e56c03..bf5812eda2 100644 --- a/submodules/TelegramUI/TelegramUI_Xcode.xcodeproj/project.pbxproj +++ b/submodules/TelegramUI/TelegramUI_Xcode.xcodeproj/project.pbxproj @@ -152,6 +152,8 @@ 09E4A805223D4A5A0038140F /* OpenSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09E4A804223D4A5A0038140F /* OpenSettings.swift */; }; 09E4A807223D4B860038140F /* AccountUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09E4A806223D4B860038140F /* AccountUtils.swift */; }; 09EC0DE722C67FB100E7185B /* UpdateInfoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09EC0DE622C67FB100E7185B /* UpdateInfoController.swift */; }; + 09EC0DEB22CAFF1400E7185B /* UpdateInfoItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09EC0DEA22CAFF1400E7185B /* UpdateInfoItem.swift */; }; + 09EC0DED22CB583C00E7185B /* TextLinkHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09EC0DEC22CB583C00E7185B /* TextLinkHandling.swift */; }; 09EDAD26220D30980012A50B /* AutodownloadConnectionTypeController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09EDAD25220D30980012A50B /* AutodownloadConnectionTypeController.swift */; }; 09EDAD2A220DA6A40012A50B /* VolumeButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09EDAD29220DA6A40012A50B /* VolumeButtons.swift */; }; 09EDAD2C2211552F0012A50B /* AutodownloadMediaCategoryController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09EDAD2B2211552F0012A50B /* AutodownloadMediaCategoryController.swift */; }; @@ -1372,6 +1374,8 @@ 09E4A804223D4A5A0038140F /* OpenSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenSettings.swift; sourceTree = ""; }; 09E4A806223D4B860038140F /* AccountUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountUtils.swift; sourceTree = ""; }; 09EC0DE622C67FB100E7185B /* UpdateInfoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateInfoController.swift; sourceTree = ""; }; + 09EC0DEA22CAFF1400E7185B /* UpdateInfoItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateInfoItem.swift; sourceTree = ""; }; + 09EC0DEC22CB583C00E7185B /* TextLinkHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextLinkHandling.swift; sourceTree = ""; }; 09EDAD25220D30980012A50B /* AutodownloadConnectionTypeController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutodownloadConnectionTypeController.swift; sourceTree = ""; }; 09EDAD29220DA6A40012A50B /* VolumeButtons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VolumeButtons.swift; sourceTree = ""; }; 09EDAD2B2211552F0012A50B /* AutodownloadMediaCategoryController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutodownloadMediaCategoryController.swift; sourceTree = ""; }; @@ -2743,6 +2747,7 @@ D0FC194C201F82A000FEDBB2 /* OpenResolvedUrl.swift */, D023836F1DDF0462004018B6 /* UrlHandling.swift */, 09E4A804223D4A5A0038140F /* OpenSettings.swift */, + 09EC0DEC22CB583C00E7185B /* TextLinkHandling.swift */, ); name = Routing; sourceTree = ""; @@ -2777,6 +2782,7 @@ isa = PBXGroup; children = ( 09EC0DE622C67FB100E7185B /* UpdateInfoController.swift */, + 09EC0DEA22CAFF1400E7185B /* UpdateInfoItem.swift */, ); name = Update; sourceTree = ""; @@ -6037,6 +6043,7 @@ 09F664C821EB4A2600AB7E26 /* ThemeGridSearchItem.swift in Sources */, 09A218D9229EE1B600DE6898 /* HorizontalStickerGridItem.swift in Sources */, D07E413B208A432100FCA8F0 /* ChatListTitleProxyNode.swift in Sources */, + 09EC0DED22CB583C00E7185B /* TextLinkHandling.swift in Sources */, D080B27F1F4C7C6000AA3847 /* InstantPageManagedMediaId.swift in Sources */, D08984F02114AE0C00918162 /* DataPrivacySettingsController.swift in Sources */, D087BFAD1F741B9D003FD209 /* ShareContentContainerNode.swift in Sources */, @@ -6171,6 +6178,7 @@ D0E9BA651F055B4500F079A4 /* BotCheckoutNativeCardEntryController.swift in Sources */, D02D60B3206C18A600FEFE1E /* SecureIdPlaintextFormControllerNode.swift in Sources */, D00ACA5A2022897D0045D427 /* ProcessedPeerRestrictionText.swift in Sources */, + 09EC0DEB22CAFF1400E7185B /* UpdateInfoItem.swift in Sources */, D0192D3C210A44D00005FA10 /* DeviceContactData.swift in Sources */, D0EC6E391EB9F58900EBF1C3 /* ItemListCheckboxItem.swift in Sources */, D0EC6E3A1EB9F58900EBF1C3 /* ItemListSwitchItem.swift in Sources */,