Implemented groups nearby

This commit is contained in:
Ilya Laktyushin 2019-06-14 19:50:51 +02:00
parent c67619116b
commit aae6c245f6
50 changed files with 4504 additions and 3594 deletions

View File

@ -4410,3 +4410,27 @@ Any member of this group will be able to see messages in the channel.";
"ContactInfo.PhoneNumberHidden" = "Hidden";
"Common.ActionNotAllowedError" = "Sorry, you are not allowed to do this.";
"Group.Location.Title" = "Location (Optional)";
"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";
"Channel.AdminLog.MessageTransferedName" = "transferred ownership to %1$@";
"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";
"Permissions.PeopleNearbyTitle.v0" = "People Nearby";
"Permissions.PeopleNearbyText.v0" = "Use this section to quickly add people near you and discover nearby group chats.\n\nPlease allow location access\nto start using this feature.";
"Permissions.PeopleNearbyAllow.v0" = "Allow Access";
"Permissions.PeopleNearbyAllowInSettings.v0" = "Allow in Settings";
"Conversation.ReportGroupLocation" = "Group unrelated to tocation?";
"ReportGroupLocation.Title" = "Report Unrelated Group";
"ReportGroupLocation.Text" = "Please tell us if this group is not related to this location.";
"ReportGroupLocation.Report" = "Report";

View File

@ -12,6 +12,7 @@
- (void)configureForCurrentLocationWithAccuracy:(CLLocationAccuracy)accuracy;
- (void)configureForCustomLocationWithAddress:(NSString *)address;
- (void)configureForGroupLocationWithAddress:(NSString *)address;
- (void)configureForLiveLocationWithAccuracy:(CLLocationAccuracy)accuracy;
- (void)configureForStopWithMessage:(TGMessage *)message remaining:(SSignal *)remaining;

View File

@ -341,6 +341,48 @@ const CGFloat TGLocationCurrentLocationCellHeight = 68;
[self setNeedsLayout];
}
- (void)configureForGroupLocationWithAddress:(NSString *)address
{
_messageId = 0;
UIImage *icon = TGComponentsImageNamed(@"LocationMessagePinIcon");
if (_pallete != nil)
icon = TGTintedImage(icon, _pallete.iconColor);
_iconView.image = icon;
_titleLabel.textColor = self.pallete != nil ? self.pallete.accentColor : TGAccentColor();
_elapsedView.hidden = true;
if (_isCurrentLocation)
{
[UIView transitionWithView:self duration:0.2f options:UIViewAnimationOptionTransitionCrossDissolve animations:^
{
_titleLabel.text = TGLocalized(@"Map.SetThisLocation");
_subtitleLabel.text = [self _subtitleForAddress:address];
_circleView.alpha = 1.0f;
_titleLabel.alpha = 1.0f;
_subtitleLabel.alpha = 1.0f;
} completion:nil];
_isCurrentLocation = false;
}
else
{
[UIView transitionWithView:self duration:0.2f options:UIViewAnimationOptionTransitionCrossDissolve animations:^
{
_subtitleLabel.text = [self _subtitleForAddress:address];
} completion:nil];
}
[self setCircleColor:_pallete != nil ? _pallete.locationColor : UIColorRGB(0x008df2)];
_separatorView.hidden = true;
[_wavesView stop];
_wavesView.hidden = true;
[self setNeedsLayout];
}
- (NSString *)_subtitleForAddress:(NSString *)address
{
if (address != nil && address.length == 0)

View File

@ -373,7 +373,7 @@ const CGPoint TGLocationPickerPinOffset = { 0.0f, 33.0f };
_pinMovedFromUserLocation = false;
[self hidePickerAnnotationAnimated:true];
[_pickerPinView setPinRaised:true animated:true completion:nil];
[_pickerPinView setPinRaised:true avatar:_intent == TGLocationPickerControllerCustomLocationIntent animated:true completion:nil];
MKCoordinateSpan span = _fullScreenMapSpan != nil ? _fullScreenMapSpan.MKCoordinateSpanValue : TGLocationDefaultSpan;
[self setMapCenterCoordinate:_mapView.userLocation.location.coordinate span:span offset:TGLocationPickerPinOffset animated:true];
@ -399,7 +399,7 @@ const CGPoint TGLocationPickerPinOffset = { 0.0f, 33.0f };
[self switchToFullscreen];
}
[_pickerPinView setPinRaised:true animated:true completion:nil];
[_pickerPinView setPinRaised:true avatar:_intent == TGLocationPickerControllerCustomLocationIntent animated:true completion:nil];
_pinMovedFromUserLocation = true;
_updatePinAnnotation = false;
@ -435,7 +435,7 @@ const CGPoint TGLocationPickerPinOffset = { 0.0f, 33.0f };
- (void)pinPinView
{
__weak TGLocationPickerController *weakSelf = self;
[_pickerPinView setPinRaised:false animated:true completion:^
[_pickerPinView setPinRaised:false avatar:_intent == TGLocationPickerControllerCustomLocationIntent animated:true completion:^
{
__strong TGLocationPickerController *strongSelf = weakSelf;
if (strongSelf == nil)
@ -662,7 +662,9 @@ const CGPoint TGLocationPickerPinOffset = { 0.0f, 33.0f };
_ownLocationView.hidden = true;
_pickerPinWrapper.hidden = false;
[_pickerPinView setCustomPin:true animated:true];
if (_intent != TGLocationPickerControllerCustomLocationIntent) {
[_pickerPinView setCustomPin:true animated:true];
}
_mapView.tapEnabled = false;
_mapView.longPressAsTapEnabled = false;
@ -767,6 +769,9 @@ const CGPoint TGLocationPickerPinOffset = { 0.0f, 33.0f };
- (UIBarButtonItem *)controllerRightBarButtonItem
{
if (_intent == TGLocationPickerControllerCustomLocationIntent) {
return nil;
}
if (iosMajorVersion() < 7)
{
TGModernBarButton *searchButton = [[TGModernBarButton alloc] initWithImage:TGComponentsImageNamed(@"NavigationSearchIcon.png")];
@ -944,7 +949,9 @@ const CGPoint TGLocationPickerPinOffset = { 0.0f, 33.0f };
if (_currentUserLocation != nil)
[self fetchNearbyVenuesWithLocation:_currentUserLocation];
}
[cell configureWithTitle:TGLocalized(@"Map.ChooseAPlace")];
if (_intent != TGLocationPickerControllerCustomLocationIntent) {
[cell configureWithTitle:TGLocalized(@"Map.ChooseAPlace")];
}
if (scrollView.contentOffset.y > -scrollView.contentInset.top + TGLocationSectionHeaderHeight)
{
@ -975,7 +982,9 @@ const CGPoint TGLocationPickerPinOffset = { 0.0f, 33.0f };
else
{
_activityIndicator.alpha = 0.0f;
[cell configureWithTitle:TGLocalized(@"Map.PullUpForPlaces")];
if (_intent != TGLocationPickerControllerCustomLocationIntent) {
[cell configureWithTitle:TGLocalized(@"Map.PullUpForPlaces")];
}
if (_safeAreaCurtainView != nil)
{
@ -1004,10 +1013,14 @@ const CGPoint TGLocationPickerPinOffset = { 0.0f, 33.0f };
{
TGLocationCurrentLocationCell *locationCell = (TGLocationCurrentLocationCell *)cell;
if (_mapInFullScreenMode)
[locationCell configureForCustomLocationWithAddress:_customAddress];
else
[locationCell configureForCurrentLocationWithAccuracy:_currentUserLocation.horizontalAccuracy];
if (_intent == TGLocationPickerControllerCustomLocationIntent) {
[locationCell configureForGroupLocationWithAddress:_customAddress];
} else {
if (_mapInFullScreenMode)
[locationCell configureForCustomLocationWithAddress:_customAddress];
else
[locationCell configureForCurrentLocationWithAccuracy:_currentUserLocation.horizontalAccuracy];
}
}
cell = [_tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:0]];
@ -1096,7 +1109,9 @@ const CGPoint TGLocationPickerPinOffset = { 0.0f, 33.0f };
}
else if ((_allowLiveLocationSharing && indexPath.row == 2) || (!_allowLiveLocationSharing && indexPath.row == 1))
{
[self _presentVenuesList];
if (_intent != TGLocationPickerControllerCustomLocationIntent) {
[self _presentVenuesList];
}
}
}
else
@ -1151,10 +1166,14 @@ const CGPoint TGLocationPickerPinOffset = { 0.0f, 33.0f };
locationCell.pallete = self.pallete;
locationCell.edgeView = _edgeHighlightView;
if (_mapInFullScreenMode)
[locationCell configureForCustomLocationWithAddress:_customAddress];
else
[locationCell configureForCurrentLocationWithAccuracy:_currentUserLocation.horizontalAccuracy];
if (_intent == TGLocationPickerControllerCustomLocationIntent) {
[locationCell configureForGroupLocationWithAddress:_customAddress];
} else {
if (_mapInFullScreenMode)
[locationCell configureForCustomLocationWithAddress:_customAddress];
else
[locationCell configureForCurrentLocationWithAccuracy:_currentUserLocation.horizontalAccuracy];
}
cell = locationCell;
}
@ -1180,10 +1199,12 @@ const CGPoint TGLocationPickerPinOffset = { 0.0f, 33.0f };
sectionCell = [[TGLocationSectionHeaderCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:TGLocationSectionHeaderKind];
sectionCell.pallete = self.pallete;
if (tableView.contentOffset.y > -tableView.contentInset.top)
[sectionCell configureWithTitle:TGLocalized(@"Map.ChooseAPlace")];
else
[sectionCell configureWithTitle:TGLocalized(@"Map.PullUpForPlaces")];
if (_intent != TGLocationPickerControllerCustomLocationIntent) {
if (tableView.contentOffset.y > -tableView.contentInset.top)
[sectionCell configureWithTitle:TGLocalized(@"Map.ChooseAPlace")];
else
[sectionCell configureWithTitle:TGLocalized(@"Map.PullUpForPlaces")];
}
cell = sectionCell;
}

View File

@ -7,7 +7,7 @@
- (instancetype)initWithAnnotation:(id<MKAnnotation>)annotation;
@property (nonatomic, assign, getter=isPinRaised) bool pinRaised;
- (void)setPinRaised:(bool)raised animated:(bool)animated completion:(void (^)(void))completion;
- (void)setPinRaised:(bool)raised avatar:(bool)avatar animated:(bool)animated completion:(void (^)(void))completion;
- (void)setCustomPin:(bool)customPin animated:(bool)animated;

View File

@ -429,10 +429,10 @@ NSString *const TGLocationPinAnnotationKind = @"TGLocationPinAnnotation";
- (void)setPinRaised:(bool)raised
{
[self setPinRaised:raised animated:false completion:nil];
[self setPinRaised:raised avatar:false animated:false completion:nil];
}
- (void)setPinRaised:(bool)raised animated:(bool)animated completion:(void (^)(void))completion
- (void)setPinRaised:(bool)raised avatar:(bool)avatar animated:(bool)animated completion:(void (^)(void))completion
{
_pinRaised = raised;
@ -447,6 +447,8 @@ NSString *const TGLocationPinAnnotationKind = @"TGLocationPinAnnotation";
[UIView animateWithDuration:0.2 delay:0.0 options:7 << 16 | UIViewAnimationOptionAllowAnimatedContent animations:^
{
_shadowView.center = CGPointMake(TGScreenPixel, -66.0f);
if (avatar)
_avatarView.center = CGPointMake(TGScreenPixel, -71.0f);
} completion:^(BOOL finished) {
if (finished && completion != nil)
completion();
@ -457,6 +459,8 @@ NSString *const TGLocationPinAnnotationKind = @"TGLocationPinAnnotation";
[UIView animateWithDuration:0.2 delay:0.0 usingSpringWithDamping:0.6 initialSpringVelocity:0.0 options:UIViewAnimationOptionAllowAnimatedContent animations:^
{
_shadowView.center = CGPointMake(TGScreenPixel, -36.0f);
if (avatar)
_avatarView.center = CGPointMake(TGScreenPixel, -41.0f);
} completion:^(BOOL finished)
{
if (finished && completion != nil)
@ -467,6 +471,8 @@ NSString *const TGLocationPinAnnotationKind = @"TGLocationPinAnnotation";
else
{
_shadowView.center = CGPointMake(TGScreenPixel, raised ? -66.0f : -36.0f);
if (avatar)
_avatarView.center = CGPointMake(TGScreenPixel, raised ? -71.0 : -41.0f);
if (completion != nil)
completion();

View File

@ -1291,9 +1291,9 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
updatedState.updatePeerChatInclusion(peerId: peer.peerId, groupId: PeerGroupId(rawValue: folderId), changedGroup: true)
}
}
case let .updatePeerLocated(contacts):
case let .updatePeerLocated(peers):
var peersNearby: [PeerNearby] = []
for case let .peerLocated(peer, expires, distance) in contacts {
for case let .peerLocated(peer, expires, distance) in peers {
peersNearby.append(PeerNearby(id: peer.peerId, expires: expires, distance: distance))
}
updatedState.updatePeersNearby(peersNearby)

View File

@ -125,6 +125,34 @@ public struct ChannelMigrationReference: PostboxCoding, Equatable {
}
}
public struct PeerGeoLocation: PostboxCoding, Equatable {
public let latitude: Double
public let longitude: Double
public let address: String
public init(latitude: Double, longitude: Double, address: String) {
self.latitude = latitude
self.longitude = longitude
self.address = address
}
public init(decoder: PostboxDecoder) {
self.latitude = decoder.decodeDoubleForKey("la", orElse: 0.0)
self.longitude = decoder.decodeDoubleForKey("lo", orElse: 0.0)
self.address = decoder.decodeStringForKey("a", orElse: "")
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeDouble(self.latitude, forKey: "la")
encoder.encodeDouble(self.longitude, forKey: "lo")
encoder.encodeString(self.address, forKey: "a")
}
public static func ==(lhs: PeerGeoLocation, rhs: PeerGeoLocation) -> Bool {
return lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude && lhs.address == rhs.address
}
}
public final class CachedChannelData: CachedPeerData {
public let isNotAccessible: Bool
public let flags: CachedChannelFlags
@ -137,8 +165,8 @@ public final class CachedChannelData: CachedPeerData {
public let stickerPack: StickerPackCollectionInfo?
public let minAvailableMessageId: MessageId?
public let migrationReference: ChannelMigrationReference?
public let linkedDiscussionPeerId: PeerId?
public let peerGeoLocation: PeerGeoLocation?
public let peerIds: Set<PeerId>
public let messageIds: Set<MessageId>
@ -161,9 +189,10 @@ public final class CachedChannelData: CachedPeerData {
self.minAvailableMessageId = nil
self.migrationReference = nil
self.linkedDiscussionPeerId = nil
self.peerGeoLocation = nil
}
init(isNotAccessible: Bool, flags: CachedChannelFlags, about: String?, participantsSummary: CachedChannelParticipantsSummary, exportedInvitation: ExportedInvitation?, botInfos: [CachedPeerBotInfo], peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, stickerPack: StickerPackCollectionInfo?, minAvailableMessageId: MessageId?, migrationReference: ChannelMigrationReference?, linkedDiscussionPeerId: PeerId?) {
init(isNotAccessible: Bool, flags: CachedChannelFlags, about: String?, participantsSummary: CachedChannelParticipantsSummary, exportedInvitation: ExportedInvitation?, botInfos: [CachedPeerBotInfo], peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, stickerPack: StickerPackCollectionInfo?, minAvailableMessageId: MessageId?, migrationReference: ChannelMigrationReference?, linkedDiscussionPeerId: PeerId?, peerGeoLocation: PeerGeoLocation?) {
self.isNotAccessible = isNotAccessible
self.flags = flags
self.about = about
@ -176,6 +205,7 @@ public final class CachedChannelData: CachedPeerData {
self.minAvailableMessageId = minAvailableMessageId
self.migrationReference = migrationReference
self.linkedDiscussionPeerId = linkedDiscussionPeerId
self.peerGeoLocation = peerGeoLocation
var peerIds = Set<PeerId>()
for botInfo in botInfos {
@ -196,51 +226,55 @@ public final class CachedChannelData: CachedPeerData {
}
func withUpdatedIsNotAccessible(_ isNotAccessible: Bool) -> CachedChannelData {
return CachedChannelData(isNotAccessible: isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId)
return CachedChannelData(isNotAccessible: isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation)
}
func withUpdatedFlags(_ flags: CachedChannelFlags) -> CachedChannelData {
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId)
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation)
}
func withUpdatedAbout(_ about: String?) -> CachedChannelData {
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId)
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation)
}
func withUpdatedParticipantsSummary(_ participantsSummary: CachedChannelParticipantsSummary) -> CachedChannelData {
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId)
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation)
}
func withUpdatedExportedInvitation(_ exportedInvitation: ExportedInvitation?) -> CachedChannelData {
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId)
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation)
}
func withUpdatedBotInfos(_ botInfos: [CachedPeerBotInfo]) -> CachedChannelData {
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId)
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation)
}
func withUpdatedPeerStatusSettings(_ peerStatusSettings: PeerStatusSettings?) -> CachedChannelData {
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId)
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation)
}
func withUpdatedPinnedMessageId(_ pinnedMessageId: MessageId?) -> CachedChannelData {
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId)
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation)
}
func withUpdatedStickerPack(_ stickerPack: StickerPackCollectionInfo?) -> CachedChannelData {
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId)
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation)
}
func withUpdatedMinAvailableMessageId(_ minAvailableMessageId: MessageId?) -> CachedChannelData {
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId)
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation)
}
func withUpdatedMigrationReference(_ migrationReference: ChannelMigrationReference?) -> CachedChannelData {
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId)
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation)
}
func withUpdatedLinkedDiscussionPeerId(_ linkedDiscussionPeerId: PeerId?) -> CachedChannelData {
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: linkedDiscussionPeerId)
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation)
}
func withUpdatedPeerGeoLocation(peerGeoLocation: PeerGeoLocation?) -> CachedChannelData {
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: peerGeoLocation)
}
public init(decoder: PostboxDecoder) {
@ -286,6 +320,12 @@ public final class CachedChannelData: CachedPeerData {
self.linkedDiscussionPeerId = nil
}
if let peerGeoLocation = decoder.decodeObjectForKey("pgl", decoder: { PeerGeoLocation(decoder: $0) }) as? PeerGeoLocation {
self.peerGeoLocation = peerGeoLocation
} else {
self.peerGeoLocation = nil
}
if let linkedDiscussionPeerId = self.linkedDiscussionPeerId {
peerIds.insert(linkedDiscussionPeerId)
}
@ -352,6 +392,11 @@ public final class CachedChannelData: CachedPeerData {
} else {
encoder.encodeNil(forKey: "dgi")
}
if let peerGeoLocation = self.peerGeoLocation {
encoder.encodeObject(peerGeoLocation, forKey: "pgl")
} else {
encoder.encodeNil(forKey: "pgl")
}
}
public func isEqual(to: CachedPeerData) -> Bool {
@ -407,6 +452,25 @@ public final class CachedChannelData: CachedPeerData {
return false
}
if other.peerGeoLocation != self.peerGeoLocation {
return false
}
return true
}
}
extension PeerGeoLocation {
init?(apiLocation: Api.ChannelLocation) {
switch apiLocation {
case let .channelLocation(geopoint, address):
if case let .geoPoint(longitude, latitude, _) = geopoint {
self.init(latitude: latitude, longitude: longitude, address: address)
} else {
return nil
}
default:
return nil
}
}
}

