mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-04 21:41:45 +00:00
Implemented groups nearby
This commit is contained in:
parent
c67619116b
commit
aae6c245f6
@ -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";
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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_") {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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: {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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: {})]
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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()
|
||||
}
|
||||
})
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -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])!
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
@ -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)
|
||||
}
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -43,6 +43,7 @@ enum PresentationResourceKey: Int32 {
|
||||
case itemListDeleteIndicatorIcon
|
||||
case itemListReorderIndicatorIcon
|
||||
case itemListAddPersonIcon
|
||||
case itemListCreateGroupIcon
|
||||
case itemListAddExceptionIcon
|
||||
case itemListAddPhoneIcon
|
||||
case itemListClearInputIcon
|
||||
|
||||
@ -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
Binary file not shown.
@ -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 }
|
||||
|
||||
@ -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 ?? "")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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 */,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user