Merge commit 'f77f2f0dbf307a45c4f6efebaf844e104850e7a6'

This commit is contained in:
Peter 2019-06-20 19:10:25 +02:00
commit 11bcd1216c
74 changed files with 1469 additions and 1517 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
Telegram-iOS/BlackFilledIconIpad@2x.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

0
Telegram-iOS/BlackIconIpad.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

0
Telegram-iOS/BlackIconIpad@2x.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

0
Telegram-iOS/BlackIconLargeIpad@2x.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

BIN
Telegram-iOS/BlueIconIpad.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
Telegram-iOS/BlueIconIpad@2x.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

BIN
Telegram-iOS/BlueIconLargeIpad@2x.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -1532,7 +1532,7 @@
"Forward.ChannelReadOnly" = "Sorry, you can't post to this channel.";
"Channel.ErrorAccessDenied" = "Sorry, this channel is private.";
"Group.ErrorAccessDenied" = "Sorry, this channel is private.";
"Group.ErrorAccessDenied" = "Sorry, this group is private.";
"Conversation.InputTextBroadcastPlaceholder" = "Broadcast";
"Channel.NotificationLoading" = "Loading...";
@ -4409,7 +4409,7 @@ Any member of this group will be able to see messages in the channel.";
"PeopleNearby.UsersEmpty" = "Looking for users around you...";
"PeopleNearby.Groups" = "Groups Nearby";
"PeopleNearby.CreateGroup" = "Create a Group Here";
"PeopleNearby.Channels" = "Channels Nearby";
"PeopleNearby.NoMembers" = "no members";
"Channel.Management.LabelOwner" = "Owner";
"Channel.Management.LabelAdministrator" = "Administrator";
@ -4425,7 +4425,6 @@ Any member of this group will be able to see messages in the channel.";
"Channel.AdminLog.MessageTransferedNameUsername" = "transferred ownership to %1$@ (%2$@)";
"Channel.AdminLog.MessageChangedGroupGeoLocation" = "changed group location to \"%@\"";
"Channel.AdminLog.MessageRemovedGroupGeoLocation" = "%@ removed group location";
"Map.SetThisLocation" = "Set This Location";
@ -4450,6 +4449,8 @@ Any member of this group will be able to see messages in the channel.";
"Group.PublicLink.Title" = "Public Link";
"Group.PublicLink.Placeholder" = "link";
"Group.PublicLink.Info" = "People can share this link with others and find your group using Telegram search.\n\nYou can use use **a-z**, **0-9** and undescores. Minimum length is **5** characters.";
"Group.PublicLink.Info" = "People can share this link with others and find your group using Telegram search.\n\nYou can use **a-z**, **0-9** and underscores. Minimum length is **5** characters.";
"CreateGroup.ErrorLocatedGroupsTooMuch" = "Sorry, you have too many location-based groups already. Please delete one of your existing ones first.";
"GroupInfo.LabelOwner" = "owner";

View File