View File

@ -56,10 +56,10 @@ public enum AdminLogEventAction {
case participantToggleAdmin(prev: RenderedChannelParticipant, new: RenderedChannelParticipant)
case changeStickerPack(prev: StickerPackReference?, new: StickerPackReference?)
case togglePreHistoryHidden(Bool)
case updateDefaultBannedRights(prev: TelegramChatBannedRights, new: TelegramChatBannedRights
)
case updateDefaultBannedRights(prev: TelegramChatBannedRights, new: TelegramChatBannedRights)
case pollStopped(Message)
case linkedPeerUpdated(previous: Peer?, updated: Peer?)
case changeGeoLocation(previous: PeerGeoLocation?, updated: PeerGeoLocation?)
}
public enum ChannelAdminLogEventError {
@ -215,7 +215,7 @@ public func channelAdminLogEvents(postbox: Postbox, network: Network, peerId: Pe
case let .channelAdminLogEventActionChangeLinkedChat(prevValue, newValue):
action = .linkedPeerUpdated(previous: prevValue == 0 ? nil : peers[PeerId(namespace: Namespaces.Peer.CloudChannel, id: prevValue)], updated: newValue == 0 ? nil : peers[PeerId(namespace: Namespaces.Peer.CloudChannel, id: newValue)])
case let .channelAdminLogEventActionChangeLocation(prevValue, newValue):
break
action = .changeGeoLocation(previous: PeerGeoLocation(apiLocation: prevValue), updated: PeerGeoLocation(apiLocation: newValue))
}
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
if let action = action {

View File

@ -20,7 +20,58 @@ public enum ChannelOwnershipTransferError {
case userBlocked
}
public func updateChannelOwnership(postbox: Postbox, network: Network, accountStateManager: AccountStateManager, channelId: PeerId, memberId: PeerId, password: String?) -> Signal<Never, ChannelOwnershipTransferError> {
public func checkOwnershipTranfserAvailability(postbox: Postbox, network: Network, accountStateManager: AccountStateManager, memberId: PeerId) -> Signal<Never, ChannelOwnershipTransferError> {
return postbox.transaction { transaction -> Peer? in
return transaction.getPeer(memberId)
}
|> introduceError(ChannelOwnershipTransferError.self)
|> mapToSignal { user -> Signal<Never, ChannelOwnershipTransferError> in
guard let user = user else {
return .fail(.generic)
}
guard let apiUser = apiInputUser(user) else {
return .fail(.generic)
}
return network.request(Api.functions.channels.editCreator(channel: .inputChannelEmpty, userId: apiUser, password: .inputCheckPasswordEmpty))
|> mapError { error -> ChannelOwnershipTransferError in
if error.errorDescription == "PASSWORD_HASH_INVALID" {
return .requestPassword
} else if error.errorDescription == "PASSWORD_MISSING" {
return .twoStepAuthMissing
} else if error.errorDescription.hasPrefix("PASSWORD_TOO_FRESH_") {
let timeout = String(error.errorDescription[error.errorDescription.index(error.errorDescription.startIndex, offsetBy: "PASSWORD_TOO_FRESH_".count)...])
if let value = Int32(timeout) {
return .twoStepAuthTooFresh(value)
}
} else if error.errorDescription.hasPrefix("SESSION_TOO_FRESH_") {
let timeout = String(error.errorDescription[error.errorDescription.index(error.errorDescription.startIndex, offsetBy: "SESSION_TOO_FRESH_".count)...])
if let value = Int32(timeout) {
return .authSessionTooFresh(value)
}
} else if error.errorDescription == "CHANNELS_ADMIN_PUBLIC_TOO_MUCH" {
return .userPublicChannelsTooMuch
} else if error.errorDescription == "ADMINS_TOO_MUCH" {
return .adminsTooMuch
} else if error.errorDescription == "USER_PRIVACY_RESTRICTED" {
return .restricted
} else if error.errorDescription == "USER_BLOCKED" {
return .userBlocked
}
return .generic
}
|> mapToSignal { updates -> Signal<Never, ChannelOwnershipTransferError> in
accountStateManager.addUpdates(updates)
return.complete()
}
}
}
public func updateChannelOwnership(postbox: Postbox, network: Network, accountStateManager: AccountStateManager, channelId: PeerId, memberId: PeerId, password: String) -> Signal<Never, ChannelOwnershipTransferError> {
guard !password.isEmpty else {
return .fail(.invalidPassword)
}
return postbox.transaction { transaction -> (channel: Peer?, user: Peer?) in
return (channel: transaction.getPeer(channelId), user: transaction.getPeer(memberId))
}
@ -36,22 +87,17 @@ public func updateChannelOwnership(postbox: Postbox, network: Network, accountSt
return .fail(.generic)
}
let checkPassword: Signal<Api.InputCheckPasswordSRP, ChannelOwnershipTransferError>
if let password = password, !password.isEmpty {
checkPassword = twoStepAuthData(network)
|> mapError { _ in ChannelOwnershipTransferError.generic }
|> mapToSignal { authData -> Signal<Api.InputCheckPasswordSRP, ChannelOwnershipTransferError> in
if let currentPasswordDerivation = authData.currentPasswordDerivation, let srpSessionData = authData.srpSessionData {
guard let kdfResult = passwordKDF(password: password, derivation: currentPasswordDerivation, srpSessionData: srpSessionData) else {
return .fail(.generic)
}
return .single(.inputCheckPasswordSRP(srpId: kdfResult.id, A: Buffer(data: kdfResult.A), M1: Buffer(data: kdfResult.M1)))
} else {
return .fail(.twoStepAuthMissing)
let checkPassword = twoStepAuthData(network)
|> mapError { _ in ChannelOwnershipTransferError.generic }
|> mapToSignal { authData -> Signal<Api.InputCheckPasswordSRP, ChannelOwnershipTransferError> in
if let currentPasswordDerivation = authData.currentPasswordDerivation, let srpSessionData = authData.srpSessionData {
guard let kdfResult = passwordKDF(password: password, derivation: currentPasswordDerivation, srpSessionData: srpSessionData) else {
return .fail(.generic)
}
return .single(.inputCheckPasswordSRP(srpId: kdfResult.id, A: Buffer(data: kdfResult.A), M1: Buffer(data: kdfResult.M1)))
} else {
return .fail(.twoStepAuthMissing)
}
} else {
checkPassword = .single(.inputCheckPasswordEmpty)
}
return checkPassword
@ -59,11 +105,7 @@ public func updateChannelOwnership(postbox: Postbox, network: Network, accountSt
return network.request(Api.functions.channels.editCreator(channel: apiChannel, userId: apiUser, password: password))
|> mapError { error -> ChannelOwnershipTransferError in
if error.errorDescription == "PASSWORD_HASH_INVALID" {
if case .inputCheckPasswordEmpty = password {
return .requestPassword
} else {
return .invalidPassword
}
return .invalidPassword
} else if error.errorDescription == "PASSWORD_MISSING" {
return .twoStepAuthMissing
} else if error.errorDescription.hasPrefix("PASSWORD_TOO_FRESH_") {

View File

@ -41,7 +41,6 @@ public enum ChannelDiscussionGroupError {
}
public func updateGroupDiscussionForChannel(network: Network, postbox: Postbox, channelId: PeerId, groupId: PeerId?) -> Signal<Bool, ChannelDiscussionGroupError> {
return postbox.transaction { transaction -> (channel: Peer?, group: Peer?) in
return (channel: transaction.getPeer(channelId), group: groupId != nil ? transaction.getPeer(groupId!) : nil)
}
@ -101,5 +100,4 @@ public func updateGroupDiscussionForChannel(network: Network, postbox: Postbox,
return .single(result)
}
}
}

View File

@ -13,7 +13,7 @@ public struct PeerNearby {
public let distance: Int32
}
public func peersNearby(network: Network, accountStateManager: AccountStateManager, coordinate: (latitude: Double, longitude: Double), radius: Int32) -> Signal<[PeerNearby], NoError> {
public func peersNearby(network: Network, accountStateManager: AccountStateManager, coordinate: (latitude: Double, longitude: Double)) -> Signal<[PeerNearby], NoError> {
let inputGeoPoint = Api.InputGeoPoint.inputGeoPoint(lat: coordinate.latitude, long: coordinate.longitude)
return network.request(Api.functions.contacts.getLocated(geoPoint: inputGeoPoint))
@ -44,3 +44,55 @@ public func peersNearby(network: Network, accountStateManager: AccountStateManag
|> then(accountStateManager.updatedPeersNearby())
}
}
public func updateChannelGeoLocation(postbox: Postbox, network: Network, channelId: PeerId, coordinate: (latitude: Double, longitude: Double)?, address: String?) -> Signal<Bool, NoError> {
return postbox.transaction { transaction -> Peer? in
return transaction.getPeer(channelId)
}
|> mapToSignal { channel -> Signal<Bool, NoError> in
guard let channel = channel, let apiChannel = apiInputChannel(channel) else {
return .single(false)
}
let geoPoint: Api.InputGeoPoint
if let (latitude, longitude) = coordinate, let _ = address {
geoPoint = .inputGeoPoint(lat: latitude, long: longitude)
} else {
geoPoint = .inputGeoPointEmpty
}
return network.request(Api.functions.channels.editLocation(channel: apiChannel, geoPoint: geoPoint, address: address ?? ""))
|> map { result -> Bool in
switch result {
case .boolTrue:
return true
case .boolFalse:
return false
}
}
|> `catch` { error -> Signal<Bool, NoError> in
return .single(false)
}
|> mapToSignal { result in
if result {
return postbox.transaction { transaction in
transaction.updatePeerCachedData(peerIds: Set([channelId]), update: { (_, current) -> CachedPeerData? in
let current: CachedChannelData = current as? CachedChannelData ?? CachedChannelData()
let peerGeoLocation: PeerGeoLocation?
if let (latitude, longitude) = coordinate, let address = address {
peerGeoLocation = PeerGeoLocation(latitude: latitude, longitude: longitude, address: address)
} else {
peerGeoLocation = nil
}
return current.withUpdatedPeerGeoLocation(peerGeoLocation: peerGeoLocation)
})
}
|> map { _ in
return result
}
} else {
return .single(result)
}
}
}
}

View File

@ -297,13 +297,19 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
}
let linkedDiscussionPeerId: PeerId?
if let linkedChatId = linkedChatId, linkedChatId != 0 {
linkedDiscussionPeerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: linkedChatId)
} else {
linkedDiscussionPeerId = nil
}
let peerGeoLocation: PeerGeoLocation?
if let location = location {
peerGeoLocation = PeerGeoLocation(apiLocation: location)
} else {
peerGeoLocation = nil
}
var botInfos: [CachedPeerBotInfo] = []
for botInfo in apiBotInfos {
switch botInfo {
@ -391,6 +397,7 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
.withUpdatedMinAvailableMessageId(minAvailableMessageId)
.withUpdatedMigrationReference(migrationReference)
.withUpdatedLinkedDiscussionPeerId(linkedDiscussionPeerId)
.withUpdatedPeerGeoLocation(peerGeoLocation: peerGeoLocation)
})
if let minAvailableMessageId = minAvailableMessageId, minAvailableMessageIdUpdated {

View File

@ -77,6 +77,130 @@ func validateAnimationComposition(json: [AnyHashable: Any]) -> Bool {
return true
}
func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: CGSize) -> Signal<String, NoError> {
return Signal({ subscriber in
let startTime = CACurrentMediaTime()
var drawingTime: Double = 0
var appendingTime: Double = 0
let decompressedData = TGGUnzipData(data)
if let decompressedData = decompressedData, let json = (try? JSONSerialization.jsonObject(with: decompressedData, options: [])) as? [AnyHashable: Any] {
if validateAnimationComposition(json: json) {
let model = LOTComposition(json: json)
if let startFrame = model.startFrame?.int32Value, let endFrame = model.endFrame?.int32Value {
print("read at \(CACurrentMediaTime() - startTime)")
var randomId: Int64 = 0
arc4random_buf(&randomId, 8)
let path = NSTemporaryDirectory() + "\(randomId).mp4"
let url = URL(fileURLWithPath: path)
let videoSize = CGSize(width: size.width, height: size.height * 2.0)
let scale = size.width / 512.0
if let assetWriter = try? AVAssetWriter(outputURL: url, fileType: AVFileType.mp4) {
let videoSettings: [String: AnyObject] = [AVVideoCodecKey : AVVideoCodecH264 as AnyObject, AVVideoWidthKey : videoSize.width as AnyObject, AVVideoHeightKey : videoSize.height as AnyObject]
let assetWriterInput = AVAssetWriterInput(mediaType: AVMediaType.video, outputSettings: videoSettings)
let sourceBufferAttributes = [(kCVPixelBufferPixelFormatTypeKey as String): Int(kCVPixelFormatType_32ARGB),
(kCVPixelBufferWidthKey as String): Float(videoSize.width),
(kCVPixelBufferHeightKey as String): Float(videoSize.height)] as [String : Any]
let pixelBufferAdaptor = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: assetWriterInput, sourcePixelBufferAttributes: sourceBufferAttributes)
assetWriter.add(assetWriterInput)
if assetWriter.startWriting() {
print("startedWriting at \(CACurrentMediaTime() - startTime)")
assetWriter.startSession(atSourceTime: kCMTimeZero)
var currentFrame: Int32 = 0
let writeQueue = DispatchQueue(label: "assetWriterQueue")
writeQueue.async {
let container = LOTAnimationLayerContainer(model: model, size: size)
let singleContext = DrawingContext(size: size, scale: 1.0, clear: true)
let context = DrawingContext(size: videoSize, scale: 1.0, clear: false)
let fps: Int32 = model.framerate?.int32Value ?? 30
let frameDuration = CMTimeMake(1, fps)
assetWriterInput.requestMediaDataWhenReady(on: writeQueue) {
while assetWriterInput.isReadyForMoreMediaData && startFrame + currentFrame < endFrame {
let lastFrameTime = CMTimeMake(Int64(currentFrame - startFrame), fps)
let presentationTime = currentFrame == 0 ? lastFrameTime : CMTimeAdd(lastFrameTime, frameDuration)
let drawStartTime = CACurrentMediaTime()
singleContext.withContext { context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.saveGState()
context.scaleBy(x: scale, y: scale)
container?.renderFrame(startFrame + currentFrame, in: context)
context.restoreGState()
}
if let image = singleContext.generateImage()?.cgImage {
let maskDecode = [
CGFloat(1.0), CGFloat(1.0),
CGFloat(1.0), CGFloat(1.0),
CGFloat(1.0), CGFloat(1.0),
CGFloat(1.0), CGFloat(1.0)]
let maskImage = CGImage(width: image.width, height: image.height, bitsPerComponent: image.bitsPerComponent, bitsPerPixel: image.bitsPerPixel, bytesPerRow: image.bytesPerRow, space: image.colorSpace!, bitmapInfo: image.bitmapInfo, provider: image.dataProvider!, decode: maskDecode, shouldInterpolate: image.shouldInterpolate, intent: image.renderingIntent)!
context.withFlippedContext { context in
context.setFillColor(UIColor.white.cgColor)
context.fill(CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: videoSize))
context.draw(image, in: CGRect(origin: CGPoint(x: 0.0, y: size.height), size: size))
context.draw(maskImage, in: CGRect(origin: CGPoint(), size: size))
}
drawingTime += CACurrentMediaTime() - drawStartTime
let appendStartTime = CACurrentMediaTime()
if let image = context.generateImage() {
if let pixelBufferPool = pixelBufferAdaptor.pixelBufferPool {
let pixelBufferPointer = UnsafeMutablePointer<CVPixelBuffer?>.allocate(capacity: 1)
let status = CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, pixelBufferPool, pixelBufferPointer)
if let pixelBuffer = pixelBufferPointer.pointee, status == 0 {
fillPixelBufferFromImage(image, pixelBuffer: pixelBuffer)
pixelBufferAdaptor.append(pixelBuffer, withPresentationTime: presentationTime)
pixelBufferPointer.deinitialize(count: 1)
} else {
break
}
pixelBufferPointer.deallocate()
} else {
break
}
}
appendingTime += CACurrentMediaTime() - appendStartTime
}
currentFrame += 1
}
if startFrame + currentFrame == endFrame {
assetWriterInput.markAsFinished()
assetWriter.finishWriting {
subscriber.putNext(path)
subscriber.putCompletion()
print("animation render time \(CACurrentMediaTime() - startTime)")
print("of which drawing time \(drawingTime)")
print("of which appending time \(appendingTime)")
}
}
}
}
}
}
}
}
}
return EmptyDisposable
})
}
func convertCompressedLottieToCombinedMp4(data: Data, size: CGSize) -> Signal<String, NoError> {
return Signal({ subscriber in
let startTime = CACurrentMediaTime()
@ -129,6 +253,7 @@ func convertCompressedLottieToCombinedMp4(data: Data, size: CGSize) -> Signal<St
let lastFrameTime = CMTimeMake(Int64(currentFrame - startFrame), fps)
let presentationTime = currentFrame == 0 ? lastFrameTime : CMTimeAdd(lastFrameTime, frameDuration)
let drawStartTime = CACurrentMediaTime()
singleContext.withContext { context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.saveGState()
@ -138,7 +263,7 @@ func convertCompressedLottieToCombinedMp4(data: Data, size: CGSize) -> Signal<St
}
if let image = singleContext.generateImage()?.cgImage {
let drawStartTime = CACurrentMediaTime()
let maskDecode = [
CGFloat(1.0), CGFloat(1.0),
CGFloat(1.0), CGFloat(1.0),

View File

@ -464,7 +464,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
entries.append(.addAdminsInfo(presentationData.theme, currentRightsFlags.contains(.canAddAdmins) ? presentationData.strings.Channel_EditAdmin_PermissinAddAdminOn : presentationData.strings.Channel_EditAdmin_PermissinAddAdminOff))
}
if let admin = admin as? TelegramUser, admin.botInfo == nil && channel.flags.contains(.isCreator) && areAllAdminRightsEnabled(currentRightsFlags, group: isGroup) {
if let admin = admin as? TelegramUser, admin.botInfo == nil && !admin.isDeleted && channel.flags.contains(.isCreator) && areAllAdminRightsEnabled(currentRightsFlags, group: isGroup) {
entries.append(.transfer(presentationData.theme, isGroup ? presentationData.strings.Group_EditAdmin_TransferOwnership : presentationData.strings.Channel_EditAdmin_TransferOwnership))
}
@ -534,7 +534,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
entries.append(.addAdminsInfo(presentationData.theme, currentRightsFlags.contains(.canAddAdmins) ? presentationData.strings.Channel_EditAdmin_PermissinAddAdminOn : presentationData.strings.Channel_EditAdmin_PermissinAddAdminOff))
}
if let admin = admin as? TelegramUser, admin.botInfo == nil && group.role == .creator && areAllAdminRightsEnabled(currentRightsFlags, group: true) {
if let admin = admin as? TelegramUser, admin.botInfo == nil && !admin.isDeleted && group.role == .creator && areAllAdminRightsEnabled(currentRightsFlags, group: true) {
entries.append(.transfer(presentationData.theme, presentationData.strings.Group_EditAdmin_TransferOwnership))
}
@ -588,47 +588,21 @@ public func channelAdminController(context: AccountContext, peerId: PeerId, admi
return
}
var signal: Signal<Never, ChannelOwnershipTransferError> = .complete()
if let channel = peer as? TelegramChannel {
signal = updateChannelOwnership(postbox: context.account.postbox, network: context.account.network, accountStateManager: context.account.stateManager, channelId: channel.id, memberId: adminId, password: nil)
} else if let _ = peer as? TelegramGroup {
signal = convertGroupToSupergroup(account: context.account, peerId: peerId)
|> map(Optional.init)
|> mapError { _ in ChannelOwnershipTransferError.generic }
|> mapToSignal { upgradedPeerId -> Signal<Never, ChannelOwnershipTransferError> in
guard let upgradedPeerId = upgradedPeerId else {
return .fail(.generic)
}
upgradedToSupergroupImpl(upgradedPeerId, {})
return updateChannelOwnership(postbox: context.account.postbox, network: context.account.network, accountStateManager: context.account.stateManager, channelId: upgradedPeerId, memberId: adminId, password: nil)
}
}
transferOwnershipDisposable.set((signal |> deliverOnMainQueue).start(error: { error in
let currentPeerId = actualPeerId.with { $0 }
let channel: Signal<Peer?, NoError>
if currentPeerId == peerId {
channel = .single(peer)
} else {
channel = context.account.postbox.transaction { transaction -> Peer? in
return transaction.getPeer(currentPeerId)
}
}
let _ = (channel |> deliverOnMainQueue).start(next: { channel in
guard let channel = channel as? TelegramChannel else {
return
}
let controller = channelOwnershipTransferController(context: context, channel: channel, member: member, initialError: error, present: { c, a in
presentControllerImpl?(c, a)
}, completion: {
transferOwnershipDisposable.set((checkOwnershipTranfserAvailability(postbox: context.account.postbox, network: context.account.network, accountStateManager: context.account.stateManager, memberId: adminId) |> deliverOnMainQueue).start(error: { error in
let controller = channelOwnershipTransferController(context: context, peer: peer, member: member, initialError: error, present: { c, a in
presentControllerImpl?(c, a)
}, completion: { upgradedPeerId in
if let upgradedPeerId = upgradedPeerId {
upgradedToSupergroupImpl(upgradedPeerId, {
dismissImpl?()
transferedOwnership(member.id)
})
} else {
dismissImpl?()
transferedOwnership(member.id)
})
presentControllerImpl?(controller, nil)
}
})
presentControllerImpl?(controller, nil)
}))
})
}, dismissAdmin: {

View File

@ -823,6 +823,7 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
updateActivity(true)
let foundGroupMembers: Signal<[RenderedChannelParticipant], NoError>
let foundMembers: Signal<[RenderedChannelParticipant], NoError>
let foundRemotePeers: Signal<([FoundPeer], [FoundPeer]), NoError>
switch mode {
case .searchMembers, .banAndPromoteActions:
@ -882,8 +883,15 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
foundMembers = .single([])
}
return combineLatest(foundGroupMembers, foundMembers, themeAndStringsPromise.get(), statePromise.get())
|> map { foundGroupMembers, foundMembers, themeAndStrings, state -> [ChannelMembersSearchEntry]? in
if mode == .banAndPromoteActions || mode == .inviteActions {
foundRemotePeers = .single(([], [])) |> then(searchPeers(account: context.account, query: query)
|> delay(0.2, queue: Queue.concurrentDefaultQueue()))
} else {
foundRemotePeers = .single(([], []))
}
return combineLatest(foundGroupMembers, foundMembers, foundRemotePeers, themeAndStringsPromise.get(), statePromise.get())
|> map { foundGroupMembers, foundMembers, foundRemotePeers, themeAndStrings, state -> [ChannelMembersSearchEntry]? in
var entries: [ChannelMembersSearchEntry] = []
var existingPeerIds = Set<PeerId>()
@ -1063,6 +1071,24 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
}
}
for foundPeer in foundRemotePeers.0 {
let peer = foundPeer.peer
if !existingPeerIds.contains(peer.id) && peer is TelegramUser {
existingPeerIds.insert(peer.id)
entries.append(ChannelMembersSearchEntry(index: index, content: .peer(peer), section: .global, dateTimeFormat: themeAndStrings.4))
index += 1
}
}
for foundPeer in foundRemotePeers.1 {
let peer = foundPeer.peer
if !existingPeerIds.contains(peer.id) && peer is TelegramUser {
existingPeerIds.insert(peer.id)
entries.append(ChannelMembersSearchEntry(index: index, content: .peer(peer), section: .global, dateTimeFormat: themeAndStrings.4))
index += 1
}
}
return entries
}
} else {

View File

@ -396,7 +396,7 @@ private final class ChannelOwnershipTransferAlertContentNode: AlertContentNode {
}
}
private func commitChannelOwnershipTransferController(context: AccountContext, channel: TelegramChannel, member: TelegramUser, completion: @escaping () -> Void) -> ViewController {
private func commitChannelOwnershipTransferController(context: AccountContext, peer: Peer, member: TelegramUser, completion: @escaping (PeerId?) -> Void) -> ViewController {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
var dismissImpl: (() -> Void)?
@ -428,29 +428,59 @@ private func commitChannelOwnershipTransferController(context: AccountContext, c
return
}
contentNode.updateIsChecking(true)
disposable.set((updateChannelOwnership(postbox: context.account.postbox, network: context.account.network, accountStateManager: context.account.stateManager, channelId: channel.id, memberId: member.id, password: contentNode.password) |> deliverOnMainQueue).start(error: { [weak contentNode] error in
let signal: Signal<PeerId?, ChannelOwnershipTransferError>
if let peer = peer as? TelegramChannel {
signal = updateChannelOwnership(postbox: context.account.postbox, network: context.account.network, accountStateManager: context.account.stateManager, channelId: peer.id, memberId: member.id, password: contentNode.password) |> mapToSignal { _ in
return .complete()
}
|> then(.single(nil))
} else if let peer = peer as? TelegramGroup {
signal = convertGroupToSupergroup(account: context.account, peerId: peer.id)
|> map(Optional.init)
|> mapError { _ in ChannelOwnershipTransferError.generic }
|> deliverOnMainQueue
|> mapToSignal { upgradedPeerId -> Signal<PeerId?, ChannelOwnershipTransferError> in
guard let upgradedPeerId = upgradedPeerId else {
return .fail(.generic)
}
return updateChannelOwnership(postbox: context.account.postbox, network: context.account.network, accountStateManager: context.account.stateManager, channelId: upgradedPeerId, memberId: member.id, password: contentNode.password) |> mapToSignal { _ in
return .complete()
}
|> then(.single(upgradedPeerId))
}
} else {
signal = .never()
}
disposable.set((signal |> deliverOnMainQueue).start(next: { upgradedPeerId in
dismissImpl?()
completion(upgradedPeerId)
}, error: { [weak contentNode] error in
contentNode?.updateIsChecking(false)
contentNode?.animateError()
}, completed: {
dismissImpl?()
completion()
}))
}
return controller
}
private func confirmChannelOwnershipTransferController(context: AccountContext, channel: TelegramChannel, member: TelegramUser, present: @escaping (ViewController, Any?) -> Void, completion: @escaping () -> Void) -> ViewController {
private func confirmChannelOwnershipTransferController(context: AccountContext, peer: Peer, member: TelegramUser, present: @escaping (ViewController, Any?) -> Void, completion: @escaping (PeerId?) -> Void) -> ViewController {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let theme = AlertControllerTheme(presentationTheme: presentationData.theme)
var isGroup = true
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
isGroup = false
}
var title: String
var text: String
if case .group = channel.info {
if isGroup {
title = presentationData.strings.Group_OwnershipTransfer_Title
text = presentationData.strings.Group_OwnershipTransfer_DescriptionInfo(channel.displayTitle, member.displayTitle).0
text = presentationData.strings.Group_OwnershipTransfer_DescriptionInfo(peer.displayTitle, member.displayTitle).0
} else {
title = presentationData.strings.Channel_OwnershipTransfer_Title
text = presentationData.strings.Channel_OwnershipTransfer_DescriptionInfo(channel.displayTitle, member.displayTitle).0
text = presentationData.strings.Channel_OwnershipTransfer_DescriptionInfo(peer.displayTitle, member.displayTitle).0
}
let attributedTitle = NSAttributedString(string: title, font: Font.medium(17.0), textColor: theme.primaryColor, paragraphAlignment: .center)
@ -462,7 +492,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, channel: channel, member: member, completion: completion), nil)
present(commitChannelOwnershipTransferController(context: context, peer: peer, member: member, completion: completion), nil)
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {
dismissImpl?()
})], actionLayout: .vertical)
@ -472,16 +502,16 @@ private func confirmChannelOwnershipTransferController(context: AccountContext,
return controller
}
func channelOwnershipTransferController(context: AccountContext, channel: TelegramChannel, member: TelegramUser, initialError: ChannelOwnershipTransferError, present: @escaping (ViewController, Any?) -> Void, completion: @escaping () -> Void) -> ViewController {
func channelOwnershipTransferController(context: AccountContext, peer: Peer, member: TelegramUser, initialError: ChannelOwnershipTransferError, present: @escaping (ViewController, Any?) -> Void, completion: @escaping (PeerId?) -> Void) -> ViewController {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let theme = AlertControllerTheme(presentationTheme: presentationData.theme)
var title: NSAttributedString? = NSAttributedString(string: presentationData.strings.OwnershipTransfer_SecurityCheck, font: Font.medium(17.0), textColor: theme.primaryColor, paragraphAlignment: .center)
var text = presentationData.strings.OwnershipTransfer_SecurityRequirements
var isGroup = false
if case .group = channel.info {
isGroup = true
var isGroup = true
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
isGroup = false
}
var dismissImpl: (() -> Void)?
@ -489,7 +519,7 @@ func channelOwnershipTransferController(context: AccountContext, channel: Telegr
switch initialError {
case .requestPassword:
return confirmChannelOwnershipTransferController(context: context, channel: channel, member: member, present: present, completion: completion)
return confirmChannelOwnershipTransferController(context: context, peer: peer, member: member, present: present, completion: completion)
case .twoStepAuthTooFresh, .authSessionTooFresh:
text = text + presentationData.strings.OwnershipTransfer_ComeBackLater
actions = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]

View File

@ -17,8 +17,10 @@ private final class ChannelVisibilityControllerArguments {
let copyPrivateLink: () -> Void
let revokePrivateLink: () -> Void
let sharePrivateLink: () -> Void
let setLocation: () -> Void
let removeLocation: () -> Void
init(account: Account, updateCurrentType: @escaping (CurrentChannelType) -> Void, updatePublicLinkText: @escaping (String?, String) -> Void, scrollToPublicLinkText: @escaping () -> Void, displayPrivateLinkMenu: @escaping (String) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, revokePeerId: @escaping (PeerId) -> Void, copyPrivateLink: @escaping () -> Void, revokePrivateLink: @escaping () -> Void, sharePrivateLink: @escaping () -> Void) {
init(account: Account, updateCurrentType: @escaping (CurrentChannelType) -> Void, updatePublicLinkText: @escaping (String?, String) -> Void, scrollToPublicLinkText: @escaping () -> Void, displayPrivateLinkMenu: @escaping (String) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, revokePeerId: @escaping (PeerId) -> Void, copyPrivateLink: @escaping () -> Void, revokePrivateLink: @escaping () -> Void, sharePrivateLink: @escaping () -> Void, setLocation: @escaping () -> Void, removeLocation: @escaping () -> Void) {
self.account = account
self.updateCurrentType = updateCurrentType
self.updatePublicLinkText = updatePublicLinkText
@ -29,6 +31,8 @@ private final class ChannelVisibilityControllerArguments {
self.copyPrivateLink = copyPrivateLink
self.revokePrivateLink = revokePrivateLink
self.sharePrivateLink = sharePrivateLink
self.setLocation = setLocation
self.removeLocation = removeLocation
}
}
@ -36,6 +40,7 @@ private enum ChannelVisibilitySection: Int32 {
case type
case link
case linkActions
case location
}
private enum ChannelVisibilityEntryTag: ItemListItemTag {
@ -70,6 +75,12 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
case existingLinksInfo(PresentationTheme, String)
case existingLinkPeerItem(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer, ItemListPeerItemEditing, Bool)
case locationHeader(PresentationTheme, String)
case location(PresentationTheme, PeerGeoLocation)
case locationSetup(PresentationTheme, String)
case locationRemove(PresentationTheme, String)
case locationInfo(PresentationTheme, String)
var section: ItemListSectionId {
switch self {
case .typeHeader, .typePublic, .typePrivate, .typeInfo:
@ -80,6 +91,8 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
return ChannelVisibilitySection.linkActions.rawValue
case .existingLinksInfo, .existingLinkPeerItem:
return ChannelVisibilitySection.link.rawValue
case .locationHeader, .location, .locationSetup, .locationRemove, .locationInfo:
return ChannelVisibilitySection.location.rawValue
}
}
@ -93,7 +106,6 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
return 2
case .typeInfo:
return 3
case .publicLinkAvailability:
return 4
case .privateLink:
@ -116,6 +128,16 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
return 13
case let .existingLinkPeerItem(index, _, _, _, _, _, _, _):
return 14 + index
case .locationHeader:
return 1000
case .location:
return 1001
case .locationSetup:
return 1002
case .locationRemove:
return 1003
case .locationInfo:
return 1004
}
}
@ -235,6 +257,36 @@ private enum ChannelVisibilityEntry: 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
}
case let .locationSetup(lhsTheme, lhsTitle):
if case let .locationSetup(rhsTheme, rhsTitle) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle {
return true
} else {
return false
}
case let .locationRemove(lhsTheme, lhsTitle):
if case let .locationRemove(rhsTheme, rhsTitle) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle {
return true
} else {
return false
}
case let .locationInfo(lhsTheme, lhsTitle):
if case let .locationInfo(rhsTheme, rhsTitle) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle {
return true
} else {
return false
}
}
}
@ -321,6 +373,21 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
}, removePeer: { peerId in
arguments.revokePeerId(peerId)
})
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, 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()
})
case let .locationRemove(theme, text):
return ItemListActionItem(theme: theme, title: text, kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
arguments.removeLocation()
})
case let .locationInfo(theme, text):
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
}
}
}
@ -330,6 +397,11 @@ private enum CurrentChannelType {
case privateChannel
}
private enum CurrentChannelLocation: Equatable {
case removed
case location(PeerGeoLocation)
}
private struct ChannelVisibilityControllerState: Equatable {
let selectedType: CurrentChannelType?
let editingPublicLinkText: String?
@ -338,6 +410,7 @@ private struct ChannelVisibilityControllerState: Equatable {
let revealedRevokePeerId: PeerId?
let revokingPeerId: PeerId?
let revokingPrivateLink: Bool
let editingLocation: CurrentChannelLocation?
init() {
self.selectedType = nil
@ -347,9 +420,10 @@ private struct ChannelVisibilityControllerState: Equatable {
self.revealedRevokePeerId = nil
self.revokingPeerId = nil
self.revokingPrivateLink = false
self.editingLocation = nil
}
init(selectedType: CurrentChannelType?, editingPublicLinkText: String?, addressNameValidationStatus: AddressNameValidationStatus?, updatingAddressName: Bool, revealedRevokePeerId: PeerId?, revokingPeerId: PeerId?, revokingPrivateLink: Bool) {
init(selectedType: CurrentChannelType?, editingPublicLinkText: String?, addressNameValidationStatus: AddressNameValidationStatus?, updatingAddressName: Bool, revealedRevokePeerId: PeerId?, revokingPeerId: PeerId?, revokingPrivateLink: Bool, editingLocation: CurrentChannelLocation?) {
self.selectedType = selectedType
self.editingPublicLinkText = editingPublicLinkText
self.addressNameValidationStatus = addressNameValidationStatus
@ -357,6 +431,7 @@ private struct ChannelVisibilityControllerState: Equatable {
self.revealedRevokePeerId = revealedRevokePeerId
self.revokingPeerId = revokingPeerId
self.revokingPrivateLink = revokingPrivateLink
self.editingLocation = editingLocation
}
static func ==(lhs: ChannelVisibilityControllerState, rhs: ChannelVisibilityControllerState) -> Bool {
@ -381,36 +456,42 @@ private struct ChannelVisibilityControllerState: Equatable {
if lhs.revokingPrivateLink != rhs.revokingPrivateLink {
return false
}
if lhs.editingLocation != rhs.editingLocation {
return false
}
return true
}
func withUpdatedSelectedType(_ selectedType: CurrentChannelType?) -> ChannelVisibilityControllerState {
return ChannelVisibilityControllerState(selectedType: selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: self.updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink)
return ChannelVisibilityControllerState(selectedType: selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: self.updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, editingLocation: self.editingLocation)
}
func withUpdatedEditingPublicLinkText(_ editingPublicLinkText: String?) -> ChannelVisibilityControllerState {
return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: self.updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink)
return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: self.updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, editingLocation: self.editingLocation)
}
func withUpdatedAddressNameValidationStatus(_ addressNameValidationStatus: AddressNameValidationStatus?) -> ChannelVisibilityControllerState {
return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: addressNameValidationStatus, updatingAddressName: self.updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink)
return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: addressNameValidationStatus, updatingAddressName: self.updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, editingLocation: self.editingLocation)
}
func withUpdatedUpdatingAddressName(_ updatingAddressName: Bool) -> ChannelVisibilityControllerState {
return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink)
return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, editingLocation: self.editingLocation)
}
func withUpdatedRevealedRevokePeerId(_ revealedRevokePeerId: PeerId?) -> ChannelVisibilityControllerState {
return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink)
return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, editingLocation: self.editingLocation)
}
func withUpdatedRevokingPeerId(_ revokingPeerId: PeerId?) -> ChannelVisibilityControllerState {
return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: revokingPeerId, revokingPrivateLink: self.revokingPrivateLink)
return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, editingLocation: self.editingLocation)
}
func withUpdatedRevokingPrivateLink(_ revokingPrivateLink: Bool) -> ChannelVisibilityControllerState {
return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: revokingPrivateLink)
return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: revokingPrivateLink, editingLocation: self.editingLocation)
}
func withUpdatedEditingLocation(_ editingLocation: CurrentChannelLocation?) -> ChannelVisibilityControllerState {
return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, editingLocation: editingLocation)
}
}
@ -544,6 +625,28 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
}
if isGroup {
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 {
if let location = (view.cachedData as? CachedChannelData)?.peerGeoLocation {
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))
}
}
entries.append(.locationInfo(presentationData.theme, presentationData.strings.Group_Location_Info))
} else {
entries.append(.publicLinkInfo(presentationData.theme, presentationData.strings.Channel_Username_CreatePublicLinkHelp))
}
@ -698,6 +801,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
return entries
}
private func effectiveChannelType(state: ChannelVisibilityControllerState, peer: TelegramChannel) -> CurrentChannelType {
let selectedType: CurrentChannelType
if let current = state.selectedType {
@ -793,13 +897,16 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
let updateAddressNameDisposable = MetaDisposable()
actionsDisposable.add(updateAddressNameDisposable)
let updateLocationDisposable = MetaDisposable()
actionsDisposable.add(updateLocationDisposable)
let revokeAddressNameDisposable = MetaDisposable()
actionsDisposable.add(revokeAddressNameDisposable)
let revokeLinkDisposable = MetaDisposable()
actionsDisposable.add(revokeLinkDisposable)
actionsDisposable.add( (context.account.viewTracker.peerView(peerId) |> filter { $0.cachedData != nil } |> take(1) |> mapToSignal { view -> Signal<Void, NoError> in
actionsDisposable.add((context.account.viewTracker.peerView(peerId) |> filter { $0.cachedData != nil } |> take(1) |> mapToSignal { view -> Signal<Void, NoError> in
return ensuredExistingPeerExportedInvitation(account: context.account, peerId: peerId)
}).start())
@ -824,11 +931,11 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
}
checkAddressNameDisposable.set((validateAddressNameInteractive(account: context.account, domain: .peer(peerId), name: text)
|> deliverOnMainQueue).start(next: { result in
updateState { state in
return state.withUpdatedAddressNameValidationStatus(result)
}
}))
|> deliverOnMainQueue).start(next: { result in
updateState { state in
return state.withUpdatedAddressNameValidationStatus(result)
}
}))
}
}, scrollToPublicLinkText: {
scrollToPublicLinkTextImpl?()
@ -924,9 +1031,41 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
presentControllerImpl?(shareController, nil)
}
})
}, setLocation: {
let _ = (context.account.postbox.transaction { transaction -> Peer? in
return transaction.getPeer(peerId)
} |> deliverOnMainQueue).start(next: { peer in
guard let peer = peer else {
return
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let controller = legacyLocationPickerController(context: context, selfPeer: peer, peer: peer, sendLocation: { coordinate, _ in
updateState { state in
return state.withUpdatedEditingLocation(.location(PeerGeoLocation(latitude: coordinate.latitude, longitude: coordinate.longitude, address: "Locating...")))
}
let _ = (reverseGeocodeLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
|> deliverOnMainQueue).start(next: { placemark in
updateState { state in
let address: String
if let placemark = placemark {
address = placemark.fullAddress.replacingOccurrences(of: ", ", with: "\n")
} else {
address = "\(coordinate.latitude), \(coordinate.longitude)"
}
return state.withUpdatedEditingLocation(.location(PeerGeoLocation(latitude: coordinate.latitude, longitude: coordinate.longitude, address: address)))
}
})
}, sendLiveLocation: { _, _ in }, theme: presentationData.theme, customLocationPicker: true)
presentControllerImpl?(controller, nil)
})
}, removeLocation: {
updateState { state in
return state.withUpdatedEditingLocation(.removed)
}
})
let peerView = context.account.viewTracker.peerView(peerId)
|> deliverOnMainQueue
@ -960,11 +1099,37 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
rightNavigationButton = ItemListNavigationButton(content: .text(mode == .initialSetup ? presentationData.strings.Common_Next : presentationData.strings.Common_Done), style: state.updatingAddressName ? .activity : .bold, enabled: doneEnabled, action: {
var updatedAddressNameValue: String?
var updatedLocation: CurrentChannelLocation?
updateState { state in
updatedAddressNameValue = updatedAddressName(state: state, peer: peer)
updatedLocation = state.editingLocation
return state
}
let updateLocation: (@escaping (Bool) -> Void) -> Void = { completion in
guard let updatedLocation = updatedLocation else {
completion(true)
return
}
switch updatedLocation {
case let .location(location):
updateLocationDisposable.set((updateChannelGeoLocation(postbox: context.account.postbox, network: context.account.network, channelId: peerId, coordinate: (location.latitude, location.longitude), address: location.address)
|> deliverOnMainQueue).start(error: { error in
completion(false)
}, completed: {
completion(true)
}))
case .removed:
updateLocationDisposable.set((updateChannelGeoLocation(postbox: context.account.postbox, network: context.account.network, channelId: peerId, coordinate: nil, address: nil)
|> deliverOnMainQueue).start(error: { error in
completion(false)
}, completed: {
completion(true)
}))
}
}
if let updatedAddressNameValue = updatedAddressNameValue {
let invokeAction: () -> Void = {
updateState { state in
@ -984,12 +1149,14 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
return state.withUpdatedUpdatingAddressName(false)
}
switch mode {
case .initialSetup:
nextImpl?()
case .generic, .privateLink:
dismissImpl?()
}
updateLocation({ success in
switch mode {
case .initialSetup:
nextImpl?()
case .generic, .privateLink:
dismissImpl?()
}
})
}))
}
@ -1002,12 +1169,14 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
}
})
} else {
switch mode {
case .initialSetup:
nextImpl?()
case .generic, .privateLink:
dismissImpl?()
}
updateLocation({ success in
switch mode {
case .initialSetup:
nextImpl?()
case .generic, .privateLink:
dismissImpl?()
}
})
}
})
} else if let peer = peer as? TelegramGroup {

View File

@ -458,7 +458,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
}
//self.snapToBottomInsetUntilFirstInteraction = true
let messageViewQueue = self.messageViewQueue
let messageViewQueue = Queue.mainQueue() //self.messageViewQueue
let fixedCombinedReadStates = Atomic<MessageHistoryViewReadState?>(value: nil)

