Various settings UI improvements
@ -4097,6 +4097,7 @@ Unused sets are archived when you add more.";
|
||||
|
||||
"ChatSettings.AutoDownloadSettings.TypePhoto" = "Photos";
|
||||
"ChatSettings.AutoDownloadSettings.TypeVideo" = "Videos (%@)";
|
||||
"ChatSettings.AutoDownloadSettings.TypeMedia" = "Media (%@)";
|
||||
"ChatSettings.AutoDownloadSettings.TypeFile" = "Files (%@)";
|
||||
"ChatSettings.AutoDownloadSettings.OffForAll" = "Disabled";
|
||||
"ChatSettings.AutoDownloadSettings.Delimeter" = ", ";
|
||||
@ -6938,3 +6939,21 @@ Sorry for the inconvenience.";
|
||||
"Map.ETADays_3_10" = "%@ days";
|
||||
"Map.ETADays_many" = "%@ days";
|
||||
"Map.ETADays_any" = "%@ days";
|
||||
|
||||
"ChatSettings.UseLessDataForCalls" = "Use Less Data for Calls";
|
||||
|
||||
"Time.JustNow" = "just now";
|
||||
"Time.MinutesAgo_0" = "%@ minutes ago"; //three to ten
|
||||
"Time.MinutesAgo_1" = "%@ minute ago"; //one
|
||||
"Time.MinutesAgo_2" = "%@ minutes ago"; //two
|
||||
"Time.MinutesAgo_3_10" = "%@ minutes ago"; //three to ten
|
||||
"Time.MinutesAgo_many" = "%@ minutes ago"; // more than ten
|
||||
"Time.MinutesAgo_any" = "%@ minutes ago"; // more than ten
|
||||
"Time.HoursAgo_0" = "%@ hours ago";
|
||||
"Time.HoursAgo_1" = "%@ hour ago";
|
||||
"Time.HoursAgo_2" = "%@ hours ago";
|
||||
"Time.HoursAgo_3_10" = "%@ hours ago";
|
||||
"Time.HoursAgo_any" = "%@ hours ago";
|
||||
"Time.HoursAgo_many" = "%@ hours ago";
|
||||
"Time.HoursAgo_0" = "%@ hours ago";
|
||||
"Time.AtDate" = "last seen %@";
|
||||
|
@ -343,7 +343,7 @@ public final class AvatarNode: ASDisplayNode {
|
||||
|
||||
let parameters: AvatarNodeParameters
|
||||
|
||||
if let peer = peer, let signal = peerAvatarImage(account: context.account, peerReference: PeerReference(peer._asPeer()), authorOfMessage: authorOfMessage, representation: representation, displayDimensions: displayDimensions, emptyColor: emptyColor, synchronousLoad: synchronousLoad, provideUnrounded: storeUnrounded) {
|
||||
if let peer = peer, let signal = peerAvatarImage(account: context.account, peerReference: PeerReference(peer._asPeer()), authorOfMessage: authorOfMessage, representation: representation, displayDimensions: displayDimensions, round: clipStyle == .round, emptyColor: emptyColor, synchronousLoad: synchronousLoad, provideUnrounded: storeUnrounded) {
|
||||
self.contents = nil
|
||||
self.displaySuspended = true
|
||||
self.imageReady.set(self.imageNode.contentReady)
|
||||
|
@ -188,6 +188,7 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
private let highlightedBackgroundNode: ASDisplayNode
|
||||
private let maskNode: ASImageNode
|
||||
|
||||
private let avatarNode: AvatarNode
|
||||
private let titleNode: TextNode
|
||||
@ -206,6 +207,8 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
|
||||
self.maskNode = ASImageNode()
|
||||
|
||||
self.topStripeNode = ASDisplayNode()
|
||||
self.topStripeNode.isLayerBacked = true
|
||||
|
||||
@ -523,7 +526,9 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
|
||||
} else if last && strongSelf.bottomStripeNode.supernode != nil {
|
||||
strongSelf.bottomStripeNode.removeFromSupernode()
|
||||
}
|
||||
|
||||
if strongSelf.maskNode.supernode != nil {
|
||||
strongSelf.maskNode.removeFromSupernode()
|
||||
}
|
||||
transition.updateFrameAdditive(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - leftInset, height: separatorHeight)))
|
||||
case .blocks:
|
||||
if strongSelf.backgroundNode.supernode == nil {
|
||||
@ -535,11 +540,18 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
|
||||
if strongSelf.bottomStripeNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
|
||||
}
|
||||
if strongSelf.maskNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
|
||||
}
|
||||
let hasCorners = itemListHasRoundedBlockLayout(params)
|
||||
var hasTopCorners = false
|
||||
var hasBottomCorners = false
|
||||
switch neighbors.top {
|
||||
case .sameSection(false):
|
||||
strongSelf.topStripeNode.isHidden = true
|
||||
default:
|
||||
strongSelf.topStripeNode.isHidden = false
|
||||
hasTopCorners = true
|
||||
strongSelf.topStripeNode.isHidden = hasCorners
|
||||
}
|
||||
let bottomStripeInset: CGFloat
|
||||
switch neighbors.bottom {
|
||||
@ -547,9 +559,14 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
|
||||
bottomStripeInset = leftInset
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
hasBottomCorners = true
|
||||
strongSelf.bottomStripeNode.isHidden = hasCorners
|
||||
}
|
||||
|
||||
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
|
||||
|
||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
|
||||
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: nodeLayout.size.width, height: separatorHeight))
|
||||
transition.updateFrameAdditive(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: nodeLayout.size.width - bottomStripeInset, height: separatorHeight)))
|
||||
}
|
||||
|
@ -262,6 +262,12 @@ public final class CallListController: TelegramBaseController {
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if case .navigation = self.mode {
|
||||
self.controllerNode.navigationBar = self.navigationBar
|
||||
self.navigationBar?.updateBackgroundAlpha(0.0, transition: .immediate)
|
||||
}
|
||||
|
||||
self.controllerNode.startNewCall = { [weak self] in
|
||||
self?.beginCallImpl()
|
||||
}
|
||||
|
@ -117,7 +117,7 @@ private func mappedInsertEntries(context: AccountContext, presentationData: Item
|
||||
return entries.map { entry -> ListViewInsertItem in
|
||||
switch entry.entry {
|
||||
case let .displayTab(_, text, value):
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enabled: true, noCorners: true, sectionId: 0, style: .blocks, updated: { value in
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enabled: true, noCorners: false, sectionId: 0, style: .blocks, updated: { value in
|
||||
nodeInteraction.updateShowCallsTab(value)
|
||||
}), directionHint: entry.directionHint)
|
||||
case let .displayTabInfo(_, text):
|
||||
@ -136,7 +136,7 @@ private func mappedUpdateEntries(context: AccountContext, presentationData: Item
|
||||
return entries.map { entry -> ListViewUpdateItem in
|
||||
switch entry.entry {
|
||||
case let .displayTab(_, text, value):
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enabled: true, noCorners: true, sectionId: 0, style: .blocks, updated: { value in
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enabled: true, noCorners: false, sectionId: 0, style: .blocks, updated: { value in
|
||||
nodeInteraction.updateShowCallsTab(value)
|
||||
}), directionHint: entry.directionHint)
|
||||
case let .displayTabInfo(_, text):
|
||||
@ -177,6 +177,8 @@ final class CallListControllerNode: ASDisplayNode {
|
||||
return _ready.get()
|
||||
}
|
||||
|
||||
weak var navigationBar: NavigationBar?
|
||||
|
||||
var peerSelected: ((EnginePeer.Id) -> Void)?
|
||||
var activateSearch: (() -> Void)?
|
||||
var deletePeerChat: ((EnginePeer.Id) -> Void)?
|
||||
@ -215,6 +217,8 @@ final class CallListControllerNode: ASDisplayNode {
|
||||
|
||||
private let openGroupCallDisposable = MetaDisposable()
|
||||
|
||||
private var previousContentOffset: ListViewVisibleContentOffset?
|
||||
|
||||
init(controller: CallListController, context: AccountContext, mode: CallListControllerMode, presentationData: PresentationData, call: @escaping (EnginePeer.Id, Bool) -> Void, joinGroupCall: @escaping (EnginePeer.Id, EngineGroupCallDescription) -> Void, openInfo: @escaping (EnginePeer.Id, [EngineMessage]) -> Void, emptyStateUpdated: @escaping (Bool) -> Void) {
|
||||
self.controller = controller
|
||||
self.context = context
|
||||
@ -272,7 +276,7 @@ final class CallListControllerNode: ASDisplayNode {
|
||||
self.addSubnode(self.emptyButtonTextNode)
|
||||
self.addSubnode(self.emptyButtonIconNode)
|
||||
self.addSubnode(self.emptyButtonNode)
|
||||
|
||||
|
||||
switch self.mode {
|
||||
case .tab:
|
||||
self.backgroundColor = presentationData.theme.chatList.backgroundColor
|
||||
@ -607,6 +611,39 @@ final class CallListControllerNode: ASDisplayNode {
|
||||
strongSelf.updateEmptyPlaceholder(theme: state.presentationData.theme, strings: state.presentationData.strings, type: type, isHidden: !isEmpty)
|
||||
}
|
||||
}))
|
||||
|
||||
if case .navigation = mode {
|
||||
self.listNode.itemNodeHitTest = { [weak self] point in
|
||||
if let strongSelf = self {
|
||||
return point.x > strongSelf.leftOverlayNode.frame.maxX && point.x < strongSelf.rightOverlayNode.frame.minX
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
self.listNode.visibleContentOffsetChanged = { [weak self] offset in
|
||||
if let strongSelf = self {
|
||||
var previousContentOffsetValue: CGFloat?
|
||||
if let previousContentOffset = strongSelf.previousContentOffset, case let .known(value) = previousContentOffset {
|
||||
previousContentOffsetValue = value
|
||||
}
|
||||
switch offset {
|
||||
case let .known(value):
|
||||
let transition: ContainedViewLayoutTransition
|
||||
if let previousContentOffsetValue = previousContentOffsetValue, value <= 0.0, previousContentOffsetValue > 30.0 {
|
||||
transition = .animated(duration: 0.2, curve: .easeInOut)
|
||||
} else {
|
||||
transition = .immediate
|
||||
}
|
||||
strongSelf.navigationBar?.updateBackgroundAlpha(min(30.0, value) / 30.0, transition: transition)
|
||||
case .unknown, .none:
|
||||
strongSelf.navigationBar?.updateBackgroundAlpha(1.0, transition: .immediate)
|
||||
}
|
||||
|
||||
strongSelf.previousContentOffset = offset
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
@ -838,8 +875,25 @@ final class CallListControllerNode: ASDisplayNode {
|
||||
|
||||
var insets = layout.insets(options: [.input])
|
||||
insets.top += max(navigationBarHeight, layout.insets(options: [.statusBar]).top)
|
||||
insets.left += layout.safeInsets.left
|
||||
insets.right += layout.safeInsets.right
|
||||
|
||||
let inset = max(16.0, floor((layout.size.width - 674.0) / 2.0))
|
||||
if case .navigation = self.mode {
|
||||
insets.left += inset
|
||||
insets.right += inset
|
||||
|
||||
self.leftOverlayNode.frame = CGRect(x: 0.0, y: 0.0, width: insets.left, height: layout.size.height)
|
||||
self.rightOverlayNode.frame = CGRect(x: layout.size.width - insets.right, y: 0.0, width: insets.right, height: layout.size.height)
|
||||
|
||||
if self.leftOverlayNode.supernode == nil {
|
||||
self.insertSubnode(self.leftOverlayNode, aboveSubnode: self.listNode)
|
||||
}
|
||||
if self.rightOverlayNode.supernode == nil {
|
||||
self.insertSubnode(self.rightOverlayNode, aboveSubnode: self.listNode)
|
||||
}
|
||||
} else {
|
||||
insets.left += layout.safeInsets.left
|
||||
insets.right += layout.safeInsets.right
|
||||
}
|
||||
|
||||
self.listNode.bounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)
|
||||
self.listNode.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0)
|
||||
|
@ -922,8 +922,8 @@ open class NavigationBar: ASDisplayNode {
|
||||
}
|
||||
|
||||
public func updateBackgroundAlpha(_ alpha: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
transition.updateAlpha(node: self.backgroundNode, alpha: alpha)
|
||||
transition.updateAlpha(node: self.stripeNode, alpha: alpha)
|
||||
transition.updateAlpha(node: self.backgroundNode, alpha: alpha, delay: 0.15)
|
||||
transition.updateAlpha(node: self.stripeNode, alpha: alpha, delay: 0.15)
|
||||
}
|
||||
|
||||
public func updatePresentationData(_ presentationData: NavigationBarPresentationData) {
|
||||
|
@ -357,13 +357,13 @@ open class ItemListControllerNode: ASDisplayNode {
|
||||
case let .known(value):
|
||||
let transition: ContainedViewLayoutTransition
|
||||
if let previousContentOffsetValue = previousContentOffsetValue, value <= 0.0, previousContentOffsetValue > 30.0 {
|
||||
transition = .animated(duration: 0.3, curve: .linear)
|
||||
transition = .animated(duration: 0.2, curve: .easeInOut)
|
||||
} else {
|
||||
transition = .immediate
|
||||
}
|
||||
strongSelf.navigationBar.updateBackgroundAlpha(min(30.0, value) / 30.0, transition: transition)
|
||||
case .unknown, .none:
|
||||
strongSelf.navigationBar.updateBackgroundAlpha(0.0, transition: .immediate)
|
||||
strongSelf.navigationBar.updateBackgroundAlpha(1.0, transition: .immediate)
|
||||
}
|
||||
|
||||
strongSelf.previousContentOffset = offset
|
||||
|
@ -157,15 +157,15 @@ private enum AutodownloadMediaCategoryEntry: ItemListNodeEntry {
|
||||
case let .typesHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .photos(_, text, value, enabled):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/MenuIcons/Photos")?.precomposed(), title: text, enabled: enabled, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: {
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Photos")?.precomposed(), title: text, enabled: enabled, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.customize(.photo)
|
||||
})
|
||||
case let .videos(_, text, value, enabled):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/MenuIcons/Videos")?.precomposed(), title: text, enabled: enabled, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: {
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Videos")?.precomposed(), title: text, enabled: enabled, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.customize(.video)
|
||||
})
|
||||
case let .files(_, text, value, enabled):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/MenuIcons/Files")?.precomposed(), title: text, enabled: enabled, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: {
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Files")?.precomposed(), title: text, enabled: enabled, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.customize(.file)
|
||||
})
|
||||
case let .voiceMessagesInfo(_, text):
|
||||
|
@ -18,7 +18,7 @@ private final class DataAndStorageControllerArguments {
|
||||
let openProxy: () -> Void
|
||||
let openAutomaticDownloadConnectionType: (AutomaticDownloadConnectionType) -> Void
|
||||
let resetAutomaticDownload: () -> Void
|
||||
let openVoiceUseLessData: () -> Void
|
||||
let toggleVoiceUseLessData: (Bool) -> Void
|
||||
let openSaveIncomingPhotos: () -> Void
|
||||
let toggleSaveEditedPhotos: (Bool) -> Void
|
||||
let toggleAutoplayGifs: (Bool) -> Void
|
||||
@ -28,13 +28,13 @@ private final class DataAndStorageControllerArguments {
|
||||
let openIntents: () -> Void
|
||||
let toggleEnableSensitiveContent: (Bool) -> Void
|
||||
|
||||
init(openStorageUsage: @escaping () -> Void, openNetworkUsage: @escaping () -> Void, openProxy: @escaping () -> Void, openAutomaticDownloadConnectionType: @escaping (AutomaticDownloadConnectionType) -> Void, resetAutomaticDownload: @escaping () -> Void, openVoiceUseLessData: @escaping () -> Void, openSaveIncomingPhotos: @escaping () -> Void, toggleSaveEditedPhotos: @escaping (Bool) -> Void, toggleAutoplayGifs: @escaping (Bool) -> Void, toggleAutoplayVideos: @escaping (Bool) -> Void, toggleDownloadInBackground: @escaping (Bool) -> Void, openBrowserSelection: @escaping () -> Void, openIntents: @escaping () -> Void, toggleEnableSensitiveContent: @escaping (Bool) -> Void) {
|
||||
init(openStorageUsage: @escaping () -> Void, openNetworkUsage: @escaping () -> Void, openProxy: @escaping () -> Void, openAutomaticDownloadConnectionType: @escaping (AutomaticDownloadConnectionType) -> Void, resetAutomaticDownload: @escaping () -> Void, toggleVoiceUseLessData: @escaping (Bool) -> Void, openSaveIncomingPhotos: @escaping () -> Void, toggleSaveEditedPhotos: @escaping (Bool) -> Void, toggleAutoplayGifs: @escaping (Bool) -> Void, toggleAutoplayVideos: @escaping (Bool) -> Void, toggleDownloadInBackground: @escaping (Bool) -> Void, openBrowserSelection: @escaping () -> Void, openIntents: @escaping () -> Void, toggleEnableSensitiveContent: @escaping (Bool) -> Void) {
|
||||
self.openStorageUsage = openStorageUsage
|
||||
self.openNetworkUsage = openNetworkUsage
|
||||
self.openProxy = openProxy
|
||||
self.openAutomaticDownloadConnectionType = openAutomaticDownloadConnectionType
|
||||
self.resetAutomaticDownload = resetAutomaticDownload
|
||||
self.openVoiceUseLessData = openVoiceUseLessData
|
||||
self.toggleVoiceUseLessData = toggleVoiceUseLessData
|
||||
self.openSaveIncomingPhotos = openSaveIncomingPhotos
|
||||
self.toggleSaveEditedPhotos = toggleSaveEditedPhotos
|
||||
self.toggleAutoplayGifs = toggleAutoplayGifs
|
||||
@ -49,6 +49,7 @@ private final class DataAndStorageControllerArguments {
|
||||
private enum DataAndStorageSection: Int32 {
|
||||
case usage
|
||||
case autoDownload
|
||||
case backgroundDownload
|
||||
case autoPlay
|
||||
case voiceCalls
|
||||
case other
|
||||
@ -79,18 +80,20 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
|
||||
case automaticDownloadCellular(PresentationTheme, String, String)
|
||||
case automaticDownloadWifi(PresentationTheme, String, String)
|
||||
case automaticDownloadReset(PresentationTheme, String, Bool)
|
||||
case downloadInBackground(PresentationTheme, String, Bool)
|
||||
case downloadInBackgroundInfo(PresentationTheme, String)
|
||||
|
||||
case autoplayHeader(PresentationTheme, String)
|
||||
case autoplayGifs(PresentationTheme, String, Bool)
|
||||
case autoplayVideos(PresentationTheme, String, Bool)
|
||||
case voiceCallsHeader(PresentationTheme, String)
|
||||
case useLessVoiceData(PresentationTheme, String, String)
|
||||
case useLessVoiceData(PresentationTheme, String, Bool)
|
||||
case useLessVoiceDataInfo(PresentationTheme, String)
|
||||
case otherHeader(PresentationTheme, String)
|
||||
case shareSheet(PresentationTheme, String)
|
||||
case saveIncomingPhotos(PresentationTheme, String)
|
||||
case saveEditedPhotos(PresentationTheme, String, Bool)
|
||||
case openLinksIn(PresentationTheme, String, String)
|
||||
case downloadInBackground(PresentationTheme, String, Bool)
|
||||
case downloadInBackgroundInfo(PresentationTheme, String)
|
||||
|
||||
case connectionHeader(PresentationTheme, String)
|
||||
case connectionProxy(PresentationTheme, String, String)
|
||||
case enableSensitiveContent(String, Bool)
|
||||
@ -101,11 +104,13 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
|
||||
return DataAndStorageSection.usage.rawValue
|
||||
case .automaticDownloadHeader, .automaticDownloadCellular, .automaticDownloadWifi, .automaticDownloadReset:
|
||||
return DataAndStorageSection.autoDownload.rawValue
|
||||
case .downloadInBackground, .downloadInBackgroundInfo:
|
||||
return DataAndStorageSection.backgroundDownload.rawValue
|
||||
case .useLessVoiceData, .useLessVoiceDataInfo:
|
||||
return DataAndStorageSection.voiceCalls.rawValue
|
||||
case .autoplayHeader, .autoplayGifs, .autoplayVideos:
|
||||
return DataAndStorageSection.autoPlay.rawValue
|
||||
case .voiceCallsHeader, .useLessVoiceData:
|
||||
return DataAndStorageSection.voiceCalls.rawValue
|
||||
case .otherHeader, .shareSheet, .saveIncomingPhotos, .saveEditedPhotos, .openLinksIn, .downloadInBackground, .downloadInBackgroundInfo:
|
||||
case .otherHeader, .shareSheet, .saveIncomingPhotos, .saveEditedPhotos, .openLinksIn:
|
||||
return DataAndStorageSection.other.rawValue
|
||||
case .connectionHeader, .connectionProxy:
|
||||
return DataAndStorageSection.connection.rawValue
|
||||
@ -128,36 +133,36 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
|
||||
return 4
|
||||
case .automaticDownloadReset:
|
||||
return 5
|
||||
case .autoplayHeader:
|
||||
return 6
|
||||
case .autoplayGifs:
|
||||
return 7
|
||||
case .autoplayVideos:
|
||||
return 8
|
||||
case .voiceCallsHeader:
|
||||
return 9
|
||||
case .useLessVoiceData:
|
||||
return 10
|
||||
case .otherHeader:
|
||||
return 11
|
||||
case .shareSheet:
|
||||
return 12
|
||||
case .saveIncomingPhotos:
|
||||
return 14
|
||||
case .saveEditedPhotos:
|
||||
return 15
|
||||
case .openLinksIn:
|
||||
return 16
|
||||
case .downloadInBackground:
|
||||
return 17
|
||||
return 6
|
||||
case .downloadInBackgroundInfo:
|
||||
return 18
|
||||
return 7
|
||||
case .useLessVoiceData:
|
||||
return 8
|
||||
case .useLessVoiceDataInfo:
|
||||
return 9
|
||||
case .autoplayHeader:
|
||||
return 10
|
||||
case .autoplayGifs:
|
||||
return 11
|
||||
case .autoplayVideos:
|
||||
return 12
|
||||
case .otherHeader:
|
||||
return 13
|
||||
case .shareSheet:
|
||||
return 14
|
||||
case .saveIncomingPhotos:
|
||||
return 15
|
||||
case .saveEditedPhotos:
|
||||
return 16
|
||||
case .openLinksIn:
|
||||
return 17
|
||||
case .connectionHeader:
|
||||
return 19
|
||||
return 18
|
||||
case .connectionProxy:
|
||||
return 20
|
||||
return 19
|
||||
case .enableSensitiveContent:
|
||||
return 21
|
||||
return 20
|
||||
}
|
||||
}
|
||||
|
||||
@ -217,14 +222,14 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .voiceCallsHeader(lhsTheme, lhsText):
|
||||
if case let .voiceCallsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
case let .useLessVoiceData(lhsTheme, lhsText, lhsValue):
|
||||
if case let .useLessVoiceData(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .useLessVoiceData(lhsTheme, lhsText, lhsValue):
|
||||
if case let .useLessVoiceData(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
||||
case let .useLessVoiceDataInfo(lhsTheme, lhsText):
|
||||
if case let .useLessVoiceDataInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -300,21 +305,21 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
|
||||
let arguments = arguments as! DataAndStorageControllerArguments
|
||||
switch self {
|
||||
case let .storageUsage(_, text):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/MenuIcons/Storage")?.precomposed(), title: text, label: "", sectionId: self.section, style: .blocks, action: {
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Storage")?.precomposed(), title: text, label: "", sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openStorageUsage()
|
||||
})
|
||||
case let .networkUsage(_, text):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/MenuIcons/Network")?.precomposed(), title: text, label: "", sectionId: self.section, style: .blocks, action: {
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Network")?.precomposed(), title: text, label: "", sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openNetworkUsage()
|
||||
})
|
||||
case let .automaticDownloadHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .automaticDownloadCellular(_, text, value):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/MenuIcons/Cellular")?.precomposed(), title: text, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: {
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Cellular")?.precomposed(), title: text, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openAutomaticDownloadConnectionType(.cellular)
|
||||
})
|
||||
case let .automaticDownloadWifi(_, text, value):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/MenuIcons/WiFi")?.precomposed(), title: text, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: {
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/WiFi")?.precomposed(), title: text, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openAutomaticDownloadConnectionType(.wifi)
|
||||
})
|
||||
case let .automaticDownloadReset(_, text, enabled):
|
||||
@ -333,12 +338,12 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.toggleAutoplayVideos(value)
|
||||
}, tag: DataAndStorageEntryTag.autoplayVideos)
|
||||
case let .voiceCallsHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .useLessVoiceData(_, text, value):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openVoiceUseLessData()
|
||||
})
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.toggleVoiceUseLessData(value)
|
||||
}, tag: DataAndStorageEntryTag.autoplayVideos)
|
||||
case let .useLessVoiceDataInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
case let .otherHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .shareSheet(_, text):
|
||||
@ -418,11 +423,15 @@ private func stringForUseLessDataSetting(_ dataSaving: VoiceCallDataSaving, stri
|
||||
|
||||
private func stringForAutoDownloadTypes(strings: PresentationStrings, decimalSeparator: String, photo: Bool, videoSize: Int32?, fileSize: Int32?) -> String {
|
||||
var types: [String] = []
|
||||
if photo {
|
||||
if photo && videoSize == nil {
|
||||
types.append(strings.ChatSettings_AutoDownloadSettings_TypePhoto)
|
||||
}
|
||||
if let videoSize = videoSize {
|
||||
types.append(strings.ChatSettings_AutoDownloadSettings_TypeVideo(autodownloadDataSizeString(Int64(videoSize), decimalSeparator: decimalSeparator)).string)
|
||||
if photo {
|
||||
types.append(strings.ChatSettings_AutoDownloadSettings_TypeMedia(autodownloadDataSizeString(Int64(videoSize), decimalSeparator: decimalSeparator)).string)
|
||||
} else {
|
||||
types.append(strings.ChatSettings_AutoDownloadSettings_TypeVideo(autodownloadDataSizeString(Int64(videoSize), decimalSeparator: decimalSeparator)).string)
|
||||
}
|
||||
}
|
||||
if let fileSize = fileSize {
|
||||
types.append(strings.ChatSettings_AutoDownloadSettings_TypeFile(autodownloadDataSizeString(Int64(fileSize), decimalSeparator: decimalSeparator)).string)
|
||||
@ -476,14 +485,17 @@ private func dataAndStorageControllerEntries(state: DataAndStorageControllerStat
|
||||
let defaultSettings = MediaAutoDownloadSettings.defaultSettings
|
||||
entries.append(.automaticDownloadReset(presentationData.theme, presentationData.strings.ChatSettings_AutoDownloadReset, data.automaticMediaDownloadSettings.cellular != defaultSettings.cellular || data.automaticMediaDownloadSettings.wifi != defaultSettings.wifi))
|
||||
|
||||
entries.append(.downloadInBackground(presentationData.theme, presentationData.strings.ChatSettings_DownloadInBackground, data.automaticMediaDownloadSettings.downloadInBackground))
|
||||
entries.append(.downloadInBackgroundInfo(presentationData.theme, presentationData.strings.ChatSettings_DownloadInBackgroundInfo))
|
||||
|
||||
let dataSaving = effectiveDataSaving(for: data.voiceCallSettings, autodownloadSettings: data.autodownloadSettings)
|
||||
entries.append(.useLessVoiceData(presentationData.theme, presentationData.strings.ChatSettings_UseLessDataForCalls, dataSaving != .never))
|
||||
entries.append(.useLessVoiceDataInfo(presentationData.theme, presentationData.strings.CallSettings_UseLessDataLongDescription))
|
||||
|
||||
entries.append(.autoplayHeader(presentationData.theme, presentationData.strings.ChatSettings_AutoPlayTitle))
|
||||
entries.append(.autoplayGifs(presentationData.theme, presentationData.strings.ChatSettings_AutoPlayGifs, data.automaticMediaDownloadSettings.autoplayGifs))
|
||||
entries.append(.autoplayVideos(presentationData.theme, presentationData.strings.ChatSettings_AutoPlayVideos, data.automaticMediaDownloadSettings.autoplayVideos))
|
||||
|
||||
entries.append(.voiceCallsHeader(presentationData.theme, presentationData.strings.Settings_CallSettings.uppercased()))
|
||||
let dataSaving = effectiveDataSaving(for: data.voiceCallSettings, autodownloadSettings: data.autodownloadSettings)
|
||||
entries.append(.useLessVoiceData(presentationData.theme, presentationData.strings.CallSettings_UseLessData, stringForUseLessDataSetting(dataSaving, strings: presentationData.strings)))
|
||||
|
||||
entries.append(.otherHeader(presentationData.theme, presentationData.strings.ChatSettings_Other))
|
||||
if #available(iOSApplicationExtension 13.2, iOS 13.2, *) {
|
||||
entries.append(.shareSheet(presentationData.theme, presentationData.strings.ChatSettings_IntentsSettings))
|
||||
@ -491,9 +503,7 @@ private func dataAndStorageControllerEntries(state: DataAndStorageControllerStat
|
||||
entries.append(.saveIncomingPhotos(presentationData.theme, presentationData.strings.Settings_SaveIncomingPhotos))
|
||||
entries.append(.saveEditedPhotos(presentationData.theme, presentationData.strings.Settings_SaveEditedPhotos, data.generatedMediaStoreSettings.storeEditedPhotos))
|
||||
entries.append(.openLinksIn(presentationData.theme, presentationData.strings.ChatSettings_OpenLinksIn, defaultWebBrowser))
|
||||
entries.append(.downloadInBackground(presentationData.theme, presentationData.strings.ChatSettings_DownloadInBackground, data.automaticMediaDownloadSettings.downloadInBackground))
|
||||
entries.append(.downloadInBackgroundInfo(presentationData.theme, presentationData.strings.ChatSettings_DownloadInBackgroundInfo))
|
||||
|
||||
|
||||
let proxyValue: String
|
||||
if let proxySettings = data.proxySettings, let activeServer = proxySettings.activeServer, proxySettings.enabled {
|
||||
switch activeServer.connection {
|
||||
@ -608,8 +618,12 @@ public func dataAndStorageController(context: AccountContext, focusOnItemTag: Da
|
||||
})
|
||||
])])
|
||||
presentControllerImpl?(actionSheet, nil)
|
||||
}, openVoiceUseLessData: {
|
||||
pushControllerImpl?(voiceCallDataSavingController(context: context))
|
||||
}, toggleVoiceUseLessData: { value in
|
||||
let _ = updateVoiceCallSettingsSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
|
||||
var current = current
|
||||
current.dataSaving = value ? .always : .never
|
||||
return current
|
||||
}).start()
|
||||
}, openSaveIncomingPhotos: {
|
||||
pushControllerImpl?(saveIncomingMediaController(context: context))
|
||||
}, toggleSaveEditedPhotos: { value in
|
||||
|
@ -0,0 +1,433 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import DeviceAccess
|
||||
import ItemListUI
|
||||
import PresentationDataUtils
|
||||
import AccountContext
|
||||
import AlertUI
|
||||
import PresentationDataUtils
|
||||
import TelegramNotices
|
||||
import NotificationSoundSelectionUI
|
||||
import TelegramStringFormatting
|
||||
import ItemListPeerItem
|
||||
import ItemListPeerActionItem
|
||||
|
||||
private final class NotificationsPeerCategoryControllerArguments {
|
||||
let context: AccountContext
|
||||
let presentController: (ViewController, ViewControllerPresentationArguments?) -> Void
|
||||
let pushController: (ViewController) -> Void
|
||||
let soundSelectionDisposable: MetaDisposable
|
||||
|
||||
let updateEnabled: (Bool) -> Void
|
||||
let updatePreviews: (Bool) -> Void
|
||||
let updateSound: (PeerMessageSound) -> Void
|
||||
|
||||
let addException: () -> Void
|
||||
let openException: (Peer) -> Void
|
||||
let removeAllExceptions: () -> Void
|
||||
let updateRevealedPeerId: (PeerId?) -> Void
|
||||
let removePeer: (Peer) -> Void
|
||||
|
||||
let updatedExceptionMode: (NotificationExceptionMode) -> Void
|
||||
|
||||
init(context: AccountContext, presentController: @escaping (ViewController, ViewControllerPresentationArguments?) -> Void, pushController: @escaping (ViewController) -> Void, soundSelectionDisposable: MetaDisposable, updateEnabled: @escaping (Bool) -> Void, updatePreviews: @escaping (Bool) -> Void, updateSound: @escaping (PeerMessageSound) -> Void, addException: @escaping () -> Void, openException: @escaping (Peer) -> Void, removeAllExceptions: @escaping () -> Void, updateRevealedPeerId: @escaping (PeerId?) -> Void, removePeer: @escaping (Peer) -> Void, updatedExceptionMode: @escaping (NotificationExceptionMode) -> Void) {
|
||||
self.context = context
|
||||
self.presentController = presentController
|
||||
self.pushController = pushController
|
||||
self.soundSelectionDisposable = soundSelectionDisposable
|
||||
|
||||
self.updateEnabled = updateEnabled
|
||||
self.updatePreviews = updatePreviews
|
||||
self.updateSound = updateSound
|
||||
|
||||
self.addException = addException
|
||||
self.openException = openException
|
||||
self.removeAllExceptions = removeAllExceptions
|
||||
|
||||
self.updateRevealedPeerId = updateRevealedPeerId
|
||||
self.removePeer = removePeer
|
||||
|
||||
self.updatedExceptionMode = updatedExceptionMode
|
||||
}
|
||||
}
|
||||
|
||||
private enum NotificationsPeerCategorySection: Int32 {
|
||||
case enable
|
||||
case options
|
||||
case exceptions
|
||||
}
|
||||
|
||||
public enum NotificationsPeerCategoryEntryTag: ItemListItemTag {
|
||||
case enable
|
||||
case previews
|
||||
case sound
|
||||
|
||||
public func isEqual(to other: ItemListItemTag) -> Bool {
|
||||
if let other = other as? NotificationsPeerCategoryEntryTag, self == other {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum NotificationsPeerCategoryEntry: ItemListNodeEntry {
|
||||
case enable(PresentationTheme, String, Bool)
|
||||
case optionsHeader(PresentationTheme, String)
|
||||
case previews(PresentationTheme, String, Bool)
|
||||
case sound(PresentationTheme, String, String, PeerMessageSound)
|
||||
|
||||
case exceptionsHeader(PresentationTheme, String)
|
||||
case addException(PresentationTheme, String)
|
||||
case exception(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer, TelegramPeerNotificationSettings, Bool, Bool)
|
||||
case removeAllExceptions(PresentationTheme, String)
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
case .enable:
|
||||
return NotificationsPeerCategorySection.enable.rawValue
|
||||
case .optionsHeader, .previews, .sound:
|
||||
return NotificationsPeerCategorySection.options.rawValue
|
||||
case .exceptionsHeader, .addException, .exception, .removeAllExceptions:
|
||||
return NotificationsPeerCategorySection.exceptions.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
var stableId: Int32 {
|
||||
switch self {
|
||||
case .enable:
|
||||
return 0
|
||||
case .optionsHeader:
|
||||
return 1
|
||||
case .previews:
|
||||
return 2
|
||||
case .sound:
|
||||
return 3
|
||||
case .exceptionsHeader:
|
||||
return 4
|
||||
case .addException:
|
||||
return 5
|
||||
case let .exception(index, _, _, _, _, _, _, _, _):
|
||||
return 6 + index
|
||||
case .removeAllExceptions:
|
||||
return 100000
|
||||
}
|
||||
}
|
||||
|
||||
var tag: ItemListItemTag? {
|
||||
switch self {
|
||||
case .enable:
|
||||
return NotificationsPeerCategoryEntryTag.enable
|
||||
case .previews:
|
||||
return NotificationsPeerCategoryEntryTag.previews
|
||||
case .sound:
|
||||
return NotificationsPeerCategoryEntryTag.sound
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
static func ==(lhs: NotificationsPeerCategoryEntry, rhs: NotificationsPeerCategoryEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .enable(lhsTheme, lhsText, lhsValue):
|
||||
if case let .enable(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .optionsHeader(lhsTheme, lhsText):
|
||||
if case let .optionsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .previews(lhsTheme, lhsText, lhsValue):
|
||||
if case let .previews(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .sound(lhsTheme, lhsText, lhsValue, lhsSound):
|
||||
if case let .sound(rhsTheme, rhsText, rhsValue, rhsSound) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue, lhsSound == rhsSound {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .exceptionsHeader(lhsTheme, lhsText):
|
||||
if case let .exceptionsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .addException(lhsTheme, lhsText):
|
||||
if case let .addException(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .exception(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsDisplayNameOrder, lhsPeer, lhsSettings, lhsEditing, lhsRevealed):
|
||||
if case let .exception(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsDisplayNameOrder, rhsPeer, rhsSettings, rhsEditing, rhsRevealed) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsDisplayNameOrder == rhsDisplayNameOrder, arePeersEqual(lhsPeer, rhsPeer), lhsSettings == rhsSettings, lhsEditing == rhsEditing, lhsRevealed == rhsRevealed {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .removeAllExceptions(lhsTheme, lhsText):
|
||||
if case let .removeAllExceptions(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
static func <(lhs: NotificationsPeerCategoryEntry, rhs: NotificationsPeerCategoryEntry) -> Bool {
|
||||
return lhs.stableId < rhs.stableId
|
||||
}
|
||||
|
||||
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
||||
let arguments = arguments as! NotificationsPeerCategoryControllerArguments
|
||||
switch self {
|
||||
case let .enable(_, text, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in
|
||||
arguments.updateEnabled(updatedValue)
|
||||
}, tag: self.tag)
|
||||
case let .optionsHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .previews(_, text, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.updatePreviews(value)
|
||||
})
|
||||
case let .sound(_, text, value, _):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
|
||||
|
||||
}, tag: self.tag)
|
||||
case let .exceptionsHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .addException(theme, text):
|
||||
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.addPersonIcon(theme), title: text, sectionId: self.section, height: .generic, color: .accent, editing: false, action: {
|
||||
arguments.addException()
|
||||
})
|
||||
case let .removeAllExceptions(theme, text):
|
||||
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.addPersonIcon(theme), title: text, sectionId: self.section, height: .generic, color: .destructive, editing: false, action: {
|
||||
arguments.removeAllExceptions()
|
||||
})
|
||||
case let .exception(_, _, _, dateTimeFormat, nameDisplayOrder, peer, _, editing, revealed):
|
||||
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(peer), presence: nil, text: .text("", .secondary), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: editing, revealed: revealed), switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: {
|
||||
arguments.openException(peer)
|
||||
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
|
||||
arguments.updateRevealedPeerId(peerId)
|
||||
}, removePeer: { peerId in
|
||||
arguments.removePeer(peer)
|
||||
}, hasTopStripe: false, hasTopGroupInset: false, noInsets: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func filteredGlobalSound(_ sound: PeerMessageSound) -> PeerMessageSound {
|
||||
if case .default = sound {
|
||||
return .bundledModern(id: 0)
|
||||
} else {
|
||||
return sound
|
||||
}
|
||||
}
|
||||
|
||||
private func notificationsPeerCategoryEntries(category: NotificationsPeerCategory, globalSettings: GlobalNotificationSettingsSet, exceptions: (users: NotificationExceptionMode, groups: NotificationExceptionMode, channels: NotificationExceptionMode), presentationData: PresentationData) -> [NotificationsPeerCategoryEntry] {
|
||||
var entries: [NotificationsPeerCategoryEntry] = []
|
||||
|
||||
let notificationSettings: MessageNotificationSettings
|
||||
switch category {
|
||||
case .privateChat:
|
||||
notificationSettings = globalSettings.privateChats
|
||||
case .group:
|
||||
notificationSettings = globalSettings.groupChats
|
||||
case .channel:
|
||||
notificationSettings = globalSettings.channels
|
||||
}
|
||||
|
||||
entries.append(.enable(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsAlert, notificationSettings.enabled))
|
||||
|
||||
if notificationSettings.enabled || !exceptions.users.isEmpty {
|
||||
entries.append(.optionsHeader(presentationData.theme, presentationData.strings.Notifications_MessageNotifications.uppercased()))
|
||||
entries.append(.previews(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsPreview, notificationSettings.displayPreviews))
|
||||
entries.append(.sound(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsSound, localizedPeerNotificationSoundString(strings: presentationData.strings, sound: filteredGlobalSound(notificationSettings.sound)), filteredGlobalSound(notificationSettings.sound)))
|
||||
}
|
||||
|
||||
entries.append(.exceptionsHeader(presentationData.theme, presentationData.strings.Notifications_MessageNotifications.uppercased()))
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
public enum NotificationsPeerCategory {
|
||||
case privateChat
|
||||
case group
|
||||
case channel
|
||||
}
|
||||
|
||||
public func notificationsPeerCategoryController(context: AccountContext, category: NotificationsPeerCategory, exceptionsList: NotificationExceptionsList?, focusOnItemTag: NotificationsPeerCategoryEntryTag? = nil) -> ViewController {
|
||||
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
|
||||
var pushControllerImpl: ((ViewController) -> Void)?
|
||||
|
||||
let notificationExceptions: Promise<(users: NotificationExceptionMode, groups: NotificationExceptionMode, channels: NotificationExceptionMode)> = Promise()
|
||||
|
||||
let updateNotificationExceptions:((users: NotificationExceptionMode, groups: NotificationExceptionMode, channels: NotificationExceptionMode)) -> Void = { value in
|
||||
notificationExceptions.set(.single(value))
|
||||
}
|
||||
|
||||
let arguments = NotificationsPeerCategoryControllerArguments(context: context, presentController: { controller, arguments in
|
||||
presentControllerImpl?(controller, arguments)
|
||||
}, pushController: { controller in
|
||||
pushControllerImpl?(controller)
|
||||
}, soundSelectionDisposable: MetaDisposable(), updateEnabled: { value in
|
||||
let _ = updateGlobalNotificationSettingsInteractively(postbox: context.account.postbox, { settings in
|
||||
var settings = settings
|
||||
switch category {
|
||||
case .privateChat:
|
||||
settings.privateChats.enabled = value
|
||||
case .group:
|
||||
settings.groupChats.enabled = value
|
||||
case .channel:
|
||||
settings.channels.enabled = value
|
||||
}
|
||||
return settings
|
||||
}).start()
|
||||
}, updatePreviews: { value in
|
||||
let _ = updateGlobalNotificationSettingsInteractively(postbox: context.account.postbox, { settings in
|
||||
var settings = settings
|
||||
switch category {
|
||||
case .privateChat:
|
||||
settings.privateChats.displayPreviews = value
|
||||
case .group:
|
||||
settings.groupChats.displayPreviews = value
|
||||
case .channel:
|
||||
settings.channels.displayPreviews = value
|
||||
}
|
||||
return settings
|
||||
}).start()
|
||||
}, updateSound: { value in
|
||||
let _ = updateGlobalNotificationSettingsInteractively(postbox: context.account.postbox, { settings in
|
||||
var settings = settings
|
||||
switch category {
|
||||
case .privateChat:
|
||||
settings.privateChats.sound = value
|
||||
case .group:
|
||||
settings.groupChats.sound = value
|
||||
case .channel:
|
||||
settings.channels.sound = value
|
||||
}
|
||||
return settings
|
||||
}).start()
|
||||
}, addException: {
|
||||
|
||||
}, openException: { peer in
|
||||
|
||||
}, removeAllExceptions: {
|
||||
|
||||
}, updateRevealedPeerId: { peerId in
|
||||
|
||||
}, removePeer: { peer in
|
||||
|
||||
}, updatedExceptionMode: { mode in
|
||||
_ = (notificationExceptions.get() |> take(1) |> deliverOnMainQueue).start(next: { (users, groups, channels) in
|
||||
switch mode {
|
||||
case .users:
|
||||
updateNotificationExceptions((mode, groups, channels))
|
||||
case .groups:
|
||||
updateNotificationExceptions((users, mode, channels))
|
||||
case .channels:
|
||||
updateNotificationExceptions((users, groups, mode))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
let sharedData = context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.inAppNotificationSettings])
|
||||
let preferences = context.account.postbox.preferencesView(keys: [PreferencesKeys.globalNotifications])
|
||||
|
||||
let exceptionsSignal = Signal<NotificationExceptionsList?, NoError>.single(exceptionsList) |> then(context.engine.peers.notificationExceptionsList() |> map(Optional.init))
|
||||
|
||||
notificationExceptions.set(exceptionsSignal |> map { list -> (NotificationExceptionMode, NotificationExceptionMode, NotificationExceptionMode) in
|
||||
var users:[PeerId : NotificationExceptionWrapper] = [:]
|
||||
var groups: [PeerId : NotificationExceptionWrapper] = [:]
|
||||
var channels:[PeerId : NotificationExceptionWrapper] = [:]
|
||||
if let list = list {
|
||||
for (key, value) in list.settings {
|
||||
if let peer = list.peers[key], !peer.debugDisplayTitle.isEmpty, peer.id != context.account.peerId {
|
||||
switch value.muteState {
|
||||
case .default:
|
||||
switch value.messageSound {
|
||||
case .default:
|
||||
break
|
||||
default:
|
||||
switch key.namespace {
|
||||
case Namespaces.Peer.CloudUser:
|
||||
users[key] = NotificationExceptionWrapper(settings: value, peer: peer)
|
||||
default:
|
||||
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
|
||||
channels[key] = NotificationExceptionWrapper(settings: value, peer: peer)
|
||||
} else {
|
||||
groups[key] = NotificationExceptionWrapper(settings: value, peer: peer)
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
switch key.namespace {
|
||||
case Namespaces.Peer.CloudUser:
|
||||
users[key] = NotificationExceptionWrapper(settings: value, peer: peer)
|
||||
default:
|
||||
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
|
||||
channels[key] = NotificationExceptionWrapper(settings: value, peer: peer)
|
||||
} else {
|
||||
groups[key] = NotificationExceptionWrapper(settings: value, peer: peer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (.users(users), .groups(groups), .channels(channels))
|
||||
})
|
||||
|
||||
let signal = combineLatest(context.sharedContext.presentationData, sharedData, preferences, notificationExceptions.get())
|
||||
|> map { presentationData, sharedData, view, exceptions -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
let viewSettings: GlobalNotificationSettingsSet
|
||||
if let settings = view.values[PreferencesKeys.globalNotifications]?.get(GlobalNotificationSettings.self) {
|
||||
viewSettings = settings.effective
|
||||
} else {
|
||||
viewSettings = GlobalNotificationSettingsSet.defaultSettings
|
||||
}
|
||||
|
||||
let entries = notificationsPeerCategoryEntries(category: category, globalSettings: viewSettings, exceptions: exceptions, presentationData: presentationData)
|
||||
|
||||
var index = 0
|
||||
var scrollToItem: ListViewScrollToItem?
|
||||
if let focusOnItemTag = focusOnItemTag {
|
||||
for entry in entries {
|
||||
if entry.tag?.isEqual(to: focusOnItemTag) ?? false {
|
||||
scrollToItem = ListViewScrollToItem(index: index, position: .top(0.0), animated: false, curve: .Default(duration: 0.0), directionHint: .Up)
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.Notifications_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, ensureVisibleItemTag: focusOnItemTag, initialScrollToItem: scrollToItem)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|
||||
let controller = ItemListController(context: context, state: signal)
|
||||
presentControllerImpl = { [weak controller] c, a in
|
||||
controller?.present(c, in: .window(.root), with: a)
|
||||
}
|
||||
pushControllerImpl = { [weak controller] c in
|
||||
(controller?.navigationController as? NavigationController)?.pushViewController(c)
|
||||
}
|
||||
return controller
|
||||
}
|
@ -287,7 +287,7 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
|
||||
case let .privacyHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .blockedPeers(_, text, value):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/MenuIcons/Blocked")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: {
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Blocked")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openBlockedUsers()
|
||||
})
|
||||
case let .phoneNumberPrivacy(_, text, value):
|
||||
@ -317,15 +317,15 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
|
||||
arguments.openVoiceCallPrivacy()
|
||||
})
|
||||
case let .passcode(_, text, hasFaceId, value):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: hasFaceId ? "Settings/MenuIcons/FaceId" : "Settings/MenuIcons/TouchId")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: {
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: hasFaceId ? "Settings/Menu/FaceId" : "Settings/Menu/TouchId")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openPasscode()
|
||||
})
|
||||
case let .twoStepVerification(_, text, value, data):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/MenuIcons/TwoStepAuth")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: {
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/TwoStepAuth")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openTwoStepVerification(data)
|
||||
})
|
||||
case let .activeSessions(_, text, value):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/MenuIcons/Websites")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: {
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Websites")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openActiveSessions()
|
||||
})
|
||||
case let .autoArchiveHeader(text):
|
||||
|
@ -107,10 +107,10 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
|
||||
private var disabledOverlayNode: ASDisplayNode?
|
||||
private let maskNode: ASImageNode
|
||||
|
||||
let iconNode: ASImageNode
|
||||
private let titleNode: TextNode
|
||||
private let appNode: TextNode
|
||||
private let locationNode: TextNode
|
||||
private let labelNode: TextNode
|
||||
|
||||
private let activateArea: AccessibilityAreaNode
|
||||
|
||||
@ -130,6 +130,10 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
self.maskNode = ASImageNode()
|
||||
|
||||
self.iconNode = ASImageNode()
|
||||
self.iconNode.cornerRadius = 7.0
|
||||
self.iconNode.clipsToBounds = true
|
||||
|
||||
self.titleNode = TextNode()
|
||||
self.titleNode.isUserInteractionEnabled = false
|
||||
self.titleNode.contentMode = .left
|
||||
@ -144,12 +148,7 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
|
||||
self.locationNode.isUserInteractionEnabled = false
|
||||
self.locationNode.contentMode = .left
|
||||
self.locationNode.contentsScale = UIScreen.main.scale
|
||||
|
||||
self.labelNode = TextNode()
|
||||
self.labelNode.isUserInteractionEnabled = false
|
||||
self.labelNode.contentMode = .left
|
||||
self.labelNode.contentsScale = UIScreen.main.scale
|
||||
|
||||
|
||||
self.highlightedBackgroundNode = ASDisplayNode()
|
||||
self.highlightedBackgroundNode.isLayerBacked = true
|
||||
|
||||
@ -157,10 +156,10 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
|
||||
|
||||
self.addSubnode(self.iconNode)
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.appNode)
|
||||
self.addSubnode(self.locationNode)
|
||||
self.addSubnode(self.labelNode)
|
||||
|
||||
self.addSubnode(self.activateArea)
|
||||
}
|
||||
@ -169,7 +168,6 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
let makeAppLayout = TextNode.asyncLayout(self.appNode)
|
||||
let makeLocationLayout = TextNode.asyncLayout(self.locationNode)
|
||||
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
|
||||
let editableControlLayout = ItemListEditableControlNode.asyncLayout(self.editableControlNode)
|
||||
|
||||
var currentDisabledOverlayNode = self.disabledOverlayNode
|
||||
@ -179,8 +177,8 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
|
||||
return { item, params, neighbors in
|
||||
var updatedTheme: PresentationTheme?
|
||||
|
||||
let titleFont = Font.medium(floor(item.presentationData.fontSize.itemListBaseFontSize * 15.0 / 17.0))
|
||||
let textFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 13.0 / 17.0))
|
||||
let titleFont = Font.medium(floor(item.presentationData.fontSize.itemListBaseFontSize * 16.0 / 17.0))
|
||||
let textFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0))
|
||||
|
||||
let verticalInset: CGFloat = 10.0
|
||||
let titleSpacing: CGFloat = 1.0
|
||||
@ -193,7 +191,6 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
|
||||
var titleAttributedString: NSAttributedString?
|
||||
var appAttributedString: NSAttributedString?
|
||||
var locationAttributedString: NSAttributedString?
|
||||
var labelAttributedString: NSAttributedString?
|
||||
|
||||
let peerRevealOptions: [ItemListRevealOption]
|
||||
if item.editable && item.enabled {
|
||||
@ -226,16 +223,18 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
|
||||
appAttributedString = NSAttributedString(string: appString, font: textFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor)
|
||||
locationAttributedString = NSAttributedString(string: "\(item.session.ip) — \(item.session.country)", font: textFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
|
||||
|
||||
let label: String
|
||||
if item.session.isCurrent {
|
||||
labelAttributedString = NSAttributedString(string: item.presentationData.strings.Presence_online, font: textFont, textColor: item.presentationData.theme.list.itemAccentColor)
|
||||
label = item.presentationData.strings.Presence_online
|
||||
} else {
|
||||
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
|
||||
let dateText = stringForRelativeTimestamp(strings: item.presentationData.strings, relativeTimestamp: item.session.activityDate, relativeTo: timestamp, dateTimeFormat: item.dateTimeFormat)
|
||||
labelAttributedString = NSAttributedString(string: dateText, font: textFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
|
||||
label = stringForRelativeActivityTimestamp(strings: item.presentationData.strings, dateTimeFormat: item.dateTimeFormat, relativeTimestamp: item.session.activityDate, relativeTo: timestamp)
|
||||
}
|
||||
|
||||
let leftInset: CGFloat = 15.0 + params.leftInset
|
||||
locationAttributedString = NSAttributedString(string: "\(item.session.country) • \(label)", font: textFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
|
||||
|
||||
let leftInset: CGFloat = 59.0 + params.leftInset
|
||||
|
||||
var editableControlSizeAndApply: (CGFloat, (CGFloat) -> ItemListEditableControlNode)?
|
||||
|
||||
@ -248,8 +247,7 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
|
||||
editingOffset = 0.0
|
||||
}
|
||||
|
||||
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: labelAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - editingOffset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 16.0 - editingOffset - rightInset - labelLayout.size.width - 5.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 16.0 - editingOffset - rightInset - 5.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (appLayout, appApply) = makeAppLayout(TextNodeLayoutArguments(attributedString: appAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - editingOffset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (locationLayout, locationApply) = makeLocationLayout(TextNodeLayoutArguments(attributedString: locationAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - editingOffset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
@ -364,7 +362,6 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
|
||||
})
|
||||
}
|
||||
|
||||
let _ = labelApply()
|
||||
let _ = titleApply()
|
||||
let _ = appApply()
|
||||
let _ = locationApply()
|
||||
@ -412,7 +409,7 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
|
||||
transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)))
|
||||
transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)))
|
||||
|
||||
transition.updateFrame(node: strongSelf.labelNode, frame: CGRect(origin: CGPoint(x: revealOffset + params.width - labelLayout.size.width - 15.0 - rightInset, y: verticalInset), size: labelLayout.size))
|
||||
transition.updateFrame(node: strongSelf.iconNode, frame: CGRect(origin: CGPoint(x: params.leftInset + revealOffset + editingOffset + 16.0, y: 12.0), size: CGSize(width: 30.0, height: 30.0)))
|
||||
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: verticalInset), size: titleLayout.size))
|
||||
transition.updateFrame(node: strongSelf.appNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: strongSelf.titleNode.frame.maxY + titleSpacing), size: appLayout.size))
|
||||
transition.updateFrame(node: strongSelf.locationNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: strongSelf.appNode.frame.maxY + textSpacing), size: locationLayout.size))
|
||||
@ -455,7 +452,7 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
|
||||
editingOffset = 0.0
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.labelNode, frame: CGRect(origin: CGPoint(x: revealOffset + params.width - params.rightInset - self.labelNode.bounds.size.width - 15.0, y: self.labelNode.frame.minY), size: self.labelNode.bounds.size))
|
||||
transition.updateFrame(node: self.iconNode, frame: CGRect(origin: CGPoint(x: params.leftInset + self.revealOffset + editingOffset + 16.0, y: self.iconNode.frame.minY), size: self.iconNode.bounds.size))
|
||||
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: self.titleNode.frame.minY), size: self.titleNode.bounds.size))
|
||||
transition.updateFrame(node: self.appNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: self.appNode.frame.minY), size: self.appNode.bounds.size))
|
||||
transition.updateFrame(node: self.locationNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: self.locationNode.frame.minY), size: self.locationNode.bounds.size))
|
||||
|
@ -31,8 +31,7 @@ struct ItemListWebsiteItemEditing: Equatable {
|
||||
|
||||
final class ItemListWebsiteItem: ListViewItem, ItemListItem {
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let presentationData: ItemListPresentationData
|
||||
let dateTimeFormat: PresentationDateTimeFormat
|
||||
let nameDisplayOrder: PresentationPersonNameOrder
|
||||
let website: WebAuthorization
|
||||
@ -44,10 +43,9 @@ final class ItemListWebsiteItem: ListViewItem, ItemListItem {
|
||||
let setSessionIdWithRevealedOptions: (Int64?, Int64?) -> Void
|
||||
let removeSession: (Int64) -> Void
|
||||
|
||||
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, website: WebAuthorization, peer: Peer?, enabled: Bool, editing: Bool, revealed: Bool, sectionId: ItemListSectionId, setSessionIdWithRevealedOptions: @escaping (Int64?, Int64?) -> Void, removeSession: @escaping (Int64) -> Void) {
|
||||
init(context: AccountContext, presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, website: WebAuthorization, peer: Peer?, enabled: Bool, editing: Bool, revealed: Bool, sectionId: ItemListSectionId, setSessionIdWithRevealedOptions: @escaping (Int64?, Int64?) -> Void, removeSession: @escaping (Int64) -> Void) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.presentationData = presentationData
|
||||
self.dateTimeFormat = dateTimeFormat
|
||||
self.nameDisplayOrder = nameDisplayOrder
|
||||
self.website = website
|
||||
@ -99,9 +97,7 @@ final class ItemListWebsiteItem: ListViewItem, ItemListItem {
|
||||
}
|
||||
}
|
||||
|
||||
private let avatarFont = avatarPlaceholderFont(size: 9.0)
|
||||
private let titleFont = Font.medium(15.0)
|
||||
private let textFont = Font.regular(13.0)
|
||||
private let avatarFont = avatarPlaceholderFont(size: 11.0)
|
||||
|
||||
class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
@ -134,6 +130,8 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode {
|
||||
self.maskNode = ASImageNode()
|
||||
|
||||
self.avatarNode = AvatarNode(font: avatarFont)
|
||||
self.avatarNode.cornerRadius = 7.0
|
||||
self.avatarNode.clipsToBounds = true
|
||||
|
||||
self.titleNode = TextNode()
|
||||
self.titleNode.isUserInteractionEnabled = false
|
||||
@ -171,7 +169,6 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode {
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
let makeAppLayout = TextNode.asyncLayout(self.appNode)
|
||||
let makeLocationLayout = TextNode.asyncLayout(self.locationNode)
|
||||
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
|
||||
let editableControlLayout = ItemListEditableControlNode.asyncLayout(self.editableControlNode)
|
||||
|
||||
var currentDisabledOverlayNode = self.disabledOverlayNode
|
||||
@ -181,21 +178,23 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode {
|
||||
return { item, params, neighbors in
|
||||
var updatedTheme: PresentationTheme?
|
||||
|
||||
if currentItem?.theme !== item.theme {
|
||||
updatedTheme = item.theme
|
||||
let titleFont = Font.medium(floor(item.presentationData.fontSize.itemListBaseFontSize * 16.0 / 17.0))
|
||||
let textFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0))
|
||||
|
||||
if currentItem?.presentationData !== item.presentationData.theme {
|
||||
updatedTheme = item.presentationData.theme
|
||||
}
|
||||
|
||||
var titleAttributedString: NSAttributedString?
|
||||
var appAttributedString: NSAttributedString?
|
||||
var locationAttributedString: NSAttributedString?
|
||||
var labelAttributedString: NSAttributedString?
|
||||
|
||||
let peerRevealOptions = [ItemListRevealOption(key: 0, title: item.strings.AuthSessions_LogOut, icon: .none, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)]
|
||||
let peerRevealOptions = [ItemListRevealOption(key: 0, title: item.presentationData.strings.AuthSessions_LogOut, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)]
|
||||
|
||||
let rightInset: CGFloat = params.rightInset
|
||||
|
||||
if let user = item.peer as? TelegramUser {
|
||||
titleAttributedString = NSAttributedString(string: EnginePeer(user).displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder), font: titleFont, textColor: item.theme.list.itemPrimaryTextColor)
|
||||
titleAttributedString = NSAttributedString(string: EnginePeer(user).displayTitle(strings: item.presentationData.strings, displayOrder: item.nameDisplayOrder), font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor)
|
||||
}
|
||||
|
||||
var appString = ""
|
||||
@ -217,28 +216,27 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode {
|
||||
appString += item.website.platform
|
||||
}
|
||||
|
||||
appAttributedString = NSAttributedString(string: appString, font: textFont, textColor: item.theme.list.itemPrimaryTextColor)
|
||||
locationAttributedString = NSAttributedString(string: "\(item.website.ip) — \(item.website.region)", font: textFont, textColor: item.theme.list.itemSecondaryTextColor)
|
||||
|
||||
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
|
||||
let dateText = stringForRelativeTimestamp(strings: item.strings, relativeTimestamp: item.website.dateActive, relativeTo: timestamp, dateTimeFormat: item.dateTimeFormat)
|
||||
labelAttributedString = NSAttributedString(string: dateText, font: textFont, textColor: item.theme.list.itemSecondaryTextColor)
|
||||
appAttributedString = NSAttributedString(string: appString, font: textFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor)
|
||||
|
||||
let leftInset: CGFloat = 15.0 + params.leftInset
|
||||
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
|
||||
let label = stringForRelativeActivityTimestamp(strings: item.presentationData.strings, dateTimeFormat: item.dateTimeFormat, relativeTimestamp: item.website.dateActive, relativeTo: timestamp)
|
||||
|
||||
locationAttributedString = NSAttributedString(string: "\(item.website.region) • \(label)", font: textFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
|
||||
|
||||
let leftInset: CGFloat = 59.0 + params.leftInset
|
||||
|
||||
var editableControlSizeAndApply: (CGFloat, (CGFloat) -> ItemListEditableControlNode)?
|
||||
|
||||
let editingOffset: CGFloat
|
||||
if item.editing {
|
||||
let sizeAndApply = editableControlLayout(item.theme, false)
|
||||
let sizeAndApply = editableControlLayout(item.presentationData.theme, false)
|
||||
editableControlSizeAndApply = sizeAndApply
|
||||
editingOffset = sizeAndApply.0
|
||||
} else {
|
||||
editingOffset = 0.0
|
||||
}
|
||||
|
||||
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: labelAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - editingOffset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - editingOffset - rightInset - labelLayout.size.width - 5.0 - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - editingOffset - rightInset - 5.0 - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (appLayout, appApply) = makeAppLayout(TextNodeLayoutArguments(attributedString: appAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - editingOffset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (locationLayout, locationApply) = makeLocationLayout(TextNodeLayoutArguments(attributedString: locationAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - editingOffset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
@ -263,14 +261,14 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode {
|
||||
strongSelf.layoutParams = (item, params, neighbors)
|
||||
|
||||
if let _ = updatedTheme {
|
||||
strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||
strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor
|
||||
strongSelf.highlightedBackgroundNode.backgroundColor = item.theme.list.itemHighlightedBackgroundColor
|
||||
strongSelf.topStripeNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor
|
||||
strongSelf.bottomStripeNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor
|
||||
strongSelf.backgroundNode.backgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor
|
||||
strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor
|
||||
}
|
||||
|
||||
if let peer = item.peer {
|
||||
strongSelf.avatarNode.setPeer(context: item.context, theme: item.theme, peer: EnginePeer(peer), authorOfMessage: nil, overrideImage: nil, emptyColor: nil, clipStyle: .none, synchronousLoad: false)
|
||||
strongSelf.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: EnginePeer(peer), authorOfMessage: nil, overrideImage: nil, emptyColor: nil, clipStyle: .none, synchronousLoad: false)
|
||||
}
|
||||
|
||||
let revealOffset = strongSelf.revealOffset
|
||||
@ -329,7 +327,6 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode {
|
||||
})
|
||||
}
|
||||
|
||||
let _ = labelApply()
|
||||
let _ = titleApply()
|
||||
let _ = appApply()
|
||||
let _ = locationApply()
|
||||
@ -370,7 +367,7 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode {
|
||||
strongSelf.bottomStripeNode.isHidden = hasCorners
|
||||
}
|
||||
|
||||
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
|
||||
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
|
||||
|
||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
|
||||
@ -378,9 +375,9 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode {
|
||||
transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)))
|
||||
|
||||
|
||||
transition.updateFrame(node: strongSelf.avatarNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset + 1.0, y: 12.0), size: CGSize(width: 13.0, height: 13.0)))
|
||||
transition.updateFrame(node: strongSelf.labelNode, frame: CGRect(origin: CGPoint(x: revealOffset + params.width - labelLayout.size.width - 15.0 - rightInset, y: 10.0), size: labelLayout.size))
|
||||
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset + 20.0, y: 10.0), size: titleLayout.size))
|
||||
transition.updateFrame(node: strongSelf.avatarNode, frame: CGRect(origin: CGPoint(x: params.leftInset + revealOffset + editingOffset + 16.0, y: 12.0), size: CGSize(width: 30.0, height: 30.0)))
|
||||
|
||||
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 10.0), size: titleLayout.size))
|
||||
transition.updateFrame(node: strongSelf.appNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 30.0), size: appLayout.size))
|
||||
transition.updateFrame(node: strongSelf.locationNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 50.0), size: locationLayout.size))
|
||||
|
||||
@ -422,7 +419,7 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode {
|
||||
editingOffset = 0.0
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.avatarNode, frame: CGRect(origin: CGPoint(x: leftInset + self.revealOffset + editingOffset + 1.0, y: self.avatarNode.frame.minY), size: self.avatarNode.bounds.size))
|
||||
transition.updateFrame(node: self.avatarNode, frame: CGRect(origin: CGPoint(x: params.leftInset + self.revealOffset + editingOffset + 16.0, y: self.avatarNode.frame.minY), size: self.avatarNode.bounds.size))
|
||||
transition.updateFrame(node: self.labelNode, frame: CGRect(origin: CGPoint(x: self.revealOffset + params.width - params.rightInset - self.labelNode.bounds.size.width - 15.0, y: self.labelNode.frame.minY), size: self.labelNode.bounds.size))
|
||||
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + self.revealOffset + editingOffset + 20.0, y: self.titleNode.frame.minY), size: self.titleNode.bounds.size))
|
||||
transition.updateFrame(node: self.appNode, frame: CGRect(origin: CGPoint(x: leftInset + self.revealOffset + editingOffset, y: self.appNode.frame.minY), size: self.appNode.bounds.size))
|
||||
|
@ -280,8 +280,8 @@ private enum RecentSessionsEntry: ItemListNodeEntry {
|
||||
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.blockDestructiveIcon(theme), title: text, sectionId: self.section, height: .generic, color: .destructive, editing: false, action: {
|
||||
arguments.terminateAllWebSessions()
|
||||
})
|
||||
case let .currentAddDevice(_, text):
|
||||
return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
case let .currentAddDevice(theme, text):
|
||||
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.addDeviceIcon(theme), title: text, sectionId: self.section, height: .generic, color: .accent, editing: false, action: {
|
||||
arguments.addDevice()
|
||||
})
|
||||
case let .currentSessionInfo(_, text):
|
||||
@ -303,8 +303,8 @@ private enum RecentSessionsEntry: ItemListNodeEntry {
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
case let .otherSessionsHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .addDevice(_, text):
|
||||
return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
case let .addDevice(theme, text):
|
||||
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.addDeviceIcon(theme), title: text, sectionId: self.section, height: .generic, color: .accent, editing: false, action: {
|
||||
arguments.addDevice()
|
||||
})
|
||||
case let .session(_, _, _, dateTimeFormat, session, enabled, editing, revealed):
|
||||
@ -313,8 +313,8 @@ private enum RecentSessionsEntry: ItemListNodeEntry {
|
||||
}, removeSession: { id in
|
||||
arguments.removeSession(id)
|
||||
})
|
||||
case let .website(_, theme, strings, dateTimeFormat, nameDisplayOrder, website, peer, enabled, editing, revealed):
|
||||
return ItemListWebsiteItem(context: arguments.context, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, website: website, peer: peer, enabled: enabled, editing: editing, revealed: revealed, sectionId: self.section, setSessionIdWithRevealedOptions: { previousId, id in
|
||||
case let .website(_, _, _, dateTimeFormat, nameDisplayOrder, website, peer, enabled, editing, revealed):
|
||||
return ItemListWebsiteItem(context: arguments.context, presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, website: website, peer: peer, enabled: enabled, editing: editing, revealed: revealed, sectionId: self.section, setSessionIdWithRevealedOptions: { previousId, id in
|
||||
arguments.setSessionIdWithRevealedOptions(previousId, id)
|
||||
}, removeSession: { id in
|
||||
arguments.removeWebSession(id)
|
||||
|
@ -126,7 +126,7 @@ class BubbleSettingsRadiusItemNode: ListViewItemNode, ItemListItemNode {
|
||||
sliderView.enablePanHandling = true
|
||||
sliderView.trackCornerRadius = 2.0
|
||||
sliderView.lineSize = 4.0
|
||||
sliderView.dotSize = 5.0
|
||||
sliderView.dotSize = 8.0
|
||||
sliderView.minimumValue = 0.0
|
||||
sliderView.maximumValue = 4.0
|
||||
sliderView.startValue = 0.0
|
||||
|
@ -123,8 +123,8 @@ class ThemeSettingsFontSizeItemNode: ListViewItemNode, ItemListItemNode {
|
||||
sliderView.enablePanHandling = true
|
||||
sliderView.enablePanHandling = true
|
||||
sliderView.trackCornerRadius = 1.0
|
||||
sliderView.lineSize = 2.0
|
||||
sliderView.dotSize = 5.0
|
||||
sliderView.lineSize = 4.0
|
||||
sliderView.dotSize = 8.0
|
||||
sliderView.minimumValue = 0.0
|
||||
sliderView.maximumValue = 6.0
|
||||
sliderView.startValue = 0.0
|
||||
|
@ -62,6 +62,7 @@ public enum PresentationResourceKey: Int32 {
|
||||
case itemListKnob
|
||||
case itemListBlockAccentIcon
|
||||
case itemListBlockDestructiveIcon
|
||||
case itemListAddDeviceIcon
|
||||
|
||||
case itemListVoiceCallIcon
|
||||
case itemListVideoCallIcon
|
||||
|
@ -210,6 +210,12 @@ public struct PresentationResourcesItemList {
|
||||
})
|
||||
}
|
||||
|
||||
public static func addDeviceIcon(_ theme: PresentationTheme) -> UIImage? {
|
||||
return theme.image(PresentationResourceKey.itemListAddDeviceIcon.rawValue, { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Settings/QrIcon"), color: theme.list.itemAccentColor)
|
||||
})
|
||||
}
|
||||
|
||||
public static func cornersImage(_ theme: PresentationTheme, top: Bool, bottom: Bool) -> UIImage? {
|
||||
if !top && !bottom {
|
||||
return nil
|
||||
|
@ -23,19 +23,19 @@ private func renderIcon(name: String) -> UIImage? {
|
||||
}
|
||||
|
||||
public struct PresentationResourcesSettings {
|
||||
public static let editProfile = renderIcon(name: "Settings/MenuIcons/EditProfile")
|
||||
public static let proxy = renderIcon(name: "Settings/MenuIcons/Proxy")
|
||||
public static let savedMessages = renderIcon(name: "Settings/MenuIcons/SavedMessages")
|
||||
public static let recentCalls = renderIcon(name: "Settings/MenuIcons/RecentCalls")
|
||||
public static let devices = renderIcon(name: "Settings/MenuIcons/Sessions")
|
||||
public static let chatFolders = renderIcon(name: "Settings/MenuIcons/ChatListFilters")
|
||||
public static let stickers = renderIcon(name: "Settings/MenuIcons/Stickers")
|
||||
public static let editProfile = renderIcon(name: "Settings/Menu/EditProfile")
|
||||
public static let proxy = renderIcon(name: "Settings/Menu/Proxy")
|
||||
public static let savedMessages = renderIcon(name: "Settings/Menu/SavedMessages")
|
||||
public static let recentCalls = renderIcon(name: "Settings/Menu/RecentCalls")
|
||||
public static let devices = renderIcon(name: "Settings/Menu/Sessions")
|
||||
public static let chatFolders = renderIcon(name: "Settings/Menu/ChatListFilters")
|
||||
public static let stickers = renderIcon(name: "Settings/Menu/Stickers")
|
||||
|
||||
public static let notifications = renderIcon(name: "Settings/MenuIcons/Notifications")
|
||||
public static let security = renderIcon(name: "Settings/MenuIcons/Security")
|
||||
public static let dataAndStorage = renderIcon(name: "Settings/MenuIcons/DataAndStorage")
|
||||
public static let appearance = renderIcon(name: "Settings/MenuIcons/Appearance")
|
||||
public static let language = renderIcon(name: "Settings/MenuIcons/Language")
|
||||
public static let notifications = renderIcon(name: "Settings/Menu/Notifications")
|
||||
public static let security = renderIcon(name: "Settings/Menu/Security")
|
||||
public static let dataAndStorage = renderIcon(name: "Settings/Menu/DataAndStorage")
|
||||
public static let appearance = renderIcon(name: "Settings/Menu/Appearance")
|
||||
public static let language = renderIcon(name: "Settings/Menu/Language")
|
||||
|
||||
public static let wallet = generateImage(CGSize(width: 29.0, height: 29.0), contextGenerator: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
@ -44,24 +44,24 @@ public struct PresentationResourcesSettings {
|
||||
context.setFillColor(UIColor.white.cgColor)
|
||||
context.fill(bounds.insetBy(dx: 5.0, dy: 5.0))
|
||||
|
||||
if let image = generateTintedImage(image: UIImage(bundleImageName: "Settings/MenuIcons/Wallet"), color: UIColor(rgb: 0x1b1b1c))?.cgImage {
|
||||
if let image = generateTintedImage(image: UIImage(bundleImageName: "Settings/Menu/Wallet"), color: UIColor(rgb: 0x1b1b1c))?.cgImage {
|
||||
context.draw(image, in: bounds)
|
||||
}
|
||||
|
||||
drawBorder(context: context, rect: bounds)
|
||||
})
|
||||
|
||||
public static let passport = renderIcon(name: "Settings/MenuIcons/Passport")
|
||||
public static let watch = renderIcon(name: "Settings/MenuIcons/Watch")
|
||||
public static let passport = renderIcon(name: "Settings/Menu/Passport")
|
||||
public static let watch = renderIcon(name: "Settings/Menu/Watch")
|
||||
|
||||
public static let support = renderIcon(name: "Settings/MenuIcons/Support")
|
||||
public static let faq = renderIcon(name: "Settings/MenuIcons/Faq")
|
||||
public static let tips = renderIcon(name: "Settings/MenuIcons/Tips")
|
||||
public static let support = renderIcon(name: "Settings/Menu/Support")
|
||||
public static let faq = renderIcon(name: "Settings/Menu/Faq")
|
||||
public static let tips = renderIcon(name: "Settings/Menu/Tips")
|
||||
|
||||
public static let addAccount = renderIcon(name: "Settings/MenuIcons/AddAccount")
|
||||
public static let setPasscode = renderIcon(name: "Settings/MenuIcons/SetPasscode")
|
||||
public static let clearCache = renderIcon(name: "Settings/MenuIcons/ClearCache")
|
||||
public static let changePhoneNumber = renderIcon(name: "Settings/MenuIcons/ChangePhoneNumber")
|
||||
public static let addAccount = renderIcon(name: "Settings/Menu/AddAccount")
|
||||
public static let setPasscode = renderIcon(name: "Settings/Menu/SetPasscode")
|
||||
public static let clearCache = renderIcon(name: "Settings/Menu/ClearCache")
|
||||
public static let changePhoneNumber = renderIcon(name: "Settings/Menu/ChangePhoneNumber")
|
||||
|
||||
public static let websites = renderIcon(name: "Settings/MenuIcons/Websites")
|
||||
public static let websites = renderIcon(name: "Settings/Menu/Websites")
|
||||
}
|
||||
|
@ -345,6 +345,42 @@ public func stringForRelativeLiveLocationUpdateTimestamp(strings: PresentationSt
|
||||
}
|
||||
}
|
||||
|
||||
public func stringForRelativeActivityTimestamp(strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, relativeTimestamp: Int32, relativeTo timestamp: Int32) -> String {
|
||||
let difference = timestamp - relativeTimestamp
|
||||
if difference < 60 {
|
||||
return strings.Time_JustNow
|
||||
} else if difference < 60 * 60 {
|
||||
let minutes = difference / 60
|
||||
return strings.Time_MinutesAgo(minutes)
|
||||
} else {
|
||||
var t: time_t = time_t(relativeTimestamp)
|
||||
var timeinfo: tm = tm()
|
||||
localtime_r(&t, &timeinfo)
|
||||
|
||||
var now: time_t = time_t(timestamp)
|
||||
var timeinfoNow: tm = tm()
|
||||
localtime_r(&now, &timeinfoNow)
|
||||
|
||||
if timeinfo.tm_year != timeinfoNow.tm_year {
|
||||
return strings.Time_AtDate(stringForTimestamp(day: timeinfo.tm_mday, month: timeinfo.tm_mon + 1, year: timeinfo.tm_year, dateTimeFormat: dateTimeFormat)).string
|
||||
}
|
||||
|
||||
let dayDifference = timeinfo.tm_yday - timeinfoNow.tm_yday
|
||||
if dayDifference == 0 || dayDifference == -1 {
|
||||
let day: RelativeTimestampFormatDay
|
||||
if dayDifference == 0 {
|
||||
let minutes = difference / (60 * 60)
|
||||
return strings.Time_HoursAgo(minutes)
|
||||
} else {
|
||||
day = .yesterday
|
||||
}
|
||||
return humanReadableStringForTimestamp(strings: strings, day: day, dateTimeFormat: dateTimeFormat, hours: timeinfo.tm_hour, minutes: timeinfo.tm_min).string
|
||||
} else {
|
||||
return strings.Time_AtDate(stringForTimestamp(day: timeinfo.tm_mday, month: timeinfo.tm_mon + 1, year: timeinfo.tm_year, dateTimeFormat: dateTimeFormat)).string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func stringAndActivityForUserPresence(strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, presence: EnginePeer.Presence, relativeTo timestamp: Int32, expanded: Bool = false) -> (String, Bool) {
|
||||
switch presence.status {
|
||||
case let .present(statusTimestamp):
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Settings/Devices/Android.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Icon-31.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
122
submodules/TelegramUI/Images.xcassets/Settings/Devices/Android.imageset/Icon-31.pdf
vendored
Normal file
@ -0,0 +1,122 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
|
||||
0.203922 0.780392 0.349020 scn
|
||||
0.000000 18.799999 m
|
||||
0.000000 22.720367 0.000000 24.680552 0.762954 26.177933 c
|
||||
1.434068 27.495068 2.504932 28.565931 3.822066 29.237045 c
|
||||
5.319448 30.000000 7.279633 30.000000 11.200000 30.000000 c
|
||||
18.799999 30.000000 l
|
||||
22.720367 30.000000 24.680552 30.000000 26.177933 29.237045 c
|
||||
27.495068 28.565931 28.565931 27.495068 29.237045 26.177933 c
|
||||
30.000000 24.680552 30.000000 22.720367 30.000000 18.799999 c
|
||||
30.000000 11.200001 l
|
||||
30.000000 7.279633 30.000000 5.319448 29.237045 3.822067 c
|
||||
28.565931 2.504932 27.495068 1.434069 26.177933 0.762955 c
|
||||
24.680552 0.000000 22.720367 0.000000 18.799999 0.000000 c
|
||||
11.200000 0.000000 l
|
||||
7.279633 0.000000 5.319448 0.000000 3.822066 0.762955 c
|
||||
2.504932 1.434069 1.434068 2.504932 0.762954 3.822067 c
|
||||
0.000000 5.319448 0.000000 7.279633 0.000000 11.200001 c
|
||||
0.000000 18.799999 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 4.123901 8.382080 cm
|
||||
1.000000 1.000000 1.000000 scn
|
||||
4.601601 14.520930 m
|
||||
4.378984 14.921641 3.873677 15.066014 3.472967 14.843397 c
|
||||
3.072257 14.620780 2.927883 14.115474 3.150500 13.714764 c
|
||||
5.004922 10.376803 l
|
||||
2.789933 8.917612 1.109708 6.612994 0.339578 3.738848 c
|
||||
0.004541 2.488480 -0.162977 1.863297 0.219400 1.161238 c
|
||||
0.304677 1.004665 0.468011 0.791803 0.597177 0.668907 c
|
||||
1.176347 0.117853 1.962918 0.117853 3.536060 0.117853 c
|
||||
18.216061 0.117853 l
|
||||
19.789202 0.117853 20.575773 0.117853 21.154943 0.668907 c
|
||||
21.284109 0.791803 21.447443 1.004665 21.532721 1.161236 c
|
||||
21.915098 1.863297 21.747580 2.488480 21.412542 3.738846 c
|
||||
20.642410 6.613000 18.962179 8.917621 16.747183 10.376812 c
|
||||
18.601601 13.714764 l
|
||||
18.824217 14.115474 18.679844 14.620780 18.279133 14.843397 c
|
||||
17.878422 15.066014 17.373116 14.921641 17.150499 14.520930 c
|
||||
15.293184 11.177763 l
|
||||
13.941256 11.788494 12.447121 12.117853 10.876060 12.117853 c
|
||||
9.304994 12.117853 7.810853 11.788492 6.458920 11.177755 c
|
||||
4.601601 14.520930 l
|
||||
h
|
||||
7.876053 5.617853 m
|
||||
7.876053 4.789426 7.204480 4.117853 6.376053 4.117853 c
|
||||
5.547626 4.117853 4.876053 4.789426 4.876053 5.617853 c
|
||||
4.876053 6.446280 5.547626 7.117853 6.376053 7.117853 c
|
||||
7.204480 7.117853 7.876053 6.446280 7.876053 5.617853 c
|
||||
h
|
||||
16.876053 5.617853 m
|
||||
16.876053 4.789426 16.204479 4.117853 15.376053 4.117853 c
|
||||
14.547626 4.117853 13.876053 4.789426 13.876053 5.617853 c
|
||||
13.876053 6.446280 14.547626 7.117853 15.376053 7.117853 c
|
||||
16.204479 7.117853 16.876053 6.446280 16.876053 5.617853 c
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
2502
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Type /Catalog
|
||||
/Pages 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000002592 00000 n
|
||||
0000002615 00000 n
|
||||
0000002788 00000 n
|
||||
0000002862 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
2921
|
||||
%%EOF
|
12
submodules/TelegramUI/Images.xcassets/Settings/Devices/Opera.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Icon-32.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
101
submodules/TelegramUI/Images.xcassets/Settings/Devices/Opera.imageset/Icon-32.pdf
vendored
Normal file
@ -0,0 +1,101 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
|
||||
1.000000 0.231373 0.188235 scn
|
||||
0.000000 18.799999 m
|
||||
0.000000 22.720367 0.000000 24.680552 0.762954 26.177933 c
|
||||
1.434068 27.495068 2.504932 28.565931 3.822066 29.237045 c
|
||||
5.319448 30.000000 7.279633 30.000000 11.200000 30.000000 c
|
||||
18.799999 30.000000 l
|
||||
22.720367 30.000000 24.680552 30.000000 26.177933 29.237045 c
|
||||
27.495068 28.565931 28.565931 27.495068 29.237045 26.177933 c
|
||||
30.000000 24.680552 30.000000 22.720367 30.000000 18.799999 c
|
||||
30.000000 11.200001 l
|
||||
30.000000 7.279633 30.000000 5.319448 29.237045 3.822067 c
|
||||
28.565931 2.504932 27.495068 1.434069 26.177933 0.762955 c
|
||||
24.680552 0.000000 22.720367 0.000000 18.799999 0.000000 c
|
||||
11.200000 0.000000 l
|
||||
7.279633 0.000000 5.319448 0.000000 3.822066 0.762955 c
|
||||
2.504932 1.434069 1.434068 2.504932 0.762954 3.822067 c
|
||||
0.000000 5.319448 0.000000 7.279633 0.000000 11.200001 c
|
||||
0.000000 18.799999 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 5.000000 5.000000 cm
|
||||
1.000000 1.000000 1.000000 scn
|
||||
10.000000 0.000000 m
|
||||
15.522847 0.000000 20.000000 4.477153 20.000000 10.000000 c
|
||||
20.000000 15.522848 15.522847 20.000000 10.000000 20.000000 c
|
||||
4.477152 20.000000 0.000000 15.522848 0.000000 10.000000 c
|
||||
0.000000 4.477153 4.477152 0.000000 10.000000 0.000000 c
|
||||
h
|
||||
10.000000 2.000000 m
|
||||
12.761424 2.000000 15.000000 5.581722 15.000000 10.000000 c
|
||||
15.000000 14.418278 12.761424 18.000000 10.000000 18.000000 c
|
||||
7.238576 18.000000 5.000000 14.418278 5.000000 10.000000 c
|
||||
5.000000 5.581722 7.238576 2.000000 10.000000 2.000000 c
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
1564
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Type /Catalog
|
||||
/Pages 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000001654 00000 n
|
||||
0000001677 00000 n
|
||||
0000001850 00000 n
|
||||
0000001924 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
1983
|
||||
%%EOF
|
12
submodules/TelegramUI/Images.xcassets/Settings/Devices/iOS.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Icon-30.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
123
submodules/TelegramUI/Images.xcassets/Settings/Devices/iOS.imageset/Icon-30.pdf
vendored
Normal file
@ -0,0 +1,123 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
|
||||
0.000000 0.478431 1.000000 scn
|
||||
0.000000 18.799999 m
|
||||
0.000000 22.720367 0.000000 24.680552 0.762954 26.177933 c
|
||||
1.434068 27.495068 2.504932 28.565931 3.822066 29.237045 c
|
||||
5.319448 30.000000 7.279633 30.000000 11.200000 30.000000 c
|
||||
18.799999 30.000000 l
|
||||
22.720367 30.000000 24.680552 30.000000 26.177933 29.237045 c
|
||||
27.495068 28.565931 28.565931 27.495068 29.237045 26.177933 c
|
||||
30.000000 24.680552 30.000000 22.720367 30.000000 18.799999 c
|
||||
30.000000 11.200001 l
|
||||
30.000000 7.279633 30.000000 5.319448 29.237045 3.822067 c
|
||||
28.565931 2.504932 27.495068 1.434069 26.177933 0.762955 c
|
||||
24.680552 0.000000 22.720367 0.000000 18.799999 0.000000 c
|
||||
11.200000 0.000000 l
|
||||
7.279633 0.000000 5.319448 0.000000 3.822066 0.762955 c
|
||||
2.504932 1.434069 1.434068 2.504932 0.762954 3.822067 c
|
||||
0.000000 5.319448 0.000000 7.279633 0.000000 11.200001 c
|
||||
0.000000 18.799999 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 5.709961 5.475098 cm
|
||||
1.000000 1.000000 1.000000 scn
|
||||
17.318722 4.661448 m
|
||||
17.012165 3.946257 16.632772 3.264536 16.186548 2.627071 c
|
||||
15.593926 1.777941 15.107445 1.194162 14.735950 0.866894 c
|
||||
14.152173 0.336187 13.533016 0.061989 12.869633 0.044298 c
|
||||
12.391996 0.044298 11.817065 0.176975 11.144837 0.460018 c
|
||||
10.557850 0.714588 9.927019 0.852770 9.287363 0.866894 c
|
||||
8.630317 0.855038 7.981685 0.716904 7.376821 0.460018 c
|
||||
6.695747 0.185820 6.147350 0.044300 5.722785 0.026609 c
|
||||
5.085937 0.000074 4.449090 0.283115 3.812242 0.875738 c
|
||||
3.405367 1.229542 2.901196 1.839857 2.290883 2.697832 c
|
||||
1.606449 3.680990 1.064481 4.755993 0.681073 5.890916 c
|
||||
0.229973 7.199992 0.000000 8.464844 0.000000 9.694314 c
|
||||
0.000000 11.100687 0.300734 12.312468 0.911046 13.329655 c
|
||||
1.372815 14.127305 2.030385 14.794008 2.821589 15.266735 c
|
||||
3.601674 15.737316 4.493386 15.990782 5.404361 16.000879 c
|
||||
5.908533 16.000879 6.580760 15.841667 7.403356 15.532089 c
|
||||
8.225950 15.222509 8.756658 15.063297 8.986630 15.063297 c
|
||||
9.163532 15.063297 9.747309 15.249044 10.746806 15.611694 c
|
||||
11.690285 15.947809 12.486344 16.089331 13.134986 16.036259 c
|
||||
14.904008 15.894737 16.230774 15.195974 17.115284 13.939968 c
|
||||
15.532009 12.984696 14.753640 11.640240 14.771331 9.924288 c
|
||||
14.789021 8.579831 15.275502 7.465346 16.230774 6.580835 c
|
||||
16.655304 6.180765 17.146141 5.857531 17.681372 5.625564 c
|
||||
17.575230 5.289450 17.451399 4.971027 17.318722 4.661448 c
|
||||
h
|
||||
13.267662 20.600336 m
|
||||
13.267662 19.547770 12.887323 18.565962 12.117799 17.663759 c
|
||||
11.197907 16.584656 10.074579 15.965499 8.871644 16.062796 c
|
||||
8.853408 16.194679 8.844540 16.327688 8.845108 16.460825 c
|
||||
8.845108 17.469168 9.287364 18.548271 10.065734 19.432783 c
|
||||
10.479326 19.902321 10.985150 20.281689 11.551712 20.547266 c
|
||||
12.079977 20.818914 12.657554 20.981358 13.249973 21.024902 c
|
||||
13.258819 20.883381 13.267662 20.741859 13.267662 20.600336 c
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
2862
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Type /Catalog
|
||||
/Pages 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000002952 00000 n
|
||||
0000002975 00000 n
|
||||
0000003148 00000 n
|
||||
0000003222 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
3281
|
||||
%%EOF
|
@ -0,0 +1,9 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"provides-namespace" : true
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 666 B After Width: | Height: | Size: 666 B |
Before Width: | Height: | Size: 954 B After Width: | Height: | Size: 954 B |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 854 B After Width: | Height: | Size: 854 B |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 925 B After Width: | Height: | Size: 925 B |
Before Width: | Height: | Size: 760 B After Width: | Height: | Size: 760 B |