Various improvements

This commit is contained in:
Ilya Laktyushin 2025-01-18 13:44:42 +04:00
parent a8fd8c6085
commit ef6c097f6f
57 changed files with 1892 additions and 485 deletions

View File

@ -13095,6 +13095,9 @@ Sorry for the inconvenience.";
"Gift.Send.Remains_any" = "%@ left"; "Gift.Send.Remains_any" = "%@ left";
"Gift.Send.Sold_1" = "%@ sold"; "Gift.Send.Sold_1" = "%@ sold";
"Gift.Send.Sold_any" = "%@ sold"; "Gift.Send.Sold_any" = "%@ sold";
"Gift.Send.Success" = "You sent a gift to **%1$@** for **%2$@**.";
"Gift.Send.Success.Stars_1" = "%@ Star";
"Gift.Send.Success.Stars_any" = "%@ Stars";
"Gift.Send.ErrorUnknown" = "An error occurred. Please try again."; "Gift.Send.ErrorUnknown" = "An error occurred. Please try again.";
"Gift.Send.ErrorOutOfStock" = "Sorry, this gift is sold out. Please choose another gift."; "Gift.Send.ErrorOutOfStock" = "Sorry, this gift is sold out. Please choose another gift.";
@ -13667,6 +13670,11 @@ Sorry for the inconvenience.";
"Gift.View.Header.TakeOff" = "take off"; "Gift.View.Header.TakeOff" = "take off";
"Gift.View.Header.Share" = "share"; "Gift.View.Header.Share" = "share";
"Gift.View.PutOn" = "You put on %@";
"Gift.View.TookOff" = "You took off %@";
"Gift.View.TooltipPremiumWearing" = "Subscribe to [Telegram Premium]() to wear collectibles.";
"Conversation.AddToContactsLong" = "Add to Contacts"; "Conversation.AddToContactsLong" = "Add to Contacts";
"PeerInfo.PaneRecommendedBots" = "Similar Bots"; "PeerInfo.PaneRecommendedBots" = "Similar Bots";
@ -13696,3 +13704,42 @@ Sorry for the inconvenience.";
"ChatListFilter.NameEnableAnimations" = "Enable Animations"; "ChatListFilter.NameEnableAnimations" = "Enable Animations";
"ChatListFilter.NameDisableAnimations" = "Disable Animations"; "ChatListFilter.NameDisableAnimations" = "Disable Animations";
"Chat.SendGiftTooltip" = "Tap here to send a gift";
"ChannelBoost.Table.WearGift" = "Wear Unique Collectibles";
"ChannelBoost.WearGift" = "Wear Item";
"ChannelBoost.WearGiftLevelText" = "Your channel needs **Level %1$@** to wear collectibles.";
"PeerInfo.Gifts.SendGift" = "Send Gift";
"PeerInfo.Gifts.ChannelNotify" = "Notify About New Gifts";
"PeerInfo.Gifts.ChannelNotifyTooltip" = "You will receive a message from Telegram when your channel receives a gift.";
"Notification.StarsGift.Channel.Sent" = "%1$@ sent a gift to %2$@ for %3$@";
"Gift.View.Display.Channel" = "Display in Gifts";
"Gift.View.DisplayedInfoHide.Channel" = "The item is visible in your channel's Gifts. [Hide >]()";
"Gift.View.HiddenInfo.Channel" = "This item is hidden from visitors of your channel.";
"Gift.View.HiddenInfoShow.Channel" = "This item is hidden from visitors of your channel. [Show >]()";
"Gift.View.KeepOrConvertDescription.Channel" = "Your channel can keep this gift in your Profile or convert it to %@. [More About Stars >]()";
"Gift.View.KeepUpgradeOrConvertDescription.Channel" = "Your channel can keep this gift, upgrade it, or sell it for %@. [More About Stars >]()";
"MediaPicker.UseAnEmoji" = "Use an Emoji";
"MediaPicker.SetNewPhoto" = "Set new profile photo";
"MediaPicker.ChooseCover" = "Choose Cover";
"MediaPicker.RemovePhoto" = "Remove Photo";
"EmojiInput.SectionTitleCollectibles" = "COLLECTIBLES";
"Gift.Options.GiftChannel.Title" = "Send a Gift";
"Gift.Options.GiftChannel.Text" = "Select a gift to show appreciation for **%@**.";
"Gift.SendChannel.Title" = "Gift Preview";
"Gift.SendChannel.HideMyName.Info" = "Hide my name and message from visitors of this channel. The channel admins will still see them.";
"Media.EditCover" = "Edit Cover";
"Media.ChooseFromGallery" = "Choose From Gallery";
"Media.SelectFrame" = "Select Frame";
"Media.SaveCover" = "Save Cover";

View File