View File

@ -264,7 +264,21 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
return signal |> then(contextBot)
case let .emojiSearch(query, languageCode, range):
return searchEmojiKeywords(postbox: context.account.postbox, inputLanguageCode: languageCode, query: query, completeMatch: query.count < 3)
var signal = searchEmojiKeywords(postbox: context.account.postbox, inputLanguageCode: languageCode, query: query, completeMatch: query.count < 3)
if !languageCode.lowercased().hasPrefix("en") {
signal = signal
|> mapToSignal { keywords in
return .single(keywords)
|> then(
searchEmojiKeywords(postbox: context.account.postbox, inputLanguageCode: "en-US", query: query, completeMatch: query.count < 3)
|> map { englishKeywords in
return keywords + englishKeywords
}
)
}
}
return signal
|> map { keywords -> [(String, String)] in
var result: [(String, String)] = []
for keyword in keywords {

View File

@ -135,6 +135,11 @@ private final class StickerAnimationNode: ASDisplayNode {
self.fetchDisposable.set(fetchedMediaResource(postbox: account.postbox, reference: fileReference.resourceReference(fileReference.media.resource)).start())
}
func reset() {
self.disposable.set(nil)
self.fetchDisposable.set(nil)
}
private func setupLooping() {
guard let playerItem = self.playerItem, let player = self.player else {
return
@ -166,7 +171,7 @@ private final class StickerAnimationNode: ASDisplayNode {
}
})
playerItem.videoComposition = composition
ready = true
self.ready = true
if self.visibility {
self.player?.play()
}
@ -188,6 +193,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
private var shareButtonNode: HighlightableButtonNode?
var telegramFile: TelegramMediaFile?
private let disposable = MetaDisposable()
private let dateAndStatusNode: ChatMessageDateAndStatusNode
private var replyInfoNode: ChatMessageReplyInfoNode?
@ -214,6 +220,10 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
self.addSubnode(self.dateAndStatusNode)
}
deinit {
self.disposable.dispose()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@ -245,14 +255,17 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
self.view.addGestureRecognizer(replyRecognizer)
}
private var visibilityPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
override var visibility: ListViewItemNodeVisibility {
didSet {
if self.visibility != oldValue {
switch self.visibility {
case .visible:
self.animationNode.visibility = true
self.visibilityPromise.set(true)
case .none:
self.animationNode.visibility = false
self.visibilityPromise.set(false)
}
}
}

View File

@ -412,6 +412,14 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
self.eventLogContext.loadMoreEntries()
let historyViewUpdate = self.eventLogContext.get()
|> map { (entries, hasEarlier, type, hasEntries) in
return (entries.filter { entry in
if case let .participantToggleAdmin(prev, new) = entry.event.action, case .creator = prev.participant, case .member = new.participant {
return false
}
return true
}, hasEarlier, type, hasEntries)
}
let previousView = Atomic<[ChatRecentActionsEntry]?>(value: nil)
@ -419,14 +427,6 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|> mapToQueue { update, chatPresentationData -> Signal<ChatRecentActionsHistoryTransition, NoError> in
let processedView = chatRecentActionsEntries(entries: update.0, presentationData: chatPresentationData)
let previous = previousView.swap(processedView)
var prepareOnMainQueue = false
if let previous = previous, previous == processedView {
} else {
}
return .single(chatRecentActionsHistoryPreparedTransition(from: previous ?? [], to: processedView, type: update.2, canLoadEarlier: update.1, displayingResults: update.3, context: context, peer: peer, controllerInteraction: controllerInteraction))
}
@ -452,7 +452,6 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}
}
//if controllerInteraction.hiddenMedia != messageIdAndMedia {
controllerInteraction.hiddenMedia = messageIdAndMedia
strongSelf.listNode.forEachItemNode { itemNode in
@ -460,15 +459,12 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
itemNode.updateHiddenMedia()
}
}
//}
}
}))
self.presentationDataDisposable = (context.sharedContext.presentationData
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
if let strongSelf = self {
let previousTheme = strongSelf.presentationData.theme
strongSelf.presentationData = presentationData
strongSelf.chatPresentationDataPromise.set(.single(ChatPresentationData(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: presentationData.chatWallpaper), fontSize: presentationData.fontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: presentationData.disableAnimations, largeEmoji: presentationData.largeEmoji)))
@ -807,7 +803,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}, dismissInput: {
self?.view.endEditing(true)
})
case let .wallpaper(slug):
case .wallpaper:
break
}
}

