Merge branch 'master' into 125layer

This commit is contained in:
overtake 2021-03-01 17:24:28 +04:00
commit 796c77b11d
273 changed files with 5506 additions and 4732 deletions

View File

@ -17,7 +17,7 @@ jobs:
fetch-depth: '0'
- name: Set active Xcode path
run: sudo xcode-select -s /Applications/Xcode_12.3.app/Contents/Developer
run: sudo xcode-select -s /Applications/Xcode_12.4.app/Contents/Developer
- name: Create canonical source directory
run: |
@ -40,8 +40,8 @@ jobs:
# download bazel
mkdir -p $HOME/bazel-dist
pushd $HOME/bazel-dist
curl -O -L https://github.com/bazelbuild/bazel/releases/download/3.7.0/bazel-3.7.0-darwin-x86_64
mv bazel-3.7.0* bazel
curl -O -L https://github.com/bazelbuild/bazel/releases/download/4.0.0/bazel-4.0.0-darwin-x86_64
mv bazel-4.0.0* bazel
chmod +x bazel
./bazel --version
popd

View File

@ -6153,3 +6153,8 @@ Sorry for the inconvenience.";
"Conversation.UsersTooMuchError" = "Sorry, this group is full.";
"Conversation.UploadFileTooLarge" = "File could not be sent, because it is larger than 2 GB.\n\nYou can send as many files as you like, but each must be smaller than 2 GB.";
"Channel.AddUserLeftError" = "Sorry, if a person is no longer part of a channel, you need to be in their Telegram contacts in order to add them back.\n\nNote that they can still join via the channel's invite link as long as they are not in the Removed Users list.";
"Message.ScamAccount" = "Scam";
"Message.FakeAccount" = "Fake";

View File

