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.TypeVideo" = "Videos (%@)";
"ChatSettings.AutoDownloadSettings.TypeMedia" = "Media (%@)";
"ChatSettings.AutoDownloadSettings.TypeFile" = "Files (%@)";
"ChatSettings.AutoDownloadSettings.OffForAll" = "Disabled";
"ChatSettings.AutoDownloadSettings.Delimeter" = ", ";
@ -6938,3 +6939,21 @@ Sorry for the inconvenience.";
"Map.ETADays_3_10" = "%@ days";
"Map.ETADays_many" = "%@ days";
"Map.ETADays_any" = "%@ days";
"ChatSettings.UseLessDataForCalls" = "Use Less Data for Calls";
"Time.JustNow" = "just now";
"Time.MinutesAgo_0" = "%@ minutes ago"; //three to ten
"Time.MinutesAgo_1" = "%@ minute ago"; //one
"Time.MinutesAgo_2" = "%@ minutes ago"; //two
"Time.MinutesAgo_3_10" = "%@ minutes ago"; //three to ten
"Time.MinutesAgo_many" = "%@ minutes ago"; // more than ten
"Time.MinutesAgo_any" = "%@ minutes ago"; // more than ten
"Time.HoursAgo_0" = "%@ hours ago";
"Time.HoursAgo_1" = "%@ hour ago";
"Time.HoursAgo_2" = "%@ hours ago";
"Time.HoursAgo_3_10" = "%@ hours ago";
"Time.HoursAgo_any" = "%@ hours ago";
"Time.HoursAgo_many" = "%@ hours ago";
"Time.HoursAgo_0" = "%@ hours ago";
"Time.AtDate" = "last seen %@";

View File

@ -343,7 +343,7 @@ public final class AvatarNode: ASDisplayNode {
let parameters: AvatarNodeParameters
if let peer = peer, let signal = peerAvatarImage(account: context.account, peerReference: PeerReference(peer._asPeer()), authorOfMessage: authorOfMessage, representation: representation, displayDimensions: displayDimensions, emptyColor: emptyColor, synchronousLoad: synchronousLoad, provideUnrounded: storeUnrounded) {
if let peer = peer, let signal = peerAvatarImage(account: context.account, peerReference: PeerReference(peer._asPeer()), authorOfMessage: authorOfMessage, representation: representation, displayDimensions: displayDimensions, round: clipStyle == .round, emptyColor: emptyColor, synchronousLoad: synchronousLoad, provideUnrounded: storeUnrounded) {
self.contents = nil
self.displaySuspended = true
self.imageReady.set(self.imageNode.contentReady)

View File

@ -188,6 +188,7 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode
private let highlightedBackgroundNode: ASDisplayNode
private let maskNode: ASImageNode
private let avatarNode: AvatarNode
private let titleNode: TextNode
@ -206,6 +207,8 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isLayerBacked = true
self.maskNode = ASImageNode()
self.topStripeNode = ASDisplayNode()
self.topStripeNode.isLayerBacked = true
@ -523,7 +526,9 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
} else if last && strongSelf.bottomStripeNode.supernode != nil {
strongSelf.bottomStripeNode.removeFromSupernode()
}
if strongSelf.maskNode.supernode != nil {
strongSelf.maskNode.removeFromSupernode()
}
transition.updateFrameAdditive(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - leftInset, height: separatorHeight)))
case .blocks:
if strongSelf.backgroundNode.supernode == nil {
@ -535,11 +540,18 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
if strongSelf.bottomStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
}
if strongSelf.maskNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
}
let hasCorners = itemListHasRoundedBlockLayout(params)
var hasTopCorners = false
var hasBottomCorners = false
switch neighbors.top {
case .sameSection(false):
strongSelf.topStripeNode.isHidden = true
default:
strongSelf.topStripeNode.isHidden = false
hasTopCorners = true
strongSelf.topStripeNode.isHidden = hasCorners
}
let bottomStripeInset: CGFloat
switch neighbors.bottom {
@ -547,9 +559,14 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
bottomStripeInset = leftInset
default:
bottomStripeInset = 0.0
hasBottomCorners = true
strongSelf.bottomStripeNode.isHidden = hasCorners
}
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: nodeLayout.size.width, height: separatorHeight))
transition.updateFrameAdditive(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: nodeLayout.size.width - bottomStripeInset, height: separatorHeight)))
}

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?.beginCallImpl()
}

View File

