mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
e2fc108c03
@ -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?";
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -18,5 +18,6 @@ typedef NS_ENUM(NSInteger, STPCardBrand) {
|
||||
STPCardBrandDiscover,
|
||||
STPCardBrandJCB,
|
||||
STPCardBrandDinersClub,
|
||||
STPCardBrandOther,
|
||||
STPCardBrandUnknown,
|
||||
};
|
||||
|
@ -19,6 +19,7 @@
|
||||
case STPCardBrandMasterCard: return @"MasterCard";
|
||||
case STPCardBrandUnknown: return @"Unknown";
|
||||
case STPCardBrandVisa: return @"Visa";
|
||||
case STPCardBrandOther: return @"Other";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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";
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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
|
Loading…
x
Reference in New Issue
Block a user