@ -158,6 +158,21 @@ public func updateAddressName(account: Account, domain: AddressNameDomain, name:
} |> mapError { _ -> UpdateAddressNameError in return .generic } |> switchToLatest
}
public func checkPublicChannelCreationAvailability(account: Account, location: Bool = false) -> Signal<Bool, NoError> {
var flags: Int32 = (1 << 1)
if location {
flags |= (1 << 0)
}
return account.network.request(Api.functions.channels.getAdminedPublicChannels(flags: flags))
|> map { _ -> Bool in
return true
}
|> `catch` { error -> Signal<Bool, NoError> in
return .single(false)
}
}
public func adminedPublicChannels(account: Account, location: Bool = false) -> Signal<[Peer], NoError> {
var flags: Int32 = 0
if location {

View File

@ -40,7 +40,7 @@ private func createChannel(account: Account, title: String, description: String?
return account.network.request(Api.functions.channels.createChannel(flags: flags, title: title, about: description ?? "", geoPoint: geoPoint, address: address), automaticFloodWait: false)
|> mapError { error -> CreateChannelError in
if error.errorDescription == "" {
if error.errorDescription == "CHANNELS_ADMIN_LOCATED_TOO_MUCH" {
return .tooMuchLocationBasedGroups
} else if error.errorDescription == "USER_RESTRICTED" {
return .restricted

View File

@ -56,7 +56,7 @@ public func checkOwnershipTranfserAvailability(postbox: Postbox, network: Networ
}
} else if error.errorDescription == "CHANNELS_ADMIN_PUBLIC_TOO_MUCH" {
return .userPublicChannelsTooMuch
} else if error.errorDescription == "CHANNELS_ADMIN_LOCATED_TOO_MUCHs" {
} else if error.errorDescription == "CHANNELS_ADMIN_LOCATED_TOO_MUCH" {
return .userLocatedGroupsTooMuch
} else if error.errorDescription == "ADMINS_TOO_MUCH" {
return .adminsTooMuch
@ -85,10 +85,17 @@ public func updateChannelOwnership(account: Account, accountStateManager: Accoun
}
|> mapToSignal { currentCreator, currentParticipant -> Signal<[(ChannelParticipant?, RenderedChannelParticipant)], ChannelOwnershipTransferError> in
return account.postbox.transaction { transaction -> Signal<[(ChannelParticipant?, RenderedChannelParticipant)], ChannelOwnershipTransferError> in
if let channel = transaction.getPeer(channelId), let inputChannel = apiInputChannel(channel), let accountUser = transaction.getPeer(account.peerId), let user = transaction.getPeer(memberId), let inputUser = apiInputUser(user) {
if let channel = transaction.getPeer(channelId) as? TelegramChannel, let inputChannel = apiInputChannel(channel), let accountUser = transaction.getPeer(account.peerId), let user = transaction.getPeer(memberId), let inputUser = apiInputUser(user) {
var flags: TelegramChatAdminRightsFlags
if case .broadcast = channel.info {
flags = TelegramChatAdminRightsFlags.broadcastSpecific
} else {
flags = TelegramChatAdminRightsFlags.groupSpecific
}
let updatedParticipant = ChannelParticipant.creator(id: user.id)
let updatedPreviousCreator = ChannelParticipant.member(id: accountUser.id, invitedAt: Int32(Date().timeIntervalSince1970), adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(flags:[]), promotedBy: accountUser.id, canBeEditedByAccountPeer: false), banInfo: nil)
let updatedPreviousCreator = ChannelParticipant.member(id: accountUser.id, invitedAt: Int32(Date().timeIntervalSince1970), adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(flags: flags), promotedBy: accountUser.id, canBeEditedByAccountPeer: false), banInfo: nil)
let checkPassword = twoStepAuthData(account.network)
|> mapError { error -> ChannelOwnershipTransferError in
@ -131,6 +138,8 @@ public func updateChannelOwnership(account: Account, accountStateManager: Accoun
}
} else if error.errorDescription == "CHANNELS_ADMIN_PUBLIC_TOO_MUCH" {
return .userPublicChannelsTooMuch
} else if error.errorDescription == "CHANNELS_ADMIN_LOCATED_TOO_MUCH" {
return .userLocatedGroupsTooMuch
} else if error.errorDescription == "ADMINS_TOO_MUCH" {
return .adminsTooMuch
} else if error.errorDescription == "USER_PRIVACY_RESTRICTED" {

View File

@ -31,12 +31,15 @@ public final class PeersNearbyContext {
private var entries: [PeerNearby]?
public init(network: Network, accountStateManager: AccountStateManager, coordinate: (latitude: Double, longitude: Double)) {
self.disposable.set((network.request(Api.functions.contacts.getLocated(geoPoint: .inputGeoPoint(lat: coordinate.latitude, long: coordinate.longitude)))
let expiryExtension: Double = 10.0
let poll = network.request(Api.functions.contacts.getLocated(geoPoint: .inputGeoPoint(lat: coordinate.latitude, long: coordinate.longitude)))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
return .single(nil)
}
|> mapToSignal { updates -> Signal<[PeerNearby], NoError> in
|> introduceError(Void.self)
|> mapToSignal { updates -> Signal<[PeerNearby], Void> in
var peersNearby: [PeerNearby] = []
if let updates = updates {
switch updates {
@ -54,16 +57,31 @@ public final class PeersNearbyContext {
accountStateManager.addUpdates(updates)
}
return .single(peersNearby)
|> then(accountStateManager.updatedPeersNearby())
|> then(
accountStateManager.updatedPeersNearby()
|> introduceError(Void.self)
)
}
let error: Signal<Void, Void> = .single(Void()) |> then(Signal.fail(Void()) |> suspendAwareDelay(25.0, queue: self.queue))
let combined = combineLatest(poll, error)
|> map { data, _ -> [PeerNearby] in
return data
}
|> restartIfError
|> `catch` { _ -> Signal<[PeerNearby], NoError> in
return .single([])
}
self.disposable.set((combined
|> deliverOn(self.queue)).start(next: { [weak self] updatedEntries in
guard let strongSelf = self else {
return
}
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
var entries = strongSelf.entries?.filter { Double($0.expires) > timestamp } ?? []
let updatedEntries = updatedEntries.filter { Double($0.expires) > timestamp }
var entries = strongSelf.entries?.filter { Double($0.expires) + expiryExtension > timestamp } ?? []
let updatedEntries = updatedEntries.filter { Double($0.expires) + expiryExtension > timestamp }
var existingPeerIds: [PeerId: Int] = [:]
for i in 0 ..< entries.count {
@ -79,7 +97,6 @@ public final class PeersNearbyContext {
}
strongSelf.entries = entries
for subscriber in strongSelf.subscribers.copyItems() {
subscriber(strongSelf.entries)
}
@ -91,7 +108,10 @@ public final class PeersNearbyContext {
}
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
strongSelf.entries = strongSelf.entries?.filter { Double($0.expires) > timestamp }
strongSelf.entries = strongSelf.entries?.filter { Double($0.expires) + expiryExtension > timestamp }
for subscriber in strongSelf.subscribers.copyItems() {
subscriber(strongSelf.entries)
}
}, queue: self.queue)
self.timer?.start()
}

View File

@ -305,13 +305,10 @@ func apiEntitiesFromMessageTextEntities(_ entities: [MessageTextEntity], associa
break
case .Strikethrough:
apiEntities.append(.messageEntityStrike(offset: offset, length: length))
break
case .BlockQuote:
apiEntities.append(.messageEntityBlockquote(offset: offset, length: length))
break
case .Underline:
apiEntities.append(.messageEntityUnderline(offset: offset, length: length))
break
case .Custom:
break
}

View File

@ -72,8 +72,6 @@ apple_library(
'TelegramUI/DeviceProximityManager.h',
'TelegramUI/RaiseToListenActivator.h',
'TelegramUI/TGMimeTypeMap.h',
'TelegramUI/TGEmojiSuggestions.h',
'TelegramUI/TGChannelIntroController.h',
'TelegramUI/EDSunriseSet.h',
'TelegramUI/TGBridgeAudioDecoder.h',
'TelegramUI/TGBridgeAudioEncoder.h',

View File

@ -1641,8 +1641,8 @@ final class SharedApplicationContext {
} else if let sendMessageIntent = userActivity.interaction?.intent as? INSendMessageIntent {
if let contact = sendMessageIntent.recipients?.first, let handle = contact.customIdentifier, handle.hasPrefix("tg") {
let string = handle.suffix(from: handle.index(handle.startIndex, offsetBy: 2))
if let id = Int32(string), let context = self.contextValue {
navigateToChatController(navigationController: context.rootController, context: context.context, chatLocation: .peer(PeerId(namespace: Namespaces.Peer.CloudUser, id: id)))
if let id = Int32(string) {
self.openChatWhenReady(accountId: nil, peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: id), activateInput: true)
}
}
}
@ -1689,7 +1689,7 @@ final class SharedApplicationContext {
})
}
private func openChatWhenReady(accountId: AccountRecordId?, peerId: PeerId, messageId: MessageId? = nil) {
private func openChatWhenReady(accountId: AccountRecordId?, peerId: PeerId, messageId: MessageId? = nil, activateInput: Bool = false) {
let signal = self.sharedContextPromise.get()
|> take(1)
|> mapToSignal { sharedApplicationContext -> Signal<AuthorizedApplicationContext, NoError> in
@ -1707,7 +1707,7 @@ final class SharedApplicationContext {
}
self.openChatWhenReadyDisposable.set((signal
|> deliverOnMainQueue).start(next: { context in
context.openChatWithPeerId(peerId: peerId, messageId: messageId)
context.openChatWithPeerId(peerId: peerId, messageId: messageId, activateInput: activateInput)
}))
}

View File

@ -58,7 +58,7 @@ final class AuthorizedApplicationContext {
let rootController: TelegramRootController
let notificationController: NotificationContainerController
private var scheduledOperChatWithPeerId: PeerId?
private var scheduledOperChatWithPeerId: (PeerId, MessageId?, Bool)?
private var scheduledOpenExternalUrl: URL?
private let passcodeStatusDisposable = MetaDisposable()
@ -268,9 +268,9 @@ final class AuthorizedApplicationContext {
strongSelf.notificationController.view.isHidden = false
if strongSelf.rootController.rootTabController == nil {
strongSelf.rootController.addRootControllers(showCallsTab: strongSelf.showCallsTab)
if let peerId = strongSelf.scheduledOperChatWithPeerId {
if let (peerId, messageId, activateInput) = strongSelf.scheduledOperChatWithPeerId {
strongSelf.scheduledOperChatWithPeerId = nil
strongSelf.openChatWithPeerId(peerId: peerId)
strongSelf.openChatWithPeerId(peerId: peerId, messageId: messageId, activateInput: activateInput)
}
if let url = strongSelf.scheduledOpenExternalUrl {
@ -782,7 +782,7 @@ final class AuthorizedApplicationContext {
self.permissionsDisposable.dispose()
}
func openChatWithPeerId(peerId: PeerId, messageId: MessageId? = nil) {
func openChatWithPeerId(peerId: PeerId, messageId: MessageId? = nil, activateInput: Bool = false) {
var visiblePeerId: PeerId?
if let controller = self.rootController.topViewController as? ChatController, case let .peer(peerId) = controller.chatLocation {
visiblePeerId = peerId
@ -790,9 +790,9 @@ final class AuthorizedApplicationContext {
if visiblePeerId != peerId || messageId != nil {
if self.rootController.rootTabController != nil {
navigateToChatController(navigationController: self.rootController, context: self.context, chatLocation: .peer(peerId), messageId: messageId)
navigateToChatController(navigationController: self.rootController, context: self.context, chatLocation: .peer(peerId), messageId: messageId, activateInput: activateInput)
} else {
self.scheduledOperChatWithPeerId = peerId
self.scheduledOperChatWithPeerId = (peerId, messageId, activateInput)
}
}
}

View File

@ -294,7 +294,9 @@ private final class ChannelMemberSingleCategoryListContext: ChannelMemberCategor
}
switch self.category {
case let .admins(query):
if let updated = updated, let _ = updated.participant.adminInfo, (query == nil || updated.peer.indexName.matchesByTokens(query!)) {
if let updated = updated, (query == nil || updated.peer.indexName.matchesByTokens(query!)) {
if case let .member(_, _, adminInfo, _) = updated.participant, adminInfo == nil {
} else {
var found = false
loop: for i in 0 ..< list.count {
if list[i].peer.id == updated.peer.id {
@ -308,6 +310,7 @@ private final class ChannelMemberSingleCategoryListContext: ChannelMemberCategor
list.insert(updated, at: 0)
updatedList = true
}
}
} else if let previous = previous, let _ = previous.adminInfo {
loop: for i in 0 ..< list.count {
if list[i].peer.id == previous.peerId {
@ -316,7 +319,7 @@ private final class ChannelMemberSingleCategoryListContext: ChannelMemberCategor
break loop
}
}
if let updated = updated, case .creator = updated.participant{
if let updated = updated, case .creator = updated.participant {
list.insert(updated, at: 0)
updatedList = true
}

View File

@ -1193,8 +1193,16 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
title = isGroup ? presentationData.strings.GroupInfo_GroupType : presentationData.strings.Channel_TypeSetup_Title
}
}
let entries = channelVisibilityControllerEntries(presentationData: presentationData, mode: mode, view: view, publicChannelsToRevoke: publicChannelsToRevoke, state: state)
var focusItemTag: ItemListItemTag?
if entries.count > 1, let cachedChannelData = view.cachedData as? CachedChannelData, cachedChannelData.peerGeoLocation != nil {
focusItemTag = ChannelVisibilityEntryTag.publicLink
}
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
let listState = ItemListNodeState(entries: channelVisibilityControllerEntries(presentationData: presentationData, mode: mode, view: view, publicChannelsToRevoke: publicChannelsToRevoke, state: state), style: .blocks, crossfadeState: crossfade, animateChanges: false)
let listState = ItemListNodeState(entries: entries, style: .blocks, focusItemTag: focusItemTag, crossfadeState: crossfade, animateChanges: false)
return (controllerState, (listState, arguments))
} |> afterDisposed {

View File

@ -136,7 +136,7 @@ final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
} else {
text = presentationInterfaceState.strings.Group_ErrorAccessDenied
}
strongSelf.interfaceInteraction?.presentController(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationInterfaceState.theme), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationInterfaceState.strings.Common_OK, action: {})]), nil)
strongSelf.interfaceInteraction?.presentController(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationInterfaceState.strings.Common_OK, action: {})]), nil)
}))
case .kicked:
break

View File

@ -223,6 +223,10 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
private var screenCaptureEventsDisposable: Disposable?
private let chatAdditionalDataDisposable = MetaDisposable()
private var reportIrrelvantGeoNoticePromise = Promise<Bool?>()
private var reportIrrelvantGeoNotice: Bool?
private var reportIrrelvantGeoDisposable: Disposable?
private var volumeButtonsListener: VolumeButtonsListener?
private var beginMediaRecordingRequestId: Int = 0
@ -1449,17 +1453,32 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
}
onlineMemberCount = recentOnlineSignal
|> map(Optional.init)
self.reportIrrelvantGeoNoticePromise.set(context.account.postbox.transaction { transaction -> Bool? in
if let _ = transaction.getNoticeEntry(key: ApplicationSpecificNotice.irrelevantPeerGeoReportKey(peerId: peerId)) as? ApplicationSpecificBoolNotice {
return true
} else {
return false
}
self.peerDisposable.set((combineLatest(queue: Queue.mainQueue(), peerView.get(), onlineMemberCount)
|> deliverOnMainQueue).start(next: { [weak self] peerView, onlineMemberCount in
})
} else {
self.reportIrrelvantGeoNoticePromise.set(.single(nil))
}
self.peerDisposable.set((combineLatest(queue: Queue.mainQueue(), peerView.get(), onlineMemberCount, self.reportIrrelvantGeoNoticePromise.get())
|> deliverOnMainQueue).start(next: { [weak self] peerView, onlineMemberCount, peerReportNotice in
if let strongSelf = self {
if let peer = peerViewMainPeer(peerView) {
strongSelf.chatTitleView?.titleContent = .peer(peerView: peerView, onlineMemberCount: onlineMemberCount)
(strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.avatarNode.setPeer(account: strongSelf.context.account, theme: strongSelf.presentationData.theme, peer: peer, overrideImage: peer.isDeleted ? .deletedIcon : .none)
}
if strongSelf.peerView === peerView {
if strongSelf.peerView === peerView && strongSelf.reportIrrelvantGeoNotice == peerReportNotice {
return
}
strongSelf.reportIrrelvantGeoNotice = peerReportNotice
var upgradedToPeerId: PeerId?
if let previous = strongSelf.peerView, let group = previous.peers[previous.peerId] as? TelegramGroup, group.migrationReference == nil, let updatedGroup = peerView.peers[peerView.peerId] as? TelegramGroup, let migrationReference = updatedGroup.migrationReference {
upgradedToPeerId = migrationReference.peerId
@ -1516,11 +1535,18 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
var contactStatus: ChatContactStatus?
if let peer = peerView.peers[peerView.peerId] {
if let cachedData = peerView.cachedData as? CachedUserData {
contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, peerStatusSettings: cachedData.peerStatusSettings)
contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings)
} else if let cachedData = peerView.cachedData as? CachedGroupData {
contactStatus = ChatContactStatus(canAddContact: false, peerStatusSettings: cachedData.peerStatusSettings)
contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings)
} else if let cachedData = peerView.cachedData as? CachedChannelData {
contactStatus = ChatContactStatus(canAddContact: false, peerStatusSettings: cachedData.peerStatusSettings)
var canReportIrrelevantLocation = true
if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, peer.participationStatus == .member {
canReportIrrelevantLocation = false
}
if let peerReportNotice = peerReportNotice, peerReportNotice {
canReportIrrelevantLocation = false
}
contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: canReportIrrelevantLocation, peerStatusSettings: cachedData.peerStatusSettings)
}
var peers = SimpleDictionary<PeerId, Peer>()
@ -1581,7 +1607,7 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
didDisplayActionsPanel = true
} else if peerStatusSettings.contains(.canShareContact) {
didDisplayActionsPanel = true
} else if peerStatusSettings.contains(.canReportIrrelevantGeoLocation) {
} else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) {
didDisplayActionsPanel = true
}
}
@ -1596,7 +1622,7 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
displayActionsPanel = true
} else if peerStatusSettings.contains(.canShareContact) {
displayActionsPanel = true
} else if peerStatusSettings.contains(.canReportIrrelevantGeoLocation) {
} else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) {
displayActionsPanel = true
}
}
@ -1932,6 +1958,7 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
self.shareStatusDisposable?.dispose()
self.context.sharedContext.mediaManager.galleryHiddenMediaManager.removeTarget(self)
self.preloadHistoryPeerIdDisposable.dispose()
self.reportIrrelvantGeoDisposable?.dispose()
}
public func updatePresentationMode(_ mode: ChatControllerPresentationMode) {
@ -3445,15 +3472,28 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: false, { $0.updatedInputMode({ _ in return .none }) })
}
}, reportPeerIrrelevantGeoLocation: { [weak self] in
if let strongSelf = self {
guard let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation else {
return
}
strongSelf.chatDisplayNode.dismissInput()
let actions = [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {
}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.ReportGroupLocation_Report, action: {
}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.ReportGroupLocation_Report, action: { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.reportIrrelvantGeoDisposable = (TelegramCore.reportPeer(account: strongSelf.context.account, peerId: peerId, reason: .irrelevantLocation)
|> deliverOnMainQueue).start(completed: { [weak self] in
if let strongSelf = self {
strongSelf.reportIrrelvantGeoNoticePromise.set(.single(true))
let _ = ApplicationSpecificNotice.setIrrelevantPeerGeoReport(postbox: strongSelf.context.account.postbox, peerId: peerId).start()
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.ReportPeer_AlertSuccess, actions: [TextAlertAction(type: TextAlertActionType.defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}
})
})]
strongSelf.present(textAlertController(context: strongSelf.context, title: strongSelf.presentationData.strings.ReportGroupLocation_Title, text: strongSelf.presentationData.strings.ReportGroupLocation_Text, actions: actions), in: .window(.root))
}
}, statuses: ChatPanelInterfaceInteractionStatuses(editingMessage: self.editingMessage.get(), startingBot: self.startingBot.get(), unblockingPeer: self.unblockingPeer.get(), searching: self.searching.get(), loadingMessage: self.loadingMessage.get()))
switch self.chatLocation {
@ -6729,7 +6769,7 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in
return state.updatedInterfaceState { interfaceState in
return interfaceState.withUpdatedEffectiveInputState(interfaceState.effectiveInputState)
}.updatedInputMode({ _ in ChatInputMode.text })
}.updatedInputMode({ _ in .text })
})
}
}),
@ -6740,7 +6780,7 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
return state.updatedInterfaceState { interfaceState in
let effectiveInputState = ChatTextInputState(inputText: NSAttributedString(string: "/"))
return interfaceState.withUpdatedEffectiveInputState(effectiveInputState)
}.updatedInputMode({ _ in ChatInputMode.text })
}.updatedInputMode({ _ in .text })
} else {
return state
}
@ -6754,7 +6794,7 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
return state.updatedInterfaceState { interfaceState in
let effectiveInputState = ChatTextInputState(inputText: NSAttributedString(string: "@"))
return interfaceState.withUpdatedEffectiveInputState(effectiveInputState)
}.updatedInputMode({ _ in ChatInputMode.text })
}.updatedInputMode({ _ in .text })
} else {
return state
}
@ -6768,7 +6808,7 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
return state.updatedInterfaceState { interfaceState in
let effectiveInputState = ChatTextInputState(inputText: NSAttributedString(string: "#"))
return interfaceState.withUpdatedEffectiveInputState(effectiveInputState)
}.updatedInputMode({ _ in ChatInputMode.text })
}.updatedInputMode({ _ in .text })
} else {
return state
}
@ -6846,6 +6886,12 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
}
}
func activateInput() {
self.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in
return state.updatedInputMode({ _ in .text })
})
}
private func clearInputText() {
self.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in
if !state.interfaceState.effectiveInputState.inputText.string.isEmpty {

View File

@ -83,7 +83,7 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
case .installed:
scope = [.installed]
}
return searchStickers(account: context.account, query: query, scope: scope)
return searchStickers(account: context.account, query: query.trimmedEmoji, scope: scope)
}
|> map { stickers -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
return { _ in

View File

@ -37,7 +37,7 @@ func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceStat
displayActionsPanel = true
} else if peerStatusSettings.contains(.canShareContact) {
displayActionsPanel = true
} else if peerStatusSettings.contains(.canReportIrrelevantGeoLocation) {
} else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) {
displayActionsPanel = true
}
}

