Various settings UI improvements

This commit is contained in:
Ilya Laktyushin 2021-10-26 02:55:06 +04:00
parent 0e13325154
commit f3fe192c02
104 changed files with 1517 additions and 175 deletions

View File

@ -4097,6 +4097,7 @@ Unused sets are archived when you add more.";
"ChatSettings.AutoDownloadSettings.TypePhoto" = "Photos"; "ChatSettings.AutoDownloadSettings.TypePhoto" = "Photos";
"ChatSettings.AutoDownloadSettings.TypeVideo" = "Videos (%@)"; "ChatSettings.AutoDownloadSettings.TypeVideo" = "Videos (%@)";
"ChatSettings.AutoDownloadSettings.TypeMedia" = "Media (%@)";
"ChatSettings.AutoDownloadSettings.TypeFile" = "Files (%@)"; "ChatSettings.AutoDownloadSettings.TypeFile" = "Files (%@)";
"ChatSettings.AutoDownloadSettings.OffForAll" = "Disabled"; "ChatSettings.AutoDownloadSettings.OffForAll" = "Disabled";
"ChatSettings.AutoDownloadSettings.Delimeter" = ", "; "ChatSettings.AutoDownloadSettings.Delimeter" = ", ";
@ -6938,3 +6939,21 @@ Sorry for the inconvenience.";
"Map.ETADays_3_10" = "%@ days"; "Map.ETADays_3_10" = "%@ days";
"Map.ETADays_many" = "%@ days"; "Map.ETADays_many" = "%@ days";
"Map.ETADays_any" = "%@ 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 %@";

View File

@ -343,7 +343,7 @@ public final class AvatarNode: ASDisplayNode {
let parameters: AvatarNodeParameters 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.contents = nil
self.displaySuspended = true self.displaySuspended = true
self.imageReady.set(self.imageNode.contentReady) self.imageReady.set(self.imageNode.contentReady)

View File

@ -188,6 +188,7 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
private let topStripeNode: ASDisplayNode private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode private let bottomStripeNode: ASDisplayNode
private let highlightedBackgroundNode: ASDisplayNode private let highlightedBackgroundNode: ASDisplayNode
private let maskNode: ASImageNode
private let avatarNode: AvatarNode private let avatarNode: AvatarNode
private let titleNode: TextNode private let titleNode: TextNode
@ -206,6 +207,8 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
self.backgroundNode = ASDisplayNode() self.backgroundNode = ASDisplayNode()
self.backgroundNode.isLayerBacked = true self.backgroundNode.isLayerBacked = true
self.maskNode = ASImageNode()
self.topStripeNode = ASDisplayNode() self.topStripeNode = ASDisplayNode()
self.topStripeNode.isLayerBacked = true self.topStripeNode.isLayerBacked = true
@ -523,7 +526,9 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
} else if last && strongSelf.bottomStripeNode.supernode != nil { } else if last && strongSelf.bottomStripeNode.supernode != nil {
strongSelf.bottomStripeNode.removeFromSupernode() 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))) 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: case .blocks:
if strongSelf.backgroundNode.supernode == nil { if strongSelf.backgroundNode.supernode == nil {
@ -535,11 +540,18 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
if strongSelf.bottomStripeNode.supernode == nil { if strongSelf.bottomStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2) 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 { switch neighbors.top {
case .sameSection(false): case .sameSection(false):
strongSelf.topStripeNode.isHidden = true strongSelf.topStripeNode.isHidden = true
default: default:
strongSelf.topStripeNode.isHidden = false hasTopCorners = true
strongSelf.topStripeNode.isHidden = hasCorners
} }
let bottomStripeInset: CGFloat let bottomStripeInset: CGFloat
switch neighbors.bottom { switch neighbors.bottom {
@ -547,9 +559,14 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
bottomStripeInset = leftInset bottomStripeInset = leftInset
default: default:
bottomStripeInset = 0.0 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.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)) 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))) transition.updateFrameAdditive(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: nodeLayout.size.width - bottomStripeInset, height: separatorHeight)))
} }

View File

@ -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.controllerNode.startNewCall = { [weak self] in
self?.beginCallImpl() self?.beginCallImpl()
} }

View File