@ -258,7 +258,7 @@ private final class ImportManager {
if !pathExtension.isEmpty, let value = TGMimeTypeMap.mimeType(forExtension: pathExtension) {
mimeType = value
}
return ChatHistoryImport.uploadMedia(account: account, session: session, file: tempFile, fileName: entry.0.path, mimeType: mimeType, type: entry.2)
return ChatHistoryImport.uploadMedia(account: account, session: session, file: tempFile, disposeFileAfterDone: true, fileName: entry.0.path, mimeType: mimeType, type: entry.2)
|> mapError { error -> ImportError in
switch error {
case .chatAdminRequired:

View File

@ -332,7 +332,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
if case .search = source {
if let _ = peer as? TelegramChannel {
items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_JoinChannel, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor) }, action: { _, f in
var createSignal = context.peerChannelMemberCategoriesContextsManager.join(account: context.account, peerId: peerId)
var createSignal = context.peerChannelMemberCategoriesContextsManager.join(account: context.account, peerId: peerId, hash: nil)
var cancelImpl: (() -> Void)?
let progressSignal = Signal<Never, NoError> { subscriber in
let presentationData = context.sharedContext.currentPresentationData.with { $0 }

View File

@ -1307,10 +1307,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _):
if let peer = messages.last?.author {
if peer.isScam {
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, type: .regular)
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
credibilityIconOffset = 2.0
} else if peer.isFake {
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, type: .regular)
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
credibilityIconOffset = 2.0
} else if peer.isVerified {
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
@ -1322,10 +1322,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
}
} else if case let .chat(itemPeer) = contentPeer, let peer = itemPeer.chatMainPeer {
if peer.isScam {
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, type: .regular)
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
credibilityIconOffset = 2.0
} else if peer.isFake {
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, type: .regular)
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
credibilityIconOffset = 2.0
} else if peer.isVerified {
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)

View File

@ -95,10 +95,17 @@ func contactContextMenuItems(context: AccountContext, peerId: PeerId, contactsCo
if let navigationController = (contactsController?.navigationController as? NavigationController) {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId), peekData: nil))
}
}, error: { _ in
}, error: { error in
if let contactsController = contactsController {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
contactsController.present(textAlertController(context: context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
let text: String
switch error {
case .limitExceeded:
text = presentationData.strings.TwoStepAuth_FloodError
default:
text = presentationData.strings.Login_UnknownError
}
contactsController.present(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}
}))
}
@ -121,10 +128,17 @@ func contactContextMenuItems(context: AccountContext, peerId: PeerId, contactsCo
}
if canCall {
items.append(.action(ContextMenuActionItem(text: strings.ContactList_Context_Call, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Call"), color: theme.contextMenu.primaryColor) }, action: { _, f in
if let contactsController = contactsController {
context.requestCall(peerId: peerId, isVideo: false, completion: {})
}
items.append(.action(ContextMenuActionItem(text: strings.ContactList_Context_Call, icon: { theme in
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Call"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
context.requestCall(peerId: peerId, isVideo: false, completion: {})
f(.default)
})))
}
if canVideoCall {
items.append(.action(ContextMenuActionItem(text: strings.ContactList_Context_VideoCall, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/VideoCall"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
context.requestCall(peerId: peerId, isVideo: true, completion: {})
f(.default)
})))
}

View File

@ -663,12 +663,17 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
transition.updateAlpha(node: self.textNode, alpha: displayCaption ? 1.0 : 0.0)
self.actionButton.frame = CGRect(origin: CGPoint(x: leftInset, y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0))
self.deleteButton.frame = CGRect(origin: CGPoint(x: width - 44.0 - rightInset, y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0))
self.editButton.frame = CGRect(origin: CGPoint(x: width - 44.0 - 50.0 - rightInset, y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0))
let deleteFrame = CGRect(origin: CGPoint(x: width - 44.0 - rightInset, y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0))
var editFrame = CGRect(origin: CGPoint(x: width - 44.0 - 50.0 - rightInset, y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0))
if self.deleteButton.isHidden {
editFrame = deleteFrame
}
self.deleteButton.frame = deleteFrame
self.editButton.frame = editFrame
if let image = self.backwardButton.image(for: .normal) {
self.backwardButton.frame = CGRect(origin: CGPoint(x: floor((width - image.size.width) / 2.0) - 66.0, y: panelHeight - bottomInset - 44.0 + 7.0), size: image.size)
}
if let image = self.forwardButton.image(for: .normal) {
self.forwardButton.frame = CGRect(origin: CGPoint(x: floor((width - image.size.width) / 2.0) + 66.0, y: panelHeight - bottomInset - 44.0 + 7.0), size: image.size)
@ -911,15 +916,20 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
if let strongSelf = self, !messages.isEmpty {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
var generalMessageContentKind: MessageContentKind?
var beganContentKindScanning = false
var messageContentKinds = Set<MessageContentKindKey>()
for message in messages {
let currentKind = messageContentKind(contentSettings: strongSelf.context.currentContentSettings.with { $0 }, message: message, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, accountPeerId: strongSelf.context.account.peerId)
if generalMessageContentKind == nil || generalMessageContentKind == currentKind {
generalMessageContentKind = currentKind
} else {
if beganContentKindScanning && currentKind != generalMessageContentKind {
generalMessageContentKind = nil
break
} else if !beganContentKindScanning || currentKind == generalMessageContentKind {
beganContentKindScanning = true
generalMessageContentKind = currentKind
}
messageContentKinds.insert(currentKind.key)
}
var preferredAction = ShareControllerPreferredAction.default
if let generalMessageContentKind = generalMessageContentKind {
switch generalMessageContentKind {
@ -928,6 +938,8 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
default:
break
}
} else if messageContentKinds.count == 2 && messageContentKinds.contains(.image) && messageContentKinds.contains(.video) {
preferredAction = .saveToCameraRoll
}
if messages.count == 1 {

View File

@ -164,7 +164,11 @@ open class ZoomableContentGalleryItemNode: GalleryItemNode, UIScrollViewDelegate
self.centerScrollViewContents(transition: transition)
self.ignoreZoom = false
let updatedZoomScale = self.scrollNode.view.zoomScale != self.scrollNode.view.minimumZoomScale
self.scrollNode.view.zoomScale = self.scrollNode.view.minimumZoomScale
if !updatedZoomScale {
self.scrollViewDidZoom(self.scrollNode.view)
}
self.ignoreZoomTransition = nil
}

View File

@ -309,7 +309,7 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode {
@objc func joinPressed() {
if let peer = self.peer, case .notJoined = self.joinState {
self.updateJoinState(.inProgress)
self.joinDisposable.set((joinChannel(account: self.context.account, peerId: peer.id) |> deliverOnMainQueue).start(error: { [weak self] _ in
self.joinDisposable.set((joinChannel(account: self.context.account, peerId: peer.id, hash: nil) |> deliverOnMainQueue).start(error: { [weak self] _ in
if let strongSelf = self {
if case .inProgress = strongSelf.joinState {
strongSelf.updateJoinState(.notJoined)

View File

@ -30,11 +30,11 @@ private final class InviteLinkListControllerArguments {
let mainLinkContextAction: (ExportedInvitation?, ASDisplayNode, ContextGesture?) -> Void
let createLink: () -> Void
let openLink: (ExportedInvitation) -> Void
let linkContextAction: (ExportedInvitation?, ASDisplayNode, ContextGesture?) -> Void
let linkContextAction: (ExportedInvitation?, Bool, ASDisplayNode, ContextGesture?) -> Void
let openAdmin: (ExportedInvitationCreator) -> Void
let deleteAllRevokedLinks: () -> Void
init(context: AccountContext, shareMainLink: @escaping (ExportedInvitation) -> Void, openMainLink: @escaping (ExportedInvitation) -> Void, copyLink: @escaping (ExportedInvitation) -> Void, mainLinkContextAction: @escaping (ExportedInvitation?, ASDisplayNode, ContextGesture?) -> Void, createLink: @escaping () -> Void, openLink: @escaping (ExportedInvitation?) -> Void, linkContextAction: @escaping (ExportedInvitation?, ASDisplayNode, ContextGesture?) -> Void, openAdmin: @escaping (ExportedInvitationCreator) -> Void, deleteAllRevokedLinks: @escaping () -> Void) {
init(context: AccountContext, shareMainLink: @escaping (ExportedInvitation) -> Void, openMainLink: @escaping (ExportedInvitation) -> Void, copyLink: @escaping (ExportedInvitation) -> Void, mainLinkContextAction: @escaping (ExportedInvitation?, ASDisplayNode, ContextGesture?) -> Void, createLink: @escaping () -> Void, openLink: @escaping (ExportedInvitation?) -> Void, linkContextAction: @escaping (ExportedInvitation?, Bool, ASDisplayNode, ContextGesture?) -> Void, openAdmin: @escaping (ExportedInvitationCreator) -> Void, deleteAllRevokedLinks: @escaping () -> Void) {
self.context = context
self.shareMainLink = shareMainLink
self.openMainLink = openMainLink
@ -65,7 +65,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
case linksHeader(PresentationTheme, String)
case linksCreate(PresentationTheme, String)
case link(Int32, PresentationTheme, ExportedInvitation?, Int32?)
case link(Int32, PresentationTheme, ExportedInvitation?, Bool, Int32?)
case linksInfo(PresentationTheme, String)
case revokedLinksHeader(PresentationTheme, String)
@ -104,7 +104,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
return 4
case .linksCreate:
return 5
case let .link(index, _, _, _):
case let .link(index, _, _, _, _):
return 6 + index
case .linksInfo:
return 10000
@ -159,8 +159,8 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
} else {
return false
}
case let .link(lhsIndex, lhsTheme, lhsLink, lhsTick):
if case let .link(rhsIndex, rhsTheme, rhsLink, rhsTick) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsLink == rhsLink, lhsTick == rhsTick {
case let .link(lhsIndex, lhsTheme, lhsLink, lhsCanEdit, lhsTick):
if case let .link(rhsIndex, rhsTheme, rhsLink, rhsCanEdit, rhsTick) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsLink == rhsLink, lhsCanEdit == rhsCanEdit, lhsTick == rhsTick {
return true
} else {
return false
@ -239,11 +239,11 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.plusIconImage(theme), title: text, sectionId: self.section, editing: false, action: {
arguments.createLink()
})
case let .link(_, _, invite, _):
case let .link(_, _, invite, canEdit, _):
return ItemListInviteLinkItem(presentationData: presentationData, invite: invite, share: false, sectionId: self.section, style: .blocks) { invite in
arguments.openLink(invite)
} contextAction: { invite, node, gesture in
arguments.linkContextAction(invite, node, gesture)
arguments.linkContextAction(invite, canEdit, node, gesture)
}
case let .linksInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
@ -257,12 +257,12 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
return ItemListInviteLinkItem(presentationData: presentationData, invite: invite, share: false, sectionId: self.section, style: .blocks) { invite in
arguments.openLink(invite)
} contextAction: { invite, node, gesture in
arguments.linkContextAction(invite, node, gesture)
arguments.linkContextAction(invite, false, node, gesture)
}
case let .adminsHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .admin(_, _, creator):
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(timeFormat: .regular, dateFormat: .monthFirst, dateSeparator: ".", decimalSeparator: ".", groupingSeparator: "."), nameDisplayOrder: .firstLast, context: arguments.context, peer: creator.peer.peer!, height: .peerList, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .none, label: .disclosure("\(creator.count)"), editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: nil), revealOptions: nil, switchValue: nil, enabled: true, highlighted: false, selectable: true, sectionId: self.section, action: {
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(timeFormat: .regular, dateFormat: .monthFirst, dateSeparator: ".", decimalSeparator: ".", groupingSeparator: "."), nameDisplayOrder: .firstLast, context: arguments.context, peer: creator.peer.peer!, height: .peerList, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .none, label: creator.count > 1 ? .disclosure("\(creator.count)") : .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: nil), revealOptions: nil, switchValue: nil, enabled: true, highlighted: false, selectable: true, sectionId: self.section, action: {
arguments.openAdmin(creator)
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, toggleUpdated: nil, contextAction: nil)
}
@ -333,16 +333,21 @@ private func inviteLinkListControllerEntries(presentationData: PresentationData,
entries.append(.linksCreate(presentationData.theme, presentationData.strings.InviteLink_Create))
}
var canEditLinks = true
if let peer = admin?.peer.peer as? TelegramUser, peer.botInfo != nil {
canEditLinks = false
}
if let additionalInvites = additionalInvites {
var index: Int32 = 0
for invite in additionalInvites {
entries.append(.link(index, presentationData.theme, invite, invite.expireDate != nil ? tick : nil))
entries.append(.link(index, presentationData.theme, invite, canEditLinks, invite.expireDate != nil ? tick : nil))
index += 1
}
} else if let admin = admin, admin.count > 1 {
var index: Int32 = 0
for _ in 0 ..< admin.count - 1 {
entries.append(.link(index, presentationData.theme, nil, nil))
entries.append(.link(index, presentationData.theme, nil, false, nil))
index += 1
}
}
@ -558,7 +563,7 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId, ad
let controller = InviteLinkViewController(context: context, peerId: peerId, invite: invite, invitationsContext: invitesContext, revokedInvitationsContext: revokedInvitesContext, importersContext: nil)
pushControllerImpl?(controller)
}
}, linkContextAction: { invite, node, gesture in
}, linkContextAction: { invite, canEdit, node, gesture in
guard let node = node as? ContextExtractedContentContainingNode, let controller = getControllerImpl?(), let invite = invite else {
return
}
@ -615,7 +620,7 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId, ad
})))
}
if !invite.isPermanent {
if !invite.isPermanent && canEdit {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextEdit, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
@ -687,8 +692,10 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId, ad
ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: {
dismissAction()
revokeLinkDisposable.set((revokePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(completed: {
revokeLinkDisposable.set((revokePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(next: { result in
if case let .replace(_, newInvite) = result {
invitesContext.add(newInvite)
}
}))
invitesContext.remove(invite)

View File

@ -537,8 +537,10 @@ public final class InviteLinkViewController: ViewController {
dismissAction()
self?.controller?.dismiss()
let _ = (revokePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(completed: {
let _ = (revokePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(next: { result in
if case let .replace(_, newInvite) = result {
self?.controller?.invitationsContext?.add(newInvite)
}
})
self?.controller?.invitationsContext?.remove(invite)

View File

@ -374,10 +374,10 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo
var credibilityIconOffset: CGFloat = 4.0
if let peer = item.peer {
if peer.isScam {
credibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, type: .regular)
credibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
credibilityIconOffset = 6.0
} else if peer.isFake {
credibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, type: .regular)
credibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
credibilityIconOffset = 2.0
} else if peer.isVerified {
credibilityIconImage = PresentationResourcesItemList.verifiedPeerIcon(item.presentationData.theme)

View File

@ -4,7 +4,7 @@
#define GPUImageRotationSwapsWidthAndHeight(rotation) ((rotation) == kGPUImageRotateLeft || (rotation) == kGPUImageRotateRight || (rotation) == kGPUImageRotateRightFlipVertical || (rotation) == kGPUImageRotateRightFlipHorizontal)
typedef enum { kGPUImageNoRotation, kGPUImageRotateLeft, kGPUImageRotateRight, kGPUImageFlipVertical, kGPUImageFlipHorizonal, kGPUImageRotateRightFlipVertical, kGPUImageRotateRightFlipHorizontal, kGPUImageRotate180 } GPUImageRotationMode;
typedef enum { kGPUImageNoRotation, kGPUImageRotateLeft, kGPUImageRotateRight, kGPUImageFlipVertical, kGPUImageFlipHorizonal, kGPUImageRotateRightFlipVertical, kGPUImageRotateRightFlipHorizontal, kGPUImageRotate180, kGPUImageRotate180FlipHorizontal } GPUImageRotationMode;
@interface GPUImageContext : NSObject

View File

@ -283,6 +283,13 @@ NSString *const kGPUImagePassthroughFragmentShaderString = SHADER_STRING
1.0f, 0.0f,
0.0f, 0.0f,
};
static const GLfloat rotate180HorizontalFlipTextureCoordinates[] = {
0.0f, 1.0f,
1.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f,
};
switch(rotationMode)
{
@ -294,6 +301,7 @@ NSString *const kGPUImagePassthroughFragmentShaderString = SHADER_STRING
case kGPUImageRotateRightFlipVertical: return rotateRightVerticalFlipTextureCoordinates;
case kGPUImageRotateRightFlipHorizontal: return rotateRightHorizontalFlipTextureCoordinates;
case kGPUImageRotate180: return rotate180TextureCoordinates;
case kGPUImageRotate180FlipHorizontal: return rotate180HorizontalFlipTextureCoordinates;
}
}
@ -642,6 +650,11 @@ NSString *const kGPUImagePassthroughFragmentShaderString = SHADER_STRING
rotatedPoint.x = 1.0f - pointToRotate.x;
rotatedPoint.y = 1.0f - pointToRotate.y;
}; break;
case kGPUImageRotate180FlipHorizontal:
{
rotatedPoint.x = pointToRotate.x;
rotatedPoint.y = 1.0f - pointToRotate.y;
}; break;
}
return rotatedPoint;

View File

@ -214,7 +214,7 @@
_rotationMode = cropMirrored ? kGPUImageRotateRightFlipHorizontal : kGPUImageRotateRight;
break;
case UIImageOrientationDown:
_rotationMode = kGPUImageRotate180;
_rotationMode = cropMirrored ? kGPUImageRotate180FlipHorizontal : kGPUImageRotate180;
break;
case UIImageOrientationUp:
if (cropMirrored)

View File

@ -285,6 +285,13 @@
0.0f, 1.0f,
};
static const GLfloat rotate180HorizontalFlipTextureCoordinates[] = {
0.0f, 1.0f,
1.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f,
};
switch(rotationMode)
{
case kGPUImageNoRotation: return noRotationTextureCoordinates;
@ -295,6 +302,7 @@
case kGPUImageRotateRightFlipVertical: return rotateRightVerticalFlipTextureCoordinates;
case kGPUImageRotateRightFlipHorizontal: return rotateRightHorizontalFlipTextureCoordinates;
case kGPUImageRotate180: return rotate180TextureCoordinates;
case kGPUImageRotate180FlipHorizontal: return rotate180HorizontalFlipTextureCoordinates;
}
}

View File

@ -118,16 +118,20 @@ NSString *const kYUVVideoRangeConversionForLAFragmentShaderString = SHADER_STRIN
- (GPUImageRotationMode)rotationForTrack:(AVAsset *)asset {
AVAssetTrack *videoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
CGAffineTransform trackTransform = [videoTrack preferredTransform];
CGAffineTransform t = [videoTrack preferredTransform];
if (trackTransform.a == -1 && trackTransform.d == -1) {
if (t.a == -1 && t.d == -1) {
return kGPUImageRotate180;
} else if (trackTransform.a == 1 && trackTransform.d == 1) {
} else if (t.a == 1 && t.d == 1) {
return kGPUImageNoRotation;
} else if (trackTransform.b == -1 && trackTransform.c == 1) {
} else if (t.b == -1 && t.c == 1) {
return kGPUImageRotateLeft;
} else if (t.a == -1 && t.d == 1) {
return kGPUImageFlipHorizonal;
} else if (t.a == 1 && t.d == -1) {
return kGPUImageRotate180FlipHorizontal;
} else {
if (trackTransform.c == 1) {
if (t.c == 1) {
return kGPUImageRotateRightFlipVertical;
} else {
return kGPUImageRotateRight;

View File

@ -593,28 +593,30 @@ UIImageOrientation TGVideoOrientationForAsset(AVAsset *asset, bool *mirrored)
{
AVAssetTrack *videoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] firstObject];
CGAffineTransform t = videoTrack.preferredTransform;
double videoRotation = atan2((float)t.b, (float)t.a);
if (mirrored != NULL)
{
CGFloat scaleX = sqrt(t.a * t.a + t.c * t.c);
CGFloat scaleY = sqrt(t.b * t.b + t.d * t.d);
CGSize scale = CGSizeMake(scaleX, scaleY);
*mirrored = (scale.width < 0);
}
if (fabs(videoRotation - M_PI) < FLT_EPSILON) {
if (t.a == -1 && t.d == -1) {
return UIImageOrientationLeft;
} else if (fabs(videoRotation - M_PI_2) < FLT_EPSILON) {
if (t.c == 1 && mirrored != NULL) {
} else if (t.a == 1 && t.d == 1) {
return UIImageOrientationRight;
} else if (t.b == -1 && t.c == 1) {
return UIImageOrientationDown;
} else if (t.a == -1 && t.d == 1) {
if (mirrored != NULL) {
*mirrored = true;
}
return UIImageOrientationLeft;
} else if (t.a == 1 && t.d == -1) {
if (mirrored != NULL) {
*mirrored = true;
}
return UIImageOrientationUp;
} else if (fabs(videoRotation + M_PI_2) < FLT_EPSILON) {
return UIImageOrientationDown;
} else {
return UIImageOrientationRight;
} else {
if (t.c == 1) {
if (mirrored != NULL) {
*mirrored = true;
}
}
return UIImageOrientationUp;
}
}

View File

@ -696,8 +696,8 @@ static void addRoundedRectToPath(CGContextRef context, CGRect rect, CGFloat oval
(id)UIColorRGB(0x80c864).CGColor, //green
(id)UIColorRGB(0xfcde65).CGColor, //yellow
(id)UIColorRGB(0xfc964d).CGColor, //orange
(id)[UIColor blackColor].CGColor, //black
(id)[UIColor whiteColor].CGColor //white
(id)UIColorRGB(0x000000).CGColor, //black
(id)UIColorRGB(0xffffff).CGColor //white
];
});
return colors;

View File

@ -61,7 +61,7 @@ const CGFloat TGPhotoPaintSettingsPadPickerWidth = 360.0f;
_eyedropperButton.exclusiveTouch = true;
[_eyedropperButton setImage:TGTintedImage([UIImage imageNamed:@"Editor/Eyedropper"], [UIColor whiteColor]) forState:UIControlStateNormal];
[_eyedropperButton addTarget:self action:@selector(eyedropperButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:_eyedropperButton];
// [self addSubview:_eyedropperButton];
_settingsButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0, 0, 44.0f, 44.0f)];
_settingsButton.exclusiveTouch = true;
@ -211,7 +211,8 @@ const CGFloat TGPhotoPaintSettingsPadPickerWidth = 360.0f;
- (void)layoutSubviews
{
CGFloat inset = 66.0f;
CGFloat leftInset = 23.0f;
CGFloat rightInset = 66.0f;
CGFloat colorPickerHeight = 10.0f;
if (self.frame.size.width > self.frame.size.height)
{
@ -222,14 +223,14 @@ const CGFloat TGPhotoPaintSettingsPadPickerWidth = 360.0f;
}
else
{
_colorPicker.frame = CGRectMake(inset, ceil((self.frame.size.height - colorPickerHeight) / 2.0f), self.frame.size.width - inset - inset, colorPickerHeight);
_colorPicker.frame = CGRectMake(leftInset, ceil((self.frame.size.height - colorPickerHeight) / 2.0f), self.frame.size.width - leftInset - rightInset, colorPickerHeight);
_eyedropperButton.frame = CGRectMake(10.0f, floor((self.frame.size.height - _eyedropperButton.frame.size.height) / 2.0f) + 1.0f, _eyedropperButton.frame.size.width, _eyedropperButton.frame.size.height);
_settingsButton.frame = CGRectMake(self.frame.size.width - _settingsButton.frame.size.width - 10.0f, floor((self.frame.size.height - _settingsButton.frame.size.height) / 2.0f) + 1.0f, _settingsButton.frame.size.width, _settingsButton.frame.size.height);
}
}
else
{
_colorPicker.frame = CGRectMake(ceil((self.frame.size.width - colorPickerHeight) / 2.0f), inset, colorPickerHeight, self.frame.size.height - inset - inset);
_colorPicker.frame = CGRectMake(ceil((self.frame.size.width - colorPickerHeight) / 2.0f), rightInset, colorPickerHeight, self.frame.size.height - leftInset - rightInset);
_eyedropperButton.frame = CGRectMake(floor((self.frame.size.width - _eyedropperButton.frame.size.width) / 2.0f), self.frame.size.height - _eyedropperButton.frame.size.height - 10.0, _eyedropperButton.frame.size.width, _eyedropperButton.frame.size.height);
_settingsButton.frame = CGRectMake(floor((self.frame.size.width - _settingsButton.frame.size.width) / 2.0f), 10.0f, _settingsButton.frame.size.width, _settingsButton.frame.size.height);
}

View File

@ -187,7 +187,7 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
_style = style;
switch (_style) {
case TGPhotoPaintTextEntityStyleRegular:
_textView.layer.shadowColor = [[UIColor blackColor] CGColor];
_textView.layer.shadowColor = [UIColorRGB(0x000000) CGColor];
_textView.layer.shadowOffset = CGSizeMake(0.0f, 4.0f);
_textView.layer.shadowOpacity = 0.4f;
_textView.layer.shadowRadius = 4.0f;
@ -218,7 +218,7 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
case TGPhotoPaintTextEntityStyleOutlined:
{
_textView.textColor = [UIColor whiteColor];
_textView.textColor = UIColorRGB(0xffffff);
_textView.strokeColor = _swatch.color;
_textView.frameColor = nil;
}
@ -238,9 +238,9 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
}
if (lightness > 0.87) {
_textView.textColor = [UIColor blackColor];
_textView.textColor = UIColorRGB(0x000000);
} else {
_textView.textColor = [UIColor whiteColor];
_textView.textColor = UIColorRGB(0xffffff);
}
_textView.strokeColor = nil;
_textView.frameColor = _swatch.color;
@ -484,6 +484,7 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
@implementation TGPhotoTextView
{
UIFont *_font;
UIColor *_forcedTextColor;
}
- (instancetype)initWithFrame:(CGRect)frame
@ -564,6 +565,11 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
self.layoutManager.textContainers.firstObject.lineFragmentPadding = floor(font.pointSize * 0.3);
}
- (void)setTextColor:(UIColor *)textColor {
_forcedTextColor = textColor;
[super setTextColor:textColor];
}
- (void)insertText:(NSString *)text {
[self fixTypingAttributes];
[super insertText:text];
@ -577,9 +583,14 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
}
- (void)fixTypingAttributes {
NSMutableDictionary *attributes = [[NSMutableDictionary alloc] init];
if (_font != nil) {
self.typingAttributes = @{NSFontAttributeName: _font};
attributes[NSFontAttributeName] = _font;
}
if (_forcedTextColor != nil) {
attributes[NSForegroundColorAttributeName] = _forcedTextColor;
}
self.typingAttributes = attributes;
}
@end

View File

@ -530,7 +530,7 @@ public func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) -
}
}
}
if estimatedSize > 5 * 1024 * 1024 {
if estimatedSize > 10 * 1024 * 1024 {
fileAttributes.append(.hintFileIsLarge)
}

View File

@ -11,11 +11,29 @@ final class FFMpegAudioFrameDecoder: MediaTrackFrameDecoder {
private var delayedFrames: [MediaTrackFrame] = []
init(codecContext: FFMpegAVCodecContext) {
init(codecContext: FFMpegAVCodecContext, sampleRate: Int = 44100, channelCount: Int = 2) {
self.codecContext = codecContext
self.audioFrame = FFMpegAVFrame()
self.swrContext = FFMpegSWResample(sourceChannelCount: Int(codecContext.channels()), sourceSampleRate: Int(codecContext.sampleRate()), sourceSampleFormat: codecContext.sampleFormat(), destinationChannelCount: 2, destinationSampleRate: 44100, destinationSampleFormat: FFMPEG_AV_SAMPLE_FMT_S16)
self.swrContext = FFMpegSWResample(sourceChannelCount: Int(codecContext.channels()), sourceSampleRate: Int(codecContext.sampleRate()), sourceSampleFormat: codecContext.sampleFormat(), destinationChannelCount: channelCount, destinationSampleRate: sampleRate, destinationSampleFormat: FFMPEG_AV_SAMPLE_FMT_S16)
}
func decodeRaw(frame: MediaTrackDecodableFrame) -> Data? {
let status = frame.packet.send(toDecoder: self.codecContext)
if status == 0 {
let result = self.codecContext.receive(into: self.audioFrame)
if case .success = result {
guard let data = self.swrContext.resample(self.audioFrame) else {
return nil
}
return data
} else {
return nil
}
} else {
return nil
}
}
func decode(frame: MediaTrackDecodableFrame) -> MediaTrackFrame? {

View File

@ -23,4 +23,8 @@ public final class MediaTrackDecodableFrame {
self.packet = packet
}
public func copyPacketData() -> Data {
return Data(bytes: self.packet.data, count: Int(self.packet.size))
}
}

View File

@ -179,7 +179,7 @@ public final class SoftwareVideoSource {
if endOfStream {
break
} else {
if let avFormatContext = self.avFormatContext, let videoStream = self.videoStream {
if let _ = self.avFormatContext, let _ = self.videoStream {
endOfStream = true
break
} else {
@ -254,3 +254,197 @@ public final class SoftwareVideoSource {
}
}
}
private final class SoftwareAudioStream {
let index: Int
let fps: CMTime
let timebase: CMTime
let duration: CMTime
let decoder: FFMpegAudioFrameDecoder
init(index: Int, fps: CMTime, timebase: CMTime, duration: CMTime, decoder: FFMpegAudioFrameDecoder) {
self.index = index
self.fps = fps
self.timebase = timebase
self.duration = duration
self.decoder = decoder
}
}
public final class SoftwareAudioSource {
private var readingError = false
private var audioStream: SoftwareAudioStream?
private var avIoContext: FFMpegAVIOContext?
private var avFormatContext: FFMpegAVFormatContext?
private let path: String
fileprivate let fd: Int32?
fileprivate let size: Int32
private var hasReadToEnd: Bool = false
public init(path: String) {
let _ = FFMpegMediaFrameSourceContextHelpers.registerFFMpegGlobals
var s = stat()
stat(path, &s)
self.size = Int32(s.st_size)
let fd = open(path, O_RDONLY, S_IRUSR)
if fd >= 0 {
self.fd = fd
} else {
self.fd = nil
}
self.path = path
let avFormatContext = FFMpegAVFormatContext()
let ioBufferSize = 64 * 1024
let avIoContext = FFMpegAVIOContext(bufferSize: Int32(ioBufferSize), opaqueContext: Unmanaged.passUnretained(self).toOpaque(), readPacket: readPacketCallback, writePacket: nil, seek: seekCallback)
self.avIoContext = avIoContext
avFormatContext.setIO(self.avIoContext!)
if !avFormatContext.openInput() {
self.readingError = true
return
}
if !avFormatContext.findStreamInfo() {
self.readingError = true
return
}
self.avFormatContext = avFormatContext
var audioStream: SoftwareAudioStream?
for streamIndexNumber in avFormatContext.streamIndices(for: FFMpegAVFormatStreamTypeAudio) {
let streamIndex = streamIndexNumber.int32Value
if avFormatContext.isAttachedPic(atStreamIndex: streamIndex) {
continue
}
let codecId = avFormatContext.codecId(atStreamIndex: streamIndex)
let fpsAndTimebase = avFormatContext.fpsAndTimebase(forStreamIndex: streamIndex, defaultTimeBase: CMTimeMake(value: 1, timescale: 40000))
let (fps, timebase) = (fpsAndTimebase.fps, fpsAndTimebase.timebase)
let duration = CMTimeMake(value: avFormatContext.duration(atStreamIndex: streamIndex), timescale: timebase.timescale)
let codec = FFMpegAVCodec.find(forId: codecId)
if let codec = codec {
let codecContext = FFMpegAVCodecContext(codec: codec)
if avFormatContext.codecParams(atStreamIndex: streamIndex, to: codecContext) {
if codecContext.open() {
audioStream = SoftwareAudioStream(index: Int(streamIndex), fps: fps, timebase: timebase, duration: duration, decoder: FFMpegAudioFrameDecoder(codecContext: codecContext, sampleRate: 48000, channelCount: 1))
break
}
}
}
}
self.audioStream = audioStream
if let audioStream = self.audioStream {
avFormatContext.seekFrame(forStreamIndex: Int32(audioStream.index), pts: 0, positionOnKeyframe: false)
}
}
deinit {
if let fd = self.fd {
close(fd)
}
}
private func readPacketInternal() -> FFMpegPacket? {
guard let avFormatContext = self.avFormatContext else {
return nil
}
let packet = FFMpegPacket()
if avFormatContext.readFrame(into: packet) {
return packet
} else {
return nil
}
}
func readDecodableFrame() -> (MediaTrackDecodableFrame?, Bool) {
var frames: [MediaTrackDecodableFrame] = []
var endOfStream = false
while !self.readingError && frames.isEmpty {
if let packet = self.readPacketInternal() {
if let audioStream = audioStream, Int(packet.streamIndex) == audioStream.index {
let packetPts = packet.pts
let pts = CMTimeMake(value: packetPts, timescale: audioStream.timebase.timescale)
let dts = CMTimeMake(value: packet.dts, timescale: audioStream.timebase.timescale)
let duration: CMTime
let frameDuration = packet.duration
if frameDuration != 0 {
duration = CMTimeMake(value: frameDuration * audioStream.timebase.value, timescale: audioStream.timebase.timescale)
} else {
duration = audioStream.fps
}
let frame = MediaTrackDecodableFrame(type: .audio, packet: packet, pts: pts, dts: dts, duration: duration)
frames.append(frame)
}
} else {
if endOfStream {
break
} else {
if let _ = self.avFormatContext, let _ = self.audioStream {
endOfStream = true
break
} else {
endOfStream = true
break
}
}
}
}
return (frames.first, endOfStream)
}
public func readFrame() -> Data? {
guard let audioStream = self.audioStream, let _ = self.avFormatContext else {
return nil
}
let (decodableFrame, _) = self.readDecodableFrame()
if let decodableFrame = decodableFrame {
return audioStream.decoder.decodeRaw(frame: decodableFrame)
} else {
return nil
}
}
public func readEncodedFrame() -> (Data, Int)? {
guard let _ = self.audioStream, let _ = self.avFormatContext else {
return nil
}
let (decodableFrame, _) = self.readDecodableFrame()
if let decodableFrame = decodableFrame {
return (decodableFrame.copyPacketData(), Int(decodableFrame.packet.duration - max(0, -decodableFrame.packet.pts)))
} else {
return nil
}
}
public func seek(timestamp: Double) {
if let stream = self.audioStream, let avFormatContext = self.avFormatContext {
let pts = CMTimeMakeWithSeconds(timestamp, preferredTimescale: stream.timebase.timescale)
avFormatContext.seekFrame(forStreamIndex: Int32(stream.index), pts: pts.value, positionOnKeyframe: false)
}
}
}

View File

@ -2,12 +2,21 @@
NS_ASSUME_NONNULL_BEGIN
@interface OggOpusFrame : NSObject
@property (nonatomic, readonly) int numSamples;
@property (nonatomic, strong, readonly) NSData *data;
@end
@interface OggOpusReader : NSObject
- (instancetype _Nullable)initWithPath:(NSString *)path;
- (int32_t)read:(void *)pcmData bufSize:(int)bufSize;
+ (NSArray<OggOpusFrame *> * _Nullable)extractFrames:(NSData *)data;
@end
NS_ASSUME_NONNULL_END

View File

@ -2,6 +2,37 @@
#import "opusfile/opusfile.h"
static int is_opus(ogg_page *og) {
ogg_stream_state os;
ogg_packet op;
ogg_stream_init(&os, ogg_page_serialno(og));
ogg_stream_pagein(&os, og);
if (ogg_stream_packetout(&os, &op) == 1)
{
if (op.bytes >= 19 && !memcmp(op.packet, "OpusHead", 8))
{
ogg_stream_clear(&os);
return 1;
}
}
ogg_stream_clear(&os);
return 0;
}
@implementation OggOpusFrame
- (instancetype)initWithNumSamples:(int)numSamples data:(NSData *)data {
self = [super init];
if (self != nil) {
_numSamples = numSamples;
_data = data;
}
return self;
}
@end
@interface OggOpusReader () {
OggOpusFile *_opusFile;
}
@ -32,4 +63,132 @@
return op_read(_opusFile, pcmData, bufSize, NULL);
}
+ (NSArray<OggOpusFrame *> * _Nullable)extractFrames:(NSData *)data {
NSMutableArray *result = [[NSMutableArray alloc] init];
ogg_page opage;
ogg_packet opacket;
ogg_sync_state ostate;
ogg_stream_state ostream;
int sampleRate = 48000;
if (ogg_sync_init(&ostate) < 0) {
return nil;
}
char *obuffer;
long obufferSize = (long)data.length;
obuffer = ogg_sync_buffer(&ostate, obufferSize);
if (!obuffer) {
return nil;
}
memcpy(obuffer, data.bytes, data.length);
// ogg_sync_wrote function is used to tell the ogg_sync_state struct how many bytes we wrote into the buffer.
if (ogg_sync_wrote(&ostate, obufferSize) < 0) {
return nil;
}
int pages = 0;
int packetsout = 0;
int invalid = 0;
int eos = 0;
int headers = 0;
int serialno = 0;
/* LOOP START */
while (ogg_sync_pageout(&ostate, &opage) == 1) {
pages++;
if (headers == 0) {
if (is_opus(&opage)) {
/* this is the start of an Opus stream */
serialno = ogg_page_serialno(&opage);
if (ogg_stream_init(&ostream, ogg_page_serialno(&opage)) < 0) {
return nil;
}
headers++;
} else if (!ogg_page_bos(&opage)) {
// We're past the header and haven't found an Opus stream.
// Time to give up.
break;
} else {
/* try again */
continue;
}
}
eos = ogg_page_eos(&opage);
/* submit the page for packetization */
if (ogg_stream_pagein(&ostream, &opage) < 0) {
return nil;
}
/* read and process available packets */
while (ogg_stream_packetout(&ostream, &opacket) == 1) {
packetsout++;
int samples;
/* skip header packets */
if (headers == 1 && opacket.bytes >= 19 && !memcmp(opacket.packet, "OpusHead", 8)) {
headers++;
continue;
}
if (headers == 2 && opacket.bytes >= 16 && !memcmp(opacket.packet, "OpusTags", 8)) {
headers++;
continue;
}
/* get packet duration */
samples = opus_packet_get_nb_samples(opacket.packet, opacket.bytes, sampleRate);
if (samples <= 0) {
invalid++;
continue; // skipping invalid packet
}
[result addObject:[[OggOpusFrame alloc] initWithNumSamples:samples data:[NSData dataWithBytes:opacket.packet length:opacket.bytes]]];
/* update the rtp header and send */
/*this->rtp.header_size = 12 + 4 * this->rtp.cc;
this->rtp.seq++;
this->rtp.time += samples;
this->rtp.payload_size = opacket.bytes;
// Create RTP Packet
unsigned char *packet;
size_t packetSize = this->rtp.header_size + this->rtp.payload_size;
packet = (unsigned char *)malloc(packetSize);
if (!packet)
throw Napi::Error::New(info.Env(), "Couldn't allocate packet buffer.");
// Serialize header and copy to packet. Then copy payload to packet.
serialize_rtp_header(packet, this->rtp.header_size, &this->rtp);
memcpy(packet + this->rtp.header_size, opacket.packet, opacket.bytes);
Napi::Buffer<unsigned char> output = Napi::Buffer<unsigned char>::Copy(env, reinterpret_cast<unsigned char *>(packet), packetSize);
push.Call(thisObj, {output});*/
}
if (eos > 0) {
// End of the logical bitstream, clear headers to reset.
headers = 0;
}
}
/* CLEAN UP */
if (eos > 0)
{
ogg_stream_clear(&ostream);
ogg_sync_clear(&ostate);
}
return result;
}
@end

View File

@ -1049,11 +1049,20 @@ public func channelAdminController(context: AccountContext, peerId: PeerId, admi
case .tooMuchJoined:
text = presentationData.strings.Group_ErrorSupergroupConversionNotPossible
case .restricted:
if let peer = adminView.peers[adminView.peerId] {
text = presentationData.strings.Privacy_GroupsAndChannels_InviteToGroupError(peer.compactDisplayTitle, peer.compactDisplayTitle).0
if let admin = adminView.peers[adminView.peerId] {
switch channel.info {
case .broadcast:
text = presentationData.strings.Privacy_GroupsAndChannels_InviteToChannelError(admin.compactDisplayTitle, admin.compactDisplayTitle).0
case .group:
text = presentationData.strings.Privacy_GroupsAndChannels_InviteToGroupError(admin.compactDisplayTitle, admin.compactDisplayTitle).0
}
}
case .notMutualContact:
text = presentationData.strings.GroupInfo_AddUserLeftError
if case .broadcast = channel.info {
text = presentationData.strings.Channel_AddUserLeftError
} else {
text = presentationData.strings.GroupInfo_AddUserLeftError
}
default:
break
}
@ -1119,17 +1128,28 @@ public func channelAdminController(context: AccountContext, peerId: PeerId, admi
return current.withUpdatedUpdating(true)
}
updateRightsDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberAdminRights(account: context.account, peerId: peerId, memberId: adminId, adminRights: TelegramChatAdminRights(rights: updateFlags), rank: updateRank) |> deliverOnMainQueue).start(error: { error in
if case let .addMemberError(error) = error, let admin = adminView.peers[adminView.peerId] {
if case .restricted = error {
var text = presentationData.strings.Privacy_GroupsAndChannels_InviteToChannelError(admin.compactDisplayTitle, admin.compactDisplayTitle).0
if case .group = channel.info {
text = presentationData.strings.Privacy_GroupsAndChannels_InviteToGroupError(admin.compactDisplayTitle, admin.compactDisplayTitle).0
}
presentControllerImpl?(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
} else if case .tooMuchJoined = error {
let text = presentationData.strings.Invite_ChannelsTooMuch
presentControllerImpl?(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
if case let .addMemberError(addMemberError) = error, let admin = adminView.peers[adminView.peerId] {
var text = presentationData.strings.Login_UnknownError
switch addMemberError {
case .tooMuchJoined:
text = presentationData.strings.Group_ErrorSupergroupConversionNotPossible
case .restricted:
switch channel.info {
case .broadcast:
text = presentationData.strings.Privacy_GroupsAndChannels_InviteToChannelError(admin.compactDisplayTitle, admin.compactDisplayTitle).0
case .group:
text = presentationData.strings.Privacy_GroupsAndChannels_InviteToGroupError(admin.compactDisplayTitle, admin.compactDisplayTitle).0
}
case .notMutualContact:
if case .broadcast = channel.info {
text = presentationData.strings.Channel_AddUserLeftError
} else {
text = presentationData.strings.GroupInfo_AddUserLeftError
}
default:
break
}
presentControllerImpl?(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
} else if case .adminsTooMuch = error {
let text: String
if case .broadcast = channel.info {
@ -1146,7 +1166,7 @@ public func channelAdminController(context: AccountContext, peerId: PeerId, admi
}))
}
}
} else if let group = channelView.peers[channelView.peerId] as? TelegramGroup {
} else if let _ = channelView.peers[channelView.peerId] as? TelegramGroup {
var updateFlags: TelegramChatAdminRightsFlags?
var updateRank: String?
updateState { current in
@ -1163,12 +1183,7 @@ public func channelAdminController(context: AccountContext, peerId: PeerId, admi
}
let maskRightsFlags: TelegramChatAdminRightsFlags = .groupSpecific
let defaultFlags: TelegramChatAdminRightsFlags
if case .creator = group.role {
defaultFlags = maskRightsFlags.subtracting(.canBeAnonymous)
} else {
defaultFlags = maskRightsFlags.subtracting(.canAddAdmins).subtracting(.canBeAnonymous)
}
let defaultFlags = maskRightsFlags.subtracting([.canBeAnonymous, .canAddAdmins])
if updateFlags == nil {
updateFlags = defaultFlags

View File

@ -370,27 +370,30 @@ public func channelMembersController(context: AccountContext, peerId: PeerId) ->
}
}).start(error: { [weak contactsController] error in
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let text: String
switch error {
case .limitExceeded:
text = presentationData.strings.Channel_ErrorAddTooMuch
case .tooMuchJoined:
text = presentationData.strings.Invite_ChannelsTooMuch
case .generic:
text = presentationData.strings.Login_UnknownError
case .restricted:
text = presentationData.strings.Channel_ErrorAddBlocked
case .notMutualContact:
text = presentationData.strings.GroupInfo_AddUserLeftError
case let .bot(memberId):
let _ = (context.account.postbox.transaction { transaction in
return transaction.getPeer(peerId)
}
|> deliverOnMainQueue).start(next: { peer in
let _ = (context.account.postbox.transaction { transaction in
return transaction.getPeer(peerId)
}
|> deliverOnMainQueue).start(next: { peer in
let text: String
switch error {
case .limitExceeded:
text = presentationData.strings.Channel_ErrorAddTooMuch
case .tooMuchJoined:
text = presentationData.strings.Invite_ChannelsTooMuch
case .generic:
text = presentationData.strings.Login_UnknownError
case .restricted:
text = presentationData.strings.Channel_ErrorAddBlocked
case .notMutualContact:
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
text = presentationData.strings.Channel_AddUserLeftError
} else {
text = presentationData.strings.GroupInfo_AddUserLeftError
}
case let .bot(memberId):
guard let peer = peer as? TelegramChannel else {
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
contactsController?.dismiss()
return
}
@ -407,15 +410,15 @@ public func channelMembersController(context: AccountContext, peerId: PeerId) ->
}
contactsController?.dismiss()
})
return
case .botDoesntSupportGroups:
text = presentationData.strings.Channel_BotDoesntSupportGroups
case .tooMuchBots:
text = presentationData.strings.Channel_TooMuchBots
}
presentControllerImpl?(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
contactsController?.dismiss()
return
case .botDoesntSupportGroups:
text = presentationData.strings.Channel_BotDoesntSupportGroups
case .tooMuchBots:
text = presentationData.strings.Channel_TooMuchBots
}
presentControllerImpl?(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
contactsController?.dismiss()
})
}))
presentControllerImpl?(contactsController, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))

View File

@ -1059,6 +1059,7 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
|> deliverOnMainQueue
let previousHadNamesToRevoke = Atomic<Bool?>(value: nil)
let previousInvitation = Atomic<ExportedInvitation?>(value: nil)
let signal = combineLatest(context.sharedContext.presentationData, statePromise.get() |> deliverOnMainQueue, peerView, peersDisablingAddressNameAssignment.get() |> deliverOnMainQueue)
|> deliverOnMainQueue
@ -1253,6 +1254,15 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
}
var crossfade: Bool = false
if let cachedData = view.cachedData as? CachedChannelData {
let invitation = cachedData.exportedInvitation
let previousInvitation = previousInvitation.swap(invitation)
if invitation != previousInvitation {
crossfade = true
}
}
let hasNamesToRevoke = publicChannelsToRevoke != nil && !publicChannelsToRevoke!.isEmpty
let hadNamesToRevoke = previousHadNamesToRevoke.swap(hasNamesToRevoke)
if let peer = view.peers[view.peerId] as? TelegramChannel {
@ -1273,7 +1283,7 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
}
}
if selectedType == .publicChannel, let hadNamesToRevoke = hadNamesToRevoke {
if selectedType == .publicChannel, let hadNamesToRevoke = hadNamesToRevoke, !crossfade {
crossfade = hadNamesToRevoke != hasNamesToRevoke
}
}

View File

@ -1420,10 +1420,17 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Pe
if let navigationController = (controller?.navigationController as? NavigationController) {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId)))
}
}, error: { [weak controller] _ in
}, error: { [weak controller] error in
if let controller = controller {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
controller.present(textAlertController(context: context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
let text: String
switch error {
case .limitExceeded:
text = presentationData.strings.TwoStepAuth_FloodError
default:
text = presentationData.strings.Login_UnknownError
}
controller.present(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}
}))
})]), in: .window(.root))

View File

@ -100,6 +100,10 @@ public func chatMessagePhotoDatas(postbox: Postbox, photoReference: ImageMediaRe
switch results[i].0 {
case .image:
if let data = results[i].1, data.count != 0 {
if Int(fullRepresentationSize.width) > 100 && i <= 1 && !isLastSize {
continue
}
subscriber.putNext(Tuple4(nil, data, .full, isLastSize))
foundData = true
if isLastSize {
@ -623,6 +627,11 @@ public func chatMessagePhotoInternal(photoData: Signal<Tuple4<Data?, Data?, Chat
if let thumbnailData = thumbnailData, let imageSource = CGImageSourceCreateWithData(thumbnailData as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) {
thumbnailImage = image
}
if quality == .blurred && fullSizeImage != nil {
thumbnailImage = fullSizeImage
fullSizeImage = nil
}
var blurredThumbnailImage: UIImage?
if let thumbnailImage = thumbnailImage {

View File

@ -73,7 +73,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
case optimizeDatabase(PresentationTheme)
case photoPreview(PresentationTheme, Bool)
case knockoutWallpaper(PresentationTheme, Bool)
case alternativeFolderTabs(Bool)
case demoAudioStream(Bool)
case snapPinListToTop(Bool)
case playerEmbedding(Bool)
case playlistPlayback(Bool)
@ -94,7 +94,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return DebugControllerSection.logging.rawValue
case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries:
return DebugControllerSection.experiments.rawValue
case .clearTips, .reimport, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .alternativeFolderTabs, .snapPinListToTop, .playerEmbedding, .playlistPlayback, .voiceConference:
case .clearTips, .reimport, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .demoAudioStream, .snapPinListToTop, .playerEmbedding, .playlistPlayback, .voiceConference:
return DebugControllerSection.experiments.rawValue
case .preferredVideoCodec:
return DebugControllerSection.videoExperiments.rawValue
@ -155,7 +155,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return 22
case .knockoutWallpaper:
return 23
case .alternativeFolderTabs:
case .demoAudioStream:
return 24
case .snapPinListToTop:
return 25
@ -696,7 +696,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
})
}).start()
})
case let .knockoutWallpaper(theme, value):
case let .knockoutWallpaper(_, value):
return ItemListSwitchItem(presentationData: presentationData, title: "Knockout Wallpaper", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
@ -706,12 +706,12 @@ private enum DebugControllerEntry: ItemListNodeEntry {
})
}).start()
})
case let .alternativeFolderTabs(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Alternative Tabs", value: value, sectionId: self.section, style: .blocks, updated: { value in
case let .demoAudioStream(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Demo Audio Stream", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings as? ExperimentalUISettings ?? ExperimentalUISettings.defaultSettings
settings.foldersTabAtBottom = value
settings.demoAudioStream = value
return settings
})
}).start()
@ -826,7 +826,7 @@ private func debugControllerEntries(presentationData: PresentationData, loggingS
entries.append(.optimizeDatabase(presentationData.theme))
//entries.append(.photoPreview(presentationData.theme, experimentalSettings.chatListPhotos))
entries.append(.knockoutWallpaper(presentationData.theme, experimentalSettings.knockoutWallpaper))
entries.append(.alternativeFolderTabs(experimentalSettings.foldersTabAtBottom))
entries.append(.demoAudioStream(experimentalSettings.demoAudioStream))
entries.append(.snapPinListToTop(experimentalSettings.snapPinListToTop))
entries.append(.playerEmbedding(experimentalSettings.playerEmbedding))
entries.append(.playlistPlayback(experimentalSettings.playlistPlayback))

View File

@ -469,7 +469,7 @@ public final class ShareController: ViewController {
}
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: title, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}, externalShare: self.externalShare, immediateExternalShare: self.immediateExternalShare, immediatePeerId: self.immediatePeerId, shares: self.shares, fromForeignApp: self.fromForeignApp, forcedTheme: self.forcedTheme)
self.controllerNode.completed = completed
self.controllerNode.completed = self.completed
self.controllerNode.dismiss = { [weak self] shared in
self?.presentingViewController?.dismiss(animated: false, completion: nil)
self?.dismissed?(shared)
@ -721,13 +721,18 @@ public final class ShareController: ViewController {
})
activities = [shareToInstagram]
}
let activityController = UIActivityViewController(activityItems: activityItems, applicationActivities: activities)
if let window = strongSelf.view.window, let rootViewController = window.rootViewController {
activityController.popoverPresentationController?.sourceView = window
activityController.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: window.bounds.width / 2.0, y: window.bounds.size.height - 1.0), size: CGSize(width: 1.0, height: 1.0))
rootViewController.present(activityController, animated: true, completion: nil)
}
let _ = (strongSelf.didAppearPromise.get()
|> filter { $0 }
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] _ in
let activityController = UIActivityViewController(activityItems: activityItems, applicationActivities: activities)
if let strongSelf = self, let window = strongSelf.view.window, let rootViewController = window.rootViewController {
activityController.popoverPresentationController?.sourceView = window
activityController.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: window.bounds.width / 2.0, y: window.bounds.size.height - 1.0), size: CGSize(width: 1.0, height: 1.0))
rootViewController.present(activityController, animated: true, completion: nil)
}
})
}
return .done
}
@ -785,12 +790,16 @@ public final class ShareController: ViewController {
super.loadView()
}
let didAppearPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if !self.animatedIn {
self.animatedIn = true
self.controllerNode.animateIn()
self.didAppearPromise.set(true)
if !self.immediateExternalShare {
self.controllerNode.animateIn()
}
}
}

