Various improvements

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

View File

@ -13095,6 +13095,9 @@ Sorry for the inconvenience.";
"Gift.Send.Remains_any" = "%@ left";
"Gift.Send.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";

View File

@ -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,

View File

@ -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<ChatListNotice?, NoError> in
#if DEBUG
var suggestions = suggestions
suggestions.insert(.setupPhoto, at: 0)
#endif
|> mapToSignal { suggestions, dismissedSuggestions, configuration, newSessionReviews, data, birthdays, starsSubscriptionsContext -> Signal<ChatListNotice?, NoError> in
let (accountPeer, birthday) = data
if let newSessionReview = newSessionReviews.first {

View File

@ -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)

View File

@ -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:

View File

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

View File

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

View File

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

View File

@ -1331,6 +1331,8 @@
CGSize dimensions = [TGMediaVideoConverter dimensionsFor:asset.originalSize adjustments:adjustments preset:preset];
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;

View File

@ -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<TGMediaEditableItem> *)item {
return [self coverImageSignalForIdentifier:item.uniqueIdentifier];
}
- (UIImage *)coverImageForItem:(NSObject<TGMediaEditableItem> *)item {
NSString *itemId = [TGMediaEditingContext _coverImageUriForItemId:item.uniqueIdentifier];
if (itemId == nil)
return nil;
return [_coverImageCache imageForKey:itemId attributes:NULL];
}
- (void)setCoverImage:(UIImage *)image forItem:(id<TGMediaEditableItem>)item
{
NSString *itemId = [TGMediaEditingContext _coverImageUriForItemId:item.uniqueIdentifier];
if (itemId == nil)
return;
[_coverImageCache setImage:image forKey:itemId attributes:NULL];
_coverImagePipe.sink([TGMediaImageUpdate imageUpdateWithItem:item representation:image]);
}
- (void)setFullSizeImage:(UIImage *)image forItem:(id<TGMediaEditableItem>)item
{
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

View File

@ -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<SDisposable> _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;
id<TGMediaSelectableItem>selectableItem = nil;
if ([_currentItem conformsToProtocol:@protocol(TGModernGallerySelectableItem)])
selectableItem = ((id<TGModernGallerySelectableItem>)_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<TGModernGalleryEditableItem> galleryEditableItem = (id<TGModernGalleryEditableItem>)_currentItem;
TGModernGalleryItemView *currentItemView = _currentItemView;
if ([currentItemView isKindOfClass:[TGMediaPickerGalleryVideoItemView class]]) {
id<TGMediaEditableItem> editableMediaItem = [galleryEditableItem editableMediaItem];
[_editingContext setCoverImage:[(TGMediaPickerGalleryVideoItemView *)currentItemView screenImage] forItem:editableMediaItem];
}
TGDispatchAfter(0.01, dispatch_get_main_queue(), ^{
[self coverEditorTransitionOut];
});
}
- (void)coverGalleryButtonPressed {
if (_currentItem == nil)
return;
id<TGModernGalleryEditableItem> galleryEditableItem = (id<TGModernGalleryEditableItem>)_currentItem;
if ([_currentItem conformsToProtocol:@protocol(TGModernGalleryEditableItem)])
{
id<TGMediaEditableItem> editableMediaItem = [galleryEditableItem editableMediaItem];
CGSize originalSize = CGSizeZero;
if ([editableMediaItem respondsToSelector:@selector(originalSize)])
originalSize = editableMediaItem.originalSize;
__weak TGMediaPickerGalleryInterfaceView *weakSelf = self;
_captionMixin.stickersContext.editCover(originalSize, ^(UIImage *cover){
__strong TGMediaPickerGalleryInterfaceView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[strongSelf->_editingContext setCoverImage:cover forItem:editableMediaItem];
[strongSelf coverEditorTransitionOut];
});
}
}
- (void)updateEditorButtonsForItem:(id<TGModernGalleryItem>)item animated:(bool)animated
{
__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];

View File

@ -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:^
{

View File

@ -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

View File

@ -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];

View File

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

View File

@ -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

View File

@ -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

View File

@ -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<UITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
[self setWrapperScale:0.85f animated:true];
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
[self setWrapperScale:1.0f animated:true];
}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesCancelled:touches withEvent:event];
[self setWrapperScale:1.0f animated:true];
}
@end