@ -121,6 +121,7 @@ public enum BoostSubject: Equatable {
case audioTranscription case audioTranscription
case emojiPack case emojiPack
case noAds case noAds
case wearGift
} }
public enum StarsPurchasePurpose: Equatable { public enum StarsPurchasePurpose: Equatable {
@ -157,6 +158,7 @@ public struct PremiumConfiguration {
minChannelWallpaperLevel: 9, minChannelWallpaperLevel: 9,
minChannelCustomWallpaperLevel: 10, minChannelCustomWallpaperLevel: 10,
minChannelRestrictAdsLevel: 50, minChannelRestrictAdsLevel: 50,
minChannelWearGiftLevel: 8,
minGroupProfileIconLevel: 7, minGroupProfileIconLevel: 7,
minGroupEmojiStatusLevel: 8, minGroupEmojiStatusLevel: 8,
minGroupWallpaperLevel: 9, minGroupWallpaperLevel: 9,
@ -185,6 +187,7 @@ public struct PremiumConfiguration {
public let minChannelWallpaperLevel: Int32 public let minChannelWallpaperLevel: Int32
public let minChannelCustomWallpaperLevel: Int32 public let minChannelCustomWallpaperLevel: Int32
public let minChannelRestrictAdsLevel: Int32 public let minChannelRestrictAdsLevel: Int32
public let minChannelWearGiftLevel: Int32
public let minGroupProfileIconLevel: Int32 public let minGroupProfileIconLevel: Int32
public let minGroupEmojiStatusLevel: Int32 public let minGroupEmojiStatusLevel: Int32
public let minGroupWallpaperLevel: Int32 public let minGroupWallpaperLevel: Int32
@ -212,6 +215,7 @@ public struct PremiumConfiguration {
minChannelWallpaperLevel: Int32, minChannelWallpaperLevel: Int32,
minChannelCustomWallpaperLevel: Int32, minChannelCustomWallpaperLevel: Int32,
minChannelRestrictAdsLevel: Int32, minChannelRestrictAdsLevel: Int32,
minChannelWearGiftLevel: Int32,
minGroupProfileIconLevel: Int32, minGroupProfileIconLevel: Int32,
minGroupEmojiStatusLevel: Int32, minGroupEmojiStatusLevel: Int32,
minGroupWallpaperLevel: Int32, minGroupWallpaperLevel: Int32,
@ -238,6 +242,7 @@ public struct PremiumConfiguration {
self.minChannelWallpaperLevel = minChannelWallpaperLevel self.minChannelWallpaperLevel = minChannelWallpaperLevel
self.minChannelCustomWallpaperLevel = minChannelCustomWallpaperLevel self.minChannelCustomWallpaperLevel = minChannelCustomWallpaperLevel
self.minChannelRestrictAdsLevel = minChannelRestrictAdsLevel self.minChannelRestrictAdsLevel = minChannelRestrictAdsLevel
self.minChannelWearGiftLevel = minChannelWearGiftLevel
self.minGroupProfileIconLevel = minGroupProfileIconLevel self.minGroupProfileIconLevel = minGroupProfileIconLevel
self.minGroupEmojiStatusLevel = minGroupEmojiStatusLevel self.minGroupEmojiStatusLevel = minGroupEmojiStatusLevel
self.minGroupWallpaperLevel = minGroupWallpaperLevel self.minGroupWallpaperLevel = minGroupWallpaperLevel
@ -272,6 +277,7 @@ public struct PremiumConfiguration {
minChannelWallpaperLevel: get(data["channel_wallpaper_level_min"]) ?? defaultValue.minChannelWallpaperLevel, minChannelWallpaperLevel: get(data["channel_wallpaper_level_min"]) ?? defaultValue.minChannelWallpaperLevel,
minChannelCustomWallpaperLevel: get(data["channel_custom_wallpaper_level_min"]) ?? defaultValue.minChannelCustomWallpaperLevel, minChannelCustomWallpaperLevel: get(data["channel_custom_wallpaper_level_min"]) ?? defaultValue.minChannelCustomWallpaperLevel,
minChannelRestrictAdsLevel: get(data["channel_restrict_sponsored_level_min"]) ?? defaultValue.minChannelRestrictAdsLevel, minChannelRestrictAdsLevel: get(data["channel_restrict_sponsored_level_min"]) ?? defaultValue.minChannelRestrictAdsLevel,
minChannelWearGiftLevel: get(data["channel_wear_collectible_level_min"]) ?? defaultValue.minChannelWearGiftLevel,
minGroupProfileIconLevel: get(data["group_profile_bg_icon_level_min"]) ?? defaultValue.minGroupProfileIconLevel, minGroupProfileIconLevel: get(data["group_profile_bg_icon_level_min"]) ?? defaultValue.minGroupProfileIconLevel,
minGroupEmojiStatusLevel: get(data["group_emoji_status_level_min"]) ?? defaultValue.minGroupEmojiStatusLevel, minGroupEmojiStatusLevel: get(data["group_emoji_status_level_min"]) ?? defaultValue.minGroupEmojiStatusLevel,
minGroupWallpaperLevel: get(data["group_wallpaper_level_min"]) ?? defaultValue.minGroupWallpaperLevel, minGroupWallpaperLevel: get(data["group_wallpaper_level_min"]) ?? defaultValue.minGroupWallpaperLevel,

View File

@ -1875,9 +1875,8 @@ public final class ChatListNode: ListView {
})) }))
case .premiumGrace: case .premiumGrace:
let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: .gracePremium).startStandalone() let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: .gracePremium).startStandalone()
// self.present?(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.ChatList_BirthdayInSettingsInfo, timeout: 5.0, customUndoText: nil), elevatedLayout: false, action: { _ in case .setupPhoto:
// return true let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: .setupPhoto).startStandalone()
// }))
default: default:
break break
} }
@ -1994,11 +1993,6 @@ public final class ChatListNode: ListView {
starsSubscriptionsContextPromise.get() starsSubscriptionsContextPromise.get()
) )
|> mapToSignal { suggestions, dismissedSuggestions, configuration, newSessionReviews, data, birthdays, starsSubscriptionsContext -> Signal<ChatListNotice?, NoError> in |> mapToSignal { suggestions, dismissedSuggestions, configuration, newSessionReviews, data, birthdays, starsSubscriptionsContext -> Signal<ChatListNotice?, NoError> in
#if DEBUG
var suggestions = suggestions
suggestions.insert(.setupPhoto, at: 0)
#endif
let (accountPeer, birthday) = data let (accountPeer, birthday) = data
if let newSessionReview = newSessionReviews.first { if let newSessionReview = newSessionReviews.first {

View File

@ -294,7 +294,7 @@ public class DrawingReactionEntityView: DrawingStickerEntityView {
let context = self.context let context = self.context
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let controller = UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, loop: true, title: nil, text: presentationData.strings.Story_Editor_TooltipPremiumReaction, undoText: nil, customAction: nil), elevatedLayout: true, animateInAsReplacement: false, blurred: true, action: { [weak self] action in let controller = UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, loop: true, title: nil, text: presentationData.strings.Story_Editor_TooltipPremiumReaction, undoText: nil, customAction: nil), elevatedLayout: true, animateInAsReplacement: false, appearance: UndoOverlayController.Appearance(isBlurred: true), action: { [weak self] action in
if case .info = action, let self { if case .info = action, let self {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .storiesExpirationDurations, forceDark: true, dismissed: nil) let controller = context.sharedContext.makePremiumIntroController(context: context, source: .storiesExpirationDurations, forceDark: true, dismissed: nil)
self.containerView?.push(controller) self.containerView?.push(controller)

View File

@ -464,7 +464,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
storeAttributedTextInPasteboard(text) storeAttributedTextInPasteboard(text)
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let undoController = UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: false, animateInAsReplacement: false, blurred: true, action: { _ in true }) let undoController = UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: false, animateInAsReplacement: false, appearance: UndoOverlayController.Appearance(isBlurred: true), action: { _ in true })
self.controllerInteraction?.presentController(undoController, nil) self.controllerInteraction?.presentController(undoController, nil)
case .share: case .share:

View File

@ -62,6 +62,10 @@
- (void)setImage:(UIImage *)image thumbnailImage:(UIImage *)thumbnailImage forItem:(id<TGMediaEditableItem>)item synchronous:(bool)synchronous; - (void)setImage:(UIImage *)image thumbnailImage:(UIImage *)thumbnailImage forItem:(id<TGMediaEditableItem>)item synchronous:(bool)synchronous;
- (void)setFullSizeImage:(UIImage *)image forItem:(id<TGMediaEditableItem>)item; - (void)setFullSizeImage:(UIImage *)image forItem:(id<TGMediaEditableItem>)item;
- (SSignal *)coverImageSignalForItem:(NSObject<TGMediaEditableItem> *)item;
- (void)setCoverImage:(UIImage *)image forItem:(id<TGMediaEditableItem>)item;
- (UIImage *)coverImageForItem:(NSObject<TGMediaEditableItem> *)item;
- (void)setTemporaryRep:(id)rep forItem:(id<TGMediaEditableItem>)item; - (void)setTemporaryRep:(id)rep forItem:(id<TGMediaEditableItem>)item;
- (SSignal *)fullSizeImageUrlForItem:(id<TGMediaEditableItem>)item; - (SSignal *)fullSizeImageUrlForItem:(id<TGMediaEditableItem>)item;

View File

@ -31,6 +31,9 @@
- (void)prepareForEditing; - (void)prepareForEditing;
- (void)returnFromEditing; - (void)returnFromEditing;
- (void)prepareForCoverEditing;
- (void)returnFromCoverEditing;
- (UIImage *)screenImage; - (UIImage *)screenImage;
- (UIImage *)transitionImage; - (UIImage *)transitionImage;
- (CGRect)editorTransitionViewRect; - (CGRect)editorTransitionViewRect;

View File

@ -120,6 +120,8 @@
@property (nonatomic, copy) id<TGCaptionPanelView> _Nullable(^ _Nullable captionPanelView)(void); @property (nonatomic, copy) id<TGCaptionPanelView> _Nullable(^ _Nullable captionPanelView)(void);
@property (nonatomic, copy) void (^ _Nullable editCover)(CGSize dimensions, void(^_Nonnull completion)(UIImage * _Nonnull));
- (UIView<TGPhotoSolidRoundedButtonView> *_Nonnull)solidRoundedButton:(NSString *_Nonnull)title action:(void(^_Nonnull)(void))action; - (UIView<TGPhotoSolidRoundedButtonView> *_Nonnull)solidRoundedButton:(NSString *_Nonnull)title action:(void(^_Nonnull)(void))action;
- (id<TGPhotoDrawingAdapter> _Nonnull)drawingAdapter:(CGSize)size originalSize:(CGSize)originalSize isVideo:(bool)isVideo isAvatar:(bool)isAvatar entitiesView:(UIView<TGPhotoDrawingEntitiesView> * _Nullable)entitiesView; - (id<TGPhotoDrawingAdapter> _Nonnull)drawingAdapter:(CGSize)size originalSize:(CGSize)originalSize isVideo:(bool)isVideo isAvatar:(bool)isAvatar entitiesView:(UIView<TGPhotoDrawingEntitiesView> * _Nullable)entitiesView;

View File

@ -1331,6 +1331,8 @@
CGSize dimensions = [TGMediaVideoConverter dimensionsFor:asset.originalSize adjustments:adjustments preset:preset]; CGSize dimensions = [TGMediaVideoConverter dimensionsFor:asset.originalSize adjustments:adjustments preset:preset];
NSTimeInterval duration = adjustments.trimApplied ? (adjustments.trimEndValue - adjustments.trimStartValue) : asset.videoDuration; NSTimeInterval duration = adjustments.trimApplied ? (adjustments.trimEndValue - adjustments.trimStartValue) : asset.videoDuration;
UIImage *coverImage = [editingContext coverImageForItem:asset];
[signals addObject:[thumbnailSignal map:^id(UIImage *image) [signals addObject:[thumbnailSignal map:^id(UIImage *image)
{ {
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
@ -1341,6 +1343,7 @@
dict[@"adjustments"] = adjustments; dict[@"adjustments"] = adjustments;
dict[@"dimensions"] = [NSValue valueWithCGSize:dimensions]; dict[@"dimensions"] = [NSValue valueWithCGSize:dimensions];
dict[@"duration"] = @(duration); dict[@"duration"] = @(duration);
dict[@"coverImage"] = coverImage;
if (adjustments.paintingData.stickers.count > 0) if (adjustments.paintingData.stickers.count > 0)
dict[@"stickers"] = adjustments.paintingData.stickers; dict[@"stickers"] = adjustments.paintingData.stickers;

View File

@ -106,6 +106,8 @@
TGMemoryImageCache *_originalImageCache; TGMemoryImageCache *_originalImageCache;
TGMemoryImageCache *_originalThumbnailImageCache; TGMemoryImageCache *_originalThumbnailImageCache;
TGMemoryImageCache *_coverImageCache;
TGModernCache *_diskCache; TGModernCache *_diskCache;
NSURL *_fullSizeResultsUrl; NSURL *_fullSizeResultsUrl;
NSURL *_paintingDatasUrl; NSURL *_paintingDatasUrl;
@ -119,6 +121,7 @@
SPipe *_representationPipe; SPipe *_representationPipe;
SPipe *_thumbnailImagePipe; SPipe *_thumbnailImagePipe;
SPipe *_coverImagePipe;
SPipe *_adjustmentsPipe; SPipe *_adjustmentsPipe;
SPipe *_captionPipe; SPipe *_captionPipe;
SPipe *_timerPipe; SPipe *_timerPipe;
@ -166,6 +169,9 @@
_originalThumbnailImageCache = [[TGMemoryImageCache alloc] initWithSoftMemoryLimit:[[self class] thumbnailImageSoftMemoryLimit] _originalThumbnailImageCache = [[TGMemoryImageCache alloc] initWithSoftMemoryLimit:[[self class] thumbnailImageSoftMemoryLimit]
hardMemoryLimit:[[self class] thumbnailImageHardMemoryLimit]]; hardMemoryLimit:[[self class] thumbnailImageHardMemoryLimit]];
_coverImageCache = [[TGMemoryImageCache alloc] initWithSoftMemoryLimit:[[self class] thumbnailImageSoftMemoryLimit] * 10
hardMemoryLimit:[[self class] thumbnailImageHardMemoryLimit] * 10];
NSString *diskCachePath = [[[LegacyComponentsGlobals provider] dataStoragePath] stringByAppendingPathComponent:[[self class] diskCachePath]]; NSString *diskCachePath = [[[LegacyComponentsGlobals provider] dataStoragePath] stringByAppendingPathComponent:[[self class] diskCachePath]];
_diskCache = [[TGModernCache alloc] initWithPath:diskCachePath size:[[self class] diskMemoryLimit]]; _diskCache = [[TGModernCache alloc] initWithPath:diskCachePath size:[[self class] diskMemoryLimit]];
@ -194,6 +200,7 @@
_thumbnailImagePipe = [[SPipe alloc] init]; _thumbnailImagePipe = [[SPipe alloc] init];
_adjustmentsPipe = [[SPipe alloc] init]; _adjustmentsPipe = [[SPipe alloc] init];
_captionPipe = [[SPipe alloc] init]; _captionPipe = [[SPipe alloc] init];
_coverImagePipe = [[SPipe alloc] init];
_timerPipe = [[SPipe alloc] init]; _timerPipe = [[SPipe alloc] init];
_spoilerPipe = [[SPipe alloc] init]; _spoilerPipe = [[SPipe alloc] init];
_pricePipe = [[SPipe alloc] init]; _pricePipe = [[SPipe alloc] init];
@ -908,6 +915,46 @@
[_faces removeObjectForKey:itemId]; [_faces removeObjectForKey:itemId];
} }
- (SSignal *)coverImageSignalForIdentifier:(NSString *)identifier
{
NSString *itemId = [TGMediaEditingContext _coverImageUriForItemId:identifier];
if (itemId == nil)
return [SSignal fail:nil];
SSignal *updateSignal = [[_coverImagePipe.signalProducer() filter:^bool(TGMediaImageUpdate *update)
{
return [update.item.uniqueIdentifier isEqualToString:identifier];
}] map:^id(TGMediaImageUpdate *update)
{
return update.representation;
}];
return [[SSignal single:[_coverImageCache imageForKey:itemId attributes:NULL]]
then:updateSignal];
}
- (SSignal *)coverImageSignalForItem:(NSObject<TGMediaEditableItem> *)item {
return [self coverImageSignalForIdentifier:item.uniqueIdentifier];
}
- (UIImage *)coverImageForItem:(NSObject<TGMediaEditableItem> *)item {
NSString *itemId = [TGMediaEditingContext _coverImageUriForItemId:item.uniqueIdentifier];
if (itemId == nil)
return nil;
return [_coverImageCache imageForKey:itemId attributes:NULL];
}
- (void)setCoverImage:(UIImage *)image forItem:(id<TGMediaEditableItem>)item
{
NSString *itemId = [TGMediaEditingContext _coverImageUriForItemId:item.uniqueIdentifier];
if (itemId == nil)
return;
[_coverImageCache setImage:image forKey:itemId attributes:NULL];
_coverImagePipe.sink([TGMediaImageUpdate imageUpdateWithItem:item representation:image]);
}
- (void)setFullSizeImage:(UIImage *)image forItem:(id<TGMediaEditableItem>)item - (void)setFullSizeImage:(UIImage *)image forItem:(id<TGMediaEditableItem>)item
{ {
NSString *itemId = [self _contextualIdForItemId:item.uniqueIdentifier]; NSString *itemId = [self _contextualIdForItemId:item.uniqueIdentifier];
@ -1167,6 +1214,11 @@
return [NSString stringWithFormat:@"%@://%@", [self thumbnailImageUriScheme], itemId]; return [NSString stringWithFormat:@"%@://%@", [self thumbnailImageUriScheme], itemId];
} }
+ (NSString *)_coverImageUriForItemId:(NSString *)itemId
{
return [NSString stringWithFormat:@"%@://%@", @"photo-editor-cover", itemId];
}
#pragma mark - Constants #pragma mark - Constants
+ (NSString *)imageUriScheme + (NSString *)imageUriScheme

View File

@ -92,10 +92,17 @@
TGMediaPickerGroupButton *_groupButton; TGMediaPickerGroupButton *_groupButton;
TGMediaPickerCameraButton *_cameraButton; TGMediaPickerCameraButton *_cameraButton;
TGMediaPickerCoverButton *_coverButton;
TGModernButton *_cancelCoverButton;
TGModernButton *_saveCoverButton;
TGMediaPickerCoverButton *_coverGalleryButton;
UILabel *_coverTitleLabel;
TGMediaPickerPhotoStripView *_selectedPhotosView; TGMediaPickerPhotoStripView *_selectedPhotosView;
SMetaDisposable *_adjustmentsDisposable; SMetaDisposable *_adjustmentsDisposable;
SMetaDisposable *_captionDisposable; SMetaDisposable *_captionDisposable;
SMetaDisposable *_coverDisposable;
SMetaDisposable *_itemAvailabilityDisposable; SMetaDisposable *_itemAvailabilityDisposable;
SMetaDisposable *_itemSelectedDisposable; SMetaDisposable *_itemSelectedDisposable;
id<SDisposable> _selectionChangedDisposable; id<SDisposable> _selectionChangedDisposable;
@ -136,6 +143,7 @@
_adjustmentsDisposable = [[SMetaDisposable alloc] init]; _adjustmentsDisposable = [[SMetaDisposable alloc] init];
_captionDisposable = [[SMetaDisposable alloc] init]; _captionDisposable = [[SMetaDisposable alloc] init];
_coverDisposable = [[SMetaDisposable alloc] init];
_itemSelectedDisposable = [[SMetaDisposable alloc] init]; _itemSelectedDisposable = [[SMetaDisposable alloc] init];
_itemAvailabilityDisposable = [[SMetaDisposable alloc] init]; _itemAvailabilityDisposable = [[SMetaDisposable alloc] init];
_tooltipDismissDisposable = [[SMetaDisposable alloc] init]; _tooltipDismissDisposable = [[SMetaDisposable alloc] init];
@ -200,7 +208,7 @@
[[NSUserDefaults standardUserDefaults] setObject:@(3) forKey:@"TG_displayedMediaTimerTooltip_v3"]; [[NSUserDefaults standardUserDefaults] setObject:@(3) forKey:@"TG_displayedMediaTimerTooltip_v3"];
}; };
_muteButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0, 0, 39.0f, 39.0f)]; _muteButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0, 0, 40.0f, 40.0f)];
_muteButton.hidden = true; _muteButton.hidden = true;
_muteButton.adjustsImageWhenHighlighted = false; _muteButton.adjustsImageWhenHighlighted = false;
[_muteButton setBackgroundImage:[TGPhotoEditorInterfaceAssets gifBackgroundImage] forState:UIControlStateNormal]; [_muteButton setBackgroundImage:[TGPhotoEditorInterfaceAssets gifBackgroundImage] forState:UIControlStateNormal];
@ -239,6 +247,16 @@
// [_cameraButton setHidden:true animated:false]; // [_cameraButton setHidden:true animated:false];
} }
_coverButton = [[TGMediaPickerCoverButton alloc] initWithFrame:CGRectMake(0, 0, 120, 26) gallery:false];
_coverButton.hidden = true;
[_coverButton addTarget:self action:@selector(coverButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[_wrapperView addSubview:_coverButton];
_coverGalleryButton = [[TGMediaPickerCoverButton alloc] initWithFrame:CGRectMake(0, 0, 120, 26) gallery:true];
_coverGalleryButton.hidden = true;
[_coverGalleryButton addTarget:self action:@selector(coverGalleryButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[_wrapperView addSubview:_coverGalleryButton];
if (_selectionContext != nil) if (_selectionContext != nil)
{ {
_checkButton = [[TGCheckButtonView alloc] initWithStyle:TGCheckButtonStyleGallery]; _checkButton = [[TGCheckButtonView alloc] initWithStyle:TGCheckButtonStyleGallery];
@ -423,6 +441,34 @@
if ([UIDevice currentDevice].userInterfaceIdiom != UIUserInterfaceIdiomPad) if ([UIDevice currentDevice].userInterfaceIdiom != UIUserInterfaceIdiomPad)
[_wrapperView addSubview:_landscapeToolbarView]; [_wrapperView addSubview:_landscapeToolbarView];
_cancelCoverButton = [[TGModernButton alloc] init];
_cancelCoverButton.hidden = true;
_cancelCoverButton.titleLabel.font = TGSystemFontOfSize(17.0);
[_cancelCoverButton setTitle:TGLocalized(@"Common.Cancel") forState:UIControlStateNormal];
[_cancelCoverButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[_cancelCoverButton addTarget:self action:@selector(cancelCoverButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[_cancelCoverButton sizeToFit];
[_wrapperView addSubview:_cancelCoverButton];
_coverTitleLabel = [[UILabel alloc] init];
_coverTitleLabel.hidden = true;
_coverTitleLabel.textColor = [UIColor whiteColor];
_coverTitleLabel.font = TGBoldSystemFontOfSize(17.0);
_coverTitleLabel.text = TGLocalized(@"Media.SelectFrame");
[_coverTitleLabel sizeToFit];
[_wrapperView addSubview:_coverTitleLabel];
_saveCoverButton = [[TGModernButton alloc] init];
_saveCoverButton.clipsToBounds = true;
_saveCoverButton.layer.cornerRadius = 10.0;
_saveCoverButton.hidden = true;
[_saveCoverButton setBackgroundColor:UIColorRGB(0x007aff)];
_saveCoverButton.titleLabel.font = TGBoldSystemFontOfSize(17.0);
[_saveCoverButton setTitle:TGLocalized(@"Media.SaveCover") forState:UIControlStateNormal];
[_saveCoverButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[_saveCoverButton addTarget:self action:@selector(saveCoverButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[_wrapperView addSubview:_saveCoverButton];
} }
return self; return self;
} }
@ -433,6 +479,7 @@
[_adjustmentsDisposable dispose]; [_adjustmentsDisposable dispose];
[_captionDisposable dispose]; [_captionDisposable dispose];
[_coverDisposable dispose];
[_itemSelectedDisposable dispose]; [_itemSelectedDisposable dispose];
[_itemAvailabilityDisposable dispose]; [_itemAvailabilityDisposable dispose];
[_selectionChangedDisposable dispose]; [_selectionChangedDisposable dispose];
@ -630,6 +677,9 @@
} }
} }
strongSelf->_muteButton.hidden = !sendableAsGif; strongSelf->_muteButton.hidden = !sendableAsGif;
bool canHaveCover = [strongItemView isKindOfClass:[TGMediaPickerGalleryVideoItemView class]];
strongSelf->_coverButton.hidden = !canHaveCover;
} }
} file:__FILE_NAME__ line:__LINE__]]; } file:__FILE_NAME__ line:__LINE__]];
@ -777,6 +827,119 @@
[self updateGroupingButtonVisibility]; [self updateGroupingButtonVisibility];
} }
- (void)coverButtonPressed
{
[self coverEditorTransitionIn];
}
- (void)coverEditorTransitionIn {
[self setAllInterfaceHidden:true keepHeader:true delay:0.0 animated:true];
_coverTitleLabel.hidden = false;
_cancelCoverButton.hidden = false;
_saveCoverButton.hidden = false;
_coverGalleryButton.hidden = false;
_coverTitleLabel.alpha = 0.0;
_cancelCoverButton.alpha = 0.0;
_saveCoverButton.alpha = 0.0;
_coverGalleryButton.alpha = 0.0;
[UIView animateWithDuration:0.2 animations:^{
_coverTitleLabel.alpha = 1.0;
_cancelCoverButton.alpha = 1.0;
_saveCoverButton.alpha = 1.0;
_coverGalleryButton.alpha = 1.0;
for (UIView *headerView in _itemHeaderViews) {
if ([headerView isKindOfClass:[TGMediaPickerScrubberHeaderView class]]) {
((TGMediaPickerScrubberHeaderView *)headerView).scrubberView.alpha = 0.0f;
((TGMediaPickerScrubberHeaderView *)headerView).coverScrubberView.alpha = 1.0f;
}
}
}];
TGModernGalleryItemView *currentItemView = _currentItemView;
if ([currentItemView isKindOfClass:[TGMediaPickerGalleryVideoItemView class]]) {
[(TGMediaPickerGalleryVideoItemView *)currentItemView prepareForCoverEditing];
}
}
- (void)coverEditorTransitionOut {
[self setAllInterfaceHidden:false keepHeader:true delay:0.0 animated:true];
[_cancelCoverButton.layer removeAllAnimations];
[_saveCoverButton.layer removeAllAnimations];
[UIView animateWithDuration:0.3 animations:^{
_coverTitleLabel.alpha = 0.0;
_cancelCoverButton.alpha = 0.0;
_saveCoverButton.alpha = 0.0;
_coverGalleryButton.alpha = 0.0;
for (UIView *headerView in _itemHeaderViews) {
if ([headerView isKindOfClass:[TGMediaPickerScrubberHeaderView class]]) {
((TGMediaPickerScrubberHeaderView *)headerView).scrubberView.alpha = 1.0f;
((TGMediaPickerScrubberHeaderView *)headerView).coverScrubberView.alpha = 0.0f;
}
}
} completion:^(BOOL finished) {
_coverTitleLabel.hidden = true;
_cancelCoverButton.hidden = true;
_saveCoverButton.hidden = true;
_coverGalleryButton.hidden = true;
}];
TGModernGalleryItemView *currentItemView = _currentItemView;
if ([currentItemView isKindOfClass:[TGMediaPickerGalleryVideoItemView class]]) {
[(TGMediaPickerGalleryVideoItemView *)currentItemView returnFromCoverEditing];
}
}
- (void)cancelCoverButtonPressed
{
TGDispatchAfter(0.01, dispatch_get_main_queue(), ^{
[self coverEditorTransitionOut];
});
}
- (void)saveCoverButtonPressed
{
id<TGModernGalleryEditableItem> galleryEditableItem = (id<TGModernGalleryEditableItem>)_currentItem;
TGModernGalleryItemView *currentItemView = _currentItemView;
if ([currentItemView isKindOfClass:[TGMediaPickerGalleryVideoItemView class]]) {
id<TGMediaEditableItem> editableMediaItem = [galleryEditableItem editableMediaItem];
[_editingContext setCoverImage:[(TGMediaPickerGalleryVideoItemView *)currentItemView screenImage] forItem:editableMediaItem];
}
TGDispatchAfter(0.01, dispatch_get_main_queue(), ^{
[self coverEditorTransitionOut];
});
}
- (void)coverGalleryButtonPressed {
if (_currentItem == nil)
return;
id<TGModernGalleryEditableItem> galleryEditableItem = (id<TGModernGalleryEditableItem>)_currentItem;
if ([_currentItem conformsToProtocol:@protocol(TGModernGalleryEditableItem)])
{
id<TGMediaEditableItem> editableMediaItem = [galleryEditableItem editableMediaItem];
CGSize originalSize = CGSizeZero;
if ([editableMediaItem respondsToSelector:@selector(originalSize)])
originalSize = editableMediaItem.originalSize;
__weak TGMediaPickerGalleryInterfaceView *weakSelf = self;
_captionMixin.stickersContext.editCover(originalSize, ^(UIImage *cover){
__strong TGMediaPickerGalleryInterfaceView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[strongSelf->_editingContext setCoverImage:cover forItem:editableMediaItem];
[strongSelf coverEditorTransitionOut];
});
}
}
- (void)updateEditorButtonsForItem:(id<TGModernGalleryItem>)item animated:(bool)animated - (void)updateEditorButtonsForItem:(id<TGModernGalleryItem>)item animated:(bool)animated
{ {
__weak TGMediaPickerGalleryInterfaceView *weakSelf = self; __weak TGMediaPickerGalleryInterfaceView *weakSelf = self;
@ -791,6 +954,14 @@
return; return;
[strongSelf->_captionMixin setCaption:caption animated:animated]; [strongSelf->_captionMixin setCaption:caption animated:animated];
} file:__FILE_NAME__ line:__LINE__]]; } file:__FILE_NAME__ line:__LINE__]];
[_coverDisposable setDisposable:[[galleryEditableItem.editingContext coverImageSignalForItem:editableMediaItem] startStrictWithNext:^(UIImage *cover)
{
__strong TGMediaPickerGalleryInterfaceView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[strongSelf->_coverButton setImage:cover];
} file:__FILE_NAME__ line:__LINE__]];
} }
if (_editingContext == nil || _editingContext.inhibitEditing) if (_editingContext == nil || _editingContext.inhibitEditing)
@ -938,7 +1109,6 @@
qualityButton.iconImage = icon; qualityButton.iconImage = icon;
} }
bool willShowTimerTooltip = false;
TGPhotoEditorButton *timerButton = [_portraitToolbarView buttonForTab:TGPhotoEditorTimerTab]; TGPhotoEditorButton *timerButton = [_portraitToolbarView buttonForTab:TGPhotoEditorTimerTab];
if (timerButton != nil) if (timerButton != nil)
{ {
@ -959,7 +1129,6 @@
if ([self shouldDisplayTooltip]) if ([self shouldDisplayTooltip])
{ {
willShowTimerTooltip = true;
TGDispatchAfter(0.5, dispatch_get_main_queue(), ^ TGDispatchAfter(0.5, dispatch_get_main_queue(), ^
{ {
if (!TGIsPad() && self.frame.size.width > self.frame.size.height) if (!TGIsPad() && self.frame.size.width > self.frame.size.height)
@ -1098,6 +1267,7 @@
{ {
_checkButton.alpha = alpha; _checkButton.alpha = alpha;
_muteButton.alpha = alpha; _muteButton.alpha = alpha;
_coverButton.alpha = alpha;
_arrowView.alpha = alpha * 0.6f; _arrowView.alpha = alpha * 0.6f;
_recipientLabel.alpha = alpha * 0.6; _recipientLabel.alpha = alpha * 0.6;
} completion:^(BOOL finished) } completion:^(BOOL finished)
@ -1106,6 +1276,7 @@
{ {
_checkButton.userInteractionEnabled = !hidden; _checkButton.userInteractionEnabled = !hidden;
_muteButton.userInteractionEnabled = !hidden; _muteButton.userInteractionEnabled = !hidden;
_coverButton.userInteractionEnabled = !hidden;
} }
}]; }];
@ -1129,6 +1300,9 @@
_muteButton.alpha = alpha; _muteButton.alpha = alpha;
_muteButton.userInteractionEnabled = !hidden; _muteButton.userInteractionEnabled = !hidden;
_coverButton.alpha = alpha;
_coverButton.userInteractionEnabled = !hidden;
_arrowView.alpha = alpha * 0.6f; _arrowView.alpha = alpha * 0.6f;
_recipientLabel.alpha = alpha * 0.6; _recipientLabel.alpha = alpha * 0.6;
} }
@ -1145,7 +1319,11 @@
[_groupButton setHidden:true animated:animated]; [_groupButton setHidden:true animated:animated];
} }
- (void)setAllInterfaceHidden:(bool)hidden delay:(NSTimeInterval)__unused delay animated:(bool)animated - (void)setAllInterfaceHidden:(bool)hidden delay:(NSTimeInterval)delay animated:(bool)animated {
[self setAllInterfaceHidden:hidden keepHeader:false delay:delay animated:animated];
}
- (void)setAllInterfaceHidden:(bool)hidden keepHeader:(bool)keepHeader delay:(NSTimeInterval)__unused delay animated:(bool)animated
{ {
CGFloat alpha = (hidden ? 0.0f : 1.0f); CGFloat alpha = (hidden ? 0.0f : 1.0f);
if (animated) if (animated)
@ -1154,8 +1332,9 @@
{ {
_checkButton.alpha = alpha; _checkButton.alpha = alpha;
_muteButton.alpha = alpha; _muteButton.alpha = alpha;
_coverButton.alpha = alpha;
_arrowView.alpha = alpha * 0.6; _arrowView.alpha = alpha * 0.6;
_recipientLabel.alpha = alpha; _recipientLabel.alpha = alpha * 0.6;
_portraitToolbarView.alpha = alpha; _portraitToolbarView.alpha = alpha;
_landscapeToolbarView.alpha = alpha; _landscapeToolbarView.alpha = alpha;
_captionMixin.inputPanelView.alpha = alpha; _captionMixin.inputPanelView.alpha = alpha;
@ -1166,6 +1345,7 @@
{ {
_checkButton.userInteractionEnabled = !hidden; _checkButton.userInteractionEnabled = !hidden;
_muteButton.userInteractionEnabled = !hidden; _muteButton.userInteractionEnabled = !hidden;
_coverButton.userInteractionEnabled = !hidden;
_portraitToolbarView.userInteractionEnabled = !hidden; _portraitToolbarView.userInteractionEnabled = !hidden;
_landscapeToolbarView.userInteractionEnabled = !hidden; _landscapeToolbarView.userInteractionEnabled = !hidden;
_captionMixin.inputPanelView.userInteractionEnabled = !hidden; _captionMixin.inputPanelView.userInteractionEnabled = !hidden;
@ -1193,6 +1373,9 @@
_muteButton.alpha = alpha; _muteButton.alpha = alpha;
_muteButton.userInteractionEnabled = !hidden; _muteButton.userInteractionEnabled = !hidden;
_coverButton.alpha = alpha;
_coverButton.userInteractionEnabled = !hidden;
_arrowView.alpha = alpha * 0.6; _arrowView.alpha = alpha * 0.6;
_recipientLabel.alpha = alpha; _recipientLabel.alpha = alpha;
@ -1219,7 +1402,7 @@
if (!_groupButton.hidden) if (!_groupButton.hidden)
[_groupButton setHidden:true animated:animated]; [_groupButton setHidden:true animated:animated];
[self setItemHeaderViewHidden:hidden animated:animated]; [self setItemHeaderViewHidden:!keepHeader && hidden animated:animated];
} }
#pragma mark - #pragma mark -
@ -1431,6 +1614,10 @@
|| view == _muteButton || view == _muteButton
|| view == _groupButton || view == _groupButton
|| view == _cameraButton || view == _cameraButton
|| view == _coverButton
|| view == _cancelCoverButton
|| view == _saveCoverButton
|| view == _coverGalleryButton
|| [view isDescendantOfView:_headerWrapperView] || [view isDescendantOfView:_headerWrapperView]
|| [view isDescendantOfView:_portraitToolbarView] || [view isDescendantOfView:_portraitToolbarView]
|| [view isDescendantOfView:_landscapeToolbarView] || [view isDescendantOfView:_landscapeToolbarView]
@ -1492,7 +1679,7 @@
break; break;
default: default:
frame = CGRectMake(screenEdges.left + 5, screenEdges.bottom - TGPhotoEditorToolbarSize - [_captionMixin.inputPanel baseHeight] - 45 - _safeAreaInset.bottom - panelInset - (hasHeaderView ? 64.0 : 0.0), _muteButton.frame.size.width, _muteButton.frame.size.height); frame = CGRectMake(screenEdges.left + 5, screenEdges.bottom - TGPhotoEditorToolbarSize - [_captionMixin.inputPanel baseHeight] - 26 - _safeAreaInset.bottom - panelInset - (hasHeaderView ? 64.0 : 0.0), _muteButton.frame.size.width, _muteButton.frame.size.height);
break; break;
} }
@ -1526,6 +1713,34 @@
return frame; return frame;
} }
- (CGRect)_coverButtonFrameForOrientation:(UIInterfaceOrientation)orientation screenEdges:(UIEdgeInsets)screenEdges hasHeaderView:(bool)hasHeaderView
{
CGRect frame = CGRectZero;
if (_safeAreaInset.top > 20.0f)
screenEdges.top += _safeAreaInset.top;
screenEdges.left += _safeAreaInset.left;
screenEdges.right -= _safeAreaInset.right;
screenEdges.bottom -= _safeAreaInset.bottom;
CGFloat panelInset = 0.0f;
frame = CGRectMake(screenEdges.left + ((screenEdges.right - screenEdges.left) - _coverButton.frame.size.width) / 2.0, screenEdges.bottom - TGPhotoEditorToolbarSize - [_captionMixin.inputPanel baseHeight] - 49 - _safeAreaInset.bottom - panelInset, _coverButton.frame.size.width, _coverButton.frame.size.height);
return frame;
}
- (CGRect)_coverGalleryButtonFrameForOrientation:(UIInterfaceOrientation)orientation screenEdges:(UIEdgeInsets)screenEdges hasHeaderView:(bool)hasHeaderView
{
CGRect frame = CGRectZero;
if (_safeAreaInset.top > 20.0f)
screenEdges.top += _safeAreaInset.top;
screenEdges.left += _safeAreaInset.left;
screenEdges.right -= _safeAreaInset.right;
screenEdges.bottom -= _safeAreaInset.bottom;
CGFloat panelInset = 0.0f;
frame = CGRectMake(screenEdges.left + ((screenEdges.right - screenEdges.left) - _coverButton.frame.size.width) / 2.0, screenEdges.bottom - TGPhotoEditorToolbarSize - [_captionMixin.inputPanel baseHeight] - 33 - _safeAreaInset.bottom - panelInset, _coverButton.frame.size.width, _coverButton.frame.size.height);
return frame;
}
- (CGRect)_checkButtonFrameForOrientation:(UIInterfaceOrientation)orientation screenEdges:(UIEdgeInsets)screenEdges hasHeaderView:(bool)hasHeaderView - (CGRect)_checkButtonFrameForOrientation:(UIInterfaceOrientation)orientation screenEdges:(UIEdgeInsets)screenEdges hasHeaderView:(bool)hasHeaderView
{ {
CGRect frame = CGRectZero; CGRect frame = CGRectZero;
@ -1712,6 +1927,14 @@
portraitToolbarViewBottomEdge = screenEdges.bottom; portraitToolbarViewBottomEdge = screenEdges.bottom;
_portraitToolbarView.frame = CGRectMake(screenEdges.left, portraitToolbarViewBottomEdge - TGPhotoEditorToolbarSize - _safeAreaInset.bottom, self.frame.size.width, TGPhotoEditorToolbarSize + _safeAreaInset.bottom); _portraitToolbarView.frame = CGRectMake(screenEdges.left, portraitToolbarViewBottomEdge - TGPhotoEditorToolbarSize - _safeAreaInset.bottom, self.frame.size.width, TGPhotoEditorToolbarSize + _safeAreaInset.bottom);
CGFloat coverTitleTopY = screenEdges.top;
if (_safeAreaInset.top > 20.0f + FLT_EPSILON)
coverTitleTopY += _safeAreaInset.top;
_saveCoverButton.frame = CGRectMake(screenEdges.left + 16.0, portraitToolbarViewBottomEdge - 50.0 - 16.0 - _safeAreaInset.bottom, self.frame.size.width - 16.0 * 2.0, 50.0);
_cancelCoverButton.frame = CGRectMake(screenEdges.left + 16.0, coverTitleTopY + 20, _cancelCoverButton.frame.size.width, _cancelCoverButton.frame.size.height);
_coverTitleLabel.frame = CGRectMake(screenEdges.left + floor((self.frame.size.width - _coverTitleLabel.frame.size.width) / 2.0), coverTitleTopY + 26, _coverTitleLabel.frame.size.width, _coverTitleLabel.frame.size.height);
UIEdgeInsets captionEdgeInsets = screenEdges; UIEdgeInsets captionEdgeInsets = screenEdges;
captionEdgeInsets.bottom = _portraitToolbarView.frame.size.height; captionEdgeInsets.bottom = _portraitToolbarView.frame.size.height;
[_captionMixin updateLayoutWithFrame:self.bounds edgeInsets:captionEdgeInsets animated:false]; [_captionMixin updateLayoutWithFrame:self.bounds edgeInsets:captionEdgeInsets animated:false];
@ -1752,9 +1975,9 @@
{ {
[UIView performWithoutAnimation:^ [UIView performWithoutAnimation:^
{ {
_photoCounterButton.frame = CGRectMake(screenEdges.right - 56 - _safeAreaInset.right, screenEdges.bottom - TGPhotoEditorToolbarSize - [_captionMixin.inputPanel baseHeight] - 40 - _safeAreaInset.bottom - (hasHeaderView ? 64.0 : 0.0), 64, 38); _photoCounterButton.frame = CGRectMake(screenEdges.right - 56 - _safeAreaInset.right, screenEdges.bottom - TGPhotoEditorToolbarSize - [_captionMixin.inputPanel baseHeight] - 22 - _safeAreaInset.bottom - (hasHeaderView ? 64.0 : 0.0), 64, 38);
_selectedPhotosView.frame = CGRectMake(screenEdges.left + 4, screenEdges.bottom - TGPhotoEditorToolbarSize - [_captionMixin.inputPanel baseHeight] - photosViewSize - 54 - _safeAreaInset.bottom - (hasHeaderView ? 64.0 : 0.0), self.frame.size.width - 4 * 2 - _safeAreaInset.right, photosViewSize); _selectedPhotosView.frame = CGRectMake(screenEdges.left + 4, screenEdges.bottom - TGPhotoEditorToolbarSize - [_captionMixin.inputPanel baseHeight] - photosViewSize - 36 - _safeAreaInset.bottom - (hasHeaderView ? 64.0 : 0.0), self.frame.size.width - 4 * 2 - _safeAreaInset.right, photosViewSize);
}]; }];
_landscapeToolbarView.frame = CGRectMake(_landscapeToolbarView.frame.origin.x, screenEdges.top, TGPhotoEditorToolbarSize, self.frame.size.height); _landscapeToolbarView.frame = CGRectMake(_landscapeToolbarView.frame.origin.x, screenEdges.top, TGPhotoEditorToolbarSize, self.frame.size.height);
@ -1767,6 +1990,9 @@
_muteButton.frame = [self _muteButtonFrameForOrientation:orientation screenEdges:screenEdges hasHeaderView:true]; _muteButton.frame = [self _muteButtonFrameForOrientation:orientation screenEdges:screenEdges hasHeaderView:true];
_checkButton.frame = [self _checkButtonFrameForOrientation:orientation screenEdges:screenEdges hasHeaderView:hasHeaderView]; _checkButton.frame = [self _checkButtonFrameForOrientation:orientation screenEdges:screenEdges hasHeaderView:hasHeaderView];
_groupButton.frame = [self _groupButtonFrameForOrientation:orientation screenEdges:screenEdges hasHeaderView:hasHeaderView]; _groupButton.frame = [self _groupButtonFrameForOrientation:orientation screenEdges:screenEdges hasHeaderView:hasHeaderView];
_coverButton.frame = [self _coverButtonFrameForOrientation:orientation screenEdges:screenEdges hasHeaderView:hasHeaderView];
_coverGalleryButton.frame = [self _coverGalleryButtonFrameForOrientation:orientation screenEdges:screenEdges hasHeaderView:hasHeaderView];
[UIView performWithoutAnimation:^ [UIView performWithoutAnimation:^
{ {
_cameraButton.frame = [self _cameraButtonFrameForOrientation:orientation screenEdges:screenEdges hasHeaderView:hasHeaderView panelVisible:_selectedPhotosView != nil && !_selectedPhotosView.isInternalHidden]; _cameraButton.frame = [self _cameraButtonFrameForOrientation:orientation screenEdges:screenEdges hasHeaderView:hasHeaderView panelVisible:_selectedPhotosView != nil && !_selectedPhotosView.isInternalHidden];

View File

@ -67,6 +67,9 @@
UIView *_headerView; UIView *_headerView;
UIView *_scrubberPanelView; UIView *_scrubberPanelView;
TGMediaPickerGalleryVideoScrubber *_scrubberView; TGMediaPickerGalleryVideoScrubber *_scrubberView;
TGMediaPickerGalleryVideoScrubber *_coverScrubberView;
bool _wasPlayingBeforeScrubbing; bool _wasPlayingBeforeScrubbing;
bool _appeared; bool _appeared;
bool _scrubbingPanelPresented; bool _scrubbingPanelPresented;
@ -225,13 +228,21 @@
//scrubberBackgroundView.backgroundColor = [TGPhotoEditorInterfaceAssets toolbarTransparentBackgroundColor]; //scrubberBackgroundView.backgroundColor = [TGPhotoEditorInterfaceAssets toolbarTransparentBackgroundColor];
[_scrubberPanelView addSubview:scrubberBackgroundView]; [_scrubberPanelView addSubview:scrubberBackgroundView];
_scrubberView = [[TGMediaPickerGalleryVideoScrubber alloc] initWithFrame:CGRectMake(0.0f, _headerView.frame.size.height - 44.0f, _headerView.frame.size.width, 68.0f)]; _scrubberView = [[TGMediaPickerGalleryVideoScrubber alloc] initWithFrame:CGRectMake(0.0f, _headerView.frame.size.height - 44.0f, _headerView.frame.size.width, 68.0f) cover:false];
_scrubberView.autoresizingMask = UIViewAutoresizingFlexibleWidth; _scrubberView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
_scrubberView.dataSource = self; _scrubberView.dataSource = self;
_scrubberView.delegate = self; _scrubberView.delegate = self;
headerView.scrubberView = _scrubberView; headerView.scrubberView = _scrubberView;
[_scrubberPanelView addSubview:_scrubberView]; [_scrubberPanelView addSubview:_scrubberView];
_coverScrubberView = [[TGMediaPickerGalleryVideoScrubber alloc] initWithFrame:CGRectMake(0.0f, _headerView.frame.size.height - 44.0f, _headerView.frame.size.width, 68.0f) cover:true];
_coverScrubberView.alpha = 0.0;
_coverScrubberView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
_coverScrubberView.dataSource = self;
_coverScrubberView.delegate = self;
headerView.coverScrubberView = _coverScrubberView;
[_scrubberPanelView addSubview:_coverScrubberView];
_fileInfoLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 10.0f, _scrubberPanelView.frame.size.width, 21)]; _fileInfoLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 10.0f, _scrubberPanelView.frame.size.width, 21)];
_fileInfoLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth; _fileInfoLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth;
_fileInfoLabel.backgroundColor = [UIColor clearColor]; _fileInfoLabel.backgroundColor = [UIColor clearColor];
@ -420,6 +431,7 @@
} }
_scrubberView.allowsTrimming = false; _scrubberView.allowsTrimming = false;
_coverScrubberView.allowsTrimming = false;
_videoDimensions = item.dimensions; _videoDimensions = item.dimensions;
if (_entitiesView == nil) { if (_entitiesView == nil) {
@ -596,6 +608,12 @@
strongSelf->_scrubberView.trimStartValue = adjustments.trimStartValue; strongSelf->_scrubberView.trimStartValue = adjustments.trimStartValue;
strongSelf->_scrubberView.trimEndValue = adjustments.trimEndValue; strongSelf->_scrubberView.trimEndValue = adjustments.trimEndValue;
strongSelf->_scrubberView.value = adjustments.trimStartValue; strongSelf->_scrubberView.value = adjustments.trimStartValue;
strongSelf->_coverScrubberView.trimStartValue = adjustments.trimStartValue;
strongSelf->_coverScrubberView.trimEndValue = adjustments.trimEndValue;
strongSelf->_coverScrubberView.value = adjustments.trimStartValue;
[strongSelf->_coverScrubberView _layoutTrimCurtainViews];
[strongSelf->_scrubberView setTrimApplied:(adjustments.trimStartValue > 0 || adjustments.trimEndValue < videoDuration)]; [strongSelf->_scrubberView setTrimApplied:(adjustments.trimStartValue > 0 || adjustments.trimEndValue < videoDuration)];
strongSelf->_shouldResetScrubber = false; strongSelf->_shouldResetScrubber = false;
} }
@ -603,14 +621,21 @@
{ {
strongSelf->_scrubberView.trimStartValue = 0; strongSelf->_scrubberView.trimStartValue = 0;
strongSelf->_scrubberView.trimEndValue = videoDuration; strongSelf->_scrubberView.trimEndValue = videoDuration;
strongSelf->_coverScrubberView.trimStartValue = 0;
strongSelf->_coverScrubberView.trimEndValue = videoDuration;
[strongSelf->_scrubberView setTrimApplied:false]; [strongSelf->_scrubberView setTrimApplied:false];
strongSelf->_shouldResetScrubber = true; strongSelf->_shouldResetScrubber = true;
} }
[strongSelf->_scrubberView reloadData]; [strongSelf->_scrubberView reloadData];
[strongSelf->_coverScrubberView reloadData];
if (!strongSelf->_appeared) if (!strongSelf->_appeared)
{ {
[strongSelf->_scrubberView resetToStart]; [strongSelf->_scrubberView resetToStart];
[strongSelf->_coverScrubberView resetToStart];
strongSelf->_appeared = true; strongSelf->_appeared = true;
} }
} file:__FILE_NAME__ line:__LINE__]]; } file:__FILE_NAME__ line:__LINE__]];
@ -629,6 +654,7 @@
if (afterReload) { if (afterReload) {
_cachedThumbnails = nil; _cachedThumbnails = nil;
[_scrubberView reloadData]; [_scrubberView reloadData];
[_coverScrubberView reloadData];
} }
else { else {
[self setScrubbingPanelHidden:false animated:true]; [self setScrubbingPanelHidden:false animated:true];
@ -648,8 +674,10 @@
if (hidden) if (hidden)
{ {
if (!_scrubbingPanelPresented) if (!_scrubbingPanelPresented) {
[_scrubberView ignoreThumbnails]; [_scrubberView ignoreThumbnails];
[_coverScrubberView ignoreThumbnails];
}
_scrubbingPanelPresented = false; _scrubbingPanelPresented = false;
@ -680,6 +708,7 @@
[_scrubberPanelView layoutSubviews]; [_scrubberPanelView layoutSubviews];
[_scrubberView layoutSubviews]; [_scrubberView layoutSubviews];
[_coverScrubberView layoutSubviews];
void (^changeBlock)(void) = ^ void (^changeBlock)(void) = ^
{ {
@ -732,6 +761,18 @@
[self setPlayButtonHidden:false animated:true]; [self setPlayButtonHidden:false animated:true];
} }
- (void)prepareForCoverEditing
{
[self setPlayButtonHidden:true animated:true];
[self stop];
}
- (void)returnFromCoverEditing
{
if (![self usePhotoBehavior])
[self setPlayButtonHidden:false animated:true];
}
- (void)setFrame:(CGRect)frame - (void)setFrame:(CGRect)frame
{ {
bool frameChanged = !CGRectEqualToRect(frame, self.frame); bool frameChanged = !CGRectEqualToRect(frame, self.frame);
@ -741,6 +782,7 @@
if (_appeared && frameChanged) if (_appeared && frameChanged)
{ {
[_scrubberView resetThumbnails]; [_scrubberView resetThumbnails];
[_coverScrubberView resetThumbnails];
[_scrubberPanelView setNeedsLayout]; [_scrubberPanelView setNeedsLayout];
[_scrubberPanelView layoutIfNeeded]; [_scrubberPanelView layoutIfNeeded];
@ -748,6 +790,7 @@
dispatch_async(dispatch_get_main_queue(), ^ dispatch_async(dispatch_get_main_queue(), ^
{ {
[_scrubberView reloadThumbnails]; [_scrubberView reloadThumbnails];
[_coverScrubberView reloadThumbnails];
[_scrubberPanelView layoutSubviews]; [_scrubberPanelView layoutSubviews];
}); });
} }
@ -1116,6 +1159,7 @@
self.isPlaying = false; self.isPlaying = false;
[_scrubberView setIsPlaying:false]; [_scrubberView setIsPlaying:false];
[_scrubberView resetToStart]; [_scrubberView resetToStart];
[_coverScrubberView resetToStart];
[_positionTimer invalidate]; [_positionTimer invalidate];
_positionTimer = nil; _positionTimer = nil;
@ -1249,6 +1293,7 @@
{ {
[self _seekToPosition:_scrubberView.trimStartValue manual:false]; [self _seekToPosition:_scrubberView.trimStartValue manual:false];
[_scrubberView setValue:_scrubberView.trimStartValue resetPosition:true]; [_scrubberView setValue:_scrubberView.trimStartValue resetPosition:true];
[_coverScrubberView setValue:_scrubberView.trimStartValue resetPosition:true];
} }
[_player play]; [_player play];
@ -1310,10 +1355,12 @@
_positionTimer = nil; _positionTimer = nil;
[_scrubberView resetToStart]; [_scrubberView resetToStart];
[_coverScrubberView resetToStart];
} }
else else
{ {
[_scrubberView setValue:_scrubberView.trimStartValue resetPosition:true]; [_scrubberView setValue:_scrubberView.trimStartValue resetPosition:true];
[_coverScrubberView setValue:_scrubberView.trimStartValue resetPosition:true];
} }
[self _seekToPosition:_scrubberView.trimStartValue manual:false]; [self _seekToPosition:_scrubberView.trimStartValue manual:false];
@ -1322,6 +1369,7 @@
- (void)positionTimerEvent - (void)positionTimerEvent
{ {
[_scrubberView setValue:CMTimeGetSeconds(_player.currentItem.currentTime)]; [_scrubberView setValue:CMTimeGetSeconds(_player.currentItem.currentTime)];
[_coverScrubberView setValue:CMTimeGetSeconds(_player.currentItem.currentTime)];
} }
- (void)_seekToPosition:(NSTimeInterval)position manual:(bool)__unused manual - (void)_seekToPosition:(NSTimeInterval)position manual:(bool)__unused manual
@ -1396,6 +1444,11 @@
- (void)videoScrubber:(TGMediaPickerGalleryVideoScrubber *)__unused videoScrubber valueDidChange:(NSTimeInterval)position - (void)videoScrubber:(TGMediaPickerGalleryVideoScrubber *)__unused videoScrubber valueDidChange:(NSTimeInterval)position
{ {
[self _seekToPosition:position manual:true]; [self _seekToPosition:position manual:true];
if (videoScrubber == _scrubberView) {
[_coverScrubberView setValue:position resetPosition:true];
} else {
[_scrubberView setValue:position resetPosition:true];
}
} }
#pragma mark Trimming #pragma mark Trimming
@ -1426,6 +1479,10 @@
[self updatePlayerRange:videoScrubber.trimEndValue]; [self updatePlayerRange:videoScrubber.trimEndValue];
[self updateEditAdjusments]; [self updateEditAdjusments];
_coverScrubberView.trimStartValue = videoScrubber.trimStartValue;
_coverScrubberView.trimEndValue = videoScrubber.trimEndValue;
[_coverScrubberView _layoutTrimCurtainViews];
[self setPlayButtonHidden:false animated:true]; [self setPlayButtonHidden:false animated:true];
} }
@ -1622,8 +1679,12 @@
return [SSignal single:thumbnails]; return [SSignal single:thumbnails];
} }
- (void)videoScrubber:(TGMediaPickerGalleryVideoScrubber *)__unused videoScrubber requestThumbnailImagesForTimestamps:(NSArray *)timestamps size:(CGSize)size isSummaryThumbnails:(bool)isSummaryThumbnails - (void)videoScrubber:(TGMediaPickerGalleryVideoScrubber *)videoScrubber requestThumbnailImagesForTimestamps:(NSArray *)timestamps size:(CGSize)size isSummaryThumbnails:(bool)isSummaryThumbnails
{ {
if (isSummaryThumbnails && videoScrubber == _coverScrubberView) {
return;
}
if (timestamps.count == 0) if (timestamps.count == 0)
return; return;
@ -1692,8 +1753,10 @@
[images enumerateObjectsUsingBlock:^(UIImage *image, NSUInteger index, __unused BOOL *stop) [images enumerateObjectsUsingBlock:^(UIImage *image, NSUInteger index, __unused BOOL *stop)
{ {
if (index < timestamps.count) if (index < timestamps.count) {
[strongSelf->_scrubberView setThumbnailImage:image forTimestamp:[timestamps[index] doubleValue] index:index isSummaryThubmnail:isSummaryThumbnails last:index == (images.count - 1)]; [strongSelf->_scrubberView setThumbnailImage:image forTimestamp:[timestamps[index] doubleValue] index:index isSummaryThubmnail:isSummaryThumbnails last:index == (images.count - 1)];
[strongSelf->_coverScrubberView setThumbnailImage:image forTimestamp:[timestamps[index] doubleValue] index:index isSummaryThubmnail:isSummaryThumbnails last:index == (images.count - 1)];
}
}]; }];
} completed:^ } completed:^
{ {

View File

@ -28,6 +28,9 @@
@property (nonatomic, readonly) bool isScrubbing; @property (nonatomic, readonly) bool isScrubbing;
@property (nonatomic, assign) bool isPlaying; @property (nonatomic, assign) bool isPlaying;
@property (nonatomic, assign) NSTimeInterval value; @property (nonatomic, assign) NSTimeInterval value;
- (instancetype)initWithFrame:(CGRect)frame cover:(bool)cover;
- (void)setValue:(NSTimeInterval)value resetPosition:(bool)resetPosition; - (void)setValue:(NSTimeInterval)value resetPosition:(bool)resetPosition;
- (void)setTrimApplied:(bool)trimApplied; - (void)setTrimApplied:(bool)trimApplied;
@ -48,6 +51,7 @@
- (CGPoint)scrubberPositionForPosition:(NSTimeInterval)position; - (CGPoint)scrubberPositionForPosition:(NSTimeInterval)position;
- (void)_updateScrubberAnimationsAndResetCurrentPosition:(bool)resetCurrentPosition; - (void)_updateScrubberAnimationsAndResetCurrentPosition:(bool)resetCurrentPosition;
- (void)_layoutTrimCurtainViews;
@end @end

View File

@ -35,8 +35,8 @@ typedef enum
UIView *_zoomedThumbnailWrapperView; UIView *_zoomedThumbnailWrapperView;
UIView *_summaryThumbnailWrapperView; UIView *_summaryThumbnailWrapperView;
TGMediaPickerGalleryVideoTrimView *_trimView; TGMediaPickerGalleryVideoTrimView *_trimView;
UIView *_leftCurtainView; UIImageView *_leftCurtainView;
UIView *_rightCurtainView; UIImageView *_rightCurtainView;
UIControl *_scrubberHandle; UIControl *_scrubberHandle;
UIControl *_dotHandle; UIControl *_dotHandle;
@ -86,7 +86,7 @@ typedef enum
@implementation TGMediaPickerGalleryVideoScrubber @implementation TGMediaPickerGalleryVideoScrubber
- (instancetype)initWithFrame:(CGRect)frame - (instancetype)initWithFrame:(CGRect)frame cover:(bool)cover
{ {
self = [super initWithFrame:frame]; self = [super initWithFrame:frame];
if (self != nil) if (self != nil)
@ -105,7 +105,7 @@ typedef enum
_currentTimeLabel.layer.shadowOpacity = 0.6; _currentTimeLabel.layer.shadowOpacity = 0.6;
_currentTimeLabel.layer.rasterizationScale = TGScreenScaling(); _currentTimeLabel.layer.rasterizationScale = TGScreenScaling();
_currentTimeLabel.layer.shouldRasterize = true; _currentTimeLabel.layer.shouldRasterize = true;
[self addSubview:_currentTimeLabel]; //[self addSubview:_currentTimeLabel];
_inverseTimeLabel = [[UILabel alloc] initWithFrame:CGRectMake(frame.size.width - 108, 4, 100, 15)]; _inverseTimeLabel = [[UILabel alloc] initWithFrame:CGRectMake(frame.size.width - 108, 4, 100, 15)];
_inverseTimeLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin; _inverseTimeLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
@ -120,9 +120,9 @@ typedef enum
_inverseTimeLabel.layer.shadowOpacity = 0.6; _inverseTimeLabel.layer.shadowOpacity = 0.6;
_inverseTimeLabel.layer.rasterizationScale = TGScreenScaling(); _inverseTimeLabel.layer.rasterizationScale = TGScreenScaling();
_inverseTimeLabel.layer.shouldRasterize = true; _inverseTimeLabel.layer.shouldRasterize = true;
[self addSubview:_inverseTimeLabel]; //[self addSubview:_inverseTimeLabel];
_wrapperView = [[UIControl alloc] initWithFrame:CGRectMake(8, 24, 0, 36)]; _wrapperView = [[UIControl alloc] initWithFrame:CGRectMake(8, 24, 0, 40)];
_wrapperView.hitTestEdgeInsets = UIEdgeInsetsMake(-5, -10, -5, -10); _wrapperView.hitTestEdgeInsets = UIEdgeInsetsMake(-5, -10, -5, -10);
[self addSubview:_wrapperView]; [self addSubview:_wrapperView];
@ -131,19 +131,45 @@ typedef enum
_summaryThumbnailWrapperView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 32)]; _summaryThumbnailWrapperView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 32)];
_summaryThumbnailWrapperView.clipsToBounds = true; _summaryThumbnailWrapperView.clipsToBounds = true;
_summaryThumbnailWrapperView.layer.cornerRadius = 5.0; _summaryThumbnailWrapperView.layer.cornerRadius = 9.0;
[_wrapperView addSubview:_summaryThumbnailWrapperView]; [_wrapperView addSubview:_summaryThumbnailWrapperView];
_leftCurtainView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)]; static dispatch_once_t onceToken;
_leftCurtainView.backgroundColor = [[TGPhotoEditorInterfaceAssets toolbarBackgroundColor] colorWithAlphaComponent:0.8f]; static UIImage *leftCurtain;
static UIImage *rightCurtain;
dispatch_once(&onceToken, ^
{
UIGraphicsBeginImageContextWithOptions(CGSizeMake(24.0f, 40.0f), false, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, UIColorRGBA(0x000000, 0.8).CGColor);
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 40, 40) cornerRadius:9.0];
CGContextAddPath(context, path.CGPath);
CGContextFillPath(context);
leftCurtain = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIGraphicsBeginImageContextWithOptions(CGSizeMake(24.0f, 40.0f), false, 0.0f);
context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, UIColorRGBA(0x000000, 0.8).CGColor);
path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(-16.0, 0, 40, 40) cornerRadius:9.0];
CGContextAddPath(context, path.CGPath);
CGContextFillPath(context);
rightCurtain = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
});
_leftCurtainView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];
_leftCurtainView.image = [leftCurtain stretchableImageWithLeftCapWidth:11 topCapHeight:20];
_leftCurtainView.clipsToBounds = true; _leftCurtainView.clipsToBounds = true;
_leftCurtainView.layer.cornerRadius = 5.0;
[_wrapperView addSubview:_leftCurtainView]; [_wrapperView addSubview:_leftCurtainView];
_rightCurtainView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)]; _rightCurtainView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];
_rightCurtainView.backgroundColor = [[TGPhotoEditorInterfaceAssets toolbarBackgroundColor] colorWithAlphaComponent:0.8f]; _rightCurtainView.image = [rightCurtain stretchableImageWithLeftCapWidth:11 topCapHeight:20];
_rightCurtainView.clipsToBounds = true; _rightCurtainView.clipsToBounds = true;
_rightCurtainView.layer.cornerRadius = 5.0;
[_wrapperView addSubview:_rightCurtainView]; [_wrapperView addSubview:_rightCurtainView];
__weak TGMediaPickerGalleryVideoScrubber *weakSelf = self; __weak TGMediaPickerGalleryVideoScrubber *weakSelf = self;
@ -308,8 +334,8 @@ typedef enum
[_wrapperView addSubview:_dotHandle]; [_wrapperView addSubview:_dotHandle];
static UIImage *dotFrameImage = nil; static UIImage *dotFrameImage = nil;
static dispatch_once_t onceToken; static dispatch_once_t onceToken2;
dispatch_once(&onceToken, ^ dispatch_once(&onceToken2, ^
{ {
UIGraphicsBeginImageContextWithOptions(CGSizeMake(_dotHandle.frame.size.width, _dotHandle.frame.size.height), false, 0.0f); UIGraphicsBeginImageContextWithOptions(CGSizeMake(_dotHandle.frame.size.width, _dotHandle.frame.size.height), false, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext(); CGContextRef context = UIGraphicsGetCurrentContext();
@ -337,25 +363,33 @@ typedef enum
_dotFrameView.image = dotFrameImage; _dotFrameView.image = dotFrameImage;
[_dotHandle addSubview:_dotFrameView]; [_dotHandle addSubview:_dotFrameView];
_scrubberHandle = [[UIControl alloc] initWithFrame:CGRectMake(0, -4.0f, 5.0f, 44.0f)]; _scrubberHandle = [[UIControl alloc] initWithFrame:CGRectMake(0, -4.0f, cover ? 30.0 : 5.0f, 48.0f)];
_scrubberHandle.hitTestEdgeInsets = UIEdgeInsetsMake(-5, -12, -5, -12); _scrubberHandle.hitTestEdgeInsets = UIEdgeInsetsMake(-5, -12, -5, -12);
[_wrapperView addSubview:_scrubberHandle]; [_wrapperView addSubview:_scrubberHandle];
static UIImage *handleViewImage = nil; UIImage *handleViewImage = nil;
static dispatch_once_t onceToken2;
dispatch_once(&onceToken2, ^
{ {
UIGraphicsBeginImageContextWithOptions(CGSizeMake(_scrubberHandle.frame.size.width, _scrubberHandle.frame.size.height), false, 0.0f); UIGraphicsBeginImageContextWithOptions(CGSizeMake(_scrubberHandle.frame.size.width, _scrubberHandle.frame.size.height), false, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext(); CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetShadowWithColor(context, CGSizeMake(0, 0), 0.5f, [UIColor colorWithWhite:0.0f alpha:0.65f].CGColor); CGContextSetShadowWithColor(context, CGSizeMake(0, 0), 0.5f, [UIColor colorWithWhite:0.0f alpha:0.65f].CGColor);
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor); CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0.5f, 0.5f, _scrubberHandle.frame.size.width - 1, _scrubberHandle.frame.size.height - 1.0f) cornerRadius:2.0f]; if (cover) {
[path fill]; CGFloat lineWidth = 2.0 - TGSeparatorHeight();
CGContextSetLineWidth(context, lineWidth);
CGContextSetStrokeColorWithColor(context, UIColor.whiteColor.CGColor);
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(CGRectMake(0, 0, 30, 48), lineWidth / 2.0, lineWidth / 2.0) cornerRadius:8];
CGContextAddPath(context, path.CGPath);
CGContextStrokePath(context);
} else {
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake((_scrubberHandle.frame.size.width - 2.0) / 2.0, 1.5f, 2.0, _scrubberHandle.frame.size.height - 3.0f) cornerRadius:2.0f];
CGContextAddPath(context, path.CGPath);
CGContextFillPath(context);
}
handleViewImage = UIGraphicsGetImageFromCurrentImageContext(); handleViewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext(); UIGraphicsEndImageContext();
}); }
UIImageView *scrubberImageView = [[UIImageView alloc] initWithFrame:_scrubberHandle.bounds]; UIImageView *scrubberImageView = [[UIImageView alloc] initWithFrame:_scrubberHandle.bounds];
scrubberImageView.image = handleViewImage; scrubberImageView.image = handleViewImage;
@ -780,7 +814,6 @@ typedef enum
- (void)setThumbnailImage:(UIImage *)image forTimestamp:(NSTimeInterval)__unused timestamp index:(NSInteger)index isSummaryThubmnail:(bool)isSummaryThumbnail last:(bool)last - (void)setThumbnailImage:(UIImage *)image forTimestamp:(NSTimeInterval)__unused timestamp index:(NSInteger)index isSummaryThubmnail:(bool)isSummaryThumbnail last:(bool)last
{ {
bool exists = false;
if (isSummaryThumbnail) if (isSummaryThumbnail)
{ {
if (index == 0 && _summaryThumbnailViews.count > 0 && _summaryThumbnailSnapshotView == nil) { if (index == 0 && _summaryThumbnailViews.count > 0 && _summaryThumbnailSnapshotView == nil) {
@ -790,7 +823,6 @@ typedef enum
} }
if (_summaryThumbnailViews.count >= index + 1) { if (_summaryThumbnailViews.count >= index + 1) {
exists = true;
[_summaryThumbnailViews[index] setImage:image animated:true]; [_summaryThumbnailViews[index] setImage:image animated:true];
} else { } else {
TGMediaPickerGalleryVideoScrubberThumbnailView *thumbnailView = [[TGMediaPickerGalleryVideoScrubberThumbnailView alloc] initWithImage:image originalSize:_originalSize cropRect:_cropRect cropOrientation:_cropOrientation cropMirrored:_cropMirrored]; TGMediaPickerGalleryVideoScrubberThumbnailView *thumbnailView = [[TGMediaPickerGalleryVideoScrubberThumbnailView alloc] initWithImage:image originalSize:_originalSize cropRect:_cropRect cropOrientation:_cropOrientation cropMirrored:_cropMirrored];
@ -852,7 +884,7 @@ typedef enum
if (orientation == UIImageOrientationLeft || orientation == UIImageOrientationRight) if (orientation == UIImageOrientationLeft || orientation == UIImageOrientationRight)
aspectRatio = 1.0f / aspectRatio; aspectRatio = 1.0f / aspectRatio;
return CGSizeMake(CGCeil(36.0f * aspectRatio), 36.0f); return CGSizeMake(CGCeil(40.0f * aspectRatio), 40.0f);
} }
- (void)_layoutSummaryThumbnailViewsForZoom:(bool)forZoom - (void)_layoutSummaryThumbnailViewsForZoom:(bool)forZoom
@ -1431,7 +1463,7 @@ typedef enum
CGFloat minX = duration > FLT_EPSILON ? ((CGFloat)startPosition * trimRect.size.width / (CGFloat)duration + trimRect.origin.x - normalScrubbingRect.origin.x) : 0.0f; CGFloat minX = duration > FLT_EPSILON ? ((CGFloat)startPosition * trimRect.size.width / (CGFloat)duration + trimRect.origin.x - normalScrubbingRect.origin.x) : 0.0f;
CGFloat maxX = duration > FLT_EPSILON ? ((CGFloat)endPosition * trimRect.size.width / (CGFloat)duration + trimRect.origin.x + normalScrubbingRect.origin.x) : 0.0f; CGFloat maxX = duration > FLT_EPSILON ? ((CGFloat)endPosition * trimRect.size.width / (CGFloat)duration + trimRect.origin.x + normalScrubbingRect.origin.x) : 0.0f;
return CGRectMake(minX, 0, maxX - minX, 36); return CGRectMake(minX, 0, maxX - minX, 40);
} }
- (void)_layoutTrimViewZoomedIn:(bool)zoomedIn - (void)_layoutTrimViewZoomedIn:(bool)zoomedIn
@ -1462,8 +1494,8 @@ typedef enum
CGRect scrubbingRect = [self _scrubbingRect]; CGRect scrubbingRect = [self _scrubbingRect];
CGRect normalScrubbingRect = [self _scrubbingRectZoomedIn:false]; CGRect normalScrubbingRect = [self _scrubbingRectZoomedIn:false];
_leftCurtainView.frame = CGRectMake(scrubbingRect.origin.x - 12.0f, 0.0f, _trimView.frame.origin.x - scrubbingRect.origin.x + normalScrubbingRect.origin.x + 12.0f, 36.0f); _leftCurtainView.frame = CGRectMake(scrubbingRect.origin.x - 12.0f, 0.0f, _trimView.frame.origin.x - scrubbingRect.origin.x + normalScrubbingRect.origin.x + 11.0f, 40.0f);
_rightCurtainView.frame = CGRectMake(CGRectGetMaxX(_trimView.frame) - 4.0f, 0.0, scrubbingRect.origin.x + scrubbingRect.size.width - CGRectGetMaxX(_trimView.frame) - scrubbingRect.origin.x + normalScrubbingRect.origin.x + 4.0f + 12.0f, 36.0f); _rightCurtainView.frame = CGRectMake(CGRectGetMaxX(_trimView.frame) - 7.0f, 0.0, scrubbingRect.origin.x + scrubbingRect.size.width - CGRectGetMaxX(_trimView.frame) - scrubbingRect.origin.x + normalScrubbingRect.origin.x + 7.0f + 12.0f, 40.0f);
} }
} }
@ -1479,14 +1511,14 @@ typedef enum
- (void)layoutSubviews - (void)layoutSubviews
{ {
_wrapperView.frame = CGRectMake(TGVideoScrubberPadding, 24, self.frame.size.width - TGVideoScrubberPadding * 2.0f, 36); _wrapperView.frame = CGRectMake(TGVideoScrubberPadding, 24, self.frame.size.width - TGVideoScrubberPadding * 2.0f, 40);
[self _layoutTrimViewZoomedIn:_zoomedIn]; [self _layoutTrimViewZoomedIn:_zoomedIn];
CGRect scrubbingRect = [self _scrubbingRect]; CGRect scrubbingRect = [self _scrubbingRect];
if (isnan(scrubbingRect.origin.x) || isnan(scrubbingRect.origin.y)) if (isnan(scrubbingRect.origin.x) || isnan(scrubbingRect.origin.y))
return; return;
_summaryThumbnailWrapperView.frame = CGRectMake(MIN(0.0, scrubbingRect.origin.x), 0.0f, MAX(_wrapperView.frame.size.width, scrubbingRect.size.width), 36.0f); _summaryThumbnailWrapperView.frame = CGRectMake(MIN(0.0, scrubbingRect.origin.x), 0.0f, MAX(_wrapperView.frame.size.width, scrubbingRect.size.width), 40.0f);
_zoomedThumbnailWrapperView.frame = _summaryThumbnailWrapperView.frame; _zoomedThumbnailWrapperView.frame = _summaryThumbnailWrapperView.frame;
[self _updateScrubberAnimationsAndResetCurrentPosition:true]; [self _updateScrubberAnimationsAndResetCurrentPosition:true];

View File

@ -10,5 +10,6 @@
@property (nonatomic, assign) bool trimmingEnabled; @property (nonatomic, assign) bool trimmingEnabled;
- (void)setTrimming:(bool)trimming animated:(bool)animated; - (void)setTrimming:(bool)trimming animated:(bool)animated;
- (void)setTrimmingEnabled:(bool)trimmingEnabled animated:(bool)animated;
@end @end

View File

@ -11,6 +11,10 @@
{ {
UIButton *_leftSegmentView; UIButton *_leftSegmentView;
UIButton *_rightSegmentView; UIButton *_rightSegmentView;
UIImageView *_borderView;
UIImageView *_leftCapsuleView;
UIImageView *_rightCapsuleView;
UILongPressGestureRecognizer *_startHandlePressGestureRecognizer; UILongPressGestureRecognizer *_startHandlePressGestureRecognizer;
UILongPressGestureRecognizer *_endHandlePressGestureRecognizer; UILongPressGestureRecognizer *_endHandlePressGestureRecognizer;
@ -34,28 +38,48 @@
{ {
self.hitTestEdgeInsets = UIEdgeInsetsMake(-5, -25, -5, -25); self.hitTestEdgeInsets = UIEdgeInsetsMake(-5, -25, -5, -25);
UIColor *normalColor = UIColorRGB(0x4d4d4d); UIColor *normalColor = UIColorRGB(0xffffff);
UIColor *accentColor = [TGPhotoEditorInterfaceAssets accentColor]; UIColor *accentColor = UIColorRGB(0xf8d74a);
static dispatch_once_t onceToken; static dispatch_once_t onceToken;
static UIImage *handle; static UIImage *handle;
static UIImage *border;
dispatch_once(&onceToken, ^ dispatch_once(&onceToken, ^
{ {
UIGraphicsBeginImageContextWithOptions(CGSizeMake(12.0f, 36.0f), false, 0.0f); UIGraphicsBeginImageContextWithOptions(CGSizeMake(12.0f, 40.0f), false, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
[normalColor setFill]; CGContextSetFillColorWithColor(context, normalColor.CGColor);
[[UIBezierPath bezierPathWithRoundedRect:CGRectMake(0.0f, 0.0f, 12.0f, 36.0f) byRoundingCorners:UIRectCornerTopLeft | UIRectCornerBottomLeft cornerRadii:CGSizeMake(4.0f, 4.0f)] fill]; UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 40, 40) cornerRadius:9.0];
CGContextAddPath(context, path.CGPath);
CGContextFillPath(context);
CGContextSetBlendMode(context, kCGBlendModeClear);
CGContextSetFillColorWithColor(context, [UIColor clearColor].CGColor);
path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(11.0f, 1.0f + TGSeparatorHeight(), 20, 40.0f - (1.0f + TGSeparatorHeight()) * 2.0) cornerRadius:2.0];
CGContextAddPath(context, path.CGPath);
CGContextFillPath(context);
handle = UIGraphicsGetImageFromCurrentImageContext(); handle = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext(); UIGraphicsEndImageContext();
UIGraphicsBeginImageContextWithOptions(CGSizeMake(1.0f, 40.0f), false, 0.0f);
context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, normalColor.CGColor);
CGContextFillRect(context, CGRectMake(0, 0, 1, 1.0 + TGSeparatorHeight()));
CGContextFillRect(context, CGRectMake(0, 40.0 - 1.0 - TGSeparatorHeight(), 1, 1.0 + TGSeparatorHeight()));
border = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}); });
UIImage *leftImage = handle; UIImage *leftImage = handle;
UIImage *leftHighlightedImage = TGTintedImage(handle, accentColor); UIImage *leftHighlightedImage = TGTintedImage(handle, accentColor);
UIImage *rightImage = [UIImage imageWithCGImage:handle.CGImage scale:handle.scale orientation:UIImageOrientationUpMirrored]; UIImage *rightImage = [UIImage imageWithCGImage:handle.CGImage scale:handle.scale orientation:UIImageOrientationUpMirrored];
UIImage *rightHighlightedImage = [UIImage imageWithCGImage:leftHighlightedImage.CGImage scale:handle.scale orientation:UIImageOrientationUpMirrored]; UIImage *rightHighlightedImage = [UIImage imageWithCGImage:leftHighlightedImage.CGImage scale:handle.scale orientation:UIImageOrientationUpMirrored];
UIImage *borderHighlightedImage = TGTintedImage(border, accentColor);
_leftSegmentView = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 12, 36)]; _leftSegmentView = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 12, 40)];
_leftSegmentView.adjustsImageWhenHighlighted = false; _leftSegmentView.adjustsImageWhenHighlighted = false;
[_leftSegmentView setBackgroundImage:leftImage forState:UIControlStateNormal]; [_leftSegmentView setBackgroundImage:leftImage forState:UIControlStateNormal];
[_leftSegmentView setBackgroundImage:leftHighlightedImage forState:UIControlStateSelected]; [_leftSegmentView setBackgroundImage:leftHighlightedImage forState:UIControlStateSelected];
@ -63,7 +87,7 @@
_leftSegmentView.hitTestEdgeInsets = UIEdgeInsetsMake(-5, -25, -5, -10); _leftSegmentView.hitTestEdgeInsets = UIEdgeInsetsMake(-5, -25, -5, -10);
[self addSubview:_leftSegmentView]; [self addSubview:_leftSegmentView];
_rightSegmentView = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 12, 36)]; _rightSegmentView = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 12, 40)];
_rightSegmentView.adjustsImageWhenHighlighted = false; _rightSegmentView.adjustsImageWhenHighlighted = false;
[_rightSegmentView setBackgroundImage:rightImage forState:UIControlStateNormal]; [_rightSegmentView setBackgroundImage:rightImage forState:UIControlStateNormal];
[_rightSegmentView setBackgroundImage:rightHighlightedImage forState:UIControlStateSelected]; [_rightSegmentView setBackgroundImage:rightHighlightedImage forState:UIControlStateSelected];
@ -71,6 +95,22 @@
_rightSegmentView.hitTestEdgeInsets = UIEdgeInsetsMake(-5, -10, -5, -25); _rightSegmentView.hitTestEdgeInsets = UIEdgeInsetsMake(-5, -10, -5, -25);
[self addSubview:_rightSegmentView]; [self addSubview:_rightSegmentView];
_borderView = [[UIImageView alloc] initWithImage:border];
_borderView.highlightedImage = borderHighlightedImage;
[self addSubview:_borderView];
_leftCapsuleView = [[UIImageView alloc] initWithFrame:CGRectMake(5.0 - TGSeparatorHeight(), 14.0 + TGSeparatorHeight(), 2.0, 11.0)];
_leftCapsuleView.backgroundColor = UIColorRGB(0x343436);
_leftCapsuleView.clipsToBounds = true;
_leftCapsuleView.layer.cornerRadius = 1.0;
[_leftSegmentView addSubview:_leftCapsuleView];
_rightCapsuleView = [[UIImageView alloc] initWithFrame:CGRectMake(12.0 - 3.0 - 4.0 + TGSeparatorHeight(), 14.0 + TGSeparatorHeight(), 2.0, 11.0)];
_rightCapsuleView.backgroundColor = UIColorRGB(0x343436);
_rightCapsuleView.clipsToBounds = true;
_rightCapsuleView.layer.cornerRadius = 1.0;
[_rightSegmentView addSubview:_rightCapsuleView];
_startHandlePressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleHandlePress:)]; _startHandlePressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleHandlePress:)];
_startHandlePressGestureRecognizer.delegate = self; _startHandlePressGestureRecognizer.delegate = self;
_startHandlePressGestureRecognizer.minimumPressDuration = 0.1f; _startHandlePressGestureRecognizer.minimumPressDuration = 0.1f;
@ -98,10 +138,23 @@
_leftSegmentView.hidden = !trimmingEnabled; _leftSegmentView.hidden = !trimmingEnabled;
_rightSegmentView.hidden = !trimmingEnabled; _rightSegmentView.hidden = !trimmingEnabled;
_borderView.hidden = !trimmingEnabled;
[self setNeedsLayout]; [self setNeedsLayout];
} }
- (void)setTrimmingEnabled:(bool)trimmingEnabled animated:(bool)animated {
_trimmingEnabled = trimmingEnabled;
CGFloat alpha = trimmingEnabled ? 1.0 : 0.0;
[UIView animateWithDuration:0.2 animations:^{
_leftSegmentView.alpha = alpha;
_rightSegmentView.alpha = alpha;
_borderView.alpha = alpha;
}];
}
- (void)setTrimming:(bool)trimming animated:(bool)animated - (void)setTrimming:(bool)trimming animated:(bool)animated
{ {
if (animated) if (animated)
@ -110,12 +163,14 @@
{ {
[_leftSegmentView setSelected:trimming]; [_leftSegmentView setSelected:trimming];
[_rightSegmentView setSelected:trimming]; [_rightSegmentView setSelected:trimming];
[_borderView setHighlighted:trimming];
}]; }];
} }
else else
{ {
[_leftSegmentView setSelected:trimming]; [_leftSegmentView setSelected:trimming];
[_rightSegmentView setSelected:trimming]; [_rightSegmentView setSelected:trimming];
[_borderView setHighlighted:trimming];
} }
} }
@ -227,6 +282,7 @@
_leftSegmentView.frame = CGRectMake(0, 0, handleWidth, self.frame.size.height); _leftSegmentView.frame = CGRectMake(0, 0, handleWidth, self.frame.size.height);
_rightSegmentView.frame = CGRectMake(self.frame.size.width - handleWidth, 0, handleWidth, self.frame.size.height); _rightSegmentView.frame = CGRectMake(self.frame.size.width - handleWidth, 0, handleWidth, self.frame.size.height);
_borderView.frame = CGRectMake(handleWidth, 0, self.frame.size.width - handleWidth * 2.0, self.frame.size.height);
} }
@end @end

