mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-25 17:43:18 +00:00
Peers nearby improvements
This commit is contained in:
parent
3adf49cde0
commit
1a42a4c389
@ -4398,12 +4398,12 @@ Any member of this group will be able to see messages in the channel.";
|
|||||||
"Contacts.AddPeopleNearby" = "Add People Nearby";
|
"Contacts.AddPeopleNearby" = "Add People Nearby";
|
||||||
|
|
||||||
"PeopleNearby.Title" = "People Nearby";
|
"PeopleNearby.Title" = "People Nearby";
|
||||||
"PeopleNearby.Description" = "Use this section to quickly find people and groups near you.";
|
"PeopleNearby.Description" = "Ask your friend nearby to open this page to exchange phone numbers.";
|
||||||
"PeopleNearby.Users" = "People Around You";
|
"PeopleNearby.Users" = "People Nearby";
|
||||||
"PeopleNearby.UsersEmpty" = "No one else is viewing \"People Nearby\" around you now";
|
"PeopleNearby.UsersEmpty" = "Looking for users around you...";
|
||||||
"PeopleNearby.Groups" = "Groups Around You";
|
"PeopleNearby.Groups" = "Groups Nearby";
|
||||||
"PeopleNearby.CreateGroup" = "Start a Group Chat Here";
|
"PeopleNearby.CreateGroup" = "Create a Group Here";
|
||||||
"PeopleNearby.Channels" = "Channels Around You";
|
"PeopleNearby.Channels" = "Channels Nearby";
|
||||||
|
|
||||||
"Channel.Management.LabelOwner" = "Owner";
|
"Channel.Management.LabelOwner" = "Owner";
|
||||||
"Channel.Management.LabelAdministrator" = "Administrator";
|
"Channel.Management.LabelAdministrator" = "Administrator";
|
||||||
@ -4412,12 +4412,8 @@ Any member of this group will be able to see messages in the channel.";
|
|||||||
"Common.ActionNotAllowedError" = "Sorry, you are not allowed to do this.";
|
"Common.ActionNotAllowedError" = "Sorry, you are not allowed to do this.";
|
||||||
|
|
||||||
"Group.Location.Title" = "Location";
|
"Group.Location.Title" = "Location";
|
||||||
"Group.Location.SetLocation" = "Set Location";
|
|
||||||
"Group.Location.ChangeLocation" = "Change Location";
|
"Group.Location.ChangeLocation" = "Change Location";
|
||||||
"Group.Location.RemoveLocation" = "Remove Location";
|
"Group.Location.Info" = "People can find your group using People Nearby section.";
|
||||||
"Group.Location.Info" = "People will be able to find your group in the Groups Nearby section (Contacts > Add People Nearby).";
|
|
||||||
|
|
||||||
"Group.Username.Title" = "Username";
|
|
||||||
|
|
||||||
"Channel.AdminLog.MessageTransferedName" = "transferred ownership to %1$@";
|
"Channel.AdminLog.MessageTransferedName" = "transferred ownership to %1$@";
|
||||||
"Channel.AdminLog.MessageTransferedNameUsername" = "transferred ownership to %1$@ (%2$@)";
|
"Channel.AdminLog.MessageTransferedNameUsername" = "transferred ownership to %1$@ (%2$@)";
|
||||||
@ -4432,9 +4428,16 @@ Any member of this group will be able to see messages in the channel.";
|
|||||||
"Permissions.PeopleNearbyAllow.v0" = "Allow Access";
|
"Permissions.PeopleNearbyAllow.v0" = "Allow Access";
|
||||||
"Permissions.PeopleNearbyAllowInSettings.v0" = "Allow in Settings";
|
"Permissions.PeopleNearbyAllowInSettings.v0" = "Allow in Settings";
|
||||||
|
|
||||||
"Conversation.ReportGroupLocation" = "Group unrelated to tocation?";
|
"Conversation.ReportGroupLocation" = "Group unrelated to location?";
|
||||||
"ReportGroupLocation.Title" = "Report Unrelated Group";
|
"ReportGroupLocation.Title" = "Report Unrelated Group";
|
||||||
"ReportGroupLocation.Text" = "Please tell us if this group is not related to this location.";
|
"ReportGroupLocation.Text" = "Please tell us if this group is not related to this location.";
|
||||||
"ReportGroupLocation.Report" = "Report";
|
"ReportGroupLocation.Report" = "Report";
|
||||||
|
|
||||||
"Group.Setup.TypePublicWithLocationHelp" = "Public groups can be found in search, chat history is available to everyone and anyone can join.\n\nTo make your group public, set a username or add a location.";
|
"LocalGroup.Title" = "Create a Local Group";
|
||||||
|
"LocalGroup.Text" = "Anyone close to this location (neighbors, co-workers, fellow students, event attendees, visitors of a venue) will see your group in the People Nearby section.";
|
||||||
|
"LocalGroup.ButtonTitle" = "Start Group";
|
||||||
|
"LocalGroup.IrrelevantWarning" = "If you start an unrelated group at this location, you may get restricted in creating new location-based groups.";
|
||||||
|
|
||||||
|
"GroupInfo.Location" = "Location";
|
||||||
|
"GroupInfo.PublicLink" = "Public Link";
|
||||||
|
"GroupInfo.PublicLinkAdd" = "Add";
|
||||||
|
|||||||
@ -14,7 +14,7 @@ typedef enum {
|
|||||||
|
|
||||||
@interface TGLocationPickerController : TGLocationMapViewController
|
@interface TGLocationPickerController : TGLocationMapViewController
|
||||||
|
|
||||||
@property (nonatomic, copy) void (^locationPicked)(CLLocationCoordinate2D coordinate, TGVenueAttachment *venue);
|
@property (nonatomic, copy) void (^locationPicked)(CLLocationCoordinate2D coordinate, TGVenueAttachment *venue, NSString *address);
|
||||||
|
|
||||||
@property (nonatomic, copy) SSignal *(^nearbyPlacesSignal)(NSString *query, CLLocation *coordinate);
|
@property (nonatomic, copy) SSignal *(^nearbyPlacesSignal)(NSString *query, CLLocation *coordinate);
|
||||||
|
|
||||||
|
|||||||
@ -345,7 +345,7 @@ const CGPoint TGLocationPickerPinOffset = { 0.0f, 33.0f };
|
|||||||
coordinate = [self mapCenterCoordinateForPickerPin];
|
coordinate = [self mapCenterCoordinateForPickerPin];
|
||||||
|
|
||||||
if (self.locationPicked != nil)
|
if (self.locationPicked != nil)
|
||||||
self.locationPicked(coordinate, nil);
|
self.locationPicked(coordinate, nil, _customAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)searchButtonPressed
|
- (void)searchButtonPressed
|
||||||
@ -520,7 +520,7 @@ const CGPoint TGLocationPickerPinOffset = { 0.0f, 33.0f };
|
|||||||
|
|
||||||
NSString *address = @"";
|
NSString *address = @"";
|
||||||
if (result != nil)
|
if (result != nil)
|
||||||
address = result.displayAddress;
|
address = result.fullAddress;
|
||||||
|
|
||||||
strongSelf->_customAddress = address;
|
strongSelf->_customAddress = address;
|
||||||
[strongSelf updateCurrentLocationCell];
|
[strongSelf updateCurrentLocationCell];
|
||||||
@ -662,9 +662,9 @@ const CGPoint TGLocationPickerPinOffset = { 0.0f, 33.0f };
|
|||||||
|
|
||||||
_ownLocationView.hidden = true;
|
_ownLocationView.hidden = true;
|
||||||
_pickerPinWrapper.hidden = false;
|
_pickerPinWrapper.hidden = false;
|
||||||
if (_intent != TGLocationPickerControllerCustomLocationIntent) {
|
//if (_intent != TGLocationPickerControllerCustomLocationIntent) {
|
||||||
[_pickerPinView setCustomPin:true animated:true];
|
[_pickerPinView setCustomPin:true animated:true];
|
||||||
}
|
//}
|
||||||
|
|
||||||
_mapView.tapEnabled = false;
|
_mapView.tapEnabled = false;
|
||||||
_mapView.longPressAsTapEnabled = false;
|
_mapView.longPressAsTapEnabled = false;
|
||||||
@ -1128,7 +1128,7 @@ const CGPoint TGLocationPickerPinOffset = { 0.0f, 33.0f };
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (self.locationPicked != nil)
|
if (self.locationPicked != nil)
|
||||||
self.locationPicked(venue.coordinate, [venue venueAttachment]);
|
self.locationPicked(venue.coordinate, [venue venueAttachment], _customAddress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -435,6 +435,7 @@ NSString *const TGLocationPinAnnotationKind = @"TGLocationPinAnnotation";
|
|||||||
- (void)setPinRaised:(bool)raised avatar:(bool)avatar animated:(bool)animated completion:(void (^)(void))completion
|
- (void)setPinRaised:(bool)raised avatar:(bool)avatar animated:(bool)animated completion:(void (^)(void))completion
|
||||||
{
|
{
|
||||||
_pinRaised = raised;
|
_pinRaised = raised;
|
||||||
|
avatar = false;
|
||||||
|
|
||||||
[_shadowView.layer removeAllAnimations];
|
[_shadowView.layer removeAllAnimations];
|
||||||
if (iosMajorVersion() < 7)
|
if (iosMajorVersion() < 7)
|
||||||
@ -491,6 +492,9 @@ NSString *const TGLocationPinAnnotationKind = @"TGLocationPinAnnotation";
|
|||||||
_iconView.image = image;
|
_iconView.image = image;
|
||||||
[_backgroundView addSubview:_avatarView];
|
[_backgroundView addSubview:_avatarView];
|
||||||
_avatarView.center = CGPointMake(_backgroundView.frame.size.width / 2.0f, _backgroundView.frame.size.height / 2.0f - 5.0f);
|
_avatarView.center = CGPointMake(_backgroundView.frame.size.width / 2.0f, _backgroundView.frame.size.height / 2.0f - 5.0f);
|
||||||
|
_shadowView.center = CGPointMake(TGScreenPixel, -36.0f);
|
||||||
|
_backgroundView.center = CGPointMake(_shadowView.frame.size.width / 2.0f, _shadowView.frame.size.height / 2.0f);
|
||||||
|
_iconView.center = CGPointMake(_shadowView.frame.size.width / 2.0f, _shadowView.frame.size.height / 2.0f - 5.0f);
|
||||||
|
|
||||||
TGDispatchAfter(0.01, dispatch_get_main_queue(), ^
|
TGDispatchAfter(0.01, dispatch_get_main_queue(), ^
|
||||||
{
|
{
|
||||||
@ -504,8 +508,11 @@ NSString *const TGLocationPinAnnotationKind = @"TGLocationPinAnnotation";
|
|||||||
if (!customPin)
|
if (!customPin)
|
||||||
[self addSubview:_avatarView];
|
[self addSubview:_avatarView];
|
||||||
_animating = false;
|
_animating = false;
|
||||||
|
[self setNeedsLayout];
|
||||||
}];
|
}];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
[self setNeedsLayout];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@ -14,6 +14,8 @@
|
|||||||
@property (nonatomic, readonly) NSString *city;
|
@property (nonatomic, readonly) NSString *city;
|
||||||
@property (nonatomic, readonly) NSString *district;
|
@property (nonatomic, readonly) NSString *district;
|
||||||
@property (nonatomic, readonly) NSString *street;
|
@property (nonatomic, readonly) NSString *street;
|
||||||
|
|
||||||
|
@property (nonatomic, readonly) NSString *fullAddress;
|
||||||
|
|
||||||
+ (TGLocationReverseGeocodeResult *)reverseGeocodeResultWithDictionary:(NSDictionary *)dictionary;
|
+ (TGLocationReverseGeocodeResult *)reverseGeocodeResultWithDictionary:(NSDictionary *)dictionary;
|
||||||
+ (TGLocationReverseGeocodeResult *)reverseGeocodeResultWithPlacemark:(CLPlacemark *)placemark;
|
+ (TGLocationReverseGeocodeResult *)reverseGeocodeResultWithPlacemark:(CLPlacemark *)placemark;
|
||||||
|
|||||||
@ -69,5 +69,17 @@
|
|||||||
|
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (NSString *)fullAddress
|
||||||
|
{
|
||||||
|
NSMutableArray *components = [[NSMutableArray alloc] init];
|
||||||
|
if (self.street.length > 0)
|
||||||
|
[components addObject:self.street];
|
||||||
|
if (self.city.length > 0)
|
||||||
|
[components addObject:self.city];
|
||||||
|
if (self.country.length > 0)
|
||||||
|
[components addObject:self.country];
|
||||||
|
return [components componentsJoinedByString:@", "];
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@ -3073,22 +3073,6 @@ public extension Api {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func createChannel(flags: Int32, title: String, about: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
|
||||||
let buffer = Buffer()
|
|
||||||
buffer.appendInt32(-192332417)
|
|
||||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
|
||||||
serializeString(title, buffer: buffer, boxed: false)
|
|
||||||
serializeString(about, buffer: buffer, boxed: false)
|
|
||||||
return (FunctionDescription(name: "channels.createChannel", parameters: [("flags", flags), ("title", title), ("about", about)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
|
||||||
let reader = BufferReader(buffer)
|
|
||||||
var result: Api.Updates?
|
|
||||||
if let signature = reader.readInt32() {
|
|
||||||
result = Api.parse(reader, signature: signature) as? Api.Updates
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func editTitle(channel: Api.InputChannel, title: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
public static func editTitle(channel: Api.InputChannel, title: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
buffer.appendInt32(1450044624)
|
buffer.appendInt32(1450044624)
|
||||||
@ -3241,20 +3225,6 @@ public extension Api {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func getAdminedPublicChannels() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.Chats>) {
|
|
||||||
let buffer = Buffer()
|
|
||||||
buffer.appendInt32(-1920105769)
|
|
||||||
|
|
||||||
return (FunctionDescription(name: "channels.getAdminedPublicChannels", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Chats? in
|
|
||||||
let reader = BufferReader(buffer)
|
|
||||||
var result: Api.messages.Chats?
|
|
||||||
if let signature = reader.readInt32() {
|
|
||||||
result = Api.parse(reader, signature: signature) as? Api.messages.Chats
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func getAdminLog(flags: Int32, channel: Api.InputChannel, q: String, eventsFilter: Api.ChannelAdminLogEventsFilter?, admins: [Api.InputUser]?, maxId: Int64, minId: Int64, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.channels.AdminLogResults>) {
|
public static func getAdminLog(flags: Int32, channel: Api.InputChannel, q: String, eventsFilter: Api.ChannelAdminLogEventsFilter?, admins: [Api.InputUser]?, maxId: Int64, minId: Int64, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.channels.AdminLogResults>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
buffer.appendInt32(870184064)
|
buffer.appendInt32(870184064)
|
||||||
@ -3503,6 +3473,38 @@ public extension Api {
|
|||||||
return result
|
return result
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static func createChannel(flags: Int32, title: String, about: String, geoPoint: Api.InputGeoPoint?, address: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||||
|
let buffer = Buffer()
|
||||||
|
buffer.appendInt32(1029681423)
|
||||||
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
|
serializeString(title, buffer: buffer, boxed: false)
|
||||||
|
serializeString(about, buffer: buffer, boxed: false)
|
||||||
|
if Int(flags) & Int(1 << 2) != 0 {geoPoint!.serialize(buffer, true)}
|
||||||
|
if Int(flags) & Int(1 << 2) != 0 {serializeString(address!, buffer: buffer, boxed: false)}
|
||||||
|
return (FunctionDescription(name: "channels.createChannel", parameters: [("flags", flags), ("title", title), ("about", about), ("geoPoint", geoPoint), ("address", address)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
||||||
|
let reader = BufferReader(buffer)
|
||||||
|
var result: Api.Updates?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
result = Api.parse(reader, signature: signature) as? Api.Updates
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func getAdminedPublicChannels(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.Chats>) {
|
||||||
|
let buffer = Buffer()
|
||||||
|
buffer.appendInt32(-122669393)
|
||||||
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
|
return (FunctionDescription(name: "channels.getAdminedPublicChannels", parameters: [("flags", flags)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Chats? in
|
||||||
|
let reader = BufferReader(buffer)
|
||||||
|
var result: Api.messages.Chats?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
result = Api.parse(reader, signature: signature) as? Api.messages.Chats
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public struct payments {
|
public struct payments {
|
||||||
public static func getPaymentForm(msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.payments.PaymentForm>) {
|
public static func getPaymentForm(msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.payments.PaymentForm>) {
|
||||||
|
|||||||
@ -157,8 +157,13 @@ public func updateAddressName(account: Account, domain: AddressNameDomain, name:
|
|||||||
} |> mapError { _ -> UpdateAddressNameError in return .generic } |> switchToLatest
|
} |> mapError { _ -> UpdateAddressNameError in return .generic } |> switchToLatest
|
||||||
}
|
}
|
||||||
|
|
||||||
public func adminedPublicChannels(account: Account) -> Signal<[Peer], NoError> {
|
public func adminedPublicChannels(account: Account, location: Bool = false) -> Signal<[Peer], NoError> {
|
||||||
return account.network.request(Api.functions.channels.getAdminedPublicChannels())
|
var flags: Int32 = 0
|
||||||
|
if location {
|
||||||
|
flags |= (1 << 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return account.network.request(Api.functions.channels.getAdminedPublicChannels(flags: flags))
|
||||||
|> retryRequest
|
|> retryRequest
|
||||||
|> mapToSignal { result -> Signal<[Peer], NoError> in
|
|> mapToSignal { result -> Signal<[Peer], NoError> in
|
||||||
var peers: [Peer] = []
|
var peers: [Peer] = []
|
||||||
|
|||||||
@ -22,6 +22,7 @@ public struct CachedChannelFlags: OptionSet {
|
|||||||
public static let canSetStickerSet = CachedChannelFlags(rawValue: 1 << 2)
|
public static let canSetStickerSet = CachedChannelFlags(rawValue: 1 << 2)
|
||||||
public static let preHistoryEnabled = CachedChannelFlags(rawValue: 1 << 3)
|
public static let preHistoryEnabled = CachedChannelFlags(rawValue: 1 << 3)
|
||||||
public static let canViewStats = CachedChannelFlags(rawValue: 1 << 4)
|
public static let canViewStats = CachedChannelFlags(rawValue: 1 << 4)
|
||||||
|
public static let canChangePeerGeoLocation = CachedChannelFlags(rawValue: 1 << 5)
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct CachedChannelParticipantsSummary: PostboxCoding, Equatable {
|
public struct CachedChannelParticipantsSummary: PostboxCoding, Equatable {
|
||||||
|
|||||||
@ -14,9 +14,29 @@ import Foundation
|
|||||||
#endif
|
#endif
|
||||||
import TelegramApi
|
import TelegramApi
|
||||||
|
|
||||||
private func createChannel(account: Account, title: String, description: String?, isSupergroup:Bool) -> Signal<PeerId, CreateChannelError> {
|
public enum CreateChannelError {
|
||||||
|
case generic
|
||||||
|
case restricted
|
||||||
|
}
|
||||||
|
|
||||||
|
private func createChannel(account: Account, title: String, description: String?, isSupergroup:Bool, location: (latitude: Double, longitude: Double, address: String)? = nil) -> Signal<PeerId, CreateChannelError> {
|
||||||
return account.postbox.transaction { transaction -> Signal<PeerId, CreateChannelError> in
|
return account.postbox.transaction { transaction -> Signal<PeerId, CreateChannelError> in
|
||||||
return account.network.request(Api.functions.channels.createChannel(flags: isSupergroup ? 1 << 1 : 1 << 0, title: title, about: description ?? ""), automaticFloodWait: false)
|
var flags: Int32 = 0
|
||||||
|
if isSupergroup {
|
||||||
|
flags |= (1 << 1)
|
||||||
|
} else {
|
||||||
|
flags |= (1 << 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
var geoPoint: Api.InputGeoPoint?
|
||||||
|
var address: String?
|
||||||
|
if let location = location {
|
||||||
|
flags |= (1 << 2)
|
||||||
|
geoPoint = .inputGeoPoint(lat: location.latitude, long: location.longitude)
|
||||||
|
address = location.address
|
||||||
|
}
|
||||||
|
|
||||||
|
return account.network.request(Api.functions.channels.createChannel(flags: flags, title: title, about: description ?? "", geoPoint: geoPoint, address: address), automaticFloodWait: false)
|
||||||
|> mapError { error -> CreateChannelError in
|
|> mapError { error -> CreateChannelError in
|
||||||
if error.errorDescription == "USER_RESTRICTED" {
|
if error.errorDescription == "USER_RESTRICTED" {
|
||||||
return .restricted
|
return .restricted
|
||||||
@ -46,17 +66,12 @@ private func createChannel(account: Account, title: String, description: String?
|
|||||||
|> switchToLatest
|
|> switchToLatest
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum CreateChannelError {
|
|
||||||
case generic
|
|
||||||
case restricted
|
|
||||||
}
|
|
||||||
|
|
||||||
public func createChannel(account: Account, title: String, description: String?) -> Signal<PeerId, CreateChannelError> {
|
public func createChannel(account: Account, title: String, description: String?) -> Signal<PeerId, CreateChannelError> {
|
||||||
return createChannel(account: account, title: title, description: description, isSupergroup: false)
|
return createChannel(account: account, title: title, description: description, isSupergroup: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func createSupergroup(account: Account, title: String, description: String?) -> Signal<PeerId, CreateChannelError> {
|
public func createSupergroup(account: Account, title: String, description: String?, location: (latitude: Double, longitude: Double, address: String)? = nil) -> Signal<PeerId, CreateChannelError> {
|
||||||
return createChannel(account: account, title: title, description: description, isSupergroup: true)
|
return createChannel(account: account, title: title, description: description, isSupergroup: true, location: location)
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum DeleteChannelError {
|
public enum DeleteChannelError {
|
||||||
|
|||||||
@ -90,7 +90,13 @@ public func updateChannelOwnership(postbox: Postbox, network: Network, accountSt
|
|||||||
}
|
}
|
||||||
|
|
||||||
let checkPassword = twoStepAuthData(network)
|
let checkPassword = twoStepAuthData(network)
|
||||||
|> mapError { _ in ChannelOwnershipTransferError.generic }
|
|> mapError { error -> ChannelOwnershipTransferError in
|
||||||
|
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||||
|
return .limitExceeded
|
||||||
|
} else {
|
||||||
|
return .generic
|
||||||
|
}
|
||||||
|
}
|
||||||
|> mapToSignal { authData -> Signal<Api.InputCheckPasswordSRP, ChannelOwnershipTransferError> in
|
|> mapToSignal { authData -> Signal<Api.InputCheckPasswordSRP, ChannelOwnershipTransferError> in
|
||||||
if let currentPasswordDerivation = authData.currentPasswordDerivation, let srpSessionData = authData.srpSessionData {
|
if let currentPasswordDerivation = authData.currentPasswordDerivation, let srpSessionData = authData.srpSessionData {
|
||||||
guard let kdfResult = passwordKDF(password: password, derivation: currentPasswordDerivation, srpSessionData: srpSessionData) else {
|
guard let kdfResult = passwordKDF(password: password, derivation: currentPasswordDerivation, srpSessionData: srpSessionData) else {
|
||||||
|
|||||||
@ -18,7 +18,7 @@ public struct PeerStatusSettings: OptionSet {
|
|||||||
public static let canBlock = PeerStatusSettings(rawValue: 1 << 3)
|
public static let canBlock = PeerStatusSettings(rawValue: 1 << 3)
|
||||||
public static let canAddContact = PeerStatusSettings(rawValue: 1 << 4)
|
public static let canAddContact = PeerStatusSettings(rawValue: 1 << 4)
|
||||||
public static let addExceptionWhenAddingContact = PeerStatusSettings(rawValue: 1 << 5)
|
public static let addExceptionWhenAddingContact = PeerStatusSettings(rawValue: 1 << 5)
|
||||||
public static let canReportIrrelevantGeoLocation = PeerStatusSettings(rawValue: 1 << 5)
|
public static let canReportIrrelevantGeoLocation = PeerStatusSettings(rawValue: 1 << 6)
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PeerStatusSettings {
|
extension PeerStatusSettings {
|
||||||
|
|||||||
@ -16,11 +16,11 @@ public struct PeerNearby {
|
|||||||
|
|
||||||
public final class PeersNearbyContext {
|
public final class PeersNearbyContext {
|
||||||
private let queue: Queue = Queue.mainQueue()
|
private let queue: Queue = Queue.mainQueue()
|
||||||
private var subscribers = Bag<([PeerNearby]) -> Void>()
|
private var subscribers = Bag<([PeerNearby]?) -> Void>()
|
||||||
private let disposable = MetaDisposable()
|
private let disposable = MetaDisposable()
|
||||||
private var timer: SwiftSignalKit.Timer?
|
private var timer: SwiftSignalKit.Timer?
|
||||||
|
|
||||||
private var entries: [PeerNearby] = []
|
private var entries: [PeerNearby]?
|
||||||
|
|
||||||
public init(network: Network, accountStateManager: AccountStateManager, coordinate: (latitude: Double, longitude: Double)) {
|
public init(network: Network, accountStateManager: AccountStateManager, coordinate: (latitude: Double, longitude: Double)) {
|
||||||
self.disposable.set((network.request(Api.functions.contacts.getLocated(geoPoint: .inputGeoPoint(lat: coordinate.latitude, long: coordinate.longitude)))
|
self.disposable.set((network.request(Api.functions.contacts.getLocated(geoPoint: .inputGeoPoint(lat: coordinate.latitude, long: coordinate.longitude)))
|
||||||
@ -53,7 +53,7 @@ public final class PeersNearbyContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
|
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
|
||||||
var entries = strongSelf.entries.filter { Double($0.expires) > timestamp }
|
var entries = strongSelf.entries?.filter { Double($0.expires) > timestamp } ?? []
|
||||||
let updatedEntries = updatedEntries.filter { Double($0.expires) > timestamp }
|
let updatedEntries = updatedEntries.filter { Double($0.expires) > timestamp }
|
||||||
|
|
||||||
var existingPeerIds: [PeerId: Int] = [:]
|
var existingPeerIds: [PeerId: Int] = [:]
|
||||||
@ -82,7 +82,7 @@ public final class PeersNearbyContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
|
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
|
||||||
strongSelf.entries = strongSelf.entries.filter { Double($0.expires) > timestamp }
|
strongSelf.entries = strongSelf.entries?.filter { Double($0.expires) > timestamp }
|
||||||
}, queue: self.queue)
|
}, queue: self.queue)
|
||||||
self.timer?.start()
|
self.timer?.start()
|
||||||
}
|
}
|
||||||
@ -92,7 +92,7 @@ public final class PeersNearbyContext {
|
|||||||
self.timer?.invalidate()
|
self.timer?.invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func get() -> Signal<[PeerNearby], NoError> {
|
public func get() -> Signal<[PeerNearby]?, NoError> {
|
||||||
let queue = self.queue
|
let queue = self.queue
|
||||||
return Signal { [weak self] subscriber in
|
return Signal { [weak self] subscriber in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
|
|||||||
@ -296,6 +296,9 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
|
|||||||
if (flags & (1 << 7)) != 0 {
|
if (flags & (1 << 7)) != 0 {
|
||||||
channelFlags.insert(.canSetStickerSet)
|
channelFlags.insert(.canSetStickerSet)
|
||||||
}
|
}
|
||||||
|
if (flags & (1 << 16)) != 0 {
|
||||||
|
channelFlags.insert(.canChangePeerGeoLocation)
|
||||||
|
}
|
||||||
|
|
||||||
let linkedDiscussionPeerId: PeerId?
|
let linkedDiscussionPeerId: PeerId?
|
||||||
if let linkedChatId = linkedChatId, linkedChatId != 0 {
|
if let linkedChatId = linkedChatId, linkedChatId != 0 {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"provides-namespace" : true
|
||||||
|
}
|
||||||
|
}
|
||||||
22
submodules/TelegramUI/Images.xcassets/Location/CreateGroupIcon.imageset/Contents.json
vendored
Normal file
22
submodules/TelegramUI/Images.xcassets/Location/CreateGroupIcon.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "createlocalgroup@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "createlocalgroup@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
submodules/TelegramUI/Images.xcassets/Location/CreateGroupIcon.imageset/createlocalgroup@2x.png
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Location/CreateGroupIcon.imageset/createlocalgroup@2x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
BIN
submodules/TelegramUI/Images.xcassets/Location/CreateGroupIcon.imageset/createlocalgroup@3x.png
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Location/CreateGroupIcon.imageset/createlocalgroup@3x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.6 KiB |
22
submodules/TelegramUI/Images.xcassets/Location/LocalGroupDarkIcon.imageset/Contents.json
vendored
Normal file
22
submodules/TelegramUI/Images.xcassets/Location/LocalGroupDarkIcon.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "map_dark@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "map_dark@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
submodules/TelegramUI/Images.xcassets/Location/LocalGroupDarkIcon.imageset/map_dark@2x.png
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Location/LocalGroupDarkIcon.imageset/map_dark@2x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
BIN
submodules/TelegramUI/Images.xcassets/Location/LocalGroupDarkIcon.imageset/map_dark@3x.png
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Location/LocalGroupDarkIcon.imageset/map_dark@3x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
22
submodules/TelegramUI/Images.xcassets/Location/LocalGroupLightIcon.imageset/Contents.json
vendored
Normal file
22
submodules/TelegramUI/Images.xcassets/Location/LocalGroupLightIcon.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "map_day@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "map_day@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
submodules/TelegramUI/Images.xcassets/Location/LocalGroupLightIcon.imageset/map_day@2x.png
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Location/LocalGroupLightIcon.imageset/map_day@2x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
BIN
submodules/TelegramUI/Images.xcassets/Location/LocalGroupLightIcon.imageset/map_day@3x.png
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Location/LocalGroupLightIcon.imageset/map_day@3x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
@ -569,7 +569,7 @@ final class AuthorizedApplicationContext {
|
|||||||
strongSelf.currentPermissionsController = controller
|
strongSelf.currentPermissionsController = controller
|
||||||
}
|
}
|
||||||
|
|
||||||
controller.setState(state, animated: didAppear)
|
controller.setState(.permission(state), animated: didAppear)
|
||||||
controller.proceed = { resolved in
|
controller.proceed = { resolved in
|
||||||
permissionsPosition.set(position + 1)
|
permissionsPosition.set(position + 1)
|
||||||
switch state {
|
switch state {
|
||||||
|
|||||||
@ -235,7 +235,7 @@ public func channelDiscussionGroupSetupController(context: AccountContext, peerI
|
|||||||
guard let peer = peer else {
|
guard let peer = peer else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pushControllerImpl?(createGroupController(context: context, peerIds: [], initialTitle: peer.displayTitle + " Chat", type: .supergroup, completion: { groupId, dismiss in
|
pushControllerImpl?(createGroupController(context: context, peerIds: [], initialTitle: peer.displayTitle + " Chat", mode: .supergroup, completion: { groupId, dismiss in
|
||||||
var applySignal = updateGroupDiscussionForChannel(network: context.account.network, postbox: context.account.postbox, channelId: peerId, groupId: groupId)
|
var applySignal = updateGroupDiscussionForChannel(network: context.account.network, postbox: context.account.postbox, channelId: peerId, groupId: groupId)
|
||||||
var cancelImpl: (() -> Void)?
|
var cancelImpl: (() -> Void)?
|
||||||
let progressSignal = Signal<Never, NoError> { subscriber in
|
let progressSignal = Signal<Never, NoError> { subscriber in
|
||||||
|
|||||||
@ -19,10 +19,8 @@ private final class ChannelVisibilityControllerArguments {
|
|||||||
let copyPrivateLink: () -> Void
|
let copyPrivateLink: () -> Void
|
||||||
let revokePrivateLink: () -> Void
|
let revokePrivateLink: () -> Void
|
||||||
let sharePrivateLink: () -> 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, setLocation: @escaping () -> Void, removeLocation: @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) {
|
||||||
self.account = account
|
self.account = account
|
||||||
self.updateCurrentType = updateCurrentType
|
self.updateCurrentType = updateCurrentType
|
||||||
self.updatePublicLinkText = updatePublicLinkText
|
self.updatePublicLinkText = updatePublicLinkText
|
||||||
@ -33,8 +31,6 @@ private final class ChannelVisibilityControllerArguments {
|
|||||||
self.copyPrivateLink = copyPrivateLink
|
self.copyPrivateLink = copyPrivateLink
|
||||||
self.revokePrivateLink = revokePrivateLink
|
self.revokePrivateLink = revokePrivateLink
|
||||||
self.sharePrivateLink = sharePrivateLink
|
self.sharePrivateLink = sharePrivateLink
|
||||||
self.setLocation = setLocation
|
|
||||||
self.removeLocation = removeLocation
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,12 +74,6 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
|
|||||||
case existingLinksInfo(PresentationTheme, String)
|
case existingLinksInfo(PresentationTheme, String)
|
||||||
case existingLinkPeerItem(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer, ItemListPeerItemEditing, Bool)
|
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 {
|
var section: ItemListSectionId {
|
||||||
switch self {
|
switch self {
|
||||||
case .typeHeader, .typePublic, .typePrivate, .typeInfo:
|
case .typeHeader, .typePublic, .typePrivate, .typeInfo:
|
||||||
@ -94,8 +84,6 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
|
|||||||
return ChannelVisibilitySection.linkActions.rawValue
|
return ChannelVisibilitySection.linkActions.rawValue
|
||||||
case .existingLinksInfo, .existingLinkPeerItem:
|
case .existingLinksInfo, .existingLinkPeerItem:
|
||||||
return ChannelVisibilitySection.link.rawValue
|
return ChannelVisibilitySection.link.rawValue
|
||||||
case .locationHeader, .location, .locationSetup, .locationRemove, .locationInfo:
|
|
||||||
return ChannelVisibilitySection.location.rawValue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,16 +121,6 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
|
|||||||
return 14
|
return 14
|
||||||
case let .existingLinkPeerItem(index, _, _, _, _, _, _, _):
|
case let .existingLinkPeerItem(index, _, _, _, _, _, _, _):
|
||||||
return 15 + index
|
return 15 + index
|
||||||
case .locationHeader:
|
|
||||||
return 1000
|
|
||||||
case .location:
|
|
||||||
return 1001
|
|
||||||
case .locationSetup:
|
|
||||||
return 1002
|
|
||||||
case .locationRemove:
|
|
||||||
return 1003
|
|
||||||
case .locationInfo:
|
|
||||||
return 1004
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,36 +246,6 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -386,21 +334,6 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
|
|||||||
}, removePeer: { peerId in
|
}, removePeer: { peerId in
|
||||||
arguments.revokePeerId(peerId)
|
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.replacingOccurrences(of: ", ", with: "\n"), imageSignal: imageSignal, selected: nil, sectionId: self.section, style: .blocks, action: nil)
|
|
||||||
case let .locationSetup(theme, text):
|
|
||||||
return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
|
||||||
arguments.setLocation()
|
|
||||||
}, clearHighlightAutomatically: false)
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -526,6 +459,8 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
|
|||||||
} else {
|
} else {
|
||||||
if let addressName = peer.addressName, !addressName.isEmpty {
|
if let addressName = peer.addressName, !addressName.isEmpty {
|
||||||
selectedType = .publicChannel
|
selectedType = .publicChannel
|
||||||
|
} else if let cachedChannelData = view.cachedData as? CachedChannelData, cachedChannelData.peerGeoLocation != nil {
|
||||||
|
selectedType = .publicChannel
|
||||||
} else {
|
} else {
|
||||||
selectedType = .privateChannel
|
selectedType = .privateChannel
|
||||||
}
|
}
|
||||||
@ -543,30 +478,33 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch mode {
|
if let _ = (view.cachedData as? CachedChannelData)?.peerGeoLocation {
|
||||||
case .privateLink:
|
} else {
|
||||||
break
|
switch mode {
|
||||||
case .initialSetup, .generic:
|
case .privateLink:
|
||||||
entries.append(.typeHeader(presentationData.theme, isGroup ? presentationData.strings.Group_Setup_TypeHeader : presentationData.strings.Channel_Edit_LinkItem))
|
break
|
||||||
entries.append(.typePublic(presentationData.theme, presentationData.strings.Channel_Setup_TypePublic, selectedType == .publicChannel))
|
case .initialSetup, .generic:
|
||||||
entries.append(.typePrivate(presentationData.theme, presentationData.strings.Channel_Setup_TypePrivate, selectedType == .privateChannel))
|
entries.append(.typeHeader(presentationData.theme, isGroup ? presentationData.strings.Group_Setup_TypeHeader : presentationData.strings.Channel_Edit_LinkItem))
|
||||||
|
entries.append(.typePublic(presentationData.theme, presentationData.strings.Channel_Setup_TypePublic, selectedType == .publicChannel))
|
||||||
switch selectedType {
|
entries.append(.typePrivate(presentationData.theme, presentationData.strings.Channel_Setup_TypePrivate, selectedType == .privateChannel))
|
||||||
case .publicChannel:
|
|
||||||
if isGroup {
|
switch selectedType {
|
||||||
entries.append(.typeInfo(presentationData.theme, presentationData.strings.Group_Setup_TypePublicWithLocationHelp))
|
case .publicChannel:
|
||||||
} else {
|
if isGroup {
|
||||||
entries.append(.typeInfo(presentationData.theme, presentationData.strings.Channel_Setup_TypePublicHelp))
|
entries.append(.typeInfo(presentationData.theme, presentationData.strings.Group_Setup_TypePublicHelp))
|
||||||
}
|
} else {
|
||||||
case .privateChannel:
|
entries.append(.typeInfo(presentationData.theme, presentationData.strings.Channel_Setup_TypePublicHelp))
|
||||||
if isGroup {
|
}
|
||||||
entries.append(.typeInfo(presentationData.theme, presentationData.strings.Group_Setup_TypePrivateHelp))
|
case .privateChannel:
|
||||||
} else {
|
if isGroup {
|
||||||
entries.append(.typeInfo(presentationData.theme, presentationData.strings.Channel_Setup_TypePrivateHelp))
|
entries.append(.typeInfo(presentationData.theme, presentationData.strings.Group_Setup_TypePrivateHelp))
|
||||||
}
|
} else {
|
||||||
}
|
entries.append(.typeInfo(presentationData.theme, presentationData.strings.Channel_Setup_TypePrivateHelp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch selectedType {
|
switch selectedType {
|
||||||
case .publicChannel:
|
case .publicChannel:
|
||||||
var displayAvailability = false
|
var displayAvailability = false
|
||||||
@ -596,7 +534,6 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
|
|||||||
entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp, true))
|
entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp, true))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
entries.append(.publicLinkHeader(presentationData.theme, presentationData.strings.Group_Username_Title.uppercased()))
|
|
||||||
entries.append(.editablePublicLink(presentationData.theme, currentAddressName))
|
entries.append(.editablePublicLink(presentationData.theme, currentAddressName))
|
||||||
if let status = state.addressNameValidationStatus {
|
if let status = state.addressNameValidationStatus {
|
||||||
let text: String
|
let text: String
|
||||||
@ -639,26 +576,6 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
|
|||||||
}
|
}
|
||||||
if isGroup {
|
if isGroup {
|
||||||
entries.append(.publicLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp))
|
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 {
|
} else {
|
||||||
entries.append(.publicLinkInfo(presentationData.theme, presentationData.strings.Channel_Username_CreatePublicLinkHelp))
|
entries.append(.publicLinkInfo(presentationData.theme, presentationData.strings.Channel_Username_CreatePublicLinkHelp))
|
||||||
}
|
}
|
||||||
@ -727,7 +644,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
|
|||||||
entries.append(.typePublic(presentationData.theme, presentationData.strings.Channel_Setup_TypePublic, selectedType == .publicChannel))
|
entries.append(.typePublic(presentationData.theme, presentationData.strings.Channel_Setup_TypePublic, selectedType == .publicChannel))
|
||||||
entries.append(.typePrivate(presentationData.theme, presentationData.strings.Channel_Setup_TypePrivate, selectedType == .privateChannel))
|
entries.append(.typePrivate(presentationData.theme, presentationData.strings.Channel_Setup_TypePrivate, selectedType == .privateChannel))
|
||||||
|
|
||||||
entries.append(.typeInfo(presentationData.theme, presentationData.strings.Group_Setup_TypePublicWithLocationHelp))
|
entries.append(.typeInfo(presentationData.theme, presentationData.strings.Group_Setup_TypePublicHelp))
|
||||||
|
|
||||||
switch selectedType {
|
switch selectedType {
|
||||||
case .publicChannel:
|
case .publicChannel:
|
||||||
@ -755,7 +672,6 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
|
|||||||
entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp, true))
|
entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp, true))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
entries.append(.publicLinkHeader(presentationData.theme, presentationData.strings.Group_Username_Title.uppercased()))
|
|
||||||
entries.append(.editablePublicLink(presentationData.theme, currentAddressName))
|
entries.append(.editablePublicLink(presentationData.theme, currentAddressName))
|
||||||
if let status = state.addressNameValidationStatus {
|
if let status = state.addressNameValidationStatus {
|
||||||
let text: String
|
let text: String
|
||||||
@ -789,19 +705,6 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
|
|||||||
entries.append(.publicLinkStatus(presentationData.theme, text, status))
|
entries.append(.publicLinkStatus(presentationData.theme, text, status))
|
||||||
}
|
}
|
||||||
entries.append(.publicLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp))
|
entries.append(.publicLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp))
|
||||||
|
|
||||||
entries.append(.locationHeader(presentationData.theme, presentationData.strings.Group_Location_Title.uppercased()))
|
|
||||||
if let currentEditingLocation = state.editingLocation {
|
|
||||||
if case .removed = currentEditingLocation {
|
|
||||||
entries.append(.locationSetup(presentationData.theme, presentationData.strings.Group_Location_SetLocation))
|
|
||||||
} else if case let .location(location) = currentEditingLocation {
|
|
||||||
entries.append(.location(presentationData.theme, location))
|
|
||||||
entries.append(.locationSetup(presentationData.theme, presentationData.strings.Group_Location_ChangeLocation))
|
|
||||||
entries.append(.locationRemove(presentationData.theme, presentationData.strings.Group_Location_RemoveLocation))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
entries.append(.locationSetup(presentationData.theme, presentationData.strings.Group_Location_SetLocation))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case .privateChannel:
|
case .privateChannel:
|
||||||
let link = (view.cachedData as? CachedGroupData)?.exportedInvitation?.link
|
let link = (view.cachedData as? CachedGroupData)?.exportedInvitation?.link
|
||||||
@ -902,8 +805,8 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
|
|||||||
let peersDisablingAddressNameAssignment = Promise<[Peer]?>()
|
let peersDisablingAddressNameAssignment = Promise<[Peer]?>()
|
||||||
peersDisablingAddressNameAssignment.set(.single(nil) |> then(channelAddressNameAssignmentAvailability(account: context.account, peerId: peerId.namespace == Namespaces.Peer.CloudChannel ? peerId : nil) |> mapToSignal { result -> Signal<[Peer]?, NoError> in
|
peersDisablingAddressNameAssignment.set(.single(nil) |> then(channelAddressNameAssignmentAvailability(account: context.account, peerId: peerId.namespace == Namespaces.Peer.CloudChannel ? peerId : nil) |> mapToSignal { result -> Signal<[Peer]?, NoError> in
|
||||||
if case .addressNameLimitReached = result {
|
if case .addressNameLimitReached = result {
|
||||||
return adminedPublicChannels(account: context.account)
|
return adminedPublicChannels(account: context.account, location: false)
|
||||||
|> map(Optional.init)
|
|> map(Optional.init)
|
||||||
} else {
|
} else {
|
||||||
return .single([])
|
return .single([])
|
||||||
}
|
}
|
||||||
@ -1059,60 +962,6 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
|
|||||||
presentControllerImpl?(shareController, nil)
|
presentControllerImpl?(shareController, nil)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, setLocation: {
|
|
||||||
dismissInputImpl?()
|
|
||||||
|
|
||||||
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: "")))
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
} 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, presentationCompleted: {
|
|
||||||
clearHighlightImpl?()
|
|
||||||
})
|
|
||||||
presentControllerImpl?(controller, nil)
|
|
||||||
})
|
|
||||||
}, removeLocation: {
|
|
||||||
dismissInputImpl?()
|
|
||||||
|
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
||||||
let controller = ActionSheetController(presentationTheme: presentationData.theme)
|
|
||||||
let dismissAction: () -> Void = { [weak controller] in
|
|
||||||
controller?.dismissAnimated()
|
|
||||||
}
|
|
||||||
controller.setItemGroups([
|
|
||||||
ActionSheetItemGroup(items: [
|
|
||||||
ActionSheetButtonItem(title: presentationData.strings.Group_Location_RemoveLocation, color: .destructive, action: {
|
|
||||||
dismissAction()
|
|
||||||
|
|
||||||
updateState { state in
|
|
||||||
return state.withUpdatedEditingLocation(.removed)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
]),
|
|
||||||
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
|
||||||
])
|
|
||||||
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
let peerView = context.account.viewTracker.peerView(peerId)
|
let peerView = context.account.viewTracker.peerView(peerId)
|
||||||
@ -1134,14 +983,7 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
|
|||||||
break
|
break
|
||||||
case .publicChannel:
|
case .publicChannel:
|
||||||
var hasLocation = false
|
var hasLocation = false
|
||||||
if let editingLocation = state.editingLocation {
|
if let cachedChannelData = view.cachedData as? CachedChannelData, cachedChannelData.peerGeoLocation != nil {
|
||||||
switch editingLocation {
|
|
||||||
case .location:
|
|
||||||
hasLocation = true
|
|
||||||
case .removed:
|
|
||||||
hasLocation = false
|
|
||||||
}
|
|
||||||
} else if let cachedChannelData = view.cachedData as? CachedChannelData, cachedChannelData.peerGeoLocation != nil {
|
|
||||||
hasLocation = true
|
hasLocation = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1247,16 +1089,6 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
|
|||||||
case .privateChannel:
|
case .privateChannel:
|
||||||
break
|
break
|
||||||
case .publicChannel:
|
case .publicChannel:
|
||||||
var hasLocation = false
|
|
||||||
if let editingLocation = state.editingLocation {
|
|
||||||
switch editingLocation {
|
|
||||||
case .location:
|
|
||||||
hasLocation = true
|
|
||||||
case .removed:
|
|
||||||
hasLocation = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let addressNameValidationStatus = state.addressNameValidationStatus {
|
if let addressNameValidationStatus = state.addressNameValidationStatus {
|
||||||
switch addressNameValidationStatus {
|
switch addressNameValidationStatus {
|
||||||
case .availability(.available):
|
case .availability(.available):
|
||||||
@ -1265,7 +1097,7 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
|
|||||||
doneEnabled = false
|
doneEnabled = false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
doneEnabled = !(peer.addressName?.isEmpty ?? true) || hasLocation
|
doneEnabled = !(peer.addressName?.isEmpty ?? true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4744,7 +4744,7 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
|
|||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.chatDisplayNode.dismissInput()
|
strongSelf.chatDisplayNode.dismissInput()
|
||||||
strongSelf.present(legacyLocationPickerController(context: strongSelf.context, selfPeer: selfPeer, peer: peer, sendLocation: { coordinate, venue in
|
strongSelf.present(legacyLocationPickerController(context: strongSelf.context, selfPeer: selfPeer, peer: peer, sendLocation: { coordinate, venue, _ in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -169,7 +169,15 @@ public class ComposeController: ViewController {
|
|||||||
|
|
||||||
self.contactsNode.openCreateNewChannel = { [weak self] in
|
self.contactsNode.openCreateNewChannel = { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
(strongSelf.navigationController as? NavigationController)?.pushViewController(legacyChannelIntroController(context: strongSelf.context, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings), completion: { [weak self] in
|
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
let controller = PermissionController(context: strongSelf.context, splashScreen: true)
|
||||||
|
controller.setState(.custom(icon: PermissionControllerCustomIcon(light: UIImage(bundleImageName: "Chat/Intro/ChannelIntro"), dark: nil), title: presentationData.strings.ChannelIntro_Title, subtitle: nil, text: presentationData.strings.ChannelIntro_Text, buttonTitle: presentationData.strings.ChannelIntro_CreateChannel, footerText: nil), animated: false)
|
||||||
|
controller.proceed = { [weak self] result in
|
||||||
|
if let strongSelf = self {
|
||||||
|
(strongSelf.navigationController as? NavigationController)?.replaceTopController(createChannelController(context: strongSelf.context), animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(strongSelf.navigationController as? NavigationController)?.pushViewController(controller, completion: { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true)
|
strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -855,7 +855,7 @@ final class ContactListNode: ASDisplayNode {
|
|||||||
var authorizeImpl: (() -> Void)?
|
var authorizeImpl: (() -> Void)?
|
||||||
var openPrivacyPolicyImpl: (() -> Void)?
|
var openPrivacyPolicyImpl: (() -> Void)?
|
||||||
|
|
||||||
self.authorizationNode = PermissionContentNode(theme: self.presentationData.theme, strings: self.presentationData.strings, kind: .contacts, icon: UIImage(bundleImageName: "Settings/Permissions/Contacts"), title: self.presentationData.strings.Contacts_PermissionsTitle, text: self.presentationData.strings.Contacts_PermissionsText, buttonTitle: self.presentationData.strings.Contacts_PermissionsAllow, buttonAction: {
|
self.authorizationNode = PermissionContentNode(theme: self.presentationData.theme, strings: self.presentationData.strings, kind: PermissionKind.contacts.rawValue, icon: UIImage(bundleImageName: "Settings/Permissions/Contacts"), title: self.presentationData.strings.Contacts_PermissionsTitle, text: self.presentationData.strings.Contacts_PermissionsText, buttonTitle: self.presentationData.strings.Contacts_PermissionsAllow, buttonAction: {
|
||||||
authorizeImpl?()
|
authorizeImpl?()
|
||||||
}, openPrivacyPolicy: {
|
}, openPrivacyPolicy: {
|
||||||
openPrivacyPolicyImpl?()
|
openPrivacyPolicyImpl?()
|
||||||
@ -1256,7 +1256,7 @@ final class ContactListNode: ASDisplayNode {
|
|||||||
|
|
||||||
let authorizationPreviousHidden = strongSelf.authorizationNode.isHidden
|
let authorizationPreviousHidden = strongSelf.authorizationNode.isHidden
|
||||||
strongSelf.authorizationNode.removeFromSupernode()
|
strongSelf.authorizationNode.removeFromSupernode()
|
||||||
strongSelf.authorizationNode = PermissionContentNode(theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, kind: .contacts, icon: UIImage(bundleImageName: "Settings/Permissions/Contacts"), title: strongSelf.presentationData.strings.Contacts_PermissionsTitle, text: strongSelf.presentationData.strings.Contacts_PermissionsText, buttonTitle: strongSelf.presentationData.strings.Contacts_PermissionsAllow, buttonAction: {
|
strongSelf.authorizationNode = PermissionContentNode(theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, kind: PermissionKind.contacts.rawValue, icon: UIImage(bundleImageName: "Settings/Permissions/Contacts"), title: strongSelf.presentationData.strings.Contacts_PermissionsTitle, text: strongSelf.presentationData.strings.Contacts_PermissionsText, buttonTitle: strongSelf.presentationData.strings.Contacts_PermissionsAllow, buttonAction: {
|
||||||
authorizeImpl?()
|
authorizeImpl?()
|
||||||
}, openPrivacyPolicy: {
|
}, openPrivacyPolicy: {
|
||||||
openPrivacyPolicyImpl?()
|
openPrivacyPolicyImpl?()
|
||||||
|
|||||||
@ -277,7 +277,7 @@ public class ContactsController: ViewController {
|
|||||||
presentPeersNearby()
|
presentPeersNearby()
|
||||||
default:
|
default:
|
||||||
let controller = PermissionController(context: strongSelf.context, splashScreen: false)
|
let controller = PermissionController(context: strongSelf.context, splashScreen: false)
|
||||||
controller.setState(.nearbyLocation(status: PermissionRequestStatus(accessType: status)), animated: false)
|
controller.setState(.permission(.nearbyLocation(status: PermissionRequestStatus(accessType: status))), animated: false)
|
||||||
controller.proceed = { result in
|
controller.proceed = { result in
|
||||||
if result {
|
if result {
|
||||||
presentPeersNearby()
|
presentPeersNearby()
|
||||||
|
|||||||
@ -8,12 +8,19 @@ import TelegramPresentationData
|
|||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
import LegacyComponents
|
import LegacyComponents
|
||||||
|
|
||||||
|
public enum CreateGroupMode {
|
||||||
|
case generic
|
||||||
|
case supergroup
|
||||||
|
case locatedGroup(latitude: Double, longitude: Double, address: String?)
|
||||||
|
}
|
||||||
|
|
||||||
private struct CreateGroupArguments {
|
private struct CreateGroupArguments {
|
||||||
let account: Account
|
let account: Account
|
||||||
|
|
||||||
let updateEditingName: (ItemListAvatarAndNameInfoItemName) -> Void
|
let updateEditingName: (ItemListAvatarAndNameInfoItemName) -> Void
|
||||||
let done: () -> Void
|
let done: () -> Void
|
||||||
let changeProfilePhoto: () -> Void
|
let changeProfilePhoto: () -> Void
|
||||||
|
let changeLocation: () -> Void
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum CreateGroupSection: Int32 {
|
private enum CreateGroupSection: Int32 {
|
||||||
@ -47,6 +54,8 @@ private enum CreateGroupEntry: ItemListNodeEntry {
|
|||||||
case member(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer, PeerPresence?)
|
case member(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer, PeerPresence?)
|
||||||
case locationHeader(PresentationTheme, String)
|
case locationHeader(PresentationTheme, String)
|
||||||
case location(PresentationTheme, PeerGeoLocation)
|
case location(PresentationTheme, PeerGeoLocation)
|
||||||
|
case changeLocation(PresentationTheme, String)
|
||||||
|
case locationInfo(PresentationTheme, String)
|
||||||
|
|
||||||
var section: ItemListSectionId {
|
var section: ItemListSectionId {
|
||||||
switch self {
|
switch self {
|
||||||
@ -54,7 +63,7 @@ private enum CreateGroupEntry: ItemListNodeEntry {
|
|||||||
return CreateGroupSection.info.rawValue
|
return CreateGroupSection.info.rawValue
|
||||||
case .member:
|
case .member:
|
||||||
return CreateGroupSection.members.rawValue
|
return CreateGroupSection.members.rawValue
|
||||||
case .locationHeader, .location:
|
case .locationHeader, .location, .changeLocation, .locationInfo:
|
||||||
return CreateGroupSection.location.rawValue
|
return CreateGroupSection.location.rawValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -71,6 +80,10 @@ private enum CreateGroupEntry: ItemListNodeEntry {
|
|||||||
return 10000
|
return 10000
|
||||||
case .location:
|
case .location:
|
||||||
return 10001
|
return 10001
|
||||||
|
case .changeLocation:
|
||||||
|
return 10002
|
||||||
|
case .locationInfo:
|
||||||
|
return 10003
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,6 +166,18 @@ private enum CreateGroupEntry: ItemListNodeEntry {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
case let .changeLocation(lhsTheme, lhsTitle):
|
||||||
|
if case let .changeLocation(rhsTheme, rhsTitle) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .locationInfo(lhsTheme, lhsText):
|
||||||
|
if case let .locationInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,6 +203,12 @@ private enum CreateGroupEntry: ItemListNodeEntry {
|
|||||||
case let .location(theme, location):
|
case let .location(theme, location):
|
||||||
let imageSignal = chatMapSnapshotImage(account: arguments.account, resource: MapSnapshotMediaResource(latitude: location.latitude, longitude: location.longitude, width: 90, height: 90))
|
let imageSignal = chatMapSnapshotImage(account: arguments.account, resource: MapSnapshotMediaResource(latitude: location.latitude, longitude: location.longitude, width: 90, height: 90))
|
||||||
return ItemListAddressItem(theme: theme, label: "", text: location.address.replacingOccurrences(of: ", ", with: "\n"), imageSignal: imageSignal, selected: nil, sectionId: self.section, style: .blocks, action: nil)
|
return ItemListAddressItem(theme: theme, label: "", text: location.address.replacingOccurrences(of: ", ", with: "\n"), imageSignal: imageSignal, selected: nil, sectionId: self.section, style: .blocks, action: nil)
|
||||||
|
case let .changeLocation(theme, text):
|
||||||
|
return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
||||||
|
arguments.changeLocation()
|
||||||
|
}, clearHighlightAutomatically: false)
|
||||||
|
case let .locationInfo(theme, text):
|
||||||
|
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -186,6 +217,7 @@ private struct CreateGroupState: Equatable {
|
|||||||
var creating: Bool
|
var creating: Bool
|
||||||
var editingName: ItemListAvatarAndNameInfoItemName
|
var editingName: ItemListAvatarAndNameInfoItemName
|
||||||
var avatar: ItemListAvatarAndNameInfoItemUpdatingAvatar?
|
var avatar: ItemListAvatarAndNameInfoItemUpdatingAvatar?
|
||||||
|
var location: PeerGeoLocation?
|
||||||
|
|
||||||
static func ==(lhs: CreateGroupState, rhs: CreateGroupState) -> Bool {
|
static func ==(lhs: CreateGroupState, rhs: CreateGroupState) -> Bool {
|
||||||
if lhs.creating != rhs.creating {
|
if lhs.creating != rhs.creating {
|
||||||
@ -197,12 +229,14 @@ private struct CreateGroupState: Equatable {
|
|||||||
if lhs.avatar != rhs.avatar {
|
if lhs.avatar != rhs.avatar {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.location != rhs.location {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func createGroupEntries(presentationData: PresentationData, state: CreateGroupState, peerIds: [PeerId], view: MultiplePeersView, geoLocation: PeerGeoLocation?) -> [CreateGroupEntry] {
|
private func createGroupEntries(presentationData: PresentationData, state: CreateGroupState, peerIds: [PeerId], view: MultiplePeersView) -> [CreateGroupEntry] {
|
||||||
var entries: [CreateGroupEntry] = []
|
var entries: [CreateGroupEntry] = []
|
||||||
|
|
||||||
let groupInfoState = ItemListAvatarAndNameInfoItemState(editingName: state.editingName, updatingName: nil)
|
let groupInfoState = ItemListAvatarAndNameInfoItemState(editingName: state.editingName, updatingName: nil)
|
||||||
@ -243,22 +277,23 @@ private func createGroupEntries(presentationData: PresentationData, state: Creat
|
|||||||
entries.append(.member(Int32(i), presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, peers[i], view.presences[peers[i].id]))
|
entries.append(.member(Int32(i), presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, peers[i], view.presences[peers[i].id]))
|
||||||
}
|
}
|
||||||
|
|
||||||
if let geoLocation = geoLocation {
|
if let location = state.location {
|
||||||
entries.append(.locationHeader(presentationData.theme, presentationData.strings.Group_Location_Title.uppercased()))
|
entries.append(.locationHeader(presentationData.theme, presentationData.strings.Group_Location_Title.uppercased()))
|
||||||
entries.append(.location(presentationData.theme, geoLocation))
|
entries.append(.location(presentationData.theme, location))
|
||||||
|
entries.append(.changeLocation(presentationData.theme, presentationData.strings.Group_Location_ChangeLocation))
|
||||||
|
entries.append(.locationInfo(presentationData.theme, presentationData.strings.Group_Location_Info))
|
||||||
}
|
}
|
||||||
|
|
||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum CreateGroupType {
|
public func createGroupController(context: AccountContext, peerIds: [PeerId], initialTitle: String? = nil, mode: CreateGroupMode = .generic, completion: ((PeerId, @escaping () -> Void) -> Void)? = nil) -> ViewController {
|
||||||
case generic
|
var location: PeerGeoLocation?
|
||||||
case supergroup
|
if case let .locatedGroup(latitude, longitude, address) = mode {
|
||||||
case locatedGroup(latitude: Double, longitude: Double)
|
location = PeerGeoLocation(latitude: latitude, longitude: longitude, address: address ?? "")
|
||||||
}
|
}
|
||||||
|
|
||||||
public func createGroupController(context: AccountContext, peerIds: [PeerId], initialTitle: String? = nil, type: CreateGroupType = .generic, completion: ((PeerId, @escaping () -> Void) -> Void)? = nil) -> ViewController {
|
let initialState = CreateGroupState(creating: false, editingName: .title(title: initialTitle ?? "", type: .group), avatar: nil, location: location)
|
||||||
let initialState = CreateGroupState(creating: false, editingName: .title(title: initialTitle ?? "", type: .group), avatar: nil)
|
|
||||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||||
let stateValue = Atomic(value: initialState)
|
let stateValue = Atomic(value: initialState)
|
||||||
let updateState: ((CreateGroupState) -> CreateGroupState) -> Void = { f in
|
let updateState: ((CreateGroupState) -> CreateGroupState) -> Void = { f in
|
||||||
@ -269,6 +304,7 @@ public func createGroupController(context: AccountContext, peerIds: [PeerId], in
|
|||||||
var dismissImpl: (() -> Void)?
|
var dismissImpl: (() -> Void)?
|
||||||
var presentControllerImpl: ((ViewController, Any?) -> Void)?
|
var presentControllerImpl: ((ViewController, Any?) -> Void)?
|
||||||
var endEditingImpl: (() -> Void)?
|
var endEditingImpl: (() -> Void)?
|
||||||
|
var clearHighlightImpl: (() -> Void)?
|
||||||
|
|
||||||
let actionsDisposable = DisposableSet()
|
let actionsDisposable = DisposableSet()
|
||||||
|
|
||||||
@ -276,9 +312,16 @@ public func createGroupController(context: AccountContext, peerIds: [PeerId], in
|
|||||||
|
|
||||||
let uploadedAvatar = Promise<UploadedPeerPhotoData>()
|
let uploadedAvatar = Promise<UploadedPeerPhotoData>()
|
||||||
|
|
||||||
let placemarkPromise = Promise<ReverseGeocodedPlacemark?>()
|
let addressPromise = Promise<String?>(nil)
|
||||||
if case let .locatedGroup(latitude, longitude) = type {
|
if case let .locatedGroup(latitude, longitude, address) = mode {
|
||||||
placemarkPromise.set(reverseGeocodeLocation(latitude: latitude, longitude: longitude))
|
if let address = address {
|
||||||
|
addressPromise.set(.single(address))
|
||||||
|
} else {
|
||||||
|
addressPromise.set(reverseGeocodeLocation(latitude: latitude, longitude: longitude)
|
||||||
|
|> map { placemark in
|
||||||
|
return placemark?.fullAddress ?? "\(latitude), \(longitude)"
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let arguments = CreateGroupArguments(account: context.account, updateEditingName: { editingName in
|
let arguments = CreateGroupArguments(account: context.account, updateEditingName: { editingName in
|
||||||
@ -288,8 +331,8 @@ public func createGroupController(context: AccountContext, peerIds: [PeerId], in
|
|||||||
return current
|
return current
|
||||||
}
|
}
|
||||||
}, done: {
|
}, done: {
|
||||||
let (creating, title) = stateValue.with { state -> (Bool, String) in
|
let (creating, title, location) = stateValue.with { state -> (Bool, String, PeerGeoLocation?) in
|
||||||
return (state.creating, state.editingName.composedTitle)
|
return (state.creating, state.editingName.composedTitle, state.location)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !creating && !title.isEmpty {
|
if !creating && !title.isEmpty {
|
||||||
@ -301,7 +344,7 @@ public func createGroupController(context: AccountContext, peerIds: [PeerId], in
|
|||||||
endEditingImpl?()
|
endEditingImpl?()
|
||||||
|
|
||||||
let createSignal: Signal<PeerId?, CreateGroupError>
|
let createSignal: Signal<PeerId?, CreateGroupError>
|
||||||
switch type {
|
switch mode {
|
||||||
case .generic:
|
case .generic:
|
||||||
createSignal = createGroup(account: context.account, title: title, peerIds: peerIds)
|
createSignal = createGroup(account: context.account, title: title, peerIds: peerIds)
|
||||||
case .supergroup:
|
case .supergroup:
|
||||||
@ -315,28 +358,25 @@ public func createGroupController(context: AccountContext, peerIds: [PeerId], in
|
|||||||
return .restricted
|
return .restricted
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case let .locatedGroup(latitude, longitude):
|
case .locatedGroup:
|
||||||
createSignal = createSupergroup(account: context.account, title: title, description: nil)
|
guard let location = location else {
|
||||||
|> map(Optional.init)
|
return
|
||||||
|> mapError { error -> CreateGroupError in
|
|
||||||
switch error {
|
|
||||||
case .generic:
|
|
||||||
return .generic
|
|
||||||
case .restricted:
|
|
||||||
return .restricted
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|> mapToSignal { peerId in
|
|
||||||
guard let peerId = peerId else {
|
createSignal = addressPromise.get()
|
||||||
return .single(nil)
|
|> introduceError(CreateGroupError.self)
|
||||||
|
|> mapToSignal { address -> Signal<PeerId?, CreateGroupError> in
|
||||||
|
guard let address = address else {
|
||||||
|
return .complete()
|
||||||
}
|
}
|
||||||
return placemarkPromise.get()
|
return createSupergroup(account: context.account, title: title, description: nil, location: (location.latitude, location.longitude, address))
|
||||||
|> introduceError(CreateGroupError.self)
|
|> map(Optional.init)
|
||||||
|> mapToSignal { placemark in
|
|> mapError { error -> CreateGroupError in
|
||||||
return updateChannelGeoLocation(postbox: context.account.postbox, network: context.account.network, channelId: peerId, coordinate: (latitude, longitude), address: placemark?.fullAddress ?? "\(latitude), \(longitude)")
|
switch error {
|
||||||
|> introduceError(CreateGroupError.self)
|
case .generic:
|
||||||
|> map { _ in
|
return .generic
|
||||||
return peerId
|
case .restricted:
|
||||||
|
return .restricted
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -475,10 +515,43 @@ public func createGroupController(context: AccountContext, peerIds: [PeerId], in
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}, changeLocation: {
|
||||||
|
endEditingImpl?()
|
||||||
|
|
||||||
|
let peer = TelegramChannel(id: PeerId(0), accessHash: nil, title: "", username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .group(TelegramChannelGroupInfo(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil)
|
||||||
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
let controller = legacyLocationPickerController(context: context, selfPeer: peer, peer: peer, sendLocation: { coordinate, _, address in
|
||||||
|
let addressSignal: Signal<String, NoError>
|
||||||
|
if let address = address {
|
||||||
|
addressSignal = .single(address)
|
||||||
|
} else {
|
||||||
|
addressSignal = reverseGeocodeLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
|
||||||
|
|> map { placemark in
|
||||||
|
if let placemark = placemark {
|
||||||
|
return placemark.fullAddress
|
||||||
|
} else {
|
||||||
|
return "\(coordinate.latitude), \(coordinate.longitude)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = (addressSignal
|
||||||
|
|> deliverOnMainQueue).start(next: { address in
|
||||||
|
addressPromise.set(.single(address))
|
||||||
|
updateState { current in
|
||||||
|
var current = current
|
||||||
|
current.location = PeerGeoLocation(latitude: coordinate.latitude, longitude: coordinate.longitude, address: address)
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, sendLiveLocation: { _, _ in }, theme: presentationData.theme, customLocationPicker: true, presentationCompleted: {
|
||||||
|
clearHighlightImpl?()
|
||||||
|
})
|
||||||
|
presentControllerImpl?(controller, nil)
|
||||||
})
|
})
|
||||||
|
|
||||||
let signal = combineLatest(context.sharedContext.presentationData, statePromise.get(), context.account.postbox.multiplePeersView(peerIds), .single(nil) |> then(placemarkPromise.get()))
|
let signal = combineLatest(context.sharedContext.presentationData, statePromise.get(), context.account.postbox.multiplePeersView(peerIds), .single(nil) |> then(addressPromise.get()))
|
||||||
|> map { presentationData, state, view, placemark -> (ItemListControllerState, (ItemListNodeState<CreateGroupEntry>, CreateGroupEntry.ItemGenerationArguments)) in
|
|> map { presentationData, state, view, address -> (ItemListControllerState, (ItemListNodeState<CreateGroupEntry>, CreateGroupEntry.ItemGenerationArguments)) in
|
||||||
|
|
||||||
let rightNavigationButton: ItemListNavigationButton
|
let rightNavigationButton: ItemListNavigationButton
|
||||||
if state.creating {
|
if state.creating {
|
||||||
@ -489,17 +562,8 @@ public func createGroupController(context: AccountContext, peerIds: [PeerId], in
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
var geoLocation: PeerGeoLocation?
|
|
||||||
if case let .locatedGroup(latitude, longitude) = type {
|
|
||||||
if let placemark = placemark {
|
|
||||||
geoLocation = PeerGeoLocation(latitude: latitude, longitude: longitude, address: placemark.fullAddress)
|
|
||||||
} else {
|
|
||||||
geoLocation = PeerGeoLocation(latitude: latitude, longitude: longitude, address: "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Compose_NewGroupTitle), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
|
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Compose_NewGroupTitle), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
|
||||||
let listState = ItemListNodeState(entries: createGroupEntries(presentationData: presentationData, state: state, peerIds: peerIds, view: view, geoLocation: geoLocation), style: .blocks, focusItemTag: CreateGroupEntryTag.info)
|
let listState = ItemListNodeState(entries: createGroupEntries(presentationData: presentationData, state: state, peerIds: peerIds, view: view), style: .blocks, focusItemTag: CreateGroupEntryTag.info)
|
||||||
|
|
||||||
return (controllerState, (listState, arguments))
|
return (controllerState, (listState, arguments))
|
||||||
}
|
}
|
||||||
@ -526,5 +590,8 @@ public func createGroupController(context: AccountContext, peerIds: [PeerId], in
|
|||||||
[weak controller] in
|
[weak controller] in
|
||||||
controller?.view.endEditing(true)
|
controller?.view.endEditing(true)
|
||||||
}
|
}
|
||||||
|
clearHighlightImpl = { [weak controller] in
|
||||||
|
controller?.clearItemNodesHighlight(animated: true)
|
||||||
|
}
|
||||||
return controller
|
return controller
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,9 +39,10 @@ private final class GroupInfoArguments {
|
|||||||
let openGroupTypeSetup: () -> Void
|
let openGroupTypeSetup: () -> Void
|
||||||
let openLinkedChannelSetup: () -> Void
|
let openLinkedChannelSetup: () -> Void
|
||||||
let openLocation: (PeerGeoLocation) -> Void
|
let openLocation: (PeerGeoLocation) -> Void
|
||||||
|
let changeLocation: () -> Void
|
||||||
let displayLocationContextMenu: (String) -> Void
|
let displayLocationContextMenu: (String) -> 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, displayLocationContextMenu: @escaping (String) -> 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, changeLocation: @escaping () -> Void, displayLocationContextMenu: @escaping (String) -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.avatarAndNameInfoContext = avatarAndNameInfoContext
|
self.avatarAndNameInfoContext = avatarAndNameInfoContext
|
||||||
self.tapAvatarAction = tapAvatarAction
|
self.tapAvatarAction = tapAvatarAction
|
||||||
@ -69,6 +70,7 @@ private final class GroupInfoArguments {
|
|||||||
self.openGroupTypeSetup = openGroupTypeSetup
|
self.openGroupTypeSetup = openGroupTypeSetup
|
||||||
self.openLinkedChannelSetup = openLinkedChannelSetup
|
self.openLinkedChannelSetup = openLinkedChannelSetup
|
||||||
self.openLocation = openLocation
|
self.openLocation = openLocation
|
||||||
|
self.changeLocation = changeLocation
|
||||||
self.displayLocationContextMenu = displayLocationContextMenu
|
self.displayLocationContextMenu = displayLocationContextMenu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -141,9 +143,10 @@ private enum GroupInfoEntry: ItemListNodeEntry {
|
|||||||
case info(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, peer: Peer?, cachedData: CachedPeerData?, state: ItemListAvatarAndNameInfoItemState, updatingAvatar: ItemListAvatarAndNameInfoItemUpdatingAvatar?)
|
case info(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, peer: Peer?, cachedData: CachedPeerData?, state: ItemListAvatarAndNameInfoItemState, updatingAvatar: ItemListAvatarAndNameInfoItemUpdatingAvatar?)
|
||||||
case setGroupPhoto(PresentationTheme, String)
|
case setGroupPhoto(PresentationTheme, String)
|
||||||
case groupDescriptionSetup(PresentationTheme, String, String)
|
case groupDescriptionSetup(PresentationTheme, String, String)
|
||||||
case aboutHeader(PresentationTheme, String)
|
|
||||||
case about(PresentationTheme, String)
|
case about(PresentationTheme, String)
|
||||||
|
case locationHeader(PresentationTheme, String)
|
||||||
case location(PresentationTheme, PeerGeoLocation)
|
case location(PresentationTheme, PeerGeoLocation)
|
||||||
|
case changeLocation(PresentationTheme, String)
|
||||||
case link(PresentationTheme, String)
|
case link(PresentationTheme, String)
|
||||||
case sharedMedia(PresentationTheme, String)
|
case sharedMedia(PresentationTheme, String)
|
||||||
case notifications(PresentationTheme, String, String)
|
case notifications(PresentationTheme, String, String)
|
||||||
@ -159,9 +162,9 @@ private enum GroupInfoEntry: ItemListNodeEntry {
|
|||||||
|
|
||||||
var section: ItemListSectionId {
|
var section: ItemListSectionId {
|
||||||
switch self {
|
switch self {
|
||||||
case .info, .setGroupPhoto, .groupDescriptionSetup:
|
case .info, .setGroupPhoto, .groupDescriptionSetup, .about:
|
||||||
return GroupInfoSection.info.rawValue
|
return GroupInfoSection.info.rawValue
|
||||||
case .aboutHeader, .about, .link, .location:
|
case .locationHeader, .location, .changeLocation, .link:
|
||||||
return GroupInfoSection.about.rawValue
|
return GroupInfoSection.about.rawValue
|
||||||
case .groupTypeSetup, .linkedChannelSetup, .preHistory, .stickerPack:
|
case .groupTypeSetup, .linkedChannelSetup, .preHistory, .stickerPack:
|
||||||
return GroupInfoSection.infoManagement.rawValue
|
return GroupInfoSection.infoManagement.rawValue
|
||||||
@ -237,20 +240,14 @@ private enum GroupInfoEntry: ItemListNodeEntry {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .aboutHeader(lhsTheme, lhsText):
|
|
||||||
if case let .aboutHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
case let .about(lhsTheme, lhsText):
|
case let .about(lhsTheme, lhsText):
|
||||||
if case let .about(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
if case let .about(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .link(lhsTheme, lhsText):
|
case let .locationHeader(lhsTheme, lhsText):
|
||||||
if case let .link(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
if case let .locationHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
@ -261,6 +258,18 @@ private enum GroupInfoEntry: ItemListNodeEntry {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
case let .changeLocation(lhsTheme, lhsText):
|
||||||
|
if case let .changeLocation(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .link(lhsTheme, lhsText):
|
||||||
|
if case let .link(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
case let .notifications(lhsTheme, lhsTitle, lhsText):
|
case let .notifications(lhsTheme, lhsTitle, lhsText):
|
||||||
if case let .notifications(rhsTheme, rhsTitle, rhsText) = rhs {
|
if case let .notifications(rhsTheme, rhsTitle, rhsText) = rhs {
|
||||||
if lhsTheme !== rhsTheme {
|
if lhsTheme !== rhsTheme {
|
||||||
@ -399,13 +408,15 @@ private enum GroupInfoEntry: ItemListNodeEntry {
|
|||||||
return 1
|
return 1
|
||||||
case .groupDescriptionSetup:
|
case .groupDescriptionSetup:
|
||||||
return 2
|
return 2
|
||||||
case .aboutHeader:
|
|
||||||
return 4
|
|
||||||
case .about:
|
case .about:
|
||||||
return 5
|
return 3
|
||||||
case .link:
|
case .locationHeader:
|
||||||
return 6
|
return 4
|
||||||
case .location:
|
case .location:
|
||||||
|
return 5
|
||||||
|
case .changeLocation:
|
||||||
|
return 6
|
||||||
|
case .link:
|
||||||
return 7
|
return 7
|
||||||
case .groupTypeSetup:
|
case .groupTypeSetup:
|
||||||
return 8
|
return 8
|
||||||
@ -424,11 +435,11 @@ private enum GroupInfoEntry: ItemListNodeEntry {
|
|||||||
case .administrators:
|
case .administrators:
|
||||||
return 15
|
return 15
|
||||||
case .addMember:
|
case .addMember:
|
||||||
return 17
|
return 16
|
||||||
case let .member(_, _, _, _, index, _, _, _, _, _, _, _, _, _):
|
case let .member(_, _, _, _, index, _, _, _, _, _, _, _, _, _):
|
||||||
return 20 + index
|
return 20 + index
|
||||||
case .leave:
|
case .leave:
|
||||||
return 100000 + 1
|
return 200000 + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -448,27 +459,31 @@ private enum GroupInfoEntry: ItemListNodeEntry {
|
|||||||
return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||||
arguments.changeProfilePhoto()
|
arguments.changeProfilePhoto()
|
||||||
})
|
})
|
||||||
case let .aboutHeader(theme, text):
|
|
||||||
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
|
||||||
case let .about(theme, text):
|
case let .about(theme, text):
|
||||||
return ItemListMultilineTextItem(theme: theme, text: foldMultipleLineBreaks(text), enabledEntitiyTypes: [.url, .mention, .hashtag], sectionId: self.section, style: .blocks, longTapAction: {
|
return ItemListMultilineTextItem(theme: theme, text: foldMultipleLineBreaks(text), enabledEntitiyTypes: [.url, .mention, .hashtag], sectionId: self.section, style: .blocks, longTapAction: {
|
||||||
arguments.displayAboutContextMenu(text)
|
arguments.displayAboutContextMenu(text)
|
||||||
}, linkItemAction: { action, itemLink in
|
}, linkItemAction: { action, itemLink in
|
||||||
arguments.aboutLinkAction(action, itemLink)
|
arguments.aboutLinkAction(action, itemLink)
|
||||||
}, tag: GroupInfoEntryTag.about)
|
}, tag: GroupInfoEntryTag.about)
|
||||||
case let .link(theme, url):
|
case let .locationHeader(theme, text):
|
||||||
return ItemListActionItem(theme: theme, title: url, kind: .neutral, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||||
arguments.displayUsernameShareMenu(url)
|
|
||||||
}, longTapAction: {
|
|
||||||
arguments.displayUsernameContextMenu(url)
|
|
||||||
}, tag: GroupInfoEntryTag.link)
|
|
||||||
case let .location(theme, location):
|
case let .location(theme, location):
|
||||||
let imageSignal = chatMapSnapshotImage(account: arguments.context.account, resource: MapSnapshotMediaResource(latitude: location.latitude, longitude: location.longitude, width: 90, height: 90))
|
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: {
|
return ItemListAddressItem(theme: theme, label: "", text: location.address.replacingOccurrences(of: ", ", with: "\n"), imageSignal: imageSignal, selected: nil, sectionId: self.section, style: .blocks, action: {
|
||||||
arguments.openLocation(location)
|
arguments.openLocation(location)
|
||||||
}, longTapAction: {
|
}, longTapAction: {
|
||||||
arguments.displayLocationContextMenu(location.address.replacingOccurrences(of: "\n", with: ", "))
|
arguments.displayLocationContextMenu(location.address.replacingOccurrences(of: "\n", with: ", "))
|
||||||
}, tag: GroupInfoEntryTag.location)
|
}, tag: GroupInfoEntryTag.location)
|
||||||
|
case let .changeLocation(theme, text):
|
||||||
|
return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||||
|
arguments.changeLocation()
|
||||||
|
}, clearHighlightAutomatically: false)
|
||||||
|
case let .link(theme, url):
|
||||||
|
return ItemListActionItem(theme: theme, title: url, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||||
|
arguments.displayUsernameShareMenu(url)
|
||||||
|
}, longTapAction: {
|
||||||
|
arguments.displayUsernameContextMenu(url)
|
||||||
|
}, tag: GroupInfoEntryTag.link)
|
||||||
case let .notifications(theme, title, text):
|
case let .notifications(theme, title, text):
|
||||||
return ItemListDisclosureItem(theme: theme, title: title, label: text, sectionId: self.section, style: .blocks, action: {
|
return ItemListDisclosureItem(theme: theme, title: title, label: text, sectionId: self.section, style: .blocks, action: {
|
||||||
arguments.changeNotificationMuteSettings()
|
arguments.changeNotificationMuteSettings()
|
||||||
@ -806,21 +821,33 @@ private func groupInfoEntries(account: Account, presentationData: PresentationDa
|
|||||||
entries.append(.administrators(presentationData.theme, presentationData.strings.GroupInfo_Administrators, ""))
|
entries.append(.administrators(presentationData.theme, presentationData.strings.GroupInfo_Administrators, ""))
|
||||||
}
|
}
|
||||||
} else if let channel = view.peers[view.peerId] as? TelegramChannel, let cachedChannelData = view.cachedData as? CachedChannelData {
|
} else if let channel = view.peers[view.peerId] as? TelegramChannel, let cachedChannelData = view.cachedData as? CachedChannelData {
|
||||||
if isCreator || (channel.adminRights != nil && channel.hasPermission(.pinMessages)) {
|
if isCreator, let location = cachedChannelData.peerGeoLocation {
|
||||||
if cachedChannelData.flags.contains(.canChangeUsername) {
|
entries.append(.locationHeader(presentationData.theme, presentationData.strings.GroupInfo_Location.uppercased()))
|
||||||
entries.append(GroupInfoEntry.groupTypeSetup(presentationData.theme, presentationData.strings.GroupInfo_GroupType, isPublic ? presentationData.strings.Channel_Setup_TypePublic : presentationData.strings.Channel_Setup_TypePrivate))
|
entries.append(.location(presentationData.theme, location))
|
||||||
if let linkedDiscussionPeerId = cachedChannelData.linkedDiscussionPeerId, let peer = view.peers[linkedDiscussionPeerId] {
|
if cachedChannelData.flags.contains(.canChangePeerGeoLocation) {
|
||||||
let peerTitle: String
|
entries.append(.changeLocation(presentationData.theme, presentationData.strings.Group_Location_ChangeLocation))
|
||||||
if let addressName = peer.addressName, !addressName.isEmpty {
|
|
||||||
peerTitle = "@\(addressName)"
|
|
||||||
} else {
|
|
||||||
peerTitle = peer.displayTitle
|
|
||||||
}
|
|
||||||
entries.append(GroupInfoEntry.linkedChannelSetup(presentationData.theme, presentationData.strings.Group_LinkedChannel, peerTitle))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if !isPublic && cachedChannelData.linkedDiscussionPeerId == nil {
|
}
|
||||||
entries.append(GroupInfoEntry.preHistory(presentationData.theme, presentationData.strings.GroupInfo_GroupHistory, cachedChannelData.flags.contains(.preHistoryEnabled) ? presentationData.strings.GroupInfo_GroupHistoryVisible : presentationData.strings.GroupInfo_GroupHistoryHidden))
|
|
||||||
|
if isCreator || (channel.adminRights != nil && channel.hasPermission(.pinMessages)) {
|
||||||
|
if cachedChannelData.peerGeoLocation != nil {
|
||||||
|
entries.append(GroupInfoEntry.groupTypeSetup(presentationData.theme, presentationData.strings.GroupInfo_PublicLink, channel.addressName ?? presentationData.strings.GroupInfo_PublicLinkAdd))
|
||||||
|
} else {
|
||||||
|
if cachedChannelData.flags.contains(.canChangeUsername) {
|
||||||
|
entries.append(GroupInfoEntry.groupTypeSetup(presentationData.theme, presentationData.strings.GroupInfo_GroupType, isPublic ? presentationData.strings.Channel_Setup_TypePublic : presentationData.strings.Channel_Setup_TypePrivate))
|
||||||
|
if let linkedDiscussionPeerId = cachedChannelData.linkedDiscussionPeerId, let peer = view.peers[linkedDiscussionPeerId] {
|
||||||
|
let peerTitle: String
|
||||||
|
if let addressName = peer.addressName, !addressName.isEmpty {
|
||||||
|
peerTitle = "@\(addressName)"
|
||||||
|
} else {
|
||||||
|
peerTitle = peer.displayTitle
|
||||||
|
}
|
||||||
|
entries.append(GroupInfoEntry.linkedChannelSetup(presentationData.theme, presentationData.strings.Group_LinkedChannel, peerTitle))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !isPublic && cachedChannelData.linkedDiscussionPeerId == nil {
|
||||||
|
entries.append(GroupInfoEntry.preHistory(presentationData.theme, presentationData.strings.GroupInfo_GroupHistory, cachedChannelData.flags.contains(.preHistoryEnabled) ? presentationData.strings.GroupInfo_GroupHistoryVisible : presentationData.strings.GroupInfo_GroupHistoryHidden))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -855,24 +882,23 @@ private func groupInfoEntries(account: Account, presentationData: PresentationDa
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let peer = peerViewMainPeer(view), peer.isScam {
|
if let peer = peerViewMainPeer(view), peer.isScam {
|
||||||
entries.append(.aboutHeader(presentationData.theme, presentationData.strings.Channel_About_Title.uppercased()))
|
|
||||||
entries.append(.about(presentationData.theme, presentationData.strings.GroupInfo_ScamGroupWarning))
|
entries.append(.about(presentationData.theme, presentationData.strings.GroupInfo_ScamGroupWarning))
|
||||||
}
|
}
|
||||||
else if let cachedChannelData = view.cachedData as? CachedChannelData {
|
else if let cachedChannelData = view.cachedData as? CachedChannelData {
|
||||||
if let about = cachedChannelData.about, !about.isEmpty {
|
if let about = cachedChannelData.about, !about.isEmpty {
|
||||||
entries.append(.aboutHeader(presentationData.theme, presentationData.strings.Channel_About_Title.uppercased()))
|
|
||||||
entries.append(.about(presentationData.theme, about))
|
entries.append(.about(presentationData.theme, about))
|
||||||
}
|
}
|
||||||
if let peer = view.peers[view.peerId] as? TelegramChannel, let username = peer.username, !username.isEmpty {
|
if let peer = view.peers[view.peerId] as? TelegramChannel {
|
||||||
entries.append(.link(presentationData.theme, "t.me/" + username))
|
|
||||||
|
|
||||||
if let location = cachedChannelData.peerGeoLocation {
|
if let location = cachedChannelData.peerGeoLocation {
|
||||||
|
entries.append(.locationHeader(presentationData.theme, presentationData.strings.GroupInfo_Location.uppercased()))
|
||||||
entries.append(.location(presentationData.theme, location))
|
entries.append(.location(presentationData.theme, location))
|
||||||
}
|
}
|
||||||
|
if let username = peer.username, !username.isEmpty {
|
||||||
|
entries.append(.link(presentationData.theme, "t.me/" + username))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if let cachedGroupData = view.cachedData as? CachedGroupData {
|
} else if let cachedGroupData = view.cachedData as? CachedGroupData {
|
||||||
if let about = cachedGroupData.about, !about.isEmpty {
|
if let about = cachedGroupData.about, !about.isEmpty {
|
||||||
entries.append(.aboutHeader(presentationData.theme, presentationData.strings.Channel_About_Title.uppercased()))
|
|
||||||
entries.append(.about(presentationData.theme, about))
|
entries.append(.about(presentationData.theme, about))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1239,6 +1265,7 @@ public func groupInfoController(context: AccountContext, peerId originalPeerId:
|
|||||||
var endEditingImpl: (() -> Void)?
|
var endEditingImpl: (() -> Void)?
|
||||||
var removePeerChatImpl: ((Peer, Bool) -> Void)?
|
var removePeerChatImpl: ((Peer, Bool) -> Void)?
|
||||||
var errorImpl: (() -> Void)?
|
var errorImpl: (() -> Void)?
|
||||||
|
var clearHighlightImpl: (() -> Void)?
|
||||||
|
|
||||||
let actionsDisposable = DisposableSet()
|
let actionsDisposable = DisposableSet()
|
||||||
|
|
||||||
@ -1967,6 +1994,40 @@ public func groupInfoController(context: AccountContext, peerId originalPeerId:
|
|||||||
let controller = legacyLocationController(message: nil, mapMedia: mapMedia, context: context, isModal: false, openPeer: { _ in }, sendLiveLocation: { _, _ in }, stopLiveLocation: {}, openUrl: { _ in })
|
let controller = legacyLocationController(message: nil, mapMedia: mapMedia, context: context, isModal: false, openPeer: { _ in }, sendLiveLocation: { _, _ in }, stopLiveLocation: {}, openUrl: { _ in })
|
||||||
pushControllerImpl?(controller)
|
pushControllerImpl?(controller)
|
||||||
})
|
})
|
||||||
|
}, changeLocation: {
|
||||||
|
let _ = (peerView.get()
|
||||||
|
|> take(1)
|
||||||
|
|> deliverOnMainQueue).start(next: { peerView in
|
||||||
|
guard let peer = peerView.peers[peerView.peerId] else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
let controller = legacyLocationPickerController(context: context, selfPeer: peer, peer: peer, sendLocation: { coordinate, _, address in
|
||||||
|
let addressSignal: Signal<String, NoError>
|
||||||
|
if let address = address {
|
||||||
|
addressSignal = .single(address)
|
||||||
|
} else {
|
||||||
|
addressSignal = reverseGeocodeLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
|
||||||
|
|> map { placemark in
|
||||||
|
if let placemark = placemark {
|
||||||
|
return placemark.fullAddress
|
||||||
|
} else {
|
||||||
|
return "\(coordinate.latitude), \(coordinate.longitude)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = (addressSignal
|
||||||
|
|> mapToSignal { address -> Signal<Bool, NoError> in
|
||||||
|
return updateChannelGeoLocation(postbox: context.account.postbox, network: context.account.network, channelId: peer.id, coordinate: (coordinate.latitude, coordinate.longitude), address: address)
|
||||||
|
}
|
||||||
|
|> deliverOnMainQueue).start()
|
||||||
|
}, sendLiveLocation: { _, _ in }, theme: presentationData.theme, customLocationPicker: true, presentationCompleted: {
|
||||||
|
clearHighlightImpl?()
|
||||||
|
})
|
||||||
|
presentControllerImpl?(controller, nil)
|
||||||
|
})
|
||||||
}, displayLocationContextMenu: { text in
|
}, displayLocationContextMenu: { text in
|
||||||
displayCopyContextMenuImpl?(text, .location)
|
displayCopyContextMenuImpl?(text, .location)
|
||||||
})
|
})
|
||||||
@ -2320,6 +2381,9 @@ public func groupInfoController(context: AccountContext, peerId originalPeerId:
|
|||||||
[weak controller] in
|
[weak controller] in
|
||||||
controller?.view.endEditing(true)
|
controller?.view.endEditing(true)
|
||||||
}
|
}
|
||||||
|
clearHighlightImpl = { [weak controller] in
|
||||||
|
controller?.clearItemNodesHighlight(animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
let hapticFeedback = HapticFeedback()
|
let hapticFeedback = HapticFeedback()
|
||||||
errorImpl = { [weak controller] in
|
errorImpl = { [weak controller] in
|
||||||
|
|||||||
@ -11,7 +11,7 @@ private func generateClearIcon(color: UIColor) -> UIImage? {
|
|||||||
return generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: color)
|
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, customLocationPicker: Bool = false, presentationCompleted: @escaping () -> Void = {}) -> ViewController {
|
func legacyLocationPickerController(context: AccountContext, selfPeer: Peer, peer: Peer, sendLocation: @escaping (CLLocationCoordinate2D, MapVenue?, String?) -> Void, sendLiveLocation: @escaping (CLLocationCoordinate2D, Int32) -> Void, theme: PresentationTheme, customLocationPicker: Bool = false, presentationCompleted: @escaping () -> Void = {}) -> ViewController {
|
||||||
let legacyController = LegacyController(presentation: .modal(animateIn: true), theme: theme)
|
let legacyController = LegacyController(presentation: .modal(animateIn: true), theme: theme)
|
||||||
legacyController.presentationCompleted = {
|
legacyController.presentationCompleted = {
|
||||||
presentationCompleted()
|
presentationCompleted()
|
||||||
@ -34,10 +34,10 @@ func legacyLocationPickerController(context: AccountContext, selfPeer: Peer, pee
|
|||||||
}, rootController: nil)
|
}, rootController: nil)
|
||||||
legacyController.bind(controller: navigationController)
|
legacyController.bind(controller: navigationController)
|
||||||
legacyController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
legacyController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||||
controller.locationPicked = { [weak legacyController] coordinate, venue in
|
controller.locationPicked = { [weak legacyController] coordinate, venue, address in
|
||||||
sendLocation(coordinate, venue.flatMap { venue in
|
sendLocation(coordinate, venue.flatMap { venue in
|
||||||
return MapVenue(title: venue.title, address: venue.address, provider: venue.provider, id: venue.venueId, type: venue.type)
|
return MapVenue(title: venue.title, address: venue.address, provider: venue.provider, id: venue.venueId, type: venue.type)
|
||||||
})
|
}, address)
|
||||||
legacyController?.dismiss()
|
legacyController?.dismiss()
|
||||||
}
|
}
|
||||||
controller.liveLocationStarted = { [weak legacyController] coordinate, period in
|
controller.liveLocationStarted = { [weak legacyController] coordinate, period in
|
||||||
|
|||||||
@ -37,9 +37,9 @@ private func arePeerNearbyArraysEqual(_ lhs: [PeerNearbyEntry], _ rhs: [PeerNear
|
|||||||
private final class PeersNearbyControllerArguments {
|
private final class PeersNearbyControllerArguments {
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let openChat: (Peer) -> Void
|
let openChat: (Peer) -> Void
|
||||||
let openCreateGroup: (Double, Double) -> Void
|
let openCreateGroup: (Double, Double, String?) -> Void
|
||||||
|
|
||||||
init(context: AccountContext, openChat: @escaping (Peer) -> Void, openCreateGroup: @escaping (Double, Double) -> Void) {
|
init(context: AccountContext, openChat: @escaping (Peer) -> Void, openCreateGroup: @escaping (Double, Double, String?) -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.openChat = openChat
|
self.openChat = openChat
|
||||||
self.openCreateGroup = openCreateGroup
|
self.openCreateGroup = openCreateGroup
|
||||||
@ -61,7 +61,7 @@ private enum PeersNearbyEntry: ItemListNodeEntry {
|
|||||||
case user(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PeerNearbyEntry)
|
case user(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PeerNearbyEntry)
|
||||||
|
|
||||||
case groupsHeader(PresentationTheme, String)
|
case groupsHeader(PresentationTheme, String)
|
||||||
case createGroup(PresentationTheme, String, Double?, Double?)
|
case createGroup(PresentationTheme, String, Double?, Double?, String?)
|
||||||
case group(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PeerNearbyEntry)
|
case group(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PeerNearbyEntry)
|
||||||
|
|
||||||
case channelsHeader(PresentationTheme, String)
|
case channelsHeader(PresentationTheme, String)
|
||||||
@ -135,8 +135,8 @@ private enum PeersNearbyEntry: ItemListNodeEntry {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .createGroup(lhsTheme, lhsText, lhsLatitude, lhsLongitude):
|
case let .createGroup(lhsTheme, lhsText, lhsLatitude, lhsLongitude, lhsAddress):
|
||||||
if case let .createGroup(rhsTheme, rhsText, rhsLatitude, rhsLongitude) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsLatitude == rhsLatitude && lhsLongitude == rhsLongitude {
|
if case let .createGroup(rhsTheme, rhsText, rhsLatitude, rhsLongitude, rhsAddress) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsLatitude == rhsLatitude && lhsLongitude == rhsLongitude && lhsAddress == rhsAddress {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
@ -183,7 +183,7 @@ private enum PeersNearbyEntry: ItemListNodeEntry {
|
|||||||
return PeersNearbyHeaderItem(theme: theme, text: text, sectionId: self.section)
|
return PeersNearbyHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||||
case let .usersHeader(theme, text):
|
case let .usersHeader(theme, text):
|
||||||
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||||
case let .empty(theme, text, loading):
|
case let .empty(theme, text, _):
|
||||||
return ItemListPlaceholderItem(theme: theme, text: text, sectionId: self.section, style: .blocks)
|
return ItemListPlaceholderItem(theme: theme, text: text, sectionId: self.section, style: .blocks)
|
||||||
case let .user(_, theme, strings, dateTimeFormat, nameDisplayOrder, peer):
|
case let .user(_, theme, strings, dateTimeFormat, nameDisplayOrder, peer):
|
||||||
return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, account: arguments.context.account, peer: peer.peer.0, aliasHandling: .standard, nameColor: .primary, nameStyle: .distinctBold, presence: nil, text: .text(strings.Map_DistanceAway(stringForDistance(peer.distance)).0), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: {
|
return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, account: arguments.context.account, peer: peer.peer.0, aliasHandling: .standard, nameColor: .primary, nameStyle: .distinctBold, presence: nil, text: .text(strings.Map_DistanceAway(stringForDistance(peer.distance)).0), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: {
|
||||||
@ -191,10 +191,10 @@ private enum PeersNearbyEntry: ItemListNodeEntry {
|
|||||||
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, toggleUpdated: nil, hasTopGroupInset: false, tag: nil)
|
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, toggleUpdated: nil, hasTopGroupInset: false, tag: nil)
|
||||||
case let .groupsHeader(theme, text):
|
case let .groupsHeader(theme, text):
|
||||||
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||||
case let .createGroup(theme, title, latitude, longitude):
|
case let .createGroup(theme, title, latitude, longitude, address):
|
||||||
return ItemListPeerActionItem(theme: theme, icon: PresentationResourcesItemList.createGroupIcon(theme), title: title, alwaysPlain: false, sectionId: self.section, editing: false, action: {
|
return ItemListPeerActionItem(theme: theme, icon: PresentationResourcesItemList.createGroupIcon(theme), title: title, alwaysPlain: false, sectionId: self.section, editing: false, action: {
|
||||||
if let latitude = latitude, let longitude = longitude {
|
if let latitude = latitude, let longitude = longitude {
|
||||||
arguments.openCreateGroup(latitude, longitude)
|
arguments.openCreateGroup(latitude, longitude, address)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
case let .group(_, theme, strings, dateTimeFormat, nameDisplayOrder, peer):
|
case let .group(_, theme, strings, dateTimeFormat, nameDisplayOrder, peer):
|
||||||
@ -226,20 +226,22 @@ private enum PeersNearbyEntry: ItemListNodeEntry {
|
|||||||
private struct PeersNearbyData: Equatable {
|
private struct PeersNearbyData: Equatable {
|
||||||
let latitude: Double
|
let latitude: Double
|
||||||
let longitude: Double
|
let longitude: Double
|
||||||
|
let address: String?
|
||||||
let users: [PeerNearbyEntry]
|
let users: [PeerNearbyEntry]
|
||||||
let groups: [PeerNearbyEntry]
|
let groups: [PeerNearbyEntry]
|
||||||
let channels: [PeerNearbyEntry]
|
let channels: [PeerNearbyEntry]
|
||||||
|
|
||||||
init(latitude: Double, longitude: Double, users: [PeerNearbyEntry], groups: [PeerNearbyEntry], channels: [PeerNearbyEntry]) {
|
init(latitude: Double, longitude: Double, address: String?, users: [PeerNearbyEntry], groups: [PeerNearbyEntry], channels: [PeerNearbyEntry]) {
|
||||||
self.latitude = latitude
|
self.latitude = latitude
|
||||||
self.longitude = longitude
|
self.longitude = longitude
|
||||||
|
self.address = address
|
||||||
self.users = users
|
self.users = users
|
||||||
self.groups = groups
|
self.groups = groups
|
||||||
self.channels = channels
|
self.channels = channels
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: PeersNearbyData, rhs: PeersNearbyData) -> Bool {
|
static func ==(lhs: PeersNearbyData, rhs: PeersNearbyData) -> Bool {
|
||||||
return lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude && arePeerNearbyArraysEqual(lhs.users, rhs.users) && arePeerNearbyArraysEqual(lhs.groups, rhs.groups) && arePeerNearbyArraysEqual(lhs.channels, rhs.channels)
|
return lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude && lhs.address == rhs.address && arePeerNearbyArraysEqual(lhs.users, rhs.users) && arePeerNearbyArraysEqual(lhs.groups, rhs.groups) && arePeerNearbyArraysEqual(lhs.channels, rhs.channels)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,7 +261,7 @@ private func peersNearbyControllerEntries(data: PeersNearbyData?, presentationDa
|
|||||||
}
|
}
|
||||||
|
|
||||||
entries.append(.groupsHeader(presentationData.theme, presentationData.strings.PeopleNearby_Groups.uppercased()))
|
entries.append(.groupsHeader(presentationData.theme, presentationData.strings.PeopleNearby_Groups.uppercased()))
|
||||||
entries.append(.createGroup(presentationData.theme, presentationData.strings.PeopleNearby_CreateGroup, data?.latitude, data?.longitude))
|
entries.append(.createGroup(presentationData.theme, presentationData.strings.PeopleNearby_CreateGroup, data?.latitude, data?.longitude, data?.address))
|
||||||
if let data = data, !data.groups.isEmpty {
|
if let data = data, !data.groups.isEmpty {
|
||||||
var i: Int32 = 0
|
var i: Int32 = 0
|
||||||
for group in data.groups {
|
for group in data.groups {
|
||||||
@ -282,18 +284,25 @@ private func peersNearbyControllerEntries(data: PeersNearbyData?, presentationDa
|
|||||||
|
|
||||||
public func peersNearbyController(context: AccountContext) -> ViewController {
|
public func peersNearbyController(context: AccountContext) -> ViewController {
|
||||||
var pushControllerImpl: ((ViewController) -> Void)?
|
var pushControllerImpl: ((ViewController) -> Void)?
|
||||||
var replaceTopControllerImpl: ((ViewController, Bool) -> Void)?
|
var replaceAllButRootControllerImpl: ((ViewController, Bool) -> Void)?
|
||||||
|
var replaceTopControllerImpl: ((ViewController) -> Void)?
|
||||||
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
|
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
|
||||||
var navigateToChatImpl: ((Peer) -> Void)?
|
var navigateToChatImpl: ((Peer) -> Void)?
|
||||||
|
|
||||||
let actionsDisposable = DisposableSet()
|
let actionsDisposable = DisposableSet()
|
||||||
|
|
||||||
let dataPromise = Promise<PeersNearbyData?>(nil)
|
let dataPromise = Promise<PeersNearbyData?>(nil)
|
||||||
|
let addressPromise = Promise<String?>(nil)
|
||||||
|
|
||||||
let arguments = PeersNearbyControllerArguments(context: context, openChat: { peer in
|
let arguments = PeersNearbyControllerArguments(context: context, openChat: { peer in
|
||||||
navigateToChatImpl?(peer)
|
navigateToChatImpl?(peer)
|
||||||
}, openCreateGroup: { latitude, longitude in
|
}, openCreateGroup: { latitude, longitude, address in
|
||||||
let controller = createGroupController(context: context, peerIds: [], type: .locatedGroup(latitude: latitude, longitude: longitude))
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
let controller = PermissionController(context: context, splashScreen: true)
|
||||||
|
controller.setState(.custom(icon: PermissionControllerCustomIcon(light: UIImage(bundleImageName: "Location/LocalGroupLightIcon"), dark: UIImage(bundleImageName: "Location/LocalGroupDarkIcon")), title: presentationData.strings.LocalGroup_Title, subtitle: address, text: presentationData.strings.LocalGroup_Text, buttonTitle: presentationData.strings.LocalGroup_ButtonTitle, footerText: presentationData.strings.LocalGroup_IrrelevantWarning), animated: false)
|
||||||
|
controller.proceed = { result in
|
||||||
|
replaceTopControllerImpl?(createGroupController(context: context, peerIds: [], mode: .locatedGroup(latitude: latitude, longitude: longitude, address: address)))
|
||||||
|
}
|
||||||
pushControllerImpl?(controller)
|
pushControllerImpl?(controller)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -307,9 +316,23 @@ public func peersNearbyController(context: AccountContext) -> ViewController {
|
|||||||
return Signal { subscriber in
|
return Signal { subscriber in
|
||||||
let peersNearbyContext = PeersNearbyContext(network: context.account.network, accountStateManager: context.account.stateManager, coordinate: (latitude: coordinate.latitude, longitude: coordinate.longitude))
|
let peersNearbyContext = PeersNearbyContext(network: context.account.network, accountStateManager: context.account.stateManager, coordinate: (latitude: coordinate.latitude, longitude: coordinate.longitude))
|
||||||
|
|
||||||
let peersNearby: Signal<PeersNearbyData?, Void> = peersNearbyContext.get()
|
let peersNearby: Signal<PeersNearbyData?, Void> = combineLatest(peersNearbyContext.get(), addressPromise.get())
|
||||||
|> introduceError(Void.self)
|
|> introduceError(Void.self)
|
||||||
|> mapToSignal { peersNearby -> Signal<PeersNearbyData?, Void> in
|
|> mapToSignal { peersNearby, address -> Signal<([PeerNearby]?, String?), Void> in
|
||||||
|
if let address = address {
|
||||||
|
return .single((peersNearby, address))
|
||||||
|
} else {
|
||||||
|
return reverseGeocodeLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
|
||||||
|
|> introduceError(Void.self)
|
||||||
|
|> map { placemark in
|
||||||
|
return (peersNearby, placemark?.fullAddress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> mapToSignal { peersNearby, address -> Signal<PeersNearbyData?, Void> in
|
||||||
|
guard let peersNearby = peersNearby else {
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
return context.account.postbox.transaction { transaction -> PeersNearbyData? in
|
return context.account.postbox.transaction { transaction -> PeersNearbyData? in
|
||||||
var users: [PeerNearbyEntry] = []
|
var users: [PeerNearbyEntry] = []
|
||||||
var groups: [PeerNearbyEntry] = []
|
var groups: [PeerNearbyEntry] = []
|
||||||
@ -323,7 +346,7 @@ public func peersNearbyController(context: AccountContext) -> ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return PeersNearbyData(latitude: coordinate.latitude, longitude: coordinate.longitude, users: users, groups: groups, channels: [])
|
return PeersNearbyData(latitude: coordinate.latitude, longitude: coordinate.longitude, address: address, users: users, groups: groups, channels: [])
|
||||||
}
|
}
|
||||||
|> introduceError(Void.self)
|
|> introduceError(Void.self)
|
||||||
}
|
}
|
||||||
@ -346,8 +369,10 @@ public func peersNearbyController(context: AccountContext) -> ViewController {
|
|||||||
|> restartIfError
|
|> restartIfError
|
||||||
|> `catch` { _ -> Signal<PeersNearbyData?, NoError> in
|
|> `catch` { _ -> Signal<PeersNearbyData?, NoError> in
|
||||||
return .single(nil)
|
return .single(nil)
|
||||||
|
} |> filter { value in
|
||||||
|
return value != nil
|
||||||
}
|
}
|
||||||
dataPromise.set(combinedSignal)
|
dataPromise.set(.single(nil) |> then(combinedSignal))
|
||||||
|
|
||||||
let signal = combineLatest(context.sharedContext.presentationData, dataPromise.get())
|
let signal = combineLatest(context.sharedContext.presentationData, dataPromise.get())
|
||||||
|> deliverOnMainQueue
|
|> deliverOnMainQueue
|
||||||
@ -362,11 +387,14 @@ public func peersNearbyController(context: AccountContext) -> ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let controller = ItemListController(context: context, state: signal)
|
let controller = ItemListController(context: context, state: signal)
|
||||||
|
controller.didDisappear = { [weak controller] _ in
|
||||||
|
controller?.clearItemNodesHighlight(animated: true)
|
||||||
|
}
|
||||||
navigateToChatImpl = { [weak controller] peer in
|
navigateToChatImpl = { [weak controller] peer in
|
||||||
if let navigationController = controller?.navigationController as? NavigationController {
|
if let navigationController = controller?.navigationController as? NavigationController {
|
||||||
navigateToChatController(navigationController: navigationController, context: context, chatLocation: .peer(peer.id), keepStack: .always, purposefulAction: { [weak navigationController] in
|
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 {
|
if let navigationController = navigationController, let chatController = navigationController.viewControllers.last as? ChatController {
|
||||||
replaceTopControllerImpl?(chatController, false)
|
replaceAllButRootControllerImpl?(chatController, false)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -376,11 +404,16 @@ public func peersNearbyController(context: AccountContext) -> ViewController {
|
|||||||
(controller.navigationController as? NavigationController)?.pushViewController(c, animated: true)
|
(controller.navigationController as? NavigationController)?.pushViewController(c, animated: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
replaceTopControllerImpl = { [weak controller] c, a in
|
replaceAllButRootControllerImpl = { [weak controller] c, a in
|
||||||
if let controller = controller {
|
if let controller = controller {
|
||||||
(controller.navigationController as? NavigationController)?.replaceAllButRootController(c, animated: a)
|
(controller.navigationController as? NavigationController)?.replaceAllButRootController(c, animated: a)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
replaceTopControllerImpl = { [weak controller] c in
|
||||||
|
if let controller = controller {
|
||||||
|
(controller.navigationController as? NavigationController)?.replaceTopController(c, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
presentControllerImpl = { [weak controller] c, p in
|
presentControllerImpl = { [weak controller] c, p in
|
||||||
if let controller = controller {
|
if let controller = controller {
|
||||||
controller.present(c, in: .window(.root), with: p)
|
controller.present(c, in: .window(.root), with: p)
|
||||||
|
|||||||
@ -76,7 +76,7 @@ class PeersNearbyHeaderItemNode: ListViewItemNode {
|
|||||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||||
|
|
||||||
return { item, params, neighbors in
|
return { item, params, neighbors in
|
||||||
let leftInset: CGFloat = 54.0 + params.leftInset
|
let leftInset: CGFloat = 48.0 + params.leftInset
|
||||||
let topInset: CGFloat = 92.0
|
let topInset: CGFloat = 92.0
|
||||||
|
|
||||||
let attributedText = NSAttributedString(string: item.text, font: titleFont, textColor: item.theme.list.freeTextColor)
|
let attributedText = NSAttributedString(string: item.text, font: titleFont, textColor: item.theme.list.freeTextColor)
|
||||||
|
|||||||
@ -6,13 +6,15 @@ import TelegramPresentationData
|
|||||||
|
|
||||||
final class PermissionContentNode: ASDisplayNode {
|
final class PermissionContentNode: ASDisplayNode {
|
||||||
private var theme: PresentationTheme
|
private var theme: PresentationTheme
|
||||||
let kind: PermissionKind
|
let kind: Int32
|
||||||
|
|
||||||
private let iconNode: ASImageNode
|
private let iconNode: ASImageNode
|
||||||
private let nearbyIconNode: PeersNearbyIconNode?
|
private let nearbyIconNode: PeersNearbyIconNode?
|
||||||
private let titleNode: ImmediateTextNode
|
private let titleNode: ImmediateTextNode
|
||||||
|
private let subtitleNode: ImmediateTextNode
|
||||||
private let textNode: ImmediateTextNode
|
private let textNode: ImmediateTextNode
|
||||||
private let actionButton: SolidRoundedButtonNode
|
private let actionButton: SolidRoundedButtonNode
|
||||||
|
private let footerNode: ImmediateTextNode
|
||||||
private let privacyPolicyButton: HighlightableButtonNode
|
private let privacyPolicyButton: HighlightableButtonNode
|
||||||
|
|
||||||
private var title: String
|
private var title: String
|
||||||
@ -20,7 +22,7 @@ final class PermissionContentNode: ASDisplayNode {
|
|||||||
var buttonAction: (() -> Void)?
|
var buttonAction: (() -> Void)?
|
||||||
var openPrivacyPolicy: (() -> Void)?
|
var openPrivacyPolicy: (() -> Void)?
|
||||||
|
|
||||||
init(theme: PresentationTheme, strings: PresentationStrings, kind: PermissionKind, icon: UIImage?, title: String, text: String, buttonTitle: String, buttonAction: @escaping () -> Void, openPrivacyPolicy: (() -> Void)?) {
|
init(theme: PresentationTheme, strings: PresentationStrings, kind: Int32, icon: UIImage?, title: String, subtitle: String? = nil, text: String, buttonTitle: String, footerText: String? = nil, buttonAction: @escaping () -> Void, openPrivacyPolicy: (() -> Void)?) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.kind = kind
|
self.kind = kind
|
||||||
|
|
||||||
@ -34,7 +36,7 @@ final class PermissionContentNode: ASDisplayNode {
|
|||||||
self.iconNode.displayWithoutProcessing = true
|
self.iconNode.displayWithoutProcessing = true
|
||||||
self.iconNode.displaysAsynchronously = false
|
self.iconNode.displaysAsynchronously = false
|
||||||
|
|
||||||
if kind == .nearbyLocation {
|
if kind == PermissionKind.nearbyLocation.rawValue {
|
||||||
self.nearbyIconNode = PeersNearbyIconNode(theme: theme)
|
self.nearbyIconNode = PeersNearbyIconNode(theme: theme)
|
||||||
} else {
|
} else {
|
||||||
self.nearbyIconNode = nil
|
self.nearbyIconNode = nil
|
||||||
@ -46,6 +48,12 @@ final class PermissionContentNode: ASDisplayNode {
|
|||||||
self.titleNode.isUserInteractionEnabled = false
|
self.titleNode.isUserInteractionEnabled = false
|
||||||
self.titleNode.displaysAsynchronously = false
|
self.titleNode.displaysAsynchronously = false
|
||||||
|
|
||||||
|
self.subtitleNode = ImmediateTextNode()
|
||||||
|
self.subtitleNode.maximumNumberOfLines = 1
|
||||||
|
self.subtitleNode.textAlignment = .center
|
||||||
|
self.subtitleNode.isUserInteractionEnabled = false
|
||||||
|
self.subtitleNode.displaysAsynchronously = false
|
||||||
|
|
||||||
self.textNode = ImmediateTextNode()
|
self.textNode = ImmediateTextNode()
|
||||||
self.textNode.textAlignment = .center
|
self.textNode.textAlignment = .center
|
||||||
self.textNode.maximumNumberOfLines = 0
|
self.textNode.maximumNumberOfLines = 0
|
||||||
@ -53,6 +61,11 @@ final class PermissionContentNode: ASDisplayNode {
|
|||||||
|
|
||||||
self.actionButton = SolidRoundedButtonNode(theme: theme, height: 48.0, cornerRadius: 9.0)
|
self.actionButton = SolidRoundedButtonNode(theme: theme, height: 48.0, cornerRadius: 9.0)
|
||||||
|
|
||||||
|
self.footerNode = ImmediateTextNode()
|
||||||
|
self.footerNode.textAlignment = .center
|
||||||
|
self.footerNode.maximumNumberOfLines = 0
|
||||||
|
self.footerNode.displaysAsynchronously = false
|
||||||
|
|
||||||
self.privacyPolicyButton = HighlightableButtonNode()
|
self.privacyPolicyButton = HighlightableButtonNode()
|
||||||
self.privacyPolicyButton.setTitle(strings.Permissions_PrivacyPolicy, with: Font.regular(16.0), with: theme.list.itemAccentColor, for: .normal)
|
self.privacyPolicyButton.setTitle(strings.Permissions_PrivacyPolicy, with: Font.regular(16.0), with: theme.list.itemAccentColor, for: .normal)
|
||||||
|
|
||||||
@ -68,13 +81,23 @@ final class PermissionContentNode: ASDisplayNode {
|
|||||||
self.actionButton.title = buttonTitle
|
self.actionButton.title = buttonTitle
|
||||||
self.privacyPolicyButton.isHidden = openPrivacyPolicy == nil
|
self.privacyPolicyButton.isHidden = openPrivacyPolicy == nil
|
||||||
|
|
||||||
|
if let subtitle = subtitle {
|
||||||
|
self.subtitleNode.attributedText = NSAttributedString(string: subtitle, font: Font.regular(13.0), textColor: theme.list.freeTextColor, paragraphAlignment: .center)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let footerText = footerText {
|
||||||
|
self.footerNode.attributedText = NSAttributedString(string: footerText, font: Font.regular(13.0), textColor: theme.list.freeTextColor, paragraphAlignment: .center)
|
||||||
|
}
|
||||||
|
|
||||||
self.addSubnode(self.iconNode)
|
self.addSubnode(self.iconNode)
|
||||||
if let nearbyIconNode = self.nearbyIconNode {
|
if let nearbyIconNode = self.nearbyIconNode {
|
||||||
self.addSubnode(nearbyIconNode)
|
self.addSubnode(nearbyIconNode)
|
||||||
}
|
}
|
||||||
self.addSubnode(self.titleNode)
|
self.addSubnode(self.titleNode)
|
||||||
|
self.addSubnode(self.subtitleNode)
|
||||||
self.addSubnode(self.textNode)
|
self.addSubnode(self.textNode)
|
||||||
self.addSubnode(self.actionButton)
|
self.addSubnode(self.actionButton)
|
||||||
|
self.addSubnode(self.footerNode)
|
||||||
self.addSubnode(self.privacyPolicyButton)
|
self.addSubnode(self.privacyPolicyButton)
|
||||||
|
|
||||||
self.actionButton.pressed = { [weak self] in
|
self.actionButton.pressed = { [weak self] in
|
||||||
@ -93,25 +116,33 @@ final class PermissionContentNode: ASDisplayNode {
|
|||||||
let fontSize: CGFloat
|
let fontSize: CGFloat
|
||||||
if min(size.width, size.height) > 330.0 {
|
if min(size.width, size.height) > 330.0 {
|
||||||
fontSize = 24.0
|
fontSize = 24.0
|
||||||
sidePadding = 38.0
|
sidePadding = 36.0
|
||||||
} else {
|
} else {
|
||||||
fontSize = 20.0
|
fontSize = 20.0
|
||||||
sidePadding = 20.0
|
sidePadding = 20.0
|
||||||
}
|
}
|
||||||
|
|
||||||
self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.semibold(fontSize), textColor: self.theme.list.itemPrimaryTextColor)
|
let smallerSidePadding: CGFloat = 20.0
|
||||||
|
|
||||||
|
self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.bold(fontSize), textColor: self.theme.list.itemPrimaryTextColor)
|
||||||
|
|
||||||
let titleSize = self.titleNode.updateLayout(CGSize(width: size.width - sidePadding * 2.0, height: .greatestFiniteMagnitude))
|
let titleSize = self.titleNode.updateLayout(CGSize(width: size.width - sidePadding * 2.0, height: .greatestFiniteMagnitude))
|
||||||
|
let subtitleSize = self.subtitleNode.updateLayout(CGSize(width: size.width - smallerSidePadding * 2.0, height: .greatestFiniteMagnitude))
|
||||||
let textSize = self.textNode.updateLayout(CGSize(width: size.width - sidePadding * 2.0, height: .greatestFiniteMagnitude))
|
let textSize = self.textNode.updateLayout(CGSize(width: size.width - sidePadding * 2.0, height: .greatestFiniteMagnitude))
|
||||||
let buttonWidth = min(size.width, size.height)
|
let buttonWidth = min(size.width, size.height)
|
||||||
let buttonHeight = self.actionButton.updateLayout(width: buttonWidth, transition: transition)
|
let buttonHeight = self.actionButton.updateLayout(width: buttonWidth, transition: transition)
|
||||||
|
let footerSize = self.footerNode.updateLayout(CGSize(width: size.width - smallerSidePadding * 2.0, height: .greatestFiniteMagnitude))
|
||||||
let privacyButtonSize = self.privacyPolicyButton.measure(CGSize(width: size.width - sidePadding * 2.0, height: .greatestFiniteMagnitude))
|
let privacyButtonSize = self.privacyPolicyButton.measure(CGSize(width: size.width - sidePadding * 2.0, height: .greatestFiniteMagnitude))
|
||||||
|
|
||||||
let availableHeight = floor(size.height - insets.top - insets.bottom - titleSize.height - textSize.height - buttonHeight)
|
let availableHeight = floor(size.height - insets.top - insets.bottom - titleSize.height - subtitleSize.height - textSize.height - buttonHeight)
|
||||||
|
|
||||||
let titleSubtitleSpacing: CGFloat = max(15.0, floor(availableHeight * 0.055))
|
let titleTextSpacing: CGFloat = max(15.0, floor(availableHeight * 0.045))
|
||||||
|
let titleSubtitleSpacing: CGFloat = 6.0
|
||||||
let buttonSpacing: CGFloat = max(19.0, floor(availableHeight * 0.075))
|
let buttonSpacing: CGFloat = max(19.0, floor(availableHeight * 0.075))
|
||||||
var contentHeight = titleSize.height + titleSubtitleSpacing + textSize.height + buttonHeight + buttonSpacing
|
var contentHeight = titleSize.height + titleTextSpacing + textSize.height + buttonHeight + buttonSpacing
|
||||||
|
if subtitleSize.height > 0.0 {
|
||||||
|
contentHeight += titleSubtitleSpacing + subtitleSize.height
|
||||||
|
}
|
||||||
|
|
||||||
var imageSize = CGSize()
|
var imageSize = CGSize()
|
||||||
var imageSpacing: CGFloat = 0.0
|
var imageSpacing: CGFloat = 0.0
|
||||||
@ -126,24 +157,36 @@ final class PermissionContentNode: ASDisplayNode {
|
|||||||
contentHeight += imageSize.height + imageSpacing
|
contentHeight += imageSize.height + imageSpacing
|
||||||
}
|
}
|
||||||
|
|
||||||
let privacySpacing: CGFloat = max(30.0 + privacyButtonSize.height, (availableHeight - titleSubtitleSpacing - buttonSpacing - imageSize.height - imageSpacing) / 2.0)
|
let privacySpacing: CGFloat = max(30.0 + privacyButtonSize.height, (availableHeight - titleTextSpacing - buttonSpacing - imageSize.height - imageSpacing) / 2.0)
|
||||||
|
|
||||||
let contentOrigin = insets.top + floor((size.height - insets.top - insets.bottom - contentHeight) / 2.0)
|
let contentOrigin = insets.top + floor((size.height - insets.top - insets.bottom - contentHeight) / 2.0) - availableHeight * 0.05
|
||||||
let iconFrame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: contentOrigin), size: imageSize)
|
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 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 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))
|
|
||||||
let privacyButtonFrame = CGRect(origin: CGPoint(x: floor((size.width - privacyButtonSize.width) / 2.0), y: buttonFrame.maxY + floor((privacySpacing - privacyButtonSize.height) / 2.0)), size: privacyButtonSize)
|
|
||||||
|
|
||||||
|
let subtitleFrame: CGRect
|
||||||
|
if subtitleSize.height > 0.0 {
|
||||||
|
subtitleFrame = CGRect(origin: CGPoint(x: floor((size.width - subtitleSize.width) / 2.0), y: titleFrame.maxY + titleSubtitleSpacing), size: subtitleSize)
|
||||||
|
} else {
|
||||||
|
subtitleFrame = titleFrame
|
||||||
|
}
|
||||||
|
|
||||||
|
let textFrame = CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: subtitleFrame.maxY + titleTextSpacing), size: textSize)
|
||||||
|
let buttonFrame = CGRect(origin: CGPoint(x: floor((size.width - buttonWidth) / 2.0), y: textFrame.maxY + buttonSpacing), size: CGSize(width: buttonWidth, height: buttonHeight))
|
||||||
|
|
||||||
|
let footerFrame = CGRect(origin: CGPoint(x: floor((size.width - footerSize.width) / 2.0), y: size.height - footerSize.height - insets.bottom - 8.0), size: footerSize)
|
||||||
|
|
||||||
|
let privacyButtonFrame = CGRect(origin: CGPoint(x: floor((size.width - privacyButtonSize.width) / 2.0), y: buttonFrame.maxY + floor((privacySpacing - privacyButtonSize.height) / 2.0)), size: privacyButtonSize)
|
||||||
|
|
||||||
transition.updateFrame(node: self.iconNode, frame: iconFrame)
|
transition.updateFrame(node: self.iconNode, frame: iconFrame)
|
||||||
if let nearbyIconNode = self.nearbyIconNode {
|
if let nearbyIconNode = self.nearbyIconNode {
|
||||||
transition.updateFrame(node: nearbyIconNode, frame: nearbyIconFrame)
|
transition.updateFrame(node: nearbyIconNode, frame: nearbyIconFrame)
|
||||||
}
|
}
|
||||||
transition.updateFrame(node: self.titleNode, frame: titleFrame)
|
transition.updateFrame(node: self.titleNode, frame: titleFrame)
|
||||||
|
transition.updateFrame(node: self.subtitleNode, frame: subtitleFrame)
|
||||||
transition.updateFrame(node: self.textNode, frame: textFrame)
|
transition.updateFrame(node: self.textNode, frame: textFrame)
|
||||||
transition.updateFrame(node: self.actionButton, frame: buttonFrame)
|
transition.updateFrame(node: self.actionButton, frame: buttonFrame)
|
||||||
|
transition.updateFrame(node: self.footerNode, frame: footerFrame)
|
||||||
transition.updateFrame(node: self.privacyPolicyButton, frame: privacyButtonFrame)
|
transition.updateFrame(node: self.privacyPolicyButton, frame: privacyButtonFrame)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import DeviceAccess
|
|||||||
public final class PermissionController : ViewController {
|
public final class PermissionController : ViewController {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let splitTest: PermissionUISplitTest?
|
private let splitTest: PermissionUISplitTest?
|
||||||
private var state: PermissionState?
|
private var state: PermissionControllerContent?
|
||||||
private var splashScreen = false
|
private var splashScreen = false
|
||||||
|
|
||||||
private var controllerNode: PermissionControllerNode {
|
private var controllerNode: PermissionControllerNode {
|
||||||
@ -101,95 +101,103 @@ public final class PermissionController : ViewController {
|
|||||||
self.context.sharedContext.applicationBindings.openSettings()
|
self.context.sharedContext.applicationBindings.openSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func setState(_ state: PermissionState, animated: Bool) {
|
public func setState(_ state: PermissionControllerContent, animated: Bool) {
|
||||||
guard state != self.state else {
|
guard state != self.state else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.state = state
|
self.state = state
|
||||||
switch state {
|
if case let .permission(permission) = state, let state = permission {
|
||||||
case let .contacts(status):
|
switch state {
|
||||||
self.splitTest?.addEvent(.ContactsModalRequest)
|
case let .contacts(status):
|
||||||
|
self.splitTest?.addEvent(.ContactsModalRequest)
|
||||||
self.allow = { [weak self] in
|
|
||||||
if let strongSelf = self {
|
self.allow = { [weak self] in
|
||||||
switch status {
|
if let strongSelf = self {
|
||||||
case .requestable:
|
switch status {
|
||||||
strongSelf.splitTest?.addEvent(.ContactsRequest)
|
case .requestable:
|
||||||
DeviceAccess.authorizeAccess(to: .contacts, { [weak self] result in
|
strongSelf.splitTest?.addEvent(.ContactsRequest)
|
||||||
if let strongSelf = self {
|
DeviceAccess.authorizeAccess(to: .contacts, { [weak self] result in
|
||||||
if result {
|
if let strongSelf = self {
|
||||||
strongSelf.splitTest?.addEvent(.ContactsAllowed)
|
if result {
|
||||||
} else {
|
strongSelf.splitTest?.addEvent(.ContactsAllowed)
|
||||||
strongSelf.splitTest?.addEvent(.ContactsDenied)
|
} else {
|
||||||
|
strongSelf.splitTest?.addEvent(.ContactsDenied)
|
||||||
|
}
|
||||||
|
strongSelf.proceed?(true)
|
||||||
}
|
}
|
||||||
strongSelf.proceed?(true)
|
})
|
||||||
}
|
case .denied:
|
||||||
})
|
strongSelf.openAppSettings()
|
||||||
case .denied:
|
strongSelf.proceed?(true)
|
||||||
strongSelf.openAppSettings()
|
default:
|
||||||
strongSelf.proceed?(true)
|
break
|
||||||
default:
|
}
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
case let .notifications(status):
|
||||||
case let .notifications(status):
|
self.splitTest?.addEvent(.NotificationsModalRequest)
|
||||||
self.splitTest?.addEvent(.NotificationsModalRequest)
|
|
||||||
|
self.allow = { [weak self] in
|
||||||
self.allow = { [weak self] in
|
if let strongSelf = self {
|
||||||
if let strongSelf = self {
|
switch status {
|
||||||
switch status {
|
case .requestable:
|
||||||
case .requestable:
|
strongSelf.splitTest?.addEvent(.NotificationsRequest)
|
||||||
strongSelf.splitTest?.addEvent(.NotificationsRequest)
|
let context = strongSelf.context
|
||||||
let context = strongSelf.context
|
DeviceAccess.authorizeAccess(to: .notifications, registerForNotifications: { [weak context] result in
|
||||||
DeviceAccess.authorizeAccess(to: .notifications, registerForNotifications: { [weak context] result in
|
context?.sharedContext.applicationBindings.registerForNotifications(result)
|
||||||
context?.sharedContext.applicationBindings.registerForNotifications(result)
|
}, { [weak self] result in
|
||||||
}, { [weak self] result in
|
if let strongSelf = self {
|
||||||
if let strongSelf = self {
|
if result {
|
||||||
if result {
|
strongSelf.splitTest?.addEvent(.NotificationsAllowed)
|
||||||
strongSelf.splitTest?.addEvent(.NotificationsAllowed)
|
} else {
|
||||||
} else {
|
strongSelf.splitTest?.addEvent(.NotificationsDenied)
|
||||||
strongSelf.splitTest?.addEvent(.NotificationsDenied)
|
}
|
||||||
|
strongSelf.proceed?(true)
|
||||||
}
|
}
|
||||||
strongSelf.proceed?(true)
|
})
|
||||||
}
|
case .denied, .unreachable:
|
||||||
})
|
strongSelf.openAppSettings()
|
||||||
|
strongSelf.proceed?(true)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case .siri:
|
||||||
|
self.allow = { [weak self] in
|
||||||
|
self?.proceed?(true)
|
||||||
|
}
|
||||||
|
case .cellularData:
|
||||||
|
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), presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, { [weak self] result in
|
||||||
|
self?.proceed?(result)
|
||||||
|
})
|
||||||
case .denied, .unreachable:
|
case .denied, .unreachable:
|
||||||
strongSelf.openAppSettings()
|
strongSelf.openAppSettings()
|
||||||
strongSelf.proceed?(true)
|
strongSelf.proceed?(false)
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.allow = { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.proceed?(true)
|
||||||
}
|
}
|
||||||
case .siri:
|
}
|
||||||
self.allow = { [weak self] in
|
|
||||||
self?.proceed?(true)
|
|
||||||
}
|
|
||||||
case .cellularData:
|
|
||||||
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), presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, { [weak self] result in
|
|
||||||
self?.proceed?(result)
|
|
||||||
})
|
|
||||||
case .denied, .unreachable:
|
|
||||||
strongSelf.openAppSettings()
|
|
||||||
strongSelf.proceed?(false)
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.skip = { [weak self] in
|
self.skip = { [weak self] in
|
||||||
|
|||||||
@ -6,8 +6,23 @@ import SwiftSignalKit
|
|||||||
import TelegramCore
|
import TelegramCore
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
|
|
||||||
|
public struct PermissionControllerCustomIcon: Equatable {
|
||||||
|
let light: UIImage?
|
||||||
|
let dark: UIImage?
|
||||||
|
|
||||||
|
init(light: UIImage?, dark: UIImage?) {
|
||||||
|
self.light = light
|
||||||
|
self.dark = dark
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum PermissionControllerContent: Equatable {
|
||||||
|
case permission(PermissionState?)
|
||||||
|
case custom(icon: PermissionControllerCustomIcon, title: String, subtitle: String?, text: String, buttonTitle: String, footerText: String?)
|
||||||
|
}
|
||||||
|
|
||||||
private struct PermissionControllerDataState: Equatable {
|
private struct PermissionControllerDataState: Equatable {
|
||||||
var state: PermissionState?
|
var state: PermissionControllerContent?
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct PermissionControllerLayoutState: Equatable {
|
private struct PermissionControllerLayoutState: Equatable {
|
||||||
@ -92,7 +107,7 @@ final class PermissionControllerNode: ASDisplayNode {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public func setState(_ state: PermissionState, transition: ContainedViewLayoutTransition) {
|
public func setState(_ state: PermissionControllerContent, transition: ContainedViewLayoutTransition) {
|
||||||
self.updateState({ currentState -> PermissionControllerInnerState in
|
self.updateState({ currentState -> PermissionControllerInnerState in
|
||||||
return PermissionControllerInnerState(layout: currentState.layout, data: PermissionControllerDataState(state: state))
|
return PermissionControllerInnerState(layout: currentState.layout, data: PermissionControllerDataState(state: state))
|
||||||
}, transition: transition)
|
}, transition: transition)
|
||||||
@ -112,107 +127,132 @@ final class PermissionControllerNode: ASDisplayNode {
|
|||||||
let insets = state.layout.layout.insets(options: [.statusBar])
|
let insets = state.layout.layout.insets(options: [.statusBar])
|
||||||
let contentFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: state.layout.layout.size.width, height: state.layout.layout.size.height))
|
let contentFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: state.layout.layout.size.width, height: state.layout.layout.size.height))
|
||||||
|
|
||||||
if state.data.state?.kind != self.contentNode?.kind {
|
if let state = state.data.state {
|
||||||
if let dataState = state.data.state {
|
switch state {
|
||||||
let icon: UIImage?
|
case let .permission(permission):
|
||||||
let title: String
|
if permission?.kind.rawValue != self.contentNode?.kind {
|
||||||
let text: String
|
if let dataState = permission {
|
||||||
let buttonTitle: String
|
let icon: UIImage?
|
||||||
let hasPrivacyPolicy: Bool
|
let title: String
|
||||||
|
let text: String
|
||||||
switch dataState {
|
let buttonTitle: String
|
||||||
case let .contacts(status):
|
let hasPrivacyPolicy: Bool
|
||||||
icon = UIImage(bundleImageName: "Settings/Permissions/Contacts")
|
|
||||||
if let splitTest = self.splitTest, case let .modal(titleKey, textKey, allowTitleKey, allowInSettingsTitleKey) = splitTest.configuration.contacts {
|
switch dataState {
|
||||||
title = localizedString(for: titleKey, strings: self.presentationData.strings)
|
case let .contacts(status):
|
||||||
text = localizedString(for: textKey, strings: self.presentationData.strings)
|
icon = UIImage(bundleImageName: "Settings/Permissions/Contacts")
|
||||||
if status == .denied {
|
if let splitTest = self.splitTest, case let .modal(titleKey, textKey, allowTitleKey, allowInSettingsTitleKey) = splitTest.configuration.contacts {
|
||||||
buttonTitle = localizedString(for: allowInSettingsTitleKey, strings: self.presentationData.strings)
|
title = localizedString(for: titleKey, strings: self.presentationData.strings)
|
||||||
} else {
|
text = localizedString(for: textKey, strings: self.presentationData.strings)
|
||||||
buttonTitle = localizedString(for: allowTitleKey, strings: self.presentationData.strings)
|
if status == .denied {
|
||||||
|
buttonTitle = localizedString(for: allowInSettingsTitleKey, strings: self.presentationData.strings)
|
||||||
|
} else {
|
||||||
|
buttonTitle = localizedString(for: allowTitleKey, strings: self.presentationData.strings)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
title = self.presentationData.strings.Permissions_ContactsTitle_v0
|
||||||
|
text = self.presentationData.strings.Permissions_ContactsText_v0
|
||||||
|
if status == .denied {
|
||||||
|
buttonTitle = self.presentationData.strings.Permissions_ContactsAllowInSettings_v0
|
||||||
|
} else {
|
||||||
|
buttonTitle = self.presentationData.strings.Permissions_ContactsAllow_v0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hasPrivacyPolicy = true
|
||||||
|
case let .notifications(status):
|
||||||
|
icon = UIImage(bundleImageName: "Settings/Permissions/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 {
|
||||||
|
buttonTitle = localizedString(for: allowInSettingsTitleKey, strings: self.presentationData.strings, fallback: self.presentationData.strings.Permissions_NotificationsAllowInSettings_v0)
|
||||||
|
} else {
|
||||||
|
buttonTitle = localizedString(for: allowTitleKey, strings: self.presentationData.strings, fallback: self.presentationData.strings.Permissions_NotificationsAllow_v0)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
title = self.presentationData.strings.Permissions_NotificationsTitle_v0
|
||||||
|
text = self.presentationData.strings.Permissions_NotificationsText_v0
|
||||||
|
if status == .denied {
|
||||||
|
buttonTitle = self.presentationData.strings.Permissions_NotificationsAllowInSettings_v0
|
||||||
|
} else {
|
||||||
|
buttonTitle = self.presentationData.strings.Permissions_NotificationsAllow_v0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hasPrivacyPolicy = false
|
||||||
|
case let .siri(status):
|
||||||
|
icon = UIImage(bundleImageName: "Settings/Permissions/Siri")
|
||||||
|
title = self.presentationData.strings.Permissions_SiriTitle_v0
|
||||||
|
text = self.presentationData.strings.Permissions_SiriText_v0
|
||||||
|
if status == .denied {
|
||||||
|
buttonTitle = self.presentationData.strings.Permissions_SiriAllowInSettings_v0
|
||||||
|
} else {
|
||||||
|
buttonTitle = self.presentationData.strings.Permissions_SiriAllow_v0
|
||||||
|
}
|
||||||
|
hasPrivacyPolicy = false
|
||||||
|
case .cellularData:
|
||||||
|
icon = UIImage(bundleImageName: "Settings/Permissions/CellularData")
|
||||||
|
title = self.presentationData.strings.Permissions_CellularDataTitle_v0
|
||||||
|
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
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
title = self.presentationData.strings.Permissions_ContactsTitle_v0
|
let contentNode = PermissionContentNode(theme: self.presentationData.theme, strings: self.presentationData.strings, kind: dataState.kind.rawValue, icon: icon, title: title, text: text, buttonTitle: buttonTitle, buttonAction: { [weak self] in
|
||||||
text = self.presentationData.strings.Permissions_ContactsText_v0
|
self?.allow?()
|
||||||
if status == .denied {
|
}, openPrivacyPolicy: hasPrivacyPolicy ? self.openPrivacyPolicy : nil)
|
||||||
buttonTitle = self.presentationData.strings.Permissions_ContactsAllowInSettings_v0
|
self.insertSubnode(contentNode, at: 0)
|
||||||
} else {
|
contentNode.updateLayout(size: contentFrame.size, insets: insets, transition: .immediate)
|
||||||
buttonTitle = self.presentationData.strings.Permissions_ContactsAllow_v0
|
contentNode.frame = contentFrame
|
||||||
|
if let currentContentNode = self.contentNode {
|
||||||
|
transition.updatePosition(node: currentContentNode, position: CGPoint(x: -contentFrame.size.width / 2.0, y: contentFrame.midY), completion: { [weak currentContentNode] _ in
|
||||||
|
currentContentNode?.removeFromSupernode()
|
||||||
|
})
|
||||||
|
transition.animateHorizontalOffsetAdditive(node: contentNode, offset: -contentFrame.width)
|
||||||
|
} else if transition.isAnimated {
|
||||||
|
contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
}
|
}
|
||||||
|
self.contentNode = contentNode
|
||||||
|
} else if let currentContentNode = self.contentNode {
|
||||||
|
transition.updateAlpha(node: currentContentNode, alpha: 0.0, completion: { [weak currentContentNode] _ in
|
||||||
|
currentContentNode?.removeFromSupernode()
|
||||||
|
})
|
||||||
|
self.contentNode = nil
|
||||||
}
|
}
|
||||||
hasPrivacyPolicy = true
|
} else if let contentNode = self.contentNode {
|
||||||
case let .notifications(status):
|
transition.updateFrame(node: contentNode, frame: contentFrame)
|
||||||
icon = UIImage(bundleImageName: "Settings/Permissions/Notifications")
|
contentNode.updateLayout(size: contentFrame.size, insets: insets, transition: transition)
|
||||||
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)
|
case let .custom(icon, title, subtitle, text, buttonTitle, footerText):
|
||||||
text = localizedString(for: textKey, strings: self.presentationData.strings, fallback: self.presentationData.strings.Permissions_NotificationsText_v0)
|
if let contentNode = self.contentNode {
|
||||||
if status == .denied {
|
transition.updateFrame(node: contentNode, frame: contentFrame)
|
||||||
buttonTitle = localizedString(for: allowInSettingsTitleKey, strings: self.presentationData.strings, fallback: self.presentationData.strings.Permissions_NotificationsAllowInSettings_v0)
|
contentNode.updateLayout(size: contentFrame.size, insets: insets, transition: transition)
|
||||||
} else {
|
} else {
|
||||||
buttonTitle = localizedString(for: allowTitleKey, strings: self.presentationData.strings, fallback: self.presentationData.strings.Permissions_NotificationsAllow_v0)
|
let iconImage: UIImage?
|
||||||
}
|
if self.presentationData.theme.overallDarkAppearance {
|
||||||
|
iconImage = icon.dark ?? icon.light
|
||||||
} else {
|
} else {
|
||||||
title = self.presentationData.strings.Permissions_NotificationsTitle_v0
|
iconImage = icon.light
|
||||||
text = self.presentationData.strings.Permissions_NotificationsText_v0
|
|
||||||
if status == .denied {
|
|
||||||
buttonTitle = self.presentationData.strings.Permissions_NotificationsAllowInSettings_v0
|
|
||||||
} else {
|
|
||||||
buttonTitle = self.presentationData.strings.Permissions_NotificationsAllow_v0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
hasPrivacyPolicy = false
|
|
||||||
case let .siri(status):
|
let contentNode = PermissionContentNode(theme: self.presentationData.theme, strings: self.presentationData.strings, kind: 0, icon: iconImage, title: title, subtitle: subtitle, text: text, buttonTitle: buttonTitle, footerText: footerText, buttonAction: { [weak self] in
|
||||||
icon = UIImage(bundleImageName: "Settings/Permissions/Siri")
|
self?.allow?()
|
||||||
title = self.presentationData.strings.Permissions_SiriTitle_v0
|
}, openPrivacyPolicy: nil)
|
||||||
text = self.presentationData.strings.Permissions_SiriText_v0
|
self.insertSubnode(contentNode, at: 0)
|
||||||
if status == .denied {
|
contentNode.updateLayout(size: contentFrame.size, insets: insets, transition: .immediate)
|
||||||
buttonTitle = self.presentationData.strings.Permissions_SiriAllowInSettings_v0
|
contentNode.frame = contentFrame
|
||||||
} else {
|
self.contentNode = contentNode
|
||||||
buttonTitle = self.presentationData.strings.Permissions_SiriAllow_v0
|
}
|
||||||
}
|
|
||||||
hasPrivacyPolicy = false
|
|
||||||
case .cellularData:
|
|
||||||
icon = UIImage(bundleImageName: "Settings/Permissions/CellularData")
|
|
||||||
title = self.presentationData.strings.Permissions_CellularDataTitle_v0
|
|
||||||
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
|
|
||||||
self?.allow?()
|
|
||||||
}, openPrivacyPolicy: hasPrivacyPolicy ? self.openPrivacyPolicy : nil)
|
|
||||||
self.insertSubnode(contentNode, at: 0)
|
|
||||||
contentNode.updateLayout(size: contentFrame.size, insets: insets, transition: .immediate)
|
|
||||||
contentNode.frame = contentFrame
|
|
||||||
if let currentContentNode = self.contentNode {
|
|
||||||
transition.updatePosition(node: currentContentNode, position: CGPoint(x: -contentFrame.size.width / 2.0, y: contentFrame.midY), completion: { [weak currentContentNode] _ in
|
|
||||||
currentContentNode?.removeFromSupernode()
|
|
||||||
})
|
|
||||||
transition.animateHorizontalOffsetAdditive(node: contentNode, offset: -contentFrame.width)
|
|
||||||
} else if transition.isAnimated {
|
|
||||||
contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
|
||||||
}
|
|
||||||
self.contentNode = contentNode
|
|
||||||
} else if let currentContentNode = self.contentNode {
|
|
||||||
transition.updateAlpha(node: currentContentNode, alpha: 0.0, completion: { [weak currentContentNode] _ in
|
|
||||||
currentContentNode?.removeFromSupernode()
|
|
||||||
})
|
|
||||||
self.contentNode = nil
|
|
||||||
}
|
}
|
||||||
} else if let contentNode = self.contentNode {
|
|
||||||
transition.updateFrame(node: contentNode, frame: contentFrame)
|
|
||||||
contentNode.updateLayout(size: contentFrame.size, insets: insets, transition: transition)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -110,7 +110,7 @@ struct PresentationResourcesItemList {
|
|||||||
|
|
||||||
static func createGroupIcon(_ theme: PresentationTheme) -> UIImage? {
|
static func createGroupIcon(_ theme: PresentationTheme) -> UIImage? {
|
||||||
return theme.image(PresentationResourceKey.itemListCreateGroupIcon.rawValue, { theme in
|
return theme.image(PresentationResourceKey.itemListCreateGroupIcon.rawValue, { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Contact List/CreateGroupActionIcon"), color: theme.list.itemAccentColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Location/CreateGroupIcon"), color: theme.list.itemAccentColor)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@ -1,7 +0,0 @@
|
|||||||
#import <Foundation/Foundation.h>
|
|
||||||
|
|
||||||
@interface TGEmojiSuggestions : NSObject
|
|
||||||
|
|
||||||
+ (NSArray *)suggestionsForQuery:(NSString *)query;
|
|
||||||
|
|
||||||
@end
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
#import "TGEmojiSuggestions.h"
|
|
||||||
|
|
||||||
#import "emoji_suggestions.h"
|
|
||||||
|
|
||||||
#import <LegacyComponents/TGAlphacode.h>
|
|
||||||
|
|
||||||
std::vector<Ui::Emoji::utf16char> convertToUtf16(NSString *string) {
|
|
||||||
auto cf = (__bridge CFStringRef)string;
|
|
||||||
auto range = CFRangeMake(0, CFStringGetLength(cf));
|
|
||||||
auto bufferLength = CFIndex(0);
|
|
||||||
CFStringGetBytes(cf, range, kCFStringEncodingUTF16LE, 0, FALSE, nullptr, 0, &bufferLength);
|
|
||||||
if (!bufferLength) {
|
|
||||||
return std::vector<Ui::Emoji::utf16char>();
|
|
||||||
}
|
|
||||||
auto result = std::vector<Ui::Emoji::utf16char>(bufferLength / 2 + 1, 0);
|
|
||||||
CFStringGetBytes(cf, range, kCFStringEncodingUTF16LE, 0, FALSE, reinterpret_cast<UInt8*>(result.data()), result.size() * 2, &bufferLength);
|
|
||||||
result.resize(bufferLength / 2);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString *convertFromUtf16(Ui::Emoji::utf16string string) {
|
|
||||||
auto result = CFStringCreateWithBytes(nullptr, reinterpret_cast<const UInt8*>(string.data()), string.size() * 2, kCFStringEncodingUTF16LE, false);
|
|
||||||
return (__bridge NSString*)result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void test() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@implementation TGEmojiSuggestions
|
|
||||||
|
|
||||||
+ (NSArray *)suggestionsForQuery:(NSString *)queryText {
|
|
||||||
auto query = convertToUtf16(queryText);
|
|
||||||
auto values = Ui::Emoji::GetSuggestions(Ui::Emoji::utf16string(query.data(), query.size()));
|
|
||||||
|
|
||||||
NSMutableArray *array = [[NSMutableArray alloc] init];
|
|
||||||
|
|
||||||
for (auto &item : values) {
|
|
||||||
NSString *emoji = convertFromUtf16(item.emoji());
|
|
||||||
NSString *label = convertFromUtf16(item.label());
|
|
||||||
NSString *replacement = convertFromUtf16(item.replacement());
|
|
||||||
|
|
||||||
[array addObject:[[TGAlphacodeEntry alloc] initWithEmoji:emoji code:replacement]];
|
|
||||||
}
|
|
||||||
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
@ -12,7 +12,6 @@ module TelegramUIPrivateModule {
|
|||||||
header "../DeviceProximityManager.h"
|
header "../DeviceProximityManager.h"
|
||||||
header "../RaiseToListenActivator.h"
|
header "../RaiseToListenActivator.h"
|
||||||
header "../TGMimeTypeMap.h"
|
header "../TGMimeTypeMap.h"
|
||||||
header "../TGEmojiSuggestions.h"
|
|
||||||
header "../TGChannelIntroController.h"
|
header "../TGChannelIntroController.h"
|
||||||
header "../Bridge Audio/TGBridgeAudioDecoder.h"
|
header "../Bridge Audio/TGBridgeAudioDecoder.h"
|
||||||
header "../Bridge Audio/TGBridgeAudioEncoder.h"
|
header "../Bridge Audio/TGBridgeAudioEncoder.h"
|
||||||
|
|||||||
@ -0,0 +1,325 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import SwiftSignalKit
|
||||||
|
import TelegramCore
|
||||||
|
import TelegramPresentationData
|
||||||
|
|
||||||
|
private func generateBorderImage(theme: PresentationTheme, bordered: Bool, selected: Bool) -> UIImage? {
|
||||||
|
return generateImage(CGSize(width: 30.0, height: 30.0), rotatedContext: { size, context in
|
||||||
|
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||||
|
context.setFillColor(theme.list.itemBlocksBackgroundColor.cgColor)
|
||||||
|
context.fill(bounds)
|
||||||
|
|
||||||
|
context.setBlendMode(.clear)
|
||||||
|
context.fillEllipse(in: bounds)
|
||||||
|
context.setBlendMode(.normal)
|
||||||
|
|
||||||
|
let lineWidth: CGFloat
|
||||||
|
if selected {
|
||||||
|
var accentColor = theme.list.itemAccentColor
|
||||||
|
if accentColor.rgb == UIColor.white.rgb {
|
||||||
|
accentColor = UIColor(rgb: 0x999999)
|
||||||
|
}
|
||||||
|
context.setStrokeColor(accentColor.cgColor)
|
||||||
|
lineWidth = 2.0
|
||||||
|
} else {
|
||||||
|
context.setStrokeColor(theme.list.disclosureArrowColor.withAlphaComponent(0.4).cgColor)
|
||||||
|
lineWidth = 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
if bordered || selected {
|
||||||
|
context.setLineWidth(lineWidth)
|
||||||
|
context.strokeEllipse(in: bounds.insetBy(dx: lineWidth / 2.0, dy: lineWidth / 2.0))
|
||||||
|
}
|
||||||
|
})?.stretchableImage(withLeftCapWidth: 15, topCapHeight: 15)
|
||||||
|
}
|
||||||
|
|
||||||
|
class ThemeSettingsAccentColorItem: ListViewItem, ItemListItem {
|
||||||
|
var sectionId: ItemListSectionId
|
||||||
|
|
||||||
|
let theme: PresentationTheme
|
||||||
|
let strings: PresentationStrings
|
||||||
|
let colors: [UIColor]
|
||||||
|
let currentColor: UIColor
|
||||||
|
let updated: (UIColor) -> Void
|
||||||
|
let tag: ItemListItemTag?
|
||||||
|
|
||||||
|
init(theme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId, colors: [UIColor], currentColor: UIColor, updated: @escaping (UIColor) -> Void, tag: ItemListItemTag? = nil) {
|
||||||
|
self.theme = theme
|
||||||
|
self.strings = strings
|
||||||
|
self.colors = colors
|
||||||
|
self.currentColor = currentColor
|
||||||
|
self.updated = updated
|
||||||
|
self.tag = tag
|
||||||
|
self.sectionId = sectionId
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = ThemeSettingsAccentColorItemNode()
|
||||||
|
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||||
|
|
||||||
|
node.contentSize = layout.contentSize
|
||||||
|
node.insets = layout.insets
|
||||||
|
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
completion(node, {
|
||||||
|
return (nil, { _ in apply() })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
if let nodeValue = node() as? ThemeSettingsAccentColorItemNode {
|
||||||
|
let makeLayout = nodeValue.asyncLayout()
|
||||||
|
|
||||||
|
async {
|
||||||
|
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
completion(layout, { _ in
|
||||||
|
apply()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class ThemeSettingsAccentColorNode : ASDisplayNode {
|
||||||
|
private let iconNode: ASImageNode
|
||||||
|
private let overlayNode: ASImageNode
|
||||||
|
private let textNode: ASTextNode
|
||||||
|
private var action: (() -> Void)?
|
||||||
|
|
||||||
|
override init() {
|
||||||
|
self.iconNode = ASImageNode()
|
||||||
|
self.iconNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 62.0, height: 62.0))
|
||||||
|
self.iconNode.isLayerBacked = true
|
||||||
|
|
||||||
|
self.overlayNode = ASImageNode()
|
||||||
|
self.overlayNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 62.0, height: 62.0))
|
||||||
|
self.overlayNode.isLayerBacked = true
|
||||||
|
|
||||||
|
self.textNode = ASTextNode()
|
||||||
|
self.textNode.isUserInteractionEnabled = false
|
||||||
|
self.textNode.displaysAsynchronously = true
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.addSubnode(self.iconNode)
|
||||||
|
self.addSubnode(self.overlayNode)
|
||||||
|
self.addSubnode(self.textNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setup(theme: PresentationTheme, icon: UIImage, title: NSAttributedString, bordered: Bool, selected: Bool, action: @escaping () -> Void) {
|
||||||
|
self.iconNode.image = icon
|
||||||
|
self.textNode.attributedText = title
|
||||||
|
self.overlayNode.image = generateBorderImage(theme: theme, bordered: bordered, selected: selected)
|
||||||
|
self.action = {
|
||||||
|
action()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didLoad() {
|
||||||
|
super.didLoad()
|
||||||
|
|
||||||
|
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||||
|
if case .ended = recognizer.state {
|
||||||
|
self.action?()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func layout() {
|
||||||
|
super.layout()
|
||||||
|
|
||||||
|
let bounds = self.bounds
|
||||||
|
|
||||||
|
self.iconNode.frame = CGRect(origin: CGPoint(x: 10.0, y: 14.0), size: CGSize(width: 62.0, height: 62.0))
|
||||||
|
self.overlayNode.frame = CGRect(origin: CGPoint(x: 10.0, y: 14.0), size: CGSize(width: 62.0, height: 62.0))
|
||||||
|
self.textNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 14.0 + 60.0 + 4.0 + 9.0), size: CGSize(width: bounds.size.width, height: 16.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private let textFont = Font.regular(11.0)
|
||||||
|
private let itemSize = Font.regular(11.0)
|
||||||
|
|
||||||
|
class ThemeSettingsAccentColorItemNode: ListViewItemNode, ItemListItemNode {
|
||||||
|
private let backgroundNode: ASDisplayNode
|
||||||
|
private let topStripeNode: ASDisplayNode
|
||||||
|
private let bottomStripeNode: ASDisplayNode
|
||||||
|
|
||||||
|
private let scrollNode: ASScrollNode
|
||||||
|
private var nodes: [ThemeSettingsAccentColorNode] = []
|
||||||
|
|
||||||
|
private var item: ThemeSettingsAccentColorItem?
|
||||||
|
private var layoutParams: ListViewItemLayoutParams?
|
||||||
|
|
||||||
|
var tag: ItemListItemTag? {
|
||||||
|
return self.item?.tag
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
self.backgroundNode = ASDisplayNode()
|
||||||
|
self.backgroundNode.isLayerBacked = true
|
||||||
|
|
||||||
|
self.topStripeNode = ASDisplayNode()
|
||||||
|
self.topStripeNode.isLayerBacked = true
|
||||||
|
|
||||||
|
self.bottomStripeNode = ASDisplayNode()
|
||||||
|
self.bottomStripeNode.isLayerBacked = true
|
||||||
|
|
||||||
|
self.scrollNode = ASScrollNode()
|
||||||
|
|
||||||
|
super.init(layerBacked: false, dynamicBounce: false)
|
||||||
|
|
||||||
|
self.addSubnode(self.scrollNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didLoad() {
|
||||||
|
super.didLoad()
|
||||||
|
self.scrollNode.view.disablesInteractiveTransitionGestureRecognizer = true
|
||||||
|
self.scrollNode.view.showsHorizontalScrollIndicator = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func asyncLayout() -> (_ item: ThemeSettingsAccentColorItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||||
|
let currentItem = self.item
|
||||||
|
|
||||||
|
return { item, params, neighbors in
|
||||||
|
var themeUpdated = false
|
||||||
|
if currentItem?.theme !== item.theme {
|
||||||
|
themeUpdated = true
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
let contentSize: CGSize
|
||||||
|
let insets: UIEdgeInsets
|
||||||
|
let separatorHeight = UIScreenPixel
|
||||||
|
|
||||||
|
contentSize = CGSize(width: params.width, height: 116.0)
|
||||||
|
insets = itemListNeighborsGroupedInsets(neighbors)
|
||||||
|
|
||||||
|
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||||
|
let layoutSize = layout.size
|
||||||
|
|
||||||
|
return (layout, { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.item = item
|
||||||
|
strongSelf.layoutParams = params
|
||||||
|
|
||||||
|
strongSelf.scrollNode.view.contentInset = UIEdgeInsetsMake(0.0, params.leftInset, 0.0, params.rightInset)
|
||||||
|
strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor
|
||||||
|
strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||||
|
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||||
|
|
||||||
|
if strongSelf.backgroundNode.supernode == nil {
|
||||||
|
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
||||||
|
}
|
||||||
|
if strongSelf.topStripeNode.supernode == nil {
|
||||||
|
strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1)
|
||||||
|
}
|
||||||
|
if strongSelf.bottomStripeNode.supernode == nil {
|
||||||
|
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
|
||||||
|
}
|
||||||
|
switch neighbors.top {
|
||||||
|
case .sameSection(false):
|
||||||
|
strongSelf.topStripeNode.isHidden = true
|
||||||
|
default:
|
||||||
|
strongSelf.topStripeNode.isHidden = false
|
||||||
|
}
|
||||||
|
let bottomStripeInset: CGFloat
|
||||||
|
let bottomStripeOffset: CGFloat
|
||||||
|
switch neighbors.bottom {
|
||||||
|
case .sameSection(false):
|
||||||
|
bottomStripeInset = params.leftInset + 16.0
|
||||||
|
bottomStripeOffset = -separatorHeight
|
||||||
|
default:
|
||||||
|
bottomStripeInset = 0.0
|
||||||
|
bottomStripeOffset = 0.0
|
||||||
|
}
|
||||||
|
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||||
|
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
|
||||||
|
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))
|
||||||
|
|
||||||
|
strongSelf.scrollNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 2.0), size: CGSize(width: layoutSize.width, height: layoutSize.height))
|
||||||
|
|
||||||
|
let nodeInset: CGFloat = 4.0
|
||||||
|
let nodeSize = CGSize(width: 80.0, height: 112.0)
|
||||||
|
var nodeOffset = nodeInset
|
||||||
|
|
||||||
|
var i = 0
|
||||||
|
for icon in item.colors {
|
||||||
|
let imageNode: ThemeSettingsAccentColorNode
|
||||||
|
if strongSelf.nodes.count > i {
|
||||||
|
imageNode = strongSelf.nodes[i]
|
||||||
|
} else {
|
||||||
|
imageNode = ThemeSettingsAccentColorNode()
|
||||||
|
strongSelf.nodes.append(imageNode)
|
||||||
|
strongSelf.scrollNode.addSubnode(imageNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if let image = UIImage(named: icon.imageName, in: Bundle.main, compatibleWith: nil) {
|
||||||
|
// let selected = icon.name == item.currentIconName
|
||||||
|
//
|
||||||
|
// var name = "Icon"
|
||||||
|
// var bordered = true
|
||||||
|
// switch icon.name {
|
||||||
|
// case "Blue":
|
||||||
|
// name = item.strings.Appearance_AppIconDefault
|
||||||
|
// case "Black":
|
||||||
|
// name = item.strings.Appearance_AppIconDefaultX
|
||||||
|
// case "BlueClassic":
|
||||||
|
// name = item.strings.Appearance_AppIconClassic
|
||||||
|
// case "BlackClassic":
|
||||||
|
// name = item.strings.Appearance_AppIconClassicX
|
||||||
|
// case "BlueFilled":
|
||||||
|
// name = item.strings.Appearance_AppIconFilled
|
||||||
|
// bordered = false
|
||||||
|
// case "BlackFilled":
|
||||||
|
// name = item.strings.Appearance_AppIconFilledX
|
||||||
|
// bordered = false
|
||||||
|
// case "WhiteFilled":
|
||||||
|
// name = "⍺ White"
|
||||||
|
// default:
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// imageNode.setup(theme: item.theme, icon: image, title: NSAttributedString(string: name, font: textFont, textColor: selected ? item.theme.list.itemAccentColor : item.theme.list.itemPrimaryTextColor, paragraphAlignment: .center), bordered: bordered, selected: selected, action: {
|
||||||
|
// item.updated(icon.name)
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
imageNode.frame = CGRect(origin: CGPoint(x: nodeOffset, y: 0.0), size: nodeSize)
|
||||||
|
nodeOffset += nodeSize.width + 15.0
|
||||||
|
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if let lastNode = strongSelf.nodes.last {
|
||||||
|
let contentSize = CGSize(width: lastNode.frame.maxX + nodeInset, height: strongSelf.scrollNode.frame.height)
|
||||||
|
if strongSelf.scrollNode.view.contentSize != contentSize {
|
||||||
|
strongSelf.scrollNode.view.contentSize = contentSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
|
||||||
|
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||||
|
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -294,6 +294,8 @@ private func themeSettingsControllerEntries(presentationData: PresentationData,
|
|||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private let themeColors = [UIColor(rgb: 0x007aff), UIColor(rgb: 0x70bb23), UIColor(rgb: 0xeb6ca4), UIColor(rgb: 0xf08200), UIColor(rgb: 0x9472ee), UIColor(rgb: 0xd33213), UIColor(rgb: 0xedb400), UIColor(rgb: 0x6d839e), UIColor(rgb: 0x000000)]
|
||||||
|
|
||||||
public func themeSettingsController(context: AccountContext, focusOnItemTag: ThemeSettingsEntryTag? = nil) -> ViewController {
|
public func themeSettingsController(context: AccountContext, focusOnItemTag: ThemeSettingsEntryTag? = nil) -> ViewController {
|
||||||
var pushControllerImpl: ((ViewController) -> Void)?
|
var pushControllerImpl: ((ViewController) -> Void)?
|
||||||
var presentControllerImpl: ((ViewController) -> Void)?
|
var presentControllerImpl: ((ViewController) -> Void)?
|
||||||
|
|||||||
@ -1,432 +0,0 @@
|
|||||||
/*
|
|
||||||
This file is part of Telegram Desktop,
|
|
||||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
|
||||||
|
|
||||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
It is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
In addition, as a special exception, the copyright holders give permission
|
|
||||||
to link the code of portions of this program with the OpenSSL library.
|
|
||||||
|
|
||||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
|
||||||
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|
||||||
*/
|
|
||||||
#include "emoji_suggestions.h"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include "emoji_suggestions_data.h"
|
|
||||||
|
|
||||||
#ifndef Expects
|
|
||||||
#include <cassert>
|
|
||||||
#define Expects(condition) assert(condition)
|
|
||||||
#endif // Expects
|
|
||||||
|
|
||||||
namespace Ui {
|
|
||||||
namespace Emoji {
|
|
||||||
namespace internal {
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
checksum Crc32Table[256];
|
|
||||||
class Crc32Initializer {
|
|
||||||
public:
|
|
||||||
Crc32Initializer() {
|
|
||||||
checksum poly = 0x04C11DB7U;
|
|
||||||
for (auto i = 0; i != 256; ++i) {
|
|
||||||
Crc32Table[i] = reflect(i, 8) << 24;
|
|
||||||
for (auto j = 0; j != 8; ++j) {
|
|
||||||
Crc32Table[i] = (Crc32Table[i] << 1) ^ (Crc32Table[i] & (1 << 31) ? poly : 0);
|
|
||||||
}
|
|
||||||
Crc32Table[i] = reflect(Crc32Table[i], 32);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
checksum reflect(checksum val, char ch) {
|
|
||||||
checksum result = 0;
|
|
||||||
for (int i = 1; i < (ch + 1); ++i) {
|
|
||||||
if (val & 1) {
|
|
||||||
result |= 1 << (ch - i);
|
|
||||||
}
|
|
||||||
val >>= 1;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
checksum countChecksum(const void *data, std::size_t size) {
|
|
||||||
static Crc32Initializer InitTable;
|
|
||||||
|
|
||||||
auto buffer = static_cast<const unsigned char*>(data);
|
|
||||||
auto result = checksum(0xFFFFFFFFU);
|
|
||||||
for (auto i = std::size_t(0); i != size; ++i) {
|
|
||||||
result = (result >> 8) ^ Crc32Table[(result & 0xFFU) ^ buffer[i]];
|
|
||||||
}
|
|
||||||
return (result ^ 0xFFFFFFFFU);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace internal
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
class string_span {
|
|
||||||
public:
|
|
||||||
string_span() = default;
|
|
||||||
string_span(const utf16string *data, std::size_t size) : begin_(data), size_(size) {
|
|
||||||
}
|
|
||||||
string_span(const std::vector<utf16string> &data) : begin_(data.data()), size_(data.size()) {
|
|
||||||
}
|
|
||||||
string_span(const string_span &other) = default;
|
|
||||||
string_span &operator=(const string_span &other) = default;
|
|
||||||
|
|
||||||
const utf16string *begin() const {
|
|
||||||
return begin_;
|
|
||||||
}
|
|
||||||
const utf16string *end() const {
|
|
||||||
return begin_ + size_;
|
|
||||||
}
|
|
||||||
std::size_t size() const {
|
|
||||||
return size_;
|
|
||||||
}
|
|
||||||
|
|
||||||
string_span subspan(std::size_t offset, std::size_t size) {
|
|
||||||
return string_span(begin_ + offset, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
const utf16string *begin_ = nullptr;
|
|
||||||
std::size_t size_ = 0;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
bool IsNumber(utf16char ch) {
|
|
||||||
return (ch >= '0' && ch <= '9');
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsLetterOrNumber(utf16char ch) {
|
|
||||||
return (ch >= 'a' && ch <= 'z') || IsNumber(ch);
|
|
||||||
}
|
|
||||||
|
|
||||||
using Replacement = internal::Replacement;
|
|
||||||
|
|
||||||
class Completer {
|
|
||||||
public:
|
|
||||||
Completer(utf16string query);
|
|
||||||
|
|
||||||
std::vector<Suggestion> resolve();
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct Result {
|
|
||||||
const Replacement *replacement;
|
|
||||||
int wordsUsed;
|
|
||||||
};
|
|
||||||
|
|
||||||
static std::vector<utf16char> NormalizeQuery(utf16string query);
|
|
||||||
void addResult(const Replacement *replacement);
|
|
||||||
bool isDuplicateOfLastResult(const Replacement *replacement) const;
|
|
||||||
bool isBetterThanLastResult(const Replacement *replacement) const;
|
|
||||||
void processInitialList();
|
|
||||||
void filterInitialList();
|
|
||||||
void initWordsTracking();
|
|
||||||
bool matchQueryForCurrentItem();
|
|
||||||
bool matchQueryTailStartingFrom(int position);
|
|
||||||
string_span findWordsStartingWith(utf16char ch);
|
|
||||||
int findEqualCharsCount(int position, const utf16string *word);
|
|
||||||
std::vector<Suggestion> prepareResult();
|
|
||||||
bool startsWithQuery(utf16string word);
|
|
||||||
bool isExactMatch(utf16string replacement);
|
|
||||||
|
|
||||||
std::vector<Result> _result;
|
|
||||||
|
|
||||||
utf16string _initialQuery;
|
|
||||||
const std::vector<utf16char> _query;
|
|
||||||
const utf16char *_queryBegin = nullptr;
|
|
||||||
int _querySize = 0;
|
|
||||||
|
|
||||||
const std::vector<const Replacement*> *_initialList = nullptr;
|
|
||||||
|
|
||||||
string_span _currentItemWords;
|
|
||||||
int _currentItemWordsUsedCount = 0;
|
|
||||||
|
|
||||||
class UsedWordGuard {
|
|
||||||
public:
|
|
||||||
UsedWordGuard(std::vector<small> &map, int index);
|
|
||||||
UsedWordGuard(const UsedWordGuard &other) = delete;
|
|
||||||
UsedWordGuard(UsedWordGuard &&other);
|
|
||||||
UsedWordGuard &operator=(const UsedWordGuard &other) = delete;
|
|
||||||
UsedWordGuard &operator=(UsedWordGuard &&other) = delete;
|
|
||||||
explicit operator bool() const;
|
|
||||||
~UsedWordGuard();
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::vector<small> &_map;
|
|
||||||
int _index = 0;
|
|
||||||
bool _guarded = false;
|
|
||||||
|
|
||||||
};
|
|
||||||
std::vector<small> _currentItemWordsUsedMap;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
Completer::UsedWordGuard::UsedWordGuard(std::vector<small> &map, int index) : _map(map), _index(index) {
|
|
||||||
Expects(_map.size() > _index);
|
|
||||||
if (!_map[_index]) {
|
|
||||||
_guarded = _map[_index] = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Completer::UsedWordGuard::UsedWordGuard(UsedWordGuard &&other) : _map(other._map), _index(other._index), _guarded(other._guarded) {
|
|
||||||
other._guarded = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
Completer::UsedWordGuard::operator bool() const {
|
|
||||||
return _guarded;
|
|
||||||
}
|
|
||||||
|
|
||||||
Completer::UsedWordGuard::~UsedWordGuard() {
|
|
||||||
if (_guarded) {
|
|
||||||
_map[_index] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Completer::Completer(utf16string query) : _initialQuery(query), _query(NormalizeQuery(query)) {
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove all non-letters-or-numbers.
|
|
||||||
// Leave '-' and '+' only if they're followed by a number or
|
|
||||||
// at the end of the query (so it is possibly followed by a number).
|
|
||||||
std::vector<utf16char> Completer::NormalizeQuery(utf16string query) {
|
|
||||||
auto result = std::vector<utf16char>();
|
|
||||||
result.reserve(query.size());
|
|
||||||
auto copyFrom = query.data();
|
|
||||||
auto e = copyFrom + query.size();
|
|
||||||
auto copyTo = result.data();
|
|
||||||
for (auto i = query.data(); i != e; ++i) {
|
|
||||||
if (IsLetterOrNumber(*i)) {
|
|
||||||
continue;
|
|
||||||
} else if (*i == '-' || *i == '+') {
|
|
||||||
if (i + 1 == e || IsNumber(*(i + 1))) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (i > copyFrom) {
|
|
||||||
result.resize(result.size() + (i - copyFrom));
|
|
||||||
memcpy(copyTo, copyFrom, (i - copyFrom) * sizeof(utf16char));
|
|
||||||
copyTo += (i - copyFrom);
|
|
||||||
}
|
|
||||||
copyFrom = i + 1;
|
|
||||||
}
|
|
||||||
if (e > copyFrom) {
|
|
||||||
result.resize(result.size() + (e - copyFrom));
|
|
||||||
memcpy(copyTo, copyFrom, (e - copyFrom) * sizeof(utf16char));
|
|
||||||
copyTo += (e - copyFrom);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<Suggestion> Completer::resolve() {
|
|
||||||
_queryBegin = _query.data();
|
|
||||||
_querySize = _query.size();
|
|
||||||
if (!_querySize) {
|
|
||||||
return std::vector<Suggestion>();
|
|
||||||
}
|
|
||||||
_initialList = Ui::Emoji::internal::GetReplacements(*_queryBegin);
|
|
||||||
if (!_initialList) {
|
|
||||||
return std::vector<Suggestion>();
|
|
||||||
}
|
|
||||||
_result.reserve(_initialList->size());
|
|
||||||
processInitialList();
|
|
||||||
return prepareResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Completer::isDuplicateOfLastResult(const Replacement *item) const {
|
|
||||||
if (_result.empty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return (_result.back().replacement->emoji == item->emoji);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Completer::isBetterThanLastResult(const Replacement *item) const {
|
|
||||||
Expects(!_result.empty());
|
|
||||||
auto &last = _result.back();
|
|
||||||
if (_currentItemWordsUsedCount < last.wordsUsed) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto firstCharOfQuery = _query[0];
|
|
||||||
auto firstCharAfterColonLast = last.replacement->replacement[1];
|
|
||||||
auto firstCharAfterColonCurrent = item->replacement[1];
|
|
||||||
auto goodLast = (firstCharAfterColonLast == firstCharOfQuery);
|
|
||||||
auto goodCurrent = (firstCharAfterColonCurrent == firstCharOfQuery);
|
|
||||||
return !goodLast && goodCurrent;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Completer::addResult(const Replacement *item) {
|
|
||||||
if (!isDuplicateOfLastResult(item)) {
|
|
||||||
_result.push_back({ item, _currentItemWordsUsedCount });
|
|
||||||
} else if (isBetterThanLastResult(item)) {
|
|
||||||
_result.back() = { item, _currentItemWordsUsedCount };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Completer::processInitialList() {
|
|
||||||
if (_querySize > 1) {
|
|
||||||
filterInitialList();
|
|
||||||
} else {
|
|
||||||
_currentItemWordsUsedCount = 1;
|
|
||||||
for (auto item : *_initialList) {
|
|
||||||
addResult(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Completer::initWordsTracking() {
|
|
||||||
auto maxWordsCount = 0;
|
|
||||||
for (auto item : *_initialList) {
|
|
||||||
auto wordsCount = item->words.size();
|
|
||||||
if (maxWordsCount < wordsCount) {
|
|
||||||
maxWordsCount = wordsCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_currentItemWordsUsedMap = std::vector<small>(maxWordsCount, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Completer::filterInitialList() {
|
|
||||||
initWordsTracking();
|
|
||||||
for (auto item : *_initialList) {
|
|
||||||
_currentItemWords = string_span(item->words);
|
|
||||||
_currentItemWordsUsedCount = 1;
|
|
||||||
if (matchQueryForCurrentItem()) {
|
|
||||||
addResult(item);
|
|
||||||
}
|
|
||||||
_currentItemWordsUsedCount = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Completer::matchQueryForCurrentItem() {
|
|
||||||
Expects(_currentItemWords.size() != 0);
|
|
||||||
if (_currentItemWords.size() < 2) {
|
|
||||||
return startsWithQuery(*_currentItemWords.begin());
|
|
||||||
}
|
|
||||||
return matchQueryTailStartingFrom(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Completer::startsWithQuery(utf16string word) {
|
|
||||||
if (word.size() < _query.size()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (auto i = std::size_t(0), size = _query.size(); i != size; ++i) {
|
|
||||||
if (word[i] != _query[i]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Completer::isExactMatch(utf16string replacement) {
|
|
||||||
if (replacement.size() != _initialQuery.size() + 1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (auto i = std::size_t(0), size = _initialQuery.size(); i != size; ++i) {
|
|
||||||
if (replacement[i] != _initialQuery[i]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Completer::matchQueryTailStartingFrom(int position) {
|
|
||||||
auto charsLeftToMatch = (_querySize - position);
|
|
||||||
if (!charsLeftToMatch) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto firstCharToMatch = *(_queryBegin + position);
|
|
||||||
auto foundWords = findWordsStartingWith(firstCharToMatch);
|
|
||||||
|
|
||||||
for (auto word = foundWords.begin(), foundWordsEnd = word + foundWords.size(); word != foundWordsEnd; ++word) {
|
|
||||||
auto wordIndex = word - _currentItemWords.begin();
|
|
||||||
if (auto guard = UsedWordGuard(_currentItemWordsUsedMap, wordIndex)) {
|
|
||||||
++_currentItemWordsUsedCount;
|
|
||||||
auto equalCharsCount = findEqualCharsCount(position, word);
|
|
||||||
for (auto check = equalCharsCount; check != 0; --check) {
|
|
||||||
if (matchQueryTailStartingFrom(position + check)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
--_currentItemWordsUsedCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int Completer::findEqualCharsCount(int position, const utf16string *word) {
|
|
||||||
auto charsLeft = (_querySize - position);
|
|
||||||
auto wordBegin = word->data();
|
|
||||||
auto wordSize = word->size();
|
|
||||||
auto possibleEqualCharsCount = (charsLeft > wordSize ? wordSize : charsLeft);
|
|
||||||
for (auto equalTill = 1; equalTill != possibleEqualCharsCount; ++equalTill) {
|
|
||||||
auto wordCh = *(wordBegin + equalTill);
|
|
||||||
auto queryCh = *(_queryBegin + position + equalTill);
|
|
||||||
if (wordCh != queryCh) {
|
|
||||||
return equalTill;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return possibleEqualCharsCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<Suggestion> Completer::prepareResult() {
|
|
||||||
auto firstCharOfQuery = _query[0];
|
|
||||||
std::stable_partition(_result.begin(), _result.end(), [firstCharOfQuery](Result &result) {
|
|
||||||
auto firstCharAfterColon = result.replacement->replacement[1];
|
|
||||||
return (firstCharAfterColon == firstCharOfQuery);
|
|
||||||
});
|
|
||||||
std::stable_partition(_result.begin(), _result.end(), [](Result &result) {
|
|
||||||
return (result.wordsUsed < 2);
|
|
||||||
});
|
|
||||||
std::stable_partition(_result.begin(), _result.end(), [](Result &result) {
|
|
||||||
return (result.wordsUsed < 3);
|
|
||||||
});
|
|
||||||
std::stable_partition(_result.begin(), _result.end(), [this](Result &result) {
|
|
||||||
return isExactMatch(result.replacement->replacement);
|
|
||||||
});
|
|
||||||
|
|
||||||
auto result = std::vector<Suggestion>();
|
|
||||||
result.reserve(_result.size());
|
|
||||||
for (auto &item : _result) {
|
|
||||||
result.emplace_back(item.replacement->emoji, item.replacement->replacement, item.replacement->replacement);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
string_span Completer::findWordsStartingWith(utf16char ch) {
|
|
||||||
auto begin = std::lower_bound(_currentItemWords.begin(), _currentItemWords.end(), ch, [](utf16string word, utf16char ch) {
|
|
||||||
return word[0] < ch;
|
|
||||||
});
|
|
||||||
auto end = std::upper_bound(_currentItemWords.begin(), _currentItemWords.end(), ch, [](utf16char ch, utf16string word) {
|
|
||||||
return ch < word[0];
|
|
||||||
});
|
|
||||||
return _currentItemWords.subspan(begin - _currentItemWords.begin(), end - begin);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
std::vector<Suggestion> GetSuggestions(utf16string query) {
|
|
||||||
return Completer(query).resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
int GetSuggestionMaxLength() {
|
|
||||||
return internal::kReplacementMaxLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Emoji
|
|
||||||
} // namespace Ui
|
|
||||||
@ -1,107 +0,0 @@
|
|||||||
/*
|
|
||||||
This file is part of Telegram Desktop,
|
|
||||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
|
||||||
|
|
||||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
It is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
In addition, as a special exception, the copyright holders give permission
|
|
||||||
to link the code of portions of this program with the OpenSSL library.
|
|
||||||
|
|
||||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
|
||||||
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|
||||||
*/
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace Ui {
|
|
||||||
namespace Emoji {
|
|
||||||
|
|
||||||
using small = unsigned char;
|
|
||||||
using medium = unsigned short;
|
|
||||||
using utf16char = unsigned short;
|
|
||||||
|
|
||||||
static_assert(sizeof(utf16char) == 2, "Bad UTF-16 character size.");
|
|
||||||
|
|
||||||
class utf16string {
|
|
||||||
public:
|
|
||||||
utf16string() = default;
|
|
||||||
utf16string(const utf16char *data, std::size_t size) : data_(data), size_(size) {
|
|
||||||
}
|
|
||||||
utf16string(const utf16string &other) = default;
|
|
||||||
utf16string &operator=(const utf16string &other) = default;
|
|
||||||
|
|
||||||
const utf16char *data() const {
|
|
||||||
return data_;
|
|
||||||
}
|
|
||||||
std::size_t size() const {
|
|
||||||
return size_;
|
|
||||||
}
|
|
||||||
|
|
||||||
utf16char operator[](int index) const {
|
|
||||||
return data_[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
const utf16char *data_ = nullptr;
|
|
||||||
std::size_t size_ = 0;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
inline bool operator==(utf16string a, utf16string b) {
|
|
||||||
return (a.size() == b.size()) && (!a.size() || !memcmp(a.data(), b.data(), a.size() * sizeof(utf16char)));
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace internal {
|
|
||||||
|
|
||||||
using checksum = unsigned int;
|
|
||||||
checksum countChecksum(const void *data, std::size_t size);
|
|
||||||
|
|
||||||
utf16string GetReplacementEmoji(utf16string replacement);
|
|
||||||
|
|
||||||
} // namespace internal
|
|
||||||
|
|
||||||
class Suggestion {
|
|
||||||
public:
|
|
||||||
Suggestion() = default;
|
|
||||||
Suggestion(utf16string emoji, utf16string label, utf16string replacement) : emoji_(emoji), label_(label), replacement_(replacement) {
|
|
||||||
}
|
|
||||||
Suggestion(const Suggestion &other) = default;
|
|
||||||
Suggestion &operator=(const Suggestion &other) = default;
|
|
||||||
|
|
||||||
utf16string emoji() const {
|
|
||||||
return emoji_;
|
|
||||||
}
|
|
||||||
utf16string label() const {
|
|
||||||
return label_;
|
|
||||||
}
|
|
||||||
utf16string replacement() const {
|
|
||||||
return replacement_;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
utf16string emoji_;
|
|
||||||
utf16string label_;
|
|
||||||
utf16string replacement_;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<Suggestion> GetSuggestions(utf16string query);
|
|
||||||
|
|
||||||
inline utf16string GetSuggestionEmoji(utf16string replacement) {
|
|
||||||
return internal::GetReplacementEmoji(replacement);
|
|
||||||
}
|
|
||||||
|
|
||||||
int GetSuggestionMaxLength();
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace Emoji
|
|
||||||
} // namespace Ui
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,46 +0,0 @@
|
|||||||
/*
|
|
||||||
WARNING! All changes made in this file will be lost!
|
|
||||||
Created from 'empty' by 'codegen_emoji'
|
|
||||||
|
|
||||||
This file is part of Telegram Desktop,
|
|
||||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
|
||||||
|
|
||||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
It is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
In addition, as a special exception, the copyright holders give permission
|
|
||||||
to link the code of portions of this program with the OpenSSL library.
|
|
||||||
|
|
||||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
|
||||||
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|
||||||
*/
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "emoji_suggestions.h"
|
|
||||||
|
|
||||||
namespace Ui {
|
|
||||||
namespace Emoji {
|
|
||||||
namespace internal {
|
|
||||||
|
|
||||||
struct Replacement {
|
|
||||||
utf16string emoji;
|
|
||||||
utf16string replacement;
|
|
||||||
std::vector<utf16string> words;
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr auto kReplacementMaxLength = 55;
|
|
||||||
|
|
||||||
void InitReplacements();
|
|
||||||
const std::vector<const Replacement*> *GetReplacements(utf16char first);
|
|
||||||
utf16string GetReplacementEmoji(utf16string replacement);
|
|
||||||
|
|
||||||
} // namespace internal
|
|
||||||
} // namespace Emoji
|
|
||||||
} // namespace Ui
|
|
||||||
@ -93,6 +93,7 @@
|
|||||||
09874E5721078FA100E190B8 /* YoutubeUserScript.js in Resources */ = {isa = PBXBuildFile; fileRef = 0979788121065F8B0077D77F /* YoutubeUserScript.js */; };
|
09874E5721078FA100E190B8 /* YoutubeUserScript.js in Resources */ = {isa = PBXBuildFile; fileRef = 0979788121065F8B0077D77F /* YoutubeUserScript.js */; };
|
||||||
09874E582107A4C300E190B8 /* VimeoEmbedImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09874E3A21075BF400E190B8 /* VimeoEmbedImplementation.swift */; };
|
09874E582107A4C300E190B8 /* VimeoEmbedImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09874E3A21075BF400E190B8 /* VimeoEmbedImplementation.swift */; };
|
||||||
09874E592107BD4100E190B8 /* GenericEmbedImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09874E4021075C1700E190B8 /* GenericEmbedImplementation.swift */; };
|
09874E592107BD4100E190B8 /* GenericEmbedImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09874E4021075C1700E190B8 /* GenericEmbedImplementation.swift */; };
|
||||||
|
098CF79222B924E200AF6134 /* ThemeSettingsAccentColorItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 098CF79122B924E200AF6134 /* ThemeSettingsAccentColorItem.swift */; };
|
||||||
099529AA21CDB27900805E13 /* ShareProxyServerActionSheetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099529A921CDB27900805E13 /* ShareProxyServerActionSheetController.swift */; };
|
099529AA21CDB27900805E13 /* ShareProxyServerActionSheetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099529A921CDB27900805E13 /* ShareProxyServerActionSheetController.swift */; };
|
||||||
099529AC21CDBBB200805E13 /* QRCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099529AB21CDBBB200805E13 /* QRCode.swift */; };
|
099529AC21CDBBB200805E13 /* QRCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099529AB21CDBBB200805E13 /* QRCode.swift */; };
|
||||||
099529B021D2123E00805E13 /* ChatMessageUnsupportedBubbleContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099529AF21D2123E00805E13 /* ChatMessageUnsupportedBubbleContentNode.swift */; };
|
099529B021D2123E00805E13 /* ChatMessageUnsupportedBubbleContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099529AF21D2123E00805E13 /* ChatMessageUnsupportedBubbleContentNode.swift */; };
|
||||||
@ -324,12 +325,6 @@
|
|||||||
D0380DAD204ED434000414AB /* LegacyLiveUploadInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0380DAC204ED434000414AB /* LegacyLiveUploadInterface.swift */; };
|
D0380DAD204ED434000414AB /* LegacyLiveUploadInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0380DAC204ED434000414AB /* LegacyLiveUploadInterface.swift */; };
|
||||||
D0380DB8204EE0A5000414AB /* ChatInstantVideoMessageDurationNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0380DB7204EE0A5000414AB /* ChatInstantVideoMessageDurationNode.swift */; };
|
D0380DB8204EE0A5000414AB /* ChatInstantVideoMessageDurationNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0380DB7204EE0A5000414AB /* ChatInstantVideoMessageDurationNode.swift */; };
|
||||||
D0383ED4207CFBB900C45548 /* GalleryThumbnailContainerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0383ED3207CFBB900C45548 /* GalleryThumbnailContainerNode.swift */; };
|
D0383ED4207CFBB900C45548 /* GalleryThumbnailContainerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0383ED3207CFBB900C45548 /* GalleryThumbnailContainerNode.swift */; };
|
||||||
D0383EDC207D1A1600C45548 /* emoji_suggestions_data.h in Headers */ = {isa = PBXBuildFile; fileRef = D0383ED6207D1A1500C45548 /* emoji_suggestions_data.h */; };
|
|
||||||
D0383EDD207D1A1600C45548 /* TGEmojiSuggestions.h in Headers */ = {isa = PBXBuildFile; fileRef = D0383ED7207D1A1500C45548 /* TGEmojiSuggestions.h */; };
|
|
||||||
D0383EDE207D1A1600C45548 /* emoji_suggestions.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0383ED8207D1A1600C45548 /* emoji_suggestions.cpp */; };
|
|
||||||
D0383EDF207D1A1600C45548 /* emoji_suggestions_data.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0383ED9207D1A1600C45548 /* emoji_suggestions_data.cpp */; };
|
|
||||||
D0383EE0207D1A1600C45548 /* TGEmojiSuggestions.mm in Sources */ = {isa = PBXBuildFile; fileRef = D0383EDA207D1A1600C45548 /* TGEmojiSuggestions.mm */; };
|
|
||||||
D0383EE1207D1A1600C45548 /* emoji_suggestions.h in Headers */ = {isa = PBXBuildFile; fileRef = D0383EDB207D1A1600C45548 /* emoji_suggestions.h */; };
|
|
||||||
D0383EE4207D292800C45548 /* EmojisChatInputContextPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0383EE3207D292800C45548 /* EmojisChatInputContextPanelNode.swift */; };
|
D0383EE4207D292800C45548 /* EmojisChatInputContextPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0383EE3207D292800C45548 /* EmojisChatInputContextPanelNode.swift */; };
|
||||||
D0383EE6207D299600C45548 /* EmojisChatInputPanelItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0383EE5207D299600C45548 /* EmojisChatInputPanelItem.swift */; };
|
D0383EE6207D299600C45548 /* EmojisChatInputPanelItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0383EE5207D299600C45548 /* EmojisChatInputPanelItem.swift */; };
|
||||||
D039FB152170D99D00BD1BAD /* RadialCloudProgressContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D039FB142170D99D00BD1BAD /* RadialCloudProgressContentNode.swift */; };
|
D039FB152170D99D00BD1BAD /* RadialCloudProgressContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D039FB142170D99D00BD1BAD /* RadialCloudProgressContentNode.swift */; };
|
||||||
@ -1265,6 +1260,7 @@
|
|||||||
0979788821065F8C0077D77F /* GenericUserScript.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = GenericUserScript.js; sourceTree = "<group>"; };
|
0979788821065F8C0077D77F /* GenericUserScript.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = GenericUserScript.js; sourceTree = "<group>"; };
|
||||||
09874E3A21075BF400E190B8 /* VimeoEmbedImplementation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VimeoEmbedImplementation.swift; sourceTree = "<group>"; };
|
09874E3A21075BF400E190B8 /* VimeoEmbedImplementation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VimeoEmbedImplementation.swift; sourceTree = "<group>"; };
|
||||||
09874E4021075C1700E190B8 /* GenericEmbedImplementation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericEmbedImplementation.swift; sourceTree = "<group>"; };
|
09874E4021075C1700E190B8 /* GenericEmbedImplementation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericEmbedImplementation.swift; sourceTree = "<group>"; };
|
||||||
|
098CF79122B924E200AF6134 /* ThemeSettingsAccentColorItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeSettingsAccentColorItem.swift; sourceTree = "<group>"; };
|
||||||
099529A921CDB27900805E13 /* ShareProxyServerActionSheetController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareProxyServerActionSheetController.swift; sourceTree = "<group>"; };
|
099529A921CDB27900805E13 /* ShareProxyServerActionSheetController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareProxyServerActionSheetController.swift; sourceTree = "<group>"; };
|
||||||
099529AB21CDBBB200805E13 /* QRCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCode.swift; sourceTree = "<group>"; };
|
099529AB21CDBBB200805E13 /* QRCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCode.swift; sourceTree = "<group>"; };
|
||||||
099529AF21D2123E00805E13 /* ChatMessageUnsupportedBubbleContentNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessageUnsupportedBubbleContentNode.swift; sourceTree = "<group>"; };
|
099529AF21D2123E00805E13 /* ChatMessageUnsupportedBubbleContentNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessageUnsupportedBubbleContentNode.swift; sourceTree = "<group>"; };
|
||||||
@ -1588,12 +1584,6 @@
|
|||||||
D0380DAC204ED434000414AB /* LegacyLiveUploadInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyLiveUploadInterface.swift; sourceTree = "<group>"; };
|
D0380DAC204ED434000414AB /* LegacyLiveUploadInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyLiveUploadInterface.swift; sourceTree = "<group>"; };
|
||||||
D0380DB7204EE0A5000414AB /* ChatInstantVideoMessageDurationNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatInstantVideoMessageDurationNode.swift; sourceTree = "<group>"; };
|
D0380DB7204EE0A5000414AB /* ChatInstantVideoMessageDurationNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatInstantVideoMessageDurationNode.swift; sourceTree = "<group>"; };
|
||||||
D0383ED3207CFBB900C45548 /* GalleryThumbnailContainerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GalleryThumbnailContainerNode.swift; sourceTree = "<group>"; };
|
D0383ED3207CFBB900C45548 /* GalleryThumbnailContainerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GalleryThumbnailContainerNode.swift; sourceTree = "<group>"; };
|
||||||
D0383ED6207D1A1500C45548 /* emoji_suggestions_data.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = emoji_suggestions_data.h; sourceTree = "<group>"; };
|
|
||||||
D0383ED7207D1A1500C45548 /* TGEmojiSuggestions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGEmojiSuggestions.h; sourceTree = "<group>"; };
|
|
||||||
D0383ED8207D1A1600C45548 /* emoji_suggestions.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = emoji_suggestions.cpp; sourceTree = "<group>"; };
|
|
||||||
D0383ED9207D1A1600C45548 /* emoji_suggestions_data.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = emoji_suggestions_data.cpp; sourceTree = "<group>"; };
|
|
||||||
D0383EDA207D1A1600C45548 /* TGEmojiSuggestions.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TGEmojiSuggestions.mm; sourceTree = "<group>"; };
|
|
||||||
D0383EDB207D1A1600C45548 /* emoji_suggestions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = emoji_suggestions.h; sourceTree = "<group>"; };
|
|
||||||
D0383EE3207D292800C45548 /* EmojisChatInputContextPanelNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojisChatInputContextPanelNode.swift; sourceTree = "<group>"; };
|
D0383EE3207D292800C45548 /* EmojisChatInputContextPanelNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojisChatInputContextPanelNode.swift; sourceTree = "<group>"; };
|
||||||
D0383EE5207D299600C45548 /* EmojisChatInputPanelItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojisChatInputPanelItem.swift; sourceTree = "<group>"; };
|
D0383EE5207D299600C45548 /* EmojisChatInputPanelItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojisChatInputPanelItem.swift; sourceTree = "<group>"; };
|
||||||
D039EB021DEAEFEE00886EBC /* ChatTextInputAudioRecordingOverlayButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatTextInputAudioRecordingOverlayButton.swift; sourceTree = "<group>"; };
|
D039EB021DEAEFEE00886EBC /* ChatTextInputAudioRecordingOverlayButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatTextInputAudioRecordingOverlayButton.swift; sourceTree = "<group>"; };
|
||||||
@ -3007,19 +2997,6 @@
|
|||||||
name = "Plaintext Fields";
|
name = "Plaintext Fields";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
D0383ED5207D19BC00C45548 /* Emoji */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
D0383ED9207D1A1600C45548 /* emoji_suggestions_data.cpp */,
|
|
||||||
D0383ED6207D1A1500C45548 /* emoji_suggestions_data.h */,
|
|
||||||
D0383ED8207D1A1600C45548 /* emoji_suggestions.cpp */,
|
|
||||||
D0383EDB207D1A1600C45548 /* emoji_suggestions.h */,
|
|
||||||
D0383ED7207D1A1500C45548 /* TGEmojiSuggestions.h */,
|
|
||||||
D0383EDA207D1A1600C45548 /* TGEmojiSuggestions.mm */,
|
|
||||||
);
|
|
||||||
name = Emoji;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
D0383EE2207D291100C45548 /* Emojis */ = {
|
D0383EE2207D291100C45548 /* Emojis */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@ -3228,6 +3205,7 @@
|
|||||||
D0B37C5F1F8D286E004252DF /* ThemeSettingsFontSizeItem.swift */,
|
D0B37C5F1F8D286E004252DF /* ThemeSettingsFontSizeItem.swift */,
|
||||||
D06F31E52135A41C001A0F12 /* ThemeSettingsBrightnessItem.swift */,
|
D06F31E52135A41C001A0F12 /* ThemeSettingsBrightnessItem.swift */,
|
||||||
09A218F422A15F1400DE6898 /* ThemeSettingsAppIconItem.swift */,
|
09A218F422A15F1400DE6898 /* ThemeSettingsAppIconItem.swift */,
|
||||||
|
098CF79122B924E200AF6134 /* ThemeSettingsAccentColorItem.swift */,
|
||||||
090E777822A6A32E00CD99F5 /* ThemeSettingsThemeItem.swift */,
|
090E777822A6A32E00CD99F5 /* ThemeSettingsThemeItem.swift */,
|
||||||
D06E4C322134A59700088087 /* ThemeAccentColorActionSheet.swift */,
|
D06E4C322134A59700088087 /* ThemeAccentColorActionSheet.swift */,
|
||||||
D06E4C342134AE3C00088087 /* ThemeAutoNightSettingsController.swift */,
|
D06E4C342134AE3C00088087 /* ThemeAutoNightSettingsController.swift */,
|
||||||
@ -4728,7 +4706,6 @@
|
|||||||
09E4A7FE223ADFD80038140F /* Data */,
|
09E4A7FE223ADFD80038140F /* Data */,
|
||||||
D0B69C3A20EBD8B3003632C7 /* Device */,
|
D0B69C3A20EBD8B3003632C7 /* Device */,
|
||||||
D01C7EFE1EF9D434008305F1 /* Device Contacts */,
|
D01C7EFE1EF9D434008305F1 /* Device Contacts */,
|
||||||
D0383ED5207D19BC00C45548 /* Emoji */,
|
|
||||||
D025A4241F79428300563950 /* Fetch Manager */,
|
D025A4241F79428300563950 /* Fetch Manager */,
|
||||||
09E4A7FC223ADF300038140F /* Images */,
|
09E4A7FC223ADF300038140F /* Images */,
|
||||||
D04614352005093B00EC0EF2 /* Location */,
|
D04614352005093B00EC0EF2 /* Location */,
|
||||||
@ -4882,7 +4859,6 @@
|
|||||||
D0208AD51FA33D14001F0D5F /* RaiseToListenActivator.h in Headers */,
|
D0208AD51FA33D14001F0D5F /* RaiseToListenActivator.h in Headers */,
|
||||||
D00817DA22B47A14008A895F /* TGPresentationAutoNightPreferences.h in Headers */,
|
D00817DA22B47A14008A895F /* TGPresentationAutoNightPreferences.h in Headers */,
|
||||||
D0E9BAE31F0574D800F079A4 /* STPBankAccountParams.h in Headers */,
|
D0E9BAE31F0574D800F079A4 /* STPBankAccountParams.h in Headers */,
|
||||||
D0383EE1207D1A1600C45548 /* emoji_suggestions.h in Headers */,
|
|
||||||
D0E9BA361F05585000F079A4 /* STPPhoneNumberValidator.h in Headers */,
|
D0E9BA361F05585000F079A4 /* STPPhoneNumberValidator.h in Headers */,
|
||||||
D0E9BA511F0559DA00F079A4 /* STPImageLibrary.h in Headers */,
|
D0E9BA511F0559DA00F079A4 /* STPImageLibrary.h in Headers */,
|
||||||
D0E9BA4C1F0559C700F079A4 /* NSString+Stripe_CardBrands.h in Headers */,
|
D0E9BA4C1F0559C700F079A4 /* NSString+Stripe_CardBrands.h in Headers */,
|
||||||
@ -4892,7 +4868,6 @@
|
|||||||
D0E9BA2A1F0557A600F079A4 /* STPFormEncoder.h in Headers */,
|
D0E9BA2A1F0557A600F079A4 /* STPFormEncoder.h in Headers */,
|
||||||
D0E9BA321F05583A00F079A4 /* STPPostalCodeValidator.h in Headers */,
|
D0E9BA321F05583A00F079A4 /* STPPostalCodeValidator.h in Headers */,
|
||||||
D0E9BADC1F0574D800F079A4 /* PKPayment+Stripe.h in Headers */,
|
D0E9BADC1F0574D800F079A4 /* PKPayment+Stripe.h in Headers */,
|
||||||
D0383EDC207D1A1600C45548 /* emoji_suggestions_data.h in Headers */,
|
|
||||||
D0E9BA491F0559B600F079A4 /* STPPaymentMethod.h in Headers */,
|
D0E9BA491F0559B600F079A4 /* STPPaymentMethod.h in Headers */,
|
||||||
D08803C51F6064CF00DD7951 /* TelegramUI.h in Headers */,
|
D08803C51F6064CF00DD7951 /* TelegramUI.h in Headers */,
|
||||||
D0E9BA171F05574500F079A4 /* STPPaymentCardTextFieldViewModel.h in Headers */,
|
D0E9BA171F05574500F079A4 /* STPPaymentCardTextFieldViewModel.h in Headers */,
|
||||||
@ -4924,7 +4899,6 @@
|
|||||||
D00817CE22B47A14008A895F /* TGProxyItem.h in Headers */,
|
D00817CE22B47A14008A895F /* TGProxyItem.h in Headers */,
|
||||||
D0E9BA401F0558FE00F079A4 /* StripeError.h in Headers */,
|
D0E9BA401F0558FE00F079A4 /* StripeError.h in Headers */,
|
||||||
D0E9BA191F05574500F079A4 /* STPPaymentCardTextField.h in Headers */,
|
D0E9BA191F05574500F079A4 /* STPPaymentCardTextField.h in Headers */,
|
||||||
D0383EDD207D1A1600C45548 /* TGEmojiSuggestions.h in Headers */,
|
|
||||||
D0E9BA3F1F0558FE00F079A4 /* STPSource.h in Headers */,
|
D0E9BA3F1F0558FE00F079A4 /* STPSource.h in Headers */,
|
||||||
D008177A22B46B7E008A895F /* TGShareLocationSignals.h in Headers */,
|
D008177A22B46B7E008A895F /* TGShareLocationSignals.h in Headers */,
|
||||||
D0E9BABC1F05735F00F079A4 /* STPPaymentConfiguration.h in Headers */,
|
D0E9BABC1F05735F00F079A4 /* STPPaymentConfiguration.h in Headers */,
|
||||||
@ -5655,7 +5629,6 @@
|
|||||||
D056CD761FF2A30900880D28 /* ChatSwipeToReplyRecognizer.swift in Sources */,
|
D056CD761FF2A30900880D28 /* ChatSwipeToReplyRecognizer.swift in Sources */,
|
||||||
D00580B321E4B51600CB7CD3 /* DeleteChatPeerActionSheetItem.swift in Sources */,
|
D00580B321E4B51600CB7CD3 /* DeleteChatPeerActionSheetItem.swift in Sources */,
|
||||||
D091C7A41F8EBB1E00D7DE13 /* ChatPresentationData.swift in Sources */,
|
D091C7A41F8EBB1E00D7DE13 /* ChatPresentationData.swift in Sources */,
|
||||||
D0383EE0207D1A1600C45548 /* TGEmojiSuggestions.mm in Sources */,
|
|
||||||
09A218F522A15F1400DE6898 /* ThemeSettingsAppIconItem.swift in Sources */,
|
09A218F522A15F1400DE6898 /* ThemeSettingsAppIconItem.swift in Sources */,
|
||||||
D013630C208FA62400EB3653 /* SecureIdDocumentGalleryFooterContentNode.swift in Sources */,
|
D013630C208FA62400EB3653 /* SecureIdDocumentGalleryFooterContentNode.swift in Sources */,
|
||||||
D0EB41F31F2FEAB800838FE6 /* LegacyComponentsStickers.swift in Sources */,
|
D0EB41F31F2FEAB800838FE6 /* LegacyComponentsStickers.swift in Sources */,
|
||||||
@ -6014,6 +5987,7 @@
|
|||||||
D081E106217F5834003CD921 /* LanguageLinkPreviewControllerNode.swift in Sources */,
|
D081E106217F5834003CD921 /* LanguageLinkPreviewControllerNode.swift in Sources */,
|
||||||
D093D7E72063E57F00BC3599 /* BotPaymentActionItemNode.swift in Sources */,
|
D093D7E72063E57F00BC3599 /* BotPaymentActionItemNode.swift in Sources */,
|
||||||
D01C06BA1FBBB076001561AB /* ItemListSelectableControlNode.swift in Sources */,
|
D01C06BA1FBBB076001561AB /* ItemListSelectableControlNode.swift in Sources */,
|
||||||
|
098CF79222B924E200AF6134 /* ThemeSettingsAccentColorItem.swift in Sources */,
|
||||||
D0EC6E541EB9F58900EBF1C3 /* ConvertToSupergroupController.swift in Sources */,
|
D0EC6E541EB9F58900EBF1C3 /* ConvertToSupergroupController.swift in Sources */,
|
||||||
D0EC6E561EB9F58900EBF1C3 /* UserInfoController.swift in Sources */,
|
D0EC6E561EB9F58900EBF1C3 /* UserInfoController.swift in Sources */,
|
||||||
D0EC6E571EB9F58900EBF1C3 /* GroupsInCommonController.swift in Sources */,
|
D0EC6E571EB9F58900EBF1C3 /* GroupsInCommonController.swift in Sources */,
|
||||||
@ -6044,14 +6018,12 @@
|
|||||||
D0EC6E651EB9F58900EBF1C3 /* TwoStepVerificationResetController.swift in Sources */,
|
D0EC6E651EB9F58900EBF1C3 /* TwoStepVerificationResetController.swift in Sources */,
|
||||||
D0EC6E661EB9F58900EBF1C3 /* PasscodeOptionsController.swift in Sources */,
|
D0EC6E661EB9F58900EBF1C3 /* PasscodeOptionsController.swift in Sources */,
|
||||||
09CE95082237A53900A7D2C3 /* SettingsSearchableItems.swift in Sources */,
|
09CE95082237A53900A7D2C3 /* SettingsSearchableItems.swift in Sources */,
|
||||||
D0383EDE207D1A1600C45548 /* emoji_suggestions.cpp in Sources */,
|
|
||||||
D0EC6E671EB9F58900EBF1C3 /* DataAndStorageSettingsController.swift in Sources */,
|
D0EC6E671EB9F58900EBF1C3 /* DataAndStorageSettingsController.swift in Sources */,
|
||||||
D0EC6E681EB9F58900EBF1C3 /* VoiceCallDataSavingController.swift in Sources */,
|
D0EC6E681EB9F58900EBF1C3 /* VoiceCallDataSavingController.swift in Sources */,
|
||||||
D0EC6E691EB9F58900EBF1C3 /* NetworkUsageStatsController.swift in Sources */,
|
D0EC6E691EB9F58900EBF1C3 /* NetworkUsageStatsController.swift in Sources */,
|
||||||
D0EC6E6A1EB9F58900EBF1C3 /* StorageUsageController.swift in Sources */,
|
D0EC6E6A1EB9F58900EBF1C3 /* StorageUsageController.swift in Sources */,
|
||||||
D079FCDF1F05C9280038FADE /* BotReceiptController.swift in Sources */,
|
D079FCDF1F05C9280038FADE /* BotReceiptController.swift in Sources */,
|
||||||
D0EC6E6B1EB9F58900EBF1C3 /* InstalledStickerPacksController.swift in Sources */,
|
D0EC6E6B1EB9F58900EBF1C3 /* InstalledStickerPacksController.swift in Sources */,
|
||||||
D0383EDF207D1A1600C45548 /* emoji_suggestions_data.cpp in Sources */,
|
|
||||||
D0EC6E6C1EB9F58900EBF1C3 /* FeaturedStickerPacksController.swift in Sources */,
|
D0EC6E6C1EB9F58900EBF1C3 /* FeaturedStickerPacksController.swift in Sources */,
|
||||||
D0B85C231FF70BF400E795B4 /* AuthorizationSequenceAwaitingAccountResetController.swift in Sources */,
|
D0B85C231FF70BF400E795B4 /* AuthorizationSequenceAwaitingAccountResetController.swift in Sources */,
|
||||||
D0EC6E6D1EB9F58900EBF1C3 /* ItemListStickerPackItem.swift in Sources */,
|
D0EC6E6D1EB9F58900EBF1C3 /* ItemListStickerPackItem.swift in Sources */,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user