diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index af9218df16..98a944baf5 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -13095,6 +13095,9 @@ Sorry for the inconvenience."; "Gift.Send.Remains_any" = "%@ left"; "Gift.Send.Sold_1" = "%@ 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.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.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"; "PeerInfo.PaneRecommendedBots" = "Similar Bots"; @@ -13696,3 +13704,42 @@ Sorry for the inconvenience."; "ChatListFilter.NameEnableAnimations" = "Enable 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"; diff --git a/submodules/AccountContext/Sources/Premium.swift b/submodules/AccountContext/Sources/Premium.swift index b07630716e..8f335d4812 100644 --- a/submodules/AccountContext/Sources/Premium.swift +++ b/submodules/AccountContext/Sources/Premium.swift @@ -121,6 +121,7 @@ public enum BoostSubject: Equatable { case audioTranscription case emojiPack case noAds + case wearGift } public enum StarsPurchasePurpose: Equatable { @@ -157,6 +158,7 @@ public struct PremiumConfiguration { minChannelWallpaperLevel: 9, minChannelCustomWallpaperLevel: 10, minChannelRestrictAdsLevel: 50, + minChannelWearGiftLevel: 8, minGroupProfileIconLevel: 7, minGroupEmojiStatusLevel: 8, minGroupWallpaperLevel: 9, @@ -185,6 +187,7 @@ public struct PremiumConfiguration { public let minChannelWallpaperLevel: Int32 public let minChannelCustomWallpaperLevel: Int32 public let minChannelRestrictAdsLevel: Int32 + public let minChannelWearGiftLevel: Int32 public let minGroupProfileIconLevel: Int32 public let minGroupEmojiStatusLevel: Int32 public let minGroupWallpaperLevel: Int32 @@ -212,6 +215,7 @@ public struct PremiumConfiguration { minChannelWallpaperLevel: Int32, minChannelCustomWallpaperLevel: Int32, minChannelRestrictAdsLevel: Int32, + minChannelWearGiftLevel: Int32, minGroupProfileIconLevel: Int32, minGroupEmojiStatusLevel: Int32, minGroupWallpaperLevel: Int32, @@ -238,6 +242,7 @@ public struct PremiumConfiguration { self.minChannelWallpaperLevel = minChannelWallpaperLevel self.minChannelCustomWallpaperLevel = minChannelCustomWallpaperLevel self.minChannelRestrictAdsLevel = minChannelRestrictAdsLevel + self.minChannelWearGiftLevel = minChannelWearGiftLevel self.minGroupProfileIconLevel = minGroupProfileIconLevel self.minGroupEmojiStatusLevel = minGroupEmojiStatusLevel self.minGroupWallpaperLevel = minGroupWallpaperLevel @@ -272,6 +277,7 @@ public struct PremiumConfiguration { minChannelWallpaperLevel: get(data["channel_wallpaper_level_min"]) ?? defaultValue.minChannelWallpaperLevel, minChannelCustomWallpaperLevel: get(data["channel_custom_wallpaper_level_min"]) ?? defaultValue.minChannelCustomWallpaperLevel, 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, minGroupEmojiStatusLevel: get(data["group_emoji_status_level_min"]) ?? defaultValue.minGroupEmojiStatusLevel, minGroupWallpaperLevel: get(data["group_wallpaper_level_min"]) ?? defaultValue.minGroupWallpaperLevel, diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 39b8b52130..43d5c79605 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -1875,9 +1875,8 @@ public final class ChatListNode: ListView { })) case .premiumGrace: 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 -// return true -// })) + case .setupPhoto: + let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: .setupPhoto).startStandalone() default: break } @@ -1993,12 +1992,7 @@ public final class ChatListNode: ListView { context.account.stateManager.contactBirthdays, starsSubscriptionsContextPromise.get() ) - |> mapToSignal { suggestions, dismissedSuggestions, configuration, newSessionReviews, data, birthdays, starsSubscriptionsContext -> Signal in - #if DEBUG - var suggestions = suggestions - suggestions.insert(.setupPhoto, at: 0) - #endif - + |> mapToSignal { suggestions, dismissedSuggestions, configuration, newSessionReviews, data, birthdays, starsSubscriptionsContext -> Signal in let (accountPeer, birthday) = data if let newSessionReview = newSessionReviews.first { diff --git a/submodules/DrawingUI/Sources/DrawingReactionView.swift b/submodules/DrawingUI/Sources/DrawingReactionView.swift index 26cae6e22a..f74f56c512 100644 --- a/submodules/DrawingUI/Sources/DrawingReactionView.swift +++ b/submodules/DrawingUI/Sources/DrawingReactionView.swift @@ -294,7 +294,7 @@ public class DrawingReactionEntityView: DrawingStickerEntityView { let context = self.context 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 { let controller = context.sharedContext.makePremiumIntroController(context: context, source: .storiesExpirationDurations, forceDark: true, dismissed: nil) self.containerView?.push(controller) diff --git a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift index ad08631d8f..49e2103cc1 100644 --- a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift @@ -464,7 +464,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll storeAttributedTextInPasteboard(text) 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) case .share: diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaEditingContext.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaEditingContext.h index d035252a3c..4a34949e3c 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaEditingContext.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaEditingContext.h @@ -62,6 +62,10 @@ - (void)setImage:(UIImage *)image thumbnailImage:(UIImage *)thumbnailImage forItem:(id)item synchronous:(bool)synchronous; - (void)setFullSizeImage:(UIImage *)image forItem:(id)item; +- (SSignal *)coverImageSignalForItem:(NSObject *)item; +- (void)setCoverImage:(UIImage *)image forItem:(id)item; +- (UIImage *)coverImageForItem:(NSObject *)item; + - (void)setTemporaryRep:(id)rep forItem:(id)item; - (SSignal *)fullSizeImageUrlForItem:(id)item; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryVideoItemView.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryVideoItemView.h index e35ddc83f2..b6925cb9bd 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryVideoItemView.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryVideoItemView.h @@ -31,6 +31,9 @@ - (void)prepareForEditing; - (void)returnFromEditing; +- (void)prepareForCoverEditing; +- (void)returnFromCoverEditing; + - (UIImage *)screenImage; - (UIImage *)transitionImage; - (CGRect)editorTransitionViewRect; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h index 40d786c0f3..68e5771469 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h @@ -120,6 +120,8 @@ @property (nonatomic, copy) id _Nullable(^ _Nullable captionPanelView)(void); +@property (nonatomic, copy) void (^ _Nullable editCover)(CGSize dimensions, void(^_Nonnull completion)(UIImage * _Nonnull)); + - (UIView *_Nonnull)solidRoundedButton:(NSString *_Nonnull)title action:(void(^_Nonnull)(void))action; - (id _Nonnull)drawingAdapter:(CGSize)size originalSize:(CGSize)originalSize isVideo:(bool)isVideo isAvatar:(bool)isAvatar entitiesView:(UIView * _Nullable)entitiesView; diff --git a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m index 94f4f26632..39afde67f6 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m +++ b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m @@ -1331,6 +1331,8 @@ CGSize dimensions = [TGMediaVideoConverter dimensionsFor:asset.originalSize adjustments:adjustments preset:preset]; NSTimeInterval duration = adjustments.trimApplied ? (adjustments.trimEndValue - adjustments.trimStartValue) : asset.videoDuration; + UIImage *coverImage = [editingContext coverImageForItem:asset]; + [signals addObject:[thumbnailSignal map:^id(UIImage *image) { NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; @@ -1341,6 +1343,7 @@ dict[@"adjustments"] = adjustments; dict[@"dimensions"] = [NSValue valueWithCGSize:dimensions]; dict[@"duration"] = @(duration); + dict[@"coverImage"] = coverImage; if (adjustments.paintingData.stickers.count > 0) dict[@"stickers"] = adjustments.paintingData.stickers; diff --git a/submodules/LegacyComponents/Sources/TGMediaEditingContext.m b/submodules/LegacyComponents/Sources/TGMediaEditingContext.m index 67f7274bfd..42b7764772 100644 --- a/submodules/LegacyComponents/Sources/TGMediaEditingContext.m +++ b/submodules/LegacyComponents/Sources/TGMediaEditingContext.m @@ -106,6 +106,8 @@ TGMemoryImageCache *_originalImageCache; TGMemoryImageCache *_originalThumbnailImageCache; + TGMemoryImageCache *_coverImageCache; + TGModernCache *_diskCache; NSURL *_fullSizeResultsUrl; NSURL *_paintingDatasUrl; @@ -119,6 +121,7 @@ SPipe *_representationPipe; SPipe *_thumbnailImagePipe; + SPipe *_coverImagePipe; SPipe *_adjustmentsPipe; SPipe *_captionPipe; SPipe *_timerPipe; @@ -166,6 +169,9 @@ _originalThumbnailImageCache = [[TGMemoryImageCache alloc] initWithSoftMemoryLimit:[[self class] thumbnailImageSoftMemoryLimit] 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]]; _diskCache = [[TGModernCache alloc] initWithPath:diskCachePath size:[[self class] diskMemoryLimit]]; @@ -194,6 +200,7 @@ _thumbnailImagePipe = [[SPipe alloc] init]; _adjustmentsPipe = [[SPipe alloc] init]; _captionPipe = [[SPipe alloc] init]; + _coverImagePipe = [[SPipe alloc] init]; _timerPipe = [[SPipe alloc] init]; _spoilerPipe = [[SPipe alloc] init]; _pricePipe = [[SPipe alloc] init]; @@ -908,6 +915,46 @@ [_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 *)item { + return [self coverImageSignalForIdentifier:item.uniqueIdentifier]; +} + +- (UIImage *)coverImageForItem:(NSObject *)item { + NSString *itemId = [TGMediaEditingContext _coverImageUriForItemId:item.uniqueIdentifier]; + if (itemId == nil) + return nil; + return [_coverImageCache imageForKey:itemId attributes:NULL]; +} + +- (void)setCoverImage:(UIImage *)image forItem:(id)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)item { NSString *itemId = [self _contextualIdForItemId:item.uniqueIdentifier]; @@ -1167,6 +1214,11 @@ return [NSString stringWithFormat:@"%@://%@", [self thumbnailImageUriScheme], itemId]; } ++ (NSString *)_coverImageUriForItemId:(NSString *)itemId +{ + return [NSString stringWithFormat:@"%@://%@", @"photo-editor-cover", itemId]; +} + #pragma mark - Constants + (NSString *)imageUriScheme diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m index d4d0322e66..702fb68d00 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m @@ -92,10 +92,17 @@ TGMediaPickerGroupButton *_groupButton; TGMediaPickerCameraButton *_cameraButton; + TGMediaPickerCoverButton *_coverButton; + TGModernButton *_cancelCoverButton; + TGModernButton *_saveCoverButton; + TGMediaPickerCoverButton *_coverGalleryButton; + UILabel *_coverTitleLabel; + TGMediaPickerPhotoStripView *_selectedPhotosView; SMetaDisposable *_adjustmentsDisposable; SMetaDisposable *_captionDisposable; + SMetaDisposable *_coverDisposable; SMetaDisposable *_itemAvailabilityDisposable; SMetaDisposable *_itemSelectedDisposable; id _selectionChangedDisposable; @@ -136,6 +143,7 @@ _adjustmentsDisposable = [[SMetaDisposable alloc] init]; _captionDisposable = [[SMetaDisposable alloc] init]; + _coverDisposable = [[SMetaDisposable alloc] init]; _itemSelectedDisposable = [[SMetaDisposable alloc] init]; _itemAvailabilityDisposable = [[SMetaDisposable alloc] init]; _tooltipDismissDisposable = [[SMetaDisposable alloc] init]; @@ -200,7 +208,7 @@ [[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.adjustsImageWhenHighlighted = false; [_muteButton setBackgroundImage:[TGPhotoEditorInterfaceAssets gifBackgroundImage] forState:UIControlStateNormal]; @@ -239,13 +247,23 @@ // [_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) { _checkButton = [[TGCheckButtonView alloc] initWithStyle:TGCheckButtonStyleGallery]; _checkButton.frame = CGRectMake(self.frame.size.width - 53, 11, _checkButton.frame.size.width, _checkButton.frame.size.height); [_checkButton addTarget:self action:@selector(checkButtonPressed) forControlEvents:UIControlEventTouchUpInside]; [_wrapperView addSubview:_checkButton]; - + if (hasSelectionPanel) { _selectedPhotosView = [[TGMediaPickerPhotoStripView alloc] initWithFrame:CGRectZero]; @@ -263,14 +281,14 @@ _selectedPhotosView.hidden = true; [_wrapperView addSubview:_selectedPhotosView]; } - + _photoCounterButton = [[TGMediaPickerPhotoCounterButton alloc] initWithFrame:CGRectMake(0, 0, 64, 38)]; [_photoCounterButton addTarget:self action:@selector(photoCounterButtonPressed) forControlEvents:UIControlEventTouchUpInside]; _photoCounterButton.userInteractionEnabled = false; [_wrapperView addSubview:_photoCounterButton]; _selectionChangedDisposable = [[_selectionContext selectionChangedSignal] startStrictWithNext:^(id next) - { + { __strong TGMediaPickerGalleryInterfaceView *strongSelf = weakSelf; if (strongSelf == nil) return; @@ -290,7 +308,7 @@ if (_editingContext != nil) { _timersChangedDisposable = [_editingContext.timersUpdatedSignal startStrictWithNext:^(__unused NSNumber *next) - { + { __strong TGMediaPickerGalleryInterfaceView *strongSelf = weakSelf; if (strongSelf == nil) return; @@ -299,7 +317,7 @@ } file:__FILE_NAME__ line:__LINE__]; _adjustmentsChangedDisposable = [_editingContext.adjustmentsUpdatedSignal startStrictWithNext:^(__unused NSNumber *next) - { + { __strong TGMediaPickerGalleryInterfaceView *strongSelf = weakSelf; if (strongSelf == nil) return; @@ -377,7 +395,7 @@ offset = -keyboardHeight / 2.0f; [UIView animateWithDuration:duration delay:0.0f options:animationCurve animations:^ - { + { if (strongSelf->_scrollViewOffsetRequested != nil) strongSelf->_scrollViewOffsetRequested(offset); } completion:nil]; @@ -423,6 +441,34 @@ if ([UIDevice currentDevice].userInterfaceIdiom != UIUserInterfaceIdiomPad) [_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; } @@ -430,9 +476,10 @@ - (void)dealloc { [_actionHandle reset]; - + [_adjustmentsDisposable dispose]; [_captionDisposable dispose]; + [_coverDisposable dispose]; [_itemSelectedDisposable dispose]; [_itemAvailabilityDisposable dispose]; [_selectionChangedDisposable dispose]; @@ -472,7 +519,7 @@ bool groupingButtonVisible = _groupButton != nil && onlyGroupableMedia && _selectionContext.count > 1; dispatch_async(dispatch_get_main_queue(), ^ - { + { [_groupButton setInternalHidden:!groupingButtonVisible animated:true]; }); @@ -529,7 +576,7 @@ { if (self.timerRequested != nil) self.timerRequested(); - + if (!TGIsPad()) [self setAllInterfaceHidden:true delay:0.0f animated:true]; } @@ -566,9 +613,9 @@ [_currentItemView setSafeAreaInset:[self localSafeAreaInset]]; UIEdgeInsets screenEdges = [self screenEdges]; - + __weak TGMediaPickerGalleryInterfaceView *weakSelf = self; - + [self _layoutRecipientLabelForOrientation:[self interfaceOrientation] screenEdges:screenEdges hasHeaderView:(itemView.headerView != nil)]; if (_selectionContext != nil) @@ -587,7 +634,7 @@ [_checkButton setNumber:[_selectionContext indexOfItem:selectableItem]]; signal = [_selectionContext itemInformativeSelectedSignal:selectableItem]; [_itemSelectedDisposable setDisposable:[signal startStrictWithNext:^(TGMediaSelectionChange *next) - { + { __strong TGMediaPickerGalleryInterfaceView *strongSelf = weakSelf; if (strongSelf == nil) return; @@ -601,18 +648,18 @@ __weak TGModernGalleryItemView *weakItemView = itemView; [_itemAvailabilityDisposable setDisposable:[[[itemView contentAvailabilityStateSignal] deliverOn:[SQueue mainQueue]] startStrictWithNext:^(id next) - { + { __strong TGMediaPickerGalleryInterfaceView *strongSelf = weakSelf; __strong TGModernGalleryItemView *strongItemView = weakItemView; if (strongSelf == nil || strongItemView == nil) return; - + bool available = [next boolValue]; NSString *itemId = nil; if ([strongItemView.item respondsToSelector:@selector(uniqueId)]) itemId = [itemView.item performSelector:@selector(uniqueId)]; - + NSString *currentId = nil; if ([strongSelf->_currentItem respondsToSelector:@selector(uniqueId)]) currentId = [strongSelf->_currentItem performSelector:@selector(uniqueId)]; @@ -630,6 +677,9 @@ } } strongSelf->_muteButton.hidden = !sendableAsGif; + + bool canHaveCover = [strongItemView isKindOfClass:[TGMediaPickerGalleryVideoItemView class]]; + strongSelf->_coverButton.hidden = !canHaveCover; } } file:__FILE_NAME__ line:__LINE__]]; @@ -682,28 +732,28 @@ NSArray *items = @ [ - [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Camera.Discard") type:TGMenuSheetButtonTypeDefault fontSize:20.0 action:^ - { - __strong TGMenuSheetController *strongController = weakController; - if (strongController == nil) - return; - - __strong TGMediaPickerGalleryInterfaceView *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - strongSelf->_capturing = false; - strongSelf->_closePressed(); - - [strongController dismissAnimated:true manual:false completion:nil]; - }], - [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Common.Cancel") type:TGMenuSheetButtonTypeCancel fontSize:20.0 action:^ - { - __strong TGMenuSheetController *strongController = weakController; - if (strongController != nil) - [strongController dismissAnimated:true]; - }] - ]; + [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Camera.Discard") type:TGMenuSheetButtonTypeDefault fontSize:20.0 action:^ + { + __strong TGMenuSheetController *strongController = weakController; + if (strongController == nil) + return; + + __strong TGMediaPickerGalleryInterfaceView *strongSelf = weakSelf; + if (strongSelf == nil) + return; + + strongSelf->_capturing = false; + strongSelf->_closePressed(); + + [strongController dismissAnimated:true manual:false completion:nil]; + }], + [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Common.Cancel") type:TGMenuSheetButtonTypeCancel fontSize:20.0 action:^ + { + __strong TGMenuSheetController *strongController = weakController; + if (strongController != nil) + [strongController dismissAnimated:true]; + }] + ]; [controller setItemViews:items]; controller.sourceRect = ^ @@ -740,7 +790,7 @@ bool animated = false; if (!_selectedPhotosView.isAnimating) animated = true; - + idselectableItem = nil; if ([_currentItem conformsToProtocol:@protocol(TGModernGallerySelectableItem)]) selectableItem = ((id)_currentItem).selectableMediaItem; @@ -777,6 +827,119 @@ [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 galleryEditableItem = (id)_currentItem; + TGModernGalleryItemView *currentItemView = _currentItemView; + if ([currentItemView isKindOfClass:[TGMediaPickerGalleryVideoItemView class]]) { + id 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 galleryEditableItem = (id)_currentItem; + if ([_currentItem conformsToProtocol:@protocol(TGModernGalleryEditableItem)]) + { + id 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)item animated:(bool)animated { __weak TGMediaPickerGalleryInterfaceView *weakSelf = self; @@ -791,6 +954,14 @@ return; [strongSelf->_captionMixin setCaption:caption animated:animated]; } 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) @@ -938,7 +1109,6 @@ qualityButton.iconImage = icon; } - bool willShowTimerTooltip = false; TGPhotoEditorButton *timerButton = [_portraitToolbarView buttonForTab:TGPhotoEditorTimerTab]; if (timerButton != nil) { @@ -959,7 +1129,6 @@ if ([self shouldDisplayTooltip]) { - willShowTimerTooltip = true; TGDispatchAfter(0.5, dispatch_get_main_queue(), ^ { if (!TGIsPad() && self.frame.size.width > self.frame.size.height) @@ -1098,6 +1267,7 @@ { _checkButton.alpha = alpha; _muteButton.alpha = alpha; + _coverButton.alpha = alpha; _arrowView.alpha = alpha * 0.6f; _recipientLabel.alpha = alpha * 0.6; } completion:^(BOOL finished) @@ -1106,6 +1276,7 @@ { _checkButton.userInteractionEnabled = !hidden; _muteButton.userInteractionEnabled = !hidden; + _coverButton.userInteractionEnabled = !hidden; } }]; @@ -1129,6 +1300,9 @@ _muteButton.alpha = alpha; _muteButton.userInteractionEnabled = !hidden; + _coverButton.alpha = alpha; + _coverButton.userInteractionEnabled = !hidden; + _arrowView.alpha = alpha * 0.6f; _recipientLabel.alpha = alpha * 0.6; } @@ -1145,7 +1319,11 @@ [_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); if (animated) @@ -1154,8 +1332,9 @@ { _checkButton.alpha = alpha; _muteButton.alpha = alpha; + _coverButton.alpha = alpha; _arrowView.alpha = alpha * 0.6; - _recipientLabel.alpha = alpha; + _recipientLabel.alpha = alpha * 0.6; _portraitToolbarView.alpha = alpha; _landscapeToolbarView.alpha = alpha; _captionMixin.inputPanelView.alpha = alpha; @@ -1166,6 +1345,7 @@ { _checkButton.userInteractionEnabled = !hidden; _muteButton.userInteractionEnabled = !hidden; + _coverButton.userInteractionEnabled = !hidden; _portraitToolbarView.userInteractionEnabled = !hidden; _landscapeToolbarView.userInteractionEnabled = !hidden; _captionMixin.inputPanelView.userInteractionEnabled = !hidden; @@ -1193,6 +1373,9 @@ _muteButton.alpha = alpha; _muteButton.userInteractionEnabled = !hidden; + _coverButton.alpha = alpha; + _coverButton.userInteractionEnabled = !hidden; + _arrowView.alpha = alpha * 0.6; _recipientLabel.alpha = alpha; @@ -1219,7 +1402,7 @@ if (!_groupButton.hidden) [_groupButton setHidden:true animated:animated]; - [self setItemHeaderViewHidden:hidden animated:animated]; + [self setItemHeaderViewHidden:!keepHeader && hidden animated:animated]; } #pragma mark - @@ -1431,6 +1614,10 @@ || view == _muteButton || view == _groupButton || view == _cameraButton + || view == _coverButton + || view == _cancelCoverButton + || view == _saveCoverButton + || view == _coverGalleryButton || [view isDescendantOfView:_headerWrapperView] || [view isDescendantOfView:_portraitToolbarView] || [view isDescendantOfView:_landscapeToolbarView] @@ -1492,7 +1679,7 @@ break; 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; } @@ -1526,6 +1713,34 @@ 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 frame = CGRectZero; @@ -1712,6 +1927,14 @@ portraitToolbarViewBottomEdge = screenEdges.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; captionEdgeInsets.bottom = _portraitToolbarView.frame.size.height; [_captionMixin updateLayoutWithFrame:self.bounds edgeInsets:captionEdgeInsets animated:false]; @@ -1752,9 +1975,9 @@ { [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); @@ -1767,6 +1990,9 @@ _muteButton.frame = [self _muteButtonFrameForOrientation:orientation screenEdges:screenEdges hasHeaderView:true]; _checkButton.frame = [self _checkButtonFrameForOrientation: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:^ { _cameraButton.frame = [self _cameraButtonFrameForOrientation:orientation screenEdges:screenEdges hasHeaderView:hasHeaderView panelVisible:_selectedPhotosView != nil && !_selectedPhotosView.isInternalHidden]; diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m index 01578096c3..82fd3f1628 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m @@ -67,6 +67,9 @@ UIView *_headerView; UIView *_scrubberPanelView; TGMediaPickerGalleryVideoScrubber *_scrubberView; + + TGMediaPickerGalleryVideoScrubber *_coverScrubberView; + bool _wasPlayingBeforeScrubbing; bool _appeared; bool _scrubbingPanelPresented; @@ -225,13 +228,21 @@ //scrubberBackgroundView.backgroundColor = [TGPhotoEditorInterfaceAssets toolbarTransparentBackgroundColor]; [_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.dataSource = self; _scrubberView.delegate = self; headerView.scrubberView = _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.autoresizingMask = UIViewAutoresizingFlexibleWidth; _fileInfoLabel.backgroundColor = [UIColor clearColor]; @@ -420,6 +431,7 @@ } _scrubberView.allowsTrimming = false; + _coverScrubberView.allowsTrimming = false; _videoDimensions = item.dimensions; if (_entitiesView == nil) { @@ -596,6 +608,12 @@ strongSelf->_scrubberView.trimStartValue = adjustments.trimStartValue; strongSelf->_scrubberView.trimEndValue = adjustments.trimEndValue; 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->_shouldResetScrubber = false; } @@ -603,14 +621,21 @@ { strongSelf->_scrubberView.trimStartValue = 0; strongSelf->_scrubberView.trimEndValue = videoDuration; + + strongSelf->_coverScrubberView.trimStartValue = 0; + strongSelf->_coverScrubberView.trimEndValue = videoDuration; + [strongSelf->_scrubberView setTrimApplied:false]; strongSelf->_shouldResetScrubber = true; } [strongSelf->_scrubberView reloadData]; + [strongSelf->_coverScrubberView reloadData]; + if (!strongSelf->_appeared) { [strongSelf->_scrubberView resetToStart]; + [strongSelf->_coverScrubberView resetToStart]; strongSelf->_appeared = true; } } file:__FILE_NAME__ line:__LINE__]]; @@ -629,6 +654,7 @@ if (afterReload) { _cachedThumbnails = nil; [_scrubberView reloadData]; + [_coverScrubberView reloadData]; } else { [self setScrubbingPanelHidden:false animated:true]; @@ -648,8 +674,10 @@ if (hidden) { - if (!_scrubbingPanelPresented) + if (!_scrubbingPanelPresented) { [_scrubberView ignoreThumbnails]; + [_coverScrubberView ignoreThumbnails]; + } _scrubbingPanelPresented = false; @@ -680,6 +708,7 @@ [_scrubberPanelView layoutSubviews]; [_scrubberView layoutSubviews]; + [_coverScrubberView layoutSubviews]; void (^changeBlock)(void) = ^ { @@ -732,6 +761,18 @@ [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 { bool frameChanged = !CGRectEqualToRect(frame, self.frame); @@ -741,6 +782,7 @@ if (_appeared && frameChanged) { [_scrubberView resetThumbnails]; + [_coverScrubberView resetThumbnails]; [_scrubberPanelView setNeedsLayout]; [_scrubberPanelView layoutIfNeeded]; @@ -748,6 +790,7 @@ dispatch_async(dispatch_get_main_queue(), ^ { [_scrubberView reloadThumbnails]; + [_coverScrubberView reloadThumbnails]; [_scrubberPanelView layoutSubviews]; }); } @@ -1116,6 +1159,7 @@ self.isPlaying = false; [_scrubberView setIsPlaying:false]; [_scrubberView resetToStart]; + [_coverScrubberView resetToStart]; [_positionTimer invalidate]; _positionTimer = nil; @@ -1249,6 +1293,7 @@ { [self _seekToPosition:_scrubberView.trimStartValue manual:false]; [_scrubberView setValue:_scrubberView.trimStartValue resetPosition:true]; + [_coverScrubberView setValue:_scrubberView.trimStartValue resetPosition:true]; } [_player play]; @@ -1310,10 +1355,12 @@ _positionTimer = nil; [_scrubberView resetToStart]; + [_coverScrubberView resetToStart]; } else { [_scrubberView setValue:_scrubberView.trimStartValue resetPosition:true]; + [_coverScrubberView setValue:_scrubberView.trimStartValue resetPosition:true]; } [self _seekToPosition:_scrubberView.trimStartValue manual:false]; @@ -1322,6 +1369,7 @@ - (void)positionTimerEvent { [_scrubberView setValue:CMTimeGetSeconds(_player.currentItem.currentTime)]; + [_coverScrubberView setValue:CMTimeGetSeconds(_player.currentItem.currentTime)]; } - (void)_seekToPosition:(NSTimeInterval)position manual:(bool)__unused manual @@ -1396,6 +1444,11 @@ - (void)videoScrubber:(TGMediaPickerGalleryVideoScrubber *)__unused videoScrubber valueDidChange:(NSTimeInterval)position { [self _seekToPosition:position manual:true]; + if (videoScrubber == _scrubberView) { + [_coverScrubberView setValue:position resetPosition:true]; + } else { + [_scrubberView setValue:position resetPosition:true]; + } } #pragma mark Trimming @@ -1426,6 +1479,10 @@ [self updatePlayerRange:videoScrubber.trimEndValue]; [self updateEditAdjusments]; + _coverScrubberView.trimStartValue = videoScrubber.trimStartValue; + _coverScrubberView.trimEndValue = videoScrubber.trimEndValue; + [_coverScrubberView _layoutTrimCurtainViews]; + [self setPlayButtonHidden:false animated:true]; } @@ -1622,8 +1679,12 @@ 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) return; @@ -1692,8 +1753,10 @@ [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->_coverScrubberView setThumbnailImage:image forTimestamp:[timestamps[index] doubleValue] index:index isSummaryThubmnail:isSummaryThumbnails last:index == (images.count - 1)]; + } }]; } completed:^ { diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoScrubber.h b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoScrubber.h index cd36c923d7..cf20c43161 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoScrubber.h +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoScrubber.h @@ -28,6 +28,9 @@ @property (nonatomic, readonly) bool isScrubbing; @property (nonatomic, assign) bool isPlaying; @property (nonatomic, assign) NSTimeInterval value; + +- (instancetype)initWithFrame:(CGRect)frame cover:(bool)cover; + - (void)setValue:(NSTimeInterval)value resetPosition:(bool)resetPosition; - (void)setTrimApplied:(bool)trimApplied; @@ -48,6 +51,7 @@ - (CGPoint)scrubberPositionForPosition:(NSTimeInterval)position; - (void)_updateScrubberAnimationsAndResetCurrentPosition:(bool)resetCurrentPosition; +- (void)_layoutTrimCurtainViews; @end diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoScrubber.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoScrubber.m index 60548f5a37..ef50069cc0 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoScrubber.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoScrubber.m @@ -35,8 +35,8 @@ typedef enum UIView *_zoomedThumbnailWrapperView; UIView *_summaryThumbnailWrapperView; TGMediaPickerGalleryVideoTrimView *_trimView; - UIView *_leftCurtainView; - UIView *_rightCurtainView; + UIImageView *_leftCurtainView; + UIImageView *_rightCurtainView; UIControl *_scrubberHandle; UIControl *_dotHandle; @@ -86,7 +86,7 @@ typedef enum @implementation TGMediaPickerGalleryVideoScrubber -- (instancetype)initWithFrame:(CGRect)frame +- (instancetype)initWithFrame:(CGRect)frame cover:(bool)cover { self = [super initWithFrame:frame]; if (self != nil) @@ -105,7 +105,7 @@ typedef enum _currentTimeLabel.layer.shadowOpacity = 0.6; _currentTimeLabel.layer.rasterizationScale = TGScreenScaling(); _currentTimeLabel.layer.shouldRasterize = true; - [self addSubview:_currentTimeLabel]; + //[self addSubview:_currentTimeLabel]; _inverseTimeLabel = [[UILabel alloc] initWithFrame:CGRectMake(frame.size.width - 108, 4, 100, 15)]; _inverseTimeLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin; @@ -120,9 +120,9 @@ typedef enum _inverseTimeLabel.layer.shadowOpacity = 0.6; _inverseTimeLabel.layer.rasterizationScale = TGScreenScaling(); _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); [self addSubview:_wrapperView]; @@ -131,21 +131,47 @@ typedef enum _summaryThumbnailWrapperView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 32)]; _summaryThumbnailWrapperView.clipsToBounds = true; - _summaryThumbnailWrapperView.layer.cornerRadius = 5.0; + _summaryThumbnailWrapperView.layer.cornerRadius = 9.0; [_wrapperView addSubview:_summaryThumbnailWrapperView]; - _leftCurtainView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)]; - _leftCurtainView.backgroundColor = [[TGPhotoEditorInterfaceAssets toolbarBackgroundColor] colorWithAlphaComponent:0.8f]; + static dispatch_once_t onceToken; + 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.layer.cornerRadius = 5.0; [_wrapperView addSubview:_leftCurtainView]; - - _rightCurtainView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)]; - _rightCurtainView.backgroundColor = [[TGPhotoEditorInterfaceAssets toolbarBackgroundColor] colorWithAlphaComponent:0.8f]; + + _rightCurtainView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)]; + _rightCurtainView.image = [rightCurtain stretchableImageWithLeftCapWidth:11 topCapHeight:20]; _rightCurtainView.clipsToBounds = true; - _rightCurtainView.layer.cornerRadius = 5.0; [_wrapperView addSubview:_rightCurtainView]; - + __weak TGMediaPickerGalleryVideoScrubber *weakSelf = self; _trimView = [[TGMediaPickerGalleryVideoTrimView alloc] initWithFrame:CGRectZero]; _trimView.exclusiveTouch = true; @@ -308,9 +334,9 @@ typedef enum [_wrapperView addSubview:_dotHandle]; static UIImage *dotFrameImage = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^ - { + static dispatch_once_t onceToken2; + dispatch_once(&onceToken2, ^ + { UIGraphicsBeginImageContextWithOptions(CGSizeMake(_dotHandle.frame.size.width, _dotHandle.frame.size.height), false, 0.0f); CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor); @@ -336,26 +362,34 @@ typedef enum _dotFrameView = [[UIImageView alloc] initWithFrame:_dotHandle.bounds]; _dotFrameView.image = dotFrameImage; [_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); [_wrapperView addSubview:_scrubberHandle]; - static UIImage *handleViewImage = nil; - static dispatch_once_t onceToken2; - dispatch_once(&onceToken2, ^ + UIImage *handleViewImage = nil; { UIGraphicsBeginImageContextWithOptions(CGSizeMake(_scrubberHandle.frame.size.width, _scrubberHandle.frame.size.height), false, 0.0f); CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSetShadowWithColor(context, CGSizeMake(0, 0), 0.5f, [UIColor colorWithWhite:0.0f alpha:0.65f].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]; - [path fill]; + if (cover) { + 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(); UIGraphicsEndImageContext(); - }); + } UIImageView *scrubberImageView = [[UIImageView alloc] initWithFrame:_scrubberHandle.bounds]; 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 { - bool exists = false; if (isSummaryThumbnail) { if (index == 0 && _summaryThumbnailViews.count > 0 && _summaryThumbnailSnapshotView == nil) { @@ -790,7 +823,6 @@ typedef enum } if (_summaryThumbnailViews.count >= index + 1) { - exists = true; [_summaryThumbnailViews[index] setImage:image animated:true]; } else { 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) aspectRatio = 1.0f / aspectRatio; - return CGSizeMake(CGCeil(36.0f * aspectRatio), 36.0f); + return CGSizeMake(CGCeil(40.0f * aspectRatio), 40.0f); } - (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 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 @@ -1462,8 +1494,8 @@ typedef enum CGRect scrubbingRect = [self _scrubbingRect]; 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); - _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); + _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) - 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 { - _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]; CGRect scrubbingRect = [self _scrubbingRect]; if (isnan(scrubbingRect.origin.x) || isnan(scrubbingRect.origin.y)) 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; [self _updateScrubberAnimationsAndResetCurrentPosition:true]; diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoTrimView.h b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoTrimView.h index c94c7c9c44..8492a4f8c6 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoTrimView.h +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoTrimView.h @@ -10,5 +10,6 @@ @property (nonatomic, assign) bool trimmingEnabled; - (void)setTrimming:(bool)trimming animated:(bool)animated; +- (void)setTrimmingEnabled:(bool)trimmingEnabled animated:(bool)animated; @end diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoTrimView.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoTrimView.m index f6e6b34e52..f07983b096 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoTrimView.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoTrimView.m @@ -11,6 +11,10 @@ { UIButton *_leftSegmentView; UIButton *_rightSegmentView; + UIImageView *_borderView; + + UIImageView *_leftCapsuleView; + UIImageView *_rightCapsuleView; UILongPressGestureRecognizer *_startHandlePressGestureRecognizer; UILongPressGestureRecognizer *_endHandlePressGestureRecognizer; @@ -34,28 +38,48 @@ { self.hitTestEdgeInsets = UIEdgeInsetsMake(-5, -25, -5, -25); - UIColor *normalColor = UIColorRGB(0x4d4d4d); - UIColor *accentColor = [TGPhotoEditorInterfaceAssets accentColor]; + UIColor *normalColor = UIColorRGB(0xffffff); + UIColor *accentColor = UIColorRGB(0xf8d74a); static dispatch_once_t onceToken; static UIImage *handle; + static UIImage *border; 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]; - [[UIBezierPath bezierPathWithRoundedRect:CGRectMake(0.0f, 0.0f, 12.0f, 36.0f) byRoundingCorners:UIRectCornerTopLeft | UIRectCornerBottomLeft cornerRadii:CGSizeMake(4.0f, 4.0f)] fill]; + CGContextSetFillColorWithColor(context, normalColor.CGColor); + 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(); 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 *leftHighlightedImage = TGTintedImage(handle, accentColor); UIImage *rightImage = [UIImage imageWithCGImage:handle.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 setBackgroundImage:leftImage forState:UIControlStateNormal]; [_leftSegmentView setBackgroundImage:leftHighlightedImage forState:UIControlStateSelected]; @@ -63,7 +87,7 @@ _leftSegmentView.hitTestEdgeInsets = UIEdgeInsetsMake(-5, -25, -5, -10); [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 setBackgroundImage:rightImage forState:UIControlStateNormal]; [_rightSegmentView setBackgroundImage:rightHighlightedImage forState:UIControlStateSelected]; @@ -71,6 +95,22 @@ _rightSegmentView.hitTestEdgeInsets = UIEdgeInsetsMake(-5, -10, -5, -25); [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.delegate = self; _startHandlePressGestureRecognizer.minimumPressDuration = 0.1f; @@ -98,24 +138,39 @@ _leftSegmentView.hidden = !trimmingEnabled; _rightSegmentView.hidden = !trimmingEnabled; + _borderView.hidden = !trimmingEnabled; [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 { if (animated) { [UIView animateWithDuration:0.15f animations:^ { - [_leftSegmentView setSelected:trimming]; - [_rightSegmentView setSelected:trimming]; - }]; + [_leftSegmentView setSelected:trimming]; + [_rightSegmentView setSelected:trimming]; + [_borderView setHighlighted:trimming]; + }]; } else { [_leftSegmentView setSelected:trimming]; [_rightSegmentView setSelected:trimming]; + [_borderView setHighlighted:trimming]; } } @@ -227,6 +282,7 @@ _leftSegmentView.frame = CGRectMake(0, 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 diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerPhotoCounterButton.h b/submodules/LegacyComponents/Sources/TGMediaPickerPhotoCounterButton.h index 440a2f928a..bf217aa3fc 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerPhotoCounterButton.h +++ b/submodules/LegacyComponents/Sources/TGMediaPickerPhotoCounterButton.h @@ -30,3 +30,12 @@ - (void)setInternalHidden:(bool)internalHidden animated:(bool)animated; @end + + +@interface TGMediaPickerCoverButton : UIButton + +- (void)setImage:(UIImage *)image; + +- (instancetype)initWithFrame:(CGRect)frame gallery:(bool)gallery; + +@end diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerPhotoCounterButton.m b/submodules/LegacyComponents/Sources/TGMediaPickerPhotoCounterButton.m index 68bce634d9..17a13d7c21 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerPhotoCounterButton.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerPhotoCounterButton.m @@ -48,7 +48,7 @@ const CGFloat TGPhotoCounterButtonMaskFade = 18; { UIGraphicsBeginImageContextWithOptions(CGSizeMake(38.0f, 38.0f), false, 0.0f); 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)); @@ -778,3 +778,118 @@ const CGFloat TGPhotoCounterButtonMaskFade = 18; } @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 *)touches withEvent:(UIEvent *)event +{ + [super touchesBegan:touches withEvent:event]; + [self setWrapperScale:0.85f animated:true]; +} + +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event +{ + [super touchesEnded:touches withEvent:event]; + [self setWrapperScale:1.0f animated:true]; +} + +- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event +{ + [super touchesCancelled:touches withEvent:event]; + [self setWrapperScale:1.0f animated:true]; +} + +@end diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerScrubberHeaderView.h b/submodules/LegacyComponents/Sources/TGMediaPickerScrubberHeaderView.h index e9a1db228a..9d922961ca 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerScrubberHeaderView.h +++ b/submodules/LegacyComponents/Sources/TGMediaPickerScrubberHeaderView.h @@ -7,5 +7,6 @@ @property (nonatomic, assign) UIEdgeInsets safeAreaInset; @property (nonatomic, strong) UIView *panelView; @property (nonatomic, strong) TGMediaPickerGalleryVideoScrubber *scrubberView; +@property (nonatomic, strong) TGMediaPickerGalleryVideoScrubber *coverScrubberView; @end diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerScrubberHeaderView.m b/submodules/LegacyComponents/Sources/TGMediaPickerScrubberHeaderView.m index b7b503e4da..0e833dac52 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerScrubberHeaderView.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerScrubberHeaderView.m @@ -11,6 +11,8 @@ - (void)layoutSubviews { _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 diff --git a/submodules/LegacyComponents/Sources/TGPhotoEditorController.m b/submodules/LegacyComponents/Sources/TGPhotoEditorController.m index 147ecb48dc..048e94677c 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoEditorController.m +++ b/submodules/LegacyComponents/Sources/TGPhotoEditorController.m @@ -360,7 +360,7 @@ [_dotImageView addGestureRecognizer:dotTapRecognizer]; 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.layer.allowsGroupOpacity = true; _scrubberView.hasDotPicker = true; diff --git a/submodules/LegacyComponents/Sources/TGPhotoEditorInterfaceAssets.m b/submodules/LegacyComponents/Sources/TGPhotoEditorInterfaceAssets.m index 778a56f6c6..ac683a46ce 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoEditorInterfaceAssets.m +++ b/submodules/LegacyComponents/Sources/TGPhotoEditorInterfaceAssets.m @@ -123,11 +123,11 @@ static UIImage *muteBackground; 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); CGContextRef context = UIGraphicsGetCurrentContext(); - CGContextSetFillColorWithColor(context, UIColorRGBA(0x000000, 0.3f).CGColor); - CGContextFillEllipseInRect(context, CGRectInset(rect, 3, 3)); + CGContextSetFillColorWithColor(context, UIColorRGBA(0x000000, 0.5f).CGColor); + CGContextFillEllipseInRect(context, rect); muteBackground = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); @@ -173,7 +173,7 @@ { UIGraphicsBeginImageContextWithOptions(CGSizeMake(38.0f, 38.0f), false, 0.0f); 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)); @@ -198,7 +198,7 @@ { UIGraphicsBeginImageContextWithOptions(CGSizeMake(38.0f, 38.0f), false, 0.0f); 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)); @@ -358,7 +358,7 @@ { UIGraphicsBeginImageContextWithOptions(CGSizeMake(30.0f, 30.0f), false, 0.0f); 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]; CGContextAddPath(context, path.CGPath); diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift index b3d0492c73..b06a830370 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift @@ -127,7 +127,7 @@ private enum LegacyAssetVideoData { private enum LegacyAssetItem { case image(data: LegacyAssetImageData, thumbnail: UIImage?, caption: NSAttributedString?, stickers: [FileMediaReference]) 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 { @@ -167,13 +167,14 @@ public func legacyAssetPickerItemGenerator() -> ((Any?, NSAttributedString?, Str if (dict["type"] as! NSString) == "editedPhoto" || (dict["type"] as! NSString) == "capturedPhoto" { let image = dict["image"] as! UIImage let thumbnail = dict["previewImage"] as? UIImage + let cover = dict["coverImage"] as? UIImage var result: [AnyHashable : Any] = [:] if let isAnimation = dict["isAnimation"] as? NSNumber, isAnimation.boolValue { let url: String? = (dict["url"] as? String) ?? (dict["url"] as? URL)?.path if let url = url { 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 { 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 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 } @@ -230,6 +231,7 @@ public func legacyAssetPickerItemGenerator() -> ((Any?, NSAttributedString?, Str } } else if (dict["type"] as! NSString) == "video" { let thumbnail = dict["previewImage"] as? UIImage + let cover = dict["coverImage"] as? UIImage var asFile = false if let document = dict["document"] as? NSNumber, document.boolValue { asFile = true @@ -237,17 +239,18 @@ public func legacyAssetPickerItemGenerator() -> ((Any?, NSAttributedString?, Str if let asset = dict["asset"] as? TGMediaAsset { 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 } else if let url = (dict["url"] as? String) ?? (dict["url"] as? URL)?.absoluteString { let dimensions = (dict["dimensions"]! as AnyObject).cgSizeValue! let duration = (dict["duration"]! as AnyObject).doubleValue! 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 } } else if (dict["type"] as! NSString) == "cameraVideo" { let thumbnail = dict["previewImage"] as? UIImage + let cover = dict["coverImage"] as? UIImage var asFile = false if let document = dict["document"] as? NSNumber, document.boolValue { asFile = true @@ -259,7 +262,7 @@ public func legacyAssetPickerItemGenerator() -> ((Any?, NSAttributedString?, Str let dimensions = previewImage.pixelSize() let duration = (dict["duration"]! as AnyObject).doubleValue! 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 } } @@ -756,7 +759,7 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A default: 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 finalDuration: Double 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"))) var preset: TGMediaVideoConversionPreset = TGMediaVideoConversionPresetCompressedMedium @@ -891,7 +914,7 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A 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) { attributes.append(AutoremoveTimeoutMessageAttribute(timeout: Int32(timer), countdownBeginTime: nil)) diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift index 73f16210f2..10c395cb33 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift @@ -569,6 +569,7 @@ public final class LegacyPaintEntityRenderer: NSObject, TGPhotoPaintEntityRender public final class LegacyPaintStickersContext: NSObject, TGPhotoPaintStickersContext { public var captionPanelView: (() -> TGCaptionPanelView?)? + public var editCover: ((CGSize, @escaping (UIImage) -> Void) -> Void)? private let context: AccountContext diff --git a/submodules/LegacyUI/Sources/LegacyController.swift b/submodules/LegacyUI/Sources/LegacyController.swift index 11257c2b60..e1d22b21f4 100644 --- a/submodules/LegacyUI/Sources/LegacyController.swift +++ b/submodules/LegacyUI/Sources/LegacyController.swift @@ -619,14 +619,18 @@ open class LegacyController: ViewController, PresentableController { override open func dismiss(completion: (() -> Void)? = nil) { self.view.endEditing(true) switch self.presentation { - case .modal: - self.controllerNode.animateModalOut { [weak self] in - self?.presentingViewController?.dismiss(animated: false, completion: completion) - } - case .custom: + case .modal: + self.controllerNode.animateModalOut { [weak self] in + self?.presentingViewController?.dismiss(animated: false, completion: completion) + } + case .custom: + if let _ = self.navigationController as? NavigationController { + super.dismiss(animated: false, completion: completion) + } else { self.presentingViewController?.dismiss(animated: false, completion: completion) - case .navigation: - (self.navigationController as? NavigationController)?.filterController(self, animated: true) + } + case .navigation: + (self.navigationController as? NavigationController)?.filterController(self, animated: true) } } diff --git a/submodules/MediaPickerUI/Sources/AvatarEditorPreviewView.swift b/submodules/MediaPickerUI/Sources/AvatarEditorPreviewView.swift index b03ed229e2..f6650028f5 100644 --- a/submodules/MediaPickerUI/Sources/AvatarEditorPreviewView.swift +++ b/submodules/MediaPickerUI/Sources/AvatarEditorPreviewView.swift @@ -83,18 +83,18 @@ final class AvatarEditorPreviewView: UIView { self.currentSize = size self.backgroundView.frame = CGRect(origin: .zero, size: size) - //TODO:localize + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } let labelSize = self.label.update( transition: .immediate, component: AnyComponent( MultilineTextComponent( text: .plain(NSAttributedString( - string: "Use an Emoji", - font: Font.semibold(14.0), + string: presentationData.strings.MediaPicker_UseAnEmoji, + font: Font.semibold(12.0), textColor: .white )), - textShadowColor: UIColor(white: 0.0, alpha: 0.4), - textShadowBlur: 4.0 + textShadowColor: UIColor(white: 0.0, alpha: 0.3), + textShadowBlur: 3.0 ) ), environment: {}, @@ -104,7 +104,7 @@ final class AvatarEditorPreviewView: UIView { if view.superview == nil { 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 { diff --git a/submodules/MediaPickerUI/Sources/LegacyMediaPickerGallery.swift b/submodules/MediaPickerUI/Sources/LegacyMediaPickerGallery.swift index 8cc6f6f846..a4f35fe788 100644 --- a/submodules/MediaPickerUI/Sources/LegacyMediaPickerGallery.swift +++ b/submodules/MediaPickerUI/Sources/LegacyMediaPickerGallery.swift @@ -101,7 +101,7 @@ enum LegacyMediaPickerGallerySource { 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 hasSilentPosting = hasSilentPosting && peer?.id != context.account.peerId @@ -112,6 +112,9 @@ func presentLegacyMediaPickerGallery(context: AccountContext, peer: EnginePeer?, paintStickersContext.captionPanelView = { return getCaptionPanelView() } + paintStickersContext.editCover = { dimensions, completion in + editCover(dimensions, completion) + } let controller = TGModernGalleryController(context: legacyController.context)! controller.asyncTransitionIn = true diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index 0f0154f935..e9d86327dd 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -160,6 +160,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att case wallpaper case story case addImage + case cover case createSticker case createAvatar } @@ -234,6 +235,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att } var dismissAll: () -> Void = { } + public var editCover: (CGSize, @escaping (UIImage) -> Void) -> Void = { _, _ in } private class Node: ViewControllerTracingNode, ASGestureRecognizerDelegate { enum DisplayMode { @@ -311,7 +313,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att self.presentationData = controller.presentationData 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 } let mediaAssetsContext = MediaAssetsContext(assetType: assetType) @@ -522,7 +524,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att self.gridNode.scrollView.alwaysBounceVertical = true 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 { let selectionGesture = MediaPickerGridSelectionGesture() @@ -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 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 self?.openingMedia = false self?.hasGallery = true @@ -1227,6 +1231,8 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att self?.updateIsCameraActive() }, dismissAll: { [weak self] in 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() }, dismissAll: { [weak self] in self?.controller?.dismissAll() + }, editCover: { _, _ in + }) } @@ -1540,9 +1548,6 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att 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)) - 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 { 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 { @@ -1869,9 +1877,8 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att self.titleView.subtitle = presentationData.strings.MediaPicker_CreateSticker self.titleView.isEnabled = true case .createAvatar: - //TODO:localize 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 case .story: 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 case .addImage: self.titleView.title = presentationData.strings.MediaPicker_AddImage + case .cover: + self.titleView.title = presentationData.strings.MediaPicker_ChooseCover } } } else { @@ -3379,8 +3388,7 @@ public func avatarMediaPickerController( var mainButtonState: AttachmentMainButtonState? if canDelete { - //TODO:localize - mainButtonState = AttachmentMainButtonState(text: "Remove Photo", font: .regular, background: .color(.clear), textColor: presentationData.theme.actionSheet.destructiveActionTextColor, isVisible: true, progress: .none, isEnabled: true, hasShimmer: false) + 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) } let mediaPickerController = MediaPickerScreenImpl( @@ -3477,6 +3485,68 @@ public func avatarMediaPickerController( 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, .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 let background = ASImageNode() private let icon = ASImageNode() diff --git a/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift b/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift index 98a3a1f2db..13992bde98 100644 --- a/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift @@ -59,6 +59,8 @@ func requiredBoostSubjectLevel(subject: BoostSubject, group: Bool, context: Acco return configuration.minGroupEmojiPackLevel case .noAds: return configuration.minChannelRestrictAdsLevel + case .wearGift: + return configuration.minChannelWearGiftLevel } } @@ -240,6 +242,7 @@ private final class LevelSectionComponent: CombinedComponent { case audioTranscription case emojiPack case noAds + case wearGift func title(strings: PresentationStrings, isGroup: Bool) -> String { switch self { @@ -269,6 +272,8 @@ private final class LevelSectionComponent: CombinedComponent { return strings.GroupBoost_Table_Group_EmojiPack case .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" case .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 case .noAds: textString = strings.ChannelBoost_EnableNoAdsLevelText("\(requiredLevel)").string + case .wearGift: + textString = strings.ChannelBoost_EnableNoAdsLevelText("\(requiredLevel)").string } } else { 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), 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 .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) { perks.append(.noAds) } +// if !isGroup && level >= requiredBoostSubjectLevel(subject: .wearGift, group: isGroup, context: component.context, configuration: premiumConfiguration) { +// perks.append(.wearGift) +// } levelItems.append( AnyComponentWithIdentity( @@ -1461,6 +1464,8 @@ private final class BoostLevelsContainerComponent: CombinedComponent { titleString = strings.GroupBoost_EmojiPack case .noAds: titleString = strings.ChannelBoost_NoAds + case .wearGift: + titleString = strings.ChannelBoost_WearGift } } else { titleString = isGroup == true ? strings.GroupBoost_Title_Current : strings.ChannelBoost_Title_Current diff --git a/submodules/TelegramNotices/Sources/Notices.swift b/submodules/TelegramNotices/Sources/Notices.swift index 76a99a8cac..d084d5ea6f 100644 --- a/submodules/TelegramNotices/Sources/Notices.swift +++ b/submodules/TelegramNotices/Sources/Notices.swift @@ -200,6 +200,7 @@ private enum ApplicationSpecificGlobalNotice: Int32 { case dismissedBusinessLinksBadge = 73 case dismissedBusinessChatbotsBadge = 74 case captionAboveMediaTooltip = 75 + case channelSendGiftTooltip = 76 var key: ValueBoxKey { let v = ValueBoxKey(length: 4) @@ -544,6 +545,10 @@ private struct ApplicationSpecificNoticeKeys { static func captionAboveMediaTooltip() -> NoticeEntryKey { 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 { @@ -2303,4 +2308,31 @@ public struct ApplicationSpecificNotice { return Int(previousValue) } } + + public static func getChannelSendGiftTooltip(accountManager: AccountManager) -> Signal { + 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, count: Int = 1) -> Signal { + 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) + } + } } diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index efa9e9a297..12bebfbcc0 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -1066,7 +1066,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, attributedString = mutableString case .prizeStars: 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 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())) @@ -1090,13 +1090,28 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_Self_Bought(starsPrice)._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes]) } else if message.author?.id == accountPeerId { 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 { var attributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: peerIds) attributes[1] = boldAttributes 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 !forAdditionalServiceMessage { attributedString = NSAttributedString(string: "\(gift.title) #\(gift.number)", font: titleFont, textColor: primaryTextColor) diff --git a/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/Sources/ChatChannelSubscriberInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/Sources/ChatChannelSubscriberInputPanelNode.swift index dbc280b66e..b5ecdf447c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/Sources/ChatChannelSubscriberInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/Sources/ChatChannelSubscriberInputPanelNode.swift @@ -13,6 +13,8 @@ import ChatPresentationInterfaceState import ChatInputPanelNode import AccountContext import OldChannelsController +import TooltipUI +import TelegramNotices private enum SubscriberAction: Equatable { case join @@ -146,6 +148,7 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { private let activityIndicator: UIActivityIndicatorView private let helpButton: HighlightableButtonNode + private let giftButton: HighlightableButtonNode private var action: SubscriberAction? @@ -176,6 +179,9 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { self.badgeText.isHidden = true self.helpButton = HighlightableButtonNode() + self.helpButton.isHidden = true + self.giftButton = HighlightableButtonNode() + self.giftButton.isHidden = true self.discussButton.addSubnode(self.discussButtonText) self.discussButton.addSubnode(self.badgeBackground) @@ -189,10 +195,12 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { self.addSubnode(self.discussButton) self.view.addSubview(self.activityIndicator) self.addSubnode(self.helpButton) + self.addSubnode(self.giftButton) self.button.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) self.discussButton.addTarget(self, action: #selector(self.discussPressed), forControlEvents: .touchUpInside) self.helpButton.addTarget(self, action: #selector(self.helpPressed), forControlEvents: .touchUpInside) + self.giftButton.addTarget(self, action: #selector(self.giftPressed), forControlEvents: .touchUpInside) } deinit { @@ -207,6 +215,10 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { return super.hitTest(point, with: event) } + @objc private func giftPressed() { + self.interfaceInteraction?.openPremiumGift() + } + @objc private func helpPressed() { 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) } + 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 { self.layoutData = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, isSecondary, metrics) @@ -311,6 +368,7 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { if previousState?.theme !== interfaceState.theme { 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.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 { @@ -356,17 +414,29 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { 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)) - if let peer = interfaceState.renderedPeer?.peer as? TelegramChannel, peer.flags.contains(.isGigagroup) { - self.helpButton.isHidden = false + 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 + } } else { + self.giftButton.isHidden = true self.helpButton.isHidden = true } } else { 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.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) } else { + self.giftButton.isHidden = true self.helpButton.isHidden = true let availableWidth = min(600.0, width - leftInset - rightInset) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift index ecc44cc1f0..2d0327ee7a 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift @@ -469,16 +469,19 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { buttonTitle = item.presentationData.strings.Notification_PremiumPrize_View 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 { 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 isChannelGift = item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel + let isChannelGift = item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel || channelPeerId != nil if isSelfGift { title = item.presentationData.strings.Notification_StarGift_Self_Title } 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 } if let giftText, !giftText.isEmpty { @@ -554,7 +557,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { 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 { isStarGift = true let authorName: String diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift index cb7e670d8e..be8bc4b441 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift @@ -328,7 +328,7 @@ public extension EmojiPagerContentComponent { } else { 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])) } } @@ -614,7 +614,6 @@ public extension EmojiPagerContentComponent { } if let uniqueGifts, !uniqueGifts.items.isEmpty { - //TODO:localize let groupId = "collectible" let groupIndex: Int if let current = itemGroupIndexById[groupId] { @@ -622,7 +621,7 @@ public extension EmojiPagerContentComponent { } else { groupIndex = itemGroups.count 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 { diff --git a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift index fed1db39b7..dd50e73c3b 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift @@ -642,8 +642,7 @@ final class GiftOptionsScreenComponent: Component { if isSelfGift { premiumTitleString = strings.Gift_Options_GiftSelf_Title } else if isChannelGift { - //TODO:localize - premiumTitleString = "Send a Gift" + premiumTitleString = strings.Gift_Options_GiftChannel_Title } else { premiumTitleString = strings.Gift_Options_Premium_Title } @@ -675,8 +674,7 @@ final class GiftOptionsScreenComponent: Component { if isSelfGift { premiumDescriptionRawString = strings.Gift_Options_GiftSelf_Text } else if isChannelGift { - //TODO:localize - premiumDescriptionRawString = "Select a gift to show appreciation for **\(peerName)**." + premiumDescriptionRawString = strings.Gift_Options_GiftChannel_Text(peerName).string } else { premiumDescriptionRawString = strings.Gift_Options_Premium_Text(peerName).string } diff --git a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift index 6849e2ea48..9e1e53a46f 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift @@ -233,7 +233,7 @@ final class ChatGiftPreviewItemNode: ListViewItemNode { case let .starGift(gift): media = [ 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) ) ] } diff --git a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift index aaa61502d2..5bd4b7128e 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift @@ -34,6 +34,7 @@ import BlurredBackgroundComponent import ProgressNavigationButtonNode import Markdown import GiftViewScreen +import UndoUI final class GiftSetupScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -321,7 +322,16 @@ final class GiftSetupScreenComponent: Component { guard let component = self.component, case let .starGift(starGift) = component.subject, let starsContext = component.context.starsContext, let starsState = starsContext.currentState else { 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 guard let self else { return @@ -331,7 +341,7 @@ final class GiftSetupScreenComponent: Component { self.state?.updated() 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) |> map(Optional.init) @@ -359,22 +369,43 @@ final class GiftSetupScreenComponent: Component { return } - var controllers = navigationController.viewControllers - controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) && !($0 is PeerInfoScreen) && !($0 is ContactSelectionController) } - var foundController = false - for controller in controllers.reversed() { - if let chatController = controller as? ChatController, case .peer(id: component.peerId) = chatController.chatLocation { - chatController.hintPlayNextOutgoingGift() - foundController = true - break + 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 + controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) && !($0 is PeerInfoScreen) && !($0 is ContactSelectionController) } + var foundController = false + for controller in controllers.reversed() { + if let chatController = controller as? ChatController, case .peer(id: component.peerId) = chatController.chatLocation { + chatController.hintPlayNextOutgoingGift() + foundController = true + break + } } + if !foundController { + let chatController = component.context.sharedContext.makeChatController(context: component.context, chatLocation: .peer(id: component.peerId), subject: nil, botStart: nil, mode: .standard(.default), params: nil) + chatController.hintPlayNextOutgoingGift() + controllers.append(chatController) + } + navigationController.setViewControllers(controllers, animated: true) } - if !foundController { - let chatController = component.context.sharedContext.makeChatController(context: component.context, chatLocation: .peer(id: component.peerId), subject: nil, botStart: nil, mode: .standard(.default), params: nil) - chatController.hintPlayNextOutgoingGift() - controllers.append(chatController) - } - navigationController.setViewControllers(controllers, animated: true) } starsContext.load(force: true) @@ -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() |> filter { $0 != nil } |> take(1) @@ -415,7 +446,7 @@ final class GiftSetupScreenComponent: Component { context: component.context, starsContext: starsContext, options: options ?? [], - purpose: .starGift(peerId: component.peerId, requiredStars: starGift.price), + purpose: .starGift(peerId: component.peerId, requiredStars: finalPrice), completion: { [weak self, weak starsContext] stars in guard let self, let starsContext else { return @@ -642,8 +673,7 @@ final class GiftSetupScreenComponent: Component { if isSelfGift { navigationTitleString = environment.strings.Gift_SendSelf_Title } else if isChannelGift { - //TODO:localize - navigationTitleString = "Gift Preview" + navigationTitleString = environment.strings.Gift_SendChannel_Title } else { navigationTitleString = environment.strings.Gift_Send_TitleTo(peerName).string } @@ -988,8 +1018,7 @@ final class GiftSetupScreenComponent: Component { if isSelfGift { hideSectionFooterString = environment.strings.Gift_SendSelf_HideMyName_Info } else if isChannelGift { - //TODO:localize - hideSectionFooterString = "Hide my name and message from visitors of this channel. The channel admins will still see them." + hideSectionFooterString = environment.strings.Gift_SendChannel_HideMyName_Info } else { hideSectionFooterString = environment.strings.Gift_Send_HideMyName_Info(peerName, peerName).string } diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift index 499b46be2f..9192736c90 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -385,6 +385,7 @@ private final class GiftViewSheetContent: CombinedComponent { let strings = environment.strings let dateTimeFormat = environment.dateTimeFormat let nameDisplayOrder = component.context.sharedContext.currentPresentationData.with { $0 }.nameDisplayOrder + let controller = environment.controller let state = context.state let subject = state.subject @@ -410,6 +411,7 @@ private final class GiftViewSheetContent: CombinedComponent { var upgradeStars: Int64? var uniqueGift: StarGift.UniqueGift? var isSelfGift = false + var isChannelGift = false if case let .soldOutGift(gift) = subject { animationFile = gift.file @@ -444,7 +446,12 @@ private final class GiftViewSheetContent: CombinedComponent { uniqueGift = gift } savedToProfile = arguments.savedToProfile - incoming = arguments.incoming || arguments.peerId == component.context.account.peerId + if let reference = arguments.reference, case .peer = reference { + isChannelGift = true + incoming = true + } else { + incoming = arguments.incoming || arguments.peerId == component.context.account.peerId + } nameHidden = arguments.nameHidden isSelfGift = arguments.messageId?.peerId == component.context.account.peerId @@ -482,6 +489,9 @@ private final class GiftViewSheetContent: CombinedComponent { return } if state.inWearPreview { + if let controller = controller() as? GiftViewScreen { + controller.dismissAllTooltips() + } state.inWearPreview = false state.updated(transition: .spring(duration: 0.4)) } else if state.inUpgradePreview { @@ -849,8 +859,7 @@ private final class GiftViewSheetContent: CombinedComponent { textColor: secondaryTextColor, accentColor: linkColor, iconName: "Premium/Collectible/Tradable", - iconColor: linkColor, - badge: strings.Gift_Upgrade_Soon + iconColor: linkColor )) ) ) @@ -937,9 +946,9 @@ private final class GiftViewSheetContent: CombinedComponent { } else if let convertStars, !upgraded { if !converted { 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 { - 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 { descriptionText = strings.Gift_View_ConvertedDescription(strings.Gift_View_ConvertedDescription_Stars(Int32(convertStars))).string @@ -1152,7 +1161,7 @@ private final class GiftViewSheetContent: CombinedComponent { ), action: { component.openPeer(peer) - Queue.mainQueue().after(1.0, { + Queue.mainQueue().after(0.6, { component.cancel(false) }) } @@ -1188,7 +1197,7 @@ private final class GiftViewSheetContent: CombinedComponent { ), action: { component.openPeer(peer) - Queue.mainQueue().after(1.0, { + Queue.mainQueue().after(0.6, { component.cancel(false) }) } @@ -1215,7 +1224,7 @@ private final class GiftViewSheetContent: CombinedComponent { isBot = true } let fromComponent: AnyComponent - if incoming && !peer.isDeleted && !isBot { + if incoming && !peer.isDeleted && !isBot && !isChannelGift { fromComponent = AnyComponent( HStack([ AnyComponentWithIdentity( @@ -1231,7 +1240,7 @@ private final class GiftViewSheetContent: CombinedComponent { ), action: { component.openPeer(peer) - Queue.mainQueue().after(1.0, { + Queue.mainQueue().after(0.6, { component.cancel(false) }) } @@ -1247,7 +1256,7 @@ private final class GiftViewSheetContent: CombinedComponent { )), action: { component.sendGift(peerId) - Queue.mainQueue().after(1.0, { + Queue.mainQueue().after(0.6, { component.cancel(false) }) } @@ -1267,7 +1276,7 @@ private final class GiftViewSheetContent: CombinedComponent { ), action: { component.openPeer(peer) - Queue.mainQueue().after(1.0, { + Queue.mainQueue().after(0.6, { component.cancel(false) }) } @@ -1315,7 +1324,7 @@ private final class GiftViewSheetContent: CombinedComponent { effectAlignment: .center, action: { component.transferGift() - Queue.mainQueue().after(1.0, { + Queue.mainQueue().after(0.6, { component.cancel(false) }) } @@ -1326,9 +1335,10 @@ private final class GiftViewSheetContent: CombinedComponent { ) context.add(transferButton .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( component: PlainButtonComponent( content: AnyComponent( @@ -1345,7 +1355,7 @@ private final class GiftViewSheetContent: CombinedComponent { state.pendingWear = false 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 { if let controller = controller() as? GiftViewScreen { controller.dismissAllTooltips() @@ -1362,6 +1372,8 @@ private final class GiftViewSheetContent: CombinedComponent { ) context.add(wearButton .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( @@ -1383,6 +1395,8 @@ private final class GiftViewSheetContent: CombinedComponent { ) context.add(shareButton .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] { component.openPeer(peer) - Queue.mainQueue().after(1.0, { + Queue.mainQueue().after(0.6, { component.cancel(false) }) } @@ -1734,11 +1748,11 @@ private final class GiftViewSheetContent: CombinedComponent { } let descriptionText: String 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 { - descriptionText = strings.Gift_View_HiddenInfoShow + descriptionText = isChannelGift ? strings.Gift_View_HiddenInfoShow_Channel : strings.Gift_View_HiddenInfoShow } else { - descriptionText = strings.Gift_View_HiddenInfo + descriptionText = isChannelGift ? strings.Gift_View_HiddenInfo_Channel : strings.Gift_View_HiddenInfo } let textFont = Font.regular(13.0) @@ -1769,7 +1783,7 @@ private final class GiftViewSheetContent: CombinedComponent { }, tapAction: { _, _ in component.updateSavedToProfile(!savedToProfile) - Queue.mainQueue().after(1.0, { + Queue.mainQueue().after(0.6, { component.cancel(false) }) } @@ -1794,26 +1808,74 @@ private final class GiftViewSheetContent: CombinedComponent { ) let buttonChild: _UpdatedChildComponent if state.inWearPreview, let uniqueGift { + let buttonContent: AnyComponentWithIdentity + 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( component: ButtonComponent( background: buttonBackground, - content: 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)))) - ), + content: buttonContent, isEnabled: true, displaysProgress: false, action: { [weak state] in if let state { - state.pendingWear = true - state.pendingTakeOff = false - state.inWearPreview = false - state.updated(transition: .spring(duration: 0.4)) - - let _ = component.context.engine.accountData.setStarGiftStatus(starGift: uniqueGift, expirationDate: nil).start() - - Queue.mainQueue().after(0.2) { - component.showAttributeInfo(statusTag, "You put on \(uniqueGift.title) #\(uniqueGift.number)") + 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.pendingTakeOff = false + state.inWearPreview = false + state.updated(transition: .spring(duration: 0.4)) + + let _ = component.context.engine.accountData.setStarGiftStatus(starGift: uniqueGift, expirationDate: nil).start() + + Queue.mainQueue().after(0.2) { + component.showAttributeInfo(statusTag, strings.Gift_View_PutOn("\(uniqueGift.title) #\(uniqueGift.number)").string) + } } } }), @@ -1901,7 +1963,7 @@ private final class GiftViewSheetContent: CombinedComponent { transition: context.transition ) } 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( component: ButtonComponent( background: buttonBackground, @@ -2131,9 +2193,21 @@ public class GiftViewScreen: ViewControllerComponentContainer { case let .message(message): if let action = message.media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction { switch action.action { - case let .starGift(gift, convertStars, text, entities, nameHidden, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, _, upgradeMessageId, _, _): - 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) - case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, _): + case let .starGift(gift, convertStars, text, entities, nameHidden, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, _, upgradeMessageId, peerId, senderId, savedId): + var reference: StarGiftReference + 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 if isUpgrade { if message.author?.id != message.id.peerId { @@ -2146,7 +2220,7 @@ public class GiftViewScreen: ViewControllerComponentContainer { } else { 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: return nil } @@ -2545,7 +2619,7 @@ public class GiftViewScreen: ViewControllerComponentContainer { return } openPeerImpl?(peer) - Queue.mainQueue().after(1.0) { + Queue.mainQueue().after(0.6) { self?.dismiss(animated: false, completion: nil) } }) @@ -3398,13 +3472,16 @@ private final class GiftViewContextReferenceContentSource: ContextReferenceConte private final class HeaderButtonComponent: CombinedComponent { let title: String let iconName: String + let isLocked: Bool public init( title: String, - iconName: String + iconName: String, + isLocked: Bool = false ) { self.title = title self.iconName = iconName + self.isLocked = isLocked } static func ==(lhs: HeaderButtonComponent, rhs: HeaderButtonComponent) -> Bool { @@ -3414,6 +3491,9 @@ private final class HeaderButtonComponent: CombinedComponent { if lhs.iconName != rhs.iconName { return false } + if lhs.isLocked != rhs.isLocked { + return false + } return true } @@ -3421,6 +3501,7 @@ private final class HeaderButtonComponent: CombinedComponent { let background = Child(RoundedRectangle.self) let title = Child(MultilineTextComponent.self) let icon = Child(BundleIconComponent.self) + let lockIcon = Child(BundleIconComponent.self) return { context in let component = context.component @@ -3448,7 +3529,7 @@ private final class HeaderButtonComponent: CombinedComponent { context.add(icon .position(CGPoint(x: context.availableSize.width / 2.0, y: 22.0)) ) - + let title = title.update( component: MultilineTextComponent( text: .plain(NSAttributedString( @@ -3463,8 +3544,27 @@ private final class HeaderButtonComponent: CombinedComponent { availableSize: CGSize(width: context.availableSize.width - 16.0, height: context.availableSize.height), 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 - .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 diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftWithdrawAlertController.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftWithdrawAlertController.swift index fe8da55fdc..4ef0ad0e90 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftWithdrawAlertController.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftWithdrawAlertController.swift @@ -173,7 +173,8 @@ private final class GiftWithdrawAlertContentNode: AlertContentNode { photo: nil, media: [], uniqueGift: nil, - backgroundColor: .clear + backgroundColor: .clear, + size: avatarSize ) ), environment: {}, diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/EditCover.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/EditCover.swift new file mode 100644 index 0000000000..7310f6d9a3 --- /dev/null +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/EditCover.swift @@ -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 + } +} diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index cff7729ebe..5ce2a51e44 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -864,7 +864,7 @@ final class MediaEditorScreenComponent: Component { case .storyEditor: doneButtonTitle = isEditingStory ? environment.strings.Story_Editor_Done.uppercased() : environment.strings.Story_Editor_Next.uppercased() doneButtonIcon = UIImage(bundleImageName: "Media Editor/Next")! - case .stickerEditor, .avatarEditor: + case .stickerEditor, .avatarEditor, .coverEditor: doneButtonTitle = nil doneButtonIcon = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/Apply"), color: .white)! case .botPreview: @@ -1060,9 +1060,14 @@ final class MediaEditorScreenComponent: Component { ) var isAvatarEditor = false + var isCoverEditor = false if case .avatarEditor = controller.mode { isAvatarEditor = true - + } else if case .coverEditor = controller.mode { + isCoverEditor = true + } + + if isAvatarEditor || isCoverEditor { drawButtonFrame.origin.x = stickerButtonFrame.origin.x 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 { 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 { self.addSubview(stickerButtonView) } @@ -2688,6 +2693,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID case stickerEditor(mode: StickerEditorMode) case botPreview case avatarEditor + case coverEditor(dimensions: CGSize) } public enum TransitionIn { @@ -2882,14 +2888,17 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID var isStickerEditor = false var isAvatarEditor = false + var isCoverEditor = false if case .stickerEditor = controller.mode { isStickerEditor = true } else if case .avatarEditor = controller.mode { isAvatarEditor = true + } else if case .coverEditor = controller.mode { + isCoverEditor = true } 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 = { 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() self.stickerBackgroundView = stickerBackgroundView self.previewContainerView.addSubview(stickerBackgroundView) + case .coverEditor: + let stickerBackgroundView = UIImageView() + self.stickerBackgroundView = stickerBackgroundView + self.previewContainerView.addSubview(stickerBackgroundView) default: self.previewContainerView.addSubview(self.gradientView) } @@ -2970,7 +2983,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID self.entitiesView.addSubview(self.drawingView) switch controller.mode { - case .stickerEditor, .avatarEditor: + case .stickerEditor, .avatarEditor, .coverEditor: let stickerOverlayLayer = SimpleShapeLayer() stickerOverlayLayer.fillColor = UIColor(rgb: 0x000000, alpha: 0.7).cgColor stickerOverlayLayer.fillRule = .evenOdd @@ -3174,7 +3187,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID } else { mediaEntity.scale = storyDimensions.width / fittedSize.width } - case .stickerEditor, .avatarEditor: + case .stickerEditor, .avatarEditor, .coverEditor: if fittedSize.height > fittedSize.width { mediaEntity.scale = storyDimensions.width / fittedSize.width } else { @@ -3216,6 +3229,8 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID mediaEditorMode = .sticker } else if case .avatarEditor = controller.mode { mediaEditorMode = .avatar + } else if case .coverEditor = controller.mode { + mediaEditorMode = .avatar } 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 { 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: [:]) messages = .single([message]) } else { @@ -3860,6 +3875,9 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID } else if case .avatarEditor = controller.mode { hasSwipeToDismiss = false hasSwipeToEnhance = false + } else if case .coverEditor = controller.mode { + hasSwipeToDismiss = false + hasSwipeToEnhance = false } else if self.isCollageTimelineOpen { hasSwipeToEnhance = false } @@ -4064,7 +4082,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID } else { initialScale = self.previewContainerView.bounds.width / image.size.width } - case .stickerEditor, .avatarEditor: + case .stickerEditor, .avatarEditor, .coverEditor: if image.size.height > image.size.width { initialScale = self.previewContainerView.bounds.width / image.size.width } else { @@ -5137,7 +5155,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID controller.requestStickerCompletion(animated: true) case .botPreview: controller.requestStoryCompletion(animated: true) - case .avatarEditor: + case .avatarEditor, .coverEditor: controller.requestStoryCompletion(animated: true) } } @@ -5404,7 +5422,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID var hasInteractiveStickers = true if let controller = self.controller { switch controller.mode { - case .stickerEditor, .botPreview, .avatarEditor: + case .stickerEditor, .botPreview, .avatarEditor, .coverEditor: hasInteractiveStickers = false default: break @@ -5885,7 +5903,11 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID 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 { - 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) 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: overlayInnerRect = UIBezierPath(cgPath: CGPath(ellipseIn: stickerFrameRect, transform: nil)) 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: 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 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 { let controller = context.sharedContext.makePremiumIntroController(context: context, source: .storiesExpirationDurations, forceDark: true, dismissed: nil) self.push(controller) @@ -6884,7 +6910,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID save = presentationData.strings.Story_Editor_DraftKeepMedia } text = presentationData.strings.Story_Editor_DraftDiscaedText - case .stickerEditor, .botPreview, .avatarEditor: + case .stickerEditor, .botPreview, .avatarEditor, .coverEditor: title = presentationData.strings.Story_Editor_DraftDiscardMedia text = presentationData.strings.Story_Editor_DiscardText } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift index 09a0fb2e32..1e6afc1246 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift @@ -1560,6 +1560,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen } } + let profileGiftsContext = ProfileGiftsContext(account: context.account, peerId: peerId) + return combineLatest( context.account.viewTracker.peerView(peerId, updateData: true), 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 } } + + if availablePanes != nil, let cachedData = peerView.cachedData as? CachedChannelData { + if let starGiftsCount = cachedData.starGiftsCount, starGiftsCount > 0 { + availablePanes?.insert(.gifts, at: hasStories ? 1 : 0) + } + } } else { availablePanes = nil } @@ -1665,7 +1673,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen starsRevenueStatsContext: starsRevenueContextAndState.0, revenueStatsState: revenueContextAndState.1, revenueStatsContext: revenueContextAndState.0, - profileGiftsContext: nil, + profileGiftsContext: profileGiftsContext, premiumGiftOptions: [], webAppPermissions: nil ) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift index bbc655fc54..fb066c210d 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift @@ -463,7 +463,15 @@ private final class PeerInfoPendingPane { let paneNode: PeerInfoPaneNode switch key { 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: var canManage = false if let peer = data.peer { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index e04721f109..7c30a7169b 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -6467,8 +6467,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } else if let channel = peer as? TelegramChannel { if let cachedData = strongSelf.data?.cachedData as? CachedChannelData { if case .broadcast = channel.info { - //TODO:localize - items.append(.action(ContextMenuActionItem(text: "Send a Gift", badge: nil, icon: { theme in + items.append(.action(ContextMenuActionItem(text: presentationData.strings.Profile_SendGift, badge: nil, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Gift"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in 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 { return } @@ -12772,7 +12771,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc public func openAvatarSetup(completedWithUploadingImage: @escaping (UIImage, Signal) -> UIView?) { let proceed = { [weak self] in - self?.newopenAvatarForEditing(completedWithUploadingImage: completedWithUploadingImage) + self?.openAvatarForEditing(completedWithUploadingImage: completedWithUploadingImage) } if !self.isNodeLoaded { 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) { let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) |> deliverOnMainQueue).startStandalone(next: { peer in diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenAvatarSetup.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenAvatarSetup.swift index b1bf6aacfc..b4fe717e92 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenAvatarSetup.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenAvatarSetup.swift @@ -19,7 +19,7 @@ import LegacyComponents import LegacyMediaPickerUI extension PeerInfoScreenImpl { - func newopenAvatarForEditing(mode: PeerInfoAvatarEditingMode = .generic, fromGallery: Bool = false, completion: @escaping (UIImage?) -> Void = { _ in }, completedWithUploadingImage: @escaping (UIImage, Signal) -> UIView? = { _, _ in nil }) { + func openAvatarForEditing(mode: PeerInfoAvatarEditingMode = .generic, fromGallery: Bool = false, completion: @escaping (UIImage?) -> Void = { _ in }, completedWithUploadingImage: @escaping (UIImage, Signal) -> 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 { return } @@ -170,7 +170,6 @@ extension PeerInfoScreenImpl { default: break } - dismissImpl?() } as (MediaEditorScreenImpl.Result, @escaping (@escaping () -> Void) -> Void) -> Void ) @@ -200,6 +199,7 @@ extension PeerInfoScreenImpl { } navigationController.setViewControllers(viewControllers, animated: false) } + } mainController.navigationPresentation = .flatModal mainController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) @@ -284,12 +284,11 @@ extension PeerInfoScreenImpl { (self.navigationController?.topViewController as? ViewController)?.present(actionSheet, in: .window(.root)) } - public func updateProfilePhoto(_ image: UIImage, mode: PeerInfoAvatarEditingMode, uploadStatus: Promise?) { + private func setupProfilePhotoUpload(image: UIImage, mode: PeerInfoAvatarEditingMode, indefiniteProgress: Bool) -> LocalFileMediaResource? { guard let data = image.jpegData(compressionQuality: 0.6) else { - uploadStatus?.set(.single(.done)) - return + return nil } - + if self.controllerNode.headerNode.isAvatarExpanded { self.controllerNode.headerNode.ignoreCollapse = true self.controllerNode.headerNode.updateIsAvatarExpanded(false, transition: .immediate) @@ -303,14 +302,25 @@ extension PeerInfoScreenImpl { if [.suggest, .fallback].contains(mode) { } else { + if indefiniteProgress { + self.controllerNode.state = self.controllerNode.state.withAvatarUploadProgress(.indefinite) + } self.controllerNode.state = self.controllerNode.state.withUpdatingAvatar(.image(representation)) } - 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 + return resource + } + + public func updateProfilePhoto(_ image: UIImage, mode: PeerInfoAvatarEditingMode, uploadStatus: Promise?) { + guard let resource = setupProfilePhotoUpload(image: image, mode: mode, indefiniteProgress: false) else { + uploadStatus?.set(.single(.done)) + return + } + let postbox = self.context.account.postbox let signal: Signal if self.isSettings || self.isMyProfile { @@ -405,23 +415,228 @@ 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 +// 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 { [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 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) { - 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 if let fileId = adjustments?.documentId, let backgroundColors = adjustments?.colors as? [Int32], fileId != 0 { if let packId = adjustments?.stickerPackId, let accessHash = adjustments?.stickerPackAccessHash, packId != 0 { @@ -439,19 +654,9 @@ extension PeerInfoScreenImpl { uploadVideo = false } } - - if [.suggest, .fallback].contains(mode) { - } else { - self.controllerNode.state = self.controllerNode.state.withUpdatingAvatar(.image(representation)) - if !uploadVideo { - self.controllerNode.state = self.controllerNode.state.withAvatarUploadProgress(.indefinite) - } + guard let photoResource = self.setupProfilePhotoUpload(image: image, mode: mode, indefiniteProgress: !uploadVideo) else { + return } - - 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 if let adjustments = adjustments, adjustments.videoStartValue > 0.0 { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/BUILD b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/BUILD index 27132baef3..57f936066c 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/BUILD @@ -52,6 +52,7 @@ swift_library( "//submodules/TelegramUI/Components/Gifts/GiftViewScreen", "//submodules/TelegramUI/Components/ButtonComponent", "//submodules/Components/BalancedTextComponent", + "//submodules/TelegramUI/Components/CheckComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift index c7cf10ca65..7112eca177 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift @@ -22,11 +22,14 @@ import GiftItemComponent import PlainButtonComponent import GiftViewScreen import SolidRoundedButtonNode +import UndoUI +import CheckComponent public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDelegate { private let context: AccountContext private let peerId: PeerId private let profileGifts: ProfileGiftsContext + private let canManage: Bool private var dataDisposable: Disposable? @@ -38,10 +41,11 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr private let backgroundNode: ASDisplayNode private let scrollNode: ASScrollNode - private var unlockBackground: NavigationBackgroundNode? - private var unlockSeparator: ASDisplayNode? - private var unlockText: ComponentView? - private var unlockButton: SolidRoundedButtonNode? + private var footerText: ComponentView? + private var panelBackground: NavigationBackgroundNode? + private var panelSeparator: ASDisplayNode? + private var panelButton: SolidRoundedButtonNode? + private var panelCheck: ComponentView? 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] = [:] - 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.peerId = peerId self.chatControllerInteraction = chatControllerInteraction self.openPeerContextAction = openPeerContextAction self.profileGifts = profileGifts + self.canManage = canManage self.backgroundNode = ASDisplayNode() self.scrollNode = ASScrollNode() @@ -125,15 +130,16 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr self.updateScrolling(transition: .immediate) } + private var notify = false func updateScrolling(transition: ComponentTransition) { if let starsProducts = self.starsProducts, let params = self.currentParams { let optionSpacing: CGFloat = 10.0 - let sideInset = params.sideInset + 16.0 + let itemsSideInset = params.sideInset + 16.0 let defaultItemsInRow = 3 let itemsInRow = max(1, min(starsProducts.count, defaultItemsInRow)) - let defaultOptionWidth = (params.size.width - sideInset * 2.0 - optionSpacing * CGFloat(defaultItemsInRow - 1)) / CGFloat(defaultItemsInRow) - let optionWidth = (params.size.width - sideInset * 2.0 - optionSpacing * CGFloat(itemsInRow - 1)) / CGFloat(itemsInRow) + let defaultOptionWidth = (params.size.width - itemsSideInset * 2.0 - optionSpacing * CGFloat(defaultItemsInRow - 1)) / CGFloat(defaultItemsInRow) + let optionWidth = (params.size.width - itemsSideInset * 2.0 - optionSpacing * CGFloat(itemsInRow - 1)) / CGFloat(itemsInRow) let starsOptionSize = CGSize(width: optionWidth, height: defaultOptionWidth) @@ -142,7 +148,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr let topInset: CGFloat = 60.0 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 for product in starsProducts { @@ -233,22 +239,22 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr self.profileGifts.updateStarGiftAddedToProfile(reference: reference, added: added) }, convertToStars: { [weak self] in - guard let self else { + guard let self, let reference = product.reference else { return } - self.profileGifts.convertStarGift(reference: product.reference) + self.profileGifts.convertStarGift(reference: reference) }, transferGift: { [weak self] prepaid, peerId in - guard let self else { + guard let self, let reference = product.reference else { 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 - guard let self else { + guard let self, let reference = product.reference else { 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 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 if itemFrame.maxX > params.size.width { - itemFrame.origin.x = sideInset + itemFrame.origin.x = itemsSideInset itemFrame.origin.y += starsOptionSize.height + optionSpacing } index += 1 @@ -306,93 +312,164 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr var bottomScrollInset: CGFloat = 0.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 size = params.size - let sideInset = params.sideInset - let bottomInset = params.bottomInset - let presentationData = params.presentationData - - let themeUpdated = self.theme !== presentationData.theme - self.theme = presentationData.theme - - let unlockText: ComponentView - let unlockBackground: NavigationBackgroundNode - let unlockSeparator: ASDisplayNode - let unlockButton: SolidRoundedButtonNode - if let current = self.unlockText { - unlockText = current - } else { - unlockText = ComponentView() - self.unlockText = unlockText - } - - if let current = self.unlockBackground { - unlockBackground = current - } else { - unlockBackground = NavigationBackgroundNode(color: presentationData.theme.rootController.tabBar.backgroundColor) - self.addSubnode(unlockBackground) - self.unlockBackground = unlockBackground - } - - if let current = self.unlockSeparator { - unlockSeparator = current - } else { - unlockSeparator = ASDisplayNode() - self.addSubnode(unlockSeparator) - self.unlockSeparator = unlockSeparator - } - - if let current = self.unlockButton { - 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 - - unlockButton.pressed = { [weak self] in - self?.buttonPressed() - } - } - if themeUpdated { - unlockBackground.updateColor(color: presentationData.theme.rootController.tabBar.backgroundColor, transition: .immediate) - unlockSeparator.backgroundColor = presentationData.theme.rootController.tabBar.separatorColor - unlockButton.updateTheme(SolidRoundedButtonTheme(theme: presentationData.theme)) + + let transition = ComponentTransition.immediate + + let size = params.size + let sideInset = params.sideInset + let bottomInset = params.bottomInset + let presentationData = params.presentationData + + let themeUpdated = self.theme !== presentationData.theme + self.theme = presentationData.theme + + let panelBackground: NavigationBackgroundNode + let panelSeparator: ASDisplayNode + let panelButton: SolidRoundedButtonNode + + if let current = self.panelBackground { + panelBackground = current + } else { + panelBackground = NavigationBackgroundNode(color: presentationData.theme.rootController.tabBar.backgroundColor) + self.addSubnode(panelBackground) + self.panelBackground = panelBackground + } + + if let current = self.panelSeparator { + panelSeparator = current + } else { + panelSeparator = ASDisplayNode() + self.addSubnode(panelSeparator) + self.panelSeparator = panelSeparator + } + + if let current = self.panelButton { + panelButton = current + } else { + panelButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: presentationData.theme), height: 50.0, cornerRadius: 10.0) + self.view.addSubview(panelButton.view) + self.panelButton = panelButton + + panelButton.title = self.peerId == self.context.account.peerId ? params.presentationData.strings.PeerInfo_Gifts_Send : params.presentationData.strings.PeerInfo_Gifts_SendGift + + panelButton.pressed = { [weak self] in + self?.buttonPressed() } + } + + if themeUpdated { + panelBackground.updateColor(color: presentationData.theme.rootController.tabBar.backgroundColor, transition: .immediate) + panelSeparator.backgroundColor = presentationData.theme.rootController.tabBar.separatorColor + panelButton.updateTheme(SolidRoundedButtonTheme(theme: presentationData.theme)) + } + + let textFont = Font.regular(13.0) + let boldTextFont = Font.semibold(13.0) + let textColor = presentationData.theme.list.itemSecondaryTextColor + let linkColor = presentationData.theme.list.itemAccentColor + let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: boldTextFont, textColor: linkColor), linkAttribute: { _ in + return nil + }) + + var scrollOffset: CGFloat = max(0.0, size.height - params.visibleHeight) + + let buttonSideInset = sideInset + 16.0 + let buttonSize = CGSize(width: size.width - buttonSideInset * 2.0, height: 50.0) + var bottomPanelHeight = bottomInset + buttonSize.height + 8.0 + if params.visibleHeight < 110.0 { + scrollOffset -= bottomPanelHeight + } + + transition.setFrame(view: panelButton.view, frame: CGRect(origin: CGPoint(x: buttonSideInset, y: size.height - bottomInset - buttonSize.height - scrollOffset), size: buttonSize)) + let _ = panelButton.updateLayout(width: buttonSize.width, transition: .immediate) + + if self.canManage { + bottomPanelHeight -= 9.0 - let textFont = Font.regular(13.0) - let boldTextFont = Font.semibold(13.0) - let textColor = presentationData.theme.list.itemSecondaryTextColor - let linkColor = presentationData.theme.list.itemAccentColor - let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: boldTextFont, textColor: linkColor), linkAttribute: { _ in - return nil - }) - - var scrollOffset: CGFloat = max(0.0, size.height - params.visibleHeight) - - let buttonSideInset = sideInset + 16.0 - let buttonSize = CGSize(width: size.width - buttonSideInset * 2.0, height: 50.0) - let bottomPanelHeight = bottomInset + buttonSize.height + 8.0 - if params.visibleHeight < 110.0 { - scrollOffset -= bottomPanelHeight + let panelCheck: ComponentView + if let current = self.panelCheck { + panelCheck = current + } else { + panelCheck = ComponentView() + 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 + ) - transition.setFrame(view: unlockButton.view, frame: CGRect(origin: CGPoint(x: buttonSideInset, y: size.height - bottomInset - buttonSize.height - scrollOffset), size: buttonSize)) - let _ = unlockButton.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)) - unlockBackground.update(size: CGSize(width: size.width, height: bottomPanelHeight), transition: transition.containedViewLayoutTransition) - 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 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 + if let current = self.footerText { + footerText = current + } else { + footerText = ComponentView() + self.footerText = footerText + } + let footerTextSize = footerText.update( transition: .immediate, component: AnyComponent( BalancedTextComponent( - text: .markdown(text: params.presentationData.strings.PeerInfo_Gifts_Info, attributes: markdownAttributes), + text: .markdown(text: presentationData.strings.PeerInfo_Gifts_Info, attributes: markdownAttributes), horizontalAlignment: .center, maximumNumberOfLines: 0, lineSpacing: 0.2 @@ -401,18 +478,19 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr environment: {}, containerSize: CGSize(width: size.width - 32.0, height: 200.0) ) - if let view = unlockText.view { + if let view = footerText.view { if view.superview == nil { view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.buttonPressed))) 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 += unlockSize.height - contentHeight += bottomPanelHeight - - bottomScrollInset = bottomPanelHeight - 40.0 + contentHeight += footerTextSize.height } + contentHeight += bottomPanelHeight + + bottomScrollInset = bottomPanelHeight - 40.0 + contentHeight += params.bottomInset self.scrollNode.view.scrollIndicatorInsets = UIEdgeInsets(top: 50.0, left: 0.0, bottom: bottomScrollInset, right: 0.0) @@ -430,16 +508,21 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr } @objc private func buttonPressed() { - let _ = (self.context.account.stateManager.contactBirthdays - |> take(1) - |> deliverOnMainQueue).start(next: { [weak self] birthdays in - guard let self else { - return - } - let controller = self.context.sharedContext.makePremiumGiftController(context: self.context, source: .settings(birthdays), completion: nil) - controller.navigationPresentation = .modal + if self.peerId == self.context.account.peerId { + let _ = (self.context.account.stateManager.contactBirthdays + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] birthdays in + guard let self else { + return + } + let controller = self.context.sharedContext.makePremiumGiftController(context: self.context, source: .settings(birthdays), completion: nil) + controller.navigationPresentation = .modal + 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) { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoVisualMediaPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoVisualMediaPaneNode.swift index 49a8cd1bf3..9602dd427f 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoVisualMediaPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoVisualMediaPaneNode.swift @@ -883,7 +883,11 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding, ListShimme selectedMedia = image break } else if let file = media as? TelegramMediaFile { - selectedMedia = file + if let cover = file.videoCover { + selectedMedia = cover + } else { + selectedMedia = file + } break } } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index d4b844a0eb..7be24715ce 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -3435,7 +3435,7 @@ public final class StoryItemSetContainerComponent: Component { elevatedLayout: false, position: .top, animateInAsReplacement: false, - blurred: true, + appearance: UndoOverlayController.Appearance(isBlurred: true), action: { _ in return false } ), nil) }))) @@ -3456,7 +3456,7 @@ public final class StoryItemSetContainerComponent: Component { elevatedLayout: false, position: .top, animateInAsReplacement: false, - blurred: true, + appearance: UndoOverlayController.Appearance(isBlurred: true), action: { _ in return false } ), nil) }))) @@ -3492,7 +3492,7 @@ public final class StoryItemSetContainerComponent: Component { elevatedLayout: false, position: .top, animateInAsReplacement: false, - blurred: true, + appearance: UndoOverlayController.Appearance(isBlurred: true), action: { [weak self] action in guard let self, let component = self.component else { return false @@ -3539,7 +3539,7 @@ public final class StoryItemSetContainerComponent: Component { elevatedLayout: false, position: .top, animateInAsReplacement: false, - blurred: true, + appearance: UndoOverlayController.Appearance(isBlurred: true), action: { [weak self] action in guard let self, let component = self.component else { return false @@ -4292,7 +4292,7 @@ public final class StoryItemSetContainerComponent: Component { storeAttributedTextInPasteboard(text) 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 = undoController component.controller()?.present(undoController, in: .current) @@ -4750,7 +4750,7 @@ public final class StoryItemSetContainerComponent: Component { controller?.replace(with: c) } 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) } } @@ -5025,7 +5025,7 @@ public final class StoryItemSetContainerComponent: Component { content: .info(title: nil, text: text, timeout: nil, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, - blurred: true, + appearance: UndoOverlayController.Appearance(isBlurred: true), action: { _ in return false } ) self.sendMessageContext.tooltipScreen = controller @@ -5471,7 +5471,7 @@ public final class StoryItemSetContainerComponent: Component { ), elevatedLayout: false, animateInAsReplacement: false, - blurred: true, + appearance: UndoOverlayController.Appearance(isBlurred: true), action: { [weak self] action in guard let self else { return false @@ -6196,7 +6196,7 @@ public final class StoryItemSetContainerComponent: Component { content: .info(title: nil, text: component.strings.Story_ToastRemovedFromProfileText, timeout: nil, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, - blurred: true, + appearance: UndoOverlayController.Appearance(isBlurred: true), action: { _ in return false } ), nil) } 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), elevatedLayout: false, animateInAsReplacement: false, - blurred: true, + appearance: UndoOverlayController.Appearance(isBlurred: true), action: { _ in return false } ), nil) } @@ -6263,7 +6263,7 @@ public final class StoryItemSetContainerComponent: Component { content: .linkCopied(title: nil, text: component.strings.Story_ToastLinkCopied), elevatedLayout: false, animateInAsReplacement: false, - blurred: true, + appearance: UndoOverlayController.Appearance(isBlurred: true), action: { _ in return false } ), 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), elevatedLayout: false, animateInAsReplacement: false, - blurred: true, + appearance: UndoOverlayController.Appearance(isBlurred: true), action: { _ in return false } ) } 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), elevatedLayout: false, animateInAsReplacement: false, - blurred: true, + appearance: UndoOverlayController.Appearance(isBlurred: true), action: { _ in return false } ), nil) } @@ -6476,7 +6476,7 @@ public final class StoryItemSetContainerComponent: Component { content: .linkCopied(title: nil, text: component.strings.Story_ToastLinkCopied), elevatedLayout: false, animateInAsReplacement: false, - blurred: true, + appearance: UndoOverlayController.Appearance(isBlurred: true), action: { _ in return false } ), 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), elevatedLayout: false, animateInAsReplacement: false, - blurred: true, + appearance: UndoOverlayController.Appearance(isBlurred: true), action: { _ in return false } ), nil) } 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), elevatedLayout: false, animateInAsReplacement: false, - blurred: true, + appearance: UndoOverlayController.Appearance(isBlurred: true), action: { _ in return false } ), nil) } @@ -6775,7 +6775,7 @@ public final class StoryItemSetContainerComponent: Component { content: .linkCopied(title: nil, text: component.strings.Story_ToastLinkCopied), elevatedLayout: false, animateInAsReplacement: false, - blurred: true, + appearance: UndoOverlayController.Appearance(isBlurred: true), action: { _ in return false } ), nil) } @@ -6819,7 +6819,7 @@ public final class StoryItemSetContainerComponent: Component { content: .info(title: title, text: text, timeout: nil, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, - blurred: true, + appearance: UndoOverlayController.Appearance(isBlurred: true), action: { _ in return false } ), in: .current) @@ -6995,44 +6995,6 @@ public final class StoryItemSetContainerComponent: Component { }, 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 -// ) -// } -// ) }))) } } diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Accessory Panels/Gift.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Input/Accessory Panels/Gift.imageset/Contents.json new file mode 100644 index 0000000000..977e80dac2 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Input/Accessory Panels/Gift.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "gift_24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Accessory Panels/Gift.imageset/gift_24.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Input/Accessory Panels/Gift.imageset/gift_24.pdf new file mode 100644 index 0000000000..19e0e9d869 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Input/Accessory Panels/Gift.imageset/gift_24.pdf differ diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 0ddf45bd4d..ccc2824c67 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -1199,7 +1199,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return true case .starGift, .starGiftUnique: 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) { let controller = self.context.sharedContext.makeStorySharingScreen(context: self.context, subject: .gift(uniqueGift), parentController: self) self.push(controller) diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift index 8d154d3a89..ebf8d73be5 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift @@ -1241,6 +1241,69 @@ extension ChatControllerImpl { controller.legacyCompletion = { signals, silently, scheduleTime, parameters, getAnimatedTransitionSource, sendCompletion in 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 + 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) } diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 4c93af4318..337eb32034 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -1123,7 +1123,15 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState if data.messageActions.options.contains(.sendGift) { 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 ?? "" sendGiftTitle = chatPresentationInterfaceState.strings.Conversation_ContextMenuSendGiftTo(peerName).string } else { diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 7aaf6bc9db..8e968b1bb7 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -2450,26 +2450,26 @@ public final class SharedAccountContextImpl: SharedAccountContext { } 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 } let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) - let title: String - let text: String - if currentTime > canExportDate { - title = presentationData.strings.Gift_Transfer_UpdateRequired_Title - text = presentationData.strings.Gift_Transfer_UpdateRequired_Text + if currentTime > canExportDate || "".isEmpty { + let alertController = giftWithdrawAlertController(context: context, gift: gift, commit: { + + }) + controller.present(alertController, in: .window(.root)) } else { let delta = canExportDate - currentTime let days: Int32 = Int32(ceil(Float(delta) / 86400.0)) let daysString = presentationData.strings.Gift_Transfer_UnlockPending_Text_Days(days) - title = presentationData.strings.Gift_Transfer_UnlockPending_Title - text = presentationData.strings.Gift_Transfer_UnlockPending_Text(daysString).string + let title = presentationData.strings.Gift_Transfer_UnlockPending_Title + let text = presentationData.strings.Gift_Transfer_UnlockPending_Text(daysString).string + let alertController = textAlertController(context: context, title: title, text: text, actions: [ + TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {}) + ]) + controller.present(alertController, in: .window(.root)) } - let alertController = textAlertController(context: context, title: title, text: text, actions: [ - TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {}) - ]) - controller.present(alertController, in: .window(.root)) } presentTransferAlertImpl = { [weak controller] peer in diff --git a/submodules/UndoUI/Sources/UndoOverlayController.swift b/submodules/UndoUI/Sources/UndoOverlayController.swift index acbe58cbcc..cae06633fc 100644 --- a/submodules/UndoUI/Sources/UndoOverlayController.swift +++ b/submodules/UndoUI/Sources/UndoOverlayController.swift @@ -82,6 +82,18 @@ public final class UndoOverlayController: ViewController { 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 public var content: UndoOverlayContent { didSet { @@ -94,7 +106,7 @@ public final class UndoOverlayController: ViewController { private var action: (UndoOverlayAction) -> Bool private let additionalView: (() -> UndoOverlayControllerAdditionalView?)? - private let blurred: Bool + private let appearance: Appearance? private var didPlayPresentationAnimation = false private var dismissed = false @@ -102,13 +114,13 @@ public final class UndoOverlayController: ViewController { 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.content = content self.elevatedLayout = elevatedLayout self.position = position self.animateInAsReplacement = animateInAsReplacement - self.blurred = blurred + self.appearance = appearance self.action = action self.additionalView = additionalView @@ -122,7 +134,7 @@ public final class UndoOverlayController: ViewController { } 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 }, dismiss: { [weak self] in self?.dismiss() diff --git a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift index 80204a9663..28f30c7066 100644 --- a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift +++ b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift @@ -59,7 +59,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { private let dismiss: () -> Void private var content: UndoOverlayContent - private let blurred: Bool + private let appearance: UndoOverlayController.Appearance? private let additionalView: UndoOverlayControllerAdditionalView? @@ -77,11 +77,11 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { 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.elevatedLayout = elevatedLayout self.placementPosition = placementPosition - self.blurred = blurred + self.appearance = appearance self.content = content self.additionalView = additionalView?() @@ -1544,7 +1544,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { self.undoButtonNode = HighlightTrackingButtonNode() 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 } else { self.panelNode.backgroundColor = .clear @@ -1904,7 +1904,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { let rightInset: CGFloat = 16.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 buttonTextSize = self.undoButtonTextNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude)) @@ -1964,7 +1964,9 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { case .top: break case .bottom: - if self.elevatedLayout { + if let bottomInset = self.appearance?.bottomInset { + insets.bottom += bottomInset + } else if self.elevatedLayout { insets.bottom += 49.0 } }