Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2022-04-06 12:46:07 +04:00
commit e2fc108c03
15 changed files with 278 additions and 134 deletions

View File

@ -7485,3 +7485,19 @@ Sorry for the inconvenience.";
"PeerInfo.TooltipMutedFor" = "Notifications are muted for %@.";
"PeerInfo.TooltipMutedUntil" = "Notifications are muted until %@.";
"PeerInfo.TooltipMutedForever" = "Notifications are muted.";
"PeerInfo.DeleteToneTitle" = "Delete Tone";
"PeerInfo.DeleteToneText" = "Are you sure you want to delete\n**%@** notification sound?";
"PeerInfo.AlertLeaveAction" = "Leave";
"PeerInfo.LeaveGroupTitle" = "Leave Group";
"PeerInfo.LeaveGroupText" = "Are you sure you want to leave the group **%@**?";
"PeerInfo.LeaveChannelTitle" = "Leave Channel";
"PeerInfo.LeaveChannelText" = "Are you sure you want to leave the channel **%@**?";
"PeerInfo.DeleteGroupTitle" = "Delete for All";
"PeerInfo.DeleteGroupText" = "Are you sure you want to delete the group **%@** and all of its messages for all members of the group?";
"PeerInfo.DeleteChannelTitle" = "Delete for All";
"PeerInfo.DeleteChannelText" = "Are you sure you want to delete the channel **%@** and all of its messages for all subscribers of the channel?";

View File

@ -155,6 +155,7 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin
let titleSubtitleSpacing: CGFloat = 1.0
let iconSideInset: CGFloat = 12.0
let standardIconWidth: CGFloat = 32.0
let iconSpacing: CGFloat = 8.0
self.highlightBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor
@ -232,6 +233,7 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin
maxTextWidth -= sideInset
if let iconSize = iconSize {
maxTextWidth -= max(standardIconWidth, iconSize.width)
maxTextWidth -= iconSpacing
} else {
maxTextWidth -= sideInset
}
@ -246,6 +248,7 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin
if let iconSize = iconSize {
minSize.width += max(standardIconWidth, iconSize.width)
minSize.width += iconSideInset
minSize.width += iconSpacing
} else {
minSize.width += sideInset
}

View File