@ -117,7 +117,7 @@ private func mappedInsertEntries(context: AccountContext, presentationData: Item
return entries.map { entry -> ListViewInsertItem in return entries.map { entry -> ListViewInsertItem in
switch entry.entry { switch entry.entry {
case let .displayTab(_, text, value): 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) nodeInteraction.updateShowCallsTab(value)
}), directionHint: entry.directionHint) }), directionHint: entry.directionHint)
case let .displayTabInfo(_, text): case let .displayTabInfo(_, text):
@ -136,7 +136,7 @@ private func mappedUpdateEntries(context: AccountContext, presentationData: Item
return entries.map { entry -> ListViewUpdateItem in return entries.map { entry -> ListViewUpdateItem in
switch entry.entry { switch entry.entry {
case let .displayTab(_, text, value): 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) nodeInteraction.updateShowCallsTab(value)
}), directionHint: entry.directionHint) }), directionHint: entry.directionHint)
case let .displayTabInfo(_, text): case let .displayTabInfo(_, text):
@ -177,6 +177,8 @@ final class CallListControllerNode: ASDisplayNode {
return _ready.get() return _ready.get()
} }
weak var navigationBar: NavigationBar?
var peerSelected: ((EnginePeer.Id) -> Void)? var peerSelected: ((EnginePeer.Id) -> Void)?
var activateSearch: (() -> Void)? var activateSearch: (() -> Void)?
var deletePeerChat: ((EnginePeer.Id) -> Void)? var deletePeerChat: ((EnginePeer.Id) -> Void)?
@ -215,6 +217,8 @@ final class CallListControllerNode: ASDisplayNode {
private let openGroupCallDisposable = MetaDisposable() 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) { 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.controller = controller
self.context = context self.context = context
@ -272,7 +276,7 @@ final class CallListControllerNode: ASDisplayNode {
self.addSubnode(self.emptyButtonTextNode) self.addSubnode(self.emptyButtonTextNode)
self.addSubnode(self.emptyButtonIconNode) self.addSubnode(self.emptyButtonIconNode)
self.addSubnode(self.emptyButtonNode) self.addSubnode(self.emptyButtonNode)
switch self.mode { switch self.mode {
case .tab: case .tab:
self.backgroundColor = presentationData.theme.chatList.backgroundColor 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) 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 { deinit {
@ -838,8 +875,25 @@ final class CallListControllerNode: ASDisplayNode {
var insets = layout.insets(options: [.input]) var insets = layout.insets(options: [.input])
insets.top += max(navigationBarHeight, layout.insets(options: [.statusBar]).top) 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.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) self.listNode.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0)

View File

@ -922,8 +922,8 @@ open class NavigationBar: ASDisplayNode {
} }
public func updateBackgroundAlpha(_ alpha: CGFloat, transition: ContainedViewLayoutTransition) { public func updateBackgroundAlpha(_ alpha: CGFloat, transition: ContainedViewLayoutTransition) {
transition.updateAlpha(node: self.backgroundNode, alpha: alpha) transition.updateAlpha(node: self.backgroundNode, alpha: alpha, delay: 0.15)
transition.updateAlpha(node: self.stripeNode, alpha: alpha) transition.updateAlpha(node: self.stripeNode, alpha: alpha, delay: 0.15)
} }
public func updatePresentationData(_ presentationData: NavigationBarPresentationData) { public func updatePresentationData(_ presentationData: NavigationBarPresentationData) {

View File

@ -357,13 +357,13 @@ open class ItemListControllerNode: ASDisplayNode {
case let .known(value): case let .known(value):
let transition: ContainedViewLayoutTransition let transition: ContainedViewLayoutTransition
if let previousContentOffsetValue = previousContentOffsetValue, value <= 0.0, previousContentOffsetValue > 30.0 { 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 { } else {
transition = .immediate transition = .immediate
} }
strongSelf.navigationBar.updateBackgroundAlpha(min(30.0, value) / 30.0, transition: transition) strongSelf.navigationBar.updateBackgroundAlpha(min(30.0, value) / 30.0, transition: transition)
case .unknown, .none: case .unknown, .none:
strongSelf.navigationBar.updateBackgroundAlpha(0.0, transition: .immediate) strongSelf.navigationBar.updateBackgroundAlpha(1.0, transition: .immediate)
} }
strongSelf.previousContentOffset = offset strongSelf.previousContentOffset = offset

View File

@ -157,15 +157,15 @@ private enum AutodownloadMediaCategoryEntry: ItemListNodeEntry {
case let .typesHeader(_, text): case let .typesHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .photos(_, text, value, enabled): 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) arguments.customize(.photo)
}) })
case let .videos(_, text, value, enabled): 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) arguments.customize(.video)
}) })
case let .files(_, text, value, enabled): 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) arguments.customize(.file)
}) })
case let .voiceMessagesInfo(_, text): case let .voiceMessagesInfo(_, text):

View File