View File

@ -631,54 +631,66 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
var text: String = ""
var entities: [MessageTextEntity] = []
appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessagePromotedName(new.peer.displayTitle) : self.presentationData.strings.Channel_AdminLog_MessagePromotedNameUsername(new.peer.displayTitle, "@" + new.peer.addressName!), generateEntities: { index in
var result: [MessageTextEntityType] = []
if index == 0 {
result.append(.TextMention(peerId: new.peer.id))
} else if index == 1 {
result.append(.Mention)
}
return result
}, to: &text, entities: &entities)
text += "\n"
if case let .member(_, _, prevAdminRights, _) = prev.participant {
if case let .member(_, _, newAdminRights, _) = new.participant {
let prevFlags = prevAdminRights?.rights.flags ?? []
let newFlags = newAdminRights?.rights.flags ?? []
let order: [(TelegramChatAdminRightsFlags, String)]
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
order = [
(.canChangeInfo, self.presentationData.strings.Channel_AdminLog_CanChangeInfo),
(.canPostMessages, self.presentationData.strings.Channel_AdminLog_CanSendMessages),
(.canDeleteMessages, self.presentationData.strings.Channel_AdminLog_CanDeleteMessages),
(.canEditMessages, self.presentationData.strings.Channel_AdminLog_CanEditMessages),
(.canInviteUsers, self.presentationData.strings.Channel_AdminLog_CanInviteUsers),
(.canPinMessages, self.presentationData.strings.Channel_AdminLog_CanPinMessages),
(.canAddAdmins, self.presentationData.strings.Channel_AdminLog_CanAddAdmins)
]
} else {
order = [
(.canChangeInfo, self.presentationData.strings.Channel_AdminLog_CanChangeInfo),
(.canDeleteMessages, self.presentationData.strings.Channel_AdminLog_CanDeleteMessages),
(.canBanUsers, self.presentationData.strings.Channel_AdminLog_CanBanUsers),
(.canInviteUsers, self.presentationData.strings.Channel_AdminLog_CanInviteUsers),
(.canPinMessages, self.presentationData.strings.Channel_AdminLog_CanPinMessages),
(.canAddAdmins, self.presentationData.strings.Channel_AdminLog_CanAddAdmins)
]
if case .member = prev.participant, case .creator = new.participant {
appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageTransferedName(new.peer.displayTitle) : self.presentationData.strings.Channel_AdminLog_MessageTransferedNameUsername(new.peer.displayTitle, "@" + new.peer.addressName!), generateEntities: { index in
var result: [MessageTextEntityType] = []
if index == 0 {
result.append(.TextMention(peerId: new.peer.id))
} else if index == 1 {
result.append(.Mention)
}
for (flag, string) in order {
if prevFlags.contains(flag) != newFlags.contains(flag) {
text += "\n"
if !prevFlags.contains(flag) {
text += "+"
} else {
text += "-"
return result
}, to: &text, entities: &entities)
} else {
appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessagePromotedName(new.peer.displayTitle) : self.presentationData.strings.Channel_AdminLog_MessagePromotedNameUsername(new.peer.displayTitle, "@" + new.peer.addressName!), generateEntities: { index in
var result: [MessageTextEntityType] = []
if index == 0 {
result.append(.TextMention(peerId: new.peer.id))
} else if index == 1 {
result.append(.Mention)
}
return result
}, to: &text, entities: &entities)
text += "\n"
if case let .member(_, _, prevAdminRights, _) = prev.participant {
if case let .member(_, _, newAdminRights, _) = new.participant {
let prevFlags = prevAdminRights?.rights.flags ?? []
let newFlags = newAdminRights?.rights.flags ?? []
let order: [(TelegramChatAdminRightsFlags, String)]
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
order = [
(.canChangeInfo, self.presentationData.strings.Channel_AdminLog_CanChangeInfo),
(.canPostMessages, self.presentationData.strings.Channel_AdminLog_CanSendMessages),
(.canDeleteMessages, self.presentationData.strings.Channel_AdminLog_CanDeleteMessages),
(.canEditMessages, self.presentationData.strings.Channel_AdminLog_CanEditMessages),
(.canInviteUsers, self.presentationData.strings.Channel_AdminLog_CanInviteUsers),
(.canPinMessages, self.presentationData.strings.Channel_AdminLog_CanPinMessages),
(.canAddAdmins, self.presentationData.strings.Channel_AdminLog_CanAddAdmins)
]
} else {
order = [
(.canChangeInfo, self.presentationData.strings.Channel_AdminLog_CanChangeInfo),
(.canDeleteMessages, self.presentationData.strings.Channel_AdminLog_CanDeleteMessages),
(.canBanUsers, self.presentationData.strings.Channel_AdminLog_CanBanUsers),
(.canInviteUsers, self.presentationData.strings.Channel_AdminLog_CanInviteUsers),
(.canPinMessages, self.presentationData.strings.Channel_AdminLog_CanPinMessages),
(.canAddAdmins, self.presentationData.strings.Channel_AdminLog_CanAddAdmins)
]
}
for (flag, string) in order {
if prevFlags.contains(flag) != newFlags.contains(flag) {
text += "\n"
if !prevFlags.contains(flag) {
text += "+"
} else {
text += "-"
}
appendAttributedText(text: string, withEntities: [.Italic], to: &text, entities: &entities)
}
appendAttributedText(text: string, withEntities: [.Italic], to: &text, entities: &entities)
}
}
}
@ -915,36 +927,41 @@ 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: "", attributes: [], media: [TelegramMediaAction(action: action)], 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)))
case let .togglePreHistoryHidden(value):
var peers = SimpleDictionary<PeerId, Peer>()
var author: Peer?
if let peer = self.entry.peers[self.entry.event.peerId] {
author = peer
peers[peer.id] = peer
}
var text: String = ""
var entities: [MessageTextEntity] = []
if !value {
appendAttributedText(text: self.presentationData.strings.Channel_AdminLog_MessageGroupPreHistoryVisible(author?.displayTitle ?? ""), generateEntities: { index in
if index == 0, let author = author {
return [.TextMention(peerId: author.id)]
}
return []
}, to: &text, entities: &entities)
} else {
appendAttributedText(text: self.presentationData.strings.Channel_AdminLog_MessageGroupPreHistoryHidden(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: [])
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)))
case let .changeGeoLocation(_, updated):
var peers = SimpleDictionary<PeerId, Peer>()
var author: Peer?
if let peer = self.entry.peers[self.entry.event.peerId] {
author = peer
peers[peer.id] = peer
}
var text: String = ""
var entities: [MessageTextEntity] = []
if let updated = updated {
appendAttributedText(text: self.presentationData.strings.Channel_AdminLog_MessageChangedGroupGeoLocation(updated.address.replacingOccurrences(of: "\n", with: ", ")), generateEntities: { index in
if index == 0, let author = author {
return [.TextMention(peerId: author.id)]
}
return []
}, to: &text, entities: &entities)
let mediaMap = TelegramMediaMap(latitude: updated.latitude, longitude: updated.longitude, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil)
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: [])
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)))
}
}
}
}