View File

@ -21,10 +21,6 @@ enum ShareExternalState {
case done
}
func openExternalShare(state: () -> Signal<ShareExternalState, NoError>) {
}
final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate {
private let sharedContext: SharedAccountContext
private var context: AccountContext?
@ -175,6 +171,8 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
}
super.init()
self.isHidden = true
self.controllerInteraction = ShareControllerInteraction(togglePeer: { [weak self] peer, search in
if let strongSelf = self {
@ -621,6 +619,11 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
}
func animateIn() {
if let completion = self.outCompletion {
self.outCompletion = nil
completion()
return
}
if self.contentNode != nil {
self.isHidden = false
@ -635,6 +638,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
}
}
var outCompletion: (() -> Void)?
func animateOut(shared: Bool, completion: @escaping () -> Void) {
if self.contentNode != nil {
var dimCompleted = false
@ -664,7 +668,13 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
internalCompletion()
})
} else {
completion()
self.outCompletion = completion
Queue.mainQueue().after(0.2) {
if let completion = self.outCompletion {
self.outCompletion = nil
completion()
}
}
}
}

View File

@ -128,7 +128,7 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri
let estimatedSize = TGMediaVideoConverter.estimatedSize(for: preset, duration: finalDuration, hasAudio: true)
let resource = LocalFileVideoMediaResource(randomId: arc4random64(), path: asset.url.path, adjustments: resourceAdjustments)
return standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .resource(.standalone(resource: resource)), mimeType: "video/mp4", attributes: [.Video(duration: Int(finalDuration), size: PixelDimensions(width: Int32(finalDimensions.width), height: Int32(finalDimensions.height)), flags: flags)], hintFileIsLarge: estimatedSize > 5 * 1024 * 1024)
return standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .resource(.standalone(resource: resource)), mimeType: "video/mp4", attributes: [.Video(duration: Int(finalDuration), size: PixelDimensions(width: Int32(finalDimensions.width), height: Int32(finalDimensions.height)), flags: flags)], hintFileIsLarge: estimatedSize > 10 * 1024 * 1024)
|> mapError { _ -> Void in
return Void()
}
@ -192,7 +192,7 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri
mimeType = "animation/gif"
attributes = [.ImageSize(size: PixelDimensions(width: Int32(dimensions.width), height: Int32(dimensions.height))), .Animated, .FileName(fileName: fileName ?? "animation.gif")]
}
return standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .data(data), mimeType: mimeType, attributes: attributes, hintFileIsLarge: data.count > 5 * 1024 * 1024)
return standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .data(data), mimeType: mimeType, attributes: attributes, hintFileIsLarge: data.count > 10 * 1024 * 1024)
|> mapError { _ -> Void in return Void() }
|> mapToSignal { event -> Signal<PreparedShareItem, Void> in
switch event {
@ -223,7 +223,7 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri
thumbnailData = jpegData
}
return standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .data(data), thumbnailData: thumbnailData, mimeType: mimeType, attributes: [.FileName(fileName: fileName ?? "file")], hintFileIsLarge: data.count > 5 * 1024 * 1024)
return standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .data(data), thumbnailData: thumbnailData, mimeType: mimeType, attributes: [.FileName(fileName: fileName ?? "file")], hintFileIsLarge: data.count > 10 * 1024 * 1024)
|> mapError { _ -> Void in return Void() }
|> mapToSignal { event -> Signal<PreparedShareItem, Void> in
switch event {
@ -241,13 +241,14 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri
let isVoice = ((value["isVoice"] as? NSNumber)?.boolValue ?? false)
let title = value["title"] as? String
let artist = value["artist"] as? String
let mimeType = value["mimeType"] as? String ?? "audio/ogg"
var waveform: MemoryBuffer?
if let waveformData = TGItemProviderSignals.audioWaveform(url) {
waveform = MemoryBuffer(data: waveformData)
}
return standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .data(audioData), mimeType: "audio/ogg", attributes: [.Audio(isVoice: isVoice, duration: Int(duration), title: title, performer: artist, waveform: waveform), .FileName(fileName: fileName)], hintFileIsLarge: audioData.count > 5 * 1024 * 1024)
return standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .data(audioData), mimeType: mimeType, attributes: [.Audio(isVoice: isVoice, duration: Int(duration), title: title, performer: artist, waveform: waveform), .FileName(fileName: fileName)], hintFileIsLarge: audioData.count > 10 * 1024 * 1024)
|> mapError { _ -> Void in return Void() }
|> mapToSignal { event -> Signal<PreparedShareItem, Void> in
switch event {

View File

@ -140,33 +140,31 @@ final class StickerPackPreviewGridItemNode: GridItemNode {
if self.currentState == nil || self.currentState!.0 !== account || self.currentState!.1 != stickerItem || self.isEmpty != isEmpty {
if let stickerItem = stickerItem {
if let _ = stickerItem.file.dimensions {
if stickerItem.file.isAnimatedSticker {
let dimensions = stickerItem.file.dimensions ?? PixelDimensions(width: 512, height: 512)
self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: account.postbox, file: stickerItem.file, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0))))
if self.animationNode == nil {
let animationNode = AnimatedStickerNode()
self.animationNode = animationNode
self.addSubnode(animationNode)
animationNode.started = { [weak self] in
self?.imageNode.isHidden = true
self?.removePlaceholder(animated: false)
}
if stickerItem.file.isAnimatedSticker {
let dimensions = stickerItem.file.dimensions ?? PixelDimensions(width: 512, height: 512)
self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: account.postbox, file: stickerItem.file, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0))))
if self.animationNode == nil {
let animationNode = AnimatedStickerNode()
self.animationNode = animationNode
self.addSubnode(animationNode)
animationNode.started = { [weak self] in
self?.imageNode.isHidden = true
self?.removePlaceholder(animated: false)
}
let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0))
self.animationNode?.setup(source: AnimatedStickerResourceSource(account: account, resource: stickerItem.file.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .cached)
self.animationNode?.visibility = self.isVisibleInGrid && self.interaction?.playAnimatedStickers ?? true
self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: account, fileReference: stickerPackFileReference(stickerItem.file), resource: stickerItem.file.resource).start())
} else {
if let animationNode = self.animationNode {
animationNode.visibility = false
self.animationNode = nil
animationNode.removeFromSupernode()
}
self.imageNode.setSignal(chatMessageSticker(account: account, file: stickerItem.file, small: true))
self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: account, fileReference: stickerPackFileReference(stickerItem.file), resource: chatMessageStickerResource(file: stickerItem.file, small: true)).start())
}
let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0))
self.animationNode?.setup(source: AnimatedStickerResourceSource(account: account, resource: stickerItem.file.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .cached)
self.animationNode?.visibility = self.isVisibleInGrid && self.interaction?.playAnimatedStickers ?? true
self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: account, fileReference: stickerPackFileReference(stickerItem.file), resource: stickerItem.file.resource).start())
} else {
if let animationNode = self.animationNode {
animationNode.visibility = false
self.animationNode = nil
animationNode.removeFromSupernode()
}
self.imageNode.setSignal(chatMessageSticker(account: account, file: stickerItem.file, small: true))
self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: account, fileReference: stickerPackFileReference(stickerItem.file), resource: chatMessageStickerResource(file: stickerItem.file, small: true)).start())
}
} else {
if let placeholderNode = self.placeholderNode {

View File

@ -845,7 +845,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
}
strongSelf.maybeRequestParticipants(ssrcs: ssrcs)
}
})
}, demoAudioStream: self.accountContext.sharedContext.immediateExperimentalUISettings.demoAudioStream)
self.incomingVideoSourcePromise.set(callContext.videoSources
|> deliverOnMainQueue
|> map { [weak self] sources -> [PeerId: UInt32] in

View File

@ -887,7 +887,11 @@ public final class VoiceChatController: ViewController {
case .restricted:
text = presentationData.strings.Channel_ErrorAddBlocked
case .notMutualContact:
text = presentationData.strings.GroupInfo_AddUserLeftError
if case .broadcast = groupPeer.info {
text = presentationData.strings.Channel_AddUserLeftError
} else {
text = presentationData.strings.GroupInfo_AddUserLeftError
}
case .botDoesntSupportGroups:
text = presentationData.strings.Channel_BotDoesntSupportGroups
case .tooMuchBots:
@ -952,15 +956,9 @@ public final class VoiceChatController: ViewController {
self?.controller?.present(textAlertController(context: context, title: nil, text: presentationData.strings.Privacy_GroupsAndChannels_InviteToGroupError(peer.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
})
case .notMutualContact:
let _ = (strongSelf.context.account.postbox.loadedPeerWithId(peer.id)
|> deliverOnMainQueue).start(next: { peer in
self?.controller?.present(textAlertController(context: context, title: nil, text: presentationData.strings.GroupInfo_AddUserLeftError, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
})
strongSelf.controller?.present(textAlertController(context: context, title: nil, text: presentationData.strings.GroupInfo_AddUserLeftError, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
case .tooManyChannels:
let _ = (strongSelf.context.account.postbox.loadedPeerWithId(peer.id)
|> deliverOnMainQueue).start(next: { peer in
self?.controller?.present(textAlertController(context: context, title: nil, text: presentationData.strings.Invite_ChannelsTooMuch, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
})
strongSelf.controller?.present(textAlertController(context: context, title: nil, text: presentationData.strings.Invite_ChannelsTooMuch, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
case .groupFull, .generic:
strongSelf.controller?.present(textAlertController(context: strongSelf.context, forceTheme: strongSelf.darkTheme, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}

View File

@ -107,7 +107,7 @@ public enum ChatHistoryImport {
case chatAdminRequired
}
public static func uploadMedia(account: Account, session: Session, file: TempBoxFile, fileName: String, mimeType: String, type: MediaType) -> Signal<Float, UploadMediaError> {
public static func uploadMedia(account: Account, session: Session, file: TempBoxFile, disposeFileAfterDone: Bool, fileName: String, mimeType: String, type: MediaType) -> Signal<Float, UploadMediaError> {
var forceNoBigParts = true
guard let size = fileSize(file.path), size != 0 else {
return .single(1.0)
@ -160,6 +160,11 @@ public enum ChatHistoryImport {
|> mapToSignal { result -> Signal<Float, UploadMediaError> in
return .single(1.0)
}
|> afterDisposed {
if disposeFileAfterDone {
TempBox.shared.dispose(file)
}
}
}
}
@ -192,7 +197,7 @@ public enum ChatHistoryImport {
case invalidChatType
case userBlocked
case limitExceeded
case userIsNotMutualContact
case notMutualContact
}
public static func checkPeerImport(account: Account, peerId: PeerId) -> Signal<CheckPeerImportResult, CheckPeerImportError> {
@ -217,7 +222,7 @@ public enum ChatHistoryImport {
} else if error.errorDescription == "USER_IS_BLOCKED" {
return .userBlocked
} else if error.errorDescription == "USER_NOT_MUTUAL_CONTACT" {
return .userBlocked
return .notMutualContact
} else if error.errorDescription == "FLOOD_WAIT" {
return .limitExceeded
} else {

View File

@ -6,6 +6,7 @@ import MtProtoKit
public enum CreateSecretChatError {
case generic
case limitExceeded
}
public func createSecretChat(account: Account, peerId: PeerId) -> Signal<PeerId, CreateSecretChatError> {
@ -29,9 +30,13 @@ public func createSecretChat(account: Account, peerId: PeerId) -> Signal<PeerId,
return .fail(.generic)
}
return account.network.request(Api.functions.messages.requestEncryption(userId: inputUser, randomId: Int32(bitPattern: arc4random()), gA: Buffer(data: ga)))
|> mapError { _ -> CreateSecretChatError in
return .generic
return account.network.request(Api.functions.messages.requestEncryption(userId: inputUser, randomId: Int32(bitPattern: arc4random()), gA: Buffer(data: ga)), automaticFloodWait: false)
|> mapError { error -> CreateSecretChatError in
if error.errorDescription.hasPrefix("FLOOD_WAIT_") {
return .limitExceeded
} else {
return .generic
}
}
|> mapToSignal { result -> Signal<PeerId, CreateSecretChatError> in
return account.postbox.transaction { transaction -> PeerId in

View File

@ -529,7 +529,7 @@ private final class PeerExportedInvitationsContextImpl {
}
private func updateCache() {
guard self.hasLoadedOnce && !self.isLoadingMore else {
guard self.isMainList && self.hasLoadedOnce && !self.isLoadingMore else {
return
}

View File

@ -2,6 +2,7 @@ import Foundation
import Postbox
import TelegramApi
import SwiftSignalKit
import MtProtoKit
import SyncCore
@ -11,13 +12,19 @@ public enum JoinChannelError {
case tooMuchUsers
}
public func joinChannel(account: Account, peerId: PeerId) -> Signal<RenderedChannelParticipant?, JoinChannelError> {
public func joinChannel(account: Account, peerId: PeerId, hash: String?) -> Signal<RenderedChannelParticipant?, JoinChannelError> {
return account.postbox.loadedPeerWithId(peerId)
|> take(1)
|> castError(JoinChannelError.self)
|> mapToSignal { peer -> Signal<RenderedChannelParticipant?, JoinChannelError> in
if let inputChannel = apiInputChannel(peer) {
return account.network.request(Api.functions.channels.joinChannel(channel: inputChannel))
let request: Signal<Api.Updates, MTRpcError>
if let hash = hash {
request = account.network.request(Api.functions.messages.importChatInvite(hash: hash))
} else {
request = account.network.request(Api.functions.channels.joinChannel(channel: inputChannel))
}
return request
|> mapError { error -> JoinChannelError in
switch error.errorDescription {
case "CHANNELS_TOO_MUCH":

View File

@ -202,6 +202,8 @@ public func updateChannelAdminRights(account: Account, peerId: PeerId, adminId:
}
|> map { [$0] }
)
} else if error.errorDescription == "USER_NOT_MUTUAL_CONTACT" {
return .fail(.addMemberError(.notMutualContact))
} else if error.errorDescription == "USER_PRIVACY_RESTRICTED" {
return .fail(.addMemberError(.restricted))
} else if error.errorDescription == "USER_CHANNELS_TOO_MUCH" {

View File

@ -623,7 +623,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
hintFileIsLarge = true
break loop
default:
break loop
break
}
}

View File

@ -212,7 +212,7 @@ public struct PresentationResourcesChatList {
})
}
public static func scamIcon(_ theme: PresentationTheme, type: ScamIconType) -> UIImage? {
public static func scamIcon(_ theme: PresentationTheme, strings: PresentationStrings, type: ScamIconType) -> UIImage? {
let key: PresentationResourceKey
let color: UIColor
switch type {
@ -227,7 +227,10 @@ public struct PresentationResourcesChatList {
color = theme.chat.serviceMessage.components.withDefaultWallpaper.scam
}
return theme.image(key.rawValue, { theme in
return generateImage(CGSize(width: 37.0, height: 16.0), contextGenerator: { size, context in
let titleString = NSAttributedString(string: strings.Message_ScamAccount.uppercased(), font: Font.bold(10.0), textColor: color, paragraphAlignment: .center)
let stringRect = titleString.boundingRect(with: CGSize(width: 100.0, height: 16.0), options: .usesLineFragmentOrigin, context: nil)
return generateImage(CGSize(width: floor(stringRect.width) + 11.0, height: 16.0), contextGenerator: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds)
@ -240,7 +243,6 @@ public struct PresentationResourcesChatList {
let titlePath = CGMutablePath()
titlePath.addRect(bounds.offsetBy(dx: 0.0, dy: -2.0 + UIScreenPixel))
let titleString = NSAttributedString(string: "SCAM", font: Font.bold(10.0), textColor: color, paragraphAlignment: .center)
let titleFramesetter = CTFramesetterCreateWithAttributedString(titleString as CFAttributedString)
let titleFrame = CTFramesetterCreateFrame(titleFramesetter, CFRangeMake(0, titleString.length), titlePath, nil)
CTFrameDraw(titleFrame, context)
@ -248,7 +250,7 @@ public struct PresentationResourcesChatList {
})
}
public static func fakeIcon(_ theme: PresentationTheme, type: ScamIconType) -> UIImage? {
public static func fakeIcon(_ theme: PresentationTheme, strings: PresentationStrings, type: ScamIconType) -> UIImage? {
let key: PresentationResourceKey
let color: UIColor
switch type {
@ -263,7 +265,10 @@ public struct PresentationResourcesChatList {
color = theme.chat.serviceMessage.components.withDefaultWallpaper.scam
}
return theme.image(key.rawValue, { theme in
return generateImage(CGSize(width: 37.0, height: 16.0), contextGenerator: { size, context in
let titleString = NSAttributedString(string: strings.Message_FakeAccount.uppercased(), font: Font.bold(10.0), textColor: color, paragraphAlignment: .center)
let stringRect = titleString.boundingRect(with: CGSize(width: 100.0, height: 16.0), options: .usesLineFragmentOrigin, context: nil)
return generateImage(CGSize(width: floor(stringRect.width) + 11.0, height: 16.0), contextGenerator: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds)
@ -276,7 +281,6 @@ public struct PresentationResourcesChatList {
let titlePath = CGMutablePath()
titlePath.addRect(bounds.offsetBy(dx: 0.0, dy: -2.0 + UIScreenPixel))
let titleString = NSAttributedString(string: "FAKE", font: Font.bold(10.0), textColor: color, paragraphAlignment: .center)
let titleFramesetter = CTFramesetterCreateWithAttributedString(titleString as CFAttributedString)
let titleFrame = CTFramesetterCreateFrame(titleFramesetter, CFRangeMake(0, titleString.length), titlePath, nil)
CTFrameDraw(titleFrame, context)

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ic_menuvideocall.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Some files were not shown because too many files have changed in this diff Show More