@ -117,7 +117,7 @@ private func mappedInsertEntries(context: AccountContext, presentationData: Item
return entries.map { entry -> ListViewInsertItem in
switch entry.entry {
case let .displayTab(_, text, value):
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enabled: true, noCorners: true, sectionId: 0, style: .blocks, updated: { value in
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enabled: true, noCorners: false, sectionId: 0, style: .blocks, updated: { value in
nodeInteraction.updateShowCallsTab(value)
}), directionHint: entry.directionHint)
case let .displayTabInfo(_, text):
@ -136,7 +136,7 @@ private func mappedUpdateEntries(context: AccountContext, presentationData: Item
return entries.map { entry -> ListViewUpdateItem in
switch entry.entry {
case let .displayTab(_, text, value):
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enabled: true, noCorners: true, sectionId: 0, style: .blocks, updated: { value in
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enabled: true, noCorners: false, sectionId: 0, style: .blocks, updated: { value in
nodeInteraction.updateShowCallsTab(value)
}), directionHint: entry.directionHint)
case let .displayTabInfo(_, text):
@ -177,6 +177,8 @@ final class CallListControllerNode: ASDisplayNode {
return _ready.get()
}
weak var navigationBar: NavigationBar?
var peerSelected: ((EnginePeer.Id) -> Void)?
var activateSearch: (() -> Void)?
var deletePeerChat: ((EnginePeer.Id) -> Void)?
@ -215,6 +217,8 @@ final class CallListControllerNode: ASDisplayNode {
private let openGroupCallDisposable = MetaDisposable()
private var previousContentOffset: ListViewVisibleContentOffset?
init(controller: CallListController, context: AccountContext, mode: CallListControllerMode, presentationData: PresentationData, call: @escaping (EnginePeer.Id, Bool) -> Void, joinGroupCall: @escaping (EnginePeer.Id, EngineGroupCallDescription) -> Void, openInfo: @escaping (EnginePeer.Id, [EngineMessage]) -> Void, emptyStateUpdated: @escaping (Bool) -> Void) {
self.controller = controller
self.context = context
@ -272,7 +276,7 @@ final class CallListControllerNode: ASDisplayNode {
self.addSubnode(self.emptyButtonTextNode)
self.addSubnode(self.emptyButtonIconNode)
self.addSubnode(self.emptyButtonNode)
switch self.mode {
case .tab:
self.backgroundColor = presentationData.theme.chatList.backgroundColor
@ -607,6 +611,39 @@ final class CallListControllerNode: ASDisplayNode {
strongSelf.updateEmptyPlaceholder(theme: state.presentationData.theme, strings: state.presentationData.strings, type: type, isHidden: !isEmpty)
}
}))
if case .navigation = mode {
self.listNode.itemNodeHitTest = { [weak self] point in
if let strongSelf = self {
return point.x > strongSelf.leftOverlayNode.frame.maxX && point.x < strongSelf.rightOverlayNode.frame.minX
} else {
return true
}
}
self.listNode.visibleContentOffsetChanged = { [weak self] offset in
if let strongSelf = self {
var previousContentOffsetValue: CGFloat?
if let previousContentOffset = strongSelf.previousContentOffset, case let .known(value) = previousContentOffset {
previousContentOffsetValue = value
}
switch offset {
case let .known(value):
let transition: ContainedViewLayoutTransition
if let previousContentOffsetValue = previousContentOffsetValue, value <= 0.0, previousContentOffsetValue > 30.0 {
transition = .animated(duration: 0.2, curve: .easeInOut)
} else {
transition = .immediate
}
strongSelf.navigationBar?.updateBackgroundAlpha(min(30.0, value) / 30.0, transition: transition)
case .unknown, .none:
strongSelf.navigationBar?.updateBackgroundAlpha(1.0, transition: .immediate)
}
strongSelf.previousContentOffset = offset
}
}
}
}
deinit {
@ -838,8 +875,25 @@ final class CallListControllerNode: ASDisplayNode {
var insets = layout.insets(options: [.input])
insets.top += max(navigationBarHeight, layout.insets(options: [.statusBar]).top)
insets.left += layout.safeInsets.left
insets.right += layout.safeInsets.right
let inset = max(16.0, floor((layout.size.width - 674.0) / 2.0))
if case .navigation = self.mode {
insets.left += inset
insets.right += inset
self.leftOverlayNode.frame = CGRect(x: 0.0, y: 0.0, width: insets.left, height: layout.size.height)
self.rightOverlayNode.frame = CGRect(x: layout.size.width - insets.right, y: 0.0, width: insets.right, height: layout.size.height)
if self.leftOverlayNode.supernode == nil {
self.insertSubnode(self.leftOverlayNode, aboveSubnode: self.listNode)
}
if self.rightOverlayNode.supernode == nil {
self.insertSubnode(self.rightOverlayNode, aboveSubnode: self.listNode)
}
} else {
insets.left += layout.safeInsets.left
insets.right += layout.safeInsets.right
}
self.listNode.bounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)
self.listNode.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0)

View File

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

View File

@ -357,13 +357,13 @@ open class ItemListControllerNode: ASDisplayNode {
case let .known(value):
let transition: ContainedViewLayoutTransition
if let previousContentOffsetValue = previousContentOffsetValue, value <= 0.0, previousContentOffsetValue > 30.0 {
transition = .animated(duration: 0.3, curve: .linear)
transition = .animated(duration: 0.2, curve: .easeInOut)
} else {
transition = .immediate
}
strongSelf.navigationBar.updateBackgroundAlpha(min(30.0, value) / 30.0, transition: transition)
case .unknown, .none:
strongSelf.navigationBar.updateBackgroundAlpha(0.0, transition: .immediate)
strongSelf.navigationBar.updateBackgroundAlpha(1.0, transition: .immediate)
}
strongSelf.previousContentOffset = offset

View File

@ -157,15 +157,15 @@ private enum AutodownloadMediaCategoryEntry: ItemListNodeEntry {
case let .typesHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .photos(_, text, value, enabled):
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/MenuIcons/Photos")?.precomposed(), title: text, enabled: enabled, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: {
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Photos")?.precomposed(), title: text, enabled: enabled, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: {
arguments.customize(.photo)
})
case let .videos(_, text, value, enabled):
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/MenuIcons/Videos")?.precomposed(), title: text, enabled: enabled, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: {
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Videos")?.precomposed(), title: text, enabled: enabled, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: {
arguments.customize(.video)
})
case let .files(_, text, value, enabled):
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/MenuIcons/Files")?.precomposed(), title: text, enabled: enabled, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: {
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Files")?.precomposed(), title: text, enabled: enabled, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: {
arguments.customize(.file)
})
case let .voiceMessagesInfo(_, text):

