diff --git a/TelegramUI.xcodeproj/project.pbxproj b/TelegramUI.xcodeproj/project.pbxproj index 821fbe3281..1492688262 100644 --- a/TelegramUI.xcodeproj/project.pbxproj +++ b/TelegramUI.xcodeproj/project.pbxproj @@ -254,6 +254,7 @@ D056CD781FF2A6EE00880D28 /* ChatMessageSwipeToReplyNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D056CD771FF2A6EE00880D28 /* ChatMessageSwipeToReplyNode.swift */; }; D056CD7A1FF3CC2A00880D28 /* ListMessagePlaybackOverlayNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D056CD791FF3CC2A00880D28 /* ListMessagePlaybackOverlayNode.swift */; }; D056CD7C1FF3E92C00880D28 /* DirectionalPanGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D056CD7B1FF3E92C00880D28 /* DirectionalPanGestureRecognizer.swift */; }; + D05B077421BFC38600B1D27C /* FFMpeg.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D05B077321BFC38600B1D27C /* FFMpeg.framework */; }; D05D8B3A2192FC460064586F /* LocalizationListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05D8B392192FC460064586F /* LocalizationListController.swift */; }; D05D8B3F2192FC6E0064586F /* LocalizationListControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05D8B3E2192FC6E0064586F /* LocalizationListControllerNode.swift */; }; D05D8B412192FC8A0064586F /* LocalizationListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05D8B402192FC8A0064586F /* LocalizationListItem.swift */; }; @@ -702,7 +703,6 @@ D0EC6D1A1EB9F58800EBF1C3 /* FFMpegMediaFrameSourceContextHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69D161D6B87D30046BCD6 /* FFMpegMediaFrameSourceContextHelpers.swift */; }; D0EC6D1B1EB9F58800EBF1C3 /* FFMpegMediaVideoFrameDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69D871D6B87EC0046BCD6 /* FFMpegMediaVideoFrameDecoder.swift */; }; D0EC6D1C1EB9F58800EBF1C3 /* FFMpegMediaPassthroughVideoFrameDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69D6F1D6B87DE0046BCD6 /* FFMpegMediaPassthroughVideoFrameDecoder.swift */; }; - D0EC6D1D1EB9F58800EBF1C3 /* FFMpegPacket.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69D171D6B87D30046BCD6 /* FFMpegPacket.swift */; }; D0EC6D1E1EB9F58800EBF1C3 /* MediaPlayerScrubbingNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03922A61DF70E3F000F2CE9 /* MediaPlayerScrubbingNode.swift */; }; D0EC6D1F1EB9F58800EBF1C3 /* MediaPlayerTimeTextNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0177B7F1DFAE18500A5083A /* MediaPlayerTimeTextNode.swift */; }; D0EC6D201EB9F58800EBF1C3 /* PeerAvatar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69CDE1D6B87D30046BCD6 /* PeerAvatar.swift */; }; @@ -1053,10 +1053,6 @@ D0EC6E921EB9F5B200EBF1C3 /* SwiftSignalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D08D452C1D5E340300A7428A /* SwiftSignalKit.framework */; }; D0EC6E931EB9F5B200EBF1C3 /* TelegramCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D08D452D1D5E340300A7428A /* TelegramCore.framework */; }; D0EC6E961EB9F5B300EBF1C3 /* MtProtoKitDynamic.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0EC6E941EB9F5B300EBF1C3 /* MtProtoKitDynamic.framework */; }; - D0EC6E981EB9F5D000EBF1C3 /* libavcodec.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D0F69EA81D6B9BCB0046BCD6 /* libavcodec.a */; }; - D0EC6E991EB9F5D000EBF1C3 /* libavformat.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D0F69EA91D6B9BCB0046BCD6 /* libavformat.a */; }; - D0EC6E9A1EB9F5D000EBF1C3 /* libavutil.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D0F69EAA1D6B9BCB0046BCD6 /* libavutil.a */; }; - D0EC6E9B1EB9F5D000EBF1C3 /* libswresample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D0F69EAB1D6B9BCB0046BCD6 /* libswresample.a */; }; D0EC6E9C1EB9F5E600EBF1C3 /* libwebp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D0F69EA61D6B9BBC0046BCD6 /* libwebp.a */; }; D0EC6EA21EB9FAFA00EBF1C3 /* libopus.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D0D03B251DECB26D00220C46 /* libopus.a */; }; D0EC6EA31EB9FB7A00EBF1C3 /* SSignalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D075518E1DDA4F9E0073E051 /* SSignalKit.framework */; }; @@ -1577,6 +1573,8 @@ D05A32E91E6F143C002760B4 /* RecentSessionsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecentSessionsController.swift; sourceTree = ""; }; D05A32EB1E6F1462002760B4 /* BlockedPeersController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockedPeersController.swift; sourceTree = ""; }; D05A32ED1E6F25A0002760B4 /* ItemListRecentSessionItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemListRecentSessionItem.swift; sourceTree = ""; }; + D05B077121BFB9F600B1D27C /* FFMpeg.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FFMpeg.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D05B077321BFC38600B1D27C /* FFMpeg.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FFMpeg.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D05B724C1E720393000BD3AD /* SelectivePrivacySettingsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectivePrivacySettingsController.swift; sourceTree = ""; }; D05B724F1E720597000BD3AD /* PresentationData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresentationData.swift; sourceTree = ""; }; D05BFB5E1EAA22F900909D38 /* PresentationResourceKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresentationResourceKey.swift; sourceTree = ""; }; @@ -2153,7 +2151,6 @@ D0F69CFB1D6B87D30046BCD6 /* TouchDownGestureRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TouchDownGestureRecognizer.swift; sourceTree = ""; }; D0F69D021D6B87D30046BCD6 /* MediaPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaPlayer.swift; sourceTree = ""; }; D0F69D161D6B87D30046BCD6 /* FFMpegMediaFrameSourceContextHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FFMpegMediaFrameSourceContextHelpers.swift; sourceTree = ""; }; - D0F69D171D6B87D30046BCD6 /* FFMpegPacket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FFMpegPacket.swift; sourceTree = ""; }; D0F69D1D1D6B87D30046BCD6 /* MediaTrackDecodableFrame.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaTrackDecodableFrame.swift; sourceTree = ""; }; D0F69D6F1D6B87DE0046BCD6 /* FFMpegMediaPassthroughVideoFrameDecoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FFMpegMediaPassthroughVideoFrameDecoder.swift; sourceTree = ""; }; D0F69D701D6B87DE0046BCD6 /* MediaTrackFrameBuffer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaTrackFrameBuffer.swift; sourceTree = ""; }; @@ -2278,6 +2275,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + D05B077421BFC38600B1D27C /* FFMpeg.framework in Frameworks */, D045549A21B2F173007A6DD9 /* libturbojpeg.a in Frameworks */, 091BEAB3214552D9003AEA30 /* Vision.framework in Frameworks */, D0C45E9F213FFAFD00988156 /* Lottie.framework in Frameworks */, @@ -2294,10 +2292,6 @@ D0EC6EA31EB9FB7A00EBF1C3 /* SSignalKit.framework in Frameworks */, D0EC6EA21EB9FAFA00EBF1C3 /* libopus.a in Frameworks */, D0EC6E9C1EB9F5E600EBF1C3 /* libwebp.a in Frameworks */, - D0EC6E981EB9F5D000EBF1C3 /* libavcodec.a in Frameworks */, - D0EC6E991EB9F5D000EBF1C3 /* libavformat.a in Frameworks */, - D0EC6E9A1EB9F5D000EBF1C3 /* libavutil.a in Frameworks */, - D0EC6E9B1EB9F5D000EBF1C3 /* libswresample.a in Frameworks */, D0EC6E961EB9F5B300EBF1C3 /* MtProtoKitDynamic.framework in Frameworks */, D0EC6E8F1EB9F5B200EBF1C3 /* AsyncDisplayKit.framework in Frameworks */, D0EC6E901EB9F5B200EBF1C3 /* Display.framework in Frameworks */, @@ -3327,6 +3321,8 @@ D08D45281D5E340200A7428A /* Frameworks */ = { isa = PBXGroup; children = ( + D05B077321BFC38600B1D27C /* FFMpeg.framework */, + D05B077121BFB9F600B1D27C /* FFMpeg.framework */, D045549921B2F173007A6DD9 /* libturbojpeg.a */, D0C45E9E213FFAFD00988156 /* Lottie.framework */, D02DADBE2138D76F00116225 /* Vision.framework */, @@ -4165,7 +4161,6 @@ D0F69D161D6B87D30046BCD6 /* FFMpegMediaFrameSourceContextHelpers.swift */, D0F69D871D6B87EC0046BCD6 /* FFMpegMediaVideoFrameDecoder.swift */, D0F69D6F1D6B87DE0046BCD6 /* FFMpegMediaPassthroughVideoFrameDecoder.swift */, - D0F69D171D6B87D30046BCD6 /* FFMpegPacket.swift */, D03922A61DF70E3F000F2CE9 /* MediaPlayerScrubbingNode.swift */, D0177B7F1DFAE18500A5083A /* MediaPlayerTimeTextNode.swift */, ); @@ -5222,7 +5217,6 @@ D01C06AF1FBB461E001561AB /* JoinLinkPreviewController.swift in Sources */, D0EC6D1C1EB9F58800EBF1C3 /* FFMpegMediaPassthroughVideoFrameDecoder.swift in Sources */, D0D9DE0D20EFEA2E00F20B06 /* InstantPageMediaPlaylist.swift in Sources */, - D0EC6D1D1EB9F58800EBF1C3 /* FFMpegPacket.swift in Sources */, D01C06B11FBB4643001561AB /* JoinLinkPreviewControllerNode.swift in Sources */, D0EC6D1E1EB9F58800EBF1C3 /* MediaPlayerScrubbingNode.swift in Sources */, D0C0B59B1EE019E5000F4D2C /* ChatSearchNavigationContentNode.swift in Sources */, @@ -5948,7 +5942,7 @@ /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ - D021D510219CB2240064BEBA /* Debug Fork */ = { + D021D510219CB2240064BEBA /* DebugFork */ = { isa = XCBuildConfiguration; baseConfigurationReference = D0F69DB91D6B88190046BCD6 /* TelegramUI.xcconfig */; buildSettings = { @@ -6009,9 +6003,9 @@ VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; - name = "Debug Fork"; + name = DebugFork; }; - D021D511219CB2240064BEBA /* Debug Fork */ = { + D021D511219CB2240064BEBA /* DebugFork */ = { isa = XCBuildConfiguration; baseConfigurationReference = D0F69DB91D6B88190046BCD6 /* TelegramUI.xcconfig */; buildSettings = { @@ -6025,9 +6019,9 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 4.0; }; - name = "Debug Fork"; + name = DebugFork; }; - D021D512219CB2240064BEBA /* Debug Fork */ = { + D021D512219CB2240064BEBA /* DebugFork */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; @@ -6065,9 +6059,9 @@ SWIFT_VERSION = 4.0; USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include"; }; - name = "Debug Fork"; + name = DebugFork; }; - D0400EDB1D5B900A007931CE /* Release AppStore */ = { + D0400EDB1D5B900A007931CE /* ReleaseAppStore */ = { isa = XCBuildConfiguration; baseConfigurationReference = D0F69DB91D6B88190046BCD6 /* TelegramUI.xcconfig */; buildSettings = { @@ -6121,9 +6115,9 @@ VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; - name = "Release AppStore"; + name = ReleaseAppStore; }; - D0400EDD1D5B900A007931CE /* Release AppStore */ = { + D0400EDD1D5B900A007931CE /* ReleaseAppStore */ = { isa = XCBuildConfiguration; baseConfigurationReference = D0F69DB91D6B88190046BCD6 /* TelegramUI.xcconfig */; buildSettings = { @@ -6138,9 +6132,9 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 4.0; }; - name = "Release AppStore"; + name = ReleaseAppStore; }; - D079FD261F06BEF70038FADE /* Debug AppStore */ = { + D079FD261F06BEF70038FADE /* DebugAppStore */ = { isa = XCBuildConfiguration; baseConfigurationReference = D0F69DB91D6B88190046BCD6 /* TelegramUI.xcconfig */; buildSettings = { @@ -6201,9 +6195,9 @@ VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; - name = "Debug AppStore"; + name = DebugAppStore; }; - D079FD271F06BEF70038FADE /* Debug AppStore */ = { + D079FD271F06BEF70038FADE /* DebugAppStore */ = { isa = XCBuildConfiguration; baseConfigurationReference = D0F69DB91D6B88190046BCD6 /* TelegramUI.xcconfig */; buildSettings = { @@ -6217,9 +6211,9 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 4.0; }; - name = "Debug AppStore"; + name = DebugAppStore; }; - D079FD281F06BEF70038FADE /* Debug AppStore */ = { + D079FD281F06BEF70038FADE /* DebugAppStore */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; @@ -6257,9 +6251,9 @@ SWIFT_VERSION = 4.0; USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include"; }; - name = "Debug AppStore"; + name = DebugAppStore; }; - D0924FEE1FE52C29003F693F /* Release Hockeyapp Internal */ = { + D0924FEE1FE52C29003F693F /* ReleaseHockeyappInternal */ = { isa = XCBuildConfiguration; baseConfigurationReference = D0F69DB91D6B88190046BCD6 /* TelegramUI.xcconfig */; buildSettings = { @@ -6313,9 +6307,9 @@ VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; - name = "Release Hockeyapp Internal"; + name = ReleaseHockeyappInternal; }; - D0924FEF1FE52C29003F693F /* Release Hockeyapp Internal */ = { + D0924FEF1FE52C29003F693F /* ReleaseHockeyappInternal */ = { isa = XCBuildConfiguration; baseConfigurationReference = D0F69DB91D6B88190046BCD6 /* TelegramUI.xcconfig */; buildSettings = { @@ -6330,9 +6324,9 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 4.0; }; - name = "Release Hockeyapp Internal"; + name = ReleaseHockeyappInternal; }; - D0924FF01FE52C29003F693F /* Release Hockeyapp Internal */ = { + D0924FF01FE52C29003F693F /* ReleaseHockeyappInternal */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; @@ -6370,9 +6364,9 @@ SWIFT_VERSION = 4.0; USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include"; }; - name = "Release Hockeyapp Internal"; + name = ReleaseHockeyappInternal; }; - D0ADF948212B3B0000310BBC /* Debug AppStore LLC */ = { + D0ADF948212B3B0000310BBC /* DebugAppStoreLLC */ = { isa = XCBuildConfiguration; baseConfigurationReference = D0F69DB91D6B88190046BCD6 /* TelegramUI.xcconfig */; buildSettings = { @@ -6433,9 +6427,9 @@ VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; - name = "Debug AppStore LLC"; + name = DebugAppStoreLLC; }; - D0ADF949212B3B0000310BBC /* Debug AppStore LLC */ = { + D0ADF949212B3B0000310BBC /* DebugAppStoreLLC */ = { isa = XCBuildConfiguration; baseConfigurationReference = D0F69DB91D6B88190046BCD6 /* TelegramUI.xcconfig */; buildSettings = { @@ -6449,9 +6443,9 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 4.0; }; - name = "Debug AppStore LLC"; + name = DebugAppStoreLLC; }; - D0ADF94A212B3B0000310BBC /* Debug AppStore LLC */ = { + D0ADF94A212B3B0000310BBC /* DebugAppStoreLLC */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; @@ -6489,9 +6483,9 @@ SWIFT_VERSION = 4.0; USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include"; }; - name = "Debug AppStore LLC"; + name = DebugAppStoreLLC; }; - D0CE6F02213DC32300BCD44B /* Release AppStore LLC */ = { + D0CE6F02213DC32300BCD44B /* ReleaseAppStoreLLC */ = { isa = XCBuildConfiguration; baseConfigurationReference = D0F69DB91D6B88190046BCD6 /* TelegramUI.xcconfig */; buildSettings = { @@ -6545,9 +6539,9 @@ VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; - name = "Release AppStore LLC"; + name = ReleaseAppStoreLLC; }; - D0CE6F03213DC32300BCD44B /* Release AppStore LLC */ = { + D0CE6F03213DC32300BCD44B /* ReleaseAppStoreLLC */ = { isa = XCBuildConfiguration; baseConfigurationReference = D0F69DB91D6B88190046BCD6 /* TelegramUI.xcconfig */; buildSettings = { @@ -6562,9 +6556,9 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 4.0; }; - name = "Release AppStore LLC"; + name = ReleaseAppStoreLLC; }; - D0CE6F04213DC32300BCD44B /* Release AppStore LLC */ = { + D0CE6F04213DC32300BCD44B /* ReleaseAppStoreLLC */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; @@ -6600,9 +6594,9 @@ SWIFT_VERSION = 4.0; USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include"; }; - name = "Release AppStore LLC"; + name = ReleaseAppStoreLLC; }; - D0EC6E9E1EB9F79800EBF1C3 /* Debug Hockeyapp */ = { + D0EC6E9E1EB9F79800EBF1C3 /* DebugHockeyapp */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; @@ -6640,9 +6634,9 @@ SWIFT_VERSION = 4.0; USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include"; }; - name = "Debug Hockeyapp"; + name = DebugHockeyapp; }; - D0EC6E9F1EB9F79800EBF1C3 /* Release Hockeyapp */ = { + D0EC6E9F1EB9F79800EBF1C3 /* ReleaseHockeyapp */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; @@ -6680,9 +6674,9 @@ SWIFT_VERSION = 4.0; USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include"; }; - name = "Release Hockeyapp"; + name = ReleaseHockeyapp; }; - D0EC6EA01EB9F79800EBF1C3 /* Release AppStore */ = { + D0EC6EA01EB9F79800EBF1C3 /* ReleaseAppStore */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; @@ -6718,9 +6712,9 @@ SWIFT_VERSION = 4.0; USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include"; }; - name = "Release AppStore"; + name = ReleaseAppStore; }; - D0FC40911D5B8E7500261D9D /* Debug Hockeyapp */ = { + D0FC40911D5B8E7500261D9D /* DebugHockeyapp */ = { isa = XCBuildConfiguration; baseConfigurationReference = D0F69DB91D6B88190046BCD6 /* TelegramUI.xcconfig */; buildSettings = { @@ -6781,9 +6775,9 @@ VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; - name = "Debug Hockeyapp"; + name = DebugHockeyapp; }; - D0FC40921D5B8E7500261D9D /* Release Hockeyapp */ = { + D0FC40921D5B8E7500261D9D /* ReleaseHockeyapp */ = { isa = XCBuildConfiguration; baseConfigurationReference = D0F69DB91D6B88190046BCD6 /* TelegramUI.xcconfig */; buildSettings = { @@ -6837,9 +6831,9 @@ VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; - name = "Release Hockeyapp"; + name = ReleaseHockeyapp; }; - D0FC40971D5B8E7500261D9D /* Debug Hockeyapp */ = { + D0FC40971D5B8E7500261D9D /* DebugHockeyapp */ = { isa = XCBuildConfiguration; baseConfigurationReference = D0F69DB91D6B88190046BCD6 /* TelegramUI.xcconfig */; buildSettings = { @@ -6853,9 +6847,9 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 4.0; }; - name = "Debug Hockeyapp"; + name = DebugHockeyapp; }; - D0FC40981D5B8E7500261D9D /* Release Hockeyapp */ = { + D0FC40981D5B8E7500261D9D /* ReleaseHockeyapp */ = { isa = XCBuildConfiguration; baseConfigurationReference = D0F69DB91D6B88190046BCD6 /* TelegramUI.xcconfig */; buildSettings = { @@ -6870,7 +6864,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 4.0; }; - name = "Release Hockeyapp"; + name = ReleaseHockeyapp; }; /* End XCBuildConfiguration section */ @@ -6878,47 +6872,47 @@ D0EC6EA11EB9F79800EBF1C3 /* Build configuration list for PBXNativeTarget "TelegramUI" */ = { isa = XCConfigurationList; buildConfigurations = ( - D0EC6E9E1EB9F79800EBF1C3 /* Debug Hockeyapp */, - D021D512219CB2240064BEBA /* Debug Fork */, - D079FD281F06BEF70038FADE /* Debug AppStore */, - D0ADF94A212B3B0000310BBC /* Debug AppStore LLC */, - D0EC6E9F1EB9F79800EBF1C3 /* Release Hockeyapp */, - D0924FF01FE52C29003F693F /* Release Hockeyapp Internal */, - D0EC6EA01EB9F79800EBF1C3 /* Release AppStore */, - D0CE6F04213DC32300BCD44B /* Release AppStore LLC */, + D0EC6E9E1EB9F79800EBF1C3 /* DebugHockeyapp */, + D021D512219CB2240064BEBA /* DebugFork */, + D079FD281F06BEF70038FADE /* DebugAppStore */, + D0ADF94A212B3B0000310BBC /* DebugAppStoreLLC */, + D0EC6E9F1EB9F79800EBF1C3 /* ReleaseHockeyapp */, + D0924FF01FE52C29003F693F /* ReleaseHockeyappInternal */, + D0EC6EA01EB9F79800EBF1C3 /* ReleaseAppStore */, + D0CE6F04213DC32300BCD44B /* ReleaseAppStoreLLC */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = "Release Hockeyapp"; + defaultConfigurationName = ReleaseHockeyapp; }; D0FC40791D5B8E7400261D9D /* Build configuration list for PBXProject "TelegramUI" */ = { isa = XCConfigurationList; buildConfigurations = ( - D0FC40911D5B8E7500261D9D /* Debug Hockeyapp */, - D021D510219CB2240064BEBA /* Debug Fork */, - D079FD261F06BEF70038FADE /* Debug AppStore */, - D0ADF948212B3B0000310BBC /* Debug AppStore LLC */, - D0FC40921D5B8E7500261D9D /* Release Hockeyapp */, - D0924FEE1FE52C29003F693F /* Release Hockeyapp Internal */, - D0400EDB1D5B900A007931CE /* Release AppStore */, - D0CE6F02213DC32300BCD44B /* Release AppStore LLC */, + D0FC40911D5B8E7500261D9D /* DebugHockeyapp */, + D021D510219CB2240064BEBA /* DebugFork */, + D079FD261F06BEF70038FADE /* DebugAppStore */, + D0ADF948212B3B0000310BBC /* DebugAppStoreLLC */, + D0FC40921D5B8E7500261D9D /* ReleaseHockeyapp */, + D0924FEE1FE52C29003F693F /* ReleaseHockeyappInternal */, + D0400EDB1D5B900A007931CE /* ReleaseAppStore */, + D0CE6F02213DC32300BCD44B /* ReleaseAppStoreLLC */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = "Release Hockeyapp"; + defaultConfigurationName = ReleaseHockeyapp; }; D0FC40961D5B8E7500261D9D /* Build configuration list for PBXNativeTarget "TelegramUITests" */ = { isa = XCConfigurationList; buildConfigurations = ( - D0FC40971D5B8E7500261D9D /* Debug Hockeyapp */, - D021D511219CB2240064BEBA /* Debug Fork */, - D079FD271F06BEF70038FADE /* Debug AppStore */, - D0ADF949212B3B0000310BBC /* Debug AppStore LLC */, - D0FC40981D5B8E7500261D9D /* Release Hockeyapp */, - D0924FEF1FE52C29003F693F /* Release Hockeyapp Internal */, - D0400EDD1D5B900A007931CE /* Release AppStore */, - D0CE6F03213DC32300BCD44B /* Release AppStore LLC */, + D0FC40971D5B8E7500261D9D /* DebugHockeyapp */, + D021D511219CB2240064BEBA /* DebugFork */, + D079FD271F06BEF70038FADE /* DebugAppStore */, + D0ADF949212B3B0000310BBC /* DebugAppStoreLLC */, + D0FC40981D5B8E7500261D9D /* ReleaseHockeyapp */, + D0924FEF1FE52C29003F693F /* ReleaseHockeyappInternal */, + D0400EDD1D5B900A007931CE /* ReleaseAppStore */, + D0CE6F03213DC32300BCD44B /* ReleaseAppStoreLLC */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = "Release Hockeyapp"; + defaultConfigurationName = ReleaseHockeyapp; }; /* End XCConfigurationList section */ }; diff --git a/TelegramUI/AuthorizationSequenceCodeEntryControllerNode.swift b/TelegramUI/AuthorizationSequenceCodeEntryControllerNode.swift index 2914c024c7..b80f32d944 100644 --- a/TelegramUI/AuthorizationSequenceCodeEntryControllerNode.swift +++ b/TelegramUI/AuthorizationSequenceCodeEntryControllerNode.swift @@ -245,9 +245,17 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { self.layoutArguments = (layout, navigationBarHeight) - var insets = layout.insets(options: [.input]) + var insets = layout.insets(options: []) insets.top = navigationBarHeight + if let inputHeight = layout.inputHeight { + if inputHeight.isEqual(to: layout.standardInputHeight - 44.0) { + insets.bottom += layout.standardInputHeight + } else { + insets.bottom += inputHeight + } + } + if max(layout.size.width, layout.size.height) > 1023.0 { if let codeType = self.codeType, case .otherSession = codeType { self.titleNode.attributedText = NSAttributedString(string: self.strings.Login_CheckOtherSessionMessages, font: Font.medium(32.0), textColor: self.theme.primaryColor) diff --git a/TelegramUI/AuthorizationSequenceController.swift b/TelegramUI/AuthorizationSequenceController.swift index 48cd5a89a8..244366aaee 100644 --- a/TelegramUI/AuthorizationSequenceController.swift +++ b/TelegramUI/AuthorizationSequenceController.swift @@ -34,8 +34,23 @@ public final class AuthorizationSequenceController: NavigationController { super.init(mode: .single, theme: NavigationControllerTheme(navigationBar: AuthorizationSequenceController.navigationBarTheme(theme), emptyAreaColor: .black, emptyDetailIcon: nil)) self.stateDisposable = (account.postbox.stateView() - |> deliverOnMainQueue).start(next: { [weak self] view in - self?.updateState(state: view.state ?? UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .empty)) + |> filter { view in + if view.state is UnauthorizedAccountState || view.state == nil { + return true + } else { + return false + } + } + |> map { view -> UnauthorizedAccountStateContents in + if let state = view.state as? UnauthorizedAccountState { + return state.contents + } else { + return .empty + } + } + |> distinctUntilChanged + |> deliverOnMainQueue).start(next: { [weak self] state in + self?.updateState(state: state) }) } @@ -145,7 +160,7 @@ public final class AuthorizationSequenceController: NavigationController { } } } - controller.updateData(countryCode: countryCode, number: number) + controller.updateData(countryCode: countryCode, countryName: nil, number: number) return controller } @@ -606,28 +621,25 @@ public final class AuthorizationSequenceController: NavigationController { return controller } - private func updateState(state: PostboxCoding?) { - if let state = state as? UnauthorizedAccountState { - switch state.contents { - case .empty: - if let _ = self.viewControllers.last as? AuthorizationSequenceSplashController { - } else { - self.setViewControllers([self.splashController()], animated: !self.viewControllers.isEmpty) - } - case let .phoneEntry(countryCode, number): - self.setViewControllers([self.splashController(), self.phoneEntryController(countryCode: countryCode, number: number)], animated: !self.viewControllers.isEmpty) - case let .confirmationCodeEntry(number, type, _, timeout, nextType, termsOfService): - self.setViewControllers([self.splashController(), self.phoneEntryController(countryCode: defaultCountryCode(), number: ""), self.codeEntryController(number: number, type: type, nextType: nextType, timeout: timeout, termsOfService: termsOfService)], animated: !self.viewControllers.isEmpty) - case let .passwordEntry(hint, _, _): - self.setViewControllers([self.splashController(), self.passwordEntryController(hint: hint)], animated: !self.viewControllers.isEmpty) - case let .passwordRecovery(_, _, _, emailPattern): - self.setViewControllers([self.splashController(), self.passwordRecoveryController(emailPattern: emailPattern)], animated: !self.viewControllers.isEmpty) - case let .awaitingAccountReset(protectedUntil, number): - self.setViewControllers([self.splashController(), self.awaitingAccountResetController(protectedUntil: protectedUntil, number: number)], animated: !self.viewControllers.isEmpty) - case let .signUp(_, _, _, firstName, lastName, termsOfService): - self.setViewControllers([self.splashController(), self.signUpController(firstName: firstName, lastName: lastName, termsOfService: termsOfService)], animated: !self.viewControllers.isEmpty) - } - } else if let _ = state as? AuthorizedAccountState { + private func updateState(state: UnauthorizedAccountStateContents) { + switch state { + case .empty: + if let _ = self.viewControllers.last as? AuthorizationSequenceSplashController { + } else { + self.setViewControllers([self.splashController()], animated: !self.viewControllers.isEmpty) + } + case let .phoneEntry(countryCode, number): + self.setViewControllers([self.splashController(), self.phoneEntryController(countryCode: countryCode, number: number)], animated: !self.viewControllers.isEmpty) + case let .confirmationCodeEntry(number, type, _, timeout, nextType, termsOfService): + self.setViewControllers([self.splashController(), self.phoneEntryController(countryCode: defaultCountryCode(), number: ""), self.codeEntryController(number: number, type: type, nextType: nextType, timeout: timeout, termsOfService: termsOfService)], animated: !self.viewControllers.isEmpty) + case let .passwordEntry(hint, _, _): + self.setViewControllers([self.splashController(), self.passwordEntryController(hint: hint)], animated: !self.viewControllers.isEmpty) + case let .passwordRecovery(_, _, _, emailPattern): + self.setViewControllers([self.splashController(), self.passwordRecoveryController(emailPattern: emailPattern)], animated: !self.viewControllers.isEmpty) + case let .awaitingAccountReset(protectedUntil, number): + self.setViewControllers([self.splashController(), self.awaitingAccountResetController(protectedUntil: protectedUntil, number: number)], animated: !self.viewControllers.isEmpty) + case let .signUp(_, _, _, firstName, lastName, termsOfService): + self.setViewControllers([self.splashController(), self.signUpController(firstName: firstName, lastName: lastName, termsOfService: termsOfService)], animated: !self.viewControllers.isEmpty) } } diff --git a/TelegramUI/AuthorizationSequencePhoneEntryController.swift b/TelegramUI/AuthorizationSequencePhoneEntryController.swift index 68c11e338b..c4b5320fc3 100644 --- a/TelegramUI/AuthorizationSequencePhoneEntryController.swift +++ b/TelegramUI/AuthorizationSequencePhoneEntryController.swift @@ -14,7 +14,7 @@ final class AuthorizationSequencePhoneEntryController: ViewController { private let theme: AuthorizationTheme private let openUrl: (String) -> Void - private var currentData: (Int32, String)? + private var currentData: (Int32, String?, String)? var inProgress: Bool = false { didSet { @@ -58,25 +58,25 @@ final class AuthorizationSequencePhoneEntryController: ViewController { self.termsDisposable.dispose() } - func updateData(countryCode: Int32, number: String) { - self.currentData = (countryCode, number) + func updateData(countryCode: Int32, countryName: String?, number: String) { + self.currentData = (countryCode, countryName, number) if self.isNodeLoaded { - self.controllerNode.codeAndNumber = (countryCode, number) + self.controllerNode.codeAndNumber = (countryCode, countryName, number) } } override public func loadDisplayNode() { self.displayNode = AuthorizationSequencePhoneEntryControllerNode(strings: self.strings, theme: self.theme) - if let (code, number) = self.currentData { - self.controllerNode.codeAndNumber = (code, number) + if let (code, name, number) = self.currentData { + self.controllerNode.codeAndNumber = (code, name, number) } self.displayNodeDidLoad() self.controllerNode.selectCountryCode = { [weak self] in if let strongSelf = self { let controller = AuthorizationSequenceCountrySelectionController(strings: strongSelf.strings, theme: AuthorizationSequenceCountrySelectionTheme(authorizationTheme: strongSelf.theme)) - controller.completeWithCountryCode = { code, _ in + controller.completeWithCountryCode = { code, name in if let strongSelf = self, let currentData = strongSelf.currentData { - strongSelf.updateData(countryCode: Int32(code), number: currentData.1) + strongSelf.updateData(countryCode: Int32(code), countryName: name, number: currentData.2) strongSelf.controllerNode.activateInput() } } @@ -111,7 +111,7 @@ final class AuthorizationSequencePhoneEntryController: ViewController { } @objc func nextPressed() { - let (_, number) = self.controllerNode.codeAndNumber + let (_, _, number) = self.controllerNode.codeAndNumber if !number.isEmpty { self.loginWithNumber?(self.controllerNode.currentNumber) } else { diff --git a/TelegramUI/AuthorizationSequencePhoneEntryControllerNode.swift b/TelegramUI/AuthorizationSequencePhoneEntryControllerNode.swift index b06e0935c8..dd1dbc3951 100644 --- a/TelegramUI/AuthorizationSequencePhoneEntryControllerNode.swift +++ b/TelegramUI/AuthorizationSequencePhoneEntryControllerNode.swift @@ -117,9 +117,13 @@ private final class PhoneAndCountryNode: ASDisplayNode { self.countryButton.addTarget(self, action: #selector(self.countryPressed), forControlEvents: .touchUpInside) - self.phoneInputNode.countryCodeUpdated = { [weak self] code in + self.phoneInputNode.countryCodeUpdated = { [weak self] code, name in if let strongSelf = self { - if let code = Int(code), let (countryId, countryName) = countryCodeToIdAndName[code] { + if let code = Int(code), let name = name, let countryName = countryCodeAndIdToName[CountryCodeAndId(code: code, id: name)] { + let flagString = emojiFlagForISOCountryCode(name as NSString) + let localizedName: String = AuthorizationSequenceCountrySelectionController.lookupCountryNameById(name, strings: strongSelf.strings) ?? countryName + strongSelf.countryButton.setTitle("\(flagString) \(localizedName)", with: Font.regular(20.0), with: theme.primaryColor, for: []) + } else if let code = Int(code), let (countryId, countryName) = countryCodeToIdAndName[code] { let flagString = emojiFlagForISOCountryCode(countryId as NSString) let localizedName: String = AuthorizationSequenceCountrySelectionController.lookupCountryNameById(countryId, strings: strongSelf.strings) ?? countryName strongSelf.countryButton.setTitle("\(flagString) \(localizedName)", with: Font.regular(20.0), with: theme.primaryColor, for: []) @@ -171,7 +175,7 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode { return self.phoneAndCountryNode.phoneInputNode.number } - var codeAndNumber: (Int32?, String) { + var codeAndNumber: (Int32?, String?, String) { get { return self.phoneAndCountryNode.phoneInputNode.codeAndNumber } set(value) { @@ -246,7 +250,7 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode { return nil } } - self.termsOfServiceNode.tapAttributeAction = { [weak self] attributes in + self.termsOfServiceNode.tapAttributeAction = { attributes in if let _ = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? String { } } @@ -254,9 +258,17 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode { } func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { - var insets = layout.insets(options: [.input]) + var insets = layout.insets(options: []) insets.top = navigationBarHeight + if let inputHeight = layout.inputHeight { + if inputHeight.isEqual(to: layout.standardInputHeight - 44.0) { + insets.bottom += layout.standardInputHeight + } else { + insets.bottom += inputHeight + } + } + if max(layout.size.width, layout.size.height) > 1023.0 { self.titleNode.attributedText = NSAttributedString(string: strings.Login_PhoneTitle, font: Font.light(40.0), textColor: self.theme.primaryColor) } else { @@ -265,13 +277,11 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode { let titleSize = self.titleNode.measure(CGSize(width: layout.size.width, height: CGFloat.greatestFiniteMagnitude)) let noticeSize = self.noticeNode.measure(CGSize(width: min(274.0, layout.size.width - 28.0), height: CGFloat.greatestFiniteMagnitude)) - let termsOfServiceSize = self.termsOfServiceNode.updateLayout(CGSize(width: layout.size.width, height: CGFloat.greatestFiniteMagnitude)) var items: [AuthorizationLayoutItem] = [ AuthorizationLayoutItem(node: self.titleNode, size: titleSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)), AuthorizationLayoutItem(node: self.noticeNode, size: noticeSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 18.0, maxValue: 18.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)), - AuthorizationLayoutItem(node: self.phoneAndCountryNode, size: CGSize(width: layout.size.width, height: 115.0), spacingBefore: AuthorizationLayoutItemSpacing(weight: 44.0, maxValue: 44.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)), - //AuthorizationLayoutItem(node: self.termsOfServiceNode, size: termsOfServiceSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 90.0, maxValue: 90.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)), + AuthorizationLayoutItem(node: self.phoneAndCountryNode, size: CGSize(width: layout.size.width, height: 115.0), spacingBefore: AuthorizationLayoutItemSpacing(weight: 44.0, maxValue: 44.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)) ] if layoutAuthorizationItems(bounds: CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: layout.size.height - insets.top - insets.bottom - 10.0)), items: items, transition: transition, failIfDoesNotFit: true) { diff --git a/TelegramUI/BlockedPeersController.swift b/TelegramUI/BlockedPeersController.swift index 0e93556b23..79d6f937fa 100644 --- a/TelegramUI/BlockedPeersController.swift +++ b/TelegramUI/BlockedPeersController.swift @@ -33,7 +33,7 @@ private enum BlockedPeersEntryStableId: Hashable { private enum BlockedPeersEntry: ItemListNodeEntry { case add(PresentationTheme, String) - case peerItem(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, ItemListPeerItemEditing, Bool) + case peerItem(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer, ItemListPeerItemEditing, Bool) var section: ItemListSectionId { switch self { @@ -48,7 +48,7 @@ private enum BlockedPeersEntry: ItemListNodeEntry { switch self { case .add: return .add - case let .peerItem(_, _, _, _, peer, _, _): + case let .peerItem(_, _, _, _, _, peer, _, _): return .peer(peer.id) } } @@ -61,8 +61,8 @@ private enum BlockedPeersEntry: ItemListNodeEntry { } else { return false } - case let .peerItem(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsEditing, lhsEnabled): - if case let .peerItem(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsEditing, rhsEnabled) = rhs { + case let .peerItem(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameOrder, lhsPeer, lhsEditing, lhsEnabled): + if case let .peerItem(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameOrder, rhsPeer, rhsEditing, rhsEnabled) = rhs { if lhsIndex != rhsIndex { return false } @@ -75,6 +75,9 @@ private enum BlockedPeersEntry: ItemListNodeEntry { if lhsDateTimeFormat != rhsDateTimeFormat { return false } + if lhsNameOrder != rhsNameOrder { + return false + } if !lhsPeer.isEqual(rhsPeer) { return false } @@ -99,11 +102,11 @@ private enum BlockedPeersEntry: ItemListNodeEntry { } else { return false } - case let .peerItem(index, _, _, _, _, _, _): + case let .peerItem(index, _, _, _, _, _, _, _): switch rhs { case .add: return false - case let .peerItem(rhsIndex, _, _, _, _, _, _): + case let .peerItem(rhsIndex, _, _, _, _, _, _, _): return index < rhsIndex } } @@ -115,8 +118,8 @@ private enum BlockedPeersEntry: ItemListNodeEntry { return ItemListPeerActionItem(theme: theme, icon: PresentationResourcesItemList.addPersonIcon(theme), title: text, sectionId: self.section, editing: false, action: { arguments.addPeer() }) - case let .peerItem(_, theme, strings, dateTimeFormat, peer, editing, enabled): - return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, account: arguments.account, peer: peer, presence: nil, text: .none, label: .none, editing: editing, switchValue: nil, enabled: enabled, sectionId: self.section, action: { + case let .peerItem(_, theme, strings, dateTimeFormat, nameDisplayOrder, peer, editing, enabled): + return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, account: arguments.account, peer: peer, presence: nil, text: .none, label: .none, editing: editing, switchValue: nil, enabled: enabled, sectionId: self.section, action: { arguments.openPeer(peer) }, setPeerIdWithRevealedOptions: { previousId, id in arguments.setPeerIdWithRevealedOptions(previousId, id) @@ -179,7 +182,7 @@ private func blockedPeersControllerEntries(presentationData: PresentationData, s var index: Int32 = 0 for peer in peers { - entries.append(.peerItem(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, ItemListPeerItemEditing(editable: true, editing: state.editing, revealed: peer.id == state.peerIdWithRevealedOptions), state.removingPeerId != peer.id)) + entries.append(.peerItem(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, peer, ItemListPeerItemEditing(editable: true, editing: state.editing, revealed: peer.id == state.peerIdWithRevealedOptions), state.removingPeerId != peer.id)) index += 1 } } diff --git a/TelegramUI/CallController.swift b/TelegramUI/CallController.swift index 4c0e2f067d..3046b3e256 100644 --- a/TelegramUI/CallController.swift +++ b/TelegramUI/CallController.swift @@ -146,7 +146,7 @@ public final class CallController: ViewController { }) ]) ]) - strongSelf.present(actionSheet, in: .window(.root)) + strongSelf.present(actionSheet, in: .window(.calls)) } } diff --git a/TelegramUI/CallListControllerNode.swift b/TelegramUI/CallListControllerNode.swift index 43bfc70ddd..fbde141dcc 100644 --- a/TelegramUI/CallListControllerNode.swift +++ b/TelegramUI/CallListControllerNode.swift @@ -203,6 +203,7 @@ final class CallListControllerNode: ASDisplayNode { self.statePromise = ValuePromise(self.currentState, ignoreRepeated: true) self.listNode = ListView() + self.listNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor self.emptyTextNode = ASTextNode() self.emptyTextNode.alpha = 0.0 diff --git a/TelegramUI/ChangePhoneNumberController.swift b/TelegramUI/ChangePhoneNumberController.swift index cba4f82593..43b4df1647 100644 --- a/TelegramUI/ChangePhoneNumberController.swift +++ b/TelegramUI/ChangePhoneNumberController.swift @@ -11,7 +11,7 @@ final class ChangePhoneNumberController: ViewController { private let account: Account - private var currentData: (Int32, String)? + private var currentData: (Int32, String?, String)? private let requestDisposable = MetaDisposable() var inProgress: Bool = false { @@ -52,11 +52,11 @@ final class ChangePhoneNumberController: ViewController { self.requestDisposable.dispose() } - func updateData(countryCode: Int32, number: String) { - if self.currentData == nil || self.currentData! != (countryCode, number) { - self.currentData = (countryCode, number) + func updateData(countryCode: Int32, countryName: String, number: String) { + if self.currentData == nil || self.currentData! != (countryCode, countryName, number) { + self.currentData = (countryCode, countryName, number) if self.isNodeLoaded { - self.controllerNode.codeAndNumber = (countryCode, number) + self.controllerNode.codeAndNumber = (countryCode, countryName, number) } } } @@ -67,9 +67,9 @@ final class ChangePhoneNumberController: ViewController { self.controllerNode.selectCountryCode = { [weak self] in if let strongSelf = self { let controller = AuthorizationSequenceCountrySelectionController(strings: strongSelf.presentationData.strings, theme: AuthorizationSequenceCountrySelectionTheme(presentationTheme: strongSelf.presentationData.theme)) - controller.completeWithCountryCode = { code, _ in + controller.completeWithCountryCode = { code, name in if let strongSelf = self { - strongSelf.updateData(countryCode: Int32(code), number: strongSelf.controllerNode.codeAndNumber.1) + strongSelf.updateData(countryCode: Int32(code), countryName: name, number: strongSelf.controllerNode.codeAndNumber.2) strongSelf.controllerNode.activateInput() } } @@ -98,7 +98,7 @@ final class ChangePhoneNumberController: ViewController { } @objc func nextPressed() { - let (code, number) = self.controllerNode.codeAndNumber + let (code, _, number) = self.controllerNode.codeAndNumber var phoneNumber = number if let code = code { phoneNumber = "\(code)\(phoneNumber)" diff --git a/TelegramUI/ChangePhoneNumberControllerNode.swift b/TelegramUI/ChangePhoneNumberControllerNode.swift index 498c0edb76..2f2375d418 100644 --- a/TelegramUI/ChangePhoneNumberControllerNode.swift +++ b/TelegramUI/ChangePhoneNumberControllerNode.swift @@ -78,7 +78,7 @@ final class ChangePhoneNumberControllerNode: ASDisplayNode { return self.phoneInputNode.number } - var codeAndNumber: (Int32?, String) { + var codeAndNumber: (Int32?, String?, String) { get { return self.phoneInputNode.codeAndNumber } set(value) { @@ -148,9 +148,12 @@ final class ChangePhoneNumberControllerNode: ASDisplayNode { self.countryButton.addTarget(self, action: #selector(self.countryPressed), forControlEvents: .touchUpInside) - self.phoneInputNode.countryCodeUpdated = { [weak self] code in + self.phoneInputNode.countryCodeUpdated = { [weak self] code, name in if let strongSelf = self { - if let code = Int(code), let (_, countryName) = countryCodeToIdAndName[code] { + if let code = Int(code), let name = name, let countryName = countryCodeAndIdToName[CountryCodeAndId(code: code, id: name)] { + let localizedName: String = AuthorizationSequenceCountrySelectionController.lookupCountryNameById(name, strings: strongSelf.presentationData.strings) ?? countryName + strongSelf.countryButton.setTitle(localizedName, with: Font.regular(17.0), with: strongSelf.presentationData.theme.list.itemPrimaryTextColor, for: []) + } else if let code = Int(code), let (_, countryName) = countryCodeToIdAndName[code] { strongSelf.countryButton.setTitle(countryName, with: Font.regular(17.0), with: strongSelf.presentationData.theme.list.itemPrimaryTextColor, for: []) } else { strongSelf.countryButton.setTitle(strongSelf.presentationData.strings.Login_CountryCode, with: Font.regular(17.0), with: strongSelf.presentationData.theme.list.itemPrimaryTextColor, for: []) diff --git a/TelegramUI/ChannelAdminsController.swift b/TelegramUI/ChannelAdminsController.swift index e53c2fa1dd..1caa2bc5ad 100644 --- a/TelegramUI/ChannelAdminsController.swift +++ b/TelegramUI/ChannelAdminsController.swift @@ -67,7 +67,7 @@ private enum ChannelAdminsEntry: ItemListNodeEntry { case administrationInfo(PresentationTheme, String) case adminsHeader(PresentationTheme, String) - case adminPeerItem(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Bool, Int32, RenderedChannelParticipant, ItemListPeerItemEditing, Bool) + case adminPeerItem(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Bool, Int32, RenderedChannelParticipant, ItemListPeerItemEditing, Bool) case addAdmin(PresentationTheme, String, Bool) case adminsInfo(PresentationTheme, String) @@ -94,7 +94,7 @@ private enum ChannelAdminsEntry: ItemListNodeEntry { return .index(4) case .adminsInfo: return .index(5) - case let .adminPeerItem(_, _, _, _, _, participant, _, _): + case let .adminPeerItem(_, _, _, _, _, _, participant, _, _): return .peer(participant.peer.id) } } @@ -125,8 +125,8 @@ private enum ChannelAdminsEntry: ItemListNodeEntry { } else { return false } - case let .adminPeerItem(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsIsGroup, lhsIndex, lhsParticipant, lhsEditing, lhsEnabled): - if case let .adminPeerItem(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsIsGroup, rhsIndex, rhsParticipant, rhsEditing, rhsEnabled) = rhs { + case let .adminPeerItem(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameOrder, lhsIsGroup, lhsIndex, lhsParticipant, lhsEditing, lhsEnabled): + if case let .adminPeerItem(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameOrder, rhsIsGroup, rhsIndex, rhsParticipant, rhsEditing, rhsEnabled) = rhs { if lhsTheme !== rhsTheme { return false } @@ -136,6 +136,9 @@ private enum ChannelAdminsEntry: ItemListNodeEntry { if lhsDateTimeFormat != rhsDateTimeFormat { return false } + if lhsNameOrder != rhsNameOrder { + return false + } if lhsIsGroup != rhsIsGroup { return false } @@ -195,11 +198,11 @@ private enum ChannelAdminsEntry: ItemListNodeEntry { default: return true } - case let .adminPeerItem(_, _, _, _, index, _, _, _): + case let .adminPeerItem(_, _, _, _, _, index, _, _, _): switch rhs { case .recentActions, .administrationType, .administrationInfo, .adminsHeader, .addAdmin: return false - case let .adminPeerItem(_, _, _, _, rhsIndex, _, _, _): + case let .adminPeerItem(_, _, _, _, _, rhsIndex, _, _, _): return index < rhsIndex default: return true @@ -230,7 +233,7 @@ private enum ChannelAdminsEntry: ItemListNodeEntry { return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section) case let .adminsHeader(theme, title): return ItemListSectionHeaderItem(theme: theme, text: title, sectionId: self.section) - case let .adminPeerItem(theme, strings, dateTimeFormat, isGroup, _, participant, editing, enabled): + case let .adminPeerItem(theme, strings, dateTimeFormat, nameDisplayOrder, _, _, participant, editing, enabled): let peerText: String let action: (() -> Void)? switch participant.participant { @@ -251,7 +254,7 @@ private enum ChannelAdminsEntry: ItemListNodeEntry { arguments.openAdmin(participant.participant) } } - return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, account: arguments.account, peer: participant.peer, presence: nil, text: .text(peerText), label: .none, editing: editing, switchValue: nil, enabled: enabled, sectionId: self.section, action: action, setPeerIdWithRevealedOptions: { previousId, id in + return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, account: arguments.account, peer: participant.peer, presence: nil, text: .text(peerText), label: .none, editing: editing, switchValue: nil, enabled: enabled, sectionId: self.section, action: action, setPeerIdWithRevealedOptions: { previousId, id in arguments.setPeerIdWithRevealedOptions(previousId, id) }, removePeer: { peerId in arguments.removeAdmin(peerId) @@ -356,6 +359,10 @@ private struct ChannelAdminsControllerState: Equatable { } private func channelAdminsControllerEntries(presentationData: PresentationData, accountPeerId: PeerId, view: PeerView, state: ChannelAdminsControllerState, participants: [RenderedChannelParticipant]?) -> [ChannelAdminsEntry] { + if participants == nil || participants?.count == nil { + return [] + } + var entries: [ChannelAdminsEntry] = [] if let peer = view.peers[view.peerId] as? TelegramChannel { @@ -450,7 +457,7 @@ private func channelAdminsControllerEntries(presentationData: PresentationData, editable = false } } - entries.append(.adminPeerItem(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, isGroup, index, participant, ItemListPeerItemEditing(editable: editable, editing: state.editing, revealed: participant.peer.id == state.peerIdWithRevealedOptions), existingParticipantIds.contains(participant.peer.id))) + entries.append(.adminPeerItem(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, isGroup, index, participant, ItemListPeerItemEditing(editable: editable, editing: state.editing, revealed: participant.peer.id == state.peerIdWithRevealedOptions), existingParticipantIds.contains(participant.peer.id))) index += 1 } } @@ -473,7 +480,7 @@ public func channelAdminsController(account: Account, peerId: PeerId) -> ViewCon } var pushControllerImpl: ((ViewController) -> Void)? - var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)? + var presentControllerImpl: ((ViewController, Any?) -> Void)? let actionsDisposable = DisposableSet() @@ -669,11 +676,18 @@ public func channelAdminsController(account: Account, peerId: PeerId) -> ViewCon presentControllerImpl?(channelAdminController(account: account, peerId: peerId, adminId: participant.peerId, initialParticipant: participant, updated: { _ in }), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) } + }, present: { c, a in + presentControllerImpl?(c, a) }) } + var emptyStateItem: ItemListControllerEmptyStateItem? + if admins == nil || admins?.count == 0 { + emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme) + } + let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(isGroup ? presentationData.strings.ChatAdmins_Title : presentationData.strings.Channel_Management_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, secondaryRightNavigationButton: secondaryRightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) - let listState = ItemListNodeState(entries: channelAdminsControllerEntries(presentationData: presentationData, accountPeerId: account.peerId, view: view, state: state, participants: admins), style: .blocks, searchItem: searchItem, animateChanges: previous != nil && admins != nil && previous!.count >= admins!.count) + let listState = ItemListNodeState(entries: channelAdminsControllerEntries(presentationData: presentationData, accountPeerId: account.peerId, view: view, state: state, participants: admins), style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, animateChanges: previous != nil && admins != nil && previous!.count >= admins!.count) return (controllerState, (listState, arguments)) } |> afterDisposed { diff --git a/TelegramUI/ChannelBlacklistController.swift b/TelegramUI/ChannelBlacklistController.swift index afa60eaa34..0024ca3765 100644 --- a/TelegramUI/ChannelBlacklistController.swift +++ b/TelegramUI/ChannelBlacklistController.swift @@ -42,7 +42,7 @@ private enum ChannelBlacklistEntry: ItemListNodeEntry { case add(PresentationTheme, String) case restrictedHeader(PresentationTheme, String) case bannedHeader(PresentationTheme, String) - case peerItem(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Int32, ChannelBlacklistPeerCategory, RenderedChannelParticipant, ItemListPeerItemEditing, Bool, Bool) + case peerItem(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Int32, ChannelBlacklistPeerCategory, RenderedChannelParticipant, ItemListPeerItemEditing, Bool, Bool) var section: ItemListSectionId { switch self { @@ -52,7 +52,7 @@ private enum ChannelBlacklistEntry: ItemListNodeEntry { return ChannelBlacklistSection.restricted.rawValue case .bannedHeader: return ChannelBlacklistSection.banned.rawValue - case let .peerItem(_, _, _, _, category, _, _, _, _): + case let .peerItem(_, _, _, _, _, category, _, _, _, _): switch category { case .restricted: return ChannelBlacklistSection.restricted.rawValue @@ -70,7 +70,7 @@ private enum ChannelBlacklistEntry: ItemListNodeEntry { return .index(1) case .bannedHeader: return .index(2) - case let .peerItem(_, _, _, _, _, participant, _, _, _): + case let .peerItem(_, _, _, _, _, _, participant, _, _, _): return .peer(participant.peer.id) } } @@ -95,8 +95,8 @@ private enum ChannelBlacklistEntry: ItemListNodeEntry { } else { return false } - case let .peerItem(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsIndex, lhsCategory, lhsParticipant, lhsEditing, lhsEnabled, lhsCanOpen): - if case let .peerItem(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsIndex, rhsCategory, rhsParticipant, rhsEditing, rhsEnabled, rhsCanOpen) = rhs { + case let .peerItem(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameOrder, lhsIndex, lhsCategory, lhsParticipant, lhsEditing, lhsEnabled, lhsCanOpen): + if case let .peerItem(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameOrder, rhsIndex, rhsCategory, rhsParticipant, rhsEditing, rhsEnabled, rhsCanOpen) = rhs { if lhsTheme !== rhsTheme { return false } @@ -106,6 +106,9 @@ private enum ChannelBlacklistEntry: ItemListNodeEntry { if lhsDateTimeFormat != rhsDateTimeFormat { return false } + if lhsNameOrder != rhsNameOrder { + return false + } if lhsIndex != rhsIndex { return false } @@ -151,7 +154,7 @@ private enum ChannelBlacklistEntry: ItemListNodeEntry { switch rhs { case .add, .restrictedHeader, .bannedHeader: return false - case let .peerItem(_, _, _, _, category, _, _, _, _): + case let .peerItem(_, _, _, _, _, category, _, _, _, _): switch category { case .restricted: return false @@ -159,7 +162,7 @@ private enum ChannelBlacklistEntry: ItemListNodeEntry { return true } } - case let .peerItem(_, _, _, index, category, _, _, _, _): + case let .peerItem(_, _, _, _, index, category, _, _, _, _): switch rhs { case .add, .restrictedHeader: return false @@ -170,7 +173,7 @@ private enum ChannelBlacklistEntry: ItemListNodeEntry { case .banned: return false } - case let .peerItem(_, _, _, rhsIndex, _, _, _, _, _): + case let .peerItem(_, _, _, _, rhsIndex, _, _, _, _, _): return index < rhsIndex } } @@ -186,7 +189,7 @@ private enum ChannelBlacklistEntry: ItemListNodeEntry { return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) case let .bannedHeader(theme, text): return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) - case let .peerItem(theme, strings, dateTimeFormat, _, _, participant, editing, enabled, canOpen): + case let .peerItem(theme, strings, dateTimeFormat, nameDisplayOrder, _, _, participant, editing, enabled, canOpen): var text: ItemListPeerItemText = .none switch participant.participant { case let .member(_, _, _, banInfo): @@ -196,7 +199,7 @@ private enum ChannelBlacklistEntry: ItemListNodeEntry { default: break } - return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, account: arguments.account, peer: participant.peer, presence: nil, text: text, label: .none, editing: editing, switchValue: nil, enabled: enabled, sectionId: self.section, action: canOpen ? { + return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, account: arguments.account, peer: participant.peer, presence: nil, text: text, label: .none, editing: editing, switchValue: nil, enabled: enabled, sectionId: self.section, action: canOpen ? { arguments.openPeer(participant.participant) } : { arguments.openPeerInfo(participant.peer) @@ -285,7 +288,7 @@ private func channelBlacklistControllerEntries(presentationData: PresentationDat if case .creator = participant.participant { editable = false } - entries.append(.peerItem(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, index, .restricted, participant, ItemListPeerItemEditing(editable: editable, editing: state.editing, revealed: participant.peer.id == state.peerIdWithRevealedOptions), state.removingPeerId != participant.peer.id, canOpen)) + entries.append(.peerItem(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, index, .restricted, participant, ItemListPeerItemEditing(editable: editable, editing: state.editing, revealed: participant.peer.id == state.peerIdWithRevealedOptions), state.removingPeerId != participant.peer.id, canOpen)) index += 1 } if !blacklist.banned.isEmpty { @@ -296,7 +299,7 @@ private func channelBlacklistControllerEntries(presentationData: PresentationDat if case .creator = participant.participant { editable = false } - entries.append(.peerItem(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, index, .banned, participant, ItemListPeerItemEditing(editable: editable, editing: state.editing, revealed: participant.peer.id == state.peerIdWithRevealedOptions), state.removingPeerId != participant.peer.id, canOpen)) + entries.append(.peerItem(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, index, .banned, participant, ItemListPeerItemEditing(editable: editable, editing: state.editing, revealed: participant.peer.id == state.peerIdWithRevealedOptions), state.removingPeerId != participant.peer.id, canOpen)) index += 1 } } @@ -311,7 +314,7 @@ public func channelBlacklistController(account: Account, peerId: PeerId) -> View statePromise.set(stateValue.modify { f($0) }) } - var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)? + var presentControllerImpl: ((ViewController, Any?) -> Void)? var pushControllerImpl: ((ViewController) -> Void)? let actionsDisposable = DisposableSet() @@ -477,6 +480,8 @@ public func channelBlacklistController(account: Account, peerId: PeerId) -> View arguments.openPeerInfo(rendered.peer) } } + }, present: { c, a in + presentControllerImpl?(c, a) }) } diff --git a/TelegramUI/ChannelMemberCategoryListContext.swift b/TelegramUI/ChannelMemberCategoryListContext.swift index 7e6df18ec8..ec0de98429 100644 --- a/TelegramUI/ChannelMemberCategoryListContext.swift +++ b/TelegramUI/ChannelMemberCategoryListContext.swift @@ -379,9 +379,29 @@ private final class ChannelMemberSingleCategoryListContext: ChannelMemberCategor } } case let .recentSearch(query): - break - default: - break + if let updated = updated, isParticipantMember(updated.participant), updated.peer.indexName.matchesByTokens(query) { + var found = false + loop: for i in 0 ..< list.count { + if list[i].peer.id == updated.peer.id { + list[i] = updated + found = true + updatedList = true + break loop + } + } + if !found { + list.insert(updated, at: 0) + updatedList = true + } + } else if let previous = previous, isParticipantMember(previous) { + loop: for i in 0 ..< list.count { + if list[i].peer.id == previous.peerId { + list.remove(at: i) + updatedList = true + break loop + } + } + } } } if updatedList { diff --git a/TelegramUI/ChannelMembersController.swift b/TelegramUI/ChannelMembersController.swift index 10316c296d..1d95e6589b 100644 --- a/TelegramUI/ChannelMembersController.swift +++ b/TelegramUI/ChannelMembersController.swift @@ -62,7 +62,7 @@ private enum ChannelMembersEntry: ItemListNodeEntry { case addMember(PresentationTheme, String) case addMemberInfo(PresentationTheme, String) case inviteLink(PresentationTheme, String) - case peerItem(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, RenderedChannelParticipant, ItemListPeerItemEditing, Bool) + case peerItem(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, RenderedChannelParticipant, ItemListPeerItemEditing, Bool) var section: ItemListSectionId { switch self { @@ -81,7 +81,7 @@ private enum ChannelMembersEntry: ItemListNodeEntry { return .index(1) case .inviteLink: return .index(2) - case let .peerItem(_, _, _, _, participant, _, _): + case let .peerItem(_, _, _, _, _, participant, _, _): return .peer(participant.peer.id) } } @@ -106,8 +106,8 @@ private enum ChannelMembersEntry: ItemListNodeEntry { } else { return false } - case let .peerItem(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsParticipant, lhsEditing, lhsEnabled): - if case let .peerItem(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsParticipant, rhsEditing, rhsEnabled) = rhs { + case let .peerItem(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameOrder, lhsParticipant, lhsEditing, lhsEnabled): + if case let .peerItem(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameOrder, rhsParticipant, rhsEditing, rhsEnabled) = rhs { if lhsIndex != rhsIndex { return false } @@ -120,6 +120,9 @@ private enum ChannelMembersEntry: ItemListNodeEntry { if lhsDateTimeFormat != rhsDateTimeFormat { return false } + if lhsNameOrder != rhsNameOrder { + return false + } if lhsParticipant != rhsParticipant { return false } @@ -155,9 +158,9 @@ private enum ChannelMembersEntry: ItemListNodeEntry { return true } - case let .peerItem(index, _, _, _, _, _, _): + case let .peerItem(index, _, _, _, _, _, _, _): switch rhs { - case let .peerItem(rhsIndex, _, _, _, _, _, _): + case let .peerItem(rhsIndex, _, _, _, _, _, _, _): return index < rhsIndex case .addMember, .addMemberInfo, .inviteLink: return false @@ -177,9 +180,8 @@ private enum ChannelMembersEntry: ItemListNodeEntry { }) case let .addMemberInfo(theme, text): return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section) - case let .peerItem(_, theme, strings, dateTimeFormat, participant, editing, enabled): - - return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, account: arguments.account, peer: participant.peer, presence: participant.presences[participant.peer.id], text: .presence, label: .none, editing: editing, switchValue: nil, enabled: enabled, sectionId: self.section, action: { + case let .peerItem(_, theme, strings, dateTimeFormat, nameDisplayOrder, participant, editing, enabled): + return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, account: arguments.account, peer: participant.peer, presence: participant.presences[participant.peer.id], text: .presence, label: .none, editing: editing, switchValue: nil, enabled: enabled, sectionId: self.section, action: { arguments.openPeer(participant.peer) }, setPeerIdWithRevealedOptions: { previousId, id in arguments.setPeerIdWithRevealedOptions(previousId, id) @@ -244,6 +246,10 @@ private struct ChannelMembersControllerState: Equatable { } private func ChannelMembersControllerEntries(account: Account, presentationData: PresentationData, view: PeerView, state: ChannelMembersControllerState, participants: [RenderedChannelParticipant]?) -> [ChannelMembersEntry] { + if participants == nil || participants?.count == nil { + return [] + } + var entries: [ChannelMembersEntry] = [] if let participants = participants { @@ -295,7 +301,7 @@ private func ChannelMembersControllerEntries(account: Account, presentationData: editable = canEditMembers } } - entries.append(.peerItem(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, participant, ItemListPeerItemEditing(editable: editable, editing: state.editing, revealed: participant.peer.id == state.peerIdWithRevealedOptions), state.removingPeerId != participant.peer.id)) + entries.append(.peerItem(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, participant, ItemListPeerItemEditing(editable: editable, editing: state.editing, revealed: participant.peer.id == state.peerIdWithRevealedOptions), state.removingPeerId != participant.peer.id)) index += 1 } } @@ -310,7 +316,7 @@ public func channelMembersController(account: Account, peerId: PeerId) -> ViewCo statePromise.set(stateValue.modify { f($0) }) } - var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)? + var presentControllerImpl: ((ViewController, Any?) -> Void)? var pushControllerImpl: ((ViewController) -> Void)? let actionsDisposable = DisposableSet() @@ -444,11 +450,13 @@ public func channelMembersController(account: Account, peerId: PeerId) -> ViewCo pushControllerImpl?(infoController) // arguments.pushController(infoController) } + }, present: { c, a in + presentControllerImpl?(c, a) }) } var emptyStateItem: ItemListControllerEmptyStateItem? - if peers == nil { + if peers == nil || peers?.count == 0 { emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme) } diff --git a/TelegramUI/ChannelMembersSearchContainerNode.swift b/TelegramUI/ChannelMembersSearchContainerNode.swift index a8341d1d23..6150a55a96 100644 --- a/TelegramUI/ChannelMembersSearchContainerNode.swift +++ b/TelegramUI/ChannelMembersSearchContainerNode.swift @@ -35,7 +35,7 @@ private enum ChannelMembersSearchSection { private enum ChannelMembersSearchContent: Equatable { case peer(Peer) - case participant(participant: RenderedChannelParticipant, label: String?, revealActions: [ParticipantRevealAction], enabled: Bool) + case participant(participant: RenderedChannelParticipant, label: String?, revealActions: [ParticipantRevealAction], revealed: Bool, enabled: Bool) static func ==(lhs: ChannelMembersSearchContent, rhs: ChannelMembersSearchContent) -> Bool { switch lhs { @@ -45,8 +45,8 @@ private enum ChannelMembersSearchContent: Equatable { } else { return false } - case let .participant(participant, label, revealActions, enabled): - if case .participant(participant, label, revealActions, enabled) = rhs { + case let .participant(participant, label, revealActions, revealed, enabled): + if case .participant(participant, label, revealActions, revealed, enabled) = rhs { return true } else { return false @@ -58,12 +58,28 @@ private enum ChannelMembersSearchContent: Equatable { switch self { case let .peer(peer): return peer.id - case let .participant(participant, _, _, _): + case let .participant(participant, _, _, _, _): return participant.peer.id } } } +private final class ChannelMembersSearchContainerInteraction { + let peerSelected: (Peer, RenderedChannelParticipant?) -> Void + let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void + let promotePeer: (RenderedChannelParticipant) -> Void + let restrictPeer: (RenderedChannelParticipant) -> Void + let removePeer: (PeerId) -> Void + + init(peerSelected: @escaping (Peer, RenderedChannelParticipant?) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, promotePeer: @escaping (RenderedChannelParticipant) -> Void, restrictPeer: @escaping (RenderedChannelParticipant) -> Void, removePeer: @escaping (PeerId) -> Void) { + self.peerSelected = peerSelected + self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions + self.promotePeer = promotePeer + self.restrictPeer = restrictPeer + self.removePeer = removePeer + } +} + private final class ChannelMembersSearchEntry: Comparable, Identifiable { let index: Int let content: ChannelMembersSearchContent @@ -87,31 +103,40 @@ private final class ChannelMembersSearchEntry: Comparable, Identifiable { return lhs.index < rhs.index } - func item(account: Account, theme: PresentationTheme, strings: PresentationStrings, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, peerSelected: @escaping (Peer, RenderedChannelParticipant?) -> Void) -> ListViewItem { + func item(account: Account, theme: PresentationTheme, strings: PresentationStrings, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, interaction: ChannelMembersSearchContainerInteraction) -> ListViewItem { switch self.content { case let .peer(peer): return ContactsPeerItem(theme: theme, strings: strings, sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, account: account, peerMode: .peer, peer: .peer(peer: peer, chatPeer: peer), status: .none, enabled: true, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: self.section.chatListHeaderType.flatMap({ ChatListSearchItemHeader(type: $0, theme: theme, strings: strings, actionTitle: nil, action: nil) }), action: { _ in - peerSelected(peer, nil) + interaction.peerSelected(peer, nil) }) - case let .participant(participant, label, revealActions, enabled): + case let .participant(participant, label, revealActions, revealed, enabled): + let status: ContactsPeerItemStatus + if let label = label { + status = .custom(label) + } else { + status = .none + } + var options: [ItemListPeerItemRevealOption] = [] for action in revealActions { options.append(ItemListPeerItemRevealOption(type: action.type, title: action.title, action: { switch action.action { case .promote: - //arguments.promotePeer(participant) + interaction.promotePeer(participant) break case .restrict: - //arguments.restrictPeer(participant) + interaction.restrictPeer(participant) break case .remove: - //arguments.removePeer(peer.id) + interaction.removePeer(participant.peer.id) break } })) } - return ContactsPeerItem(theme: theme, strings: strings, sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, account: account, peerMode: .peer, peer: .peer(peer: participant.peer, chatPeer: participant.peer), status: .none, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: self.section.chatListHeaderType.flatMap({ ChatListSearchItemHeader(type: $0, theme: theme, strings: strings, actionTitle: nil, action: nil) }), action: { _ in - peerSelected(participant.peer, participant) + return ContactsPeerItem(theme: theme, strings: strings, sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, account: account, peerMode: .peer, peer: .peer(peer: participant.peer, chatPeer: participant.peer), status: status, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: revealed), options: options, index: nil, header: self.section.chatListHeaderType.flatMap({ ChatListSearchItemHeader(type: $0, theme: theme, strings: strings, actionTitle: nil, action: nil) }), action: { _ in + interaction.peerSelected(participant.peer, participant) + }, setPeerIdWithRevealedOptions: { peerId, fromPeerId in + interaction.setPeerIdWithRevealedOptions(peerId, fromPeerId) }) } } @@ -123,16 +148,21 @@ struct ChannelMembersSearchContainerTransition { let isSearching: Bool } -private func channelMembersSearchContainerPreparedRecentTransition(from fromEntries: [ChannelMembersSearchEntry], to toEntries: [ChannelMembersSearchEntry], isSearching: Bool, account: Account, theme: PresentationTheme, strings: PresentationStrings, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, peerSelected: @escaping (Peer, RenderedChannelParticipant?) -> Void) -> ChannelMembersSearchContainerTransition { +private func channelMembersSearchContainerPreparedRecentTransition(from fromEntries: [ChannelMembersSearchEntry], to toEntries: [ChannelMembersSearchEntry], isSearching: Bool, account: Account, theme: PresentationTheme, strings: PresentationStrings, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, interaction: ChannelMembersSearchContainerInteraction) -> ChannelMembersSearchContainerTransition { 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, theme: theme, strings: strings, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, peerSelected: peerSelected), directionHint: nil) } - let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: theme, strings: strings, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, peerSelected: peerSelected), directionHint: nil) } + let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: theme, strings: strings, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, interaction: interaction), directionHint: nil) } + let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: theme, strings: strings, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, interaction: interaction), directionHint: nil) } return ChannelMembersSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, isSearching: isSearching) } +private struct ChannelMembersSearchContainerState: Equatable { + var revealedPeerId: PeerId? + var removingParticipantIds = Set() +} + final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNode { private let account: Account private let openPeer: (Peer, RenderedChannelParticipant?) -> Void @@ -150,9 +180,11 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod private var presentationData: PresentationData private var presentationDataDisposable: Disposable? + private let removeMemberDisposable = MetaDisposable() + private let themeAndStringsPromise: Promise<(PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder)> - init(account: Account, peerId: PeerId, mode: ChannelMembersSearchMode, filters: [ChannelMembersSearchFilter], openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void, updateActivity: @escaping(Bool)->Void) { + init(account: Account, peerId: PeerId, mode: ChannelMembersSearchMode, filters: [ChannelMembersSearchFilter], openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void, updateActivity: @escaping (Bool) -> Void, present: @escaping (ViewController, Any?) -> Void) { self.account = account self.openPeer = openPeer self.mode = mode @@ -173,194 +205,323 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod self.addSubnode(self.dimNode) self.addSubnode(self.listNode) - let themeAndStringsPromise = self.themeAndStringsPromise - let foundItems = searchQuery.get() - |> mapToSignal { query -> Signal<[ChannelMembersSearchEntry]?, NoError> in - updateActivity(true) - if let query = query, !query.isEmpty { - let foundGroupMembers: Signal<[RenderedChannelParticipant], NoError> - let foundMembers: Signal<[RenderedChannelParticipant], NoError> - - switch mode { - case .searchMembers, .banAndPromoteActions: - foundGroupMembers = Signal { subscriber in - let (disposable, _) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.recent(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, peerId: peerId, searchQuery: query, updated: { state in - if case .ready = state.loadingState { - subscriber.putNext(state.list) - subscriber.putCompletion() - } - }) - return disposable - } - |> runOn(Queue.mainQueue()) - foundMembers = .single([]) - case .inviteActions: - foundGroupMembers = .single([]) - foundMembers = channelMembers(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, peerId: peerId, category: .recent(.search(query))) - |> map { $0 ?? [] } - case .searchAdmins: - foundGroupMembers = Signal { subscriber in - let (disposable, _) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.admins(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, peerId: peerId, searchQuery: query, updated: { state in - if case .ready = state.loadingState { - subscriber.putNext(state.list) - subscriber.putCompletion() - } - }) - return disposable - } |> runOn(Queue.mainQueue()) - foundMembers = .single([]) - case .searchBanned: - foundGroupMembers = Signal { subscriber in - let (disposable, _) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.restrictedAndBanned(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, peerId: peerId, searchQuery: query, updated: { state in - if case .ready = state.loadingState { - subscriber.putNext(state.list) - subscriber.putCompletion() - } - }) - return disposable - } |> runOn(Queue.mainQueue()) - foundMembers = .single([]) + let statePromise = ValuePromise(ChannelMembersSearchContainerState(), ignoreRepeated: true) + let stateValue = Atomic(value: ChannelMembersSearchContainerState()) + let updateState: ((ChannelMembersSearchContainerState) -> ChannelMembersSearchContainerState) -> Void = { f in + statePromise.set(stateValue.modify { f($0) }) + } + + let removeMemberDisposable = self.removeMemberDisposable + let interaction = ChannelMembersSearchContainerInteraction(peerSelected: { peer, participant in + openPeer(peer, participant) + }, setPeerIdWithRevealedOptions: { peerId, fromPeerId in + updateState { state in + var state = state + if (peerId == nil && fromPeerId == state.revealedPeerId) || (peerId != nil && fromPeerId == nil) { + state.revealedPeerId = peerId + } + return state + } + }, promotePeer: { participant in + present(channelAdminController(account: account, peerId: peerId, adminId: participant.peer.id, initialParticipant: participant.participant, updated: { _ in + }), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + }, restrictPeer: { participant in + present(channelBannedMemberController(account: account, peerId: peerId, memberId: participant.peer.id, initialParticipant: participant.participant, updated: { _ in + }), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + }, removePeer: { memberId in + let signal = account.postbox.loadedPeerWithId(memberId) + |> deliverOnMainQueue + |> mapToSignal { peer -> Signal in + let result = ValuePromise() + result.set(true) + return result.get() + } + |> mapToSignal { value -> Signal in + if value { + updateState { state in + var state = state + state.removingParticipantIds.insert(memberId) + return state } - let foundContacts: Signal<([Peer], [PeerId: PeerPresence]), NoError> - let foundRemotePeers: Signal<([FoundPeer], [FoundPeer]), NoError> - switch mode { - case .inviteActions, .banAndPromoteActions: - foundContacts = account.postbox.searchContacts(query: query.lowercased()) - foundRemotePeers = .single(([], [])) |> then(searchPeers(account: account, query: query) - |> delay(0.2, queue: Queue.concurrentDefaultQueue())) - case .searchMembers, .searchBanned, .searchAdmins: - foundContacts = .single(([], [:])) - foundRemotePeers = .single(([], [])) + if peerId.namespace == Namespaces.Peer.CloudChannel { + return account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(account: account, peerId: peerId, memberId: memberId, bannedRights: TelegramChannelBannedRights(flags: [.banReadMessages], untilDate: Int32.max)) + |> afterDisposed { + Queue.mainQueue().async { + updateState { state in + var state = state + state.removingParticipantIds.remove(memberId) + return state + } + } + } } - return combineLatest(foundGroupMembers, foundMembers, foundContacts, foundRemotePeers, themeAndStringsPromise.get()) - |> map { foundGroupMembers, foundMembers, foundContacts, foundRemotePeers, themeAndStrings -> [ChannelMembersSearchEntry]? in - var entries: [ChannelMembersSearchEntry] = [] - - var existingPeerIds = Set() - for filter in filters { - switch filter { - case let .exclude(ids): - existingPeerIds = existingPeerIds.union(ids) - case .disable: - break - } - } - switch mode { - case .inviteActions, .banAndPromoteActions: - existingPeerIds.insert(account.peerId) - case .searchMembers, .searchAdmins, .searchBanned: - break - } - - var index = 0 - - for participant in foundGroupMembers { - if !existingPeerIds.contains(participant.peer.id) { - existingPeerIds.insert(participant.peer.id) - let section: ChannelMembersSearchSection - switch mode { - case .inviteActions, .banAndPromoteActions: - section = .members - case .searchMembers, .searchBanned, .searchAdmins: - section = .none - } - - var label: String? - var enabled = true - if case .banAndPromoteActions = mode { - if case .creator = participant.participant { - label = themeAndStrings.1.Channel_Management_LabelCreator - enabled = false - } - } - switch mode { - case .searchAdmins: - switch participant.participant { - case .creator: - label = themeAndStrings.1.Channel_Management_LabelCreator - case let .member(_, _, adminInfo, _): - if let adminInfo = adminInfo { - if let peer = participant.peers[adminInfo.promotedBy] { - label = themeAndStrings.1.Channel_Management_PromotedBy(peer.displayTitle).0 - } - } - } - case .searchBanned: - switch participant.participant { - case let .member(_, _, _, banInfo): - if let banInfo = banInfo, let peer = participant.peers[banInfo.restrictedBy] { - label = themeAndStrings.1.Channel_Management_RestrictedBy(peer.displayTitle).0 - } - default: - break - } - default: - break - } - entries.append(ChannelMembersSearchEntry(index: index, content: .participant(participant: participant, label: label, revealActions: [], enabled: enabled), section: section)) - index += 1 - } - } - - for participant in foundMembers { - if !existingPeerIds.contains(participant.peer.id) { - existingPeerIds.insert(participant.peer.id) - let section: ChannelMembersSearchSection - switch mode { - case .inviteActions, .banAndPromoteActions: - section = .members - case .searchMembers, .searchBanned, .searchAdmins: - section = .none - } - - var label: String? - var enabled = true - if case .banAndPromoteActions = mode { - if case .creator = participant.participant { - label = themeAndStrings.1.Channel_Management_LabelCreator - enabled = false - } - } - - - entries.append(ChannelMembersSearchEntry(index: index, content: .participant(participant: participant, label: label, revealActions: [], enabled: enabled), section: section)) - index += 1 - } - } - - for peer in foundContacts.0 { - if !existingPeerIds.contains(peer.id) { - existingPeerIds.insert(peer.id) - entries.append(ChannelMembersSearchEntry(index: index, content: .peer(peer), section: .contacts)) - index += 1 - } - } - - for foundPeer in foundRemotePeers.0 { - let peer = foundPeer.peer - if !existingPeerIds.contains(peer.id) && peer is TelegramUser { - existingPeerIds.insert(peer.id) - entries.append(ChannelMembersSearchEntry(index: index, content: .peer(peer), section: .global)) - index += 1 - } - } - - for foundPeer in foundRemotePeers.1 { - let peer = foundPeer.peer - if !existingPeerIds.contains(peer.id) && peer is TelegramUser { - existingPeerIds.insert(peer.id) - entries.append(ChannelMembersSearchEntry(index: index, content: .peer(peer), section: .global)) - index += 1 - } - } - - return entries + return removePeerMember(account: account, peerId: peerId, memberId: memberId) + |> deliverOnMainQueue + |> afterDisposed { + updateState { state in + var state = state + state.removingParticipantIds.remove(memberId) + return state + } } } else { - return .single(nil) + return .complete() } + } + removeMemberDisposable.set(signal.start()) + }) + + let themeAndStringsPromise = self.themeAndStringsPromise + let foundItems = combineLatest(searchQuery.get(), account.postbox.multiplePeersView([peerId]) |> take(1)) + |> mapToSignal { query, peerView -> Signal<[ChannelMembersSearchEntry]?, NoError> in + guard let channel = peerView.peers[peerId] as? TelegramChannel else { + return .single(nil) + } + updateActivity(true) + if let query = query, !query.isEmpty { + let foundGroupMembers: Signal<[RenderedChannelParticipant], NoError> + let foundMembers: Signal<[RenderedChannelParticipant], NoError> + + switch mode { + case .searchMembers, .banAndPromoteActions: + foundGroupMembers = Signal { subscriber in + let (disposable, _) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.recent(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, peerId: peerId, searchQuery: query, updated: { state in + if case .ready = state.loadingState { + subscriber.putNext(state.list) + } + }) + return disposable + } + |> runOn(Queue.mainQueue()) + foundMembers = .single([]) + case .inviteActions: + foundGroupMembers = .single([]) + foundMembers = channelMembers(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, peerId: peerId, category: .recent(.search(query))) + |> map { $0 ?? [] } + case .searchAdmins: + foundGroupMembers = Signal { subscriber in + let (disposable, _) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.admins(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, peerId: peerId, searchQuery: query, updated: { state in + if case .ready = state.loadingState { + subscriber.putNext(state.list) + subscriber.putCompletion() + } + }) + return disposable + } |> runOn(Queue.mainQueue()) + foundMembers = .single([]) + case .searchBanned: + foundGroupMembers = Signal { subscriber in + let (disposable, _) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.restrictedAndBanned(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, peerId: peerId, searchQuery: query, updated: { state in + if case .ready = state.loadingState { + subscriber.putNext(state.list) + subscriber.putCompletion() + } + }) + return disposable + } + |> runOn(Queue.mainQueue()) + foundMembers = .single([]) + } + + let foundContacts: Signal<([Peer], [PeerId: PeerPresence]), NoError> + let foundRemotePeers: Signal<([FoundPeer], [FoundPeer]), NoError> + switch mode { + case .inviteActions, .banAndPromoteActions: + foundContacts = account.postbox.searchContacts(query: query.lowercased()) + foundRemotePeers = .single(([], [])) |> then(searchPeers(account: account, query: query) + |> delay(0.2, queue: Queue.concurrentDefaultQueue())) + case .searchMembers, .searchBanned, .searchAdmins: + foundContacts = .single(([], [:])) + foundRemotePeers = .single(([], [])) + } + + return combineLatest(foundGroupMembers, foundMembers, foundContacts, foundRemotePeers, themeAndStringsPromise.get(), statePromise.get()) + |> map { foundGroupMembers, foundMembers, foundContacts, foundRemotePeers, themeAndStrings, state -> [ChannelMembersSearchEntry]? in + var entries: [ChannelMembersSearchEntry] = [] + + var existingPeerIds = Set() + for filter in filters { + switch filter { + case let .exclude(ids): + existingPeerIds = existingPeerIds.union(ids) + case .disable: + break + } + } + switch mode { + case .inviteActions, .banAndPromoteActions: + existingPeerIds.insert(account.peerId) + case .searchMembers, .searchAdmins, .searchBanned: + break + } + + var index = 0 + + for participant in foundGroupMembers { + if !existingPeerIds.contains(participant.peer.id) { + existingPeerIds.insert(participant.peer.id) + let section: ChannelMembersSearchSection + switch mode { + case .inviteActions, .banAndPromoteActions: + section = .members + case .searchMembers, .searchBanned, .searchAdmins: + section = .none + } + + var canPromote: Bool + var canRestrict: Bool + switch participant.participant { + case .creator: + canPromote = false + canRestrict = false + case let .member(_, _, adminRights, bannedRights): + if channel.hasAdminRights([.canAddAdmins]) { + canPromote = true + } else { + canPromote = false + } + if channel.hasAdminRights([.canBanUsers]) { + canRestrict = true + } else { + canRestrict = false + } + if canPromote { + if let bannedRights = bannedRights { + if bannedRights.restrictedBy != account.peerId && !channel.flags.contains(.isCreator) { + canPromote = false + } + } + } + if canRestrict { + if let adminRights = adminRights { + if adminRights.promotedBy != account.peerId && !channel.flags.contains(.isCreator) { + canRestrict = false + } + } + } + } + + var label: String? + var enabled = true + if case .banAndPromoteActions = mode { + if case .creator = participant.participant { + label = themeAndStrings.1.Channel_Management_LabelCreator + enabled = false + } + } else if case .searchMembers = mode { + switch participant.participant { + case .creator: + label = themeAndStrings.1.Channel_Management_LabelCreator + case let .member(member): + if member.adminInfo != nil { + label = themeAndStrings.1.Channel_Management_LabelEditor + } + } + } + + if state.removingParticipantIds.contains(participant.peer.id) { + enabled = false + } + + var peerActions: [ParticipantRevealAction] = [] + if case .searchMembers = mode { + if canPromote { + peerActions.append(ParticipantRevealAction(type: .neutral, title: themeAndStrings.1.GroupInfo_ActionPromote, action: .promote)) + } + if canRestrict { + peerActions.append(ParticipantRevealAction(type: .warning, title: themeAndStrings.1.GroupInfo_ActionRestrict, action: .restrict)) + peerActions.append(ParticipantRevealAction(type: .destructive, title: themeAndStrings.1.Common_Delete, action: .remove)) + } + } + + switch mode { + case .searchAdmins: + switch participant.participant { + case .creator: + label = themeAndStrings.1.Channel_Management_LabelCreator + case let .member(_, _, adminInfo, _): + if let adminInfo = adminInfo { + if let peer = participant.peers[adminInfo.promotedBy] { + label = themeAndStrings.1.Channel_Management_PromotedBy(peer.displayTitle).0 + } + } + } + case .searchBanned: + switch participant.participant { + case let .member(_, _, _, banInfo): + if let banInfo = banInfo, let peer = participant.peers[banInfo.restrictedBy] { + label = themeAndStrings.1.Channel_Management_RestrictedBy(peer.displayTitle).0 + } + default: + break + } + default: + break + } + entries.append(ChannelMembersSearchEntry(index: index, content: .participant(participant: participant, label: label, revealActions: peerActions, revealed: state.revealedPeerId == participant.peer.id, enabled: enabled), section: section)) + index += 1 + } + } + + for participant in foundMembers { + if !existingPeerIds.contains(participant.peer.id) { + existingPeerIds.insert(participant.peer.id) + let section: ChannelMembersSearchSection + switch mode { + case .inviteActions, .banAndPromoteActions: + section = .members + case .searchMembers, .searchBanned, .searchAdmins: + section = .none + } + + var label: String? + var enabled = true + if case .banAndPromoteActions = mode { + if case .creator = participant.participant { + label = themeAndStrings.1.Channel_Management_LabelCreator + enabled = false + } + } + + + entries.append(ChannelMembersSearchEntry(index: index, content: .participant(participant: participant, label: label, revealActions: [], revealed: false, enabled: enabled), section: section)) + index += 1 + } + } + + for peer in foundContacts.0 { + if !existingPeerIds.contains(peer.id) { + existingPeerIds.insert(peer.id) + entries.append(ChannelMembersSearchEntry(index: index, content: .peer(peer), section: .contacts)) + index += 1 + } + } + + for foundPeer in foundRemotePeers.0 { + let peer = foundPeer.peer + if !existingPeerIds.contains(peer.id) && peer is TelegramUser { + existingPeerIds.insert(peer.id) + entries.append(ChannelMembersSearchEntry(index: index, content: .peer(peer), section: .global)) + index += 1 + } + } + + for foundPeer in foundRemotePeers.1 { + let peer = foundPeer.peer + if !existingPeerIds.contains(peer.id) && peer is TelegramUser { + existingPeerIds.insert(peer.id) + entries.append(ChannelMembersSearchEntry(index: index, content: .peer(peer), section: .global)) + index += 1 + } + } + + return entries + } + } else { + return .single(nil) + } } let previousSearchItems = Atomic<[ChannelMembersSearchEntry]?>(value: nil) @@ -371,24 +532,24 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod let previousEntries = previousSearchItems.swap(entries) updateActivity(false) let firstTime = previousEntries == nil - let transition = channelMembersSearchContainerPreparedRecentTransition(from: previousEntries ?? [], to: entries ?? [], isSearching: entries != nil, account: account, theme: themeAndStrings.0, strings: themeAndStrings.1, nameSortOrder: themeAndStrings.2, nameDisplayOrder: themeAndStrings.3, peerSelected: openPeer) + let transition = channelMembersSearchContainerPreparedRecentTransition(from: previousEntries ?? [], to: entries ?? [], isSearching: entries != nil, account: account, theme: themeAndStrings.0, strings: themeAndStrings.1, nameSortOrder: themeAndStrings.2, nameDisplayOrder: themeAndStrings.3, interaction: interaction) strongSelf.enqueueTransition(transition, firstTime: firstTime) } })) self.presentationDataDisposable = (account.telegramApplicationContext.presentationData - |> deliverOnMainQueue).start(next: { [weak self] presentationData in - if let strongSelf = self { - let previousTheme = strongSelf.presentationData.theme - let previousStrings = strongSelf.presentationData.strings - - strongSelf.presentationData = presentationData - - if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings { - strongSelf.updateThemeAndStrings(theme: presentationData.theme, strings: presentationData.strings) - } + |> deliverOnMainQueue).start(next: { [weak self] presentationData in + if let strongSelf = self { + let previousTheme = strongSelf.presentationData.theme + let previousStrings = strongSelf.presentationData.strings + + strongSelf.presentationData = presentationData + + if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings { + strongSelf.updateThemeAndStrings(theme: presentationData.theme, strings: presentationData.strings) } - }) + } + }) self.listNode.beganInteractiveDragging = { [weak self] in self?.dismissInput?() @@ -398,6 +559,7 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod deinit { self.searchDisposable.dispose() self.presentationDataDisposable?.dispose() + self.removeMemberDisposable.dispose() } override func didLoad() { diff --git a/TelegramUI/ChannelMembersSearchController.swift b/TelegramUI/ChannelMembersSearchController.swift index d7e4cde8f7..8c81ddea88 100644 --- a/TelegramUI/ChannelMembersSearchController.swift +++ b/TelegramUI/ChannelMembersSearchController.swift @@ -70,6 +70,9 @@ final class ChannelMembersSearchController: ViewController { self?.dismiss() self?.openPeer(peer, participant) } + self.controllerNode.present = { [weak self] c, a in + self?.present(c, in: .window(.root), with: a) + } self.displayNodeDidLoad() } diff --git a/TelegramUI/ChannelMembersSearchControllerNode.swift b/TelegramUI/ChannelMembersSearchControllerNode.swift index 9a39e7a9a5..50fc68e8b7 100644 --- a/TelegramUI/ChannelMembersSearchControllerNode.swift +++ b/TelegramUI/ChannelMembersSearchControllerNode.swift @@ -121,6 +121,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { var requestActivateSearch: (() -> Void)? var requestDeactivateSearch: (() -> Void)? var requestOpenPeerFromSearch: ((Peer, RenderedChannelParticipant?) -> Void)? + var present: ((ViewController, Any?) -> Void)? var themeAndStrings: (PresentationTheme, PresentationStrings) @@ -292,6 +293,8 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { self?.requestOpenPeerFromSearch?(peer, participant) }, updateActivity: { value in + }, present: { [weak self] c, a in + self?.present?(c, a) }), cancel: { [weak self] in if let requestDeactivateSearch = self?.requestDeactivateSearch { requestDeactivateSearch() diff --git a/TelegramUI/ChannelVisibilityController.swift b/TelegramUI/ChannelVisibilityController.swift index 74d045d1fe..132cc70d0e 100644 --- a/TelegramUI/ChannelVisibilityController.swift +++ b/TelegramUI/ChannelVisibilityController.swift @@ -67,7 +67,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { case publicLinkStatus(PresentationTheme, String, AddressNameValidationStatus) case existingLinksInfo(PresentationTheme, String) - case existingLinkPeerItem(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, ItemListPeerItemEditing, Bool) + case existingLinkPeerItem(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer, ItemListPeerItemEditing, Bool) var section: ItemListSectionId { switch self { @@ -113,7 +113,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { return 12 case .existingLinksInfo: return 13 - case let .existingLinkPeerItem(index, _, _, _, _, _, _): + case let .existingLinkPeerItem(index, _, _, _, _, _, _, _): return 14 + index } } @@ -204,8 +204,8 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { } else { return false } - case let .existingLinkPeerItem(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsEditing, lhsEnabled): - if case let .existingLinkPeerItem(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsEditing, rhsEnabled) = rhs { + case let .existingLinkPeerItem(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameOrder, lhsPeer, lhsEditing, lhsEnabled): + if case let .existingLinkPeerItem(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameOrder, rhsPeer, rhsEditing, rhsEnabled) = rhs { if lhsIndex != rhsIndex { return false } @@ -218,6 +218,9 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { if lhsDateTimeFormat != rhsDateTimeFormat { return false } + if lhsNameOrder != rhsNameOrder { + return false + } if !lhsPeer.isEqual(rhsPeer) { return false } @@ -307,12 +310,12 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { return ItemListActivityTextItem(displayActivity: displayActivity, theme: theme, text: NSAttributedString(string: text, textColor: color), sectionId: self.section) case let .existingLinksInfo(theme, text): return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section) - case let .existingLinkPeerItem(_, theme, strings, dateTimeFormat, peer, editing, enabled): + case let .existingLinkPeerItem(_, theme, strings, dateTimeFormat, nameDisplayOrder, peer, editing, enabled): var label = "" if let addressName = peer.addressName { label = "t.me/" + addressName } - return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, account: arguments.account, peer: peer, presence: nil, text: .text(label), label: .none, editing: editing, switchValue: nil, enabled: enabled, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { previousId, id in + return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, account: arguments.account, peer: peer, presence: nil, text: .text(label), label: .none, editing: editing, switchValue: nil, enabled: enabled, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { previousId, id in arguments.setPeerIdWithRevealedOptions(previousId, id) }, removePeer: { peerId in arguments.revokePeerId(peerId) @@ -491,7 +494,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa } return lhsDate > rhsDate }) { - entries.append(.existingLinkPeerItem(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, ItemListPeerItemEditing(editable: true, editing: true, revealed: state.revealedRevokePeerId == peer.id), state.revokingPeerId == nil)) + entries.append(.existingLinkPeerItem(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, peer, ItemListPeerItemEditing(editable: true, editing: true, revealed: state.revealedRevokePeerId == peer.id), state.revokingPeerId == nil)) index += 1 } } else { diff --git a/TelegramUI/ChatController.swift b/TelegramUI/ChatController.swift index edb3db3370..71a67f619c 100644 --- a/TelegramUI/ChatController.swift +++ b/TelegramUI/ChatController.swift @@ -236,7 +236,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal self.presentationData = (account.applicationContext as! TelegramApplicationContext).currentPresentationData.with { $0 } self.automaticMediaDownloadSettings = (account.applicationContext as! TelegramApplicationContext).currentAutomaticMediaDownloadSettings.with { $0 } - self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, fontSize: self.presentationData.fontSize, accountPeerId: account.peerId, mode: mode, chatLocation: chatLocation) + self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, fontSize: self.presentationData.fontSize, accountPeerId: account.peerId, mode: mode, chatLocation: chatLocation) var mediaAccessoryPanelVisibility = MediaAccessoryPanelVisibility.none if case .standard = mode { @@ -1102,7 +1102,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal self.controllerInteraction = controllerInteraction - self.chatTitleView = ChatTitleView(account: self.account, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat) + self.chatTitleView = ChatTitleView(account: self.account, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder) self.navigationItem.titleView = self.chatTitleView self.chatTitleView?.pressed = { [weak self] in if let strongSelf = self { diff --git a/TelegramUI/ChatDocumentGalleryItem.swift b/TelegramUI/ChatDocumentGalleryItem.swift index 4ebd1c69ec..f2a093f650 100644 --- a/TelegramUI/ChatDocumentGalleryItem.swift +++ b/TelegramUI/ChatDocumentGalleryItem.swift @@ -128,10 +128,6 @@ class ChatDocumentGalleryItemNode: GalleryItemNode, WKNavigationDelegate { super.init() - if let webView = self.webView as? WKWebView { - //webView.navigationDelegate = self - } - self.view.addSubview(self.webView) self.statusNodeContainer.addSubnode(self.statusNode) @@ -170,6 +166,11 @@ class ChatDocumentGalleryItemNode: GalleryItemNode, WKNavigationDelegate { let updateFile = self.accountAndFile?.1.media != fileReference.media self.accountAndFile = (account, fileReference) if updateFile { + if fileReference.media.mimeType.hasPrefix("image/") { + if let webView = self.webView as? WKWebView { + webView.backgroundColor = .black + } + } self.maybeLoadContent() self.setupStatus(account: account, resource: fileReference.media.resource) } diff --git a/TelegramUI/ChatHistoryGridNode.swift b/TelegramUI/ChatHistoryGridNode.swift index 7a826ab103..d35e3d022d 100644 --- a/TelegramUI/ChatHistoryGridNode.swift +++ b/TelegramUI/ChatHistoryGridNode.swift @@ -246,22 +246,18 @@ public final class ChatHistoryGridNode: GridNode, ChatHistoryNode { super.init() - // self.chatPresentationDataPromise.set(.single(())) - - self.chatPresentationDataPromise.set(account.telegramApplicationContext.presentationData |> map { presentationData in - return ChatPresentationData(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: presentationData.chatWallpaper), fontSize: presentationData.fontSize, strings: self.presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, disableAnimations: presentationData.disableAnimations) + return ChatPresentationData(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: presentationData.chatWallpaper), fontSize: presentationData.fontSize, strings: self.presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: presentationData.disableAnimations) }) self.floatingSections = true - //self.preloadPages = false let messageViewQueue = self.messageViewQueue let historyViewUpdate = self.chatHistoryLocation - |> distinctUntilChanged - |> mapToSignal { location in - return chatHistoryViewForLocation(location, account: account, chatLocation: .peer(peerId), fixedCombinedReadStates: nil, tagMask: tagMask, additionalData: [], orderStatistics: [.locationWithinMonth]) + |> distinctUntilChanged + |> mapToSignal { location in + return chatHistoryViewForLocation(location, account: account, chatLocation: .peer(peerId), fixedCombinedReadStates: nil, tagMask: tagMask, additionalData: [], orderStatistics: [.locationWithinMonth]) } let previousView = Atomic(value: nil) diff --git a/TelegramUI/ChatHistoryListNode.swift b/TelegramUI/ChatHistoryListNode.swift index dbc70c2173..24ef29bad5 100644 --- a/TelegramUI/ChatHistoryListNode.swift +++ b/TelegramUI/ChatHistoryListNode.swift @@ -356,7 +356,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { self.currentPresentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - self.chatPresentationDataPromise = Promise(ChatPresentationData(theme: ChatPresentationThemeData(theme: self.currentPresentationData.theme, wallpaper: self.currentPresentationData.chatWallpaper), fontSize: self.currentPresentationData.fontSize, strings: self.currentPresentationData.strings, dateTimeFormat: self.currentPresentationData.dateTimeFormat, disableAnimations: self.currentPresentationData.disableAnimations)) + self.chatPresentationDataPromise = Promise(ChatPresentationData(theme: ChatPresentationThemeData(theme: self.currentPresentationData.theme, wallpaper: self.currentPresentationData.chatWallpaper), fontSize: self.currentPresentationData.fontSize, strings: self.currentPresentationData.strings, dateTimeFormat: self.currentPresentationData.dateTimeFormat, nameDisplayOrder: self.currentPresentationData.nameDisplayOrder, disableAnimations: self.currentPresentationData.disableAnimations)) super.init() @@ -680,7 +680,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings || previousWallpaper != presentationData.chatWallpaper || previousDisableAnimations != presentationData.disableAnimations { let themeData = ChatPresentationThemeData(theme: presentationData.theme, wallpaper: presentationData.chatWallpaper) - let chatPresentationData = ChatPresentationData(theme: themeData, fontSize: presentationData.fontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, disableAnimations: presentationData.disableAnimations) + let chatPresentationData = ChatPresentationData(theme: themeData, fontSize: presentationData.fontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: presentationData.disableAnimations) strongSelf.dynamicBounceEnabled = !presentationData.disableAnimations diff --git a/TelegramUI/ChatInterfaceStateAccessoryPanels.swift b/TelegramUI/ChatInterfaceStateAccessoryPanels.swift index e998c8cc67..470a869735 100644 --- a/TelegramUI/ChatInterfaceStateAccessoryPanels.swift +++ b/TelegramUI/ChatInterfaceStateAccessoryPanels.swift @@ -29,7 +29,7 @@ func accessoryPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceS editPanelNode.updateThemeAndStrings(theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings) return editPanelNode } else { - let panelNode = EditAccessoryPanelNode(account: account, messageId: editMessage.messageId, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings) + let panelNode = EditAccessoryPanelNode(account: account, messageId: editMessage.messageId, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, nameDisplayOrder: chatPresentationInterfaceState.nameDisplayOrder) panelNode.interfaceInteraction = interfaceInteraction return panelNode } @@ -60,7 +60,7 @@ func accessoryPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceS replyPanelNode.updateThemeAndStrings(theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings) return replyPanelNode } else { - let panelNode = ReplyAccessoryPanelNode(account: account, messageId: replyMessageId, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings) + let panelNode = ReplyAccessoryPanelNode(account: account, messageId: replyMessageId, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, nameDisplayOrder: chatPresentationInterfaceState.nameDisplayOrder) panelNode.interfaceInteraction = interfaceInteraction return panelNode } diff --git a/TelegramUI/ChatInterfaceStateContextQueries.swift b/TelegramUI/ChatInterfaceStateContextQueries.swift index a913a13dd4..49a10c319b 100644 --- a/TelegramUI/ChatInterfaceStateContextQueries.swift +++ b/TelegramUI/ChatInterfaceStateContextQueries.swift @@ -163,7 +163,7 @@ private func updatedContextQueryResultStateForQuery(account: Account, peer: Peer return result == .orderedAscending })) sortedPeers = sortedPeers.filter { peer in - return !peer.displayTitle.isEmpty + return !peer.debugDisplayTitle.isEmpty } return { _ in return .mentions(sortedPeers) } } diff --git a/TelegramUI/ChatItemGalleryFooterContentNode.swift b/TelegramUI/ChatItemGalleryFooterContentNode.swift index 6437054648..e73749153f 100644 --- a/TelegramUI/ChatItemGalleryFooterContentNode.swift +++ b/TelegramUI/ChatItemGalleryFooterContentNode.swift @@ -557,7 +557,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { let presentationData = strongSelf.account.telegramApplicationContext.currentPresentationData.with { $0 } var generalMessageContentKind: MessageContentKind? for message in messages { - let currentKind = messageContentKind(message, strings: presentationData.strings, accountPeerId: strongSelf.account.peerId) + let currentKind = messageContentKind(message, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, accountPeerId: strongSelf.account.peerId) if generalMessageContentKind == nil || generalMessageContentKind == currentKind { generalMessageContentKind = currentKind } else { @@ -683,7 +683,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { let presentationData = strongSelf.account.telegramApplicationContext.currentPresentationData.with { $0 } var generalMessageContentKind: MessageContentKind? for message in messages { - let currentKind = messageContentKind(message, strings: presentationData.strings, accountPeerId: strongSelf.account.peerId) + let currentKind = messageContentKind(message, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, accountPeerId: strongSelf.account.peerId) if generalMessageContentKind == nil || generalMessageContentKind == currentKind { generalMessageContentKind = currentKind } else { diff --git a/TelegramUI/ChatListItem.swift b/TelegramUI/ChatListItem.swift index fcaf9b90a9..a354b009a5 100644 --- a/TelegramUI/ChatListItem.swift +++ b/TelegramUI/ChatListItem.swift @@ -520,7 +520,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let leftInset: CGFloat = params.leftInset + 78.0 - let (peer, initialHideAuthor, messageText) = chatListItemStrings(strings: item.presentationData.strings, message: message, chatPeer: itemPeer, accountPeerId: item.account.peerId) + let (peer, initialHideAuthor, messageText) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, message: message, chatPeer: itemPeer, accountPeerId: item.account.peerId) var hideAuthor = initialHideAuthor if isPeerGroup { hideAuthor = false @@ -540,11 +540,11 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { if let author = message.author as? TelegramUser, let peer = peer, !(peer is TelegramUser) { if let peer = peer as? TelegramChannel, case .broadcast = peer.info { } else { - peerText = author.id == account.peerId ? item.presentationData.strings.DialogList_You : author.displayTitle(strings: item.presentationData.strings) + peerText = author.id == account.peerId ? item.presentationData.strings.DialogList_You : author.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) } } else if case .groupReference = item.content { if let messagePeer = itemPeer.chatMainPeer { - peerText = messagePeer.displayTitle(strings: item.presentationData.strings) + peerText = messagePeer.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) } } @@ -559,7 +559,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { case .peer: if peer?.id == item.account.peerId { titleAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_SavedMessages, font: titleFont, textColor: theme.titleColor) - } else if let displayTitle = peer?.displayTitle(strings: item.presentationData.strings) { + } else if let displayTitle = peer?.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) { titleAttributedString = NSAttributedString(string: displayTitle, font: titleFont, textColor: item.index.messageIndex.id.peerId.namespace == Namespaces.Peer.SecretChat ? theme.secretTitleColor : theme.titleColor) } case .groupReference: diff --git a/TelegramUI/ChatListItemStrings.swift b/TelegramUI/ChatListItemStrings.swift index 68ced077f3..8df33f4a6a 100644 --- a/TelegramUI/ChatListItemStrings.swift +++ b/TelegramUI/ChatListItemStrings.swift @@ -2,7 +2,7 @@ import Foundation import Postbox import TelegramCore -public func chatListItemStrings(strings: PresentationStrings, message: Message?, chatPeer: RenderedPeer, accountPeerId: PeerId) -> (peer: Peer?, hideAuthor: Bool, messageText: String) { +public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, message: Message?, chatPeer: RenderedPeer, accountPeerId: PeerId) -> (peer: Peer?, hideAuthor: Bool, messageText: String) { let peer: Peer? var hideAuthor = false @@ -130,12 +130,12 @@ public func chatListItemStrings(strings: PresentationStrings, message: Message?, } } default: - if let text = plainServiceMessageString(strings: strings, message: message, accountPeerId: accountPeerId) { + if let text = plainServiceMessageString(strings: strings, nameDisplayOrder: nameDisplayOrder, message: message, accountPeerId: accountPeerId) { messageText = text } } case _ as TelegramMediaExpiredContent: - if let text = plainServiceMessageString(strings: strings, message: message, accountPeerId: accountPeerId) { + if let text = plainServiceMessageString(strings: strings, nameDisplayOrder: nameDisplayOrder, message: message, accountPeerId: accountPeerId) { messageText = text } default: diff --git a/TelegramUI/ChatMessageActionItemNode.swift b/TelegramUI/ChatMessageActionItemNode.swift index 44696746b9..108a2b64b2 100644 --- a/TelegramUI/ChatMessageActionItemNode.swift +++ b/TelegramUI/ChatMessageActionItemNode.swift @@ -22,15 +22,15 @@ private func peerMentionsAttributes(primaryTextColor: UIColor, peerIds: [(Int, P return result } -private func attributedServiceMessageString(theme: ChatPresentationThemeData, strings: PresentationStrings, message: Message, accountPeerId: PeerId) -> NSAttributedString? { - return universalServiceMessageString(theme: theme, strings: strings, message: message, accountPeerId: accountPeerId) +private func attributedServiceMessageString(theme: ChatPresentationThemeData, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, message: Message, accountPeerId: PeerId) -> NSAttributedString? { + return universalServiceMessageString(theme: theme, strings: strings, nameDisplayOrder: nameDisplayOrder, message: message, accountPeerId: accountPeerId) } -func plainServiceMessageString(strings: PresentationStrings, message: Message, accountPeerId: PeerId) -> String? { - return universalServiceMessageString(theme: nil, strings: strings, message: message, accountPeerId: accountPeerId)?.string +func plainServiceMessageString(strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, message: Message, accountPeerId: PeerId) -> String? { + return universalServiceMessageString(theme: nil, strings: strings, nameDisplayOrder: nameDisplayOrder, message: message, accountPeerId: accountPeerId)?.string } -private func universalServiceMessageString(theme: ChatPresentationThemeData?, strings: PresentationStrings, message: Message, accountPeerId: PeerId) -> NSAttributedString? { +private func universalServiceMessageString(theme: ChatPresentationThemeData?, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, message: Message, accountPeerId: PeerId) -> NSAttributedString? { var attributedString: NSAttributedString? let primaryTextColor: UIColor @@ -44,7 +44,7 @@ private func universalServiceMessageString(theme: ChatPresentationThemeData?, st for media in message.media { if let action = media as? TelegramMediaAction { - let authorName = message.author?.displayTitle ?? "" + let authorName = message.author?.displayTitle(strings: strings, displayOrder: nameDisplayOrder) ?? "" var isChannel = false if message.id.peerId.namespace == Namespaces.Peer.CloudChannel, let peer = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = peer.info { @@ -70,7 +70,7 @@ private func universalServiceMessageString(theme: ChatPresentationThemeData?, st if peerIds.count == 1 { attributePeerIds.append((1, peerIds.first)) } - attributedString = addAttributesToStringWithRanges(strings.Notification_Invited(authorName, peerDisplayTitles(peerIds, message.peers)), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: attributePeerIds)) + attributedString = addAttributesToStringWithRanges(strings.Notification_Invited(authorName, peerDebugDisplayTitles(peerIds, message.peers)), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: attributePeerIds)) } case let .removedMembers(peerIds): if peerIds.first == message.author?.id { @@ -84,7 +84,7 @@ private func universalServiceMessageString(theme: ChatPresentationThemeData?, st if peerIds.count == 1 { attributePeerIds.append((1, peerIds.first)) } - attributedString = addAttributesToStringWithRanges(strings.Notification_Kicked(authorName, peerDisplayTitles(peerIds, message.peers)), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: attributePeerIds)) + attributedString = addAttributesToStringWithRanges(strings.Notification_Kicked(authorName, peerDebugDisplayTitles(peerIds, message.peers)), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: attributePeerIds)) } case let .photoUpdated(image): if authorName.isEmpty || isChannel { @@ -510,7 +510,7 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: true, headerSpacing: 0.0, hidesBackground: .always, forceFullCorners: false, forceAlignment: .center) return (contentProperties, nil, CGFloat.greatestFiniteMagnitude, { constrainedSize, position in - let attributedString = attributedServiceMessageString(theme: item.presentationData.theme, strings: item.presentationData.strings, message: item.message, accountPeerId: item.account.peerId) + let attributedString = attributedServiceMessageString(theme: item.presentationData.theme, strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, message: item.message, accountPeerId: item.account.peerId) var image: TelegramMediaImage? for media in item.message.media { diff --git a/TelegramUI/ChatMessageAnimatedStickerItemNode.swift b/TelegramUI/ChatMessageAnimatedStickerItemNode.swift index 9bddbb8f19..b6cf563994 100644 --- a/TelegramUI/ChatMessageAnimatedStickerItemNode.swift +++ b/TelegramUI/ChatMessageAnimatedStickerItemNode.swift @@ -295,7 +295,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { // } } - let dateText = stringForMessageTimestampStatus(message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, strings: item.presentationData.strings, format: .minimal) + let dateText = stringForMessageTimestampStatus(message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: .minimal) let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.presentationData.theme, item.presentationData.strings, edited && !sentViaBot, viewCount, dateText, statusType, CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude)) @@ -305,7 +305,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { for attribute in item.message.attributes { if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] { let availableWidth = max(60.0, params.width - params.leftInset - params.rightInset - imageSize.width - 20.0 - layoutConstants.bubble.edgeInset * 2.0 - avatarInset - layoutConstants.bubble.contentInsets.left) - replyInfoApply = makeReplyInfoLayout(item.presentationData.theme, item.presentationData.strings, item.account, .standalone, replyMessage, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) + replyInfoApply = makeReplyInfoLayout(item.presentationData, item.presentationData.strings, item.account, .standalone, replyMessage, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) if let currentReplyBackgroundNode = currentReplyBackgroundNode { updatedReplyBackgroundNode = currentReplyBackgroundNode diff --git a/TelegramUI/ChatMessageAttachedContentNode.swift b/TelegramUI/ChatMessageAttachedContentNode.swift index 346aa31179..7c2fa5752b 100644 --- a/TelegramUI/ChatMessageAttachedContentNode.swift +++ b/TelegramUI/ChatMessageAttachedContentNode.swift @@ -304,7 +304,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { sentViaBot = true } - let dateText = stringForMessageTimestampStatus(message: message, dateTimeFormat: presentationData.dateTimeFormat, strings: presentationData.strings) + let dateText = stringForMessageTimestampStatus(message: message, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, strings: presentationData.strings) var webpageGalleryMediaCount: Int? for media in message.media { diff --git a/TelegramUI/ChatMessageBubbleItemNode.swift b/TelegramUI/ChatMessageBubbleItemNode.swift index 2394f596d9..500e8d10f3 100644 --- a/TelegramUI/ChatMessageBubbleItemNode.swift +++ b/TelegramUI/ChatMessageBubbleItemNode.swift @@ -604,10 +604,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { if initialDisplayHeader && displayAuthorInfo { if let peer = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case .broadcast = peer.info { - authorNameString = peer.displayTitle + authorNameString = peer.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) authorNameColor = chatMessagePeerIdColors[Int(peer.id.id % 7)] } else if let effectiveAuthor = effectiveAuthor { - authorNameString = effectiveAuthor.displayTitle + authorNameString = effectiveAuthor.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) authorNameColor = chatMessagePeerIdColors[Int(effectiveAuthor.id.id % 7)] } if let rawAuthorNameColor = authorNameColor { @@ -686,7 +686,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { sentViaBot = true } - let dateText = stringForMessageTimestampStatus(message: message, dateTimeFormat: item.presentationData.dateTimeFormat, strings: item.presentationData.strings) + let dateText = stringForMessageTimestampStatus(message: message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings) let statusType: ChatMessageDateAndStatusType if message.effectivelyIncoming(item.account.peerId) { @@ -774,7 +774,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { if let authorSignature = forwardInfo.authorSignature { forwardAuthorSignature = authorSignature } else if forwardInfo.author.id != source.id { - forwardAuthorSignature = forwardInfo.author.displayTitle + forwardAuthorSignature = forwardInfo.author.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) } else { forwardAuthorSignature = nil } @@ -782,7 +782,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { forwardSource = forwardInfo.author forwardAuthorSignature = nil } - let sizeAndApply = forwardInfoLayout(item.presentationData.theme, item.presentationData.strings, .bubble(incoming: incoming), forwardSource, forwardAuthorSignature, CGSize(width: maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, height: CGFloat.greatestFiniteMagnitude)) + let sizeAndApply = forwardInfoLayout(item.presentationData, item.presentationData.strings, .bubble(incoming: incoming), forwardSource, forwardAuthorSignature, CGSize(width: maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, height: CGFloat.greatestFiniteMagnitude)) forwardInfoSizeApply = (sizeAndApply.0, { sizeAndApply.1() }) forwardInfoOriginY = headerSize.height @@ -796,7 +796,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { } else { headerSize.height += 2.0 } - let sizeAndApply = replyInfoLayout(item.presentationData.theme, item.presentationData.strings, item.account, .bubble(incoming: incoming), replyMessage, CGSize(width: maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, height: CGFloat.greatestFiniteMagnitude)) + let sizeAndApply = replyInfoLayout(item.presentationData, item.presentationData.strings, item.account, .bubble(incoming: incoming), replyMessage, CGSize(width: maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, height: CGFloat.greatestFiniteMagnitude)) replyInfoSizeApply = (sizeAndApply.0, { sizeAndApply.1() }) replyInfoOriginY = headerSize.height diff --git a/TelegramUI/ChatMessageCallBubbleContentNode.swift b/TelegramUI/ChatMessageCallBubbleContentNode.swift index e713dc6168..339f6df553 100644 --- a/TelegramUI/ChatMessageCallBubbleContentNode.swift +++ b/TelegramUI/ChatMessageCallBubbleContentNode.swift @@ -132,7 +132,7 @@ class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode { buttonImage = PresentationResourcesChat.chatBubbleOutgoingCallButtonImage(item.presentationData.theme.theme) } - let dateText = stringForMessageTimestampStatus(message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, strings: item.presentationData.strings) + let dateText = stringForMessageTimestampStatus(message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings) let statusText: String if let callDuration = callDuration, callDuration > 1 { diff --git a/TelegramUI/ChatMessageContactBubbleContentNode.swift b/TelegramUI/ChatMessageContactBubbleContentNode.swift index e63970305c..e8a2f0cb39 100644 --- a/TelegramUI/ChatMessageContactBubbleContentNode.swift +++ b/TelegramUI/ChatMessageContactBubbleContentNode.swift @@ -151,7 +151,7 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { sentViaBot = true } - let dateText = stringForMessageTimestampStatus(message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, strings: item.presentationData.strings) + let dateText = stringForMessageTimestampStatus(message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings) let statusType: ChatMessageDateAndStatusType? switch position { diff --git a/TelegramUI/ChatMessageForwardInfoNode.swift b/TelegramUI/ChatMessageForwardInfoNode.swift index c4a06948b6..009b15b826 100644 --- a/TelegramUI/ChatMessageForwardInfoNode.swift +++ b/TelegramUI/ChatMessageForwardInfoNode.swift @@ -18,15 +18,15 @@ class ChatMessageForwardInfoNode: ASDisplayNode { super.init() } - class func asyncLayout(_ maybeNode: ChatMessageForwardInfoNode?) -> (_ theme: ChatPresentationThemeData, _ strings: PresentationStrings, _ type: ChatMessageForwardInfoType, _ peer: Peer, _ authorName: String?, _ constrainedSize: CGSize) -> (CGSize, () -> ChatMessageForwardInfoNode) { + class func asyncLayout(_ maybeNode: ChatMessageForwardInfoNode?) -> (_ presentationData: ChatPresentationData, _ strings: PresentationStrings, _ type: ChatMessageForwardInfoType, _ peer: Peer, _ authorName: String?, _ constrainedSize: CGSize) -> (CGSize, () -> ChatMessageForwardInfoNode) { let textNodeLayout = TextNode.asyncLayout(maybeNode?.textNode) - return { theme, strings, type, peer, authorName, constrainedSize in + return { presentationData, strings, type, peer, authorName, constrainedSize in let peerString: String if let authorName = authorName { - peerString = "\(peer.displayTitle(strings: strings)) (\(authorName))" + peerString = "\(peer.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder)) (\(authorName))" } else { - peerString = peer.displayTitle(strings: strings) + peerString = peer.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder) } let titleColor: UIColor @@ -34,10 +34,10 @@ class ChatMessageForwardInfoNode: ASDisplayNode { switch type { case let .bubble(incoming): - titleColor = incoming ? theme.theme.chat.bubble.incomingAccentTextColor : theme.theme.chat.bubble.outgoingAccentTextColor + titleColor = incoming ? presentationData.theme.theme.chat.bubble.incomingAccentTextColor : presentationData.theme.theme.chat.bubble.outgoingAccentTextColor completeSourceString = strings.Message_ForwardedMessage(peerString) case .standalone: - let serviceColor = serviceMessageColorComponents(theme: theme.theme, wallpaper: theme.wallpaper) + let serviceColor = serviceMessageColorComponents(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) titleColor = serviceColor.primaryText completeSourceString = strings.Message_ForwardedMessageShort(peerString) } diff --git a/TelegramUI/ChatMessageInstantVideoItemNode.swift b/TelegramUI/ChatMessageInstantVideoItemNode.swift index d9693d8a2c..38146d372c 100644 --- a/TelegramUI/ChatMessageInstantVideoItemNode.swift +++ b/TelegramUI/ChatMessageInstantVideoItemNode.swift @@ -168,7 +168,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { } } if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] { - replyInfoApply = makeReplyInfoLayout(item.presentationData.theme, item.presentationData.strings, item.account, .standalone, replyMessage, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) + replyInfoApply = makeReplyInfoLayout(item.presentationData, item.presentationData.strings, item.account, .standalone, replyMessage, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) } else if let attribute = attribute as? InlineBotMessageAttribute { if let peerId = attribute.peerId, let bot = item.message.peers[peerId] as? TelegramUser { inlineBotNameString = bot.username @@ -205,7 +205,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { if let authorSignature = forwardInfo.authorSignature { forwardAuthorSignature = authorSignature } else if forwardInfo.author.id != source.id { - forwardAuthorSignature = forwardInfo.author.displayTitle + forwardAuthorSignature = forwardInfo.author.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) } else { forwardAuthorSignature = nil } @@ -214,7 +214,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { forwardAuthorSignature = nil } let availableWidth = max(60.0, availableContentWidth - videoLayout.contentSize.width + 6.0) - forwardInfoSizeApply = makeForwardInfoLayout(item.presentationData.theme, item.presentationData.strings, .standalone, forwardSource, forwardAuthorSignature, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) + forwardInfoSizeApply = makeForwardInfoLayout(item.presentationData, item.presentationData.strings, .standalone, forwardSource, forwardAuthorSignature, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) if let currentForwardBackgroundNode = currentForwardBackgroundNode { updatedForwardBackgroundNode = currentForwardBackgroundNode diff --git a/TelegramUI/ChatMessageInteractiveFileNode.swift b/TelegramUI/ChatMessageInteractiveFileNode.swift index 4e07012007..f8da004fdd 100644 --- a/TelegramUI/ChatMessageInteractiveFileNode.swift +++ b/TelegramUI/ChatMessageInteractiveFileNode.swift @@ -17,6 +17,9 @@ private let durationFont = Font.regular(11.0) final class ChatMessageInteractiveFileNode: ASDisplayNode { private let titleNode: TextNode private let descriptionNode: TextNode + private let descriptionMeasuringNode: TextNode + private let fetchingTextNode: ImmediateTextNode + private let fetchingCompactTextNode: ImmediateTextNode private let waveformNode: AudioWaveformNode private let waveformForegroundNode: AudioWaveformNode private var waveformScrubbingNode: MediaPlayerScrubbingNode? @@ -57,6 +60,24 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { self.descriptionNode.displaysAsynchronously = true self.descriptionNode.isUserInteractionEnabled = false + self.descriptionMeasuringNode = TextNode() + + self.fetchingTextNode = ImmediateTextNode() + self.fetchingTextNode.displaysAsynchronously = true + self.fetchingTextNode.isUserInteractionEnabled = false + self.fetchingTextNode.maximumNumberOfLines = 1 + self.fetchingTextNode.contentMode = .left + self.fetchingTextNode.contentsScale = UIScreenScale + self.fetchingTextNode.isHidden = true + + self.fetchingCompactTextNode = ImmediateTextNode() + self.fetchingCompactTextNode.displaysAsynchronously = true + self.fetchingCompactTextNode.isUserInteractionEnabled = false + self.fetchingCompactTextNode.maximumNumberOfLines = 1 + self.fetchingCompactTextNode.contentMode = .left + self.fetchingCompactTextNode.contentsScale = UIScreenScale + self.fetchingCompactTextNode.isHidden = true + self.waveformNode = AudioWaveformNode() self.waveformNode.isLayerBacked = true self.waveformForegroundNode = AudioWaveformNode() @@ -70,6 +91,8 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { self.addSubnode(self.titleNode) self.addSubnode(self.descriptionNode) + self.addSubnode(self.fetchingTextNode) + self.addSubnode(self.fetchingCompactTextNode) } deinit { @@ -147,19 +170,13 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { let titleAsyncLayout = TextNode.asyncLayout(self.titleNode) let descriptionAsyncLayout = TextNode.asyncLayout(self.descriptionNode) + let descriptionMeasuringAsyncLayout = TextNode.asyncLayout(self.descriptionMeasuringNode) let statusLayout = self.dateAndStatusNode.asyncLayout() let currentMessage = self.message let currentTheme = self.themeAndStrings?.0 - let currentResourceStatus = self.resourceStatus return { account, presentationData, message, file, automaticDownload, incoming, isRecentActions, dateAndStatusType, constrainedSize in - var updatedTheme: ChatPresentationThemeData? - - if presentationData.theme != currentTheme { - updatedTheme = presentationData.theme - } - return (CGFloat.greatestFiniteMagnitude, { constrainedSize in var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? var updatedStatusSignal: Signal? @@ -233,7 +250,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { sentViaBot = true } - let dateText = stringForMessageTimestampStatus(message: message, dateTimeFormat: presentationData.dateTimeFormat, strings: presentationData.strings) + let dateText = stringForMessageTimestampStatus(message: message, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, strings: presentationData.strings) let (size, apply) = statusLayout(presentationData.theme, presentationData.strings, edited && !sentViaBot, viewCount, dateText, statusType, constrainedSize) statusSize = size @@ -273,7 +290,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { if voice { isVoice = true let durationString = stringForDuration(audioDuration) - candidateDescriptionString = NSAttributedString(string: durationString, font: durationFont, textColor:incoming ? bubbleTheme.incomingFileDurationColor : bubbleTheme.outgoingFileDurationColor) + candidateDescriptionString = NSAttributedString(string: durationString, font: durationFont, textColor: incoming ? bubbleTheme.incomingFileDurationColor : bubbleTheme.outgoingFileDurationColor) if let waveform = waveform { waveform.withDataNoCopy { data in audioWaveform = AudioWaveform(bitstream: data, bitsPerSample: 5) @@ -334,6 +351,16 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { let (titleLayout, titleApply) = titleAsyncLayout(TextNodeLayoutArguments(attributedString: titleString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (descriptionLayout, descriptionApply) = descriptionAsyncLayout(TextNodeLayoutArguments(attributedString: descriptionString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let fileSizeString: String + if let _ = file.size { + fileSizeString = "000.0 MB" + } else { + fileSizeString = "" + } + let (descriptionMeasuringLayout, descriptionMeasuringApply) = descriptionMeasuringAsyncLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "\(fileSizeString) / \(fileSizeString)", font: descriptionFont, textColor: .black), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + let descriptionMaxWidth = max(descriptionLayout.size.width, descriptionMeasuringLayout.size.width) + let minVoiceWidth: CGFloat = 120.0 let maxVoiceWidth = constrainedSize.width let maxVoiceLength: CGFloat = 30.0 @@ -341,12 +368,12 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { var minLayoutWidth: CGFloat if hasThumbnail { - minLayoutWidth = max(titleLayout.size.width, descriptionLayout.size.width) + 86.0 + minLayoutWidth = max(titleLayout.size.width, descriptionMaxWidth) + 86.0 } else if isVoice { let calcDuration = max(minVoiceLength, min(maxVoiceLength, CGFloat(audioDuration))) minLayoutWidth = minVoiceWidth + (maxVoiceWidth - minVoiceWidth) * (calcDuration - minVoiceLength) / (maxVoiceLength - minVoiceLength) } else { - minLayoutWidth = max(titleLayout.size.width, descriptionLayout.size.width) + 44.0 + 8.0 + minLayoutWidth = max(titleLayout.size.width, descriptionMaxWidth) + 44.0 + 8.0 } if let statusSize = statusSize { @@ -445,9 +472,11 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { let _ = titleApply() let _ = descriptionApply() + let _ = descriptionMeasuringApply() strongSelf.titleNode.frame = titleFrame strongSelf.descriptionNode.frame = descriptionFrame + strongSelf.descriptionMeasuringNode.frame = CGRect(origin: CGPoint(), size: descriptionMeasuringLayout.size) if let consumableContentIcon = consumableContentIcon { if strongSelf.consumableContentNode.supernode == nil { @@ -625,6 +654,25 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { let isSending = message.flags.isSending + var downloadingStrings: (String, String)? + + if !isAudio { + switch resourceStatus.mediaStatus { + case let .fetchStatus(fetchStatus): + switch fetchStatus { + case let .Fetching(_, progress): + if let size = file.size { + let compactString = dataSizeString(Int(Float(size) * progress), forceDecimal: true) + downloadingStrings = ("\(compactString) / \(dataSizeString(size, forceDecimal: true))", compactString) + } + default: + break + } + default: + break + } + } + if isAudio && !isVoice && !isSending { let streamingStatusForegroundColor: UIColor = incoming ? bubbleTheme.incomingAccentControlColor : bubbleTheme.outgoingAccentControlColor let streamingStatusBackgroundColor: UIColor = incoming ? bubbleTheme.incomingMediaInactiveControlColor : bubbleTheme.outgoingMediaInactiveControlColor @@ -743,6 +791,36 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { }) } } + + if let (expandedString, compactString) = downloadingStrings { + self.fetchingTextNode.attributedText = NSAttributedString(string: expandedString, font: descriptionFont, textColor: incoming ? bubbleTheme.incomingFileDurationColor : bubbleTheme.outgoingFileDurationColor) + self.fetchingCompactTextNode.attributedText = NSAttributedString(string: compactString, font: descriptionFont, textColor: incoming ? bubbleTheme.incomingFileDurationColor : bubbleTheme.outgoingFileDurationColor) + } else { + self.fetchingTextNode.attributedText = nil + self.fetchingCompactTextNode.attributedText = nil + } + + let maxFetchingStatusWidth = max(self.titleNode.frame.width, self.descriptionMeasuringNode.frame.width) + 2.0 + let fetchingInfo = self.fetchingTextNode.updateLayoutInfo(CGSize(width: maxFetchingStatusWidth, height: CGFloat.greatestFiniteMagnitude)) + let fetchingCompactSize = self.fetchingCompactTextNode.updateLayout(CGSize(width: maxFetchingStatusWidth, height: CGFloat.greatestFiniteMagnitude)) + + if downloadingStrings != nil { + self.descriptionNode.isHidden = true + if fetchingInfo.truncated { + self.fetchingTextNode.isHidden = true + self.fetchingCompactTextNode.isHidden = false + } else { + self.fetchingTextNode.isHidden = false + self.fetchingCompactTextNode.isHidden = true + } + } else { + self.descriptionNode.isHidden = false + self.fetchingTextNode.isHidden = true + self.fetchingCompactTextNode.isHidden = true + } + + self.fetchingTextNode.frame = CGRect(origin: self.descriptionNode.frame.origin, size: fetchingInfo.size) + self.fetchingCompactTextNode.frame = CGRect(origin: self.descriptionNode.frame.origin, size: fetchingCompactSize) } static func asyncLayout(_ node: ChatMessageInteractiveFileNode?) -> (_ account: Account, _ presentationData: ChatPresentationData, _ message: Message, _ file: TelegramMediaFile, _ automaticDownload: Bool, _ incoming: Bool, _ isRecentActions: Bool, _ dateAndStatusType: ChatMessageDateAndStatusType?, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, () -> ChatMessageInteractiveFileNode))) { diff --git a/TelegramUI/ChatMessageInteractiveInstantVideoNode.swift b/TelegramUI/ChatMessageInteractiveInstantVideoNode.swift index dabb53c8c4..64adabeb1c 100644 --- a/TelegramUI/ChatMessageInteractiveInstantVideoNode.swift +++ b/TelegramUI/ChatMessageInteractiveInstantVideoNode.swift @@ -244,7 +244,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { // sentViaBot = true // } - let dateText = stringForMessageTimestampStatus(message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, strings: item.presentationData.strings, format: .regular) + let dateText = stringForMessageTimestampStatus(message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: .regular) let maxDateAndStatusWidth: CGFloat if case .bubble = statusDisplayType { diff --git a/TelegramUI/ChatMessageMapBubbleContentNode.swift b/TelegramUI/ChatMessageMapBubbleContentNode.swift index 326109923d..0c9bb9fa1c 100644 --- a/TelegramUI/ChatMessageMapBubbleContentNode.swift +++ b/TelegramUI/ChatMessageMapBubbleContentNode.swift @@ -188,7 +188,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { } } - let dateText = stringForMessageTimestampStatus(message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, strings: item.presentationData.strings) + let dateText = stringForMessageTimestampStatus(message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings) let statusType: ChatMessageDateAndStatusType? switch position { diff --git a/TelegramUI/ChatMessageMediaBubbleContentNode.swift b/TelegramUI/ChatMessageMediaBubbleContentNode.swift index 032e2bac2d..cef98a3da7 100644 --- a/TelegramUI/ChatMessageMediaBubbleContentNode.swift +++ b/TelegramUI/ChatMessageMediaBubbleContentNode.swift @@ -119,7 +119,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { sentViaBot = true } - let dateText = stringForMessageTimestampStatus(message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, strings: item.presentationData.strings) + let dateText = stringForMessageTimestampStatus(message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings) let statusType: ChatMessageDateAndStatusType? switch position { diff --git a/TelegramUI/ChatMessageNotificationItem.swift b/TelegramUI/ChatMessageNotificationItem.swift index 149c197c12..f739aa6e12 100644 --- a/TelegramUI/ChatMessageNotificationItem.swift +++ b/TelegramUI/ChatMessageNotificationItem.swift @@ -8,6 +8,7 @@ import SwiftSignalKit public final class ChatMessageNotificationItem: NotificationItem { let account: Account let strings: PresentationStrings + let nameDisplayOrder: PresentationPersonNameOrder let messages: [Message] let tapAction: () -> Bool let expandAction: (@escaping () -> (ASDisplayNode?, () -> Void)) -> Void @@ -16,9 +17,10 @@ public final class ChatMessageNotificationItem: NotificationItem { return messages.first?.id.peerId } - public init(account: Account, strings: PresentationStrings, messages: [Message], tapAction: @escaping () -> Bool, expandAction: @escaping (() -> (ASDisplayNode?, () -> Void)) -> Void) { + public init(account: Account, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, messages: [Message], tapAction: @escaping () -> Bool, expandAction: @escaping (() -> (ASDisplayNode?, () -> Void)) -> Void) { self.account = account self.strings = strings + self.nameDisplayOrder = nameDisplayOrder self.messages = messages self.tapAction = tapAction self.expandAction = expandAction @@ -101,11 +103,11 @@ final class ChatMessageNotificationItemNode: NotificationItemNode { self.avatarNode.setPeer(account: item.account, peer: peer, emptyColor: presentationData.theme.list.mediaPlaceholderColor) if let channel = peer as? TelegramChannel, case .broadcast = channel.info { - title = peer.displayTitle + title = peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder) } else if let author = firstMessage.author, author.id != peer.id { - title = author.displayTitle + "@" + peer.displayTitle + title = author.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder) + "@" + peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder) } else { - title = peer.displayTitle + title = peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder) } } @@ -138,7 +140,7 @@ final class ChatMessageNotificationItemNode: NotificationItemNode { if message.containsSecretMedia { imageDimensions = nil } - messageText = descriptionStringForMessage(message, strings: item.strings, accountPeerId: item.account.peerId).0 + messageText = descriptionStringForMessage(message, strings: item.strings, nameDisplayOrder: item.nameDisplayOrder, accountPeerId: item.account.peerId).0 } else if item.messages.count > 1, let peer = item.messages[0].peers[item.messages[0].id.peerId] { var displayAuthor = true if let channel = peer as? TelegramChannel { @@ -155,15 +157,15 @@ final class ChatMessageNotificationItemNode: NotificationItemNode { if item.messages[0].forwardInfo != nil { if let author = item.messages[0].author, displayAuthor { title = nil - messageText = presentationData.strings.CHAT_MESSAGE_FWDS(author.compactDisplayTitle, peer.displayTitle, "\(item.messages.count)").0 + messageText = presentationData.strings.CHAT_MESSAGE_FWDS(author.compactDisplayTitle, peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder), "\(item.messages.count)").0 } else { title = nil - messageText = presentationData.strings.MESSAGE_FWDS(peer.displayTitle, "\(item.messages.count)").0 + messageText = presentationData.strings.MESSAGE_FWDS(peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder), "\(item.messages.count)").0 } } else if item.messages[0].groupingKey != nil { - var kind = messageContentKind(item.messages[0], strings: presentationData.strings, accountPeerId: item.account.peerId).key + var kind = messageContentKind(item.messages[0], strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, accountPeerId: item.account.peerId).key for i in 1 ..< item.messages.count { - let nextKind = messageContentKind(item.messages[i], strings: presentationData.strings, accountPeerId: item.account.peerId) + let nextKind = messageContentKind(item.messages[i], strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, accountPeerId: item.account.peerId) if kind != nextKind.key { kind = .text break @@ -191,16 +193,16 @@ final class ChatMessageNotificationItemNode: NotificationItemNode { } else if isGroup, let author = item.messages[0].author { switch kind { case .image: - messageText = presentationData.strings.CHAT_MESSAGE_PHOTOS(author.compactDisplayTitle, peer.displayTitle, "\(item.messages.count)").0 + messageText = presentationData.strings.CHAT_MESSAGE_PHOTOS(author.compactDisplayTitle, peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder), "\(item.messages.count)").0 default: - messageText = presentationData.strings.CHAT_MESSAGES(author.compactDisplayTitle, peer.displayTitle, "\(item.messages.count)").0 + messageText = presentationData.strings.CHAT_MESSAGES(author.compactDisplayTitle, peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder), "\(item.messages.count)").0 } } else { switch kind { case .image: - messageText = presentationData.strings.MESSAGE_PHOTOS(peer.displayTitle, "\(item.messages.count)").0 + messageText = presentationData.strings.MESSAGE_PHOTOS(peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder), "\(item.messages.count)").0 default: - messageText = presentationData.strings.MESSAGES(peer.displayTitle, "\(item.messages.count)").0 + messageText = presentationData.strings.MESSAGES(peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder), "\(item.messages.count)").0 } } } else { diff --git a/TelegramUI/ChatMessageReplyInfoNode.swift b/TelegramUI/ChatMessageReplyInfoNode.swift index 112f133f45..15be0c3cae 100644 --- a/TelegramUI/ChatMessageReplyInfoNode.swift +++ b/TelegramUI/ChatMessageReplyInfoNode.swift @@ -39,35 +39,35 @@ class ChatMessageReplyInfoNode: ASDisplayNode { self.contentNode.addSubnode(self.lineNode) } - class func asyncLayout(_ maybeNode: ChatMessageReplyInfoNode?) -> (_ theme: ChatPresentationThemeData, _ strings: PresentationStrings, _ account: Account, _ type: ChatMessageReplyInfoType, _ message: Message, _ constrainedSize: CGSize) -> (CGSize, () -> ChatMessageReplyInfoNode) { + class func asyncLayout(_ maybeNode: ChatMessageReplyInfoNode?) -> (_ theme: ChatPresentationData, _ strings: PresentationStrings, _ account: Account, _ type: ChatMessageReplyInfoType, _ message: Message, _ constrainedSize: CGSize) -> (CGSize, () -> ChatMessageReplyInfoNode) { let titleNodeLayout = TextNode.asyncLayout(maybeNode?.titleNode) let textNodeLayout = TextNode.asyncLayout(maybeNode?.textNode) let imageNodeLayout = TransformImageNode.asyncLayout(maybeNode?.imageNode) let previousMediaReference = maybeNode?.previousMediaReference - return { theme, strings, account, type, message, constrainedSize in - let titleString = message.author?.displayTitle(strings: strings) ?? strings.User_DeletedAccount - let (textString, isMedia) = descriptionStringForMessage(message, strings: strings, accountPeerId: account.peerId) + return { presentationData, strings, account, type, message, constrainedSize in + let titleString = message.author?.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder) ?? strings.User_DeletedAccount + let (textString, isMedia) = descriptionStringForMessage(message, strings: strings, nameDisplayOrder: presentationData.nameDisplayOrder, accountPeerId: account.peerId) - let placeholderColor: UIColor = message.effectivelyIncoming(account.peerId) ? theme.theme.chat.bubble.incomingMediaPlaceholderColor : theme.theme.chat.bubble.outgoingMediaPlaceholderColor + let placeholderColor: UIColor = message.effectivelyIncoming(account.peerId) ? presentationData.theme.theme.chat.bubble.incomingMediaPlaceholderColor : presentationData.theme.theme.chat.bubble.outgoingMediaPlaceholderColor let titleColor: UIColor let lineImage: UIImage? let textColor: UIColor switch type { case let .bubble(incoming): - titleColor = incoming ? theme.theme.chat.bubble.incomingAccentTextColor : theme.theme.chat.bubble.outgoingAccentTextColor - lineImage = incoming ? PresentationResourcesChat.chatBubbleVerticalLineIncomingImage(theme.theme) : PresentationResourcesChat.chatBubbleVerticalLineOutgoingImage(theme.theme) + titleColor = incoming ? presentationData.theme.theme.chat.bubble.incomingAccentTextColor : presentationData.theme.theme.chat.bubble.outgoingAccentTextColor + lineImage = incoming ? PresentationResourcesChat.chatBubbleVerticalLineIncomingImage(presentationData.theme.theme) : PresentationResourcesChat.chatBubbleVerticalLineOutgoingImage(presentationData.theme.theme) if isMedia { - textColor = incoming ? theme.theme.chat.bubble.incomingSecondaryTextColor : theme.theme.chat.bubble.outgoingSecondaryTextColor + textColor = incoming ? presentationData.theme.theme.chat.bubble.incomingSecondaryTextColor : presentationData.theme.theme.chat.bubble.outgoingSecondaryTextColor } else { - textColor = incoming ? theme.theme.chat.bubble.incomingPrimaryTextColor : theme.theme.chat.bubble.outgoingPrimaryTextColor + textColor = incoming ? presentationData.theme.theme.chat.bubble.incomingPrimaryTextColor : presentationData.theme.theme.chat.bubble.outgoingPrimaryTextColor } case .standalone: - let serviceColor = serviceMessageColorComponents(theme: theme.theme, wallpaper: theme.wallpaper) + let serviceColor = serviceMessageColorComponents(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) titleColor = serviceColor.primaryText - lineImage = PresentationResourcesChat.chatServiceVerticalLineImage(theme.theme) + lineImage = PresentationResourcesChat.chatServiceVerticalLineImage(presentationData.theme.theme) textColor = titleColor } diff --git a/TelegramUI/ChatMessageStickerItemNode.swift b/TelegramUI/ChatMessageStickerItemNode.swift index 7601994df5..d8dcbede48 100644 --- a/TelegramUI/ChatMessageStickerItemNode.swift +++ b/TelegramUI/ChatMessageStickerItemNode.swift @@ -234,7 +234,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { // } } - let dateText = stringForMessageTimestampStatus(message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, strings: item.presentationData.strings, format: .minimal) + let dateText = stringForMessageTimestampStatus(message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: .minimal) let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.presentationData.theme, item.presentationData.strings, edited && !sentViaBot, viewCount, dateText, statusType, CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude)) @@ -267,7 +267,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] { - replyInfoApply = makeReplyInfoLayout(item.presentationData.theme, item.presentationData.strings, item.account, .standalone, replyMessage, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) + replyInfoApply = makeReplyInfoLayout(item.presentationData, item.presentationData.strings, item.account, .standalone, replyMessage, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) } else if let attribute = attribute as? ReplyMarkupMessageAttribute, attribute.flags.contains(.inline), !attribute.rows.isEmpty { replyMarkup = attribute } diff --git a/TelegramUI/ChatMessageTextBubbleContentNode.swift b/TelegramUI/ChatMessageTextBubbleContentNode.swift index 0baab2d140..7a7c813849 100644 --- a/TelegramUI/ChatMessageTextBubbleContentNode.swift +++ b/TelegramUI/ChatMessageTextBubbleContentNode.swift @@ -87,7 +87,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { sentViaBot = true } - let dateText = stringForMessageTimestampStatus(message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, strings: item.presentationData.strings) + let dateText = stringForMessageTimestampStatus(message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings) let statusType: ChatMessageDateAndStatusType? switch position { diff --git a/TelegramUI/ChatPinnedMessageTitlePanelNode.swift b/TelegramUI/ChatPinnedMessageTitlePanelNode.swift index 49a35e181e..a947fd2f06 100644 --- a/TelegramUI/ChatPinnedMessageTitlePanelNode.swift +++ b/TelegramUI/ChatPinnedMessageTitlePanelNode.swift @@ -115,7 +115,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { self.currentMessage = interfaceState.pinnedMessage if let currentMessage = currentMessage, let currentLayout = self.currentLayout { - self.enqueueTransition(width: currentLayout.0, leftInset: currentLayout.1, rightInset: currentLayout.2, transition: .immediate, message: currentMessage, theme: interfaceState.theme, strings: interfaceState.strings, accountPeerId: self.account.peerId, firstTime: previousMessageWasNil) + self.enqueueTransition(width: currentLayout.0, leftInset: currentLayout.1, rightInset: currentLayout.2, transition: .immediate, message: currentMessage, theme: interfaceState.theme, strings: interfaceState.strings, nameDisplayOrder: interfaceState.nameDisplayOrder, accountPeerId: self.account.peerId, firstTime: previousMessageWasNil) } } @@ -134,14 +134,14 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { self.currentLayout = (width, leftInset, rightInset) if let currentMessage = self.currentMessage { - self.enqueueTransition(width: width, leftInset: leftInset, rightInset: rightInset, transition: .immediate, message: currentMessage, theme: interfaceState.theme, strings: interfaceState.strings, accountPeerId: interfaceState.accountPeerId, firstTime: true) + self.enqueueTransition(width: width, leftInset: leftInset, rightInset: rightInset, transition: .immediate, message: currentMessage, theme: interfaceState.theme, strings: interfaceState.strings, nameDisplayOrder: interfaceState.nameDisplayOrder, accountPeerId: interfaceState.accountPeerId, firstTime: true) } } return panelHeight } - private func enqueueTransition(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, message: Message, theme: PresentationTheme, strings: PresentationStrings, accountPeerId: PeerId, firstTime: Bool) { + private func enqueueTransition(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, message: Message, theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, accountPeerId: PeerId, firstTime: Bool) { let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let makeTextLayout = TextNode.asyncLayout(self.textNode) let imageNodeLayout = self.imageNode.asyncLayout() @@ -214,9 +214,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: strings.Conversation_PinnedMessage, font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - textLineInset - contentLeftInset - rightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 0.0, bottom: 2.0, right: 0.0))) - - - let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: descriptionStringForMessage(message, strings: strings, accountPeerId: accountPeerId).0, font: Font.regular(15.0), textColor: message.media.isEmpty || message.media.first is TelegramMediaWebpage ? theme.chat.inputPanel.primaryTextColor : theme.chat.inputPanel.secondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - textLineInset - contentLeftInset - rightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 0.0, bottom: 2.0, right: 0.0))) + let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: descriptionStringForMessage(message, strings: strings, nameDisplayOrder: nameDisplayOrder, accountPeerId: accountPeerId).0, font: Font.regular(15.0), textColor: message.media.isEmpty || message.media.first is TelegramMediaWebpage ? theme.chat.inputPanel.primaryTextColor : theme.chat.inputPanel.secondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - textLineInset - contentLeftInset - rightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 0.0, bottom: 2.0, right: 0.0))) Queue.mainQueue().async { if let strongSelf = self { diff --git a/TelegramUI/ChatPresentationData.swift b/TelegramUI/ChatPresentationData.swift index 5c5e32c3a8..d9a1c7fd10 100644 --- a/TelegramUI/ChatPresentationData.swift +++ b/TelegramUI/ChatPresentationData.swift @@ -61,6 +61,7 @@ public final class ChatPresentationData { let fontSize: PresentationFontSize let strings: PresentationStrings let dateTimeFormat: PresentationDateTimeFormat + let nameDisplayOrder: PresentationPersonNameOrder let disableAnimations: Bool let messageFont: UIFont @@ -68,11 +69,12 @@ public final class ChatPresentationData { let messageItalicFont: UIFont let messageFixedFont: UIFont - init(theme: ChatPresentationThemeData, fontSize: PresentationFontSize, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, disableAnimations: Bool) { + init(theme: ChatPresentationThemeData, fontSize: PresentationFontSize, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, disableAnimations: Bool) { self.theme = theme self.fontSize = fontSize self.strings = strings self.dateTimeFormat = dateTimeFormat + self.nameDisplayOrder = nameDisplayOrder self.disableAnimations = disableAnimations let baseFontSize = fontSize.baseDisplaySize diff --git a/TelegramUI/ChatPresentationInterfaceState.swift b/TelegramUI/ChatPresentationInterfaceState.swift index 66a32165f7..4259ee11fb 100644 --- a/TelegramUI/ChatPresentationInterfaceState.swift +++ b/TelegramUI/ChatPresentationInterfaceState.swift @@ -337,11 +337,12 @@ final class ChatPresentationInterfaceState: Equatable { let theme: PresentationTheme let strings: PresentationStrings let dateTimeFormat: PresentationDateTimeFormat + let nameDisplayOrder: PresentationPersonNameOrder let fontSize: PresentationFontSize let accountPeerId: PeerId let mode: ChatControllerPresentationMode - init(chatWallpaper: TelegramWallpaper, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, fontSize: PresentationFontSize, accountPeerId: PeerId, mode: ChatControllerPresentationMode, chatLocation: ChatLocation) { + init(chatWallpaper: TelegramWallpaper, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, fontSize: PresentationFontSize, accountPeerId: PeerId, mode: ChatControllerPresentationMode, chatLocation: ChatLocation) { self.interfaceState = ChatInterfaceState() self.inputTextPanelState = ChatTextInputPanelState() self.editMessageState = nil @@ -373,12 +374,13 @@ final class ChatPresentationInterfaceState: Equatable { self.theme = theme self.strings = strings self.dateTimeFormat = dateTimeFormat + self.nameDisplayOrder = nameDisplayOrder self.fontSize = fontSize self.accountPeerId = accountPeerId self.mode = mode } - init(interfaceState: ChatInterfaceState, chatLocation: ChatLocation, renderedPeer: RenderedPeer?, isNotAccessible: Bool, explicitelyCanPinMessages: Bool, isContact: Bool, hasBots: Bool, inputTextPanelState: ChatTextInputPanelState, editMessageState: ChatEditInterfaceMessageState?, recordedMediaPreview: ChatRecordedMediaPreview?, inputQueryResults: [ChatPresentationInputQueryKind: ChatPresentationInputQueryResult], inputMode: ChatInputMode, titlePanelContexts: [ChatTitlePanelContext], keyboardButtonsMessage: Message?, pinnedMessageId: MessageId?, pinnedMessage: Message?, peerIsBlocked: Bool, peerIsMuted: Bool, canReportPeer: Bool, callsAvailable: Bool, callsPrivate: Bool, chatHistoryState: ChatHistoryNodeHistoryState?, botStartPayload: String?, urlPreview: (String, TelegramMediaWebpage)?, editingUrlPreview: (String, TelegramMediaWebpage)?, search: ChatSearchData?, searchQuerySuggestionResult: ChatPresentationInputQueryResult?, chatWallpaper: TelegramWallpaper, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, fontSize: PresentationFontSize, accountPeerId: PeerId, mode: ChatControllerPresentationMode) { + init(interfaceState: ChatInterfaceState, chatLocation: ChatLocation, renderedPeer: RenderedPeer?, isNotAccessible: Bool, explicitelyCanPinMessages: Bool, isContact: Bool, hasBots: Bool, inputTextPanelState: ChatTextInputPanelState, editMessageState: ChatEditInterfaceMessageState?, recordedMediaPreview: ChatRecordedMediaPreview?, inputQueryResults: [ChatPresentationInputQueryKind: ChatPresentationInputQueryResult], inputMode: ChatInputMode, titlePanelContexts: [ChatTitlePanelContext], keyboardButtonsMessage: Message?, pinnedMessageId: MessageId?, pinnedMessage: Message?, peerIsBlocked: Bool, peerIsMuted: Bool, canReportPeer: Bool, callsAvailable: Bool, callsPrivate: Bool, chatHistoryState: ChatHistoryNodeHistoryState?, botStartPayload: String?, urlPreview: (String, TelegramMediaWebpage)?, editingUrlPreview: (String, TelegramMediaWebpage)?, search: ChatSearchData?, searchQuerySuggestionResult: ChatPresentationInputQueryResult?, chatWallpaper: TelegramWallpaper, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, fontSize: PresentationFontSize, accountPeerId: PeerId, mode: ChatControllerPresentationMode) { self.interfaceState = interfaceState self.chatLocation = chatLocation self.renderedPeer = renderedPeer @@ -410,6 +412,7 @@ final class ChatPresentationInterfaceState: Equatable { self.theme = theme self.strings = strings self.dateTimeFormat = dateTimeFormat + self.nameDisplayOrder = nameDisplayOrder self.fontSize = fontSize self.accountPeerId = accountPeerId self.mode = mode @@ -575,27 +578,27 @@ final class ChatPresentationInterfaceState: Equatable { } func updatedInterfaceState(_ f: (ChatInterfaceState) -> ChatInterfaceState) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: f(self.interfaceState), chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: f(self.interfaceState), chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedPeer(_ f: (RenderedPeer?) -> RenderedPeer?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: f(self.renderedPeer), isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: f(self.renderedPeer), isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedIsNotAccessible(_ isNotAccessible: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedExplicitelyCanPinMessages(_ explicitelyCanPinMessages: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedIsContact(_ isContact: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedHasBots(_ hasBots: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedInputQueryResult(queryKind: ChatPresentationInputQueryKind, _ f: (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?) -> ChatPresentationInterfaceState { @@ -606,103 +609,103 @@ final class ChatPresentationInterfaceState: Equatable { } else { inputQueryResults.removeValue(forKey: queryKind) } - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedInputTextPanelState(_ f: (ChatTextInputPanelState) -> ChatTextInputPanelState) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: f(self.inputTextPanelState), editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: f(self.inputTextPanelState), editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedEditMessageState(_ editMessageState: ChatEditInterfaceMessageState?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedRecordedMediaPreview(_ recordedMediaPreview: ChatRecordedMediaPreview?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedInputMode(_ f: (ChatInputMode) -> ChatInputMode) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: f(self.inputMode), titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: f(self.inputMode), titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedTitlePanelContext(_ f: ([ChatTitlePanelContext]) -> [ChatTitlePanelContext]) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: f(self.titlePanelContexts), keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: f(self.titlePanelContexts), keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedKeyboardButtonsMessage(_ message: Message?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: message, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: message, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedPinnedMessageId(_ pinnedMessageId: MessageId?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedPinnedMessage(_ pinnedMessage: Message?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedPeerIsBlocked(_ peerIsBlocked: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedPeerIsMuted(_ peerIsMuted: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedCanReportPeer(_ canReportPeer: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedCallsAvailable(_ callsAvailable: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedCallsPrivate(_ callsPrivate: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedBotStartPayload(_ botStartPayload: String?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedChatHistoryState(_ chatHistoryState: ChatHistoryNodeHistoryState?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedUrlPreview(_ urlPreview: (String, TelegramMediaWebpage)?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedEditingUrlPreview(_ editingUrlPreview: (String, TelegramMediaWebpage)?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedSearch(_ search: ChatSearchData?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedSearchQuerySuggestionResult(_ f: (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: f(self.searchQuerySuggestionResult), chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: f(self.searchQuerySuggestionResult), chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedMode(_ mode: ChatControllerPresentationMode) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: mode) } func updatedTheme(_ theme: PresentationTheme) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedStrings(_ strings: PresentationStrings) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: strings, dateTimeFormat: self.dateTimeFormat, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedDateTimeFormat(_ dateTimeFormat: PresentationDateTimeFormat) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: dateTimeFormat, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedChatWallpaper(_ chatWallpaper: TelegramWallpaper) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, isContact: self.isContact, hasBots: self.hasBots, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } } diff --git a/TelegramUI/ChatRecentActionsControllerNode.swift b/TelegramUI/ChatRecentActionsControllerNode.swift index 904aca463c..edb4dbf2f4 100644 --- a/TelegramUI/ChatRecentActionsControllerNode.swift +++ b/TelegramUI/ChatRecentActionsControllerNode.swift @@ -97,7 +97,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { self.state = ChatRecentActionsControllerState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, fontSize: self.presentationData.fontSize) - self.chatPresentationDataPromise = Promise(ChatPresentationData(theme: ChatPresentationThemeData(theme: self.presentationData.theme, wallpaper: self.presentationData.chatWallpaper), fontSize: self.presentationData.fontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, disableAnimations: self.presentationData.disableAnimations)) + self.chatPresentationDataPromise = Promise(ChatPresentationData(theme: ChatPresentationThemeData(theme: self.presentationData.theme, wallpaper: self.presentationData.chatWallpaper), fontSize: self.presentationData.fontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: self.presentationData.disableAnimations)) self.context = ChannelAdminEventLogContext(postbox: self.account.postbox, network: self.account.network, peerId: self.peer.id) diff --git a/TelegramUI/ChatRecentActionsFilterController.swift b/TelegramUI/ChatRecentActionsFilterController.swift index c3780ffbe5..429a5137be 100644 --- a/TelegramUI/ChatRecentActionsFilterController.swift +++ b/TelegramUI/ChatRecentActionsFilterController.swift @@ -64,7 +64,7 @@ private enum ChatRecentActionsFilterEntry: ItemListNodeEntry { case adminsTitle(PresentationTheme, String) case allAdmins(PresentationTheme, String, Bool) - case adminPeerItem(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Int32, RenderedChannelParticipant, Bool) + case adminPeerItem(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Int32, RenderedChannelParticipant, Bool) var section: ItemListSectionId { switch self { @@ -87,7 +87,7 @@ private enum ChatRecentActionsFilterEntry: ItemListNodeEntry { return .index(200) case .allAdmins: return .index(201) - case let .adminPeerItem(_, _, _, _, participant, _): + case let .adminPeerItem(_, _, _, _, _, participant, _): return .peer(participant.peer.id) } } @@ -124,8 +124,8 @@ private enum ChatRecentActionsFilterEntry: ItemListNodeEntry { } else { return false } - case let .adminPeerItem(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsIndex, lhsParticipant, lhsChecked): - if case let .adminPeerItem(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsIndex, rhsParticipant, rhsChecked) = rhs { + case let .adminPeerItem(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameDisplayOrder, lhsIndex, lhsParticipant, lhsChecked): + if case let .adminPeerItem(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameDisplayOrder, rhsIndex, rhsParticipant, rhsChecked) = rhs { if lhsTheme !== rhsTheme { return false } @@ -135,6 +135,9 @@ private enum ChatRecentActionsFilterEntry: ItemListNodeEntry { if lhsDateTimeFormat != rhsDateTimeFormat { return false } + if lhsNameDisplayOrder != rhsNameDisplayOrder { + return false + } if lhsIndex != rhsIndex { return false } @@ -185,9 +188,9 @@ private enum ChatRecentActionsFilterEntry: ItemListNodeEntry { default: return false } - case let .adminPeerItem(_, _, _, lhsIndex, _, _): + case let .adminPeerItem(_, _, _, _, lhsIndex, _, _): switch rhs { - case let .adminPeerItem(_, _, _, rhsIndex, _, _): + case let .adminPeerItem(_, _, _, _, rhsIndex, _, _): return lhsIndex < rhsIndex default: return false @@ -213,7 +216,7 @@ private enum ChatRecentActionsFilterEntry: ItemListNodeEntry { return ItemListSwitchItem(theme: theme, title: text, value: value, enabled: true, sectionId: self.section, style: .blocks, updated: { _ in arguments.toggleAllAdmins() }) - case let .adminPeerItem(theme, strings, dateTimeFormat, _, participant, checked): + case let .adminPeerItem(theme, strings, dateTimeFormat, nameDisplayOrder, _, participant, checked): let peerText: String switch participant.participant { case .creator: @@ -221,7 +224,7 @@ private enum ChatRecentActionsFilterEntry: ItemListNodeEntry { case .member: peerText = strings.ChatAdmins_AdminLabel.capitalized } - return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, account: arguments.account, peer: participant.peer, presence: nil, text: .text(peerText), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: ItemListPeerItemSwitch(value: checked, style: .check), enabled: true, sectionId: self.section, action: { + return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, account: arguments.account, peer: participant.peer, presence: nil, text: .text(peerText), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: ItemListPeerItemSwitch(value: checked, style: .check), enabled: true, sectionId: self.section, action: { arguments.toggleAdmin(participant.peer.id) }, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }) @@ -345,7 +348,7 @@ private func channelRecentActionsFilterControllerEntries(presentationData: Prese } else { adminSelected = true } - entries.append(.adminPeerItem(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, index, participant, adminSelected)) + entries.append(.adminPeerItem(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, index, participant, adminSelected)) index += 1 } } diff --git a/TelegramUI/ChatTitleView.swift b/TelegramUI/ChatTitleView.swift index 2565535cef..537a843e3f 100644 --- a/TelegramUI/ChatTitleView.swift +++ b/TelegramUI/ChatTitleView.swift @@ -78,6 +78,7 @@ final class ChatTitleView: UIView, NavigationBarTitleView { private var theme: PresentationTheme private var strings: PresentationStrings private var dateTimeFormat: PresentationDateTimeFormat + private var nameDisplayOrder: PresentationPersonNameOrder private let contentContainer: ASDisplayNode private let titleNode: ImmediateTextNode @@ -255,7 +256,7 @@ final class ChatTitleView: UIView, NavigationBarTitleView { if peerView.peerId == self.account.peerId { string = NSAttributedString(string: self.strings.Conversation_SavedMessages, font: Font.medium(17.0), textColor: self.theme.rootController.navigationBar.primaryTextColor) } else { - string = NSAttributedString(string: peer.displayTitle(strings: self.strings), font: Font.medium(17.0), textColor: self.theme.rootController.navigationBar.primaryTextColor) + string = NSAttributedString(string: peer.displayTitle(strings: self.strings, displayOrder: self.nameDisplayOrder), font: Font.medium(17.0), textColor: self.theme.rootController.navigationBar.primaryTextColor) } } if peerView.peerId.namespace == Namespaces.Peer.SecretChat { @@ -446,11 +447,12 @@ final class ChatTitleView: UIView, NavigationBarTitleView { } } - init(account: Account, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat) { + init(account: Account, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder) { self.account = account self.theme = theme self.strings = strings self.dateTimeFormat = dateTimeFormat + self.nameDisplayOrder = nameDisplayOrder self.contentContainer = ASDisplayNode() diff --git a/TelegramUI/ContactListNode.swift b/TelegramUI/ContactListNode.swift index 9d3b3b921f..96931c967a 100644 --- a/TelegramUI/ContactListNode.swift +++ b/TelegramUI/ContactListNode.swift @@ -304,44 +304,64 @@ private extension PeerIndexNameRepresentation { func isLessThan(other: PeerIndexNameRepresentation, ordering: PresentationPersonNameOrder) -> ComparisonResult { switch self { case let .title(lhsTitle, _): + let rhsString: String switch other { case let .title(title, _): - return lhsTitle.compare(title) - case let .personName(_, last, _, _): - let lastResult = lhsTitle.compare(last) - if lastResult == .orderedSame { - return .orderedAscending - } else { - return lastResult - } - } - case let .personName(lhsFirst, lhsLast, _, _): - switch other { - case let .title(title, _): - let lastResult = lhsFirst.compare(title) - if lastResult == .orderedSame { - return .orderedDescending - } else { - return lastResult - } + rhsString = title case let .personName(first, last, _, _): switch ordering { case .firstLast: - let firstResult = lhsFirst.compare(first) - if firstResult == .orderedSame { - return lhsLast.compare(last) + if first.isEmpty { + rhsString = last } else { - return firstResult + rhsString = first + last } case .lastFirst: - let lastResult = lhsLast.compare(last) - if lastResult == .orderedSame { - return lhsFirst.compare(first) + if last.isEmpty { + rhsString = first } else { - return lastResult + rhsString = last + first } } } + return lhsTitle.caseInsensitiveCompare(rhsString) + case let .personName(lhsFirst, lhsLast, _, _): + let lhsString: String + switch ordering { + case .firstLast: + if lhsFirst.isEmpty { + lhsString = lhsLast + } else { + lhsString = lhsFirst + lhsLast + } + case .lastFirst: + if lhsLast.isEmpty { + lhsString = lhsFirst + } else { + lhsString = lhsLast + lhsFirst + } + } + let rhsString: String + switch other { + case let .title(title, _): + rhsString = title + case let .personName(first, last, _, _): + switch ordering { + case .firstLast: + if first.isEmpty { + rhsString = last + } else { + rhsString = first + last + } + case .lastFirst: + if last.isEmpty { + rhsString = first + } else { + rhsString = last + first + } + } + } + return lhsString.caseInsensitiveCompare(rhsString) } } } @@ -424,21 +444,21 @@ private func contactListNodeEntries(accountPeer: Peer?, peers: [ContactListPeer] var indexHeader: unichar = 35 switch peer.indexName { case let .title(title, _): - if let c = title.utf16.first { + if let c = title.uppercased().utf16.first { indexHeader = c } case let .personName(first, last, _, _): switch sortOrder { case .firstLast: - if let c = first.utf16.first { + if let c = first.uppercased().utf16.first { indexHeader = c - } else if let c = last.utf16.first { + } else if let c = last.uppercased().utf16.first { indexHeader = c } case .lastFirst: - if let c = last.utf16.first { + if let c = last.uppercased().utf16.first { indexHeader = c - } else if let c = first.utf16.first { + } else if let c = first.uppercased().utf16.first { indexHeader = c } } @@ -511,20 +531,45 @@ private func contactListNodeEntries(accountPeer: Peer?, peers: [ContactListPeer] return entries } -private func preparedContactListNodeTransition(account: Account, from fromEntries: [ContactListNodeEntry], to toEntries: [ContactListNodeEntry], interaction: ContactListNodeInteraction, firstTime: Bool, isEmpty: Bool, animated: Bool) -> ContactsListNodeTransition { +private func preparedContactListNodeTransition(account: Account, from fromEntries: [ContactListNodeEntry], to toEntries: [ContactListNodeEntry], interaction: ContactListNodeInteraction, firstTime: Bool, isEmpty: Bool, generateIndexSections: Bool, animated: Bool) -> ContactsListNodeTransition { 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 ContactsListNodeTransition(deletions: deletions, insertions: insertions, updates: updates, firstTime: firstTime, isEmpty: isEmpty, animated: animated) + var indexSections: [String] = [] + if generateIndexSections { + var existingSections = Set() + for entry in toEntries { + switch entry { + case .search: + //indexSections.apend(CollectionIndexNode.searchIndex) + break + case let .peer(_, _, _, header, _, _, _, _, _, _, _): + if let header = header as? ContactListNameIndexHeader { + if !existingSections.contains(header.letter) { + existingSections.insert(header.letter) + if let scalar = UnicodeScalar(header.letter) { + let title = "\(Character(scalar))" + indexSections.append(title) + } + } + } + default: + break + } + } + } + + return ContactsListNodeTransition(deletions: deletions, insertions: insertions, updates: updates, indexSections: indexSections, firstTime: firstTime, isEmpty: isEmpty, animated: animated) } private struct ContactsListNodeTransition { let deletions: [ListViewDeleteItem] let insertions: [ListViewInsertItem] let updates: [ListViewUpdateItem] + let indexSections: [String] let firstTime: Bool let isEmpty: Bool let animated: Bool @@ -584,9 +629,11 @@ final class ContactListNode: ASDisplayNode { private let filters: [ContactListFilter] let listNode: ListView + private var indexNode: CollectionIndexNode? + private var indexSections: [String]? private var queuedTransitions: [ContactsListNodeTransition] = [] - private var hasValidLayout = false + private var validLayout: ContainerViewLayout? private var _ready = ValuePromise() var ready: Signal { @@ -647,6 +694,14 @@ final class ContactListNode: ASDisplayNode { self.listNode = ListView() self.listNode.dynamicBounceEnabled = !self.presentationData.disableAnimations + var generateSections = false + if case .natural = presentation { + generateSections = true + self.indexNode = CollectionIndexNode() + } else { + self.indexNode = nil + } + self.themeAndStringsPromise = Promise((self.presentationData.theme, self.presentationData.strings, self.presentationData.dateTimeFormat, self.presentationData.nameSortOrder, self.presentationData.nameDisplayOrder, self.presentationData.disableAnimations)) self.authorizationPromise = Promise(AccessType.allowed) @@ -664,12 +719,15 @@ final class ContactListNode: ASDisplayNode { super.init() self.backgroundColor = self.presentationData.theme.chatList.backgroundColor - self.listNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor + if self.indexNode == nil { + self.listNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor + } self.selectionStateValue = selectionState self.selectionStatePromise.set(.single(selectionState)) self.addSubnode(self.listNode) + self.indexNode.flatMap(self.addSubnode) self.addSubnode(self.authorizationNode) let processingQueue = Queue() @@ -683,6 +741,37 @@ final class ContactListNode: ASDisplayNode { self?.openPeer?(peer) }) + self.indexNode?.indexSelected = { [weak self] section in + guard let strongSelf = self, let entries = previousEntries.with({ $0 }) else { + return + } + var index = 0 + var peerIndex = 0 + loop: for entry in entries { + switch entry { + case .search: + if section == CollectionIndexNode.searchIndex { + strongSelf.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.PreferSynchronousDrawing, .PreferSynchronousResourceLoading], scrollToItem: ListViewScrollToItem(index: index, position: .top(0.0), animated: false, curve: .Default(duration: nil), directionHint: .Down), additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + break loop + } + case let .peer(_, _, _, header, _, _, _, _, _, _, _): + if let header = header as? ContactListNameIndexHeader { + if let scalar = UnicodeScalar(header.letter) { + let title = "\(Character(scalar))" + if title == section { + strongSelf.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.PreferSynchronousDrawing, .PreferSynchronousResourceLoading], scrollToItem: ListViewScrollToItem(index: peerIndex == 0 ? 0 : index, position: .top(0.0), animated: false, curve: .Default(duration: nil), directionHint: .Down), additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + break loop + } + } + } + peerIndex += 1 + default: + break + } + index += 1 + } + } + let account = self.account var firstTime: Int32 = 1 let selectionStateSignal = self.selectionStatePromise.get() @@ -795,7 +884,7 @@ final class ContactListNode: ASDisplayNode { let entries = contactListNodeEntries(accountPeer: nil, peers: peers, presences: localPeersAndStatuses.1, presentation: presentation, selectionState: selectionState, theme: themeAndStrings.0, strings: themeAndStrings.1, dateTimeFormat: themeAndStrings.2, sortOrder: themeAndStrings.3, displayOrder: themeAndStrings.4, disabledPeerIds: disabledPeerIds, authorizationStatus: .allowed) let previous = previousEntries.swap(entries) - return .single(preparedContactListNodeTransition(account: account, from: previous ?? [], to: entries, interaction: interaction, firstTime: previous == nil, isEmpty: false, animated: false)) + return .single(preparedContactListNodeTransition(account: account, from: previous ?? [], to: entries, interaction: interaction, firstTime: previous == nil, isEmpty: false, generateIndexSections: generateSections, animated: false)) } if OSAtomicCompareAndSwap32(1, 0, &firstTime) { @@ -844,7 +933,7 @@ final class ContactListNode: ASDisplayNode { } else { animated = false } - return .single(preparedContactListNodeTransition(account: account, from: previous ?? [], to: entries, interaction: interaction, firstTime: previous == nil, isEmpty: isEmpty, animated: animated)) + return .single(preparedContactListNodeTransition(account: account, from: previous ?? [], to: entries, interaction: interaction, firstTime: previous == nil, isEmpty: isEmpty, generateIndexSections: generateSections, animated: animated)) } if OSAtomicCompareAndSwap32(1, 0, &firstTime) { @@ -953,6 +1042,9 @@ final class ContactListNode: ASDisplayNode { } func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + let hadValidLayout = self.validLayout != nil + self.validLayout = layout + var insets = layout.insets(options: [.input]) insets.left += layout.safeInsets.left insets.right += layout.safeInsets.right @@ -985,14 +1077,18 @@ final class ContactListNode: ASDisplayNode { let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, duration: duration, curve: listViewCurve) self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + if let indexNode = self.indexNode, let indexSections = self.indexSections { + let indexNodeFrame = CGRect(origin: CGPoint(x: layout.size.width - insets.right - 20.0, y: insets.top), size: CGSize(width: 20.0, height: layout.size.height - insets.top - insets.bottom)) + transition.updateFrame(node: indexNode, frame: indexNodeFrame) + indexNode.update(size: indexNodeFrame.size, color: self.presentationData.theme.list.itemAccentColor, sections: indexSections, transition: transition) + } //if let authorizationNode = self.authorizationNode { authorizationNode.updateLayout(size: layout.size, insets: insets, transition: transition) transition.updateFrame(node: authorizationNode, frame: self.bounds) //} - if !self.hasValidLayout { - self.hasValidLayout = true + if !hadValidLayout { self.dequeueTransitions() } } @@ -1000,13 +1096,13 @@ final class ContactListNode: ASDisplayNode { private func enqueueTransition(_ transition: ContactsListNodeTransition) { self.queuedTransitions.append(transition) - if self.hasValidLayout { + if self.validLayout != nil { self.dequeueTransitions() } } private func dequeueTransitions() { - if self.hasValidLayout { + if self.validLayout != nil { while !self.queuedTransitions.isEmpty { let transition = self.queuedTransitions.removeFirst() @@ -1019,6 +1115,15 @@ final class ContactListNode: ASDisplayNode { options.insert(.AnimateCrossfade) } } + if let indexNode = self.indexNode, let layout = self.validLayout { + self.indexSections = transition.indexSections + + var insets = layout.insets(options: [.input]) + insets.left += layout.safeInsets.left + insets.right += layout.safeInsets.right + + indexNode.update(size: CGSize(width: 20.0, height: layout.size.height - insets.top - insets.bottom), color: self.presentationData.theme.list.itemAccentColor, sections: transition.indexSections, transition: .immediate) + } self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateOpaqueState: nil, completion: { [weak self] _ in if let strongSelf = self { if !strongSelf.didSetReady { diff --git a/TelegramUI/ContactSynchronizationSettings.swift b/TelegramUI/ContactSynchronizationSettings.swift index b82ababcef..2ce55817c8 100644 --- a/TelegramUI/ContactSynchronizationSettings.swift +++ b/TelegramUI/ContactSynchronizationSettings.swift @@ -4,21 +4,25 @@ import SwiftSignalKit public struct ContactSynchronizationSettings: Equatable, PreferencesEntry { public var synchronizeDeviceContacts: Bool + public var nameDisplayOrder: PresentationPersonNameOrder public static var defaultSettings: ContactSynchronizationSettings { - return ContactSynchronizationSettings(synchronizeDeviceContacts: true) + return ContactSynchronizationSettings(synchronizeDeviceContacts: true, nameDisplayOrder: .firstLast) } - public init(synchronizeDeviceContacts: Bool) { + public init(synchronizeDeviceContacts: Bool, nameDisplayOrder: PresentationPersonNameOrder) { self.synchronizeDeviceContacts = synchronizeDeviceContacts + self.nameDisplayOrder = nameDisplayOrder } public init(decoder: PostboxDecoder) { self.synchronizeDeviceContacts = decoder.decodeInt32ForKey("synchronizeDeviceContacts", orElse: 0) != 0 + self.nameDisplayOrder = PresentationPersonNameOrder(rawValue: decoder.decodeInt32ForKey("nameDisplayOrder", orElse: 0)) ?? .firstLast } public func encode(_ encoder: PostboxEncoder) { encoder.encodeInt32(self.synchronizeDeviceContacts ? 1 : 0, forKey: "synchronizeDeviceContacts") + encoder.encodeInt32(self.nameDisplayOrder.rawValue, forKey: "synchronizeDeviceContacts") } public func isEqual(to: PreferencesEntry) -> Bool { @@ -30,7 +34,7 @@ public struct ContactSynchronizationSettings: Equatable, PreferencesEntry { } } -func updateContactSynchronizationSettingsInteractively(postbox: Postbox, _ f: @escaping (ContactSynchronizationSettings) -> ContactSynchronizationSettings) -> Signal { +func updateContactSettingsInteractively(postbox: Postbox, _ f: @escaping (ContactSynchronizationSettings) -> ContactSynchronizationSettings) -> Signal { return postbox.transaction { transaction -> Void in transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.contactSynchronizationSettings, { entry in let currentSettings: ContactSynchronizationSettings diff --git a/TelegramUI/ContactsPeerItem.swift b/TelegramUI/ContactsPeerItem.swift index c2a820b9c3..148aa51ea5 100644 --- a/TelegramUI/ContactsPeerItem.swift +++ b/TelegramUI/ContactsPeerItem.swift @@ -115,6 +115,7 @@ class ContactsPeerItem: ListViewItem { let enabled: Bool let selection: ContactsPeerItemSelection let editing: ContactsPeerItemEditing + let options: [ItemListPeerItemRevealOption] let action: (ContactsPeerItemPeer) -> Void let setPeerIdWithRevealedOptions: ((PeerId?, PeerId?) -> Void)? let deletePeer: ((PeerId) -> Void)? @@ -125,7 +126,7 @@ class ContactsPeerItem: ListViewItem { let header: ListViewItemHeader? - init(theme: PresentationTheme, strings: PresentationStrings, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, account: Account, peerMode: ContactsPeerItemPeerMode, peer: ContactsPeerItemPeer, status: ContactsPeerItemStatus, badge: ContactsPeerItemBadge? = nil, enabled: Bool, selection: ContactsPeerItemSelection, editing: ContactsPeerItemEditing, index: PeerNameIndex?, header: ListViewItemHeader?, action: @escaping (ContactsPeerItemPeer) -> Void, setPeerIdWithRevealedOptions: ((PeerId?, PeerId?) -> Void)? = nil, deletePeer: ((PeerId) -> Void)? = nil) { + init(theme: PresentationTheme, strings: PresentationStrings, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, account: Account, peerMode: ContactsPeerItemPeerMode, peer: ContactsPeerItemPeer, status: ContactsPeerItemStatus, badge: ContactsPeerItemBadge? = nil, enabled: Bool, selection: ContactsPeerItemSelection, editing: ContactsPeerItemEditing, options: [ItemListPeerItemRevealOption] = [], index: PeerNameIndex?, header: ListViewItemHeader?, action: @escaping (ContactsPeerItemPeer) -> Void, setPeerIdWithRevealedOptions: ((PeerId?, PeerId?) -> Void)? = nil, deletePeer: ((PeerId) -> Void)? = nil) { self.theme = theme self.strings = strings self.sortOrder = sortOrder @@ -138,6 +139,7 @@ class ContactsPeerItem: ListViewItem { self.enabled = enabled self.selection = selection self.editing = editing + self.options = options self.action = action self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions self.deletePeer = deletePeer @@ -451,9 +453,16 @@ class ContactsPeerItemNode: ItemListRevealOptionsItemNode { titleAttributedString = NSAttributedString(string: item.strings.DialogList_SavedMessages, font: titleBoldFont, textColor: textColor) } else if let firstName = user.firstName, let lastName = user.lastName, !firstName.isEmpty, !lastName.isEmpty { let string = NSMutableAttributedString() - string.append(NSAttributedString(string: firstName, font: item.sortOrder == .firstLast ? titleBoldFont : titleFont, textColor: textColor)) - string.append(NSAttributedString(string: " ", font: titleFont, textColor: textColor)) - string.append(NSAttributedString(string: lastName, font: item.sortOrder == .firstLast ? titleFont : titleBoldFont, textColor: textColor)) + switch item.displayOrder { + case .firstLast: + string.append(NSAttributedString(string: firstName, font: item.sortOrder == .firstLast ? titleBoldFont : titleFont, textColor: textColor)) + string.append(NSAttributedString(string: " ", font: titleFont, textColor: textColor)) + string.append(NSAttributedString(string: lastName, font: item.sortOrder == .firstLast ? titleFont : titleBoldFont, textColor: textColor)) + case .lastFirst: + string.append(NSAttributedString(string: lastName, font: item.sortOrder == .firstLast ? titleFont : titleBoldFont, textColor: textColor)) + string.append(NSAttributedString(string: " ", font: titleFont, textColor: textColor)) + string.append(NSAttributedString(string: firstName, font: item.sortOrder == .firstLast ? titleBoldFont : titleFont, textColor: textColor)) + } titleAttributedString = string } else if let firstName = user.firstName, !firstName.isEmpty { titleAttributedString = NSAttributedString(string: firstName, font: titleBoldFont, textColor: textColor) @@ -562,6 +571,32 @@ class ContactsPeerItemNode: ItemListRevealOptionsItemNode { titleFrame = CGRect(origin: CGPoint(x: leftInset, y: 13.0), size: titleLayout.size) } + let peerRevealOptions: [ItemListRevealOption] + if item.enabled { + var mappedOptions: [ItemListRevealOption] = [] + var index: Int32 = 0 + for option in item.options { + let color: UIColor + let textColor: UIColor + switch option.type { + case .neutral: + color = item.theme.list.itemDisclosureActions.constructive.fillColor + textColor = item.theme.list.itemDisclosureActions.constructive.foregroundColor + case .warning: + color = item.theme.list.itemDisclosureActions.warning.fillColor + textColor = item.theme.list.itemDisclosureActions.warning.foregroundColor + case .destructive: + color = item.theme.list.itemDisclosureActions.destructive.fillColor + textColor = item.theme.list.itemDisclosureActions.destructive.foregroundColor + } + mappedOptions.append(ItemListRevealOption(key: index, title: option.title, icon: .none, color: color, textColor: textColor)) + index += 1 + } + peerRevealOptions = mappedOptions + } else { + peerRevealOptions = [] + } + return (nodeLayout, { [weak self] in if let strongSelf = self { return (.complete(), { [weak strongSelf] animated, synchronousLoads in @@ -710,12 +745,12 @@ class ContactsPeerItemNode: ItemListRevealOptionsItemNode { strongSelf.updateLayout(size: nodeLayout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset) - if item.editing.editable { strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.strings.Common_Delete, icon: .none, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)])) strongSelf.setRevealOptionsOpened(item.editing.revealed, animated: animated) } else { - strongSelf.setRevealOptions((left: [], right: [])) + strongSelf.setRevealOptions((left: [], right: peerRevealOptions)) + strongSelf.setRevealOptionsOpened(item.editing.revealed, animated: animated) } } }) @@ -800,13 +835,17 @@ class ContactsPeerItemNode: ItemListRevealOptionsItemNode { override func revealOptionSelected(_ option: ItemListRevealOption, animated: Bool) { if let item = self.item { - switch item.peer { - case let .peer(peer, chatPeer): - if let peer = chatPeer ?? peer { - item.deletePeer?(peer.id) - } - case .deviceContact: - break + if item.editing.editable { + switch item.peer { + case let .peer(peer, chatPeer): + if let peer = chatPeer ?? peer { + item.deletePeer?(peer.id) + } + case .deviceContact: + break + } + } else { + item.options[Int(option.key)].action() } } diff --git a/TelegramUI/CountryList.swift b/TelegramUI/CountryList.swift index 9aa3ba1266..6a792cee1e 100644 --- a/TelegramUI/CountryList.swift +++ b/TelegramUI/CountryList.swift @@ -73,3 +73,16 @@ let countryCodeToIdAndName: [Int: (String, String)] = { } return dict }() + +struct CountryCodeAndId: Hashable { + let code: Int + let id: String +} + +let countryCodeAndIdToName: [CountryCodeAndId: String] = { + var dict: [CountryCodeAndId: String] = [:] + for (code, id, name) in phoneCountriesInfo { + dict[CountryCodeAndId(code: code, id: id)] = name + } + return dict +}() diff --git a/TelegramUI/CreateGroupController.swift b/TelegramUI/CreateGroupController.swift index 04749ec964..eebfddf67b 100644 --- a/TelegramUI/CreateGroupController.swift +++ b/TelegramUI/CreateGroupController.swift @@ -42,7 +42,7 @@ private enum CreateGroupEntry: ItemListNodeEntry { case groupInfo(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer?, ItemListAvatarAndNameInfoItemState, ItemListAvatarAndNameInfoItemUpdatingAvatar?) case setProfilePhoto(PresentationTheme, String) - case member(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, PeerPresence?) + case member(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer, PeerPresence?) var section: ItemListSectionId { switch self { @@ -59,7 +59,7 @@ private enum CreateGroupEntry: ItemListNodeEntry { return 0 case .setProfilePhoto: return 1 - case let .member(index, _, _, _, _, _): + case let .member(index, _, _, _, _, _, _): return 2 + index } } @@ -100,8 +100,8 @@ private enum CreateGroupEntry: ItemListNodeEntry { } else { return false } - case let .member(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsPresence): - if case let .member(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsPresence) = rhs { + case let .member(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameDisplayOrder, lhsPeer, lhsPresence): + if case let .member(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameDisplayOrder, rhsPeer, rhsPresence) = rhs { if lhsIndex != rhsIndex { return false } @@ -114,6 +114,9 @@ private enum CreateGroupEntry: ItemListNodeEntry { if lhsDateTimeFormat != rhsDateTimeFormat { return false } + if lhsNameDisplayOrder != rhsNameDisplayOrder { + return false + } if !lhsPeer.isEqual(rhsPeer) { return false } @@ -146,8 +149,8 @@ private enum CreateGroupEntry: ItemListNodeEntry { return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: ItemListSectionId(self.section), style: .blocks, action: { arguments.changeProfilePhoto() }) - case let .member(_, theme, strings, dateTimeFormat, peer, presence): - return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, account: arguments.account, peer: peer, presence: presence, text: .presence, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }) + case let .member(_, theme, strings, dateTimeFormat, nameDisplayOrder, peer, presence): + return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, account: arguments.account, peer: peer, presence: presence, text: .presence, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }) } } } @@ -210,7 +213,7 @@ private func createGroupEntries(presentationData: PresentationData, state: Creat }) for i in 0 ..< peers.count { - entries.append(.member(Int32(i), presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peers[i], view.presences[peers[i].id])) + entries.append(.member(Int32(i), presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, peers[i], view.presences[peers[i].id])) } return entries diff --git a/TelegramUI/DataPrivacySettingsController.swift b/TelegramUI/DataPrivacySettingsController.swift index c92678f722..c2b28344f7 100644 --- a/TelegramUI/DataPrivacySettingsController.swift +++ b/TelegramUI/DataPrivacySettingsController.swift @@ -394,7 +394,7 @@ public func dataPrivacyController(account: Account) -> ViewController { return } - let _ = updateContactSynchronizationSettingsInteractively(postbox: account.postbox, { settings in + let _ = updateContactSettingsInteractively(postbox: account.postbox, { settings in var settings = settings settings.synchronizeDeviceContacts = false return settings @@ -413,7 +413,7 @@ public func dataPrivacyController(account: Account) -> ViewController { }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {})])) } }, updateSyncContacts: { value in - let _ = updateContactSynchronizationSettingsInteractively(postbox: account.postbox, { settings in + let _ = updateContactSettingsInteractively(postbox: account.postbox, { settings in var settings = settings settings.synchronizeDeviceContacts = value return settings diff --git a/TelegramUI/DeviceContactDataManager.swift b/TelegramUI/DeviceContactDataManager.swift index c7f79dccfa..50dbc1f557 100644 --- a/TelegramUI/DeviceContactDataManager.swift +++ b/TelegramUI/DeviceContactDataManager.swift @@ -7,6 +7,7 @@ import AddressBook public typealias DeviceContactStableId = String private protocol DeviceContactDataContext { + func personNameDisplayOrder() -> PresentationPersonNameOrder func getExtendedContactData(stableId: DeviceContactStableId) -> DeviceContactExtendedData? func appendContactData(_ contactData: DeviceContactExtendedData, to stableId: DeviceContactStableId) -> DeviceContactExtendedData? func createContactWithData(_ contactData: DeviceContactExtendedData) -> (DeviceContactStableId, DeviceContactExtendedData)? @@ -64,6 +65,15 @@ private final class DeviceContactDataModernContext: DeviceContactDataContext { return (contact.identifier, DeviceContactBasicData(firstName: contact.givenName, lastName: contact.familyName, phoneNumbers: phoneNumbers)) } + func personNameDisplayOrder() -> PresentationPersonNameOrder { + switch CNContactFormatter.nameOrder(for: CNContact()) { + case .givenNameFirst: + return .firstLast + default: + return .lastFirst + } + } + func getExtendedContactData(stableId: DeviceContactStableId) -> DeviceContactExtendedData? { let keysToFetch: [CNKeyDescriptor] = [ CNContactFormatter.descriptorForRequiredKeys(for: .fullName), @@ -318,6 +328,14 @@ private final class DeviceContactDataLegacyContext: DeviceContactDataContext { return (stableId, DeviceContactBasicData(firstName: firstName, lastName: lastName, phoneNumbers: phoneNumbers)) } + func personNameDisplayOrder() -> PresentationPersonNameOrder { + if ABPersonGetCompositeNameFormat() == kABPersonCompositeNameFormatFirstNameFirst { + return .firstLast + } else { + return .lastFirst + } + } + func getExtendedContactData(stableId: DeviceContactStableId) -> DeviceContactExtendedData? { if let contact = self.getContactById(stableId: stableId) { let basicData = DeviceContactDataLegacyContext.parseContact(contact).1 @@ -378,6 +396,7 @@ private final class DeviceContactDataManagerImpl { private var accessInitialized = false private var dataContext: DeviceContactDataContext? + let personNameDisplayOrder = ValuePromise() private var extendedContexts: [DeviceContactStableId: ExtendedContactDataContext] = [:] private var stableIdToBasicContactData: [DeviceContactStableId: DeviceContactBasicData] = [:] @@ -402,27 +421,24 @@ private final class DeviceContactDataManagerImpl { strongSelf.accessInitialized = true if authorizationStatus { if #available(iOSApplicationExtension 9.0, *) { - strongSelf.dataContext = DeviceContactDataModernContext(queue: strongSelf.queue, updated: { stableIdToBasicContactData in + let dataContext = DeviceContactDataModernContext(queue: strongSelf.queue, updated: { stableIdToBasicContactData in guard let strongSelf = self else { return } strongSelf.updateAll(stableIdToBasicContactData) }) + strongSelf.dataContext = dataContext + strongSelf.personNameDisplayOrder.set(dataContext.personNameDisplayOrder()) } else { - strongSelf.dataContext = DeviceContactDataLegacyContext(queue: strongSelf.queue, updated: { stableIdToBasicContactData in + let dataContext = DeviceContactDataLegacyContext(queue: strongSelf.queue, updated: { stableIdToBasicContactData in guard let strongSelf = self else { return } strongSelf.updateAll(stableIdToBasicContactData) }) + strongSelf.dataContext = dataContext + strongSelf.personNameDisplayOrder.set(dataContext.personNameDisplayOrder()) } - - /*strongSelf.dataContext = DeviceContactDataLegacyContext(queue: strongSelf.queue, updated: { stableIdToBasicContactData in - guard let strongSelf = self else { - return - } - strongSelf.updateAll(stableIdToBasicContactData) - })*/ } else { strongSelf.updateAll([:]) } @@ -626,6 +642,18 @@ public final class DeviceContactDataManager { }) } + public func personNameDisplayOrder() -> Signal { + return Signal { subscriber in + let disposable = MetaDisposable() + self.impl.with({ impl in + disposable.set(impl.personNameDisplayOrder.get().start(next: { value in + subscriber.putNext(value) + })) + }) + return disposable + } + } + public func basicData() -> Signal<[DeviceContactStableId: DeviceContactBasicData], NoError> { return Signal { subscriber in let disposable = MetaDisposable() diff --git a/TelegramUI/EditAccessoryPanelNode.swift b/TelegramUI/EditAccessoryPanelNode.swift index 7984558673..04c91d8202 100644 --- a/TelegramUI/EditAccessoryPanelNode.swift +++ b/TelegramUI/EditAccessoryPanelNode.swift @@ -51,12 +51,14 @@ final class EditAccessoryPanelNode: AccessoryPanelNode { private let account: Account var theme: PresentationTheme var strings: PresentationStrings + var nameDisplayOrder: PresentationPersonNameOrder - init(account: Account, messageId: MessageId, theme: PresentationTheme, strings: PresentationStrings) { + init(account: Account, messageId: MessageId, theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder) { self.account = account self.messageId = messageId self.theme = theme self.strings = strings + self.nameDisplayOrder = nameDisplayOrder self.closeButton = ASButtonNode() self.closeButton.setImage(PresentationResourcesChat.chatInputPanelCloseIconImage(theme), for: []) @@ -127,7 +129,7 @@ final class EditAccessoryPanelNode: AccessoryPanelNode { if let currentEditMediaReference = self.currentEditMediaReference { effectiveMessage = effectiveMessage.withUpdatedMedia([currentEditMediaReference.media]) } - (text, _) = descriptionStringForMessage(effectiveMessage, strings: self.strings, accountPeerId: self.account.peerId) + (text, _) = descriptionStringForMessage(effectiveMessage, strings: self.strings, nameDisplayOrder: self.nameDisplayOrder, accountPeerId: self.account.peerId) } var updatedMediaReference: AnyMediaReference? @@ -196,7 +198,7 @@ final class EditAccessoryPanelNode: AccessoryPanelNode { if let currentEditMediaReference = self.currentEditMediaReference { effectiveMessage = effectiveMessage.withUpdatedMedia([currentEditMediaReference.media]) } - switch messageContentKind(effectiveMessage, strings: strings, accountPeerId: self.account.peerId) { + switch messageContentKind(effectiveMessage, strings: strings, nameDisplayOrder: nameDisplayOrder, accountPeerId: self.account.peerId) { case .text: isMedia = false default: diff --git a/TelegramUI/FFMpegAudioFrameDecoder.swift b/TelegramUI/FFMpegAudioFrameDecoder.swift index f839b537f7..52a5687f9e 100644 --- a/TelegramUI/FFMpegAudioFrameDecoder.swift +++ b/TelegramUI/FFMpegAudioFrameDecoder.swift @@ -1,33 +1,25 @@ import Foundation -import TelegramUIPrivateModule import CoreMedia +import FFMpeg final class FFMpegAudioFrameDecoder: MediaTrackFrameDecoder { - private let codecContext: UnsafeMutablePointer - private let swrContext: FFMpegSwResample + private let codecContext: FFMpegAVCodecContext + private let swrContext: FFMpegSWResample - private let audioFrame: UnsafeMutablePointer + private let audioFrame: FFMpegAVFrame private var resetDecoderOnNextFrame = true - init(codecContext: UnsafeMutablePointer) { + init(codecContext: FFMpegAVCodecContext) { self.codecContext = codecContext - self.audioFrame = av_frame_alloc() + self.audioFrame = FFMpegAVFrame() - self.swrContext = FFMpegSwResample(sourceChannelCount: Int(codecContext.pointee.channels), sourceSampleRate: Int(codecContext.pointee.sample_rate), sourceSampleFormat: codecContext.pointee.sample_fmt, destinationChannelCount: 2, destinationSampleRate: 44100, destinationSampleFormat: AV_SAMPLE_FMT_S16) - } - - deinit { - av_frame_unref(self.audioFrame) - - var codecContextRef: UnsafeMutablePointer? = codecContext - avcodec_free_context(&codecContextRef) + self.swrContext = FFMpegSWResample(sourceChannelCount: Int(codecContext.channels()), sourceSampleRate: Int(codecContext.sampleRate()), sourceSampleFormat: codecContext.sampleFormat(), destinationChannelCount: 2, destinationSampleRate: 44100, destinationSampleFormat: FFMPEG_AV_SAMPLE_FMT_S16) } func decode(frame: MediaTrackDecodableFrame) -> MediaTrackFrame? { - var status = frame.packet.sendToDecoder(self.codecContext) + let status = frame.packet.send(toDecoder: self.codecContext) if status == 0 { - status = avcodec_receive_frame(self.codecContext, self.audioFrame) - if status == 0 { + if self.codecContext.receive(into: self.audioFrame) { return convertAudioFrame(self.audioFrame, pts: frame.pts, duration: frame.duration) } } @@ -39,7 +31,7 @@ final class FFMpegAudioFrameDecoder: MediaTrackFrameDecoder { return nil } - private func convertAudioFrame(_ frame: UnsafeMutablePointer, pts: CMTime, duration: CMTime) -> MediaTrackFrame? { + private func convertAudioFrame(_ frame: FFMpegAVFrame, pts: CMTime, duration: CMTime) -> MediaTrackFrame? { guard let data = self.swrContext.resample(frame) else { return nil } @@ -67,7 +59,7 @@ final class FFMpegAudioFrameDecoder: MediaTrackFrameDecoder { } func reset() { - avcodec_flush_buffers(self.codecContext) + self.codecContext.flushBuffers() self.resetDecoderOnNextFrame = true } } diff --git a/TelegramUI/FFMpegMediaFrameSource.swift b/TelegramUI/FFMpegMediaFrameSource.swift index 345b75022f..365a44d3cd 100644 --- a/TelegramUI/FFMpegMediaFrameSource.swift +++ b/TelegramUI/FFMpegMediaFrameSource.swift @@ -69,6 +69,7 @@ final class FFMpegMediaFrameSource: NSObject, MediaFrameSource { private let queue: Queue private let postbox: Postbox private let resourceReference: MediaResourceReference + private let tempFilePath: String? private let streamable: Bool private let video: Bool private let preferSoftwareDecoding: Bool @@ -91,10 +92,11 @@ final class FFMpegMediaFrameSource: NSObject, MediaFrameSource { } } - init(queue: Queue, postbox: Postbox, resourceReference: MediaResourceReference, streamable: Bool, video: Bool, preferSoftwareDecoding: Bool, fetchAutomatically: Bool) { + init(queue: Queue, postbox: Postbox, resourceReference: MediaResourceReference, tempFilePath: String?, streamable: Bool, video: Bool, preferSoftwareDecoding: Bool, fetchAutomatically: Bool) { self.queue = queue self.postbox = postbox self.resourceReference = resourceReference + self.tempFilePath = tempFilePath self.streamable = streamable self.video = video self.preferSoftwareDecoding = preferSoftwareDecoding @@ -146,6 +148,7 @@ final class FFMpegMediaFrameSource: NSObject, MediaFrameSource { let postbox = self.postbox let resourceReference = self.resourceReference + let tempFilePath = self.tempFilePath let queue = self.queue let streamable = self.streamable let video = self.video @@ -153,7 +156,7 @@ final class FFMpegMediaFrameSource: NSObject, MediaFrameSource { let fetchAutomatically = self.fetchAutomatically self.performWithContext { [weak self] context in - context.initializeState(postbox: postbox, resourceReference: resourceReference, streamable: streamable, video: video, preferSoftwareDecoding: preferSoftwareDecoding, fetchAutomatically: fetchAutomatically) + context.initializeState(postbox: postbox, resourceReference: resourceReference, tempFilePath: tempFilePath, streamable: streamable, video: video, preferSoftwareDecoding: preferSoftwareDecoding, fetchAutomatically: fetchAutomatically) let (frames, endOfStream) = context.takeFrames(until: timestamp) @@ -195,13 +198,14 @@ final class FFMpegMediaFrameSource: NSObject, MediaFrameSource { let queue = self.queue let postbox = self.postbox let resourceReference = self.resourceReference + let tempFilePath = self.tempFilePath let streamable = self.streamable let video = self.video let preferSoftwareDecoding = self.preferSoftwareDecoding let fetchAutomatically = self.fetchAutomatically self.performWithContext { [weak self] context in - context.initializeState(postbox: postbox, resourceReference: resourceReference, streamable: streamable, video: video, preferSoftwareDecoding: preferSoftwareDecoding, fetchAutomatically: fetchAutomatically) + context.initializeState(postbox: postbox, resourceReference: resourceReference, tempFilePath: tempFilePath, streamable: streamable, video: video, preferSoftwareDecoding: preferSoftwareDecoding, fetchAutomatically: fetchAutomatically) context.seek(timestamp: timestamp, completed: { streamDescriptions, timestamp in queue.async { diff --git a/TelegramUI/FFMpegMediaFrameSourceContext.swift b/TelegramUI/FFMpegMediaFrameSourceContext.swift index f08788b7ec..9b7e75d745 100644 --- a/TelegramUI/FFMpegMediaFrameSourceContext.swift +++ b/TelegramUI/FFMpegMediaFrameSourceContext.swift @@ -4,10 +4,11 @@ import Postbox import CoreMedia import TelegramUIPrivateModule import TelegramCore +import FFMpeg private struct StreamContext { let index: Int - let codecContext: UnsafeMutablePointer? + let codecContext: FFMpegAVCodecContext? let fps: CMTime let timebase: CMTime let duration: CMTime @@ -30,29 +31,18 @@ struct FFMpegMediaFrameSourceDescriptionSet { } private final class InitializedState { - fileprivate let avIoContext: UnsafeMutablePointer - fileprivate let avFormatContext: UnsafeMutablePointer + fileprivate let avIoContext: FFMpegAVIOContext + fileprivate let avFormatContext: FFMpegAVFormatContext fileprivate let audioStream: StreamContext? fileprivate let videoStream: StreamContext? - init(avIoContext: UnsafeMutablePointer, avFormatContext: UnsafeMutablePointer, audioStream: StreamContext?, videoStream: StreamContext?) { + init(avIoContext: FFMpegAVIOContext, avFormatContext: FFMpegAVFormatContext, audioStream: StreamContext?, videoStream: StreamContext?) { self.avIoContext = avIoContext self.avFormatContext = avFormatContext self.audioStream = audioStream self.videoStream = videoStream } - - deinit { - let avIoContext = Optional(self.avIoContext) - if self.avIoContext.pointee.buffer != nil { - av_free(self.avIoContext.pointee.buffer) - } - av_free(avIoContext) - - var avFormatContext = Optional(self.avFormatContext) - avformat_close_input(&avFormatContext) - } } struct FFMpegMediaFrameSourceStreamContextInfo { @@ -81,44 +71,66 @@ private func readPacketCallback(userData: UnsafeMutableRawPointer?, buffer: Unsa let readCount = min(resourceSize - context.readingOffset, Int(bufferSize)) let requestRange: Range = context.readingOffset ..< (context.readingOffset + readCount) data = postbox.mediaBox.resourceData(resourceReference.resource, size: resourceSize, in: requestRange, mode: .complete) - let semaphore = DispatchSemaphore(value: 0) if readCount == 0 { fetchedData = Data() } else { - let disposable = data.start(next: { data in - if data.count == readCount { - fetchedData = data + if let tempFilePath = context.tempFilePath, let fileData = (try? Data(contentsOf: URL(fileURLWithPath: tempFilePath), options: .mappedRead))?.subdata(in: requestRange) { + fetchedData = fileData + } else { + let semaphore = DispatchSemaphore(value: 0) + let disposable = data.start(next: { data in + if data.count == readCount { + fetchedData = data + semaphore.signal() + } + }) + semaphore.wait() + disposable.dispose() + } + } + } else { + if let tempFilePath = context.tempFilePath, let fileSize = fileSize(tempFilePath) { + let fd = open(tempFilePath, O_RDONLY, S_IRUSR) + if fd >= 0 { + let readingOffset = context.readingOffset + let readCount = max(0, min(fileSize - readingOffset, Int(bufferSize))) + let range = readingOffset ..< (readingOffset + readCount) + + lseek(fd, off_t(range.lowerBound), SEEK_SET) + var data = Data(count: readCount) + data.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer) -> Void in + let readBytes = read(fd, bytes, readCount) + assert(readBytes <= readCount) + } + fetchedData = data + close(fd) + } + } else { + let data = postbox.mediaBox.resourceData(resourceReference.resource, pathExtension: nil, option: .complete(waitUntilFetchStatus: false)) + let semaphore = DispatchSemaphore(value: 0) + let readingOffset = context.readingOffset + let disposable = data.start(next: { next in + if next.complete { + let readCount = max(0, min(next.size - readingOffset, Int(bufferSize))) + let range = readingOffset ..< (readingOffset + readCount) + + let fd = open(next.path, O_RDONLY, S_IRUSR) + if fd >= 0 { + lseek(fd, off_t(range.lowerBound), SEEK_SET) + var data = Data(count: readCount) + data.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer) -> Void in + let readBytes = read(fd, bytes, readCount) + assert(readBytes <= readCount) + } + fetchedData = data + close(fd) + } semaphore.signal() } }) semaphore.wait() disposable.dispose() } - } else { - let data = postbox.mediaBox.resourceData(resourceReference.resource, pathExtension: nil, option: .complete(waitUntilFetchStatus: false)) - let semaphore = DispatchSemaphore(value: 0) - let readingOffset = context.readingOffset - let disposable = data.start(next: { next in - if next.complete { - let readCount = max(0, min(next.size - readingOffset, Int(bufferSize))) - let range = readingOffset ..< (readingOffset + readCount) - - let fd = open(next.path, O_RDONLY, S_IRUSR) - if fd >= 0 { - lseek(fd, off_t(range.lowerBound), SEEK_SET) - var data = Data(count: readCount) - data.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer) -> Void in - let readBytes = read(fd, bytes, readCount) - assert(readBytes <= readCount) - } - fetchedData = data - close(fd) - } - semaphore.signal() - } - }) - semaphore.wait() - disposable.dispose() } if let fetchedData = fetchedData { fetchedData.withUnsafeBytes { (bytes: UnsafePointer) -> Void in @@ -144,24 +156,28 @@ private func seekCallback(userData: UnsafeMutableRawPointer?, offset: Int64, whe resourceSize = size } else { if !streamable { - var resultSize: Int = Int(Int32.max - 1) - let data = postbox.mediaBox.resourceData(resourceReference.resource, pathExtension: nil, option: .complete(waitUntilFetchStatus: false)) - let semaphore = DispatchSemaphore(value: 0) - let disposable = data.start(next: { next in - if next.complete { - resultSize = Int(next.size) - semaphore.signal() - } - }) - semaphore.wait() - disposable.dispose() - resourceSize = resultSize + if let tempFilePath = context.tempFilePath, let fileSize = fileSize(tempFilePath) { + resourceSize = fileSize + } else { + var resultSize: Int = Int(Int32.max - 1) + let data = postbox.mediaBox.resourceData(resourceReference.resource, pathExtension: nil, option: .complete(waitUntilFetchStatus: false)) + let semaphore = DispatchSemaphore(value: 0) + let disposable = data.start(next: { next in + if next.complete { + resultSize = Int(next.size) + semaphore.signal() + } + }) + semaphore.wait() + disposable.dispose() + resourceSize = resultSize + } } else { resourceSize = Int(Int32.max - 1) } } - if (whence & AVSEEK_SIZE) != 0 { + if (whence & FFMPEG_AVSEEK_SIZE) != 0 { result = Int64(resourceSize == Int(Int32.max - 1) ? -1 : resourceSize) } else { context.readingOffset = Int(min(Int64(resourceSize), offset)) @@ -173,11 +189,15 @@ private func seekCallback(userData: UnsafeMutableRawPointer?, offset: Int64, whe context.fetchedDataDisposable.set(nil) } else { if streamable { - let fetchRange: Range = context.readingOffset ..< Int(Int32.max) - context.fetchedDataDisposable.set(fetchedMediaResource(postbox: postbox, reference: resourceReference, range: (fetchRange, .elevated), statsCategory: statsCategory, preferBackgroundReferenceRevalidation: streamable).start()) + if context.tempFilePath == nil { + let fetchRange: Range = context.readingOffset ..< Int(Int32.max) + context.fetchedDataDisposable.set(fetchedMediaResource(postbox: postbox, reference: resourceReference, range: (fetchRange, .elevated), statsCategory: statsCategory, preferBackgroundReferenceRevalidation: streamable).start()) + } } else if !context.requestedCompleteFetch && context.fetchAutomatically { context.requestedCompleteFetch = true - context.fetchedDataDisposable.set(fetchedMediaResource(postbox: postbox, reference: resourceReference, statsCategory: statsCategory, preferBackgroundReferenceRevalidation: streamable).start()) + if context.tempFilePath == nil { + context.fetchedDataDisposable.set(fetchedMediaResource(postbox: postbox, reference: resourceReference, statsCategory: statsCategory, preferBackgroundReferenceRevalidation: streamable).start()) + } } } } @@ -193,6 +213,7 @@ final class FFMpegMediaFrameSourceContext: NSObject { fileprivate var postbox: Postbox? fileprivate var resourceReference: MediaResourceReference? + fileprivate var tempFilePath: String? fileprivate var streamable: Bool? fileprivate var statsCategory: MediaResourceStatsCategory? @@ -223,7 +244,7 @@ final class FFMpegMediaFrameSourceContext: NSObject { self.fetchedFullDataDisposable.dispose() } - func initializeState(postbox: Postbox, resourceReference: MediaResourceReference, streamable: Bool, video: Bool, preferSoftwareDecoding: Bool, fetchAutomatically: Bool) { + func initializeState(postbox: Postbox, resourceReference: MediaResourceReference, tempFilePath: String?, streamable: Bool, video: Bool, preferSoftwareDecoding: Bool, fetchAutomatically: Bool) { if self.readingError || self.initializedState != nil { return } @@ -232,173 +253,119 @@ final class FFMpegMediaFrameSourceContext: NSObject { self.postbox = postbox self.resourceReference = resourceReference + self.tempFilePath = tempFilePath self.streamable = streamable self.statsCategory = video ? .video : .audio self.preferSoftwareDecoding = preferSoftwareDecoding self.fetchAutomatically = fetchAutomatically + var preferSoftwareAudioDecoding = false + if case let .media(media, _) = resourceReference, let file = media.media as? TelegramMediaFile { + if file.isInstantVideo { + preferSoftwareAudioDecoding = true + } + } + if streamable { - self.fetchedDataDisposable.set(fetchedMediaResource(postbox: postbox, reference: resourceReference, range: (0 ..< Int(Int32.max), .elevated), statsCategory: self.statsCategory ?? .generic, preferBackgroundReferenceRevalidation: streamable).start()) + if self.tempFilePath == nil { + self.fetchedDataDisposable.set(fetchedMediaResource(postbox: postbox, reference: resourceReference, range: (0 ..< Int(Int32.max), .elevated), statsCategory: self.statsCategory ?? .generic, preferBackgroundReferenceRevalidation: streamable).start()) + } } else if !self.requestedCompleteFetch && self.fetchAutomatically { self.requestedCompleteFetch = true - self.fetchedFullDataDisposable.set(fetchedMediaResource(postbox: postbox, reference: resourceReference, statsCategory: self.statsCategory ?? .generic, preferBackgroundReferenceRevalidation: streamable).start()) + if self.tempFilePath == nil { + self.fetchedFullDataDisposable.set(fetchedMediaResource(postbox: postbox, reference: resourceReference, statsCategory: self.statsCategory ?? .generic, preferBackgroundReferenceRevalidation: streamable).start()) + } } - var avFormatContextRef = avformat_alloc_context() - guard let avFormatContext = avFormatContextRef else { + let avFormatContext = FFMpegAVFormatContext() + + guard let avIoContext = FFMpegAVIOContext(bufferSize: Int32(self.ioBufferSize), opaqueContext: Unmanaged.passUnretained(self).toOpaque(), readPacket: readPacketCallback, seek: seekCallback) else { self.readingError = true return } - let avIoBuffer = av_malloc(self.ioBufferSize)! - let avIoContextRef = avio_alloc_context(avIoBuffer.assumingMemoryBound(to: UInt8.self), Int32(self.ioBufferSize), 0, Unmanaged.passUnretained(self).toOpaque(), readPacketCallback, nil, seekCallback) + avFormatContext.setIO(avIoContext) - guard let avIoContext = avIoContextRef else { + if !avFormatContext.openInput() { self.readingError = true return } - avFormatContext.pointee.pb = avIoContext - - //avFormatContext.pointee.flags |= AVFMT_FLAG_FAST_SEEK - //print(String.init(cString: avutil_configuration())) - - var options: OpaquePointer? - av_dict_set(&options, "usetoc", "1", 0) - - guard avformat_open_input(&avFormatContextRef, nil, nil, &options) >= 0 else { - self.readingError = true - return - } - - av_dict_free(&options) - - guard avformat_find_stream_info(avFormatContext, nil) >= 0 else { - self.readingError = true + if !avFormatContext.findStreamInfo() { + self.readingError = true; return } var videoStream: StreamContext? var audioStream: StreamContext? - for streamIndex in FFMpegMediaFrameSourceContextHelpers.streamIndices(formatContext: avFormatContext, codecType: AVMEDIA_TYPE_VIDEO) { - if (avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!.pointee.disposition & Int32(AV_DISPOSITION_ATTACHED_PIC)) == 0 { - - let codecPar = avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!.pointee.codecpar! - - if self.preferSoftwareDecoding { - if let codec = avcodec_find_decoder(codecPar.pointee.codec_id) { - if let codecContext = avcodec_alloc_context3(codec) { - if avcodec_parameters_to_context(codecContext, avFormatContext.pointee.streams[streamIndex]!.pointee.codecpar) >= 0 { - if avcodec_open2(codecContext, codec, nil) >= 0 { - let (fps, timebase) = FFMpegMediaFrameSourceContextHelpers.streamFpsAndTimeBase(stream: avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!, defaultTimeBase: CMTimeMake(1, 24)) - - let duration = CMTimeMake(avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!.pointee.duration, timebase.timescale) - - var rotationAngle: Double = 0.0 - if let rotationInfo = av_dict_get(avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!.pointee.metadata, "rotate", nil, 0), let value = rotationInfo.pointee.value { - if strcmp(value, "0") != 0 { - if let angle = Double(String(cString: value)) { - rotationAngle = angle * Double.pi / 180.0 - } - } - } - - let aspect = Double(codecPar.pointee.width) / Double(codecPar.pointee.height) - - videoStream = StreamContext(index: streamIndex, codecContext: codecContext, fps: fps, timebase: timebase, duration: duration, decoder: FFMpegMediaVideoFrameDecoder(codecContext: codecContext), rotationAngle: rotationAngle, aspect: aspect) - break - } else { - var codecContextRef: UnsafeMutablePointer? = codecContext - avcodec_free_context(&codecContextRef) - } - } else { - var codecContextRef: UnsafeMutablePointer? = codecContext - avcodec_free_context(&codecContextRef) - } + for streamIndexNumber in avFormatContext.streamIndices(for: FFMpegAVFormatStreamTypeVideo) { + let streamIndex = streamIndexNumber.int32Value + if avFormatContext.isAttachedPic(atStreamIndex: streamIndex) { + continue + } + + let codecId = avFormatContext.codecId(atStreamIndex: streamIndex) + + let fpsAndTimebase = avFormatContext.fpsAndTimebase(forStreamIndex: streamIndex, defaultTimeBase: CMTimeMake(1, 40000)) + let (fps, timebase) = (fpsAndTimebase.fps, fpsAndTimebase.timebase) + + let duration = CMTimeMake(avFormatContext.duration(atStreamIndex: streamIndex), timebase.timescale) + + let metrics = avFormatContext.metricsForStream(at: streamIndex) + + let rotationAngle: Double = metrics.rotationAngle + let aspect = Double(metrics.width) / Double(metrics.height) + + if self.preferSoftwareDecoding { + if let codec = FFMpegAVCodec.find(forId: codecId) { + let codecContext = FFMpegAVCodecContext(codec: codec) + if avFormatContext.codecParams(atStreamIndex: streamIndex, to: codecContext) { + if codecContext.open() { + videoStream = StreamContext(index: Int(streamIndex), codecContext: codecContext, fps: fps, timebase: timebase, duration: duration, decoder: FFMpegMediaVideoFrameDecoder(codecContext: codecContext), rotationAngle: rotationAngle, aspect: aspect) + break } } - } else if codecPar.pointee.codec_id == AV_CODEC_ID_MPEG4 { - if let videoFormat = FFMpegMediaFrameSourceContextHelpers.createFormatDescriptionFromMpeg4CodecData(UInt32(kCMVideoCodecType_MPEG4Video), codecPar.pointee.width, codecPar.pointee.height, codecPar.pointee.extradata, codecPar.pointee.extradata_size) { - let (fps, timebase) = FFMpegMediaFrameSourceContextHelpers.streamFpsAndTimeBase(stream: avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!, defaultTimeBase: CMTimeMake(1, 1000)) - - let duration = CMTimeMake(avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!.pointee.duration, timebase.timescale) - - var rotationAngle: Double = 0.0 - if let rotationInfo = av_dict_get(avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!.pointee.metadata, "rotate", nil, 0), let value = rotationInfo.pointee.value { - if strcmp(value, "0") != 0 { - if let angle = Double(String(cString: value)) { - rotationAngle = angle * Double.pi / 180.0 - } - } - } - - let aspect = Double(codecPar.pointee.width) / Double(codecPar.pointee.height) - - videoStream = StreamContext(index: streamIndex, codecContext: nil, fps: fps, timebase: timebase, duration: duration, decoder: FFMpegMediaPassthroughVideoFrameDecoder(videoFormat: videoFormat, rotationAngle: rotationAngle), rotationAngle: rotationAngle, aspect: aspect) - break - } - } else if codecPar.pointee.codec_id == AV_CODEC_ID_H264 { - if let videoFormat = FFMpegMediaFrameSourceContextHelpers.createFormatDescriptionFromAVCCodecData(UInt32(kCMVideoCodecType_H264), codecPar.pointee.width, codecPar.pointee.height, codecPar.pointee.extradata, codecPar.pointee.extradata_size) { - let (fps, timebase) = FFMpegMediaFrameSourceContextHelpers.streamFpsAndTimeBase(stream: avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!, defaultTimeBase: CMTimeMake(1, 1000)) - - let duration = CMTimeMake(avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!.pointee.duration, timebase.timescale) - - var rotationAngle: Double = 0.0 - if let rotationInfo = av_dict_get(avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!.pointee.metadata, "rotate", nil, 0), let value = rotationInfo.pointee.value { - if strcmp(value, "0") != 0 { - if let angle = Double(String(cString: value)) { - rotationAngle = angle * Double.pi / 180.0 - } - } - } - - let aspect = Double(codecPar.pointee.width) / Double(codecPar.pointee.height) - - videoStream = StreamContext(index: streamIndex, codecContext: nil, fps: fps, timebase: timebase, duration: duration, decoder: FFMpegMediaPassthroughVideoFrameDecoder(videoFormat: videoFormat, rotationAngle: rotationAngle), rotationAngle: rotationAngle, aspect: aspect) - break - } - } else if codecPar.pointee.codec_id == AV_CODEC_ID_HEVC { - if let videoFormat = FFMpegMediaFrameSourceContextHelpers.createFormatDescriptionFromHEVCCodecData(UInt32(kCMVideoCodecType_HEVC), codecPar.pointee.width, codecPar.pointee.height, codecPar.pointee.extradata, codecPar.pointee.extradata_size) { - let (fps, timebase) = FFMpegMediaFrameSourceContextHelpers.streamFpsAndTimeBase(stream: avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!, defaultTimeBase: CMTimeMake(1, 1000)) - - let duration = CMTimeMake(avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!.pointee.duration, timebase.timescale) - - var rotationAngle: Double = 0.0 - if let rotationInfo = av_dict_get(avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!.pointee.metadata, "rotate", nil, 0), let value = rotationInfo.pointee.value { - if strcmp(value, "0") != 0 { - if let angle = Double(String(cString: value)) { - rotationAngle = angle * Double.pi / 180.0 - } - } - } - - let aspect = Double(codecPar.pointee.width) / Double(codecPar.pointee.height) - - videoStream = StreamContext(index: streamIndex, codecContext: nil, fps: fps, timebase: timebase, duration: duration, decoder: FFMpegMediaPassthroughVideoFrameDecoder(videoFormat: videoFormat, rotationAngle: rotationAngle), rotationAngle: rotationAngle, aspect: aspect) - break - } + } + } else if codecId == FFMpegCodecIdMPEG4 { + if let videoFormat = FFMpegMediaFrameSourceContextHelpers.createFormatDescriptionFromMpeg4CodecData(UInt32(kCMVideoCodecType_MPEG4Video), metrics.width, metrics.height, metrics.extradata, metrics.extradataSize) { + videoStream = StreamContext(index: Int(streamIndex), codecContext: nil, fps: fps, timebase: timebase, duration: duration, decoder: FFMpegMediaPassthroughVideoFrameDecoder(videoFormat: videoFormat, rotationAngle: rotationAngle), rotationAngle: rotationAngle, aspect: aspect) + break + } + } else if codecId == FFMpegCodecIdH264 { + if let videoFormat = FFMpegMediaFrameSourceContextHelpers.createFormatDescriptionFromAVCCodecData(UInt32(kCMVideoCodecType_H264), metrics.width, metrics.height, metrics.extradata, metrics.extradataSize) { + videoStream = StreamContext(index: Int(streamIndex), codecContext: nil, fps: fps, timebase: timebase, duration: duration, decoder: FFMpegMediaPassthroughVideoFrameDecoder(videoFormat: videoFormat, rotationAngle: rotationAngle), rotationAngle: rotationAngle, aspect: aspect) + break + } + } else if codecId == FFMpegCodecIdHEVC { + if let videoFormat = FFMpegMediaFrameSourceContextHelpers.createFormatDescriptionFromHEVCCodecData(UInt32(kCMVideoCodecType_HEVC), metrics.width, metrics.height, metrics.extradata, metrics.extradataSize) { + videoStream = StreamContext(index: Int(streamIndex), codecContext: nil, fps: fps, timebase: timebase, duration: duration, decoder: FFMpegMediaPassthroughVideoFrameDecoder(videoFormat: videoFormat, rotationAngle: rotationAngle), rotationAngle: rotationAngle, aspect: aspect) + break } } } - for streamIndex in FFMpegMediaFrameSourceContextHelpers.streamIndices(formatContext: avFormatContext, codecType: AVMEDIA_TYPE_AUDIO) { - if let codec = avcodec_find_decoder(avFormatContext.pointee.streams[streamIndex]!.pointee.codecpar.pointee.codec_id) { - if let codecContext = avcodec_alloc_context3(codec) { - if avcodec_parameters_to_context(codecContext, avFormatContext.pointee.streams[streamIndex]!.pointee.codecpar) >= 0 { - if avcodec_open2(codecContext, codec, nil) >= 0 { - let (fps, timebase) = FFMpegMediaFrameSourceContextHelpers.streamFpsAndTimeBase(stream: avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!, defaultTimeBase: CMTimeMake(1, 40000)) - - let duration = CMTimeMake(avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!.pointee.duration, timebase.timescale) - - audioStream = StreamContext(index: streamIndex, codecContext: codecContext, fps: fps, timebase: timebase, duration: duration, decoder: FFMpegAudioFrameDecoder(codecContext: codecContext), rotationAngle: 0.0, aspect: 1.0) - } else { - var codecContextRef: UnsafeMutablePointer? = codecContext - avcodec_free_context(&codecContextRef) - } - } else { - var codecContextRef: UnsafeMutablePointer? = codecContext - avcodec_free_context(&codecContextRef) + for streamIndexNumber in avFormatContext.streamIndices(for: FFMpegAVFormatStreamTypeAudio) { + let streamIndex = streamIndexNumber.int32Value + let codecId = avFormatContext.codecId(atStreamIndex: streamIndex) + + var codec: FFMpegAVCodec? + + if codec == nil { + codec = FFMpegAVCodec.find(forId: codecId) + } + + if let codec = codec { + let codecContext = FFMpegAVCodecContext(codec: codec) + if avFormatContext.codecParams(atStreamIndex: streamIndex, to: codecContext) { + if codecContext.open() { + let fpsAndTimebase = avFormatContext.fpsAndTimebase(forStreamIndex: streamIndex, defaultTimeBase: CMTimeMake(1, 40000)) + let (fps, timebase) = (fpsAndTimebase.fps, fpsAndTimebase.timebase) + + let duration = CMTimeMake(avFormatContext.duration(atStreamIndex: streamIndex), timebase.timescale) + + audioStream = StreamContext(index: Int(streamIndex), codecContext: codecContext, fps: fps, timebase: timebase, duration: duration, decoder: FFMpegAudioFrameDecoder(codecContext: codecContext), rotationAngle: 0.0, aspect: 1.0) + break } } } @@ -407,7 +374,9 @@ final class FFMpegMediaFrameSourceContext: NSObject { self.initializedState = InitializedState(avIoContext: avIoContext, avFormatContext: avFormatContext, audioStream: audioStream, videoStream: videoStream) if streamable { - self.fetchedFullDataDisposable.set(fetchedMediaResource(postbox: postbox, reference: resourceReference, range: (0 ..< Int(Int32.max), .default), statsCategory: self.statsCategory ?? .generic, preferBackgroundReferenceRevalidation: streamable).start()) + if self.tempFilePath == nil { + self.fetchedFullDataDisposable.set(fetchedMediaResource(postbox: postbox, reference: resourceReference, range: (0 ..< Int(Int32.max), .default), statsCategory: self.statsCategory ?? .generic, preferBackgroundReferenceRevalidation: streamable).start()) + } self.requestedCompleteFetch = true } } @@ -426,10 +395,10 @@ final class FFMpegMediaFrameSourceContext: NSObject { } let packet = FFMpegPacket() - if av_read_frame(initializedState.avFormatContext, &packet.packet) < 0 { - return nil - } else { + if initializedState.avFormatContext.readFrame(into: packet) { return packet + } else { + return nil } } @@ -457,24 +426,22 @@ final class FFMpegMediaFrameSourceContext: NSObject { while !self.readingError && ((videoTimestamp == nil || videoTimestamp!.isLess(than: until)) || (audioTimestamp == nil || audioTimestamp!.isLess(than: until))) { if let packet = self.readPacket() { - if let videoStream = initializedState.videoStream, Int(packet.packet.stream_index) == videoStream.index { + if let videoStream = initializedState.videoStream, Int(packet.streamIndex) == videoStream.index { let frame = videoFrameFromPacket(packet, videoStream: videoStream) frames.append(frame) if videoTimestamp == nil || videoTimestamp! < CMTimeGetSeconds(frame.pts) { videoTimestamp = CMTimeGetSeconds(frame.pts) } - } else if let audioStream = initializedState.audioStream, Int(packet.packet.stream_index) == audioStream.index { - let avNoPtsRawValue: UInt64 = 0x8000000000000000 - let avNoPtsValue = Int64(bitPattern: avNoPtsRawValue) - let packetPts = packet.packet.pts == avNoPtsValue ? packet.packet.dts : packet.packet.pts + } else if let audioStream = initializedState.audioStream, Int(packet.streamIndex) == audioStream.index { + let packetPts = packet.pts let pts = CMTimeMake(packetPts, audioStream.timebase.timescale) - let dts = CMTimeMake(packet.packet.dts, audioStream.timebase.timescale) + let dts = CMTimeMake(packet.dts, audioStream.timebase.timescale) let duration: CMTime - let frameDuration = packet.packet.duration + let frameDuration = packet.duration if frameDuration != 0 { duration = CMTimeMake(frameDuration * audioStream.timebase.value, audioStream.timebase.timescale) } else { @@ -522,7 +489,7 @@ final class FFMpegMediaFrameSourceContext: NSObject { for stream in [initializedState.videoStream, initializedState.audioStream] { if let stream = stream { let pts = CMTimeMakeWithSeconds(timestamp, stream.timebase.timescale) - av_seek_frame(initializedState.avFormatContext, Int32(stream.index), pts.value, AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_FRAME) + initializedState.avFormatContext.seekFrame(forStreamIndex: Int32(stream.index), pts: pts.value) break } } @@ -543,12 +510,12 @@ final class FFMpegMediaFrameSourceContext: NSObject { if timestamp.isZero || initializedState.videoStream == nil { for _ in 0 ..< 24 { if let packet = self.readPacketInternal() { - if let videoStream = initializedState.videoStream, Int(packet.packet.stream_index) == videoStream.index { + if let videoStream = initializedState.videoStream, Int(packet.streamIndex) == videoStream.index { self.packetQueue.append(packet) let pts = CMTimeMake(packet.pts, videoStream.timebase.timescale) actualPts = pts break - } else if let audioStream = initializedState.audioStream, Int(packet.packet.stream_index) == audioStream.index { + } else if let audioStream = initializedState.audioStream, Int(packet.streamIndex) == audioStream.index { self.packetQueue.append(packet) let pts = CMTimeMake(packet.pts, audioStream.timebase.timescale) actualPts = pts @@ -564,14 +531,14 @@ final class FFMpegMediaFrameSourceContext: NSObject { var audioPackets: [FFMpegPacket] = [] while !self.readingError { if let packet = self.readPacket() { - if let videoStream = initializedState.videoStream, Int(packet.packet.stream_index) == videoStream.index { + if let videoStream = initializedState.videoStream, Int(packet.streamIndex) == videoStream.index { let frame = videoFrameFromPacket(packet, videoStream: videoStream) extraVideoFrames.append(frame) if CMTimeCompare(frame.dts, limitPts) >= 0 && CMTimeCompare(frame.pts, limitPts) >= 0 { break } - } else if let audioStream = initializedState.audioStream, Int(packet.packet.stream_index) == audioStream.index { + } else if let audioStream = initializedState.audioStream, Int(packet.streamIndex) == audioStream.index { audioPackets.append(packet) } } else { @@ -613,16 +580,14 @@ final class FFMpegMediaFrameSourceContext: NSObject { } private func videoFrameFromPacket(_ packet: FFMpegPacket, videoStream: StreamContext) -> MediaTrackDecodableFrame { - let avNoPtsRawValue: UInt64 = 0x8000000000000000 - let avNoPtsValue = Int64(bitPattern: avNoPtsRawValue) - let packetPts = packet.packet.pts == avNoPtsValue ? packet.packet.dts : packet.packet.pts + let packetPts = packet.pts let pts = CMTimeMake(packetPts, videoStream.timebase.timescale) - let dts = CMTimeMake(packet.packet.dts, videoStream.timebase.timescale) + let dts = CMTimeMake(packet.dts, videoStream.timebase.timescale) let duration: CMTime - let frameDuration = packet.packet.duration + let frameDuration = packet.duration if frameDuration != 0 { duration = CMTimeMake(frameDuration * videoStream.timebase.value, videoStream.timebase.timescale) } else { diff --git a/TelegramUI/FFMpegMediaFrameSourceContextHelpers.swift b/TelegramUI/FFMpegMediaFrameSourceContextHelpers.swift index f05866ce91..3fd1681e8c 100644 --- a/TelegramUI/FFMpegMediaFrameSourceContextHelpers.swift +++ b/TelegramUI/FFMpegMediaFrameSourceContextHelpers.swift @@ -1,15 +1,10 @@ import Foundation import CoreMedia -import TelegramUIPrivateModule +import FFMpeg final class FFMpegMediaFrameSourceContextHelpers { static let registerFFMpegGlobals: Void = { - #if DEBUG - av_log_set_level(AV_LOG_ERROR) - #else - av_log_set_level(AV_LOG_QUIET) - #endif - av_register_all() + FFMpegGlobals.initializeGlobals() return }() @@ -95,37 +90,4 @@ final class FFMpegMediaFrameSourceContextHelpers { return formatDescription } - - static func streamIndices(formatContext: UnsafeMutablePointer, codecType: AVMediaType) -> [Int] { - var indices: [Int] = [] - for i in 0 ..< Int(formatContext.pointee.nb_streams) { - if codecType == formatContext.pointee.streams.advanced(by: i).pointee!.pointee.codecpar!.pointee.codec_type { - indices.append(i) - } - } - return indices - } - - static func streamFpsAndTimeBase(stream: UnsafePointer, defaultTimeBase: CMTime) -> (fps: CMTime, timebase: CMTime) { - let timebase: CMTime - var fps: CMTime - - if stream.pointee.time_base.den != 0 && stream.pointee.time_base.num != 0 { - timebase = CMTimeMake(Int64(stream.pointee.time_base.num), stream.pointee.time_base.den) - } else if stream.pointee.codec.pointee.time_base.den != 0 && stream.pointee.codec.pointee.time_base.num != 0 { - timebase = CMTimeMake(Int64(stream.pointee.codec.pointee.time_base.num), stream.pointee.codec.pointee.time_base.den) - } else { - timebase = defaultTimeBase - } - - if stream.pointee.avg_frame_rate.den != 0 && stream.pointee.avg_frame_rate.num != 0 { - fps = CMTimeMake(Int64(stream.pointee.avg_frame_rate.num), stream.pointee.avg_frame_rate.den) - } else if stream.pointee.r_frame_rate.den != 0 && stream.pointee.r_frame_rate.num != 0 { - fps = CMTimeMake(Int64(stream.pointee.r_frame_rate.num), stream.pointee.r_frame_rate.den) - } else { - fps = CMTimeMake(1, 24) - } - - return (fps, timebase) - } } diff --git a/TelegramUI/FFMpegMediaPassthroughVideoFrameDecoder.swift b/TelegramUI/FFMpegMediaPassthroughVideoFrameDecoder.swift index 2ae5f11ede..f4291ba09f 100644 --- a/TelegramUI/FFMpegMediaPassthroughVideoFrameDecoder.swift +++ b/TelegramUI/FFMpegMediaPassthroughVideoFrameDecoder.swift @@ -13,16 +13,16 @@ final class FFMpegMediaPassthroughVideoFrameDecoder: MediaTrackFrameDecoder { func decode(frame: MediaTrackDecodableFrame) -> MediaTrackFrame? { var blockBuffer: CMBlockBuffer? - let bytes = malloc(Int(frame.packet.packet.size))! - memcpy(bytes, frame.packet.packet.data, Int(frame.packet.packet.size)) - guard CMBlockBufferCreateWithMemoryBlock(nil, bytes, Int(frame.packet.packet.size), nil, nil, 0, Int(frame.packet.packet.size), 0, &blockBuffer) == noErr else { + let bytes = malloc(Int(frame.packet.size))! + memcpy(bytes, frame.packet.data, Int(frame.packet.size)) + guard CMBlockBufferCreateWithMemoryBlock(nil, bytes, Int(frame.packet.size), nil, nil, 0, Int(frame.packet.size), 0, &blockBuffer) == noErr else { free(bytes) return nil } var timingInfo = CMSampleTimingInfo(duration: frame.duration, presentationTimeStamp: frame.pts, decodeTimeStamp: frame.dts) var sampleBuffer: CMSampleBuffer? - var sampleSize = Int(frame.packet.packet.size) + var sampleSize = Int(frame.packet.size) guard CMSampleBufferCreate(nil, blockBuffer, true, nil, nil, self.videoFormat, 1, 1, &timingInfo, 1, &sampleSize, &sampleBuffer) == noErr else { return nil } diff --git a/TelegramUI/FFMpegMediaVideoFrameDecoder.swift b/TelegramUI/FFMpegMediaVideoFrameDecoder.swift index 2d965ff3fa..b1b49df020 100644 --- a/TelegramUI/FFMpegMediaVideoFrameDecoder.swift +++ b/TelegramUI/FFMpegMediaVideoFrameDecoder.swift @@ -1,22 +1,22 @@ -import TelegramUIPrivateModule import CoreMedia import Accelerate +import FFMpeg private let bufferCount = 32 final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder { - private let codecContext: UnsafeMutablePointer + private let codecContext: FFMpegAVCodecContext - private let videoFrame: UnsafeMutablePointer + private let videoFrame: FFMpegAVFrame private var resetDecoderOnNextFrame = true private var pixelBufferPool: CVPixelBufferPool? private var delayedFrames: [MediaTrackFrame] = [] - init(codecContext: UnsafeMutablePointer) { + init(codecContext: FFMpegAVCodecContext) { self.codecContext = codecContext - self.videoFrame = av_frame_alloc() + self.videoFrame = FFMpegAVFrame() /*var sourcePixelBufferOptions: [String: Any] = [:] sourcePixelBufferOptions[kCVPixelBufferPixelFormatTypeKey as String] = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange as NSNumber @@ -40,13 +40,6 @@ final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder { self.pixelBufferPool = pixelBufferPool*/ } - deinit { - av_frame_unref(self.videoFrame) - - var codecContextRef: UnsafeMutablePointer? = codecContext - avcodec_free_context(&codecContextRef) - } - func decodeInternal(frame: MediaTrackDecodableFrame) { } @@ -56,11 +49,10 @@ final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder { } func decode(frame: MediaTrackDecodableFrame, ptsOffset: CMTime?) -> MediaTrackFrame? { - var status = frame.packet.sendToDecoder(self.codecContext) + var status = frame.packet.send(toDecoder: self.codecContext) if status == 0 { - status = avcodec_receive_frame(self.codecContext, self.videoFrame) - if status == 0 { - var pts = CMTimeMake(self.videoFrame.pointee.pts, frame.pts.timescale) + if self.codecContext.receive(into: self.videoFrame) { + var pts = CMTimeMake(self.videoFrame.pts, frame.pts.timescale) if let ptsOffset = ptsOffset { pts = CMTimeAdd(pts, ptsOffset) } @@ -87,11 +79,11 @@ final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder { } } - private func convertVideoFrame(_ frame: UnsafeMutablePointer, pts: CMTime, dts: CMTime, duration: CMTime) -> MediaTrackFrame? { - if frame.pointee.data.0 == nil { + private func convertVideoFrame(_ frame: FFMpegAVFrame, pts: CMTime, dts: CMTime, duration: CMTime) -> MediaTrackFrame? { + if frame.data[0] == nil { return nil } - if frame.pointee.linesize.1 != frame.pointee.linesize.2 { + if frame.lineSize[1] != frame.lineSize[2] { return nil } @@ -107,15 +99,15 @@ final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder { let ioSurfaceProperties = NSMutableDictionary() ioSurfaceProperties["IOSurfaceIsGlobal"] = true as NSNumber - var options: [String: Any] = [kCVPixelBufferBytesPerRowAlignmentKey as String: frame.pointee.linesize.0 as NSNumber] + var options: [String: Any] = [kCVPixelBufferBytesPerRowAlignmentKey as String: frame.lineSize[0] as NSNumber] /*if #available(iOSApplicationExtension 9.0, *) { options[kCVPixelBufferOpenGLESTextureCacheCompatibilityKey as String] = true as NSNumber }*/ options[kCVPixelBufferIOSurfacePropertiesKey as String] = ioSurfaceProperties CVPixelBufferCreate(kCFAllocatorDefault, - Int(frame.pointee.width), - Int(frame.pointee.height), + Int(frame.width), + Int(frame.height), kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, options as CFDictionary, &pixelBufferRef) @@ -125,7 +117,7 @@ final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder { return nil } - let srcPlaneSize = Int(frame.pointee.linesize.1) * Int(frame.pointee.height / 2) + let srcPlaneSize = Int(frame.lineSize[1]) * Int(frame.height / 2) let dstPlaneSize = srcPlaneSize * 2 let dstPlane = malloc(dstPlaneSize)!.assumingMemoryBound(to: UInt8.self) @@ -134,8 +126,8 @@ final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder { } for i in 0 ..< srcPlaneSize { - dstPlane[2 * i] = frame.pointee.data.1![i] - dstPlane[2 * i + 1] = frame.pointee.data.2![i] + dstPlane[2 * i] = frame.data[1]![i] + dstPlane[2 * i + 1] = frame.data[2]![i] } let status = CVPixelBufferLockBaseAddress(pixelBuffer, []) @@ -148,13 +140,13 @@ final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder { let bytesPerRowUV = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1) var base = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0)! - if bytePerRowY == frame.pointee.linesize.0 { - memcpy(base, frame.pointee.data.0!, bytePerRowY * Int(frame.pointee.height)) + if bytePerRowY == frame.lineSize[0] { + memcpy(base, frame.data[0]!, bytePerRowY * Int(frame.height)) } else { var dest = base - var src = frame.pointee.data.0! - let linesize = Int(frame.pointee.linesize.0) - for _ in 0 ..< Int(frame.pointee.height) { + var src = frame.data[0]! + let linesize = Int(frame.lineSize[0]) + for _ in 0 ..< Int(frame.height) { memcpy(dest, src, linesize) dest = dest.advanced(by: bytePerRowY) src = src.advanced(by: linesize) @@ -162,13 +154,13 @@ final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder { } base = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1)! - if bytesPerRowUV == frame.pointee.linesize.1 * 2 { - memcpy(base, dstPlane, bytesPerRowUV * Int(frame.pointee.height) / 2) + if bytesPerRowUV == frame.lineSize[1] * 2 { + memcpy(base, dstPlane, bytesPerRowUV * Int(frame.height) / 2) } else { var dest = base var src = dstPlane - let linesize = Int(frame.pointee.linesize.1) * 2 - for _ in 0 ..< Int(frame.pointee.height) / 2 { + let linesize = Int(frame.lineSize[1]) * 2 + for _ in 0 ..< Int(frame.height) / 2 { memcpy(dest, src, linesize) dest = dest.advanced(by: bytesPerRowUV) src = src.advanced(by: linesize) @@ -229,7 +221,7 @@ final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder { } func reset() { - avcodec_flush_buffers(self.codecContext) + self.codecContext.flushBuffers() self.resetDecoderOnNextFrame = true } } diff --git a/TelegramUI/FFMpegPacket.swift b/TelegramUI/FFMpegPacket.swift deleted file mode 100644 index 56e4c40baa..0000000000 --- a/TelegramUI/FFMpegPacket.swift +++ /dev/null @@ -1,26 +0,0 @@ -import Foundation -import TelegramUIPrivateModule - -final class FFMpegPacket { - var packet = AVPacket() - - init() { - av_init_packet(&self.packet) - } - - deinit { - av_packet_unref(&self.packet) - } - - var pts: Int64 { - let avNoPtsRawValue: UInt64 = 0x8000000000000000 - let avNoPtsValue = Int64(bitPattern: avNoPtsRawValue) - let packetPts = self.packet.pts == avNoPtsValue ? self.packet.dts : self.packet.pts - - return packetPts - } - - func sendToDecoder(_ codecContext: UnsafeMutablePointer) -> Int32 { - return avcodec_send_packet(codecContext, &self.packet) - } -} diff --git a/TelegramUI/FeedGroupingControllerNode.swift b/TelegramUI/FeedGroupingControllerNode.swift index a2db9c03f6..36eeb38ece 100644 --- a/TelegramUI/FeedGroupingControllerNode.swift +++ b/TelegramUI/FeedGroupingControllerNode.swift @@ -80,7 +80,7 @@ private enum FeedGroupingSection: ItemListSectionId { private enum FeedGroupingEntry: ItemListNodeEntry { case groupHeader(PresentationTheme, String) - case peer(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Int, Peer, Bool) + case peer(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Int, Peer, Bool) case ungroup(PresentationTheme, String) var section: ItemListSectionId { @@ -96,7 +96,7 @@ private enum FeedGroupingEntry: ItemListNodeEntry { switch self { case .groupHeader: return .index(0) - case let .peer(_, _, _, _, peer, _): + case let .peer(_, _, _, _, _, peer, _): return .peer(peer.id) case .ungroup: return .index(1) @@ -111,8 +111,8 @@ private enum FeedGroupingEntry: ItemListNodeEntry { } else { return false } - case let .peer(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsIndex, lhsPeer, lhsValue): - if case let .peer(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsIndex, rhsPeer, rhsValue) = rhs { + case let .peer(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameOrder, lhsIndex, lhsPeer, lhsValue): + if case let .peer(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameOrder, rhsIndex, rhsPeer, rhsValue) = rhs { if lhsTheme !== rhsTheme { return false } @@ -122,6 +122,9 @@ private enum FeedGroupingEntry: ItemListNodeEntry { if lhsDateTimeFormat != rhsDateTimeFormat { return false } + if lhsNameOrder != rhsNameOrder { + return false + } if lhsIndex != rhsIndex { return false } @@ -148,11 +151,11 @@ private enum FeedGroupingEntry: ItemListNodeEntry { switch lhs { case .groupHeader: return true - case let .peer(_, _, _, index, _, _): + case let .peer(_, _, _, _, index, _, _): switch rhs { case .groupHeader: return false - case let .peer(_, _, _, rhsIndex, _, _): + case let .peer(_, _, _, _, rhsIndex, _, _): return index < rhsIndex default: return true @@ -166,8 +169,8 @@ private enum FeedGroupingEntry: ItemListNodeEntry { switch self { case let .groupHeader(theme, text): return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) - case let .peer(theme, strings, dateTimeFormat, _, peer, value): - return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, account: arguments.account, peer: peer, presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: ItemListPeerItemSwitch(value: value, style: .standard), enabled: true, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, toggleUpdated: { value in + case let .peer(theme, strings, dateTimeFormat, nameDisplayOrder, _, peer, value): + return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, account: arguments.account, peer: peer, presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: ItemListPeerItemSwitch(value: value, style: .standard), enabled: true, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, toggleUpdated: { value in arguments.togglePeer(peer, value) }) case let .ungroup(theme, text): @@ -200,17 +203,19 @@ private final class FeedGroupingState { let theme: PresentationTheme let strings: PresentationStrings let dateTimeFormat: PresentationDateTimeFormat + let nameDisplayOrder: PresentationPersonNameOrder let peers: [FeedGroupingPeerState] - init(theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, peers: [FeedGroupingPeerState]) { + init(theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, peers: [FeedGroupingPeerState]) { self.theme = theme self.strings = strings self.dateTimeFormat = dateTimeFormat + self.nameDisplayOrder = nameDisplayOrder self.peers = peers } func withUpdatedPeers(_ peers: [FeedGroupingPeerState]) -> FeedGroupingState { - return FeedGroupingState(theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, peers: peers) + return FeedGroupingState(theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, peers: peers) } } @@ -220,7 +225,7 @@ private func entriesStateFromState(_ state: FeedGroupingState) -> FeedGroupingEn entries.append(.groupHeader(state.theme, "GROUP CHANNELS")) var index = 0 for peer in state.peers { - entries.append(.peer(state.theme, state.strings, state.dateTimeFormat, index, peer.peer, peer.included)) + entries.append(.peer(state.theme, state.strings, state.dateTimeFormat, state.nameDisplayOrder, index, peer.peer, peer.included)) index += 1 } entries.append(.ungroup(state.theme, "Ungroup All Channels")) @@ -284,7 +289,7 @@ final class FeedGroupingControllerNode: ViewControllerTracingNode { self.blockingOverlay = ASDisplayNode() self.blockingOverlay.backgroundColor = self.presentationData.theme.list.blocksBackgroundColor - self._stateValue = FeedGroupingState(theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, peers: []) + self._stateValue = FeedGroupingState(theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, peers: []) self.statePromise.set(.single(self._stateValue)) super.init() diff --git a/TelegramUI/FetchVideoThumbnail.swift b/TelegramUI/FetchVideoThumbnail.swift index c15a800039..b206c9be41 100644 --- a/TelegramUI/FetchVideoThumbnail.swift +++ b/TelegramUI/FetchVideoThumbnail.swift @@ -7,7 +7,7 @@ import TelegramUIPrivateModule import Display import UIKit import VideoToolbox - +/* private func readPacketCallback(userData: UnsafeMutableRawPointer?, buffer: UnsafeMutablePointer?, bufferSize: Int32) -> Int32 { guard let buffer = buffer else { return 0 @@ -404,3 +404,4 @@ func streamingVideoThumbnail(postbox: Postbox, fileReference: FileMediaReference } } } +*/ diff --git a/TelegramUI/GalleryController.swift b/TelegramUI/GalleryController.swift index 1735305e19..4422cf80d3 100644 --- a/TelegramUI/GalleryController.swift +++ b/TelegramUI/GalleryController.swift @@ -131,7 +131,7 @@ private func galleryMessageCaptionText(_ message: Message) -> String { return message.text } -func galleryItemForEntry(account: Account, presentationData: PresentationData, entry: MessageHistoryEntry, streamVideos: Bool, loopVideos: Bool = false, hideControls: Bool = false, playbackCompleted: @escaping () -> Void = {}, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void = { _ in }, openActionOptions: @escaping (GalleryControllerInteractionTapAction) -> Void = { _ in }) -> GalleryItem? { +func galleryItemForEntry(account: Account, presentationData: PresentationData, entry: MessageHistoryEntry, streamVideos: Bool, loopVideos: Bool = false, hideControls: Bool = false, tempFilePath: String? = nil, playbackCompleted: @escaping () -> Void = {}, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void = { _ in }, openActionOptions: @escaping (GalleryControllerInteractionTapAction) -> Void = { _ in }) -> GalleryItem? { switch entry { case let .MessageEntry(message, _, location, _): if let (media, mediaImage) = mediaForMessage(message: message) { @@ -141,10 +141,10 @@ func galleryItemForEntry(account: Account, presentationData: PresentationData, e if file.isVideo { let content: UniversalVideoContent if file.isAnimated { - content = NativeVideoContent(id: .message(message.id, message.stableId + 1, file.fileId), fileReference: .message(message: MessageReference(message), media: file), imageReference: mediaImage.flatMap({ ImageMediaReference.message(message: MessageReference(message), media: $0) }), streamVideo: false, loopVideo: true, enableSound: false) + content = NativeVideoContent(id: .message(message.id, message.stableId + 1, file.fileId), fileReference: .message(message: MessageReference(message), media: file), imageReference: mediaImage.flatMap({ ImageMediaReference.message(message: MessageReference(message), media: $0) }), streamVideo: false, loopVideo: true, enableSound: false, tempFilePath: tempFilePath) } else { if true || (file.mimeType == "video/mpeg4" || file.mimeType == "video/mov" || file.mimeType == "video/mp4") { - content = NativeVideoContent(id: .message(message.id, message.stableId, file.fileId), fileReference: .message(message: MessageReference(message), media: file), imageReference: mediaImage.flatMap({ ImageMediaReference.message(message: MessageReference(message), media: $0) }), streamVideo: true, loopVideo: loopVideos) + content = NativeVideoContent(id: .message(message.id, message.stableId, file.fileId), fileReference: .message(message: MessageReference(message), media: file), imageReference: mediaImage.flatMap({ ImageMediaReference.message(message: MessageReference(message), media: $0) }), streamVideo: true, loopVideo: loopVideos, tempFilePath: tempFilePath) } else { content = PlatformVideoContent(id: .message(message.id, message.stableId, file.fileId), fileReference: .message(message: MessageReference(message), media: file), streamVideo: streamVideos, loopVideo: loopVideos) } @@ -161,7 +161,7 @@ func galleryItemForEntry(account: Account, presentationData: PresentationData, e return UniversalVideoGalleryItem(account: account, presentationData: presentationData, content: content, originData: GalleryItemOriginData(title: message.author?.displayTitle, timestamp: message.timestamp), indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, contentInfo: .message(message), caption: caption, hideControls: hideControls, playbackCompleted: playbackCompleted, performAction: performAction, openActionOptions: openActionOptions) } else { if file.mimeType.hasPrefix("image/") && file.mimeType != "image/gif" { - if file.size == nil || file.size! < 5 * 1024 * 1024 { + if file.size == nil || file.size! < 2 * 1024 * 1024 { return ChatImageGalleryItem(account: account, presentationData: presentationData, message: message, location: location, performAction: performAction, openActionOptions: openActionOptions) } else { return ChatDocumentGalleryItem(account: account, presentationData: presentationData, message: message, location: location) diff --git a/TelegramUI/GroupAdminsController.swift b/TelegramUI/GroupAdminsController.swift index 3cc3ba0aa2..b3909fe23a 100644 --- a/TelegramUI/GroupAdminsController.swift +++ b/TelegramUI/GroupAdminsController.swift @@ -56,7 +56,7 @@ private enum GroupAdminsEntryStableId: Hashable { private enum GroupAdminsEntry: ItemListNodeEntry { case allAdmins(PresentationTheme, String, Bool) case allAdminsInfo(PresentationTheme, String) - case peerItem(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, String, Bool, Bool) + case peerItem(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer, String, Bool, Bool) var section: ItemListSectionId { switch self { @@ -73,7 +73,7 @@ private enum GroupAdminsEntry: ItemListNodeEntry { return .index(0) case .allAdminsInfo: return .index(1) - case let .peerItem(_, _, _, _, peer, _, _, _): + case let .peerItem(_, _, _, _, _, peer, _, _, _): return .peer(peer.id) } } @@ -92,8 +92,8 @@ private enum GroupAdminsEntry: ItemListNodeEntry { } else { return false } - case let .peerItem(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsLabel, lhsToggled, lhsEnabled): - if case let .peerItem(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsLabel, rhsToggled, rhsEnabled) = rhs { + case let .peerItem(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameOrder, lhsPeer, lhsLabel, lhsToggled, lhsEnabled): + if case let .peerItem(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameOrder, rhsPeer, rhsLabel, rhsToggled, rhsEnabled) = rhs { if lhsIndex != rhsIndex { return false } @@ -106,6 +106,9 @@ private enum GroupAdminsEntry: ItemListNodeEntry { if lhsDateTimeFormat != rhsDateTimeFormat { return false } + if lhsNameOrder != rhsNameOrder { + return false + } if !lhsPeer.isEqual(rhsPeer) { return false } @@ -136,9 +139,9 @@ private enum GroupAdminsEntry: ItemListNodeEntry { default: return true } - case let .peerItem(index, _, _, _, _, _, _, _): + case let .peerItem(index, _, _, _, _, _, _, _, _): switch rhs { - case let .peerItem(rhsIndex, _, _, _, _, _, _, _): + case let .peerItem(rhsIndex, _, _, _, _, _, _, _, _): return index < rhsIndex case .allAdmins, .allAdminsInfo: return false @@ -154,8 +157,8 @@ private enum GroupAdminsEntry: ItemListNodeEntry { }) case let .allAdminsInfo(theme, text): return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section) - case let .peerItem(_, theme, strings, dateTimeFormat, peer, label, toggled, enabled): - return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, account: arguments.account, peer: peer, presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: ItemListPeerItemSwitch(value: toggled, style: .standard), enabled: enabled, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, toggleUpdated: { value in + case let .peerItem(_, theme, strings, dateTimeFormat, nameDisplayOrder, peer, label, toggled, enabled): + return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, account: arguments.account, peer: peer, presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: ItemListPeerItemSwitch(value: toggled, style: .standard), enabled: enabled, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, toggleUpdated: { value in arguments.updatePeerIsAdmin(peer.id, value) }) } @@ -276,7 +279,7 @@ private func groupAdminsControllerEntries(account: Account, presentationData: Pr } } } - entries.append(.peerItem(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, label, isAdmin, isEnabled)) + entries.append(.peerItem(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, peer, label, isAdmin, isEnabled)) index += 1 } } diff --git a/TelegramUI/GroupInfoController.swift b/TelegramUI/GroupInfoController.swift index 8f03707982..612615916b 100644 --- a/TelegramUI/GroupInfoController.swift +++ b/TelegramUI/GroupInfoController.swift @@ -149,7 +149,7 @@ private enum GroupInfoEntry: ItemListNodeEntry { case membersAdmins(PresentationTheme, String, String) case membersBlacklist(PresentationTheme, String, String) case addMember(PresentationTheme, String, editing: Bool) - case member(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, index: Int, peerId: PeerId, peer: Peer, participant: RenderedChannelParticipant?, presence: PeerPresence?, memberStatus: GroupInfoMemberStatus, editing: ItemListPeerItemEditing, revealActions: [ParticipantRevealAction], enabled: Bool) + case member(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, index: Int, peerId: PeerId, peer: Peer, participant: RenderedChannelParticipant?, presence: PeerPresence?, memberStatus: GroupInfoMemberStatus, editing: ItemListPeerItemEditing, revealActions: [ParticipantRevealAction], enabled: Bool) case convertToSupergroup(PresentationTheme, String) case leave(PresentationTheme, String) @@ -343,8 +343,8 @@ private enum GroupInfoEntry: ItemListNodeEntry { } else { return false } - case let .member(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsIndex, lhsPeerId, lhsPeer, lhsParticipant, lhsPresence, lhsMemberStatus, lhsEditing, lhsActions, lhsEnabled): - if case let .member(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsIndex, rhsPeerId, rhsPeer, rhsParticipant, rhsPresence, rhsMemberStatus, rhsEditing, rhsActions, rhsEnabled) = rhs { + case let .member(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameDisplayOrder, lhsIndex, lhsPeerId, lhsPeer, lhsParticipant, lhsPresence, lhsMemberStatus, lhsEditing, lhsActions, lhsEnabled): + if case let .member(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameDisplayOrder, rhsIndex, rhsPeerId, rhsPeer, rhsParticipant, rhsPresence, rhsMemberStatus, rhsEditing, rhsActions, rhsEnabled) = rhs { if lhsTheme !== rhsTheme { return false } @@ -354,6 +354,9 @@ private enum GroupInfoEntry: ItemListNodeEntry { if lhsDateTimeFormat != rhsDateTimeFormat { return false } + if lhsNameDisplayOrder != rhsNameDisplayOrder { + return false + } if lhsIndex != rhsIndex { return false } @@ -394,7 +397,7 @@ private enum GroupInfoEntry: ItemListNodeEntry { var stableId: GroupEntryStableId { switch self { - case let .member(_, _, _, _, peerId, _, _, _, _, _, _, _): + case let .member(_, _, _, _, _, peerId, _, _, _, _, _, _, _): return .peer(peerId) default: return .index(self.sortIndex) @@ -439,7 +442,7 @@ private enum GroupInfoEntry: ItemListNodeEntry { return 16 case .addMember: return 17 - case let .member(_, _, _, index, _, _, _, _, _, _, _, _): + case let .member(_, _, _, _, index, _, _, _, _, _, _, _, _): return 20 + index case .convertToSupergroup: return 100000 @@ -526,7 +529,7 @@ private enum GroupInfoEntry: ItemListNodeEntry { return ItemListDisclosureItem(theme: theme, title: title, label: text, sectionId: self.section, style: .blocks, action: { arguments.pushController(channelBlacklistController(account: arguments.account, peerId: arguments.peerId)) }) - case let .member(theme, strings, dateTimeFormat, _, _, peer, participant, presence, memberStatus, editing, actions, enabled): + case let .member(theme, strings, dateTimeFormat, nameDisplayOrder, _, _, peer, participant, presence, memberStatus, editing, actions, enabled): let label: String? switch memberStatus { case .admin: @@ -551,7 +554,7 @@ private enum GroupInfoEntry: ItemListNodeEntry { } })) } - return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, account: arguments.account, peer: peer, presence: presence, text: .presence, label: label == nil ? .none : .text(label!), editing: editing, revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: enabled, sectionId: self.section, action: { + return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, account: arguments.account, peer: peer, presence: presence, text: .presence, label: label == nil ? .none : .text(label!), editing: editing, revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: enabled, sectionId: self.section, action: { if let infoController = peerInfoController(account: arguments.account, peer: peer) { arguments.pushController(infoController) } @@ -957,7 +960,7 @@ private func groupInfoEntries(account: Account, presentationData: PresentationDa } else { memberStatus = .member } - entries.append(GroupInfoEntry.member(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, index: i, peerId: peer.id, peer: peer, participant: nil, presence: peerPresences[peer.id], memberStatus: memberStatus, editing: ItemListPeerItemEditing(editable: canRemoveParticipant(account: account, isAdmin: canEditMembers, participantId: peer.id, invitedBy: sortedParticipants[i].invitedBy), editing: state.editingState != nil && canRemoveAnyMember, revealed: state.peerIdWithRevealedOptions == peer.id), revealActions: [ParticipantRevealAction(type: .destructive, title: presentationData.strings.Common_Delete, action: .remove)], enabled: !disabledPeerIds.contains(peer.id))) + entries.append(GroupInfoEntry.member(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, index: i, peerId: peer.id, peer: peer, participant: nil, presence: peerPresences[peer.id], memberStatus: memberStatus, editing: ItemListPeerItemEditing(editable: canRemoveParticipant(account: account, isAdmin: canEditMembers, participantId: peer.id, invitedBy: sortedParticipants[i].invitedBy), editing: state.editingState != nil && canRemoveAnyMember, revealed: state.peerIdWithRevealedOptions == peer.id), revealActions: [ParticipantRevealAction(type: .destructive, title: presentationData.strings.Common_Delete, action: .remove)], enabled: !disabledPeerIds.contains(peer.id))) } } } else if let channel = view.peers[view.peerId] as? TelegramChannel, let cachedChannelData = view.cachedData as? CachedChannelData, let memberCount = cachedChannelData.participantsSummary.memberCount { @@ -1079,7 +1082,7 @@ private func groupInfoEntries(account: Account, presentationData: PresentationDa peerActions.append(ParticipantRevealAction(type: .destructive, title: presentationData.strings.Common_Delete, action: .remove)) } - entries.append(GroupInfoEntry.member(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, index: i, peerId: participant.peer.id, peer: participant.peer, participant: participant, presence: participant.presences[participant.peer.id], memberStatus: memberStatus, editing: ItemListPeerItemEditing(editable: !peerActions.isEmpty, editing: state.editingState != nil && canRemoveAnyMember, revealed: state.peerIdWithRevealedOptions == participant.peer.id), revealActions: peerActions, enabled: !disabledPeerIds.contains(participant.peer.id))) + entries.append(GroupInfoEntry.member(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, index: i, peerId: participant.peer.id, peer: participant.peer, participant: participant, presence: participant.presences[participant.peer.id], memberStatus: memberStatus, editing: ItemListPeerItemEditing(editable: !peerActions.isEmpty, editing: state.editingState != nil && canRemoveAnyMember, revealed: state.peerIdWithRevealedOptions == participant.peer.id), revealActions: peerActions, enabled: !disabledPeerIds.contains(participant.peer.id))) } } @@ -1780,6 +1783,8 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl if let infoController = peerInfoController(account: account, peer: peer) { arguments.pushController(infoController) } + }, present: { c, a in + presentControllerImpl?(c, a) }) } diff --git a/TelegramUI/GroupInfoSearchItem.swift b/TelegramUI/GroupInfoSearchItem.swift index 99b3df5c34..65ba371924 100644 --- a/TelegramUI/GroupInfoSearchItem.swift +++ b/TelegramUI/GroupInfoSearchItem.swift @@ -10,15 +10,19 @@ final class ChannelMembersSearchItem: ItemListControllerSearch { let peerId: PeerId let cancel: () -> Void let openPeer: (Peer, RenderedChannelParticipant?) -> Void + let present: (ViewController, Any?) -> Void let searchMode: ChannelMembersSearchMode + private var updateActivity: ((Bool) -> Void)? private var activity: ValuePromise = ValuePromise(ignoreRepeated: false) private let activityDisposable = MetaDisposable() - init(account: Account, peerId: PeerId, searchMode: ChannelMembersSearchMode = .searchMembers, cancel: @escaping () -> Void, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void) { + + init(account: Account, peerId: PeerId, searchMode: ChannelMembersSearchMode = .searchMembers, cancel: @escaping () -> Void, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void, present: @escaping (ViewController, Any?) -> Void) { self.account = account self.peerId = peerId self.cancel = cancel self.openPeer = openPeer + self.present = present self.searchMode = searchMode activityDisposable.set((activity.get() |> mapToSignal { value -> Signal in if value { @@ -32,7 +36,7 @@ final class ChannelMembersSearchItem: ItemListControllerSearch { } deinit { - activityDisposable.dispose() + self.activityDisposable.dispose() } func isEqual(to: ItemListControllerSearch) -> Bool { @@ -63,6 +67,8 @@ final class ChannelMembersSearchItem: ItemListControllerSearch { func node(current: ItemListControllerSearchNode?) -> ItemListControllerSearchNode { return ChannelMembersSearchItemNode(account: self.account, peerId: self.peerId, searchMode: self.searchMode, openPeer: self.openPeer, cancel: self.cancel, updateActivity: { [weak self] value in self?.activity.set(value) + }, present: { [weak self] c, a in + self?.present(c, a) }) } } @@ -70,10 +76,10 @@ final class ChannelMembersSearchItem: ItemListControllerSearch { private final class ChannelMembersSearchItemNode: ItemListControllerSearchNode { private let containerNode: ChannelMembersSearchContainerNode - init(account: Account, peerId: PeerId, searchMode: ChannelMembersSearchMode, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void, cancel: @escaping () -> Void, updateActivity: @escaping(Bool)->Void) { + init(account: Account, peerId: PeerId, searchMode: ChannelMembersSearchMode, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void, cancel: @escaping () -> Void, updateActivity: @escaping(Bool) -> Void, present: @escaping (ViewController, Any?) -> Void) { self.containerNode = ChannelMembersSearchContainerNode(account: account, peerId: peerId, mode: searchMode, filters: [], openPeer: { peer, participant in openPeer(peer, participant) - }, updateActivity: updateActivity) + }, updateActivity: updateActivity, present: present) self.containerNode.cancel = { cancel() } diff --git a/TelegramUI/GroupsInCommonController.swift b/TelegramUI/GroupsInCommonController.swift index a6fdc213c5..b1861eab8d 100644 --- a/TelegramUI/GroupsInCommonController.swift +++ b/TelegramUI/GroupsInCommonController.swift @@ -42,7 +42,7 @@ private enum GroupsInCommonEntryStableId: Hashable { } private enum GroupsInCommonEntry: ItemListNodeEntry { - case peerItem(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer) + case peerItem(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer) var section: ItemListSectionId { switch self { @@ -53,15 +53,15 @@ private enum GroupsInCommonEntry: ItemListNodeEntry { var stableId: GroupsInCommonEntryStableId { switch self { - case let .peerItem(_, _, _, _, peer): + case let .peerItem(_, _, _, _, _, peer): return .peer(peer.id) } } static func ==(lhs: GroupsInCommonEntry, rhs: GroupsInCommonEntry) -> Bool { switch lhs { - case let .peerItem(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer): - if case let .peerItem(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer) = rhs { + case let .peerItem(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameOrder, lhsPeer): + if case let .peerItem(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameOrder, rhsPeer) = rhs { if lhsIndex != rhsIndex { return false } @@ -77,6 +77,9 @@ private enum GroupsInCommonEntry: ItemListNodeEntry { if !lhsPeer.isEqual(rhsPeer) { return false } + if lhsNameOrder != rhsNameOrder { + return false + } return true } else { return false @@ -86,9 +89,9 @@ private enum GroupsInCommonEntry: ItemListNodeEntry { static func <(lhs: GroupsInCommonEntry, rhs: GroupsInCommonEntry) -> Bool { switch lhs { - case let .peerItem(index, _, _, _, _): + case let .peerItem(index, _, _, _, _, _): switch rhs { - case let .peerItem(rhsIndex, _, _, _, _): + case let .peerItem(rhsIndex, _, _, _, _, _): return index < rhsIndex } } @@ -96,8 +99,8 @@ private enum GroupsInCommonEntry: ItemListNodeEntry { func item(_ arguments: GroupsInCommonControllerArguments) -> ListViewItem { switch self { - case let .peerItem(_, theme, strings, dateTimeFormat, peer): - return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, account: arguments.account, peer: peer, presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, sectionId: self.section, action: { + case let .peerItem(_, theme, strings, dateTimeFormat, nameDisplayOrder, peer): + return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, account: arguments.account, peer: peer, presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, sectionId: self.section, action: { arguments.openPeer(peer.id) }, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in @@ -118,7 +121,7 @@ private func groupsInCommonControllerEntries(presentationData: PresentationData, if let peers = peers { var index: Int32 = 0 for peer in peers { - entries.append(.peerItem(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer)) + entries.append(.peerItem(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, peer)) index += 1 } } diff --git a/TelegramUI/ItemListPeerItem.swift b/TelegramUI/ItemListPeerItem.swift index e2e8c59533..e0ba8211f3 100644 --- a/TelegramUI/ItemListPeerItem.swift +++ b/TelegramUI/ItemListPeerItem.swift @@ -63,6 +63,7 @@ final class ItemListPeerItem: ListViewItem, ItemListItem { let theme: PresentationTheme let strings: PresentationStrings let dateTimeFormat: PresentationDateTimeFormat + let nameDisplayOrder: PresentationPersonNameOrder let account: Account let peer: Peer let aliasHandling: ItemListPeerItemAliasHandling @@ -82,10 +83,11 @@ final class ItemListPeerItem: ListViewItem, ItemListItem { let hasTopStripe: Bool let hasTopGroupInset: Bool - init(theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, account: Account, peer: Peer, aliasHandling: ItemListPeerItemAliasHandling = .standard, nameColor: ItemListPeerItemNameColor = .primary, presence: PeerPresence?, text: ItemListPeerItemText, label: ItemListPeerItemLabel, editing: ItemListPeerItemEditing, revealOptions: ItemListPeerItemRevealOptions? = nil, switchValue: ItemListPeerItemSwitch?, enabled: Bool, sectionId: ItemListSectionId, action: (() -> Void)?, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, removePeer: @escaping (PeerId) -> Void, toggleUpdated: ((Bool) -> Void)? = nil, hasTopStripe: Bool = true, hasTopGroupInset: Bool = true) { + init(theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, account: Account, peer: Peer, aliasHandling: ItemListPeerItemAliasHandling = .standard, nameColor: ItemListPeerItemNameColor = .primary, presence: PeerPresence?, text: ItemListPeerItemText, label: ItemListPeerItemLabel, editing: ItemListPeerItemEditing, revealOptions: ItemListPeerItemRevealOptions? = nil, switchValue: ItemListPeerItemSwitch?, enabled: Bool, sectionId: ItemListSectionId, action: (() -> Void)?, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, removePeer: @escaping (PeerId) -> Void, toggleUpdated: ((Bool) -> Void)? = nil, hasTopStripe: Bool = true, hasTopGroupInset: Bool = true) { self.theme = theme self.strings = strings self.dateTimeFormat = dateTimeFormat + self.nameDisplayOrder = nameDisplayOrder self.account = account self.peer = peer self.aliasHandling = aliasHandling @@ -332,9 +334,16 @@ class ItemListPeerItemNode: ItemListRevealOptionsItemNode { } else if let user = item.peer as? TelegramUser { if let firstName = user.firstName, let lastName = user.lastName, !firstName.isEmpty, !lastName.isEmpty { let string = NSMutableAttributedString() - string.append(NSAttributedString(string: firstName, font: titleFont, textColor: titleColor)) - string.append(NSAttributedString(string: " ", font: titleFont, textColor: titleColor)) - string.append(NSAttributedString(string: lastName, font: titleBoldFont, textColor: titleColor)) + switch item.nameDisplayOrder { + case .firstLast: + string.append(NSAttributedString(string: firstName, font: titleFont, textColor: titleColor)) + string.append(NSAttributedString(string: " ", font: titleFont, textColor: titleColor)) + string.append(NSAttributedString(string: lastName, font: titleBoldFont, textColor: titleColor)) + case .lastFirst: + string.append(NSAttributedString(string: lastName, font: titleBoldFont, textColor: titleColor)) + string.append(NSAttributedString(string: " ", font: titleFont, textColor: titleColor)) + string.append(NSAttributedString(string: firstName, font: titleFont, textColor: titleColor)) + } titleAttributedString = string } else if let firstName = user.firstName, !firstName.isEmpty { titleAttributedString = NSAttributedString(string: firstName, font: titleBoldFont, textColor: titleColor) diff --git a/TelegramUI/ItemListWebsiteItem.swift b/TelegramUI/ItemListWebsiteItem.swift index d59c38a476..3ff62be725 100644 --- a/TelegramUI/ItemListWebsiteItem.swift +++ b/TelegramUI/ItemListWebsiteItem.swift @@ -24,6 +24,7 @@ final class ItemListWebsiteItem: ListViewItem, ItemListItem { let theme: PresentationTheme let strings: PresentationStrings let dateTimeFormat: PresentationDateTimeFormat + let nameDisplayOrder: PresentationPersonNameOrder let website: WebAuthorization let peer: Peer? let enabled: Bool @@ -33,10 +34,11 @@ final class ItemListWebsiteItem: ListViewItem, ItemListItem { let setSessionIdWithRevealedOptions: (Int64?, Int64?) -> Void let removeSession: (Int64) -> Void - init(theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, website: WebAuthorization, peer: Peer?, enabled: Bool, editing: Bool, revealed: Bool, sectionId: ItemListSectionId, setSessionIdWithRevealedOptions: @escaping (Int64?, Int64?) -> Void, removeSession: @escaping (Int64) -> Void) { + init(theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, website: WebAuthorization, peer: Peer?, enabled: Bool, editing: Bool, revealed: Bool, sectionId: ItemListSectionId, setSessionIdWithRevealedOptions: @escaping (Int64?, Int64?) -> Void, removeSession: @escaping (Int64) -> Void) { self.theme = theme self.strings = strings self.dateTimeFormat = dateTimeFormat + self.nameDisplayOrder = nameDisplayOrder self.website = website self.peer = peer self.enabled = enabled @@ -174,7 +176,7 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode { let rightInset: CGFloat = params.rightInset if let user = item.peer as? TelegramUser { - titleAttributedString = NSAttributedString(string: user.displayTitle, font: titleFont, textColor: item.theme.list.itemPrimaryTextColor) + titleAttributedString = NSAttributedString(string: user.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder), font: titleFont, textColor: item.theme.list.itemPrimaryTextColor) } var appString = "" diff --git a/TelegramUI/ListMessageFileItemNode.swift b/TelegramUI/ListMessageFileItemNode.swift index 577b76ca12..500e69af22 100644 --- a/TelegramUI/ListMessageFileItemNode.swift +++ b/TelegramUI/ListMessageFileItemNode.swift @@ -144,6 +144,7 @@ final class ListMessageFileItemNode: ListMessageNode { private let titleNode: TextNode private let descriptionNode: TextNode + private let descriptionProgressNode: ImmediateTextNode private let extensionIconNode: ASImageNode private let extensionIconText: TextNode @@ -171,6 +172,7 @@ final class ListMessageFileItemNode: ListMessageNode { private var appliedItem: ListMessageItem? private var layoutParams: ListViewItemLayoutParams? + private var contentSizeValue: CGSize? private var currentLeftOffet: CGFloat = 0.0 override var canBeLongTapped: Bool { @@ -191,6 +193,10 @@ final class ListMessageFileItemNode: ListMessageNode { self.descriptionNode = TextNode() self.descriptionNode.isUserInteractionEnabled = false + self.descriptionProgressNode = ImmediateTextNode() + self.descriptionProgressNode.isUserInteractionEnabled = false + self.descriptionProgressNode.maximumNumberOfLines = 1 + self.extensionIconNode = ASImageNode() self.extensionIconNode.isLayerBacked = true self.extensionIconNode.displaysAsynchronously = false @@ -223,6 +229,7 @@ final class ListMessageFileItemNode: ListMessageNode { self.addSubnode(self.separatorNode) self.addSubnode(self.titleNode) self.addSubnode(self.descriptionNode) + self.addSubnode(self.descriptionProgressNode) self.addSubnode(self.extensionIconNode) self.addSubnode(self.extensionIconText) self.addSubnode(self.statusNode) @@ -492,6 +499,7 @@ final class ListMessageFileItemNode: ListMessageNode { strongSelf.account = item.account strongSelf.appliedItem = item strongSelf.layoutParams = params + strongSelf.contentSizeValue = nodeLayout.contentSize strongSelf.currentLeftOffet = leftOffset if let _ = updatedTheme { @@ -604,96 +612,105 @@ final class ListMessageFileItemNode: ListMessageNode { if let updatedStatusSignal = updatedStatusSignal { strongSelf.statusDisposable.set((updatedStatusSignal |> deliverOnMainQueue).start(next: { [weak strongSelf] fileStatus in - let status = fileStatus.mediaStatus - displayLinkDispatcher.dispatch { - if let strongSelf = strongSelf { - strongSelf.fetchStatus = fileStatus.fetchStatus - strongSelf.resourceStatus = status - var musicIsPlaying: Bool? - var statusState: RadialStatusNodeState = .none - if !isAudio { - if let layoutParams = strongSelf.layoutParams { - strongSelf.updateProgressFrame(size: nodeLayout.contentSize, leftInset: layoutParams.leftInset, rightInset: layoutParams.rightInset, transition: .immediate) - } - } else { - switch fileStatus.fetchStatus { - case let .Fetching(isActive, progress): - var adjustedProgress = progress - if isActive { - adjustedProgress = max(adjustedProgress, 0.027) - } - statusState = .cloudProgress(color: item.theme.list.itemAccentColor, strokeBackgroundColor: item.theme.list.itemAccentColor.withAlphaComponent(0.5), lineWidth: 2.0, value: CGFloat(adjustedProgress)) - case .Local: - break - case .Remote: - if let image = PresentationResourcesItemList.cloudFetchIcon(item.theme) { - statusState = .customIcon(image) - } - } - strongSelf.statusNode.transitionToState(statusState, completion: {}) - strongSelf.statusButtonNode.isUserInteractionEnabled = statusState != .none - - switch status { - case let .fetchStatus(fetchStatus): - switch fetchStatus { - case let .Fetching(isActive, progress): - var adjustedProgress = progress - if isActive { - adjustedProgress = max(adjustedProgress, 0.027) - } - strongSelf.progressNode.state = .Fetching(progress: adjustedProgress) - case .Local: - if isAudio { - strongSelf.progressNode.state = .Play - } else { - strongSelf.progressNode.state = .Icon - } - case .Remote: - if isAudio { - strongSelf.progressNode.state = .Play - } else { - strongSelf.progressNode.state = .Remote - } - } - case let .playbackStatus(playbackStatus): - switch playbackStatus { - case .playing: - musicIsPlaying = true - strongSelf.progressNode.state = .Pause - case .paused: - musicIsPlaying = false - strongSelf.progressNode.state = .Play - } - } - } - if let musicIsPlaying = musicIsPlaying { - if strongSelf.playbackOverlayNode == nil { - let playbackOverlayNode = ListMessagePlaybackOverlayNode() - playbackOverlayNode.frame = strongSelf.iconImageNode.frame - strongSelf.playbackOverlayNode = playbackOverlayNode - strongSelf.addSubnode(playbackOverlayNode) - } - strongSelf.playbackOverlayNode?.isPlaying = musicIsPlaying - } else if let playbackOverlayNode = strongSelf.playbackOverlayNode { - strongSelf.playbackOverlayNode = nil - playbackOverlayNode.removeFromSupernode() - } - } + if let strongSelf = strongSelf { + strongSelf.fetchStatus = fileStatus.fetchStatus + strongSelf.resourceStatus = fileStatus.mediaStatus + strongSelf.updateStatus(transition: .immediate) } })) } - strongSelf.updateProgressFrame(size: CGSize(width: params.width, height: 52.0), leftInset: params.leftInset, rightInset: params.rightInset, transition: transition) transition.updateFrame(node: strongSelf.downloadStatusIconNode, frame: CGRect(origin: CGPoint(x: leftOffset + leftInset, y: 31.0), size: CGSize(width: 11.0, height: 11.0))) if let updatedFetchControls = updatedFetchControls { let _ = strongSelf.fetchControls.swap(updatedFetchControls) } + + strongSelf.updateStatus(transition: transition) } }) } } + private func updateStatus(transition: ContainedViewLayoutTransition) { + guard let item = self.item, let media = self.currentMedia, let fetchStatus = self.fetchStatus, let status = self.resourceStatus, let layoutParams = self.layoutParams, let contentSize = self.contentSizeValue else { + return + } + + var isAudio = false + if let file = media as? TelegramMediaFile { + isAudio = file.isMusic || file.isVoice + } + + var musicIsPlaying: Bool? + var statusState: RadialStatusNodeState = .none + if !isAudio { + self.updateProgressFrame(size: contentSize, leftInset: layoutParams.leftInset, rightInset: layoutParams.rightInset, transition: .immediate) + } else { + switch fetchStatus { + case let .Fetching(isActive, progress): + var adjustedProgress = progress + if isActive { + adjustedProgress = max(adjustedProgress, 0.027) + } + statusState = .cloudProgress(color: item.theme.list.itemAccentColor, strokeBackgroundColor: item.theme.list.itemAccentColor.withAlphaComponent(0.5), lineWidth: 2.0, value: CGFloat(adjustedProgress)) + case .Local: + break + case .Remote: + if let image = PresentationResourcesItemList.cloudFetchIcon(item.theme) { + statusState = .customIcon(image) + } + } + self.statusNode.transitionToState(statusState, completion: {}) + self.statusButtonNode.isUserInteractionEnabled = statusState != .none + + switch status { + case let .fetchStatus(fetchStatus): + switch fetchStatus { + case let .Fetching(isActive, progress): + var adjustedProgress = progress + if isActive { + adjustedProgress = max(adjustedProgress, 0.027) + } + self.progressNode.state = .Fetching(progress: adjustedProgress) + case .Local: + if isAudio { + self.progressNode.state = .Play + } else { + self.progressNode.state = .Icon + } + case .Remote: + if isAudio { + self.progressNode.state = .Play + } else { + self.progressNode.state = .Remote + } + } + case let .playbackStatus(playbackStatus): + switch playbackStatus { + case .playing: + musicIsPlaying = true + self.progressNode.state = .Pause + case .paused: + musicIsPlaying = false + self.progressNode.state = .Play + } + } + } + if let musicIsPlaying = musicIsPlaying { + if self.playbackOverlayNode == nil { + let playbackOverlayNode = ListMessagePlaybackOverlayNode() + playbackOverlayNode.frame = self.iconImageNode.frame + self.playbackOverlayNode = playbackOverlayNode + self.addSubnode(playbackOverlayNode) + } + self.playbackOverlayNode?.isPlaying = musicIsPlaying + } else if let playbackOverlayNode = self.playbackOverlayNode { + self.playbackOverlayNode = nil + playbackOverlayNode.removeFromSupernode() + } + } + override func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) { super.setHighlighted(highlighted, at: point, animated: animated) @@ -742,9 +759,13 @@ final class ListMessageFileItemNode: ListMessageNode { } private func updateProgressFrame(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { + guard let item = self.appliedItem else { + return + } var descriptionOffset: CGFloat = 0.0 - if let resourceStatus = self.resourceStatus, let item = self.appliedItem { + var downloadingString: String? + if let resourceStatus = self.resourceStatus { var maybeFetchStatus: MediaResourceStatus = .Local switch resourceStatus { case .playbackStatus: @@ -752,7 +773,12 @@ final class ListMessageFileItemNode: ListMessageNode { case let .fetchStatus(fetchStatus): maybeFetchStatus = fetchStatus switch fetchStatus { - case .Remote, .Fetching: + case let .Fetching(_, progress): + if let file = self.currentMedia as? TelegramMediaFile, let size = file.size { + downloadingString = "\(dataSizeString(Int(Float(size) * progress), forceDecimal: true)) / \(dataSizeString(size, forceDecimal: true))" + } + descriptionOffset = 14.0 + case .Remote: descriptionOffset = 14.0 case .Local: break @@ -802,6 +828,18 @@ final class ListMessageFileItemNode: ListMessageNode { descriptionFrame.origin.x = originX transition.updateFrame(node: self.descriptionNode, frame: descriptionFrame) } + + if downloadingString != nil { + self.descriptionProgressNode.isHidden = false + self.descriptionNode.isHidden = true + } else { + self.descriptionProgressNode.isHidden = true + self.descriptionNode.isHidden = false + } + self.descriptionProgressNode.attributedText = NSAttributedString(string: downloadingString ?? "", font: descriptionFont, textColor: item.theme.list.itemSecondaryTextColor) + let descriptionSize = self.descriptionProgressNode.updateLayout(CGSize(width: size.width - 14.0, height: size.height)) + transition.updateFrame(node: self.descriptionProgressNode, frame: CGRect(origin: self.descriptionNode.frame.origin, size: descriptionSize)) + } func activateMedia() { diff --git a/TelegramUI/MediaPlayer.swift b/TelegramUI/MediaPlayer.swift index a5c4151a49..6bbfe7cc6e 100644 --- a/TelegramUI/MediaPlayer.swift +++ b/TelegramUI/MediaPlayer.swift @@ -63,6 +63,7 @@ private final class MediaPlayerContext { private let postbox: Postbox private let resourceReference: MediaResourceReference + private let tempFilePath: String? private let streamable: Bool private let video: Bool private let preferSoftwareDecoding: Bool @@ -88,7 +89,7 @@ private final class MediaPlayerContext { private var stoppedAtEnd = false - init(queue: Queue, audioSessionManager: ManagedAudioSession, playerStatus: Promise, postbox: Postbox, resourceReference: MediaResourceReference, streamable: Bool, video: Bool, preferSoftwareDecoding: Bool, playAutomatically: Bool, enableSound: Bool, baseRate: Double, fetchAutomatically: Bool, playAndRecord: Bool, keepAudioSessionWhilePaused: Bool) { + init(queue: Queue, audioSessionManager: ManagedAudioSession, playerStatus: Promise, postbox: Postbox, resourceReference: MediaResourceReference, tempFilePath: String?, streamable: Bool, video: Bool, preferSoftwareDecoding: Bool, playAutomatically: Bool, enableSound: Bool, baseRate: Double, fetchAutomatically: Bool, playAndRecord: Bool, keepAudioSessionWhilePaused: Bool) { assert(queue.isCurrent()) self.queue = queue @@ -96,6 +97,7 @@ private final class MediaPlayerContext { self.playerStatus = playerStatus self.postbox = postbox self.resourceReference = resourceReference + self.tempFilePath = tempFilePath self.streamable = streamable self.video = video self.preferSoftwareDecoding = preferSoftwareDecoding @@ -253,7 +255,7 @@ private final class MediaPlayerContext { self.playerStatus.set(.single(status)) } - let frameSource = FFMpegMediaFrameSource(queue: self.queue, postbox: self.postbox, resourceReference: self.resourceReference, streamable: self.streamable, video: self.video, preferSoftwareDecoding: self.preferSoftwareDecoding, fetchAutomatically: self.fetchAutomatically) + let frameSource = FFMpegMediaFrameSource(queue: self.queue, postbox: self.postbox, resourceReference: self.resourceReference, tempFilePath: self.tempFilePath, streamable: self.streamable, video: self.video, preferSoftwareDecoding: self.preferSoftwareDecoding, fetchAutomatically: self.fetchAutomatically) let disposable = MetaDisposable() let updatedSeekState: MediaPlayerSeekState? if let loadedDuration = loadedDuration { @@ -827,9 +829,9 @@ final class MediaPlayer { } } - init(audioSessionManager: ManagedAudioSession, postbox: Postbox, resourceReference: MediaResourceReference, streamable: Bool, video: Bool, preferSoftwareDecoding: Bool, playAutomatically: Bool = false, enableSound: Bool, baseRate: Double = 1.0, fetchAutomatically: Bool, playAndRecord: Bool = false, keepAudioSessionWhilePaused: Bool = true) { + init(audioSessionManager: ManagedAudioSession, postbox: Postbox, resourceReference: MediaResourceReference, tempFilePath: String? = nil, streamable: Bool, video: Bool, preferSoftwareDecoding: Bool, playAutomatically: Bool = false, enableSound: Bool, baseRate: Double = 1.0, fetchAutomatically: Bool, playAndRecord: Bool = false, keepAudioSessionWhilePaused: Bool = true) { self.queue.async { - let context = MediaPlayerContext(queue: self.queue, audioSessionManager: audioSessionManager, playerStatus: self.statusValue, postbox: postbox, resourceReference: resourceReference, streamable: streamable, video: video, preferSoftwareDecoding: preferSoftwareDecoding, playAutomatically: playAutomatically, enableSound: enableSound, baseRate: baseRate, fetchAutomatically: fetchAutomatically, playAndRecord: playAndRecord, keepAudioSessionWhilePaused: keepAudioSessionWhilePaused) + let context = MediaPlayerContext(queue: self.queue, audioSessionManager: audioSessionManager, playerStatus: self.statusValue, postbox: postbox, resourceReference: resourceReference, tempFilePath: tempFilePath, streamable: streamable, video: video, preferSoftwareDecoding: preferSoftwareDecoding, playAutomatically: playAutomatically, enableSound: enableSound, baseRate: baseRate, fetchAutomatically: fetchAutomatically, playAndRecord: playAndRecord, keepAudioSessionWhilePaused: keepAudioSessionWhilePaused) self.contextRef = Unmanaged.passRetained(context) } } diff --git a/TelegramUI/MediaTrackDecodableFrame.swift b/TelegramUI/MediaTrackDecodableFrame.swift index 72e2073f2a..d3c0991667 100644 --- a/TelegramUI/MediaTrackDecodableFrame.swift +++ b/TelegramUI/MediaTrackDecodableFrame.swift @@ -1,6 +1,6 @@ import Foundation import CoreMedia -import TelegramUIPrivateModule +import FFMpeg enum MediaTrackFrameType { case video diff --git a/TelegramUI/MentionChatInputPanelItem.swift b/TelegramUI/MentionChatInputPanelItem.swift index 164ec888b0..188bc5b89e 100644 --- a/TelegramUI/MentionChatInputPanelItem.swift +++ b/TelegramUI/MentionChatInputPanelItem.swift @@ -128,8 +128,6 @@ final class MentionChatInputPanelItemNode: ListViewItemNode { let previousItem = self.item return { [weak self] item, params, mergedTop, mergedBottom in - let baseWidth = params.width - params.leftInset - params.rightInset - let leftInset: CGFloat = 55.0 + params.leftInset let rightInset: CGFloat = 10.0 + params.rightInset @@ -139,7 +137,7 @@ final class MentionChatInputPanelItemNode: ListViewItemNode { } let string = NSMutableAttributedString() - string.append(NSAttributedString(string: item.peer.displayTitle, font: primaryFont, textColor: item.theme.list.itemPrimaryTextColor)) + string.append(NSAttributedString(string: item.peer.debugDisplayTitle, font: primaryFont, textColor: item.theme.list.itemPrimaryTextColor)) if let addressName = item.peer.addressName, !addressName.isEmpty { string.append(NSAttributedString(string: " @\(addressName)", font: secondaryFont, textColor: item.theme.list.itemSecondaryTextColor)) } diff --git a/TelegramUI/MessageContentKind.swift b/TelegramUI/MessageContentKind.swift index d988b868d4..4266bc44f1 100644 --- a/TelegramUI/MessageContentKind.swift +++ b/TelegramUI/MessageContentKind.swift @@ -69,7 +69,7 @@ public enum MessageContentKind: Equatable { } } -public func messageContentKind(_ message: Message, strings: PresentationStrings, accountPeerId: PeerId) -> MessageContentKind { +public func messageContentKind(_ message: Message, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, accountPeerId: PeerId) -> MessageContentKind { for media in message.media { switch media { case let expiredMedia as TelegramMediaExpiredContent: @@ -127,7 +127,7 @@ public func messageContentKind(_ message: Message, strings: PresentationStrings, return .location } case _ as TelegramMediaAction: - return .text(plainServiceMessageString(strings: strings, message: message, accountPeerId: accountPeerId) ?? "") + return .text(plainServiceMessageString(strings: strings, nameDisplayOrder: nameDisplayOrder, message: message, accountPeerId: accountPeerId) ?? "") default: break } @@ -135,11 +135,11 @@ public func messageContentKind(_ message: Message, strings: PresentationStrings, return .text(message.text) } -func descriptionStringForMessage(_ message: Message, strings: PresentationStrings, accountPeerId: PeerId) -> (String, Bool) { +func descriptionStringForMessage(_ message: Message, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, accountPeerId: PeerId) -> (String, Bool) { if !message.text.isEmpty { return (message.text, false) } - switch messageContentKind(message, strings: strings, accountPeerId: accountPeerId) { + switch messageContentKind(message, strings: strings, nameDisplayOrder: nameDisplayOrder, accountPeerId: accountPeerId) { case let .text(text): return (text, false) case .image: diff --git a/TelegramUI/MultiplexedVideoNode.swift b/TelegramUI/MultiplexedVideoNode.swift index b6965e44f7..d36d950dbb 100644 --- a/TelegramUI/MultiplexedVideoNode.swift +++ b/TelegramUI/MultiplexedVideoNode.swift @@ -223,61 +223,56 @@ final class MultiplexedVideoNode: UIScrollView, UIScrollViewDelegate { let progressSize = CGSize(width: 24.0, height: 24.0) let progressFrame = CGRect(origin: CGPoint(x: item.frame.midX - progressSize.width / 2.0, y: item.frame.midY - progressSize.height / 2.0), size: progressSize) - let updatedStatusSignal = account.postbox.mediaBox.resourceStatus(item.fileReference.media.resource) - - let statusDisposable: MetaDisposable - if let disposable = self.statusDisposable[item.fileReference.media.fileId] { - statusDisposable = disposable - } else { - statusDisposable = MetaDisposable() - self.statusDisposable[item.fileReference.media.fileId] = statusDisposable + if item.frame.maxY < minVisibleY { + continue + } + if item.frame.minY > maxVisibleY { + continue } - statusDisposable.set((updatedStatusSignal |> deliverOnMainQueue).start(next: { [weak self] status in - displayLinkDispatcher.dispatch { + if self.statusDisposable[item.fileReference.media.fileId] == nil { + let statusDisposable = MetaDisposable() + let updatedStatusSignal = account.postbox.mediaBox.resourceStatus(item.fileReference.media.resource) + self.statusDisposable[item.fileReference.media.fileId] = statusDisposable + statusDisposable.set((updatedStatusSignal + |> deliverOnMainQueue).start(next: { [weak self] status in guard let `self` = self else {return} let state: RadialStatusNodeState switch status { - case let .Fetching(_, progress): - state = .progress(color: .white, lineWidth: nil, value: CGFloat(max(progress, 0.2)), cancelEnabled: false) - case .Remote: - state = .progress(color: .white, lineWidth: nil, value: 0, cancelEnabled: false) - case .Local: - state = .none + case let .Fetching(_, progress): + state = .progress(color: .white, lineWidth: nil, value: CGFloat(max(progress, 0.2)), cancelEnabled: false) + case .Remote: + state = .progress(color: .white, lineWidth: nil, value: 0, cancelEnabled: false) + case .Local: + state = .none } - if state == .none { - if let statusNode = self.visibleProgressNodes[item.fileReference.media.fileId] { + if let statusNode = self.visibleProgressNodes[item.fileReference.media.fileId] { + if state == .none { self.visibleProgressNodes.removeValue(forKey: item.fileReference.media.fileId) statusNode.transitionToState(state, completion: { [weak statusNode] in - statusNode?.removeFromSupernode() + statusNode?.isHidden = true }) - } - } else { - if let visibleProgressNode = self.visibleProgressNodes[item.fileReference.media.fileId] { - if ensureFrames { - visibleProgressNode.frame = progressFrame - } } else { - let visibleProgressNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.5)) - visibleProgressNode.frame = progressFrame - self.visibleProgressNodes[item.fileReference.media.fileId] = visibleProgressNode + statusNode.isHidden = false + statusNode.transitionToState(state, completion: {}) } - - let statusNode = self.visibleProgressNodes[item.fileReference.media.fileId]! - statusNode.transitionToState(state, completion: {}) - self.addSubnode(statusNode) } - } - })) - - if item.frame.maxY < minVisibleY { - continue; + })) } - if item.frame.minY > maxVisibleY { - continue; + + if let visibleProgressNode = self.visibleProgressNodes[item.fileReference.media.fileId] { + if ensureFrames { + visibleProgressNode.frame = progressFrame + } + } else { + let statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.5)) + statusNode.isHidden = true + statusNode.frame = progressFrame + self.visibleProgressNodes[item.fileReference.media.fileId] = statusNode + self.addSubnode(statusNode) } visibleIds.insert(item.fileReference.media.fileId) @@ -315,6 +310,13 @@ final class MultiplexedVideoNode: UIScrollView, UIScrollViewDelegate { } } + var removeProgressIds: [MediaId] = [] + for id in self.visibleProgressNodes.keys { + if !visibleIds.contains(id) { + removeProgressIds.append(id) + } + } + for id in removeIds { let (_, layerHolder) = self.visibleLayers[id]! layerHolder.layer.removeFromSuperlayer() @@ -326,6 +328,13 @@ final class MultiplexedVideoNode: UIScrollView, UIScrollViewDelegate { thumbnailLayer.removeFromSuperlayer() self.visibleThumbnailLayers.removeValue(forKey: id) } + + for id in removeProgressIds { + let progressNode = self.visibleProgressNodes[id]! + progressNode.removeFromSupernode() + self.visibleProgressNodes.removeValue(forKey: id) + self.statusDisposable.removeValue(forKey: id)?.dispose() + } } private func updateVisibleItems(transition: ContainedViewLayoutTransition = .immediate) { diff --git a/TelegramUI/NativeVideoContent.swift b/TelegramUI/NativeVideoContent.swift index 8a125d23bd..02f05c7e2d 100644 --- a/TelegramUI/NativeVideoContent.swift +++ b/TelegramUI/NativeVideoContent.swift @@ -49,8 +49,9 @@ final class NativeVideoContent: UniversalVideoContent { let baseRate: Double let fetchAutomatically: Bool let placeholderColor: UIColor + let tempFilePath: String? - init(id: NativeVideoContentId, fileReference: FileMediaReference, imageReference: ImageMediaReference? = nil, streamVideo: Bool = false, loopVideo: Bool = false, enableSound: Bool = true, baseRate: Double = 1.0, fetchAutomatically: Bool = true, placeholderColor: UIColor = .white) { + init(id: NativeVideoContentId, fileReference: FileMediaReference, imageReference: ImageMediaReference? = nil, streamVideo: Bool = false, loopVideo: Bool = false, enableSound: Bool = true, baseRate: Double = 1.0, fetchAutomatically: Bool = true, placeholderColor: UIColor = .white, tempFilePath: String? = nil) { self.id = id self.nativeId = id self.fileReference = fileReference @@ -75,10 +76,11 @@ final class NativeVideoContent: UniversalVideoContent { self.baseRate = baseRate self.fetchAutomatically = fetchAutomatically self.placeholderColor = placeholderColor + self.tempFilePath = tempFilePath } func makeContentNode(postbox: Postbox, audioSession: ManagedAudioSession) -> UniversalVideoContentNode & ASDisplayNode { - return NativeVideoContentNode(postbox: postbox, audioSessionManager: audioSession, fileReference: self.fileReference, imageReference: self.imageReference, streamVideo: self.streamVideo, loopVideo: self.loopVideo, enableSound: self.enableSound, baseRate: self.baseRate, fetchAutomatically: self.fetchAutomatically, placeholderColor: self.placeholderColor) + return NativeVideoContentNode(postbox: postbox, audioSessionManager: audioSession, fileReference: self.fileReference, imageReference: self.imageReference, streamVideo: self.streamVideo, loopVideo: self.loopVideo, enableSound: self.enableSound, baseRate: self.baseRate, fetchAutomatically: self.fetchAutomatically, placeholderColor: self.placeholderColor, tempFilePath: self.tempFilePath) } func isEqual(to other: UniversalVideoContent) -> Bool { @@ -128,14 +130,14 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent private var validLayout: CGSize? - init(postbox: Postbox, audioSessionManager: ManagedAudioSession, fileReference: FileMediaReference, imageReference: ImageMediaReference?, streamVideo: Bool, loopVideo: Bool, enableSound: Bool, baseRate: Double, fetchAutomatically: Bool, placeholderColor: UIColor) { + init(postbox: Postbox, audioSessionManager: ManagedAudioSession, fileReference: FileMediaReference, imageReference: ImageMediaReference?, streamVideo: Bool, loopVideo: Bool, enableSound: Bool, baseRate: Double, fetchAutomatically: Bool, placeholderColor: UIColor, tempFilePath: String?) { self.postbox = postbox self.fileReference = fileReference self.placeholderColor = placeholderColor self.imageNode = TransformImageNode() - self.player = MediaPlayer(audioSessionManager: audioSessionManager, postbox: postbox, resourceReference: fileReference.resourceReference(fileReference.media.resource), streamable: streamVideo, video: true, preferSoftwareDecoding: false, playAutomatically: false, enableSound: enableSound, baseRate: baseRate, fetchAutomatically: fetchAutomatically) + self.player = MediaPlayer(audioSessionManager: audioSessionManager, postbox: postbox, resourceReference: fileReference.resourceReference(fileReference.media.resource), tempFilePath: tempFilePath, streamable: streamVideo, video: true, preferSoftwareDecoding: false, playAutomatically: false, enableSound: enableSound, baseRate: baseRate, fetchAutomatically: fetchAutomatically) var actionAtEndImpl: (() -> Void)? if enableSound && !loopVideo { self.player.actionAtEnd = .action({ @@ -214,7 +216,7 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent } self.imageNode.frame = CGRect(origin: CGPoint(), size: size) - self.playerNode.frame = CGRect(origin: CGPoint(), size: size).insetBy(dx: -0.0002, dy: -0.0002) + self.playerNode.frame = CGRect(origin: CGPoint(), size: size).insetBy(dx: -1.0, dy: -1.0) } func play() { diff --git a/TelegramUI/NotificationExceptionControllerNode.swift b/TelegramUI/NotificationExceptionControllerNode.swift index 7098f35485..8332fe48d9 100644 --- a/TelegramUI/NotificationExceptionControllerNode.swift +++ b/TelegramUI/NotificationExceptionControllerNode.swift @@ -5,15 +5,12 @@ import Postbox import TelegramCore import SwiftSignalKit - - - private final class NotificationExceptionState : Equatable { - let mode:NotificationExceptionMode let isSearchMode: Bool let revealedPeerId: PeerId? let editing: Bool + init(mode: NotificationExceptionMode, isSearchMode: Bool = false, revealedPeerId: PeerId? = nil, editing: Bool = false) { self.mode = mode self.isSearchMode = isSearchMode @@ -294,7 +291,7 @@ private func notificationsExceptionEntries(presentationData: PresentationData, s let soundName = localizedPeerNotificationSoundString(strings: presentationData.strings, sound: value.settings.messageSound) title += (title.isEmpty ? presentationData.strings.Notification_Exceptions_Sound(soundName).0 : ", \(presentationData.strings.Notification_Exceptions_Sound(soundName).0)") } - entries.append(.peer(index: index, peer: value.peer, theme: presentationData.theme, strings: presentationData.strings, dateFormat: presentationData.dateTimeFormat, description: title, notificationSettings: value.settings, revealed: state.revealedPeerId == value.peer.id, editing: state.editing)) + entries.append(.peer(index: index, peer: value.peer, theme: presentationData.theme, strings: presentationData.strings, dateFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, description: title, notificationSettings: value.settings, revealed: state.revealedPeerId == value.peer.id, editing: state.editing)) index += 1 } } @@ -324,14 +321,15 @@ private enum NotificationExceptionEntryId: Hashable { case search case peerId(Int64) case addException + var hashValue: Int { switch self { - case .search: - return 0 - case .addException: - return 1 - case let .peerId(peerId): - return peerId.hashValue + case .search: + return 0 + case .addException: + return 1 + case let .peerId(peerId): + return peerId.hashValue } } @@ -380,7 +378,7 @@ private enum NotificationExceptionEntry : ItemListNodeEntry { typealias ItemGenerationArguments = NotificationExceptionArguments case search(PresentationTheme, PresentationStrings) - case peer(index: Int, peer: Peer, theme: PresentationTheme, strings: PresentationStrings, dateFormat: PresentationDateTimeFormat, description: String, notificationSettings: TelegramPeerNotificationSettings, revealed: Bool, editing: Bool) + case peer(index: Int, peer: Peer, theme: PresentationTheme, strings: PresentationStrings, dateFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, description: String, notificationSettings: TelegramPeerNotificationSettings, revealed: Bool, editing: Bool) case addException(PresentationTheme, PresentationStrings, Bool) func item(_ arguments: NotificationExceptionArguments) -> ListViewItem { @@ -393,8 +391,8 @@ private enum NotificationExceptionEntry : ItemListNodeEntry { return ItemListPeerActionItem(theme: theme, icon: PresentationResourcesItemList.addExceptionIcon(theme), title: strings.Notification_Exceptions_AddException, sectionId: self.section, editing: editing, action: { arguments.selectPeer() }) - case let .peer(_, peer, theme, strings, dateTimeFormat, value, _, revealed, editing): - return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, account: arguments.account, peer: peer, presence: nil, text: .text(value), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: editing, revealed: revealed), switchValue: nil, enabled: true, sectionId: self.section, action: { + case let .peer(_, peer, theme, strings, dateTimeFormat, nameDisplayOrder, value, _, revealed, editing): + return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, account: arguments.account, peer: peer, presence: nil, text: .text(value), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: editing, revealed: revealed), switchValue: nil, enabled: true, sectionId: self.section, action: { arguments.openPeer(peer) }, setPeerIdWithRevealedOptions: { peerId, fromPeerId in arguments.updateRevealedPeerId(peerId) @@ -410,7 +408,7 @@ private enum NotificationExceptionEntry : ItemListNodeEntry { return .search case .addException: return .addException - case let .peer(_, peer, _, _, _, _, _, _, _): + case let .peer(_, peer, _, _, _, _, _, _, _, _): return .peerId(peer.id.toInt64()) } } @@ -431,10 +429,10 @@ private enum NotificationExceptionEntry : ItemListNodeEntry { default: return false } - case let .peer(lhsIndex, lhsPeer, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsValue, lhsSettings, lhsRevealed, lhsEditing): + case let .peer(lhsIndex, lhsPeer, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameOrder, lhsValue, lhsSettings, lhsRevealed, lhsEditing): switch rhs { - case let .peer(rhsIndex, rhsPeer, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsValue, rhsSettings, rhsRevealed, rhsEditing): - return lhsTheme === rhsTheme && lhsStrings === rhsStrings && lhsDateTimeFormat == rhsDateTimeFormat && lhsIndex == rhsIndex && lhsPeer.isEqual(rhsPeer) && lhsValue == rhsValue && lhsSettings == rhsSettings && lhsRevealed == rhsRevealed && lhsEditing == rhsEditing + case let .peer(rhsIndex, rhsPeer, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameOrder, rhsValue, rhsSettings, rhsRevealed, rhsEditing): + return lhsTheme === rhsTheme && lhsStrings === rhsStrings && lhsDateTimeFormat == rhsDateTimeFormat && lhsNameOrder == rhsNameOrder && lhsIndex == rhsIndex && lhsPeer.isEqual(rhsPeer) && lhsValue == rhsValue && lhsSettings == rhsSettings && lhsRevealed == rhsRevealed && lhsEditing == rhsEditing default: return false } @@ -452,11 +450,11 @@ private enum NotificationExceptionEntry : ItemListNodeEntry { default: return true } - case let .peer(lhsIndex, _, _, _, _, _, _, _ , _): + case let .peer(lhsIndex, _, _, _, _, _, _, _, _, _): switch rhs { case .search, .addException: return false - case let .peer(rhsIndex, _, _, _, _, _, _, _, _): + case let .peer(rhsIndex, _, _, _, _, _, _, _, _, _): return lhsIndex < rhsIndex } } diff --git a/TelegramUI/NotificationsAndSounds.swift b/TelegramUI/NotificationsAndSounds.swift index c03b7a5415..d4706cc808 100644 --- a/TelegramUI/NotificationsAndSounds.swift +++ b/TelegramUI/NotificationsAndSounds.swift @@ -813,7 +813,7 @@ public func notificationsAndSoundsController(account: Account, exceptionsList: N var channels:[PeerId : NotificationExceptionWrapper] = [:] if let list = list { for (key, value) in list.settings { - if let peer = list.peers[key], !peer.displayTitle.isEmpty, peer.id != account.peerId { + if let peer = list.peers[key], !peer.debugDisplayTitle.isEmpty, peer.id != account.peerId { switch value.muteState { case .default: switch value.messageSound { diff --git a/TelegramUI/PasscodeOptionsController.swift b/TelegramUI/PasscodeOptionsController.swift index 6d05060c6e..9ebcd4cfb2 100644 --- a/TelegramUI/PasscodeOptionsController.swift +++ b/TelegramUI/PasscodeOptionsController.swift @@ -541,9 +541,11 @@ public func passcodeEntryController(account: Account, animateIn: Bool = true, co completion(value != nil) dismissImpl?() })! - controller.touchIdCompletion = { - completion(true) - dismissImpl?() + if passcodeSettings?.enableBiometrics ?? false { + controller.touchIdCompletion = { + completion(true) + dismissImpl?() + } } controller.checkCurrentPasscode = { value in if let value = value { @@ -578,7 +580,9 @@ public func passcodeEntryController(account: Account, animateIn: Bool = true, co }).start() } legacyController.presentationCompleted = { [weak controller] in - controller?.refreshTouchId() + if passcodeSettings?.enableBiometrics ?? false { + controller?.refreshTouchId() + } } legacyController.bind(controller: controller) legacyController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .portrait, compactSize: .portrait) diff --git a/TelegramUI/PeerMediaCollectionControllerNode.swift b/TelegramUI/PeerMediaCollectionControllerNode.swift index 4dbbe91011..45e79c2ebb 100644 --- a/TelegramUI/PeerMediaCollectionControllerNode.swift +++ b/TelegramUI/PeerMediaCollectionControllerNode.swift @@ -10,12 +10,20 @@ struct PeerMediaCollectionMessageForGallery { let fromSearchResults: Bool } -private func historyNodeImplForMode(_ mode: PeerMediaCollectionMode, account: Account, peerId: PeerId, messageId: MessageId?, controllerInteraction: ChatControllerInteraction, selectedMessages: Signal?, NoError>) -> ChatHistoryNode & ASDisplayNode { +private func historyNodeImplForMode(_ mode: PeerMediaCollectionMode, account: Account, theme: PresentationTheme, peerId: PeerId, messageId: MessageId?, controllerInteraction: ChatControllerInteraction, selectedMessages: Signal?, NoError>) -> ChatHistoryNode & ASDisplayNode { switch mode { case .photoOrVideo: - return ChatHistoryGridNode(account: account, peerId: peerId, messageId: messageId, tagMask: .photoOrVideo, controllerInteraction: controllerInteraction) + let node = ChatHistoryGridNode(account: account, peerId: peerId, messageId: messageId, tagMask: .photoOrVideo, controllerInteraction: controllerInteraction) + node.showVerticalScrollIndicator = true + if theme.list.plainBackgroundColor.argb == 0xffffffff { + node.indicatorStyle = .default + } else { + node.indicatorStyle = .white + } + return node case .file: let node = ChatHistoryListNode(account: account, chatLocation: .peer(peerId), tagMask: .file, messageId: messageId, controllerInteraction: controllerInteraction, selectedMessages: selectedMessages, mode: .list(search: true, reversed: false)) + node.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor node.didEndScrolling = { [weak node] in guard let node = node else { return @@ -26,6 +34,7 @@ private func historyNodeImplForMode(_ mode: PeerMediaCollectionMode, account: Ac return node case .music: let node = ChatHistoryListNode(account: account, chatLocation: .peer(peerId), tagMask: .music, messageId: messageId, controllerInteraction: controllerInteraction, selectedMessages: selectedMessages, mode: .list(search: true, reversed: false)) + node.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor node.didEndScrolling = { [weak node] in guard let node = node else { return @@ -36,6 +45,7 @@ private func historyNodeImplForMode(_ mode: PeerMediaCollectionMode, account: Ac return node case .webpage: let node = ChatHistoryListNode(account: account, chatLocation: .peer(peerId), tagMask: .webPage, messageId: messageId, controllerInteraction: controllerInteraction, selectedMessages: selectedMessages, mode: .list(search: true, reversed: false)) + node.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor node.didEndScrolling = { [weak node] in guard let node = node else { return @@ -134,11 +144,11 @@ class PeerMediaCollectionControllerNode: ASDisplayNode { self.sectionsNode = PeerMediaCollectionSectionsNode(theme: self.presentationData.theme, strings: self.presentationData.strings) - self.historyNode = historyNodeImplForMode(self.mediaCollectionInterfaceState.mode, account: account, peerId: peerId, messageId: messageId, controllerInteraction: controllerInteraction, selectedMessages: self.selectedMessagesPromise.get()) + self.historyNode = historyNodeImplForMode(self.mediaCollectionInterfaceState.mode, account: account, theme: self.presentationData.theme, peerId: peerId, messageId: messageId, controllerInteraction: controllerInteraction, selectedMessages: self.selectedMessagesPromise.get()) self.historyEmptyNode = PeerMediaCollectionEmptyNode(mode: self.mediaCollectionInterfaceState.mode, theme: self.presentationData.theme, strings: self.presentationData.strings) self.historyEmptyNode.isHidden = true - self.chatPresentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, fontSize: self.presentationData.fontSize, accountPeerId: account.peerId, mode: .standard(previewing: false), chatLocation: .peer(self.peerId)) + self.chatPresentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, fontSize: self.presentationData.fontSize, accountPeerId: account.peerId, mode: .standard(previewing: false), chatLocation: .peer(self.peerId)) super.init() @@ -397,7 +407,7 @@ class PeerMediaCollectionControllerNode: ASDisplayNode { if self.mediaCollectionInterfaceState.mode != mediaCollectionInterfaceState.mode { let previousMode = self.mediaCollectionInterfaceState.mode if let containerLayout = self.containerLayout, self.candidateHistoryNode == nil || self.candidateHistoryNode!.1 != mediaCollectionInterfaceState.mode { - let node = historyNodeImplForMode(mediaCollectionInterfaceState.mode, account: self.account, peerId: self.peerId, messageId: nil, controllerInteraction: self.controllerInteraction, selectedMessages: self.selectedMessagesPromise.get()) + let node = historyNodeImplForMode(mediaCollectionInterfaceState.mode, account: self.account, theme: self.presentationData.theme, peerId: self.peerId, messageId: nil, controllerInteraction: self.controllerInteraction, selectedMessages: self.selectedMessagesPromise.get()) node.backgroundColor = mediaCollectionInterfaceState.theme.list.plainBackgroundColor self.candidateHistoryNode = (node, mediaCollectionInterfaceState.mode) diff --git a/TelegramUI/PeerTitle.swift b/TelegramUI/PeerTitle.swift index 6899244da0..363b23637f 100644 --- a/TelegramUI/PeerTitle.swift +++ b/TelegramUI/PeerTitle.swift @@ -3,15 +3,16 @@ import TelegramCore import Postbox extension Peer { - func displayTitle(strings: PresentationStrings) -> String { + func displayTitle(strings: PresentationStrings, displayOrder: PresentationPersonNameOrder) -> String { switch self { case let user as TelegramUser: if let firstName = user.firstName { if let lastName = user.lastName { - if strings.lc == 0x6b6f { - return "\(lastName) \(firstName)" - } else { - return "\(firstName) \(lastName)" + switch displayOrder { + case .firstLast: + return "\(firstName) \(lastName)" + case .lastFirst: + return "\(lastName) \(firstName)" } } else { return firstName diff --git a/TelegramUI/PhoneInputNode.swift b/TelegramUI/PhoneInputNode.swift index 523e740dc0..a5badf6f9b 100644 --- a/TelegramUI/PhoneInputNode.swift +++ b/TelegramUI/PhoneInputNode.swift @@ -113,23 +113,35 @@ final class PhoneInputNode: ASDisplayNode, UITextFieldDelegate { } } - var codeAndNumber: (Int32?, String) { + private var countryNameForCode: (Int32, String)? + + var codeAndNumber: (Int32?, String?, String) { get { var code: Int32? if let text = self.countryCodeField.textField.text, text.count <= 4, let number = Int(removePlus(text)) { code = Int32(number) - return (code, cleanPhoneNumber(self.numberField.textField.text)) + var countryName: String? + if self.countryNameForCode?.0 == code { + countryName = self.countryNameForCode?.1 + } + return (code, countryName, cleanPhoneNumber(self.numberField.textField.text)) } else if let text = self.countryCodeField.textField.text { - return (nil, cleanPhoneNumber(text + (self.numberField.textField.text ?? ""))) + return (nil, nil, cleanPhoneNumber(text + (self.numberField.textField.text ?? ""))) } else { - return (nil, "") + return (nil, nil, "") } } set(value) { - self.updateNumber("+" + (value.0 == nil ? "" : "\(value.0!)") + value.1) + let updatedCountryName = self.countryNameForCode?.0 != value.0 || self.countryNameForCode?.1 != value.1 + if let code = value.0, let name = value.1 { + self.countryNameForCode = (code, name) + } else { + self.countryNameForCode = nil + } + self.updateNumber("+" + (value.0 == nil ? "" : "\(value.0!)") + value.2, forceNotifyCountryCodeUpdated: updatedCountryName) } } - var countryCodeUpdated: ((String) -> Void)? + var countryCodeUpdated: ((String, String?) -> Void)? var countryCodeTextUpdated: ((String) -> Void)? var numberTextUpdated: ((String) -> Void)? @@ -204,7 +216,7 @@ final class PhoneInputNode: ASDisplayNode, UITextFieldDelegate { self.updateNumber(inputText) } - private func updateNumber(_ inputText: String, tryRestoringInputPosition: Bool = true) { + private func updateNumber(_ inputText: String, tryRestoringInputPosition: Bool = true, forceNotifyCountryCodeUpdated: Bool = false) { let (regionPrefix, text) = self.phoneFormatter.updateText(inputText) var realRegionPrefix: String let numberText: String @@ -226,10 +238,14 @@ final class PhoneInputNode: ASDisplayNode, UITextFieldDelegate { if realRegionPrefix != self.countryCodeField.textField.text { self.countryCodeField.textField.text = realRegionPrefix } - if self.previousCountryCodeText != realRegionPrefix { + if self.previousCountryCodeText != realRegionPrefix || forceNotifyCountryCodeUpdated { self.previousCountryCodeText = realRegionPrefix let code = removePlus(realRegionPrefix).trimmingCharacters(in: CharacterSet.whitespaces) - self.countryCodeUpdated?(code) + var countryName: String? + if self.countryNameForCode?.0 == Int32(code) { + countryName = self.countryNameForCode?.1 + } + self.countryCodeUpdated?(code, countryName) } self.countryCodeTextUpdated?(realRegionPrefix) diff --git a/TelegramUI/PhotoResources.swift b/TelegramUI/PhotoResources.swift index a58417bf73..015097e427 100644 --- a/TelegramUI/PhotoResources.swift +++ b/TelegramUI/PhotoResources.swift @@ -88,9 +88,13 @@ private func chatMessagePhotoDatas(postbox: Postbox, photoReference: ImageMediaR return thumbnail |> mapToSignal { thumbnailData in - return fullSizeData - |> map { (fullSizeData, complete) in - return (thumbnailData, fullSizeData, complete) + if let thumbnailData = thumbnailData { + return fullSizeData + |> map { (fullSizeData, complete) in + return (thumbnailData, fullSizeData, complete) + } + } else { + return .single((thumbnailData, nil, false)) } } } diff --git a/TelegramUI/PresentationData.swift b/TelegramUI/PresentationData.swift index 1135dba32a..c1c97ac2ee 100644 --- a/TelegramUI/PresentationData.swift +++ b/TelegramUI/PresentationData.swift @@ -31,9 +31,9 @@ public enum PresentationDateFormat { case dayFirst } -public enum PresentationPersonNameOrder { - case firstLast - case lastFirst +public enum PresentationPersonNameOrder: Int32 { + case firstLast = 0 + case lastFirst = 1 } extension PresentationStrings : Equatable { @@ -41,12 +41,6 @@ extension PresentationStrings : Equatable { return lhs === rhs } } -// -//extension PresentationTheme : Equatable { -// public static func ==(lhs: PresentationTheme, rhs: PresentationTheme) -> Bool { -// return lhs === rhs -// } -//} public final class PresentationData: Equatable { public let strings: PresentationStrings @@ -167,23 +161,6 @@ private func currentPersonNameSortOrder() -> PresentationPersonNameOrder { } } -private func currentPersonNameDisplayOrder() -> PresentationPersonNameOrder { - if #available(iOSApplicationExtension 9.0, *) { - switch CNContactFormatter.nameOrder(for: CNContact()) { - case .givenNameFirst: - return .firstLast - default: - return .lastFirst - } - } else { - if ABPersonGetCompositeNameFormat() == kABPersonCompositeNameFormatFirstNameFirst { - return .firstLast - } else { - return .lastFirst - } - } -} - public final class InitialPresentationDataAndSettings { public let presentationData: PresentationData public let automaticMediaDownloadSettings: AutomaticMediaDownloadSettings @@ -203,7 +180,7 @@ public final class InitialPresentationDataAndSettings { } public func currentPresentationDataAndSettings(postbox: Postbox) -> Signal { - return postbox.transaction { transaction -> (PresentationThemeSettings, LocalizationSettings?, AutomaticMediaDownloadSettings, CallListSettings, InAppNotificationSettings, MediaInputSettings, ExperimentalUISettings) in + return postbox.transaction { transaction -> (PresentationThemeSettings, LocalizationSettings?, AutomaticMediaDownloadSettings, CallListSettings, InAppNotificationSettings, MediaInputSettings, ExperimentalUISettings, ContactSynchronizationSettings) in let themeSettings: PresentationThemeSettings if let current = transaction.getPreferencesEntry(key: ApplicationSpecificPreferencesKeys.presentationThemeSettings) as? PresentationThemeSettings { themeSettings = current @@ -248,9 +225,11 @@ public func currentPresentationDataAndSettings(postbox: Postbox) -> Signal map { (themeSettings, localizationSettings, automaticMediaDownloadSettings, callListSettings, inAppNotificationSettings, mediaInputSettings, experimentalUISettings) -> InitialPresentationDataAndSettings in + |> map { (themeSettings, localizationSettings, automaticMediaDownloadSettings, callListSettings, inAppNotificationSettings, mediaInputSettings, experimentalUISettings, contactSettings) -> InitialPresentationDataAndSettings in let themeValue: PresentationTheme let effectiveTheme: PresentationThemeReference @@ -295,7 +274,7 @@ public func currentPresentationDataAndSettings(postbox: Postbox) -> Signal Signal { - let preferencesKey = PostboxViewKey.preferences(keys: Set([ApplicationSpecificPreferencesKeys.presentationThemeSettings, PreferencesKeys.localizationSettings])) + let preferencesKey = PostboxViewKey.preferences(keys: Set([ApplicationSpecificPreferencesKeys.presentationThemeSettings, PreferencesKeys.localizationSettings, ApplicationSpecificPreferencesKeys.contactSynchronizationSettings])) return postbox.combinedView(keys: [preferencesKey]) |> mapToSignal { view -> Signal in let themeSettings: PresentationThemeSettings @@ -377,6 +356,8 @@ public func updatedPresentationData(postbox: Postbox, applicationBindings: Teleg themeSettings = PresentationThemeSettings.defaultSettings } + let contactSettings: ContactSynchronizationSettings = (view.views[preferencesKey] as! PreferencesView).values[ApplicationSpecificPreferencesKeys.contactSynchronizationSettings] as? ContactSynchronizationSettings ?? ContactSynchronizationSettings.defaultSettings + return applicationBindings.applicationInForeground |> mapToSignal({ inForeground -> Signal in if inForeground { @@ -433,9 +414,9 @@ public func updatedPresentationData(postbox: Postbox, applicationBindings: Teleg } let dateTimeFormat = currentDateTimeFormat() - let nameDisplayOrder = currentPersonNameDisplayOrder() + let nameDisplayOrder = contactSettings.nameDisplayOrder let nameSortOrder = currentPersonNameSortOrder() - + return PresentationData(strings: stringsValue, theme: themeValue, chatWallpaper: effectiveChatWallpaper, volumeControlStatusBarIcons: volumeControlStatusBarIcons(), fontSize: themeSettings.fontSize, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, nameSortOrder: nameSortOrder, disableAnimations: themeSettings.disableAnimations) } } else { @@ -447,7 +428,7 @@ public func updatedPresentationData(postbox: Postbox, applicationBindings: Teleg public func defaultPresentationData() -> PresentationData { let dateTimeFormat = currentDateTimeFormat() - let nameDisplayOrder = currentPersonNameDisplayOrder() + let nameDisplayOrder: PresentationPersonNameOrder = .firstLast let nameSortOrder = currentPersonNameSortOrder() let themeSettings = PresentationThemeSettings.defaultSettings diff --git a/TelegramUI/RecentSessionsController.swift b/TelegramUI/RecentSessionsController.swift index 9c7d919efa..989627d72d 100644 --- a/TelegramUI/RecentSessionsController.swift +++ b/TelegramUI/RecentSessionsController.swift @@ -78,7 +78,7 @@ private enum RecentSessionsEntry: ItemListNodeEntry { case pendingSessionsInfo(PresentationTheme, String) case otherSessionsHeader(PresentationTheme, String) case session(index: Int32, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, session: RecentAccountSession, enabled: Bool, editing: Bool, revealed: Bool) - case website(index: Int32, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, website: WebAuthorization, peer: Peer?, enabled: Bool, editing: Bool, revealed: Bool) + case website(index: Int32, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, website: WebAuthorization, peer: Peer?, enabled: Bool, editing: Bool, revealed: Bool) var section: ItemListSectionId { switch self { @@ -113,7 +113,7 @@ private enum RecentSessionsEntry: ItemListNodeEntry { return .index(7) case let .session(_, _, _, _, session, _, _, _): return .session(session.hash) - case let .website(_, _, _, _, website, _, _, _, _): + case let .website(_, _, _, _, _, website, _, _, _, _): return .session(website.hash) } } @@ -180,8 +180,8 @@ private enum RecentSessionsEntry: ItemListNodeEntry { } else { return false } - case let .website(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsWebsite, lhsPeer, lhsEnabled, lhsEditing, lhsRevealed): - if case let .website(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsWebsite, rhsPeer, rhsEnabled, rhsEditing, rhsRevealed) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsWebsite == rhsWebsite, arePeersEqual(lhsPeer, rhsPeer), lhsEnabled == rhsEnabled, lhsEditing == rhsEditing, lhsRevealed == rhsRevealed { + case let .website(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameOrder, lhsWebsite, lhsPeer, lhsEnabled, lhsEditing, lhsRevealed): + if case let .website(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameOrder, rhsWebsite, rhsPeer, rhsEnabled, rhsEditing, rhsRevealed) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsNameOrder == rhsNameOrder, lhsWebsite == rhsWebsite, arePeersEqual(lhsPeer, rhsPeer), lhsEnabled == rhsEnabled, lhsEditing == rhsEditing, lhsRevealed == rhsRevealed { return true } else { return false @@ -221,8 +221,8 @@ private enum RecentSessionsEntry: ItemListNodeEntry { return false } } - case let .website(lhsIndex, _, _, _, _, _, _, _, _): - if case let .website(rhsIndex, _, _, _, _, _, _, _, _) = rhs { + case let .website(lhsIndex, _, _, _, _, _, _, _, _, _): + if case let .website(rhsIndex, _, _, _, _, _, _, _, _, _) = rhs { return lhsIndex <= rhsIndex } else { return false @@ -269,8 +269,8 @@ private enum RecentSessionsEntry: ItemListNodeEntry { }, removeSession: { id in arguments.removeSession(id) }) - case let .website(_, theme, strings, dateTimeFormat, website, peer, enabled, editing, revealed): - return ItemListWebsiteItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, website: website, peer: peer, enabled: enabled, editing: editing, revealed: revealed, sectionId: self.section, setSessionIdWithRevealedOptions: { previousId, id in + case let .website(_, theme, strings, dateTimeFormat, nameDisplayOrder, website, peer, enabled, editing, revealed): + return ItemListWebsiteItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, website: website, peer: peer, enabled: enabled, editing: editing, revealed: revealed, sectionId: self.section, setSessionIdWithRevealedOptions: { previousId, id in arguments.setSessionIdWithRevealedOptions(previousId, id) }, removeSession: { id in arguments.removeWebSession(id) @@ -397,7 +397,7 @@ private func recentSessionsControllerEntries(presentationData: PresentationData, let website = websites[i] if !existingSessionIds.contains(website.hash) { existingSessionIds.insert(website.hash) - entries.append(.website(index: Int32(i), theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, website: website, peer: peers[website.botId], enabled: state.removingSessionId != website.hash && !state.terminatingOtherSessions, editing: state.editing, revealed: state.sessionIdWithRevealedOptions == website.hash)) + entries.append(.website(index: Int32(i), theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, website: website, peer: peers[website.botId], enabled: state.removingSessionId != website.hash && !state.terminatingOtherSessions, editing: state.editing, revealed: state.sessionIdWithRevealedOptions == website.hash)) } } } diff --git a/TelegramUI/ReplyAccessoryPanelNode.swift b/TelegramUI/ReplyAccessoryPanelNode.swift index e0a38d8b3c..15d8785839 100644 --- a/TelegramUI/ReplyAccessoryPanelNode.swift +++ b/TelegramUI/ReplyAccessoryPanelNode.swift @@ -19,7 +19,7 @@ final class ReplyAccessoryPanelNode: AccessoryPanelNode { var theme: PresentationTheme - init(account: Account, messageId: MessageId, theme: PresentationTheme, strings: PresentationStrings) { + init(account: Account, messageId: MessageId, theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder) { self.messageId = messageId self.theme = theme @@ -62,10 +62,10 @@ final class ReplyAccessoryPanelNode: AccessoryPanelNode { var authorName = "" var text = "" if let author = message?.author { - authorName = author.displayTitle + authorName = author.displayTitle(strings: strings, displayOrder: nameDisplayOrder) } if let message = message { - (text, _) = descriptionStringForMessage(message, strings: strings, accountPeerId: account.peerId) + (text, _) = descriptionStringForMessage(message, strings: strings, nameDisplayOrder: nameDisplayOrder, accountPeerId: account.peerId) } var updatedMediaReference: AnyMediaReference? @@ -131,7 +131,7 @@ final class ReplyAccessoryPanelNode: AccessoryPanelNode { let isMedia: Bool if let message = message { - switch messageContentKind(message, strings: strings, accountPeerId: account.peerId) { + switch messageContentKind(message, strings: strings, nameDisplayOrder: nameDisplayOrder, accountPeerId: account.peerId) { case .text: isMedia = false default: diff --git a/TelegramUI/SecretMediaPreviewController.swift b/TelegramUI/SecretMediaPreviewController.swift index 647a0f5a5b..4c1aff9559 100644 --- a/TelegramUI/SecretMediaPreviewController.swift +++ b/TelegramUI/SecretMediaPreviewController.swift @@ -130,6 +130,8 @@ public final class SecretMediaPreviewController: ViewController { private var messageView: MessageView? private var currentNodeMessageId: MessageId? + private var currentNodeMessageIsVideo = false + private var tempFile: TempBoxFile? private let _hiddenMedia = Promise<(MessageId, Media)?>(nil) private var hiddenMediaManagerIndex: Int? @@ -149,6 +151,8 @@ public final class SecretMediaPreviewController: ViewController { self.statusBar.statusBarStyle = .White + + self.disposable.set((account.postbox.messageView(messageId) |> deliverOnMainQueue).start(next: { [weak self] view in if let strongSelf = self { strongSelf.messageView = view @@ -192,6 +196,9 @@ public final class SecretMediaPreviewController: ViewController { mediaManager.galleryHiddenMediaManager.removeSource(hiddenMediaManagerIndex) } self.screenCaptureEventsDisposable?.dispose() + if let tempFile = self.tempFile { + TempBox.shared.dispose(tempFile) + } } @objc func donePressed() { @@ -205,7 +212,7 @@ public final class SecretMediaPreviewController: ViewController { } }, dismissController: { [weak self] in self?.dismiss(forceAway: true) - }, replaceRootController: { [weak self] _, _ in + }, replaceRootController: { _, _ in }) self.displayNode = SecretMediaPreviewControllerNode(controllerInteraction: controllerInteraction) self.displayNodeDidLoad() @@ -215,7 +222,7 @@ public final class SecretMediaPreviewController: ViewController { self.controllerNode.transitionDataForCentralItem = { [weak self] in if let strongSelf = self { - if let centralItemNode = strongSelf.controllerNode.pager.centralItemNode(), let presentationArguments = strongSelf.presentationArguments as? GalleryControllerPresentationArguments { + if let _ = strongSelf.controllerNode.pager.centralItemNode(), let presentationArguments = strongSelf.presentationArguments as? GalleryControllerPresentationArguments { if let message = strongSelf.messageView?.message { if let media = mediaForMessage(message: message), let transitionArguments = presentationArguments.transitionArguments(message.id, media) { return (transitionArguments.transitionNode, transitionArguments.addToTransitionSurface) @@ -234,7 +241,7 @@ public final class SecretMediaPreviewController: ViewController { if let strongSelf = self { strongSelf._hiddenMedia.set(.single(nil)) - var animatedOutNode = true + let animatedOutNode = true var animatedOutInterface = false let completion = { @@ -261,10 +268,20 @@ public final class SecretMediaPreviewController: ViewController { if let _ = index { if let message = strongSelf.messageView?.message, let media = mediaForMessage(message: message) { var beginTimeAndTimeout: (Double, Double)? + var videoDuration: Int32? + for media in message.media { + if let file = media as? TelegramMediaFile { + videoDuration = file.duration + } + } for attribute in message.attributes { if let attribute = attribute as? AutoremoveTimeoutMessageAttribute { if let countdownBeginTime = attribute.countdownBeginTime { - beginTimeAndTimeout = (Double(countdownBeginTime), Double(attribute.timeout)) + if let videoDuration = videoDuration { + beginTimeAndTimeout = (CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970, Double(videoDuration)) + } else { + beginTimeAndTimeout = (Double(countdownBeginTime), Double(attribute.timeout)) + } } break } @@ -302,7 +319,7 @@ public final class SecretMediaPreviewController: ViewController { } else { text = strongSelf.presentationData.strings.SecretImage_NotViewedYet(peerTitle).0 } - contentNode.setText(text) + contentNode.setText(text) strongSelf.controllerNode.updatePresentationState({ $0.withUpdatedFooterContentNode(contentNode) }, transition: .immediate) @@ -328,7 +345,6 @@ public final class SecretMediaPreviewController: ViewController { var nodeAnimatesItself = false if let centralItemNode = self.controllerNode.pager.centralItemNode(), let message = self.messageView?.message { - if let media = mediaForMessage(message: message) { if let presentationArguments = self.presentationArguments as? GalleryControllerPresentationArguments, let transitionArguments = presentationArguments.transitionArguments(message.id, media) { nodeAnimatesItself = true @@ -394,7 +410,20 @@ public final class SecretMediaPreviewController: ViewController { if let message = message { if self.currentNodeMessageId != message.id { self.currentNodeMessageId = message.id - guard let item = galleryItemForEntry(account: account, presentationData: self.presentationData, entry: .MessageEntry(message, false, nil, nil), streamVideos: false, hideControls: true, playbackCompleted: { [weak self] in + var tempFilePath: String? + for media in message.media { + if let file = media as? TelegramMediaFile { + if let path = self.account.postbox.mediaBox.completedResourcePath(file.resource) { + let tempFile = TempBox.shared.file(path: path, fileName: file.fileName ?? "file") + self.tempFile = tempFile + tempFilePath = tempFile.path + self.currentNodeMessageIsVideo = true + } + break + } + } + + guard let item = galleryItemForEntry(account: self.account, presentationData: self.presentationData, entry: .MessageEntry(message, false, nil, nil), streamVideos: false, hideControls: true, tempFilePath: tempFilePath, playbackCompleted: { [weak self] in self?.dismiss(forceAway: false) }) else { self._ready.set(.single(true)) @@ -409,10 +438,20 @@ public final class SecretMediaPreviewController: ViewController { self.markMessageAsConsumedDisposable.set(markMessageContentAsConsumedInteractively(postbox: self.account.postbox, messageId: message.id).start()) } else { var beginTimeAndTimeout: (Double, Double)? + var videoDuration: Int32? + for media in message.media { + if let file = media as? TelegramMediaFile { + videoDuration = file.duration + } + } for attribute in message.attributes { if let attribute = attribute as? AutoremoveTimeoutMessageAttribute { if let countdownBeginTime = attribute.countdownBeginTime { - beginTimeAndTimeout = (Double(countdownBeginTime), Double(attribute.timeout)) + if let videoDuration = videoDuration { + beginTimeAndTimeout = (CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970, Double(videoDuration)) + } else { + beginTimeAndTimeout = (Double(countdownBeginTime), Double(attribute.timeout)) + } } break } @@ -428,7 +467,9 @@ public final class SecretMediaPreviewController: ViewController { if !self.didSetReady { self._ready.set(.single(true)) } - self.dismiss() + if !self.currentNodeMessageIsVideo { + self.dismiss() + } } } diff --git a/TelegramUI/SelectivePrivacySettingsPeersController.swift b/TelegramUI/SelectivePrivacySettingsPeersController.swift index e8cd7af072..a48963a038 100644 --- a/TelegramUI/SelectivePrivacySettingsPeersController.swift +++ b/TelegramUI/SelectivePrivacySettingsPeersController.swift @@ -56,7 +56,7 @@ private enum SelectivePrivacyPeersEntryStableId: Hashable { } private enum SelectivePrivacyPeersEntry: ItemListNodeEntry { - case peerItem(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, ItemListPeerItemEditing, Bool) + case peerItem(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer, ItemListPeerItemEditing, Bool) case addItem(PresentationTheme, String, Bool) var section: ItemListSectionId { @@ -70,7 +70,7 @@ private enum SelectivePrivacyPeersEntry: ItemListNodeEntry { var stableId: SelectivePrivacyPeersEntryStableId { switch self { - case let .peerItem(_, _, _, _, peer, _, _): + case let .peerItem(_, _, _, _, _, peer, _, _): return .peer(peer.id) case .addItem: return .add @@ -79,8 +79,8 @@ private enum SelectivePrivacyPeersEntry: ItemListNodeEntry { static func ==(lhs: SelectivePrivacyPeersEntry, rhs: SelectivePrivacyPeersEntry) -> Bool { switch lhs { - case let .peerItem(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsEditing, lhsEnabled): - if case let .peerItem(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsEditing, rhsEnabled) = rhs { + case let .peerItem(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameOrder, lhsPeer, lhsEditing, lhsEnabled): + if case let .peerItem(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameOrder, rhsPeer, rhsEditing, rhsEnabled) = rhs { if lhsIndex != rhsIndex { return false } @@ -96,6 +96,9 @@ private enum SelectivePrivacyPeersEntry: ItemListNodeEntry { if lhsDateTimeFormat != rhsDateTimeFormat { return false } + if lhsNameOrder != rhsNameOrder { + return false + } if lhsEditing != rhsEditing { return false } @@ -117,9 +120,9 @@ private enum SelectivePrivacyPeersEntry: ItemListNodeEntry { static func <(lhs: SelectivePrivacyPeersEntry, rhs: SelectivePrivacyPeersEntry) -> Bool { switch lhs { - case let .peerItem(index, _, _, _, _, _, _): + case let .peerItem(index, _, _, _, _, _, _, _): switch rhs { - case let .peerItem(rhsIndex, _, _, _, _, _, _): + case let .peerItem(rhsIndex, _, _, _, _, _, _, _): return index < rhsIndex case .addItem: return true @@ -131,8 +134,8 @@ private enum SelectivePrivacyPeersEntry: ItemListNodeEntry { func item(_ arguments: SelectivePrivacyPeersControllerArguments) -> ListViewItem { switch self { - case let .peerItem(_, theme, strings, dateTimeFormat, peer, editing, enabled): - return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, account: arguments.account, peer: peer, presence: nil, text: .none, label: .none, editing: editing, switchValue: nil, enabled: enabled, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { previousId, id in + case let .peerItem(_, theme, strings, dateTimeFormat, nameDisplayOrder, peer, editing, enabled): + return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, account: arguments.account, peer: peer, presence: nil, text: .none, label: .none, editing: editing, switchValue: nil, enabled: enabled, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { previousId, id in arguments.setPeerIdWithRevealedOptions(previousId, id) }, removePeer: { peerId in arguments.removePeer(peerId) @@ -183,7 +186,7 @@ private func selectivePrivacyPeersControllerEntries(presentationData: Presentati var index: Int32 = 0 for peer in peers { - entries.append(.peerItem(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, ItemListPeerItemEditing(editable: true, editing: state.editing, revealed: peer.id == state.peerIdWithRevealedOptions), true)) + entries.append(.peerItem(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, peer, ItemListPeerItemEditing(editable: true, editing: state.editing, revealed: peer.id == state.peerIdWithRevealedOptions), true)) index += 1 } diff --git a/TelegramUI/ShareInputFieldNode.swift b/TelegramUI/ShareInputFieldNode.swift index d2a10aae92..0a1a8d549e 100644 --- a/TelegramUI/ShareInputFieldNode.swift +++ b/TelegramUI/ShareInputFieldNode.swift @@ -81,11 +81,9 @@ final class ShareInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegate { self.textInputNode = EditableTextNode() let textColor: UIColor = theme.textColor - let keyboardAppearance: UIKeyboardAppearance = UIKeyboardAppearance.default self.textInputNode.typingAttributes = [NSAttributedStringKey.font.rawValue: Font.regular(17.0), NSAttributedStringKey.foregroundColor.rawValue: textColor] self.textInputNode.clipsToBounds = true self.textInputNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0) - self.textInputNode.keyboardAppearance = keyboardAppearance self.textInputNode.textContainerInset = UIEdgeInsets(top: self.inputInsets.top, left: 0.0, bottom: self.inputInsets.bottom, right: 0.0) self.textInputNode.keyboardAppearance = theme.keyboardAppearance diff --git a/TelegramUI/SoftwareVideoSource.swift b/TelegramUI/SoftwareVideoSource.swift index ce7f658b0b..207066774a 100644 --- a/TelegramUI/SoftwareVideoSource.swift +++ b/TelegramUI/SoftwareVideoSource.swift @@ -1,7 +1,7 @@ import Foundation import CoreMedia -import TelegramUIPrivateModule import SwiftSignalKit +import FFMpeg private func readPacketCallback(userData: UnsafeMutableRawPointer?, buffer: UnsafeMutablePointer?, bufferSize: Int32) -> Int32 { let context = Unmanaged.fromOpaque(userData!).takeUnretainedValue() @@ -14,7 +14,7 @@ private func readPacketCallback(userData: UnsafeMutableRawPointer?, buffer: Unsa private func seekCallback(userData: UnsafeMutableRawPointer?, offset: Int64, whence: Int32) -> Int64 { let context = Unmanaged.fromOpaque(userData!).takeUnretainedValue() if let fd = context.fd { - if (whence & AVSEEK_SIZE) != 0 { + if (whence & FFMPEG_AVSEEK_SIZE) != 0 { return Int64(context.size) } else { lseek(fd, off_t(offset), SEEK_SET) @@ -47,8 +47,8 @@ private final class SoftwareVideoStream { final class SoftwareVideoSource { private var readingError = false private var videoStream: SoftwareVideoStream? - private var avIoContext: UnsafeMutablePointer? - private var avFormatContext: UnsafeMutablePointer? + private var avIoContext: FFMpegAVIOContext? + private var avFormatContext: FFMpegAVFormatContext? private let path: String fileprivate let fd: Int32? fileprivate let size: Int32 @@ -69,25 +69,21 @@ final class SoftwareVideoSource { self.path = path - var avFormatContextRef = avformat_alloc_context() - guard let avFormatContext = avFormatContextRef else { - self.readingError = true - return - } + let avFormatContext = FFMpegAVFormatContext() let ioBufferSize = 64 * 1024 - let avIoBuffer = av_malloc(ioBufferSize)! - let avIoContextRef = avio_alloc_context(avIoBuffer.assumingMemoryBound(to: UInt8.self), Int32(ioBufferSize), 0, Unmanaged.passUnretained(self).toOpaque(), readPacketCallback, nil, seekCallback) - self.avIoContext = avIoContextRef - avFormatContext.pointee.pb = self.avIoContext + let avIoContext = FFMpegAVIOContext(bufferSize: Int32(ioBufferSize), opaqueContext: Unmanaged.passUnretained(self).toOpaque(), readPacket: readPacketCallback, seek: seekCallback) + self.avIoContext = avIoContext - guard avformat_open_input(&avFormatContextRef, nil, nil, nil) >= 0 else { + avFormatContext.setIO(self.avIoContext!) + + if !avFormatContext.openInput() { self.readingError = true return } - guard avformat_find_stream_info(avFormatContext, nil) >= 0 else { + if !avFormatContext.findStreamInfo() { self.readingError = true return } @@ -96,40 +92,30 @@ final class SoftwareVideoSource { var videoStream: SoftwareVideoStream? - for streamIndex in FFMpegMediaFrameSourceContextHelpers.streamIndices(formatContext: avFormatContext, codecType: AVMEDIA_TYPE_VIDEO) { - if (avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!.pointee.disposition & Int32(AV_DISPOSITION_ATTACHED_PIC)) == 0 { - - let codecPar = avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!.pointee.codecpar! - - if let codec = avcodec_find_decoder(codecPar.pointee.codec_id) { - if let codecContext = avcodec_alloc_context3(codec) { - if avcodec_parameters_to_context(codecContext, avFormatContext.pointee.streams[streamIndex]!.pointee.codecpar) >= 0 { - if avcodec_open2(codecContext, codec, nil) >= 0 { - let (fps, timebase) = FFMpegMediaFrameSourceContextHelpers.streamFpsAndTimeBase(stream: avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!, defaultTimeBase: CMTimeMake(1, 24)) - - let duration = CMTimeMake(avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!.pointee.duration, timebase.timescale) - - var rotationAngle: Double = 0.0 - if let rotationInfo = av_dict_get(avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!.pointee.metadata, "rotate", nil, 0), let value = rotationInfo.pointee.value { - if strcmp(value, "0") != 0 { - if let angle = Double(String(cString: value)) { - rotationAngle = angle * Double.pi / 180.0 - } - } - } - - let aspect = Double(codecPar.pointee.width) / Double(codecPar.pointee.height) - - videoStream = SoftwareVideoStream(index: streamIndex, fps: fps, timebase: timebase, duration: duration, decoder: FFMpegMediaVideoFrameDecoder(codecContext: codecContext), rotationAngle: rotationAngle, aspect: aspect) - break - } else { - var codecContextRef: UnsafeMutablePointer? = codecContext - avcodec_free_context(&codecContextRef) - } - } else { - var codecContextRef: UnsafeMutablePointer? = codecContext - avcodec_free_context(&codecContextRef) - } + for streamIndexNumber in avFormatContext.streamIndices(for: FFMpegAVFormatStreamTypeVideo) { + let streamIndex = streamIndexNumber.int32Value + if avFormatContext.isAttachedPic(atStreamIndex: streamIndex) { + continue + } + + let codecId = avFormatContext.codecId(atStreamIndex: streamIndex) + + let fpsAndTimebase = avFormatContext.fpsAndTimebase(forStreamIndex: streamIndex, defaultTimeBase: CMTimeMake(1, 40000)) + let (fps, timebase) = (fpsAndTimebase.fps, fpsAndTimebase.timebase) + + let duration = CMTimeMake(avFormatContext.duration(atStreamIndex: streamIndex), timebase.timescale) + + let metrics = avFormatContext.metricsForStream(at: streamIndex) + + let rotationAngle: Double = metrics.rotationAngle + let aspect = Double(metrics.width) / Double(metrics.height) + + if let codec = FFMpegAVCodec.find(forId: codecId) { + let codecContext = FFMpegAVCodecContext(codec: codec) + if avFormatContext.codecParams(atStreamIndex: streamIndex, to: codecContext) { + if codecContext.open() { + videoStream = SoftwareVideoStream(index: Int(streamIndex), fps: fps, timebase: timebase, duration: duration, decoder: FFMpegMediaVideoFrameDecoder(codecContext: codecContext), rotationAngle: rotationAngle, aspect: aspect) + break } } } @@ -139,15 +125,6 @@ final class SoftwareVideoSource { } deinit { - if let avIoContext = self.avIoContext { - if avIoContext.pointee.buffer != nil { - av_free(avIoContext.pointee.buffer) - } - av_free(avIoContext) - } - if let avFormatContext = self.avFormatContext { - avformat_free_context(avFormatContext) - } if let fd = self.fd { close(fd) } @@ -159,10 +136,10 @@ final class SoftwareVideoSource { } let packet = FFMpegPacket() - if av_read_frame(avFormatContext, &packet.packet) < 0 { - return nil - } else { + if avFormatContext.readFrame(into: packet) { return packet + } else { + return nil } } @@ -172,17 +149,15 @@ final class SoftwareVideoSource { while !self.readingError && frames.isEmpty { if let packet = self.readPacketInternal() { - if let videoStream = videoStream, Int(packet.packet.stream_index) == videoStream.index { - let avNoPtsRawValue: UInt64 = 0x8000000000000000 - let avNoPtsValue = Int64(bitPattern: avNoPtsRawValue) - let packetPts = packet.packet.pts == avNoPtsValue ? packet.packet.dts : packet.packet.pts + if let videoStream = videoStream, Int(packet.streamIndex) == videoStream.index { + let packetPts = packet.pts let pts = CMTimeMake(packetPts, videoStream.timebase.timescale) - let dts = CMTimeMake(packet.packet.dts, videoStream.timebase.timescale) + let dts = CMTimeMake(packet.dts, videoStream.timebase.timescale) let duration: CMTime - let frameDuration = packet.packet.duration + let frameDuration = packet.duration if frameDuration != 0 { duration = CMTimeMake(frameDuration * videoStream.timebase.value, videoStream.timebase.timescale) } else { @@ -198,7 +173,7 @@ final class SoftwareVideoSource { } else { if let avFormatContext = self.avFormatContext, let videoStream = self.videoStream { endOfStream = true - av_seek_frame(avFormatContext, Int32(videoStream.index), 0, AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_FRAME) + avFormatContext.seekFrame(forStreamIndex: Int32(videoStream.index), pts: 0) } else { endOfStream = true break diff --git a/TelegramUI/StorageUsageController.swift b/TelegramUI/StorageUsageController.swift index d166900bdc..69fb4e64f5 100644 --- a/TelegramUI/StorageUsageController.swift +++ b/TelegramUI/StorageUsageController.swift @@ -36,7 +36,7 @@ private enum StorageUsageEntry: ItemListNodeEntry { case clearAll(PresentationTheme, String, String, Bool) case peersHeader(PresentationTheme, String) - case peer(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, Peer?, String) + case peer(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer, Peer?, String) var section: ItemListSectionId { switch self { @@ -65,7 +65,7 @@ private enum StorageUsageEntry: ItemListNodeEntry { return 4 case .peersHeader: return 5 - case let .peer(index, _, _, _, _, _, _): + case let .peer(index, _, _, _, _, _, _, _): return 6 + index } } @@ -108,8 +108,8 @@ private enum StorageUsageEntry: ItemListNodeEntry { } else { return false } - case let .peer(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsChatPeer, lhsValue): - if case let .peer(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsChatPeer, rhsValue) = rhs { + case let .peer(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameOrder, lhsPeer, lhsChatPeer, lhsValue): + if case let .peer(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameOrder, rhsPeer, rhsChatPeer, rhsValue) = rhs { if lhsIndex != rhsIndex { return false } @@ -122,6 +122,9 @@ private enum StorageUsageEntry: ItemListNodeEntry { if lhsDateTimeFormat != rhsDateTimeFormat { return false } + if lhsNameOrder != rhsNameOrder { + return false + } if !arePeersEqual(lhsPeer, rhsPeer) { return false } @@ -160,8 +163,8 @@ private enum StorageUsageEntry: ItemListNodeEntry { return ItemListDisclosureItem(theme: theme, icon: nil, title: text, kind: enabled ? .generic : .disabled, label: value, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { arguments.openClearAll() }) - case let .peer(_, theme, strings, dateTimeFormat, peer, chatPeer, value): - return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, account: arguments.account, peer: peer, aliasHandling: .threatSelfAsSaved, nameColor: chatPeer == nil ? .primary : .secret, presence: nil, text: .none, label: .disclosure(value), editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, sectionId: self.section, action: { + case let .peer(_, theme, strings, dateTimeFormat, nameDisplayOrder, peer, chatPeer, value): + return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, account: arguments.account, peer: peer, aliasHandling: .threatSelfAsSaved, nameColor: chatPeer == nil ? .primary : .secret, presence: nil, text: .none, label: .disclosure(value), editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, sectionId: self.section, action: { let resolvedPeer = chatPeer ?? peer arguments.openPeerMedia(resolvedPeer.id) }, setPeerIdWithRevealedOptions: { previousId, id in @@ -223,7 +226,7 @@ private func storageUsageControllerEntries(presentationData: PresentationData, c chatPeer = mainPeer mainPeer = associatedPeer } - entries.append(.peer(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, mainPeer, chatPeer, dataSizeString(size))) + entries.append(.peer(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, mainPeer, chatPeer, dataSizeString(size))) index += 1 } } diff --git a/TelegramUI/StringForMessageTimestampStatus.swift b/TelegramUI/StringForMessageTimestampStatus.swift index fcebfcb945..3b8b979805 100644 --- a/TelegramUI/StringForMessageTimestampStatus.swift +++ b/TelegramUI/StringForMessageTimestampStatus.swift @@ -7,13 +7,13 @@ enum MessageTimestampStatusFormat { case minimal } -func stringForMessageTimestampStatus(message: Message, dateTimeFormat: PresentationDateTimeFormat, strings: PresentationStrings, format: MessageTimestampStatusFormat = .regular) -> String { +func stringForMessageTimestampStatus(message: Message, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, strings: PresentationStrings, format: MessageTimestampStatusFormat = .regular) -> String { var dateText = stringForMessageTimestamp(timestamp: message.timestamp, dateTimeFormat: dateTimeFormat) var authorTitle: String? if let author = message.author as? TelegramUser { if let peer = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = peer.info { - authorTitle = author.displayTitle + authorTitle = author.displayTitle(strings: strings, displayOrder: nameDisplayOrder) } } else { if let peer = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = peer.info { diff --git a/TelegramUI/TelegramApplicationContext.swift b/TelegramUI/TelegramApplicationContext.swift index 602ad0f823..8cd6dd4ef6 100644 --- a/TelegramUI/TelegramApplicationContext.swift +++ b/TelegramUI/TelegramApplicationContext.swift @@ -229,6 +229,14 @@ public final class TelegramApplicationContext { let _ = immediateExperimentalUISettingsValue.swap(settings) } }) + + let _ = self.contactDataManager.personNameDisplayOrder().start(next: { order in + let _ = updateContactSettingsInteractively(postbox: postbox, { settings in + var settings = settings + settings.nameDisplayOrder = order + return settings + }).start() + }) } deinit { diff --git a/TelegramUI/TelegramUIPrivate/module.modulemap b/TelegramUI/TelegramUIPrivate/module.modulemap index cab446b48e..0fd83698be 100644 --- a/TelegramUI/TelegramUIPrivate/module.modulemap +++ b/TelegramUI/TelegramUIPrivate/module.modulemap @@ -1,10 +1,11 @@ module TelegramUIPrivateModule { - private header "../../third-party/FFmpeg-iOS/include/libavcodec/avcodec.h" - private header "../../third-party/FFmpeg-iOS/include/libavformat/avformat.h" - private header "../../third-party/FFmpeg-iOS/include/libavformat/avio.h" - private header "../../third-party/FFmpeg-iOS/include/libavutil/avutil.h" - private header "../../third-party/FFmpeg-iOS/include/libavutil/pixdesc.h" - private header "../../third-party/FFmpeg-iOS/include/libswresample/swresample.h" + //private header "../../third-party/FFmpeg-iOS/include/libavcodec/avcodec.h" + //private header "../../third-party/FFmpeg-iOS/include/libavformat/avformat.h" + //private header "../../third-party/FFmpeg-iOS/include/libavformat/avio.h" + //private header "../../third-party/FFmpeg-iOS/include/libavutil/avutil.h" + //private header "../../third-party/FFmpeg-iOS/include/libavutil/pixdesc.h" + //private header "../../third-party/FFmpeg-iOS/include/libswresample/swresample.h" + header "../../third-party/opusenc/opusenc.h" header "../TGDataItem.h" header "../FFMpegSwResample.h" diff --git a/TelegramUI/ThemeSettingsChatPreviewItem.swift b/TelegramUI/ThemeSettingsChatPreviewItem.swift index fdab5d4468..2caa50dc87 100644 --- a/TelegramUI/ThemeSettingsChatPreviewItem.swift +++ b/TelegramUI/ThemeSettingsChatPreviewItem.swift @@ -14,8 +14,9 @@ class ThemeSettingsChatPreviewItem: ListViewItem, ItemListItem { let fontSize: PresentationFontSize let wallpaper: TelegramWallpaper let dateTimeFormat: PresentationDateTimeFormat + let nameDisplayOrder: PresentationPersonNameOrder - init(account: Account, theme: PresentationTheme, componentTheme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId, fontSize: PresentationFontSize, wallpaper: TelegramWallpaper, dateTimeFormat: PresentationDateTimeFormat) { + init(account: Account, theme: PresentationTheme, componentTheme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId, fontSize: PresentationFontSize, wallpaper: TelegramWallpaper, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder) { self.account = account self.theme = theme self.componentTheme = componentTheme @@ -24,6 +25,7 @@ class ThemeSettingsChatPreviewItem: ListViewItem, ItemListItem { self.fontSize = fontSize self.wallpaper = wallpaper self.dateTimeFormat = dateTimeFormat + self.nameDisplayOrder = nameDisplayOrder } func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { @@ -152,7 +154,7 @@ class ThemeSettingsChatPreviewItemNode: ListViewItemNode { let replyMessageId = MessageId(peerId: peerId, namespace: 0, id: 3) messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: item.strings.Appearance_PreviewReplyText, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - let chatPresentationData = ChatPresentationData(theme: ChatPresentationThemeData(theme: item.componentTheme, wallpaper: item.wallpaper), fontSize: item.fontSize, strings: item.strings, dateTimeFormat: item.dateTimeFormat, disableAnimations: false) + let chatPresentationData = ChatPresentationData(theme: ChatPresentationThemeData(theme: item.componentTheme, wallpaper: item.wallpaper), fontSize: item.fontSize, strings: item.strings, dateTimeFormat: item.dateTimeFormat, nameDisplayOrder: item.nameDisplayOrder, disableAnimations: false) let item2: ChatMessageItem = ChatMessageItem(presentationData: chatPresentationData, account: item.account, chatLocation: .peer(peerId), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .contact, automaticDownloadNetworkType: .cellular, isRecentActions: false), controllerInteraction: controllerInteraction, content: .message(message: Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: nil, text: item.strings.Appearance_PreviewIncomingText, attributes: [ReplyMessageAttribute(messageId: replyMessageId)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), read: true, selection: .none, isAdmin: false), disableDate: true) let item1: ChatMessageItem = ChatMessageItem(presentationData: chatPresentationData, account: item.account, chatLocation: .peer(peerId), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .contact, automaticDownloadNetworkType: .cellular, isRecentActions: false), controllerInteraction: controllerInteraction, content: .message(message: Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: TelegramUser(id: item.account.peerId, accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []), text: item.strings.Appearance_PreviewOutgoingText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), read: true, selection: .none, isAdmin: false), disableDate: true) diff --git a/TelegramUI/ThemeSettingsController.swift b/TelegramUI/ThemeSettingsController.swift index 70b5e9b0d4..cd5a2dbead 100644 --- a/TelegramUI/ThemeSettingsController.swift +++ b/TelegramUI/ThemeSettingsController.swift @@ -35,7 +35,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { case fontSizeHeader(PresentationTheme, String) case fontSize(PresentationTheme, PresentationFontSize) case chatPreviewHeader(PresentationTheme, String) - case chatPreview(PresentationTheme, PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationStrings, PresentationDateTimeFormat) + case chatPreview(PresentationTheme, PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder) case wallpaper(PresentationTheme, String) case accentColor(PresentationTheme, String, Int32) case autoNightTheme(PresentationTheme, String, String) @@ -95,8 +95,8 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { } else { return false } - case let .chatPreview(lhsTheme, lhsComponentTheme, lhsWallpaper, lhsFontSize, lhsStrings, lhsTimeFormat): - if case let .chatPreview(rhsTheme, rhsComponentTheme, rhsWallpaper, rhsFontSize, rhsStrings, rhsTimeFormat) = rhs, lhsComponentTheme === rhsComponentTheme, lhsTheme === rhsTheme, lhsWallpaper == rhsWallpaper, lhsFontSize == rhsFontSize, lhsStrings === rhsStrings, lhsTimeFormat == rhsTimeFormat { + case let .chatPreview(lhsTheme, lhsComponentTheme, lhsWallpaper, lhsFontSize, lhsStrings, lhsTimeFormat, lhsNameOrder): + if case let .chatPreview(rhsTheme, rhsComponentTheme, rhsWallpaper, rhsFontSize, rhsStrings, rhsTimeFormat, rhsNameOrder) = rhs, lhsComponentTheme === rhsComponentTheme, lhsTheme === rhsTheme, lhsWallpaper == rhsWallpaper, lhsFontSize == rhsFontSize, lhsStrings === rhsStrings, lhsTimeFormat == rhsTimeFormat, lhsNameOrder == rhsNameOrder { return true } else { return false @@ -178,8 +178,8 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { }) case let .chatPreviewHeader(theme, text): return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) - case let .chatPreview(theme, componentTheme, wallpaper, fontSize, strings, dateTimeFormat): - return ThemeSettingsChatPreviewItem(account: arguments.account, theme: theme, componentTheme: componentTheme, strings: strings, sectionId: self.section, fontSize: fontSize, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat) + case let .chatPreview(theme, componentTheme, wallpaper, fontSize, strings, dateTimeFormat, nameDisplayOrder): + return ThemeSettingsChatPreviewItem(account: arguments.account, theme: theme, componentTheme: componentTheme, strings: strings, sectionId: self.section, fontSize: fontSize, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder) case let .wallpaper(theme, text): return ItemListDisclosureItem(theme: theme, title: text, label: "", sectionId: self.section, style: .blocks, action: { arguments.openWallpaperSettings() @@ -216,7 +216,7 @@ private func themeSettingsControllerEntries(presentationData: PresentationData, entries.append(.fontSizeHeader(presentationData.theme, strings.Appearance_TextSize)) entries.append(.fontSize(presentationData.theme, fontSize)) entries.append(.chatPreviewHeader(presentationData.theme, strings.Appearance_Preview)) - entries.append(.chatPreview(presentationData.theme, theme, wallpaper, fontSize, presentationData.strings, dateTimeFormat)) + entries.append(.chatPreview(presentationData.theme, theme, wallpaper, fontSize, presentationData.strings, dateTimeFormat, presentationData.nameDisplayOrder)) entries.append(.wallpaper(presentationData.theme, strings.Settings_ChatBackground)) if theme.name == .builtin(.day) { entries.append(.accentColor(presentationData.theme, strings.Appearance_AccentColor, themeAccentColor ?? defaultDayAccentColor)) diff --git a/third-party/FFmpeg-iOS/lib/libavcodec.a b/third-party/FFmpeg-iOS/lib/libavcodec.a index f522a21d97..260d47d0c2 100644 Binary files a/third-party/FFmpeg-iOS/lib/libavcodec.a and b/third-party/FFmpeg-iOS/lib/libavcodec.a differ diff --git a/third-party/FFmpeg-iOS/lib/libavformat.a b/third-party/FFmpeg-iOS/lib/libavformat.a index 25d5b11d6a..46a855c630 100644 Binary files a/third-party/FFmpeg-iOS/lib/libavformat.a and b/third-party/FFmpeg-iOS/lib/libavformat.a differ diff --git a/third-party/FFmpeg-iOS/lib/libavutil.a b/third-party/FFmpeg-iOS/lib/libavutil.a index 767ce34fc9..f59375aa3c 100644 Binary files a/third-party/FFmpeg-iOS/lib/libavutil.a and b/third-party/FFmpeg-iOS/lib/libavutil.a differ diff --git a/third-party/FFmpeg-iOS/lib/libswresample.a b/third-party/FFmpeg-iOS/lib/libswresample.a index 8ac11e47cf..dbbcf0362d 100644 Binary files a/third-party/FFmpeg-iOS/lib/libswresample.a and b/third-party/FFmpeg-iOS/lib/libswresample.a differ