mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 06:35:51 +00:00
no message
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
D0105D5A1D80B957008755D8 /* ChatChannelSubscriberInputPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0105D591D80B957008755D8 /* ChatChannelSubscriberInputPanelNode.swift */; };
|
||||
D02958021D6F0D5F00360E5E /* TapLongTapOrDoubleTapGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02958011D6F0D5F00360E5E /* TapLongTapOrDoubleTapGestureRecognizer.swift */; };
|
||||
D03ADB481D703268005A521C /* ChatInterfaceState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03ADB471D703268005A521C /* ChatInterfaceState.swift */; };
|
||||
D03ADB4B1D70443F005A521C /* ReplyAccessoryPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03ADB4A1D70443F005A521C /* ReplyAccessoryPanelNode.swift */; };
|
||||
@@ -30,6 +31,14 @@
|
||||
D0D2686E1D7898A900C422DA /* ChatMessageSelectionNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D2686D1D7898A900C422DA /* ChatMessageSelectionNode.swift */; };
|
||||
D0D2689A1D79CF9F00C422DA /* ChatPanelInterfaceInteraction.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D268991D79CF9F00C422DA /* ChatPanelInterfaceInteraction.swift */; };
|
||||
D0D2689D1D79D33E00C422DA /* ShareRecipientsActionSheetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D2689C1D79D33E00C422DA /* ShareRecipientsActionSheetController.swift */; };
|
||||
D0DF0C951D81B063008AEB01 /* ChatInterfaceStateContextMenus.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0C941D81B063008AEB01 /* ChatInterfaceStateContextMenus.swift */; };
|
||||
D0DF0C981D81FF28008AEB01 /* HashtagChatInputContextPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0C971D81FF28008AEB01 /* HashtagChatInputContextPanelNode.swift */; };
|
||||
D0DF0C9A1D81FF3F008AEB01 /* ChatInputContextPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0C991D81FF3F008AEB01 /* ChatInputContextPanelNode.swift */; };
|
||||
D0DF0C9C1D81FFB2008AEB01 /* ChatInterfaceInputContextPanels.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0C9B1D81FFB2008AEB01 /* ChatInterfaceInputContextPanels.swift */; };
|
||||
D0DF0C9E1D82141F008AEB01 /* ChatInterfaceInputContexts.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0C9D1D82141F008AEB01 /* ChatInterfaceInputContexts.swift */; };
|
||||
D0DF0CA11D821B28008AEB01 /* HashtagsTableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0CA01D821B28008AEB01 /* HashtagsTableCell.swift */; };
|
||||
D0DF0CA41D82BCD0008AEB01 /* MentionChatInputContextPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0CA31D82BCD0008AEB01 /* MentionChatInputContextPanelNode.swift */; };
|
||||
D0DF0CA61D82BCE0008AEB01 /* MentionsTableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0CA51D82BCE0008AEB01 /* MentionsTableCell.swift */; };
|
||||
D0F69D231D6B87D30046BCD6 /* FFMpegMediaFrameSourceContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69CD31D6B87D30046BCD6 /* FFMpegMediaFrameSourceContext.swift */; };
|
||||
D0F69D241D6B87D30046BCD6 /* MediaPlayerAudioRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69CD41D6B87D30046BCD6 /* MediaPlayerAudioRenderer.swift */; };
|
||||
D0F69D261D6B87D30046BCD6 /* MediaManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69CD61D6B87D30046BCD6 /* MediaManager.swift */; };
|
||||
@@ -75,7 +84,7 @@
|
||||
D0F69DF31D6B8A6C0046BCD6 /* AuthorizationPasswordControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69DEC1D6B8A6C0046BCD6 /* AuthorizationPasswordControllerNode.swift */; };
|
||||
D0F69DF41D6B8A6C0046BCD6 /* AuthorizationPhoneController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69DED1D6B8A6C0046BCD6 /* AuthorizationPhoneController.swift */; };
|
||||
D0F69DF51D6B8A6C0046BCD6 /* AuthorizationPhoneControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69DEE1D6B8A6C0046BCD6 /* AuthorizationPhoneControllerNode.swift */; };
|
||||
D0F69DFE1D6B8A880046BCD6 /* ChatListAvatarNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69DF71D6B8A880046BCD6 /* ChatListAvatarNode.swift */; };
|
||||
D0F69DFE1D6B8A880046BCD6 /* AvatarNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69DF71D6B8A880046BCD6 /* AvatarNode.swift */; };
|
||||
D0F69DFF1D6B8A880046BCD6 /* ChatListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69DF81D6B8A880046BCD6 /* ChatListController.swift */; };
|
||||
D0F69E001D6B8A880046BCD6 /* ChatListControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69DF91D6B8A880046BCD6 /* ChatListControllerNode.swift */; };
|
||||
D0F69E011D6B8A880046BCD6 /* ChatListEmptyItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69DFA1D6B8A880046BCD6 /* ChatListEmptyItem.swift */; };
|
||||
@@ -145,7 +154,6 @@
|
||||
D0F69E8E1D6B8C850046BCD6 /* RingBuffer.h in Headers */ = {isa = PBXBuildFile; fileRef = D0F69E851D6B8C850046BCD6 /* RingBuffer.h */; };
|
||||
D0F69E8F1D6B8C850046BCD6 /* RingBuffer.m in Sources */ = {isa = PBXBuildFile; fileRef = D0F69E861D6B8C850046BCD6 /* RingBuffer.m */; };
|
||||
D0F69E901D6B8C850046BCD6 /* RingByteBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69E871D6B8C850046BCD6 /* RingByteBuffer.swift */; };
|
||||
D0F69E951D6B8C9B0046BCD6 /* ImageRepresentationsUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69E921D6B8C9B0046BCD6 /* ImageRepresentationsUtils.swift */; };
|
||||
D0F69E961D6B8C9B0046BCD6 /* ProgressiveImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69E931D6B8C9B0046BCD6 /* ProgressiveImage.swift */; };
|
||||
D0F69E971D6B8C9B0046BCD6 /* WebP.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69E941D6B8C9B0046BCD6 /* WebP.swift */; };
|
||||
D0F69E9A1D6B8D200046BCD6 /* UIImage+WebP.h in Headers */ = {isa = PBXBuildFile; fileRef = D0F69E981D6B8D200046BCD6 /* UIImage+WebP.h */; };
|
||||
@@ -175,6 +183,7 @@
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
D0105D591D80B957008755D8 /* ChatChannelSubscriberInputPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatChannelSubscriberInputPanelNode.swift; sourceTree = "<group>"; };
|
||||
D02958011D6F0D5F00360E5E /* TapLongTapOrDoubleTapGestureRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TapLongTapOrDoubleTapGestureRecognizer.swift; sourceTree = "<group>"; };
|
||||
D03ADB471D703268005A521C /* ChatInterfaceState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatInterfaceState.swift; sourceTree = "<group>"; };
|
||||
D03ADB4A1D70443F005A521C /* ReplyAccessoryPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplyAccessoryPanelNode.swift; sourceTree = "<group>"; };
|
||||
@@ -201,6 +210,14 @@
|
||||
D0D2686D1D7898A900C422DA /* ChatMessageSelectionNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMessageSelectionNode.swift; sourceTree = "<group>"; };
|
||||
D0D268991D79CF9F00C422DA /* ChatPanelInterfaceInteraction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatPanelInterfaceInteraction.swift; sourceTree = "<group>"; };
|
||||
D0D2689C1D79D33E00C422DA /* ShareRecipientsActionSheetController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShareRecipientsActionSheetController.swift; sourceTree = "<group>"; };
|
||||
D0DF0C941D81B063008AEB01 /* ChatInterfaceStateContextMenus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatInterfaceStateContextMenus.swift; sourceTree = "<group>"; };
|
||||
D0DF0C971D81FF28008AEB01 /* HashtagChatInputContextPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HashtagChatInputContextPanelNode.swift; sourceTree = "<group>"; };
|
||||
D0DF0C991D81FF3F008AEB01 /* ChatInputContextPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatInputContextPanelNode.swift; sourceTree = "<group>"; };
|
||||
D0DF0C9B1D81FFB2008AEB01 /* ChatInterfaceInputContextPanels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatInterfaceInputContextPanels.swift; sourceTree = "<group>"; };
|
||||
D0DF0C9D1D82141F008AEB01 /* ChatInterfaceInputContexts.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatInterfaceInputContexts.swift; sourceTree = "<group>"; };
|
||||
D0DF0CA01D821B28008AEB01 /* HashtagsTableCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HashtagsTableCell.swift; sourceTree = "<group>"; };
|
||||
D0DF0CA31D82BCD0008AEB01 /* MentionChatInputContextPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MentionChatInputContextPanelNode.swift; sourceTree = "<group>"; };
|
||||
D0DF0CA51D82BCE0008AEB01 /* MentionsTableCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MentionsTableCell.swift; sourceTree = "<group>"; };
|
||||
D0F69CD31D6B87D30046BCD6 /* FFMpegMediaFrameSourceContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FFMpegMediaFrameSourceContext.swift; sourceTree = "<group>"; };
|
||||
D0F69CD41D6B87D30046BCD6 /* MediaPlayerAudioRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaPlayerAudioRenderer.swift; sourceTree = "<group>"; };
|
||||
D0F69CD61D6B87D30046BCD6 /* MediaManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaManager.swift; sourceTree = "<group>"; };
|
||||
@@ -246,7 +263,7 @@
|
||||
D0F69DEC1D6B8A6C0046BCD6 /* AuthorizationPasswordControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizationPasswordControllerNode.swift; sourceTree = "<group>"; };
|
||||
D0F69DED1D6B8A6C0046BCD6 /* AuthorizationPhoneController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizationPhoneController.swift; sourceTree = "<group>"; };
|
||||
D0F69DEE1D6B8A6C0046BCD6 /* AuthorizationPhoneControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizationPhoneControllerNode.swift; sourceTree = "<group>"; };
|
||||
D0F69DF71D6B8A880046BCD6 /* ChatListAvatarNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatListAvatarNode.swift; sourceTree = "<group>"; };
|
||||
D0F69DF71D6B8A880046BCD6 /* AvatarNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarNode.swift; sourceTree = "<group>"; };
|
||||
D0F69DF81D6B8A880046BCD6 /* ChatListController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatListController.swift; sourceTree = "<group>"; };
|
||||
D0F69DF91D6B8A880046BCD6 /* ChatListControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatListControllerNode.swift; sourceTree = "<group>"; };
|
||||
D0F69DFA1D6B8A880046BCD6 /* ChatListEmptyItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatListEmptyItem.swift; sourceTree = "<group>"; };
|
||||
@@ -316,7 +333,6 @@
|
||||
D0F69E851D6B8C850046BCD6 /* RingBuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RingBuffer.h; sourceTree = "<group>"; };
|
||||
D0F69E861D6B8C850046BCD6 /* RingBuffer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RingBuffer.m; sourceTree = "<group>"; };
|
||||
D0F69E871D6B8C850046BCD6 /* RingByteBuffer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RingByteBuffer.swift; sourceTree = "<group>"; };
|
||||
D0F69E921D6B8C9B0046BCD6 /* ImageRepresentationsUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageRepresentationsUtils.swift; sourceTree = "<group>"; };
|
||||
D0F69E931D6B8C9B0046BCD6 /* ProgressiveImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProgressiveImage.swift; sourceTree = "<group>"; };
|
||||
D0F69E941D6B8C9B0046BCD6 /* WebP.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebP.swift; sourceTree = "<group>"; };
|
||||
D0F69E981D6B8D200046BCD6 /* UIImage+WebP.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+WebP.h"; sourceTree = "<group>"; };
|
||||
@@ -379,6 +395,9 @@
|
||||
D03ADB4C1D7045C9005A521C /* ChatInterfaceStateAccessoryPanels.swift */,
|
||||
D0BA6F841D784ECD0034826E /* ChatInterfaceStateInputPanels.swift */,
|
||||
D0D268661D78793B00C422DA /* ChatInterfaceStateNavigationButtons.swift */,
|
||||
D0DF0C941D81B063008AEB01 /* ChatInterfaceStateContextMenus.swift */,
|
||||
D0DF0C9D1D82141F008AEB01 /* ChatInterfaceInputContexts.swift */,
|
||||
D0DF0C9B1D81FFB2008AEB01 /* ChatInterfaceInputContextPanels.swift */,
|
||||
);
|
||||
name = "Interface State";
|
||||
sourceTree = "<group>";
|
||||
@@ -419,19 +438,12 @@
|
||||
children = (
|
||||
D0BA6F821D784C520034826E /* ChatInputPanelNode.swift */,
|
||||
D0F69E3F1D6B8B6B0046BCD6 /* Text Input */,
|
||||
D0BA6F861D784F700034826E /* Message Selection */,
|
||||
D0BA6F871D784F880034826E /* ChatMessageSelectionInputPanelNode.swift */,
|
||||
D0105D591D80B957008755D8 /* ChatChannelSubscriberInputPanelNode.swift */,
|
||||
);
|
||||
name = "Input Panels";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D0BA6F861D784F700034826E /* Message Selection */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D0BA6F871D784F880034826E /* ChatMessageSelectionInputPanelNode.swift */,
|
||||
);
|
||||
name = "Message Selection";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D0D2686A1D788F6600C422DA /* Navigation Accessory Panels */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -448,6 +460,34 @@
|
||||
name = "Share Recipients";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D0DF0C961D81FD87008AEB01 /* Input Context Panels */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D0DF0C991D81FF3F008AEB01 /* ChatInputContextPanelNode.swift */,
|
||||
D0DF0C9F1D8219C7008AEB01 /* Hashtags */,
|
||||
D0DF0CA21D82BCBC008AEB01 /* Mentions */,
|
||||
);
|
||||
name = "Input Context Panels";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D0DF0C9F1D8219C7008AEB01 /* Hashtags */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D0DF0C971D81FF28008AEB01 /* HashtagChatInputContextPanelNode.swift */,
|
||||
D0DF0CA01D821B28008AEB01 /* HashtagsTableCell.swift */,
|
||||
);
|
||||
name = Hashtags;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D0DF0CA21D82BCBC008AEB01 /* Mentions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D0DF0CA31D82BCD0008AEB01 /* MentionChatInputContextPanelNode.swift */,
|
||||
D0DF0CA51D82BCE0008AEB01 /* MentionsTableCell.swift */,
|
||||
);
|
||||
name = Mentions;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D0F69CCE1D6B87950046BCD6 /* Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -517,6 +557,7 @@
|
||||
D0F69DC41D6B89E10046BCD6 /* RadialProgressNode.swift */,
|
||||
D0F69DC21D6B89DA0046BCD6 /* TextNode.swift */,
|
||||
D0F69DC01D6B89D30046BCD6 /* ListSectionHeaderNode.swift */,
|
||||
D0F69DF71D6B8A880046BCD6 /* AvatarNode.swift */,
|
||||
D0F69DCA1D6B89F20046BCD6 /* Search */,
|
||||
);
|
||||
name = Nodes;
|
||||
@@ -595,7 +636,6 @@
|
||||
D0F69DF61D6B8A720046BCD6 /* Chat List */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D0F69DF71D6B8A880046BCD6 /* ChatListAvatarNode.swift */,
|
||||
D0F69DF81D6B8A880046BCD6 /* ChatListController.swift */,
|
||||
D0F69DF91D6B8A880046BCD6 /* ChatListControllerNode.swift */,
|
||||
D0F69DFA1D6B8A880046BCD6 /* ChatListEmptyItem.swift */,
|
||||
@@ -637,6 +677,7 @@
|
||||
D0F69E181D6B8AD10046BCD6 /* Items */,
|
||||
D03ADB461D703250005A521C /* Interface State */,
|
||||
D03ADB491D704427005A521C /* Accessory Panels */,
|
||||
D0DF0C961D81FD87008AEB01 /* Input Context Panels */,
|
||||
D0D2686A1D788F6600C422DA /* Navigation Accessory Panels */,
|
||||
D0BA6F811D784C3A0034826E /* Input Panels */,
|
||||
D0F69E441D6B8B850046BCD6 /* History Navigation */,
|
||||
@@ -787,7 +828,6 @@
|
||||
D0F69E911D6B8C8E0046BCD6 /* Utils */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D0F69E921D6B8C9B0046BCD6 /* ImageRepresentationsUtils.swift */,
|
||||
D0F69E931D6B8C9B0046BCD6 /* ProgressiveImage.swift */,
|
||||
D0F69E941D6B8C9B0046BCD6 /* WebP.swift */,
|
||||
);
|
||||
@@ -971,6 +1011,7 @@
|
||||
D0B417C31D7DE54E004562A4 /* ChatPresentationInterfaceState.swift in Sources */,
|
||||
D0F69E3C1D6B8B030046BCD6 /* ChatMessageTextBubbleContentNode.swift in Sources */,
|
||||
D03ADB4D1D7045C9005A521C /* ChatInterfaceStateAccessoryPanels.swift in Sources */,
|
||||
D0DF0C9A1D81FF3F008AEB01 /* ChatInputContextPanelNode.swift in Sources */,
|
||||
D0D2689D1D79D33E00C422DA /* ShareRecipientsActionSheetController.swift in Sources */,
|
||||
D0F69E171D6B8ACF0046BCD6 /* ChatHistoryLocation.swift in Sources */,
|
||||
D0F69E741D6B8C340046BCD6 /* ContactsControllerNode.swift in Sources */,
|
||||
@@ -995,7 +1036,6 @@
|
||||
D0F69E0C1D6B8AB10046BCD6 /* HorizontalPeerItem.swift in Sources */,
|
||||
D0F69E551D6B8BDA0046BCD6 /* GalleryController.swift in Sources */,
|
||||
D0F69E571D6B8BDA0046BCD6 /* GalleryItem.swift in Sources */,
|
||||
D0F69E951D6B8C9B0046BCD6 /* ImageRepresentationsUtils.swift in Sources */,
|
||||
D0F69D231D6B87D30046BCD6 /* FFMpegMediaFrameSourceContext.swift in Sources */,
|
||||
D0F69E431D6B8B7E0046BCD6 /* ResizeableTextInputView.swift in Sources */,
|
||||
D0F69E8D1D6B8C850046BCD6 /* Localizable.swift in Sources */,
|
||||
@@ -1006,20 +1046,26 @@
|
||||
D0F69E1A1D6B8AE60046BCD6 /* ChatHoleItem.swift in Sources */,
|
||||
D0F69D9C1D6B87EC0046BCD6 /* MediaPlaybackData.swift in Sources */,
|
||||
D0F69D241D6B87D30046BCD6 /* MediaPlayerAudioRenderer.swift in Sources */,
|
||||
D0DF0CA41D82BCD0008AEB01 /* MentionChatInputContextPanelNode.swift in Sources */,
|
||||
D0F69D4B1D6B87D30046BCD6 /* TouchDownGestureRecognizer.swift in Sources */,
|
||||
D0F69E3D1D6B8B030046BCD6 /* ChatMessageWebpageBubbleContentNode.swift in Sources */,
|
||||
D0DF0C9E1D82141F008AEB01 /* ChatInterfaceInputContexts.swift in Sources */,
|
||||
D0D2686E1D7898A900C422DA /* ChatMessageSelectionNode.swift in Sources */,
|
||||
D0F69E8B1D6B8C850046BCD6 /* FFMpegSwResample.m in Sources */,
|
||||
D0F69DD21D6B8A0D0046BCD6 /* SearchDisplayControllerContentNode.swift in Sources */,
|
||||
D0105D5A1D80B957008755D8 /* ChatChannelSubscriberInputPanelNode.swift in Sources */,
|
||||
D0F69DC51D6B89E10046BCD6 /* RadialProgressNode.swift in Sources */,
|
||||
D0F69E491D6B8BAC0046BCD6 /* ActionSheetRollImageItem.swift in Sources */,
|
||||
D0F69E761D6B8C340046BCD6 /* ContactsSearchContainerNode.swift in Sources */,
|
||||
D0DF0CA61D82BCE0008AEB01 /* MentionsTableCell.swift in Sources */,
|
||||
D0F69E011D6B8A880046BCD6 /* ChatListEmptyItem.swift in Sources */,
|
||||
D0F69E591D6B8BDA0046BCD6 /* GalleryPagerNode.swift in Sources */,
|
||||
D0F69E391D6B8B030046BCD6 /* ChatMessageMediaBubbleContentNode.swift in Sources */,
|
||||
D0F69D351D6B87D30046BCD6 /* MediaFrameSource.swift in Sources */,
|
||||
D0F69E371D6B8B030046BCD6 /* ChatMessageItem.swift in Sources */,
|
||||
D0DF0C9C1D81FFB2008AEB01 /* ChatInterfaceInputContextPanels.swift in Sources */,
|
||||
D0F69E641D6B8BF90046BCD6 /* ChatVideoGalleryItem.swift in Sources */,
|
||||
D0DF0CA11D821B28008AEB01 /* HashtagsTableCell.swift in Sources */,
|
||||
D0F69E351D6B8B030046BCD6 /* ChatMessageInteractiveFileNode.swift in Sources */,
|
||||
D0F69E151D6B8ACF0046BCD6 /* ChatControllerNode.swift in Sources */,
|
||||
D0F69E001D6B8A880046BCD6 /* ChatListControllerNode.swift in Sources */,
|
||||
@@ -1036,7 +1082,7 @@
|
||||
D0F69E0A1D6B8AA60046BCD6 /* ChatListSearchRecentPeersNode.swift in Sources */,
|
||||
D0F69E3E1D6B8B030046BCD6 /* ChatUnreadItem.swift in Sources */,
|
||||
D0F69D771D6B87DF0046BCD6 /* FFMpegMediaPassthroughVideoFrameDecoder.swift in Sources */,
|
||||
D0F69DFE1D6B8A880046BCD6 /* ChatListAvatarNode.swift in Sources */,
|
||||
D0F69DFE1D6B8A880046BCD6 /* AvatarNode.swift in Sources */,
|
||||
D0F69E9B1D6B8D200046BCD6 /* UIImage+WebP.m in Sources */,
|
||||
D0F69E581D6B8BDA0046BCD6 /* GalleryItemNode.swift in Sources */,
|
||||
D0F69DAD1D6B87EC0046BCD6 /* Cache.swift in Sources */,
|
||||
@@ -1046,6 +1092,7 @@
|
||||
D0F69E381D6B8B030046BCD6 /* ChatMessageItemView.swift in Sources */,
|
||||
D0D268671D78793B00C422DA /* ChatInterfaceStateNavigationButtons.swift in Sources */,
|
||||
D0F69E901D6B8C850046BCD6 /* RingByteBuffer.swift in Sources */,
|
||||
D0DF0C981D81FF28008AEB01 /* HashtagChatInputContextPanelNode.swift in Sources */,
|
||||
D0F69E731D6B8C340046BCD6 /* ContactsController.swift in Sources */,
|
||||
D0F69D261D6B87D30046BCD6 /* MediaManager.swift in Sources */,
|
||||
D0F69D2C1D6B87D30046BCD6 /* MediaPlayerNode.swift in Sources */,
|
||||
@@ -1058,6 +1105,7 @@
|
||||
D0F69E8C1D6B8C850046BCD6 /* FrameworkBundle.swift in Sources */,
|
||||
D0F69D661D6B87D30046BCD6 /* FFMpegMediaFrameSourceContextHelpers.swift in Sources */,
|
||||
D0F69DD11D6B8A0D0046BCD6 /* SearchDisplayController.swift in Sources */,
|
||||
D0DF0C951D81B063008AEB01 /* ChatInterfaceStateContextMenus.swift in Sources */,
|
||||
D0F69DF21D6B8A6C0046BCD6 /* AuthorizationPasswordController.swift in Sources */,
|
||||
D0F69E8F1D6B8C850046BCD6 /* RingBuffer.m in Sources */,
|
||||
D0F69DF31D6B8A6C0046BCD6 /* AuthorizationPasswordControllerNode.swift in Sources */,
|
||||
|
||||
@@ -19,44 +19,44 @@ public class AuthorizationController: NavigationController {
|
||||
|
||||
self.pushViewController(phoneController, animated: false)
|
||||
|
||||
let authorizationSequence = phoneController.result |> mapToSignal { (account, sentCode, phone) -> Signal<Api.auth.Authorization, NoError> in
|
||||
let authorizationSequence = phoneController.result |> mapToSignal { (account, sentCode, phone) -> Signal<(Api.auth.Authorization, UnauthorizedAccount), NoError> in
|
||||
return deferred { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.account = account
|
||||
let codeController = AuthorizationCodeController(account: account, phone: phone, sentCode: sentCode)
|
||||
strongSelf.pushViewController(codeController, animated: true)
|
||||
|
||||
return codeController.result |> mapToSignal { result -> Signal<Api.auth.Authorization, NoError> in
|
||||
return codeController.result |> mapToSignal { result -> Signal<(Api.auth.Authorization, UnauthorizedAccount), NoError> in
|
||||
switch result {
|
||||
case let .Authorization(authorization):
|
||||
return single(authorization, NoError.self)
|
||||
return single((authorization, account), NoError.self)
|
||||
case .Password:
|
||||
return deferred { [weak self] () -> Signal<Api.auth.Authorization, NoError> in
|
||||
return deferred { [weak self] () -> Signal<(Api.auth.Authorization, UnauthorizedAccount), NoError> in
|
||||
if let strongSelf = self {
|
||||
let passwordController = AuthorizationPasswordController(account: account)
|
||||
strongSelf.pushViewController(passwordController, animated: true)
|
||||
|
||||
return passwordController.result
|
||||
return passwordController.result |> map { ($0, account) }
|
||||
} else {
|
||||
return complete(Api.auth.Authorization.self, NoError.self)
|
||||
return .complete()
|
||||
}
|
||||
} |> runOn(Queue.mainQueue())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return complete(Api.auth.Authorization.self, NoError.self)
|
||||
return .complete()
|
||||
}
|
||||
} |> runOn(Queue.mainQueue())
|
||||
}
|
||||
|
||||
let accountSignal = authorizationSequence |> mapToSignal { [weak self] authorization -> Signal<Account, NoError> in
|
||||
let accountSignal = authorizationSequence |> mapToSignal { [weak self] authorization, account -> Signal<Account, NoError> in
|
||||
if let strongSelf = self {
|
||||
switch authorization {
|
||||
case let .authorization(user):
|
||||
let user = TelegramUser(user: user)
|
||||
|
||||
return account.postbox.modify { modifier -> AccountState in
|
||||
let state = AuthorizedAccountState(masterDatacenterId: strongSelf.account.masterDatacenterId, peerId: user.id, state: nil)
|
||||
let state = AuthorizedAccountState(masterDatacenterId: account.masterDatacenterId, peerId: user.id, state: nil)
|
||||
modifier.setState(state)
|
||||
return state
|
||||
} |> map { state -> Account in
|
||||
|
||||
@@ -5,7 +5,7 @@ import UIKit
|
||||
import Display
|
||||
import TelegramCore
|
||||
|
||||
private class ChatListAvatarNodeParameters: NSObject {
|
||||
private class AvatarNodeParameters: NSObject {
|
||||
let account: Account
|
||||
let peerId: PeerId
|
||||
let letters: [String]
|
||||
@@ -30,28 +30,28 @@ let gradientColors: [NSArray] = [
|
||||
[UIColor(0xd669ed).cgColor, UIColor(0xe0a2f3).cgColor]
|
||||
]
|
||||
|
||||
private enum ChatListAvatarNodeState: Equatable {
|
||||
private enum AvatarNodeState: Equatable {
|
||||
case Empty
|
||||
case PeerAvatar(Peer)
|
||||
case PeerAvatar(PeerId, [String], TelegramMediaImageRepresentation?)
|
||||
}
|
||||
|
||||
private func ==(lhs: ChatListAvatarNodeState, rhs: ChatListAvatarNodeState) -> Bool {
|
||||
private func ==(lhs: AvatarNodeState, rhs: AvatarNodeState) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.Empty, .Empty):
|
||||
return true
|
||||
case let (.PeerAvatar(lhsPeer), .PeerAvatar(rhsPeer)) where lhsPeer.isEqual(rhsPeer):
|
||||
return true
|
||||
case let (.PeerAvatar(lhsPeerId, lhsLetters, lhsPhotoRepresentations), .PeerAvatar(rhsPeerId, rhsLetters, rhsPhotoRepresentations)):
|
||||
return lhsPeerId == rhsPeerId && lhsLetters == rhsLetters && lhsPhotoRepresentations == rhsPhotoRepresentations
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public final class ChatListAvatarNode: ASDisplayNode {
|
||||
public final class AvatarNode: ASDisplayNode {
|
||||
var font: UIFont {
|
||||
didSet {
|
||||
if oldValue !== font {
|
||||
if let parameters = self.parameters {
|
||||
self.parameters = ChatListAvatarNodeParameters(account: parameters.account, peerId: parameters.peerId, letters: parameters.letters, font: self.font)
|
||||
self.parameters = AvatarNodeParameters(account: parameters.account, peerId: parameters.peerId, letters: parameters.letters, font: self.font)
|
||||
}
|
||||
|
||||
if !self.displaySuspended {
|
||||
@@ -60,10 +60,10 @@ public final class ChatListAvatarNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
}
|
||||
private var parameters: ChatListAvatarNodeParameters?
|
||||
private var parameters: AvatarNodeParameters?
|
||||
let imageNode: ImageNode
|
||||
|
||||
private var state: ChatListAvatarNodeState = .Empty
|
||||
private var state: AvatarNodeState = .Empty
|
||||
|
||||
public init(font: UIFont) {
|
||||
self.font = font
|
||||
@@ -92,11 +92,11 @@ public final class ChatListAvatarNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
public func setPeer(account: Account, peer: Peer) {
|
||||
let updatedState = ChatListAvatarNodeState.PeerAvatar(peer)
|
||||
let updatedState = AvatarNodeState.PeerAvatar(peer.id, peer.displayLetters, peer.smallProfileImage)
|
||||
if updatedState != self.state {
|
||||
self.state = updatedState
|
||||
|
||||
let parameters = ChatListAvatarNodeParameters(account: account, peerId: peer.id, letters: peer.displayLetters, font: self.font)
|
||||
let parameters = AvatarNodeParameters(account: account, peerId: peer.id, letters: peer.displayLetters, font: self.font)
|
||||
|
||||
self.displaySuspended = true
|
||||
self.contents = nil
|
||||
@@ -134,7 +134,7 @@ public final class ChatListAvatarNode: ASDisplayNode {
|
||||
context.clip()
|
||||
|
||||
let colorIndex: Int
|
||||
if let parameters = parameters as? ChatListAvatarNodeParameters {
|
||||
if let parameters = parameters as? AvatarNodeParameters {
|
||||
colorIndex = Int(parameters.account.peerId.id + parameters.peerId.id)
|
||||
} else {
|
||||
colorIndex = 0
|
||||
@@ -149,11 +149,9 @@ public final class ChatListAvatarNode: ASDisplayNode {
|
||||
|
||||
context.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: 0.0, y: bounds.size.height), options: CGGradientDrawingOptions())
|
||||
|
||||
//CGContextDrawRadialGradient(context, gradient, CGPoint(x: bounds.size.width * 0.5, y: -bounds.size.width * 0.2), 0.0, CGPoint(x: bounds.midX, y: bounds.midY), bounds.width, CGGradientDrawingOptions())
|
||||
|
||||
context.setBlendMode(.normal)
|
||||
|
||||
if let parameters = parameters as? ChatListAvatarNodeParameters {
|
||||
if let parameters = parameters as? AvatarNodeParameters {
|
||||
let letters = parameters.letters
|
||||
let string = letters.count == 0 ? "" : (letters[0] + (letters.count == 1 ? "" : letters[1]))
|
||||
let attributedString = NSAttributedString(string: string, attributes: [NSFontAttributeName: parameters.font, NSForegroundColorAttributeName: UIColor.white])
|
||||
@@ -161,19 +159,9 @@ public final class ChatListAvatarNode: ASDisplayNode {
|
||||
let line = CTLineCreateWithAttributedString(attributedString)
|
||||
let lineBounds = CTLineGetBoundsWithOptions(line, .useGlyphPathBounds)
|
||||
|
||||
/*var ascent: CGFloat = 0.0
|
||||
var descent: CGFloat = 0.0
|
||||
var leading: CGFloat = 0.0
|
||||
let lineWidth = CGFloat(CTLineGetTypographicBounds(line, &ascent, &descent, &leading))
|
||||
let opticalBounds = CGRect(origin: CGPoint(), size: CGSize(width: lineWidth, height: ascent + descent + leading))*/
|
||||
|
||||
//let opticalBounds = CTLineGetImageBounds(line, context)
|
||||
|
||||
let lineOffset = CGPoint(x: string == "B" ? 1.0 : 0.0, y: 0.0)
|
||||
let lineOrigin = CGPoint(x: floorToScreenPixels(-lineBounds.origin.x + (bounds.size.width - lineBounds.size.width) / 2.0) + lineOffset.x, y: floorToScreenPixels(-lineBounds.origin.y + (bounds.size.height - lineBounds.size.height) / 2.0))
|
||||
|
||||
//let lineOrigin = CGPoint(x: floorToScreenPixels(-opticalBounds.origin.x + (bounds.size.width - opticalBounds.size.width) / 2.0), y: floorToScreenPixels(-opticalBounds.origin.y + (bounds.size.height - opticalBounds.size.height) / 2.0))
|
||||
|
||||
context.translateBy(x: bounds.size.width / 2.0, y: bounds.size.height / 2.0)
|
||||
context.scaleBy(x: 1.0, y: -1.0)
|
||||
context.translateBy(x: -bounds.size.width / 2.0, y: -bounds.size.height / 2.0)
|
||||
@@ -181,13 +169,6 @@ public final class ChatListAvatarNode: ASDisplayNode {
|
||||
context.translateBy(x: lineOrigin.x, y: lineOrigin.y)
|
||||
CTLineDraw(line, context)
|
||||
context.translateBy(x: -lineOrigin.x, y: -lineOrigin.y)
|
||||
|
||||
/*var attributes: [String : AnyObject] = [:]
|
||||
attributes[NSFontAttributeName] = parameters.font
|
||||
attributes[NSForegroundColorAttributeName] = UIColor.whiteColor()
|
||||
let lettersSize = string.sizeWithAttributes(attributes)
|
||||
|
||||
string.drawAtPoint(CGPoint(x: floor((bounds.size.width - lettersSize.width) / 2.0), y: floor((bounds.size.height - lettersSize.height) / 2.0)), withAttributes: attributes)*/
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,10 @@ private let normalFont = Font.medium(16.0)
|
||||
private let smallFont = Font.medium(12.0)
|
||||
|
||||
final class ChatAvatarNavigationNode: ASDisplayNode {
|
||||
let avatarNode: ChatListAvatarNode
|
||||
let avatarNode: AvatarNode
|
||||
|
||||
override init() {
|
||||
self.avatarNode = ChatListAvatarNode(font: normalFont)
|
||||
self.avatarNode = AvatarNode(font: normalFont)
|
||||
|
||||
super.init()
|
||||
|
||||
|
||||
138
TelegramUI/ChatChannelSubscriberInputPanelNode.swift
Normal file
138
TelegramUI/ChatChannelSubscriberInputPanelNode.swift
Normal file
@@ -0,0 +1,138 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import TelegramCore
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
|
||||
private enum SubscriberAction {
|
||||
case join
|
||||
case kicked
|
||||
case muteNotifications
|
||||
case unmuteNotifications
|
||||
}
|
||||
|
||||
private func titleAndColorForAction(_ action: SubscriberAction) -> (String, UIColor) {
|
||||
switch action {
|
||||
case .join:
|
||||
return ("Join", UIColor(0x1195f2))
|
||||
case .kicked:
|
||||
return ("Join", UIColor.gray)
|
||||
case .muteNotifications:
|
||||
return ("Mute", UIColor(0x1195f2))
|
||||
case .unmuteNotifications:
|
||||
return ("Unmute", UIColor(0x1195f2))
|
||||
}
|
||||
}
|
||||
|
||||
private func actionForPeer(_ peer: Peer) -> SubscriberAction? {
|
||||
if let channel = peer as? TelegramChannel {
|
||||
switch channel.participationStatus {
|
||||
case .kicked:
|
||||
return .kicked
|
||||
case .left:
|
||||
return .join
|
||||
case .member:
|
||||
return .muteNotifications
|
||||
}
|
||||
return .join
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
|
||||
private let button: UIButton
|
||||
private let activityIndicator: UIActivityIndicatorView
|
||||
|
||||
private var action: SubscriberAction?
|
||||
|
||||
private let actionDisposable = MetaDisposable()
|
||||
|
||||
override var peer: Peer? {
|
||||
didSet {
|
||||
if let peer = self.peer, oldValue == nil || !peer.isEqual(oldValue!) {
|
||||
if let action = actionForPeer(peer) {
|
||||
self.action = action
|
||||
let (title, color) = titleAndColorForAction(action)
|
||||
self.button.setTitle(title, for: [])
|
||||
self.button.setTitleColor(color, for: [.normal])
|
||||
self.button.setTitleColor(color.withAlphaComponent(0.5), for: [.highlighted])
|
||||
self.button.sizeToFit()
|
||||
self.setNeedsLayout()
|
||||
} else {
|
||||
self.action = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override init() {
|
||||
self.button = UIButton()
|
||||
self.activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
|
||||
self.activityIndicator.isHidden = true
|
||||
|
||||
super.init()
|
||||
|
||||
self.view.addSubview(self.button)
|
||||
self.view.addSubview(self.activityIndicator)
|
||||
|
||||
button.addTarget(self, action: #selector(self.buttonPressed), for: [.touchUpInside])
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.actionDisposable.dispose()
|
||||
}
|
||||
|
||||
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
|
||||
return CGSize(width: constrainedSize.width, height: 45.0)
|
||||
}
|
||||
|
||||
override func layout() {
|
||||
super.layout()
|
||||
|
||||
let bounds = self.bounds
|
||||
|
||||
let buttonSize = self.button.bounds.size
|
||||
self.button.frame = CGRect(origin: CGPoint(x: floor((bounds.size.width - buttonSize.width) / 2.0), y: floor((bounds.size.height - buttonSize.height) / 2.0)), size: buttonSize)
|
||||
|
||||
//_activityIndicator.frame = CGRectMake(self.frame.size.width - _activityIndicator.frame.size.width - 12.0f, CGFloor((self.frame.size.height - _activityIndicator.frame.size.height) / 2.0f), _activityIndicator.frame.size.width, _activityIndicator.frame.size.height);
|
||||
let indicatorSize = self.activityIndicator.bounds.size
|
||||
self.activityIndicator.frame = CGRect(origin: CGPoint(x: bounds.size.width - indicatorSize.width - 12.0, y: floor((bounds.size.height - indicatorSize.height) / 2.0)), size: indicatorSize)
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if self.bounds.contains(point) {
|
||||
return self.button
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@objc func buttonPressed() {
|
||||
guard let account = self.account, let action = self.action, let peer = self.peer else {
|
||||
return
|
||||
}
|
||||
|
||||
switch action {
|
||||
case .join:
|
||||
self.activityIndicator.isHidden = false
|
||||
self.activityIndicator.startAnimating()
|
||||
self.actionDisposable.set((joinChannel(account: account, peerId: peer.id)
|
||||
|> afterDisposed { [weak self] in
|
||||
Queue.mainQueue().async {
|
||||
if let strongSelf = self {
|
||||
strongSelf.activityIndicator.isHidden = true
|
||||
strongSelf.activityIndicator.stopAnimating()
|
||||
}
|
||||
}
|
||||
}).start())
|
||||
case .kicked:
|
||||
break
|
||||
case .muteNotifications:
|
||||
break
|
||||
case .unmuteNotifications:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -383,11 +383,10 @@ public class ChatController: ViewController {
|
||||
private var enqueuedHistoryViewTransition: (ChatHistoryViewTransition, () -> Void)?
|
||||
private var layoutActionOnViewTransition: (@escaping () -> Void)?
|
||||
|
||||
private let _ready = Promise<Bool>()
|
||||
override public var ready: Promise<Bool> {
|
||||
return self._ready
|
||||
}
|
||||
private var didSetReady = false
|
||||
private let _historyReady = Promise<Bool>()
|
||||
private var didSetHistoryReady = false
|
||||
private let _peerReady = Promise<Bool>()
|
||||
private var didSetPeerReady = false
|
||||
|
||||
private let maxVisibleIncomingMessageId = Promise<MessageId>()
|
||||
private let canReadHistory = Promise<Bool>()
|
||||
@@ -397,7 +396,7 @@ public class ChatController: ViewController {
|
||||
return self._chatHistoryLocation.get()
|
||||
}
|
||||
|
||||
private var presentationInterfaceState = ChatPresentationInterfaceState(interfaceState: ChatInterfaceState(), peer: nil)
|
||||
private var presentationInterfaceState = ChatPresentationInterfaceState(interfaceState: ChatInterfaceState(), peer: nil, inputContext: nil)
|
||||
private let chatInterfaceStatePromise = Promise<ChatInterfaceState>()
|
||||
|
||||
private var leftNavigationButton: ChatNavigationButton?
|
||||
@@ -407,6 +406,7 @@ public class ChatController: ViewController {
|
||||
private let galleryHiddenMesageAndMediaDisposable = MetaDisposable()
|
||||
|
||||
private var controllerInteraction: ChatControllerInteraction?
|
||||
private var interfaceInteraction: ChatPanelInterfaceInteraction?
|
||||
|
||||
public init(account: Account, peerId: PeerId, messageId: MessageId? = nil) {
|
||||
self.account = account
|
||||
@@ -415,6 +415,8 @@ public class ChatController: ViewController {
|
||||
|
||||
super.init()
|
||||
|
||||
self.ready.set(combineLatest(self._historyReady.get(), self._peerReady.get()) |> map { $0 && $1 })
|
||||
|
||||
self.setupThemeWithDarkMode(useDarkMode)
|
||||
|
||||
self.scrollToTop = { [weak self] in
|
||||
@@ -487,40 +489,9 @@ public class ChatController: ViewController {
|
||||
}
|
||||
}, openMessageContextMenu: { [weak self] id, node, frame in
|
||||
if let strongSelf = self, let historyView = strongSelf.historyView {
|
||||
let contextMenuController = ContextMenuController(actions: [
|
||||
ContextMenuAction(content: .text("Reply"), action: { [weak strongSelf] in
|
||||
if let strongSelf = strongSelf, let historyView = strongSelf.historyView {
|
||||
if let strongSelf = self, let historyView = strongSelf.historyView {
|
||||
for case let .MessageEntry(message) in historyView.filteredEntries where message.id == id {
|
||||
strongSelf.updateChatInterfaceState(animated: true, { $0.withUpdatedReplyMessageId(message.id) })
|
||||
strongSelf.chatDisplayNode.ensureInputViewFocused()
|
||||
break
|
||||
}
|
||||
}
|
||||
}),
|
||||
ContextMenuAction(content: .text("Copy"), action: { [weak strongSelf] in
|
||||
if let strongSelf = strongSelf, let historyView = strongSelf.historyView {
|
||||
for case let .MessageEntry(message) in historyView.filteredEntries where message.id == id {
|
||||
if !message.text.isEmpty {
|
||||
UIPasteboard.general.string = message.text
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}),
|
||||
ContextMenuAction(content: .text("More..."), action: { [weak strongSelf] in
|
||||
if let strongSelf = strongSelf, let historyView = strongSelf.historyView {
|
||||
for case let .MessageEntry(message) in historyView.filteredEntries where message.id == id {
|
||||
if strongSelf.presentationInterfaceState.interfaceState.selectionState != nil {
|
||||
strongSelf.updateChatInterfaceState(animated: true, { $0.withoutSelectionState() })
|
||||
} else {
|
||||
strongSelf.updateChatInterfaceState(animated: true, { $0.withUpdatedSelectedMessage(message.id) })
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
])
|
||||
if let contextMenuController = contextMenuForChatPresentationIntefaceState(strongSelf.presentationInterfaceState, account: strongSelf.account, message: message, interfaceInteraction: strongSelf.interfaceInteraction) {
|
||||
strongSelf.present(contextMenuController, in: .window, with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak strongSelf, weak node] in
|
||||
if let node = node {
|
||||
return (node, frame)
|
||||
@@ -529,6 +500,11 @@ public class ChatController: ViewController {
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}, navigateToMessage: { [weak self] fromId, id in
|
||||
if let strongSelf = self, let historyView = strongSelf.historyView {
|
||||
if id.peerId == strongSelf.peerId {
|
||||
@@ -564,7 +540,7 @@ public class ChatController: ViewController {
|
||||
}, toggleMessageSelection: { [weak self] messageId in
|
||||
if let strongSelf = self, let historyView = strongSelf.historyView {
|
||||
for case let .MessageEntry(message) in historyView.filteredEntries where message.id == messageId {
|
||||
strongSelf.updateChatInterfaceState(animated: false, { $0.withToggledSelectedMessage(messageId) })
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: false, { $0.updatedInterfaceState { $0.withToggledSelectedMessage(messageId) } })
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -576,14 +552,21 @@ public class ChatController: ViewController {
|
||||
|
||||
self.chatInfoNavigationButton = ChatNavigationButton(action: .openChatInfo, buttonItem: UIBarButtonItem(customDisplayNode: ChatAvatarNavigationNode()))
|
||||
|
||||
self.updateChatInterfaceState(animated: false, { return $0 })
|
||||
self.updateChatPresentationInterfaceState(animated: false, { return $0 })
|
||||
|
||||
peerDisposable.set((account.postbox.peerWithId(peerId)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
if let strongSelf = self {
|
||||
if let peer = peer {
|
||||
strongSelf.title = peer.displayTitle
|
||||
(strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.avatarNode.setPeer(account: strongSelf.account, peer: peer)
|
||||
}
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: false, { return $0.updatedPeer { _ in return peer } })
|
||||
if !strongSelf.didSetPeerReady {
|
||||
strongSelf.didSetPeerReady = true
|
||||
strongSelf._peerReady.set(.single(true))
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
let fixedCombinedReadState = Atomic<CombinedPeerReadState?>(value: nil)
|
||||
@@ -608,9 +591,9 @@ public class ChatController: ViewController {
|
||||
case .Loading:
|
||||
Queue.mainQueue().async { [weak self] in
|
||||
if let strongSelf = self {
|
||||
if !strongSelf.didSetReady {
|
||||
strongSelf.didSetReady = true
|
||||
strongSelf._ready.set(.single(true))
|
||||
if !strongSelf.didSetHistoryReady {
|
||||
strongSelf.didSetHistoryReady = true
|
||||
strongSelf._historyReady.set(.single(true))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -775,8 +758,8 @@ public class ChatController: ViewController {
|
||||
self?.layoutActionOnViewTransition = f
|
||||
}
|
||||
|
||||
self.chatDisplayNode.requestUpdateChatInterfaceState = { [weak self] state, animated in
|
||||
self?.updateChatInterfaceState(animated: animated, { _ in return state })
|
||||
self.chatDisplayNode.requestUpdateChatInterfaceState = { [weak self] animated, f in
|
||||
self?.updateChatPresentationInterfaceState(animated: animated, { $0.updatedInterfaceState(f) })
|
||||
}
|
||||
|
||||
self.chatDisplayNode.displayAttachmentMenu = { [weak self] in
|
||||
@@ -804,7 +787,22 @@ public class ChatController: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
self.chatDisplayNode.interfaceInteraction = ChatPanelInterfaceInteraction(deleteSelectedMessages: { [weak self] in
|
||||
let interfaceInteraction = ChatPanelInterfaceInteraction(setupReplyMessage: { [weak self] messageId in
|
||||
if let strongSelf = self, let historyView = strongSelf.historyView {
|
||||
for case let .MessageEntry(message) in historyView.filteredEntries where message.id == messageId {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, { $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(message.id) } })
|
||||
strongSelf.chatDisplayNode.ensureInputViewFocused()
|
||||
break
|
||||
}
|
||||
}
|
||||
}, beginMessageSelection: { [weak self] messageId in
|
||||
if let strongSelf = self, let historyView = strongSelf.historyView {
|
||||
for case let .MessageEntry(message) in historyView.filteredEntries where message.id == messageId {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, { $0.updatedInterfaceState { $0.withUpdatedSelectedMessage(message.id) } })
|
||||
break
|
||||
}
|
||||
}
|
||||
}, deleteSelectedMessages: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
|
||||
if let messageIds = strongSelf.presentationInterfaceState.interfaceState.selectionState?.selectedIds, !messageIds.isEmpty {
|
||||
@@ -812,15 +810,22 @@ public class ChatController: ViewController {
|
||||
modifier.deleteMessages(Array(messageIds))
|
||||
}).start()
|
||||
}
|
||||
strongSelf.updateChatInterfaceState(animated: true, { $0.withoutSelectionState() })
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } })
|
||||
}
|
||||
}, forwardSelectedMessages: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
let controller = ShareRecipientsActionSheetController()
|
||||
strongSelf.present(controller, in: .window)
|
||||
}
|
||||
}, updateTextInputState: { [weak self] textInputState in
|
||||
if let strongSelf = self {
|
||||
strongSelf.updateChatPresentationInterfaceState { $0.updatedInterfaceState { $0.withUpdatedInputState(textInputState) } }
|
||||
}
|
||||
})
|
||||
|
||||
self.interfaceInteraction = interfaceInteraction
|
||||
self.chatDisplayNode.interfaceInteraction = interfaceInteraction
|
||||
|
||||
self.displayNodeDidLoad()
|
||||
|
||||
self.dequeueHistoryViewTransition()
|
||||
@@ -851,9 +856,9 @@ public class ChatController: ViewController {
|
||||
if strongSelf.isNodeLoaded {
|
||||
strongSelf.dequeueHistoryViewTransition()
|
||||
} else {
|
||||
if !strongSelf.didSetReady {
|
||||
strongSelf.didSetReady = true
|
||||
strongSelf._ready.set(.single(true))
|
||||
if !strongSelf.didSetHistoryReady {
|
||||
strongSelf.didSetHistoryReady = true
|
||||
strongSelf._historyReady.set(.single(true))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -886,9 +891,9 @@ public class ChatController: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
if !strongSelf.didSetReady {
|
||||
strongSelf.didSetReady = true
|
||||
strongSelf._ready.set(.single(true))
|
||||
if !strongSelf.didSetHistoryReady {
|
||||
strongSelf.didSetHistoryReady = true
|
||||
strongSelf._historyReady.set(.single(true))
|
||||
}
|
||||
|
||||
completion()
|
||||
@@ -944,16 +949,18 @@ public class ChatController: ViewController {
|
||||
})
|
||||
}
|
||||
|
||||
func updateChatInterfaceState(animated: Bool = true, _ f: (ChatInterfaceState) -> ChatInterfaceState) {
|
||||
let updatedChatInterfaceState = f(self.presentationInterfaceState.interfaceState)
|
||||
func updateChatPresentationInterfaceState(animated: Bool = true, _ f: (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState) {
|
||||
let temporaryChatPresentationInterfaceState = f(self.presentationInterfaceState)
|
||||
let inputContext = inputContextForChatPresentationIntefaceState(temporaryChatPresentationInterfaceState, account: self.account)
|
||||
let updatedChatPresentationInterfaceState = temporaryChatPresentationInterfaceState.updatedInputContext { _ in return inputContext }
|
||||
|
||||
if self.isNodeLoaded {
|
||||
self.chatDisplayNode.updateChatInterfaceState(updatedChatInterfaceState, animated: animated)
|
||||
self.chatDisplayNode.updateChatPresentationInterfaceState(updatedChatPresentationInterfaceState, animated: animated)
|
||||
}
|
||||
self.presentationInterfaceState = ChatPresentationInterfaceState(interfaceState: updatedChatInterfaceState, peer: nil)
|
||||
self.chatInterfaceStatePromise.set(.single(updatedChatInterfaceState))
|
||||
self.presentationInterfaceState = updatedChatPresentationInterfaceState
|
||||
self.chatInterfaceStatePromise.set(.single(updatedChatPresentationInterfaceState.interfaceState))
|
||||
|
||||
if let button = leftNavigationButtonForChatInterfaceState(updatedChatInterfaceState, currentButton: self.leftNavigationButton, target: self, selector: #selector(self.leftNavigationButtonAction)) {
|
||||
if let button = leftNavigationButtonForChatInterfaceState(updatedChatPresentationInterfaceState.interfaceState, currentButton: self.leftNavigationButton, target: self, selector: #selector(self.leftNavigationButtonAction)) {
|
||||
self.navigationItem.setLeftBarButton(button.buttonItem, animated: true)
|
||||
self.leftNavigationButton = button
|
||||
} else if let _ = self.leftNavigationButton {
|
||||
@@ -961,7 +968,7 @@ public class ChatController: ViewController {
|
||||
self.leftNavigationButton = nil
|
||||
}
|
||||
|
||||
if let button = rightNavigationButtonForChatInterfaceState(updatedChatInterfaceState, currentButton: self.rightNavigationButton, target: self, selector: #selector(self.rightNavigationButtonAction), chatInfoNavigationButton: self.chatInfoNavigationButton) {
|
||||
if let button = rightNavigationButtonForChatInterfaceState(updatedChatPresentationInterfaceState.interfaceState, currentButton: self.rightNavigationButton, target: self, selector: #selector(self.rightNavigationButtonAction), chatInfoNavigationButton: self.chatInfoNavigationButton) {
|
||||
self.navigationItem.setRightBarButton(button.buttonItem, animated: true)
|
||||
self.rightNavigationButton = button
|
||||
} else if let _ = self.rightNavigationButton {
|
||||
@@ -970,9 +977,9 @@ public class ChatController: ViewController {
|
||||
}
|
||||
|
||||
if let controllerInteraction = self.controllerInteraction {
|
||||
if updatedChatInterfaceState.selectionState != controllerInteraction.selectionState {
|
||||
let animated = controllerInteraction.selectionState == nil || updatedChatInterfaceState.selectionState == nil
|
||||
controllerInteraction.selectionState = updatedChatInterfaceState.selectionState
|
||||
if updatedChatPresentationInterfaceState.interfaceState.selectionState != controllerInteraction.selectionState {
|
||||
let animated = controllerInteraction.selectionState == nil || updatedChatPresentationInterfaceState.interfaceState.selectionState == nil
|
||||
controllerInteraction.selectionState = updatedChatPresentationInterfaceState.interfaceState.selectionState
|
||||
self.chatDisplayNode.listView.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ChatMessageItemView {
|
||||
itemNode.updateSelectionState(animated: animated)
|
||||
@@ -997,7 +1004,7 @@ public class ChatController: ViewController {
|
||||
private func navigationButtonAction(_ action: ChatNavigationButtonAction) {
|
||||
switch action {
|
||||
case .cancelMessageSelection:
|
||||
self.updateChatInterfaceState(animated: true, { $0.withoutSelectionState() })
|
||||
self.updateChatPresentationInterfaceState(animated: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } })
|
||||
case .clearHistory:
|
||||
let actionSheet = ActionSheetController()
|
||||
actionSheet.setItemGroups([ActionSheetItemGroup(items: [
|
||||
|
||||
@@ -7,6 +7,11 @@ import TelegramCore
|
||||
|
||||
private let backgroundImage = UIImage(bundleImageName: "Chat/Wallpapers/Builtin0")
|
||||
|
||||
private func shouldRequestLayoutOnPresentationInterfaceStateTransition(_ lhs: ChatPresentationInterfaceState, _ rhs: ChatPresentationInterfaceState) -> Bool {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
enum ChatMessageViewPosition: Equatable {
|
||||
case AroundUnread(count: Int)
|
||||
case Around(index: MessageIndex, anchorIndex: MessageIndex)
|
||||
@@ -46,20 +51,22 @@ class ChatControllerNode: ASDisplayNode {
|
||||
let backgroundNode: ASDisplayNode
|
||||
let listView: ListView
|
||||
|
||||
let inputPanelBackgroundNode: ASDisplayNode
|
||||
private let inputPanelBackgroundNode: ASDisplayNode
|
||||
private let inputPanelBackgroundSeparatorNode: ASDisplayNode
|
||||
|
||||
var inputPanelNode: ChatInputPanelNode?
|
||||
var accessoryPanelNode: AccessoryPanelNode?
|
||||
private var inputPanelNode: ChatInputPanelNode?
|
||||
private var accessoryPanelNode: AccessoryPanelNode?
|
||||
private var inputContextPanelNode: ChatInputContextPanelNode?
|
||||
|
||||
var textInputPanelNode: ChatTextInputPanelNode?
|
||||
private var textInputPanelNode: ChatTextInputPanelNode?
|
||||
|
||||
let navigateToLatestButton: ChatHistoryNavigationButtonNode
|
||||
|
||||
private var ignoreUpdateHeight = false
|
||||
|
||||
var chatInterfaceState = ChatInterfaceState()
|
||||
var chatPresentationInterfaceState = ChatPresentationInterfaceState()
|
||||
|
||||
var requestUpdateChatInterfaceState: (ChatInterfaceState, Bool) -> Void = { _ in }
|
||||
var requestUpdateChatInterfaceState: (Bool, (ChatInterfaceState) -> ChatInterfaceState) -> Void = { _ in }
|
||||
var displayAttachmentMenu: () -> Void = { }
|
||||
var setupSendActionOnViewUpdate: (@escaping () -> Void) -> Void = { _ in }
|
||||
var requestLayout: (ContainedViewLayoutTransition) -> Void = { _ in }
|
||||
@@ -81,6 +88,11 @@ class ChatControllerNode: ASDisplayNode {
|
||||
|
||||
self.inputPanelBackgroundNode = ASDisplayNode()
|
||||
self.inputPanelBackgroundNode.backgroundColor = UIColor(0xfafafa)
|
||||
self.inputPanelBackgroundNode.isLayerBacked = true
|
||||
|
||||
self.inputPanelBackgroundSeparatorNode = ASDisplayNode()
|
||||
self.inputPanelBackgroundSeparatorNode.backgroundColor = UIColor(0xcdccd3)
|
||||
self.inputPanelBackgroundSeparatorNode.isLayerBacked = true
|
||||
|
||||
self.navigateToLatestButton = ChatHistoryNavigationButtonNode()
|
||||
self.navigateToLatestButton.alpha = 0.0
|
||||
@@ -97,10 +109,11 @@ class ChatControllerNode: ASDisplayNode {
|
||||
self.addSubnode(self.listView)
|
||||
|
||||
self.addSubnode(self.inputPanelBackgroundNode)
|
||||
self.addSubnode(self.inputPanelBackgroundSeparatorNode)
|
||||
|
||||
self.addSubnode(self.navigateToLatestButton)
|
||||
|
||||
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||
self.listView.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||
|
||||
self.textInputPanelNode = ChatTextInputPanelNode()
|
||||
self.textInputPanelNode?.updateHeight = { [weak self] in
|
||||
@@ -119,12 +132,12 @@ class ChatControllerNode: ASDisplayNode {
|
||||
if let strongSelf = strongSelf, let textInputPanelNode = strongSelf.inputPanelNode as? ChatTextInputPanelNode {
|
||||
strongSelf.ignoreUpdateHeight = true
|
||||
textInputPanelNode.text = ""
|
||||
strongSelf.requestUpdateChatInterfaceState(strongSelf.chatInterfaceState.withUpdatedReplyMessageId(nil), false)
|
||||
strongSelf.requestUpdateChatInterfaceState(false, { $0.withUpdatedReplyMessageId(nil) })
|
||||
strongSelf.ignoreUpdateHeight = false
|
||||
}
|
||||
})
|
||||
|
||||
let _ = enqueueMessage(account: strongSelf.account, peerId: strongSelf.peerId, text: text, replyMessageId: strongSelf.chatInterfaceState.replyMessageId).start()
|
||||
let _ = enqueueMessage(account: strongSelf.account, peerId: strongSelf.peerId, text: text, replyMessageId: strongSelf.chatPresentationInterfaceState.interfaceState.replyMessageId).start()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,10 +184,11 @@ class ChatControllerNode: ASDisplayNode {
|
||||
|
||||
var dismissedInputPanelNode: ASDisplayNode?
|
||||
var dismissedAccessoryPanelNode: ASDisplayNode?
|
||||
var dismissedInputContextPanelNode: ChatInputContextPanelNode?
|
||||
|
||||
var inputPanelSize: CGSize?
|
||||
var immediatelyLayoutInputPanelAndAnimateAppearance = false
|
||||
if let inputPanelNode = inputPanelForChatIntefaceState(self.chatInterfaceState, account: self.account, currentPanel: self.inputPanelNode, textInputPanelNode: self.textInputPanelNode, interfaceInteraction: self.interfaceInteraction) {
|
||||
if let inputPanelNode = inputPanelForChatPresentationIntefaceState(self.chatPresentationInterfaceState, account: self.account, currentPanel: self.inputPanelNode, textInputPanelNode: self.textInputPanelNode, interfaceInteraction: self.interfaceInteraction) {
|
||||
inputPanelSize = inputPanelNode.measure(CGSize(width: layout.size.width, height: layout.size.height))
|
||||
|
||||
if inputPanelNode !== self.inputPanelNode {
|
||||
@@ -193,7 +207,7 @@ class ChatControllerNode: ASDisplayNode {
|
||||
|
||||
var accessoryPanelSize: CGSize?
|
||||
var immediatelyLayoutAccessoryPanelAndAnimateAppearance = false
|
||||
if let accessoryPanelNode = accessoryPanelForChatIntefaceState(self.chatInterfaceState, account: self.account, currentPanel: self.accessoryPanelNode, interfaceInteraction: self.interfaceInteraction) {
|
||||
if let accessoryPanelNode = accessoryPanelForChatPresentationIntefaceState(self.chatPresentationInterfaceState, account: self.account, currentPanel: self.accessoryPanelNode, interfaceInteraction: self.interfaceInteraction) {
|
||||
accessoryPanelSize = accessoryPanelNode.measure(CGSize(width: layout.size.width, height: layout.size.height))
|
||||
|
||||
if accessoryPanelNode !== self.accessoryPanelNode {
|
||||
@@ -208,7 +222,7 @@ class ChatControllerNode: ASDisplayNode {
|
||||
|
||||
accessoryPanelNode.dismiss = { [weak self, weak accessoryPanelNode] in
|
||||
if let strongSelf = self, let accessoryPanelNode = accessoryPanelNode, strongSelf.accessoryPanelNode === accessoryPanelNode {
|
||||
strongSelf.requestUpdateChatInterfaceState(strongSelf.chatInterfaceState.withUpdatedReplyMessageId(nil), true)
|
||||
strongSelf.requestUpdateChatInterfaceState(true, { $0.withUpdatedReplyMessageId(nil) })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,6 +234,21 @@ class ChatControllerNode: ASDisplayNode {
|
||||
self.accessoryPanelNode = nil
|
||||
}
|
||||
|
||||
var immediatelyLayoutInputContextPanelAndAnimateAppearance = false
|
||||
if let inputContextPanelNode = inputContextPanelForChatPresentationIntefaceState(self.chatPresentationInterfaceState, account: self.account, currentPanel: self.inputContextPanelNode, interfaceInteraction: self.interfaceInteraction) {
|
||||
if inputContextPanelNode !== self.inputContextPanelNode {
|
||||
dismissedInputContextPanelNode = self.inputContextPanelNode
|
||||
self.inputContextPanelNode = inputContextPanelNode
|
||||
|
||||
self.insertSubnode(inputContextPanelNode, aboveSubnode: self.navigateToLatestButton)
|
||||
immediatelyLayoutInputContextPanelAndAnimateAppearance = true
|
||||
|
||||
}
|
||||
} else if let inputContextPanelNode = self.inputContextPanelNode {
|
||||
dismissedInputContextPanelNode = inputContextPanelNode
|
||||
self.inputContextPanelNode = nil
|
||||
}
|
||||
|
||||
var inputPanelsHeight: CGFloat = 0.0
|
||||
|
||||
var inputPanelFrame: CGRect?
|
||||
@@ -244,6 +273,7 @@ class ChatControllerNode: ASDisplayNode {
|
||||
let navigateToLatestButtonFrame = CGRect(origin: CGPoint(x: layout.size.width - navigateToLatestButtonSize.width - 6.0, y: layout.size.height - insets.bottom - inputPanelsHeight - navigateToLatestButtonSize.height - 6.0), size: navigateToLatestButtonSize)
|
||||
|
||||
transition.updateFrame(node: self.inputPanelBackgroundNode, frame: inputBackgroundFrame)
|
||||
transition.updateFrame(node: self.inputPanelBackgroundSeparatorNode, frame: CGRect(origin: inputBackgroundFrame.origin, size: CGSize(width: inputBackgroundFrame.size.width, height: UIScreenPixel)))
|
||||
transition.updateFrame(node: self.navigateToLatestButton, frame: navigateToLatestButtonFrame)
|
||||
|
||||
if let inputPanelNode = self.inputPanelNode, let inputPanelFrame = inputPanelFrame, !inputPanelNode.frame.equalTo(inputPanelFrame) {
|
||||
@@ -267,6 +297,19 @@ class ChatControllerNode: ASDisplayNode {
|
||||
transition.updateAlpha(node: accessoryPanelNode, alpha: 1.0)
|
||||
}
|
||||
|
||||
let inputContextPanelsFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: max(0.0, layout.size.height - insets.bottom - inputPanelsHeight - insets.top)))
|
||||
|
||||
if let inputContextPanelNode = self.inputContextPanelNode {
|
||||
if immediatelyLayoutInputContextPanelAndAnimateAppearance {
|
||||
inputContextPanelNode.frame = inputContextPanelsFrame
|
||||
inputContextPanelNode.updateFrames(transition: .immediate)
|
||||
inputContextPanelNode.animateIn()
|
||||
} else if !inputContextPanelNode.frame.equalTo(inputContextPanelsFrame) {
|
||||
transition.updateFrame(node: inputContextPanelNode, frame: inputContextPanelsFrame)
|
||||
inputContextPanelNode.updateFrames(transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
if let dismissedInputPanelNode = dismissedInputPanelNode {
|
||||
var frameCompleted = false
|
||||
var alphaCompleted = false
|
||||
@@ -312,11 +355,43 @@ class ChatControllerNode: ASDisplayNode {
|
||||
completed()
|
||||
})
|
||||
}
|
||||
|
||||
if let dismissedInputContextPanelNode = dismissedInputContextPanelNode {
|
||||
var frameCompleted = false
|
||||
var animationCompleted = false
|
||||
var completed = { [weak dismissedInputContextPanelNode] in
|
||||
if let dismissedInputContextPanelNode = dismissedInputContextPanelNode, frameCompleted, animationCompleted {
|
||||
dismissedInputContextPanelNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
if !dismissedInputContextPanelNode.frame.equalTo(inputContextPanelsFrame) {
|
||||
transition.updateFrame(node: dismissedInputContextPanelNode, frame: inputContextPanelsFrame, completion: { _ in
|
||||
frameCompleted = true
|
||||
completed()
|
||||
})
|
||||
} else {
|
||||
frameCompleted = true
|
||||
}
|
||||
|
||||
func updateChatInterfaceState(_ chatInterfaceState: ChatInterfaceState, animated: Bool) {
|
||||
if self.chatInterfaceState != chatInterfaceState {
|
||||
self.chatInterfaceState = chatInterfaceState
|
||||
dismissedInputContextPanelNode.animateOut(completion: {
|
||||
animationCompleted = true
|
||||
completed()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func updateChatPresentationInterfaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, animated: Bool) {
|
||||
if let textInputPanelNode = self.textInputPanelNode {
|
||||
self.chatPresentationInterfaceState = self.chatPresentationInterfaceState.updatedInterfaceState { $0.withUpdatedInputState(textInputPanelNode.inputTextState) }
|
||||
}
|
||||
|
||||
if self.chatPresentationInterfaceState != chatPresentationInterfaceState {
|
||||
var updateInputTextState = self.chatPresentationInterfaceState.interfaceState.inputState != chatPresentationInterfaceState.interfaceState.inputState
|
||||
self.chatPresentationInterfaceState = chatPresentationInterfaceState
|
||||
|
||||
if let textInputPanelNode = self.textInputPanelNode, updateInputTextState {
|
||||
textInputPanelNode.inputTextState = chatPresentationInterfaceState.interfaceState.inputState
|
||||
}
|
||||
|
||||
if !self.ignoreUpdateHeight {
|
||||
self.requestLayout(animated ? .animated(duration: 0.4, curve: .spring) : .immediate)
|
||||
|
||||
18
TelegramUI/ChatInputContextPanelNode.swift
Normal file
18
TelegramUI/ChatInputContextPanelNode.swift
Normal file
@@ -0,0 +1,18 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
|
||||
class ChatInputContextPanelNode: ASDisplayNode {
|
||||
var interfaceInteraction: ChatPanelInterfaceInteraction?
|
||||
|
||||
func updateFrames(transition: ContainedViewLayoutTransition) {
|
||||
}
|
||||
|
||||
func animateIn() {
|
||||
|
||||
}
|
||||
|
||||
func animateOut(completion: @escaping () -> Void) {
|
||||
completion()
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
|
||||
class ChatInputPanelNode: ASDisplayNode {
|
||||
var account: Account?
|
||||
var interfaceInteraction: ChatPanelInterfaceInteraction?
|
||||
var peer: Peer?
|
||||
|
||||
func updateFrames(transition: ContainedViewLayoutTransition) {
|
||||
}
|
||||
|
||||
30
TelegramUI/ChatInterfaceInputContextPanels.swift
Normal file
30
TelegramUI/ChatInterfaceInputContextPanels.swift
Normal file
@@ -0,0 +1,30 @@
|
||||
import Foundation
|
||||
import TelegramCore
|
||||
|
||||
func inputContextPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, account: Account, currentPanel: ChatInputContextPanelNode?, interfaceInteraction: ChatPanelInterfaceInteraction?) -> ChatInputContextPanelNode? {
|
||||
guard let inputContext = chatPresentationInterfaceState.inputContext, let peer = chatPresentationInterfaceState.peer else {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch inputContext {
|
||||
case .hashtag:
|
||||
if let currentPanel = currentPanel as? HashtagChatInputContextPanelNode {
|
||||
return currentPanel
|
||||
} else {
|
||||
let panel = HashtagChatInputContextPanelNode()
|
||||
panel.interfaceInteraction = interfaceInteraction
|
||||
return panel
|
||||
}
|
||||
case .mention:
|
||||
if let currentPanel = currentPanel as? MentionChatInputContextPanelNode {
|
||||
return currentPanel
|
||||
} else {
|
||||
let panel = MentionChatInputContextPanelNode()
|
||||
panel.interfaceInteraction = interfaceInteraction
|
||||
panel.setup(account: account, peerId: peer.id, query: "")
|
||||
return panel
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
12
TelegramUI/ChatInterfaceInputContexts.swift
Normal file
12
TelegramUI/ChatInterfaceInputContexts.swift
Normal file
@@ -0,0 +1,12 @@
|
||||
import Foundation
|
||||
import TelegramCore
|
||||
import Postbox
|
||||
|
||||
func inputContextForChatPresentationIntefaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, account: Account) -> ChatPresentationInputContext? {
|
||||
if chatPresentationInterfaceState.interfaceState.inputState.inputText == "#" {
|
||||
return .hashtag
|
||||
} else if chatPresentationInterfaceState.interfaceState.inputState.inputText == "@" {
|
||||
return .mention
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -9,29 +9,52 @@ struct ChatInterfaceSelectionState: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
struct ChatTextInputState: Equatable {
|
||||
let inputText: String
|
||||
let selectionRange: Range<Int>
|
||||
|
||||
static func ==(lhs: ChatTextInputState, rhs: ChatTextInputState) -> Bool {
|
||||
return lhs.inputText == rhs.inputText && lhs.selectionRange == rhs.selectionRange
|
||||
}
|
||||
|
||||
init() {
|
||||
self.inputText = ""
|
||||
self.selectionRange = 0 ..< 0
|
||||
}
|
||||
|
||||
init(inputText: String, selectionRange: Range<Int>) {
|
||||
self.inputText = inputText
|
||||
self.selectionRange = selectionRange
|
||||
}
|
||||
}
|
||||
|
||||
final class ChatInterfaceState: Equatable {
|
||||
let inputText: String?
|
||||
let inputState: ChatTextInputState
|
||||
let replyMessageId: MessageId?
|
||||
let selectionState: ChatInterfaceSelectionState?
|
||||
|
||||
init() {
|
||||
self.inputText = nil
|
||||
self.inputState = ChatTextInputState()
|
||||
self.replyMessageId = nil
|
||||
self.selectionState = nil
|
||||
}
|
||||
|
||||
init(inputText: String?, replyMessageId: MessageId?, selectionState: ChatInterfaceSelectionState?) {
|
||||
self.inputText = inputText
|
||||
init(inputState: ChatTextInputState, replyMessageId: MessageId?, selectionState: ChatInterfaceSelectionState?) {
|
||||
self.inputState = inputState
|
||||
self.replyMessageId = replyMessageId
|
||||
self.selectionState = selectionState
|
||||
}
|
||||
|
||||
static func ==(lhs: ChatInterfaceState, rhs: ChatInterfaceState) -> Bool {
|
||||
return lhs.inputText == rhs.inputText && lhs.replyMessageId == rhs.replyMessageId && lhs.selectionState == rhs.selectionState
|
||||
return lhs.inputState == rhs.inputState && lhs.replyMessageId == rhs.replyMessageId && lhs.selectionState == rhs.selectionState
|
||||
}
|
||||
|
||||
func withUpdatedInputState(_ inputState: ChatTextInputState) -> ChatInterfaceState {
|
||||
return ChatInterfaceState(inputState: inputState, replyMessageId: self.replyMessageId, selectionState: self.selectionState)
|
||||
}
|
||||
|
||||
func withUpdatedReplyMessageId(_ replyMessageId: MessageId?) -> ChatInterfaceState {
|
||||
return ChatInterfaceState(inputText: self.inputText, replyMessageId: replyMessageId, selectionState: self.selectionState)
|
||||
return ChatInterfaceState(inputState: self.inputState, replyMessageId: replyMessageId, selectionState: self.selectionState)
|
||||
}
|
||||
|
||||
func withUpdatedSelectedMessage(_ messageId: MessageId) -> ChatInterfaceState {
|
||||
@@ -40,7 +63,7 @@ final class ChatInterfaceState: Equatable {
|
||||
selectedIds.formUnion(selectionState.selectedIds)
|
||||
}
|
||||
selectedIds.insert(messageId)
|
||||
return ChatInterfaceState(inputText: self.inputText, replyMessageId: self.replyMessageId, selectionState: ChatInterfaceSelectionState(selectedIds: selectedIds))
|
||||
return ChatInterfaceState(inputState: self.inputState, replyMessageId: self.replyMessageId, selectionState: ChatInterfaceSelectionState(selectedIds: selectedIds))
|
||||
}
|
||||
|
||||
func withToggledSelectedMessage(_ messageId: MessageId) -> ChatInterfaceState {
|
||||
@@ -53,10 +76,10 @@ final class ChatInterfaceState: Equatable {
|
||||
} else {
|
||||
selectedIds.insert(messageId)
|
||||
}
|
||||
return ChatInterfaceState(inputText: self.inputText, replyMessageId: self.replyMessageId, selectionState: ChatInterfaceSelectionState(selectedIds: selectedIds))
|
||||
return ChatInterfaceState(inputState: self.inputState, replyMessageId: self.replyMessageId, selectionState: ChatInterfaceSelectionState(selectedIds: selectedIds))
|
||||
}
|
||||
|
||||
func withoutSelectionState() -> ChatInterfaceState {
|
||||
return ChatInterfaceState(inputText: self.inputText, replyMessageId: self.replyMessageId, selectionState: nil)
|
||||
return ChatInterfaceState(inputState: self.inputState, replyMessageId: self.replyMessageId, selectionState: nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ import Foundation
|
||||
import AsyncDisplayKit
|
||||
import TelegramCore
|
||||
|
||||
func accessoryPanelForChatIntefaceState(_ chatInterfaceState: ChatInterfaceState, account: Account, currentPanel: AccessoryPanelNode?, interfaceInteraction: ChatPanelInterfaceInteraction?) -> AccessoryPanelNode? {
|
||||
if let _ = chatInterfaceState.selectionState {
|
||||
func accessoryPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, account: Account, currentPanel: AccessoryPanelNode?, interfaceInteraction: ChatPanelInterfaceInteraction?) -> AccessoryPanelNode? {
|
||||
if let _ = chatPresentationInterfaceState.interfaceState.selectionState {
|
||||
return nil
|
||||
}
|
||||
|
||||
if let replyMessageId = chatInterfaceState.replyMessageId {
|
||||
if let replyMessageId = chatPresentationInterfaceState.interfaceState.replyMessageId {
|
||||
if let replyPanelNode = currentPanel as? ReplyAccessoryPanelNode, replyPanelNode.messageId == replyMessageId {
|
||||
replyPanelNode.interfaceInteraction = interfaceInteraction
|
||||
return replyPanelNode
|
||||
|
||||
48
TelegramUI/ChatInterfaceStateContextMenus.swift
Normal file
48
TelegramUI/ChatInterfaceStateContextMenus.swift
Normal file
@@ -0,0 +1,48 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import Display
|
||||
import UIKit
|
||||
|
||||
func contextMenuForChatPresentationIntefaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, account: Account, message: Message, interfaceInteraction: ChatPanelInterfaceInteraction?) -> ContextMenuController? {
|
||||
guard let peer = chatPresentationInterfaceState.peer, let interfaceInteraction = interfaceInteraction else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var actions: [ContextMenuAction] = []
|
||||
|
||||
var canReply = false
|
||||
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
|
||||
switch channel.role {
|
||||
case .creator, .editor, .moderator:
|
||||
canReply = true
|
||||
case .member:
|
||||
canReply = false
|
||||
}
|
||||
} else {
|
||||
canReply = true
|
||||
}
|
||||
if canReply {
|
||||
actions.append(ContextMenuAction(content: .text("Reply"), action: {
|
||||
interfaceInteraction.setupReplyMessage(message.id)
|
||||
}))
|
||||
}
|
||||
|
||||
actions.append(ContextMenuAction(content: .text("Copy"), action: {
|
||||
if !message.text.isEmpty {
|
||||
UIPasteboard.general.string = message.text
|
||||
}
|
||||
}))
|
||||
|
||||
actions.append(ContextMenuAction(content: .text("More..."), action: {
|
||||
interfaceInteraction.beginMessageSelection(message.id)
|
||||
}))
|
||||
|
||||
|
||||
if !actions.isEmpty {
|
||||
let contextMenuController = ContextMenuController(actions: actions)
|
||||
return contextMenuController
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -2,31 +2,78 @@ import Foundation
|
||||
import AsyncDisplayKit
|
||||
import TelegramCore
|
||||
|
||||
func inputPanelForChatIntefaceState(_ chatInterfaceState: ChatInterfaceState, account: Account, currentPanel: ChatInputPanelNode?, textInputPanelNode: ChatTextInputPanelNode?, interfaceInteraction: ChatPanelInterfaceInteraction?) -> ChatInputPanelNode? {
|
||||
if let selectionState = chatInterfaceState.selectionState {
|
||||
func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, account: Account, currentPanel: ChatInputPanelNode?, textInputPanelNode: ChatTextInputPanelNode?, interfaceInteraction: ChatPanelInterfaceInteraction?) -> ChatInputPanelNode? {
|
||||
if let selectionState = chatPresentationInterfaceState.interfaceState.selectionState {
|
||||
if let currentPanel = currentPanel as? ChatMessageSelectionInputPanelNode {
|
||||
currentPanel.selectedMessageCount = selectionState.selectedIds.count
|
||||
currentPanel.interfaceInteraction = interfaceInteraction
|
||||
currentPanel.peer = chatPresentationInterfaceState.peer
|
||||
return currentPanel
|
||||
} else {
|
||||
let panel = ChatMessageSelectionInputPanelNode()
|
||||
panel.account = account
|
||||
panel.peer = chatPresentationInterfaceState.peer
|
||||
panel.selectedMessageCount = selectionState.selectedIds.count
|
||||
panel.interfaceInteraction = interfaceInteraction
|
||||
return panel
|
||||
}
|
||||
} else {
|
||||
if let peer = chatPresentationInterfaceState.peer {
|
||||
if let channel = peer as? TelegramChannel {
|
||||
switch channel.info {
|
||||
case .broadcast:
|
||||
switch channel.role {
|
||||
case .creator, .editor, .moderator:
|
||||
break
|
||||
case .member:
|
||||
if let currentPanel = currentPanel as? ChatChannelSubscriberInputPanelNode {
|
||||
currentPanel.peer = peer
|
||||
return currentPanel
|
||||
} else {
|
||||
let panel = ChatChannelSubscriberInputPanelNode()
|
||||
panel.account = account
|
||||
panel.peer = peer
|
||||
return panel
|
||||
}
|
||||
}
|
||||
case .group:
|
||||
switch channel.participationStatus {
|
||||
case .kicked, .left:
|
||||
if let currentPanel = currentPanel as? ChatChannelSubscriberInputPanelNode {
|
||||
currentPanel.peer = peer
|
||||
return currentPanel
|
||||
} else {
|
||||
let panel = ChatChannelSubscriberInputPanelNode()
|
||||
panel.account = account
|
||||
panel.peer = peer
|
||||
return panel
|
||||
}
|
||||
case .member:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let currentPanel = currentPanel as? ChatTextInputPanelNode {
|
||||
currentPanel.interfaceInteraction = interfaceInteraction
|
||||
currentPanel.peer = peer
|
||||
return currentPanel
|
||||
} else {
|
||||
if let textInputPanelNode = textInputPanelNode {
|
||||
textInputPanelNode.interfaceInteraction = interfaceInteraction
|
||||
textInputPanelNode.account = account
|
||||
textInputPanelNode.peer = peer
|
||||
return textInputPanelNode
|
||||
} else {
|
||||
let panel = ChatTextInputPanelNode()
|
||||
panel.interfaceInteraction = interfaceInteraction
|
||||
panel.account = account
|
||||
panel.peer = peer
|
||||
return panel
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ class ChatListItemNode: ListViewItemNode {
|
||||
|
||||
private let highlightedBackgroundNode: ASDisplayNode
|
||||
|
||||
let avatarNode: ChatListAvatarNode
|
||||
let avatarNode: AvatarNode
|
||||
let contentNode: ASDisplayNode
|
||||
let titleNode: TextNode
|
||||
let textNode: TextNode
|
||||
@@ -121,7 +121,7 @@ class ChatListItemNode: ListViewItemNode {
|
||||
var relativePosition: (first: Bool, last: Bool) = (false, false)
|
||||
|
||||
required init() {
|
||||
self.avatarNode = ChatListAvatarNode(font: Font.regular(24.0))
|
||||
self.avatarNode = AvatarNode(font: Font.regular(24.0))
|
||||
self.avatarNode.isLayerBacked = true
|
||||
|
||||
self.highlightedBackgroundNode = ASDisplayNode()
|
||||
|
||||
@@ -35,10 +35,10 @@ final class ChatMessageAvatarAccessoryItem: ListViewAccessoryItem {
|
||||
}
|
||||
|
||||
final class ChatMessageAvatarAccessoryItemNode: ListViewAccessoryItemNode {
|
||||
let avatarNode: ChatListAvatarNode
|
||||
let avatarNode: AvatarNode
|
||||
|
||||
override init() {
|
||||
self.avatarNode = ChatListAvatarNode(font: Font.regular(14.0))
|
||||
self.avatarNode = AvatarNode(font: Font.regular(14.0))
|
||||
self.avatarNode.isLayerBacked = true
|
||||
self.avatarNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 38.0, height: 38.0))
|
||||
|
||||
|
||||
@@ -234,7 +234,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
|
||||
return { item, width, mergedTop, mergedBottom in
|
||||
let message = item.message
|
||||
|
||||
let incoming = item.account.peerId != message.author?.id
|
||||
let incoming = item.message.effectivelyIncoming
|
||||
|
||||
let displayAuthorInfo = !mergedTop && incoming && item.peerId.isGroupOrChannel && item.message.author != nil
|
||||
|
||||
let avatarInset: CGFloat = (item.peerId.isGroupOrChannel && item.message.author != nil) ? layoutConstants.avatarDiameter : 0.0
|
||||
@@ -718,7 +719,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
|
||||
var incoming = true
|
||||
if let item = self.item {
|
||||
selected = selectionState.selectedIds.contains(item.message.id)
|
||||
incoming = item.message.flags.contains(.Incoming)
|
||||
incoming = item.message.effectivelyIncoming
|
||||
}
|
||||
let offset: CGFloat = incoming ? 42.0 : 0.0
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ public class ChatMessageItem: ListViewItem, CustomStringConvertible {
|
||||
self.message = message
|
||||
|
||||
var accessoryItem: ListViewAccessoryItem?
|
||||
let incoming = account.peerId != message.author?.id
|
||||
let incoming = message.effectivelyIncoming
|
||||
let displayAuthorInfo = incoming && message.author != nil && peerId.isGroupOrChannel
|
||||
|
||||
if displayAuthorInfo {
|
||||
|
||||
@@ -40,7 +40,7 @@ struct ChatMessageItemLayoutConstants {
|
||||
|
||||
self.bubble = ChatMessageItemBubbleLayoutConstants(edgeInset: 4.0, defaultSpacing: 2.5, mergedSpacing: 0.0, maximumWidthFillFactor: 0.9, minimumSize: CGSize(width: 40.0, height: 33.0), contentInsets: UIEdgeInsets(top: 1.0, left: 6.0, bottom: 1.0, right: 1.0))
|
||||
self.text = ChatMessageItemTextLayoutConstants(bubbleInsets: UIEdgeInsets(top: 5.0, left: 9.0, bottom: 4.0, right: 9.0))
|
||||
self.image = ChatMessageItemImageLayoutConstants(bubbleInsets: UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0), defaultCornerRadius: 15.0, mergedCornerRadius: 4.0, contentMergedCornerRadius: 2.0)
|
||||
self.image = ChatMessageItemImageLayoutConstants(bubbleInsets: UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0), defaultCornerRadius: 15.0, mergedCornerRadius: 4.0, contentMergedCornerRadius: 5.0)
|
||||
self.file = ChatMessageItemFileLayoutConstants(bubbleInsets: UIEdgeInsets(top: 15.0, left: 9.0, bottom: 15.0, right: 12.0))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,40 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
|
||||
final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode {
|
||||
private let deleteButton: UIButton
|
||||
private let forwardButton: UIButton
|
||||
|
||||
override var peer: Peer? {
|
||||
didSet {
|
||||
var canDelete = false
|
||||
if let channel = self.peer as? TelegramChannel {
|
||||
switch channel.info {
|
||||
case .broadcast:
|
||||
switch channel.role {
|
||||
case .creator, .editor, .moderator:
|
||||
canDelete = true
|
||||
case .member:
|
||||
canDelete = false
|
||||
}
|
||||
case .group:
|
||||
switch channel.role {
|
||||
case .creator, .editor, .moderator:
|
||||
canDelete = true
|
||||
case .member:
|
||||
canDelete = false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
canDelete = true
|
||||
}
|
||||
self.deleteButton.isHidden = !canDelete
|
||||
}
|
||||
}
|
||||
|
||||
var selectedMessageCount: Int = 0 {
|
||||
didSet {
|
||||
self.deleteButton.isEnabled = self.selectedMessageCount != 0
|
||||
|
||||
@@ -56,7 +56,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
let imageLayout = self.imageNode.asyncLayout()
|
||||
|
||||
return { item, width, mergedTop, mergedBottom in
|
||||
let incoming = item.account.peerId != item.message.author?.id
|
||||
let incoming = item.message.effectivelyIncoming
|
||||
var imageSize: CGSize = CGSize(width: 100.0, height: 100.0)
|
||||
if let telegramFile = telegramFile {
|
||||
if let thumbnailSize = telegramFile.previewRepresentations.first?.dimensions {
|
||||
|
||||
@@ -35,7 +35,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
return (CGFloat.greatestFiniteMagnitude, { constrainedSize in
|
||||
let message = item.message
|
||||
|
||||
let incoming = item.account.peerId != message.author?.id
|
||||
let incoming = item.message.effectivelyIncoming
|
||||
|
||||
let horizontalInset = layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right
|
||||
let textConstrainedSize = CGSize(width: constrainedSize.width - horizontalInset, height: constrainedSize.height)
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
|
||||
final class ChatPanelInterfaceInteraction {
|
||||
let setupReplyMessage: (MessageId) -> Void
|
||||
let beginMessageSelection: (MessageId) -> Void
|
||||
let deleteSelectedMessages: () -> Void
|
||||
let forwardSelectedMessages: () -> Void
|
||||
let updateTextInputState: (ChatTextInputState) -> Void
|
||||
|
||||
init(deleteSelectedMessages: @escaping () -> Void, forwardSelectedMessages: @escaping () -> Void) {
|
||||
init(setupReplyMessage: @escaping (MessageId) -> Void, beginMessageSelection: @escaping (MessageId) -> Void, deleteSelectedMessages: @escaping () -> Void, forwardSelectedMessages: @escaping () -> Void, updateTextInputState: @escaping (ChatTextInputState) -> Void) {
|
||||
self.setupReplyMessage = setupReplyMessage
|
||||
self.beginMessageSelection = beginMessageSelection
|
||||
self.deleteSelectedMessages = deleteSelectedMessages
|
||||
self.forwardSelectedMessages = forwardSelectedMessages
|
||||
self.updateTextInputState = updateTextInputState
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,56 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
|
||||
struct ChatPresentationInterfaceState {
|
||||
enum ChatPresentationInputContext {
|
||||
case hashtag
|
||||
case mention
|
||||
}
|
||||
|
||||
struct ChatPresentationInterfaceState: Equatable {
|
||||
let interfaceState: ChatInterfaceState
|
||||
let peer: Peer?
|
||||
let inputContext: ChatPresentationInputContext?
|
||||
|
||||
init() {
|
||||
self.interfaceState = ChatInterfaceState()
|
||||
self.peer = nil
|
||||
self.inputContext = nil
|
||||
}
|
||||
|
||||
init(interfaceState: ChatInterfaceState, peer: Peer?, inputContext: ChatPresentationInputContext?) {
|
||||
self.interfaceState = interfaceState
|
||||
self.peer = peer
|
||||
self.inputContext = inputContext
|
||||
}
|
||||
|
||||
static func ==(lhs: ChatPresentationInterfaceState, rhs: ChatPresentationInterfaceState) -> Bool {
|
||||
if lhs.interfaceState != rhs.interfaceState {
|
||||
return false
|
||||
}
|
||||
if let lhsPeer = lhs.peer, let rhsPeer = rhs.peer {
|
||||
if !lhsPeer.isEqual(rhsPeer) {
|
||||
return false
|
||||
}
|
||||
} else if (lhs.peer == nil) != (rhs.peer == nil) {
|
||||
return false
|
||||
}
|
||||
|
||||
if lhs.inputContext != rhs.inputContext {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func updatedInterfaceState(_ f: (ChatInterfaceState) -> ChatInterfaceState) -> ChatPresentationInterfaceState {
|
||||
return ChatPresentationInterfaceState(interfaceState: f(self.interfaceState), peer: self.peer, inputContext: self.inputContext)
|
||||
}
|
||||
|
||||
func updatedPeer(_ f: (Peer?) -> Peer?) -> ChatPresentationInterfaceState {
|
||||
return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: f(self.peer), inputContext: self.inputContext)
|
||||
}
|
||||
|
||||
func updatedInputContext(_ f: (ChatPresentationInputContext?) -> ChatPresentationInputContext?) -> ChatPresentationInterfaceState {
|
||||
return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, inputContext: f(self.inputContext))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@ import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import WebKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
|
||||
private let textInputViewBackground: UIImage = {
|
||||
let diameter: CGFloat = 10.0
|
||||
@@ -36,6 +37,48 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
var sendMessage: () -> Void = { }
|
||||
var updateHeight: () -> Void = { }
|
||||
|
||||
private var updatingInputState = false
|
||||
|
||||
private var currentPlaceholder: String?
|
||||
override var peer: Peer? {
|
||||
didSet {
|
||||
if let peer = self.peer, oldValue == nil || !peer.isEqual(oldValue!) {
|
||||
let placeholder: String
|
||||
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
|
||||
placeholder = "Broadcast"
|
||||
} else {
|
||||
placeholder = "Message"
|
||||
}
|
||||
if self.currentPlaceholder != placeholder {
|
||||
self.currentPlaceholder = placeholder
|
||||
let placeholderLayout = TextNode.asyncLayout(self.textPlaceholderNode)
|
||||
let (placeholderSize, placeholderApply) = placeholderLayout(NSAttributedString(string: placeholder, font: Font.regular(16.0), textColor: UIColor(0xbebec0)), nil, 1, .end, CGSize(width: 320.0, height: CGFloat.greatestFiniteMagnitude), nil)
|
||||
self.textPlaceholderNode.frame = CGRect(origin: self.textPlaceholderNode.frame.origin, size: placeholderSize.size)
|
||||
let _ = placeholderApply()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var inputTextState: ChatTextInputState {
|
||||
get {
|
||||
if let textInputNode = self.textInputNode {
|
||||
let text = textInputNode.attributedText?.string ?? ""
|
||||
let selectionRange: Range<Int> = textInputNode.selectedRange.location ..< (textInputNode.selectedRange.location + textInputNode.selectedRange.length)
|
||||
return ChatTextInputState(inputText: text, selectionRange: selectionRange)
|
||||
} else {
|
||||
return ChatTextInputState()
|
||||
}
|
||||
} set(value) {
|
||||
if let textInputNode = self.textInputNode {
|
||||
self.updatingInputState = true
|
||||
textInputNode.attributedText = NSAttributedString(string: value.inputText, font: Font.regular(16.0), textColor: UIColor.black)
|
||||
textInputNode.selectedRange = NSMakeRange(value.selectionRange.lowerBound, value.selectionRange.count)
|
||||
self.updatingInputState = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var text: String {
|
||||
get {
|
||||
return self.textInputNode?.attributedText?.string ?? ""
|
||||
@@ -81,7 +124,13 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
self.view.addSubview(self.sendButton)
|
||||
|
||||
self.textInputBackgroundView.clipsToBounds = true
|
||||
self.textInputBackgroundView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.textInputBackgroundViewTap(_:))))
|
||||
let recognizer = TouchDownGestureRecognizer(target: self, action: #selector(self.textInputBackgroundViewTap(_:)))
|
||||
recognizer.touchDown = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.ensureFocused()
|
||||
}
|
||||
}
|
||||
self.textInputBackgroundView.addGestureRecognizer(recognizer)
|
||||
self.textInputBackgroundView.isUserInteractionEnabled = true
|
||||
}
|
||||
|
||||
@@ -94,6 +143,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
textInputNode.typingAttributes = [NSFontAttributeName: Font.regular(16.0)]
|
||||
textInputNode.clipsToBounds = true
|
||||
textInputNode.delegate = self
|
||||
textInputNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0)
|
||||
self.addSubnode(textInputNode)
|
||||
self.textInputNode = textInputNode
|
||||
|
||||
@@ -103,6 +153,14 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
|
||||
self.textInputBackgroundView.isUserInteractionEnabled = false
|
||||
self.textInputBackgroundView.removeGestureRecognizer(self.textInputBackgroundView.gestureRecognizers![0])
|
||||
|
||||
let recognizer = TouchDownGestureRecognizer(target: self, action: #selector(self.textInputBackgroundViewTap(_:)))
|
||||
recognizer.touchDown = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.ensureFocused()
|
||||
}
|
||||
}
|
||||
textInputNode.view.addGestureRecognizer(recognizer)
|
||||
}
|
||||
|
||||
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
|
||||
@@ -155,6 +213,14 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
self.invalidateCalculatedLayout()
|
||||
self.updateHeight()
|
||||
}
|
||||
|
||||
self.interfaceInteraction?.updateTextInputState(self.inputTextState)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func editableTextNodeDidChangeSelection(_ editableTextNode: ASEditableTextNode, fromSelectedRange: NSRange, toSelectedRange: NSRange, dueToEditing: Bool) {
|
||||
if !dueToEditing && !updatingInputState {
|
||||
self.interfaceInteraction?.updateTextInputState(self.inputTextState)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ class ContactsPeerItemNode: ListViewItemNode {
|
||||
private let separatorNode: ASDisplayNode
|
||||
private let highlightedBackgroundNode: ASDisplayNode
|
||||
|
||||
private let avatarNode: ChatListAvatarNode
|
||||
private let avatarNode: AvatarNode
|
||||
private let titleNode: TextNode
|
||||
private let statusNode: TextNode
|
||||
|
||||
@@ -140,7 +140,7 @@ class ContactsPeerItemNode: ListViewItemNode {
|
||||
self.highlightedBackgroundNode.backgroundColor = UIColor(0xd9d9d9)
|
||||
self.highlightedBackgroundNode.isLayerBacked = true
|
||||
|
||||
self.avatarNode = ChatListAvatarNode(font: Font.regular(15.0))
|
||||
self.avatarNode = AvatarNode(font: Font.regular(15.0))
|
||||
self.avatarNode.isLayerBacked = true
|
||||
|
||||
self.titleNode = TextNode()
|
||||
|
||||
@@ -65,7 +65,7 @@ class ContactsVCardItemNode: ListViewItemNode {
|
||||
private let separatorNode: ASDisplayNode
|
||||
private let highlightedBackgroundNode: ASDisplayNode
|
||||
|
||||
private let avatarNode: ChatListAvatarNode
|
||||
private let avatarNode: AvatarNode
|
||||
private let titleNode: TextNode
|
||||
private let statusNode: TextNode
|
||||
|
||||
@@ -82,7 +82,7 @@ class ContactsVCardItemNode: ListViewItemNode {
|
||||
self.highlightedBackgroundNode.backgroundColor = UIColor(0xd9d9d9)
|
||||
self.highlightedBackgroundNode.isLayerBacked = true
|
||||
|
||||
self.avatarNode = ChatListAvatarNode(font: Font.regular(15.0))
|
||||
self.avatarNode = AvatarNode(font: Font.regular(15.0))
|
||||
self.avatarNode.isLayerBacked = true
|
||||
|
||||
self.titleNode = TextNode()
|
||||
|
||||
76
TelegramUI/HashtagChatInputContextPanelNode.swift
Normal file
76
TelegramUI/HashtagChatInputContextPanelNode.swift
Normal file
@@ -0,0 +1,76 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import Display
|
||||
|
||||
final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode, UITableViewDelegate, UITableViewDataSource {
|
||||
private let tableView: UITableView
|
||||
private let tableBackgroundView: UIView
|
||||
|
||||
private var results: [String] = []
|
||||
|
||||
override init() {
|
||||
self.tableView = UITableView(frame: CGRect(), style: .plain)
|
||||
self.tableBackgroundView = UIView()
|
||||
self.tableBackgroundView.backgroundColor = UIColor.white
|
||||
|
||||
super.init()
|
||||
|
||||
self.clipsToBounds = true
|
||||
|
||||
self.tableView.dataSource = self
|
||||
self.tableView.delegate = self
|
||||
self.tableView.rowHeight = 42.0
|
||||
self.tableView.showsVerticalScrollIndicator = false
|
||||
self.tableView.backgroundColor = nil
|
||||
self.tableView.isOpaque = false
|
||||
|
||||
self.view.addSubview(self.tableBackgroundView)
|
||||
self.view.addSubview(self.tableView)
|
||||
|
||||
self.results = (0 ..< 50).map { "#tag \($0)" }
|
||||
}
|
||||
|
||||
func setup(account: Account, peerId: PeerId, query: String) {
|
||||
}
|
||||
|
||||
private func updateTable() {
|
||||
let itemsHeight = CGFloat(self.results.count) * self.tableView.rowHeight
|
||||
let minimalDisplayedItemsHeight = floor(self.tableView.rowHeight * 3.5)
|
||||
let topInset = max(0.0, self.bounds.size.height - min(itemsHeight, minimalDisplayedItemsHeight))
|
||||
|
||||
self.tableView.contentInset = UIEdgeInsets(top: topInset, left: 0.0, bottom: 0.0, right: 0.0)
|
||||
self.tableView.contentOffset = CGPoint(x: 0.0, y: -topInset)
|
||||
self.tableView.setNeedsLayout()
|
||||
}
|
||||
|
||||
override func updateFrames(transition: ContainedViewLayoutTransition) {
|
||||
self.tableView.frame = self.bounds
|
||||
self.updateTable()
|
||||
}
|
||||
|
||||
override func animateIn() {
|
||||
self.layer.animateBounds(from: self.layer.bounds.offsetBy(dx: 0.0, dy: -self.layer.bounds.size.height), to: self.layer.bounds, duration: 0.45, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
}
|
||||
|
||||
override func animateOut(completion: @escaping () -> Void) {
|
||||
self.layer.animateBounds(from: self.layer.bounds, to: self.layer.bounds.offsetBy(dx: 0.0, dy: -self.layer.bounds.size.height), duration: 0.25, timingFunction: kCAMediaTimingFunctionEaseOut, removeOnCompletion: false, completion: { _ in
|
||||
completion()
|
||||
})
|
||||
}
|
||||
|
||||
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return self.results.count
|
||||
}
|
||||
|
||||
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
var cell = (tableView.dequeueReusableCell(withIdentifier: "C") as? HashtagsTableCell) ?? HashtagsTableCell()
|
||||
cell.textLabel?.text = self.results[indexPath.row]
|
||||
return cell
|
||||
}
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
self.tableBackgroundView.frame = CGRect(origin: CGPoint(x: 0.0, y: max(0.0, -self.tableView.contentOffset.y)), size: self.bounds.size)
|
||||
}
|
||||
}
|
||||
12
TelegramUI/HashtagsTableCell.swift
Normal file
12
TelegramUI/HashtagsTableCell.swift
Normal file
@@ -0,0 +1,12 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
final class HashtagsTableCell: UITableViewCell {
|
||||
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
@@ -34,13 +34,13 @@ final class HorizontalPeerItem: ListViewItem {
|
||||
}
|
||||
|
||||
private final class HorizontalPeerItemNode: ListViewItemNode {
|
||||
private let avatarNode: ChatListAvatarNode
|
||||
private let avatarNode: AvatarNode
|
||||
private let titleNode: ASTextNode
|
||||
private var peer: Peer?
|
||||
fileprivate var action: ((PeerId) -> Void)?
|
||||
|
||||
init() {
|
||||
self.avatarNode = ChatListAvatarNode(font: Font.regular(14.0))
|
||||
self.avatarNode = AvatarNode(font: Font.regular(14.0))
|
||||
//self.avatarNode.transform = CATransform3DMakeRotation(CGFloat(M_PI / 2.0), 0.0, 0.0, 1.0)
|
||||
self.avatarNode.frame = CGRect(origin: CGPoint(x: floor((92.0 - 60.0) / 2.0), y: 4.0), size: CGSize(width: 60.0, height: 60.0))
|
||||
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import TelegramCore
|
||||
|
||||
func smallestImageRepresentation(_ representations: [TelegramMediaImageRepresentation]) -> TelegramMediaImageRepresentation? {
|
||||
if representations.count == 0 {
|
||||
return nil
|
||||
} else {
|
||||
var dimensions = representations[0].dimensions
|
||||
var index = 0
|
||||
|
||||
for i in 1 ..< representations.count {
|
||||
let representationDimensions = representations[i].dimensions
|
||||
if representationDimensions.width < dimensions.width && representationDimensions.height < dimensions.height {
|
||||
dimensions = representationDimensions
|
||||
index = i
|
||||
}
|
||||
}
|
||||
|
||||
return representations[index]
|
||||
}
|
||||
}
|
||||
|
||||
func largestImageRepresentation(_ representations: [TelegramMediaImageRepresentation]) -> TelegramMediaImageRepresentation? {
|
||||
if representations.count == 0 {
|
||||
return nil
|
||||
} else {
|
||||
var dimensions = representations[0].dimensions
|
||||
var index = 0
|
||||
|
||||
for i in 1 ..< representations.count {
|
||||
let representationDimensions = representations[i].dimensions
|
||||
if representationDimensions.width > dimensions.width && representationDimensions.height > dimensions.height {
|
||||
dimensions = representationDimensions
|
||||
index = i
|
||||
}
|
||||
}
|
||||
|
||||
return representations[index]
|
||||
}
|
||||
}
|
||||
107
TelegramUI/MentionChatInputContextPanelNode.swift
Normal file
107
TelegramUI/MentionChatInputContextPanelNode.swift
Normal file
@@ -0,0 +1,107 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
|
||||
final class MentionChatInputContextPanelNode: ChatInputContextPanelNode, UITableViewDelegate, UITableViewDataSource {
|
||||
private let tableView: UITableView
|
||||
private let tableBackgroundView: UIView
|
||||
|
||||
private var account: Account?
|
||||
private var results: [Peer] = []
|
||||
|
||||
private let disposable = MetaDisposable()
|
||||
|
||||
override init() {
|
||||
self.tableView = UITableView(frame: CGRect(), style: .plain)
|
||||
self.tableBackgroundView = UIView()
|
||||
self.tableBackgroundView.backgroundColor = UIColor.white
|
||||
|
||||
super.init()
|
||||
|
||||
self.clipsToBounds = true
|
||||
|
||||
self.tableView.dataSource = self
|
||||
self.tableView.delegate = self
|
||||
self.tableView.rowHeight = 42.0
|
||||
self.tableView.showsVerticalScrollIndicator = false
|
||||
self.tableView.backgroundColor = nil
|
||||
self.tableView.isOpaque = false
|
||||
self.tableView.separatorInset = UIEdgeInsets(top: 0.0, left: 61.0, bottom: 0.0, right: 0.0)
|
||||
|
||||
self.view.addSubview(self.tableBackgroundView)
|
||||
self.view.addSubview(self.tableView)
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable.dispose()
|
||||
}
|
||||
|
||||
func setup(account: Account, peerId: PeerId, query: String) {
|
||||
self.account = account
|
||||
let signal = peerParticipants(account: account, id: peerId)
|
||||
|> deliverOnMainQueue
|
||||
|
||||
self.disposable.set(signal.start(next: { [weak self] peers in
|
||||
if let strongSelf = self {
|
||||
strongSelf.results = peers
|
||||
strongSelf.tableView.reloadData()
|
||||
strongSelf.updateTable(animated: true)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
private func updateTable(animated: Bool = false) {
|
||||
let itemsHeight = CGFloat(self.results.count) * self.tableView.rowHeight
|
||||
let minimalDisplayedItemsHeight = floor(self.tableView.rowHeight * 3.5)
|
||||
let topInset = max(0.0, self.bounds.size.height - min(itemsHeight, minimalDisplayedItemsHeight))
|
||||
|
||||
if animated {
|
||||
self.layer.animateBounds(from: self.layer.bounds.offsetBy(dx: 0.0, dy: -self.layer.bounds.size.height), to: self.layer.bounds, duration: 0.45, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
}
|
||||
|
||||
self.tableView.contentInset = UIEdgeInsets(top: topInset, left: 0.0, bottom: 0.0, right: 0.0)
|
||||
self.tableView.contentOffset = CGPoint(x: 0.0, y: -topInset)
|
||||
self.tableView.setNeedsLayout()
|
||||
}
|
||||
|
||||
override func updateFrames(transition: ContainedViewLayoutTransition) {
|
||||
self.tableView.frame = self.bounds
|
||||
self.updateTable()
|
||||
}
|
||||
|
||||
override func animateIn() {
|
||||
self.layer.animateBounds(from: self.layer.bounds.offsetBy(dx: 0.0, dy: -self.layer.bounds.size.height), to: self.layer.bounds, duration: 0.45, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
}
|
||||
|
||||
override func animateOut(completion: @escaping () -> Void) {
|
||||
self.layer.animateBounds(from: self.layer.bounds, to: self.layer.bounds.offsetBy(dx: 0.0, dy: -self.layer.bounds.size.height), duration: 0.25, timingFunction: kCAMediaTimingFunctionEaseOut, removeOnCompletion: false, completion: { _ in
|
||||
completion()
|
||||
})
|
||||
}
|
||||
|
||||
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return self.results.count
|
||||
}
|
||||
|
||||
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
var cell = (tableView.dequeueReusableCell(withIdentifier: "C") as? MentionsTableCell) ?? MentionsTableCell()
|
||||
if let account = self.account {
|
||||
cell.setupPeer(account: account, peer: self.results[indexPath.row])
|
||||
}
|
||||
return cell
|
||||
}
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
self.tableBackgroundView.frame = CGRect(origin: CGPoint(x: 0.0, y: max(0.0, -self.tableView.contentOffset.y)), size: self.bounds.size)
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
if let addressName = self.results[indexPath.row].addressName {
|
||||
let string = "@" + addressName + " "
|
||||
self.interfaceInteraction?.updateTextInputState(ChatTextInputState(inputText: string, selectionRange: string.characters.count ..< string.characters.count))
|
||||
}
|
||||
}
|
||||
}
|
||||
45
TelegramUI/MentionsTableCell.swift
Normal file
45
TelegramUI/MentionsTableCell.swift
Normal file
@@ -0,0 +1,45 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
|
||||
final class MentionsTableCell: UITableViewCell {
|
||||
private let avatarNode = AvatarNode(font: Font.regular(16.0))
|
||||
private let labelNode = TextNode()
|
||||
private var peer: Peer?
|
||||
|
||||
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
|
||||
self.contentView.addSubnode(self.avatarNode)
|
||||
self.contentView.addSubnode(self.labelNode)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func setupPeer(account: Account, peer: Peer) {
|
||||
self.peer = peer
|
||||
self.avatarNode.setPeer(account: account, peer: peer)
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
self.avatarNode.frame = CGRect(origin: CGPoint(x: 16.0, y: floor((self.bounds.size.height - 30.0) / 2.0)), size: CGSize(width: 30.0, height: 30.0))
|
||||
if let peer = self.peer {
|
||||
let makeLayout = TextNode.asyncLayout(self.labelNode)
|
||||
let string = NSMutableAttributedString()
|
||||
string.append(NSAttributedString(string: peer.displayTitle, font: Font.medium(15.0), textColor: UIColor.black))
|
||||
if let addressName = peer.addressName {
|
||||
string.append(NSAttributedString(string: " @" + addressName, font: Font.regular(15.0), textColor: UIColor(0x9099a2)))
|
||||
}
|
||||
let (layout, apply) = makeLayout(string, nil, 1, .end, CGSize(width: self.bounds.size.width - 61.0 - 10.0, height: self.bounds.size.height), nil)
|
||||
self.labelNode.frame = CGRect(origin: CGPoint(x: 61.0, y: floor((self.bounds.size.height - layout.size.height) / 2.0) + 2.0), size: layout.size)
|
||||
let _ = apply()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,23 +20,7 @@ private let roundCorners = { () -> UIImage in
|
||||
}()
|
||||
|
||||
func peerAvatarImage(account: Account, peer: Peer, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0)) -> Signal<UIImage, NoError>? {
|
||||
var location: TelegramCloudMediaLocation?
|
||||
|
||||
if let user = peer as? TelegramUser {
|
||||
if let photo = user.photo.first {
|
||||
location = photo.location.cloudLocation
|
||||
}
|
||||
} else if let group = peer as? TelegramGroup {
|
||||
if let photo = group.photo.first {
|
||||
location = photo.location.cloudLocation
|
||||
}
|
||||
} else if let channel = peer as? TelegramChannel {
|
||||
if let photo = channel.photo.first {
|
||||
location = photo.location.cloudLocation
|
||||
}
|
||||
}
|
||||
|
||||
if let location = location {
|
||||
if let location = peer.smallProfileImage?.location.cloudLocation {
|
||||
return deferred { () -> Signal<UIImage, NoError> in
|
||||
return cachedCloudFileLocation(location)
|
||||
|> `catch` { _ in
|
||||
|
||||
@@ -412,10 +412,10 @@ func chatMessagePhoto(account: Account, photo: TelegramMediaImage) -> Signal<(Tr
|
||||
if let blurredThumbnailImage = blurredThumbnailImage, let cgImage = blurredThumbnailImage.cgImage {
|
||||
c.interpolationQuality = .low
|
||||
c.draw(cgImage, in: fittedRect)
|
||||
c.setBlendMode(.normal)
|
||||
}
|
||||
|
||||
if let fullSizeImage = fullSizeImage {
|
||||
c.setBlendMode(.normal)
|
||||
c.interpolationQuality = .medium
|
||||
c.draw(fullSizeImage, in: fittedRect)
|
||||
}
|
||||
|
||||
@@ -28,13 +28,13 @@ private let nameFont = Font.medium(19.0)
|
||||
private let statusFont = Font.regular(15.0)
|
||||
|
||||
class SettingsAccountInfoItemNode: ListControllerGroupableItemNode {
|
||||
let avatarNode: ChatListAvatarNode
|
||||
let avatarNode: AvatarNode
|
||||
|
||||
let nameNode: TextNode
|
||||
let statusNode: TextNode
|
||||
|
||||
override init() {
|
||||
self.avatarNode = ChatListAvatarNode(font: Font.regular(20.0))
|
||||
self.avatarNode = AvatarNode(font: Font.regular(20.0))
|
||||
|
||||
self.nameNode = TextNode()
|
||||
self.nameNode.isLayerBacked = true
|
||||
|
||||
@@ -55,7 +55,7 @@ public class SettingsController: ListController {
|
||||
}
|
||||
peerAndConnectionStatusDisposable.set(peerAndConnectionStatus.start())
|
||||
|
||||
peer.set(account.postbox.peerWithId(account.peerId))
|
||||
peer.set(account.postbox.loadedPeerWithId(account.peerId))
|
||||
connectionStatus.set(account.network.connectionStatus)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,8 @@
|
||||
import Foundation
|
||||
import UIKit.UIGestureRecognizerSubclass
|
||||
|
||||
private class TouchDownGestureRecognizerTimerTarget: NSObject {
|
||||
weak var target: TouchDownGestureRecognizer?
|
||||
|
||||
init(target: TouchDownGestureRecognizer) {
|
||||
self.target = target
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
||||
@objc func event() {
|
||||
self.target?.timerEvent()
|
||||
}
|
||||
}
|
||||
|
||||
class TouchDownGestureRecognizer: UIGestureRecognizer, UIGestureRecognizerDelegate {
|
||||
private var touchLocation = CGPoint()
|
||||
private var timer: Foundation.Timer?
|
||||
var touchDown: (() -> Void)?
|
||||
|
||||
override init(target: Any?, action: Selector?) {
|
||||
super.init(target: target, action: action)
|
||||
@@ -26,53 +11,14 @@ class TouchDownGestureRecognizer: UIGestureRecognizer, UIGestureRecognizerDelega
|
||||
}
|
||||
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
if otherGestureRecognizer is UIPanGestureRecognizer {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override func reset() {
|
||||
self.timer?.invalidate()
|
||||
self.timer = nil
|
||||
|
||||
super.reset()
|
||||
}
|
||||
|
||||
func timerEvent() {
|
||||
self.state = .began
|
||||
}
|
||||
|
||||
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesBegan(touches, with: event)
|
||||
|
||||
if let touch = touches.first {
|
||||
self.touchLocation = touch.location(in: self.view)
|
||||
}
|
||||
|
||||
self.timer?.invalidate()
|
||||
self.timer = Timer(timeInterval: 0.08, target: TouchDownGestureRecognizerTimerTarget(target: self), selector: #selector(TouchDownGestureRecognizerTimerTarget.event), userInfo: nil, repeats: false)
|
||||
|
||||
if let timer = self.timer {
|
||||
RunLoop.main.add(timer, forMode: RunLoopMode.commonModes)
|
||||
}
|
||||
}
|
||||
|
||||
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesMoved(touches, with: event)
|
||||
|
||||
if let touch = touches.first {
|
||||
let location = touch.location(in: self.view)
|
||||
let distance = CGPoint(x: location.x - self.touchLocation.x, y: location.y - self.touchLocation.y)
|
||||
if distance.x * distance.x + distance.y * distance.y > 4.0 {
|
||||
self.state = .cancelled
|
||||
if let touchDown = self.touchDown {
|
||||
touchDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesEnded(touches, with: event)
|
||||
|
||||
self.state = .ended
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user