@ -106,6 +106,7 @@ public class ItemListCheckboxItemNode: ItemListRevealOptionsItemNode {
private let activateArea: AccessibilityAreaNode
private let contentParentNode: ASDisplayNode
private let contentContainerNode: ASDisplayNode
private let imageNode: ASImageNode
private let iconNode: ASImageNode
@ -113,6 +114,10 @@ public class ItemListCheckboxItemNode: ItemListRevealOptionsItemNode {
private var item: ItemListCheckboxItem?
override public var controlsContainer: ASDisplayNode {
return self.contentParentNode
}
public init() {
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isLayerBacked = true
@ -125,6 +130,7 @@ public class ItemListCheckboxItemNode: ItemListRevealOptionsItemNode {
self.maskNode = ASImageNode()
self.contentParentNode = ASDisplayNode()
self.contentContainerNode = ASDisplayNode()
self.imageNode = ASImageNode()
@ -149,7 +155,8 @@ public class ItemListCheckboxItemNode: ItemListRevealOptionsItemNode {
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
self.addSubnode(self.contentContainerNode)
self.addSubnode(self.contentParentNode)
self.contentParentNode.addSubnode(self.contentContainerNode)
self.contentContainerNode.addSubnode(self.imageNode)
self.contentContainerNode.addSubnode(self.iconNode)
self.contentContainerNode.addSubnode(self.titleNode)
@ -171,12 +178,12 @@ public class ItemListCheckboxItemNode: ItemListRevealOptionsItemNode {
switch item.style {
case .left:
leftInset += 44.0
leftInset += 62.0
case .right:
leftInset += 16.0
}
let iconInset: CGFloat = 44.0
let iconInset: CGFloat = 62.0
if item.icon != nil {
switch item.iconPlacement {
case .default:
@ -225,7 +232,8 @@ public class ItemListCheckboxItemNode: ItemListRevealOptionsItemNode {
if let strongSelf = self {
strongSelf.item = item
strongSelf.contentContainerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: params.width, height: layout.contentSize.height))
strongSelf.contentParentNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: params.width, height: layout.contentSize.height))
strongSelf.contentContainerNode.frame = CGRect(origin: CGPoint(x: strongSelf.contentContainerNode.frame.minX, y: 0.0), size: CGSize(width: params.width, height: layout.contentSize.height))
strongSelf.activateArea.accessibilityLabel = item.title
if item.checked {
@ -269,7 +277,7 @@ public class ItemListCheckboxItemNode: ItemListRevealOptionsItemNode {
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
}
if strongSelf.maskNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
strongSelf.insertSubnode(strongSelf.maskNode, aboveSubnode: strongSelf.contentParentNode)
}
let hasCorners = itemListHasRoundedBlockLayout(params)
var hasTopCorners = false

View File

@ -13,6 +13,7 @@ import AppBundle
import LegacyMediaPickerUI
import AVFoundation
import UndoUI
import Postbox
private struct NotificationSoundSelectionArguments {
let account: Account
@ -21,7 +22,7 @@ private struct NotificationSoundSelectionArguments {
let complete: () -> Void
let cancel: () -> Void
let upload: () -> Void
let deleteSound: (PeerMessageSound) -> Void
let deleteSound: (PeerMessageSound, String) -> Void
}
private enum NotificationSoundSelectionSection: Int32 {
@ -204,7 +205,7 @@ private enum NotificationSoundSelectionEntry: ItemListNodeEntry {
return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.selectSound(sound)
}, deleteAction: canBeDeleted ? {
arguments.deleteSound(sound)
arguments.deleteSound(sound, text)
} : nil)
}
}
@ -368,7 +369,7 @@ public func notificationSoundSelectionController(context: AccountContext, update
var completeImpl: (() -> Void)?
var cancelImpl: (() -> Void)?
var presentFilePicker: (() -> Void)?
var deleteSoundImpl: ((PeerMessageSound) -> Void)?
var deleteSoundImpl: ((PeerMessageSound, String) -> Void)?
let playSoundDisposable = MetaDisposable()
let soundActionDisposable = MetaDisposable()
@ -393,8 +394,8 @@ public func notificationSoundSelectionController(context: AccountContext, update
cancelImpl?()
}, upload: {
presentFilePicker?()
}, deleteSound: { sound in
deleteSoundImpl?(sound)
}, deleteSound: { sound, title in
deleteSoundImpl?(sound, title)
})
let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData
@ -442,45 +443,37 @@ public func notificationSoundSelectionController(context: AccountContext, update
presentCustomNotificationSoundFilePicker(context: context, controller: controller, disposable: soundActionDisposable)
}
deleteSoundImpl = { [weak controller] sound in
deleteSoundImpl = { [weak controller] sound, title in
guard let controller = controller else {
return
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let actionSheet = ActionSheetController(presentationData: presentationData)
actionSheet.setItemGroups([
ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.Common_Delete, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.PeerInfo_DeleteToneTitle, text: presentationData.strings.PeerInfo_DeleteToneText(title).string, actions: [
TextAlertAction(type: .destructiveAction, title: presentationData.strings.Common_Delete, action: {
updateState { state in
var state = state
updateState { state in
var state = state
state.removedSounds.append(sound)
if state.selectedSound.id == sound.id {
state.selectedSound = .bundledModern(id: 0)
}
return state
state.removedSounds.append(sound)
if state.selectedSound.id == sound.id {
state.selectedSound = .bundledModern(id: 0)
}
switch sound {
case let .cloud(id):
soundActionDisposable.set((context.engine.peers.deleteNotificationSound(fileId: id)
|> deliverOnMainQueue).start(completed: {
}))
default:
break
}
})
]),
ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])
])
controller.present(actionSheet, in: .window(.root))
return state
}
switch sound {
case let .cloud(id):
soundActionDisposable.set((context.engine.peers.deleteNotificationSound(fileId: id)
|> deliverOnMainQueue).start(completed: {
}))
default:
break
}
}),
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {
})
], parseMarkdown: true), in: .window(.root))
}
return controller
@ -501,6 +494,8 @@ public func presentCustomNotificationSoundFilePicker(context: AccountContext, co
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
controller.present(legacyICloudFilePicker(theme: presentationData.theme, documentTypes: ["public.mp3"], completion: { urls in
guard !urls.isEmpty, let url = urls.first else {
Logger.shared.log("NotificationSoundSelection", "url is nil")
return
}
@ -511,7 +506,28 @@ public func presentCustomNotificationSoundFilePicker(context: AccountContext, co
let coordinator = NSFileCoordinator(filePresenter: nil)
var error: NSError?
coordinator.coordinate(readingItemAt: url, options: .forUploading, error: &error, byAccessor: { url in
coordinator.coordinate(readingItemAt: url, options: .forUploading, error: &error, byAccessor: { souceUrl in
let fileName = url.lastPathComponent
var maybeUrl: URL?
let tempFile = TempBox.shared.tempFile(fileName: "file.mp3")
do {
try FileManager.default.copyItem(at: url, to: URL(fileURLWithPath: tempFile.path))
maybeUrl = URL(fileURLWithPath: tempFile.path)
} catch let e {
Logger.shared.log("NotificationSoundSelection", "copy file error \(e)")
TempBox.shared.dispose(tempFile)
souceUrl.stopAccessingSecurityScopedResource()
return
}
guard let url = maybeUrl else {
Logger.shared.log("NotificationSoundSelection", "temp url is nil")
TempBox.shared.dispose(tempFile)
souceUrl.stopAccessingSecurityScopedResource()
return
}
Queue.mainQueue().async {
do {
let asset = AVAsset(url: url)
@ -521,37 +537,72 @@ public func presentCustomNotificationSoundFilePicker(context: AccountContext, co
if data.count > settings.maxSize {
presentUndo(.info(title: presentationData.strings.Notifications_UploadError_TooLarge_Title, text: presentationData.strings.Notifications_UploadError_TooLarge_Text(dataSizeString(Int64(settings.maxSize), formatting: DataSizeStringFormatting(presentationData: presentationData))).string))
url.stopAccessingSecurityScopedResource()
souceUrl.stopAccessingSecurityScopedResource()
TempBox.shared.dispose(tempFile)
return
}
asset.loadValuesAsynchronously(forKeys: ["tracks", "duration"], completionHandler: {
if asset.statusOfValue(forKey: "duration", error: nil) != .loaded {
url.stopAccessingSecurityScopedResource()
return
}
func loadValues(asset: AVAsset, retryCount: Int, completion: @escaping () -> Void) {
asset.loadValuesAsynchronously(forKeys: ["tracks", "duration"], completionHandler: {
if asset.statusOfValue(forKey: "tracks", error: nil) == .loading {
if retryCount < 2 {
Queue.mainQueue().after(0.1, {
loadValues(asset: asset, retryCount: retryCount + 1, completion: completion)
})
} else {
completion()
}
} else {
completion()
}
})
}
loadValues(asset: asset, retryCount: 0, completion: {
var duration = 0.0
guard let track = asset.tracks(withMediaType: .audio).first else {
Logger.shared.log("NotificationSoundSelection", "track is nil")
url.stopAccessingSecurityScopedResource()
TempBox.shared.dispose(tempFile)
return
}
let duration = track.timeRange.duration.seconds
duration = track.timeRange.duration.seconds
if duration.isZero {
Logger.shared.log("NotificationSoundSelection", "duration is zero")
souceUrl.stopAccessingSecurityScopedResource()
TempBox.shared.dispose(tempFile)
return
}
TempBox.shared.dispose(tempFile)
Queue.mainQueue().async {
if duration > Double(settings.maxDuration) {
url.stopAccessingSecurityScopedResource()
souceUrl.stopAccessingSecurityScopedResource()
presentUndo(.info(title: presentationData.strings.Notifications_UploadError_TooLong_Title(url.lastPathComponent).string, text: presentationData.strings.Notifications_UploadError_TooLong_Text(stringForDuration(Int32(settings.maxDuration))).string))
presentUndo(.info(title: presentationData.strings.Notifications_UploadError_TooLong_Title(fileName).string, text: presentationData.strings.Notifications_UploadError_TooLong_Text(stringForDuration(Int32(settings.maxDuration))).string))
} else {
disposable.set((context.engine.peers.uploadNotificationSound(title: url.lastPathComponent, data: data)
Logger.shared.log("NotificationSoundSelection", "Uploading sound")
disposable.set((context.engine.peers.uploadNotificationSound(title: fileName, data: data)
|> deliverOnMainQueue).start(next: { _ in
presentUndo(.notificationSoundAdded(title: presentationData.strings.Notifications_UploadSuccess_Title, text: presentationData.strings.Notifications_UploadSuccess_Text(url.deletingPathExtension().lastPathComponent).string, action: nil))
Logger.shared.log("NotificationSoundSelection", "Upload done")
presentUndo(.notificationSoundAdded(title: presentationData.strings.Notifications_UploadSuccess_Title, text: presentationData.strings.Notifications_UploadSuccess_Text(fileName).string, action: nil))
}, error: { _ in
url.stopAccessingSecurityScopedResource()
Logger.shared.log("NotificationSoundSelection", "Upload error")
souceUrl.stopAccessingSecurityScopedResource()
}, completed: {
url.stopAccessingSecurityScopedResource()
souceUrl.stopAccessingSecurityScopedResource()
}))
}
}

View File

@ -53,9 +53,9 @@ private final class NotificationPeerExceptionArguments {
let complete: () -> Void
let cancel: () -> Void
let upload: () -> Void
let deleteSound: (PeerMessageSound) -> Void
let deleteSound: (PeerMessageSound, String) -> Void
init(account: Account, selectSound: @escaping(PeerMessageSound) -> Void, selectMode: @escaping(NotificationPeerExceptionSwitcher) -> Void, selectDisplayPreviews: @escaping (NotificationPeerExceptionSwitcher) -> Void, removeFromExceptions: @escaping () -> Void, complete: @escaping()->Void, cancel: @escaping() -> Void, upload: @escaping () -> Void, deleteSound: @escaping (PeerMessageSound) -> Void) {
init(account: Account, selectSound: @escaping(PeerMessageSound) -> Void, selectMode: @escaping(NotificationPeerExceptionSwitcher) -> Void, selectDisplayPreviews: @escaping (NotificationPeerExceptionSwitcher) -> Void, removeFromExceptions: @escaping () -> Void, complete: @escaping()->Void, cancel: @escaping() -> Void, upload: @escaping () -> Void, deleteSound: @escaping (PeerMessageSound, String) -> Void) {
self.account = account
self.selectSound = selectSound
self.selectMode = selectMode
@ -233,7 +233,7 @@ private enum NotificationPeerExceptionEntry: ItemListNodeEntry {
return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.selectSound(sound)
}, deleteAction: canBeDeleted ? {
arguments.deleteSound(sound)
arguments.deleteSound(sound, text)
} : nil)
}
}
@ -375,7 +375,7 @@ public func notificationPeerExceptionController(context: AccountContext, updated
var cancelImpl: (() -> Void)?
let playSoundDisposable = MetaDisposable()
var presentFilePicker: (() -> Void)?
var deleteSoundImpl: ((PeerMessageSound) -> Void)?
var deleteSoundImpl: ((PeerMessageSound, String) -> Void)?
let soundActionDisposable = MetaDisposable()
@ -411,8 +411,8 @@ public func notificationPeerExceptionController(context: AccountContext, updated
cancelImpl?()
}, upload: {
presentFilePicker?()
}, deleteSound: { sound in
deleteSoundImpl?(sound)
}, deleteSound: { sound, title in
deleteSoundImpl?(sound, title)
})
statePromise.set(context.account.postbox.transaction { transaction -> NotificationExceptionPeerState in
@ -502,45 +502,37 @@ public func notificationPeerExceptionController(context: AccountContext, updated
presentCustomNotificationSoundFilePicker(context: context, controller: controller, disposable: soundActionDisposable)
}
deleteSoundImpl = { [weak controller] sound in
deleteSoundImpl = { [weak controller] sound, title in
guard let controller = controller else {
return
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let actionSheet = ActionSheetController(presentationData: presentationData)
actionSheet.setItemGroups([
ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.Common_Delete, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.PeerInfo_DeleteToneTitle, text: presentationData.strings.PeerInfo_DeleteToneText(title).string, actions: [
TextAlertAction(type: .destructiveAction, title: presentationData.strings.Common_Delete, action: {
updateState { state in
var state = state
updateState { state in
var state = state
state.removedSounds.append(sound)
if state.selectedSound.id == sound.id {
state.selectedSound = .bundledModern(id: 0)
}
return state
state.removedSounds.append(sound)
if state.selectedSound.id == sound.id {
state.selectedSound = .bundledModern(id: 0)
}
switch sound {
case let .cloud(id):
soundActionDisposable.set((context.engine.peers.deleteNotificationSound(fileId: id)
|> deliverOnMainQueue).start(completed: {
}))
default:
break
}
})
]),
ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])
])
controller.present(actionSheet, in: .window(.root))
return state
}
switch sound {
case let .cloud(id):
soundActionDisposable.set((context.engine.peers.deleteNotificationSound(fileId: id)
|> deliverOnMainQueue).start(completed: {
}))
default:
break
}
}),
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {
})
], parseMarkdown: true), in: .window(.root))
}
return controller

View File

@ -18,5 +18,6 @@ typedef NS_ENUM(NSInteger, STPCardBrand) {
STPCardBrandDiscover,
STPCardBrandJCB,
STPCardBrandDinersClub,
STPCardBrandOther,
STPCardBrandUnknown,
};

View File

@ -19,6 +19,7 @@
case STPCardBrandMasterCard: return @"MasterCard";
case STPCardBrandUnknown: return @"Unknown";
case STPCardBrandVisa: return @"Visa";
case STPCardBrandOther: return @"Other";
}
}

View File

@ -66,7 +66,7 @@
@[@"492937", @"492937", @13, @(STPCardBrandVisa)],
@[@"492939", @"492939", @13, @(STPCardBrandVisa)],
@[@"492960", @"492960", @13, @(STPCardBrandVisa)],
@[@"2", @"2", @16, @(STPCardBrandVisa)],
@[@"2", @"2", @16, @(STPCardBrandOther)],
];
NSMutableArray *binRanges = [NSMutableArray array];
for (NSArray *range in ranges) {

View File

@ -64,6 +64,8 @@
return STPCardBrandJCB;
} else if ([brand isEqualToString:@"diners club"]) {
return STPCardBrandDinersClub;
} else if ([brand isEqualToString:@"other"]) {
return STPCardBrandOther;
} else {
return STPCardBrandUnknown;
}
@ -114,6 +116,8 @@
return @"MasterCard";
case STPCardBrandVisa:
return @"Visa";
case STPCardBrandOther:
return @"Other";
default:
return @"Unknown";
}

View File

@ -51,6 +51,7 @@ NS_ASSUME_NONNULL_BEGIN
* An icon representing Visa.
*/
+ (UIImage *)visaCardImage;
+ (UIImage *)otherCardImage;
/**
* An icon to use when the type of the card is unknown.

View File

@ -48,6 +48,10 @@
return [self brandImageForCardBrand:STPCardBrandUnknown];
}
+ (UIImage *)otherCardCardImage {
return [self brandImageForCardBrand:STPCardBrandUnknown];
}
+ (UIImage *)brandImageForCardBrand:(STPCardBrand)brand {
return [self brandImageForCardBrand:brand template:NO];
}
@ -140,6 +144,9 @@
break;
case STPCardBrandVisa:
imageName = shouldUseTemplate ? @"stp_card_visa_template" : @"stp_card_visa";
case STPCardBrandOther:
shouldUseTemplate = YES;
imageName = @"stp_card_placeholder_template";
break;
}
UIImage *image = [self safeImageNamed:imageName

View File

@ -9886,22 +9886,20 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self?.beginClearHistory(type: type)
}
let _ = (self.context.account.postbox.transaction { transaction -> Bool in
let _ = (self.context.account.postbox.transaction { transaction -> (isLargeGroupOrChannel: Bool, canClearChannel: Bool) in
if let cachedData = transaction.getPeerCachedData(peerId: peerId) as? CachedChannelData, let memberCount = cachedData.participantsSummary.memberCount {
if memberCount > 1000 {
return true
} else {
return false
}
return (memberCount > 1000, cachedData.flags.contains(.canDeleteHistory))
} else {
return false
return (false, false)
}
}
|> deliverOnMainQueue).start(next: { [weak self] isLargeGroupOrChannel in
|> deliverOnMainQueue).start(next: { [weak self] parameters in
guard let strongSelf = self else {
return
}
let (isLargeGroupOrChannel, canClearChannel) = parameters
guard let peer = strongSelf.presentationInterfaceState.renderedPeer, let chatPeer = peer.peers[peer.peerId], let mainPeer = peer.chatMainPeer else {
return
}
@ -9942,7 +9940,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if isLargeGroupOrChannel {
canClearCache = true
canClearForMyself = nil
canClearForEveryone = nil
canClearForEveryone = canClearChannel ? .channel : nil
} else {
canClearCache = true
canClearForMyself = nil
@ -9950,15 +9948,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
switch channel.info {
case .broadcast:
if channel.flags.contains(.isCreator) {
canClearForEveryone = nil
canClearForEveryone = canClearChannel ? .channel : nil
} else {
canClearForEveryone = nil
canClearForEveryone = canClearChannel ? .channel : nil
}
case .group:
if channel.flags.contains(.isCreator) {
canClearForEveryone = nil
canClearForEveryone = canClearChannel ? .channel : nil
} else {
canClearForEveryone = nil
canClearForEveryone = canClearChannel ? .channel : nil
}
}
}

View File

@ -377,7 +377,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
self.listButton.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
}
} else {
self.listButton.isHidden = !displayCloseButton
self.listButton.isHidden = !displayListButton
self.listButton.layer.removeAllAnimations()
}
}

View File

@ -3676,7 +3676,14 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
return
}
if let muteInterval = muteInterval, muteInterval == Int32.max {
strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .universal(animation: "anim_mute_for", scale: 0.056, colors: [:], title: nil, text: strongSelf.presentationData.strings.PeerInfo_TooltipMutedForever), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
let iconColor: UIColor = .white
strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .universal(animation: "anim_profilemute", scale: 0.075, colors: [
"Middle.Group 1.Fill 1": iconColor,
"Top.Group 1.Fill 1": iconColor,
"Bottom.Group 1.Fill 1": iconColor,
"EXAMPLE.Group 1.Fill 1": iconColor,
"Line.Group 1.Stroke 1": iconColor
], title: nil, text: strongSelf.presentationData.strings.PeerInfo_TooltipMutedForever), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
}
})
}, updatePeerDisplayPreviews: { peerId, displayPreviews in
@ -3703,7 +3710,14 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
let _ = strongSelf.context.engine.peers.updatePeerMuteSetting(peerId: strongSelf.peerId, muteInterval: Int32.max).start()
strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .universal(animation: "anim_mute_for", scale: 0.056, colors: [:], title: nil, text: strongSelf.presentationData.strings.PeerInfo_TooltipMutedForever), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
let iconColor: UIColor = .white
strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .universal(animation: "anim_profilemute", scale: 0.075, colors: [
"Middle.Group 1.Fill 1": iconColor,
"Top.Group 1.Fill 1": iconColor,
"Bottom.Group 1.Fill 1": iconColor,
"EXAMPLE.Group 1.Fill 1": iconColor,
"Line.Group 1.Stroke 1": iconColor
], title: nil, text: strongSelf.presentationData.strings.PeerInfo_TooltipMutedForever), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
})))
self.view.endEditing(true)
@ -4084,21 +4098,40 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
}, action: { [weak self] _, f in
f(.dismissWithoutContent)
self?.openLeavePeer()
self?.openLeavePeer(delete: false)
})))
if let cachedData = data.cachedData as? CachedChannelData, cachedData.flags.contains(.canDeleteHistory) {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.ChannelInfo_DeleteChannel, textColor: .destructive, icon: { theme in
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.destructiveColor)
}, action: { [weak self] _, f in
f(.dismissWithoutContent)
self?.openLeavePeer(delete: true)
})))
}
}
case .group:
if case .member = channel.participationStatus, !headerButtons.contains(.leave) {
if !items.isEmpty {
items.append(.separator)
}
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Group_LeaveGroup, textColor: .destructive, icon: { theme in
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Logout"), color: theme.contextMenu.destructiveColor)
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Group_LeaveGroup, textColor: .primary, icon: { theme in
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Logout"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in
f(.dismissWithoutContent)
self?.openLeavePeer()
self?.openLeavePeer(delete: false)
})))
if let cachedData = data.cachedData as? CachedChannelData, cachedData.flags.contains(.canDeleteHistory) {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Group_DeleteGroup, textColor: .destructive, icon: { theme in
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.destructiveColor)
}, action: { [weak self] _, f in
f(.dismissWithoutContent)
self?.openLeavePeer(delete: true)
})))
}
}
}
} else if let group = peer as? TelegramGroup {
@ -4120,8 +4153,18 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
}, action: { [weak self] _, f in
f(.dismissWithoutContent)
self?.openLeavePeer()
self?.openLeavePeer(delete: false)
})))
if case .creator = group.role {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Group_DeleteGroup, textColor: .destructive, icon: { theme in
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.destructiveColor)
}, action: { [weak self] _, f in
f(.dismissWithoutContent)
self?.openLeavePeer(delete: true)
})))
}
}
}
@ -4148,7 +4191,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
case .search:
self.openChatWithMessageSearch()
case .leave:
self.openLeavePeer()
self.openLeavePeer(delete: false)
case .stop:
self.updateBlocked(block: true)
}
@ -5549,7 +5592,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
})
}
private func openLeavePeer() {
private func openLeavePeer(delete: Bool) {
let peerId = self.peerId
let _ = (self.context.account.postbox.transaction { transaction -> Peer? in
return transaction.getPeer(peerId)
@ -5558,6 +5601,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
guard let strongSelf = self, let peer = peer else {
return
}
var isGroup = false
if let channel = peer as? TelegramChannel {
if case .group = channel.info {
@ -5566,22 +5610,40 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
} else if peer is TelegramGroup {
isGroup = true
}
let presentationData = strongSelf.presentationData
let actionSheet = ActionSheetController(presentationData: presentationData)
let dismissAction: () -> Void = { [weak actionSheet] in
actionSheet?.dismissAnimated()
let title: String
let text: String
let actionText: String
if delete {
if isGroup {
title = strongSelf.presentationData.strings.PeerInfo_DeleteGroupTitle
text = strongSelf.presentationData.strings.PeerInfo_DeleteGroupText(peer.debugDisplayTitle).string
} else {
title = strongSelf.presentationData.strings.PeerInfo_DeleteChannelTitle
text = strongSelf.presentationData.strings.PeerInfo_DeleteChannelText(peer.debugDisplayTitle).string
}
actionText = strongSelf.presentationData.strings.Common_Delete
} else {
if isGroup {
title = strongSelf.presentationData.strings.PeerInfo_LeaveGroupTitle
text = strongSelf.presentationData.strings.PeerInfo_LeaveGroupText(peer.debugDisplayTitle).string
} else {
title = strongSelf.presentationData.strings.PeerInfo_LeaveChannelTitle
text = strongSelf.presentationData.strings.PeerInfo_LeaveChannelText(peer.debugDisplayTitle).string
}
actionText = strongSelf.presentationData.strings.PeerInfo_AlertLeaveAction
}
actionSheet.setItemGroups([
ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: isGroup ? presentationData.strings.Group_LeaveGroup : presentationData.strings.Channel_LeaveChannel, color: .destructive, action: {
dismissAction()
self?.deletePeerChat(peer: peer, globally: false)
}),
]),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
])
strongSelf.view.endEditing(true)
strongSelf.controller?.present(actionSheet, in: .window(.root))
strongSelf.controller?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: title, text: text, actions: [
TextAlertAction(type: .destructiveAction, title: actionText, action: {
self?.deletePeerChat(peer: peer, globally: delete)
}),
TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {
})
], parseMarkdown: true), in: .window(.root))
})
}

@ -1 +1 @@
Subproject commit 4c1a39c67d4b03dd96ac82a9aa2420243f5bd106
Subproject commit aa4d82d73a84429b9955401bc8bb17ea92f30555