View File

@ -78,6 +78,11 @@ public final class DeviceAccess {
return self.siriPromise.get()
}
private static let locationPromise = Promise<Bool?>(nil)
static var location: Signal<Bool?, NoError> {
return self.locationPromise.get()
}
public static func isMicrophoneAccessAuthorized() -> Bool? {
return AVAudioSession.sharedInstance().recordPermission() == .granted
}
@ -173,12 +178,12 @@ public final class DeviceAccess {
if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
func statusForCellularState(_ state: CTCellularDataRestrictedState) -> AccessType? {
switch state {
case .restricted:
return .denied
case .notRestricted:
return .allowed
default:
return nil
case .restricted:
return .denied
case .notRestricted:
return .allowed
default:
return nil
}
}
let cellState = CTCellularData.init()
@ -215,6 +220,29 @@ public final class DeviceAccess {
} else {
return .single(.denied)
}
case .location:
return Signal { subscriber in
let status = CLLocationManager.authorizationStatus()
switch status {
case .authorizedAlways, .authorizedWhenInUse:
subscriber.putNext(.allowed)
case .denied, .restricted:
subscriber.putNext(.denied)
case .notDetermined:
subscriber.putNext(.notDetermined)
}
subscriber.putCompletion()
return EmptyDisposable
}
|> then(self.location
|> mapToSignal { authorized -> Signal<AccessType, NoError> in
if let authorized = authorized {
return .single(authorized ? .allowed : .denied)
} else {
return .complete()
}
}
)
default:
return .single(.notDetermined)
}

View File

@ -110,6 +110,9 @@ class ContactListActionItem: ListViewItem {
func selected(listView: ListView){
self.action()
if case .alpha = self.highlight {
listView.clearHighlightAnimated(true)
}
}
static func mergeType(item: ContactListActionItem, previousItem: ListViewItem?, nextItem: ListViewItem?) -> (first: Bool, last: Bool, firstWithHeader: Bool) {

View File

@ -254,14 +254,45 @@ public class ContactsController: ViewController {
}
self.contactsNode.openPeopleNearby = { [weak self] in
if let strongSelf = self {
let controller = peopleNearbyController(context: strongSelf.context)
(strongSelf.navigationController as? NavigationController)?.pushViewController(controller, completion: { [weak self] in
if let strongSelf = self {
strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true)
}
})
guard let strongSelf = self else {
return
}
let _ = (DeviceAccess.authorizationStatus(context: strongSelf.context, 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
if let strongSelf = self {
strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true)
}
})
}
switch status {
case .allowed:
presentPeersNearby()
default:
let controller = PermissionController(context: strongSelf.context, splashScreen: false)
controller.setState(.nearbyLocation(status: PermissionRequestStatus(accessType: status)), animated: false)
controller.proceed = { result in
if result {
presentPeersNearby()
} else {
let _ = (strongSelf.navigationController as? NavigationController)?.popViewController(animated: true)
}
}
(strongSelf.navigationController as? NavigationController)?.pushViewController(controller, completion: { [weak self] in
if let strongSelf = self {
strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true)
}
})
}
})
}
self.contactsNode.openInvite = { [weak self] in

View File

@ -188,7 +188,7 @@ private func makeDefaultPresentationTheme(accentColor: UIColor, serviceBackgroun
secretIconColor: secretColor,
pinnedArchiveAvatarColor: PresentationThemeArchiveAvatarColors(backgroundColors: (UIColor(rgb: 0x72d5fd), UIColor(rgb: 0x2a9ef1)), foregroundColor: .white),
unpinnedArchiveAvatarColor: PresentationThemeArchiveAvatarColors(backgroundColors: (UIColor(rgb: 0xDEDEE5), UIColor(rgb: 0xC5C6CC)), foregroundColor: .white),
onlineDotColor: accentColor
onlineDotColor: UIColor(rgb: 0x4cc91f)
)
let bubble = PresentationThemeChatBubble(

View File

@ -457,7 +457,7 @@ private enum DeviceContactInfoEntry: ItemListNodeEntry {
combineComponent(string: &string, component: value.city)
combineComponent(string: &string, component: value.country)
combineComponent(string: &string, component: value.postcode)
return ItemListAddressItem(theme: theme, label: title, text: string, imageSignal: imageSignal, selected: selected, sectionId: self.section, action: {
return ItemListAddressItem(theme: theme, label: title, text: string, imageSignal: imageSignal, selected: selected, sectionId: self.section, style: .plain, action: {
if selected != nil {
arguments.toggleSelection(.address(value))
} else {

View File

@ -19,12 +19,37 @@ func geocodeLocation(dictionary: [String: String]) -> Signal<(Double, Double)?,
}
}
func reverseGeocodeLocation(latitude: Double, longitude: Double) -> Signal<String, NoError> {
struct ReverseGeocodedPlacemark {
let street: String?
let city: String?
let country: String?
var fullAddress: String {
var components: [String] = []
if let street = self.street {
components.append(street)
}
if let city = self.city {
components.append(city)
}
if let country = self.country {
components.append(country)
}
return components.joined(separator: ", ")
}
}
func reverseGeocodeLocation(latitude: Double, longitude: Double) -> Signal<ReverseGeocodedPlacemark?, NoError> {
return Signal { subscriber in
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(CLLocation(latitude: latitude, longitude: longitude), completionHandler: { placemarks, _ in
if let placemarks = placemarks, let locality = placemarks.first?.locality {
subscriber.putNext(locality)
if let placemarks = placemarks, let placemark = placemarks.first {
let result = ReverseGeocodedPlacemark(street: placemark.thoroughfare, city: placemark.locality, country: placemark.country)
subscriber.putNext(result)
subscriber.putCompletion()
} else {
subscriber.putNext(nil)
subscriber.putCompletion()
}
})

View File

@ -37,8 +37,9 @@ private final class GroupInfoArguments {
let openStickerPackSetup: () -> Void
let openGroupTypeSetup: () -> Void
let openLinkedChannelSetup: () -> Void
let openLocation: (PeerGeoLocation) -> Void
init(context: AccountContext, avatarAndNameInfoContext: ItemListAvatarAndNameInfoItemContext, tapAvatarAction: @escaping () -> Void, changeProfilePhoto: @escaping () -> Void, pushController: @escaping (ViewController) -> Void, presentController: @escaping (ViewController, ViewControllerPresentationArguments) -> Void, changeNotificationMuteSettings: @escaping () -> Void, openPreHistory: @escaping () -> Void, openSharedMedia: @escaping () -> Void, openAdministrators: @escaping () -> Void, openPermissions: @escaping () -> Void, updateEditingName: @escaping (ItemListAvatarAndNameInfoItemName) -> Void, updateEditingDescriptionText: @escaping (String) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, addMember: @escaping () -> Void, promotePeer: @escaping (RenderedChannelParticipant) -> Void, restrictPeer: @escaping (RenderedChannelParticipant) -> Void, removePeer: @escaping (PeerId) -> Void, leave: @escaping () -> Void, displayUsernameShareMenu: @escaping (String) -> Void, displayUsernameContextMenu: @escaping (String) -> Void, displayAboutContextMenu: @escaping (String) -> Void, aboutLinkAction: @escaping (TextLinkItemActionType, TextLinkItem) -> Void, openStickerPackSetup: @escaping () -> Void, openGroupTypeSetup: @escaping () -> Void, openLinkedChannelSetup: @escaping () -> Void) {
init(context: AccountContext, avatarAndNameInfoContext: ItemListAvatarAndNameInfoItemContext, tapAvatarAction: @escaping () -> Void, changeProfilePhoto: @escaping () -> Void, pushController: @escaping (ViewController) -> Void, presentController: @escaping (ViewController, ViewControllerPresentationArguments) -> Void, changeNotificationMuteSettings: @escaping () -> Void, openPreHistory: @escaping () -> Void, openSharedMedia: @escaping () -> Void, openAdministrators: @escaping () -> Void, openPermissions: @escaping () -> Void, updateEditingName: @escaping (ItemListAvatarAndNameInfoItemName) -> Void, updateEditingDescriptionText: @escaping (String) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, addMember: @escaping () -> Void, promotePeer: @escaping (RenderedChannelParticipant) -> Void, restrictPeer: @escaping (RenderedChannelParticipant) -> Void, removePeer: @escaping (PeerId) -> Void, leave: @escaping () -> Void, displayUsernameShareMenu: @escaping (String) -> Void, displayUsernameContextMenu: @escaping (String) -> Void, displayAboutContextMenu: @escaping (String) -> Void, aboutLinkAction: @escaping (TextLinkItemActionType, TextLinkItem) -> Void, openStickerPackSetup: @escaping () -> Void, openGroupTypeSetup: @escaping () -> Void, openLinkedChannelSetup: @escaping () -> Void, openLocation: @escaping (PeerGeoLocation) -> Void) {
self.context = context
self.avatarAndNameInfoContext = avatarAndNameInfoContext
self.tapAvatarAction = tapAvatarAction
@ -65,6 +66,7 @@ private final class GroupInfoArguments {
self.openStickerPackSetup = openStickerPackSetup
self.openGroupTypeSetup = openGroupTypeSetup
self.openLinkedChannelSetup = openLinkedChannelSetup
self.openLocation = openLocation
}
}
@ -137,6 +139,7 @@ private enum GroupInfoEntry: ItemListNodeEntry {
case groupDescriptionSetup(PresentationTheme, String, String)
case aboutHeader(PresentationTheme, String)
case about(PresentationTheme, String)
case location(PresentationTheme, PeerGeoLocation)
case link(PresentationTheme, String)
case sharedMedia(PresentationTheme, String)
case notifications(PresentationTheme, String, String)
@ -154,7 +157,7 @@ private enum GroupInfoEntry: ItemListNodeEntry {
switch self {
case .info, .setGroupPhoto, .groupDescriptionSetup:
return GroupInfoSection.info.rawValue
case .aboutHeader, .about, .link:
case .aboutHeader, .about, .link, .location:
return GroupInfoSection.about.rawValue
case .groupTypeSetup, .linkedChannelSetup, .preHistory, .stickerPack:
return GroupInfoSection.infoManagement.rawValue
@ -248,6 +251,12 @@ private enum GroupInfoEntry: ItemListNodeEntry {
} else {
return false
}
case let .location(lhsTheme, lhsLocation):
if case let .location(rhsTheme, rhsLocation) = rhs, lhsTheme === rhsTheme, lhsLocation == rhsLocation {
return true
} else {
return false
}
case let .notifications(lhsTheme, lhsTitle, lhsText):
if case let .notifications(rhsTheme, rhsTitle, rhsText) = rhs {
if lhsTheme !== rhsTheme {
@ -392,6 +401,8 @@ private enum GroupInfoEntry: ItemListNodeEntry {
return 5
case .link:
return 6
case .location:
return 7
case .groupTypeSetup:
return 8
case .linkedChannelSetup:
@ -447,6 +458,12 @@ private enum GroupInfoEntry: ItemListNodeEntry {
}, longTapAction: {
arguments.displayUsernameContextMenu(url)
}, tag: GroupInfoEntryTag.link)
case let .location(theme, location):
let imageSignal = chatMapSnapshotImage(account: arguments.context.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: {
arguments.openLocation(location)
}, longTapAction: {
})
case let .notifications(theme, title, text):
return ItemListDisclosureItem(theme: theme, title: title, label: text, sectionId: self.section, style: .blocks, action: {
arguments.changeNotificationMuteSettings()
@ -839,6 +856,10 @@ private func groupInfoEntries(account: Account, presentationData: PresentationDa
}
if let peer = view.peers[view.peerId] as? TelegramChannel, let username = peer.username, !username.isEmpty {
entries.append(.link(presentationData.theme, "t.me/" + username))
if let location = cachedChannelData.peerGeoLocation {
entries.append(.location(presentationData.theme, location))
}
}
} else if let cachedGroupData = view.cachedData as? CachedGroupData {
if let about = cachedGroupData.about, !about.isEmpty {
@ -1926,6 +1947,17 @@ public func groupInfoController(context: AccountContext, peerId originalPeerId:
|> deliverOnMainQueue).start(next: { peerView in
pushControllerImpl?(channelDiscussionGroupSetupController(context: context, peerId: peerView.peerId))
})
}, openLocation: { location in
let _ = (peerView.get()
|> take(1)
|> deliverOnMainQueue).start(next: { peerView in
guard let peer = peerView.peers[peerView.peerId] else {
return
}
let mapMedia = TelegramMediaMap(latitude: location.latitude, longitude: location.longitude, geoPlace: nil, venue: MapVenue(title: peer.displayTitle, address: location.address, provider: nil, id: nil, type: nil), liveBroadcastingTimeout: nil)
let controller = legacyLocationController(message: nil, mapMedia: mapMedia, context: context, isModal: false, openPeer: { _ in }, sendLiveLocation: { _, _ in }, stopLiveLocation: {}, openUrl: { _ in })
pushControllerImpl?(controller)
})
})
let loadMoreControl = Atomic<(PeerId, PeerChannelMemberCategoryControl)?>(value: nil)

View File

@ -11,19 +11,21 @@ final class ItemListAddressItem: ListViewItem, ItemListItem {
let imageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
let selected: Bool?
let sectionId: ItemListSectionId
let style: ItemListStyle
let action: (() -> Void)?
let longTapAction: (() -> Void)?
let linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)?
let tag: Any?
init(theme: PresentationTheme, label: String, text: String, imageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?, selected: Bool? = nil, sectionId: ItemListSectionId, action: (() -> Void)?, longTapAction: (() -> Void)? = nil, linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? = nil, tag: Any? = nil) {
init(theme: PresentationTheme, label: String, text: String, imageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?, selected: Bool? = nil, sectionId: ItemListSectionId, style: ItemListStyle, action: (() -> Void)?, longTapAction: (() -> Void)? = nil, linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? = nil, tag: Any? = nil) {
self.theme = theme
self.label = label
self.text = text
self.imageSignal = imageSignal
self.selected = selected
self.sectionId = sectionId
self.style = style
self.action = action
self.longTapAction = longTapAction
self.linkItemAction = linkItemAction
@ -144,11 +146,24 @@ class ItemListAddressItemNode: ListViewItemNode {
updatedTheme = item.theme
}
let insets = itemListNeighborsPlainInsets(neighbors)
let insets: UIEdgeInsets
let leftInset: CGFloat = 16.0 + params.leftInset
let rightInset: CGFloat = 8.0 + params.rightInset
let separatorHeight = UIScreenPixel
let itemBackgroundColor: UIColor
let itemSeparatorColor: UIColor
switch item.style {
case .plain:
itemBackgroundColor = item.theme.list.plainBackgroundColor
itemSeparatorColor = item.theme.list.itemPlainSeparatorColor
insets = itemListNeighborsPlainInsets(neighbors)
case .blocks:
itemBackgroundColor = item.theme.list.itemBlocksBackgroundColor
itemSeparatorColor = item.theme.list.itemBlocksSeparatorColor
insets = itemListNeighborsGroupedInsets(neighbors)
}
var leftOffset: CGFloat = 0.0
var selectionNodeWidthAndApply: (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode)?
if let selected = item.selected {
@ -164,7 +179,8 @@ 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()))
let contentSize = CGSize(width: params.width, height: textLayout.size.height + 39.0)
var padding: CGFloat = !item.label.isEmpty ? 39.0 : 20.0
let contentSize = CGSize(width: params.width, height: textLayout.size.height + padding)
let imageSide = min(90.0, contentSize.height - 18.0)
let imageSize = CGSize(width: imageSide, height: imageSide)
@ -188,9 +204,9 @@ class ItemListAddressItemNode: ListViewItemNode {
}
if let _ = updatedTheme {
strongSelf.topStripeNode.backgroundColor = item.theme.list.itemPlainSeparatorColor
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemPlainSeparatorColor
strongSelf.backgroundNode.backgroundColor = item.theme.list.plainBackgroundColor
strongSelf.topStripeNode.backgroundColor = itemSeparatorColor
strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
strongSelf.highlightedBackgroundNode.backgroundColor = item.theme.list.itemHighlightedBackgroundColor
}
@ -219,12 +235,11 @@ class ItemListAddressItemNode: ListViewItemNode {
}
strongSelf.labelNode.frame = CGRect(origin: CGPoint(x: leftOffset + leftInset, y: 11.0), size: labelLayout.size)
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: leftOffset + leftInset, y: 31.0), size: textLayout.size)
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: leftOffset + leftInset, y: item.label.isEmpty ? 11.0 : 31.0), size: textLayout.size)
strongSelf.imageNode.frame = CGRect(origin: CGPoint(x: params.width - imageSize.width - rightInset, y: floorToScreenPixels((contentSize.height - imageSize.height) / 2.0)), size: imageSize)
let leftInset: CGFloat
let style = ItemListStyle.plain
switch style {
switch item.style {
case .plain:
leftInset = 16.0 + params.leftInset + leftOffset

View File

@ -145,7 +145,7 @@ class ItemListPeerActionItemNode: ListViewItemNode {
let separatorHeight = UIScreenPixel
let insets = itemListNeighborsGroupedInsets(neighbors)
let contentSize = CGSize(width: params.width, height: 44.0)
let contentSize = CGSize(width: params.width, height: 50.0)
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
let layoutSize = layout.size
@ -166,7 +166,6 @@ class ItemListPeerActionItemNode: ListViewItemNode {
let _ = titleApply()
let transition: ContainedViewLayoutTransition
if animated {
transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
@ -209,9 +208,9 @@ class ItemListPeerActionItemNode: ListViewItemNode {
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)))
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + editingOffset, y: 11.0), size: titleLayout.size))
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + editingOffset, y: 14.0), size: titleLayout.size))
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: 44.0 + UIScreenPixel + UIScreenPixel))
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: 50.0 + UIScreenPixel + UIScreenPixel))
}
})
}

View File

@ -186,7 +186,7 @@ class ItemListPlaceholderItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight))
}
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 17.0), size: textLayout.size)
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((params.width - textLayout.size.width) / 2.0), y: 17.0), size: textLayout.size)
}
})
}