View File

@ -30,3 +30,12 @@
- (void)setInternalHidden:(bool)internalHidden animated:(bool)animated; - (void)setInternalHidden:(bool)internalHidden animated:(bool)animated;
@end @end
@interface TGMediaPickerCoverButton : UIButton
- (void)setImage:(UIImage *)image;
- (instancetype)initWithFrame:(CGRect)frame gallery:(bool)gallery;
@end

View File

@ -48,7 +48,7 @@ const CGFloat TGPhotoCounterButtonMaskFade = 18;
{ {
UIGraphicsBeginImageContextWithOptions(CGSizeMake(38.0f, 38.0f), false, 0.0f); UIGraphicsBeginImageContextWithOptions(CGSizeMake(38.0f, 38.0f), false, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext(); CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, UIColorRGBA(0x000000, 0.3f).CGColor); CGContextSetFillColorWithColor(context, UIColorRGBA(0x000000, 0.5f).CGColor);
CGContextFillEllipseInRect(context, CGRectMake(3.5f, 1.0f, 31.0f, 31.0f)); CGContextFillEllipseInRect(context, CGRectMake(3.5f, 1.0f, 31.0f, 31.0f));
@ -778,3 +778,118 @@ const CGFloat TGPhotoCounterButtonMaskFade = 18;
} }
@end @end
@interface TGMediaPickerCoverButton ()
{
UIView *_wrapperView;
UIView *_backgroundView;
UIImageView *_iconView;
UIImageView *_thumbnailView;
UILabel *_label;
bool _gallery;
}
@end
@implementation TGMediaPickerCoverButton
- (instancetype)initWithFrame:(CGRect)frame gallery:(bool)gallery
{
self = [super initWithFrame:frame];
if (self != nil)
{
self.exclusiveTouch = true;
_gallery = gallery;
CGFloat width = _gallery ? 168.0 : 98.0;
_wrapperView = [[UIView alloc] initWithFrame:CGRectMake((120 - width) / 2.0, 0, width, 26)];
_wrapperView.userInteractionEnabled = false;
[self addSubview:_wrapperView];
_backgroundView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0, width, 26)];
_backgroundView.clipsToBounds = true;
_backgroundView.layer.cornerRadius = 13.0;
_backgroundView.backgroundColor = UIColorRGBA(0x000000, 0.5f);
[_wrapperView addSubview:_backgroundView];
_thumbnailView = [[UIImageView alloc] initWithFrame:CGRectMake(1.0, 1.0, 24.0, 24.0)];
_thumbnailView.clipsToBounds = true;
_thumbnailView.contentMode = UIViewContentModeScaleAspectFill;
_thumbnailView.layer.cornerRadius = 12.0f;
[_wrapperView addSubview:_thumbnailView];
_label = [[UILabel alloc] initWithFrame:CGRectZero];
_label.backgroundColor = [UIColor clearColor];
_label.font = [TGFont boldSystemFontOfSize:14];
_label.textColor = [UIColor whiteColor];
_label.text = _gallery ? TGLocalized(@"Media.ChooseFromGallery") : TGLocalized(@"Media.EditCover");
[_label sizeToFit];
_label.frame = CGRectMake(10.0, (26.0 - _label.frame.size.height) / 2.0, _label.frame.size.width, _label.frame.size.height);
[_wrapperView addSubview:_label];
_iconView = [[UIImageView alloc] initWithFrame:CGRectMake(CGRectGetMaxX(_label.frame) + 4.0, 7, 8, 12)];
_iconView.alpha = 0.3;
_iconView.contentMode = UIViewContentModeCenter;
_iconView.image = TGTintedImage([UIImage imageNamed:@"Editor/ArrowRight"], UIColorRGB(0xffffff));
[_wrapperView addSubview:_iconView];
}
return self;
}
- (void)setImage:(UIImage *)image {
if (image != nil) {
_thumbnailView.hidden = false;
_thumbnailView.image = image;
_wrapperView.frame = CGRectMake(0, 0, 120, 26);
_backgroundView.frame = CGRectMake(0.0f, 0, 120, 26);
_label.frame = CGRectMake(10.0 + 22.0, (26.0 - _label.frame.size.height) / 2.0, _label.frame.size.width, _label.frame.size.height);
} else {
_thumbnailView.hidden = true;
CGFloat width = _gallery ? 168.0 : 98.0;
_wrapperView.frame = CGRectMake(11, 0, width, 26);
_backgroundView.frame = CGRectMake(0.0f, 0, width, 26);
_label.frame = CGRectMake(10.0, (26.0 - _label.frame.size.height) / 2.0, _label.frame.size.width, _label.frame.size.height);
}
_iconView.frame = CGRectMake(CGRectGetMaxX(_label.frame) + 4.0, 7, 8, 12);
}
- (void)setWrapperScale:(CGFloat)scale animated:(bool)animated
{
void (^change)(void) = ^
{
_wrapperView.transform = CGAffineTransformMakeScale(scale, scale);
};
if (animated)
[UIView animateWithDuration:0.12 delay:0 options:UIViewAnimationOptionAllowAnimatedContent animations:change completion:nil];
else
change();
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
[self setWrapperScale:0.85f animated:true];
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
[self setWrapperScale:1.0f animated:true];
}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesCancelled:touches withEvent:event];
[self setWrapperScale:1.0f animated:true];
}
@end

