This commit is contained in:
Peter 2019-06-17 23:04:02 +01:00
commit 21520d226f
18 changed files with 3085 additions and 2881 deletions

View file

@ -4416,11 +4416,13 @@ Any member of this group will be able to see messages in the channel.";
"Common.ActionNotAllowedError" = "Sorry, you are not allowed to do this.";
"Group.Location.Title" = "Location (Optional)";
"Group.Location.Title" = "Location";
"Group.Location.SetLocation" = "Set Location";
"Group.Location.ChangeLocation" = "Change Location";
"Group.Location.RemoveLocation" = "Remove Location";
"Group.Location.Info" = "People will be able to find your group using People Nearby section";
"Group.Location.Info" = "People will be able to find your group in the Groups Nearby section (Contacts > Add People Nearby).";
"Group.Username.Title" = "Username";
"Channel.AdminLog.MessageTransferedName" = "transferred ownership to %1$@";
"Channel.AdminLog.MessageTransferedNameUsername" = "transferred ownership to %1$@ (%2$@)";
@ -4439,3 +4441,5 @@ Any member of this group will be able to see messages in the channel.";
"ReportGroupLocation.Title" = "Report Unrelated Group";
"ReportGroupLocation.Text" = "Please tell us if this group is not related to this location.";
"ReportGroupLocation.Report" = "Report";
"Group.Setup.TypePublicWithLocationHelp" = "Public groups can be found in search, chat history is available to everyone and anyone can join.\n\nTo make your group public, set a username or add a location.";

View file

