Merge commit 'f77f2f0dbf307a45c4f6efebaf844e104850e7a6'
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 8.1 KiB |
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.4 KiB |
BIN
Telegram-iOS/BlackFilledIconIpad@2x.png
Normal file → Executable file
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 7.4 KiB |
0
Telegram-iOS/BlackIconIpad.png
Normal file → Executable 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
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
0
Telegram-iOS/BlackIconLargeIpad@2x.png
Normal file → Executable file
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 5.9 KiB |
BIN
Telegram-iOS/BlueIconIpad.png
Normal file → Executable file
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.3 KiB |
BIN
Telegram-iOS/BlueIconIpad@2x.png
Normal file → Executable file
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 4.8 KiB |
BIN
Telegram-iOS/BlueIconLargeIpad@2x.png
Normal file → Executable file
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 4.1 KiB |
@ -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";
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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" {
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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',
|
||||
|
@ -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)
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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: [])
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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?()
|
||||
|
@ -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: {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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?()
|
||||
})
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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
|
||||
}
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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) })
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
@ -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
|
@ -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"
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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 */,
|
||||
|
@ -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) {
|
||||
|