View File

@ -7,5 +7,6 @@
@property (nonatomic, assign) UIEdgeInsets safeAreaInset; @property (nonatomic, assign) UIEdgeInsets safeAreaInset;
@property (nonatomic, strong) UIView *panelView; @property (nonatomic, strong) UIView *panelView;
@property (nonatomic, strong) TGMediaPickerGalleryVideoScrubber *scrubberView; @property (nonatomic, strong) TGMediaPickerGalleryVideoScrubber *scrubberView;
@property (nonatomic, strong) TGMediaPickerGalleryVideoScrubber *coverScrubberView;
@end @end

View File

@ -11,6 +11,8 @@
- (void)layoutSubviews - (void)layoutSubviews
{ {
_scrubberView.frame = CGRectMake(_safeAreaInset.left, _scrubberView.frame.origin.y, self.frame.size.width - _safeAreaInset.left - _safeAreaInset.right, _scrubberView.frame.size.height); _scrubberView.frame = CGRectMake(_safeAreaInset.left, _scrubberView.frame.origin.y, self.frame.size.width - _safeAreaInset.left - _safeAreaInset.right, _scrubberView.frame.size.height);
_coverScrubberView.frame = CGRectMake(_safeAreaInset.left, _scrubberView.frame.origin.y + 16.0, self.frame.size.width - _safeAreaInset.left - _safeAreaInset.right, _scrubberView.frame.size.height);
} }
@end @end

View File

@ -360,7 +360,7 @@
[_dotImageView addGestureRecognizer:dotTapRecognizer]; [_dotImageView addGestureRecognizer:dotTapRecognizer];
if ([self presentedForAvatarCreation] && _item.isVideo) { if ([self presentedForAvatarCreation] && _item.isVideo) {
_scrubberView = [[TGMediaPickerGalleryVideoScrubber alloc] initWithFrame:CGRectMake(0.0f, 0.0, _portraitToolbarView.frame.size.width, 68.0f)]; _scrubberView = [[TGMediaPickerGalleryVideoScrubber alloc] initWithFrame:CGRectMake(0.0f, 0.0, _portraitToolbarView.frame.size.width, 68.0f) cover: false];
_scrubberView.minimumLength = 3.0; _scrubberView.minimumLength = 3.0;
_scrubberView.layer.allowsGroupOpacity = true; _scrubberView.layer.allowsGroupOpacity = true;
_scrubberView.hasDotPicker = true; _scrubberView.hasDotPicker = true;

View File

@ -123,11 +123,11 @@
static UIImage *muteBackground; static UIImage *muteBackground;
dispatch_once(&onceToken, ^ dispatch_once(&onceToken, ^
{ {
CGRect rect = CGRectMake(0, 0, 39.0f, 39.0f); CGRect rect = CGRectMake(0, 0, 40.0f, 40.0f);
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0); UIGraphicsBeginImageContextWithOptions(rect.size, false, 0);
CGContextRef context = UIGraphicsGetCurrentContext(); CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, UIColorRGBA(0x000000, 0.3f).CGColor); CGContextSetFillColorWithColor(context, UIColorRGBA(0x000000, 0.5f).CGColor);
CGContextFillEllipseInRect(context, CGRectInset(rect, 3, 3)); CGContextFillEllipseInRect(context, rect);
muteBackground = UIGraphicsGetImageFromCurrentImageContext(); muteBackground = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext(); UIGraphicsEndImageContext();
@ -173,7 +173,7 @@
{ {
UIGraphicsBeginImageContextWithOptions(CGSizeMake(38.0f, 38.0f), false, 0.0f); UIGraphicsBeginImageContextWithOptions(CGSizeMake(38.0f, 38.0f), false, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext(); CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, UIColorRGBA(0x000000, 0.7f).CGColor); CGContextSetFillColorWithColor(context, UIColorRGBA(0x000000, 0.5f).CGColor);
CGContextFillEllipseInRect(context, CGRectMake(3.5f, 1.0f, 31.0f, 31.0f)); CGContextFillEllipseInRect(context, CGRectMake(3.5f, 1.0f, 31.0f, 31.0f));
@ -198,7 +198,7 @@
{ {
UIGraphicsBeginImageContextWithOptions(CGSizeMake(38.0f, 38.0f), false, 0.0f); UIGraphicsBeginImageContextWithOptions(CGSizeMake(38.0f, 38.0f), false, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext(); CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, UIColorRGBA(0x000000, 0.7f).CGColor); CGContextSetFillColorWithColor(context, UIColorRGBA(0x000000, 0.5f).CGColor);
CGContextFillEllipseInRect(context, CGRectMake(3.5f, 1.0f, 31.0f, 31.0f)); CGContextFillEllipseInRect(context, CGRectMake(3.5f, 1.0f, 31.0f, 31.0f));
@ -358,7 +358,7 @@
{ {
UIGraphicsBeginImageContextWithOptions(CGSizeMake(30.0f, 30.0f), false, 0.0f); UIGraphicsBeginImageContextWithOptions(CGSizeMake(30.0f, 30.0f), false, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext(); CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, UIColorRGBA(0x000000, 0.7f).CGColor); CGContextSetFillColorWithColor(context, UIColorRGBA(0x000000, 0.5f).CGColor);
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0.5f, 0.5f, 29.0f, 29.0f) cornerRadius:8.5f]; UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0.5f, 0.5f, 29.0f, 29.0f) cornerRadius:8.5f];
CGContextAddPath(context, path.CGPath); CGContextAddPath(context, path.CGPath);

View File

@ -127,7 +127,7 @@ private enum LegacyAssetVideoData {
private enum LegacyAssetItem { private enum LegacyAssetItem {
case image(data: LegacyAssetImageData, thumbnail: UIImage?, caption: NSAttributedString?, stickers: [FileMediaReference]) case image(data: LegacyAssetImageData, thumbnail: UIImage?, caption: NSAttributedString?, stickers: [FileMediaReference])
case file(data: LegacyAssetImageData, thumbnail: UIImage?, mimeType: String, name: String, caption: NSAttributedString?) case file(data: LegacyAssetImageData, thumbnail: UIImage?, mimeType: String, name: String, caption: NSAttributedString?)
case video(data: LegacyAssetVideoData, thumbnail: UIImage?, adjustments: TGVideoEditAdjustments?, caption: NSAttributedString?, asFile: Bool, asAnimation: Bool, stickers: [FileMediaReference]) case video(data: LegacyAssetVideoData, thumbnail: UIImage?, cover: UIImage?, adjustments: TGVideoEditAdjustments?, caption: NSAttributedString?, asFile: Bool, asAnimation: Bool, stickers: [FileMediaReference])
} }
private final class LegacyAssetItemWrapper: NSObject { private final class LegacyAssetItemWrapper: NSObject {
@ -167,13 +167,14 @@ public func legacyAssetPickerItemGenerator() -> ((Any?, NSAttributedString?, Str
if (dict["type"] as! NSString) == "editedPhoto" || (dict["type"] as! NSString) == "capturedPhoto" { if (dict["type"] as! NSString) == "editedPhoto" || (dict["type"] as! NSString) == "capturedPhoto" {
let image = dict["image"] as! UIImage let image = dict["image"] as! UIImage
let thumbnail = dict["previewImage"] as? UIImage let thumbnail = dict["previewImage"] as? UIImage
let cover = dict["coverImage"] as? UIImage
var result: [AnyHashable : Any] = [:] var result: [AnyHashable : Any] = [:]
if let isAnimation = dict["isAnimation"] as? NSNumber, isAnimation.boolValue { if let isAnimation = dict["isAnimation"] as? NSNumber, isAnimation.boolValue {
let url: String? = (dict["url"] as? String) ?? (dict["url"] as? URL)?.path let url: String? = (dict["url"] as? String) ?? (dict["url"] as? URL)?.path
if let url = url { if let url = url {
let dimensions = image.size let dimensions = image.size
result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: 4.0), thumbnail: thumbnail, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: false, asAnimation: true, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId) result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: 4.0), thumbnail: thumbnail, cover: cover, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: false, asAnimation: true, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId)
} }
} else { } else {
result["item" as NSString] = LegacyAssetItemWrapper(item: .image(data: .image(image), thumbnail: thumbnail, caption: caption, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId) result["item" as NSString] = LegacyAssetItemWrapper(item: .image(data: .image(image), thumbnail: thumbnail, caption: caption, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId)
@ -220,7 +221,7 @@ public func legacyAssetPickerItemGenerator() -> ((Any?, NSAttributedString?, Str
let dimensions = (dict["dimensions"]! as AnyObject).cgSizeValue! let dimensions = (dict["dimensions"]! as AnyObject).cgSizeValue!
let duration = (dict["duration"]! as AnyObject).doubleValue! let duration = (dict["duration"]! as AnyObject).doubleValue!
result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: tempFileUrl.path, dimensions: dimensions, duration: duration), thumbnail: thumbnail, adjustments: nil, caption: caption, asFile: false, asAnimation: true, stickers: []), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId) result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: tempFileUrl.path, dimensions: dimensions, duration: duration), thumbnail: thumbnail, cover: nil, adjustments: nil, caption: caption, asFile: false, asAnimation: true, stickers: []), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId)
return result return result
} }
@ -230,6 +231,7 @@ public func legacyAssetPickerItemGenerator() -> ((Any?, NSAttributedString?, Str
} }
} else if (dict["type"] as! NSString) == "video" { } else if (dict["type"] as! NSString) == "video" {
let thumbnail = dict["previewImage"] as? UIImage let thumbnail = dict["previewImage"] as? UIImage
let cover = dict["coverImage"] as? UIImage
var asFile = false var asFile = false
if let document = dict["document"] as? NSNumber, document.boolValue { if let document = dict["document"] as? NSNumber, document.boolValue {
asFile = true asFile = true
@ -237,17 +239,18 @@ public func legacyAssetPickerItemGenerator() -> ((Any?, NSAttributedString?, Str
if let asset = dict["asset"] as? TGMediaAsset { if let asset = dict["asset"] as? TGMediaAsset {
var result: [AnyHashable: Any] = [:] var result: [AnyHashable: Any] = [:]
result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .asset(asset), thumbnail: thumbnail, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId) result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .asset(asset), thumbnail: thumbnail, cover: cover, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId)
return result return result
} else if let url = (dict["url"] as? String) ?? (dict["url"] as? URL)?.absoluteString { } else if let url = (dict["url"] as? String) ?? (dict["url"] as? URL)?.absoluteString {
let dimensions = (dict["dimensions"]! as AnyObject).cgSizeValue! let dimensions = (dict["dimensions"]! as AnyObject).cgSizeValue!
let duration = (dict["duration"]! as AnyObject).doubleValue! let duration = (dict["duration"]! as AnyObject).doubleValue!
var result: [AnyHashable: Any] = [:] var result: [AnyHashable: Any] = [:]
result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: duration), thumbnail: thumbnail, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId) result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: duration), thumbnail: thumbnail, cover: cover, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId)
return result return result
} }
} else if (dict["type"] as! NSString) == "cameraVideo" { } else if (dict["type"] as! NSString) == "cameraVideo" {
let thumbnail = dict["previewImage"] as? UIImage let thumbnail = dict["previewImage"] as? UIImage
let cover = dict["coverImage"] as? UIImage
var asFile = false var asFile = false
if let document = dict["document"] as? NSNumber, document.boolValue { if let document = dict["document"] as? NSNumber, document.boolValue {
asFile = true asFile = true
@ -259,7 +262,7 @@ public func legacyAssetPickerItemGenerator() -> ((Any?, NSAttributedString?, Str
let dimensions = previewImage.pixelSize() let dimensions = previewImage.pixelSize()
let duration = (dict["duration"]! as AnyObject).doubleValue! let duration = (dict["duration"]! as AnyObject).doubleValue!
var result: [AnyHashable: Any] = [:] var result: [AnyHashable: Any] = [:]
result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: duration), thumbnail: thumbnail, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId) result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: duration), thumbnail: thumbnail, cover: cover, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId)
return result return result
} }
} }
@ -756,7 +759,7 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A
default: default:
break break
} }
case let .video(data, thumbnail, adjustments, caption, asFile, asAnimation, stickers): case let .video(data, thumbnail, cover, adjustments, caption, asFile, asAnimation, stickers):
var finalDimensions: CGSize var finalDimensions: CGSize
var finalDuration: Double var finalDuration: Double
switch data { switch data {
@ -799,6 +802,26 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A
} }
} }
var videoCover: TelegramMediaImage?
if let cover {
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
let coverSize = cover.size.aspectFitted(CGSize(width: 320.0, height: 320.0))
let coverImage = TGScaleImageToPixelSize(cover, coverSize)!
if let coverData = coverImage.jpegData(compressionQuality: 0.6) {
account.postbox.mediaBox.storeResourceData(resource.id, data: coverData)
videoCover = TelegramMediaImage(
imageId: MediaId(namespace: 0, id: 0),
representations: [
TelegramMediaImageRepresentation(dimensions: PixelDimensions(coverSize), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)
],
immediateThumbnailData: nil,
reference: nil,
partialReference: nil,
flags: []
)
}
}
let defaultPreset = TGMediaVideoConversionPreset(rawValue: UInt32(UserDefaults.standard.integer(forKey: "TG_preferredVideoPreset_v0"))) let defaultPreset = TGMediaVideoConversionPreset(rawValue: UInt32(UserDefaults.standard.integer(forKey: "TG_preferredVideoPreset_v0")))
var preset: TGMediaVideoConversionPreset = TGMediaVideoConversionPresetCompressedMedium var preset: TGMediaVideoConversionPreset = TGMediaVideoConversionPresetCompressedMedium
@ -891,7 +914,7 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A
fileAttributes.append(.HasLinkedStickers) fileAttributes.append(.HasLinkedStickers)
} }
let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: fileAttributes, alternativeRepresentations: []) let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, videoThumbnails: [], videoCover: videoCover, immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: fileAttributes, alternativeRepresentations: [])
if let timer = item.timer, timer > 0 && (timer <= 60 || timer == viewOnceTimeout) { if let timer = item.timer, timer > 0 && (timer <= 60 || timer == viewOnceTimeout) {
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: Int32(timer), countdownBeginTime: nil)) attributes.append(AutoremoveTimeoutMessageAttribute(timeout: Int32(timer), countdownBeginTime: nil))

View File

@ -569,6 +569,7 @@ public final class LegacyPaintEntityRenderer: NSObject, TGPhotoPaintEntityRender
public final class LegacyPaintStickersContext: NSObject, TGPhotoPaintStickersContext { public final class LegacyPaintStickersContext: NSObject, TGPhotoPaintStickersContext {
public var captionPanelView: (() -> TGCaptionPanelView?)? public var captionPanelView: (() -> TGCaptionPanelView?)?
public var editCover: ((CGSize, @escaping (UIImage) -> Void) -> Void)?
private let context: AccountContext private let context: AccountContext

View File

@ -624,7 +624,11 @@ open class LegacyController: ViewController, PresentableController {
self?.presentingViewController?.dismiss(animated: false, completion: completion) self?.presentingViewController?.dismiss(animated: false, completion: completion)
} }
case .custom: case .custom:
if let _ = self.navigationController as? NavigationController {
super.dismiss(animated: false, completion: completion)
} else {
self.presentingViewController?.dismiss(animated: false, completion: completion) self.presentingViewController?.dismiss(animated: false, completion: completion)
}
case .navigation: case .navigation:
(self.navigationController as? NavigationController)?.filterController(self, animated: true) (self.navigationController as? NavigationController)?.filterController(self, animated: true)
} }

View File

@ -83,18 +83,18 @@ final class AvatarEditorPreviewView: UIView {
self.currentSize = size self.currentSize = size
self.backgroundView.frame = CGRect(origin: .zero, size: size) self.backgroundView.frame = CGRect(origin: .zero, size: size)
//TODO:localize let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let labelSize = self.label.update( let labelSize = self.label.update(
transition: .immediate, transition: .immediate,
component: AnyComponent( component: AnyComponent(
MultilineTextComponent( MultilineTextComponent(
text: .plain(NSAttributedString( text: .plain(NSAttributedString(
string: "Use an Emoji", string: presentationData.strings.MediaPicker_UseAnEmoji,
font: Font.semibold(14.0), font: Font.semibold(12.0),
textColor: .white textColor: .white
)), )),
textShadowColor: UIColor(white: 0.0, alpha: 0.4), textShadowColor: UIColor(white: 0.0, alpha: 0.3),
textShadowBlur: 4.0 textShadowBlur: 3.0
) )
), ),
environment: {}, environment: {},
@ -104,7 +104,7 @@ final class AvatarEditorPreviewView: UIView {
if view.superview == nil { if view.superview == nil {
self.addSubview(view) self.addSubview(view)
} }
view.frame = CGRect(origin: CGPoint(x: floor((size.width - labelSize.width) / 2.0), y: size.height - labelSize.height - 20.0), size: labelSize) view.frame = CGRect(origin: CGPoint(x: floor((size.width - labelSize.width) / 2.0), y: size.height - labelSize.height - 22.0), size: labelSize)
} }
guard !self.files.isEmpty else { guard !self.files.isEmpty else {

View File

@ -101,7 +101,7 @@ enum LegacyMediaPickerGallerySource {
case selection(item: TGMediaSelectableItem) case selection(item: TGMediaSelectableItem)
} }
func presentLegacyMediaPickerGallery(context: AccountContext, peer: EnginePeer?, threadTitle: String?, chatLocation: ChatLocation?, isScheduledMessages: Bool, presentationData: PresentationData, source: LegacyMediaPickerGallerySource, immediateThumbnail: UIImage?, selectionContext: TGMediaSelectionContext?, editingContext: TGMediaEditingContext, hasSilentPosting: Bool, hasSchedule: Bool, hasTimer: Bool, updateHiddenMedia: @escaping (String?) -> Void, initialLayout: ContainerViewLayout?, transitionHostView: @escaping () -> UIView?, transitionView: @escaping (String) -> UIView?, completed: @escaping (TGMediaSelectableItem & TGMediaEditableItem, Bool, Int32?, @escaping () -> Void) -> Void, presentSchedulePicker: @escaping (Bool, @escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, getCaptionPanelView: @escaping () -> TGCaptionPanelView?, present: @escaping (ViewController, Any?) -> Void, finishedTransitionIn: @escaping () -> Void, willTransitionOut: @escaping () -> Void, dismissAll: @escaping () -> Void) -> TGModernGalleryController { func presentLegacyMediaPickerGallery(context: AccountContext, peer: EnginePeer?, threadTitle: String?, chatLocation: ChatLocation?, isScheduledMessages: Bool, presentationData: PresentationData, source: LegacyMediaPickerGallerySource, immediateThumbnail: UIImage?, selectionContext: TGMediaSelectionContext?, editingContext: TGMediaEditingContext, hasSilentPosting: Bool, hasSchedule: Bool, hasTimer: Bool, updateHiddenMedia: @escaping (String?) -> Void, initialLayout: ContainerViewLayout?, transitionHostView: @escaping () -> UIView?, transitionView: @escaping (String) -> UIView?, completed: @escaping (TGMediaSelectableItem & TGMediaEditableItem, Bool, Int32?, @escaping () -> Void) -> Void, presentSchedulePicker: @escaping (Bool, @escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, getCaptionPanelView: @escaping () -> TGCaptionPanelView?, present: @escaping (ViewController, Any?) -> Void, finishedTransitionIn: @escaping () -> Void, willTransitionOut: @escaping () -> Void, dismissAll: @escaping () -> Void, editCover: @escaping (CGSize, @escaping (UIImage) -> Void) -> Void = { _, _ in }) -> TGModernGalleryController {
let reminder = peer?.id == context.account.peerId let reminder = peer?.id == context.account.peerId
let hasSilentPosting = hasSilentPosting && peer?.id != context.account.peerId let hasSilentPosting = hasSilentPosting && peer?.id != context.account.peerId
@ -112,6 +112,9 @@ func presentLegacyMediaPickerGallery(context: AccountContext, peer: EnginePeer?,
paintStickersContext.captionPanelView = { paintStickersContext.captionPanelView = {
return getCaptionPanelView() return getCaptionPanelView()
} }
paintStickersContext.editCover = { dimensions, completion in
editCover(dimensions, completion)
}
let controller = TGModernGalleryController(context: legacyController.context)! let controller = TGModernGalleryController(context: legacyController.context)!
controller.asyncTransitionIn = true controller.asyncTransitionIn = true

View File

@ -160,6 +160,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
case wallpaper case wallpaper
case story case story
case addImage case addImage
case cover
case createSticker case createSticker
case createAvatar case createAvatar
} }
@ -234,6 +235,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
} }
var dismissAll: () -> Void = { } var dismissAll: () -> Void = { }
public var editCover: (CGSize, @escaping (UIImage) -> Void) -> Void = { _, _ in }
private class Node: ViewControllerTracingNode, ASGestureRecognizerDelegate { private class Node: ViewControllerTracingNode, ASGestureRecognizerDelegate {
enum DisplayMode { enum DisplayMode {
@ -311,7 +313,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
self.presentationData = controller.presentationData self.presentationData = controller.presentationData
var assetType: PHAssetMediaType? var assetType: PHAssetMediaType?
if case let .assets(_, mode) = controller.subject, [.wallpaper, .addImage, .createSticker].contains(mode) { if case let .assets(_, mode) = controller.subject, [.wallpaper, .addImage, .cover, .createSticker].contains(mode) {
assetType = .image assetType = .image
} }
let mediaAssetsContext = MediaAssetsContext(assetType: assetType) let mediaAssetsContext = MediaAssetsContext(assetType: assetType)
@ -522,7 +524,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
self.gridNode.scrollView.alwaysBounceVertical = true self.gridNode.scrollView.alwaysBounceVertical = true
self.gridNode.scrollView.showsVerticalScrollIndicator = false self.gridNode.scrollView.showsVerticalScrollIndicator = false
if case let .assets(_, mode) = controller.subject, [.wallpaper, .story, .addImage, .createSticker, .createAvatar].contains(mode) { if case let .assets(_, mode) = controller.subject, [.wallpaper, .story, .addImage, .cover, .createSticker, .createAvatar].contains(mode) {
} else { } else {
let selectionGesture = MediaPickerGridSelectionGesture<TGMediaSelectableItem>() let selectionGesture = MediaPickerGridSelectionGesture<TGMediaSelectableItem>()
@ -1217,7 +1219,9 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
} }
}, presentSchedulePicker: controller.presentSchedulePicker, presentTimerPicker: controller.presentTimerPicker, getCaptionPanelView: controller.getCaptionPanelView, present: { [weak self] c, a in }, presentSchedulePicker: controller.presentSchedulePicker, presentTimerPicker: controller.presentTimerPicker, getCaptionPanelView: controller.getCaptionPanelView, present: { [weak self] c, a in
self?.currentGalleryParentController = c self?.currentGalleryParentController = c
self?.controller?.present(c, in: .window(.root), with: a) c.navigationPresentation = .flatModal
self?.controller?.parentController()?.push(c)
//self?.controller?.present(c, in: .window(.root), with: a)
}, finishedTransitionIn: { [weak self] in }, finishedTransitionIn: { [weak self] in
self?.openingMedia = false self?.openingMedia = false
self?.hasGallery = true self?.hasGallery = true
@ -1227,6 +1231,8 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
self?.updateIsCameraActive() self?.updateIsCameraActive()
}, dismissAll: { [weak self] in }, dismissAll: { [weak self] in
self?.controller?.dismissAll() self?.controller?.dismissAll()
}, editCover: { [weak self] dimensions, completion in
self?.controller?.editCover(dimensions, completion)
}) })
} }
@ -1266,6 +1272,8 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
self?.updateIsCameraActive() self?.updateIsCameraActive()
}, dismissAll: { [weak self] in }, dismissAll: { [weak self] in
self?.controller?.dismissAll() self?.controller?.dismissAll()
}, editCover: { _, _ in
}) })
} }
@ -1540,9 +1548,6 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
var cutoutRect: CGRect? var cutoutRect: CGRect?
var cameraRect: CGRect? = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: 0.0), size: CGSize(width: itemWidth, height: itemWidth * 2.0 + 1.0)) var cameraRect: CGRect? = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: 0.0), size: CGSize(width: itemWidth, height: itemWidth * 2.0 + 1.0))
if case .assets(nil, .createAvatar) = controller.subject {
cameraRect = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: 0.0), size: CGSize(width: itemWidth, height: itemWidth))
}
if self.cameraView == nil && self.modernCameraView == nil { if self.cameraView == nil && self.modernCameraView == nil {
cameraRect = nil cameraRect = nil
} }
@ -1667,6 +1672,9 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
}) })
} }
} }
Queue.mainQueue().after(0.01) {
strongSelf.cameraWrapperView.superview?.addSubview(strongSelf.cameraWrapperView)
}
}) })
if let avatarEditorPreviewView = self.avatarEditorPreviewView { if let avatarEditorPreviewView = self.avatarEditorPreviewView {
@ -1869,9 +1877,8 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
self.titleView.subtitle = presentationData.strings.MediaPicker_CreateSticker self.titleView.subtitle = presentationData.strings.MediaPicker_CreateSticker
self.titleView.isEnabled = true self.titleView.isEnabled = true
case .createAvatar: case .createAvatar:
//TODO:localize
self.titleView.title = presentationData.strings.MediaPicker_Recents self.titleView.title = presentationData.strings.MediaPicker_Recents
self.titleView.subtitle = "Set new profile photo" self.titleView.subtitle = presentationData.strings.MediaPicker_SetNewPhoto
self.titleView.isEnabled = true self.titleView.isEnabled = true
case .story: case .story:
self.titleView.title = presentationData.strings.MediaPicker_Recents self.titleView.title = presentationData.strings.MediaPicker_Recents
@ -1880,6 +1887,8 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
self.titleView.title = presentationData.strings.Conversation_Theme_ChooseWallpaperTitle self.titleView.title = presentationData.strings.Conversation_Theme_ChooseWallpaperTitle
case .addImage: case .addImage:
self.titleView.title = presentationData.strings.MediaPicker_AddImage self.titleView.title = presentationData.strings.MediaPicker_AddImage
case .cover:
self.titleView.title = presentationData.strings.MediaPicker_ChooseCover
} }
} }
} else { } else {
@ -3379,8 +3388,7 @@ public func avatarMediaPickerController(
var mainButtonState: AttachmentMainButtonState? var mainButtonState: AttachmentMainButtonState?
if canDelete { if canDelete {
//TODO:localize mainButtonState = AttachmentMainButtonState(text: presentationData.strings.MediaPicker_RemovePhoto, font: .regular, background: .color(.clear), textColor: presentationData.theme.actionSheet.destructiveActionTextColor, isVisible: true, progress: .none, isEnabled: true, hasShimmer: false)
mainButtonState = AttachmentMainButtonState(text: "Remove Photo", font: .regular, background: .color(.clear), textColor: presentationData.theme.actionSheet.destructiveActionTextColor, isVisible: true, progress: .none, isEnabled: true, hasShimmer: false)
} }
let mediaPickerController = MediaPickerScreenImpl( let mediaPickerController = MediaPickerScreenImpl(
@ -3477,6 +3485,68 @@ public func avatarMediaPickerController(
return controller return controller
} }
public func coverMediaPickerController(
context: AccountContext,
completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void,
dismissed: @escaping () -> Void
) -> ViewController {
let presentationData = context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkColorPresentationTheme)
let updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>) = (presentationData, .single(presentationData))
let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false, makeEntityInputView: {
return nil
})
controller.requestController = { [weak controller] _, present in
let mediaPickerController = MediaPickerScreenImpl(
context: context,
updatedPresentationData: updatedPresentationData,
peer: nil,
threadTitle: nil,
chatLocation: nil,
bannedSendPhotos: nil,
bannedSendVideos: nil,
subject: .assets(nil, .cover)
)
mediaPickerController.customSelection = { controller, result in
if let result = result as? PHAsset {
controller.updateHiddenMediaId(result.localIdentifier)
if let transitionView = controller.transitionView(for: result.localIdentifier, snapshot: false) {
let transitionOut: (Bool?) -> (UIView, CGRect)? = { isNew in
if let isNew {
if isNew {
controller.updateHiddenMediaId(nil)
if let transitionView = controller.defaultTransitionView() {
return (transitionView, transitionView.bounds)
}
} else if let transitionView = controller.transitionView(for: result.localIdentifier, snapshot: false) {
return (transitionView, transitionView.bounds)
}
}
return nil
}
completion(result, transitionView, transitionView.bounds, controller.transitionImage(for: result.localIdentifier), false, transitionOut, { [weak controller] in
controller?.updateHiddenMediaId(nil)
})
}
}
}
mediaPickerController.openAvatarEditor = { [weak controller] in
completion(nil, nil, .zero, nil, false, { _ in return nil }, {
})
controller?.dismiss(animated: true)
}
present(mediaPickerController, mediaPickerController.mediaPickerContext)
}
controller.willDismiss = {
dismissed()
}
controller.navigationPresentation = .flatModal
controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
return controller
}
private class SelectedButtonNode: HighlightableButtonNode { private class SelectedButtonNode: HighlightableButtonNode {
private let background = ASImageNode() private let background = ASImageNode()
private let icon = ASImageNode() private let icon = ASImageNode()

View File

@ -59,6 +59,8 @@ func requiredBoostSubjectLevel(subject: BoostSubject, group: Bool, context: Acco
return configuration.minGroupEmojiPackLevel return configuration.minGroupEmojiPackLevel
case .noAds: case .noAds:
return configuration.minChannelRestrictAdsLevel return configuration.minChannelRestrictAdsLevel
case .wearGift:
return configuration.minChannelWearGiftLevel
} }
} }
@ -240,6 +242,7 @@ private final class LevelSectionComponent: CombinedComponent {
case audioTranscription case audioTranscription
case emojiPack case emojiPack
case noAds case noAds
case wearGift
func title(strings: PresentationStrings, isGroup: Bool) -> String { func title(strings: PresentationStrings, isGroup: Bool) -> String {
switch self { switch self {
@ -269,6 +272,8 @@ private final class LevelSectionComponent: CombinedComponent {
return strings.GroupBoost_Table_Group_EmojiPack return strings.GroupBoost_Table_Group_EmojiPack
case .noAds: case .noAds:
return strings.ChannelBoost_Table_NoAds return strings.ChannelBoost_Table_NoAds
case .wearGift:
return strings.ChannelBoost_Table_WearGift
} }
} }
@ -300,6 +305,8 @@ private final class LevelSectionComponent: CombinedComponent {
return "Premium/BoostPerk/EmojiPack" return "Premium/BoostPerk/EmojiPack"
case .noAds: case .noAds:
return "Premium/BoostPerk/NoAds" return "Premium/BoostPerk/NoAds"
case .wearGift:
return "Premium/BoostPerk/NoAds"
} }
} }
} }
@ -638,6 +645,8 @@ private final class SheetContent: CombinedComponent {
textString = strings.GroupBoost_EnableEmojiPackLevelText("\(requiredLevel)").string textString = strings.GroupBoost_EnableEmojiPackLevelText("\(requiredLevel)").string
case .noAds: case .noAds:
textString = strings.ChannelBoost_EnableNoAdsLevelText("\(requiredLevel)").string textString = strings.ChannelBoost_EnableNoAdsLevelText("\(requiredLevel)").string
case .wearGift:
textString = strings.ChannelBoost_EnableNoAdsLevelText("\(requiredLevel)").string
} }
} else { } else {
let boostsString = strings.ChannelBoost_MoreBoostsNeeded_Boosts(Int32(remaining)) let boostsString = strings.ChannelBoost_MoreBoostsNeeded_Boosts(Int32(remaining))
@ -783,15 +792,6 @@ private final class SheetContent: CombinedComponent {
availableSize: CGSize(width: 90.0, height: 90.0), availableSize: CGSize(width: 90.0, height: 90.0),
transition: .immediate transition: .immediate
) )
// let icon = icon.update(
// component: LottieComponent(
// content: LottieComponent.AppBundleContent(name: iconName),
// playOnce: state.playOnce
// ),
// availableSize: CGSize(width: 70, height: 70),
// transition: .immediate
// )
context.add(icon context.add(icon
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + iconBackground.size.height / 2.0)) .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + iconBackground.size.height / 2.0))
) )
@ -1162,6 +1162,9 @@ private final class SheetContent: CombinedComponent {
if !isGroup && level >= requiredBoostSubjectLevel(subject: .noAds, group: isGroup, context: component.context, configuration: premiumConfiguration) { if !isGroup && level >= requiredBoostSubjectLevel(subject: .noAds, group: isGroup, context: component.context, configuration: premiumConfiguration) {
perks.append(.noAds) perks.append(.noAds)
} }
// if !isGroup && level >= requiredBoostSubjectLevel(subject: .wearGift, group: isGroup, context: component.context, configuration: premiumConfiguration) {
// perks.append(.wearGift)
// }
levelItems.append( levelItems.append(
AnyComponentWithIdentity( AnyComponentWithIdentity(
@ -1461,6 +1464,8 @@ private final class BoostLevelsContainerComponent: CombinedComponent {
titleString = strings.GroupBoost_EmojiPack titleString = strings.GroupBoost_EmojiPack
case .noAds: case .noAds:
titleString = strings.ChannelBoost_NoAds titleString = strings.ChannelBoost_NoAds
case .wearGift:
titleString = strings.ChannelBoost_WearGift
} }
} else { } else {
titleString = isGroup == true ? strings.GroupBoost_Title_Current : strings.ChannelBoost_Title_Current titleString = isGroup == true ? strings.GroupBoost_Title_Current : strings.ChannelBoost_Title_Current

View File

@ -200,6 +200,7 @@ private enum ApplicationSpecificGlobalNotice: Int32 {
case dismissedBusinessLinksBadge = 73 case dismissedBusinessLinksBadge = 73
case dismissedBusinessChatbotsBadge = 74 case dismissedBusinessChatbotsBadge = 74
case captionAboveMediaTooltip = 75 case captionAboveMediaTooltip = 75
case channelSendGiftTooltip = 76
var key: ValueBoxKey { var key: ValueBoxKey {
let v = ValueBoxKey(length: 4) let v = ValueBoxKey(length: 4)
@ -544,6 +545,10 @@ private struct ApplicationSpecificNoticeKeys {
static func captionAboveMediaTooltip() -> NoticeEntryKey { static func captionAboveMediaTooltip() -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.captionAboveMediaTooltip.key) return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.captionAboveMediaTooltip.key)
} }
static func channelSendGiftTooltip() -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.channelSendGiftTooltip.key)
}
} }
public struct ApplicationSpecificNotice { public struct ApplicationSpecificNotice {
@ -2303,4 +2308,31 @@ public struct ApplicationSpecificNotice {
return Int(previousValue) return Int(previousValue)
} }
} }
public static func getChannelSendGiftTooltip(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Int32, NoError> {
return accountManager.transaction { transaction -> Int32 in
if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.channelSendGiftTooltip())?.get(ApplicationSpecificCounterNotice.self) {
return value.value
} else {
return 0
}
}
}
public static func incrementChannelSendGiftTooltip(accountManager: AccountManager<TelegramAccountManagerTypes>, count: Int = 1) -> Signal<Int, NoError> {
return accountManager.transaction { transaction -> Int in
var currentValue: Int32 = 0
if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.channelSendGiftTooltip())?.get(ApplicationSpecificCounterNotice.self) {
currentValue = value.value
}
let previousValue = currentValue
currentValue += Int32(count)
if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) {
transaction.setNotice(ApplicationSpecificNoticeKeys.channelSendGiftTooltip(), entry)
}
return Int(previousValue)
}
}
} }