View File

@ -18,7 +18,7 @@ private final class DataAndStorageControllerArguments {
let openProxy: () -> Void
let openAutomaticDownloadConnectionType: (AutomaticDownloadConnectionType) -> Void
let resetAutomaticDownload: () -> Void
let openVoiceUseLessData: () -> Void
let toggleVoiceUseLessData: (Bool) -> Void
let openSaveIncomingPhotos: () -> Void
let toggleSaveEditedPhotos: (Bool) -> Void
let toggleAutoplayGifs: (Bool) -> Void
@ -28,13 +28,13 @@ private final class DataAndStorageControllerArguments {
let openIntents: () -> Void
let toggleEnableSensitiveContent: (Bool) -> Void
init(openStorageUsage: @escaping () -> Void, openNetworkUsage: @escaping () -> Void, openProxy: @escaping () -> Void, openAutomaticDownloadConnectionType: @escaping (AutomaticDownloadConnectionType) -> Void, resetAutomaticDownload: @escaping () -> Void, openVoiceUseLessData: @escaping () -> Void, openSaveIncomingPhotos: @escaping () -> Void, toggleSaveEditedPhotos: @escaping (Bool) -> Void, toggleAutoplayGifs: @escaping (Bool) -> Void, toggleAutoplayVideos: @escaping (Bool) -> Void, toggleDownloadInBackground: @escaping (Bool) -> Void, openBrowserSelection: @escaping () -> Void, openIntents: @escaping () -> Void, toggleEnableSensitiveContent: @escaping (Bool) -> Void) {
init(openStorageUsage: @escaping () -> Void, openNetworkUsage: @escaping () -> Void, openProxy: @escaping () -> Void, openAutomaticDownloadConnectionType: @escaping (AutomaticDownloadConnectionType) -> Void, resetAutomaticDownload: @escaping () -> Void, toggleVoiceUseLessData: @escaping (Bool) -> Void, openSaveIncomingPhotos: @escaping () -> Void, toggleSaveEditedPhotos: @escaping (Bool) -> Void, toggleAutoplayGifs: @escaping (Bool) -> Void, toggleAutoplayVideos: @escaping (Bool) -> Void, toggleDownloadInBackground: @escaping (Bool) -> Void, openBrowserSelection: @escaping () -> Void, openIntents: @escaping () -> Void, toggleEnableSensitiveContent: @escaping (Bool) -> Void) {
self.openStorageUsage = openStorageUsage
self.openNetworkUsage = openNetworkUsage
self.openProxy = openProxy
self.openAutomaticDownloadConnectionType = openAutomaticDownloadConnectionType
self.resetAutomaticDownload = resetAutomaticDownload
self.openVoiceUseLessData = openVoiceUseLessData
self.toggleVoiceUseLessData = toggleVoiceUseLessData
self.openSaveIncomingPhotos = openSaveIncomingPhotos
self.toggleSaveEditedPhotos = toggleSaveEditedPhotos
self.toggleAutoplayGifs = toggleAutoplayGifs
@ -49,6 +49,7 @@ private final class DataAndStorageControllerArguments {
private enum DataAndStorageSection: Int32 {
case usage
case autoDownload
case backgroundDownload
case autoPlay
case voiceCalls
case other
@ -79,18 +80,20 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
case automaticDownloadCellular(PresentationTheme, String, String)
case automaticDownloadWifi(PresentationTheme, String, String)
case automaticDownloadReset(PresentationTheme, String, Bool)
case downloadInBackground(PresentationTheme, String, Bool)
case downloadInBackgroundInfo(PresentationTheme, String)
case autoplayHeader(PresentationTheme, String)
case autoplayGifs(PresentationTheme, String, Bool)
case autoplayVideos(PresentationTheme, String, Bool)
case voiceCallsHeader(PresentationTheme, String)
case useLessVoiceData(PresentationTheme, String, String)
case useLessVoiceData(PresentationTheme, String, Bool)
case useLessVoiceDataInfo(PresentationTheme, String)
case otherHeader(PresentationTheme, String)
case shareSheet(PresentationTheme, String)
case saveIncomingPhotos(PresentationTheme, String)
case saveEditedPhotos(PresentationTheme, String, Bool)
case openLinksIn(PresentationTheme, String, String)
case downloadInBackground(PresentationTheme, String, Bool)
case downloadInBackgroundInfo(PresentationTheme, String)
case connectionHeader(PresentationTheme, String)
case connectionProxy(PresentationTheme, String, String)
case enableSensitiveContent(String, Bool)
@ -101,11 +104,13 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
return DataAndStorageSection.usage.rawValue
case .automaticDownloadHeader, .automaticDownloadCellular, .automaticDownloadWifi, .automaticDownloadReset:
return DataAndStorageSection.autoDownload.rawValue
case .downloadInBackground, .downloadInBackgroundInfo:
return DataAndStorageSection.backgroundDownload.rawValue
case .useLessVoiceData, .useLessVoiceDataInfo:
return DataAndStorageSection.voiceCalls.rawValue
case .autoplayHeader, .autoplayGifs, .autoplayVideos:
return DataAndStorageSection.autoPlay.rawValue
case .voiceCallsHeader, .useLessVoiceData:
return DataAndStorageSection.voiceCalls.rawValue
case .otherHeader, .shareSheet, .saveIncomingPhotos, .saveEditedPhotos, .openLinksIn, .downloadInBackground, .downloadInBackgroundInfo:
case .otherHeader, .shareSheet, .saveIncomingPhotos, .saveEditedPhotos, .openLinksIn:
return DataAndStorageSection.other.rawValue
case .connectionHeader, .connectionProxy:
return DataAndStorageSection.connection.rawValue
@ -128,36 +133,36 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
return 4
case .automaticDownloadReset:
return 5
case .autoplayHeader:
return 6
case .autoplayGifs:
return 7
case .autoplayVideos:
return 8
case .voiceCallsHeader:
return 9
case .useLessVoiceData:
return 10
case .otherHeader:
return 11
case .shareSheet:
return 12
case .saveIncomingPhotos:
return 14
case .saveEditedPhotos:
return 15
case .openLinksIn:
return 16
case .downloadInBackground:
return 17
return 6
case .downloadInBackgroundInfo:
return 18
return 7
case .useLessVoiceData:
return 8
case .useLessVoiceDataInfo:
return 9
case .autoplayHeader:
return 10
case .autoplayGifs:
return 11
case .autoplayVideos:
return 12
case .otherHeader:
return 13
case .shareSheet:
return 14
case .saveIncomingPhotos:
return 15
case .saveEditedPhotos:
return 16
case .openLinksIn:
return 17
case .connectionHeader:
return 19
return 18
case .connectionProxy:
return 20
return 19
case .enableSensitiveContent:
return 21
return 20
}
}
@ -217,14 +222,14 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
} else {
return false
}
case let .voiceCallsHeader(lhsTheme, lhsText):
if case let .voiceCallsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
case let .useLessVoiceData(lhsTheme, lhsText, lhsValue):
if case let .useLessVoiceData(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .useLessVoiceData(lhsTheme, lhsText, lhsValue):
if case let .useLessVoiceData(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
case let .useLessVoiceDataInfo(lhsTheme, lhsText):
if case let .useLessVoiceDataInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
@ -300,21 +305,21 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
let arguments = arguments as! DataAndStorageControllerArguments
switch self {
case let .storageUsage(_, text):
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/MenuIcons/Storage")?.precomposed(), title: text, label: "", sectionId: self.section, style: .blocks, action: {
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Storage")?.precomposed(), title: text, label: "", sectionId: self.section, style: .blocks, action: {
arguments.openStorageUsage()
})
case let .networkUsage(_, text):
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/MenuIcons/Network")?.precomposed(), title: text, label: "", sectionId: self.section, style: .blocks, action: {
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Network")?.precomposed(), title: text, label: "", sectionId: self.section, style: .blocks, action: {
arguments.openNetworkUsage()
})
case let .automaticDownloadHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .automaticDownloadCellular(_, text, value):
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/MenuIcons/Cellular")?.precomposed(), title: text, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: {
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Cellular")?.precomposed(), title: text, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: {
arguments.openAutomaticDownloadConnectionType(.cellular)
})
case let .automaticDownloadWifi(_, text, value):
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/MenuIcons/WiFi")?.precomposed(), title: text, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: {
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/WiFi")?.precomposed(), title: text, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: {
arguments.openAutomaticDownloadConnectionType(.wifi)
})
case let .automaticDownloadReset(_, text, enabled):
@ -333,12 +338,12 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.toggleAutoplayVideos(value)
}, tag: DataAndStorageEntryTag.autoplayVideos)
case let .voiceCallsHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .useLessVoiceData(_, text, value):
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: {
arguments.openVoiceUseLessData()
})
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.toggleVoiceUseLessData(value)
}, tag: DataAndStorageEntryTag.autoplayVideos)
case let .useLessVoiceDataInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .otherHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .shareSheet(_, text):
@ -418,11 +423,15 @@ private func stringForUseLessDataSetting(_ dataSaving: VoiceCallDataSaving, stri
private func stringForAutoDownloadTypes(strings: PresentationStrings, decimalSeparator: String, photo: Bool, videoSize: Int32?, fileSize: Int32?) -> String {
var types: [String] = []
if photo {
if photo && videoSize == nil {
types.append(strings.ChatSettings_AutoDownloadSettings_TypePhoto)
}
if let videoSize = videoSize {
types.append(strings.ChatSettings_AutoDownloadSettings_TypeVideo(autodownloadDataSizeString(Int64(videoSize), decimalSeparator: decimalSeparator)).string)
if photo {
types.append(strings.ChatSettings_AutoDownloadSettings_TypeMedia(autodownloadDataSizeString(Int64(videoSize), decimalSeparator: decimalSeparator)).string)
} else {
types.append(strings.ChatSettings_AutoDownloadSettings_TypeVideo(autodownloadDataSizeString(Int64(videoSize), decimalSeparator: decimalSeparator)).string)
}
}
if let fileSize = fileSize {
types.append(strings.ChatSettings_AutoDownloadSettings_TypeFile(autodownloadDataSizeString(Int64(fileSize), decimalSeparator: decimalSeparator)).string)
@ -476,14 +485,17 @@ private func dataAndStorageControllerEntries(state: DataAndStorageControllerStat
let defaultSettings = MediaAutoDownloadSettings.defaultSettings
entries.append(.automaticDownloadReset(presentationData.theme, presentationData.strings.ChatSettings_AutoDownloadReset, data.automaticMediaDownloadSettings.cellular != defaultSettings.cellular || data.automaticMediaDownloadSettings.wifi != defaultSettings.wifi))
entries.append(.downloadInBackground(presentationData.theme, presentationData.strings.ChatSettings_DownloadInBackground, data.automaticMediaDownloadSettings.downloadInBackground))
entries.append(.downloadInBackgroundInfo(presentationData.theme, presentationData.strings.ChatSettings_DownloadInBackgroundInfo))
let dataSaving = effectiveDataSaving(for: data.voiceCallSettings, autodownloadSettings: data.autodownloadSettings)
entries.append(.useLessVoiceData(presentationData.theme, presentationData.strings.ChatSettings_UseLessDataForCalls, dataSaving != .never))
entries.append(.useLessVoiceDataInfo(presentationData.theme, presentationData.strings.CallSettings_UseLessDataLongDescription))
entries.append(.autoplayHeader(presentationData.theme, presentationData.strings.ChatSettings_AutoPlayTitle))
entries.append(.autoplayGifs(presentationData.theme, presentationData.strings.ChatSettings_AutoPlayGifs, data.automaticMediaDownloadSettings.autoplayGifs))
entries.append(.autoplayVideos(presentationData.theme, presentationData.strings.ChatSettings_AutoPlayVideos, data.automaticMediaDownloadSettings.autoplayVideos))
entries.append(.voiceCallsHeader(presentationData.theme, presentationData.strings.Settings_CallSettings.uppercased()))
let dataSaving = effectiveDataSaving(for: data.voiceCallSettings, autodownloadSettings: data.autodownloadSettings)
entries.append(.useLessVoiceData(presentationData.theme, presentationData.strings.CallSettings_UseLessData, stringForUseLessDataSetting(dataSaving, strings: presentationData.strings)))
entries.append(.otherHeader(presentationData.theme, presentationData.strings.ChatSettings_Other))
if #available(iOSApplicationExtension 13.2, iOS 13.2, *) {
entries.append(.shareSheet(presentationData.theme, presentationData.strings.ChatSettings_IntentsSettings))
@ -491,9 +503,7 @@ private func dataAndStorageControllerEntries(state: DataAndStorageControllerStat
entries.append(.saveIncomingPhotos(presentationData.theme, presentationData.strings.Settings_SaveIncomingPhotos))
entries.append(.saveEditedPhotos(presentationData.theme, presentationData.strings.Settings_SaveEditedPhotos, data.generatedMediaStoreSettings.storeEditedPhotos))
entries.append(.openLinksIn(presentationData.theme, presentationData.strings.ChatSettings_OpenLinksIn, defaultWebBrowser))
entries.append(.downloadInBackground(presentationData.theme, presentationData.strings.ChatSettings_DownloadInBackground, data.automaticMediaDownloadSettings.downloadInBackground))
entries.append(.downloadInBackgroundInfo(presentationData.theme, presentationData.strings.ChatSettings_DownloadInBackgroundInfo))
let proxyValue: String
if let proxySettings = data.proxySettings, let activeServer = proxySettings.activeServer, proxySettings.enabled {
switch activeServer.connection {
@ -608,8 +618,12 @@ public func dataAndStorageController(context: AccountContext, focusOnItemTag: Da
})
])])
presentControllerImpl?(actionSheet, nil)
}, openVoiceUseLessData: {
pushControllerImpl?(voiceCallDataSavingController(context: context))
}, toggleVoiceUseLessData: { value in
let _ = updateVoiceCallSettingsSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
var current = current
current.dataSaving = value ? .always : .never
return current
}).start()
}, openSaveIncomingPhotos: {
pushControllerImpl?(saveIncomingMediaController(context: context))
}, toggleSaveEditedPhotos: { value in

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):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .blockedPeers(_, text, value):
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/MenuIcons/Blocked")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: {
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Blocked")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: {
arguments.openBlockedUsers()
})
case let .phoneNumberPrivacy(_, text, value):
@ -317,15 +317,15 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
arguments.openVoiceCallPrivacy()
})
case let .passcode(_, text, hasFaceId, value):
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: hasFaceId ? "Settings/MenuIcons/FaceId" : "Settings/MenuIcons/TouchId")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: {
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: hasFaceId ? "Settings/Menu/FaceId" : "Settings/Menu/TouchId")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: {
arguments.openPasscode()
})
case let .twoStepVerification(_, text, value, data):
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/MenuIcons/TwoStepAuth")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: {
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/TwoStepAuth")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: {
arguments.openTwoStepVerification(data)
})
case let .activeSessions(_, text, value):
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/MenuIcons/Websites")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: {
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Websites")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: {
arguments.openActiveSessions()
})
case let .autoArchiveHeader(text):

