Various improvements

This commit is contained in:
Ilya Laktyushin 2024-04-19 11:15:18 +04:00
parent 7f7d0f1c74
commit 6e42f96c19
7 changed files with 286 additions and 40 deletions

View File

@ -158,7 +158,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
self.nextOptionButtonNode = HighlightableButtonNode() self.nextOptionButtonNode = HighlightableButtonNode()
self.nextOptionButtonNode.displaysAsynchronously = false self.nextOptionButtonNode.displaysAsynchronously = false
let (nextOptionText, nextOptionActive) = authorizationNextOptionText(currentType: .sms(length: 5), nextType: .call, timeout: 60, strings: self.strings, primaryColor: self.theme.list.itemPrimaryTextColor, accentColor: self.theme.list.itemAccentColor) let (nextOptionText, nextOptionActive) = authorizationNextOptionText(currentType: .sms(length: 5), nextType: .call, timeout: 60, strings: self.strings, primaryColor: self.theme.list.itemSecondaryTextColor, accentColor: self.theme.list.itemAccentColor)
self.nextOptionTitleNode.attributedText = nextOptionText self.nextOptionTitleNode.attributedText = nextOptionText
self.nextOptionButtonNode.isUserInteractionEnabled = nextOptionActive self.nextOptionButtonNode.isUserInteractionEnabled = nextOptionActive
self.nextOptionButtonNode.accessibilityLabel = nextOptionText.string self.nextOptionButtonNode.accessibilityLabel = nextOptionText.string
@ -441,7 +441,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
if let strongSelf = self { if let strongSelf = self {
if let currentTimeoutTime = strongSelf.currentTimeoutTime, currentTimeoutTime > 0 { if let currentTimeoutTime = strongSelf.currentTimeoutTime, currentTimeoutTime > 0 {
strongSelf.currentTimeoutTime = currentTimeoutTime - 1 strongSelf.currentTimeoutTime = currentTimeoutTime - 1
let (nextOptionText, nextOptionActive) = authorizationNextOptionText(currentType: codeType, nextType: nextType, returnToCode: hasPreviousCode, timeout: strongSelf.currentTimeoutTime, strings: strongSelf.strings, primaryColor: strongSelf.theme.list.itemPrimaryTextColor, accentColor: strongSelf.theme.list.itemAccentColor) let (nextOptionText, nextOptionActive) = authorizationNextOptionText(currentType: codeType, nextType: nextType, returnToCode: hasPreviousCode, timeout: strongSelf.currentTimeoutTime, strings: strongSelf.strings, primaryColor: strongSelf.theme.list.itemSecondaryTextColor, accentColor: strongSelf.theme.list.itemAccentColor)
strongSelf.nextOptionTitleNode.attributedText = nextOptionText strongSelf.nextOptionTitleNode.attributedText = nextOptionText
strongSelf.nextOptionButtonNode.isUserInteractionEnabled = nextOptionActive strongSelf.nextOptionButtonNode.isUserInteractionEnabled = nextOptionActive
strongSelf.nextOptionButtonNode.accessibilityLabel = nextOptionText.string strongSelf.nextOptionButtonNode.accessibilityLabel = nextOptionText.string
@ -482,7 +482,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
self.countdownDisposable.set(nil) self.countdownDisposable.set(nil)
} }
let (nextOptionText, nextOptionActive) = authorizationNextOptionText(currentType: codeType, nextType: nextType, returnToCode: hasPreviousCode, timeout: self.currentTimeoutTime, strings: self.strings, primaryColor: self.theme.list.itemPrimaryTextColor, accentColor: self.theme.list.itemAccentColor) let (nextOptionText, nextOptionActive) = authorizationNextOptionText(currentType: codeType, nextType: nextType, returnToCode: hasPreviousCode, timeout: self.currentTimeoutTime, strings: self.strings, primaryColor: self.theme.list.itemSecondaryTextColor, accentColor: self.theme.list.itemAccentColor)
self.nextOptionTitleNode.attributedText = nextOptionText self.nextOptionTitleNode.attributedText = nextOptionText
self.nextOptionButtonNode.isUserInteractionEnabled = nextOptionActive self.nextOptionButtonNode.isUserInteractionEnabled = nextOptionActive
self.nextOptionButtonNode.accessibilityLabel = nextOptionText.string self.nextOptionButtonNode.accessibilityLabel = nextOptionText.string
@ -556,7 +556,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
self.titleNode.attributedText = NSAttributedString(string: self.strings.Login_EnterCodeTelegramTitle, font: Font.semibold(40.0), textColor: self.theme.list.itemPrimaryTextColor) self.titleNode.attributedText = NSAttributedString(string: self.strings.Login_EnterCodeTelegramTitle, font: Font.semibold(40.0), textColor: self.theme.list.itemPrimaryTextColor)
} }
self.textField.textField.placeholder = textFieldPlaceholder self.textField.textField.attributedPlaceholder = NSAttributedString(string: textFieldPlaceholder, font: Font.regular(20.0), textColor: self.theme.list.itemPlaceholderTextColor)
self.titleActivateAreaNode.accessibilityLabel = self.titleNode.attributedText?.string ?? "" self.titleActivateAreaNode.accessibilityLabel = self.titleNode.attributedText?.string ?? ""
@ -808,7 +808,9 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
if let codeType = self.codeType { if let codeType = self.codeType {
let yOffset: CGFloat = layout.size.width > 320.0 ? 18.0 : 5.0 let yOffset: CGFloat = layout.size.width > 320.0 ? 18.0 : 5.0
if case .phrase = codeType { if case .phrase = codeType {
self.hintButtonNode.alpha = 1.0 if self.errorTextNode.alpha.isZero {
self.hintButtonNode.alpha = 1.0
}
self.hintButtonNode.isUserInteractionEnabled = false self.hintButtonNode.isUserInteractionEnabled = false
self.hintTextNode.attributedText = NSAttributedString(string: self.strings.Login_EnterPhraseHint, font: Font.regular(13.0), textColor: self.theme.list.itemSecondaryTextColor, paragraphAlignment: .center) self.hintTextNode.attributedText = NSAttributedString(string: self.strings.Login_EnterPhraseHint, font: Font.regular(13.0), textColor: self.theme.list.itemSecondaryTextColor, paragraphAlignment: .center)
@ -974,7 +976,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
var updated = textField.text ?? "" var updated = textField.text ?? ""
updated.replaceSubrange(updated.index(updated.startIndex, offsetBy: range.lowerBound) ..< updated.index(updated.startIndex, offsetBy: range.upperBound), with: string) updated.replaceSubrange(updated.index(updated.startIndex, offsetBy: range.lowerBound) ..< updated.index(updated.startIndex, offsetBy: range.upperBound), with: string)
return checkValidity(text: updated) return checkValidity(text: updated) && !updated.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
} }
func checkValidity(text: String) -> Bool { func checkValidity(text: String) -> Bool {

View File

@ -508,7 +508,7 @@ private final class AdminUserActionsSheetComponent: Component {
let resetScrolling = self.scrollView.bounds.width != availableSize.width let resetScrolling = self.scrollView.bounds.width != availableSize.width
let sideInset: CGFloat = 16.0 let sideInset: CGFloat = 16.0 + environment.safeInsets.left
if self.component == nil { if self.component == nil {
var (allowedParticipantRights, allowedMediaRights) = rightsFromBannedRights([]) var (allowedParticipantRights, allowedMediaRights) = rightsFromBannedRights([])
@ -584,7 +584,7 @@ private final class AdminUserActionsSheetComponent: Component {
environment: {}, environment: {},
containerSize: CGSize(width: 120.0, height: 100.0) containerSize: CGSize(width: 120.0, height: 100.0)
) )
let leftButtonFrame = CGRect(origin: CGPoint(x: 16.0, y: 0.0), size: leftButtonSize) let leftButtonFrame = CGRect(origin: CGPoint(x: 16.0 + environment.safeInsets.left, y: 0.0), size: leftButtonSize)
if let leftButtonView = self.leftButton.view { if let leftButtonView = self.leftButton.view {
if leftButtonView.superview == nil { if leftButtonView.superview == nil {
self.navigationBarContainer.addSubview(leftButtonView) self.navigationBarContainer.addSubview(leftButtonView)

View File

@ -43,6 +43,29 @@ private enum MembersActionType: Hashable, CaseIterable {
return "Members left the Group" return "Members left the Group"
} }
} }
var eventFlags: AdminLogEventsFlags {
switch self {
case .newAdminRights:
return [.promote, .demote]
case .newExceptions:
return [.ban, .unban, .kick, .unkick]
case .newMembers:
return [.invite, .join]
case .leftMembers:
return [.leave]
}
}
static func actionTypesFromFlags(_ eventFlags: AdminLogEventsFlags) -> [Self] {
var actionTypes: [Self] = []
for actionType in Self.allCases {
if !actionType.eventFlags.intersection(eventFlags).isEmpty {
actionTypes.append(actionType)
}
}
return actionTypes
}
} }
private enum SettingsActionType: Hashable, CaseIterable { private enum SettingsActionType: Hashable, CaseIterable {
@ -50,16 +73,37 @@ private enum SettingsActionType: Hashable, CaseIterable {
case inviteLinks case inviteLinks
case videoChats case videoChats
func title(strings: PresentationStrings) -> String { func title(isGroup: Bool, strings: PresentationStrings) -> String {
switch self { switch self {
case .groupInfo: case .groupInfo:
return "Group Info" return isGroup ? strings.Channel_AdminLogFilter_EventsInfo : strings.Channel_AdminLogFilter_ChannelEventsInfo
case .inviteLinks: case .inviteLinks:
return "Invite Links" return strings.Channel_AdminLogFilter_EventsInviteLinks
case .videoChats: case .videoChats:
return "Video Chats" return isGroup ? strings.Channel_AdminLogFilter_EventsCalls : strings.Channel_AdminLogFilter_EventsLiveStreams
} }
} }
var eventFlags: AdminLogEventsFlags {
switch self {
case .groupInfo:
return [.info, .settings]
case .inviteLinks:
return [.invites]
case .videoChats:
return [.calls]
}
}
static func actionTypesFromFlags(_ eventFlags: AdminLogEventsFlags) -> [Self] {
var actionTypes: [Self] = []
for actionType in Self.allCases {
if !actionType.eventFlags.intersection(eventFlags).isEmpty {
actionTypes.append(actionType)
}
}
return actionTypes
}
} }
private enum MessagesActionType: Hashable, CaseIterable { private enum MessagesActionType: Hashable, CaseIterable {
@ -70,13 +114,34 @@ private enum MessagesActionType: Hashable, CaseIterable {
func title(strings: PresentationStrings) -> String { func title(strings: PresentationStrings) -> String {
switch self { switch self {
case .deletedMessages: case .deletedMessages:
return "Deleted Messages" return strings.Channel_AdminLogFilter_EventsDeletedMessages
case .editedMessages: case .editedMessages:
return "Edited Messages" return strings.Channel_AdminLogFilter_EventsEditedMessages
case .pinnedMessages: case .pinnedMessages:
return "Pinned Messages" return strings.Channel_AdminLogFilter_EventsPinned
} }
} }
var eventFlags: AdminLogEventsFlags {
switch self {
case .deletedMessages:
return [.deleteMessages]
case .editedMessages:
return [.editMessages]
case .pinnedMessages:
return [.pinnedMessages]
}
}
static func actionTypesFromFlags(_ eventFlags: AdminLogEventsFlags) -> [Self] {
var actionTypes: [Self] = []
for actionType in Self.allCases {
if !actionType.eventFlags.intersection(eventFlags).isEmpty {
actionTypes.append(actionType)
}
}
return actionTypes
}
} }
private enum ActionType: Hashable { private enum ActionType: Hashable {
@ -84,12 +149,12 @@ private enum ActionType: Hashable {
case settings(SettingsActionType) case settings(SettingsActionType)
case messages(MessagesActionType) case messages(MessagesActionType)
func title(strings: PresentationStrings) -> String { func title(isGroup: Bool, strings: PresentationStrings) -> String {
switch self { switch self {
case let .members(value): case let .members(value):
return value.title(strings: strings) return value.title(strings: strings)
case let .settings(value): case let .settings(value):
return value.title(strings: strings) return value.title(isGroup: isGroup, strings: strings)
case let .messages(value): case let .messages(value):
return value.title(strings: strings) return value.title(strings: strings)
} }
@ -100,16 +165,22 @@ private final class RecentActionsSettingsSheetComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext let context: AccountContext
let peer: EnginePeer
let adminPeers: [EnginePeer] let adminPeers: [EnginePeer]
let completion: (RecentActionsSettingsSheet.Result) -> Void let initialValue: RecentActionsSettingsSheet.Value
let completion: (RecentActionsSettingsSheet.Value) -> Void
init( init(
context: AccountContext, context: AccountContext,
peer: EnginePeer,
adminPeers: [EnginePeer], adminPeers: [EnginePeer],
completion: @escaping (RecentActionsSettingsSheet.Result) -> Void initialValue: RecentActionsSettingsSheet.Value,
completion: @escaping (RecentActionsSettingsSheet.Value) -> Void
) { ) {
self.context = context self.context = context
self.peer = peer
self.adminPeers = adminPeers self.adminPeers = adminPeers
self.initialValue = initialValue
self.completion = completion self.completion = completion
} }
@ -117,6 +188,9 @@ private final class RecentActionsSettingsSheetComponent: Component {
if lhs.context !== rhs.context { if lhs.context !== rhs.context {
return false return false
} }
if lhs.peer != rhs.peer {
return false
}
if lhs.adminPeers != rhs.adminPeers { if lhs.adminPeers != rhs.adminPeers {
return false return false
} }
@ -274,8 +348,24 @@ private final class RecentActionsSettingsSheetComponent: Component {
} }
} }
private func calculateResult() -> RecentActionsSettingsSheet.Result { private func calculateResult() -> RecentActionsSettingsSheet.Value {
return RecentActionsSettingsSheet.Result( var events: AdminLogEventsFlags = []
var admins: [EnginePeer.Id] = []
for action in self.selectedMembersActions {
events.formUnion(action.eventFlags)
}
for action in self.selectedSettingsActions {
events.formUnion(action.eventFlags)
}
for action in self.selectedMessagesActions {
events.formUnion(action.eventFlags)
}
for peerId in self.selectedAdmins {
admins.append(peerId)
}
return RecentActionsSettingsSheet.Value(
events: events,
admins: admins
) )
} }
@ -352,13 +442,18 @@ private final class RecentActionsSettingsSheetComponent: Component {
let resetScrolling = self.scrollView.bounds.width != availableSize.width let resetScrolling = self.scrollView.bounds.width != availableSize.width
let sideInset: CGFloat = 16.0 let sideInset: CGFloat = 16.0 + environment.safeInsets.left
if self.component == nil { if self.component == nil {
self.selectedMembersActions = Set(MembersActionType.allCases) self.selectedMembersActions = Set(MembersActionType.actionTypesFromFlags(component.initialValue.events))
self.selectedSettingsActions = Set(SettingsActionType.allCases) self.selectedSettingsActions = Set(SettingsActionType.actionTypesFromFlags(component.initialValue.events))
self.selectedMessagesActions = Set(MessagesActionType.allCases) self.selectedMessagesActions = Set(MessagesActionType.actionTypesFromFlags(component.initialValue.events))
self.selectedAdmins = Set(component.adminPeers.map(\.id)) self.selectedAdmins = component.initialValue.admins.flatMap { Set($0) } ?? Set(component.adminPeers.map(\.id))
}
var isGroup = true
if case let .channel(channel) = component.peer, case .broadcast = channel.info {
isGroup = false
} }
self.component = component self.component = component
@ -394,7 +489,7 @@ private final class RecentActionsSettingsSheetComponent: Component {
environment: {}, environment: {},
containerSize: CGSize(width: 120.0, height: 100.0) containerSize: CGSize(width: 120.0, height: 100.0)
) )
let leftButtonFrame = CGRect(origin: CGPoint(x: 16.0, y: 0.0), size: leftButtonSize) let leftButtonFrame = CGRect(origin: CGPoint(x: 16.0 + environment.safeInsets.left, y: 0.0), size: leftButtonSize)
if let leftButtonView = self.leftButton.view { if let leftButtonView = self.leftButton.view {
if leftButtonView.superview == nil { if leftButtonView.superview == nil {
self.navigationBarContainer.addSubview(leftButtonView) self.navigationBarContainer.addSubview(leftButtonView)
@ -420,11 +515,11 @@ private final class RecentActionsSettingsSheetComponent: Component {
case .members: case .members:
totalCount = MembersActionType.allCases.count totalCount = MembersActionType.allCases.count
selectedCount = self.selectedMembersActions.count selectedCount = self.selectedMembersActions.count
title = "Members and Admins" title = isGroup ? "Members and Admins" : "Subscribers and Admins"
case .settings: case .settings:
totalCount = SettingsActionType.allCases.count totalCount = SettingsActionType.allCases.count
selectedCount = self.selectedSettingsActions.count selectedCount = self.selectedSettingsActions.count
title = "Group Settings" title = isGroup ? "Group Settings" : "Channel Settings"
case .messages: case .messages:
totalCount = MessagesActionType.allCases.count totalCount = MessagesActionType.allCases.count
selectedCount = self.selectedMessagesActions.count selectedCount = self.selectedMessagesActions.count
@ -524,7 +619,7 @@ private final class RecentActionsSettingsSheetComponent: Component {
var subItems: [AnyComponentWithIdentity<Empty>] = [] var subItems: [AnyComponentWithIdentity<Empty>] = []
for actionType in actionTypes { for actionType in actionTypes {
let actionItemTitle: String = actionType.title(strings: environment.strings) let actionItemTitle: String = actionType.title(isGroup: isGroup, strings: environment.strings)
let subItemToggleAction: () -> Void = { [weak self] in let subItemToggleAction: () -> Void = { [weak self] in
guard let self else { guard let self else {
@ -846,8 +941,13 @@ private final class RecentActionsSettingsSheetComponent: Component {
} }
public class RecentActionsSettingsSheet: ViewControllerComponentContainer { public class RecentActionsSettingsSheet: ViewControllerComponentContainer {
public final class Result { public final class Value {
init() { public let events: AdminLogEventsFlags
public let admins: [EnginePeer.Id]?
public init(events: AdminLogEventsFlags, admins: [EnginePeer.Id]?) {
self.events = events
self.admins = admins
} }
} }
@ -855,10 +955,10 @@ public class RecentActionsSettingsSheet: ViewControllerComponentContainer {
private var isDismissed: Bool = false private var isDismissed: Bool = false
public init(context: AccountContext, adminPeers: [EnginePeer], completion: @escaping (Result) -> Void) { public init(context: AccountContext, peer: EnginePeer, adminPeers: [EnginePeer], initialValue: Value, completion: @escaping (Value) -> Void) {
self.context = context self.context = context
super.init(context: context, component: RecentActionsSettingsSheetComponent(context: context, adminPeers: adminPeers, completion: completion), navigationBarAppearance: .none) super.init(context: context, component: RecentActionsSettingsSheetComponent(context: context, peer: peer, adminPeers: adminPeers, initialValue: initialValue, completion: completion), navigationBarAppearance: .none)
self.statusBar.statusBarStyle = .Ignore self.statusBar.statusBarStyle = .Ignore
self.navigationPresentation = .flatModal self.navigationPresentation = .flatModal

View File

@ -48,7 +48,8 @@ swift_library(
"//submodules/UndoUI", "//submodules/UndoUI",
"//submodules/WallpaperBackgroundNode", "//submodules/WallpaperBackgroundNode",
"//submodules/TextFormat", "//submodules/TextFormat",
"//submodules/CounterControllerTitleView" "//submodules/CounterControllerTitleView",
"//submodules/TelegramUI/Components/AdminUserActionsSheet",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -12,6 +12,7 @@ import PresentationDataUtils
import ChatPresentationInterfaceState import ChatPresentationInterfaceState
import ChatNavigationButton import ChatNavigationButton
import CounterControllerTitleView import CounterControllerTitleView
import AdminUserActionsSheet
public final class ChatRecentActionsController: TelegramBaseController { public final class ChatRecentActionsController: TelegramBaseController {
private var controllerNode: ChatRecentActionsControllerNode { private var controllerNode: ChatRecentActionsControllerNode {
@ -34,6 +35,8 @@ public final class ChatRecentActionsController: TelegramBaseController {
private let titleView: CounterControllerTitleView private let titleView: CounterControllerTitleView
private var rightBarButton: ChatNavigationButton? private var rightBarButton: ChatNavigationButton?
private var adminsDisposable: Disposable?
public init(context: AccountContext, peer: Peer, adminPeerId: PeerId?) { public init(context: AccountContext, peer: Peer, adminPeerId: PeerId?) {
self.context = context self.context = context
self.peer = peer self.peer = peer
@ -221,6 +224,7 @@ public final class ChatRecentActionsController: TelegramBaseController {
deinit { deinit {
self.presentationDataDisposable?.dispose() self.presentationDataDisposable?.dispose()
self.adminsDisposable?.dispose()
} }
private func updateThemeAndStrings() { private func updateThemeAndStrings() {
@ -291,11 +295,55 @@ public final class ChatRecentActionsController: TelegramBaseController {
self.updateTitle() self.updateTitle()
} }
private var adminsPromise: Promise<[RenderedChannelParticipant]?>?
func openFilterSetup() { func openFilterSetup() {
self.present(channelRecentActionsFilterController(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: self.peer, events: self.controllerNode.filter.events, adminPeerIds: self.controllerNode.filter.adminPeerIds, apply: { [weak self] events, adminPeerIds in if self.adminsPromise == nil {
self?.controllerNode.updateFilter(events: events, adminPeerIds: adminPeerIds) self.adminsPromise = Promise()
self?.updateTitle() let (disposable, _) = self.context.peerChannelMemberCategoriesContextsManager.admins(engine: self.context.engine, postbox: self.context.account.postbox, network: self.context.account.network, accountPeerId: self.context.account.peerId, peerId: self.peer.id) { membersState in
}), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) if case .loading = membersState.loadingState, membersState.list.isEmpty {
self.adminsPromise?.set(.single(nil))
} else {
self.adminsPromise?.set(.single(membersState.list))
}
}
self.adminsDisposable = disposable
}
guard let adminsPromise = self.adminsPromise else {
return
}
let _ = (adminsPromise.get()
|> filter { $0 != nil }
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] result in
guard let self else {
return
}
var adminPeers: [EnginePeer] = []
if let result {
for participant in result {
adminPeers.append(EnginePeer(participant.peer))
}
}
let controller = RecentActionsSettingsSheet(
context: self.context,
peer: EnginePeer(self.peer),
adminPeers: adminPeers,
initialValue: RecentActionsSettingsSheet.Value(
events: self.controllerNode.filter.events,
admins: self.controllerNode.filter.adminPeerIds
),
completion: { [weak self] result in
guard let self else {
return
}
self.controllerNode.updateFilter(events: result.events, adminPeerIds: result.admins)
self.updateTitle()
}
)
self.push(controller)
})
} }
private func updateTitle() { private func updateTitle() {

View File

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

View File

@ -0,0 +1,83 @@
%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 3.334961 3.334999 cm
0.070588 0.070588 0.070588 scn
1.330000 8.665002 m
1.330000 12.716011 4.613991 16.000002 8.665000 16.000002 c
10.451064 16.000002 12.088030 15.361636 13.360182 14.300619 c
3.029383 3.969820 l
1.968366 5.241972 1.330000 6.878938 1.330000 8.665002 c
h
3.969837 3.029369 m
5.241986 1.968362 6.878945 1.330002 8.665000 1.330002 c
12.716008 1.330002 16.000000 4.613994 16.000000 8.665002 c
16.000000 10.451057 15.361640 12.088016 14.300632 13.360165 c
3.969837 3.029369 l
h
8.665000 17.330002 m
3.879453 17.330002 0.000000 13.450549 0.000000 8.665002 c
0.000000 3.879455 3.879453 0.000000 8.665000 0.000000 c
13.450547 0.000000 17.330002 3.879455 17.330002 8.665002 c
17.330002 13.450549 13.450547 17.330002 8.665000 17.330002 c
h
f*
n
Q
endstream
endobj
3 0 obj
821
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 24.000000 24.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
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000000911 00000 n
0000000933 00000 n
0000001106 00000 n
0000001180 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
1239
%%EOF