View File

@ -1066,7 +1066,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
attributedString = mutableString attributedString = mutableString
case .prizeStars: case .prizeStars:
attributedString = NSAttributedString(string: strings.Notification_StarsPrize, font: titleFont, textColor: primaryTextColor) attributedString = NSAttributedString(string: strings.Notification_StarsPrize, font: titleFont, textColor: primaryTextColor)
case let .starGift(gift, _, text, entities, _, _, _, _, _, upgradeStars, _, _, _, _): case let .starGift(gift, _, text, entities, _, _, _, _, _, upgradeStars, _, _, peerId, senderId, _):
if !forAdditionalServiceMessage { if !forAdditionalServiceMessage {
if let text { if let text {
let mutableAttributedString = NSMutableAttributedString(attributedString: stringWithAppliedEntities(text, entities: entities ?? [], baseColor: primaryTextColor, linkColor: primaryTextColor, baseFont: titleFont, linkFont: titleBoldFont, boldFont: titleBoldFont, italicFont: titleFont, boldItalicFont: titleBoldFont, fixedFont: titleFont, blockQuoteFont: titleFont, underlineLinks: false, message: message._asMessage())) let mutableAttributedString = NSMutableAttributedString(attributedString: stringWithAppliedEntities(text, entities: entities ?? [], baseColor: primaryTextColor, linkColor: primaryTextColor, baseFont: titleFont, linkFont: titleBoldFont, boldFont: titleBoldFont, italicFont: titleFont, boldItalicFont: titleBoldFont, fixedFont: titleFont, blockQuoteFont: titleFont, underlineLinks: false, message: message._asMessage()))
@ -1090,13 +1090,28 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_Self_Bought(starsPrice)._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes]) attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_Self_Bought(starsPrice)._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes])
} else if message.author?.id == accountPeerId { } else if message.author?.id == accountPeerId {
attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_SentYou(starsPrice)._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes]) attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_SentYou(starsPrice)._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes])
} else if let peerId {
peerIds = [(1, peerId)]
var peerName = ""
if let name = message.peers[peerId].flatMap(EnginePeer.init)?.compactDisplayTitle {
peerName = name
}
if let senderId {
peerIds.insert((0, senderId), at: 0)
if let name = message.peers[senderId].flatMap(EnginePeer.init)?.compactDisplayTitle {
authorName = name
}
}
var attributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: peerIds)
attributes[2] = boldAttributes
attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_Channel_Sent(authorName, peerName, starsPrice)._tuple, body: bodyAttributes, argumentAttributes: attributes)
} else { } else {
var attributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: peerIds) var attributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: peerIds)
attributes[1] = boldAttributes attributes[1] = boldAttributes
attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_Sent(authorName, starsPrice)._tuple, body: bodyAttributes, argumentAttributes: attributes) attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_Sent(authorName, starsPrice)._tuple, body: bodyAttributes, argumentAttributes: attributes)
} }
} }
case let .starGiftUnique(gift, isUpgrade, _, _, _, _, _): case let .starGiftUnique(gift, isUpgrade, _, _, _, _, _, _, _, _):
if case let .unique(gift) = gift { if case let .unique(gift) = gift {
if !forAdditionalServiceMessage { if !forAdditionalServiceMessage {
attributedString = NSAttributedString(string: "\(gift.title) #\(gift.number)", font: titleFont, textColor: primaryTextColor) attributedString = NSAttributedString(string: "\(gift.title) #\(gift.number)", font: titleFont, textColor: primaryTextColor)

View File

@ -13,6 +13,8 @@ import ChatPresentationInterfaceState
import ChatInputPanelNode import ChatInputPanelNode
import AccountContext import AccountContext
import OldChannelsController import OldChannelsController
import TooltipUI
import TelegramNotices
private enum SubscriberAction: Equatable { private enum SubscriberAction: Equatable {
case join case join
@ -146,6 +148,7 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
private let activityIndicator: UIActivityIndicatorView private let activityIndicator: UIActivityIndicatorView
private let helpButton: HighlightableButtonNode private let helpButton: HighlightableButtonNode
private let giftButton: HighlightableButtonNode
private var action: SubscriberAction? private var action: SubscriberAction?
@ -176,6 +179,9 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
self.badgeText.isHidden = true self.badgeText.isHidden = true
self.helpButton = HighlightableButtonNode() self.helpButton = HighlightableButtonNode()
self.helpButton.isHidden = true
self.giftButton = HighlightableButtonNode()
self.giftButton.isHidden = true
self.discussButton.addSubnode(self.discussButtonText) self.discussButton.addSubnode(self.discussButtonText)
self.discussButton.addSubnode(self.badgeBackground) self.discussButton.addSubnode(self.badgeBackground)
@ -189,10 +195,12 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
self.addSubnode(self.discussButton) self.addSubnode(self.discussButton)
self.view.addSubview(self.activityIndicator) self.view.addSubview(self.activityIndicator)
self.addSubnode(self.helpButton) self.addSubnode(self.helpButton)
self.addSubnode(self.giftButton)
self.button.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) self.button.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
self.discussButton.addTarget(self, action: #selector(self.discussPressed), forControlEvents: .touchUpInside) self.discussButton.addTarget(self, action: #selector(self.discussPressed), forControlEvents: .touchUpInside)
self.helpButton.addTarget(self, action: #selector(self.helpPressed), forControlEvents: .touchUpInside) self.helpButton.addTarget(self, action: #selector(self.helpPressed), forControlEvents: .touchUpInside)
self.giftButton.addTarget(self, action: #selector(self.giftPressed), forControlEvents: .touchUpInside)
} }
deinit { deinit {
@ -207,6 +215,10 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
return super.hitTest(point, with: event) return super.hitTest(point, with: event)
} }
@objc private func giftPressed() {
self.interfaceInteraction?.openPremiumGift()
}
@objc private func helpPressed() { @objc private func helpPressed() {
self.interfaceInteraction?.presentGigagroupHelp() self.interfaceInteraction?.presentGigagroupHelp()
} }
@ -301,6 +313,51 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
return self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: transition, interfaceState: interfaceState, metrics: metrics, force: false) return self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: transition, interfaceState: interfaceState, metrics: metrics, force: false)
} }
private var displayedGiftTooltip = false
private func presentGiftTooltip() {
guard let context = self.context, !self.displayedGiftTooltip else {
return
}
self.displayedGiftTooltip = true
let _ = (ApplicationSpecificNotice.getChannelSendGiftTooltip(accountManager: context.sharedContext.accountManager)
|> deliverOnMainQueue).start(next: { [weak self] count in
guard let self else {
return
}
guard count < 2 else {
return
}
let _ = ApplicationSpecificNotice.incrementChannelSendGiftTooltip(accountManager: context.sharedContext.accountManager).start()
Queue.mainQueue().after(0.4, {
let absoluteFrame = self.giftButton.view.convert(self.giftButton.bounds, to: nil)
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY + 11.0), size: CGSize())
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let text: String = presentationData.strings.Chat_SendGiftTooltip
let tooltipController = TooltipScreen(
account: context.account,
sharedContext: context.sharedContext,
text: .plain(text: text),
balancedTextLayout: false,
style: .wide,
arrowStyle: .small,
icon: nil,
location: .point(location, .bottom),
displayDuration: .default,
inset: 8.0,
shouldDismissOnTouch: { _, _ in
return .ignore
}
)
self.interfaceInteraction?.presentControllerInCurrent(tooltipController, nil)
})
})
}
private func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, force: Bool) -> CGFloat { private func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, force: Bool) -> CGFloat {
self.layoutData = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, isSecondary, metrics) self.layoutData = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, isSecondary, metrics)
@ -311,6 +368,7 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
if previousState?.theme !== interfaceState.theme { if previousState?.theme !== interfaceState.theme {
self.badgeBackground.image = PresentationResourcesChatList.badgeBackgroundActive(interfaceState.theme, diameter: 20.0) self.badgeBackground.image = PresentationResourcesChatList.badgeBackgroundActive(interfaceState.theme, diameter: 20.0)
self.helpButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/Help"), color: interfaceState.theme.chat.inputPanel.panelControlAccentColor), for: .normal) self.helpButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/Help"), color: interfaceState.theme.chat.inputPanel.panelControlAccentColor), for: .normal)
self.giftButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/Gift"), color: interfaceState.theme.chat.inputPanel.panelControlAccentColor), for: .normal)
} }
if let context = self.context, let peer = interfaceState.renderedPeer?.peer, previousState?.renderedPeer?.peer == nil || !peer.isEqual(previousState!.renderedPeer!.peer!) || previousState?.theme !== interfaceState.theme || previousState?.strings !== interfaceState.strings || previousState?.peerIsMuted != interfaceState.peerIsMuted || previousState?.pinnedMessage != interfaceState.pinnedMessage || force { if let context = self.context, let peer = interfaceState.renderedPeer?.peer, previousState?.renderedPeer?.peer == nil || !peer.isEqual(previousState!.renderedPeer!.peer!) || previousState?.theme !== interfaceState.theme || previousState?.strings !== interfaceState.strings || previousState?.peerIsMuted != interfaceState.peerIsMuted || previousState?.pinnedMessage != interfaceState.pinnedMessage || force {
@ -356,17 +414,29 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
let buttonWidth = self.button.calculateSizeThatFits(CGSize(width: width, height: panelHeight)).width + 24.0 let buttonWidth = self.button.calculateSizeThatFits(CGSize(width: width, height: panelHeight)).width + 24.0
self.button.frame = CGRect(origin: CGPoint(x: floor((width - buttonWidth) / 2.0), y: 0.0), size: CGSize(width: buttonWidth, height: panelHeight)) self.button.frame = CGRect(origin: CGPoint(x: floor((width - buttonWidth) / 2.0), y: 0.0), size: CGSize(width: buttonWidth, height: panelHeight))
if let peer = interfaceState.renderedPeer?.peer as? TelegramChannel, peer.flags.contains(.isGigagroup) { if let peer = interfaceState.renderedPeer?.peer as? TelegramChannel {
if case .broadcast = peer.info {
self.giftButton.isHidden = false
self.helpButton.isHidden = true
self.presentGiftTooltip()
} else if peer.flags.contains(.isGigagroup) {
self.giftButton.isHidden = true
self.helpButton.isHidden = false self.helpButton.isHidden = false
}
} else { } else {
self.giftButton.isHidden = true
self.helpButton.isHidden = true self.helpButton.isHidden = true
} }
} else { } else {
self.button.frame = CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: width - leftInset - rightInset, height: panelHeight)) self.button.frame = CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: width - leftInset - rightInset, height: panelHeight))
self.giftButton.isHidden = true
self.helpButton.isHidden = true self.helpButton.isHidden = true
} }
self.giftButton.frame = CGRect(x: width - rightInset - panelHeight - 5.0, y: -3.0, width: panelHeight, height: panelHeight)
self.helpButton.frame = CGRect(x: width - rightInset - panelHeight, y: 0.0, width: panelHeight, height: panelHeight) self.helpButton.frame = CGRect(x: width - rightInset - panelHeight, y: 0.0, width: panelHeight, height: panelHeight)
} else { } else {
self.giftButton.isHidden = true
self.helpButton.isHidden = true self.helpButton.isHidden = true
let availableWidth = min(600.0, width - leftInset - rightInset) let availableWidth = min(600.0, width - leftInset - rightInset)

View File

@ -469,16 +469,19 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
buttonTitle = item.presentationData.strings.Notification_PremiumPrize_View buttonTitle = item.presentationData.strings.Notification_PremiumPrize_View
hasServiceMessage = false hasServiceMessage = false
} }
case let .starGift(gift, convertStars, giftText, giftEntities, _, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, isRefunded, _, _, _): case let .starGift(gift, convertStars, giftText, giftEntities, _, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, isRefunded, _, channelPeerId, senderPeerId, _):
if case let .generic(gift) = gift { if case let .generic(gift) = gift {
isStarGift = true isStarGift = true
let authorName = item.message.author.flatMap { EnginePeer($0) }?.compactDisplayTitle ?? "" var authorName = item.message.author.flatMap { EnginePeer($0) }?.compactDisplayTitle ?? ""
let isSelfGift = item.message.id.peerId == item.context.account.peerId let isSelfGift = item.message.id.peerId == item.context.account.peerId
let isChannelGift = item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel let isChannelGift = item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel || channelPeerId != nil
if isSelfGift { if isSelfGift {
title = item.presentationData.strings.Notification_StarGift_Self_Title title = item.presentationData.strings.Notification_StarGift_Self_Title
} else { } else {
if let senderPeerId, let name = item.message.peers[senderPeerId].flatMap(EnginePeer.init)?.compactDisplayTitle {
authorName = name
}
title = item.presentationData.strings.Notification_StarGift_Title(authorName).string title = item.presentationData.strings.Notification_StarGift_Title(authorName).string
} }
if let giftText, !giftText.isEmpty { if let giftText, !giftText.isEmpty {
@ -554,7 +557,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
buttonTitle = item.presentationData.strings.Notification_StarGift_View buttonTitle = item.presentationData.strings.Notification_StarGift_View
} }
} }
case let .starGiftUnique(gift, isUpgrade, _, _, _, _, isRefunded): case let .starGiftUnique(gift, isUpgrade, _, _, _, _, isRefunded, _, _, _):
if case let .unique(uniqueGift) = gift { if case let .unique(uniqueGift) = gift {
isStarGift = true isStarGift = true
let authorName: String let authorName: String

View File

@ -328,7 +328,7 @@ public extension EmojiPagerContentComponent {
} else { } else {
itemGroupIndexById[groupId] = itemGroups.count itemGroupIndexById[groupId] = itemGroups.count
let title = context.sharedContext.currentPresentationData.with({ $0 }).strings.EmojiInput_TrendingEmoji let title = strings.EmojiInput_TrendingEmoji
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: title, subtitle: nil, badge: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: 0, isClearable: false, headerItem: nil, items: [resultItem])) itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: title, subtitle: nil, badge: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: 0, isClearable: false, headerItem: nil, items: [resultItem]))
} }
} }
@ -614,7 +614,6 @@ public extension EmojiPagerContentComponent {
} }
if let uniqueGifts, !uniqueGifts.items.isEmpty { if let uniqueGifts, !uniqueGifts.items.isEmpty {
//TODO:localize
let groupId = "collectible" let groupId = "collectible"
let groupIndex: Int let groupIndex: Int
if let current = itemGroupIndexById[groupId] { if let current = itemGroupIndexById[groupId] {
@ -622,7 +621,7 @@ public extension EmojiPagerContentComponent {
} else { } else {
groupIndex = itemGroups.count groupIndex = itemGroups.count
itemGroupIndexById[groupId] = groupIndex itemGroupIndexById[groupId] = groupIndex
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: "COLLECTIBLES".uppercased(), subtitle: nil, badge: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: 2, isClearable: false, headerItem: nil, items: [])) itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: strings.EmojiInput_SectionTitleCollectibles.uppercased(), subtitle: nil, badge: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: 2, isClearable: false, headerItem: nil, items: []))
} }
for item in uniqueGifts.items { for item in uniqueGifts.items {

View File

@ -642,8 +642,7 @@ final class GiftOptionsScreenComponent: Component {
if isSelfGift { if isSelfGift {
premiumTitleString = strings.Gift_Options_GiftSelf_Title premiumTitleString = strings.Gift_Options_GiftSelf_Title
} else if isChannelGift { } else if isChannelGift {
//TODO:localize premiumTitleString = strings.Gift_Options_GiftChannel_Title
premiumTitleString = "Send a Gift"
} else { } else {
premiumTitleString = strings.Gift_Options_Premium_Title premiumTitleString = strings.Gift_Options_Premium_Title
} }
@ -675,8 +674,7 @@ final class GiftOptionsScreenComponent: Component {
if isSelfGift { if isSelfGift {
premiumDescriptionRawString = strings.Gift_Options_GiftSelf_Text premiumDescriptionRawString = strings.Gift_Options_GiftSelf_Text
} else if isChannelGift { } else if isChannelGift {
//TODO:localize premiumDescriptionRawString = strings.Gift_Options_GiftChannel_Text(peerName).string
premiumDescriptionRawString = "Select a gift to show appreciation for **\(peerName)**."
} else { } else {
premiumDescriptionRawString = strings.Gift_Options_Premium_Text(peerName).string premiumDescriptionRawString = strings.Gift_Options_Premium_Text(peerName).string
} }

View File

@ -233,7 +233,7 @@ final class ChatGiftPreviewItemNode: ListViewItemNode {
case let .starGift(gift): case let .starGift(gift):
media = [ media = [
TelegramMediaAction( TelegramMediaAction(
action: .starGift(gift: .generic(gift), convertStars: gift.convertStars, text: item.text, entities: item.entities, nameHidden: false, savedToProfile: false, converted: false, upgraded: false, canUpgrade: true, upgradeStars: item.upgradeStars, isRefunded: false, upgradeMessageId: nil, peerId: nil, senderId: nil) action: .starGift(gift: .generic(gift), convertStars: gift.convertStars, text: item.text, entities: item.entities, nameHidden: false, savedToProfile: false, converted: false, upgraded: false, canUpgrade: true, upgradeStars: item.upgradeStars, isRefunded: false, upgradeMessageId: nil, peerId: nil, senderId: nil, savedId: nil)
) )
] ]
} }

View File

@ -34,6 +34,7 @@ import BlurredBackgroundComponent
import ProgressNavigationButtonNode import ProgressNavigationButtonNode
import Markdown import Markdown
import GiftViewScreen import GiftViewScreen
import UndoUI
final class GiftSetupScreenComponent: Component { final class GiftSetupScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment typealias EnvironmentType = ViewControllerComponentContainer.Environment
@ -322,6 +323,15 @@ final class GiftSetupScreenComponent: Component {
return return
} }
let context = component.context
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let peerId = component.peerId
var finalPrice = starGift.price
if self.includeUpgrade, let upgradeStars = starGift.upgradeStars {
finalPrice += upgradeStars
}
let proceed = { [weak self] in let proceed = { [weak self] in
guard let self else { guard let self else {
return return
@ -331,7 +341,7 @@ final class GiftSetupScreenComponent: Component {
self.state?.updated() self.state?.updated()
let entities = generateChatInputTextEntities(self.textInputState.text) let entities = generateChatInputTextEntities(self.textInputState.text)
let source: BotPaymentInvoiceSource = .starGift(hideName: self.hideName, includeUpgrade: self.includeUpgrade, peerId: component.peerId, giftId: starGift.id, text: self.textInputState.text.string, entities: entities) let source: BotPaymentInvoiceSource = .starGift(hideName: self.hideName, includeUpgrade: self.includeUpgrade, peerId: peerId, giftId: starGift.id, text: self.textInputState.text.string, entities: entities)
let inputData = BotCheckoutController.InputData.fetch(context: component.context, source: source) let inputData = BotCheckoutController.InputData.fetch(context: component.context, source: source)
|> map(Optional.init) |> map(Optional.init)
@ -359,6 +369,26 @@ final class GiftSetupScreenComponent: Component {
return return
} }
if peerId.namespace == Namespaces.Peer.CloudChannel {
var controllers = navigationController.viewControllers
controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) }
navigationController.setViewControllers(controllers, animated: true)
let tooltipController = UndoOverlayController(
presentationData: presentationData,
content: .sticker(
context: context,
file: starGift.file,
loop: true,
title: nil,
text: presentationData.strings.Gift_Send_Success(self.peerMap[peerId]?.compactDisplayTitle ?? "", presentationData.strings.Gift_Send_Success_Stars(Int32(starGift.price))).string,
undoText: nil,
customAction: nil
),
action: { _ in return true }
)
(navigationController.viewControllers.last as? ViewController)?.present(tooltipController, in: .current)
} else {
var controllers = navigationController.viewControllers var controllers = navigationController.viewControllers
controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) && !($0 is PeerInfoScreen) && !($0 is ContactSelectionController) } controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) && !($0 is PeerInfoScreen) && !($0 is ContactSelectionController) }
var foundController = false var foundController = false
@ -376,6 +406,7 @@ final class GiftSetupScreenComponent: Component {
} }
navigationController.setViewControllers(controllers, animated: true) navigationController.setViewControllers(controllers, animated: true)
} }
}
starsContext.load(force: true) starsContext.load(force: true)
}, error: { [weak self] error in }, error: { [weak self] error in
@ -403,7 +434,7 @@ final class GiftSetupScreenComponent: Component {
}) })
} }
if starsState.balance < StarsAmount(value: starGift.price, nanos: 0) { if starsState.balance < StarsAmount(value: finalPrice, nanos: 0) {
let _ = (self.optionsPromise.get() let _ = (self.optionsPromise.get()
|> filter { $0 != nil } |> filter { $0 != nil }
|> take(1) |> take(1)
@ -415,7 +446,7 @@ final class GiftSetupScreenComponent: Component {
context: component.context, context: component.context,
starsContext: starsContext, starsContext: starsContext,
options: options ?? [], options: options ?? [],
purpose: .starGift(peerId: component.peerId, requiredStars: starGift.price), purpose: .starGift(peerId: component.peerId, requiredStars: finalPrice),
completion: { [weak self, weak starsContext] stars in completion: { [weak self, weak starsContext] stars in
guard let self, let starsContext else { guard let self, let starsContext else {
return return
@ -642,8 +673,7 @@ final class GiftSetupScreenComponent: Component {
if isSelfGift { if isSelfGift {
navigationTitleString = environment.strings.Gift_SendSelf_Title navigationTitleString = environment.strings.Gift_SendSelf_Title
} else if isChannelGift { } else if isChannelGift {
//TODO:localize navigationTitleString = environment.strings.Gift_SendChannel_Title
navigationTitleString = "Gift Preview"
} else { } else {
navigationTitleString = environment.strings.Gift_Send_TitleTo(peerName).string navigationTitleString = environment.strings.Gift_Send_TitleTo(peerName).string
} }
@ -988,8 +1018,7 @@ final class GiftSetupScreenComponent: Component {
if isSelfGift { if isSelfGift {
hideSectionFooterString = environment.strings.Gift_SendSelf_HideMyName_Info hideSectionFooterString = environment.strings.Gift_SendSelf_HideMyName_Info
} else if isChannelGift { } else if isChannelGift {
//TODO:localize hideSectionFooterString = environment.strings.Gift_SendChannel_HideMyName_Info
hideSectionFooterString = "Hide my name and message from visitors of this channel. The channel admins will still see them."
} else { } else {
hideSectionFooterString = environment.strings.Gift_Send_HideMyName_Info(peerName, peerName).string hideSectionFooterString = environment.strings.Gift_Send_HideMyName_Info(peerName, peerName).string
} }

View File

@ -385,6 +385,7 @@ private final class GiftViewSheetContent: CombinedComponent {
let strings = environment.strings let strings = environment.strings
let dateTimeFormat = environment.dateTimeFormat let dateTimeFormat = environment.dateTimeFormat
let nameDisplayOrder = component.context.sharedContext.currentPresentationData.with { $0 }.nameDisplayOrder let nameDisplayOrder = component.context.sharedContext.currentPresentationData.with { $0 }.nameDisplayOrder
let controller = environment.controller
let state = context.state let state = context.state
let subject = state.subject let subject = state.subject
@ -410,6 +411,7 @@ private final class GiftViewSheetContent: CombinedComponent {
var upgradeStars: Int64? var upgradeStars: Int64?
var uniqueGift: StarGift.UniqueGift? var uniqueGift: StarGift.UniqueGift?
var isSelfGift = false var isSelfGift = false
var isChannelGift = false
if case let .soldOutGift(gift) = subject { if case let .soldOutGift(gift) = subject {
animationFile = gift.file animationFile = gift.file
@ -444,7 +446,12 @@ private final class GiftViewSheetContent: CombinedComponent {
uniqueGift = gift uniqueGift = gift
} }
savedToProfile = arguments.savedToProfile savedToProfile = arguments.savedToProfile
if let reference = arguments.reference, case .peer = reference {
isChannelGift = true
incoming = true
} else {
incoming = arguments.incoming || arguments.peerId == component.context.account.peerId incoming = arguments.incoming || arguments.peerId == component.context.account.peerId
}
nameHidden = arguments.nameHidden nameHidden = arguments.nameHidden
isSelfGift = arguments.messageId?.peerId == component.context.account.peerId isSelfGift = arguments.messageId?.peerId == component.context.account.peerId
@ -482,6 +489,9 @@ private final class GiftViewSheetContent: CombinedComponent {
return return
} }
if state.inWearPreview { if state.inWearPreview {
if let controller = controller() as? GiftViewScreen {
controller.dismissAllTooltips()
}
state.inWearPreview = false state.inWearPreview = false
state.updated(transition: .spring(duration: 0.4)) state.updated(transition: .spring(duration: 0.4))
} else if state.inUpgradePreview { } else if state.inUpgradePreview {
@ -849,8 +859,7 @@ private final class GiftViewSheetContent: CombinedComponent {
textColor: secondaryTextColor, textColor: secondaryTextColor,
accentColor: linkColor, accentColor: linkColor,
iconName: "Premium/Collectible/Tradable", iconName: "Premium/Collectible/Tradable",
iconColor: linkColor, iconColor: linkColor
badge: strings.Gift_Upgrade_Soon
)) ))
) )
) )
@ -937,9 +946,9 @@ private final class GiftViewSheetContent: CombinedComponent {
} else if let convertStars, !upgraded { } else if let convertStars, !upgraded {
if !converted { if !converted {
if canUpgrade || upgradeStars != nil { if canUpgrade || upgradeStars != nil {
descriptionText = strings.Gift_View_KeepUpgradeOrConvertDescription(strings.Gift_View_KeepOrConvertDescription_Stars(Int32(convertStars))).string descriptionText = isChannelGift ? strings.Gift_View_KeepUpgradeOrConvertDescription_Channel(strings.Gift_View_KeepOrConvertDescription_Stars(Int32(convertStars))).string : strings.Gift_View_KeepUpgradeOrConvertDescription(strings.Gift_View_KeepOrConvertDescription_Stars(Int32(convertStars))).string
} else { } else {
descriptionText = strings.Gift_View_KeepOrConvertDescription(strings.Gift_View_KeepOrConvertDescription_Stars(Int32(convertStars))).string descriptionText = isChannelGift ? strings.Gift_View_KeepOrConvertDescription_Channel(strings.Gift_View_KeepOrConvertDescription_Stars(Int32(convertStars))).string : strings.Gift_View_KeepOrConvertDescription(strings.Gift_View_KeepOrConvertDescription_Stars(Int32(convertStars))).string
} }
} else { } else {
descriptionText = strings.Gift_View_ConvertedDescription(strings.Gift_View_ConvertedDescription_Stars(Int32(convertStars))).string descriptionText = strings.Gift_View_ConvertedDescription(strings.Gift_View_ConvertedDescription_Stars(Int32(convertStars))).string
@ -1152,7 +1161,7 @@ private final class GiftViewSheetContent: CombinedComponent {
), ),
action: { action: {
component.openPeer(peer) component.openPeer(peer)
Queue.mainQueue().after(1.0, { Queue.mainQueue().after(0.6, {
component.cancel(false) component.cancel(false)
}) })
} }
@ -1188,7 +1197,7 @@ private final class GiftViewSheetContent: CombinedComponent {
), ),
action: { action: {
component.openPeer(peer) component.openPeer(peer)
Queue.mainQueue().after(1.0, { Queue.mainQueue().after(0.6, {
component.cancel(false) component.cancel(false)
}) })
} }
@ -1215,7 +1224,7 @@ private final class GiftViewSheetContent: CombinedComponent {
isBot = true isBot = true
} }
let fromComponent: AnyComponent<Empty> let fromComponent: AnyComponent<Empty>
if incoming && !peer.isDeleted && !isBot { if incoming && !peer.isDeleted && !isBot && !isChannelGift {
fromComponent = AnyComponent( fromComponent = AnyComponent(
HStack([ HStack([
AnyComponentWithIdentity( AnyComponentWithIdentity(
@ -1231,7 +1240,7 @@ private final class GiftViewSheetContent: CombinedComponent {
), ),
action: { action: {
component.openPeer(peer) component.openPeer(peer)
Queue.mainQueue().after(1.0, { Queue.mainQueue().after(0.6, {
component.cancel(false) component.cancel(false)
}) })
} }
@ -1247,7 +1256,7 @@ private final class GiftViewSheetContent: CombinedComponent {
)), )),
action: { action: {
component.sendGift(peerId) component.sendGift(peerId)
Queue.mainQueue().after(1.0, { Queue.mainQueue().after(0.6, {
component.cancel(false) component.cancel(false)
}) })
} }
@ -1267,7 +1276,7 @@ private final class GiftViewSheetContent: CombinedComponent {
), ),
action: { action: {
component.openPeer(peer) component.openPeer(peer)
Queue.mainQueue().after(1.0, { Queue.mainQueue().after(0.6, {
component.cancel(false) component.cancel(false)
}) })
} }
@ -1315,7 +1324,7 @@ private final class GiftViewSheetContent: CombinedComponent {
effectAlignment: .center, effectAlignment: .center,
action: { action: {
component.transferGift() component.transferGift()
Queue.mainQueue().after(1.0, { Queue.mainQueue().after(0.6, {
component.cancel(false) component.cancel(false)
}) })
} }
@ -1326,9 +1335,10 @@ private final class GiftViewSheetContent: CombinedComponent {
) )
context.add(transferButton context.add(transferButton
.position(CGPoint(x: sideInset + buttonWidth / 2.0, y: headerHeight - buttonHeight / 2.0 - 16.0)) .position(CGPoint(x: sideInset + buttonWidth / 2.0, y: headerHeight - buttonHeight / 2.0 - 16.0))
.appear(.default(scale: true, alpha: true))
.disappear(.default(scale: true, alpha: true))
) )
let controller = environment.controller
let wearButton = wearButton.update( let wearButton = wearButton.update(
component: PlainButtonComponent( component: PlainButtonComponent(
content: AnyComponent( content: AnyComponent(
@ -1345,7 +1355,7 @@ private final class GiftViewSheetContent: CombinedComponent {
state.pendingWear = false state.pendingWear = false
state.updated(transition: .spring(duration: 0.4)) state.updated(transition: .spring(duration: 0.4))
component.showAttributeInfo(statusTag, "You took off \(uniqueGift.title) #\(uniqueGift.number)") component.showAttributeInfo(statusTag, strings.Gift_View_TookOff("\(uniqueGift.title) #\(uniqueGift.number)").string)
} else { } else {
if let controller = controller() as? GiftViewScreen { if let controller = controller() as? GiftViewScreen {
controller.dismissAllTooltips() controller.dismissAllTooltips()
@ -1362,6 +1372,8 @@ private final class GiftViewSheetContent: CombinedComponent {
) )
context.add(wearButton context.add(wearButton
.position(CGPoint(x: context.availableSize.width / 2.0, y: headerHeight - buttonHeight / 2.0 - 16.0)) .position(CGPoint(x: context.availableSize.width / 2.0, y: headerHeight - buttonHeight / 2.0 - 16.0))
.appear(.default(scale: true, alpha: true))
.disappear(.default(scale: true, alpha: true))
) )
let shareButton = shareButton.update( let shareButton = shareButton.update(
@ -1383,6 +1395,8 @@ private final class GiftViewSheetContent: CombinedComponent {
) )
context.add(shareButton context.add(shareButton
.position(CGPoint(x: context.availableSize.width - sideInset - buttonWidth / 2.0, y: headerHeight - buttonHeight / 2.0 - 16.0)) .position(CGPoint(x: context.availableSize.width - sideInset - buttonWidth / 2.0, y: headerHeight - buttonHeight / 2.0 - 16.0))
.appear(.default(scale: true, alpha: true))
.disappear(.default(scale: true, alpha: true))
) )
} }
@ -1506,7 +1520,7 @@ private final class GiftViewSheetContent: CombinedComponent {
} }
if let mention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention, let peer = state.peerMap[mention.peerId] { if let mention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention, let peer = state.peerMap[mention.peerId] {
component.openPeer(peer) component.openPeer(peer)
Queue.mainQueue().after(1.0, { Queue.mainQueue().after(0.6, {
component.cancel(false) component.cancel(false)
}) })
} }
@ -1734,11 +1748,11 @@ private final class GiftViewSheetContent: CombinedComponent {
} }
let descriptionText: String let descriptionText: String
if savedToProfile { if savedToProfile {
descriptionText = strings.Gift_View_DisplayedInfoHide descriptionText = isChannelGift ? strings.Gift_View_DisplayedInfoHide_Channel : strings.Gift_View_DisplayedInfoHide
} else if let upgradeStars, upgradeStars > 0 && !upgraded { } else if let upgradeStars, upgradeStars > 0 && !upgraded {
descriptionText = strings.Gift_View_HiddenInfoShow descriptionText = isChannelGift ? strings.Gift_View_HiddenInfoShow_Channel : strings.Gift_View_HiddenInfoShow
} else { } else {
descriptionText = strings.Gift_View_HiddenInfo descriptionText = isChannelGift ? strings.Gift_View_HiddenInfo_Channel : strings.Gift_View_HiddenInfo
} }
let textFont = Font.regular(13.0) let textFont = Font.regular(13.0)
@ -1769,7 +1783,7 @@ private final class GiftViewSheetContent: CombinedComponent {
}, },
tapAction: { _, _ in tapAction: { _, _ in
component.updateSavedToProfile(!savedToProfile) component.updateSavedToProfile(!savedToProfile)
Queue.mainQueue().after(1.0, { Queue.mainQueue().after(0.6, {
component.cancel(false) component.cancel(false)
}) })
} }
@ -1794,17 +1808,64 @@ private final class GiftViewSheetContent: CombinedComponent {
) )
let buttonChild: _UpdatedChildComponent let buttonChild: _UpdatedChildComponent
if state.inWearPreview, let uniqueGift { if state.inWearPreview, let uniqueGift {
let buttonContent: AnyComponentWithIdentity<Empty>
if !component.context.isPremium {
buttonContent = AnyComponentWithIdentity(
id: AnyHashable("wear_locked"),
component: AnyComponent(
HStack([
AnyComponentWithIdentity(
id: AnyHashable("icon"),
component: AnyComponent(BundleIconComponent(name: "Chat/Stickers/Lock", tintColor: theme.list.itemCheckColors.foregroundColor))
),
AnyComponentWithIdentity(
id: AnyHashable("label"),
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Gift_Wear_Start, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center))))
)
], spacing: 3.0)
)
)
} else {
buttonContent = AnyComponentWithIdentity(
id: AnyHashable("wear"),
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Gift_Wear_Start, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center))))
)
}
buttonChild = button.update( buttonChild = button.update(
component: ButtonComponent( component: ButtonComponent(
background: buttonBackground, background: buttonBackground,
content: AnyComponentWithIdentity( content: buttonContent,
id: AnyHashable("wear"),
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Gift_Wear_Start, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center))))
),
isEnabled: true, isEnabled: true,
displaysProgress: false, displaysProgress: false,
action: { [weak state] in action: { [weak state] in
if let state { if let state {
let context = component.context
if !context.isPremium, let controller = controller() as? GiftViewScreen {
controller.dismissAllTooltips()
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let text = strings.Gift_View_TooltipPremiumWearing
let tooltipController = UndoOverlayController(
presentationData: presentationData,
content: .premiumPaywall(title: nil, text: text, customUndoText: nil, timeout: nil, linkAction: nil),
position: .bottom,
animateInAsReplacement: false,
appearance: UndoOverlayController.Appearance(sideInset: 16.0, bottomInset: 62.0),
action: { [weak controller] action in
if case .info = action {
controller?.dismissAllTooltips()
let premiumController = context.sharedContext.makePremiumIntroController(context: context, source: .messageEffects, forceDark: false, dismissed: nil)
controller?.push(premiumController)
Queue.mainQueue().after(0.6, {
component.cancel(false)
})
}
return false
}
)
controller.present(tooltipController, in: .window(.root))
} else {
state.pendingWear = true state.pendingWear = true
state.pendingTakeOff = false state.pendingTakeOff = false
state.inWearPreview = false state.inWearPreview = false
@ -1813,7 +1874,8 @@ private final class GiftViewSheetContent: CombinedComponent {
let _ = component.context.engine.accountData.setStarGiftStatus(starGift: uniqueGift, expirationDate: nil).start() let _ = component.context.engine.accountData.setStarGiftStatus(starGift: uniqueGift, expirationDate: nil).start()
Queue.mainQueue().after(0.2) { Queue.mainQueue().after(0.2) {
component.showAttributeInfo(statusTag, "You put on \(uniqueGift.title) #\(uniqueGift.number)") component.showAttributeInfo(statusTag, strings.Gift_View_PutOn("\(uniqueGift.title) #\(uniqueGift.number)").string)
}
} }
} }
}), }),
@ -1901,7 +1963,7 @@ private final class GiftViewSheetContent: CombinedComponent {
transition: context.transition transition: context.transition
) )
} else if incoming && !converted && !savedToProfile { } else if incoming && !converted && !savedToProfile {
let buttonTitle = savedToProfile ? strings.Gift_View_Hide : strings.Gift_View_Display let buttonTitle = isChannelGift ? strings.Gift_View_Display_Channel : strings.Gift_View_Display
buttonChild = button.update( buttonChild = button.update(
component: ButtonComponent( component: ButtonComponent(
background: buttonBackground, background: buttonBackground,
@ -2131,9 +2193,21 @@ public class GiftViewScreen: ViewControllerComponentContainer {
case let .message(message): case let .message(message):
if let action = message.media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction { if let action = message.media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction {
switch action.action { switch action.action {
case let .starGift(gift, convertStars, text, entities, nameHidden, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, _, upgradeMessageId, _, _): case let .starGift(gift, convertStars, text, entities, nameHidden, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, _, upgradeMessageId, peerId, senderId, savedId):
return (message.id.peerId, message.author?.id, message.author?.compactDisplayTitle, message.id, .message(messageId: message.id), message.flags.contains(.Incoming), gift, message.timestamp, convertStars, text, entities, nameHidden, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, nil, nil, upgradeMessageId) var reference: StarGiftReference
case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, _): if let peerId, let savedId {
reference = .peer(peerId: peerId, id: savedId)
} else {
reference = .message(messageId: message.id)
}
return (message.id.peerId, senderId ?? message.author?.id, message.author?.compactDisplayTitle, message.id, reference, message.flags.contains(.Incoming), gift, message.timestamp, convertStars, text, entities, nameHidden, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, nil, nil, upgradeMessageId)
case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, _, peerId, senderId, savedId):
var reference: StarGiftReference
if let peerId, let savedId {
reference = .peer(peerId: peerId, id: savedId)
} else {
reference = .message(messageId: message.id)
}
var incoming = false var incoming = false
if isUpgrade { if isUpgrade {
if message.author?.id != message.id.peerId { if message.author?.id != message.id.peerId {
@ -2146,7 +2220,7 @@ public class GiftViewScreen: ViewControllerComponentContainer {
} else { } else {
incoming = message.flags.contains(.Incoming) incoming = message.flags.contains(.Incoming)
} }
return (message.id.peerId, message.author?.id, message.author?.compactDisplayTitle, message.id, .message(messageId: message.id), incoming, gift, message.timestamp, nil, nil, nil, false, savedToProfile, false, false, false, nil, transferStars, canExportDate, nil) return (message.id.peerId, senderId ?? message.author?.id, message.author?.compactDisplayTitle, message.id, reference, incoming, gift, message.timestamp, nil, nil, nil, false, savedToProfile, false, false, false, nil, transferStars, canExportDate, nil)
default: default:
return nil return nil
} }
@ -2545,7 +2619,7 @@ public class GiftViewScreen: ViewControllerComponentContainer {
return return
} }
openPeerImpl?(peer) openPeerImpl?(peer)
Queue.mainQueue().after(1.0) { Queue.mainQueue().after(0.6) {
self?.dismiss(animated: false, completion: nil) self?.dismiss(animated: false, completion: nil)
} }
}) })
@ -3398,13 +3472,16 @@ private final class GiftViewContextReferenceContentSource: ContextReferenceConte
private final class HeaderButtonComponent: CombinedComponent { private final class HeaderButtonComponent: CombinedComponent {
let title: String let title: String
let iconName: String let iconName: String
let isLocked: Bool
public init( public init(
title: String, title: String,
iconName: String iconName: String,
isLocked: Bool = false
) { ) {
self.title = title self.title = title
self.iconName = iconName self.iconName = iconName
self.isLocked = isLocked
} }
static func ==(lhs: HeaderButtonComponent, rhs: HeaderButtonComponent) -> Bool { static func ==(lhs: HeaderButtonComponent, rhs: HeaderButtonComponent) -> Bool {
@ -3414,6 +3491,9 @@ private final class HeaderButtonComponent: CombinedComponent {
if lhs.iconName != rhs.iconName { if lhs.iconName != rhs.iconName {
return false return false
} }
if lhs.isLocked != rhs.isLocked {
return false
}
return true return true
} }
@ -3421,6 +3501,7 @@ private final class HeaderButtonComponent: CombinedComponent {
let background = Child(RoundedRectangle.self) let background = Child(RoundedRectangle.self)
let title = Child(MultilineTextComponent.self) let title = Child(MultilineTextComponent.self)
let icon = Child(BundleIconComponent.self) let icon = Child(BundleIconComponent.self)
let lockIcon = Child(BundleIconComponent.self)
return { context in return { context in
let component = context.component let component = context.component
@ -3463,8 +3544,27 @@ private final class HeaderButtonComponent: CombinedComponent {
availableSize: CGSize(width: context.availableSize.width - 16.0, height: context.availableSize.height), availableSize: CGSize(width: context.availableSize.width - 16.0, height: context.availableSize.height),
transition: .immediate transition: .immediate
) )
var totalTitleWidth = title.size.width
var titleOriginX = context.availableSize.width / 2.0 - totalTitleWidth / 2.0
if component.isLocked {
let titleSpacing: CGFloat = 2.0
let lockIcon = lockIcon.update(
component: BundleIconComponent(
name: "Chat List/StatusLockIcon",
tintColor: UIColor.white
),
availableSize: context.availableSize,
transition: .immediate
)
totalTitleWidth += lockIcon.size.width + titleSpacing
titleOriginX = context.availableSize.width / 2.0 - totalTitleWidth / 2.0
context.add(lockIcon
.position(CGPoint(x: titleOriginX + lockIcon.size.width / 2.0, y: 42.0))
)
titleOriginX += lockIcon.size.width + titleSpacing
}
context.add(title context.add(title
.position(CGPoint(x: context.availableSize.width / 2.0, y: 42.0)) .position(CGPoint(x: titleOriginX + title.size.width / 2.0, y: 42.0))
) )
return context.availableSize return context.availableSize

View File

@ -173,7 +173,8 @@ private final class GiftWithdrawAlertContentNode: AlertContentNode {
photo: nil, photo: nil,
media: [], media: [],
uniqueGift: nil, uniqueGift: nil,
backgroundColor: .clear backgroundColor: .clear,
size: avatarSize
) )
), ),
environment: {}, environment: {},

View File

@ -0,0 +1,45 @@
import Foundation
import UIKit
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
import AccountContext
import TextFormat
public extension MediaEditorScreenImpl {
static func makeEditVideoCoverController(
context: AccountContext,
video: MediaEditorScreenImpl.Subject,
completed: @escaping () -> Void = {},
willDismiss: @escaping () -> Void = {},
update: @escaping (Disposable?) -> Void
) -> MediaEditorScreenImpl? {
let controller = MediaEditorScreenImpl(
context: context,
mode: .storyEditor,
subject: .single(video),
isEditing: true,
isEditingCover: true,
forwardSource: nil,
initialCaption: nil,
initialPrivacy: nil,
initialMediaAreas: nil,
initialVideoPosition: 0.0,
transitionIn: .noAnimation,
transitionOut: { finished, isNew in
return nil
},
completion: { result, commit in
if let _ = result.coverTimestamp {
}
commit({})
}
)
controller.willDismiss = willDismiss
controller.navigationPresentation = .flatModal
return controller
}
}

View File

@ -864,7 +864,7 @@ final class MediaEditorScreenComponent: Component {
case .storyEditor: case .storyEditor:
doneButtonTitle = isEditingStory ? environment.strings.Story_Editor_Done.uppercased() : environment.strings.Story_Editor_Next.uppercased() doneButtonTitle = isEditingStory ? environment.strings.Story_Editor_Done.uppercased() : environment.strings.Story_Editor_Next.uppercased()
doneButtonIcon = UIImage(bundleImageName: "Media Editor/Next")! doneButtonIcon = UIImage(bundleImageName: "Media Editor/Next")!
case .stickerEditor, .avatarEditor: case .stickerEditor, .avatarEditor, .coverEditor:
doneButtonTitle = nil doneButtonTitle = nil
doneButtonIcon = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/Apply"), color: .white)! doneButtonIcon = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/Apply"), color: .white)!
case .botPreview: case .botPreview:
@ -1060,9 +1060,14 @@ final class MediaEditorScreenComponent: Component {
) )
var isAvatarEditor = false var isAvatarEditor = false
var isCoverEditor = false
if case .avatarEditor = controller.mode { if case .avatarEditor = controller.mode {
isAvatarEditor = true isAvatarEditor = true
} else if case .coverEditor = controller.mode {
isCoverEditor = true
}
if isAvatarEditor || isCoverEditor {
drawButtonFrame.origin.x = stickerButtonFrame.origin.x drawButtonFrame.origin.x = stickerButtonFrame.origin.x
if let rotateButtonView = self.rotateButton.view { if let rotateButtonView = self.rotateButton.view {
@ -1104,7 +1109,7 @@ final class MediaEditorScreenComponent: Component {
} }
} }
if !isAvatarEditor, let textButtonView = self.textButton.view { if !isAvatarEditor && !isCoverEditor, let textButtonView = self.textButton.view {
if textButtonView.superview == nil { if textButtonView.superview == nil {
self.addSubview(textButtonView) self.addSubview(textButtonView)
} }
@ -1115,7 +1120,7 @@ final class MediaEditorScreenComponent: Component {
} }
} }
if !isAvatarEditor, let stickerButtonView = self.stickerButton.view { if !isAvatarEditor && !isCoverEditor, let stickerButtonView = self.stickerButton.view {
if stickerButtonView.superview == nil { if stickerButtonView.superview == nil {
self.addSubview(stickerButtonView) self.addSubview(stickerButtonView)
} }
@ -2688,6 +2693,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
case stickerEditor(mode: StickerEditorMode) case stickerEditor(mode: StickerEditorMode)
case botPreview case botPreview
case avatarEditor case avatarEditor
case coverEditor(dimensions: CGSize)
} }
public enum TransitionIn { public enum TransitionIn {
@ -2882,14 +2888,17 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
var isStickerEditor = false var isStickerEditor = false
var isAvatarEditor = false var isAvatarEditor = false
var isCoverEditor = false
if case .stickerEditor = controller.mode { if case .stickerEditor = controller.mode {
isStickerEditor = true isStickerEditor = true
} else if case .avatarEditor = controller.mode { } else if case .avatarEditor = controller.mode {
isAvatarEditor = true isAvatarEditor = true
} else if case .coverEditor = controller.mode {
isCoverEditor = true
} }
self.entitiesContainerView = UIView(frame: CGRect(origin: .zero, size: storyDimensions)) self.entitiesContainerView = UIView(frame: CGRect(origin: .zero, size: storyDimensions))
self.entitiesView = DrawingEntitiesView(context: controller.context, size: storyDimensions, hasBin: !isStickerEditor && !isAvatarEditor, isStickerEditor: isStickerEditor) self.entitiesView = DrawingEntitiesView(context: controller.context, size: storyDimensions, hasBin: !isStickerEditor && !isAvatarEditor && !isCoverEditor, isStickerEditor: isStickerEditor)
self.entitiesView.getEntityCenterPosition = { self.entitiesView.getEntityCenterPosition = {
return CGPoint(x: storyDimensions.width / 2.0, y: storyDimensions.height / 2.0) return CGPoint(x: storyDimensions.width / 2.0, y: storyDimensions.height / 2.0)
} }
@ -2957,6 +2966,10 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
let stickerBackgroundView = UIImageView() let stickerBackgroundView = UIImageView()
self.stickerBackgroundView = stickerBackgroundView self.stickerBackgroundView = stickerBackgroundView
self.previewContainerView.addSubview(stickerBackgroundView) self.previewContainerView.addSubview(stickerBackgroundView)
case .coverEditor:
let stickerBackgroundView = UIImageView()
self.stickerBackgroundView = stickerBackgroundView
self.previewContainerView.addSubview(stickerBackgroundView)
default: default:
self.previewContainerView.addSubview(self.gradientView) self.previewContainerView.addSubview(self.gradientView)
} }
@ -2970,7 +2983,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
self.entitiesView.addSubview(self.drawingView) self.entitiesView.addSubview(self.drawingView)
switch controller.mode { switch controller.mode {
case .stickerEditor, .avatarEditor: case .stickerEditor, .avatarEditor, .coverEditor:
let stickerOverlayLayer = SimpleShapeLayer() let stickerOverlayLayer = SimpleShapeLayer()
stickerOverlayLayer.fillColor = UIColor(rgb: 0x000000, alpha: 0.7).cgColor stickerOverlayLayer.fillColor = UIColor(rgb: 0x000000, alpha: 0.7).cgColor
stickerOverlayLayer.fillRule = .evenOdd stickerOverlayLayer.fillRule = .evenOdd
@ -3174,7 +3187,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
} else { } else {
mediaEntity.scale = storyDimensions.width / fittedSize.width mediaEntity.scale = storyDimensions.width / fittedSize.width
} }
case .stickerEditor, .avatarEditor: case .stickerEditor, .avatarEditor, .coverEditor:
if fittedSize.height > fittedSize.width { if fittedSize.height > fittedSize.width {
mediaEntity.scale = storyDimensions.width / fittedSize.width mediaEntity.scale = storyDimensions.width / fittedSize.width
} else { } else {
@ -3216,6 +3229,8 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
mediaEditorMode = .sticker mediaEditorMode = .sticker
} else if case .avatarEditor = controller.mode { } else if case .avatarEditor = controller.mode {
mediaEditorMode = .avatar mediaEditorMode = .avatar
} else if case .coverEditor = controller.mode {
mediaEditorMode = .avatar
} }
if let mediaEntityView = self.entitiesView.add(mediaEntity, announce: false) as? DrawingMediaEntityView { if let mediaEntityView = self.entitiesView.add(mediaEntity, announce: false) as? DrawingMediaEntityView {
@ -3367,7 +3382,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
} }
} else if case let .gift(gift) = effectiveSubject { } else if case let .gift(gift) = effectiveSubject {
isGift = true isGift = true
let media: [Media] = [TelegramMediaAction(action: .starGiftUnique(gift: .unique(gift), isUpgrade: false, isTransferred: false, savedToProfile: false, canExportDate: nil, transferStars: nil, isRefunded: false))] let media: [Media] = [TelegramMediaAction(action: .starGiftUnique(gift: .unique(gift), isUpgrade: false, isTransferred: false, savedToProfile: false, canExportDate: nil, transferStars: nil, isRefunded: false, peerId: nil, senderId: nil, savedId: nil))]
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: self.context.account.peerId, namespace: Namespaces.Message.Cloud, id: -1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: media, peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: self.context.account.peerId, namespace: Namespaces.Message.Cloud, id: -1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: media, peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
messages = .single([message]) messages = .single([message])
} else { } else {
@ -3860,6 +3875,9 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
} else if case .avatarEditor = controller.mode { } else if case .avatarEditor = controller.mode {
hasSwipeToDismiss = false hasSwipeToDismiss = false
hasSwipeToEnhance = false hasSwipeToEnhance = false
} else if case .coverEditor = controller.mode {
hasSwipeToDismiss = false
hasSwipeToEnhance = false
} else if self.isCollageTimelineOpen { } else if self.isCollageTimelineOpen {
hasSwipeToEnhance = false hasSwipeToEnhance = false
} }
@ -4064,7 +4082,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
} else { } else {
initialScale = self.previewContainerView.bounds.width / image.size.width initialScale = self.previewContainerView.bounds.width / image.size.width
} }
case .stickerEditor, .avatarEditor: case .stickerEditor, .avatarEditor, .coverEditor:
if image.size.height > image.size.width { if image.size.height > image.size.width {
initialScale = self.previewContainerView.bounds.width / image.size.width initialScale = self.previewContainerView.bounds.width / image.size.width
} else { } else {
@ -5137,7 +5155,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
controller.requestStickerCompletion(animated: true) controller.requestStickerCompletion(animated: true)
case .botPreview: case .botPreview:
controller.requestStoryCompletion(animated: true) controller.requestStoryCompletion(animated: true)
case .avatarEditor: case .avatarEditor, .coverEditor:
controller.requestStoryCompletion(animated: true) controller.requestStoryCompletion(animated: true)
} }
} }
@ -5404,7 +5422,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
var hasInteractiveStickers = true var hasInteractiveStickers = true
if let controller = self.controller { if let controller = self.controller {
switch controller.mode { switch controller.mode {
case .stickerEditor, .botPreview, .avatarEditor: case .stickerEditor, .botPreview, .avatarEditor, .coverEditor:
hasInteractiveStickers = false hasInteractiveStickers = false
default: default:
break break
@ -5885,7 +5903,11 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
transition.setFrame(view: self.selectionContainerView, frame: CGRect(origin: .zero, size: previewFrame.size)) transition.setFrame(view: self.selectionContainerView, frame: CGRect(origin: .zero, size: previewFrame.size))
if let stickerBackgroundView = self.stickerBackgroundView, let stickerOverlayLayer = self.stickerOverlayLayer, let stickerFrameLayer = self.stickerFrameLayer { if let stickerBackgroundView = self.stickerBackgroundView, let stickerOverlayLayer = self.stickerOverlayLayer, let stickerFrameLayer = self.stickerFrameLayer {
let stickerFrameWidth = floorToScreenPixels(previewSize.width * 0.97) var stickerFrameFraction: CGFloat = 1.0
if case .avatarEditor = controller.mode {
stickerFrameFraction = 0.97
}
let stickerFrameWidth = floorToScreenPixels(previewSize.width * stickerFrameFraction)
stickerOverlayLayer.frame = CGRect(origin: .zero, size: previewSize) stickerOverlayLayer.frame = CGRect(origin: .zero, size: previewSize)
let stickerFrameRect = CGRect(origin: CGPoint(x: floorToScreenPixels((previewSize.width - stickerFrameWidth) / 2.0), y: floorToScreenPixels((previewSize.height - stickerFrameWidth) / 2.0)), size: CGSize(width: stickerFrameWidth, height: stickerFrameWidth)) let stickerFrameRect = CGRect(origin: CGPoint(x: floorToScreenPixels((previewSize.width - stickerFrameWidth) / 2.0), y: floorToScreenPixels((previewSize.height - stickerFrameWidth) / 2.0)), size: CGSize(width: stickerFrameWidth, height: stickerFrameWidth))
@ -5897,6 +5919,10 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
case .avatarEditor: case .avatarEditor:
overlayInnerRect = UIBezierPath(cgPath: CGPath(ellipseIn: stickerFrameRect, transform: nil)) overlayInnerRect = UIBezierPath(cgPath: CGPath(ellipseIn: stickerFrameRect, transform: nil))
stickerFrameLayer.isHidden = true stickerFrameLayer.isHidden = true
case let .coverEditor(dimensions):
let fittedSize = dimensions.aspectFilled(stickerFrameRect.size)
overlayInnerRect = UIBezierPath(rect: fittedSize.centered(around: stickerFrameRect.center))
stickerFrameLayer.isHidden = true
default: default:
overlayInnerRect = UIBezierPath(cgPath: CGPath(roundedRect: stickerFrameRect, cornerWidth: stickerFrameWidth / 8.0, cornerHeight: stickerFrameWidth / 8.0, transform: nil)) overlayInnerRect = UIBezierPath(cgPath: CGPath(roundedRect: stickerFrameRect, cornerWidth: stickerFrameWidth / 8.0, cornerHeight: stickerFrameWidth / 8.0, transform: nil))
} }
@ -6770,7 +6796,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
let context = self.context let context = self.context
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let controller = UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, loop: true, title: nil, text: presentationData.strings.Story_Editor_TooltipPremiumReaction, undoText: nil, customAction: nil), elevatedLayout: true, position: .top, animateInAsReplacement: false, blurred: true, action: { [weak self] action in let controller = UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, loop: true, title: nil, text: presentationData.strings.Story_Editor_TooltipPremiumReaction, undoText: nil, customAction: nil), elevatedLayout: true, position: .top, animateInAsReplacement: false, appearance: UndoOverlayController.Appearance(isBlurred: true), action: { [weak self] action in
if case .info = action, let self { if case .info = action, let self {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .storiesExpirationDurations, forceDark: true, dismissed: nil) let controller = context.sharedContext.makePremiumIntroController(context: context, source: .storiesExpirationDurations, forceDark: true, dismissed: nil)
self.push(controller) self.push(controller)
@ -6884,7 +6910,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
save = presentationData.strings.Story_Editor_DraftKeepMedia save = presentationData.strings.Story_Editor_DraftKeepMedia
} }
text = presentationData.strings.Story_Editor_DraftDiscaedText text = presentationData.strings.Story_Editor_DraftDiscaedText
case .stickerEditor, .botPreview, .avatarEditor: case .stickerEditor, .botPreview, .avatarEditor, .coverEditor:
title = presentationData.strings.Story_Editor_DraftDiscardMedia title = presentationData.strings.Story_Editor_DraftDiscardMedia
text = presentationData.strings.Story_Editor_DiscardText text = presentationData.strings.Story_Editor_DiscardText
} }

View File

@ -1560,6 +1560,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
} }
} }
let profileGiftsContext = ProfileGiftsContext(account: context.account, peerId: peerId)
return combineLatest( return combineLatest(
context.account.viewTracker.peerView(peerId, updateData: true), context.account.viewTracker.peerView(peerId, updateData: true),
peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: chatLocation, isMyProfile: false, chatLocationContextHolder: chatLocationContextHolder), peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: chatLocation, isMyProfile: false, chatLocationContextHolder: chatLocationContextHolder),
@ -1601,6 +1603,12 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
availablePanes = availablePanesValue availablePanes = availablePanesValue
} }
} }
if availablePanes != nil, let cachedData = peerView.cachedData as? CachedChannelData {
if let starGiftsCount = cachedData.starGiftsCount, starGiftsCount > 0 {
availablePanes?.insert(.gifts, at: hasStories ? 1 : 0)
}
}
} else { } else {
availablePanes = nil availablePanes = nil
} }
@ -1665,7 +1673,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
starsRevenueStatsContext: starsRevenueContextAndState.0, starsRevenueStatsContext: starsRevenueContextAndState.0,
revenueStatsState: revenueContextAndState.1, revenueStatsState: revenueContextAndState.1,
revenueStatsContext: revenueContextAndState.0, revenueStatsContext: revenueContextAndState.0,
profileGiftsContext: nil, profileGiftsContext: profileGiftsContext,
premiumGiftOptions: [], premiumGiftOptions: [],
webAppPermissions: nil webAppPermissions: nil
) )