View File

@ -322,6 +322,7 @@ final class ChatRecordedMediaPreview: Equatable {
struct ChatContactStatus: Equatable {
var canAddContact: Bool
var canReportIrrelevantLocation: Bool
var peerStatusSettings: PeerStatusSettings?
var isEmpty: Bool {
@ -331,6 +332,9 @@ struct ChatContactStatus: Equatable {
if !self.canAddContact {
peerStatusSettings.remove(.canAddContact)
}
if !self.canReportIrrelevantLocation {
peerStatusSettings.remove(.canReportIrrelevantGeoLocation)
}
return peerStatusSettings.isEmpty
}
}

View File

@ -772,7 +772,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
strongSelf.openPeer(peerId: peerId, peer: nil)
}
case .inaccessiblePeer:
strongSelf.controllerInteraction.presentController(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: strongSelf.presentationData.theme), title: nil, text: strongSelf.presentationData.strings.Conversation_ErrorInaccessibleMessage, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), nil)
strongSelf.controllerInteraction.presentController(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Conversation_ErrorInaccessibleMessage, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), nil)
case .botStart:
break
//strongSelf.openPeer(peerId: peerId, navigation: .withBotStartPayload(ChatControllerInitialBotStart(payload: payload, behavior: .interactive)), fromMessage: nil)

View File

@ -950,13 +950,6 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: [], media: [mediaMap], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(isAdmin: false, isContact: false)))
} else {
appendAttributedText(text: self.presentationData.strings.Channel_AdminLog_MessageRemovedGroupGeoLocation(author?.displayTitle ?? ""), generateEntities: { index in
if index == 0, let author = author {
return [.TextMention(peerId: author.id)]
}
return []
}, to: &text, entities: &entities)
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])

View File