@ -18,7 +18,7 @@ private final class DataAndStorageControllerArguments {
let openProxy: () -> Void let openProxy: () -> Void
let openAutomaticDownloadConnectionType: (AutomaticDownloadConnectionType) -> Void let openAutomaticDownloadConnectionType: (AutomaticDownloadConnectionType) -> Void
let resetAutomaticDownload: () -> Void let resetAutomaticDownload: () -> Void
let openVoiceUseLessData: () -> Void let toggleVoiceUseLessData: (Bool) -> Void
let openSaveIncomingPhotos: () -> Void let openSaveIncomingPhotos: () -> Void
let toggleSaveEditedPhotos: (Bool) -> Void let toggleSaveEditedPhotos: (Bool) -> Void
let toggleAutoplayGifs: (Bool) -> Void let toggleAutoplayGifs: (Bool) -> Void
@ -28,13 +28,13 @@ private final class DataAndStorageControllerArguments {
let openIntents: () -> Void let openIntents: () -> Void
let toggleEnableSensitiveContent: (Bool) -> 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.openStorageUsage = openStorageUsage
self.openNetworkUsage = openNetworkUsage self.openNetworkUsage = openNetworkUsage
self.openProxy = openProxy self.openProxy = openProxy
self.openAutomaticDownloadConnectionType = openAutomaticDownloadConnectionType self.openAutomaticDownloadConnectionType = openAutomaticDownloadConnectionType
self.resetAutomaticDownload = resetAutomaticDownload self.resetAutomaticDownload = resetAutomaticDownload
self.openVoiceUseLessData = openVoiceUseLessData self.toggleVoiceUseLessData = toggleVoiceUseLessData
self.openSaveIncomingPhotos = openSaveIncomingPhotos self.openSaveIncomingPhotos = openSaveIncomingPhotos
self.toggleSaveEditedPhotos = toggleSaveEditedPhotos self.toggleSaveEditedPhotos = toggleSaveEditedPhotos
self.toggleAutoplayGifs = toggleAutoplayGifs self.toggleAutoplayGifs = toggleAutoplayGifs
@ -49,6 +49,7 @@ private final class DataAndStorageControllerArguments {
private enum DataAndStorageSection: Int32 { private enum DataAndStorageSection: Int32 {
case usage case usage
case autoDownload case autoDownload
case backgroundDownload
case autoPlay case autoPlay
case voiceCalls case voiceCalls
case other case other
@ -79,18 +80,20 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
case automaticDownloadCellular(PresentationTheme, String, String) case automaticDownloadCellular(PresentationTheme, String, String)
case automaticDownloadWifi(PresentationTheme, String, String) case automaticDownloadWifi(PresentationTheme, String, String)
case automaticDownloadReset(PresentationTheme, String, Bool) case automaticDownloadReset(PresentationTheme, String, Bool)
case downloadInBackground(PresentationTheme, String, Bool)
case downloadInBackgroundInfo(PresentationTheme, String)
case autoplayHeader(PresentationTheme, String) case autoplayHeader(PresentationTheme, String)
case autoplayGifs(PresentationTheme, String, Bool) case autoplayGifs(PresentationTheme, String, Bool)
case autoplayVideos(PresentationTheme, String, Bool) case autoplayVideos(PresentationTheme, String, Bool)
case voiceCallsHeader(PresentationTheme, String) case useLessVoiceData(PresentationTheme, String, Bool)
case useLessVoiceData(PresentationTheme, String, String) case useLessVoiceDataInfo(PresentationTheme, String)
case otherHeader(PresentationTheme, String) case otherHeader(PresentationTheme, String)
case shareSheet(PresentationTheme, String) case shareSheet(PresentationTheme, String)
case saveIncomingPhotos(PresentationTheme, String) case saveIncomingPhotos(PresentationTheme, String)
case saveEditedPhotos(PresentationTheme, String, Bool) case saveEditedPhotos(PresentationTheme, String, Bool)
case openLinksIn(PresentationTheme, String, String) case openLinksIn(PresentationTheme, String, String)
case downloadInBackground(PresentationTheme, String, Bool)
case downloadInBackgroundInfo(PresentationTheme, String)
case connectionHeader(PresentationTheme, String) case connectionHeader(PresentationTheme, String)
case connectionProxy(PresentationTheme, String, String) case connectionProxy(PresentationTheme, String, String)
case enableSensitiveContent(String, Bool) case enableSensitiveContent(String, Bool)
@ -101,11 +104,13 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
return DataAndStorageSection.usage.rawValue return DataAndStorageSection.usage.rawValue
case .automaticDownloadHeader, .automaticDownloadCellular, .automaticDownloadWifi, .automaticDownloadReset: case .automaticDownloadHeader, .automaticDownloadCellular, .automaticDownloadWifi, .automaticDownloadReset:
return DataAndStorageSection.autoDownload.rawValue return DataAndStorageSection.autoDownload.rawValue
case .downloadInBackground, .downloadInBackgroundInfo:
return DataAndStorageSection.backgroundDownload.rawValue
case .useLessVoiceData, .useLessVoiceDataInfo:
return DataAndStorageSection.voiceCalls.rawValue
case .autoplayHeader, .autoplayGifs, .autoplayVideos: case .autoplayHeader, .autoplayGifs, .autoplayVideos:
return DataAndStorageSection.autoPlay.rawValue return DataAndStorageSection.autoPlay.rawValue
case .voiceCallsHeader, .useLessVoiceData: case .otherHeader, .shareSheet, .saveIncomingPhotos, .saveEditedPhotos, .openLinksIn:
return DataAndStorageSection.voiceCalls.rawValue
case .otherHeader, .shareSheet, .saveIncomingPhotos, .saveEditedPhotos, .openLinksIn, .downloadInBackground, .downloadInBackgroundInfo:
return DataAndStorageSection.other.rawValue return DataAndStorageSection.other.rawValue
case .connectionHeader, .connectionProxy: case .connectionHeader, .connectionProxy:
return DataAndStorageSection.connection.rawValue return DataAndStorageSection.connection.rawValue
@ -128,36 +133,36 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
return 4 return 4
case .automaticDownloadReset: case .automaticDownloadReset:
return 5 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: case .downloadInBackground:
return 17 return 6
case .downloadInBackgroundInfo: 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: case .connectionHeader:
return 19 return 18
case .connectionProxy: case .connectionProxy:
return 20 return 19
case .enableSensitiveContent: case .enableSensitiveContent:
return 21 return 20
} }
} }
@ -217,14 +222,14 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .voiceCallsHeader(lhsTheme, lhsText): case let .useLessVoiceData(lhsTheme, lhsText, lhsValue):
if case let .voiceCallsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { if case let .useLessVoiceData(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true return true
} else { } else {
return false return false
} }
case let .useLessVoiceData(lhsTheme, lhsText, lhsValue): case let .useLessVoiceDataInfo(lhsTheme, lhsText):
if case let .useLessVoiceData(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { if case let .useLessVoiceDataInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true return true
} else { } else {
return false return false
@ -300,21 +305,21 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
let arguments = arguments as! DataAndStorageControllerArguments let arguments = arguments as! DataAndStorageControllerArguments
switch self { switch self {
case let .storageUsage(_, text): 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() arguments.openStorageUsage()
}) })
case let .networkUsage(_, text): 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() arguments.openNetworkUsage()
}) })
case let .automaticDownloadHeader(_, text): case let .automaticDownloadHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .automaticDownloadCellular(_, text, value): 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) arguments.openAutomaticDownloadConnectionType(.cellular)
}) })
case let .automaticDownloadWifi(_, text, value): 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) arguments.openAutomaticDownloadConnectionType(.wifi)
}) })
case let .automaticDownloadReset(_, text, enabled): 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 return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.toggleAutoplayVideos(value) arguments.toggleAutoplayVideos(value)
}, tag: DataAndStorageEntryTag.autoplayVideos) }, tag: DataAndStorageEntryTag.autoplayVideos)
case let .voiceCallsHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .useLessVoiceData(_, text, value): case let .useLessVoiceData(_, text, value):
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: { return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.openVoiceUseLessData() arguments.toggleVoiceUseLessData(value)
}) }, tag: DataAndStorageEntryTag.autoplayVideos)
case let .useLessVoiceDataInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .otherHeader(_, text): case let .otherHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .shareSheet(_, text): 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 { private func stringForAutoDownloadTypes(strings: PresentationStrings, decimalSeparator: String, photo: Bool, videoSize: Int32?, fileSize: Int32?) -> String {
var types: [String] = [] var types: [String] = []
if photo { if photo && videoSize == nil {
types.append(strings.ChatSettings_AutoDownloadSettings_TypePhoto) types.append(strings.ChatSettings_AutoDownloadSettings_TypePhoto)
} }
if let videoSize = videoSize { 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 { if let fileSize = fileSize {
types.append(strings.ChatSettings_AutoDownloadSettings_TypeFile(autodownloadDataSizeString(Int64(fileSize), decimalSeparator: decimalSeparator)).string) 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 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(.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(.autoplayHeader(presentationData.theme, presentationData.strings.ChatSettings_AutoPlayTitle))
entries.append(.autoplayGifs(presentationData.theme, presentationData.strings.ChatSettings_AutoPlayGifs, data.automaticMediaDownloadSettings.autoplayGifs)) 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(.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)) entries.append(.otherHeader(presentationData.theme, presentationData.strings.ChatSettings_Other))
if #available(iOSApplicationExtension 13.2, iOS 13.2, *) { if #available(iOSApplicationExtension 13.2, iOS 13.2, *) {
entries.append(.shareSheet(presentationData.theme, presentationData.strings.ChatSettings_IntentsSettings)) 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(.saveIncomingPhotos(presentationData.theme, presentationData.strings.Settings_SaveIncomingPhotos))
entries.append(.saveEditedPhotos(presentationData.theme, presentationData.strings.Settings_SaveEditedPhotos, data.generatedMediaStoreSettings.storeEditedPhotos)) entries.append(.saveEditedPhotos(presentationData.theme, presentationData.strings.Settings_SaveEditedPhotos, data.generatedMediaStoreSettings.storeEditedPhotos))
entries.append(.openLinksIn(presentationData.theme, presentationData.strings.ChatSettings_OpenLinksIn, defaultWebBrowser)) 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 let proxyValue: String
if let proxySettings = data.proxySettings, let activeServer = proxySettings.activeServer, proxySettings.enabled { if let proxySettings = data.proxySettings, let activeServer = proxySettings.activeServer, proxySettings.enabled {
switch activeServer.connection { switch activeServer.connection {
@ -608,8 +618,12 @@ public func dataAndStorageController(context: AccountContext, focusOnItemTag: Da
}) })
])]) ])])
presentControllerImpl?(actionSheet, nil) presentControllerImpl?(actionSheet, nil)
}, openVoiceUseLessData: { }, toggleVoiceUseLessData: { value in
pushControllerImpl?(voiceCallDataSavingController(context: context)) let _ = updateVoiceCallSettingsSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
var current = current
current.dataSaving = value ? .always : .never
return current
}).start()
}, openSaveIncomingPhotos: { }, openSaveIncomingPhotos: {
pushControllerImpl?(saveIncomingMediaController(context: context)) pushControllerImpl?(saveIncomingMediaController(context: context))
}, toggleSaveEditedPhotos: { value in }, toggleSaveEditedPhotos: { value in

View File

@ -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
}