View File

@ -107,10 +107,10 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
private var disabledOverlayNode: ASDisplayNode?
private let maskNode: ASImageNode
let iconNode: ASImageNode
private let titleNode: TextNode
private let appNode: TextNode
private let locationNode: TextNode
private let labelNode: TextNode
private let activateArea: AccessibilityAreaNode
@ -130,6 +130,10 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
self.maskNode = ASImageNode()
self.iconNode = ASImageNode()
self.iconNode.cornerRadius = 7.0
self.iconNode.clipsToBounds = true
self.titleNode = TextNode()
self.titleNode.isUserInteractionEnabled = false
self.titleNode.contentMode = .left
@ -144,12 +148,7 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
self.locationNode.isUserInteractionEnabled = false
self.locationNode.contentMode = .left
self.locationNode.contentsScale = UIScreen.main.scale
self.labelNode = TextNode()
self.labelNode.isUserInteractionEnabled = false
self.labelNode.contentMode = .left
self.labelNode.contentsScale = UIScreen.main.scale
self.highlightedBackgroundNode = ASDisplayNode()
self.highlightedBackgroundNode.isLayerBacked = true
@ -157,10 +156,10 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
self.addSubnode(self.iconNode)
self.addSubnode(self.titleNode)
self.addSubnode(self.appNode)
self.addSubnode(self.locationNode)
self.addSubnode(self.labelNode)
self.addSubnode(self.activateArea)
}
@ -169,7 +168,6 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let makeAppLayout = TextNode.asyncLayout(self.appNode)
let makeLocationLayout = TextNode.asyncLayout(self.locationNode)
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
let editableControlLayout = ItemListEditableControlNode.asyncLayout(self.editableControlNode)
var currentDisabledOverlayNode = self.disabledOverlayNode
@ -179,8 +177,8 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
return { item, params, neighbors in
var updatedTheme: PresentationTheme?
let titleFont = Font.medium(floor(item.presentationData.fontSize.itemListBaseFontSize * 15.0 / 17.0))
let textFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 13.0 / 17.0))
let titleFont = Font.medium(floor(item.presentationData.fontSize.itemListBaseFontSize * 16.0 / 17.0))
let textFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0))
let verticalInset: CGFloat = 10.0
let titleSpacing: CGFloat = 1.0
@ -193,7 +191,6 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
var titleAttributedString: NSAttributedString?
var appAttributedString: NSAttributedString?
var locationAttributedString: NSAttributedString?
var labelAttributedString: NSAttributedString?
let peerRevealOptions: [ItemListRevealOption]
if item.editable && item.enabled {
@ -226,16 +223,18 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
}
appAttributedString = NSAttributedString(string: appString, font: textFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor)
locationAttributedString = NSAttributedString(string: "\(item.session.ip)\(item.session.country)", font: textFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
let label: String
if item.session.isCurrent {
labelAttributedString = NSAttributedString(string: item.presentationData.strings.Presence_online, font: textFont, textColor: item.presentationData.theme.list.itemAccentColor)
label = item.presentationData.strings.Presence_online
} else {
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
let dateText = stringForRelativeTimestamp(strings: item.presentationData.strings, relativeTimestamp: item.session.activityDate, relativeTo: timestamp, dateTimeFormat: item.dateTimeFormat)
labelAttributedString = NSAttributedString(string: dateText, font: textFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
label = stringForRelativeActivityTimestamp(strings: item.presentationData.strings, dateTimeFormat: item.dateTimeFormat, relativeTimestamp: item.session.activityDate, relativeTo: timestamp)
}
let leftInset: CGFloat = 15.0 + params.leftInset
locationAttributedString = NSAttributedString(string: "\(item.session.country)\(label)", font: textFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
let leftInset: CGFloat = 59.0 + params.leftInset
var editableControlSizeAndApply: (CGFloat, (CGFloat) -> ItemListEditableControlNode)?
@ -248,8 +247,7 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
editingOffset = 0.0
}
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: labelAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - editingOffset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 16.0 - editingOffset - rightInset - labelLayout.size.width - 5.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 16.0 - editingOffset - rightInset - 5.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (appLayout, appApply) = makeAppLayout(TextNodeLayoutArguments(attributedString: appAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - editingOffset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (locationLayout, locationApply) = makeLocationLayout(TextNodeLayoutArguments(attributedString: locationAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - editingOffset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
@ -364,7 +362,6 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
})
}
let _ = labelApply()
let _ = titleApply()
let _ = appApply()
let _ = locationApply()
@ -412,7 +409,7 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)))
transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)))
transition.updateFrame(node: strongSelf.labelNode, frame: CGRect(origin: CGPoint(x: revealOffset + params.width - labelLayout.size.width - 15.0 - rightInset, y: verticalInset), size: labelLayout.size))
transition.updateFrame(node: strongSelf.iconNode, frame: CGRect(origin: CGPoint(x: params.leftInset + revealOffset + editingOffset + 16.0, y: 12.0), size: CGSize(width: 30.0, height: 30.0)))
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: verticalInset), size: titleLayout.size))
transition.updateFrame(node: strongSelf.appNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: strongSelf.titleNode.frame.maxY + titleSpacing), size: appLayout.size))
transition.updateFrame(node: strongSelf.locationNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: strongSelf.appNode.frame.maxY + textSpacing), size: locationLayout.size))
@ -455,7 +452,7 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
editingOffset = 0.0
}
transition.updateFrame(node: self.labelNode, frame: CGRect(origin: CGPoint(x: revealOffset + params.width - params.rightInset - self.labelNode.bounds.size.width - 15.0, y: self.labelNode.frame.minY), size: self.labelNode.bounds.size))
transition.updateFrame(node: self.iconNode, frame: CGRect(origin: CGPoint(x: params.leftInset + self.revealOffset + editingOffset + 16.0, y: self.iconNode.frame.minY), size: self.iconNode.bounds.size))
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: self.titleNode.frame.minY), size: self.titleNode.bounds.size))
transition.updateFrame(node: self.appNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: self.appNode.frame.minY), size: self.appNode.bounds.size))
transition.updateFrame(node: self.locationNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: self.locationNode.frame.minY), size: self.locationNode.bounds.size))