@ -278,6 +278,25 @@ struct AccountMutableState {
mutating func mergeChats(_ chats: [Api.Chat]) {
self.addOperation(.MergeApiChats(chats))
for chat in chats {
switch chat {
case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount):
if let participantsCount = participantsCount {
self.addOperation(.UpdateCachedPeerData(chat.peerId, { current in
var previous: CachedChannelData
if let current = current as? CachedChannelData {
previous = current
} else {
previous = CachedChannelData()
}
return previous.withUpdatedParticipantsSummary(previous.participantsSummary.withUpdatedMemberCount(participantsCount))
}))
}
default:
break
}
}
}
mutating func updatePeer(_ id: PeerId, _ f: @escaping (Peer?) -> Peer?) {

View file

@ -13,6 +13,7 @@ public enum ChannelOwnershipTransferError {
case twoStepAuthMissing
case twoStepAuthTooFresh(Int32)
case authSessionTooFresh(Int32)
case limitExceeded
case requestPassword
case invalidPassword
case adminsTooMuch
@ -105,7 +106,9 @@ public func updateChannelOwnership(postbox: Postbox, network: Network, accountSt
|> mapToSignal { password -> Signal<Never, ChannelOwnershipTransferError> in
return network.request(Api.functions.channels.editCreator(channel: apiChannel, userId: apiUser, password: password))
|> mapError { error -> ChannelOwnershipTransferError in
if error.errorDescription == "PASSWORD_HASH_INVALID" {
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded
} else if error.errorDescription == "PASSWORD_HASH_INVALID" {
return .invalidPassword
} else if error.errorDescription == "PASSWORD_MISSING" {
return .twoStepAuthMissing

View file

@ -112,7 +112,26 @@ public func addGroupAdmin(account: Account, peerId: PeerId, adminId: PeerId) ->
if let peer = transaction.getPeer(peerId), let adminPeer = transaction.getPeer(adminId), let inputUser = apiInputUser(adminPeer) {
if let group = peer as? TelegramGroup {
return account.network.request(Api.functions.messages.editChatAdmin(chatId: group.id.id, userId: inputUser, isAdmin: .boolTrue))
|> mapError { _ -> AddGroupAdminError in return .generic }
|> `catch` { error -> Signal<Api.Bool, AddGroupAdminError> in
if error.errorDescription == "USER_NOT_PARTICIPANT" {
return addGroupMember(account: account, peerId: peerId, memberId: adminId)
|> mapError { error -> AddGroupAdminError in
return .addMemberError(error)
}
|> mapToSignal { _ -> Signal<Api.Bool, AddGroupAdminError> in
return .complete()
}
|> then(
account.network.request(Api.functions.messages.editChatAdmin(chatId: group.id.id, userId: inputUser, isAdmin: .boolTrue))
|> mapError { error -> AddGroupAdminError in
return .generic
}
)
} else if error.errorDescription == "USER_PRIVACY_RESTRICTED" {
return .fail(.addMemberError(.privacy))
}
return .fail(.generic)
}
|> mapToSignal { result -> Signal<Void, AddGroupAdminError> in
return account.postbox.transaction { transaction -> Void in
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
@ -205,67 +224,69 @@ public func updateChannelAdminRights(account: Account, peerId: PeerId, adminId:
updatedParticipant = ChannelParticipant.member(id: adminId, invitedAt: Int32(Date().timeIntervalSince1970), adminInfo: adminInfo, banInfo: nil)
}
return account.network.request(Api.functions.channels.editAdmin(channel: inputChannel, userId: inputUser, adminRights: rights.apiAdminRights))
|> map { [$0] }
|> `catch` { error -> Signal<[Api.Updates], UpdateChannelAdminRightsError> in
if error.errorDescription == "USER_NOT_PARTICIPANT" {
return addChannelMember(account: account, peerId: peerId, memberId: adminId)
|> map { _ -> [Api.Updates] in
return []
}
|> map { [$0] }
|> `catch` { error -> Signal<[Api.Updates], UpdateChannelAdminRightsError> in
if error.errorDescription == "USER_NOT_PARTICIPANT" {
return addChannelMember(account: account, peerId: peerId, memberId: adminId)
|> map { _ -> [Api.Updates] in
return []
}
|> mapError { error -> UpdateChannelAdminRightsError in
return .addMemberError(error)
}
|> then(account.network.request(Api.functions.channels.editAdmin(channel: inputChannel, userId: inputUser, adminRights: rights.apiAdminRights))
|> mapError { error -> UpdateChannelAdminRightsError in
return .addMemberError(error)
return .generic
}
|> then(account.network.request(Api.functions.channels.editAdmin(channel: inputChannel, userId: inputUser, adminRights: rights.apiAdminRights))
|> mapError { error -> UpdateChannelAdminRightsError in
return .generic
}
|> map { [$0] })
}
return .fail(.generic)
|> map { [$0] })
} else if error.errorDescription == "USER_PRIVACY_RESTRICTED" {
return .fail(.addMemberError(.restricted))
}
|> mapToSignal { result -> Signal<(ChannelParticipant?, RenderedChannelParticipant), UpdateChannelAdminRightsError> in
for updates in result {
account.stateManager.addUpdates(updates)
}
return account.postbox.transaction { transaction -> (ChannelParticipant?, RenderedChannelParticipant) in
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in
if let cachedData = cachedData as? CachedChannelData, let adminCount = cachedData.participantsSummary.adminCount {
var updatedAdminCount = adminCount
var wasAdmin = false
if let currentParticipant = currentParticipant {
switch currentParticipant {
case .creator:
return .fail(.generic)
}
|> mapToSignal { result -> Signal<(ChannelParticipant?, RenderedChannelParticipant), UpdateChannelAdminRightsError> in
for updates in result {
account.stateManager.addUpdates(updates)
}
return account.postbox.transaction { transaction -> (ChannelParticipant?, RenderedChannelParticipant) in
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in
if let cachedData = cachedData as? CachedChannelData, let adminCount = cachedData.participantsSummary.adminCount {
var updatedAdminCount = adminCount
var wasAdmin = false
if let currentParticipant = currentParticipant {
switch currentParticipant {
case .creator:
wasAdmin = true
case let .member(_, _, adminInfo, _):
if let adminInfo = adminInfo, !adminInfo.rights.isEmpty {
wasAdmin = true
case let .member(_, _, adminInfo, _):
if let adminInfo = adminInfo, !adminInfo.rights.isEmpty {
wasAdmin = true
}
}
}
}
if wasAdmin && rights.isEmpty {
updatedAdminCount = max(1, adminCount - 1)
} else if !wasAdmin && !rights.isEmpty {
updatedAdminCount = adminCount + 1
}
return cachedData.withUpdatedParticipantsSummary(cachedData.participantsSummary.withUpdatedAdminCount(updatedAdminCount))
} else {
return cachedData
}
})
var peers: [PeerId: Peer] = [:]
var presences: [PeerId: PeerPresence] = [:]
peers[adminPeer.id] = adminPeer
if let presence = transaction.getPeerPresence(peerId: adminPeer.id) {
presences[adminPeer.id] = presence
}
if case let .member(_, _, maybeAdminInfo, _) = updatedParticipant, let adminInfo = maybeAdminInfo {
if let peer = transaction.getPeer(adminInfo.promotedBy) {
peers[peer.id] = peer
if wasAdmin && rights.isEmpty {
updatedAdminCount = max(1, adminCount - 1)
} else if !wasAdmin && !rights.isEmpty {
updatedAdminCount = adminCount + 1
}
return cachedData.withUpdatedParticipantsSummary(cachedData.participantsSummary.withUpdatedAdminCount(updatedAdminCount))
} else {
return cachedData
}
return (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: adminPeer, peers: peers, presences: presences))
} |> mapError { _ -> UpdateChannelAdminRightsError in return .generic }
})
var peers: [PeerId: Peer] = [:]
var presences: [PeerId: PeerPresence] = [:]
peers[adminPeer.id] = adminPeer
if let presence = transaction.getPeerPresence(peerId: adminPeer.id) {
presences[adminPeer.id] = presence
}
if case let .member(_, _, maybeAdminInfo, _) = updatedParticipant, let adminInfo = maybeAdminInfo {
if let peer = transaction.getPeer(adminInfo.promotedBy) {
peers[peer.id] = peer
}
}
return (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: adminPeer, peers: peers, presences: presences))
} |> mapError { _ -> UpdateChannelAdminRightsError in return .generic }
}
} else {
return .fail(.generic)

View file

@ -752,6 +752,7 @@ public func channelAdminController(context: AccountContext, peerId: PeerId, admi
}
presentControllerImpl?(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
}
dismissImpl?()
}, completed: {
updated(TelegramChatAdminRights(flags: updateFlags))
dismissImpl?()
@ -778,7 +779,13 @@ public func channelAdminController(context: AccountContext, peerId: PeerId, admi
return current.withUpdatedUpdating(true)
}
updateRightsDisposable.set((addGroupAdmin(account: context.account, peerId: peerId, adminId: adminId)
|> deliverOnMainQueue).start(completed: {
|> deliverOnMainQueue).start(error: { error in
if case let .addMemberError(error) = error, case .privacy = error, let admin = adminView.peers[adminView.peerId] {
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Privacy_GroupsAndChannels_InviteToGroupError(admin.compactDisplayTitle, admin.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
}
dismissImpl?()
}, completed: {
dismissImpl?()
}))
} else if updateFlags != defaultFlags {
@ -813,9 +820,7 @@ public func channelAdminController(context: AccountContext, peerId: PeerId, admi
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Privacy_GroupsAndChannels_InviteToGroupError(admin.compactDisplayTitle, admin.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
}
updateState { current in
return current.withUpdatedUpdating(false)
}
dismissImpl?()
}))
} else {
dismissImpl?()

View file

@ -235,7 +235,7 @@ public func channelDiscussionGroupSetupController(context: AccountContext, peerI
guard let peer = peer else {
return
}
pushControllerImpl?(createGroupController(context: context, peerIds: [], initialTitle: peer.displayTitle + " Chat", supergroup: true, completion: { groupId, dismiss in
pushControllerImpl?(createGroupController(context: context, peerIds: [], initialTitle: peer.displayTitle + " Chat", type: .supergroup, completion: { groupId, dismiss in
var applySignal = updateGroupDiscussionForChannel(network: context.account.network, postbox: context.account.postbox, channelId: peerId, groupId: groupId)
var cancelImpl: (() -> Void)?
let progressSignal = Signal<Never, NoError> { subscriber in

View file

@ -397,7 +397,7 @@ private final class ChannelOwnershipTransferAlertContentNode: AlertContentNode {
}
}
private func commitChannelOwnershipTransferController(context: AccountContext, peer: Peer, member: TelegramUser, completion: @escaping (PeerId?) -> Void) -> ViewController {
private func commitChannelOwnershipTransferController(context: AccountContext, peer: Peer, member: TelegramUser, present: @escaping (ViewController, Any?) -> Void, completion: @escaping (PeerId?) -> Void) -> ViewController {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
var dismissImpl: (() -> Void)?
@ -462,8 +462,32 @@ private func commitChannelOwnershipTransferController(context: AccountContext, p
dismissImpl?()
completion(upgradedPeerId)
}, error: { [weak contentNode] error in
var isGroup = true
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
isGroup = false
}
var errorTextAndActions: (String, [TextAlertAction])?
switch error {
case .invalidPassword:
contentNode?.animateError()
case .limitExceeded:
errorTextAndActions = (presentationData.strings.TwoStepAuth_FloodError, [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
case .adminsTooMuch:
errorTextAndActions = (isGroup ? presentationData.strings.Group_OwnershipTransfer_ErrorAdminsTooMuch : presentationData.strings.Channel_OwnershipTransfer_ErrorAdminsTooMuch, [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
case .userPublicChannelsTooMuch:
errorTextAndActions = (presentationData.strings.Channel_OwnershipTransfer_ErrorPublicChannelsTooMuch, [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
case .userBlocked, .restricted:
errorTextAndActions = (isGroup ? presentationData.strings.Group_OwnershipTransfer_ErrorPrivacyRestricted : presentationData.strings.Channel_OwnershipTransfer_ErrorPrivacyRestricted, [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
default:
errorTextAndActions = (presentationData.strings.Login_UnknownError, [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
}
contentNode?.updateIsChecking(false)
contentNode?.animateError()
if let (text, actions) = errorTextAndActions {
dismissImpl?()
present(textAlertController(context: context, title: nil, text: text, actions: actions), nil)
}
}))
}
return controller
@ -497,7 +521,7 @@ private func confirmChannelOwnershipTransferController(context: AccountContext,
let controller = richTextAlertController(context: context, title: attributedTitle, text: attributedText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Channel_OwnershipTransfer_ChangeOwner, action: {
dismissImpl?()
present(commitChannelOwnershipTransferController(context: context, peer: peer, member: member, completion: completion), nil)
present(commitChannelOwnershipTransferController(context: context, peer: peer, member: member, present: present, completion: completion), nil)
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {
dismissImpl?()
})], actionLayout: .vertical)
@ -519,7 +543,6 @@ func channelOwnershipTransferController(context: AccountContext, peer: Peer, mem
isGroup = false
}
var dismissImpl: (() -> Void)?
var actions: [TextAlertAction] = []
switch initialError {
@ -559,9 +582,5 @@ func channelOwnershipTransferController(context: AccountContext, peer: Peer, mem
let bold = MarkdownAttributeSet(font: Font.semibold(13.0), textColor: theme.primaryColor)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .center)
let controller = richTextAlertController(context: context, title: title, text: attributedText, actions: actions)
dismissImpl = { [weak controller] in
controller?.dismissAnimated()
}
return controller
return richTextAlertController(context: context, title: title, text: attributedText, actions: actions)
}

View file

@ -64,6 +64,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
case typePrivate(PresentationTheme, String, Bool)
case typeInfo(PresentationTheme, String)
case publicLinkHeader(PresentationTheme, String)
case publicLinkAvailability(PresentationTheme, String, Bool)
case privateLink(PresentationTheme, String, String?)
case editablePublicLink(PresentationTheme, String)
@ -87,7 +88,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
switch self {
case .typeHeader, .typePublic, .typePrivate, .typeInfo:
return ChannelVisibilitySection.type.rawValue
case .publicLinkAvailability, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkInfo, .publicLinkStatus:
case .publicLinkHeader, .publicLinkAvailability, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkInfo, .publicLinkStatus:
return ChannelVisibilitySection.link.rawValue
case .privateLinkCopy, .privateLinkRevoke, .privateLinkShare:
return ChannelVisibilitySection.linkActions.rawValue
@ -108,28 +109,30 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
return 2
case .typeInfo:
return 3
case .publicLinkAvailability:
case .publicLinkHeader:
return 4
case .privateLink:
case .publicLinkAvailability:
return 5
case .editablePublicLink:
case .privateLink:
return 6
case .privateLinkInfo:
case .editablePublicLink:
return 7
case .privateLinkCopy:
case .privateLinkInfo:
return 8
case .privateLinkRevoke:
case .privateLinkCopy:
return 9
case .privateLinkShare:
case .privateLinkRevoke:
return 10
case .publicLinkStatus:
case .privateLinkShare:
return 11
case .publicLinkInfo:
case .publicLinkStatus:
return 12
case .existingLinksInfo:
case .publicLinkInfo:
return 13
case .existingLinksInfo:
return 14
case let .existingLinkPeerItem(index, _, _, _, _, _, _, _):
return 14 + index
return 15 + index
case .locationHeader:
return 1000
case .location:
@ -169,6 +172,12 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
} else {
return false
}
case let .publicLinkHeader(lhsTheme, lhsTitle):
if case let .publicLinkHeader(rhsTheme, rhsTitle) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle {
return true
} else {
return false
}
case let .publicLinkAvailability(lhsTheme, lhsText, lhsValue):
if case let .publicLinkAvailability(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
@ -310,6 +319,8 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
})
case let .typeInfo(theme, text):
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
case let .publicLinkHeader(theme, title):
return ItemListSectionHeaderItem(theme: theme, text: title, sectionId: self.section)
case let .publicLinkAvailability(theme, text, value):
let attr = NSMutableAttributedString(string: text, textColor: value ? theme.list.freeTextColor : theme.list.freeTextErrorColor)
attr.addAttribute(.font, value: Font.regular(13), range: NSMakeRange(0, attr.length))
@ -379,7 +390,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
return ItemListSectionHeaderItem(theme: theme, text: title, sectionId: self.section)
case let .location(theme, location):
let imageSignal = chatMapSnapshotImage(account: arguments.account, resource: MapSnapshotMediaResource(latitude: location.latitude, longitude: location.longitude, width: 90, height: 90))
return ItemListAddressItem(theme: theme, label: "", text: location.address, imageSignal: imageSignal, selected: nil, sectionId: self.section, style: .blocks, action: nil)
return ItemListAddressItem(theme: theme, label: "", text: location.address.replacingOccurrences(of: ", ", with: "\n"), imageSignal: imageSignal, selected: nil, sectionId: self.section, style: .blocks, action: nil)
case let .locationSetup(theme, text):
return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
arguments.setLocation()
@ -543,7 +554,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
switch selectedType {
case .publicChannel:
if isGroup {
entries.append(.typeInfo(presentationData.theme, presentationData.strings.Group_Setup_TypePublicHelp))
entries.append(.typeInfo(presentationData.theme, presentationData.strings.Group_Setup_TypePublicWithLocationHelp))
} else {
entries.append(.typeInfo(presentationData.theme, presentationData.strings.Channel_Setup_TypePublicHelp))
}
@ -585,6 +596,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp, true))
}
} else {
entries.append(.publicLinkHeader(presentationData.theme, presentationData.strings.Group_Username_Title.uppercased()))
entries.append(.editablePublicLink(presentationData.theme, currentAddressName))
if let status = state.addressNameValidationStatus {
let text: String
@ -629,8 +641,6 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
entries.append(.publicLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp))
entries.append(.locationHeader(presentationData.theme, presentationData.strings.Group_Location_Title.uppercased()))
if let currentEditingLocation = state.editingLocation {
if case .removed = currentEditingLocation {
entries.append(.locationSetup(presentationData.theme, presentationData.strings.Group_Location_SetLocation))
@ -717,7 +727,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
entries.append(.typePublic(presentationData.theme, presentationData.strings.Channel_Setup_TypePublic, selectedType == .publicChannel))
entries.append(.typePrivate(presentationData.theme, presentationData.strings.Channel_Setup_TypePrivate, selectedType == .privateChannel))
entries.append(.typeInfo(presentationData.theme, presentationData.strings.Group_Setup_TypePublicHelp))
entries.append(.typeInfo(presentationData.theme, presentationData.strings.Group_Setup_TypePublicWithLocationHelp))
switch selectedType {
case .publicChannel:
@ -745,6 +755,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp, true))
}
} else {
entries.append(.publicLinkHeader(presentationData.theme, presentationData.strings.Group_Username_Title.uppercased()))
entries.append(.editablePublicLink(presentationData.theme, currentAddressName))
if let status = state.addressNameValidationStatus {
let text: String
@ -778,6 +789,19 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
entries.append(.publicLinkStatus(presentationData.theme, text, status))
}
entries.append(.publicLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp))
entries.append(.locationHeader(presentationData.theme, presentationData.strings.Group_Location_Title.uppercased()))
if let currentEditingLocation = state.editingLocation {
if case .removed = currentEditingLocation {
entries.append(.locationSetup(presentationData.theme, presentationData.strings.Group_Location_SetLocation))
} else if case let .location(location) = currentEditingLocation {
entries.append(.location(presentationData.theme, location))
entries.append(.locationSetup(presentationData.theme, presentationData.strings.Group_Location_ChangeLocation))
entries.append(.locationRemove(presentationData.theme, presentationData.strings.Group_Location_RemoveLocation))
}
} else {
entries.append(.locationSetup(presentationData.theme, presentationData.strings.Group_Location_SetLocation))
}
}
case .privateChannel:
let link = (view.cachedData as? CachedGroupData)?.exportedInvitation?.link
@ -1056,7 +1080,7 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
updateState { state in
let address: String
if let placemark = placemark {
address = placemark.fullAddress.replacingOccurrences(of: ", ", with: "\n")
address = placemark.fullAddress
} else {
address = "\(coordinate.latitude), \(coordinate.longitude)"
}
@ -1109,6 +1133,18 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
case .privateChannel:
break
case .publicChannel:
var hasLocation = false
if let editingLocation = state.editingLocation {
switch editingLocation {
case .location:
hasLocation = true
case .removed:
hasLocation = false
}
} else if let cachedChannelData = view.cachedData as? CachedChannelData, cachedChannelData.peerGeoLocation != nil {
hasLocation = true
}
if let addressNameValidationStatus = state.addressNameValidationStatus {
switch addressNameValidationStatus {
case .availability(.available):
@ -1117,7 +1153,7 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
doneEnabled = false
}
} else {
doneEnabled = !(peer.addressName?.isEmpty ?? true)
doneEnabled = !(peer.addressName?.isEmpty ?? true) || hasLocation
}
}
}
@ -1211,6 +1247,16 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
case .privateChannel:
break
case .publicChannel:
var hasLocation = false
if let editingLocation = state.editingLocation {
switch editingLocation {
case .location:
hasLocation = true
case .removed:
hasLocation = false
}
}
if let addressNameValidationStatus = state.addressNameValidationStatus {
switch addressNameValidationStatus {
case .availability(.available):
@ -1219,7 +1265,7 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
doneEnabled = false
}
} else {
doneEnabled = !(peer.addressName?.isEmpty ?? true)
doneEnabled = !(peer.addressName?.isEmpty ?? true) || hasLocation
}
}
}
@ -1329,6 +1375,8 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
} else {
if let addressName = peer.addressName, !addressName.isEmpty {
selectedType = .publicChannel
} else if let cachedChannelData = view.cachedData as? CachedChannelData, cachedChannelData.peerGeoLocation != nil {
selectedType = .publicChannel
} else {
selectedType = .privateChannel
}
@ -1360,7 +1408,7 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
controller?.dismiss()
}
dismissInputImpl = { [weak controller] in
controller?.dismiss()
controller?.view.endEditing(true)
}
nextImpl = { [weak controller] in
if let controller = controller {

View file

@ -3445,7 +3445,9 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
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: {
})]
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()))
@ -5431,7 +5433,7 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
case .upperBound:
searchLocation = .index(MessageIndex.upperBound(peerId: peerId))
}
let historyView = preloadedShatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(location: searchLocation, count: 50), id: 0), account: self.context.account, chatLocation: self.chatLocation, fixedCombinedReadStates: nil, tagMask: nil, additionalData: [])
let historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(location: searchLocation, count: 50), id: 0), account: self.context.account, chatLocation: self.chatLocation, fixedCombinedReadStates: nil, tagMask: nil, additionalData: [])
let signal = historyView
|> mapToSignal { historyView -> Signal<(MessageIndex?, Bool), NoError> in
switch historyView {
@ -5523,7 +5525,7 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
self.historyNavigationStack.add(fromIndex)
}
self.loadingMessage.set(true)
let historyView = preloadedShatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(location: searchLocation, count: 50), id: 0), account: self.context.account, chatLocation: self.chatLocation, fixedCombinedReadStates: nil, tagMask: nil, additionalData: [])
let historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(location: searchLocation, count: 50), id: 0), account: self.context.account, chatLocation: self.chatLocation, fixedCombinedReadStates: nil, tagMask: nil, additionalData: [])
let signal = historyView
|> mapToSignal { historyView -> Signal<MessageIndex?, NoError> in
switch historyView {

View file

@ -5,7 +5,7 @@ import TelegramCore
import SwiftSignalKit
import Display
func preloadedShatHistoryViewForLocation(_ location: ChatHistoryLocationInput, account: Account, chatLocation: ChatLocation, fixedCombinedReadStates: MessageHistoryViewReadState?, tagMask: MessageTags?, additionalData: [AdditionalMessageHistoryViewData], orderStatistics: MessageHistoryViewOrderStatistics = []) -> Signal<ChatHistoryViewUpdate, NoError> {
func preloadedChatHistoryViewForLocation(_ location: ChatHistoryLocationInput, account: Account, chatLocation: ChatLocation, fixedCombinedReadStates: MessageHistoryViewReadState?, tagMask: MessageTags?, additionalData: [AdditionalMessageHistoryViewData], orderStatistics: MessageHistoryViewOrderStatistics = []) -> Signal<ChatHistoryViewUpdate, NoError> {
return chatHistoryViewForLocation(location, account: account, chatLocation: chatLocation, fixedCombinedReadStates: fixedCombinedReadStates, tagMask: tagMask, additionalData: additionalData, orderStatistics: orderStatistics)
|> introduceError(Bool.self)
|> mapToSignal { update -> Signal<ChatHistoryViewUpdate, Bool> in

View file

@ -60,15 +60,17 @@ class ContactListActionItem: ListViewItem {
let title: String
let icon: ContactListActionItemIcon
let highlight: ContactListActionItemHighlight
let clearHighlightAutomatically: Bool
let action: () -> Void
let header: ListViewItemHeader?
init(theme: PresentationTheme, title: String, icon: ContactListActionItemIcon, highlight: ContactListActionItemHighlight = .cell, header: ListViewItemHeader?, action: @escaping () -> Void) {
init(theme: PresentationTheme, title: String, icon: ContactListActionItemIcon, highlight: ContactListActionItemHighlight = .cell, clearHighlightAutomatically: Bool = true, header: ListViewItemHeader?, action: @escaping () -> Void) {
self.theme = theme
self.title = title
self.icon = icon
self.highlight = highlight
self.header = header
self.clearHighlightAutomatically = clearHighlightAutomatically
self.action = action
}
@ -111,7 +113,7 @@ class ContactListActionItem: ListViewItem {
func selected(listView: ListView){
self.action()
if case .alpha = self.highlight {
if self.clearHighlightAutomatically {
listView.clearHighlightAnimated(true)
}
}

View file

@ -217,7 +217,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
interaction.authorize()
})
case let .option(_, option, header, theme, _):
return ContactListActionItem(theme: theme, title: option.title, icon: option.icon, header: header, action: option.action)
return ContactListActionItem(theme: theme, title: option.title, icon: option.icon, clearHighlightAutomatically: false, header: header, action: option.action)
case let .peer(_, peer, presence, header, selection, theme, strings, dateTimeFormat, nameSortOrder, nameDisplayOrder, enabled):
let status: ContactsPeerItemStatus
let itemPeer: ContactsPeerItemPeer

View file

@ -257,16 +257,12 @@ public class ContactsController: ViewController {
}
self.contactsNode.openPeopleNearby = { [weak self] in
guard let strongSelf = self else {
return
}
let _ = (DeviceAccess.authorizationStatus(subject: .location(.tracking))
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] status in
guard let strongSelf = self else {
return
}
let presentPeersNearby = {
let controller = peersNearbyController(context: strongSelf.context)
(strongSelf.navigationController as? NavigationController)?.replaceAllButRootController(controller, animated: true, completion: { [weak self] in

View file

@ -19,6 +19,7 @@ private struct CreateGroupArguments {
private enum CreateGroupSection: Int32 {
case info
case members
case location
}
private enum CreateGroupEntryTag: ItemListItemTag {
@ -43,8 +44,9 @@ private enum CreateGroupEntryTag: ItemListItemTag {
private enum CreateGroupEntry: ItemListNodeEntry {
case groupInfo(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer?, ItemListAvatarAndNameInfoItemState, ItemListAvatarAndNameInfoItemUpdatingAvatar?)
case setProfilePhoto(PresentationTheme, String)
case member(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer, PeerPresence?)
case locationHeader(PresentationTheme, String)
case location(PresentationTheme, PeerGeoLocation)
var section: ItemListSectionId {
switch self {
@ -52,6 +54,8 @@ private enum CreateGroupEntry: ItemListNodeEntry {
return CreateGroupSection.info.rawValue
case .member:
return CreateGroupSection.members.rawValue
case .locationHeader, .location:
return CreateGroupSection.location.rawValue
}
}
@ -63,6 +67,10 @@ private enum CreateGroupEntry: ItemListNodeEntry {
return 1
case let .member(index, _, _, _, _, _, _):
return 2 + index
case .locationHeader:
return 10000
case .location:
return 10001
}
}
@ -133,6 +141,18 @@ private enum CreateGroupEntry: ItemListNodeEntry {
} else {
return false
}
case let .locationHeader(lhsTheme, lhsTitle):
if case let .locationHeader(rhsTheme, rhsTitle) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle {
return true
} else {
return false
}
case let .location(lhsTheme, lhsLocation):
if case let .location(rhsTheme, rhsLocation) = rhs, lhsTheme === rhsTheme, lhsLocation == rhsLocation {
return true
} else {
return false
}
}
}
@ -153,6 +173,11 @@ private enum CreateGroupEntry: ItemListNodeEntry {
})
case let .member(_, theme, strings, dateTimeFormat, nameDisplayOrder, peer, presence):
return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, account: arguments.account, peer: peer, presence: presence, text: .presence, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in })
case let .locationHeader(theme, title):
return ItemListSectionHeaderItem(theme: theme, text: title, sectionId: self.section)
case let .location(theme, location):
let imageSignal = chatMapSnapshotImage(account: arguments.account, resource: MapSnapshotMediaResource(latitude: location.latitude, longitude: location.longitude, width: 90, height: 90))
return ItemListAddressItem(theme: theme, label: "", text: location.address.replacingOccurrences(of: ", ", with: "\n"), imageSignal: imageSignal, selected: nil, sectionId: self.section, style: .blocks, action: nil)
}
}
}
@ -177,7 +202,7 @@ private struct CreateGroupState: Equatable {
}
}
private func createGroupEntries(presentationData: PresentationData, state: CreateGroupState, peerIds: [PeerId], view: MultiplePeersView) -> [CreateGroupEntry] {
private func createGroupEntries(presentationData: PresentationData, state: CreateGroupState, peerIds: [PeerId], view: MultiplePeersView, geoLocation: PeerGeoLocation?) -> [CreateGroupEntry] {
var entries: [CreateGroupEntry] = []
let groupInfoState = ItemListAvatarAndNameInfoItemState(editingName: state.editingName, updatingName: nil)
@ -218,10 +243,21 @@ private func createGroupEntries(presentationData: PresentationData, state: Creat
entries.append(.member(Int32(i), presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, peers[i], view.presences[peers[i].id]))
}
if let geoLocation = geoLocation {
entries.append(.locationHeader(presentationData.theme, presentationData.strings.Group_Location_Title.uppercased()))
entries.append(.location(presentationData.theme, geoLocation))
}
return entries
}
public func createGroupController(context: AccountContext, peerIds: [PeerId], initialTitle: String? = nil, supergroup: Bool = false, completion: ((PeerId, @escaping () -> Void) -> Void)? = nil) -> ViewController {
public enum CreateGroupType {
case generic
case supergroup
case locatedGroup(latitude: Double, longitude: Double)
}
public func createGroupController(context: AccountContext, peerIds: [PeerId], initialTitle: String? = nil, type: CreateGroupType = .generic, completion: ((PeerId, @escaping () -> Void) -> Void)? = nil) -> ViewController {
let initialState = CreateGroupState(creating: false, editingName: .title(title: initialTitle ?? "", type: .group), avatar: nil)
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
let stateValue = Atomic(value: initialState)
@ -240,6 +276,11 @@ public func createGroupController(context: AccountContext, peerIds: [PeerId], in
let uploadedAvatar = Promise<UploadedPeerPhotoData>()
let placemarkPromise = Promise<ReverseGeocodedPlacemark?>()
if case let .locatedGroup(latitude, longitude) = type {
placemarkPromise.set(reverseGeocodeLocation(latitude: latitude, longitude: longitude))
}
let arguments = CreateGroupArguments(account: context.account, updateEditingName: { editingName in
updateState { current in
var current = current
@ -260,19 +301,45 @@ public func createGroupController(context: AccountContext, peerIds: [PeerId], in
endEditingImpl?()
let createSignal: Signal<PeerId?, CreateGroupError>
if supergroup {
createSignal = createSupergroup(account: context.account, title: title, description: nil)
|> map(Optional.init)
|> mapError { error -> CreateGroupError in
switch error {
case .generic:
return .generic
case .restricted:
return .restricted
switch type {
case .generic:
createSignal = createGroup(account: context.account, title: title, peerIds: peerIds)
case .supergroup:
createSignal = createSupergroup(account: context.account, title: title, description: nil)
|> map(Optional.init)
|> mapError { error -> CreateGroupError in
switch error {
case .generic:
return .generic
case .restricted:
return .restricted
}
}
case let .locatedGroup(latitude, longitude):
createSignal = createSupergroup(account: context.account, title: title, description: nil)
|> map(Optional.init)
|> mapError { error -> CreateGroupError in
switch error {
case .generic:
return .generic
case .restricted:
return .restricted
}
}
|> mapToSignal { peerId in
guard let peerId = peerId else {
return .single(nil)
}
return placemarkPromise.get()
|> introduceError(CreateGroupError.self)
|> mapToSignal { placemark in
return updateChannelGeoLocation(postbox: context.account.postbox, network: context.account.network, channelId: peerId, coordinate: (latitude, longitude), address: placemark?.fullAddress ?? "\(latitude), \(longitude)")
|> introduceError(CreateGroupError.self)
|> map { _ in
return peerId
}
}
}
}
} else {
createSignal = createGroup(account: context.account, title: title, peerIds: peerIds)
}
actionsDisposable.add((createSignal
@ -410,8 +477,8 @@ public func createGroupController(context: AccountContext, peerIds: [PeerId], in
})
})
let signal = combineLatest(context.sharedContext.presentationData, statePromise.get(), context.account.postbox.multiplePeersView(peerIds))
|> map { presentationData, state, view -> (ItemListControllerState, (ItemListNodeState<CreateGroupEntry>, CreateGroupEntry.ItemGenerationArguments)) in
let signal = combineLatest(context.sharedContext.presentationData, statePromise.get(), context.account.postbox.multiplePeersView(peerIds), .single(nil) |> then(placemarkPromise.get()))
|> map { presentationData, state, view, placemark -> (ItemListControllerState, (ItemListNodeState<CreateGroupEntry>, CreateGroupEntry.ItemGenerationArguments)) in
let rightNavigationButton: ItemListNavigationButton
if state.creating {
@ -422,8 +489,17 @@ public func createGroupController(context: AccountContext, peerIds: [PeerId], in
})
}
var geoLocation: PeerGeoLocation?
if case let .locatedGroup(latitude, longitude) = type {
if let placemark = placemark {
geoLocation = PeerGeoLocation(latitude: latitude, longitude: longitude, address: placemark.fullAddress)
} else {
geoLocation = PeerGeoLocation(latitude: latitude, longitude: longitude, address: "")
}
}
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Compose_NewGroupTitle), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
let listState = ItemListNodeState(entries: createGroupEntries(presentationData: presentationData, state: state, peerIds: peerIds, view: view), style: .blocks, focusItemTag: CreateGroupEntryTag.info)
let listState = ItemListNodeState(entries: createGroupEntries(presentationData: presentationData, state: state, peerIds: peerIds, view: view, geoLocation: geoLocation), style: .blocks, focusItemTag: CreateGroupEntryTag.info)
return (controllerState, (listState, arguments))
}

View file

@ -37,9 +37,9 @@ private func arePeerNearbyArraysEqual(_ lhs: [PeerNearbyEntry], _ rhs: [PeerNear
private final class PeersNearbyControllerArguments {
let context: AccountContext
let openChat: (Peer) -> Void
let openCreateGroup: () -> Void
let openCreateGroup: (Double, Double) -> Void
init(context: AccountContext, openChat: @escaping (Peer) -> Void, openCreateGroup: @escaping () -> Void) {
init(context: AccountContext, openChat: @escaping (Peer) -> Void, openCreateGroup: @escaping (Double, Double) -> Void) {
self.context = context
self.openChat = openChat
self.openCreateGroup = openCreateGroup
@ -61,7 +61,7 @@ private enum PeersNearbyEntry: ItemListNodeEntry {
case user(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PeerNearbyEntry)
case groupsHeader(PresentationTheme, String)
case createGroup(PresentationTheme, String)
case createGroup(PresentationTheme, String, Double?, Double?)
case group(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PeerNearbyEntry)
case channelsHeader(PresentationTheme, String)
@ -135,8 +135,8 @@ private enum PeersNearbyEntry: ItemListNodeEntry {
} else {
return false
}
case let .createGroup(lhsTheme, lhsText):
if case let .createGroup(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
case let .createGroup(lhsTheme, lhsText, lhsLatitude, lhsLongitude):
if case let .createGroup(rhsTheme, rhsText, rhsLatitude, rhsLongitude) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsLatitude == rhsLatitude && lhsLongitude == rhsLongitude {
return true
} else {
return false
@ -186,21 +186,23 @@ private enum PeersNearbyEntry: ItemListNodeEntry {
case let .empty(theme, text, loading):
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(stringForDistance(peer.distance)), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: {
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 .createGroup(theme, title):
case let .createGroup(theme, title, latitude, longitude):
return ItemListPeerActionItem(theme: theme, icon: PresentationResourcesItemList.createGroupIcon(theme), title: title, alwaysPlain: false, sectionId: self.section, editing: false, action: {
arguments.openCreateGroup()
if let latitude = latitude, let longitude = longitude {
arguments.openCreateGroup(latitude, longitude)
}
})
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("\(stringForDistance(peer.distance)), \(strings.Conversation_StatusMembers(memberCount))")
text = .text("\(strings.Map_DistanceAway(stringForDistance(peer.distance)).0), \(strings.Conversation_StatusMembers(memberCount))")
} else {
text = .text(stringForDistance(peer.distance))
text = .text(strings.Map_DistanceAway(stringForDistance(peer.distance)).0)
}
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, 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)
@ -210,9 +212,9 @@ private enum PeersNearbyEntry: ItemListNodeEntry {
case let .channel(_, theme, strings, dateTimeFormat, nameDisplayOrder, peer):
var text: ItemListPeerItemText
if let cachedData = peer.peer.1 as? CachedChannelData, let memberCount = cachedData.participantsSummary.memberCount {
text = .text("\(stringForDistance(peer.distance)), \(strings.Conversation_StatusSubscribers(memberCount))")
text = .text("\(strings.Map_DistanceAway(stringForDistance(peer.distance)).0), \(strings.Conversation_StatusSubscribers(memberCount))")
} else {
text = .text(stringForDistance(peer.distance))
text = .text(strings.Map_DistanceAway(stringForDistance(peer.distance)).0)
}
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, 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)
@ -221,29 +223,27 @@ private enum PeersNearbyEntry: ItemListNodeEntry {
}
}
private struct PeersNearbyControllerState: Equatable {
static func ==(lhs: PeersNearbyControllerState, rhs: PeersNearbyControllerState) -> Bool {
return true
}
}
private struct PeersNearbyData: Equatable {
let latitude: Double
let longitude: Double
let users: [PeerNearbyEntry]
let groups: [PeerNearbyEntry]
let channels: [PeerNearbyEntry]
init(users: [PeerNearbyEntry], groups: [PeerNearbyEntry], channels: [PeerNearbyEntry]) {
init(latitude: Double, longitude: Double, users: [PeerNearbyEntry], groups: [PeerNearbyEntry], channels: [PeerNearbyEntry]) {
self.latitude = latitude
self.longitude = longitude
self.users = users
self.groups = groups
self.channels = channels
}
static func ==(lhs: PeersNearbyData, rhs: PeersNearbyData) -> Bool {
return arePeerNearbyArraysEqual(lhs.users, rhs.users) && arePeerNearbyArraysEqual(lhs.groups, rhs.groups) && arePeerNearbyArraysEqual(lhs.channels, rhs.channels)
return lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude && arePeerNearbyArraysEqual(lhs.users, rhs.users) && arePeerNearbyArraysEqual(lhs.groups, rhs.groups) && arePeerNearbyArraysEqual(lhs.channels, rhs.channels)
}
}
private func peersNearbyControllerEntries(state: PeersNearbyControllerState, data: PeersNearbyData?, presentationData: PresentationData) -> [PeersNearbyEntry] {
private func peersNearbyControllerEntries(data: PeersNearbyData?, presentationData: PresentationData) -> [PeersNearbyEntry] {
var entries: [PeersNearbyEntry] = []
entries.append(.header(presentationData.theme, presentationData.strings.PeopleNearby_Description))
@ -259,7 +259,7 @@ private func peersNearbyControllerEntries(state: PeersNearbyControllerState, dat
}
entries.append(.groupsHeader(presentationData.theme, presentationData.strings.PeopleNearby_Groups.uppercased()))
entries.append(.createGroup(presentationData.theme, presentationData.strings.PeopleNearby_CreateGroup))
entries.append(.createGroup(presentationData.theme, presentationData.strings.PeopleNearby_CreateGroup, data?.latitude, data?.longitude))
if let data = data, !data.groups.isEmpty {
var i: Int32 = 0
for group in data.groups {
@ -281,12 +281,6 @@ private func peersNearbyControllerEntries(state: PeersNearbyControllerState, dat
}
public func peersNearbyController(context: AccountContext) -> ViewController {
let statePromise = ValuePromise(PeersNearbyControllerState(), ignoreRepeated: true)
let stateValue = Atomic(value: PeersNearbyControllerState())
let updateState: ((PeersNearbyControllerState) -> PeersNearbyControllerState) -> Void = { f in
statePromise.set(stateValue.modify { f($0) })
}
var pushControllerImpl: ((ViewController) -> Void)?
var replaceTopControllerImpl: ((ViewController, Bool) -> Void)?
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
@ -298,13 +292,14 @@ public func peersNearbyController(context: AccountContext) -> ViewController {
let arguments = PeersNearbyControllerArguments(context: context, openChat: { peer in
navigateToChatImpl?(peer)
}, openCreateGroup: {
let controller = createGroupController(context: context, peerIds: [], supergroup: true)
replaceTopControllerImpl?(controller, true)
}, openCreateGroup: { latitude, longitude in
let controller = createGroupController(context: context, peerIds: [], type: .locatedGroup(latitude: latitude, longitude: longitude))
pushControllerImpl?(controller)
})
let dataSignal: Signal<PeersNearbyData?, NoError> = currentLocationManagerCoordinate(manager: context.sharedContext.locationManager!, timeout: 5.0)
|> mapToSignal { coordinate -> Signal<PeersNearbyData?, NoError> in
let dataSignal: Signal<PeersNearbyData?, Void> = currentLocationManagerCoordinate(manager: context.sharedContext.locationManager!, timeout: 5.0)
|> introduceError(Void.self)
|> mapToSignal { coordinate -> Signal<PeersNearbyData?, Void> in
guard let coordinate = coordinate else {
return .single(nil)
}
@ -312,8 +307,9 @@ public func peersNearbyController(context: AccountContext) -> ViewController {
return Signal { subscriber in
let peersNearbyContext = PeersNearbyContext(network: context.account.network, accountStateManager: context.account.stateManager, coordinate: (latitude: coordinate.latitude, longitude: coordinate.longitude))
let disposable = (peersNearbyContext.get()
|> mapToSignal { peersNearby -> Signal<PeersNearbyData?, NoError> in
let peersNearby: Signal<PeersNearbyData?, Void> = peersNearbyContext.get()
|> introduceError(Void.self)
|> mapToSignal { peersNearby -> Signal<PeersNearbyData?, Void> in
return context.account.postbox.transaction { transaction -> PeersNearbyData? in
var users: [PeerNearbyEntry] = []
var groups: [PeerNearbyEntry] = []
@ -327,9 +323,12 @@ public func peersNearbyController(context: AccountContext) -> ViewController {
}
}
}
return PeersNearbyData(users: users, groups: groups, channels: [])
return PeersNearbyData(latitude: coordinate.latitude, longitude: coordinate.longitude, users: users, groups: groups, channels: [])
}
}).start(next: { data in
|> introduceError(Void.self)
}
let disposable = peersNearby.start(next: { data in
subscriber.putNext(data)
})
@ -339,14 +338,22 @@ public func peersNearbyController(context: AccountContext) -> ViewController {
}
}
}
dataPromise.set((dataSignal |> then(.complete() |> suspendAwareDelay(25.0, queue: Queue.concurrentDefaultQueue()))) |> restart)
let signal = combineLatest(context.sharedContext.presentationData, statePromise.get(), dataPromise.get())
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)
}
dataPromise.set(combinedSignal)
let signal = combineLatest(context.sharedContext.presentationData, dataPromise.get())
|> deliverOnMainQueue
|> map { presentationData, state, data -> (ItemListControllerState, (ItemListNodeState<PeersNearbyEntry>, PeersNearbyEntry.ItemGenerationArguments)) in
|> map { presentationData, data -> (ItemListControllerState, (ItemListNodeState<PeersNearbyEntry>, PeersNearbyEntry.ItemGenerationArguments)) in
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(state: state, data: data, presentationData: presentationData), style: .blocks, emptyStateItem: nil, crossfadeState: false, animateChanges: true, userInteractionEnabled: true)
let listState = ItemListNodeState(entries: peersNearbyControllerEntries(data: data, presentationData: presentationData), style: .blocks, emptyStateItem: nil, crossfadeState: false, animateChanges: true, userInteractionEnabled: true)
return (controllerState, (listState, arguments))
}

View file

@ -530,7 +530,7 @@ public class TelegramController: ViewController, KeyShortcutResponder {
}
if let id = state.id as? PeerMessagesMediaPlaylistItemId {
if type == .music {
let historyView = preloadedShatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(location: .id(id.messageId), count: 60), id: 0), account: account, chatLocation: .peer(id.messageId.peerId), fixedCombinedReadStates: nil, tagMask: MessageTags.music, additionalData: [])
let historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(location: .id(id.messageId), count: 60), id: 0), account: account, chatLocation: .peer(id.messageId.peerId), fixedCombinedReadStates: nil, tagMask: MessageTags.music, additionalData: [])
let signal = historyView
|> mapToSignal { historyView -> Signal<(MessageIndex?, Bool), NoError> in
switch historyView {