View File

@ -287,7 +287,7 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
case let .privacyHeader(_, text): case let .privacyHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .blockedPeers(_, text, value): 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() arguments.openBlockedUsers()
}) })
case let .phoneNumberPrivacy(_, text, value): case let .phoneNumberPrivacy(_, text, value):
@ -317,15 +317,15 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
arguments.openVoiceCallPrivacy() arguments.openVoiceCallPrivacy()
}) })
case let .passcode(_, text, hasFaceId, value): 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() arguments.openPasscode()
}) })
case let .twoStepVerification(_, text, value, data): 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) arguments.openTwoStepVerification(data)
}) })
case let .activeSessions(_, text, value): 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() arguments.openActiveSessions()
}) })
case let .autoArchiveHeader(text): case let .autoArchiveHeader(text):

View File

@ -107,10 +107,10 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
private var disabledOverlayNode: ASDisplayNode? private var disabledOverlayNode: ASDisplayNode?
private let maskNode: ASImageNode private let maskNode: ASImageNode
let iconNode: ASImageNode
private let titleNode: TextNode private let titleNode: TextNode
private let appNode: TextNode private let appNode: TextNode
private let locationNode: TextNode private let locationNode: TextNode
private let labelNode: TextNode
private let activateArea: AccessibilityAreaNode private let activateArea: AccessibilityAreaNode
@ -130,6 +130,10 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
self.maskNode = ASImageNode() self.maskNode = ASImageNode()
self.iconNode = ASImageNode()
self.iconNode.cornerRadius = 7.0
self.iconNode.clipsToBounds = true
self.titleNode = TextNode() self.titleNode = TextNode()
self.titleNode.isUserInteractionEnabled = false self.titleNode.isUserInteractionEnabled = false
self.titleNode.contentMode = .left self.titleNode.contentMode = .left
@ -144,12 +148,7 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
self.locationNode.isUserInteractionEnabled = false self.locationNode.isUserInteractionEnabled = false
self.locationNode.contentMode = .left self.locationNode.contentMode = .left
self.locationNode.contentsScale = UIScreen.main.scale 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 = ASDisplayNode()
self.highlightedBackgroundNode.isLayerBacked = true self.highlightedBackgroundNode.isLayerBacked = true
@ -157,10 +156,10 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
self.addSubnode(self.iconNode)
self.addSubnode(self.titleNode) self.addSubnode(self.titleNode)
self.addSubnode(self.appNode) self.addSubnode(self.appNode)
self.addSubnode(self.locationNode) self.addSubnode(self.locationNode)
self.addSubnode(self.labelNode)
self.addSubnode(self.activateArea) self.addSubnode(self.activateArea)
} }
@ -169,7 +168,6 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let makeAppLayout = TextNode.asyncLayout(self.appNode) let makeAppLayout = TextNode.asyncLayout(self.appNode)
let makeLocationLayout = TextNode.asyncLayout(self.locationNode) let makeLocationLayout = TextNode.asyncLayout(self.locationNode)
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
let editableControlLayout = ItemListEditableControlNode.asyncLayout(self.editableControlNode) let editableControlLayout = ItemListEditableControlNode.asyncLayout(self.editableControlNode)
var currentDisabledOverlayNode = self.disabledOverlayNode var currentDisabledOverlayNode = self.disabledOverlayNode
@ -179,8 +177,8 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
return { item, params, neighbors in return { item, params, neighbors in
var updatedTheme: PresentationTheme? var updatedTheme: PresentationTheme?
let titleFont = Font.medium(floor(item.presentationData.fontSize.itemListBaseFontSize * 15.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 * 13.0 / 17.0)) let textFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0))
let verticalInset: CGFloat = 10.0 let verticalInset: CGFloat = 10.0
let titleSpacing: CGFloat = 1.0 let titleSpacing: CGFloat = 1.0
@ -193,7 +191,6 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
var titleAttributedString: NSAttributedString? var titleAttributedString: NSAttributedString?
var appAttributedString: NSAttributedString? var appAttributedString: NSAttributedString?
var locationAttributedString: NSAttributedString? var locationAttributedString: NSAttributedString?
var labelAttributedString: NSAttributedString?
let peerRevealOptions: [ItemListRevealOption] let peerRevealOptions: [ItemListRevealOption]
if item.editable && item.enabled { if item.editable && item.enabled {
@ -226,16 +223,18 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
} }
appAttributedString = NSAttributedString(string: appString, font: textFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor) 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 { 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 { } else {
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
let dateText = stringForRelativeTimestamp(strings: item.presentationData.strings, relativeTimestamp: item.session.activityDate, relativeTo: timestamp, dateTimeFormat: item.dateTimeFormat) label = stringForRelativeActivityTimestamp(strings: item.presentationData.strings, dateTimeFormat: item.dateTimeFormat, relativeTimestamp: item.session.activityDate, relativeTo: timestamp)
labelAttributedString = NSAttributedString(string: dateText, font: textFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
} }
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)? var editableControlSizeAndApply: (CGFloat, (CGFloat) -> ItemListEditableControlNode)?
@ -248,8 +247,7 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
editingOffset = 0.0 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 - 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 - labelLayout.size.width - 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 (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())) 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 _ = titleApply()
let _ = appApply() let _ = appApply()
let _ = locationApply() 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.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.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.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.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)) 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 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.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.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)) transition.updateFrame(node: self.locationNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: self.locationNode.frame.minY), size: self.locationNode.bounds.size))

