diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 6b90a90d48..c3807a7048 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -166,6 +166,7 @@ public enum ResolvedUrl { case cancelAccountReset(phone: String, hash: String) case share(url: String?, text: String?, to: String?) case wallpaper(WallpaperUrlParameter) + case theme(String) } public enum NavigateToChatKeepStack { diff --git a/submodules/AlertUI/Sources/ThemedTextAlertController.swift b/submodules/AlertUI/Sources/ThemedTextAlertController.swift index 5779ac6b09..26f21b0fbd 100644 --- a/submodules/AlertUI/Sources/ThemedTextAlertController.swift +++ b/submodules/AlertUI/Sources/ThemedTextAlertController.swift @@ -6,7 +6,7 @@ import AccountContext public func textAlertController(context: AccountContext, title: String?, text: String, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout = .horizontal) -> AlertController { let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let controller = standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: title, text: text, actions: actions) + let controller = standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: title, text: text, actions: actions, actionLayout: actionLayout) let presentationDataDisposable = context.sharedContext.presentationData.start(next: { [weak controller] presentationData in controller?.theme = AlertControllerTheme(presentationTheme: presentationData.theme) }) diff --git a/submodules/Display/Display/NavigationController.swift b/submodules/Display/Display/NavigationController.swift index 616e7dc6fa..e5245c5089 100644 --- a/submodules/Display/Display/NavigationController.swift +++ b/submodules/Display/Display/NavigationController.swift @@ -366,44 +366,7 @@ open class NavigationController: UINavigationController, ContainableController, } } - if let _ = self.controllerView.emptyDetailView { -// transition.updateFrame(view: navigationBackgroundView, frame: navigationBackgroundFrame) -// transition.updateFrame(view: navigationSeparatorView, frame: CGRect(origin: CGPoint(x: navigationBackgroundFrame.minX, y: navigationBackgroundFrame.maxY), size: CGSize(width: navigationBackgroundFrame.width, height: UIScreenPixel))) -// if let image = emptyDetailView.image { -// transition.updateFrame(view: emptyDetailView, frame: CGRect(origin: CGPoint(x: masterData.0.maxX + floor((lastControllerFrameAndLayout.0.size.width - image.size.width) / 2.0), y: floor((lastControllerFrameAndLayout.0.size.height - image.size.height) / 2.0)), size: image.size)) -// } - } else { - // let navigationBackgroundView = UIView() -// navigationBackgroundView.backgroundColor = self.theme.navigationBar.c -// let navigationSeparatorView = UIView() -// navigationSeparatorView.backgroundColor = self.theme.navigationBar.separatorColor -// let emptyDetailView = UIImageView() -// emptyDetailView.image = self.theme.emptyDetailIcon -// emptyDetailView.alpha = 0.0 -// -// self.controllerView.navigationBackgroundView = navigationBackgroundView -// self.controllerView.navigationSeparatorView = navigationSeparatorView -// self.controllerView.emptyDetailView = emptyDetailView -// -// self.controllerView.insertSubview(navigationBackgroundView, at: 0) -// self.controllerView.insertSubview(navigationSeparatorView, at: 1) - // self.controllerView.insertSubview(emptyDetailView, at: 0) - -// navigationBackgroundView.frame = navigationBackgroundFrame -// navigationSeparatorView.frame = CGRect(origin: CGPoint(x: navigationBackgroundFrame.minX, y: navigationBackgroundFrame.maxY), size: CGSize(width: navigationBackgroundFrame.width, height: UIScreenPixel)) -// -// transition.animatePositionAdditive(layer: navigationBackgroundView.layer, offset: CGPoint(x: navigationBackgroundFrame.width, y: 0.0)) -// transition.animatePositionAdditive(layer: navigationSeparatorView.layer, offset: CGPoint(x: navigationBackgroundFrame.width, y: 0.0)) - -// if let image = emptyDetailView.image { -// emptyDetailView.frame = CGRect(origin: CGPoint(x: masterData.0.maxX + floor((lastControllerFrameAndLayout.0.size.width - image.size.width) / 2.0), y: floor((lastControllerFrameAndLayout.0.size.height - image.size.height) / 2.0)), size: image.size) -// } -// -// transition.updateAlpha(layer: emptyDetailView.layer, alpha: 1.0) - } - if let blackout = self.masterDetailsBlackout { - let blackoutFrame: CGRect switch blackout { case .details: @@ -493,15 +456,15 @@ open class NavigationController: UINavigationController, ContainableController, } else if case .masterDetail = layoutConfiguration, i == 1 { controller.navigationBar?.previousItem = .close } else { - controller.navigationBar?.previousItem = .item(viewControllers[i - 1].navigationItem) + controller.navigationBar?.previousItem = .item(self.viewControllers[i - 1].navigationItem) } if i < self._viewControllers.count - 1 { - controller.updateNavigationCustomData((viewControllers[i + 1] as? ViewController)?.customData, progress: 1.0, transition: transition) + controller.updateNavigationCustomData((self.viewControllers[i + 1] as? ViewController)?.customData, progress: 1.0, transition: transition) } else { controller.updateNavigationCustomData(nil, progress: 1.0, transition: transition) } } - viewControllers[i].navigation_setNavigationController(self) + self.viewControllers[i].navigation_setNavigationController(self) if i == 0, let (_, layout) = firstControllerFrameAndLayout { controllersAndFrames.append((true, self._viewControllers[i], layout)) @@ -526,7 +489,15 @@ open class NavigationController: UINavigationController, ContainableController, frame = lastControllerFrameAndLayout.0 } let isAppearing = record.controller.view.superview == nil - (record.controller as? ViewController)?.containerLayoutUpdated(layout, transition: isAppearing ? .immediate : transition) + if let controller = record.controller as? ViewController { + let updatedLayout: ContainerViewLayout + if previousControllers.count == self.viewControllers.count + 1, previousControllers[previousControllers.count - 2].controller === controller { + updatedLayout = layout.withUpdatedInputHeight(controller.hasActiveInput ? layout.inputHeight : nil) + } else { + updatedLayout = layout + } + controller.containerLayoutUpdated(updatedLayout, transition: isAppearing ? .immediate : transition) + } if isAppearing { if isMaster { appearingMasterController = record diff --git a/submodules/Display/Display/UIKitUtils.swift b/submodules/Display/Display/UIKitUtils.swift index 3c9f5ac627..9faf783388 100644 --- a/submodules/Display/Display/UIKitUtils.swift +++ b/submodules/Display/Display/UIKitUtils.swift @@ -83,9 +83,13 @@ public extension UIColor { var red: CGFloat = 0.0 var green: CGFloat = 0.0 var blue: CGFloat = 0.0 - self.getRed(&red, green: &green, blue: &blue, alpha: nil) - - return (UInt32(red * 255.0) << 16) | (UInt32(green * 255.0) << 8) | (UInt32(blue * 255.0)) + if self.getRed(&red, green: &green, blue: &blue, alpha: nil) { + return (UInt32(max(0.0, red) * 255.0) << 16) | (UInt32(max(0.0, green) * 255.0) << 8) | (UInt32(max(0.0, blue) * 255.0)) + } else if self.getWhite(&red, alpha: nil) { + return (UInt32(max(0.0, red) * 255.0) << 16) | (UInt32(max(0.0, red) * 255.0) << 8) | (UInt32(max(0.0, red) * 255.0)) + } else { + return 0 + } } var argb: UInt32 { @@ -93,9 +97,13 @@ public extension UIColor { var green: CGFloat = 0.0 var blue: CGFloat = 0.0 var alpha: CGFloat = 0.0 - self.getRed(&red, green: &green, blue: &blue, alpha: &alpha) - - return (UInt32(alpha * 255.0) << 24) | (UInt32(red * 255.0) << 16) | (UInt32(green * 255.0) << 8) | (UInt32(blue * 255.0)) + if self.getRed(&red, green: &green, blue: &blue, alpha: &alpha) { + return (UInt32(alpha * 255.0) << 24) | (UInt32(max(0.0, red) * 255.0) << 16) | (UInt32(max(0.0, green) * 255.0) << 8) | (UInt32(max(0.0, blue) * 255.0)) + } else if self.getWhite(&red, alpha: &alpha) { + return (UInt32(max(0.0, alpha) * 255.0) << 24) | (UInt32(max(0.0, red) * 255.0) << 16) | (UInt32(max(0.0, red) * 255.0) << 8) | (UInt32(max(0.0, red) * 255.0)) + } else { + return 0 + } } var hsv: (CGFloat, CGFloat, CGFloat) { diff --git a/submodules/ItemListAvatarAndNameInfoItem/Sources/ItemListAvatarAndNameItem.swift b/submodules/ItemListAvatarAndNameInfoItem/Sources/ItemListAvatarAndNameItem.swift index 432d266f93..e16ca21c30 100644 --- a/submodules/ItemListAvatarAndNameInfoItem/Sources/ItemListAvatarAndNameItem.swift +++ b/submodules/ItemListAvatarAndNameInfoItem/Sources/ItemListAvatarAndNameItem.swift @@ -927,7 +927,7 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo if strongSelf.arrowNode.supernode == nil { strongSelf.addSubnode(strongSelf.arrowNode) } - strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 15.0 - arrowImage.size.width, y: floor((layout.contentSize.height - arrowImage.size.height) / 2.0)), size: arrowImage.size) + strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 7.0 - arrowImage.size.width, y: floor((layout.contentSize.height - arrowImage.size.height) / 2.0)), size: arrowImage.size) } else if strongSelf.arrowNode.supernode != nil { strongSelf.arrowNode.removeFromSupernode() } diff --git a/submodules/ItemListPeerActionItem/Sources/ItemListPeerActionItem.swift b/submodules/ItemListPeerActionItem/Sources/ItemListPeerActionItem.swift index 4d763c81a9..e5b497cfdd 100644 --- a/submodules/ItemListPeerActionItem/Sources/ItemListPeerActionItem.swift +++ b/submodules/ItemListPeerActionItem/Sources/ItemListPeerActionItem.swift @@ -196,7 +196,7 @@ class ItemListPeerActionItemNode: ListViewItemNode { strongSelf.iconNode.image = item.icon if let image = item.icon { - transition.updateFrame(node: strongSelf.iconNode, frame: CGRect(origin: CGPoint(x: params.leftInset + editingOffset + floor((leftInset - params.leftInset - image.size.width) / 2.0), y: floor((contentSize.height - image.size.height) / 2.0)), size: image.size)) + transition.updateFrame(node: strongSelf.iconNode, frame: CGRect(origin: CGPoint(x: params.leftInset + editingOffset + floor((leftInset - params.leftInset - image.size.width) / 2.0) + 3.0, y: floor((contentSize.height - image.size.height) / 2.0)), size: image.size)) } if strongSelf.backgroundNode.supernode == nil { diff --git a/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift b/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift index a087d6a945..f66a5dfd24 100644 --- a/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift +++ b/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift @@ -859,7 +859,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo if let labelArrowNode = self.labelArrowNode { if let image = labelArrowNode.image { - let labelArrowNodeFrame = CGRect(origin: CGPoint(x: revealOffset + params.width - rightLabelInset - image.size.width, y: labelArrowNode.frame.minY), size: image.size) + let labelArrowNodeFrame = CGRect(origin: CGPoint(x: revealOffset + params.width - rightLabelInset - image.size.width + 8.0, y: labelArrowNode.frame.minY), size: image.size) transition.updateFrame(node: labelArrowNode, frame: labelArrowNodeFrame) rightLabelInset += 19.0 } diff --git a/submodules/ItemListUI/Sources/ItemListController.swift b/submodules/ItemListUI/Sources/ItemListController.swift index 20edf03b49..81344e6c05 100644 --- a/submodules/ItemListUI/Sources/ItemListController.swift +++ b/submodules/ItemListUI/Sources/ItemListController.swift @@ -25,6 +25,7 @@ public enum ItemListNavigationButtonStyle { public enum ItemListNavigationButtonContentIcon { case search case add + case action } public enum ItemListNavigationButtonContent: Equatable { @@ -309,6 +310,8 @@ open class ItemListController: ViewController, KeyShor image = PresentationResourcesRootController.navigationCompactSearchIcon(controllerState.theme) case .add: image = PresentationResourcesRootController.navigationAddIcon(controllerState.theme) + case .action: + image = PresentationResourcesRootController.navigationShareIcon(controllerState.theme) } item = UIBarButtonItem(image: image, style: leftNavigationButton.style.barButtonItemStyle, target: strongSelf, action: #selector(strongSelf.leftNavigationButtonPressed)) } @@ -364,6 +367,8 @@ open class ItemListController: ViewController, KeyShor image = PresentationResourcesRootController.navigationCompactSearchIcon(controllerState.theme) case .add: image = PresentationResourcesRootController.navigationAddIcon(controllerState.theme) + case .action: + image = PresentationResourcesRootController.navigationShareIcon(controllerState.theme) } item = UIBarButtonItem(image: image, style: style.barButtonItemStyle, target: strongSelf, action: action) } diff --git a/submodules/ItemListUI/Sources/Items/ItemListDisclosureItem.swift b/submodules/ItemListUI/Sources/Items/ItemListDisclosureItem.swift index bfcda62617..6986cf2537 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListDisclosureItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListDisclosureItem.swift @@ -431,7 +431,7 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode { } if let arrowImage = strongSelf.arrowNode.image { - strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 15.0 - arrowImage.size.width, y: floorToScreenPixels((height - arrowImage.size.height) / 2.0)), size: arrowImage.size) + strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 7.0 - arrowImage.size.width, y: floorToScreenPixels((height - arrowImage.size.height) / 2.0)), size: arrowImage.size) } switch item.disclosureStyle { diff --git a/submodules/LegacyComponents/LegacyComponents/TGAttachmentCarouselItemView.h b/submodules/LegacyComponents/LegacyComponents/TGAttachmentCarouselItemView.h index 3f218c9302..da42f7f657 100644 --- a/submodules/LegacyComponents/LegacyComponents/TGAttachmentCarouselItemView.h +++ b/submodules/LegacyComponents/LegacyComponents/TGAttachmentCarouselItemView.h @@ -32,12 +32,13 @@ @property (nonatomic) bool disableStickers; @property (nonatomic) bool hasSilentPosting; @property (nonatomic) bool hasSchedule; +@property (nonatomic, copy) void (^presentScheduleController)(void (^)(int32_t)); @property (nonatomic, strong) NSArray *underlyingViews; @property (nonatomic, assign) bool openEditor; @property (nonatomic, copy) void (^cameraPressed)(TGAttachmentCameraView *cameraView); -@property (nonatomic, copy) void (^sendPressed)(TGMediaAsset *currentItem, bool asFiles, TGMediaPickerGalleryCompletionMode mode); +@property (nonatomic, copy) void (^sendPressed)(TGMediaAsset *currentItem, bool asFiles, bool silentPosting, int32_t scheduleTime); @property (nonatomic, copy) void (^avatarCompletionBlock)(UIImage *image); @property (nonatomic, copy) void (^editorOpened)(void); diff --git a/submodules/LegacyComponents/LegacyComponents/TGAttachmentCarouselItemView.m b/submodules/LegacyComponents/LegacyComponents/TGAttachmentCarouselItemView.m index 8f78600fb8..17318f32fd 100644 --- a/submodules/LegacyComponents/LegacyComponents/TGAttachmentCarouselItemView.m +++ b/submodules/LegacyComponents/LegacyComponents/TGAttachmentCarouselItemView.m @@ -264,7 +264,7 @@ const NSUInteger TGAttachmentDisplayedAssetLimit = 500; { if (strongSelf->_selectionContext.allowGrouping) [[NSUserDefaults standardUserDefaults] setObject:@(!strongSelf->_selectionContext.grouping) forKey:@"TG_mediaGroupingDisabled_v0"]; - strongSelf.sendPressed(nil, false, false); + strongSelf.sendPressed(nil, false, false, 0); } }]; [_sendMediaItemView setHidden:true animated:false]; @@ -276,7 +276,7 @@ const NSUInteger TGAttachmentDisplayedAssetLimit = 500; { __strong TGAttachmentCarouselItemView *strongSelf = weakSelf; if (strongSelf != nil && strongSelf.sendPressed != nil) - strongSelf.sendPressed(nil, true, false); + strongSelf.sendPressed(nil, true, false, 0); }]; _sendFileItemView.requiresDivider = false; [_sendFileItemView setHidden:true animated:false]; @@ -774,14 +774,14 @@ const NSUInteger TGAttachmentDisplayedAssetLimit = 500; strongSelf->_galleryMixin = nil; }; - mixin.completeWithItem = ^(TGMediaPickerGalleryItem *item, TGMediaPickerGalleryCompletionMode mode) + mixin.completeWithItem = ^(TGMediaPickerGalleryItem *item, bool silentPosting, int32_t scheduleTime) { __strong TGAttachmentCarouselItemView *strongSelf = weakSelf; if (strongSelf != nil && strongSelf.sendPressed != nil) { if (strongSelf->_selectionContext.allowGrouping) [[NSUserDefaults standardUserDefaults] setObject:@(!strongSelf->_selectionContext.grouping) forKey:@"TG_mediaGroupingDisabled_v0"]; - strongSelf.sendPressed(item.asset, strongSelf.asFile, mode); + strongSelf.sendPressed(item.asset, strongSelf.asFile, silentPosting, scheduleTime); } }; @@ -802,7 +802,7 @@ const NSUInteger TGAttachmentDisplayedAssetLimit = 500; thumbnailImage = cell.imageView.image; TGMediaPickerModernGalleryMixin *mixin = [[TGMediaPickerModernGalleryMixin alloc] initWithContext:_context item:asset fetchResult:_fetchResult parentController:self.parentController thumbnailImage:thumbnailImage selectionContext:_selectionContext editingContext:_editingContext suggestionContext:self.suggestionContext hasCaptions:(_allowCaptions && !_forProfilePhoto) allowCaptionEntities:self.allowCaptionEntities hasTimer:self.hasTimer onlyCrop:self.onlyCrop inhibitDocumentCaptions:_inhibitDocumentCaptions inhibitMute:self.inhibitMute asFile:self.asFile itemsLimit:TGAttachmentDisplayedAssetLimit recipientName:self.recipientName hasSilentPosting:self.hasSilentPosting hasSchedule:self.hasSchedule]; - + mixin.presentScheduleController = self.presentScheduleController; __weak TGAttachmentCarouselItemView *weakSelf = self; mixin.thumbnailSignalForItem = ^SSignal *(id item) { diff --git a/submodules/LegacyComponents/LegacyComponents/TGMediaAssetsController.h b/submodules/LegacyComponents/LegacyComponents/TGMediaAssetsController.h index 2264bdf0f9..d60bdba4ba 100644 --- a/submodules/LegacyComponents/LegacyComponents/TGMediaAssetsController.h +++ b/submodules/LegacyComponents/LegacyComponents/TGMediaAssetsController.h @@ -59,6 +59,7 @@ typedef enum @property (nonatomic, assign) bool inhibitMute; @property (nonatomic, assign) bool hasSilentPosting; @property (nonatomic, assign) bool hasSchedule; +@property (nonatomic, copy) void (^presentScheduleController)(void (^)(int32_t)); @property (nonatomic, assign) bool liveVideoUploadEnabled; @property (nonatomic, assign) bool shouldShowFileTipIfNeeded; @@ -67,7 +68,7 @@ typedef enum @property (nonatomic, copy) NSDictionary *(^descriptionGenerator)(id, NSString *, NSArray *, NSString *); @property (nonatomic, copy) void (^avatarCompletionBlock)(UIImage *image); -@property (nonatomic, copy) void (^completionBlock)(NSArray *signals, TGMediaPickerGalleryCompletionMode mode); +@property (nonatomic, copy) void (^completionBlock)(NSArray *signals, bool silentPosting, int32_t scheduleTime); @property (nonatomic, copy) void (^singleCompletionBlock)(id item, TGMediaEditingContext *editingContext); @property (nonatomic, copy) void (^dismissalBlock)(void); @property (nonatomic, copy) void (^selectionBlock)(TGMediaAsset *asset, UIImage *); @@ -85,7 +86,7 @@ typedef enum - (NSArray *)resultSignalsWithCurrentItem:(TGMediaAsset *)currentItem descriptionGenerator:(id (^)(id, NSString *, NSArray *, NSString *))descriptionGenerator; - (void)completeWithAvatarImage:(UIImage *)image; -- (void)completeWithCurrentItem:(TGMediaAsset *)currentItem mode:(TGMediaPickerGalleryCompletionMode)mode; +- (void)completeWithCurrentItem:(TGMediaAsset *)currentItem silentPosting:(bool)silentPosting scheduleTime:(int32_t)scheduleTime; - (void)dismiss; diff --git a/submodules/LegacyComponents/LegacyComponents/TGMediaAssetsController.m b/submodules/LegacyComponents/LegacyComponents/TGMediaAssetsController.m index 36417330bc..a542031965 100644 --- a/submodules/LegacyComponents/LegacyComponents/TGMediaAssetsController.m +++ b/submodules/LegacyComponents/LegacyComponents/TGMediaAssetsController.m @@ -122,6 +122,7 @@ pickerController.onlyCrop = strongController.onlyCrop; pickerController.hasSilentPosting = strongController.hasSilentPosting; pickerController.hasSchedule = strongController.hasSchedule; + pickerController.presentScheduleController = strongController.presentScheduleController; [strongController pushViewController:pickerController animated:true]; }; [groupsController loadViewIfNeeded]; @@ -213,6 +214,11 @@ self.pickerController.hasSchedule = hasSchedule; } +- (void)setPresentScheduleController:(void (^)(void (^)(int32_t)))presentScheduleController { + _presentScheduleController = [presentScheduleController copy]; + self.pickerController.presentScheduleController = presentScheduleController; +} + - (void)setOnlyCrop:(bool)onlyCrop { _onlyCrop = onlyCrop; @@ -457,7 +463,7 @@ { __strong TGMediaAssetsController *strongSelf = weakSelf; if (strongSelf != nil) - [strongSelf completeWithCurrentItem:nil mode:TGMediaPickerGalleryCompletionModeGeneric]; + [strongSelf completeWithCurrentItem:nil silentPosting:false scheduleTime:0]; }; } @@ -538,12 +544,12 @@ self.avatarCompletionBlock(image); } -- (void)completeWithCurrentItem:(TGMediaAsset *)currentItem mode:(TGMediaPickerGalleryCompletionMode)mode +- (void)completeWithCurrentItem:(TGMediaAsset *)currentItem silentPosting:(bool)silentPosting scheduleTime:(int32_t)scheduleTime { if (self.completionBlock != nil) { NSArray *signals = [self resultSignalsWithCurrentItem:currentItem descriptionGenerator:self.descriptionGenerator]; - self.completionBlock(signals, mode); + self.completionBlock(signals, silentPosting, scheduleTime); } else if (self.singleCompletionBlock != nil) { diff --git a/submodules/LegacyComponents/LegacyComponents/TGMediaAssetsPickerController.m b/submodules/LegacyComponents/LegacyComponents/TGMediaAssetsPickerController.m index acad64e531..2eaa4c328c 100644 --- a/submodules/LegacyComponents/LegacyComponents/TGMediaAssetsPickerController.m +++ b/submodules/LegacyComponents/LegacyComponents/TGMediaAssetsPickerController.m @@ -311,13 +311,13 @@ strongSelf->_galleryMixin = nil; }; - mixin.completeWithItem = ^(TGMediaPickerGalleryItem *item, TGMediaPickerGalleryCompletionMode mode) + mixin.completeWithItem = ^(TGMediaPickerGalleryItem *item, bool silentPosting, int32_t scheduleTime) { __strong TGMediaAssetsPickerController *strongSelf = weakSelf; if (strongSelf == nil) return; - [(TGMediaAssetsController *)strongSelf.navigationController completeWithCurrentItem:item.asset mode:mode]; + [(TGMediaAssetsController *)strongSelf.navigationController completeWithCurrentItem:item.asset silentPosting:silentPosting scheduleTime:scheduleTime]; }; } @@ -341,7 +341,7 @@ bool asFile = (_intent == TGMediaAssetsControllerSendFileIntent); TGMediaPickerModernGalleryMixin *mixin = [self _galleryMixinForContext:_context item:asset thumbnailImage:thumbnailImage selectionContext:self.selectionContext editingContext:self.editingContext suggestionContext:self.suggestionContext hasCaptions:self.captionsEnabled allowCaptionEntities:self.allowCaptionEntities inhibitDocumentCaptions:self.inhibitDocumentCaptions asFile:asFile]; - + mixin.presentScheduleController = self.presentScheduleController; __weak TGMediaAssetsPickerController *weakSelf = self; mixin.thumbnailSignalForItem = ^SSignal *(id item) { diff --git a/submodules/LegacyComponents/LegacyComponents/TGMediaAssetsUtils.h b/submodules/LegacyComponents/LegacyComponents/TGMediaAssetsUtils.h index 993fedd8a3..b4d5ed7c8e 100644 --- a/submodules/LegacyComponents/LegacyComponents/TGMediaAssetsUtils.h +++ b/submodules/LegacyComponents/LegacyComponents/TGMediaAssetsUtils.h @@ -4,12 +4,6 @@ @class TGMediaAsset; @class TGMediaSelectionContext; -typedef NS_ENUM(NSUInteger, TGMediaPickerGalleryCompletionMode) { - TGMediaPickerGalleryCompletionModeGeneric, - TGMediaPickerGalleryCompletionModeSilent, - TGMediaPickerGalleryCompletionModeSchedule -}; - @interface TGMediaAssetsPreheatMixin : NSObject @property (nonatomic, copy) NSInteger (^assetCount)(void); diff --git a/submodules/LegacyComponents/LegacyComponents/TGMediaPickerController.h b/submodules/LegacyComponents/LegacyComponents/TGMediaPickerController.h index cf734aa4f2..b78e565560 100644 --- a/submodules/LegacyComponents/LegacyComponents/TGMediaPickerController.h +++ b/submodules/LegacyComponents/LegacyComponents/TGMediaPickerController.h @@ -29,6 +29,7 @@ @property (nonatomic, strong) NSString *recipientName; @property (nonatomic, assign) bool hasSilentPosting; @property (nonatomic, assign) bool hasSchedule; +@property (nonatomic, copy) void (^presentScheduleController)(void (^)(int32_t)); @property (nonatomic, strong) TGMediaAssetsPallete *pallete; diff --git a/submodules/LegacyComponents/LegacyComponents/TGMediaPickerModernGalleryMixin.h b/submodules/LegacyComponents/LegacyComponents/TGMediaPickerModernGalleryMixin.h index 8e4401c5c8..d3deb03362 100644 --- a/submodules/LegacyComponents/LegacyComponents/TGMediaPickerModernGalleryMixin.h +++ b/submodules/LegacyComponents/LegacyComponents/TGMediaPickerModernGalleryMixin.h @@ -22,11 +22,13 @@ @property (nonatomic, copy) void (^didTransitionOut)(); @property (nonatomic, copy) UIView *(^referenceViewForItem)(TGMediaPickerGalleryItem *); -@property (nonatomic, copy) void (^completeWithItem)(TGMediaPickerGalleryItem *item, TGMediaPickerGalleryCompletionMode mode); +@property (nonatomic, copy) void (^completeWithItem)(TGMediaPickerGalleryItem *item, bool silentPosting, int32_t scheduleTime); @property (nonatomic, copy) void (^editorOpened)(void); @property (nonatomic, copy) void (^editorClosed)(void); +@property (nonatomic, copy) void (^presentScheduleController)(void (^)(int32_t)); + - (instancetype)initWithContext:(id)context item:(id)item fetchResult:(TGMediaAssetFetchResult *)fetchResult parentController:(TGViewController *)parentController thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext suggestionContext:(TGSuggestionContext *)suggestionContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions inhibitMute:(bool)inhibitMute asFile:(bool)asFile itemsLimit:(NSUInteger)itemsLimit recipientName:(NSString *)recipientName hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule; - (instancetype)initWithContext:(id)context item:(id)item momentList:(TGMediaAssetMomentList *)momentList parentController:(TGViewController *)parentController thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext suggestionContext:(TGSuggestionContext *)suggestionContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions inhibitMute:(bool)inhibitMute asFile:(bool)asFile itemsLimit:(NSUInteger)itemsLimit hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule; diff --git a/submodules/LegacyComponents/LegacyComponents/TGMediaPickerModernGalleryMixin.m b/submodules/LegacyComponents/LegacyComponents/TGMediaPickerModernGalleryMixin.m index 29960e53d3..1ce2b7b2e8 100644 --- a/submodules/LegacyComponents/LegacyComponents/TGMediaPickerModernGalleryMixin.m +++ b/submodules/LegacyComponents/LegacyComponents/TGMediaPickerModernGalleryMixin.m @@ -136,7 +136,7 @@ strongSelf->_galleryModel.dismiss(true, false); if (strongSelf.completeWithItem != nil) - strongSelf.completeWithItem(item, false); + strongSelf.completeWithItem(item, false, 0); }; model.interfaceView.doneLongPressed = ^(TGMediaPickerGalleryItem *item) { @@ -156,27 +156,33 @@ strongSelf->_galleryModel.dismiss(true, false); if (strongSelf.completeWithItem != nil) - strongSelf.completeWithItem(item, TGMediaPickerGalleryCompletionModeGeneric); + strongSelf.completeWithItem(item, false, 0); }; controller.sendSilently = ^{ __strong TGMediaPickerModernGalleryMixin *strongSelf = weakSelf; if (strongSelf == nil) return; - strongSelf->_galleryModel.dismiss(true, TGMediaPickerGalleryCompletionModeSilent); - - if (strongSelf.completeWithItem != nil) - strongSelf.completeWithItem(item, true); - }; - controller.schedule = ^{ - __strong TGMediaPickerModernGalleryMixin *strongSelf = weakSelf; - if (strongSelf == nil) - return; - strongSelf->_galleryModel.dismiss(true, false); if (strongSelf.completeWithItem != nil) - strongSelf.completeWithItem(item, TGMediaPickerGalleryCompletionModeSchedule); + strongSelf.completeWithItem(item, true, 0); + }; + controller.schedule = ^{ + __strong TGMediaPickerModernGalleryMixin *strongSelf = weakSelf; + if (strongSelf == nil) + return; + + strongSelf.presentScheduleController(^(int32_t time) { + __strong TGMediaPickerModernGalleryMixin *strongSelf = weakSelf; + if (strongSelf == nil) + return; + + strongSelf->_galleryModel.dismiss(true, false); + + if (strongSelf.completeWithItem != nil) + strongSelf.completeWithItem(item, false, time); + }); }; TGOverlayControllerWindow *controllerWindow = [[TGOverlayControllerWindow alloc] initWithManager:[strongSelf->_context makeOverlayWindowManager] parentController:strongSelf->_parentController contentController:controller]; diff --git a/submodules/LegacyComponents/LegacyComponents/TGMediaPickerSendActionSheetController.m b/submodules/LegacyComponents/LegacyComponents/TGMediaPickerSendActionSheetController.m index d27edeb0c4..9d4365a90e 100644 --- a/submodules/LegacyComponents/LegacyComponents/TGMediaPickerSendActionSheetController.m +++ b/submodules/LegacyComponents/LegacyComponents/TGMediaPickerSendActionSheetController.m @@ -115,12 +115,21 @@ _containerView.layer.cornerRadius = 12.0; [self.view addSubview:_containerView]; + __weak TGMediaPickerSendActionSheetController *weakSelf = self; if (_canSendSilently) { _sendSilentlyButton = [[TGMediaPickerSendActionSheetItemView alloc] initWithTitle:TGLocalized(@"Conversation.SendMessage.SendSilently") icon:TGComponentsImageNamed(@"MediaMute")]; + _sendSilentlyButton.pressed = ^{ + __strong TGMediaPickerSendActionSheetController *strongSelf = weakSelf; + [strongSelf sendSilentlyPressed]; + }; [_containerView addSubview:_sendSilentlyButton]; } _scheduleButton = [[TGMediaPickerSendActionSheetItemView alloc] initWithTitle:TGLocalized(@"Conversation.SendMessage.ScheduleMessage") icon:TGComponentsImageNamed(@"MediaSchedule")]; + _scheduleButton.pressed = ^{ + __strong TGMediaPickerSendActionSheetController *strongSelf = weakSelf; + [strongSelf schedulePressed]; + }; [_containerView addSubview:_scheduleButton]; TGMediaAssetsPallete *pallete = nil; diff --git a/submodules/LegacyComponents/LegacyComponents/TGPassportAttachMenu.m b/submodules/LegacyComponents/LegacyComponents/TGPassportAttachMenu.m index 606e7184c4..12d35852dc 100644 --- a/submodules/LegacyComponents/LegacyComponents/TGPassportAttachMenu.m +++ b/submodules/LegacyComponents/LegacyComponents/TGPassportAttachMenu.m @@ -67,7 +67,7 @@ [TGPassportAttachMenu _displayCameraWithView:cameraView menuController:strongController parentController:strongParentController context:context intent:intent uploadAction:uploadAction]; }; - carouselItem.sendPressed = ^(TGMediaAsset *currentItem, __unused bool asFiles, __unused TGMediaPickerGalleryCompletionMode mode) + carouselItem.sendPressed = ^(TGMediaAsset *currentItem, __unused bool asFiles, __unused bool silentPosting, __unused int32_t scheduleTime) { __strong TGMenuSheetController *strongController = weakController; if (strongController == nil) diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift index fce72c2ae6..f2a833798e 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift @@ -20,7 +20,7 @@ public struct LegacyAttachmentMenuMediaEditing: OptionSet { public static let imageOrVideo = LegacyAttachmentMenuMediaEditing(rawValue: 1 << 0) } -public func legacyAttachmentMenu(context: AccountContext, peer: Peer, editMediaOptions: LegacyAttachmentMenuMediaEditing?, saveEditedPhotos: Bool, allowGrouping: Bool, theme: PresentationTheme, strings: PresentationStrings, parentController: LegacyController, recentlyUsedInlineBots: [Peer], initialCaption: String, openGallery: @escaping () -> Void, openCamera: @escaping (TGAttachmentCameraView?, TGMenuSheetController?) -> Void, openFileGallery: @escaping () -> Void, openWebSearch: @escaping () -> Void, openMap: @escaping () -> Void, openContacts: @escaping () -> Void, openPoll: @escaping () -> Void, presentSelectionLimitExceeded: @escaping () -> Void, presentCantSendMultipleFiles: @escaping () -> Void, sendMessagesWithSignals: @escaping ([Any]?, Bool) -> Void, selectRecentlyUsedInlineBot: @escaping (Peer) -> Void) -> TGMenuSheetController { +public func legacyAttachmentMenu(context: AccountContext, peer: Peer, editMediaOptions: LegacyAttachmentMenuMediaEditing?, saveEditedPhotos: Bool, allowGrouping: Bool, theme: PresentationTheme, strings: PresentationStrings, parentController: LegacyController, recentlyUsedInlineBots: [Peer], initialCaption: String, openGallery: @escaping () -> Void, openCamera: @escaping (TGAttachmentCameraView?, TGMenuSheetController?) -> Void, openFileGallery: @escaping () -> Void, openWebSearch: @escaping () -> Void, openMap: @escaping () -> Void, openContacts: @escaping () -> Void, openPoll: @escaping () -> Void, presentSelectionLimitExceeded: @escaping () -> Void, presentCantSendMultipleFiles: @escaping () -> Void, presentScheduleController: @escaping (@escaping (Int32) -> Void) -> Void, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32) -> Void, selectRecentlyUsedInlineBot: @escaping (Peer) -> Void) -> TGMenuSheetController { let isSecretChat = peer.id.namespace == Namespaces.Peer.SecretChat let controller = TGMenuSheetController(context: parentController.context, dark: false)! @@ -77,7 +77,12 @@ public func legacyAttachmentMenu(context: AccountContext, peer: Peer, editMediaO carouselItem.hasSilentPosting = !isSecretChat } carouselItem.hasSchedule = !isSecretChat - carouselItem.sendPressed = { [weak controller, weak carouselItem] currentItem, asFiles, mode in + carouselItem.presentScheduleController = { done in + presentScheduleController { time in + done?(time) + } + } + carouselItem.sendPressed = { [weak controller, weak carouselItem] currentItem, asFiles, silentPosting, scheduleTime in if let controller = controller, let carouselItem = carouselItem { let intent: TGMediaAssetsControllerIntent = asFiles ? TGMediaAssetsControllerSendFileIntent : TGMediaAssetsControllerSendMediaIntent let signals = TGMediaAssetsController.resultSignals(for: carouselItem.selectionContext, editingContext: carouselItem.editingContext, intent: intent, currentItem: currentItem, storeAssets: true, useMediaCache: false, descriptionGenerator: legacyAssetPickerItemGenerator(), saveEditedPhotos: saveEditedPhotos) @@ -85,7 +90,7 @@ public func legacyAttachmentMenu(context: AccountContext, peer: Peer, editMediaO presentCantSendMultipleFiles() } else { controller.dismiss(animated: true) - sendMessagesWithSignals(signals, false) + sendMessagesWithSignals(signals, silentPosting, scheduleTime) } } }; diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift index 1bef9419c7..a1ed9bffc6 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift @@ -17,7 +17,7 @@ public func guessMimeTypeByFileExtension(_ ext: String) -> String { return TGMimeTypeMap.mimeType(forExtension: ext) ?? "application/binary" } -public func configureLegacyAssetPicker(_ controller: TGMediaAssetsController, context: AccountContext, peer: Peer, captionsEnabled: Bool = true, storeCreatedAssets: Bool = true, showFileTooltip: Bool = false, initialCaption: String, presentWebSearch: (() -> Void)?, presentSelectionLimitExceeded: @escaping () -> Void) { +public func configureLegacyAssetPicker(_ controller: TGMediaAssetsController, context: AccountContext, peer: Peer, captionsEnabled: Bool = true, storeCreatedAssets: Bool = true, showFileTooltip: Bool = false, initialCaption: String, presentWebSearch: (() -> Void)?, presentSelectionLimitExceeded: @escaping () -> Void, presentScheduleController: @escaping (@escaping (Int32) -> Void) -> Void) { let isSecretChat = peer.id.namespace == Namespaces.Peer.SecretChat controller.captionsEnabled = captionsEnabled @@ -30,6 +30,11 @@ public func configureLegacyAssetPicker(_ controller: TGMediaAssetsController, co controller.hasSilentPosting = !isSecretChat } controller.hasSchedule = !isSecretChat + controller.presentScheduleController = { done in + presentScheduleController { time in + done?(time) + } + } controller.dismissalBlock = { } controller.selectionLimitExceeded = { diff --git a/submodules/PassportUI/Sources/SecureIdAuthFormFieldNode.swift b/submodules/PassportUI/Sources/SecureIdAuthFormFieldNode.swift index 0290391218..438f8eb2c9 100644 --- a/submodules/PassportUI/Sources/SecureIdAuthFormFieldNode.swift +++ b/submodules/PassportUI/Sources/SecureIdAuthFormFieldNode.swift @@ -1005,7 +1005,7 @@ final class SecureIdAuthFormFieldNode: ASDisplayNode { transition.updateFrame(node: self.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -(hasPrevious ? UIScreenPixel : 0.0)), size: CGSize(width: width, height: height + (hasPrevious ? UIScreenPixel : 0.0)))) if let image = self.disclosureNode.image { - self.disclosureNode.frame = CGRect(origin: CGPoint(x: width - 15.0 - image.size.width, y: floor((height - image.size.height) / 2.0)), size: image.size) + self.disclosureNode.frame = CGRect(origin: CGPoint(x: width - 7.0 - image.size.width, y: floor((height - image.size.height) / 2.0)), size: image.size) } if let image = self.checkNode.image { diff --git a/submodules/PassportUI/Sources/SecureIdAuthListFieldNode.swift b/submodules/PassportUI/Sources/SecureIdAuthListFieldNode.swift index 5e7005e5c9..fff18c5755 100644 --- a/submodules/PassportUI/Sources/SecureIdAuthListFieldNode.swift +++ b/submodules/PassportUI/Sources/SecureIdAuthListFieldNode.swift @@ -240,7 +240,7 @@ final class SecureIdAuthListFieldNode: ASDisplayNode { transition.updateFrame(node: self.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -(hasPrevious ? UIScreenPixel : 0.0)), size: CGSize(width: width, height: height + (hasPrevious ? UIScreenPixel : 0.0)))) if let image = self.disclosureNode.image { - self.disclosureNode.frame = CGRect(origin: CGPoint(x: width - 15.0 - image.size.width, y: floor((height - image.size.height) / 2.0)), size: image.size) + self.disclosureNode.frame = CGRect(origin: CGPoint(x: width - 7.0 - image.size.width, y: floor((height - image.size.height) / 2.0)), size: image.size) } return height diff --git a/submodules/PeerInfoUI/Sources/ItemListSecretChatKeyItem.swift b/submodules/PeerInfoUI/Sources/ItemListSecretChatKeyItem.swift index 0eec849879..e39bc62ac5 100644 --- a/submodules/PeerInfoUI/Sources/ItemListSecretChatKeyItem.swift +++ b/submodules/PeerInfoUI/Sources/ItemListSecretChatKeyItem.swift @@ -273,7 +273,7 @@ class ItemListSecretChatKeyItemNode: ListViewItemNode { } if let arrowImage = strongSelf.arrowNode.image { - strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 15.0 - arrowImage.size.width, y: 15.0), size: arrowImage.size) + strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 7.0 - arrowImage.size.width, y: 15.0), size: arrowImage.size) } switch item.disclosureStyle { diff --git a/submodules/SettingsUI/SettingsUI_Xcode.xcodeproj/project.pbxproj b/submodules/SettingsUI/SettingsUI_Xcode.xcodeproj/project.pbxproj index cc24ab68cd..dd27f05c9a 100644 --- a/submodules/SettingsUI/SettingsUI_Xcode.xcodeproj/project.pbxproj +++ b/submodules/SettingsUI/SettingsUI_Xcode.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 09B4A9B823102B7A005C2E08 /* CreateThemeController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4A9B723102B7A005C2E08 /* CreateThemeController.swift */; }; D03E465223075D930049C28B /* SettingsUI.h in Headers */ = {isa = PBXBuildFile; fileRef = D03E465023075D930049C28B /* SettingsUI.h */; settings = {ATTRIBUTES = (Public, ); }; }; D03E466823075E660049C28B /* TabBarAccountSwitchControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E465C23075E630049C28B /* TabBarAccountSwitchControllerNode.swift */; }; D03E466923075E660049C28B /* LogoutOptionsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E465D23075E630049C28B /* LogoutOptionsController.swift */; }; @@ -25,7 +26,6 @@ D03E467F23075EE90049C28B /* NotificationExceptionSettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E467C23075EE90049C28B /* NotificationExceptionSettingsController.swift */; }; D03E468023075EE90049C28B /* NotificationExceptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E467D23075EE90049C28B /* NotificationExceptions.swift */; }; D03E468123075EE90049C28B /* NotificationExceptionControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E467E23075EE90049C28B /* NotificationExceptionControllerNode.swift */; }; - D03E468423075EF60049C28B /* LanguageSuggestionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E468323075EF60049C28B /* LanguageSuggestionController.swift */; }; D03E468A23075F010049C28B /* SettingsSearchRecentQueries.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E468523075EFF0049C28B /* SettingsSearchRecentQueries.swift */; }; D03E468B23075F010049C28B /* SettingsSearchItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E468623075F000049C28B /* SettingsSearchItem.swift */; }; D03E468C23075F010049C28B /* SettingsSearchableItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E468723075F000049C28B /* SettingsSearchableItems.swift */; }; @@ -191,6 +191,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 09B4A9B723102B7A005C2E08 /* CreateThemeController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateThemeController.swift; sourceTree = ""; }; D03E464D23075D930049C28B /* SettingsUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SettingsUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D03E465023075D930049C28B /* SettingsUI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SettingsUI.h; sourceTree = ""; }; D03E465123075D930049C28B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -211,7 +212,6 @@ D03E467C23075EE90049C28B /* NotificationExceptionSettingsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationExceptionSettingsController.swift; sourceTree = ""; }; D03E467D23075EE90049C28B /* NotificationExceptions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationExceptions.swift; sourceTree = ""; }; D03E467E23075EE90049C28B /* NotificationExceptionControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationExceptionControllerNode.swift; sourceTree = ""; }; - D03E468323075EF60049C28B /* LanguageSuggestionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LanguageSuggestionController.swift; sourceTree = ""; }; D03E468523075EFF0049C28B /* SettingsSearchRecentQueries.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsSearchRecentQueries.swift; sourceTree = ""; }; D03E468623075F000049C28B /* SettingsSearchItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsSearchItem.swift; sourceTree = ""; }; D03E468723075F000049C28B /* SettingsSearchableItems.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsSearchableItems.swift; sourceTree = ""; }; @@ -484,7 +484,6 @@ isa = PBXGroup; children = ( D03E467423075E6C0049C28B /* Search */, - D03E467523075E920049C28B /* Language Suggestion */, D03E467623075EA90049C28B /* Notifications */, D03E468223075EEB0049C28B /* Privacy and Security */, D03E46B423075F610049C28B /* Data and Storage */, @@ -527,14 +526,6 @@ path = Search; sourceTree = ""; }; - D03E467523075E920049C28B /* Language Suggestion */ = { - isa = PBXGroup; - children = ( - D03E468323075EF60049C28B /* LanguageSuggestionController.swift */, - ); - path = "Language Suggestion"; - sourceTree = ""; - }; D03E467623075EA90049C28B /* Notifications */ = { isa = PBXGroup; children = ( @@ -658,6 +649,7 @@ D03E46E523075FD40049C28B /* WallpaperGalleryToolbarNode.swift */, D03E46EB23075FD60049C28B /* WallpaperPatternPanelNode.swift */, D03E46EE23075FD60049C28B /* WallpaperSearchRecentQueries.swift */, + 09B4A9B723102B7A005C2E08 /* CreateThemeController.swift */, ); path = Themes; sourceTree = ""; @@ -939,7 +931,7 @@ D03E468E23075F010049C28B /* SettingsSearchRecentItem.swift in Sources */, D03E467223075E660049C28B /* ChangePhoneNumberCodeController.swift in Sources */, D03E466823075E660049C28B /* TabBarAccountSwitchControllerNode.swift in Sources */, - D03E468423075EF60049C28B /* LanguageSuggestionController.swift in Sources */, + 09B4A9B823102B7A005C2E08 /* CreateThemeController.swift in Sources */, D03E471623075FE40049C28B /* WallpaperColorPanelNode.swift in Sources */, D03E470023075FE40049C28B /* ThemeColorsGridControllerItem.swift in Sources */, D03E468B23075F010049C28B /* SettingsSearchItem.swift in Sources */, diff --git a/submodules/SettingsUI/Sources/Language Suggestion/LanguageSuggestionController.swift b/submodules/SettingsUI/Sources/Language Suggestion/LanguageSuggestionController.swift deleted file mode 100644 index e77eb77318..0000000000 --- a/submodules/SettingsUI/Sources/Language Suggestion/LanguageSuggestionController.swift +++ /dev/null @@ -1,368 +0,0 @@ -import Foundation -import UIKit -import SwiftSignalKit -import AsyncDisplayKit -import Display -import TelegramCore -import TelegramPresentationData -import ActivityIndicator -import AccountContext - -struct LanguageSuggestionControllerStrings { - let ChooseLanguage: String - let Other: String - let English: String - - init(localization: SuggestedLocalizationInfo) { - var chooseLanguage = "Choose Your Language" - var other = "Other" - var english = "English" - - for entry in localization.extractedEntries { - switch entry { - case let .string(key, value): - switch key { - case "Localization.ChooseLanguage": - chooseLanguage = value - case "Localization.LanguageOther": - other = value - case "Localization.EnglishLanguageName": - english = value - default: - break - } - default: - break - } - } - - self.ChooseLanguage = chooseLanguage - self.Other = other - self.English = english - } - - init(bundle: Bundle?) { - var chooseLanguage = "Choose Your Language" - var other = "Other" - var english = "English" - - if let bundle = bundle { - for key in LanguageSuggestionControllerStrings.keys { - let value = bundle.localizedString(forKey: key, value: nil, table: nil) - if value != key { - switch key { - case "Localization.ChooseLanguage": - chooseLanguage = value - case "Localization.LanguageOther": - other = value - case "Localization.EnglishLanguageName": - english = value - default: - break - } - } - } - } - - self.ChooseLanguage = chooseLanguage - self.Other = other - self.English = english - } - - static let keys: [String] = ["Localization.ChooseLanguage", - "Localization.LanguageOther", - "Localization.EnglishLanguageName"] -} - -private enum LanguageSuggestionItemType { - case localization(String) - case disclosure - case action -} - -private struct LanguageSuggestionItem { - public let type: LanguageSuggestionItemType - public let title: String - public let subtitle: String? - public let action: () -> Void - - public init(type: LanguageSuggestionItemType, title: String, subtitle: String?, action: @escaping () -> Void) { - self.type = type - self.title = title - self.subtitle = subtitle - self.action = action - } -} - -private final class LanguageSuggestionItemNode: HighlightableButtonNode { - private let backgroundNode: ASDisplayNode - private let separatorNode: ASDisplayNode - private let subtitleNode: ASTextNode - private let iconNode: ASImageNode - - let item: LanguageSuggestionItem - - override var isSelected: Bool { - didSet { - if case .localization = self.item.type { - self.iconNode.isHidden = !self.isSelected - } - } - } - - init(theme: PresentationTheme, item: LanguageSuggestionItem) { - self.item = item - - self.backgroundNode = ASDisplayNode() - self.backgroundNode.isLayerBacked = true - self.backgroundNode.backgroundColor = theme.actionSheet.opaqueItemHighlightedBackgroundColor - self.backgroundNode.alpha = 0.0 - - self.separatorNode = ASDisplayNode() - self.separatorNode.backgroundColor = theme.actionSheet.opaqueItemSeparatorColor - - self.subtitleNode = ASTextNode() - - self.iconNode = ASImageNode() - - super.init() - - self.addSubnode(self.subtitleNode) - self.addSubnode(self.separatorNode) - self.addSubnode(self.iconNode) - - var color: UIColor = theme.actionSheet.primaryTextColor - var alignment: ASHorizontalAlignment = .left - var inset: CGFloat = 19.0 - var icon: UIImage? - switch item.type { - case .action: - alignment = .middle - color = theme.actionSheet.controlAccentColor - inset = 0.0 - case .disclosure: - icon = PresentationResourcesItemList.disclosureArrowImage(theme) - case .localization: - icon = PresentationResourcesItemList.checkIconImage(theme) - } - - self.iconNode.image = icon - self.contentHorizontalAlignment = alignment - self.setTitle(item.title, with: Font.regular(17.0), with: color, for: []) - - var titleVerticalOffset: CGFloat = 0.0 - if let subtitle = item.subtitle { - self.subtitleNode.attributedText = NSAttributedString(string: subtitle, font: Font.regular(14.0), textColor: theme.actionSheet.secondaryTextColor) - titleVerticalOffset = 20.0 - } - self.contentEdgeInsets = UIEdgeInsets(top: 0.0, left: inset, bottom: titleVerticalOffset, right: 0.0) - - self.highligthedChanged = { [weak self] value in - if let strongSelf = self { - if value { - if strongSelf.backgroundNode.supernode == nil { - strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0) - } - strongSelf.backgroundNode.layer.removeAnimation(forKey: "opacity") - strongSelf.backgroundNode.alpha = 1.0 - } else if !strongSelf.backgroundNode.alpha.isZero { - strongSelf.backgroundNode.alpha = 0.0 - strongSelf.backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25) - } - } - } - } - - override func didLoad() { - super.didLoad() - - self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside) - } - - @objc func pressed() { - self.item.action() - } - - public func updateLayout(_ constrainedSize: CGSize) -> CGSize { - let bounds = CGRect(origin: CGPoint(), size: CGSize(width: constrainedSize.width, height: self.item.subtitle != nil ? 58.0 : 44.0)) - self.backgroundNode.frame = bounds - - let subtitleSize = self.subtitleNode.measure(bounds.size) - self.subtitleNode.frame = CGRect(origin: CGPoint(x: 19.0, y: 31.0), size: subtitleSize) - self.separatorNode.frame = CGRect(x: 0.0, y: bounds.height - UIScreenPixel, width: bounds.width, height: UIScreenPixel) - if let icon = self.iconNode.image { - self.iconNode.frame = CGRect(origin: CGPoint(x: bounds.width - icon.size.width - 19.0, y: floorToScreenPixels((bounds.height - icon.size.height) / 2.0)), size: icon.size) - } - return bounds.size - } -} - -private final class LanguageSuggestionAlertContentNode: AlertContentNode { - private var validLayout: CGSize? - - private let titleNode: ASTextNode - private let subtitleNode: ASTextNode - private let titleSeparatorNode: ASDisplayNode - private let activityIndicator: ActivityIndicator - - private var nodes: [LanguageSuggestionItemNode] - - private let disposable = MetaDisposable() - - override var dismissOnOutsideTap: Bool { - return self.isUserInteractionEnabled - } - - init(theme: PresentationTheme, strings: LanguageSuggestionControllerStrings, englishStrings: LanguageSuggestionControllerStrings, suggestedLocalization: LocalizationInfo, openSelection: @escaping () -> Void, applyLocalization: @escaping (String, () -> Void) -> Void, dismiss: @escaping () -> Void) { - let selectedLocalization = ValuePromise(suggestedLocalization.languageCode, ignoreRepeated: true) - - self.titleNode = ASTextNode() - self.titleNode.attributedText = NSAttributedString(string: strings.ChooseLanguage, font: Font.bold(17.0), textColor: theme.actionSheet.primaryTextColor, paragraphAlignment: .center) - self.titleNode.maximumNumberOfLines = 2 - - self.subtitleNode = ASTextNode() - self.subtitleNode.attributedText = NSAttributedString(string: englishStrings.ChooseLanguage, font: Font.regular(14.0), textColor: theme.actionSheet.secondaryTextColor, paragraphAlignment: .center) - self.subtitleNode.maximumNumberOfLines = 2 - - self.titleSeparatorNode = ASDisplayNode() - self.titleSeparatorNode.backgroundColor = theme.actionSheet.opaqueItemSeparatorColor - - self.activityIndicator = ActivityIndicator(type: .custom(theme.actionSheet.controlAccentColor, 22.0, 1.0, false)) - self.activityIndicator.isHidden = true - - var items: [LanguageSuggestionItem] = [] - items.append(LanguageSuggestionItem(type: .localization(suggestedLocalization.languageCode), title: suggestedLocalization.localizedTitle, subtitle: suggestedLocalization.title, action: { - selectedLocalization.set(suggestedLocalization.languageCode) - })) - items.append(LanguageSuggestionItem(type: .localization("en"), title: strings.English, subtitle: englishStrings.English, action: { - selectedLocalization.set("en") - })) - items.append(LanguageSuggestionItem(type: .disclosure, title: strings.Other, subtitle: englishStrings.Other != strings.Other ? englishStrings.Other : nil, action: { - openSelection() - })) - - var applyImpl: (() -> Void)? - items.append(LanguageSuggestionItem(type: .action, title: "OK", subtitle: nil, action: { - applyImpl?() - })) - - self.nodes = items.map { LanguageSuggestionItemNode(theme: theme, item: $0) } - - super.init() - - self.addSubnode(self.titleNode) - self.addSubnode(self.subtitleNode) - self.addSubnode(self.titleSeparatorNode) - self.addSubnode(self.activityIndicator) - for node in self.nodes { - self.addSubnode(node) - } - - self.disposable.set(selectedLocalization.get().start(next: { [weak self] selectedCode in - if let strongSelf = self { - for node in strongSelf.nodes { - if case let .localization(code) = node.item.type { - node.isSelected = code == selectedCode - } - } - } - })) - - applyImpl = { [weak self] in - if let strongSelf = self { - strongSelf.isUserInteractionEnabled = false - - _ = (selectedLocalization.get() - |> take(1)).start(next: { selectedCode in - applyLocalization(selectedCode, { [weak self] in - if let strongSelf = self { - strongSelf.activityIndicator.isHidden = false - if let lastNode = strongSelf.nodes.last { - lastNode.isHidden = true - } - } - }) - }) - } - } - } - - deinit { - self.disposable.dispose() - } - - override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - var size = size - size.width = min(size.width, 270.0) - - self.validLayout = size - - var origin: CGPoint = CGPoint(x: 0.0, y: 17.0) - - let titleSize = self.titleNode.measure(size) - transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize)) - origin.y += titleSize.height + 3.0 - - let subtitleSize = self.subtitleNode.measure(size) - transition.updateFrame(node: self.subtitleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - subtitleSize.width) / 2.0), y: origin.y), size: subtitleSize)) - origin.y += subtitleSize.height + 17.0 - transition.updateFrame(node: self.titleSeparatorNode, frame: CGRect(x: 0.0, y: origin.y - UIScreenPixel, width: size.width, height: UIScreenPixel)) - - var lastNodeSize: CGSize? - for node in self.nodes { - let size = node.updateLayout(size) - transition.updateFrame(node: node, frame: CGRect(origin: origin, size: size)) - origin.y += size.height - lastNodeSize = size - } - - if let lastSize = lastNodeSize { - let indicatorSize = self.activityIndicator.measure(CGSize(width: 100.0, height: 100.0)) - transition.updateFrame(node: self.activityIndicator, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - indicatorSize.width) / 2.0), y: origin.y - lastSize.height + floorToScreenPixels((lastSize.height - indicatorSize.height) / 2.0)), size: indicatorSize)) - } - - return CGSize(width: size.width, height: origin.y - UIScreenPixel) - } -} - -func languageSuggestionController(context: AccountContext, suggestedLocalization: SuggestedLocalizationInfo, currentLanguageCode: String, openSelection: @escaping () -> Void) -> AlertController? { - guard let localization = suggestedLocalization.availableLocalizations.filter({ $0.languageCode == suggestedLocalization.languageCode }).first else { - return nil - } - - let theme = context.sharedContext.currentPresentationData.with { $0 }.theme - let strings = LanguageSuggestionControllerStrings(localization: suggestedLocalization) - guard let mainPath = Bundle.main.path(forResource: "en", ofType: "lproj") else { - return nil - } - let englishStrings = LanguageSuggestionControllerStrings(bundle: Bundle(path: mainPath)) - - let disposable = MetaDisposable() - - var dismissImpl: ((Bool) -> Void)? - let contentNode = LanguageSuggestionAlertContentNode(theme: theme, strings: strings, englishStrings: englishStrings, suggestedLocalization: localization, openSelection: { - dismissImpl?(true) - openSelection() - }, applyLocalization: { languageCode, startActivity in - if languageCode == currentLanguageCode { - dismissImpl?(true) - } else { - startActivity() - disposable.set((downloadAndApplyLocalization(accountManager: context.sharedContext.accountManager, postbox: context.account.postbox, network: context.account.network, languageCode: languageCode) - |> deliverOnMainQueue).start(completed: { - dismissImpl?(true) - })) - } - }, dismiss: { - dismissImpl?(true) - }) - let controller = AlertController(theme: AlertControllerTheme(presentationTheme: theme), contentNode: contentNode) - dismissImpl = { [weak controller] animated in - if animated { - controller?.dismissAnimated() - } else { - controller?.dismiss() - } - } - return controller -} diff --git a/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionControllerNode.swift b/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionControllerNode.swift index a9f4d0df0f..e24985854f 100644 --- a/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionControllerNode.swift +++ b/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionControllerNode.swift @@ -20,7 +20,7 @@ import ItemListPeerActionItem import TelegramStringFormatting private final class NotificationExceptionState : Equatable { - let mode:NotificationExceptionMode + let mode: NotificationExceptionMode let isSearchMode: Bool let revealedPeerId: PeerId? let editing: Bool @@ -93,7 +93,15 @@ public struct NotificationExceptionWrapper : Equatable { } } + + public enum NotificationExceptionMode : Equatable { + fileprivate enum Mode { + case users + case groups + case channels + } + public static func == (lhs: NotificationExceptionMode, rhs: NotificationExceptionMode) -> Bool { switch lhs { case let .users(lhsValue): @@ -117,6 +125,17 @@ public enum NotificationExceptionMode : Equatable { } } + fileprivate var mode: Mode { + switch self { + case .users: + return .users + case .groups: + return .groups + case .channels: + return .channels + } + } + var isEmpty: Bool { switch self { case let .users(value), let .groups(value), let .channels(value): @@ -271,7 +290,7 @@ private func notificationsExceptionEntries(presentationData: PresentationData, s var entries: [NotificationExceptionEntry] = [] if !state.isSearchMode { - entries.append(.addException(presentationData.theme, presentationData.strings, state.editing)) + entries.append(.addException(presentationData.theme, presentationData.strings, state.mode.mode, state.editing)) } var existingPeerIds = Set() @@ -486,7 +505,7 @@ private enum NotificationExceptionEntry : ItemListNodeEntry { case search(PresentationTheme, PresentationStrings) case peer(index: Int, peer: Peer, theme: PresentationTheme, strings: PresentationStrings, dateFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, description: String, notificationSettings: TelegramPeerNotificationSettings, revealed: Bool, editing: Bool, isSearching: Bool) case addPeer(index: Int, peer: Peer, theme: PresentationTheme, strings: PresentationStrings, dateFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder) - case addException(PresentationTheme, PresentationStrings, Bool) + case addException(PresentationTheme, PresentationStrings, NotificationExceptionMode.Mode, Bool) case removeAll(PresentationTheme, PresentationStrings) func item(_ arguments: NotificationExceptionArguments) -> ListViewItem { @@ -495,8 +514,17 @@ private enum NotificationExceptionEntry : ItemListNodeEntry { return NotificationSearchItem(theme: theme, placeholder: strings.Common_Search, activate: { arguments.activateSearch() }) - case let .addException(theme, strings, editing): - return ItemListPeerActionItem(theme: theme, icon: PresentationResourcesItemList.addExceptionIcon(theme), title: strings.Notification_Exceptions_AddException, alwaysPlain: true, sectionId: self.section, editing: editing, action: { + case let .addException(theme, strings, mode, editing): + let icon: UIImage? + switch mode { + case .users: + icon = PresentationResourcesItemList.addPersonIcon(theme) + case .groups: + icon = PresentationResourcesItemList.createGroupIcon(theme) + case .channels: + icon = PresentationResourcesItemList.addChannelIcon(theme) + } + return ItemListPeerActionItem(theme: theme, icon: icon, title: strings.Notification_Exceptions_AddException, alwaysPlain: true, sectionId: self.section, editing: editing, action: { arguments.selectPeer() }) case let .peer(_, peer, theme, strings, dateTimeFormat, nameDisplayOrder, value, _, revealed, editing, isSearching): @@ -543,10 +571,10 @@ private enum NotificationExceptionEntry : ItemListNodeEntry { default: return false } - case let .addException(lhsTheme, lhsStrings, lhsEditing): + case let .addException(lhsTheme, lhsStrings, lhsMode, lhsEditing): switch rhs { - case let .addException(rhsTheme, rhsStrings, rhsEditing): - return lhsTheme === rhsTheme && lhsStrings === rhsStrings && lhsEditing == rhsEditing + case let .addException(rhsTheme, rhsStrings, rhsMode, rhsEditing): + return lhsTheme === rhsTheme && lhsStrings === rhsStrings && lhsMode == rhsMode && lhsEditing == rhsEditing default: return false } diff --git a/submodules/SettingsUI/Sources/Themes/CreateThemeController.swift b/submodules/SettingsUI/Sources/Themes/CreateThemeController.swift new file mode 100644 index 0000000000..06c9b2d5c8 --- /dev/null +++ b/submodules/SettingsUI/Sources/Themes/CreateThemeController.swift @@ -0,0 +1,189 @@ +import Foundation +import UIKit +import Display +import SwiftSignalKit +import Postbox +import TelegramCore +import TelegramPresentationData +import TelegramUIPreferences +import ItemListUI +import AlertUI +import AccountContext + +private final class CreateThemeControllerArguments { + let context: AccountContext + let updateState: ((CreateThemeControllerState) -> CreateThemeControllerState) -> Void + + init(context: AccountContext, updateState: @escaping ((CreateThemeControllerState) -> CreateThemeControllerState) -> Void) { + self.context = context + self.updateState = updateState + } +} + +private enum CreateThemeControllerSection: Int32 { + case chatPreview + case info +} + +private enum CreateThemeControllerEntry: ItemListNodeEntry { + case chatPreviewHeader(PresentationTheme, String) + case chatPreview(PresentationTheme, PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder) + case title(PresentationTheme, PresentationStrings, String, String) + case slug(PresentationTheme, PresentationStrings, String, String, Bool) + + var section: ItemListSectionId { + switch self { + case .chatPreviewHeader, .chatPreview: + return CreateThemeControllerSection.chatPreview.rawValue + case .title, .slug: + return CreateThemeControllerSection.info.rawValue + } + } + + var stableId: Int32 { + switch self { + case .chatPreviewHeader: + return 0 + case .chatPreview: + return 1 + case .title: + return 2 + case .slug: + return 3 + } + } + + static func ==(lhs: CreateThemeControllerEntry, rhs: CreateThemeControllerEntry) -> Bool { + switch lhs { + case let .chatPreviewHeader(lhsTheme, lhsText): + if case let .chatPreviewHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + return true + } else { + return false + } + case let .chatPreview(lhsTheme, lhsComponentTheme, lhsWallpaper, lhsFontSize, lhsStrings, lhsTimeFormat, lhsNameOrder): + if case let .chatPreview(rhsTheme, rhsComponentTheme, rhsWallpaper, rhsFontSize, rhsStrings, rhsTimeFormat, rhsNameOrder) = rhs, lhsComponentTheme === rhsComponentTheme, lhsTheme === rhsTheme, lhsWallpaper == rhsWallpaper, lhsFontSize == rhsFontSize, lhsStrings === rhsStrings, lhsTimeFormat == rhsTimeFormat, lhsNameOrder == rhsNameOrder { + return true + } else { + return false + } + case let .title(lhsTheme, lhsStrings, lhsTitle, lhsValue): + if case let .title(rhsTheme, rhsStrings, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsTitle == rhsTitle, lhsValue == rhsValue { + return true + } else { + return false + } + case let .slug(lhsTheme, lhsStrings, lhsTitle, lhsValue, lhsEnabled): + if case let .slug(rhsTheme, rhsStrings, rhsTitle, rhsValue, rhsEnabled) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsTitle == rhsTitle, lhsValue == rhsValue, lhsEnabled == rhsEnabled { + return true + } else { + return false + } + } + } + + static func <(lhs: CreateThemeControllerEntry, rhs: CreateThemeControllerEntry) -> Bool { + return lhs.stableId < rhs.stableId + } + + func item(_ arguments: CreateThemeControllerArguments) -> ListViewItem { + switch self { + case let .chatPreviewHeader(theme, text): + return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) + case let .chatPreview(theme, componentTheme, wallpaper, fontSize, strings, dateTimeFormat, nameDisplayOrder): + return ThemeSettingsChatPreviewItem(context: arguments.context, theme: theme, componentTheme: componentTheme, strings: strings, sectionId: self.section, fontSize: fontSize, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder) + case let .title(theme, strings, title, text): + return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(), text: text, placeholder: title, sectionId: self.section, textUpdated: { value in + arguments.updateState { current in + var state = current + state.title = value + return state + } + }, action: {}) + case let .slug(theme, strings, title, text, enabled): + return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(string: "t.me/addtheme/", textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: "Short Name", type: .username, enabled: enabled, sectionId: self.section, textUpdated: { value in + arguments.updateState { current in + var state = current + state.slug = value + return state + } + }, action: {}) + } + } +} + +private enum CreateThemeControllerMode { + case create + case update +} + +private struct CreateThemeControllerState: Equatable { + var mode: CreateThemeControllerMode + var title: String + var slug: String + + var isComplete: Bool { + if self.title.isEmpty || self.slug.isEmpty { + return false + } + return true + } +} + +private func createThemeControllerEntries(presentationData: PresentationData, theme: PresentationTheme, state: CreateThemeControllerState) -> [CreateThemeControllerEntry] { + var entries: [CreateThemeControllerEntry] = [] + + entries.append(.chatPreviewHeader(presentationData.theme, "Preview".uppercased())) + entries.append(.chatPreview(presentationData.theme, theme, theme.chat.defaultWallpaper, presentationData.fontSize, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder)) + + entries.append(.title(presentationData.theme, presentationData.strings, "Title", state.title)) + entries.append(.slug(presentationData.theme, presentationData.strings, "Slug", state.slug, true)) + + return entries +} + +public func createThemeController(context: AccountContext, theme: PresentationTheme, resource: MediaResource) -> ViewController { + let initialState = CreateThemeControllerState(mode: .create, title: "", slug: "") + let statePromise = ValuePromise(initialState, ignoreRepeated: true) + let stateValue = Atomic(value: initialState) + let updateState: ((CreateThemeControllerState) -> CreateThemeControllerState) -> Void = { f in + statePromise.set(stateValue.modify { f($0) }) + } + + var pushControllerImpl: ((ViewController) -> Void)? + var presentControllerImpl: ((ViewController, Any?) -> Void)? + var dismissImpl: (() -> Void)? + + let arguments = CreateThemeControllerArguments(context: context, updateState: { f in + updateState(f) + }) + + let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, statePromise.get()) + |> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, CreateThemeControllerEntry.ItemGenerationArguments)) in + let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { + dismissImpl?() + }) + let rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: state.isComplete, action: { + let _ = (createTheme(account: context.account, resource: resource, title: state.title, slug: state.slug) |> deliverOnMainQueue).start(completed: { + dismissImpl?() + }) + }) + + let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text("Publish Theme"), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false) + let listState = ItemListNodeState(entries: createThemeControllerEntries(presentationData: presentationData, theme: theme, state: state), style: .blocks, emptyStateItem: nil, animateChanges: false) + + return (controllerState, (listState, arguments)) + } + + let controller = ItemListController(context: context, state: signal) + pushControllerImpl = { [weak controller] c in + (controller?.navigationController as? NavigationController)?.pushViewController(c) + } + presentControllerImpl = { [weak controller] c, a in + controller?.present(c, in: .window(.root), with: a) + } + dismissImpl = { [weak controller] in + let _ = controller?.dismiss() + } + return controller +} diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift index 41199e1961..99b8ec862f 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift @@ -11,10 +11,15 @@ import AccountContext import ShareController import CounterContollerTitleView +public enum ThemePreviewSource { + case theme(TelegramTheme) + case media(AnyMediaReference) +} + public final class ThemePreviewController: ViewController { private let context: AccountContext private let previewTheme: PresentationTheme - private let media: AnyMediaReference + private let source: ThemePreviewSource private var controllerNode: ThemePreviewControllerNode { return self.displayNode as! ThemePreviewControllerNode @@ -25,21 +30,28 @@ public final class ThemePreviewController: ViewController { private var presentationData: PresentationData private var presentationDataDisposable: Disposable? - public init(context: AccountContext, previewTheme: PresentationTheme, media: AnyMediaReference) { + public init(context: AccountContext, previewTheme: PresentationTheme, source: ThemePreviewSource) { self.context = context self.previewTheme = previewTheme - self.media = media + self.source = source self.presentationData = context.sharedContext.currentPresentationData.with { $0 } super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationTheme: self.previewTheme, presentationStrings: self.presentationData.strings)) + let themeName: String + if case let .theme(theme) = source { + themeName = theme.title + } else { + themeName = previewTheme.name.string + } + if let author = previewTheme.author { let titleView = CounterContollerTitleView(theme: self.previewTheme) - titleView.title = CounterContollerTitle(title: self.previewTheme.name.string, counter: author) + titleView.title = CounterContollerTitle(title: themeName, counter: author) self.navigationItem.titleView = titleView } else { - self.title = previewTheme.name.string + self.title = themeName } self.statusBar.statusBarStyle = self.previewTheme.rootController.statusBarStyle.style self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) @@ -83,6 +95,13 @@ public final class ThemePreviewController: ViewController { } }, apply: { [weak self] in if let strongSelf = self { + let theme: PresentationThemeReference + if case let .theme(info) = strongSelf.source { + theme = .cloud(info) + } else { + theme = .builtin(.day) + } + let _ = (strongSelf.context.sharedContext.accountManager.transaction { transaction -> Void in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings, { entry in let current: PresentationThemeSettings @@ -92,7 +111,7 @@ public final class ThemePreviewController: ViewController { current = PresentationThemeSettings.defaultSettings } - return PresentationThemeSettings(chatWallpaper: .color(0xffffff), theme: .builtin(.day), themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) + return PresentationThemeSettings(chatWallpaper: .color(0xffffff), theme: theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) }) }).start(completed: { [weak self] in if let strongSelf = self { @@ -122,7 +141,22 @@ public final class ThemePreviewController: ViewController { } @objc private func actionPressed() { - let controller = ShareController(context: self.context, subject: .media(self.media)) + let subject: ShareControllerSubject + let preferredAction: ShareControllerPreferredAction + switch self.source { + case let .theme(theme): + subject = .url("https://t.me/addtheme/\(theme.slug)") + preferredAction = .default + case let .media(media): + subject = .media(media) + preferredAction = .custom(action: ShareControllerAction(title: "Publish", action: { [weak self] in + if let strongSelf = self, let file = media.media as? TelegramMediaFile { + let controller = createThemeController(context: strongSelf.context, theme: strongSelf.previewTheme, resource: file.resource) + strongSelf.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet), blockInteraction: true) + } + })) + } + let controller = ShareController(context: self.context, subject: subject, preferredAction: preferredAction) self.present(controller, in: .window(.root), blockInteraction: true) } } diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index 1911169468..40ad17ddcb 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -195,22 +195,30 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { peers[otherPeerId] = TelegramUser(id: otherPeerId, accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) let replyMessageId = MessageId(peerId: peerId, namespace: 0, id: 3) - messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_ReplyText, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - //let chatPresentationData = ChatPresentationData(theme: ChatPresentationThemeData(theme: self.previewTheme, wallpaper: self.previewTheme.chat.defaultWallpaper), fontSize: self.presentationData.fontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: false, largeEmoji: false) + let message1 = Message(stableId: 4, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66003, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [ReplyMessageAttribute(messageId: replyMessageId)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message1, theme: self.previewTheme, strings: self.presentationData.strings, wallpaper: self.previewTheme.chat.defaultWallpaper, fontSize: self.presentationData.fontSize, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil)) - items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: Message(stableId: 4, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66003, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: "", attributes: [ReplyMessageAttribute(messageId: replyMessageId)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), theme: self.previewTheme, strings: self.presentationData.strings, wallpaper: self.previewTheme.chat.defaultWallpaper, fontSize: self.presentationData.fontSize, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil)) + let message2 = Message(stableId: 3, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66002, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) - items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: Message(stableId: 3, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66002, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), theme: self.previewTheme, strings: self.presentationData.strings, wallpaper: self.previewTheme.chat.defaultWallpaper, fontSize: self.presentationData.fontSize, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil)) + items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message2, theme: self.previewTheme, strings: self.presentationData.strings, wallpaper: self.previewTheme.chat.defaultWallpaper, fontSize: self.presentationData.fontSize, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil)) - items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), theme: self.previewTheme, strings: self.presentationData.strings, wallpaper: self.previewTheme.chat.defaultWallpaper, fontSize: self.presentationData.fontSize, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil)) + let message3 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + + items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message3, theme: self.previewTheme, strings: self.presentationData.strings, wallpaper: self.previewTheme.chat.defaultWallpaper, fontSize: self.presentationData.fontSize, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil)) let voiceAttributes: [TelegramMediaFileAttribute] = [.Audio(isVoice: true, duration: 14, title: nil, performer: nil, waveform: MemoryBuffer())] let voiceMedia = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: nil, attributes: voiceAttributes) - items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: []), theme: self.previewTheme, strings: self.presentationData.strings, wallpaper: self.previewTheme.chat.defaultWallpaper, fontSize: self.presentationData.fontSize, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: FileMediaResourceStatus(mediaStatus: .playbackStatus(.playing), fetchStatus: .Local))) + let message4 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: []) - items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: "", attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), theme: self.previewTheme, strings: self.presentationData.strings, wallpaper: self.previewTheme.chat.defaultWallpaper, fontSize: self.presentationData.fontSize, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil)) + items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message4, theme: self.previewTheme, strings: self.presentationData.strings, wallpaper: self.previewTheme.chat.defaultWallpaper, fontSize: self.presentationData.fontSize, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: FileMediaResourceStatus(mediaStatus: .playbackStatus(.playing), fetchStatus: .Local))) + + //items.append(ChatMessageItem(presentationData: chatPresentationData, context: self.context, chatLocation: .peer(peerId), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .contact, automaticDownloadNetworkType: .cellular, isRecentActions: false, forcedResourceStatus: FileMediaResourceStatus(mediaStatus: .playbackStatus(.playing), fetchStatus: .Local)), controllerInteraction: controllerInteraction, content: .message(message: , read: true, selection: .none, attributes: ChatMessageEntryAttributes()), disableDate: false)) + + let message5 = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message5, theme: self.previewTheme, strings: self.presentationData.strings, wallpaper: self.previewTheme.chat.defaultWallpaper, fontSize: self.presentationData.fontSize, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil)) let params = ListViewItemLayoutParams(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right) if let messageNodes = self.messageNodes { @@ -260,8 +268,8 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { self.scrollNode.frame = bounds let toolbarHeight = 49.0 + layout.intrinsicInsets.bottom - self.chatListBackgroundNode.frame = CGRect(x: 0.0, y: 0.0, width: bounds.width, height: bounds.height) - self.chatBackgroundNode.frame = CGRect(x: bounds.width, y: 0.0, width: bounds.width, height: bounds.height) + self.chatListBackgroundNode.frame = CGRect(x: bounds.width, y: 0.0, width: bounds.width, height: bounds.height) + self.chatBackgroundNode.frame = CGRect(x: 0.0, y: 0.0, width: bounds.width, height: bounds.height) self.scrollNode.view.contentSize = CGSize(width: bounds.width * 2.0, height: bounds.height) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsAppIconItem.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsAppIconItem.swift index dbd03cc362..142baa02f8 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsAppIconItem.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsAppIconItem.swift @@ -20,7 +20,7 @@ private func generateBorderImage(theme: PresentationTheme, bordered: Bool, selec let lineWidth: CGFloat if selected { var accentColor = theme.list.itemAccentColor - if accentColor.rgb == UIColor.white.rgb { + if accentColor.rgb == 0xffffff { accentColor = UIColor(rgb: 0x999999) } context.setStrokeColor(accentColor.cgColor) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift index f75e3d12e4..85f5b1fc3d 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift @@ -7,6 +7,7 @@ import TelegramCore import TelegramPresentationData import TelegramUIPreferences import ItemListUI +import AlertUI import AccountContext private final class ThemeSettingsControllerArguments { @@ -294,16 +295,15 @@ private struct ThemeSettingsState: Equatable { } } -private func themeSettingsControllerEntries(presentationData: PresentationData, theme: PresentationTheme, themeReference: PresentationThemeReference, themeSpecificAccentColors: [Int64: PresentationThemeAccentColor], autoNightSettings: AutomaticThemeSwitchSetting, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, dateTimeFormat: PresentationDateTimeFormat, largeEmoji: Bool, disableAnimations: Bool, availableAppIcons: [PresentationAppIcon], currentAppIconName: String?) -> [ThemeSettingsControllerEntry] { +private func themeSettingsControllerEntries(presentationData: PresentationData, theme: PresentationTheme, themeReference: PresentationThemeReference, themeSpecificAccentColors: [Int64: PresentationThemeAccentColor], availableThemes: [PresentationThemeReference], autoNightSettings: AutomaticThemeSwitchSetting, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, dateTimeFormat: PresentationDateTimeFormat, largeEmoji: Bool, disableAnimations: Bool, availableAppIcons: [PresentationAppIcon], currentAppIconName: String?) -> [ThemeSettingsControllerEntry] { var entries: [ThemeSettingsControllerEntry] = [] entries.append(.themeListHeader(presentationData.theme, strings.Appearance_ColorTheme.uppercased())) entries.append(.chatPreview(presentationData.theme, theme, wallpaper, fontSize, presentationData.strings, dateTimeFormat, presentationData.nameDisplayOrder)) - let availableThemes: [PresentationThemeReference] = [.builtin(.dayClassic), .builtin(.day), .builtin(.night), .builtin(.nightAccent)] entries.append(.themeItem(presentationData.theme, presentationData.strings, availableThemes, themeReference, themeSpecificAccentColors, themeSpecificAccentColors[themeReference.index])) - if theme.name != .builtin(.dayClassic) { + if case let .builtin(theme) = themeReference, theme != .dayClassic { entries.append(.accentColor(presentationData.theme, themeReference, strings.Appearance_AccentColor, themeSpecificAccentColors[themeReference.index])) } @@ -349,6 +349,8 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The var pushControllerImpl: ((ViewController) -> Void)? var presentControllerImpl: ((ViewController, Any?) -> Void)? + var moreImpl: (() -> Void)? + let _ = telegramWallpapers(postbox: context.account.postbox, network: context.account.network).start() let currentAppIcon: PresentationAppIcon? @@ -426,9 +428,14 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The context.sharedContext.applicationBindings.requestSetAlternateIconName(name, { _ in }) }) - - let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings]), availableAppIcons, currentAppIconName.get(), statePromise.get()) - |> map { presentationData, sharedData, availableAppIcons, currentAppIconName, state -> (ItemListControllerState, (ItemListNodeState, ThemeSettingsControllerEntry.ItemGenerationArguments)) in + + let savedThemes = telegramThemes(postbox: context.account.postbox, network: context.account.network) + |> map { themes -> [PresentationThemeReference] in + return themes.map { .cloud($0) } + } + + let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings]), savedThemes, availableAppIcons, currentAppIconName.get(), statePromise.get()) + |> map { presentationData, sharedData, savedThemes, availableAppIcons, currentAppIconName, state -> (ItemListControllerState, (ItemListNodeState, ThemeSettingsControllerEntry.ItemGenerationArguments)) in let settings = (sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings] as? PresentationThemeSettings) ?? PresentationThemeSettings.defaultSettings let fontSize = settings.fontSize @@ -446,8 +453,15 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The wallpaper = settings.chatWallpaper } - let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Appearance_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back)) - let listState = ItemListNodeState(entries: themeSettingsControllerEntries(presentationData: presentationData, theme: theme, themeReference: settings.theme, themeSpecificAccentColors: settings.themeSpecificAccentColors, autoNightSettings: settings.automaticThemeSwitchSetting, strings: presentationData.strings, wallpaper: wallpaper, fontSize: fontSize, dateTimeFormat: dateTimeFormat, largeEmoji: largeEmoji, disableAnimations: disableAnimations, availableAppIcons: availableAppIcons, currentAppIconName: currentAppIconName), style: .blocks, ensureVisibleItemTag: focusOnItemTag, animateChanges: false) + let rightNavigationButton = ItemListNavigationButton(content: .icon(.action), style: .regular, enabled: true, action: { + moreImpl?() + }) + + let defaultThemes: [PresentationThemeReference] = [.builtin(.dayClassic), .builtin(.day), .builtin(.night), .builtin(.nightAccent)] + let availableThemes = defaultThemes + savedThemes + + let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Appearance_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back)) + let listState = ItemListNodeState(entries: themeSettingsControllerEntries(presentationData: presentationData, theme: theme, themeReference: settings.theme, themeSpecificAccentColors: settings.themeSpecificAccentColors, availableThemes: availableThemes, autoNightSettings: settings.automaticThemeSwitchSetting, strings: presentationData.strings, wallpaper: wallpaper, fontSize: fontSize, dateTimeFormat: dateTimeFormat, largeEmoji: largeEmoji, disableAnimations: disableAnimations, availableAppIcons: availableAppIcons, currentAppIconName: currentAppIconName), style: .blocks, ensureVisibleItemTag: focusOnItemTag, animateChanges: false) return (controllerState, (listState, arguments)) } @@ -460,6 +474,46 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The presentControllerImpl = { [weak controller] c, a in controller?.present(c, in: .window(.root), with: a) } + moreImpl = { [weak controller] in + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let actionSheet = ActionSheetController(presentationTheme: presentationData.theme) + var items: [ActionSheetItem] = [] + items.append(ActionSheetButtonItem(title: "Create New Theme", color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + + var randomId: Int64 = 0 + arc4random_buf(&randomId, 8) + let path = NSTemporaryDirectory() + "\(randomId)" + + let encoder = PresentationThemeEncoder() + let theme = presentationData.theme + guard let strings = try? encoder.encode(theme), let _ = try? strings.write(toFile: path, atomically: true, encoding: .utf8) else { + return + } + + let id = arc4random64() + let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: id), previewRepresentations: [], immediateThumbnailData: nil, mimeType: "application/x-tgtheme-ios", size: nil, attributes: [.FileName(fileName: "\(theme.name.string).tgios-theme")]) + let message = EnqueueMessage.message(text: "", attributes: [], mediaReference: .standalone(media: file), replyToMessageId: nil, localGroupingKey: nil) + + let _ = enqueueMessages(account: context.account, peerId: context.account.peerId, messages: [message]).start() + + presentControllerImpl?(textAlertController(context: context, title: nil, text: "A new theme template has been added to your Saved Messages.", actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Settings_SavedMessages, action: { + if let controller = controller, let navigationController = controller.navigationController as? NavigationController { + context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(context.account.peerId))) + } + }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})], actionLayout: .vertical), nil) + })) + items.append(ActionSheetButtonItem(title: "Upload Theme", color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + + })) + actionSheet.setItemGroups([ActionSheetItemGroup(items:items), ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ])]) + presentControllerImpl?(actionSheet, nil) + } return controller } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsThemeItem.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsThemeItem.swift index 40b13f8db3..b5845a82ae 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsThemeItem.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsThemeItem.swift @@ -21,7 +21,7 @@ private func generateBorderImage(theme: PresentationTheme, bordered: Bool, selec let lineWidth: CGFloat if selected { var accentColor = theme.list.itemAccentColor - if accentColor.rgb == UIColor.white.rgb { + if accentColor.rgb == 0xffffff { accentColor = UIColor(rgb: 0x999999) } context.setStrokeColor(accentColor.cgColor) @@ -40,32 +40,40 @@ private func generateBorderImage(theme: PresentationTheme, bordered: Bool, selec private func generateThemeIconImage(theme: PresentationThemeReference, accentColor: UIColor?) -> UIImage { return generateImage(CGSize(width: 98.0, height: 62.0), rotatedContext: { size, context in - guard case let .builtin(theme) = theme else { - return - } let bounds = CGRect(origin: CGPoint(), size: size) let background: UIColor let incomingFill: UIColor let outgoingFill: UIColor switch theme { - case .dayClassic: + case let .builtin(theme): + switch theme { + case .dayClassic: + background = UIColor(rgb: 0xd6e2ee) + incomingFill = UIColor(rgb: 0xffffff) + outgoingFill = UIColor(rgb: 0xe1ffc7) + case .day: + background = .white + incomingFill = UIColor(rgb: 0xd5dde6) + outgoingFill = accentColor ?? UIColor(rgb: 0x007aff) + case .night: + background = UIColor(rgb: 0x000000) + incomingFill = UIColor(rgb: 0x1f1f1f) + outgoingFill = accentColor ?? UIColor(rgb: 0x313131) + case .nightAccent: + let accentColor = accentColor ?? UIColor(rgb: 0x007aff) + background = accentColor.withMultiplied(hue: 1.024, saturation: 0.573, brightness: 0.18) + incomingFill = accentColor.withMultiplied(hue: 1.024, saturation: 0.585, brightness: 0.25) + outgoingFill = accentColor.withMultiplied(hue: 1.019, saturation: 0.731, brightness: 0.59) + } + case let .cloud(theme): + background = UIColor(rgb: 0xd6e2ee) + incomingFill = UIColor(rgb: 0xffffff) + outgoingFill = UIColor(rgb: 0xe1ffc7) + default: background = UIColor(rgb: 0xd6e2ee) incomingFill = UIColor(rgb: 0xffffff) outgoingFill = UIColor(rgb: 0xe1ffc7) - case .day: - background = .white - incomingFill = UIColor(rgb: 0xd5dde6) - outgoingFill = accentColor ?? UIColor(rgb: 0x007aff) - case .night: - background = UIColor(rgb: 0x000000) - incomingFill = UIColor(rgb: 0x1f1f1f) - outgoingFill = accentColor ?? UIColor(rgb: 0x313131) - case .nightAccent: - let accentColor = accentColor ?? UIColor(rgb: 0x007aff) - background = accentColor.withMultiplied(hue: 1.024, saturation: 0.573, brightness: 0.18) - incomingFill = accentColor.withMultiplied(hue: 1.024, saturation: 0.585, brightness: 0.25) - outgoingFill = accentColor.withMultiplied(hue: 1.019, saturation: 0.731, brightness: 0.59) } context.setFillColor(background.cgColor) @@ -336,19 +344,22 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode { } let name: String? - if case let .builtin(theme) = theme { - switch theme { - case .dayClassic: - name = item.strings.Appearance_ThemeCarouselClassic - case .day: - name = item.strings.Appearance_ThemeCarouselDay - case .night: - name = item.strings.Appearance_ThemeCarouselNewNight - case .nightAccent: - name = item.strings.Appearance_ThemeCarouselTintedNight - } - } else { - name = nil + switch theme { + case let .builtin(theme): + switch theme { + case .dayClassic: + name = item.strings.Appearance_ThemeCarouselClassic + case .day: + name = item.strings.Appearance_ThemeCarouselDay + case .night: + name = item.strings.Appearance_ThemeCarouselNewNight + case .nightAccent: + name = item.strings.Appearance_ThemeCarouselTintedNight + } + case let .cloud(theme): + name = theme.title + default: + name = nil } if let name = name { diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index dafa6f5ca6..d8ef0a30d7 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -238,6 +238,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[967122427] = { return Api.Update.parse_updateNewScheduledMessage($0) } dict[-1870238482] = { return Api.Update.parse_updateDeleteScheduledMessages($0) } dict[357013699] = { return Api.Update.parse_updateMessageReactions($0) } + dict[-2112423005] = { return Api.Update.parse_updateTheme($0) } dict[1558266229] = { return Api.PopularContact.parse_popularContact($0) } dict[-373643672] = { return Api.FolderPeer.parse_folderPeer($0) } dict[367766557] = { return Api.ChannelParticipant.parse_channelParticipant($0) } @@ -263,6 +264,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-748155807] = { return Api.ContactStatus.parse_contactStatus($0) } dict[1679398724] = { return Api.SecureFile.parse_secureFileEmpty($0) } dict[-534283678] = { return Api.SecureFile.parse_secureFile($0) } + dict[-199313886] = { return Api.account.Themes.parse_themesNotModified($0) } + dict[2137482273] = { return Api.account.Themes.parse_themes($0) } dict[236446268] = { return Api.PhotoSize.parse_photoSizeEmpty($0) } dict[2009052699] = { return Api.PhotoSize.parse_photoSize($0) } dict[-374917894] = { return Api.PhotoSize.parse_photoCachedSize($0) } @@ -274,6 +277,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1359533640] = { return Api.messages.FoundStickerSets.parse_foundStickerSets($0) } dict[471437699] = { return Api.account.WallPapers.parse_wallPapersNotModified($0) } dict[1881892265] = { return Api.account.WallPapers.parse_wallPapers($0) } + dict[1012306921] = { return Api.InputTheme.parse_inputTheme($0) } + dict[-175567375] = { return Api.InputTheme.parse_inputThemeSlug($0) } dict[1158290442] = { return Api.messages.FoundGifs.parse_foundGifs($0) } dict[-1199954735] = { return Api.MessageReactions.parse_messageReactions($0) } dict[-1132476723] = { return Api.FileLocation.parse_fileLocationToBeDeprecated($0) } @@ -420,7 +425,6 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-2128640689] = { return Api.account.SentEmailCode.parse_sentEmailCode($0) } dict[-1038136962] = { return Api.EncryptedFile.parse_encryptedFileEmpty($0) } dict[1248893260] = { return Api.EncryptedFile.parse_encryptedFile($0) } - dict[-557924733] = { return Api.CodeSettings.parse_codeSettings($0) } dict[-391902247] = { return Api.SecureValueError.parse_secureValueErrorData($0) } dict[12467706] = { return Api.SecureValueError.parse_secureValueErrorFrontSide($0) } dict[-2037765467] = { return Api.SecureValueError.parse_secureValueErrorReverseSide($0) } @@ -715,6 +719,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1363483106] = { return Api.DialogPeer.parse_dialogPeerFolder($0) } dict[-104284986] = { return Api.WebDocument.parse_webDocumentNoProxy($0) } dict[475467473] = { return Api.WebDocument.parse_webDocument($0) } + dict[1211967244] = { return Api.Theme.parse_themeDocumentNotModified($0) } + dict[1464749545] = { return Api.Theme.parse_theme($0) } dict[-1290580579] = { return Api.contacts.Found.parse_found($0) } dict[-368018716] = { return Api.ChannelAdminLogEventsFilter.parse_channelAdminLogEventsFilter($0) } dict[1889961234] = { return Api.PeerNotifySettings.parse_peerNotifySettingsEmpty($0) } @@ -944,6 +950,8 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.SecureFile: _1.serialize(buffer, boxed) + case let _1 as Api.account.Themes: + _1.serialize(buffer, boxed) case let _1 as Api.PhotoSize: _1.serialize(buffer, boxed) case let _1 as Api.messages.Stickers: @@ -954,6 +962,8 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.account.WallPapers: _1.serialize(buffer, boxed) + case let _1 as Api.InputTheme: + _1.serialize(buffer, boxed) case let _1 as Api.messages.FoundGifs: _1.serialize(buffer, boxed) case let _1 as Api.MessageReactions: @@ -1068,8 +1078,6 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.EncryptedFile: _1.serialize(buffer, boxed) - case let _1 as Api.CodeSettings: - _1.serialize(buffer, boxed) case let _1 as Api.SecureValueError: _1.serialize(buffer, boxed) case let _1 as Api.NotifyPeer: @@ -1302,6 +1310,8 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.WebDocument: _1.serialize(buffer, boxed) + case let _1 as Api.Theme: + _1.serialize(buffer, boxed) case let _1 as Api.contacts.Found: _1.serialize(buffer, boxed) case let _1 as Api.ChannelAdminLogEventsFilter: diff --git a/submodules/TelegramApi/Sources/Api1.swift b/submodules/TelegramApi/Sources/Api1.swift index 6ed0942678..7575cc1f7c 100644 --- a/submodules/TelegramApi/Sources/Api1.swift +++ b/submodules/TelegramApi/Sources/Api1.swift @@ -4022,6 +4022,7 @@ public extension Api { case updateNewScheduledMessage(message: Api.Message) case updateDeleteScheduledMessages(peer: Api.Peer, messages: [Int32]) case updateMessageReactions(peer: Api.Peer, msgId: Int32, reactions: Api.MessageReactions) + case updateTheme(theme: Api.Theme) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -4648,6 +4649,12 @@ public extension Api { serializeInt32(msgId, buffer: buffer, boxed: false) reactions.serialize(buffer, true) break + case .updateTheme(let theme): + if boxed { + buffer.appendInt32(-2112423005) + } + theme.serialize(buffer, true) + break } } @@ -4801,6 +4808,8 @@ public extension Api { return ("updateDeleteScheduledMessages", [("peer", peer), ("messages", messages)]) case .updateMessageReactions(let peer, let msgId, let reactions): return ("updateMessageReactions", [("peer", peer), ("msgId", msgId), ("reactions", reactions)]) + case .updateTheme(let theme): + return ("updateTheme", [("theme", theme)]) } } @@ -6067,6 +6076,19 @@ public extension Api { return nil } } + public static func parse_updateTheme(_ reader: BufferReader) -> Update? { + var _1: Api.Theme? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Theme + } + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateTheme(theme: _1!) + } + else { + return nil + } + } } public enum PopularContact: TypeConstructorDescription { @@ -6964,6 +6986,64 @@ public extension Api { } } + } + public enum InputTheme: TypeConstructorDescription { + case inputTheme(id: Int64, accessHash: Int64) + case inputThemeSlug(slug: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputTheme(let id, let accessHash): + if boxed { + buffer.appendInt32(1012306921) + } + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + break + case .inputThemeSlug(let slug): + if boxed { + buffer.appendInt32(-175567375) + } + serializeString(slug, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .inputTheme(let id, let accessHash): + return ("inputTheme", [("id", id), ("accessHash", accessHash)]) + case .inputThemeSlug(let slug): + return ("inputThemeSlug", [("slug", slug)]) + } + } + + public static func parse_inputTheme(_ reader: BufferReader) -> InputTheme? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.InputTheme.inputTheme(id: _1!, accessHash: _2!) + } + else { + return nil + } + } + public static func parse_inputThemeSlug(_ reader: BufferReader) -> InputTheme? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.InputTheme.inputThemeSlug(slug: _1!) + } + else { + return nil + } + } + } public enum MessageReactions: TypeConstructorDescription { case messageReactions(flags: Int32, results: [Api.ReactionCount]) @@ -10430,40 +10510,6 @@ public extension Api { } } - } - public enum CodeSettings: TypeConstructorDescription { - case codeSettings(flags: Int32) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .codeSettings(let flags): - if boxed { - buffer.appendInt32(-557924733) - } - serializeInt32(flags, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .codeSettings(let flags): - return ("codeSettings", [("flags", flags)]) - } - } - - public static func parse_codeSettings(_ reader: BufferReader) -> CodeSettings? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.CodeSettings.codeSettings(flags: _1!) - } - else { - return nil - } - } - } public enum SecureValueError: TypeConstructorDescription { case secureValueErrorData(type: Api.SecureValueType, dataHash: Buffer, field: String, text: String) @@ -18178,6 +18224,74 @@ public extension Api { } } + } + public enum Theme: TypeConstructorDescription { + case themeDocumentNotModified + case theme(flags: Int32, id: Int64, accessHash: Int64, slug: String, title: String, document: Api.Document) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .themeDocumentNotModified: + if boxed { + buffer.appendInt32(1211967244) + } + + break + case .theme(let flags, let id, let accessHash, let slug, let title, let document): + if boxed { + buffer.appendInt32(1464749545) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + serializeString(slug, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + document.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .themeDocumentNotModified: + return ("themeDocumentNotModified", []) + case .theme(let flags, let id, let accessHash, let slug, let title, let document): + return ("theme", [("flags", flags), ("id", id), ("accessHash", accessHash), ("slug", slug), ("title", title), ("document", document)]) + } + } + + public static func parse_themeDocumentNotModified(_ reader: BufferReader) -> Theme? { + return Api.Theme.themeDocumentNotModified + } + public static func parse_theme(_ reader: BufferReader) -> Theme? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + var _4: String? + _4 = parseString(reader) + var _5: String? + _5 = parseString(reader) + var _6: Api.Document? + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.Document + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.Theme.theme(flags: _1!, id: _2!, accessHash: _3!, slug: _4!, title: _5!, document: _6!) + } + else { + return nil + } + } + } public enum ChannelAdminLogEventsFilter: TypeConstructorDescription { case channelAdminLogEventsFilter(flags: Int32) diff --git a/submodules/TelegramApi/Sources/Api3.swift b/submodules/TelegramApi/Sources/Api3.swift index a5b0363284..16344d8e88 100644 --- a/submodules/TelegramApi/Sources/Api3.swift +++ b/submodules/TelegramApi/Sources/Api3.swift @@ -415,6 +415,62 @@ public struct account { } } + } + public enum Themes: TypeConstructorDescription { + case themesNotModified + case themes(hash: Int32, themes: [Api.Theme]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .themesNotModified: + if boxed { + buffer.appendInt32(-199313886) + } + + break + case .themes(let hash, let themes): + if boxed { + buffer.appendInt32(2137482273) + } + serializeInt32(hash, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(themes.count)) + for item in themes { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .themesNotModified: + return ("themesNotModified", []) + case .themes(let hash, let themes): + return ("themes", [("hash", hash), ("themes", themes)]) + } + } + + public static func parse_themesNotModified(_ reader: BufferReader) -> Themes? { + return Api.account.Themes.themesNotModified + } + public static func parse_themes(_ reader: BufferReader) -> Themes? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.Theme]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Theme.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.account.Themes.themes(hash: _1!, themes: _2!) + } + else { + return nil + } + } + } public enum WallPapers: TypeConstructorDescription { case wallPapersNotModified @@ -2868,6 +2924,33 @@ public extension Api { }) } + public static func forwardMessages(flags: Int32, fromPeer: Api.InputPeer, id: [Int32], randomId: [Int64], toPeer: Api.InputPeer, scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-637606386) + serializeInt32(flags, buffer: buffer, boxed: false) + fromPeer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomId.count)) + for item in randomId { + serializeInt64(item, buffer: buffer, boxed: false) + } + toPeer.serialize(buffer, true) + if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "messages.forwardMessages", parameters: [("flags", flags), ("fromPeer", fromPeer), ("id", id), ("randomId", randomId), ("toPeer", toPeer), ("scheduleDate", scheduleDate)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } + public static func sendInlineBotResult(flags: Int32, peer: Api.InputPeer, replyToMsgId: Int32?, randomId: Int64, queryId: Int64, id: String, scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() buffer.appendInt32(570955184) @@ -2888,6 +2971,31 @@ public extension Api { }) } + public static func editMessage(flags: Int32, peer: Api.InputPeer, id: Int32, message: String?, media: Api.InputMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1224152952) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 11) != 0 {serializeString(message!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 14) != 0 {media!.serialize(buffer, true)} + if Int(flags) & Int(1 << 2) != 0 {replyMarkup!.serialize(buffer, true)} + if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 15) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "messages.editMessage", parameters: [("flags", flags), ("peer", peer), ("id", id), ("message", message), ("media", media), ("replyMarkup", replyMarkup), ("entities", entities), ("scheduleDate", scheduleDate)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } + public static func sendMultiMedia(flags: Int32, peer: Api.InputPeer, replyToMsgId: Int32?, multiMedia: [Api.InputSingleMedia], scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() buffer.appendInt32(-872345397) @@ -2910,33 +3018,6 @@ public extension Api { }) } - public static func forwardMessages(flags: Int32, fromPeer: Api.InputPeer, id: [Int32], randomId: [Int64], toPeer: Api.InputPeer, scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-637606386) - serializeInt32(flags, buffer: buffer, boxed: false) - fromPeer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(randomId.count)) - for item in randomId { - serializeInt64(item, buffer: buffer, boxed: false) - } - toPeer.serialize(buffer, true) - if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "messages.forwardMessages", parameters: [("flags", flags), ("fromPeer", fromPeer), ("id", id), ("randomId", randomId), ("toPeer", toPeer), ("scheduleDate", scheduleDate)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } - public static func getScheduledHistory(peer: Api.InputPeer, hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() buffer.appendInt32(-490575781) @@ -3009,31 +3090,6 @@ public extension Api { }) } - public static func editMessage(flags: Int32, peer: Api.InputPeer, id: Int32, message: String?, media: Api.InputMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1224152952) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(id, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 11) != 0 {serializeString(message!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 14) != 0 {media!.serialize(buffer, true)} - if Int(flags) & Int(1 << 2) != 0 {replyMarkup!.serialize(buffer, true)} - if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 15) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "messages.editMessage", parameters: [("flags", flags), ("peer", peer), ("id", id), ("message", message), ("media", media), ("replyMarkup", replyMarkup), ("entities", entities), ("scheduleDate", scheduleDate)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } - public static func sendReaction(flags: Int32, peer: Api.InputPeer, msgId: Int32, reaction: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() buffer.appendInt32(627641572) @@ -5628,6 +5684,119 @@ public extension Api { return result }) } + + public static func uploadTheme(flags: Int32, file: Api.InputFile, thumb: Api.InputFile?, fileName: String, mimeType: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(473805619) + serializeInt32(flags, buffer: buffer, boxed: false) + file.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {thumb!.serialize(buffer, true)} + serializeString(fileName, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.uploadTheme", parameters: [("flags", flags), ("file", file), ("thumb", thumb), ("fileName", fileName), ("mimeType", mimeType)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Document? in + let reader = BufferReader(buffer) + var result: Api.Document? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Document + } + return result + }) + } + + public static func createTheme(slug: String, title: String, document: Api.InputDocument) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(729808255) + serializeString(slug, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + document.serialize(buffer, true) + return (FunctionDescription(name: "account.createTheme", parameters: [("slug", slug), ("title", title), ("document", document)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Theme? in + let reader = BufferReader(buffer) + var result: Api.Theme? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Theme + } + return result + }) + } + + public static func updateTheme(flags: Int32, theme: Api.InputTheme, slug: String?, title: String?, document: Api.InputDocument?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-124161674) + serializeInt32(flags, buffer: buffer, boxed: false) + theme.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeString(slug!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeString(title!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {document!.serialize(buffer, true)} + return (FunctionDescription(name: "account.updateTheme", parameters: [("flags", flags), ("theme", theme), ("slug", slug), ("title", title), ("document", document)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Theme? in + let reader = BufferReader(buffer) + var result: Api.Theme? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Theme + } + return result + }) + } + + public static func saveTheme(theme: Api.InputTheme, unsave: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-229175188) + theme.serialize(buffer, true) + unsave.serialize(buffer, true) + return (FunctionDescription(name: "account.saveTheme", parameters: [("theme", theme), ("unsave", unsave)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } + + public static func installTheme(format: String, theme: Api.InputTheme) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-520600676) + serializeString(format, buffer: buffer, boxed: false) + theme.serialize(buffer, true) + return (FunctionDescription(name: "account.installTheme", parameters: [("format", format), ("theme", theme)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } + + public static func getTheme(format: String, theme: Api.InputTheme, documentId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1919060949) + serializeString(format, buffer: buffer, boxed: false) + theme.serialize(buffer, true) + serializeInt64(documentId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.getTheme", parameters: [("format", format), ("theme", theme), ("documentId", documentId)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Theme? in + let reader = BufferReader(buffer) + var result: Api.Theme? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Theme + } + return result + }) + } + + public static func getThemes(format: String, hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(676939512) + serializeString(format, buffer: buffer, boxed: false) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.getThemes", parameters: [("format", format), ("hash", hash)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.Themes? in + let reader = BufferReader(buffer) + var result: Api.account.Themes? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.Themes + } + return result + }) + } } public struct langpack { public static func getLangPack(langPack: String, langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { diff --git a/submodules/TelegramCore/TelegramCore/AccountManager.swift b/submodules/TelegramCore/TelegramCore/AccountManager.swift index e7053b31fe..2f37b84baf 100644 --- a/submodules/TelegramCore/TelegramCore/AccountManager.swift +++ b/submodules/TelegramCore/TelegramCore/AccountManager.swift @@ -122,12 +122,14 @@ private var declaredEncodables: Void = { declareEncodable(CachedLocalizationInfos.self, f: { CachedLocalizationInfos(decoder: $0) }) declareEncodable(CachedSecureIdConfiguration.self, f: { CachedSecureIdConfiguration(decoder: $0) }) declareEncodable(CachedWallpapersConfiguration.self, f: { CachedWallpapersConfiguration(decoder: $0) }) + declareEncodable(CachedThemesConfiguration.self, f: { CachedThemesConfiguration(decoder: $0) }) declareEncodable(SynchronizeGroupedPeersOperation.self, f: { SynchronizeGroupedPeersOperation(decoder: $0) }) declareEncodable(ContentPrivacySettings.self, f: { ContentPrivacySettings(decoder: $0) }) declareEncodable(TelegramDeviceContactImportedData.self, f: { TelegramDeviceContactImportedData(decoder: $0) }) declareEncodable(SecureFileMediaResource.self, f: { SecureFileMediaResource(decoder: $0) }) declareEncodable(CachedStickerQueryResult.self, f: { CachedStickerQueryResult(decoder: $0) }) declareEncodable(TelegramWallpaper.self, f: { TelegramWallpaper(decoder: $0) }) + declareEncodable(TelegramTheme.self, f: { TelegramTheme(decoder: $0) }) declareEncodable(SynchronizeMarkAllUnseenPersonalMessagesOperation.self, f: { SynchronizeMarkAllUnseenPersonalMessagesOperation(decoder: $0) }) declareEncodable(SynchronizeAppLogEventsOperation.self, f: { SynchronizeAppLogEventsOperation(decoder: $0) }) declareEncodable(CachedRecentPeers.self, f: { CachedRecentPeers(decoder: $0) }) diff --git a/submodules/TelegramCore/TelegramCore/AccountStateManagementUtils.swift b/submodules/TelegramCore/TelegramCore/AccountStateManagementUtils.swift index 2539e788bb..9b79249143 100644 --- a/submodules/TelegramCore/TelegramCore/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/TelegramCore/AccountStateManagementUtils.swift @@ -1314,6 +1314,8 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo messageIds.append(MessageId(peerId: peer.peerId, namespace: Namespaces.Message.ScheduledCloud, id: message)) } updatedState.deleteMessages(messageIds) + case let .updateTheme(theme): + break default: break } diff --git a/submodules/TelegramCore/TelegramCore/EnqueueMessage.swift b/submodules/TelegramCore/TelegramCore/EnqueueMessage.swift index bddb8cf8f4..5d6bb1e239 100644 --- a/submodules/TelegramCore/TelegramCore/EnqueueMessage.swift +++ b/submodules/TelegramCore/TelegramCore/EnqueueMessage.swift @@ -398,7 +398,6 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId, } } - storeMessages.append(StoreMessage(peerId: peerId, namespace: messageNamespace, globallyUniqueId: randomId, groupingKey: localGroupingKey, timestamp: effectiveTimestamp, flags: flags, tags: tags, globalTags: globalTags, localTags: localTags, forwardInfo: nil, authorId: authorId, text: text, attributes: attributes, media: mediaList)) case let .forward(source, grouping, requestedAttributes): let sourceMessage = transaction.getMessage(source) diff --git a/submodules/TelegramCore/TelegramCore/Namespaces.swift b/submodules/TelegramCore/TelegramCore/Namespaces.swift index 1b6fc51ff3..65af9b9e27 100644 --- a/submodules/TelegramCore/TelegramCore/Namespaces.swift +++ b/submodules/TelegramCore/TelegramCore/Namespaces.swift @@ -59,6 +59,7 @@ public struct Namespaces { public static let CloudWallpapers: Int32 = 6 public static let CloudSavedStickers: Int32 = 7 public static let RecentlyUsedHashtags: Int32 = 8 + public static let CloudThemes: Int32 = 9 } struct CachedItemCollection { @@ -70,6 +71,7 @@ public struct Namespaces { public static let cachedStickerQueryResults: Int8 = 5 public static let cachedSecureIdConfiguration: Int8 = 6 public static let cachedWallpapersConfiguration: Int8 = 7 + public static let cachedThemesConfiguration: Int8 = 7 } struct UnorderedItemList { diff --git a/submodules/TelegramCore/TelegramCore/Theme.swift b/submodules/TelegramCore/TelegramCore/Theme.swift new file mode 100644 index 0000000000..96e936ede8 --- /dev/null +++ b/submodules/TelegramCore/TelegramCore/Theme.swift @@ -0,0 +1,90 @@ +import Foundation +#if os(macOS) +import PostboxMac +import SwiftSignalKitMac +import TelegramApiMac +#else +import Postbox +import SwiftSignalKit +import TelegramApi +#endif + +public final class TelegramTheme: OrderedItemListEntryContents, Equatable { + public let id: Int64 + public let accessHash: Int64 + public let slug: String + public let title: String + public let file: TelegramMediaFile + public let isCreator: Bool + public let isDefault: Bool + + public init(id: Int64, accessHash: Int64, slug: String, title: String, file: TelegramMediaFile, isCreator: Bool, isDefault: Bool) { + self.id = id + self.accessHash = accessHash + self.slug = slug + self.title = title + self.file = file + self.isCreator = isCreator + self.isDefault = isDefault + } + + public init(decoder: PostboxDecoder) { + self.id = decoder.decodeInt64ForKey("id", orElse: 0) + self.accessHash = decoder.decodeInt64ForKey("accessHash", orElse: 0) + self.slug = decoder.decodeStringForKey("slug", orElse: "") + self.title = decoder.decodeStringForKey("title", orElse: "") + self.file = decoder.decodeObjectForKey("file", decoder: { TelegramMediaFile(decoder: $0) }) as! TelegramMediaFile + self.isCreator = decoder.decodeInt32ForKey("isCreator", orElse: 0) != 0 + self.isDefault = decoder.decodeInt32ForKey("isDefault", orElse: 0) != 0 + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt64(self.id, forKey: "id") + encoder.encodeInt64(self.accessHash, forKey: "accessHash") + encoder.encodeString(self.slug, forKey: "slug") + encoder.encodeString(self.title, forKey: "title") + encoder.encodeObject(self.file, forKey: "file") + encoder.encodeInt32(self.isCreator ? 1 : 0, forKey: "isCreator") + encoder.encodeInt32(self.isDefault ? 1 : 0, forKey: "isDefault") + } + + public static func ==(lhs: TelegramTheme, rhs: TelegramTheme) -> Bool { + if lhs.id != rhs.id { + return false + } + if lhs.accessHash != rhs.accessHash { + return false + } + if lhs.slug != rhs.slug { + return false + } + if lhs.title != rhs.title { + return false + } + if lhs.file.id != rhs.file.id { + return false + } + if lhs.isCreator != rhs.isCreator { + return false + } + if lhs.isDefault != rhs.isDefault { + return false + } + return true + } +} + +extension TelegramTheme { + convenience init?(apiTheme: Api.Theme) { + switch apiTheme { + case let .theme(flags, id, accessHash, slug, title, document): + if let file = telegramMediaFileFromApiDocument(document) { + self.init(id: id, accessHash: accessHash, slug: slug, title: title, file: file, isCreator: (flags & 1 << 0) != 0, isDefault: (flags & 1 << 1) != 0) + } else { + return nil + } + default: + return nil + } + } +} diff --git a/submodules/TelegramCore/TelegramCore/Themes.swift b/submodules/TelegramCore/TelegramCore/Themes.swift new file mode 100644 index 0000000000..bfbc5c46fb --- /dev/null +++ b/submodules/TelegramCore/TelegramCore/Themes.swift @@ -0,0 +1,226 @@ +import Foundation +#if os(macOS) +import PostboxMac +import SwiftSignalKitMac +import TelegramApiMac +#else +import Postbox +import SwiftSignalKit +import TelegramApi +#endif + +final class CachedThemesConfiguration: PostboxCoding { + let hash: Int32 + + init(hash: Int32) { + self.hash = hash + } + + init(decoder: PostboxDecoder) { + self.hash = decoder.decodeInt32ForKey("hash", orElse: 0) + } + + func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt32(self.hash, forKey: "hash") + } +} + +#if os(macOS) +private let themeFormat = "macos" +private let themeFileExtension = "palette" +#else +private let themeFormat = "ios" +private let themeFileExtension = "tgios-theme" +#endif + +public func telegramThemes(postbox: Postbox, network: Network, forceUpdate: Bool = false) -> Signal<[TelegramTheme], NoError> { + let fetch: ([TelegramTheme]?, Int32?) -> Signal<[TelegramTheme], NoError> = { current, hash in + network.request(Api.functions.account.getThemes(format: themeFormat, hash: hash ?? 0)) + |> retryRequest + |> mapToSignal { result -> Signal<([TelegramTheme], Int32), NoError> in + switch result { + case let .themes(hash, themes): + let result = themes.compactMap { TelegramTheme(apiTheme: $0) } + if result == current { + return .complete() + } else { + return .single((result, hash)) + } + case .themesNotModified: + return .complete() + } + } + |> mapToSignal { items, hash -> Signal<[TelegramTheme], NoError> in + return postbox.transaction { transaction -> [TelegramTheme] in + var entries: [OrderedItemListEntry] = [] + for item in items { + var intValue = Int32(entries.count) + let id = MemoryBuffer(data: Data(bytes: &intValue, count: 4)) + entries.append(OrderedItemListEntry(id: id, contents: item)) + } + transaction.replaceOrderedItemListItems(collectionId: Namespaces.OrderedItemList.CloudThemes, items: entries) + transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedThemesConfiguration, key: ValueBoxKey(length: 0)), entry: CachedThemesConfiguration(hash: hash), collectionSpec: ItemCacheCollectionSpec(lowWaterItemCount: 1, highWaterItemCount: 1)) + return items + } + } + } + + if forceUpdate { + return fetch(nil, nil) + } else { + return postbox.transaction { transaction -> ([TelegramTheme], Int32?) in + let configuration = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedThemesConfiguration, key: ValueBoxKey(length: 0))) as? CachedThemesConfiguration + let items = transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudThemes) + return (items.map { $0.contents as! TelegramTheme }, configuration?.hash) + } + |> mapToSignal { current, hash -> Signal<[TelegramTheme], NoError> in + return .single(current) + |> then(fetch(current, hash)) + } + } +} + +public enum GetThemeError { + case generic +} + +public func getTheme(account: Account, slug: String) -> Signal { + return account.network.request(Api.functions.account.getTheme(format: themeFormat, theme: .inputThemeSlug(slug: slug), documentId: 0)) + |> mapError { _ -> GetThemeError in return .generic } + |> mapToSignal { theme -> Signal in + if let theme = TelegramTheme(apiTheme: theme) { + return .single(theme) + } else { + return .fail(.generic) + } + } +} + +public func saveTheme(account: Account, theme: TelegramTheme) -> Signal { + return saveUnsaveTheme(account: account, theme: theme, unsave: false) +} + +public func deleteTheme(account: Account, theme: TelegramTheme) -> Signal { + return saveUnsaveTheme(account: account, theme: theme, unsave: true) +} + +private func saveUnsaveTheme(account: Account, theme: TelegramTheme, unsave: Bool) -> Signal { + return account.network.request(Api.functions.account.saveTheme(theme: Api.InputTheme.inputTheme(id: theme.id, accessHash: theme.accessHash), unsave: unsave ? Api.Bool.boolTrue : Api.Bool.boolFalse)) + |> `catch` { _ -> Signal in + return .complete() + } + |> mapToSignal { _ -> Signal in + return .single(Void()) + } +} + +public func installTheme(account: Account, theme: TelegramTheme) -> Signal { + return account.network.request(Api.functions.account.installTheme(format: themeFormat, theme: Api.InputTheme.inputTheme(id: theme.id, accessHash: theme.accessHash))) + |> `catch` { _ -> Signal in + return .complete() + } + |> mapToSignal { _ -> Signal in + return .complete() + } +} + +public enum UploadThemeStatus { + case progress(Float) + case complete(TelegramMediaFile) +} + +public enum UploadThemeError { + case generic +} + +private struct UploadedThemeData { + fileprivate let resource: MediaResource + fileprivate let content: UploadedThemeDataContent +} + +private enum UploadedThemeDataContent { + case result(MultipartUploadResult) + case error +} + +private func uploadedTheme(postbox: Postbox, network: Network, resource: MediaResource) -> Signal { + return multipartUpload(network: network, postbox: postbox, source: .resource(.standalone(resource: resource)), encrypt: false, tag: TelegramMediaResourceFetchTag(statsCategory: .file), hintFileSize: nil, hintFileIsLarge: false) + |> map { result -> UploadedThemeData in + return UploadedThemeData(resource: resource, content: .result(result)) + } + |> `catch` { _ -> Signal in + return .single(UploadedThemeData(resource: resource, content: .error)) + } +} + +private func uploadTheme(account: Account, resource: MediaResource) -> Signal { + let fileName = "theme.\(themeFileExtension)" + let mimeType = "application/x-tgtheme-\(themeFormat)" + + return uploadedTheme(postbox: account.postbox, network: account.network, resource: resource) + |> mapError { _ -> UploadThemeError in return .generic } + |> mapToSignal { result -> Signal<(UploadThemeStatus, MediaResource?), UploadThemeError> in + switch result.content { + case .error: + return .fail(.generic) + case let .result(resultData): + switch resultData { + case let .progress(progress): + return .single((.progress(progress), result.resource)) + case let .inputFile(file): + return account.network.request(Api.functions.account.uploadTheme(flags: 0, file: file, thumb: nil, fileName: fileName, mimeType: mimeType)) + |> mapError { _ in return UploadThemeError.generic } + |> mapToSignal { document -> Signal<(UploadThemeStatus, MediaResource?), UploadThemeError> in + if let file = telegramMediaFileFromApiDocument(document) { + return .single((.complete(file), result.resource)) + } else { + return .fail(.generic) + } + } + default: + return .fail(.generic) + } + } + } + |> map { result, _ -> UploadThemeStatus in + return result + } +} + +public enum CreateThemeError { + case generic +} + +public func createTheme(account: Account, resource: MediaResource, title: String, slug: String) -> Signal { + return uploadTheme(account: account, resource: resource) + |> mapError { _ in return CreateThemeError.generic } + |> mapToSignal { status -> Signal in + switch status { + case let .complete(file): + if let resource = file.resource as? CloudDocumentMediaResource { + return account.network.request(Api.functions.account.createTheme(slug: slug, title: title, document: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference)))) + |> mapError { _ in return CreateThemeError.generic } + |> mapToSignal { apiTheme -> Signal in + if let theme = TelegramTheme(apiTheme: apiTheme) { + return .single(theme) + } else { + return .fail(.generic) + } + } + } + else { + return .fail(.generic) + } + default: + return .complete() + } + } +} + +public func updateTheme(account: Account, theme: TelegramTheme, title: String?, resource: MediaResource?) -> Signal { + guard title != nil || resource != nil else { + return .complete() + } + + return .never() +} diff --git a/submodules/TelegramCore/TelegramCore/Wallpapers.swift b/submodules/TelegramCore/TelegramCore/Wallpapers.swift index a84b61d740..565dce34e7 100644 --- a/submodules/TelegramCore/TelegramCore/Wallpapers.swift +++ b/submodules/TelegramCore/TelegramCore/Wallpapers.swift @@ -26,7 +26,7 @@ final class CachedWallpapersConfiguration: PostboxCoding { } public func telegramWallpapers(postbox: Postbox, network: Network, forceUpdate: Bool = false) -> Signal<[TelegramWallpaper], NoError> { - let fetch: ([TelegramWallpaper]?, Int32?) -> Signal<[TelegramWallpaper], NoError> = { list, hash in + let fetch: ([TelegramWallpaper]?, Int32?) -> Signal<[TelegramWallpaper], NoError> = { current, hash in network.request(Api.functions.account.getWallPapers(hash: hash ?? 0)) |> retryRequest |> mapToSignal { result -> Signal<([TelegramWallpaper], Int32), NoError> in @@ -49,7 +49,7 @@ public func telegramWallpapers(postbox: Postbox, network: Network, forceUpdate: items.append(.builtin(WallpaperSettings())) } - if items == list { + if items == current { return .complete() } else { return .single((items, hash)) @@ -85,9 +85,9 @@ public func telegramWallpapers(postbox: Postbox, network: Network, forceUpdate: return (items.map { $0.contents as! TelegramWallpaper }, configuration?.hash) } } - |> mapToSignal { list, hash -> Signal<[TelegramWallpaper], NoError> in - return .single(list) - |> then(fetch(list, hash)) + |> mapToSignal { current, hash -> Signal<[TelegramWallpaper], NoError> in + return .single(current) + |> then(fetch(current, hash)) } } } @@ -101,7 +101,7 @@ public enum UploadWallpaperError { case generic } -public struct UploadedWallpaperData { +private struct UploadedWallpaperData { fileprivate let resource: MediaResource fileprivate let content: UploadedWallpaperDataContent } @@ -134,9 +134,9 @@ public func uploadWallpaper(account: Account, resource: MediaResource, mimeType: return .single((.progress(progress), result.resource)) case let .inputFile(file): return account.network.request(Api.functions.account.uploadWallPaper(file: file, mimeType: mimeType, settings: apiWallpaperSettings(settings))) - |> mapError {_ in return UploadWallpaperError.generic} - |> mapToSignal { wallpaper -> Signal<(UploadWallpaperStatus, MediaResource?), UploadWallpaperError> in - return .single((.complete(TelegramWallpaper(apiWallpaper: wallpaper)), result.resource)) + |> mapError { _ in return UploadWallpaperError.generic } + |> map { wallpaper -> (UploadWallpaperStatus, MediaResource?) in + return (.complete(TelegramWallpaper(apiWallpaper: wallpaper)), result.resource) } default: return .fail(.generic) diff --git a/submodules/TelegramCore/TelegramCore_Xcode.xcodeproj/project.pbxproj b/submodules/TelegramCore/TelegramCore_Xcode.xcodeproj/project.pbxproj index 93fa992764..3f4ff9c444 100644 --- a/submodules/TelegramCore/TelegramCore_Xcode.xcodeproj/project.pbxproj +++ b/submodules/TelegramCore/TelegramCore_Xcode.xcodeproj/project.pbxproj @@ -22,6 +22,8 @@ 0962E66F21B6147600245FD9 /* AppConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0962E66E21B6147600245FD9 /* AppConfiguration.swift */; }; 0962E67521B6437600245FD9 /* SplitTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0962E67421B6437600245FD9 /* SplitTest.swift */; }; 0962E68121BAA20E00245FD9 /* SearchBotsConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0962E68021BAA20E00245FD9 /* SearchBotsConfiguration.swift */; }; + 09B4A9B4230FB70B005C2E08 /* Themes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4A9B3230FB70B005C2E08 /* Themes.swift */; }; + 09B4A9B6230FBB2B005C2E08 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4A9B5230FBB2B005C2E08 /* Theme.swift */; }; 09EC0DE922C6825D00E7185B /* AppUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09EC0DE822C6825D00E7185B /* AppUpdate.swift */; }; 09EDAD382213120C0012A50B /* AutodownloadSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09EDAD372213120C0012A50B /* AutodownloadSettings.swift */; }; 09EDAD3A22131D010012A50B /* ManagedAutodownloadSettingsUpdates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09EDAD3922131D010012A50B /* ManagedAutodownloadSettingsUpdates.swift */; }; @@ -808,6 +810,8 @@ 0962E66E21B6147600245FD9 /* AppConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfiguration.swift; sourceTree = ""; }; 0962E67421B6437600245FD9 /* SplitTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitTest.swift; sourceTree = ""; }; 0962E68021BAA20E00245FD9 /* SearchBotsConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchBotsConfiguration.swift; sourceTree = ""; }; + 09B4A9B3230FB70B005C2E08 /* Themes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Themes.swift; sourceTree = ""; }; + 09B4A9B5230FBB2B005C2E08 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; 09EC0DE822C6825D00E7185B /* AppUpdate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUpdate.swift; sourceTree = ""; }; 09EDAD372213120C0012A50B /* AutodownloadSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutodownloadSettings.swift; sourceTree = ""; }; 09EDAD3922131D010012A50B /* ManagedAutodownloadSettingsUpdates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedAutodownloadSettingsUpdates.swift; sourceTree = ""; }; @@ -1270,6 +1274,7 @@ 0962E66E21B6147600245FD9 /* AppConfiguration.swift */, 0962E68021BAA20E00245FD9 /* SearchBotsConfiguration.swift */, 09EDAD372213120C0012A50B /* AutodownloadSettings.swift */, + 09B4A9B5230FBB2B005C2E08 /* Theme.swift */, ); name = Settings; sourceTree = ""; @@ -1667,6 +1672,7 @@ D099E221229420D600561B75 /* BlockedPeersContext.swift */, D098907E22942E3B0053F151 /* ActiveSessionsContext.swift */, D069257022D8B526002FC021 /* SecretChatSettings.swift */, + 09B4A9B3230FB70B005C2E08 /* Themes.swift */, ); name = Settings; sourceTree = ""; @@ -2148,6 +2154,7 @@ D03B0CE01D62249100955575 /* StoreMessage_Telegram.swift in Sources */, D03E416C2304D5B30049C28B /* ValidateAddressNameInteractive.swift in Sources */, D08774FE1E3E3A3500A97350 /* GlobalNotificationSettings.swift in Sources */, + 09B4A9B6230FBB2B005C2E08 /* Theme.swift in Sources */, D023E67821540624008C27D1 /* UpdateMessageMedia.swift in Sources */, D0EE7FC420986C5300981319 /* SecureIdPassportRegistrationValue.swift in Sources */, D03B0CB91D62233400955575 /* Either.swift in Sources */, @@ -2155,6 +2162,7 @@ 0962E66D21B5C56F00245FD9 /* JSON.swift in Sources */, D0338740223BD48B007A2CE4 /* ContactsSettings.swift in Sources */, D03B0CBD1D62234300955575 /* Regex.swift in Sources */, + 09B4A9B4230FB70B005C2E08 /* Themes.swift in Sources */, D00BDA191EE593D600C64C5E /* TelegramChannelAdminRights.swift in Sources */, 09EDAD3A22131D010012A50B /* ManagedAutodownloadSettingsUpdates.swift in Sources */, D0EA188220D3D2B1001AEE19 /* RemoteStorageConfiguration.swift in Sources */, diff --git a/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift index 2bac77cd0c..d369fb0062 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift @@ -9,6 +9,7 @@ private func makeDarkPresentationTheme(accentColor: UIColor, baseColor: Presenta let badgeFillColor: UIColor let badgeTextColor: UIColor + let secondaryBadgeTextColor: UIColor let outgoingBubbleFillColor: UIColor let outgoingBubbleHighlightedFillColor: UIColor let outgoingScamColor: UIColor @@ -23,6 +24,7 @@ private func makeDarkPresentationTheme(accentColor: UIColor, baseColor: Presenta if accentColor.rgb == UIColor.white.rgb { badgeFillColor = .white badgeTextColor = .black + secondaryBadgeTextColor = .black outgoingBubbleFillColor = UIColor(rgb: 0x313131) outgoingBubbleHighlightedFillColor = UIColor(rgb: 0x464646) outgoingScamColor = destructiveColor @@ -41,6 +43,7 @@ private func makeDarkPresentationTheme(accentColor: UIColor, baseColor: Presenta if lightness > 0.7 { outgoingScamColor = .black + secondaryBadgeTextColor = .black outgoingPrimaryTextColor = .black outgoingSecondaryTextColor = UIColor(rgb: 0x000000, alpha: 0.5) outgoingLinkTextColor = .black @@ -48,6 +51,7 @@ private func makeDarkPresentationTheme(accentColor: UIColor, baseColor: Presenta } else { outgoingScamColor = .white + secondaryBadgeTextColor = .white outgoingPrimaryTextColor = .white outgoingSecondaryTextColor = UIColor(rgb: 0xffffff, alpha: 0.5) outgoingLinkTextColor = .white @@ -153,7 +157,7 @@ private func makeDarkPresentationTheme(accentColor: UIColor, baseColor: Presenta itemCheckColors: PresentationThemeFillStrokeForeground( fillColor: accentColor, strokeColor: UIColor(rgb: 0xffffff, alpha: 0.5), - foregroundColor: badgeTextColor + foregroundColor: secondaryBadgeTextColor ), controlSecondaryColor: UIColor(rgb: 0xffffff, alpha: 0.5), freeInputField: PresentationInputFieldTheme( @@ -188,7 +192,7 @@ private func makeDarkPresentationTheme(accentColor: UIColor, baseColor: Presenta failedForegroundColor: .white, muteIconColor: UIColor(rgb: 0x8e8e92), unreadBadgeActiveBackgroundColor: accentColor, - unreadBadgeActiveTextColor: badgeTextColor, + unreadBadgeActiveTextColor: secondaryBadgeTextColor, unreadBadgeInactiveBackgroundColor: UIColor(rgb: 0x666666), unreadBadgeInactiveTextColor:UIColor(rgb: 0x000000), pinnedBadgeColor: UIColor(rgb: 0x767677), @@ -207,7 +211,7 @@ private func makeDarkPresentationTheme(accentColor: UIColor, baseColor: Presenta let message = PresentationThemeChatMessage( incoming: PresentationThemePartedColors(bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x262628), highlightedFill: UIColor(rgb: 0x353539), stroke: UIColor(rgb: 0x262628)), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x262628), highlightedFill: UIColor(rgb: 0x353539), stroke: UIColor(rgb: 0x262628))), primaryTextColor: .white, secondaryTextColor: UIColor(rgb: 0xffffff, alpha: 0.5), linkTextColor: accentColor, linkHighlightColor: accentColor.withAlphaComponent(0.5), scamColor: destructiveColor, textHighlightColor: UIColor(rgb: 0xffe438), accentTextColor: accentColor, accentControlColor: accentColor, mediaActiveControlColor: accentColor, mediaInactiveControlColor: accentColor.withAlphaComponent(0.4), pendingActivityColor: UIColor(rgb: 0xffffff, alpha: 0.5), fileTitleColor: accentColor, fileDescriptionColor: UIColor(rgb: 0xffffff, alpha: 0.5), fileDurationColor: UIColor(rgb: 0xffffff, alpha: 0.5), mediaPlaceholderColor: UIColor(rgb: 0x1f1f1f).mixedWith(.white, alpha: 0.05), polls: PresentationThemeChatBubblePolls(radioButton: UIColor(rgb: 0x737373), radioProgress: accentColor, highlight: accentColor.withAlphaComponent(0.12), separator: UIColor(rgb: 0x000000), bar: accentColor), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x000000, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x000000, alpha: 0.5)), actionButtonsStrokeColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xb2b2b2, alpha: 0.18)), actionButtonsTextColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xffffff))), - outgoing: PresentationThemePartedColors(bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: outgoingBubbleFillColor, highlightedFill: outgoingBubbleHighlightedFillColor, stroke: outgoingBubbleFillColor), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: outgoingBubbleFillColor, highlightedFill: outgoingBubbleHighlightedFillColor, stroke: outgoingBubbleFillColor)), primaryTextColor: outgoingPrimaryTextColor, secondaryTextColor: outgoingSecondaryTextColor, linkTextColor: outgoingLinkTextColor, linkHighlightColor: UIColor.white.withAlphaComponent(0.5), scamColor: outgoingScamColor, textHighlightColor: UIColor(rgb: 0xffe438), accentTextColor: .white, accentControlColor: .white, mediaActiveControlColor: .white, mediaInactiveControlColor: UIColor(rgb: 0xffffff, alpha: 0.3), pendingActivityColor: UIColor(rgb: 0xffffff, alpha: 0.5), fileTitleColor: .white, fileDescriptionColor: UIColor(rgb: 0xffffff, alpha: 0.5), fileDurationColor: UIColor(rgb: 0xffffff, alpha: 0.5), mediaPlaceholderColor: UIColor(rgb: 0x313131).mixedWith(.white, alpha: 0.05), polls: PresentationThemeChatBubblePolls(radioButton: .white, radioProgress: .white, highlight: UIColor(white: 1.0, alpha: 0.12), separator: UIColor(white: 1.0, alpha: 0.3), bar: .white), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x000000, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x000000, alpha: 0.5)), actionButtonsStrokeColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xb2b2b2, alpha: 0.18)), actionButtonsTextColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xffffff))), + outgoing: PresentationThemePartedColors(bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: outgoingBubbleFillColor, highlightedFill: outgoingBubbleHighlightedFillColor, stroke: outgoingBubbleFillColor), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: outgoingBubbleFillColor, highlightedFill: outgoingBubbleHighlightedFillColor, stroke: outgoingBubbleFillColor)), primaryTextColor: outgoingPrimaryTextColor, secondaryTextColor: outgoingSecondaryTextColor, linkTextColor: outgoingLinkTextColor, linkHighlightColor: UIColor.white.withAlphaComponent(0.5), scamColor: outgoingScamColor, textHighlightColor: UIColor(rgb: 0xffe438), accentTextColor: outgoingPrimaryTextColor, accentControlColor: outgoingPrimaryTextColor, mediaActiveControlColor: outgoingPrimaryTextColor, mediaInactiveControlColor: outgoingSecondaryTextColor, pendingActivityColor: outgoingSecondaryTextColor, fileTitleColor: outgoingPrimaryTextColor, fileDescriptionColor: outgoingSecondaryTextColor, fileDurationColor: outgoingSecondaryTextColor, mediaPlaceholderColor: UIColor(rgb: 0x313131).mixedWith(.white, alpha: 0.05), polls: PresentationThemeChatBubblePolls(radioButton: outgoingPrimaryTextColor, radioProgress: outgoingPrimaryTextColor, highlight: outgoingPrimaryTextColor.withAlphaComponent(0.12), separator: outgoingSecondaryTextColor, bar: outgoingPrimaryTextColor), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x000000, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x000000, alpha: 0.5)), actionButtonsStrokeColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xb2b2b2, alpha: 0.18)), actionButtonsTextColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xffffff))), freeform: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x1f1f1f), highlightedFill: UIColor(rgb: 0x2a2a2a), stroke: UIColor(rgb: 0x1f1f1f)), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x1f1f1f), highlightedFill: UIColor(rgb: 0x2a2a2a), stroke: UIColor(rgb: 0x1f1f1f))), infoPrimaryTextColor: .white, infoLinkTextColor: accentColor, @@ -320,7 +324,7 @@ private func makeDarkPresentationTheme(accentColor: UIColor, baseColor: Presenta inputPlaceholderColor: UIColor(rgb: 0x8f8f8f), inputTextColor: .white, inputClearButtonColor: UIColor(rgb: 0x8f8f8f), - checkContentColor: .white + checkContentColor: secondaryBadgeTextColor ) let contextMenu = PresentationThemeContextMenu( diff --git a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift index 92adff2cb6..5f62d281b1 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift @@ -16,16 +16,22 @@ private func makeDefaultDayPresentationTheme(accentColor: UIColor, serviceBackgr let outgoingCheckColor: UIColor let outgoingControlColor: UIColor let outgoingBubbleFillColor: UIColor - if accentColor.lightness > 0.7 { + let outgoingBubbleStrokeColor: UIColor + if accentColor.lightness > 0.705 { outgoingPrimaryTextColor = .black - outgoingSecondaryTextColor = UIColor(rgb: 0x000000, alpha: 0.65) + outgoingSecondaryTextColor = UIColor(rgb: 0x000000, alpha: 0.55) outgoingLinkTextColor = .black outgoingCheckColor = .black outgoingControlColor = outgoingPrimaryTextColor outgoingBubbleFillColor = accentColor + if outgoingBubbleFillColor.rgb == 0xffffff { + outgoingBubbleStrokeColor = UIColor(rgb: 0xc8c7cc) + } else { + outgoingBubbleStrokeColor = outgoingBubbleFillColor + } let hsv = accentColor.hsv - accentColor = UIColor(hue: hsv.0, saturation: hsv.1 * 1.1, brightness: min(hsv.2, 0.7), alpha: 1.0) + accentColor = UIColor(hue: hsv.0, saturation: min(1.0, hsv.1 * 1.1), brightness: min(hsv.2, 0.6), alpha: 1.0) } else { outgoingPrimaryTextColor = .white outgoingSecondaryTextColor = UIColor(rgb: 0xffffff, alpha: 0.65) @@ -33,6 +39,7 @@ private func makeDefaultDayPresentationTheme(accentColor: UIColor, serviceBackgr outgoingCheckColor = .white outgoingControlColor = outgoingPrimaryTextColor outgoingBubbleFillColor = accentColor + outgoingBubbleStrokeColor = outgoingBubbleFillColor } let rootTabBar = PresentationThemeRootTabBar( @@ -201,8 +208,8 @@ private func makeDefaultDayPresentationTheme(accentColor: UIColor, serviceBackgr ) let messageDay = PresentationThemeChatMessage( - incoming: PresentationThemePartedColors(bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xffffff), highlightedFill: UIColor(rgb: 0xdadade), stroke: UIColor(rgb: 0xffffff)), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xf1f1f4), highlightedFill: UIColor(rgb: 0xdadade), stroke: UIColor(rgb: 0xf1f1f4))), primaryTextColor: .black, secondaryTextColor: UIColor(rgb: 0x525252, alpha: 0.6), linkTextColor: UIColor(rgb: 0x004bad), linkHighlightColor: accentColor.withAlphaComponent(0.3), scamColor: destructiveColor, textHighlightColor: UIColor(rgb: 0xffe438), accentTextColor: accentColor, accentControlColor: accentColor, mediaActiveControlColor: accentColor, mediaInactiveControlColor: UIColor(rgb: 0xcacaca), pendingActivityColor: UIColor(rgb: 0x525252, alpha: 0.6), fileTitleColor: UIColor(rgb: 0x0b8bed), fileDescriptionColor: UIColor(rgb: 0x999999), fileDurationColor: UIColor(rgb: 0x525252, alpha: 0.6), mediaPlaceholderColor: UIColor(rgb: 0xffffff).withMultipliedBrightnessBy(0.95), polls: PresentationThemeChatBubblePolls(radioButton: UIColor(rgb: 0xc8c7cc), radioProgress: accentColor, highlight: accentColor.withAlphaComponent(0.12), separator: UIColor(rgb: 0xc8c7cc), bar: accentColor), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: serviceBackgroundColor, withoutWallpaper: UIColor(rgb: 0xffffff, alpha: 0.8)), actionButtonsStrokeColor: PresentationThemeVariableColor(withWallpaper: .clear, withoutWallpaper: accentColor), actionButtonsTextColor: PresentationThemeVariableColor(withWallpaper: .white, withoutWallpaper: accentColor)), - outgoing: PresentationThemePartedColors(bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: outgoingBubbleFillColor, highlightedFill: outgoingBubbleFillColor.withMultipliedBrightnessBy(0.7), stroke: outgoingBubbleFillColor), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: outgoingBubbleFillColor, highlightedFill: outgoingBubbleFillColor.withMultipliedBrightnessBy(0.7), stroke: outgoingBubbleFillColor)), primaryTextColor: outgoingPrimaryTextColor, secondaryTextColor: outgoingSecondaryTextColor, linkTextColor: outgoingLinkTextColor, linkHighlightColor: UIColor(rgb: 0xffffff, alpha: 0.3), scamColor: outgoingPrimaryTextColor, textHighlightColor: UIColor(rgb: 0xffe438), accentTextColor: outgoingPrimaryTextColor, accentControlColor: outgoingPrimaryTextColor, mediaActiveControlColor: outgoingPrimaryTextColor, mediaInactiveControlColor: outgoingSecondaryTextColor, pendingActivityColor: outgoingSecondaryTextColor, fileTitleColor: outgoingPrimaryTextColor, fileDescriptionColor: outgoingSecondaryTextColor, fileDurationColor: outgoingSecondaryTextColor, mediaPlaceholderColor: accentColor.withMultipliedBrightnessBy(0.95), polls: PresentationThemeChatBubblePolls(radioButton: outgoingSecondaryTextColor, radioProgress: outgoingPrimaryTextColor, highlight: outgoingPrimaryTextColor.withAlphaComponent(0.12), separator: outgoingSecondaryTextColor, bar: outgoingPrimaryTextColor), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: serviceBackgroundColor, withoutWallpaper: UIColor(rgb: 0xffffff, alpha: 0.8)), actionButtonsStrokeColor: PresentationThemeVariableColor(withWallpaper: .clear, withoutWallpaper: accentColor), actionButtonsTextColor: PresentationThemeVariableColor(withWallpaper: .white, withoutWallpaper: accentColor)), + incoming: PresentationThemePartedColors(bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xffffff), highlightedFill: UIColor(rgb: 0xdadade), stroke: UIColor(rgb: 0xffffff)), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xf1f1f4), highlightedFill: UIColor(rgb: 0xdadade), stroke: UIColor(rgb: 0xf1f1f4))), primaryTextColor: .black, secondaryTextColor: UIColor(rgb: 0x525252, alpha: 0.6), linkTextColor: UIColor(rgb: 0x004bad), linkHighlightColor: accentColor.withAlphaComponent(0.3), scamColor: destructiveColor, textHighlightColor: UIColor(rgb: 0xffe438), accentTextColor: accentColor, accentControlColor: accentColor, mediaActiveControlColor: accentColor, mediaInactiveControlColor: UIColor(rgb: 0xcacaca), pendingActivityColor: UIColor(rgb: 0x525252, alpha: 0.6), fileTitleColor: accentColor, fileDescriptionColor: UIColor(rgb: 0x999999), fileDurationColor: UIColor(rgb: 0x525252, alpha: 0.6), mediaPlaceholderColor: UIColor(rgb: 0xffffff).withMultipliedBrightnessBy(0.95), polls: PresentationThemeChatBubblePolls(radioButton: UIColor(rgb: 0xc8c7cc), radioProgress: accentColor, highlight: accentColor.withAlphaComponent(0.12), separator: UIColor(rgb: 0xc8c7cc), bar: accentColor), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: serviceBackgroundColor, withoutWallpaper: UIColor(rgb: 0xffffff, alpha: 0.8)), actionButtonsStrokeColor: PresentationThemeVariableColor(withWallpaper: .clear, withoutWallpaper: accentColor), actionButtonsTextColor: PresentationThemeVariableColor(withWallpaper: .white, withoutWallpaper: accentColor)), + outgoing: PresentationThemePartedColors(bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: outgoingBubbleFillColor, highlightedFill: outgoingBubbleFillColor.withMultipliedBrightnessBy(0.7), stroke: outgoingBubbleFillColor), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: outgoingBubbleFillColor, highlightedFill: outgoingBubbleFillColor.withMultipliedBrightnessBy(0.7), stroke: outgoingBubbleStrokeColor)), primaryTextColor: outgoingPrimaryTextColor, secondaryTextColor: outgoingSecondaryTextColor, linkTextColor: outgoingLinkTextColor, linkHighlightColor: UIColor(rgb: 0xffffff, alpha: 0.3), scamColor: outgoingPrimaryTextColor, textHighlightColor: UIColor(rgb: 0xffe438), accentTextColor: outgoingPrimaryTextColor, accentControlColor: outgoingPrimaryTextColor, mediaActiveControlColor: outgoingPrimaryTextColor, mediaInactiveControlColor: outgoingSecondaryTextColor, pendingActivityColor: outgoingSecondaryTextColor, fileTitleColor: outgoingPrimaryTextColor, fileDescriptionColor: outgoingSecondaryTextColor, fileDurationColor: outgoingSecondaryTextColor, mediaPlaceholderColor: accentColor.withMultipliedBrightnessBy(0.95), polls: PresentationThemeChatBubblePolls(radioButton: outgoingSecondaryTextColor, radioProgress: outgoingPrimaryTextColor, highlight: outgoingPrimaryTextColor.withAlphaComponent(0.12), separator: outgoingSecondaryTextColor, bar: outgoingPrimaryTextColor), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: serviceBackgroundColor, withoutWallpaper: UIColor(rgb: 0xffffff, alpha: 0.8)), actionButtonsStrokeColor: PresentationThemeVariableColor(withWallpaper: .clear, withoutWallpaper: accentColor), actionButtonsTextColor: PresentationThemeVariableColor(withWallpaper: .white, withoutWallpaper: accentColor)), freeform: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xE5E5EA), highlightedFill: UIColor(rgb: 0xDADADE), stroke: UIColor(rgb: 0xE5E5EA)), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xE5E5EA), highlightedFill: UIColor(rgb: 0xdadade), stroke: UIColor(rgb: 0xE5E5EA))), infoPrimaryTextColor: UIColor(rgb: 0x000000), infoLinkTextColor: UIColor(rgb: 0x004bad), diff --git a/submodules/TelegramPresentationData/Sources/MakePresentationTheme.swift b/submodules/TelegramPresentationData/Sources/MakePresentationTheme.swift index b1d7061085..fb65ef16a7 100644 --- a/submodules/TelegramPresentationData/Sources/MakePresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/MakePresentationTheme.swift @@ -16,6 +16,10 @@ public func makePresentationTheme(themeReference: PresentationThemeReference, ac case .day: theme = makeDefaultDayPresentationTheme(accentColor: accentColor, serviceBackgroundColor: serviceBackgroundColor, baseColor: baseColor, day: true, preview: preview) } + case let .local(resource): + theme = makeDefaultDayPresentationTheme(serviceBackgroundColor: serviceBackgroundColor, baseColor: baseColor, day: false, preview: preview) + case let .cloud(info): + theme = makeDefaultDayPresentationTheme(serviceBackgroundColor: serviceBackgroundColor, baseColor: baseColor, day: false, preview: preview) } return theme } diff --git a/submodules/TelegramPresentationData/Sources/PresentationData.swift b/submodules/TelegramPresentationData/Sources/PresentationData.swift index ae7fa81dfa..bee81a651c 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationData.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationData.swift @@ -338,6 +338,8 @@ private func automaticThemeShouldSwitchNow(_ parameters: AutomaticThemeSwitchPar default: break } + default: + return false } switch parameters.trigger { case .none: diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift index 10be928c5c..84a91cad0f 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift @@ -2,19 +2,6 @@ import Foundation import UIKit import Display -private func generateArrowImage(_ theme: PresentationTheme) -> UIImage? { - return generateImage(CGSize(width: 7.0, height: 13.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setStrokeColor(theme.list.disclosureArrowColor.cgColor) - context.setLineWidth(1.98) - context.setLineCap(.round) - context.setLineJoin(.round) - context.translateBy(x: 1.0, y: 1.0) - - let _ = try? drawSvgPath(context, path: "M0,0 L4.79819816,5.27801798 L4.79819816,5.27801798 C4.91262453,5.40388698 4.91262453,5.59611302 4.79819816,5.72198202 L0,11 S ") - }) -} - public func generateItemListCheckIcon(color: UIColor) -> UIImage? { return generateImage(CGSize(width: 12.0, height: 10.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) @@ -29,19 +16,14 @@ public func generateItemListCheckIcon(color: UIColor) -> UIImage? { } public func generateItemListPlusIcon(_ color: UIColor) -> UIImage? { - return generateImage(CGSize(width: 18.0, height: 18.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(color.cgColor) - context.setLineCap(.round) - let lineWidth: CGFloat = 2.0 - context.fill(CGRect(x: floorToScreenPixels((18.0 - lineWidth) / 2.0), y: 0.0, width: lineWidth, height: 18.0)) - context.fill(CGRect(x: 0.0, y: floorToScreenPixels((18.0 - lineWidth) / 2.0), width: 18.0, height: lineWidth)) - }) + return generateTintedImage(image: UIImage(bundleImageName: "Chat List/AddIcon"), color: color) } public struct PresentationResourcesItemList { public static func disclosureArrowImage(_ theme: PresentationTheme) -> UIImage? { - return theme.image(PresentationResourceKey.itemListDisclosureArrow.rawValue, generateArrowImage) + return theme.image(PresentationResourceKey.itemListDisclosureArrow.rawValue, { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Item List/DisclosureArrow"), color: theme.list.disclosureArrowColor) + }) } public static func checkIconImage(_ theme: PresentationTheme) -> UIImage? { @@ -113,9 +95,9 @@ public struct PresentationResourcesItemList { }) } - public static func addExceptionIcon(_ theme: PresentationTheme) -> UIImage? { + public static func addChannelIcon(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.itemListAddExceptionIcon.rawValue, { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Item List/AddExceptionIcon"), color: theme.list.itemAccentColor) + return generateTintedImage(image: UIImage(bundleImageName: "Item List/AddChannelIcon"), color: theme.list.itemAccentColor) }) } diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesRootController.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesRootController.swift index 2ef6886592..ba4958757d 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesRootController.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesRootController.swift @@ -48,7 +48,7 @@ public struct PresentationResourcesRootController { public static func navigationCallIcon(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.navigationCallIcon.rawValue, { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Info/CallButton"), color: theme.rootController.navigationBar.accentTextColor) + return generateTintedImage(image: UIImage(bundleImageName: "Call List/CallIcon"), color: theme.rootController.navigationBar.accentTextColor) }) } @@ -66,14 +66,15 @@ public struct PresentationResourcesRootController { public static func navigationCompactSearchIcon(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.navigationCompactSearchIcon.rawValue, { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat List/SearchIcon"), color: theme.rootController.navigationBar.accentTextColor).flatMap({ image in - let factor: CGFloat = 0.8 - let size = CGSize(width: floor(image.size.width * factor), height: floor(image.size.height * factor)) - return generateImage(size, contextGenerator: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: size)) - }) - }) + return generateTintedImage(image: UIImage(bundleImageName: "Chat List/SearchIcon"), color: theme.rootController.navigationBar.accentTextColor) +// return generateTintedImage(image: UIImage(bundleImageName: "Chat List/SearchIcon"), color: theme.rootController.navigationBar.accentTextColor).flatMap({ image in +// let factor: CGFloat = 0.8 +// let size = CGSize(width: floor(image.size.width * factor), height: floor(image.size.height * factor)) +// return generateImage(size, contextGenerator: { size, context in +// context.clear(CGRect(origin: CGPoint(), size: size)) +// context.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: size)) +// }) +// }) }) } diff --git a/submodules/TelegramUI/Images.xcassets/Call List/CallIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Call List/CallIcon.imageset/Contents.json new file mode 100644 index 0000000000..679a5bed44 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Call List/CallIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_addcall.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Call List/CallIcon.imageset/ic_addcall.pdf b/submodules/TelegramUI/Images.xcassets/Call List/CallIcon.imageset/ic_addcall.pdf new file mode 100644 index 0000000000..266479298e Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Call List/CallIcon.imageset/ic_addcall.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/PeerMutedIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/PeerMutedIcon.imageset/Contents.json index 0300ffdc7f..65904a6348 100644 --- a/submodules/TelegramUI/Images.xcassets/Chat List/PeerMutedIcon.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Chat List/PeerMutedIcon.imageset/Contents.json @@ -2,16 +2,7 @@ "images" : [ { "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "DialogList_Muted@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" + "filename" : "ic_mutedchat.pdf" } ], "info" : { diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/PeerMutedIcon.imageset/DialogList_Muted@2x.png b/submodules/TelegramUI/Images.xcassets/Chat List/PeerMutedIcon.imageset/DialogList_Muted@2x.png deleted file mode 100644 index 06f297b781..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Chat List/PeerMutedIcon.imageset/DialogList_Muted@2x.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/PeerMutedIcon.imageset/ic_mutedchat.pdf b/submodules/TelegramUI/Images.xcassets/Chat List/PeerMutedIcon.imageset/ic_mutedchat.pdf new file mode 100644 index 0000000000..830c7b2533 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat List/PeerMutedIcon.imageset/ic_mutedchat.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/ShareIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Message/ShareIcon.imageset/Contents.json index 52f3acda4b..7de0b19bb7 100644 --- a/submodules/TelegramUI/Images.xcassets/Chat/Message/ShareIcon.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/ShareIcon.imageset/Contents.json @@ -2,17 +2,7 @@ "images" : [ { "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "ConversationChannelInlineShareIcon@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "ConversationChannelInlineShareIcon@3x.png", - "scale" : "3x" + "filename" : "ic_chat_share.pdf" } ], "info" : { diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/ShareIcon.imageset/ConversationChannelInlineShareIcon@2x.png b/submodules/TelegramUI/Images.xcassets/Chat/Message/ShareIcon.imageset/ConversationChannelInlineShareIcon@2x.png deleted file mode 100644 index 1551864522..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Chat/Message/ShareIcon.imageset/ConversationChannelInlineShareIcon@2x.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/ShareIcon.imageset/ConversationChannelInlineShareIcon@3x.png b/submodules/TelegramUI/Images.xcassets/Chat/Message/ShareIcon.imageset/ConversationChannelInlineShareIcon@3x.png deleted file mode 100644 index 600f3a10be..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Chat/Message/ShareIcon.imageset/ConversationChannelInlineShareIcon@3x.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/ShareIcon.imageset/ic_chat_share.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Message/ShareIcon.imageset/ic_chat_share.pdf new file mode 100644 index 0000000000..1566462353 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Message/ShareIcon.imageset/ic_chat_share.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Item List/AddChannelIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Item List/AddChannelIcon.imageset/Contents.json new file mode 100644 index 0000000000..8033bd89cd --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Item List/AddChannelIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_addchannel.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Item List/AddChannelIcon.imageset/ic_addchannel.pdf b/submodules/TelegramUI/Images.xcassets/Item List/AddChannelIcon.imageset/ic_addchannel.pdf new file mode 100644 index 0000000000..0eb106737a Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Item List/AddChannelIcon.imageset/ic_addchannel.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Item List/AddExceptionIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Item List/AddExceptionIcon.imageset/Contents.json deleted file mode 100644 index ebc5665c79..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Item List/AddExceptionIcon.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "plus@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "plus@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Item List/AddExceptionIcon.imageset/plus@2x.png b/submodules/TelegramUI/Images.xcassets/Item List/AddExceptionIcon.imageset/plus@2x.png deleted file mode 100644 index 7fd6bbc381..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Item List/AddExceptionIcon.imageset/plus@2x.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Item List/AddExceptionIcon.imageset/plus@3x.png b/submodules/TelegramUI/Images.xcassets/Item List/AddExceptionIcon.imageset/plus@3x.png deleted file mode 100644 index 478317dd02..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Item List/AddExceptionIcon.imageset/plus@3x.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Item List/DisclosureArrow.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Item List/DisclosureArrow.imageset/Contents.json new file mode 100644 index 0000000000..73d58c942a --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Item List/DisclosureArrow.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_open.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Item List/DisclosureArrow.imageset/ic_open.pdf b/submodules/TelegramUI/Images.xcassets/Item List/DisclosureArrow.imageset/ic_open.pdf new file mode 100644 index 0000000000..058efa69b6 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Item List/DisclosureArrow.imageset/ic_open.pdf differ diff --git a/submodules/TelegramUI/TelegramUI/ChatController.swift b/submodules/TelegramUI/TelegramUI/ChatController.swift index 6fc99639b2..98434a588e 100644 --- a/submodules/TelegramUI/TelegramUI/ChatController.swift +++ b/submodules/TelegramUI/TelegramUI/ChatController.swift @@ -4267,6 +4267,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.validLayout = layout self.chatTitleView?.layout = layout + if self.hasScheduledMessages, let h = layout.inputHeight, h > 100.0 { + print() + } + self.chatDisplayNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition, listViewTransaction: { updateSizeAndInsets, additionalScrollDistance, scrollToTop in self.chatDisplayNode.historyNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, additionalScrollDistance: additionalScrollDistance, scrollToTop: scrollToTop) }) @@ -4554,11 +4558,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.chatDisplayNode.updateChatPresentationInterfaceState(updatedChatPresentationInterfaceState, transition: transition, interactive: interactive) } - if let selectionState = self.presentationInterfaceState.interfaceState.selectionState, !selectionState.selectedIds.isEmpty { - self.chatTitleView?.titleContent = .custom(self.presentationData.strings.Conversation_SelectedMessages(Int32(selectionState.selectedIds.count))) - } else { - - } +// if let selectionState = self.presentationInterfaceState.interfaceState.selectionState, !selectionState.selectedIds.isEmpty { +// self.chatTitleView?.titleContent = .custom(self.presentationData.strings.Conversation_SelectedMessages(Int32(selectionState.selectedIds.count))) +// } else { +// +// } if let button = leftNavigationButtonForChatInterfaceState(updatedChatPresentationInterfaceState, strings: updatedChatPresentationInterfaceState.strings, currentButton: self.leftNavigationButton, target: self, selector: #selector(self.leftNavigationButtonAction)) { if self.leftNavigationButton != button { @@ -4892,14 +4896,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return result } let controller = legacyAttachmentMenu(context: strongSelf.context, peer: peer, editMediaOptions: menuEditMediaOptions, saveEditedPhotos: settings.storeEditedPhotos, allowGrouping: true, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, parentController: legacyController, recentlyUsedInlineBots: strongSelf.recentlyUsedInlineBotsValue, initialCaption: inputText.string, openGallery: { - self?.presentMediaPicker(fileMode: false, editingMedia: editMediaOptions != nil, completion: { signals, silentPosting in + self?.presentMediaPicker(fileMode: false, editingMedia: editMediaOptions != nil, completion: { signals, silentPosting, scheduleTime in if !inputText.string.isEmpty { //strongSelf.clearInputText() } if editMediaOptions != nil { self?.editMessageMediaWithLegacySignals(signals) } else { - self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting) + self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime > 0 ? scheduleTime : nil) } }) }, openCamera: { [weak self] cameraView, menuController in @@ -4919,6 +4923,26 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let strongSelf = self, let (host, port, username, password, secret) = parseProxyUrl(code) { strongSelf.openResolved(ResolvedUrl.proxy(host: host, port: port, username: username, password: password, secret: secret)) } + }, presentScheduleController: { [weak self] done in + guard let strongSelf = self else { + return + } + let mode: ChatScheduleTimeControllerMode + if case let .peer(peerId) = strongSelf.presentationInterfaceState.chatLocation, peerId == strongSelf.context.account.peerId { + mode = .reminders + } else { + mode = .scheduledMessages + } + let controller = ChatScheduleTimeController(context: strongSelf.context, mode: mode, completion: { [weak self] time in + if let strongSelf = self { + done(time) + if !strongSelf.presentationInterfaceState.isScheduledMessages { + let controller = ChatControllerImpl(context: strongSelf.context, chatLocation: strongSelf.chatLocation, subject: .scheduledMessages) + (strongSelf.navigationController as? NavigationController)?.pushViewController(controller) + } + } + }) + strongSelf.present(controller, in: .window(.root)) }) } }, openFileGallery: { @@ -4941,16 +4965,34 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: strongSelf.presentationData.theme), title: nil, text: strongSelf.presentationData.strings.Chat_AttachmentMultipleFilesDisabled, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) - }, sendMessagesWithSignals: { [weak self] signals, mode in + }, presentScheduleController: { [weak self] done in + guard let strongSelf = self else { + return + } + let mode: ChatScheduleTimeControllerMode + if case let .peer(peerId) = strongSelf.presentationInterfaceState.chatLocation, peerId == strongSelf.context.account.peerId { + mode = .reminders + } else { + mode = .scheduledMessages + } + let controller = ChatScheduleTimeController(context: strongSelf.context, mode: mode, completion: { [weak self] time in + if let strongSelf = self { + done(time) + if !strongSelf.presentationInterfaceState.isScheduledMessages { + let controller = ChatControllerImpl(context: strongSelf.context, chatLocation: strongSelf.chatLocation, subject: .scheduledMessages) + (strongSelf.navigationController as? NavigationController)?.pushViewController(controller) + } + } + }) + strongSelf.present(controller, in: .window(.root)) + }, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime in if !inputText.string.isEmpty { //strongSelf.clearInputText() } if editMediaOptions != nil { self?.editMessageMediaWithLegacySignals(signals!) } else { - var silentPosting = false - //if mode == - self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting) + self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime > 0 ? scheduleTime : nil) } }, selectRecentlyUsedInlineBot: { [weak self] peer in if let strongSelf = self, let addressName = peer.addressName { @@ -4986,11 +5028,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G ActionSheetButtonItem(title: self.presentationData.strings.Conversation_FilePhotoOrVideo, action: { [weak self, weak actionSheet] in actionSheet?.dismissAnimated() if let strongSelf = self { - strongSelf.presentMediaPicker(fileMode: true, editingMedia: editingMessage, completion: { signals, silentPosting in + strongSelf.presentMediaPicker(fileMode: true, editingMedia: editingMessage, completion: { signals, silentPosting, scheduleTime in if editingMessage { self?.editMessageMediaWithLegacySignals(signals) } else { - self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting) + self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime > 0 ? scheduleTime : nil) } }) } @@ -5054,7 +5096,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.present(actionSheet, in: .window(.root)) } - private func presentMediaPicker(fileMode: Bool, editingMedia: Bool, completion: @escaping ([Any], Bool) -> Void) { + private func presentMediaPicker(fileMode: Bool, editingMedia: Bool, completion: @escaping ([Any], Bool, Int32) -> Void) { let postbox = self.context.account.postbox let _ = (self.context.sharedContext.accountManager.transaction { transaction -> Signal<(GeneratedMediaStoreSettings, SearchBotsConfiguration), NoError> in let entry = transaction.getSharedData(ApplicationSpecificSharedDataKeys.generatedMediaStoreSettings) as? GeneratedMediaStoreSettings @@ -5108,13 +5150,32 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: strongSelf.presentationData.theme), title: nil, text: strongSelf.presentationData.strings.Chat_AttachmentLimitReached, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + }, presentScheduleController: { [weak self] done in + guard let strongSelf = self else { + return + } + let mode: ChatScheduleTimeControllerMode + if case let .peer(peerId) = strongSelf.presentationInterfaceState.chatLocation, peerId == strongSelf.context.account.peerId { + mode = .reminders + } else { + mode = .scheduledMessages + } + let controller = ChatScheduleTimeController(context: strongSelf.context, mode: mode, completion: { [weak self] time in + if let strongSelf = self { + done(time) + if !strongSelf.presentationInterfaceState.isScheduledMessages { + let controller = ChatControllerImpl(context: strongSelf.context, chatLocation: strongSelf.chatLocation, subject: .scheduledMessages) + (strongSelf.navigationController as? NavigationController)?.pushViewController(controller) + } + } + }) + strongSelf.present(controller, in: .window(.root)) }) controller.descriptionGenerator = legacyAssetPickerItemGenerator() - controller.completionBlock = { [weak legacyController] signals, mode in + controller.completionBlock = { [weak legacyController] signals, silentPosting, scheduleTime in if let legacyController = legacyController { legacyController.dismiss() - var silentPosting = false - completion(signals!, silentPosting) + completion(signals!, silentPosting, scheduleTime) } } controller.dismissalBlock = { [weak legacyController] in @@ -5381,7 +5442,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } else { mode = .scheduledMessages } - let controller = ChatScheduleTimeController(context: self.context, mode: mode, completion: { [weak self] time in + let controller = ChatScheduleTimeController(context: self.context, mode: mode, dismissByTapOutside: false, completion: { [weak self] time in if let strongSelf = self { strongSelf.sendMessages(strongSelf.transformEnqueueMessages(messages, silentPosting: false, scheduleTime: time), commit: true) } diff --git a/submodules/TelegramUI/TelegramUI/ChatHistoryEntriesForView.swift b/submodules/TelegramUI/TelegramUI/ChatHistoryEntriesForView.swift index 35adb0d555..259e46dd23 100644 --- a/submodules/TelegramUI/TelegramUI/ChatHistoryEntriesForView.swift +++ b/submodules/TelegramUI/TelegramUI/ChatHistoryEntriesForView.swift @@ -20,7 +20,7 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView, } } } - + var groupBucket: [(Message, Bool, ChatHistoryMessageSelection, ChatMessageEntryAttributes)] = [] loop: for entry in view.entries { for media in entry.message.media { diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageBubbleItemNode.swift index ec7696a61a..a9dc1f3898 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageBubbleItemNode.swift @@ -400,7 +400,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode let reactionRecognizer = ReactionSwipeGestureRecognizer(target: nil, action: nil) self.reactionRecognizer = reactionRecognizer reactionRecognizer.availableReactions = { [weak self] in - guard let strongSelf = self, let item = strongSelf.item, !Namespaces.Message.allScheduled.contains(item.message.id.namespace) else { + guard let strongSelf = self, let item = strongSelf.item, !item.presentationData.isPreview && !Namespaces.Message.allScheduled.contains(item.message.id.namespace) else { return [] } if strongSelf.selectionNode != nil { diff --git a/submodules/TelegramUI/TelegramUI/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/TelegramUI/ChatRecentActionsControllerNode.swift index 0e377b5f14..3cdbbaf05d 100644 --- a/submodules/TelegramUI/TelegramUI/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatRecentActionsControllerNode.swift @@ -817,6 +817,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { }) case .wallpaper: break + case .theme: + break } } })) diff --git a/submodules/TelegramUI/TelegramUI/ChatScheduleTimeController.swift b/submodules/TelegramUI/TelegramUI/ChatScheduleTimeController.swift index 06bff51447..852511b40d 100644 --- a/submodules/TelegramUI/TelegramUI/ChatScheduleTimeController.swift +++ b/submodules/TelegramUI/TelegramUI/ChatScheduleTimeController.swift @@ -22,14 +22,16 @@ final class ChatScheduleTimeController: ViewController { private let context: AccountContext private let mode: ChatScheduleTimeControllerMode private let currentTime: Int32? + private let dismissByTapOutside: Bool private let completion: (Int32) -> Void private var presentationDataDisposable: Disposable? - init(context: AccountContext, mode: ChatScheduleTimeControllerMode, currentTime: Int32? = nil, completion: @escaping (Int32) -> Void) { + init(context: AccountContext, mode: ChatScheduleTimeControllerMode, currentTime: Int32? = nil, dismissByTapOutside: Bool = true, completion: @escaping (Int32) -> Void) { self.context = context self.mode = mode self.currentTime = currentTime + self.dismissByTapOutside = dismissByTapOutside self.completion = completion super.init(navigationBarPresentationData: nil) @@ -53,7 +55,7 @@ final class ChatScheduleTimeController: ViewController { } override public func loadDisplayNode() { - self.displayNode = ChatScheduleTimeControllerNode(context: self.context, mode: self.mode, currentTime: self.currentTime) + self.displayNode = ChatScheduleTimeControllerNode(context: self.context, mode: self.mode, currentTime: self.currentTime, dismissByTapOutside: self.dismissByTapOutside) self.controllerNode.completion = { [weak self] time in self?.completion(time + 5) self?.dismiss() diff --git a/submodules/TelegramUI/TelegramUI/ChatScheduleTimeControllerNode.swift b/submodules/TelegramUI/TelegramUI/ChatScheduleTimeControllerNode.swift index 2f1edf11ca..ee96d48d71 100644 --- a/submodules/TelegramUI/TelegramUI/ChatScheduleTimeControllerNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatScheduleTimeControllerNode.swift @@ -14,6 +14,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel private let context: AccountContext private let mode: ChatScheduleTimeControllerMode private var presentationData: PresentationData + private let dismissByTapOutside: Bool private let dimNode: ASDisplayNode private let wrappingScrollNode: ASScrollNode @@ -32,10 +33,11 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel var dismiss: (() -> Void)? var cancel: (() -> Void)? - init(context: AccountContext, mode: ChatScheduleTimeControllerMode, currentTime: Int32?) { + init(context: AccountContext, mode: ChatScheduleTimeControllerMode, currentTime: Int32?, dismissByTapOutside: Bool) { self.context = context self.mode = mode self.presentationData = context.sharedContext.currentPresentationData.with { $0 } + self.dismissByTapOutside = dismissByTapOutside self.wrappingScrollNode = ASScrollNode() self.wrappingScrollNode.view.alwaysBounceVertical = true @@ -208,7 +210,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel } @objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) { - if case .ended = recognizer.state { + if self.dismissByTapOutside, case .ended = recognizer.state { self.cancelButtonPressed() } } diff --git a/submodules/TelegramUI/TelegramUI/LegacyCamera.swift b/submodules/TelegramUI/TelegramUI/LegacyCamera.swift index 0dc3f57023..9becf57ef4 100644 --- a/submodules/TelegramUI/TelegramUI/LegacyCamera.swift +++ b/submodules/TelegramUI/TelegramUI/LegacyCamera.swift @@ -10,7 +10,7 @@ import ShareController import LegacyUI import LegacyMediaPickerUI -func presentedLegacyCamera(context: AccountContext, peer: Peer, cameraView: TGAttachmentCameraView?, menuController: TGMenuSheetController?, parentController: ViewController, editingMedia: Bool, saveCapturedPhotos: Bool, mediaGrouping: Bool, initialCaption: String, sendMessagesWithSignals: @escaping ([Any]?) -> Void, recognizedQRCode: @escaping (String) -> Void = { _ in }) { +func presentedLegacyCamera(context: AccountContext, peer: Peer, cameraView: TGAttachmentCameraView?, menuController: TGMenuSheetController?, parentController: ViewController, editingMedia: Bool, saveCapturedPhotos: Bool, mediaGrouping: Bool, initialCaption: String, sendMessagesWithSignals: @escaping ([Any]?) -> Void, recognizedQRCode: @escaping (String) -> Void = { _ in }, presentScheduleController: @escaping (@escaping (Int32) -> Void) -> Void) { let presentationData = context.sharedContext.currentPresentationData.with { $0 } let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme) legacyController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .portrait, compactSize: .portrait) diff --git a/submodules/TelegramUI/TelegramUI/OpenChatMessage.swift b/submodules/TelegramUI/TelegramUI/OpenChatMessage.swift index 83f94dad37..0a16d86dd7 100644 --- a/submodules/TelegramUI/TelegramUI/OpenChatMessage.swift +++ b/submodules/TelegramUI/TelegramUI/OpenChatMessage.swift @@ -138,7 +138,9 @@ private func chatMessageGalleryControllerData(context: AccountContext, message: if let file = galleryMedia as? TelegramMediaFile { if let fileName = file.fileName { let ext = (fileName as NSString).pathExtension.lowercased() - if ext == "wav" || ext == "opus" { + if ext == "tgios-theme" { + return .theme(file) + } else if ext == "wav" || ext == "opus" { return .audio(file) } else if ext == "json", let fileSize = file.size, fileSize < 1024 * 1024 { if let path = context.account.postbox.mediaBox.completedResourcePath(file.resource), let _ = LOTComposition(filePath: path) { @@ -365,7 +367,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { return nil })) case let .theme(media): - let controller = ThemePreviewController(context: params.context, previewTheme: makeDefaultDayPresentationTheme(accentColor: nil, serviceBackgroundColor: .black, baseColor: nil, day: true, preview: false), media: .message(message: MessageReference(params.message), media: media)) + let controller = ThemePreviewController(context: params.context, previewTheme: makeDefaultDayPresentationTheme(accentColor: nil, serviceBackgroundColor: .black, baseColor: nil, day: true, preview: false), source: .media(.message(message: MessageReference(params.message), media: media))) params.present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) } } diff --git a/submodules/TelegramUI/TelegramUI/OpenResolvedUrl.swift b/submodules/TelegramUI/TelegramUI/OpenResolvedUrl.swift index c5e09200be..30bc71d58a 100644 --- a/submodules/TelegramUI/TelegramUI/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/TelegramUI/OpenResolvedUrl.swift @@ -5,6 +5,7 @@ import Postbox import Display import SwiftSignalKit import TelegramUIPreferences +import TelegramPresentationData import AccountContext import OverlayStatusController import AlertUI @@ -236,5 +237,22 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur controller?.dismiss() }) dismissInput() + case let .theme(slug): + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + + let signal = getTheme(account: context.account, slug: slug) + let controller = OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .loading(cancelled: nil)) + present(controller, nil) + + let _ = (signal + |> deliverOnMainQueue).start(next: { [weak controller] theme in + controller?.dismiss() + let previewTheme = makePresentationTheme(themeReference: .cloud(theme), accentColor: nil, serviceBackgroundColor: .black, baseColor: nil) + let previewController = ThemePreviewController(context: context, previewTheme: previewTheme, source: .theme(theme)) + present(previewController, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + }, error: { [weak controller] error in + controller?.dismiss() + }) + dismissInput() } } diff --git a/submodules/TelegramUI/TelegramUI/OpenUrl.swift b/submodules/TelegramUI/TelegramUI/OpenUrl.swift index 0aba6f1aa2..cee562f1d5 100644 --- a/submodules/TelegramUI/TelegramUI/OpenUrl.swift +++ b/submodules/TelegramUI/TelegramUI/OpenUrl.swift @@ -538,6 +538,22 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur convertedUrl = "https://t.me/bg/\(parameter)\(mode)" } } + } else if parsedUrl.host == "addtheme" { + if let components = URLComponents(string: "/?" + query) { + var parameter: String? + if let queryItems = components.queryItems { + for queryItem in queryItems { + if let value = queryItem.value { + if queryItem.name == "slug" { + parameter = value + } + } + } + } + if let parameter = parameter { + convertedUrl = "https://t.me/addtheme/\(parameter)" + } + } } if parsedUrl.host == "resolve" { diff --git a/submodules/TelegramUIPreferences/Sources/PresentationThemeSettings.swift b/submodules/TelegramUIPreferences/Sources/PresentationThemeSettings.swift index 482392b2dc..764864694d 100644 --- a/submodules/TelegramUIPreferences/Sources/PresentationThemeSettings.swift +++ b/submodules/TelegramUIPreferences/Sources/PresentationThemeSettings.swift @@ -28,11 +28,17 @@ public struct WallpaperPresentationOptions: OptionSet { public enum PresentationThemeReference: PostboxCoding, Equatable { case builtin(PresentationBuiltinThemeReference) + case local(LocalFileMediaResource) + case cloud(TelegramTheme) public init(decoder: PostboxDecoder) { switch decoder.decodeInt32ForKey("v", orElse: 0) { case 0: self = .builtin(PresentationBuiltinThemeReference(rawValue: decoder.decodeInt32ForKey("t", orElse: 0))!) + case 1: + self = .local(decoder.decodeObjectForKey("resource", decoder: { LocalFileMediaResource(decoder: $0) }) as! LocalFileMediaResource) + case 2: + self = .cloud(decoder.decodeObjectForKey("theme", decoder: { TelegramTheme(decoder: $0) }) as! TelegramTheme) default: assertionFailure() self = .builtin(.dayClassic) @@ -44,6 +50,12 @@ public enum PresentationThemeReference: PostboxCoding, Equatable { case let .builtin(reference): encoder.encodeInt32(0, forKey: "v") encoder.encodeInt32(reference.rawValue, forKey: "t") + case let .local(resource): + encoder.encodeInt32(1, forKey: "v") + encoder.encodeObject(resource, forKey: "resource") + case let .cloud(theme): + encoder.encodeInt32(2, forKey: "v") + encoder.encodeObject(theme, forKey: "theme") } } @@ -55,6 +67,18 @@ public enum PresentationThemeReference: PostboxCoding, Equatable { } else { return false } + case let .local(lhsResource): + if case let .local(rhsResource) = rhs, lhsResource.isEqual(to: rhsResource) { + return true + } else { + return false + } + case let .cloud(lhsTheme): + if case let .cloud(rhsTheme) = rhs, lhsTheme == rhsTheme { + return true + } else { + return false + } } } @@ -65,6 +89,12 @@ public enum PresentationThemeReference: PostboxCoding, Equatable { case let .builtin(reference): namespace = 0 id = reference.rawValue + case let .local(resource): + namespace = 1 + id = 1//1reference.rawValue + case let .cloud(theme): + namespace = 2 + id = Int32(clamping: theme.id) } return (Int64(namespace) << 32) | Int64(bitPattern: UInt64(UInt32(bitPattern: id))) @@ -302,6 +332,14 @@ public struct PresentationThemeSettings: PreferencesEntry { for (_, chatWallpaper) in self.themeSpecificChatWallpapers { resources.append(contentsOf: wallpaperResources(chatWallpaper)) } + switch self.theme { + case .builtin: + break + case let .local(resource): + resources.append(resource.id) + case let .cloud(theme): + resources.append(theme.file.resource.id) + } return resources } diff --git a/submodules/UrlHandling/Sources/UrlHandling.swift b/submodules/UrlHandling/Sources/UrlHandling.swift index 439601002e..e73c413b63 100644 --- a/submodules/UrlHandling/Sources/UrlHandling.swift +++ b/submodules/UrlHandling/Sources/UrlHandling.swift @@ -31,6 +31,7 @@ public enum ParsedInternalUrl { case cancelAccountReset(phone: String, hash: String) case share(url: String?, text: String?, to: String?) case wallpaper(WallpaperUrlParameter) + case theme(String) } private enum ParsedUrl { @@ -202,6 +203,8 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? { parameter = .slug(component, options, color, intensity) } return .wallpaper(parameter) + } else if pathComponents[0] == "addtheme" { + return .theme(pathComponents[1]) } else if pathComponents.count == 3 && pathComponents[0] == "c" { if let channelId = Int32(pathComponents[1]), let messageId = Int32(pathComponents[2]) { return .privateMessage(MessageId(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId), namespace: Namespaces.Message.Cloud, id: messageId)) @@ -305,6 +308,8 @@ private func resolveInternalUrl(account: Account, url: ParsedInternalUrl) -> Sig return .single(.share(url: url, text: text, to: to)) case let .wallpaper(parameter): return .single(.wallpaper(parameter)) + case let .theme(slug): + return .single(.theme(slug)) } }