View File

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

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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))

View File

@ -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

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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

View File

@ -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<TGMediaSelectableItem>()
@ -1217,7 +1219,9 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
}
}, presentSchedulePicker: controller.presentSchedulePicker, presentTimerPicker: controller.presentTimerPicker, getCaptionPanelView: controller.getCaptionPanelView, present: { [weak self] c, a in
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, NoError>) = (presentationData, .single(presentationData))
let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false, makeEntityInputView: {
return nil
})
controller.requestController = { [weak controller] _, present in
let mediaPickerController = MediaPickerScreenImpl(
context: context,
updatedPresentationData: updatedPresentationData,
peer: nil,
threadTitle: nil,
chatLocation: nil,
bannedSendPhotos: nil,
bannedSendVideos: nil,
subject: .assets(nil, .cover)
)
mediaPickerController.customSelection = { controller, result in
if let result = result as? PHAsset {
controller.updateHiddenMediaId(result.localIdentifier)
if let transitionView = controller.transitionView(for: result.localIdentifier, snapshot: false) {
let transitionOut: (Bool?) -> (UIView, CGRect)? = { isNew in
if let isNew {
if isNew {
controller.updateHiddenMediaId(nil)
if let transitionView = controller.defaultTransitionView() {
return (transitionView, transitionView.bounds)
}
} else if let transitionView = controller.transitionView(for: result.localIdentifier, snapshot: false) {
return (transitionView, transitionView.bounds)
}
}
return nil
}
completion(result, transitionView, transitionView.bounds, controller.transitionImage(for: result.localIdentifier), false, transitionOut, { [weak controller] in
controller?.updateHiddenMediaId(nil)
})
}
}
}
mediaPickerController.openAvatarEditor = { [weak controller] in
completion(nil, nil, .zero, nil, false, { _ in return nil }, {
})
controller?.dismiss(animated: true)
}
present(mediaPickerController, mediaPickerController.mediaPickerContext)
}
controller.willDismiss = {
dismissed()
}
controller.navigationPresentation = .flatModal
controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
return controller
}
private class SelectedButtonNode: HighlightableButtonNode {
private let background = ASImageNode()
private let icon = ASImageNode()

View File

@ -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

View File

@ -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<TelegramAccountManagerTypes>) -> Signal<Int32, NoError> {
return accountManager.transaction { transaction -> Int32 in
if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.channelSendGiftTooltip())?.get(ApplicationSpecificCounterNotice.self) {
return value.value
} else {
return 0
}
}
}
public static func incrementChannelSendGiftTooltip(accountManager: AccountManager<TelegramAccountManagerTypes>, count: Int = 1) -> Signal<Int, NoError> {
return accountManager.transaction { transaction -> Int in
var currentValue: Int32 = 0
if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.channelSendGiftTooltip())?.get(ApplicationSpecificCounterNotice.self) {
currentValue = value.value
}
let previousValue = currentValue
currentValue += Int32(count)
if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) {
transaction.setNotice(ApplicationSpecificNoticeKeys.channelSendGiftTooltip(), entry)
}
return Int(previousValue)
}
}
}

View File

@ -1066,7 +1066,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
attributedString = mutableString
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)

View File

@ -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)

View File

@ -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

View File

@ -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 {

View File

@ -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
}

View File

@ -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)
)
]
}

View File

@ -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
}

View File