@ -54,7 +54,7 @@ private func peerButtons(_ state: ChatPresentationInterfaceState) -> [ChatReport
}
}
} else if let _ = state.renderedPeer?.chatMainPeer {
if let contactStatus = state.contactStatus, let peerStatusSettings = contactStatus.peerStatusSettings, peerStatusSettings.contains(.canReportIrrelevantGeoLocation) {
if let contactStatus = state.contactStatus, contactStatus.canReportIrrelevantLocation, let peerStatusSettings = contactStatus.peerStatusSettings, peerStatusSettings.contains(.canReportIrrelevantGeoLocation) {
buttons.append(.reportIrrelevantGeoLocation)
} else {
buttons.append(.reportSpam)

View File

@ -11,6 +11,7 @@ struct ChatTextInputAttributes {
static let bold = NSAttributedStringKey(rawValue: "Attribute__Bold")
static let italic = NSAttributedStringKey(rawValue: "Attribute__Italic")
static let monospace = NSAttributedStringKey(rawValue: "Attribute__Monospace")
static let strikethrough = NSAttributedStringKey(rawValue: "Attribute__Strikethrough")
static let textMention = NSAttributedStringKey(rawValue: "Attribute__TextMention")
static let textUrl = NSAttributedStringKey(rawValue: "Attribute__TextUrl")
}
@ -23,7 +24,7 @@ func stateAttributedStringForText(_ text: NSAttributedString) -> NSAttributedStr
for (key, value) in attributes {
if key == ChatTextInputAttributes.textMention || key == ChatTextInputAttributes.textUrl {
result.addAttribute(key, value: value, range: range)
} else if key == ChatTextInputAttributes.bold || key == ChatTextInputAttributes.italic || key == ChatTextInputAttributes.monospace {
} else if key == ChatTextInputAttributes.bold || key == ChatTextInputAttributes.italic || key == ChatTextInputAttributes.monospace || key == ChatTextInputAttributes.strikethrough {
result.addAttribute(key, value: value, range: range)
}
}
@ -37,7 +38,6 @@ private struct FontAttributes: OptionSet {
static let bold = FontAttributes(rawValue: 1 << 0)
static let italic = FontAttributes(rawValue: 1 << 1)
static let monospace = FontAttributes(rawValue: 1 << 2)
static let strikethrough = FontAttributes(rawValue: 1 << 3)
}
func textAttributedStringForStateText(_ stateText: NSAttributedString, fontSize: CGFloat, textColor: UIColor, accentTextColor: UIColor) -> NSAttributedString {
@ -66,13 +66,20 @@ func textAttributedStringForStateText(_ stateText: NSAttributedString, fontSize:
} else if key == ChatTextInputAttributes.monospace {
result.addAttribute(key, value: value, range: range)
fontAttributes.insert(.monospace)
} else if key == ChatTextInputAttributes.strikethrough {
result.addAttribute(key, value: value, range: range)
result.addAttribute(NSAttributedStringKey.strikethroughStyle, value: NSUnderlineStyle.styleSingle.rawValue as NSNumber, range: range)
}
}
if !fontAttributes.isEmpty {
var font: UIFont?
if fontAttributes == [.bold, .italic, .monospace] {
font = Font.semiboldItalicMonospace(fontSize)
} else if fontAttributes == [.bold, .monospace] {
font = Font.semiboldMonospace(fontSize)
} else if fontAttributes == [.italic, .monospace] {
font = Font.italicMonospace(fontSize)
} else if fontAttributes == [.bold, .italic] {
font = Font.semiboldItalic(fontSize)
} else if fontAttributes == [.bold] {
@ -384,7 +391,7 @@ private func refreshTextUrls(text: NSString, initialAttributedText: NSAttributed
}
func refreshChatTextInputAttributes(_ textNode: ASEditableTextNode, theme: PresentationTheme, baseFontSize: CGFloat) {
guard var initialAttributedText = textNode.attributedText, initialAttributedText.length != 0 else {
guard let initialAttributedText = textNode.attributedText, initialAttributedText.length != 0 else {
return
}
@ -406,6 +413,7 @@ func refreshChatTextInputAttributes(_ textNode: ASEditableTextNode, theme: Prese
textNode.textView.textStorage.removeAttribute(NSAttributedStringKey.font, range: fullRange)
textNode.textView.textStorage.removeAttribute(NSAttributedStringKey.foregroundColor, range: fullRange)
textNode.textView.textStorage.removeAttribute(NSAttributedStringKey.underlineStyle, range: fullRange)
textNode.textView.textStorage.removeAttribute(NSAttributedStringKey.strikethroughStyle, range: fullRange)
textNode.textView.textStorage.removeAttribute(ChatTextInputAttributes.textMention, range: fullRange)
textNode.textView.textStorage.removeAttribute(ChatTextInputAttributes.textUrl, range: fullRange)
@ -432,6 +440,9 @@ func refreshChatTextInputAttributes(_ textNode: ASEditableTextNode, theme: Prese
} else if key == ChatTextInputAttributes.monospace {
textNode.textView.textStorage.addAttribute(key, value: value, range: range)
fontAttributes.insert(.monospace)
} else if key == ChatTextInputAttributes.strikethrough {
textNode.textView.textStorage.addAttribute(key, value: value, range: range)
textNode.textView.textStorage.addAttribute(NSAttributedStringKey.strikethroughStyle, value: NSUnderlineStyle.styleSingle.rawValue as NSNumber, range: range)
}
}
@ -492,10 +503,10 @@ func chatTextInputAddFormattingAttribute(_ state: ChatTextInputState, attribute:
for (key, _) in attributes {
if key == attribute && range == nsRange {
addAttribute = false
}
attributesToRemove.append(key)
}
}
}
let result = NSMutableAttributedString(attributedString: state.inputText)
for attribute in attributesToRemove {
@ -634,7 +645,7 @@ func breakChatInputText(_ text: NSAttributedString) -> [NSAttributedString] {
}
}
private let markdownRegexFormat = "(^|\\s|\\n)(````?)([\\s\\S]+?)(````?)([\\s\\n\\.,:?!;]|$)|(^|\\s)(`|\\*\\*|__)([^\\n]+?)\\7([\\s\\.,:?!;]|$)|@(\\d+)\\s*\\((.+?)\\)"
private let markdownRegexFormat = "(^|\\s|\\n)(````?)([\\s\\S]+?)(````?)([\\s\\n\\.,:?!;]|$)|(^|\\s)(`|\\*\\*|__|~~)([^\\n]+?)\\7([\\s\\.,:?!;]|$)|@(\\d+)\\s*\\((.+?)\\)"
private let markdownRegex = try? NSRegularExpression(pattern: markdownRegexFormat, options: [.caseInsensitive, .anchorsMatchLines])
func convertMarkdownToAttributes(_ text: NSAttributedString) -> NSAttributedString {
@ -678,6 +689,9 @@ func convertMarkdownToAttributes(_ text: NSAttributedString) -> NSAttributedStri
case "__":
result.append(NSAttributedString(string: substring, attributes: [ChatTextInputAttributes.italic: true as NSNumber]))
offsetRanges.append((NSMakeRange(matchIndex + match.range(at: 6).length, text.count), match.range(at: 6).length * 2))
case "~~":
result.append(NSAttributedString(string: substring, attributes: [ChatTextInputAttributes.strikethrough: true as NSNumber]))
offsetRanges.append((NSMakeRange(matchIndex + match.range(at: 6).length, text.count), match.range(at: 6).length * 2))
default:
break
}

View File

@ -855,7 +855,7 @@ final class ContactListNode: ASDisplayNode {
var authorizeImpl: (() -> Void)?
var openPrivacyPolicyImpl: (() -> Void)?
self.authorizationNode = PermissionContentNode(theme: self.presentationData.theme, strings: self.presentationData.strings, kind: PermissionKind.contacts.rawValue, icon: UIImage(bundleImageName: "Settings/Permissions/Contacts"), title: self.presentationData.strings.Contacts_PermissionsTitle, text: self.presentationData.strings.Contacts_PermissionsText, buttonTitle: self.presentationData.strings.Contacts_PermissionsAllow, buttonAction: {
self.authorizationNode = PermissionContentNode(theme: self.presentationData.theme, strings: self.presentationData.strings, kind: PermissionKind.contacts.rawValue, icon: .image(UIImage(bundleImageName: "Settings/Permissions/Contacts")), title: self.presentationData.strings.Contacts_PermissionsTitle, text: self.presentationData.strings.Contacts_PermissionsText, buttonTitle: self.presentationData.strings.Contacts_PermissionsAllow, buttonAction: {
authorizeImpl?()
}, openPrivacyPolicy: {
openPrivacyPolicyImpl?()
@ -1261,7 +1261,7 @@ final class ContactListNode: ASDisplayNode {
let authorizationPreviousHidden = strongSelf.authorizationNode.isHidden
strongSelf.authorizationNode.removeFromSupernode()
strongSelf.authorizationNode = PermissionContentNode(theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, kind: PermissionKind.contacts.rawValue, icon: UIImage(bundleImageName: "Settings/Permissions/Contacts"), title: strongSelf.presentationData.strings.Contacts_PermissionsTitle, text: strongSelf.presentationData.strings.Contacts_PermissionsText, buttonTitle: strongSelf.presentationData.strings.Contacts_PermissionsAllow, buttonAction: {
strongSelf.authorizationNode = PermissionContentNode(theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, kind: PermissionKind.contacts.rawValue, icon: .image(UIImage(bundleImageName: "Settings/Permissions/Contacts")), title: strongSelf.presentationData.strings.Contacts_PermissionsTitle, text: strongSelf.presentationData.strings.Contacts_PermissionsText, buttonTitle: strongSelf.presentationData.strings.Contacts_PermissionsAllow, buttonAction: {
authorizeImpl?()
}, openPrivacyPolicy: {
openPrivacyPolicyImpl?()

View File

@ -443,7 +443,7 @@ public func createGroupController(context: AccountContext, peerIds: [PeerId], in
case .tooMuchLocationBasedGroups:
text = presentationData.strings.CreateGroup_ErrorLocatedGroupsTooMuch
}
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
presentControllerImpl?(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
}))
}
}, changeProfilePhoto: {

View File

@ -135,4 +135,12 @@ extension String {
}
return string
}
var trimmedEmoji: String {
if self.unicodeScalars.count > 1, self.unicodeScalars.first?.value == 0x2764 {
return String(self.unicodeScalars.prefix(self.unicodeScalars.count - 1))
} else {
return self
}
}
}

View File

@ -132,6 +132,8 @@ func generateChatInputTextEntities(_ text: NSAttributedString) -> [MessageTextEn
entities.append(MessageTextEntity(range: range.lowerBound ..< range.upperBound, type: .Italic))
} else if key == ChatTextInputAttributes.monospace {
entities.append(MessageTextEntity(range: range.lowerBound ..< range.upperBound, type: .Code))
} else if key == ChatTextInputAttributes.strikethrough {
entities.append(MessageTextEntity(range: range.lowerBound ..< range.upperBound, type: .Strikethrough))
} else if key == ChatTextInputAttributes.textMention, let value = value as? ChatTextInputTextMentionAttribute {
entities.append(MessageTextEntity(range: range.lowerBound ..< range.upperBound, type: .TextMention(peerId: value.peerId)))
} else if key == ChatTextInputAttributes.textUrl, let value = value as? ChatTextInputTextUrlAttribute {

View File

@ -94,6 +94,7 @@ private enum GroupInfoEntryTag {
private enum GroupInfoMemberStatus {
case member
case admin
case owner
}
private enum GroupEntryStableId: Hashable, Equatable {
@ -529,6 +530,8 @@ private enum GroupInfoEntry: ItemListNodeEntry {
case let .member(theme, strings, dateTimeFormat, nameDisplayOrder, _, _, peer, participant, presence, memberStatus, editing, actions, enabled, selectable):
let label: String?
switch memberStatus {
case .owner:
label = strings.GroupInfo_LabelOwner
case .admin:
label = strings.GroupInfo_LabelAdmin
case .member:
@ -831,7 +834,9 @@ private func groupInfoEntries(account: Account, presentationData: PresentationDa
if isCreator || (channel.adminRights != nil && channel.hasPermission(.pinMessages)) {
if cachedChannelData.peerGeoLocation != nil {
if isCreator {
entries.append(GroupInfoEntry.groupTypeSetup(presentationData.theme, presentationData.strings.GroupInfo_PublicLink, channel.addressName ?? presentationData.strings.GroupInfo_PublicLinkAdd))
}
} else {
if cachedChannelData.flags.contains(.canChangeUsername) {
entries.append(GroupInfoEntry.groupTypeSetup(presentationData.theme, presentationData.strings.GroupInfo_GroupType, isPublic ? presentationData.strings.Channel_Setup_TypePublic : presentationData.strings.Channel_Setup_TypePrivate))
@ -1128,7 +1133,7 @@ private func groupInfoEntries(account: Account, presentationData: PresentationDa
let memberStatus: GroupInfoMemberStatus
switch participant.participant {
case .creator:
memberStatus = .admin
memberStatus = .owner
case let .member(_, _, adminInfo, _):
if adminInfo != nil {
memberStatus = .admin
@ -1563,7 +1568,7 @@ public func groupInfoController(context: AccountContext, peerId originalPeerId:
}
} else if let channel = groupPeer as? TelegramChannel {
if channel.hasPermission(.inviteMembers) {
if channel.flags.contains(.isCreator) || channel.adminRights != nil {
if channel.flags.contains(.isCreator) || (channel.adminRights != nil && channel.username == nil) {
canCreateInviteLink = true
}
}
@ -2027,7 +2032,9 @@ public func groupInfoController(context: AccountContext, peerId originalPeerId:
|> mapToSignal { address -> Signal<Bool, NoError> in
return updateChannelGeoLocation(postbox: context.account.postbox, network: context.account.network, channelId: peer.id, coordinate: (coordinate.latitude, coordinate.longitude), address: address)
}
|> deliverOnMainQueue).start()
|> deliverOnMainQueue).start(error: { errror in
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), nil)
})
}, sendLiveLocation: { _, _ in }, theme: presentationData.theme, customLocationPicker: true, presentationCompleted: {
clearHighlightImpl?()
})

View File

@ -184,13 +184,14 @@ class ItemListAddressItemNode: ListViewItemNode {
let string = stringWithAppliedEntities(item.text, entities: [], baseColor: baseColor, linkColor: item.theme.list.itemAccentColor, baseFont: textFont, linkFont: textFont, boldFont: textBoldFont, italicFont: textItalicFont, fixedFont: textFixedFont)
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: string, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftOffset - leftInset - rightInset - 98.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
var padding: CGFloat = !item.label.isEmpty ? 39.0 : 20.0
let contentSize = CGSize(width: params.width, height: textLayout.size.height + padding)
let padding: CGFloat = !item.label.isEmpty ? 39.0 : 20.0
let imageSide = min(90.0, contentSize.height - 18.0)
let imageSide = min(90.0, max(46.0, textLayout.size.height + padding - 18.0))
let imageSize = CGSize(width: imageSide, height: imageSide)
let imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(radius: 4.0), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))
let contentSize = CGSize(width: params.width, height: max(textLayout.size.height + padding, imageSize.height + 18.0))
let nodeLayout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
return (nodeLayout, { [weak self] animation in
if let strongSelf = self {
@ -251,6 +252,7 @@ class ItemListAddressItemNode: ListViewItemNode {
if let icon = strongSelf.iconNode.image {
strongSelf.iconNode.frame = CGRect(origin: CGPoint(x: imageFrame.minX + floorToScreenPixels((imageFrame.width - icon.size.width) / 2.0), y: imageFrame.minY + floorToScreenPixels((imageFrame.height - icon.size.height) / 2.0) - 7.0), size: icon.size)
strongSelf.iconNode.isHidden = imageSize.height < 50.0
}
let leftInset: CGFloat

View File

@ -19,15 +19,17 @@ class ItemListSectionHeaderItem: ListViewItem, ItemListItem {
let theme: PresentationTheme
let text: String
let multiline: Bool
let activityIndicator: Bool
let accessoryText: ItemListSectionHeaderAccessoryText?
let sectionId: ItemListSectionId
let isAlwaysPlain: Bool = true
init(theme: PresentationTheme, text: String, multiline: Bool = false, accessoryText: ItemListSectionHeaderAccessoryText? = nil, sectionId: ItemListSectionId) {
init(theme: PresentationTheme, text: String, multiline: Bool = false, activityIndicator: Bool = false, accessoryText: ItemListSectionHeaderAccessoryText? = nil, sectionId: ItemListSectionId) {
self.theme = theme
self.text = text
self.multiline = multiline
self.activityIndicator = activityIndicator
self.accessoryText = accessoryText
self.sectionId = sectionId
}
@ -72,8 +74,11 @@ class ItemListSectionHeaderItem: ListViewItem, ItemListItem {
private let titleFont = Font.regular(14.0)
class ItemListSectionHeaderItemNode: ListViewItemNode {
private var item: ItemListSectionHeaderItem?
private let titleNode: TextNode
private let accessoryTextNode: TextNode
private var activityIndicator: ActivityIndicator?
private let activateArea: AccessibilityAreaNode
@ -102,6 +107,8 @@ class ItemListSectionHeaderItemNode: ListViewItemNode {
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let makeAccessoryTextLayout = TextNode.asyncLayout(self.accessoryTextNode)
let previousItem = self.item
return { item, params, neighbors in
let leftInset: CGFloat = 15.0 + params.leftInset
@ -136,6 +143,8 @@ class ItemListSectionHeaderItemNode: ListViewItemNode {
return (layout, { [weak self] in
if let strongSelf = self {
strongSelf.item = item
let _ = titleApply()
let _ = accessoryApply()
@ -144,6 +153,31 @@ class ItemListSectionHeaderItemNode: ListViewItemNode {
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 7.0), size: titleLayout.size)
strongSelf.accessoryTextNode.frame = CGRect(origin: CGPoint(x: params.width - leftInset - accessoryLayout.size.width, y: 7.0), size: accessoryLayout.size)
if previousItem?.activityIndicator != item.activityIndicator {
if item.activityIndicator {
let activityIndicator: ActivityIndicator
if let currentActivityIndicator = strongSelf.activityIndicator {
activityIndicator = currentActivityIndicator
} else {
activityIndicator = ActivityIndicator(type: .custom(item.theme.list.sectionHeaderTextColor, 18.0, 1.0, false))
strongSelf.addSubnode(activityIndicator)
strongSelf.activityIndicator = activityIndicator
}
activityIndicator.isHidden = false
if previousItem != nil {
activityIndicator.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, removeOnCompletion: false)
}
} else if let activityIndicator = strongSelf.activityIndicator {
activityIndicator.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { finished in
if finished {
activityIndicator.isHidden = true
}
})
}
}
strongSelf.activityIndicator?.frame = CGRect(origin: CGPoint(x: strongSelf.titleNode.frame.maxX + 6.0, y: 7.0 - UIScreenPixel), size: CGSize(width: 18.0, height: 18.0))
}
})
}

View File

@ -1,31 +0,0 @@
import Foundation
import TelegramCore
import Display
import TelegramPresentationData
import TelegramUIPrivateModule
func legacyChannelIntroController(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings) -> ViewController {
let controller = LegacyController(presentation: .custom, theme: theme)
controller.bind(controller: TGChannelIntroController(context: controller.context, getLocalizedString: { string in
guard let string = string else {
return nil
}
if let value = strings.primaryComponent.dict[string] {
return value
} else if let value = strings.secondaryComponent?.dict[string] {
return value
} else {
return string
}
}, theme: TGChannelIntroControllerTheme(backgroundColor: theme.list.plainBackgroundColor, primaryColor: theme.list.itemPrimaryTextColor, secondaryColor: theme.list.itemSecondaryTextColor, accentColor: theme.list.itemAccentColor, backArrowImage: NavigationBarTheme.generateBackArrowImage(color: theme.list.itemAccentColor), introImage: UIImage(bundleImageName: "Chat/Intro/ChannelIntro")), dismiss: { [weak controller] in
if let navigationController = controller?.navigationController as? NavigationController {
_ = navigationController.popViewController(animated: true)
}
}, completion: { [weak controller] in
if let navigationController = controller?.navigationController as? NavigationController {
navigationController.replaceTopController(createChannelController(context: context), animated: true)
}
})!)
return controller
}

View File

@ -10,7 +10,7 @@ public enum NavigateToChatKeepStack {
case never
}
public func navigateToChatController(navigationController: NavigationController, chatController: ChatController? = nil, context: AccountContext, chatLocation: ChatLocation, messageId: MessageId? = nil, botStart: ChatControllerInitialBotStart? = nil, updateTextInputState: ChatTextInputState? = nil, keepStack: NavigateToChatKeepStack = .default, purposefulAction: (() -> Void)? = nil, scrollToEndIfExists: Bool = false, animated: Bool = true, parentGroupId: PeerGroupId? = nil, completion: @escaping () -> Void = {}) {
public func navigateToChatController(navigationController: NavigationController, chatController: ChatController? = nil, context: AccountContext, chatLocation: ChatLocation, messageId: MessageId? = nil, botStart: ChatControllerInitialBotStart? = nil, updateTextInputState: ChatTextInputState? = nil, activateInput: Bool = false, keepStack: NavigateToChatKeepStack = .default, purposefulAction: (() -> Void)? = nil, scrollToEndIfExists: Bool = false, animated: Bool = true, parentGroupId: PeerGroupId? = nil, completion: @escaping () -> Void = {}) {
var found = false
var isFirst = true
for controller in navigationController.viewControllers.reversed() {
@ -35,6 +35,9 @@ public func navigateToChatController(navigationController: NavigationController,
completion()
}
controller.purposefulAction = purposefulAction
if activateInput {
controller.activateInput()
}
found = true
break
}
@ -80,6 +83,9 @@ public func navigateToChatController(navigationController: NavigationController,
navigationController.replaceControllersAndPush(controllers: viewControllers, controller: controller, animated: animated, completion: completion)
}
}
if activateInput {
controller.activateInput()
}
}
navigationController.currentWindow?.forEachController { controller in

View File

@ -141,11 +141,16 @@ private struct ApplicationSpecificNoticeKeys {
private static let botPaymentLiabilityNamespace: Int32 = 1
private static let globalNamespace: Int32 = 2
private static let permissionsNamespace: Int32 = 3
private static let peerReportNamespace: Int32 = 4
static func botPaymentLiabilityNotice(peerId: PeerId) -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: botPaymentLiabilityNamespace), key: noticeKey(peerId: peerId, key: 0))
}
static func irrelevantPeerGeoNotice(peerId: PeerId) -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: peerReportNamespace), key: noticeKey(peerId: peerId, key: 0))
}
static func secretChatInlineBotUsage() -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.secretChatInlineBotUsage.key)
}
@ -200,6 +205,16 @@ private struct ApplicationSpecificNoticeKeys {
}
public struct ApplicationSpecificNotice {
static func irrelevantPeerGeoReportKey(peerId: PeerId) -> NoticeEntryKey {
return ApplicationSpecificNoticeKeys.irrelevantPeerGeoNotice(peerId: peerId)
}
static func setIrrelevantPeerGeoReport(postbox: Postbox, peerId: PeerId) -> Signal<Void, NoError> {
return postbox.transaction { transaction -> Void in
transaction.setNoticeEntry(key: ApplicationSpecificNoticeKeys.irrelevantPeerGeoNotice(peerId: peerId), value: ApplicationSpecificBoolNotice())
}
}
static func getBotPaymentLiability(accountManager: AccountManager, peerId: PeerId) -> Signal<Bool, NoError> {
return accountManager.transaction { transaction -> Bool in
if let _ = transaction.getNotice(ApplicationSpecificNoticeKeys.botPaymentLiabilityNotice(peerId: peerId)) as? ApplicationSpecificBoolNotice {

View File

@ -253,9 +253,7 @@ final class PeerChannelMemberCategoriesContextsManager {
strongSelf.impl.with { impl in
for (contextPeerId, context) in impl.contexts {
if peerId == contextPeerId {
for (previous, updated) in results {
context.replayUpdates([(previous, updated, nil)])
}
context.replayUpdates(results.map { ($0.0, $0.1, nil) })
}
}
}

View File

@ -56,11 +56,11 @@ private enum PeersNearbySection: Int32 {
private enum PeersNearbyEntry: ItemListNodeEntry {
case header(PresentationTheme, String)
case usersHeader(PresentationTheme, String)
case empty(PresentationTheme, String, Bool)
case usersHeader(PresentationTheme, String, Bool)
case empty(PresentationTheme, String)
case user(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PeerNearbyEntry)
case groupsHeader(PresentationTheme, String)
case groupsHeader(PresentationTheme, String, Bool)
case createGroup(PresentationTheme, String, Double?, Double?, String?)
case group(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PeerNearbyEntry)
@ -111,14 +111,14 @@ private enum PeersNearbyEntry: ItemListNodeEntry {
} else {
return false
}
case let .usersHeader(lhsTheme, lhsText):
if case let .usersHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
case let .usersHeader(lhsTheme, lhsText, lhsLoading):
if case let .usersHeader(rhsTheme, rhsText, rhsLoading) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsLoading == rhsLoading {
return true
} else {
return false
}
case let .empty(lhsTheme, lhsText, lhsLoading):
if case let .empty(rhsTheme, rhsText, rhsLoading) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsLoading == rhsLoading {
case let .empty(lhsTheme, lhsText):
if case let .empty(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
@ -129,8 +129,8 @@ private enum PeersNearbyEntry: ItemListNodeEntry {
} else {
return false
}
case let .groupsHeader(lhsTheme, lhsText):
if case let .groupsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
case let .groupsHeader(lhsTheme, lhsText, lhsLoading):
if case let .groupsHeader(rhsTheme, rhsText, rhsLoading) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsLoading == rhsLoading {
return true
} else {
return false
@ -181,16 +181,16 @@ private enum PeersNearbyEntry: ItemListNodeEntry {
switch self {
case let .header(theme, text):
return PeersNearbyHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .usersHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .empty(theme, text, _):
case let .usersHeader(theme, text, loading):
return ItemListSectionHeaderItem(theme: theme, text: text, activityIndicator: loading, sectionId: self.section)
case let .empty(theme, text):
return ItemListPlaceholderItem(theme: theme, text: text, sectionId: self.section, style: .blocks)
case let .user(_, theme, strings, dateTimeFormat, nameDisplayOrder, peer):
return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, account: arguments.context.account, peer: peer.peer.0, aliasHandling: .standard, nameColor: .primary, nameStyle: .distinctBold, presence: nil, text: .text(strings.Map_DistanceAway(stringForDistance(peer.distance)).0), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: {
arguments.openChat(peer.peer.0)
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, toggleUpdated: nil, hasTopGroupInset: false, tag: nil)
case let .groupsHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .groupsHeader(theme, text, loading):
return ItemListSectionHeaderItem(theme: theme, text: text, activityIndicator: loading, sectionId: self.section)
case let .createGroup(theme, title, latitude, longitude, address):
return ItemListPeerActionItem(theme: theme, icon: PresentationResourcesItemList.createGroupIcon(theme), title: title, alwaysPlain: false, sectionId: self.section, editing: false, action: {
if let latitude = latitude, let longitude = longitude {
@ -200,7 +200,7 @@ private enum PeersNearbyEntry: ItemListNodeEntry {
case let .group(_, theme, strings, dateTimeFormat, nameDisplayOrder, peer):
var text: ItemListPeerItemText
if let cachedData = peer.peer.1 as? CachedChannelData, let memberCount = cachedData.participantsSummary.memberCount {
text = .text("\(strings.Map_DistanceAway(stringForDistance(peer.distance)).0), \(strings.Conversation_StatusMembers(memberCount))")
text = .text("\(strings.Map_DistanceAway(stringForDistance(peer.distance)).0), \(memberCount > 0 ? strings.Conversation_StatusMembers(memberCount) : strings.PeopleNearby_NoMembers)")
} else {
text = .text(strings.Map_DistanceAway(stringForDistance(peer.distance)).0)
}
@ -245,11 +245,11 @@ private struct PeersNearbyData: Equatable {
}
}
private func peersNearbyControllerEntries(data: PeersNearbyData?, presentationData: PresentationData) -> [PeersNearbyEntry] {
private func peersNearbyControllerEntries(data: PeersNearbyData?, presentationData: PresentationData, displayLoading: Bool) -> [PeersNearbyEntry] {
var entries: [PeersNearbyEntry] = []
entries.append(.header(presentationData.theme, presentationData.strings.PeopleNearby_Description))
entries.append(.usersHeader(presentationData.theme, presentationData.strings.PeopleNearby_Users.uppercased()))
entries.append(.usersHeader(presentationData.theme, presentationData.strings.PeopleNearby_Users.uppercased(), displayLoading && data == nil))
if let data = data, !data.users.isEmpty {
var i: Int32 = 0
for user in data.users {
@ -257,10 +257,10 @@ private func peersNearbyControllerEntries(data: PeersNearbyData?, presentationDa
i += 1
}
} else {
entries.append(.empty(presentationData.theme, presentationData.strings.PeopleNearby_UsersEmpty, data == nil))
entries.append(.empty(presentationData.theme, presentationData.strings.PeopleNearby_UsersEmpty))
}
entries.append(.groupsHeader(presentationData.theme, presentationData.strings.PeopleNearby_Groups.uppercased()))
entries.append(.groupsHeader(presentationData.theme, presentationData.strings.PeopleNearby_Groups.uppercased(), displayLoading && data == nil))
entries.append(.createGroup(presentationData.theme, presentationData.strings.PeopleNearby_CreateGroup, data?.latitude, data?.longitude, data?.address))
if let data = data, !data.groups.isEmpty {
var i: Int32 = 0
@ -271,7 +271,6 @@ private func peersNearbyControllerEntries(data: PeersNearbyData?, presentationDa
}
if let data = data, !data.channels.isEmpty {
entries.append(.channelsHeader(presentationData.theme, presentationData.strings.PeopleNearby_Channels.uppercased()))
var i: Int32 = 0
for channel in data.channels {
entries.append(.channel(i, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, channel))
@ -290,6 +289,8 @@ public func peersNearbyController(context: AccountContext) -> ViewController {
var navigateToChatImpl: ((Peer) -> Void)?
let actionsDisposable = DisposableSet()
let checkCreationAvailabilityDisposable = MetaDisposable()
actionsDisposable.add(checkCreationAvailabilityDisposable)
let dataPromise = Promise<PeersNearbyData?>(nil)
let addressPromise = Promise<String?>(nil)
@ -298,40 +299,66 @@ public func peersNearbyController(context: AccountContext) -> ViewController {
navigateToChatImpl?(peer)
}, openCreateGroup: { latitude, longitude, address in
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
var cancelImpl: (() -> Void)?
let progressSignal = Signal<Never, NoError> { subscriber in
let controller = OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .loading(cancelled: {
cancelImpl?()
}))
presentControllerImpl?(controller, nil)
return ActionDisposable { [weak controller] in
Queue.mainQueue().async() {
controller?.dismiss()
}
}
}
|> runOn(Queue.mainQueue())
|> delay(0.5, queue: Queue.mainQueue())
let progressDisposable = progressSignal.start()
cancelImpl = {
checkCreationAvailabilityDisposable.set(nil)
}
checkCreationAvailabilityDisposable.set((checkPublicChannelCreationAvailability(account: context.account, location: true)
|> afterDisposed {
Queue.mainQueue().async {
progressDisposable.dispose()
}
}
|> deliverOnMainQueue).start(next: { available in
if available {
let controller = PermissionController(context: context, splashScreen: true)
controller.setState(.custom(icon: PermissionControllerCustomIcon(light: UIImage(bundleImageName: "Location/LocalGroupLightIcon"), dark: UIImage(bundleImageName: "Location/LocalGroupDarkIcon")), title: presentationData.strings.LocalGroup_Title, subtitle: address, text: presentationData.strings.LocalGroup_Text, buttonTitle: presentationData.strings.LocalGroup_ButtonTitle, footerText: presentationData.strings.LocalGroup_IrrelevantWarning), animated: false)
controller.proceed = { result in
replaceTopControllerImpl?(createGroupController(context: context, peerIds: [], mode: .locatedGroup(latitude: latitude, longitude: longitude, address: address)))
}
pushControllerImpl?(controller)
} else {
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.CreateGroup_ErrorLocatedGroupsTooMuch, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
}
}))
})
let dataSignal: Signal<PeersNearbyData?, Void> = currentLocationManagerCoordinate(manager: context.sharedContext.locationManager!, timeout: 5.0)
|> introduceError(Void.self)
|> mapToSignal { coordinate -> Signal<PeersNearbyData?, Void> in
let dataSignal: Signal<PeersNearbyData?, NoError> = currentLocationManagerCoordinate(manager: context.sharedContext.locationManager!, timeout: 5.0)
|> mapToSignal { coordinate -> Signal<PeersNearbyData?, NoError> in
guard let coordinate = coordinate else {
return .single(nil)
}
print("TTTTT: \(CFAbsoluteTimeGetCurrent())")
return Signal { subscriber in
let peersNearbyContext = PeersNearbyContext(network: context.account.network, accountStateManager: context.account.stateManager, coordinate: (latitude: coordinate.latitude, longitude: coordinate.longitude))
let peersNearby: Signal<PeersNearbyData?, Void> = combineLatest(peersNearbyContext.get(), addressPromise.get())
|> introduceError(Void.self)
|> mapToSignal { peersNearby, address -> Signal<([PeerNearby]?, String?), Void> in
let peersNearby: Signal<PeersNearbyData?, NoError> = combineLatest(peersNearbyContext.get(), addressPromise.get())
|> mapToSignal { peersNearby, address -> Signal<([PeerNearby]?, String?), NoError> in
if let address = address {
return .single((peersNearby, address))
} else {
return reverseGeocodeLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
|> introduceError(Void.self)
|> map { placemark in
return (peersNearby, placemark?.fullAddress)
}
}
}
|> mapToSignal { peersNearby, address -> Signal<PeersNearbyData?, Void> in
|> mapToSignal { peersNearby, address -> Signal<PeersNearbyData?, NoError> in
guard let peersNearby = peersNearby else {
return .single(nil)
}
@ -350,7 +377,6 @@ public func peersNearbyController(context: AccountContext) -> ViewController {
}
return PeersNearbyData(latitude: coordinate.latitude, longitude: coordinate.longitude, address: address, users: users, groups: groups, channels: [])
}
|> introduceError(Void.self)
}
let disposable = peersNearby.start(next: { data in
@ -363,25 +389,18 @@ public func peersNearbyController(context: AccountContext) -> ViewController {
}
}
}
let errorSignal: Signal<Void, Void> = .single(Void()) |> then( Signal.fail(Void()) |> suspendAwareDelay(25.0, queue: Queue.concurrentDefaultQueue()) )
let combinedSignal = combineLatest(dataSignal, errorSignal) |> map { data, _ -> PeersNearbyData? in
return data
}
|> restartIfError
|> `catch` { _ -> Signal<PeersNearbyData?, NoError> in
return .single(nil)
} |> filter { value in
return value != nil
}
dataPromise.set(.single(nil) |> then(combinedSignal))
dataPromise.set(dataSignal)
let previousData = Atomic<PeersNearbyData?>(value: nil)
let displayLoading: Signal<Bool, NoError> = .single(false)
|> then(
.single(true)
|> delay(1.0, queue: Queue.mainQueue())
)
let signal = combineLatest(context.sharedContext.presentationData, dataPromise.get())
let signal = combineLatest(context.sharedContext.presentationData, dataPromise.get(), displayLoading)
|> deliverOnMainQueue
|> map { presentationData, data -> (ItemListControllerState, (ItemListNodeState<PeersNearbyEntry>, PeersNearbyEntry.ItemGenerationArguments)) in
|> map { presentationData, data, displayLoading -> (ItemListControllerState, (ItemListNodeState<PeersNearbyEntry>, PeersNearbyEntry.ItemGenerationArguments)) in
let previous = previousData.swap(data)
var crossfade = false
@ -393,7 +412,7 @@ public func peersNearbyController(context: AccountContext) -> ViewController {
}
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.PeopleNearby_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
let listState = ItemListNodeState(entries: peersNearbyControllerEntries(data: data, presentationData: presentationData), style: .blocks, emptyStateItem: nil, crossfadeState: crossfade, animateChanges: !crossfade, userInteractionEnabled: true)
let listState = ItemListNodeState(entries: peersNearbyControllerEntries(data: data, presentationData: presentationData, displayLoading: displayLoading), style: .blocks, emptyStateItem: nil, crossfadeState: crossfade, animateChanges: !crossfade, userInteractionEnabled: true)
return (controllerState, (listState, arguments))
}

View File

@ -4,6 +4,20 @@ import Display
import AsyncDisplayKit
import TelegramPresentationData
enum PermissionContentIcon {
case image(UIImage?)
case icon(PermissionControllerCustomIcon)
func imageForTheme(_ theme: PresentationTheme) -> UIImage? {
switch self {
case let .image(image):
return image
case let .icon(icon):
return theme.overallDarkAppearance ? (icon.dark ?? icon.light) : icon.light
}
}
}
final class PermissionContentNode: ASDisplayNode {
private var theme: PresentationTheme
let kind: Int32
@ -17,19 +31,25 @@ final class PermissionContentNode: ASDisplayNode {
private let footerNode: ImmediateTextNode
private let privacyPolicyButton: HighlightableButtonNode
private let icon: PermissionContentIcon
private var title: String
private var text: String
var buttonAction: (() -> Void)?
var openPrivacyPolicy: (() -> Void)?
init(theme: PresentationTheme, strings: PresentationStrings, kind: Int32, icon: UIImage?, title: String, subtitle: String? = nil, text: String, buttonTitle: String, footerText: String? = nil, buttonAction: @escaping () -> Void, openPrivacyPolicy: (() -> Void)?) {
var validLayout: (CGSize, UIEdgeInsets)?
init(theme: PresentationTheme, strings: PresentationStrings, kind: Int32, icon: PermissionContentIcon, title: String, subtitle: String? = nil, text: String, buttonTitle: String, footerText: String? = nil, buttonAction: @escaping () -> Void, openPrivacyPolicy: (() -> Void)?) {
self.theme = theme
self.kind = kind
self.buttonAction = buttonAction
self.openPrivacyPolicy = openPrivacyPolicy
self.icon = icon
self.title = title
self.text = text
self.iconNode = ASImageNode()
self.iconNode.isLayerBacked = true
@ -71,7 +91,7 @@ final class PermissionContentNode: ASDisplayNode {
super.init()
self.iconNode.image = icon
self.iconNode.image = icon.imageForTheme(theme)
self.title = title
let body = MarkdownAttributeSet(font: Font.regular(16.0), textColor: theme.list.itemPrimaryTextColor)
@ -107,11 +127,39 @@ final class PermissionContentNode: ASDisplayNode {
self.privacyPolicyButton.addTarget(self, action: #selector(self.privacyPolicyPressed), forControlEvents: .touchUpInside)
}
func updatePresentationData(_ presentationData: PresentationData) {
let theme = presentationData.theme
self.theme = theme
self.iconNode.image = self.icon.imageForTheme(theme)
let body = MarkdownAttributeSet(font: Font.regular(16.0), textColor: theme.list.itemPrimaryTextColor)
let link = MarkdownAttributeSet(font: Font.regular(16.0), textColor: theme.list.itemAccentColor, additionalAttributes: [TelegramTextAttributes.URL: ""])
self.textNode.attributedText = parseMarkdownIntoAttributedString(self.text.replacingOccurrences(of: "]", with: "]()"), attributes: MarkdownAttributes(body: body, bold: body, link: link, linkAttribute: { _ in nil }), textAlignment: .center)
if let subtitle = self.subtitleNode.attributedText?.string {
self.subtitleNode.attributedText = NSAttributedString(string: subtitle, font: Font.regular(13.0), textColor: theme.list.freeTextColor, paragraphAlignment: .center)
}
if let footerText = self.footerNode.attributedText?.string {
self.footerNode.attributedText = NSAttributedString(string: footerText, font: Font.regular(13.0), textColor: theme.list.freeTextColor, paragraphAlignment: .center)
}
if let privacyPolicyTitle = self.privacyPolicyButton.attributedTitle(for: .normal)?.string {
self.privacyPolicyButton.setTitle(privacyPolicyTitle, with: Font.regular(16.0), with: theme.list.itemAccentColor, for: .normal)
}
if let validLayout = self.validLayout {
self.updateLayout(size: validLayout.0, insets: validLayout.1, transition: .immediate)
}
}
@objc func privacyPolicyPressed() {
self.openPrivacyPolicy?()
}
func updateLayout(size: CGSize, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) {
self.validLayout = (size, insets)
let sidePadding: CGFloat
let fontSize: CGFloat
if min(size.width, size.height) > 330.0 {
@ -159,7 +207,12 @@ final class PermissionContentNode: ASDisplayNode {
let privacySpacing: CGFloat = max(30.0 + privacyButtonSize.height, (availableHeight - titleTextSpacing - buttonSpacing - imageSize.height - imageSpacing) / 2.0)
let contentOrigin = insets.top + floor((size.height - insets.top - insets.bottom - contentHeight) / 2.0) - availableHeight * 0.05
var verticalOffset: CGFloat = 0.0
if size.height >= 568.0 {
verticalOffset = availableHeight * 0.05
}
let contentOrigin = insets.top + floor((size.height - insets.top - insets.bottom - contentHeight) / 2.0) - verticalOffset
let iconFrame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: contentOrigin), size: imageSize)
let nearbyIconFrame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: contentOrigin), size: imageSize)
let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: iconFrame.maxY + imageSpacing), size: titleSize)
@ -188,5 +241,7 @@ final class PermissionContentNode: ASDisplayNode {
transition.updateFrame(node: self.actionButton, frame: buttonFrame)
transition.updateFrame(node: self.footerNode, frame: footerFrame)
transition.updateFrame(node: self.privacyPolicyButton, frame: privacyButtonFrame)
self.footerNode.isHidden = size.height < 568.0
}
}

View File

@ -84,17 +84,14 @@ final class PermissionControllerNode: ASDisplayNode {
return UITracingLayerView()
})
self.applyPresentationData()
self.updatePresentationData(self.presentationData)
}
func updatePresentationData(_ presentationData: PresentationData) {
self.presentationData = presentationData
self.applyPresentationData()
}
private func applyPresentationData() {
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
self.contentNode?.updatePresentationData(self.presentationData)
}
func animateIn(completion: (() -> Void)? = nil) {
@ -207,7 +204,7 @@ final class PermissionControllerNode: ASDisplayNode {
hasPrivacyPolicy = false
}
let contentNode = PermissionContentNode(theme: self.presentationData.theme, strings: self.presentationData.strings, kind: dataState.kind.rawValue, icon: icon, title: title, text: text, buttonTitle: buttonTitle, buttonAction: { [weak self] in
let contentNode = PermissionContentNode(theme: self.presentationData.theme, strings: self.presentationData.strings, kind: dataState.kind.rawValue, icon: .image(icon), title: title, text: text, buttonTitle: buttonTitle, buttonAction: { [weak self] in
self?.allow?()
}, openPrivacyPolicy: hasPrivacyPolicy ? self.openPrivacyPolicy : nil)
self.insertSubnode(contentNode, at: 0)
@ -237,14 +234,7 @@ final class PermissionControllerNode: ASDisplayNode {
transition.updateFrame(node: contentNode, frame: contentFrame)
contentNode.updateLayout(size: contentFrame.size, insets: insets, transition: transition)
} else {
let iconImage: UIImage?
if self.presentationData.theme.overallDarkAppearance {
iconImage = icon.dark ?? icon.light
} else {
iconImage = icon.light
}
let contentNode = PermissionContentNode(theme: self.presentationData.theme, strings: self.presentationData.strings, kind: 0, icon: iconImage, title: title, subtitle: subtitle, text: text, buttonTitle: buttonTitle, footerText: footerText, buttonAction: { [weak self] in
let contentNode = PermissionContentNode(theme: self.presentationData.theme, strings: self.presentationData.strings, kind: 0, icon: .icon(icon), title: title, subtitle: subtitle, text: text, buttonTitle: buttonTitle, footerText: footerText, buttonAction: { [weak self] in
self?.allow?()
}, openPrivacyPolicy: nil)
self.insertSubnode(contentNode, at: 0)

View File

@ -68,6 +68,14 @@ final class SolidRoundedButtonNode: ASDisplayNode {
}
}
func updateTheme(_ theme: PresentationTheme) {
self.theme = theme
self.buttonBackgroundNode.image = generateStretchableFilledCircleImage(radius: cornerRadius, color: theme.list.itemCheckColors.fillColor)
self.buttonGlossNode.color = theme.list.itemCheckColors.foregroundColor
self.labelNode.attributedText = NSAttributedString(string: self.title ?? "", font: Font.medium(17.0), textColor: theme.list.itemCheckColors.foregroundColor)
}
func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
self.validLayout = width

View File

@ -267,7 +267,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
let query = text.trimmingCharacters(in: .whitespacesAndNewlines)
if query.isSingleEmoji {
signals = .single([searchStickers(account: account, query: text)
signals = .single([searchStickers(account: account, query: text.trimmedEmoji)
|> take(1)
|> map { (nil, $0) }])
} else if query.count > 1, let languageCode = languageCode, !languageCode.isEmpty && languageCode != "emoji" {
@ -290,7 +290,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
var signals: [Signal<(String?, [FoundStickerItem]), NoError>] = []
let emoticons = keywords.flatMap { $0.emoticons }
for emoji in emoticons {
signals.append(searchStickers(account: self.context.account, query: emoji)
signals.append(searchStickers(account: self.context.account, query: emoji.trimmedEmoji)
|> take(1)
|> map { (emoji, $0) })
}

View File

@ -34,6 +34,8 @@ func chatInputStateStringWithAppliedEntities(_ text: String, entities: [MessageT
string.addAttribute(ChatTextInputAttributes.textUrl, value: ChatTextInputTextUrlAttribute(url: url), range: range)
case .Code, .Pre:
string.addAttribute(ChatTextInputAttributes.monospace, value: true as NSNumber, range: range)
case .Strikethrough:
string.addAttribute(ChatTextInputAttributes.strikethrough, value: true as NSNumber, range: range)
default:
break
}
@ -121,6 +123,8 @@ func stringWithAppliedEntities(_ text: String, entities: [MessageTextEntity], ba
nsString = text as NSString
}
string.addAttribute(NSAttributedStringKey(rawValue: TelegramTextAttributes.PeerTextMention), value: nsString!.substring(with: range), range: range)
case .Strikethrough:
string.addAttribute(NSAttributedStringKey.strikethroughStyle, value: NSUnderlineStyle.styleSingle.rawValue as NSNumber, range: range)
case let .TextMention(peerId):
string.addAttribute(NSAttributedStringKey.foregroundColor, value: linkColor, range: range)
if underlineLinks && underlineAllLinks {

View File

@ -1,20 +0,0 @@
#import <LegacyComponents/LegacyComponents.h>
@interface TGChannelIntroControllerTheme : NSObject
@property (nonatomic, strong, readonly) UIColor *backgroundColor;
@property (nonatomic, strong, readonly) UIColor *primaryColor;
@property (nonatomic, strong, readonly) UIColor *secondaryColor;
@property (nonatomic, strong, readonly) UIColor *accentColor;
@property (nonatomic, strong, readonly) UIImage *backArrowImage;
@property (nonatomic, strong, readonly) UIImage *introImage;
- (instancetype)initWithBackgroundColor:(UIColor *)backgroundColor primaryColor:(UIColor *)primaryColor secondaryColor:(UIColor *)secondaryColor accentColor:(UIColor *)accentColor backArrowImage:(UIImage *)backArrowImage introImage:(UIImage *)introImage;
@end
@interface TGChannelIntroController : TGViewController
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context getLocalizedString:(NSString *(^)(NSString *))getLocalizedString theme:(TGChannelIntroControllerTheme *)theme dismiss:(void (^)(void))dismiss completion:(void (^)(void))completion;
@end

View File

@ -1,266 +0,0 @@
#import "TGChannelIntroController.h"
#import <LegacyComponents/LegacyComponents.h>
#import <LegacyComponents/TGModernButton.h>
@implementation TGChannelIntroControllerTheme
- (instancetype)initWithBackgroundColor:(UIColor *)backgroundColor primaryColor:(UIColor *)primaryColor secondaryColor:(UIColor *)secondaryColor accentColor:(UIColor *)accentColor backArrowImage:(UIImage *)backArrowImage introImage:(UIImage *)introImage {
self = [super init];
if (self != nil) {
_backgroundColor = backgroundColor;
_primaryColor = primaryColor;
_secondaryColor = secondaryColor;
_accentColor = accentColor;
_backArrowImage = backArrowImage;
_introImage = introImage;
}
return self;
}
@end
@interface TGChannelIntroController ()
{
TGModernButton *_backButton;
UIImageView *_phoneImageView;
UILabel *_titleLabel;
UILabel *_descriptionLabel;
TGModernButton *_createButton;
TGChannelIntroControllerTheme *_theme;
NSString *(^_getLocalizedString)(NSString *);
void (^_dismiss)(void);
void (^_completion)(void);
}
@end
@implementation TGChannelIntroController
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context getLocalizedString:(NSString *(^)(NSString *))getLocalizedString theme:(TGChannelIntroControllerTheme *)theme dismiss:(void (^)(void))dismiss completion:(void (^)(void))completion {
self = [super initWithContext:context];
if (self != nil) {
_getLocalizedString = [getLocalizedString copy];
_theme = theme;
_dismiss = [dismiss copy];
_completion = [completion copy];
}
return self;
}
- (void)loadView
{
[super loadView];
self.view.backgroundColor = _theme.backgroundColor;
UIImage *image = _theme.backArrowImage;
UIGraphicsBeginImageContextWithOptions(image.size, false, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
[image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
CGContextSetBlendMode (context, kCGBlendModeSourceAtop);
CGContextSetFillColorWithColor(context, _theme.accentColor.CGColor);
CGContextFillRect(context, CGRectMake(0, 0, image.size.width, image.size.height));
UIImage *arrowImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
_backButton = [[TGModernButton alloc] initWithFrame:CGRectZero];
_backButton.exclusiveTouch = true;
_backButton.titleLabel.font = TGSystemFontOfSize(17);
[_backButton setTitle:_getLocalizedString(@"Common.Back") forState:UIControlStateNormal];
[_backButton setTitleColor:_theme.accentColor];
[_backButton addTarget:self action:@selector(backButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:_backButton];
UIImageView *arrowView = [[UIImageView alloc] initWithFrame:CGRectMake(-19, 5.5f, 13, 22)];
arrowView.image = arrowImage;
[_backButton addSubview:arrowView];
_phoneImageView = [[UIImageView alloc] initWithImage:_theme.introImage];
_phoneImageView.frame = CGRectMake(0, 0, 154, 220);
[self.view addSubview:_phoneImageView];
_titleLabel = [[UILabel alloc] init];
_titleLabel.backgroundColor = [UIColor clearColor];
_titleLabel.font = TGSystemFontOfSize(21);
_titleLabel.textColor = _theme.primaryColor;
_titleLabel.textAlignment = NSTextAlignmentCenter;
_titleLabel.text = _getLocalizedString(@"ChannelIntro.Title");
[self.view addSubview:_titleLabel];
_descriptionLabel = [[UILabel alloc] init];
_descriptionLabel.backgroundColor = [UIColor clearColor];
_descriptionLabel.numberOfLines = 0;
_descriptionLabel.textAlignment = NSTextAlignmentCenter;
[self.view addSubview:_descriptionLabel];
NSString *description = _getLocalizedString(@"ChannelIntro.Text");
NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:description];
NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
style.lineSpacing = 2;
style.alignment = NSTextAlignmentCenter;
[attrString addAttribute:NSParagraphStyleAttributeName value:style range:NSMakeRange(0, description.length)];
[attrString addAttribute:NSForegroundColorAttributeName value:_theme.secondaryColor range:NSMakeRange(0, description.length)];
[attrString addAttribute:NSFontAttributeName value:TGSystemFontOfSize(16) range:NSMakeRange(0, description.length)];
_descriptionLabel.attributedText = attrString;
_createButton = [[TGModernButton alloc] init];
_createButton.exclusiveTouch = true;
_createButton.backgroundColor = [UIColor clearColor];
_createButton.titleLabel.font = TGSystemFontOfSize(21);
[_createButton setTitleColor:_theme.accentColor];
[_createButton setTitle:_getLocalizedString(@"ChannelIntro.CreateChannel") forState:UIControlStateNormal];
[_createButton addTarget:self action:@selector(buttonPressed) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:_createButton];
}
- (bool)navigationBarShouldBeHidden
{
return true;
}
- (void)backButtonPressed
{
if (_dismiss != nil)
_dismiss();
else
[self.navigationController popViewControllerAnimated:true];
}
- (void)buttonPressed
{
_completion();
}
- (void)viewWillLayoutSubviews
{
CGRect bounds = self.context.fullscreenBounds;
UIInterfaceOrientation orientation = UIInterfaceOrientationPortrait;
if (bounds.size.width > bounds.size.height) {
orientation = UIInterfaceOrientationLandscapeLeft;
}
UIEdgeInsets safeAreaInset = [self calculatedSafeAreaInset];
int iosVersion = [[[UIDevice currentDevice] systemVersion] intValue];
if (UIEdgeInsetsEqualToEdgeInsets(safeAreaInset, UIEdgeInsetsZero) && (iosVersion < 11 || TGIsPad() || UIInterfaceOrientationIsPortrait(orientation)))
safeAreaInset.top = 20.0f;
[_backButton sizeToFit];
_backButton.frame = CGRectMake(27 + safeAreaInset.left, 5.0f + TGScreenPixel + safeAreaInset.top, ceil(_backButton.frame.size.width), ceil(_backButton.frame.size.height));
[_titleLabel sizeToFit];
[_descriptionLabel sizeToFit];
[_createButton sizeToFit];
int screenSize = (int)TGScreenSize().height;
CGFloat titleY = 0;
CGFloat imageY = 0;
CGFloat descY = 0;
CGFloat buttonY = 0;
if (UIInterfaceOrientationIsPortrait(orientation))
{
switch (screenSize)
{
case 812:
case 896:
titleY = 445 + 44;
imageY = 141 + 44;
descY = 490 + 44;
buttonY = 610 + 44;
break;
case 736:
titleY = 445;
imageY = 141;
descY = 490;
buttonY = 610;
break;
case 667:
titleY = 407;
imageY = 120;
descY = 448;
buttonY = 558;
break;
case 568:
titleY = 354;
imageY = 87;
descY = 397;
buttonY = 496;
break;
default:
titleY = 307;
imageY = 60;
descY = 344;
buttonY = 424;
break;
}
_phoneImageView.frame = CGRectMake((self.view.frame.size.width - _phoneImageView.frame.size.width) / 2, imageY, _phoneImageView.frame.size.width, _phoneImageView.frame.size.height);
_titleLabel.frame = CGRectMake((self.view.frame.size.width - _titleLabel.frame.size.width) / 2, titleY, ceil(_titleLabel.frame.size.width), ceil(_titleLabel.frame.size.height));
_descriptionLabel.frame = CGRectMake((self.view.frame.size.width - _descriptionLabel.frame.size.width) / 2, descY, ceil(_descriptionLabel.frame.size.width), ceil(_descriptionLabel.frame.size.height));
_createButton.frame = CGRectMake((self.view.frame.size.width - _createButton.frame.size.width) / 2, buttonY, ceil(_createButton.frame.size.width), ceil(_createButton.frame.size.height));
}
else
{
CGFloat leftX = 0;
CGFloat rightX = 0;
switch (screenSize)
{
case 812:
leftX = 190 + 44;
rightX = 448 + 44;
titleY = 103;
descY = 148;
buttonY = 237;
break;
case 736:
leftX = 209;
rightX = 504;
titleY = 115;
descY = 156;
buttonY = 278;
break;
case 667:
leftX = 190;
rightX = 448;
titleY = 103;
descY = 148;
buttonY = 237;
break;
case 568:
leftX = 164;
rightX = 388;
titleY = 78;
descY = 121;
buttonY = 217;
break;
default:
leftX = 125;
rightX = 328;
titleY = 78;
descY = 121;
buttonY = 219;
break;
}
_phoneImageView.frame = CGRectMake(leftX - _phoneImageView.frame.size.width / 2, (self.view.frame.size.height - _phoneImageView.frame.size.height) / 2, _phoneImageView.frame.size.width, _phoneImageView.frame.size.height);
_titleLabel.frame = CGRectMake(rightX - _titleLabel.frame.size.width / 2, titleY, ceil(_titleLabel.frame.size.width), ceil(_titleLabel.frame.size.height));
_descriptionLabel.frame = CGRectMake(rightX - _descriptionLabel.frame.size.width / 2, descY, ceil(_descriptionLabel.frame.size.width), ceil(_descriptionLabel.frame.size.height));
_createButton.frame = CGRectMake(rightX - _createButton.frame.size.width / 2, buttonY, ceil(_createButton.frame.size.width), ceil(_createButton.frame.size.height));
}
}
@end

View File

@ -12,7 +12,6 @@ module TelegramUIPrivateModule {
header "../DeviceProximityManager.h"
header "../RaiseToListenActivator.h"
header "../TGMimeTypeMap.h"
header "../TGChannelIntroController.h"
header "../Bridge Audio/TGBridgeAudioDecoder.h"
header "../Bridge Audio/TGBridgeAudioEncoder.h"
header "../TGContactModel.h"

View File

@ -190,16 +190,19 @@ class ThemeSettingsAppIconItemNode: ListViewItemNode, ItemListItemNode {
self.scrollNode.view.showsHorizontalScrollIndicator = false
}
private func scrollToNode(_ node: ThemeSettingsAppIconNode, animated: Bool) {
let bounds = self.scrollNode.view.bounds
let frame = node.frame.insetBy(dx: -48.0, dy: 0.0)
if frame.minX < bounds.minX || frame.maxX > bounds.maxX {
self.scrollNode.view.scrollRectToVisible(frame, animated: animated)
}
}
func asyncLayout() -> (_ item: ThemeSettingsAppIconItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
let currentItem = self.item
return { item, params, neighbors in
var themeUpdated = false
if currentItem?.theme !== item.theme {
themeUpdated = true
}
let contentSize: CGSize
let insets: UIEdgeInsets
let separatorHeight = UIScreenPixel
@ -255,6 +258,9 @@ class ThemeSettingsAppIconItemNode: ListViewItemNode, ItemListItemNode {
let nodeSize = CGSize(width: 80.0, height: 112.0)
var nodeOffset = nodeInset
var updated = false
var selectedNode: ThemeSettingsAppIconNode?
var i = 0
for icon in item.icons {
let imageNode: ThemeSettingsAppIconNode
@ -264,10 +270,14 @@ class ThemeSettingsAppIconItemNode: ListViewItemNode, ItemListItemNode {
imageNode = ThemeSettingsAppIconNode()
strongSelf.nodes.append(imageNode)
strongSelf.scrollNode.addSubnode(imageNode)
updated = true
}
if let image = UIImage(named: icon.imageName, in: Bundle.main, compatibleWith: nil) {
let selected = icon.name == item.currentIconName
if selected {
selectedNode = imageNode
}
var name = "Icon"
var bordered = true
@ -292,8 +302,11 @@ class ThemeSettingsAppIconItemNode: ListViewItemNode, ItemListItemNode {
break
}
imageNode.setup(theme: item.theme, icon: image, title: NSAttributedString(string: name, font: textFont, textColor: selected ? item.theme.list.itemAccentColor : item.theme.list.itemPrimaryTextColor, paragraphAlignment: .center), bordered: bordered, selected: selected, action: {
imageNode.setup(theme: item.theme, icon: image, title: NSAttributedString(string: name, font: textFont, textColor: selected ? item.theme.list.itemAccentColor : item.theme.list.itemPrimaryTextColor, paragraphAlignment: .center), bordered: bordered, selected: selected, action: { [weak self, weak imageNode] in
item.updated(icon.name)
if let imageNode = imageNode {
self?.scrollToNode(imageNode, animated: true)
}
})
}
@ -309,6 +322,10 @@ class ThemeSettingsAppIconItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.scrollNode.view.contentSize = contentSize
}
}
if updated, let selectedNode = selectedNode {
strongSelf.scrollToNode(selectedNode, animated: false)
}
}
})
}

View File

@ -240,15 +240,19 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode {
self.scrollNode.view.showsHorizontalScrollIndicator = false
}
private func scrollToNode(_ node: ThemeSettingsThemeItemIconNode, animated: Bool) {
let bounds = self.scrollNode.view.bounds
let frame = node.frame.insetBy(dx: -48.0, dy: 0.0)
if frame.minX < bounds.minX || frame.maxX > bounds.maxX {
self.scrollNode.view.scrollRectToVisible(frame, animated: animated)
}
}
func asyncLayout() -> (_ item: ThemeSettingsThemeItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
let currentItem = self.item
return { item, params, neighbors in
var themeUpdated = false
if currentItem?.theme !== item.theme {
themeUpdated = true
}
let contentSize: CGSize
let insets: UIEdgeInsets
let separatorHeight = UIScreenPixel
@ -304,6 +308,9 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode {
let nodeSize = CGSize(width: 116.0, height: 112.0)
var nodeOffset = nodeInset
var updated = false
var selectedNode: ThemeSettingsThemeItemIconNode?
var i = 0
for (theme, accentColor) in item.themes {
let imageNode: ThemeSettingsThemeItemIconNode
@ -313,9 +320,14 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode {
imageNode = ThemeSettingsThemeItemIconNode()
strongSelf.nodes.append(imageNode)
strongSelf.scrollNode.addSubnode(imageNode)
updated = true
}
let selected = theme == item.currentTheme
if selected {
selectedNode = imageNode
}
let name: String
switch theme {
case .dayClassic:
@ -328,8 +340,11 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode {
name = item.strings.Appearance_ThemeCarouselNightBlue
}
imageNode.setup(theme: item.theme, icon: generateThemeIconImage(theme: theme, accentColor: accentColor), title: NSAttributedString(string: name, font: textFont, textColor: selected ? item.theme.list.itemAccentColor : item.theme.list.itemPrimaryTextColor, paragraphAlignment: .center), bordered: true, selected: selected, action: {
imageNode.setup(theme: item.theme, icon: generateThemeIconImage(theme: theme, accentColor: accentColor), title: NSAttributedString(string: name, font: textFont, textColor: selected ? item.theme.list.itemAccentColor : item.theme.list.itemPrimaryTextColor, paragraphAlignment: .center), bordered: true, selected: selected, action: { [weak self, weak imageNode] in
item.updated(theme)
if let imageNode = imageNode {
self?.scrollToNode(imageNode, animated: true)
}
})
imageNode.frame = CGRect(origin: CGPoint(x: nodeOffset, y: 0.0), size: nodeSize)
@ -344,6 +359,10 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.scrollNode.view.contentSize = contentSize
}
}
if updated, let selectedNode = selectedNode {
strongSelf.scrollToNode(selectedNode, animated: false)
}
}
})
}

View File

@ -407,9 +407,6 @@
D0642EFC1F3E1E7B00792790 /* ChatHistoryNavigationButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0642EFB1F3E1E7B00792790 /* ChatHistoryNavigationButtons.swift */; };
D064EF871F69A06F00AC0398 /* MessageContentKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = D064EF861F69A06F00AC0398 /* MessageContentKind.swift */; };
D0671F2D2145AB28000A8AE7 /* LegacyAvatarPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0671F2C2145AB28000A8AE7 /* LegacyAvatarPicker.swift */; };
D067B4A5211C911C00796039 /* LegacyChannelIntroController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D067B4A4211C911C00796039 /* LegacyChannelIntroController.swift */; };
D067B4AA211C916300796039 /* TGChannelIntroController.h in Headers */ = {isa = PBXBuildFile; fileRef = D067B4A6211C916200796039 /* TGChannelIntroController.h */; };
D067B4AD211C916300796039 /* TGChannelIntroController.m in Sources */ = {isa = PBXBuildFile; fileRef = D067B4A9211C916200796039 /* TGChannelIntroController.m */; };
D0684A041F6C3AD50059F570 /* ChatListTypingNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0684A031F6C3AD50059F570 /* ChatListTypingNode.swift */; };
D06887F01F72DEE6000AB936 /* ShareInputFieldNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06887EF1F72DEE6000AB936 /* ShareInputFieldNode.swift */; };
D069F5D0212700B90000565A /* StickerPanePeerSpecificSetupGridItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D069F5CF212700B90000565A /* StickerPanePeerSpecificSetupGridItem.swift */; };
@ -1733,9 +1730,6 @@
D0642EFB1F3E1E7B00792790 /* ChatHistoryNavigationButtons.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatHistoryNavigationButtons.swift; sourceTree = "<group>"; };
D064EF861F69A06F00AC0398 /* MessageContentKind.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageContentKind.swift; sourceTree = "<group>"; };
D0671F2C2145AB28000A8AE7 /* LegacyAvatarPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyAvatarPicker.swift; sourceTree = "<group>"; };
D067B4A4211C911C00796039 /* LegacyChannelIntroController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyChannelIntroController.swift; sourceTree = "<group>"; };
D067B4A6211C916200796039 /* TGChannelIntroController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGChannelIntroController.h; sourceTree = "<group>"; };
D067B4A9211C916200796039 /* TGChannelIntroController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGChannelIntroController.m; sourceTree = "<group>"; };
D0684A031F6C3AD50059F570 /* ChatListTypingNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListTypingNode.swift; sourceTree = "<group>"; };
D06879541DB8F1FC00424BBD /* CachedResourceRepresentations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CachedResourceRepresentations.swift; sourceTree = "<group>"; };
D06879561DB8F22200424BBD /* FetchCachedRepresentations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchCachedRepresentations.swift; sourceTree = "<group>"; };
@ -3267,16 +3261,6 @@
name = "Setup Two Step Verification";
sourceTree = "<group>";
};
D067B4AE211C916D00796039 /* Channel Intro */ = {
isa = PBXGroup;
children = (
D067B4A4211C911C00796039 /* LegacyChannelIntroController.swift */,
D067B4A6211C916200796039 /* TGChannelIntroController.h */,
D067B4A9211C916200796039 /* TGChannelIntroController.m */,
);
name = "Channel Intro";
sourceTree = "<group>";
};
D0736F261DF4D2F300F2C02A /* Telegram Controller */ = {
isa = PBXGroup;
children = (
@ -3336,7 +3320,6 @@
D07551891DDA4C7C0073E051 /* Legacy Components */ = {
isa = PBXGroup;
children = (
D067B4AE211C916D00796039 /* Channel Intro */,
D0AE2FDB22B1D3610058D3BC /* Bridge Audio */,
D075518A1DDA4D7D0073E051 /* LegacyController.swift */,
D075518C1DDA4E0B0073E051 /* LegacyControllerNode.swift */,
@ -4899,7 +4882,6 @@
D0E9BACB1F05738600F079A4 /* STPAPIPostRequest.h in Headers */,
D0E9BA561F055A0B00F079A4 /* STPFormTextField.h in Headers */,
D008177C22B46B7E008A895F /* TGItemProviderSignals.h in Headers */,
D067B4AA211C916300796039 /* TGChannelIntroController.h in Headers */,
D0E9BABE1F05735F00F079A4 /* STPPaymentConfiguration+Private.h in Headers */,
D0E9BACA1F05738600F079A4 /* STPAPIClient+Private.h in Headers */,
D0E9BA251F05578900F079A4 /* STPCardBrand.h in Headers */,
@ -5232,7 +5214,6 @@
D0AB263321C3DFEA008F6685 /* CreatePollOptionActionItem.swift in Sources */,
09FFBCDB22849CB500C33B4B /* PDF.swift in Sources */,
09D968A1221F7FF100B1458A /* ChatTypingActivityContentNode.swift in Sources */,
D067B4AD211C916300796039 /* TGChannelIntroController.m in Sources */,
D0BE303220601FFC00FBE6D8 /* LocationBroadcastActionSheetItem.swift in Sources */,
090E778E22AA863A00CD99F5 /* PeersNearbyIconNode.swift in Sources */,
D0EC6CF41EB9F58800EBF1C3 /* ManagedMediaId.swift in Sources */,
@ -5959,7 +5940,6 @@
091417F421EF4F5F00C8325A /* WallpaperGalleryItem.swift in Sources */,
D02F4AE91FCF370B004DFBAE /* ChatMessageInteractiveMediaBadge.swift in Sources */,
D0EC6E461EB9F58900EBF1C3 /* ItemListLoadingIndicatorEmptyStateItem.swift in Sources */,
D067B4A5211C911C00796039 /* LegacyChannelIntroController.swift in Sources */,
D00817CF22B47A14008A895F /* LegacyFileImport.swift in Sources */,
D01A21AF1F39EA2E00DDA104 /* InstantPageTheme.swift in Sources */,
D0EC6E471EB9F58900EBF1C3 /* ItemListTextEmptyStateItem.swift in Sources */,

View File

@ -6,7 +6,7 @@ public struct CallListSettings: PreferencesEntry, Equatable {
public var showTab: Bool
public static var defaultSettings: CallListSettings {
return CallListSettings(showTab: false)
return CallListSettings(showTab: true)
}
public init(showTab: Bool) {