View File

@ -463,7 +463,15 @@ private final class PeerInfoPendingPane {
let paneNode: PeerInfoPaneNode let paneNode: PeerInfoPaneNode
switch key { switch key {
case .gifts: case .gifts:
paneNode = PeerInfoGiftsPaneNode(context: context, peerId: peerId, chatControllerInteraction: chatControllerInteraction, openPeerContextAction: openPeerContextAction, profileGifts: data.profileGiftsContext!) var canManage = false
if let peer = data.peer {
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
if channel.hasPermission(.sendSomething) {
canManage = true
}
}
}
paneNode = PeerInfoGiftsPaneNode(context: context, peerId: peerId, chatControllerInteraction: chatControllerInteraction, openPeerContextAction: openPeerContextAction, profileGifts: data.profileGiftsContext!, canManage: canManage)
case .stories, .storyArchive, .botPreview: case .stories, .storyArchive, .botPreview:
var canManage = false var canManage = false
if let peer = data.peer { if let peer = data.peer {

View File

@ -6467,8 +6467,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
} else if let channel = peer as? TelegramChannel { } else if let channel = peer as? TelegramChannel {
if let cachedData = strongSelf.data?.cachedData as? CachedChannelData { if let cachedData = strongSelf.data?.cachedData as? CachedChannelData {
if case .broadcast = channel.info { if case .broadcast = channel.info {
//TODO:localize items.append(.action(ContextMenuActionItem(text: presentationData.strings.Profile_SendGift, badge: nil, icon: { theme in
items.append(.action(ContextMenuActionItem(text: "Send a Gift", badge: nil, icon: { theme in
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Gift"), color: theme.contextMenu.primaryColor) generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Gift"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in }, action: { [weak self] _, f in
f(.dismissWithoutContent) f(.dismissWithoutContent)
@ -9597,7 +9596,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
} }
} }
func openAvatarForEditing(mode: PeerInfoAvatarEditingMode = .generic, fromGallery: Bool = false, completion: @escaping (UIImage?) -> Void = { _ in }) { func oldOpenAvatarForEditing(mode: PeerInfoAvatarEditingMode = .generic, fromGallery: Bool = false, completion: @escaping (UIImage?) -> Void = { _ in }) {
guard let peer = self.data?.peer, mode != .generic || canEditPeerInfo(context: self.context, peer: peer, chatLocation: self.chatLocation, threadData: self.data?.threadData) else { guard let peer = self.data?.peer, mode != .generic || canEditPeerInfo(context: self.context, peer: peer, chatLocation: self.chatLocation, threadData: self.data?.threadData) else {
return return
} }
@ -12772,7 +12771,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
public func openAvatarSetup(completedWithUploadingImage: @escaping (UIImage, Signal<PeerInfoAvatarUploadStatus, NoError>) -> UIView?) { public func openAvatarSetup(completedWithUploadingImage: @escaping (UIImage, Signal<PeerInfoAvatarUploadStatus, NoError>) -> UIView?) {
let proceed = { [weak self] in let proceed = { [weak self] in
self?.newopenAvatarForEditing(completedWithUploadingImage: completedWithUploadingImage) self?.openAvatarForEditing(completedWithUploadingImage: completedWithUploadingImage)
} }
if !self.isNodeLoaded { if !self.isNodeLoaded {
self.loadDisplayNode() self.loadDisplayNode()
@ -12796,10 +12795,6 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
}) })
} }
func openAvatarForEditing(mode: PeerInfoAvatarEditingMode = .generic, fromGallery: Bool = false, completion: @escaping (UIImage?) -> Void = { _ in }) {
self.controllerNode.openAvatarForEditing(mode: mode, fromGallery: fromGallery, completion: completion)
}
static func openPeer(context: AccountContext, peerId: PeerId, navigation: ChatControllerInteractionNavigateToPeer, navigationController: NavigationController) { static func openPeer(context: AccountContext, peerId: PeerId, navigation: ChatControllerInteractionNavigateToPeer, navigationController: NavigationController) {
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).startStandalone(next: { peer in |> deliverOnMainQueue).startStandalone(next: { peer in

View File

@ -19,7 +19,7 @@ import LegacyComponents
import LegacyMediaPickerUI import LegacyMediaPickerUI
extension PeerInfoScreenImpl { extension PeerInfoScreenImpl {
func newopenAvatarForEditing(mode: PeerInfoAvatarEditingMode = .generic, fromGallery: Bool = false, completion: @escaping (UIImage?) -> Void = { _ in }, completedWithUploadingImage: @escaping (UIImage, Signal<PeerInfoAvatarUploadStatus, NoError>) -> UIView? = { _, _ in nil }) { func openAvatarForEditing(mode: PeerInfoAvatarEditingMode = .generic, fromGallery: Bool = false, completion: @escaping (UIImage?) -> Void = { _ in }, completedWithUploadingImage: @escaping (UIImage, Signal<PeerInfoAvatarUploadStatus, NoError>) -> UIView? = { _, _ in nil }) {
guard let data = self.controllerNode.data, let peer = data.peer, mode != .generic || canEditPeerInfo(context: self.context, peer: peer, chatLocation: self.chatLocation, threadData: data.threadData) else { guard let data = self.controllerNode.data, let peer = data.peer, mode != .generic || canEditPeerInfo(context: self.context, peer: peer, chatLocation: self.chatLocation, threadData: data.threadData) else {
return return
} }
@ -170,7 +170,6 @@ extension PeerInfoScreenImpl {
default: default:
break break
} }
dismissImpl?() dismissImpl?()
} as (MediaEditorScreenImpl.Result, @escaping (@escaping () -> Void) -> Void) -> Void } as (MediaEditorScreenImpl.Result, @escaping (@escaping () -> Void) -> Void) -> Void
) )
@ -200,6 +199,7 @@ extension PeerInfoScreenImpl {
} }
navigationController.setViewControllers(viewControllers, animated: false) navigationController.setViewControllers(viewControllers, animated: false)
} }
} }
mainController.navigationPresentation = .flatModal mainController.navigationPresentation = .flatModal
mainController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) mainController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
@ -284,10 +284,9 @@ extension PeerInfoScreenImpl {
(self.navigationController?.topViewController as? ViewController)?.present(actionSheet, in: .window(.root)) (self.navigationController?.topViewController as? ViewController)?.present(actionSheet, in: .window(.root))
} }
public func updateProfilePhoto(_ image: UIImage, mode: PeerInfoAvatarEditingMode, uploadStatus: Promise<PeerInfoAvatarUploadStatus>?) { private func setupProfilePhotoUpload(image: UIImage, mode: PeerInfoAvatarEditingMode, indefiniteProgress: Bool) -> LocalFileMediaResource? {
guard let data = image.jpegData(compressionQuality: 0.6) else { guard let data = image.jpegData(compressionQuality: 0.6) else {
uploadStatus?.set(.single(.done)) return nil
return
} }
if self.controllerNode.headerNode.isAvatarExpanded { if self.controllerNode.headerNode.isAvatarExpanded {
@ -303,14 +302,25 @@ extension PeerInfoScreenImpl {
if [.suggest, .fallback].contains(mode) { if [.suggest, .fallback].contains(mode) {
} else { } else {
if indefiniteProgress {
self.controllerNode.state = self.controllerNode.state.withAvatarUploadProgress(.indefinite)
}
self.controllerNode.state = self.controllerNode.state.withUpdatingAvatar(.image(representation)) self.controllerNode.state = self.controllerNode.state.withUpdatingAvatar(.image(representation))
} }
if let (layout, navigationHeight) = self.controllerNode.validLayout { if let (layout, navigationHeight) = self.controllerNode.validLayout {
self.controllerNode.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: mode == .custom ? .animated(duration: 0.2, curve: .easeInOut) : .immediate, additive: false) self.controllerNode.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: mode == .custom ? .animated(duration: 0.2, curve: .easeInOut) : .immediate, additive: false)
} }
self.controllerNode.headerNode.ignoreCollapse = false self.controllerNode.headerNode.ignoreCollapse = false
return resource
}
public func updateProfilePhoto(_ image: UIImage, mode: PeerInfoAvatarEditingMode, uploadStatus: Promise<PeerInfoAvatarUploadStatus>?) {
guard let resource = setupProfilePhotoUpload(image: image, mode: mode, indefiniteProgress: false) else {
uploadStatus?.set(.single(.done))
return
}
let postbox = self.context.account.postbox let postbox = self.context.account.postbox
let signal: Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> let signal: Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError>
if self.isSettings || self.isMyProfile { if self.isSettings || self.isMyProfile {
@ -406,22 +416,227 @@ extension PeerInfoScreenImpl {
})) }))
} }
// public func updateProfileVideo(_ image: UIImage, video: MediaEditorScreenImpl.MediaResult.VideoResult, values: MediaEditorValues, mode: PeerInfoAvatarEditingMode) {
// var markup: UploadPeerPhotoMarkup? = nil
// if let fileId = adjustments?.documentId, let backgroundColors = adjustments?.colors as? [Int32], fileId != 0 {
// if let packId = adjustments?.stickerPackId, let accessHash = adjustments?.stickerPackAccessHash, packId != 0 {
// markup = .sticker(packReference: .id(id: packId, accessHash: accessHash), fileId: fileId, backgroundColors: backgroundColors)
// } else {
// markup = .emoji(fileId: fileId, backgroundColors: backgroundColors)
// }
// }
//
// var videoStartTimestamp: Double? = nil
// if let adjustments = adjustments, adjustments.videoStartValue > 0.0 {
// videoStartTimestamp = adjustments.videoStartValue - adjustments.trimStartValue
// }
//
// var uploadVideo = true
// if let _ = markup {
// if let data = self.context.currentAppConfiguration.with({ $0 }).data, let uploadVideoValue = data["upload_markup_video"] as? Bool, uploadVideoValue {
// uploadVideo = true
// } else {
// uploadVideo = false
// }
// }
//
// guard let photoResource = self.setupProfilePhotoUpload(image: image, mode: mode, indefiniteProgress: !uploadVideo) else {
// return
// }
//
// let context = self.context
//
// let videoResource: Signal<TelegramMediaResource?, UploadPeerPhotoError>
// if uploadVideo {
// let path = NSTemporaryDirectory() + "\(Int64.random(in: Int64.min ... Int64.max)).mp4"
// let videoExport = MediaEditorVideoExport(
// postbox: context.account.postbox,
// subject: .image(image: image),
// configuration: configuration,
// outputPath: path
// )
//
// videoResource = Signal<TelegramMediaResource?, UploadPeerPhotoError> { [weak self] subscriber in
// let entityRenderer: LegacyPaintEntityRenderer? = adjustments.flatMap { adjustments in
// if let paintingData = adjustments.paintingData, paintingData.hasAnimation {
// return LegacyPaintEntityRenderer(postbox: account.postbox, adjustments: adjustments)
// } else {
// return nil
// }
// }
//
// let tempFile = EngineTempBox.shared.tempFile(fileName: "video.mp4")
// let uploadInterface = LegacyLiveUploadInterface(context: context)
// let signal: SSignal
// if let url = asset as? URL, url.absoluteString.hasSuffix(".jpg"), let data = try? Data(contentsOf: url, options: [.mappedRead]), let image = UIImage(data: data), let entityRenderer = entityRenderer {
// let durationSignal: SSignal = SSignal(generator: { subscriber in
// let disposable = (entityRenderer.duration()).start(next: { duration in
// subscriber.putNext(duration)
// subscriber.putCompletion()
// })
//
// return SBlockDisposable(block: {
// disposable.dispose()
// })
// })
// signal = durationSignal.map(toSignal: { duration -> SSignal in
// if let duration = duration as? Double {
// return TGMediaVideoConverter.renderUIImage(image, duration: duration, adjustments: adjustments, path: tempFile.path, watcher: nil, entityRenderer: entityRenderer)!
// } else {
// return SSignal.single(nil)
// }
// })
// } else if let asset = asset as? AVAsset {
// signal = TGMediaVideoConverter.convert(asset, adjustments: adjustments, path: tempFile.path, watcher: uploadInterface, entityRenderer: entityRenderer)!
// } else {
// signal = SSignal.complete()
// }
//
// let signalDisposable = signal.start(next: { next in
// if let result = next as? TGMediaVideoConversionResult {
// if let image = result.coverImage, let data = image.jpegData(compressionQuality: 0.7) {
// account.postbox.mediaBox.storeResourceData(photoResource.id, data: data)
// }
//
// if let timestamp = videoStartTimestamp {
// videoStartTimestamp = max(0.0, min(timestamp, result.duration - 0.05))
// }
//
// var value = stat()
// if stat(result.fileURL.path, &value) == 0 {
// if let data = try? Data(contentsOf: result.fileURL) {
// let resource: TelegramMediaResource
// if let liveUploadData = result.liveUploadData as? LegacyLiveUploadInterfaceResult {
// resource = LocalFileMediaResource(fileId: liveUploadData.id)
// } else {
// resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
// }
// account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true)
// subscriber.putNext(resource)
//
// EngineTempBox.shared.dispose(tempFile)
// }
// }
// subscriber.putCompletion()
// } else if let strongSelf = self, let progress = next as? NSNumber {
// Queue.mainQueue().async {
// strongSelf.controllerNode.state = strongSelf.controllerNode.state.withAvatarUploadProgress(.value(CGFloat(progress.floatValue * 0.45)))
// if let (layout, navigationHeight) = strongSelf.controllerNode.validLayout {
// strongSelf.controllerNode.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
// }
// }
// }
// }, error: { _ in
// }, completed: nil)
//
// let disposable = ActionDisposable {
// signalDisposable?.dispose()
// }
//
// return ActionDisposable {
// disposable.dispose()
// }
// }
// } else {
// videoResource = .single(nil)
// }
//
// var dismissStatus: (() -> Void)?
// if [.suggest, .fallback, .accept].contains(mode) {
// let statusController = OverlayStatusController(theme: self.presentationData.theme, type: .loading(cancelled: { [weak self] in
// self?.controllerNode.updateAvatarDisposable.set(nil)
// dismissStatus?()
// }))
// dismissStatus = { [weak statusController] in
// statusController?.dismiss()
// }
// if let topController = self.navigationController?.topViewController as? ViewController {
// topController.presentInGlobalOverlay(statusController)
// } else if let topController = self.parentController?.topViewController as? ViewController {
// topController.presentInGlobalOverlay(statusController)
// } else {
// self.presentInGlobalOverlay(statusController)
// }
// }
//
// let peerId = self.peerId
// let isSettings = self.isSettings
// let isMyProfile = self.isMyProfile
// self.controllerNode.updateAvatarDisposable.set((videoResource
// |> mapToSignal { videoResource -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> in
// if isSettings || isMyProfile {
// if case .fallback = mode {
// return context.engine.accountData.updateFallbackPhoto(resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in
// return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations)
// })
// } else {
// return context.engine.accountData.updateAccountPhoto(resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in
// return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations)
// })
// }
// } else if case .custom = mode {
// return context.engine.contacts.updateContactPhoto(peerId: peerId, resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mode: .custom, mapResourceToAvatarSizes: { resource, representations in
// return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations)
// })
// } else if case .suggest = mode {
// return context.engine.contacts.updateContactPhoto(peerId: peerId, resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mode: .suggest, mapResourceToAvatarSizes: { resource, representations in
// return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations)
// })
// } else {
// return context.engine.peers.updatePeerPhoto(peerId: peerId, photo: context.engine.peers.uploadedPeerPhoto(resource: photoResource), video: videoResource.flatMap { context.engine.peers.uploadedPeerVideo(resource: $0) |> map(Optional.init) }, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in
// return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations)
// })
// }
// }
// |> deliverOnMainQueue).startStrict(next: { [weak self] result in
// guard let strongSelf = self else {
// return
// }
// switch result {
// case .complete:
// strongSelf.controllerNode.state = strongSelf.controllerNode.state.withUpdatingAvatar(nil).withAvatarUploadProgress(nil)
// case let .progress(value):
// strongSelf.controllerNode.state = strongSelf.controllerNode.state.withAvatarUploadProgress(.value(CGFloat(0.45 + value * 0.55)))
// }
// if let (layout, navigationHeight) = strongSelf.controllerNode.validLayout {
// strongSelf.controllerNode.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
// }
//
// if case .complete = result {
// dismissStatus?()
//
// let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.peerId))
// |> deliverOnMainQueue).startStandalone(next: { [weak self] peer in
// if let strongSelf = self, let peer {
// switch mode {
// case .fallback:
// (strongSelf.parentController?.topViewController as? ViewController)?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .image(image: image, title: nil, text: strongSelf.presentationData.strings.Privacy_ProfilePhoto_PublicVideoSuccess, round: true, undoText: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
// case .custom:
// strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, title: nil, text: strongSelf.presentationData.strings.UserInfo_SetCustomPhoto_SuccessVideoText(peer.compactDisplayTitle).string, action: nil, duration: 5), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
//
// let _ = (strongSelf.context.peerChannelMemberCategoriesContextsManager.profilePhotos(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, peerId: strongSelf.peerId, fetch: peerInfoProfilePhotos(context: strongSelf.context, peerId: strongSelf.peerId)) |> ignoreValues).startStandalone()
// case .suggest:
// if let navigationController = (strongSelf.navigationController as? NavigationController) {
// strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), keepStack: .default, completion: { _ in
// }))
// }
// case .accept:
// (strongSelf.parentController?.topViewController as? ViewController)?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .image(image: image, title: strongSelf.presentationData.strings.Conversation_SuggestedVideoSuccess, text: strongSelf.presentationData.strings.Conversation_SuggestedVideoSuccessText, round: true, undoText: nil), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] action in
// if case .info = action {
// self?.parentController?.openSettings()
// }
// return false
// }), in: .current)
// default:
// break
// }
// }
// })
// }
// }))
// }
public func updateProfileVideo(_ image: UIImage, asset: Any?, adjustments: TGVideoEditAdjustments?, mode: PeerInfoAvatarEditingMode) { public func updateProfileVideo(_ image: UIImage, asset: Any?, adjustments: TGVideoEditAdjustments?, mode: PeerInfoAvatarEditingMode) {
guard let data = image.jpegData(compressionQuality: 0.6) else {
return
}
if self.controllerNode.headerNode.isAvatarExpanded {
self.controllerNode.headerNode.ignoreCollapse = true
self.controllerNode.headerNode.updateIsAvatarExpanded(false, transition: .immediate)
self.controllerNode.updateNavigationExpansionPresentation(isExpanded: false, animated: true)
}
self.controllerNode.scrollNode.view.setContentOffset(CGPoint(), animated: false)
let photoResource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
self.context.account.postbox.mediaBox.storeResourceData(photoResource.id, data: data)
let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: photoResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: mode == .custom ? true : false)
var markup: UploadPeerPhotoMarkup? = nil var markup: UploadPeerPhotoMarkup? = nil
if let fileId = adjustments?.documentId, let backgroundColors = adjustments?.colors as? [Int32], fileId != 0 { if let fileId = adjustments?.documentId, let backgroundColors = adjustments?.colors as? [Int32], fileId != 0 {
if let packId = adjustments?.stickerPackId, let accessHash = adjustments?.stickerPackAccessHash, packId != 0 { if let packId = adjustments?.stickerPackId, let accessHash = adjustments?.stickerPackAccessHash, packId != 0 {
@ -439,19 +654,9 @@ extension PeerInfoScreenImpl {
uploadVideo = false uploadVideo = false
} }
} }
guard let photoResource = self.setupProfilePhotoUpload(image: image, mode: mode, indefiniteProgress: !uploadVideo) else {
if [.suggest, .fallback].contains(mode) { return
} else {
self.controllerNode.state = self.controllerNode.state.withUpdatingAvatar(.image(representation))
if !uploadVideo {
self.controllerNode.state = self.controllerNode.state.withAvatarUploadProgress(.indefinite)
} }
}
if let (layout, navigationHeight) = self.controllerNode.validLayout {
self.controllerNode.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: mode == .custom ? .animated(duration: 0.2, curve: .easeInOut) : .immediate, additive: false)
}
self.controllerNode.headerNode.ignoreCollapse = false
var videoStartTimestamp: Double? = nil var videoStartTimestamp: Double? = nil
if let adjustments = adjustments, adjustments.videoStartValue > 0.0 { if let adjustments = adjustments, adjustments.videoStartValue > 0.0 {

View File

@ -52,6 +52,7 @@ swift_library(
"//submodules/TelegramUI/Components/Gifts/GiftViewScreen", "//submodules/TelegramUI/Components/Gifts/GiftViewScreen",
"//submodules/TelegramUI/Components/ButtonComponent", "//submodules/TelegramUI/Components/ButtonComponent",
"//submodules/Components/BalancedTextComponent", "//submodules/Components/BalancedTextComponent",
"//submodules/TelegramUI/Components/CheckComponent",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -22,11 +22,14 @@ import GiftItemComponent
import PlainButtonComponent import PlainButtonComponent
import GiftViewScreen import GiftViewScreen
import SolidRoundedButtonNode import SolidRoundedButtonNode
import UndoUI
import CheckComponent
public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDelegate { public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDelegate {
private let context: AccountContext private let context: AccountContext
private let peerId: PeerId private let peerId: PeerId
private let profileGifts: ProfileGiftsContext private let profileGifts: ProfileGiftsContext
private let canManage: Bool
private var dataDisposable: Disposable? private var dataDisposable: Disposable?
@ -38,10 +41,11 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
private let backgroundNode: ASDisplayNode private let backgroundNode: ASDisplayNode
private let scrollNode: ASScrollNode private let scrollNode: ASScrollNode
private var unlockBackground: NavigationBackgroundNode? private var footerText: ComponentView<Empty>?
private var unlockSeparator: ASDisplayNode? private var panelBackground: NavigationBackgroundNode?
private var unlockText: ComponentView<Empty>? private var panelSeparator: ASDisplayNode?
private var unlockButton: SolidRoundedButtonNode? private var panelButton: SolidRoundedButtonNode?
private var panelCheck: ComponentView<Empty>?
private var currentParams: (size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, presentationData: PresentationData)? private var currentParams: (size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, presentationData: PresentationData)?
@ -68,12 +72,13 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
private var starsItems: [AnyHashable: ComponentView<Empty>] = [:] private var starsItems: [AnyHashable: ComponentView<Empty>] = [:]
public init(context: AccountContext, peerId: PeerId, chatControllerInteraction: ChatControllerInteraction, openPeerContextAction: @escaping (Bool, Peer, ASDisplayNode, ContextGesture?) -> Void, profileGifts: ProfileGiftsContext) { public init(context: AccountContext, peerId: PeerId, chatControllerInteraction: ChatControllerInteraction, openPeerContextAction: @escaping (Bool, Peer, ASDisplayNode, ContextGesture?) -> Void, profileGifts: ProfileGiftsContext, canManage: Bool) {
self.context = context self.context = context
self.peerId = peerId self.peerId = peerId
self.chatControllerInteraction = chatControllerInteraction self.chatControllerInteraction = chatControllerInteraction
self.openPeerContextAction = openPeerContextAction self.openPeerContextAction = openPeerContextAction
self.profileGifts = profileGifts self.profileGifts = profileGifts
self.canManage = canManage
self.backgroundNode = ASDisplayNode() self.backgroundNode = ASDisplayNode()
self.scrollNode = ASScrollNode() self.scrollNode = ASScrollNode()
@ -125,15 +130,16 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
self.updateScrolling(transition: .immediate) self.updateScrolling(transition: .immediate)
} }
private var notify = false
func updateScrolling(transition: ComponentTransition) { func updateScrolling(transition: ComponentTransition) {
if let starsProducts = self.starsProducts, let params = self.currentParams { if let starsProducts = self.starsProducts, let params = self.currentParams {
let optionSpacing: CGFloat = 10.0 let optionSpacing: CGFloat = 10.0
let sideInset = params.sideInset + 16.0 let itemsSideInset = params.sideInset + 16.0
let defaultItemsInRow = 3 let defaultItemsInRow = 3
let itemsInRow = max(1, min(starsProducts.count, defaultItemsInRow)) let itemsInRow = max(1, min(starsProducts.count, defaultItemsInRow))
let defaultOptionWidth = (params.size.width - sideInset * 2.0 - optionSpacing * CGFloat(defaultItemsInRow - 1)) / CGFloat(defaultItemsInRow) let defaultOptionWidth = (params.size.width - itemsSideInset * 2.0 - optionSpacing * CGFloat(defaultItemsInRow - 1)) / CGFloat(defaultItemsInRow)
let optionWidth = (params.size.width - sideInset * 2.0 - optionSpacing * CGFloat(itemsInRow - 1)) / CGFloat(itemsInRow) let optionWidth = (params.size.width - itemsSideInset * 2.0 - optionSpacing * CGFloat(itemsInRow - 1)) / CGFloat(itemsInRow)
let starsOptionSize = CGSize(width: optionWidth, height: defaultOptionWidth) let starsOptionSize = CGSize(width: optionWidth, height: defaultOptionWidth)
@ -142,7 +148,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
let topInset: CGFloat = 60.0 let topInset: CGFloat = 60.0
var validIds: [AnyHashable] = [] var validIds: [AnyHashable] = []
var itemFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset), size: starsOptionSize) var itemFrame = CGRect(origin: CGPoint(x: itemsSideInset, y: topInset), size: starsOptionSize)
var index: Int32 = 0 var index: Int32 = 0
for product in starsProducts { for product in starsProducts {
@ -233,22 +239,22 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
self.profileGifts.updateStarGiftAddedToProfile(reference: reference, added: added) self.profileGifts.updateStarGiftAddedToProfile(reference: reference, added: added)
}, },
convertToStars: { [weak self] in convertToStars: { [weak self] in
guard let self else { guard let self, let reference = product.reference else {
return return
} }
self.profileGifts.convertStarGift(reference: product.reference) self.profileGifts.convertStarGift(reference: reference)
}, },
transferGift: { [weak self] prepaid, peerId in transferGift: { [weak self] prepaid, peerId in
guard let self else { guard let self, let reference = product.reference else {
return return
} }
self.profileGifts.transferStarGift(prepaid: prepaid, reference: product.reference, peerId: peerId) self.profileGifts.transferStarGift(prepaid: prepaid, reference: reference, peerId: peerId)
}, },
upgradeGift: { [weak self] formId, keepOriginalInfo in upgradeGift: { [weak self] formId, keepOriginalInfo in
guard let self else { guard let self, let reference = product.reference else {
return .never() return .never()
} }
return self.profileGifts.upgradeStarGift(formId: formId, reference: product.reference, keepOriginalInfo: keepOriginalInfo) return self.profileGifts.upgradeStarGift(formId: formId, reference: reference, keepOriginalInfo: keepOriginalInfo)
}, },
shareStory: { [weak self] in shareStory: { [weak self] in
guard let self, case let .unique(uniqueGift) = product.gift, let parentController = self.parentController else { guard let self, case let .unique(uniqueGift) = product.gift, let parentController = self.parentController else {
@ -278,7 +284,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
} }
itemFrame.origin.x += itemFrame.width + optionSpacing itemFrame.origin.x += itemFrame.width + optionSpacing
if itemFrame.maxX > params.size.width { if itemFrame.maxX > params.size.width {
itemFrame.origin.x = sideInset itemFrame.origin.x = itemsSideInset
itemFrame.origin.y += starsOptionSize.height + optionSpacing itemFrame.origin.y += starsOptionSize.height + optionSpacing
} }
index += 1 index += 1
@ -306,7 +312,8 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
var bottomScrollInset: CGFloat = 0.0 var bottomScrollInset: CGFloat = 0.0
var contentHeight = ceil(CGFloat(starsProducts.count) / 3.0) * (starsOptionSize.height + optionSpacing) - optionSpacing + topInset + 16.0 var contentHeight = ceil(CGFloat(starsProducts.count) / 3.0) * (starsOptionSize.height + optionSpacing) - optionSpacing + topInset + 16.0
if self.peerId == self.context.account.peerId {
let transition = ComponentTransition.immediate let transition = ComponentTransition.immediate
let size = params.size let size = params.size
@ -317,51 +324,44 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
let themeUpdated = self.theme !== presentationData.theme let themeUpdated = self.theme !== presentationData.theme
self.theme = presentationData.theme self.theme = presentationData.theme
let unlockText: ComponentView<Empty> let panelBackground: NavigationBackgroundNode
let unlockBackground: NavigationBackgroundNode let panelSeparator: ASDisplayNode
let unlockSeparator: ASDisplayNode let panelButton: SolidRoundedButtonNode
let unlockButton: SolidRoundedButtonNode
if let current = self.unlockText { if let current = self.panelBackground {
unlockText = current panelBackground = current
} else { } else {
unlockText = ComponentView<Empty>() panelBackground = NavigationBackgroundNode(color: presentationData.theme.rootController.tabBar.backgroundColor)
self.unlockText = unlockText self.addSubnode(panelBackground)
self.panelBackground = panelBackground
} }
if let current = self.unlockBackground { if let current = self.panelSeparator {
unlockBackground = current panelSeparator = current
} else { } else {
unlockBackground = NavigationBackgroundNode(color: presentationData.theme.rootController.tabBar.backgroundColor) panelSeparator = ASDisplayNode()
self.addSubnode(unlockBackground) self.addSubnode(panelSeparator)
self.unlockBackground = unlockBackground self.panelSeparator = panelSeparator
} }
if let current = self.unlockSeparator { if let current = self.panelButton {
unlockSeparator = current panelButton = current
} else { } else {
unlockSeparator = ASDisplayNode() panelButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: presentationData.theme), height: 50.0, cornerRadius: 10.0)
self.addSubnode(unlockSeparator) self.view.addSubview(panelButton.view)
self.unlockSeparator = unlockSeparator self.panelButton = panelButton
}
if let current = self.unlockButton { panelButton.title = self.peerId == self.context.account.peerId ? params.presentationData.strings.PeerInfo_Gifts_Send : params.presentationData.strings.PeerInfo_Gifts_SendGift
unlockButton = current
} else {
unlockButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: presentationData.theme), height: 50.0, cornerRadius: 10.0)
self.view.addSubview(unlockButton.view)
self.unlockButton = unlockButton
unlockButton.title = params.presentationData.strings.PeerInfo_Gifts_Send panelButton.pressed = { [weak self] in
unlockButton.pressed = { [weak self] in
self?.buttonPressed() self?.buttonPressed()
} }
} }
if themeUpdated { if themeUpdated {
unlockBackground.updateColor(color: presentationData.theme.rootController.tabBar.backgroundColor, transition: .immediate) panelBackground.updateColor(color: presentationData.theme.rootController.tabBar.backgroundColor, transition: .immediate)
unlockSeparator.backgroundColor = presentationData.theme.rootController.tabBar.separatorColor panelSeparator.backgroundColor = presentationData.theme.rootController.tabBar.separatorColor
unlockButton.updateTheme(SolidRoundedButtonTheme(theme: presentationData.theme)) panelButton.updateTheme(SolidRoundedButtonTheme(theme: presentationData.theme))
} }
let textFont = Font.regular(13.0) let textFont = Font.regular(13.0)
@ -376,23 +376,100 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
let buttonSideInset = sideInset + 16.0 let buttonSideInset = sideInset + 16.0
let buttonSize = CGSize(width: size.width - buttonSideInset * 2.0, height: 50.0) let buttonSize = CGSize(width: size.width - buttonSideInset * 2.0, height: 50.0)
let bottomPanelHeight = bottomInset + buttonSize.height + 8.0 var bottomPanelHeight = bottomInset + buttonSize.height + 8.0
if params.visibleHeight < 110.0 { if params.visibleHeight < 110.0 {
scrollOffset -= bottomPanelHeight scrollOffset -= bottomPanelHeight
} }
transition.setFrame(view: unlockButton.view, frame: CGRect(origin: CGPoint(x: buttonSideInset, y: size.height - bottomInset - buttonSize.height - scrollOffset), size: buttonSize)) transition.setFrame(view: panelButton.view, frame: CGRect(origin: CGPoint(x: buttonSideInset, y: size.height - bottomInset - buttonSize.height - scrollOffset), size: buttonSize))
let _ = unlockButton.updateLayout(width: buttonSize.width, transition: .immediate) let _ = panelButton.updateLayout(width: buttonSize.width, transition: .immediate)
transition.setFrame(view: unlockBackground.view, frame: CGRect(x: 0.0, y: size.height - bottomInset - buttonSize.height - 8.0 - scrollOffset, width: size.width, height: bottomPanelHeight)) if self.canManage {
unlockBackground.update(size: CGSize(width: size.width, height: bottomPanelHeight), transition: transition.containedViewLayoutTransition) bottomPanelHeight -= 9.0
transition.setFrame(view: unlockSeparator.view, frame: CGRect(x: 0.0, y: size.height - bottomInset - buttonSize.height - 8.0 - scrollOffset, width: size.width, height: UIScreenPixel))
let unlockSize = unlockText.update( let panelCheck: ComponentView<Empty>
if let current = self.panelCheck {
panelCheck = current
} else {
panelCheck = ComponentView<Empty>()
self.panelCheck = panelCheck
}
let checkTheme = CheckComponent.Theme(
backgroundColor: presentationData.theme.list.itemCheckColors.fillColor,
strokeColor: presentationData.theme.list.itemCheckColors.foregroundColor,
borderColor: presentationData.theme.list.itemCheckColors.strokeColor,
overlayBorder: false,
hasInset: false,
hasShadow: false
)
let panelCheckSize = panelCheck.update(
transition: .immediate,
component: AnyComponent(
PlainButtonComponent(
content: AnyComponent(HStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(CheckComponent(
theme: checkTheme,
size: CGSize(width: 22.0, height: 22.0),
selected: self.notify
))),
AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: presentationData.strings.PeerInfo_Gifts_ChannelNotify, font: Font.regular(17.0), textColor: presentationData.theme.list.itemPrimaryTextColor))
)))
],
spacing: 16.0
)),
effectAlignment: .center,
action: { [weak self] in
guard let self else {
return
}
self.notify = !self.notify
if self.notify {
let controller = UndoOverlayController(
presentationData: presentationData,
content: .universal(animation: "anim_profileunmute", scale: 0.075, colors: ["__allcolors__": UIColor.white], title: nil, text: presentationData.strings.PeerInfo_Gifts_ChannelNotifyTooltip, customUndoText: nil, timeout: nil),
appearance: UndoOverlayController.Appearance(bottomInset: 53.0),
action: { _ in return true }
)
self.chatControllerInteraction.presentController(controller, nil)
}
self.updateScrolling(transition: .immediate)
},
animateAlpha: false,
animateScale: false
)
),
environment: {},
containerSize: buttonSize
)
if let panelCheckView = panelCheck.view {
if panelCheckView.superview == nil {
self.view.addSubview(panelCheckView)
}
panelCheckView.frame = CGRect(origin: CGPoint(x: floor((size.width - panelCheckSize.width) / 2.0), y: size.height - bottomInset - panelCheckSize.height - 11.0 - scrollOffset), size: panelCheckSize)
}
panelButton.isHidden = true
}
transition.setFrame(view: panelBackground.view, frame: CGRect(x: 0.0, y: size.height - bottomPanelHeight - scrollOffset, width: size.width, height: bottomPanelHeight))
panelBackground.update(size: CGSize(width: size.width, height: bottomPanelHeight), transition: transition.containedViewLayoutTransition)
transition.setFrame(view: panelSeparator.view, frame: CGRect(x: 0.0, y: size.height - bottomPanelHeight - scrollOffset, width: size.width, height: UIScreenPixel))
if self.peerId == self.context.account.peerId {
let footerText: ComponentView<Empty>
if let current = self.footerText {
footerText = current
} else {
footerText = ComponentView<Empty>()
self.footerText = footerText
}
let footerTextSize = footerText.update(
transition: .immediate, transition: .immediate,
component: AnyComponent( component: AnyComponent(
BalancedTextComponent( BalancedTextComponent(
text: .markdown(text: params.presentationData.strings.PeerInfo_Gifts_Info, attributes: markdownAttributes), text: .markdown(text: presentationData.strings.PeerInfo_Gifts_Info, attributes: markdownAttributes),
horizontalAlignment: .center, horizontalAlignment: .center,
maximumNumberOfLines: 0, maximumNumberOfLines: 0,
lineSpacing: 0.2 lineSpacing: 0.2
@ -401,18 +478,19 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
environment: {}, environment: {},
containerSize: CGSize(width: size.width - 32.0, height: 200.0) containerSize: CGSize(width: size.width - 32.0, height: 200.0)
) )
if let view = unlockText.view { if let view = footerText.view {
if view.superview == nil { if view.superview == nil {
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.buttonPressed))) view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.buttonPressed)))
self.scrollNode.view.addSubview(view) self.scrollNode.view.addSubview(view)
} }
transition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: floor((size.width - unlockSize.width) / 2.0), y: contentHeight), size: unlockSize)) transition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: floor((size.width - footerTextSize.width) / 2.0), y: contentHeight), size: footerTextSize))
}
contentHeight += footerTextSize.height
} }
contentHeight += unlockSize.height
contentHeight += bottomPanelHeight contentHeight += bottomPanelHeight
bottomScrollInset = bottomPanelHeight - 40.0 bottomScrollInset = bottomPanelHeight - 40.0
}
contentHeight += params.bottomInset contentHeight += params.bottomInset
self.scrollNode.view.scrollIndicatorInsets = UIEdgeInsets(top: 50.0, left: 0.0, bottom: bottomScrollInset, right: 0.0) self.scrollNode.view.scrollIndicatorInsets = UIEdgeInsets(top: 50.0, left: 0.0, bottom: bottomScrollInset, right: 0.0)
@ -430,6 +508,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
} }
@objc private func buttonPressed() { @objc private func buttonPressed() {
if self.peerId == self.context.account.peerId {
let _ = (self.context.account.stateManager.contactBirthdays let _ = (self.context.account.stateManager.contactBirthdays
|> take(1) |> take(1)
|> deliverOnMainQueue).start(next: { [weak self] birthdays in |> deliverOnMainQueue).start(next: { [weak self] birthdays in
@ -440,6 +519,10 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
controller.navigationPresentation = .modal controller.navigationPresentation = .modal
self.chatControllerInteraction.navigationController()?.pushViewController(controller) self.chatControllerInteraction.navigationController()?.pushViewController(controller)
}) })
} else {
let controller = self.context.sharedContext.makeGiftOptionsController(context: self.context, peerId: self.peerId, premiumOptions: [], hasBirthday: false)
self.chatControllerInteraction.navigationController()?.pushViewController(controller)
}
} }
public func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { public func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) {

View File

@ -883,7 +883,11 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding, ListShimme
selectedMedia = image selectedMedia = image
break break
} else if let file = media as? TelegramMediaFile { } else if let file = media as? TelegramMediaFile {
if let cover = file.videoCover {
selectedMedia = cover
} else {
selectedMedia = file selectedMedia = file
}
break break
} }
} }