View File

@ -10,9 +10,9 @@ private func generateClearIcon(color: UIColor) -> UIImage? {
return generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: color)
}
func legacyLocationPickerController(context: AccountContext, selfPeer: Peer, peer: Peer, sendLocation: @escaping (CLLocationCoordinate2D, MapVenue?) -> Void, sendLiveLocation: @escaping (CLLocationCoordinate2D, Int32) -> Void, theme: PresentationTheme) -> ViewController {
func legacyLocationPickerController(context: AccountContext, selfPeer: Peer, peer: Peer, sendLocation: @escaping (CLLocationCoordinate2D, MapVenue?) -> Void, sendLiveLocation: @escaping (CLLocationCoordinate2D, Int32) -> Void, theme: PresentationTheme, customLocationPicker: Bool = false) -> ViewController {
let legacyController = LegacyController(presentation: .modal(animateIn: true), theme: theme)
let controller = TGLocationPickerController(context: legacyController.context, intent: TGLocationPickerControllerDefaultIntent)!
let controller = TGLocationPickerController(context: legacyController.context, intent: customLocationPicker ? TGLocationPickerControllerCustomLocationIntent : TGLocationPickerControllerDefaultIntent)!
controller.peer = makeLegacyPeer(selfPeer)
controller.receivingPeer = makeLegacyPeer(peer)
controller.pallete = legacyLocationPalette(from: theme)
@ -21,7 +21,7 @@ func legacyLocationPickerController(context: AccountContext, selfPeer: Peer, pee
Namespaces.Peer.CloudGroup,
Namespaces.Peer.CloudUser
])
if namespacesWithEnabledLiveLocation.contains(peer.id.namespace) {
if namespacesWithEnabledLiveLocation.contains(peer.id.namespace) && !customLocationPicker {
controller.allowLiveLocationSharing = true
}
let navigationController = TGNavigationController(controllers: [controller])!

View File

@ -244,6 +244,25 @@ final class PeerChannelMemberCategoriesContextsManager {
}
}
func transferOwnership(account: Account, peerId: PeerId, memberId: PeerId, password: String) -> Signal<Never, ChannelOwnershipTransferError> {
return updateChannelOwnership(postbox: account.postbox, network: account.network, accountStateManager: account.stateManager, channelId: peerId, memberId: memberId, password: password)
|> deliverOnMainQueue
// |> beforeNext { [weak self] result in
// if let strongSelf = self, let (previous, updated) = result {
// strongSelf.impl.with { impl in
// for (contextPeerId, context) in impl.contexts {
// if peerId == contextPeerId {
// context.replayUpdates([(previous, updated, nil)])
// }
// }
// }
// }
// }
// |> mapToSignal { _ -> Signal<Void, ChannelOwnershipTransferError> in
// return .complete()
// }
}
func join(account: Account, peerId: PeerId) -> Signal<Never, JoinChannelError> {
return joinChannel(account: account, peerId: peerId)
|> deliverOnMainQueue

View File

@ -7,14 +7,14 @@ import TelegramCore
import MapKit
private struct PeerNearbyEntry {
let peer: Peer
let peer: (Peer, CachedPeerData?)
let expires: Int32
let distance: Int32
}
private func arePeersNearbyEqual(_ lhs: PeerNearbyEntry?, _ rhs: PeerNearbyEntry?) -> Bool {
if let lhs = lhs, let rhs = rhs {
return lhs.peer.isEqual(rhs.peer) && lhs.expires == rhs.expires && lhs.distance == rhs.distance
return lhs.peer.0.isEqual(rhs.peer.0) && lhs.expires == rhs.expires && lhs.distance == rhs.distance
} else {
return (lhs != nil) == (rhs != nil)
}
@ -25,14 +25,14 @@ private func arePeerNearbyArraysEqual(_ lhs: [PeerNearbyEntry], _ rhs: [PeerNear
return false
}
for i in 0 ..< lhs.count {
if !lhs[i].peer.isEqual(rhs[i].peer) || lhs[i].expires != rhs[i].expires || lhs[i].distance != rhs[i].distance {
if !lhs[i].peer.0.isEqual(rhs[i].peer.0) || lhs[i].expires != rhs[i].expires || lhs[i].distance != rhs[i].distance {
return false
}
}
return true
}
private final class PeopleNearbyControllerArguments {
private final class PeersNearbyControllerArguments {
let context: AccountContext
let openChat: (Peer) -> Void
let openCreateGroup: () -> Void
@ -44,14 +44,14 @@ private final class PeopleNearbyControllerArguments {
}
}
private enum PeopleNearbySection: Int32 {
private enum PeersNearbySection: Int32 {
case header
case users
case groups
case channels
}
private enum PeopleNearbyEntry: ItemListNodeEntry {
private enum PeersNearbyEntry: ItemListNodeEntry {
case header(PresentationTheme, String)
case usersHeader(PresentationTheme, String)
@ -68,13 +68,13 @@ private enum PeopleNearbyEntry: ItemListNodeEntry {
var section: ItemListSectionId {
switch self {
case .header:
return PeopleNearbySection.header.rawValue
return PeersNearbySection.header.rawValue
case .usersHeader, .empty, .user:
return PeopleNearbySection.users.rawValue
return PeersNearbySection.users.rawValue
case .groupsHeader, .createGroup, .group:
return PeopleNearbySection.groups.rawValue
return PeersNearbySection.groups.rawValue
case .channelsHeader, .channel:
return PeopleNearbySection.channels.rawValue
return PeersNearbySection.channels.rawValue
}
}
@ -101,7 +101,7 @@ private enum PeopleNearbyEntry: ItemListNodeEntry {
}
}
static func ==(lhs: PeopleNearbyEntry, rhs: PeopleNearbyEntry) -> Bool {
static func ==(lhs: PeersNearbyEntry, rhs: PeersNearbyEntry) -> Bool {
switch lhs {
case let .header(lhsTheme, lhsText):
if case let .header(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
@ -160,56 +160,71 @@ private enum PeopleNearbyEntry: ItemListNodeEntry {
}
}
static func <(lhs: PeopleNearbyEntry, rhs: PeopleNearbyEntry) -> Bool {
static func <(lhs: PeersNearbyEntry, rhs: PeersNearbyEntry) -> Bool {
return lhs.stableId < rhs.stableId
}
func item(_ arguments: PeopleNearbyControllerArguments) -> ListViewItem {
func item(_ arguments: PeersNearbyControllerArguments) -> ListViewItem {
switch self {
case let .header(theme, text):
return PeopleNearbyHeaderItem(theme: theme, text: text, sectionId: self.section)
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):
return ItemListPlaceholderItem(theme: theme, text: text, sectionId: self.section, style: .blocks)
case let .user(_, theme, strings, dateTimeFormat, nameDisplayOrder, peer):
func distance(_ distance: Int32) -> String {
var distance = max(1, distance)
let distance = max(1, distance)
let formatter = MKDistanceFormatter()
formatter.unitStyle = .abbreviated
return formatter.string(fromDistance: Double(distance))
var result = formatter.string(fromDistance: Double(distance))
if result.hasPrefix("0 ") {
result = result.replacingOccurrences(of: "0 ", with: "1 ")
}
return result
}
return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, account: arguments.context.account, peer: peer.peer, aliasHandling: .standard, nameColor: .primary, nameStyle: .distinctBold, presence: nil, text: .text(distance(peer.distance)), 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)
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, toggleUpdated: nil, hasTopStripe: false, hasTopGroupInset: false, tag: nil)
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(distance(peer.distance)), 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):
return ContactListActionItem(theme: theme, title: title, icon: .generic(UIImage(bundleImageName: "Contact List/CreateGroupActionIcon")!), header: nil, action: {
return ItemListPeerActionItem(theme: theme, icon: PresentationResourcesItemList.createGroupIcon(theme), title: title, alwaysPlain: false, sectionId: self.section, editing: false, action: {
arguments.openCreateGroup()
})
case let .group(_, theme, strings, dateTimeFormat, nameDisplayOrder, peer):
return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, account: arguments.context.account, peer: peer.peer, aliasHandling: .standard, nameColor: .primary, nameStyle: .distinctBold, presence: nil, text: .text("10 members"), 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)
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, toggleUpdated: nil, hasTopStripe: false, hasTopGroupInset: false, tag: nil)
var text: ItemListPeerItemText
if let cachedData = peer.peer.1 as? CachedChannelData, let memberCount = cachedData.participantsSummary.memberCount {
text = .text(strings.Conversation_StatusMembers(memberCount))
} else {
text = .none
}
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)
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, toggleUpdated: nil, hasTopGroupInset: false, tag: nil)
case let .channelsHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .channel(_, theme, strings, dateTimeFormat, nameDisplayOrder, peer):
return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, account: arguments.context.account, peer: peer.peer, aliasHandling: .standard, nameColor: .primary, nameStyle: .distinctBold, presence: nil, text: .text("10 members"), 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)
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, toggleUpdated: nil, hasTopStripe: false, hasTopGroupInset: false, tag: nil)
var text: ItemListPeerItemText
if let cachedData = peer.peer.1 as? CachedChannelData, let memberCount = cachedData.participantsSummary.memberCount {
text = .text(strings.Conversation_StatusSubscribers(memberCount))
} else {
text = .none
}
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)
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, toggleUpdated: nil, hasTopGroupInset: false, tag: nil)
}
}
}
private struct PeopleNearbyControllerState: Equatable {
static func ==(lhs: PeopleNearbyControllerState, rhs: PeopleNearbyControllerState) -> Bool {
private struct PeersNearbyControllerState: Equatable {
static func ==(lhs: PeersNearbyControllerState, rhs: PeersNearbyControllerState) -> Bool {
return true
}
}
private struct PeopleNearbyData: Equatable {
private struct PeersNearbyData: Equatable {
let users: [PeerNearbyEntry]
let groups: [PeerNearbyEntry]
let channels: [PeerNearbyEntry]
@ -220,13 +235,13 @@ private struct PeopleNearbyData: Equatable {
self.channels = channels
}
static func ==(lhs: PeopleNearbyData, rhs: PeopleNearbyData) -> Bool {
static func ==(lhs: PeersNearbyData, rhs: PeersNearbyData) -> Bool {
return arePeerNearbyArraysEqual(lhs.users, rhs.users) && arePeerNearbyArraysEqual(lhs.groups, rhs.groups) && arePeerNearbyArraysEqual(lhs.channels, rhs.channels)
}
}
private func peopleNearbyControllerEntries(state: PeopleNearbyControllerState, data: PeopleNearbyData?, presentationData: PresentationData) -> [PeopleNearbyEntry] {
var entries: [PeopleNearbyEntry] = []
private func peersNearbyControllerEntries(state: PeersNearbyControllerState, data: PeersNearbyData?, presentationData: PresentationData) -> [PeersNearbyEntry] {
var entries: [PeersNearbyEntry] = []
entries.append(.header(presentationData.theme, presentationData.strings.PeopleNearby_Description))
entries.append(.usersHeader(presentationData.theme, presentationData.strings.PeopleNearby_Users.uppercased()))
@ -240,62 +255,72 @@ private func peopleNearbyControllerEntries(state: PeopleNearbyControllerState, d
entries.append(.empty(presentationData.theme, presentationData.strings.PeopleNearby_UsersEmpty))
}
// entries.append(.groupsHeader(presentationData.theme, presentationData.strings.PeopleNearby_Groups.uppercased()))
// entries.append(.createGroup(presentationData.theme, presentationData.strings.PeopleNearby_CreateGroup))
// if let data = data, !data.groups.isEmpty {
// var i: Int32 = 0
// for group in data.groups {
// entries.append(.group(i, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, group))
// i += 1
// }
// }
//
// if let data = data, !data.channels.isEmpty {
// var i: Int32 = 0
// for channel in data.channels {
// entries.append(.channel(i, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, channel))
// i += 1
// }
// }
//entries.append(.createGroup(presentationData.theme, presentationData.strings.PeopleNearby_CreateGroup))
if let data = data, !data.groups.isEmpty {
entries.append(.groupsHeader(presentationData.theme, presentationData.strings.PeopleNearby_Groups.uppercased()))
var i: Int32 = 0
for group in data.groups {
entries.append(.group(i, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, group))
i += 1
}
}
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))
i += 1
}
}
return entries
}
public func peopleNearbyController(context: AccountContext) -> ViewController {
let statePromise = ValuePromise(PeopleNearbyControllerState(), ignoreRepeated: true)
let stateValue = Atomic(value: PeopleNearbyControllerState())
let updateState: ((PeopleNearbyControllerState) -> PeopleNearbyControllerState) -> Void = { f in
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)?
var navigateToChatImpl: ((Peer) -> Void)?
let actionsDisposable = DisposableSet()
let dataPromise = Promise<PeopleNearbyData?>(nil)
let dataPromise = Promise<PeersNearbyData?>(nil)
let arguments = PeopleNearbyControllerArguments(context: context, openChat: { peer in
let arguments = PeersNearbyControllerArguments(context: context, openChat: { peer in
navigateToChatImpl?(peer)
}, openCreateGroup: {
let controller = createGroupController(context: context, peerIds: [], supergroup: true)
replaceTopControllerImpl?(controller, true)
})
let dataSignal: Signal<PeopleNearbyData?, NoError> = currentLocationManagerCoordinate(manager: context.sharedContext.locationManager!, timeout: 5.0)
|> mapToSignal { coordinate -> Signal<PeopleNearbyData?, NoError> 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)
}
let poll = peersNearby(network: context.account.network, accountStateManager: context.account.stateManager, coordinate: (latitude: coordinate.latitude, longitude: coordinate.longitude), radius: 100)
|> mapToSignal { peersNearby -> Signal<PeopleNearbyData?, NoError> in
return context.account.postbox.transaction { transaction -> PeopleNearbyData? in
var result: [PeerNearbyEntry] = []
let poll = peersNearby(network: context.account.network, accountStateManager: context.account.stateManager, coordinate: (latitude: coordinate.latitude, longitude: coordinate.longitude))
|> mapToSignal { peersNearby -> Signal<PeersNearbyData?, NoError> in
return context.account.postbox.transaction { transaction -> PeersNearbyData? in
var users: [PeerNearbyEntry] = []
var groups: [PeerNearbyEntry] = []
for peerNearby in peersNearby {
if peerNearby.id != context.account.peerId, let peer = transaction.getPeer(peerNearby.id) {
result.append(PeerNearbyEntry(peer: peer, expires: peerNearby.expires, distance: peerNearby.distance))
if peerNearby.id.namespace == Namespaces.Peer.CloudUser {
users.append(PeerNearbyEntry(peer: (peer, nil), expires: peerNearby.expires, distance: peerNearby.distance))
} else {
let cachedData = transaction.getPeerCachedData(peerId: peerNearby.id) as? CachedChannelData
groups.append(PeerNearbyEntry(peer: (peer, cachedData), expires: peerNearby.expires, distance: peerNearby.distance))
}
}
}
return PeopleNearbyData(users: result, groups: [], channels: [])
return PeersNearbyData(users: users, groups: groups, channels: [])
}
}
return (poll |> then(.complete() |> suspendAwareDelay(25.0, queue: Queue.concurrentDefaultQueue()))) |> restart
@ -305,19 +330,34 @@ public func peopleNearbyController(context: AccountContext) -> ViewController {
let signal = combineLatest(context.sharedContext.presentationData, statePromise.get(), dataPromise.get())
|> deliverOnMainQueue
|> map { presentationData, state, data -> (ItemListControllerState, (ItemListNodeState<PeopleNearbyEntry>, PeopleNearbyEntry.ItemGenerationArguments)) in
|> map { presentationData, state, 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: peopleNearbyControllerEntries(state: state, data: data, presentationData: presentationData), style: .blocks, emptyStateItem: nil, crossfadeState: false, animateChanges: true, userInteractionEnabled: true)
let listState = ItemListNodeState(entries: peersNearbyControllerEntries(state: state, data: data, presentationData: presentationData), style: .blocks, emptyStateItem: nil, crossfadeState: false, animateChanges: true, userInteractionEnabled: true)
return (controllerState, (listState, arguments))
} |> afterDisposed {
}
|> afterDisposed {
actionsDisposable.dispose()
}
let controller = ItemListController(context: context, state: signal)
navigateToChatImpl = { [weak controller] peer in
if let navigationController = controller?.navigationController as? NavigationController {
navigateToChatController(navigationController: navigationController, context: context, chatLocation: .peer(peer.id), keepStack: .always)
navigateToChatController(navigationController: navigationController, context: context, chatLocation: .peer(peer.id), keepStack: .always, purposefulAction: { [weak navigationController] in
if let navigationController = navigationController, let chatController = navigationController.viewControllers.last as? ChatController {
replaceTopControllerImpl?(chatController, false)
}
})
}
}
pushControllerImpl = { [weak controller] c in
if let controller = controller {
(controller.navigationController as? NavigationController)?.pushViewController(c, animated: true)
}
}
replaceTopControllerImpl = { [weak controller] c, a in
if let controller = controller {
(controller.navigationController as? NavigationController)?.replaceAllButRootController(c, animated: a)
}
}
presentControllerImpl = { [weak controller] c, p in

View File

@ -4,7 +4,7 @@ import Display
import AsyncDisplayKit
import SwiftSignalKit
class PeopleNearbyHeaderItem: ListViewItem, ItemListItem {
class PeersNearbyHeaderItem: ListViewItem, ItemListItem {
let theme: PresentationTheme
let text: String
let sectionId: ItemListSectionId
@ -17,7 +17,7 @@ class PeopleNearbyHeaderItem: ListViewItem, ItemListItem {
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
async {
let node = PeopleNearbyHeaderItemNode()
let node = PeersNearbyHeaderItemNode()
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
node.contentSize = layout.contentSize
@ -33,7 +33,7 @@ class PeopleNearbyHeaderItem: ListViewItem, ItemListItem {
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
Queue.mainQueue().async {
guard let nodeValue = node() as? PeopleNearbyHeaderItemNode else {
guard let nodeValue = node() as? PeersNearbyHeaderItemNode else {
assertionFailure()
return
}
@ -54,11 +54,11 @@ class PeopleNearbyHeaderItem: ListViewItem, ItemListItem {
private let titleFont = Font.regular(13.0)
class PeopleNearbyHeaderItemNode: ListViewItemNode {
class PeersNearbyHeaderItemNode: ListViewItemNode {
private let titleNode: TextNode
private var iconNode: PeopleNearbyIconNode?
private var iconNode: PeersNearbyIconNode?
private var item: PeopleNearbyHeaderItem?
private var item: PeersNearbyHeaderItem?
init() {
self.titleNode = TextNode()
@ -71,7 +71,7 @@ class PeopleNearbyHeaderItemNode: ListViewItemNode {
self.addSubnode(self.titleNode)
}
func asyncLayout() -> (_ item: PeopleNearbyHeaderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
func asyncLayout() -> (_ item: PeersNearbyHeaderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
return { item, params, neighbors in
@ -93,12 +93,12 @@ class PeopleNearbyHeaderItemNode: ListViewItemNode {
strongSelf.item = item
strongSelf.accessibilityLabel = attributedText.string
let iconNode: PeopleNearbyIconNode
let iconNode: PeersNearbyIconNode
if let node = strongSelf.iconNode {
iconNode = node
iconNode.updateTheme(item.theme)
} else {
iconNode = PeopleNearbyIconNode(theme: item.theme)
iconNode = PeersNearbyIconNode(theme: item.theme)
strongSelf.iconNode = iconNode
strongSelf.addSubnode(iconNode)
}

View File

@ -5,7 +5,7 @@ import AsyncDisplayKit
import LegacyComponents
private final class PeopleNearbyIconWavesNodeParams: NSObject {
private final class PeersNearbyIconWavesNodeParams: NSObject {
let color: UIColor
let progress: CGFloat
@ -21,7 +21,7 @@ private func degToRad(_ degrees: CGFloat) -> CGFloat {
return degrees * CGFloat.pi / 180.0
}
final class PeopleNearbyIconWavesNode: ASDisplayNode {
final class PeersNearbyIconWavesNode: ASDisplayNode {
var color: UIColor {
didSet {
self.setNeedsDisplay()
@ -51,10 +51,10 @@ final class PeopleNearbyIconWavesNode: ASDisplayNode {
let animation = POPBasicAnimation()
animation.property = (POPAnimatableProperty.property(withName: "progress", initializer: { property in
property?.readBlock = { node, values in
values?.pointee = (node as! PeopleNearbyIconWavesNode).effectiveProgress
values?.pointee = (node as! PeersNearbyIconWavesNode).effectiveProgress
}
property?.writeBlock = { node, values in
(node as! PeopleNearbyIconWavesNode).effectiveProgress = values!.pointee
(node as! PeersNearbyIconWavesNode).effectiveProgress = values!.pointee
}
property?.threshold = 0.01
}) as! POPAnimatableProperty)
@ -75,7 +75,7 @@ final class PeopleNearbyIconWavesNode: ASDisplayNode {
override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
let t = CACurrentMediaTime()
let value: CGFloat = CGFloat(t.truncatingRemainder(dividingBy: 2.0)) / 2.0
return PeopleNearbyIconWavesNodeParams(color: self.color, progress: value)
return PeersNearbyIconWavesNodeParams(color: self.color, progress: value)
}
@objc override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
@ -87,7 +87,7 @@ final class PeopleNearbyIconWavesNode: ASDisplayNode {
context.fill(bounds)
}
if let parameters = parameters as? PeopleNearbyIconWavesNodeParams {
if let parameters = parameters as? PeersNearbyIconWavesNodeParams {
let center = CGPoint(x: bounds.width / 2.0, y: bounds.height / 2.0)
let radius: CGFloat = bounds.width * 0.3333
let range: CGFloat = (bounds.width - radius * 2.0) / 2.0
@ -158,22 +158,27 @@ private func generateIcon(size: CGSize, color: UIColor, contentColor: UIColor) -
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
context.translateBy(x: 0.0, y: 6.0)
context.setFillColor(contentColor.cgColor)
if size.width == 120.0 {
context.translateBy(x: 30.0, y: 30.0)
}
let _ = try? drawSvgPath(context, path: "M27.8628211,52.2347452 L27.8628211,27.1373017 L2.76505663,27.1373017 C1.55217431,27.1373017 0.568938916,26.1540663 0.568938916,24.941184 C0.568938916,24.0832172 1.06857435,23.3038117 1.84819149,22.9456161 L51.2643819,0.241311309 C52.586928,-0.366333451 54.1516568,0.213208572 54.7593016,1.53575465 C55.0801868,2.23416513 55.080181,3.03785964 54.7592857,3.7362655 L32.0544935,53.1516391 C31.548107,54.2537536 30.2441593,54.7366865 29.1420449,54.2302999 C28.3624433,53.8720978 27.8628211,53.0927006 27.8628211,52.2347452 Z ")
})!
}
final class PeopleNearbyIconNode: ASDisplayNode {
final class PeersNearbyIconNode: ASDisplayNode {
private var theme: PresentationTheme
private var iconNode: ASImageNode
private var wavesNode: PeopleNearbyIconWavesNode
private var wavesNode: PeersNearbyIconWavesNode
init(theme: PresentationTheme) {
self.theme = theme
self.iconNode = ASImageNode()
self.iconNode.isOpaque = false
self.wavesNode = PeopleNearbyIconWavesNode(color: theme.list.itemAccentColor)
self.wavesNode = PeersNearbyIconWavesNode(color: theme.list.itemAccentColor)
super.init()

View File

@ -8,6 +8,7 @@ public enum PermissionKind: Int32 {
case notifications
case siri
case cellularData
case nearbyLocation
}
public enum PermissionRequestStatus {
@ -35,6 +36,7 @@ public enum PermissionState: Equatable {
case notifications(status: PermissionRequestStatus)
case siri(status: PermissionRequestStatus)
case cellularData
case nearbyLocation(status: PermissionRequestStatus)
var kind: PermissionKind {
switch self {
@ -46,6 +48,8 @@ public enum PermissionState: Equatable {
return .siri
case .cellularData:
return .cellularData
case .nearbyLocation:
return .nearbyLocation
}
}
@ -59,6 +63,8 @@ public enum PermissionState: Equatable {
return status
case .cellularData:
return .unreachable
case let .nearbyLocation(status):
return status
}
}
}

View File

@ -8,6 +8,7 @@ final class PermissionContentNode: ASDisplayNode {
let kind: PermissionKind
private let iconNode: ASImageNode
private let nearbyIconNode: PeersNearbyIconNode?
private let titleNode: ImmediateTextNode
private let textNode: ImmediateTextNode
private let actionButton: SolidRoundedButtonNode
@ -32,6 +33,12 @@ final class PermissionContentNode: ASDisplayNode {
self.iconNode.displayWithoutProcessing = true
self.iconNode.displaysAsynchronously = false
if kind == .nearbyLocation {
self.nearbyIconNode = PeersNearbyIconNode(theme: theme)
} else {
self.nearbyIconNode = nil
}
self.titleNode = ImmediateTextNode()
self.titleNode.maximumNumberOfLines = 0
self.titleNode.textAlignment = .center
@ -61,6 +68,9 @@ final class PermissionContentNode: ASDisplayNode {
self.privacyPolicyButton.isHidden = openPrivacyPolicy == nil
self.addSubnode(self.iconNode)
if let nearbyIconNode = self.nearbyIconNode {
self.addSubnode(nearbyIconNode)
}
self.addSubnode(self.titleNode)
self.addSubnode(self.textNode)
self.addSubnode(self.actionButton)
@ -109,11 +119,17 @@ final class PermissionContentNode: ASDisplayNode {
imageSize = icon.size
contentHeight += imageSize.height + imageSpacing
}
if let _ = self.nearbyIconNode, size.width < size.height {
imageSpacing = floor(availableHeight * 0.12)
imageSize = CGSize(width: 120.0, height: 120.0)
contentHeight += imageSize.height + imageSpacing
}
let privacySpacing: CGFloat = max(30.0 + privacyButtonSize.height, (availableHeight - titleSubtitleSpacing - buttonSpacing - imageSize.height - imageSpacing) / 2.0)
let contentOrigin = insets.top + floor((size.height - insets.top - insets.bottom - contentHeight) / 2.0)
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)
let textFrame = CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: titleFrame.maxY + titleSubtitleSpacing), size: textSize)
let buttonFrame = CGRect(origin: CGPoint(x: floor((size.width - buttonWidth) / 2.0), y: textFrame.maxY + buttonSpacing), size: CGSize(width: buttonWidth, height: buttonHeight))
@ -121,6 +137,9 @@ final class PermissionContentNode: ASDisplayNode {
transition.updateFrame(node: self.iconNode, frame: iconFrame)
if let nearbyIconNode = self.nearbyIconNode {
transition.updateFrame(node: nearbyIconNode, frame: nearbyIconFrame)
}
transition.updateFrame(node: self.titleNode, frame: titleFrame)
transition.updateFrame(node: self.textNode, frame: textFrame)
transition.updateFrame(node: self.actionButton, frame: buttonFrame)

View File

@ -7,18 +7,14 @@ import TelegramCore
public final class PermissionController : ViewController {
private let context: AccountContext
private let splitTest: PermissionUISplitTest
private let splitTest: PermissionUISplitTest?
private var state: PermissionState?
private var splashScreen = false
private var controllerNode: PermissionControllerNode {
return self.displayNode as! PermissionControllerNode
}
private var _ready = Promise<Bool>()
override public var ready: Promise<Bool> {
return self._ready
}
private var didPlayPresentationAnimation = false
private var presentationData: PresentationData
@ -28,12 +24,20 @@ public final class PermissionController : ViewController {
private var skip: (() -> Void)?
public var proceed: ((Bool) -> Void)?
public init(context: AccountContext, splitTest: PermissionUISplitTest) {
public init(context: AccountContext, splashScreen: Bool = true, splitTest: PermissionUISplitTest? = nil) {
self.context = context
self.splitTest = splitTest
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.splashScreen = splashScreen
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(buttonColor: self.presentationData.theme.rootController.navigationBar.accentTextColor, disabledButtonColor: self.presentationData.theme.rootController.navigationBar.disabledButtonColor, primaryTextColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor, backgroundColor: .clear, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)))
let navigationBarPresentationData: NavigationBarPresentationData
if splashScreen {
navigationBarPresentationData = NavigationBarPresentationData(theme: NavigationBarTheme(buttonColor: self.presentationData.theme.rootController.navigationBar.accentTextColor, disabledButtonColor: self.presentationData.theme.rootController.navigationBar.disabledButtonColor, primaryTextColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor, backgroundColor: .clear, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings))
} else {
navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData)
}
super.init(navigationBarPresentationData: navigationBarPresentationData)
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
@ -75,9 +79,19 @@ public final class PermissionController : ViewController {
private func updateThemeAndStrings() {
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: NavigationBarTheme(buttonColor: self.presentationData.theme.rootController.navigationBar.accentTextColor, disabledButtonColor: self.presentationData.theme.rootController.navigationBar.disabledButtonColor, primaryTextColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor, backgroundColor: .clear, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)))
let navigationBarPresentationData: NavigationBarPresentationData
if self.splashScreen {
navigationBarPresentationData = NavigationBarPresentationData(theme: NavigationBarTheme(buttonColor: self.presentationData.theme.rootController.navigationBar.accentTextColor, disabledButtonColor: self.presentationData.theme.rootController.navigationBar.disabledButtonColor, primaryTextColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor, backgroundColor: .clear, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings))
} else {
navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData)
}
self.navigationBar?.updatePresentationData(navigationBarPresentationData)
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: nil, style: .plain, target: nil, action: nil)
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Permissions_Skip, style: .plain, target: self, action: #selector(PermissionController.nextPressed))
if self.navigationItem.rightBarButtonItem != nil {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Permissions_Skip, style: .plain, target: self, action: #selector(PermissionController.nextPressed))
}
self.controllerNode.updatePresentationData(self.presentationData)
}
@ -93,19 +107,19 @@ public final class PermissionController : ViewController {
self.state = state
switch state {
case let .contacts(status):
self.splitTest.addEvent(.ContactsModalRequest)
self.splitTest?.addEvent(.ContactsModalRequest)
self.allow = { [weak self] in
if let strongSelf = self {
switch status {
case .requestable:
strongSelf.splitTest.addEvent(.ContactsRequest)
strongSelf.splitTest?.addEvent(.ContactsRequest)
DeviceAccess.authorizeAccess(to: .contacts, context: strongSelf.context, { [weak self] result in
if let strongSelf = self {
if result {
strongSelf.splitTest.addEvent(.ContactsAllowed)
strongSelf.splitTest?.addEvent(.ContactsAllowed)
} else {
strongSelf.splitTest.addEvent(.ContactsDenied)
strongSelf.splitTest?.addEvent(.ContactsDenied)
}
strongSelf.proceed?(true)
}
@ -119,19 +133,19 @@ public final class PermissionController : ViewController {
}
}
case let .notifications(status):
self.splitTest.addEvent(.NotificationsModalRequest)
self.splitTest?.addEvent(.NotificationsModalRequest)
self.allow = { [weak self] in
if let strongSelf = self {
switch status {
case .requestable:
strongSelf.splitTest.addEvent(.NotificationsRequest)
strongSelf.splitTest?.addEvent(.NotificationsRequest)
DeviceAccess.authorizeAccess(to: .notifications, context: strongSelf.context, { [weak self] result in
if let strongSelf = self {
if result {
strongSelf.splitTest.addEvent(.NotificationsAllowed)
strongSelf.splitTest?.addEvent(.NotificationsAllowed)
} else {
strongSelf.splitTest.addEvent(.NotificationsDenied)
strongSelf.splitTest?.addEvent(.NotificationsDenied)
}
strongSelf.proceed?(true)
}
@ -144,7 +158,7 @@ public final class PermissionController : ViewController {
}
}
}
case let .siri(status):
case .siri:
self.allow = { [weak self] in
self?.proceed?(true)
}
@ -152,6 +166,25 @@ public final class PermissionController : ViewController {
self.allow = { [weak self] in
self?.proceed?(true)
}
case let .nearbyLocation(status):
self.title = self.presentationData.strings.Permissions_PeopleNearbyTitle_v0
self.navigationItem.rightBarButtonItem = nil
self.allow = { [weak self] in
if let strongSelf = self {
switch status {
case .requestable:
DeviceAccess.authorizeAccess(to: .location(.tracking), context: strongSelf.context, { [weak self] result in
self?.proceed?(result)
})
case .denied, .unreachable:
strongSelf.openAppSettings()
strongSelf.proceed?(false)
default:
break
}
}
}
}
self.skip = { [weak self] in

View File

@ -46,7 +46,7 @@ private func localizedString(for key: String, strings: PresentationStrings, fall
final class PermissionControllerNode: ASDisplayNode {
private let context: AccountContext
private var presentationData: PresentationData
private let splitTest: PermissionUISplitTest
private let splitTest: PermissionUISplitTest?
private var innerState: PermissionControllerInnerState
@ -56,7 +56,7 @@ final class PermissionControllerNode: ASDisplayNode {
var openPrivacyPolicy: (() -> Void)?
var dismiss: (() -> Void)?
init(context: AccountContext, splitTest: PermissionUISplitTest) {
init(context: AccountContext, splitTest: PermissionUISplitTest?) {
self.context = context
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.splitTest = splitTest
@ -122,7 +122,7 @@ final class PermissionControllerNode: ASDisplayNode {
switch dataState {
case let .contacts(status):
icon = UIImage(bundleImageName: "Settings/Permissions/Contacts")
if case let .modal(titleKey, textKey, allowTitleKey, allowInSettingsTitleKey) = self.splitTest.configuration.contacts {
if let splitTest = self.splitTest, case let .modal(titleKey, textKey, allowTitleKey, allowInSettingsTitleKey) = splitTest.configuration.contacts {
title = localizedString(for: titleKey, strings: self.presentationData.strings)
text = localizedString(for: textKey, strings: self.presentationData.strings)
if status == .denied {
@ -142,7 +142,7 @@ final class PermissionControllerNode: ASDisplayNode {
hasPrivacyPolicy = true
case let .notifications(status):
icon = UIImage(bundleImageName: "Settings/Permissions/Notifications")
if case let .modal(titleKey, textKey, allowTitleKey, allowInSettingsTitleKey) = self.splitTest.configuration.notifications {
if let splitTest = self.splitTest, case let .modal(titleKey, textKey, allowTitleKey, allowInSettingsTitleKey) = splitTest.configuration.notifications {
title = localizedString(for: titleKey, strings: self.presentationData.strings, fallback: self.presentationData.strings.Permissions_NotificationsTitle_v0)
text = localizedString(for: textKey, strings: self.presentationData.strings, fallback: self.presentationData.strings.Permissions_NotificationsText_v0)
if status == .denied {
@ -176,6 +176,16 @@ final class PermissionControllerNode: ASDisplayNode {
text = self.presentationData.strings.Permissions_CellularDataText_v0
buttonTitle = self.presentationData.strings.Permissions_CellularDataAllowInSettings_v0
hasPrivacyPolicy = false
case let .nearbyLocation(status):
icon = nil
title = self.presentationData.strings.Permissions_PeopleNearbyTitle_v0
text = self.presentationData.strings.Permissions_PeopleNearbyText_v0
if status == .denied {
buttonTitle = self.presentationData.strings.Permissions_PeopleNearbyAllowInSettings_v0
} else {
buttonTitle = self.presentationData.strings.Permissions_PeopleNearbyAllow_v0
}
hasPrivacyPolicy = false
}
let contentNode = PermissionContentNode(theme: self.presentationData.theme, strings: self.presentationData.strings, kind: dataState.kind, icon: icon, title: title, text: text, buttonTitle: buttonTitle, buttonAction: { [weak self] in

View File

@ -43,6 +43,7 @@ enum PresentationResourceKey: Int32 {
case itemListDeleteIndicatorIcon
case itemListReorderIndicatorIcon
case itemListAddPersonIcon
case itemListCreateGroupIcon
case itemListAddExceptionIcon
case itemListAddPhoneIcon
case itemListClearInputIcon

View File

@ -107,6 +107,12 @@ struct PresentationResourcesItemList {
})
}
static func createGroupIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListCreateGroupIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Contact List/CreateGroupActionIcon"), color: theme.list.itemAccentColor)
})
}
static func addExceptionIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListAddExceptionIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Item List/AddExceptionIcon"), color: theme.list.itemAccentColor)

File diff suppressed because it is too large Load Diff

View File

@ -271,7 +271,21 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
|> take(1)
|> map { (nil, $0) }])
} else if query.count > 1, let languageCode = languageCode, !languageCode.isEmpty && languageCode != "emoji" {
signals = searchEmojiKeywords(postbox: account.postbox, inputLanguageCode: languageCode, query: query.lowercased(), completeMatch: query.count < 3)
var signal = searchEmojiKeywords(postbox: account.postbox, inputLanguageCode: languageCode, query: query.lowercased(), completeMatch: query.count < 3)
if !languageCode.lowercased().hasPrefix("en") {
signal = signal
|> mapToSignal { keywords in
return .single(keywords)
|> then(
searchEmojiKeywords(postbox: account.postbox, inputLanguageCode: "en-US", query: query.lowercased(), completeMatch: query.count < 3)
|> map { englishKeywords in
return keywords + englishKeywords
}
)
}
}
signals = signal
|> map { keywords -> [Signal<(String?, [FoundStickerItem]), NoError>] in
var signals: [Signal<(String?, [FoundStickerItem]), NoError>] = []
let emoticons = keywords.flatMap { $0.emoticons }

View File

@ -366,7 +366,7 @@ public func themeAutoNightSettingsController(context: AccountContext) -> ViewCon
|> mapToSignal { coordinates -> Signal<(Double, Double, String), NoError> in
return reverseGeocodeLocation(latitude: coordinates.0, longitude: coordinates.1)
|> map { locality in
return (coordinates.0, coordinates.1, locality)
return (coordinates.0, coordinates.1, locality?.city ?? "")
}
}

View File

@ -30,9 +30,9 @@ private final class ThemeSettingsControllerArguments {
}
private enum ThemeSettingsControllerSection: Int32 {
case fontSize
case chatPreview
case theme
case background
case fontSize
case icon
case other
}
@ -57,12 +57,10 @@ public enum ThemeSettingsEntryTag: ItemListItemTag {
private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
case fontSizeHeader(PresentationTheme, String)
case fontSize(PresentationTheme, PresentationFontSize)
case chatPreviewHeader(PresentationTheme, String)
case chatPreview(PresentationTheme, PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder)
case wallpaper(PresentationTheme, String)
case accentColor(PresentationTheme, String, Int32)
case autoNightTheme(PresentationTheme, String, String)
case themeListHeader(PresentationTheme, String)
case themeItem(PresentationTheme, PresentationStrings, [PresentationBuiltinThemeReference], PresentationBuiltinThemeReference, UIColor?)
case iconHeader(PresentationTheme, String)
case iconItem(PresentationTheme, PresentationStrings, [PresentationAppIcon], String?)
@ -73,12 +71,12 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
var section: ItemListSectionId {
switch self {
case .chatPreview, .themeItem, .accentColor:
return ThemeSettingsControllerSection.chatPreview.rawValue
case .fontSizeHeader, .fontSize:
return ThemeSettingsControllerSection.fontSize.rawValue
case .chatPreviewHeader, .chatPreview, .wallpaper:
return ThemeSettingsControllerSection.chatPreview.rawValue
case .themeListHeader, .themeItem, .accentColor, .autoNightTheme:
return ThemeSettingsControllerSection.theme.rawValue
case .wallpaper, .autoNightTheme:
return ThemeSettingsControllerSection.background.rawValue
case .iconHeader, .iconItem:
return ThemeSettingsControllerSection.icon.rawValue
case .otherHeader, .largeEmoji, .animations, .animationsInfo:
@ -88,47 +86,37 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
var stableId: Int32 {
switch self {
case .fontSizeHeader:
return 0
case .fontSize:
return 1
case .chatPreviewHeader:
return 2
case .chatPreview:
return 3
case .wallpaper:
return 4
case .themeListHeader:
return 5
return 0
case .themeItem:
return 6
return 1
case .accentColor:
return 7
return 2
case .wallpaper:
return 3
case .autoNightTheme:
return 8
return 4
case .fontSizeHeader:
return 5
case .fontSize:
return 6
case .iconHeader:
return 100
return 7
case .iconItem:
return 101
return 8
case .otherHeader:
return 102
return 9
case .largeEmoji:
return 103
return 10
case .animations:
return 104
return 11
case .animationsInfo:
return 105
return 12
}
}
static func ==(lhs: ThemeSettingsControllerEntry, rhs: ThemeSettingsControllerEntry) -> Bool {
switch lhs {
case let .chatPreviewHeader(lhsTheme, lhsText):
if case let .chatPreviewHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .chatPreview(lhsTheme, lhsComponentTheme, lhsWallpaper, lhsFontSize, lhsStrings, lhsTimeFormat, lhsNameOrder):
if case let .chatPreview(rhsTheme, rhsComponentTheme, rhsWallpaper, rhsFontSize, rhsStrings, rhsTimeFormat, rhsNameOrder) = rhs, lhsComponentTheme === rhsComponentTheme, lhsTheme === rhsTheme, lhsWallpaper == rhsWallpaper, lhsFontSize == rhsFontSize, lhsStrings === rhsStrings, lhsTimeFormat == rhsTimeFormat, lhsNameOrder == rhsNameOrder {
return true
@ -153,12 +141,6 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
} else {
return false
}
case let .themeListHeader(lhsTheme, lhsText):
if case let .themeListHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .themeItem(lhsTheme, lhsStrings, lhsThemes, lhsCurrentTheme, lhsThemeAccentColor):
if case let .themeItem(rhsTheme, rhsStrings, rhsThemes, rhsCurrentTheme, rhsThemeAccentColor) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsThemes == rhsThemes, lhsCurrentTheme == rhsCurrentTheme, lhsThemeAccentColor == rhsThemeAccentColor {
return true
@ -228,8 +210,6 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
return ThemeSettingsFontSizeItem(theme: theme, fontSize: fontSize, sectionId: self.section, updated: { value in
arguments.selectFontSize(value)
}, tag: ThemeSettingsEntryTag.fontSize)
case let .chatPreviewHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .chatPreview(theme, componentTheme, wallpaper, fontSize, strings, dateTimeFormat, nameDisplayOrder):
return ThemeSettingsChatPreviewItem(context: arguments.context, theme: theme, componentTheme: componentTheme, strings: strings, sectionId: self.section, fontSize: fontSize, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder)
case let .wallpaper(theme, text):
@ -244,8 +224,6 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
return ItemListDisclosureItem(theme: theme, icon: nil, title: text, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
arguments.openAutoNightTheme()
})
case let .themeListHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .themeItem(theme, strings, themes, currentTheme, themeAccentColor):
return ThemeSettingsThemeItem(theme: theme, strings: strings, sectionId: self.section, themes: themes.map { ($0, $0 == .day ? themeAccentColor : nil) }, currentTheme: currentTheme, updated: { theme in
arguments.selectTheme(theme.rawValue)
@ -275,20 +253,16 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
private func themeSettingsControllerEntries(presentationData: PresentationData, theme: PresentationTheme, themeAccentColor: Int32?, autoNightSettings: AutomaticThemeSwitchSetting, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, dateTimeFormat: PresentationDateTimeFormat, largeEmoji: Bool, disableAnimations: Bool, availableAppIcons: [PresentationAppIcon], currentAppIconName: String?) -> [ThemeSettingsControllerEntry] {
var entries: [ThemeSettingsControllerEntry] = []
entries.append(.fontSizeHeader(presentationData.theme, strings.Appearance_TextSize.uppercased()))
entries.append(.fontSize(presentationData.theme, fontSize))
entries.append(.chatPreviewHeader(presentationData.theme, strings.Appearance_Preview))
entries.append(.chatPreview(presentationData.theme, theme, wallpaper, fontSize, presentationData.strings, dateTimeFormat, presentationData.nameDisplayOrder))
entries.append(.wallpaper(presentationData.theme, strings.Settings_ChatBackground))
entries.append(.themeListHeader(presentationData.theme, strings.Appearance_ColorTheme.uppercased()))
if case let .builtin(theme) = theme.name {
entries.append(.themeItem(presentationData.theme, presentationData.strings, [.dayClassic, .day, .nightAccent, .nightGrayscale], theme.reference, themeAccentColor != nil ? UIColor(rgb: UInt32(bitPattern: themeAccentColor!)) : nil))
}
if theme.name == .builtin(.day) {
entries.append(.accentColor(presentationData.theme, strings.Appearance_AccentColor, themeAccentColor ?? defaultDayAccentColor))
}
entries.append(.wallpaper(presentationData.theme, strings.Settings_ChatBackground))
if theme.name == .builtin(.day) || theme.name == .builtin(.dayClassic) {
let title: String
switch autoNightSettings.trigger {
@ -302,6 +276,9 @@ private func themeSettingsControllerEntries(presentationData: PresentationData,
entries.append(.autoNightTheme(presentationData.theme, strings.Appearance_AutoNightTheme, title))
}
entries.append(.fontSizeHeader(presentationData.theme, strings.Appearance_TextSize.uppercased()))
entries.append(.fontSize(presentationData.theme, fontSize))
if !availableAppIcons.isEmpty {
entries.append(.iconHeader(presentationData.theme, strings.Appearance_AppIcon.uppercased()))
entries.append(.iconItem(presentationData.theme, presentationData.strings, availableAppIcons, currentAppIconName))

View File

@ -16,11 +16,11 @@
090E63E62195880F00E3C035 /* ContactAddItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090E63E52195880F00E3C035 /* ContactAddItem.swift */; };
090E63EE2196FE3A00E3C035 /* OpenAddContact.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090E63ED2196FE3A00E3C035 /* OpenAddContact.swift */; };
090E777922A6A32E00CD99F5 /* ThemeSettingsThemeItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090E777822A6A32E00CD99F5 /* ThemeSettingsThemeItem.swift */; };
090E778622A9B95A00CD99F5 /* PeopleNearbyController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090E778522A9B95A00CD99F5 /* PeopleNearbyController.swift */; };
090E778822A9B96100CD99F5 /* PeopleNearbyHeaderItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090E778722A9B96000CD99F5 /* PeopleNearbyHeaderItem.swift */; };
090E778622A9B95A00CD99F5 /* PeersNearbyController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090E778522A9B95A00CD99F5 /* PeersNearbyController.swift */; };
090E778822A9B96100CD99F5 /* PeersNearbyHeaderItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090E778722A9B96000CD99F5 /* PeersNearbyHeaderItem.swift */; };
090E778A22A9F23C00CD99F5 /* ChannelOwnershipTransferController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090E778922A9F23C00CD99F5 /* ChannelOwnershipTransferController.swift */; };
090E778C22AA842300CD99F5 /* anim_success.json in Resources */ = {isa = PBXBuildFile; fileRef = 090E778B22AA842200CD99F5 /* anim_success.json */; };
090E778E22AA863A00CD99F5 /* PeopleNearbyIconNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090E778D22AA863A00CD99F5 /* PeopleNearbyIconNode.swift */; };
090E778E22AA863A00CD99F5 /* PeersNearbyIconNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090E778D22AA863A00CD99F5 /* PeersNearbyIconNode.swift */; };
0910B0ED21FA178C00F8F87D /* WallpaperPreviewMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0910B0EC21FA178C00F8F87D /* WallpaperPreviewMedia.swift */; };
0910B0EF21FA532D00F8F87D /* WallpaperResources.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0910B0EE21FA532D00F8F87D /* WallpaperResources.swift */; };
0910B0F121FB3DE100F8F87D /* WallpaperPatternPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0910B0F021FB3DE100F8F87D /* WallpaperPatternPanelNode.swift */; };
@ -1223,11 +1223,11 @@
090E63E52195880F00E3C035 /* ContactAddItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactAddItem.swift; sourceTree = "<group>"; };
090E63ED2196FE3A00E3C035 /* OpenAddContact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenAddContact.swift; sourceTree = "<group>"; };
090E777822A6A32E00CD99F5 /* ThemeSettingsThemeItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeSettingsThemeItem.swift; sourceTree = "<group>"; };
090E778522A9B95A00CD99F5 /* PeopleNearbyController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeopleNearbyController.swift; sourceTree = "<group>"; };
090E778722A9B96000CD99F5 /* PeopleNearbyHeaderItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeopleNearbyHeaderItem.swift; sourceTree = "<group>"; };
090E778522A9B95A00CD99F5 /* PeersNearbyController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeersNearbyController.swift; sourceTree = "<group>"; };
090E778722A9B96000CD99F5 /* PeersNearbyHeaderItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeersNearbyHeaderItem.swift; sourceTree = "<group>"; };
090E778922A9F23C00CD99F5 /* ChannelOwnershipTransferController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelOwnershipTransferController.swift; sourceTree = "<group>"; };
090E778B22AA842200CD99F5 /* anim_success.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = anim_success.json; sourceTree = "<group>"; };
090E778D22AA863A00CD99F5 /* PeopleNearbyIconNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeopleNearbyIconNode.swift; sourceTree = "<group>"; };
090E778D22AA863A00CD99F5 /* PeersNearbyIconNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeersNearbyIconNode.swift; sourceTree = "<group>"; };
0910B0EC21FA178C00F8F87D /* WallpaperPreviewMedia.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallpaperPreviewMedia.swift; sourceTree = "<group>"; };
0910B0EE21FA532D00F8F87D /* WallpaperResources.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallpaperResources.swift; sourceTree = "<group>"; };
0910B0F021FB3DE100F8F87D /* WallpaperPatternPanelNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallpaperPatternPanelNode.swift; sourceTree = "<group>"; };
@ -2554,14 +2554,14 @@
name = "Language Suggestion";
sourceTree = "<group>";
};
090E778422A9B94700CD99F5 /* People Nearby */ = {
090E778422A9B94700CD99F5 /* Peers Nearby */ = {
isa = PBXGroup;
children = (
090E778522A9B95A00CD99F5 /* PeopleNearbyController.swift */,
090E778722A9B96000CD99F5 /* PeopleNearbyHeaderItem.swift */,
090E778D22AA863A00CD99F5 /* PeopleNearbyIconNode.swift */,
090E778522A9B95A00CD99F5 /* PeersNearbyController.swift */,
090E778722A9B96000CD99F5 /* PeersNearbyHeaderItem.swift */,
090E778D22AA863A00CD99F5 /* PeersNearbyIconNode.swift */,
);
name = "People Nearby";
name = "Peers Nearby";
sourceTree = "<group>";
};
0919546D229458E900E11046 /* Animated Stickers */ = {
@ -4691,7 +4691,7 @@
0941A99E210B053300EBE194 /* Open In */,
09F215982263E61400AEDF6D /* Passcode */,
09B4EE5721A82F5900847FA6 /* Permissions */,
090E778422A9B94700CD99F5 /* People Nearby */,
090E778422A9B94700CD99F5 /* Peers Nearby */,
);
name = Controllers;
sourceTree = "<group>";
@ -5553,7 +5553,7 @@
D0EC6CF31EB9F58800EBF1C3 /* PresentationThemeSettings.swift in Sources */,
D067B4AD211C916300796039 /* TGChannelIntroController.m in Sources */,
D0BE303220601FFC00FBE6D8 /* LocationBroadcastActionSheetItem.swift in Sources */,
090E778E22AA863A00CD99F5 /* PeopleNearbyIconNode.swift in Sources */,
090E778E22AA863A00CD99F5 /* PeersNearbyIconNode.swift in Sources */,
D0EC6CF41EB9F58800EBF1C3 /* ManagedMediaId.swift in Sources */,
09D968A3221F800A00B1458A /* ChatUploadingActivityContentNode.swift in Sources */,
D0CFBB971FD8B0F700B65C0D /* ChatBubbleInstantVideoDecoration.swift in Sources */,
@ -5562,7 +5562,7 @@
D0E9BA521F0559DA00F079A4 /* STPImageLibrary.m in Sources */,
D0EC6CF61EB9F58800EBF1C3 /* ChatContextResultManagedMediaId.swift in Sources */,
D048B33B203C777500038D05 /* RenderedTotalUnreadCount.swift in Sources */,
090E778622A9B95A00CD99F5 /* PeopleNearbyController.swift in Sources */,
090E778622A9B95A00CD99F5 /* PeersNearbyController.swift in Sources */,
D04ECD721FFBF22B00DE9029 /* OpenUrl.swift in Sources */,
D04B4D661EEA993A00711AF6 /* LegacyLocationController.swift in Sources */,
D056CD7A1FF3CC2A00880D28 /* ListMessagePlaybackOverlayNode.swift in Sources */,
@ -6414,7 +6414,7 @@
D0EC6E801EB9F58900EBF1C3 /* ChangePhoneNumberCodeController.swift in Sources */,
D09E637C1F0E7C28003444CD /* SharedMediaPlayer.swift in Sources */,
D0EC6E811EB9F58900EBF1C3 /* NotificationContainerController.swift in Sources */,
090E778822A9B96100CD99F5 /* PeopleNearbyHeaderItem.swift in Sources */,
090E778822A9B96100CD99F5 /* PeersNearbyHeaderItem.swift in Sources */,
D0754D271EEE10C800884F6E /* BotCheckoutController.swift in Sources */,
D053DADA201A4C4400993D32 /* ChatTextInputAttributes.swift in Sources */,
0952D1752176DEB500194860 /* NotificationMuteSettingsController.swift in Sources */,