@ -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<Empty>
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<Empty>
if !component.context.isPremium {
buttonContent = AnyComponentWithIdentity(
id: AnyHashable("wear_locked"),
component: AnyComponent(
HStack([
AnyComponentWithIdentity(
id: AnyHashable("icon"),
component: AnyComponent(BundleIconComponent(name: "Chat/Stickers/Lock", tintColor: theme.list.itemCheckColors.foregroundColor))
),
AnyComponentWithIdentity(
id: AnyHashable("label"),
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Gift_Wear_Start, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center))))
)
], spacing: 3.0)
)
)
} else {
buttonContent = AnyComponentWithIdentity(
id: AnyHashable("wear"),
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Gift_Wear_Start, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center))))
)
}
buttonChild = button.update(
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

View File

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

View File

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

View File

@ -864,7 +864,7 @@ final class MediaEditorScreenComponent: Component {
case .storyEditor:
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
}

View File

@ -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
)

View File

@ -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 {

View File

@ -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<PeerInfoAvatarUploadStatus, NoError>) -> 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

View File

@ -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<PeerInfoAvatarUploadStatus, NoError>) -> UIView? = { _, _ in nil }) {
func openAvatarForEditing(mode: PeerInfoAvatarEditingMode = .generic, fromGallery: Bool = false, completion: @escaping (UIImage?) -> Void = { _ in }, completedWithUploadingImage: @escaping (UIImage, Signal<PeerInfoAvatarUploadStatus, NoError>) -> UIView? = { _, _ in nil }) {
guard let data = self.controllerNode.data, let peer = data.peer, mode != .generic || canEditPeerInfo(context: self.context, peer: peer, chatLocation: self.chatLocation, threadData: data.threadData) else {
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<PeerInfoAvatarUploadStatus>?) {
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<PeerInfoAvatarUploadStatus>?) {
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<UpdatePeerPhotoStatus, UploadPeerPhotoError>
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<TelegramMediaResource?, UploadPeerPhotoError>
// if uploadVideo {
// let path = NSTemporaryDirectory() + "\(Int64.random(in: Int64.min ... Int64.max)).mp4"
// let videoExport = MediaEditorVideoExport(
// postbox: context.account.postbox,
// subject: .image(image: image),
// configuration: configuration,
// outputPath: path
// )
//
// videoResource = Signal<TelegramMediaResource?, UploadPeerPhotoError> { [weak self] subscriber in
// let entityRenderer: LegacyPaintEntityRenderer? = adjustments.flatMap { adjustments in
// if let paintingData = adjustments.paintingData, paintingData.hasAnimation {
// return LegacyPaintEntityRenderer(postbox: account.postbox, adjustments: adjustments)
// } else {
// return nil
// }
// }
//
// let tempFile = EngineTempBox.shared.tempFile(fileName: "video.mp4")
// let uploadInterface = LegacyLiveUploadInterface(context: context)
// let signal: SSignal
// if let url = asset as? URL, url.absoluteString.hasSuffix(".jpg"), let data = try? Data(contentsOf: url, options: [.mappedRead]), let image = UIImage(data: data), let entityRenderer = entityRenderer {
// let durationSignal: SSignal = SSignal(generator: { subscriber in
// let disposable = (entityRenderer.duration()).start(next: { duration in
// subscriber.putNext(duration)
// subscriber.putCompletion()
// })
//
// return SBlockDisposable(block: {
// disposable.dispose()
// })
// })
// signal = durationSignal.map(toSignal: { duration -> SSignal in
// if let duration = duration as? Double {
// return TGMediaVideoConverter.renderUIImage(image, duration: duration, adjustments: adjustments, path: tempFile.path, watcher: nil, entityRenderer: entityRenderer)!
// } else {
// return SSignal.single(nil)
// }
// })
// } else if let asset = asset as? AVAsset {
// signal = TGMediaVideoConverter.convert(asset, adjustments: adjustments, path: tempFile.path, watcher: uploadInterface, entityRenderer: entityRenderer)!
// } else {
// signal = SSignal.complete()
// }
//
// let signalDisposable = signal.start(next: { next in
// if let result = next as? TGMediaVideoConversionResult {
// if let image = result.coverImage, let data = image.jpegData(compressionQuality: 0.7) {
// account.postbox.mediaBox.storeResourceData(photoResource.id, data: data)
// }
//
// if let timestamp = videoStartTimestamp {
// videoStartTimestamp = max(0.0, min(timestamp, result.duration - 0.05))
// }
//
// var value = stat()
// if stat(result.fileURL.path, &value) == 0 {
// if let data = try? Data(contentsOf: result.fileURL) {
// let resource: TelegramMediaResource
// if let liveUploadData = result.liveUploadData as? LegacyLiveUploadInterfaceResult {
// resource = LocalFileMediaResource(fileId: liveUploadData.id)
// } else {
// resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
// }
// account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true)
// subscriber.putNext(resource)
//
// EngineTempBox.shared.dispose(tempFile)
// }
// }
// subscriber.putCompletion()
// } else if let strongSelf = self, let progress = next as? NSNumber {
// Queue.mainQueue().async {
// strongSelf.controllerNode.state = strongSelf.controllerNode.state.withAvatarUploadProgress(.value(CGFloat(progress.floatValue * 0.45)))
// if let (layout, navigationHeight) = strongSelf.controllerNode.validLayout {
// strongSelf.controllerNode.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
// }
// }
// }
// }, error: { _ in
// }, completed: nil)
//
// let disposable = ActionDisposable {
// signalDisposable?.dispose()
// }
//
// return ActionDisposable {
// disposable.dispose()
// }
// }
// } else {
// videoResource = .single(nil)
// }
//
// var dismissStatus: (() -> Void)?
// if [.suggest, .fallback, .accept].contains(mode) {
// let statusController = OverlayStatusController(theme: self.presentationData.theme, type: .loading(cancelled: { [weak self] in
// self?.controllerNode.updateAvatarDisposable.set(nil)
// dismissStatus?()
// }))
// dismissStatus = { [weak statusController] in
// statusController?.dismiss()
// }
// if let topController = self.navigationController?.topViewController as? ViewController {
// topController.presentInGlobalOverlay(statusController)
// } else if let topController = self.parentController?.topViewController as? ViewController {
// topController.presentInGlobalOverlay(statusController)
// } else {
// self.presentInGlobalOverlay(statusController)
// }
// }
//
// let peerId = self.peerId
// let isSettings = self.isSettings
// let isMyProfile = self.isMyProfile
// self.controllerNode.updateAvatarDisposable.set((videoResource
// |> mapToSignal { videoResource -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> in
// if isSettings || isMyProfile {
// if case .fallback = mode {
// return context.engine.accountData.updateFallbackPhoto(resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in
// return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations)
// })
// } else {
// return context.engine.accountData.updateAccountPhoto(resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in
// return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations)
// })
// }
// } else if case .custom = mode {
// return context.engine.contacts.updateContactPhoto(peerId: peerId, resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mode: .custom, mapResourceToAvatarSizes: { resource, representations in
// return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations)
// })
// } else if case .suggest = mode {
// return context.engine.contacts.updateContactPhoto(peerId: peerId, resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mode: .suggest, mapResourceToAvatarSizes: { resource, representations in
// return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations)
// })
// } else {
// return context.engine.peers.updatePeerPhoto(peerId: peerId, photo: context.engine.peers.uploadedPeerPhoto(resource: photoResource), video: videoResource.flatMap { context.engine.peers.uploadedPeerVideo(resource: $0) |> map(Optional.init) }, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in
// return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations)
// })
// }
// }
// |> deliverOnMainQueue).startStrict(next: { [weak self] result in
// guard let strongSelf = self else {
// return
// }
// switch result {
// case .complete:
// strongSelf.controllerNode.state = strongSelf.controllerNode.state.withUpdatingAvatar(nil).withAvatarUploadProgress(nil)
// case let .progress(value):
// strongSelf.controllerNode.state = strongSelf.controllerNode.state.withAvatarUploadProgress(.value(CGFloat(0.45 + value * 0.55)))
// }
// if let (layout, navigationHeight) = strongSelf.controllerNode.validLayout {
// strongSelf.controllerNode.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
// }
//
// if case .complete = result {
// dismissStatus?()
//
// let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.peerId))
// |> deliverOnMainQueue).startStandalone(next: { [weak self] peer in
// if let strongSelf = self, let peer {
// switch mode {
// case .fallback:
// (strongSelf.parentController?.topViewController as? ViewController)?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .image(image: image, title: nil, text: strongSelf.presentationData.strings.Privacy_ProfilePhoto_PublicVideoSuccess, round: true, undoText: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
// case .custom:
// strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, title: nil, text: strongSelf.presentationData.strings.UserInfo_SetCustomPhoto_SuccessVideoText(peer.compactDisplayTitle).string, action: nil, duration: 5), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
//
// let _ = (strongSelf.context.peerChannelMemberCategoriesContextsManager.profilePhotos(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, peerId: strongSelf.peerId, fetch: peerInfoProfilePhotos(context: strongSelf.context, peerId: strongSelf.peerId)) |> ignoreValues).startStandalone()
// case .suggest:
// if let navigationController = (strongSelf.navigationController as? NavigationController) {
// strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), keepStack: .default, completion: { _ in
// }))
// }
// case .accept:
// (strongSelf.parentController?.topViewController as? ViewController)?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .image(image: image, title: strongSelf.presentationData.strings.Conversation_SuggestedVideoSuccess, text: strongSelf.presentationData.strings.Conversation_SuggestedVideoSuccessText, round: true, undoText: nil), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] action in
// if case .info = action {
// self?.parentController?.openSettings()
// }
// return false
// }), in: .current)
// default:
// break
// }
// }
// })
// }
// }))
// }
public func updateProfileVideo(_ image: UIImage, asset: Any?, adjustments: TGVideoEditAdjustments?, mode: PeerInfoAvatarEditingMode) {
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 {

View File

@ -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",

View File

@ -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<Empty>?
private var unlockButton: SolidRoundedButtonNode?
private var footerText: ComponentView<Empty>?
private var panelBackground: NavigationBackgroundNode?
private var panelSeparator: ASDisplayNode?
private var panelButton: SolidRoundedButtonNode?
private var panelCheck: ComponentView<Empty>?
private var currentParams: (size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, presentationData: PresentationData)?
@ -68,12 +72,13 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
private var starsItems: [AnyHashable: ComponentView<Empty>] = [:]
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<Empty>
let unlockBackground: NavigationBackgroundNode
let unlockSeparator: ASDisplayNode
let unlockButton: SolidRoundedButtonNode
if let current = self.unlockText {
unlockText = current
} else {
unlockText = ComponentView<Empty>()
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<Empty>
if let current = self.panelCheck {
panelCheck = current
} else {
panelCheck = ComponentView<Empty>()
self.panelCheck = panelCheck
}
let checkTheme = CheckComponent.Theme(
backgroundColor: presentationData.theme.list.itemCheckColors.fillColor,
strokeColor: presentationData.theme.list.itemCheckColors.foregroundColor,
borderColor: presentationData.theme.list.itemCheckColors.strokeColor,
overlayBorder: false,
hasInset: false,
hasShadow: false
)
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<Empty>
if let current = self.footerText {
footerText = current
} else {
footerText = ComponentView<Empty>()
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) {

View File

@ -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
}
}

View File

@ -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
// )
// }
// )
})))
}
}

View File

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

View File

@ -1199,7 +1199,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return true
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)

View File

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

View File

@ -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 {

View File

@ -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

View File

@ -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()

View File

@ -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
}
}