View File

@ -3435,7 +3435,7 @@ public final class StoryItemSetContainerComponent: Component {
elevatedLayout: false, elevatedLayout: false,
position: .top, position: .top,
animateInAsReplacement: false, animateInAsReplacement: false,
blurred: true, appearance: UndoOverlayController.Appearance(isBlurred: true),
action: { _ in return false } action: { _ in return false }
), nil) ), nil)
}))) })))
@ -3456,7 +3456,7 @@ public final class StoryItemSetContainerComponent: Component {
elevatedLayout: false, elevatedLayout: false,
position: .top, position: .top,
animateInAsReplacement: false, animateInAsReplacement: false,
blurred: true, appearance: UndoOverlayController.Appearance(isBlurred: true),
action: { _ in return false } action: { _ in return false }
), nil) ), nil)
}))) })))
@ -3492,7 +3492,7 @@ public final class StoryItemSetContainerComponent: Component {
elevatedLayout: false, elevatedLayout: false,
position: .top, position: .top,
animateInAsReplacement: false, animateInAsReplacement: false,
blurred: true, appearance: UndoOverlayController.Appearance(isBlurred: true),
action: { [weak self] action in action: { [weak self] action in
guard let self, let component = self.component else { guard let self, let component = self.component else {
return false return false
@ -3539,7 +3539,7 @@ public final class StoryItemSetContainerComponent: Component {
elevatedLayout: false, elevatedLayout: false,
position: .top, position: .top,
animateInAsReplacement: false, animateInAsReplacement: false,
blurred: true, appearance: UndoOverlayController.Appearance(isBlurred: true),
action: { [weak self] action in action: { [weak self] action in
guard let self, let component = self.component else { guard let self, let component = self.component else {
return false return false
@ -4292,7 +4292,7 @@ public final class StoryItemSetContainerComponent: Component {
storeAttributedTextInPasteboard(text) storeAttributedTextInPasteboard(text)
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let undoController = UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: false, animateInAsReplacement: false, blurred: true, action: { _ in true }) let undoController = UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: false, animateInAsReplacement: false, appearance: UndoOverlayController.Appearance(isBlurred: true), action: { _ in true })
self.sendMessageContext.tooltipScreen?.dismiss() self.sendMessageContext.tooltipScreen?.dismiss()
self.sendMessageContext.tooltipScreen = undoController self.sendMessageContext.tooltipScreen = undoController
component.controller()?.present(undoController, in: .current) component.controller()?.present(undoController, in: .current)
@ -4750,7 +4750,7 @@ public final class StoryItemSetContainerComponent: Component {
controller?.replace(with: c) controller?.replace(with: c)
} }
component.controller()?.push(controller) component.controller()?.push(controller)
}), elevatedLayout: false, animateInAsReplacement: false, blurred: true, action: { _ in true }) }), elevatedLayout: false, animateInAsReplacement: false, appearance: UndoOverlayController.Appearance(isBlurred: true), action: { _ in true })
component.controller()?.present(undoController, in: .current) component.controller()?.present(undoController, in: .current)
} }
} }
@ -5025,7 +5025,7 @@ public final class StoryItemSetContainerComponent: Component {
content: .info(title: nil, text: text, timeout: nil, customUndoText: nil), content: .info(title: nil, text: text, timeout: nil, customUndoText: nil),
elevatedLayout: false, elevatedLayout: false,
animateInAsReplacement: false, animateInAsReplacement: false,
blurred: true, appearance: UndoOverlayController.Appearance(isBlurred: true),
action: { _ in return false } action: { _ in return false }
) )
self.sendMessageContext.tooltipScreen = controller self.sendMessageContext.tooltipScreen = controller
@ -5471,7 +5471,7 @@ public final class StoryItemSetContainerComponent: Component {
), ),
elevatedLayout: false, elevatedLayout: false,
animateInAsReplacement: false, animateInAsReplacement: false,
blurred: true, appearance: UndoOverlayController.Appearance(isBlurred: true),
action: { [weak self] action in action: { [weak self] action in
guard let self else { guard let self else {
return false return false
@ -6196,7 +6196,7 @@ public final class StoryItemSetContainerComponent: Component {
content: .info(title: nil, text: component.strings.Story_ToastRemovedFromProfileText, timeout: nil, customUndoText: nil), content: .info(title: nil, text: component.strings.Story_ToastRemovedFromProfileText, timeout: nil, customUndoText: nil),
elevatedLayout: false, elevatedLayout: false,
animateInAsReplacement: false, animateInAsReplacement: false,
blurred: true, appearance: UndoOverlayController.Appearance(isBlurred: true),
action: { _ in return false } action: { _ in return false }
), nil) ), nil)
} else { } else {
@ -6205,7 +6205,7 @@ public final class StoryItemSetContainerComponent: Component {
content: .info(title: component.strings.Story_ToastSavedToProfileTitle, text: component.strings.Story_ToastSavedToProfileText, timeout: nil, customUndoText: nil), content: .info(title: component.strings.Story_ToastSavedToProfileTitle, text: component.strings.Story_ToastSavedToProfileText, timeout: nil, customUndoText: nil),
elevatedLayout: false, elevatedLayout: false,
animateInAsReplacement: false, animateInAsReplacement: false,
blurred: true, appearance: UndoOverlayController.Appearance(isBlurred: true),
action: { _ in return false } action: { _ in return false }
), nil) ), nil)
} }
@ -6263,7 +6263,7 @@ public final class StoryItemSetContainerComponent: Component {
content: .linkCopied(title: nil, text: component.strings.Story_ToastLinkCopied), content: .linkCopied(title: nil, text: component.strings.Story_ToastLinkCopied),
elevatedLayout: false, elevatedLayout: false,
animateInAsReplacement: false, animateInAsReplacement: false,
blurred: true, appearance: UndoOverlayController.Appearance(isBlurred: true),
action: { _ in return false } action: { _ in return false }
), nil) ), nil)
} }
@ -6403,7 +6403,7 @@ public final class StoryItemSetContainerComponent: Component {
content: .info(title: nil, text: isGroup ? presentationData.strings.Story_ToastRemovedFromGroupText : presentationData.strings.Story_ToastRemovedFromChannelText, timeout: nil, customUndoText: nil), content: .info(title: nil, text: isGroup ? presentationData.strings.Story_ToastRemovedFromGroupText : presentationData.strings.Story_ToastRemovedFromChannelText, timeout: nil, customUndoText: nil),
elevatedLayout: false, elevatedLayout: false,
animateInAsReplacement: false, animateInAsReplacement: false,
blurred: true, appearance: UndoOverlayController.Appearance(isBlurred: true),
action: { _ in return false } action: { _ in return false }
) )
} else { } else {
@ -6412,7 +6412,7 @@ public final class StoryItemSetContainerComponent: Component {
content: .info(title: isGroup ? presentationData.strings.Story_ToastSavedToGroupTitle : presentationData.strings.Story_ToastSavedToChannelTitle, text: isGroup ? presentationData.strings.Story_ToastSavedToGroupText : presentationData.strings.Story_ToastSavedToChannelText, timeout: nil, customUndoText: nil), content: .info(title: isGroup ? presentationData.strings.Story_ToastSavedToGroupTitle : presentationData.strings.Story_ToastSavedToChannelTitle, text: isGroup ? presentationData.strings.Story_ToastSavedToGroupText : presentationData.strings.Story_ToastSavedToChannelText, timeout: nil, customUndoText: nil),
elevatedLayout: false, elevatedLayout: false,
animateInAsReplacement: false, animateInAsReplacement: false,
blurred: true, appearance: UndoOverlayController.Appearance(isBlurred: true),
action: { _ in return false } action: { _ in return false }
), nil) ), nil)
} }
@ -6476,7 +6476,7 @@ public final class StoryItemSetContainerComponent: Component {
content: .linkCopied(title: nil, text: component.strings.Story_ToastLinkCopied), content: .linkCopied(title: nil, text: component.strings.Story_ToastLinkCopied),
elevatedLayout: false, elevatedLayout: false,
animateInAsReplacement: false, animateInAsReplacement: false,
blurred: true, appearance: UndoOverlayController.Appearance(isBlurred: true),
action: { _ in return false } action: { _ in return false }
), nil) ), nil)
} }
@ -6730,7 +6730,7 @@ public final class StoryItemSetContainerComponent: Component {
], title: nil, text: component.strings.StoryFeed_TooltipNotifyOn(component.slice.effectivePeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string, customUndoText: nil, timeout: nil), ], title: nil, text: component.strings.StoryFeed_TooltipNotifyOn(component.slice.effectivePeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string, customUndoText: nil, timeout: nil),
elevatedLayout: false, elevatedLayout: false,
animateInAsReplacement: false, animateInAsReplacement: false,
blurred: true, appearance: UndoOverlayController.Appearance(isBlurred: true),
action: { _ in return false } action: { _ in return false }
), nil) ), nil)
} else { } else {
@ -6745,7 +6745,7 @@ public final class StoryItemSetContainerComponent: Component {
], title: nil, text: component.strings.StoryFeed_TooltipNotifyOff(component.slice.effectivePeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string, customUndoText: nil, timeout: nil), ], title: nil, text: component.strings.StoryFeed_TooltipNotifyOff(component.slice.effectivePeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string, customUndoText: nil, timeout: nil),
elevatedLayout: false, elevatedLayout: false,
animateInAsReplacement: false, animateInAsReplacement: false,
blurred: true, appearance: UndoOverlayController.Appearance(isBlurred: true),
action: { _ in return false } action: { _ in return false }
), nil) ), nil)
} }
@ -6775,7 +6775,7 @@ public final class StoryItemSetContainerComponent: Component {
content: .linkCopied(title: nil, text: component.strings.Story_ToastLinkCopied), content: .linkCopied(title: nil, text: component.strings.Story_ToastLinkCopied),
elevatedLayout: false, elevatedLayout: false,
animateInAsReplacement: false, animateInAsReplacement: false,
blurred: true, appearance: UndoOverlayController.Appearance(isBlurred: true),
action: { _ in return false } action: { _ in return false }
), nil) ), nil)
} }
@ -6819,7 +6819,7 @@ public final class StoryItemSetContainerComponent: Component {
content: .info(title: title, text: text, timeout: nil, customUndoText: nil), content: .info(title: title, text: text, timeout: nil, customUndoText: nil),
elevatedLayout: false, elevatedLayout: false,
animateInAsReplacement: false, animateInAsReplacement: false,
blurred: true, appearance: UndoOverlayController.Appearance(isBlurred: true),
action: { _ in return false } action: { _ in return false }
), in: .current) ), in: .current)
@ -6995,44 +6995,6 @@ public final class StoryItemSetContainerComponent: Component {
}, },
requestSelectMessages: nil requestSelectMessages: nil
) )
// let options: [PeerReportOption] = [.spam, .violence, .pornography, .childAbuse, .copyright, .illegalDrugs, .personalDetails, .other]
// presentPeerReportOptions(
// context: component.context,
// parent: controller,
// contextController: c,
// backAction: { _ in },
// subject: .story(component.slice.effectivePeer.id, component.slice.item.storyItem.id),
// options: options,
// passthrough: true,
// forceTheme: defaultDarkPresentationTheme,
// isDetailedReportingVisible: { [weak self] isReporting in
// guard let self else {
// return
// }
// self.isReporting = isReporting
// self.updateIsProgressPaused()
// },
// completion: { [weak self] reason, _ in
// guard let self, let component = self.component, let controller = component.controller(), let reason else {
// return
// }
// let _ = component.context.engine.peers.reportPeerStory(peerId: component.slice.effectivePeer.id, storyId: component.slice.item.storyItem.id, reason: reason, message: "").startStandalone()
// controller.present(
// UndoOverlayController(
// presentationData: presentationData,
// content: .emoji(
// name: "PoliceCar",
// text: presentationData.strings.Report_Succeed
// ),
// elevatedLayout: false,
// blurred: true,
// action: { _ in return false }
// )
// , in: .current
// )
// }
// )
}))) })))
} }
} }

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "gift_24.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1199,7 +1199,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return true return true
case .starGift, .starGiftUnique: case .starGift, .starGiftUnique:
let controller = strongSelf.context.sharedContext.makeGiftViewScreen(context: strongSelf.context, message: EngineMessage(message), shareStory: { [weak self] in let controller = strongSelf.context.sharedContext.makeGiftViewScreen(context: strongSelf.context, message: EngineMessage(message), shareStory: { [weak self] in
if let self, case let .starGiftUnique(gift, _, _, _, _, _, _) = action.action, case let .unique(uniqueGift) = gift { if let self, case let .starGiftUnique(gift, _, _, _, _, _, _, _, _, _) = action.action, case let .unique(uniqueGift) = gift {
Queue.mainQueue().after(0.15) { Queue.mainQueue().after(0.15) {
let controller = self.context.sharedContext.makeStorySharingScreen(context: self.context, subject: .gift(uniqueGift), parentController: self) let controller = self.context.sharedContext.makeStorySharingScreen(context: self.context, subject: .gift(uniqueGift), parentController: self)
self.push(controller) self.push(controller)

View File

@ -1241,6 +1241,69 @@ extension ChatControllerImpl {
controller.legacyCompletion = { signals, silently, scheduleTime, parameters, getAnimatedTransitionSource, sendCompletion in controller.legacyCompletion = { signals, silently, scheduleTime, parameters, getAnimatedTransitionSource, sendCompletion in
completion(signals, silently, scheduleTime, parameters, getAnimatedTransitionSource, sendCompletion) completion(signals, silently, scheduleTime, parameters, getAnimatedTransitionSource, sendCompletion)
} }
controller.editCover = { [weak self] dimensions, completion in
guard let self else {
return
}
var dismissImpl: (() -> Void)?
let mainController = coverMediaPickerController(
context: self.context,
completion: { result, transitionView, transitionRect, transitionImage, fromCamera, transitionOut, cancelled in
let subject: Signal<MediaEditorScreenImpl.Subject?, NoError>
if let asset = result as? PHAsset {
subject = .single(.asset(asset))
} else {
return
}
let editorController = MediaEditorScreenImpl(
context: self.context,
mode: .coverEditor(dimensions: dimensions),
subject: subject,
transitionIn: fromCamera ? .camera : transitionView.flatMap({ .gallery(
MediaEditorScreenImpl.TransitionIn.GalleryTransitionIn(
sourceView: $0,
sourceRect: transitionRect,
sourceImage: transitionImage
)
) }),
transitionOut: { finished, isNew in
if !finished, let transitionView {
return MediaEditorScreenImpl.TransitionOut(
destinationView: transitionView,
destinationRect: transitionView.bounds,
destinationCornerRadius: 0.0
)
}
return nil
}, completion: { result, commit in
if case let .image(image, _) = result.media {
completion(image)
commit({})
}
dismissImpl?()
} as (MediaEditorScreenImpl.Result, @escaping (@escaping () -> Void) -> Void) -> Void
)
editorController.cancelled = { _ in
cancelled()
}
self.push(editorController)
}, dismissed: {
}
)
(self.navigationController as? NavigationController)?.pushViewController(mainController, animated: true)
dismissImpl = { [weak self, weak mainController] in
if let self, let navigationController = self.navigationController, let mainController {
var viewControllers = navigationController.viewControllers
viewControllers = viewControllers.filter { c in
return c !== mainController
}
navigationController.setViewControllers(viewControllers, animated: false)
}
}
}
present(controller, mediaPickerContext) present(controller, mediaPickerContext)
} }

View File

@ -1123,7 +1123,15 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
if data.messageActions.options.contains(.sendGift) { if data.messageActions.options.contains(.sendGift) {
let sendGiftTitle: String let sendGiftTitle: String
if message.effectivelyIncoming(context.account.peerId) { var isIncoming = message.effectivelyIncoming(context.account.peerId)
for media in message.media {
if let action = media as? TelegramMediaAction, case let .starGiftUnique(_, isUpgrade, _, _, _, _, _, _, _, _) = action.action {
if isUpgrade && message.author?.id == context.account.peerId {
isIncoming = true
}
}
}
if isIncoming {
let peerName = message.peers[message.id.peerId].flatMap(EnginePeer.init)?.compactDisplayTitle ?? "" let peerName = message.peers[message.id.peerId].flatMap(EnginePeer.init)?.compactDisplayTitle ?? ""
sendGiftTitle = chatPresentationInterfaceState.strings.Conversation_ContextMenuSendGiftTo(peerName).string sendGiftTitle = chatPresentationInterfaceState.strings.Conversation_ContextMenuSendGiftTo(peerName).string
} else { } else {

View File

@ -2450,27 +2450,27 @@ public final class SharedAccountContextImpl: SharedAccountContext {
} }
presentExportAlertImpl = { [weak controller] in presentExportAlertImpl = { [weak controller] in
guard let controller, case let .starGiftTransfer(_, _, _, _, canExportDate) = source, let canExportDate else { guard let controller, case let .starGiftTransfer(_, _, gift, _, canExportDate) = source, let canExportDate else {
return return
} }
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
let title: String if currentTime > canExportDate || "".isEmpty {
let text: String let alertController = giftWithdrawAlertController(context: context, gift: gift, commit: {
if currentTime > canExportDate {
title = presentationData.strings.Gift_Transfer_UpdateRequired_Title })
text = presentationData.strings.Gift_Transfer_UpdateRequired_Text controller.present(alertController, in: .window(.root))
} else { } else {
let delta = canExportDate - currentTime let delta = canExportDate - currentTime
let days: Int32 = Int32(ceil(Float(delta) / 86400.0)) let days: Int32 = Int32(ceil(Float(delta) / 86400.0))
let daysString = presentationData.strings.Gift_Transfer_UnlockPending_Text_Days(days) let daysString = presentationData.strings.Gift_Transfer_UnlockPending_Text_Days(days)
title = presentationData.strings.Gift_Transfer_UnlockPending_Title let title = presentationData.strings.Gift_Transfer_UnlockPending_Title
text = presentationData.strings.Gift_Transfer_UnlockPending_Text(daysString).string let text = presentationData.strings.Gift_Transfer_UnlockPending_Text(daysString).string
}
let alertController = textAlertController(context: context, title: title, text: text, actions: [ let alertController = textAlertController(context: context, title: title, text: text, actions: [
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {}) TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})
]) ])
controller.present(alertController, in: .window(.root)) controller.present(alertController, in: .window(.root))
} }
}
presentTransferAlertImpl = { [weak controller] peer in presentTransferAlertImpl = { [weak controller] peer in
guard let controller, case let .starGiftTransfer(_, _, gift, transferStars, _) = source else { guard let controller, case let .starGiftTransfer(_, _, gift, transferStars, _) = source else {

View File

@ -82,6 +82,18 @@ public final class UndoOverlayController: ViewController {
case bottom case bottom
} }
public struct Appearance {
public var isBlurred: Bool?
public var sideInset: CGFloat?
public var bottomInset: CGFloat?
public init(isBlurred: Bool? = nil, sideInset: CGFloat? = nil, bottomInset: CGFloat? = nil) {
self.isBlurred = isBlurred
self.sideInset = sideInset
self.bottomInset = bottomInset
}
}
private let presentationData: PresentationData private let presentationData: PresentationData
public var content: UndoOverlayContent { public var content: UndoOverlayContent {
didSet { didSet {
@ -94,7 +106,7 @@ public final class UndoOverlayController: ViewController {
private var action: (UndoOverlayAction) -> Bool private var action: (UndoOverlayAction) -> Bool
private let additionalView: (() -> UndoOverlayControllerAdditionalView?)? private let additionalView: (() -> UndoOverlayControllerAdditionalView?)?
private let blurred: Bool private let appearance: Appearance?
private var didPlayPresentationAnimation = false private var didPlayPresentationAnimation = false
private var dismissed = false private var dismissed = false
@ -102,13 +114,13 @@ public final class UndoOverlayController: ViewController {
public var tag: Any? public var tag: Any?
public init(presentationData: PresentationData, content: UndoOverlayContent, elevatedLayout: Bool, position: Position = .bottom, animateInAsReplacement: Bool = false, blurred: Bool = false, action: @escaping (UndoOverlayAction) -> Bool, additionalView: (() -> UndoOverlayControllerAdditionalView?)? = nil) { public init(presentationData: PresentationData, content: UndoOverlayContent, elevatedLayout: Bool = false, position: Position = .bottom, animateInAsReplacement: Bool = false, appearance: Appearance? = nil, action: @escaping (UndoOverlayAction) -> Bool, additionalView: (() -> UndoOverlayControllerAdditionalView?)? = nil) {
self.presentationData = presentationData self.presentationData = presentationData
self.content = content self.content = content
self.elevatedLayout = elevatedLayout self.elevatedLayout = elevatedLayout
self.position = position self.position = position
self.animateInAsReplacement = animateInAsReplacement self.animateInAsReplacement = animateInAsReplacement
self.blurred = blurred self.appearance = appearance
self.action = action self.action = action
self.additionalView = additionalView self.additionalView = additionalView
@ -122,7 +134,7 @@ public final class UndoOverlayController: ViewController {
} }
override public func loadDisplayNode() { override public func loadDisplayNode() {
self.displayNode = UndoOverlayControllerNode(presentationData: self.presentationData, content: self.content, elevatedLayout: self.elevatedLayout, placementPosition: self.position, blurred: self.blurred, additionalView: self.additionalView, action: { [weak self] value in self.displayNode = UndoOverlayControllerNode(presentationData: self.presentationData, content: self.content, elevatedLayout: self.elevatedLayout, placementPosition: self.position, appearance: self.appearance, additionalView: self.additionalView, action: { [weak self] value in
return self?.action(value) ?? false return self?.action(value) ?? false
}, dismiss: { [weak self] in }, dismiss: { [weak self] in
self?.dismiss() self?.dismiss()

View File

@ -59,7 +59,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
private let dismiss: () -> Void private let dismiss: () -> Void
private var content: UndoOverlayContent private var content: UndoOverlayContent
private let blurred: Bool private let appearance: UndoOverlayController.Appearance?
private let additionalView: UndoOverlayControllerAdditionalView? private let additionalView: UndoOverlayControllerAdditionalView?
@ -77,11 +77,11 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
private var fetchResourceDisposable: Disposable? private var fetchResourceDisposable: Disposable?
init(presentationData: PresentationData, content: UndoOverlayContent, elevatedLayout: Bool, placementPosition: UndoOverlayController.Position, blurred: Bool, additionalView: (() -> UndoOverlayControllerAdditionalView?)?, action: @escaping (UndoOverlayAction) -> Bool, dismiss: @escaping () -> Void) { init(presentationData: PresentationData, content: UndoOverlayContent, elevatedLayout: Bool, placementPosition: UndoOverlayController.Position, appearance: UndoOverlayController.Appearance?, additionalView: (() -> UndoOverlayControllerAdditionalView?)?, action: @escaping (UndoOverlayAction) -> Bool, dismiss: @escaping () -> Void) {
self.presentationData = presentationData self.presentationData = presentationData
self.elevatedLayout = elevatedLayout self.elevatedLayout = elevatedLayout
self.placementPosition = placementPosition self.placementPosition = placementPosition
self.blurred = blurred self.appearance = appearance
self.content = content self.content = content
self.additionalView = additionalView?() self.additionalView = additionalView?()
@ -1544,7 +1544,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.undoButtonNode = HighlightTrackingButtonNode() self.undoButtonNode = HighlightTrackingButtonNode()
self.panelNode = ASDisplayNode() self.panelNode = ASDisplayNode()
if presentationData.theme.overallDarkAppearance && !self.blurred { if presentationData.theme.overallDarkAppearance && !(self.appearance?.isBlurred == true) {
self.panelNode.backgroundColor = presentationData.theme.rootController.tabBar.backgroundColor self.panelNode.backgroundColor = presentationData.theme.rootController.tabBar.backgroundColor
} else { } else {
self.panelNode.backgroundColor = .clear self.panelNode.backgroundColor = .clear
@ -1904,7 +1904,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
let rightInset: CGFloat = 16.0 let rightInset: CGFloat = 16.0
var contentHeight: CGFloat = 20.0 var contentHeight: CGFloat = 20.0
let margin: CGFloat = 12.0 let margin: CGFloat = self.appearance?.sideInset ?? 12.0
let leftMargin = margin + layout.insets(options: []).left let leftMargin = margin + layout.insets(options: []).left
let buttonTextSize = self.undoButtonTextNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude)) let buttonTextSize = self.undoButtonTextNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude))
@ -1964,7 +1964,9 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
case .top: case .top:
break break
case .bottom: case .bottom:
if self.elevatedLayout { if let bottomInset = self.appearance?.bottomInset {
insets.bottom += bottomInset
} else if self.elevatedLayout {
insets.bottom += 49.0 insets.bottom += 49.0
} }
} }