View File

@ -31,8 +31,7 @@ struct ItemListWebsiteItemEditing: Equatable {
final class ItemListWebsiteItem: ListViewItem, ItemListItem { final class ItemListWebsiteItem: ListViewItem, ItemListItem {
let context: AccountContext let context: AccountContext
let theme: PresentationTheme let presentationData: ItemListPresentationData
let strings: PresentationStrings
let dateTimeFormat: PresentationDateTimeFormat let dateTimeFormat: PresentationDateTimeFormat
let nameDisplayOrder: PresentationPersonNameOrder let nameDisplayOrder: PresentationPersonNameOrder
let website: WebAuthorization let website: WebAuthorization
@ -44,10 +43,9 @@ final class ItemListWebsiteItem: ListViewItem, ItemListItem {
let setSessionIdWithRevealedOptions: (Int64?, Int64?) -> Void let setSessionIdWithRevealedOptions: (Int64?, Int64?) -> Void
let removeSession: (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.context = context
self.theme = theme self.presentationData = presentationData
self.strings = strings
self.dateTimeFormat = dateTimeFormat self.dateTimeFormat = dateTimeFormat
self.nameDisplayOrder = nameDisplayOrder self.nameDisplayOrder = nameDisplayOrder
self.website = website self.website = website
@ -99,9 +97,7 @@ final class ItemListWebsiteItem: ListViewItem, ItemListItem {
} }
} }
private let avatarFont = avatarPlaceholderFont(size: 9.0) private let avatarFont = avatarPlaceholderFont(size: 11.0)
private let titleFont = Font.medium(15.0)
private let textFont = Font.regular(13.0)
class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode { class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode {
private let backgroundNode: ASDisplayNode private let backgroundNode: ASDisplayNode
@ -134,6 +130,8 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode {
self.maskNode = ASImageNode() self.maskNode = ASImageNode()
self.avatarNode = AvatarNode(font: avatarFont) self.avatarNode = AvatarNode(font: avatarFont)
self.avatarNode.cornerRadius = 7.0
self.avatarNode.clipsToBounds = true
self.titleNode = TextNode() self.titleNode = TextNode()
self.titleNode.isUserInteractionEnabled = false self.titleNode.isUserInteractionEnabled = false
@ -171,7 +169,6 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode {
let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let makeAppLayout = TextNode.asyncLayout(self.appNode) let makeAppLayout = TextNode.asyncLayout(self.appNode)
let makeLocationLayout = TextNode.asyncLayout(self.locationNode) let makeLocationLayout = TextNode.asyncLayout(self.locationNode)
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
let editableControlLayout = ItemListEditableControlNode.asyncLayout(self.editableControlNode) let editableControlLayout = ItemListEditableControlNode.asyncLayout(self.editableControlNode)
var currentDisabledOverlayNode = self.disabledOverlayNode var currentDisabledOverlayNode = self.disabledOverlayNode
@ -181,21 +178,23 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode {
return { item, params, neighbors in return { item, params, neighbors in
var updatedTheme: PresentationTheme? var updatedTheme: PresentationTheme?
if currentItem?.theme !== item.theme { let titleFont = Font.medium(floor(item.presentationData.fontSize.itemListBaseFontSize * 16.0 / 17.0))
updatedTheme = item.theme 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 titleAttributedString: NSAttributedString?
var appAttributedString: NSAttributedString? var appAttributedString: NSAttributedString?
var locationAttributedString: 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 let rightInset: CGFloat = params.rightInset
if let user = item.peer as? TelegramUser { 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 = "" var appString = ""
@ -217,28 +216,27 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode {
appString += item.website.platform appString += item.website.platform
} }
appAttributedString = NSAttributedString(string: appString, font: textFont, textColor: item.theme.list.itemPrimaryTextColor) appAttributedString = NSAttributedString(string: appString, font: textFont, textColor: item.presentationData.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)
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)? var editableControlSizeAndApply: (CGFloat, (CGFloat) -> ItemListEditableControlNode)?
let editingOffset: CGFloat let editingOffset: CGFloat
if item.editing { if item.editing {
let sizeAndApply = editableControlLayout(item.theme, false) let sizeAndApply = editableControlLayout(item.presentationData.theme, false)
editableControlSizeAndApply = sizeAndApply editableControlSizeAndApply = sizeAndApply
editingOffset = sizeAndApply.0 editingOffset = sizeAndApply.0
} else { } else {
editingOffset = 0.0 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 - 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 - labelLayout.size.width - 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 (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())) 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) strongSelf.layoutParams = (item, params, neighbors)
if let _ = updatedTheme { if let _ = updatedTheme {
strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor strongSelf.topStripeNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor strongSelf.bottomStripeNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor
strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor strongSelf.backgroundNode.backgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor
strongSelf.highlightedBackgroundNode.backgroundColor = item.theme.list.itemHighlightedBackgroundColor strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor
} }
if let peer = item.peer { 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 let revealOffset = strongSelf.revealOffset
@ -329,7 +327,6 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode {
}) })
} }
let _ = labelApply()
let _ = titleApply() let _ = titleApply()
let _ = appApply() let _ = appApply()
let _ = locationApply() let _ = locationApply()
@ -370,7 +367,7 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode {
strongSelf.bottomStripeNode.isHidden = hasCorners 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.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.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.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.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.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.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.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)) 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 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.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.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)) transition.updateFrame(node: self.appNode, frame: CGRect(origin: CGPoint(x: leftInset + self.revealOffset + editingOffset, y: self.appNode.frame.minY), size: self.appNode.bounds.size))

View File

@ -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: { return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.blockDestructiveIcon(theme), title: text, sectionId: self.section, height: .generic, color: .destructive, editing: false, action: {
arguments.terminateAllWebSessions() arguments.terminateAllWebSessions()
}) })
case let .currentAddDevice(_, text): case let .currentAddDevice(theme, text):
return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.addDeviceIcon(theme), title: text, sectionId: self.section, height: .generic, color: .accent, editing: false, action: {
arguments.addDevice() arguments.addDevice()
}) })
case let .currentSessionInfo(_, text): case let .currentSessionInfo(_, text):
@ -303,8 +303,8 @@ private enum RecentSessionsEntry: ItemListNodeEntry {
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .otherSessionsHeader(_, text): case let .otherSessionsHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .addDevice(_, text): case let .addDevice(theme, text):
return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.addDeviceIcon(theme), title: text, sectionId: self.section, height: .generic, color: .accent, editing: false, action: {
arguments.addDevice() arguments.addDevice()
}) })
case let .session(_, _, _, dateTimeFormat, session, enabled, editing, revealed): case let .session(_, _, _, dateTimeFormat, session, enabled, editing, revealed):
@ -313,8 +313,8 @@ private enum RecentSessionsEntry: ItemListNodeEntry {
}, removeSession: { id in }, removeSession: { id in
arguments.removeSession(id) arguments.removeSession(id)
}) })
case let .website(_, theme, strings, dateTimeFormat, nameDisplayOrder, website, peer, enabled, editing, revealed): case let .website(_, _, _, 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 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) arguments.setSessionIdWithRevealedOptions(previousId, id)
}, removeSession: { id in }, removeSession: { id in
arguments.removeWebSession(id) arguments.removeWebSession(id)

View File

@ -126,7 +126,7 @@ class BubbleSettingsRadiusItemNode: ListViewItemNode, ItemListItemNode {
sliderView.enablePanHandling = true sliderView.enablePanHandling = true
sliderView.trackCornerRadius = 2.0 sliderView.trackCornerRadius = 2.0
sliderView.lineSize = 4.0 sliderView.lineSize = 4.0
sliderView.dotSize = 5.0 sliderView.dotSize = 8.0
sliderView.minimumValue = 0.0 sliderView.minimumValue = 0.0
sliderView.maximumValue = 4.0 sliderView.maximumValue = 4.0
sliderView.startValue = 0.0 sliderView.startValue = 0.0

View File

@ -123,8 +123,8 @@ class ThemeSettingsFontSizeItemNode: ListViewItemNode, ItemListItemNode {
sliderView.enablePanHandling = true sliderView.enablePanHandling = true
sliderView.enablePanHandling = true sliderView.enablePanHandling = true
sliderView.trackCornerRadius = 1.0 sliderView.trackCornerRadius = 1.0
sliderView.lineSize = 2.0 sliderView.lineSize = 4.0
sliderView.dotSize = 5.0 sliderView.dotSize = 8.0
sliderView.minimumValue = 0.0 sliderView.minimumValue = 0.0
sliderView.maximumValue = 6.0 sliderView.maximumValue = 6.0
sliderView.startValue = 0.0 sliderView.startValue = 0.0

View File

@ -62,6 +62,7 @@ public enum PresentationResourceKey: Int32 {
case itemListKnob case itemListKnob
case itemListBlockAccentIcon case itemListBlockAccentIcon
case itemListBlockDestructiveIcon case itemListBlockDestructiveIcon
case itemListAddDeviceIcon
case itemListVoiceCallIcon case itemListVoiceCallIcon
case itemListVideoCallIcon case itemListVideoCallIcon

View File

@ -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? { public static func cornersImage(_ theme: PresentationTheme, top: Bool, bottom: Bool) -> UIImage? {
if !top && !bottom { if !top && !bottom {
return nil return nil

View File

@ -23,19 +23,19 @@ private func renderIcon(name: String) -> UIImage? {
} }
public struct PresentationResourcesSettings { public struct PresentationResourcesSettings {
public static let editProfile = renderIcon(name: "Settings/MenuIcons/EditProfile") public static let editProfile = renderIcon(name: "Settings/Menu/EditProfile")
public static let proxy = renderIcon(name: "Settings/MenuIcons/Proxy") public static let proxy = renderIcon(name: "Settings/Menu/Proxy")
public static let savedMessages = renderIcon(name: "Settings/MenuIcons/SavedMessages") public static let savedMessages = renderIcon(name: "Settings/Menu/SavedMessages")
public static let recentCalls = renderIcon(name: "Settings/MenuIcons/RecentCalls") public static let recentCalls = renderIcon(name: "Settings/Menu/RecentCalls")
public static let devices = renderIcon(name: "Settings/MenuIcons/Sessions") public static let devices = renderIcon(name: "Settings/Menu/Sessions")
public static let chatFolders = renderIcon(name: "Settings/MenuIcons/ChatListFilters") public static let chatFolders = renderIcon(name: "Settings/Menu/ChatListFilters")
public static let stickers = renderIcon(name: "Settings/MenuIcons/Stickers") public static let stickers = renderIcon(name: "Settings/Menu/Stickers")
public static let notifications = renderIcon(name: "Settings/MenuIcons/Notifications") public static let notifications = renderIcon(name: "Settings/Menu/Notifications")
public static let security = renderIcon(name: "Settings/MenuIcons/Security") public static let security = renderIcon(name: "Settings/Menu/Security")
public static let dataAndStorage = renderIcon(name: "Settings/MenuIcons/DataAndStorage") public static let dataAndStorage = renderIcon(name: "Settings/Menu/DataAndStorage")
public static let appearance = renderIcon(name: "Settings/MenuIcons/Appearance") public static let appearance = renderIcon(name: "Settings/Menu/Appearance")
public static let language = renderIcon(name: "Settings/MenuIcons/Language") 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 public static let wallet = generateImage(CGSize(width: 29.0, height: 29.0), contextGenerator: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size) let bounds = CGRect(origin: CGPoint(), size: size)
@ -44,24 +44,24 @@ public struct PresentationResourcesSettings {
context.setFillColor(UIColor.white.cgColor) context.setFillColor(UIColor.white.cgColor)
context.fill(bounds.insetBy(dx: 5.0, dy: 5.0)) 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) context.draw(image, in: bounds)
} }
drawBorder(context: context, rect: bounds) drawBorder(context: context, rect: bounds)
}) })
public static let passport = renderIcon(name: "Settings/MenuIcons/Passport") public static let passport = renderIcon(name: "Settings/Menu/Passport")
public static let watch = renderIcon(name: "Settings/MenuIcons/Watch") public static let watch = renderIcon(name: "Settings/Menu/Watch")
public static let support = renderIcon(name: "Settings/MenuIcons/Support") public static let support = renderIcon(name: "Settings/Menu/Support")
public static let faq = renderIcon(name: "Settings/MenuIcons/Faq") public static let faq = renderIcon(name: "Settings/Menu/Faq")
public static let tips = renderIcon(name: "Settings/MenuIcons/Tips") public static let tips = renderIcon(name: "Settings/Menu/Tips")
public static let addAccount = renderIcon(name: "Settings/MenuIcons/AddAccount") public static let addAccount = renderIcon(name: "Settings/Menu/AddAccount")
public static let setPasscode = renderIcon(name: "Settings/MenuIcons/SetPasscode") public static let setPasscode = renderIcon(name: "Settings/Menu/SetPasscode")
public static let clearCache = renderIcon(name: "Settings/MenuIcons/ClearCache") public static let clearCache = renderIcon(name: "Settings/Menu/ClearCache")
public static let changePhoneNumber = renderIcon(name: "Settings/MenuIcons/ChangePhoneNumber") 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")
} }

View File

@ -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) { public func stringAndActivityForUserPresence(strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, presence: EnginePeer.Presence, relativeTo timestamp: Int32, expanded: Bool = false) -> (String, Bool) {
switch presence.status { switch presence.status {
case let .present(statusTimestamp): case let .present(statusTimestamp):

View File

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

View 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

View File

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

View 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

View File

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

View 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

View File

@ -0,0 +1,9 @@
{
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"provides-namespace" : true
}
}

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