View File

@ -31,8 +31,7 @@ struct ItemListWebsiteItemEditing: Equatable {
final class ItemListWebsiteItem: ListViewItem, ItemListItem {
let context: AccountContext
let theme: PresentationTheme
let strings: PresentationStrings
let presentationData: ItemListPresentationData
let dateTimeFormat: PresentationDateTimeFormat
let nameDisplayOrder: PresentationPersonNameOrder
let website: WebAuthorization
@ -44,10 +43,9 @@ final class ItemListWebsiteItem: ListViewItem, ItemListItem {
let setSessionIdWithRevealedOptions: (Int64?, Int64?) -> Void
let removeSession: (Int64) -> Void
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, website: WebAuthorization, peer: Peer?, enabled: Bool, editing: Bool, revealed: Bool, sectionId: ItemListSectionId, setSessionIdWithRevealedOptions: @escaping (Int64?, Int64?) -> Void, removeSession: @escaping (Int64) -> Void) {
init(context: AccountContext, presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, website: WebAuthorization, peer: Peer?, enabled: Bool, editing: Bool, revealed: Bool, sectionId: ItemListSectionId, setSessionIdWithRevealedOptions: @escaping (Int64?, Int64?) -> Void, removeSession: @escaping (Int64) -> Void) {
self.context = context
self.theme = theme
self.strings = strings
self.presentationData = presentationData
self.dateTimeFormat = dateTimeFormat
self.nameDisplayOrder = nameDisplayOrder
self.website = website
@ -99,9 +97,7 @@ final class ItemListWebsiteItem: ListViewItem, ItemListItem {
}
}
private let avatarFont = avatarPlaceholderFont(size: 9.0)
private let titleFont = Font.medium(15.0)
private let textFont = Font.regular(13.0)
private let avatarFont = avatarPlaceholderFont(size: 11.0)
class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode {
private let backgroundNode: ASDisplayNode
@ -134,6 +130,8 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode {
self.maskNode = ASImageNode()
self.avatarNode = AvatarNode(font: avatarFont)
self.avatarNode.cornerRadius = 7.0
self.avatarNode.clipsToBounds = true
self.titleNode = TextNode()
self.titleNode.isUserInteractionEnabled = false
@ -171,7 +169,6 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode {
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let makeAppLayout = TextNode.asyncLayout(self.appNode)
let makeLocationLayout = TextNode.asyncLayout(self.locationNode)
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
let editableControlLayout = ItemListEditableControlNode.asyncLayout(self.editableControlNode)
var currentDisabledOverlayNode = self.disabledOverlayNode
@ -181,21 +178,23 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode {
return { item, params, neighbors in
var updatedTheme: PresentationTheme?
if currentItem?.theme !== item.theme {
updatedTheme = item.theme
let titleFont = Font.medium(floor(item.presentationData.fontSize.itemListBaseFontSize * 16.0 / 17.0))
let textFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0))
if currentItem?.presentationData !== item.presentationData.theme {
updatedTheme = item.presentationData.theme
}
var titleAttributedString: NSAttributedString?
var appAttributedString: NSAttributedString?
var locationAttributedString: NSAttributedString?
var labelAttributedString: NSAttributedString?
let peerRevealOptions = [ItemListRevealOption(key: 0, title: item.strings.AuthSessions_LogOut, icon: .none, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)]
let peerRevealOptions = [ItemListRevealOption(key: 0, title: item.presentationData.strings.AuthSessions_LogOut, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)]
let rightInset: CGFloat = params.rightInset
if let user = item.peer as? TelegramUser {
titleAttributedString = NSAttributedString(string: EnginePeer(user).displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder), font: titleFont, textColor: item.theme.list.itemPrimaryTextColor)
titleAttributedString = NSAttributedString(string: EnginePeer(user).displayTitle(strings: item.presentationData.strings, displayOrder: item.nameDisplayOrder), font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor)
}
var appString = ""
@ -217,28 +216,27 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode {
appString += item.website.platform
}
appAttributedString = NSAttributedString(string: appString, font: textFont, textColor: item.theme.list.itemPrimaryTextColor)
locationAttributedString = NSAttributedString(string: "\(item.website.ip)\(item.website.region)", font: textFont, textColor: item.theme.list.itemSecondaryTextColor)
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
let dateText = stringForRelativeTimestamp(strings: item.strings, relativeTimestamp: item.website.dateActive, relativeTo: timestamp, dateTimeFormat: item.dateTimeFormat)
labelAttributedString = NSAttributedString(string: dateText, font: textFont, textColor: item.theme.list.itemSecondaryTextColor)
appAttributedString = NSAttributedString(string: appString, font: textFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor)
let leftInset: CGFloat = 15.0 + params.leftInset
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
let label = stringForRelativeActivityTimestamp(strings: item.presentationData.strings, dateTimeFormat: item.dateTimeFormat, relativeTimestamp: item.website.dateActive, relativeTo: timestamp)
locationAttributedString = NSAttributedString(string: "\(item.website.region)\(label)", font: textFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
let leftInset: CGFloat = 59.0 + params.leftInset
var editableControlSizeAndApply: (CGFloat, (CGFloat) -> ItemListEditableControlNode)?
let editingOffset: CGFloat
if item.editing {
let sizeAndApply = editableControlLayout(item.theme, false)
let sizeAndApply = editableControlLayout(item.presentationData.theme, false)
editableControlSizeAndApply = sizeAndApply
editingOffset = sizeAndApply.0
} else {
editingOffset = 0.0
}
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: labelAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - editingOffset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - editingOffset - rightInset - labelLayout.size.width - 5.0 - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - editingOffset - rightInset - 5.0 - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (appLayout, appApply) = makeAppLayout(TextNodeLayoutArguments(attributedString: appAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - editingOffset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (locationLayout, locationApply) = makeLocationLayout(TextNodeLayoutArguments(attributedString: locationAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - editingOffset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
@ -263,14 +261,14 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode {
strongSelf.layoutParams = (item, params, neighbors)
if let _ = updatedTheme {
strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor
strongSelf.highlightedBackgroundNode.backgroundColor = item.theme.list.itemHighlightedBackgroundColor
strongSelf.topStripeNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor
strongSelf.bottomStripeNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor
strongSelf.backgroundNode.backgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor
strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor
}
if let peer = item.peer {
strongSelf.avatarNode.setPeer(context: item.context, theme: item.theme, peer: EnginePeer(peer), authorOfMessage: nil, overrideImage: nil, emptyColor: nil, clipStyle: .none, synchronousLoad: false)
strongSelf.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: EnginePeer(peer), authorOfMessage: nil, overrideImage: nil, emptyColor: nil, clipStyle: .none, synchronousLoad: false)
}
let revealOffset = strongSelf.revealOffset
@ -329,7 +327,6 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode {
})
}
let _ = labelApply()
let _ = titleApply()
let _ = appApply()
let _ = locationApply()
@ -370,7 +367,7 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode {
strongSelf.bottomStripeNode.isHidden = hasCorners
}
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
@ -378,9 +375,9 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode {
transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)))
transition.updateFrame(node: strongSelf.avatarNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset + 1.0, y: 12.0), size: CGSize(width: 13.0, height: 13.0)))
transition.updateFrame(node: strongSelf.labelNode, frame: CGRect(origin: CGPoint(x: revealOffset + params.width - labelLayout.size.width - 15.0 - rightInset, y: 10.0), size: labelLayout.size))
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset + 20.0, y: 10.0), size: titleLayout.size))
transition.updateFrame(node: strongSelf.avatarNode, frame: CGRect(origin: CGPoint(x: params.leftInset + revealOffset + editingOffset + 16.0, y: 12.0), size: CGSize(width: 30.0, height: 30.0)))
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 10.0), size: titleLayout.size))
transition.updateFrame(node: strongSelf.appNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 30.0), size: appLayout.size))
transition.updateFrame(node: strongSelf.locationNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 50.0), size: locationLayout.size))
@ -422,7 +419,7 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode {
editingOffset = 0.0
}
transition.updateFrame(node: self.avatarNode, frame: CGRect(origin: CGPoint(x: leftInset + self.revealOffset + editingOffset + 1.0, y: self.avatarNode.frame.minY), size: self.avatarNode.bounds.size))
transition.updateFrame(node: self.avatarNode, frame: CGRect(origin: CGPoint(x: params.leftInset + self.revealOffset + editingOffset + 16.0, y: self.avatarNode.frame.minY), size: self.avatarNode.bounds.size))
transition.updateFrame(node: self.labelNode, frame: CGRect(origin: CGPoint(x: self.revealOffset + params.width - params.rightInset - self.labelNode.bounds.size.width - 15.0, y: self.labelNode.frame.minY), size: self.labelNode.bounds.size))
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + self.revealOffset + editingOffset + 20.0, y: self.titleNode.frame.minY), size: self.titleNode.bounds.size))
transition.updateFrame(node: self.appNode, frame: CGRect(origin: CGPoint(x: leftInset + self.revealOffset + editingOffset, y: self.appNode.frame.minY), size: self.appNode.bounds.size))

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

View File

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

View File

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

View File

@ -62,6 +62,7 @@ public enum PresentationResourceKey: Int32 {
case itemListKnob
case itemListBlockAccentIcon
case itemListBlockDestructiveIcon
case itemListAddDeviceIcon
case itemListVoiceCallIcon
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? {
if !top && !bottom {
return nil

View File

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

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) {
switch presence.status {
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