diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 2877357edf..49bb401ea7 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -7559,6 +7559,7 @@ Sorry for the inconvenience."; "Group.Username.RemoveExistingUsernamesFinalInfo" = "You have reserved too many public links. Try revoking the link from an older group or channel, or create a private one instead."; "OldChannels.TooManyCommunitiesText" = "You are a member of **%@** groups and channels. Please leave some before joining a new one or upgrade to **Telegram Premium** to double the limit to **%@** groups and channels."; +"OldChannels.TooManyCommunitiesNoPremiumText" = "You are a member of **%@** groups and channels. Please leave some before joining a new one. We are working to let you increase this limit in the future."; "OldChannels.TooManyCommunitiesFinalText" = "You are a member of **%@** groups and channels. Please leave some before joining a new one."; "OldChannels.LeaveCommunities_1" = "Leave %@ Community"; "OldChannels.LeaveCommunities_any" = "Leave %@ Communities"; diff --git a/submodules/ItemListUI/Sources/ItemListControllerNode.swift b/submodules/ItemListUI/Sources/ItemListControllerNode.swift index 693fd63e18..b92dc66bf1 100644 --- a/submodules/ItemListUI/Sources/ItemListControllerNode.swift +++ b/submodules/ItemListUI/Sources/ItemListControllerNode.swift @@ -862,6 +862,7 @@ open class ItemListControllerNode: ASDisplayNode { updateFooterItem = true } if updateFooterItem { + let hadFooter = self.footerItem != nil self.footerItem = transition.footerItem if let footerItem = transition.footerItem { let updatedNode = footerItem.node(current: self.footerItemNode) @@ -870,13 +871,27 @@ open class ItemListControllerNode: ASDisplayNode { } if self.footerItemNode !== updatedNode { self.footerItemNode = updatedNode + + let footerHeight: CGFloat if let validLayout = self.validLayout { - let _ = updatedNode.updateLayout(layout: validLayout.0, transition: .immediate) + footerHeight = updatedNode.updateLayout(layout: validLayout.0, transition: .immediate) + } else { + footerHeight = 100.0 } self.addSubnode(updatedNode) + + if !hadFooter && !transition.firstTime { + updatedNode.layer.animatePosition(from: CGPoint(x: 0.0, y: footerHeight), to: .zero, duration: 0.25, additive: true) + } } } else if let footerItemNode = self.footerItemNode { - footerItemNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak footerItemNode] _ in + let footerHeight: CGFloat + if let validLayout = self.validLayout { + footerHeight = footerItemNode.updateLayout(layout: validLayout.0, transition: .immediate) + } else { + footerHeight = 100.0 + } + footerItemNode.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: footerHeight), duration: 0.25, removeOnCompletion: false, additive: true, completion: { [weak footerItemNode] _ in footerItemNode?.removeFromSupernode() }) self.footerItemNode = nil diff --git a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift index f0ed748267..5dba73a98c 100644 --- a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift @@ -89,7 +89,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { case publicLinkHeader(PresentationTheme, String) case publicLinkAvailability(PresentationTheme, String, Bool) - case linksLimitInfo(PresentationTheme, String, Int32, Int32) + case linksLimitInfo(PresentationTheme, String, Int32, Int32, Int32, Bool) case editablePublicLink(PresentationTheme, PresentationStrings, String, String) case privateLinkHeader(PresentationTheme, String) case privateLink(PresentationTheme, ExportedInvitation?, [EnginePeer], Int32, Bool) @@ -228,12 +228,12 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { } else { return false } - case let .linksLimitInfo(lhsTheme, lhsText, lhsLimit, lhsPremiumLimit): - if case let .linksLimitInfo(rhsTheme, rhsText, rhsLimit, rhsPremiumLimit) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsLimit == rhsLimit, lhsPremiumLimit == rhsPremiumLimit { - return true - } else { - return false - } + case let .linksLimitInfo(lhsTheme, lhsText, lhsCount, lhsLimit, lhsPremiumLimit, lhsIsPremiumDisabled): + if case let .linksLimitInfo(rhsTheme, rhsText, rhsCount, rhsLimit, rhsPremiumLimit, rhsIsPremiumDisabled) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsCount == rhsCount, lhsLimit == rhsLimit, lhsPremiumLimit == rhsPremiumLimit, lhsIsPremiumDisabled == rhsIsPremiumDisabled { + return true + } else { + return false + } case let .privateLinkHeader(lhsTheme, lhsTitle): if case let .privateLinkHeader(rhsTheme, rhsTitle) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle { return true @@ -394,8 +394,8 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { let attr = NSMutableAttributedString(string: text, textColor: value ? theme.list.freeTextColor : theme.list.freeTextErrorColor) attr.addAttribute(.font, value: Font.regular(13), range: NSMakeRange(0, attr.length)) return ItemListActivityTextItem(displayActivity: value, presentationData: presentationData, text: attr, sectionId: self.section) - case let .linksLimitInfo(theme, text, limit, premiumLimit): - return IncreaseLimitHeaderItem(theme: theme, strings: presentationData.strings, icon: .link, count: limit, premiumCount: premiumLimit, text: text, sectionId: self.section) + case let .linksLimitInfo(theme, text, count, limit, premiumLimit, isPremiumDisabled): + return IncreaseLimitHeaderItem(theme: theme, strings: presentationData.strings, icon: .link, count: count, limit: limit, premiumCount: premiumLimit, text: text, isPremiumDisabled: isPremiumDisabled, sectionId: self.section) case let .privateLinkHeader(_, title): return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) case let .privateLink(_, invite, peers, importersCount, displayImporters): @@ -728,9 +728,9 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa displayAvailability = publicChannelsToRevoke != nil && !(publicChannelsToRevoke!.isEmpty) } - if displayAvailability { + if !"".isEmpty && displayAvailability { if let publicChannelsToRevoke = publicChannelsToRevoke { - entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(20)").string, limits.maxPublicLinksCount, premiumLimits.maxPublicLinksCount)) +// entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(20)").string, limits.maxPublicLinksCount, premiumLimits.maxPublicLinksCount)) var index: Int32 = 0 for peer in publicChannelsToRevoke.sorted(by: { lhs, rhs in @@ -859,7 +859,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa switch mode { case .revokeNames: if let publicChannelsToRevoke = publicChannelsToRevoke { - entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(20)").string, 10, 20)) +// entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(20)").string, limits.maxPublicLinksCount, premiumLimits.maxPublicLinksCount)) entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesInfo, false)) var index: Int32 = 0 @@ -922,7 +922,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa if displayAvailability { if let publicChannelsToRevoke = publicChannelsToRevoke { - entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(20)").string, 10, 20)) +// entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(20)").string, limits.maxPublicLinksCount, premiumLimits.maxPublicLinksCount)) entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesInfo, false)) var index: Int32 = 0 @@ -1352,7 +1352,8 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta let signal = combineLatest( presentationData, statePromise.get() |> deliverOnMainQueue, - peerView, peersDisablingAddressNameAssignment.get() |> deliverOnMainQueue, + peerView, + peersDisablingAddressNameAssignment.get() |> deliverOnMainQueue, importersContext, importersState.get(), context.engine.data.get( diff --git a/submodules/PeerInfoUI/Sources/IncreaseLimitHeaderItem.swift b/submodules/PeerInfoUI/Sources/IncreaseLimitHeaderItem.swift index 5eb7619454..75efe60da1 100644 --- a/submodules/PeerInfoUI/Sources/IncreaseLimitHeaderItem.swift +++ b/submodules/PeerInfoUI/Sources/IncreaseLimitHeaderItem.swift @@ -20,17 +20,21 @@ class IncreaseLimitHeaderItem: ListViewItem, ItemListItem { let strings: PresentationStrings let icon: Icon let count: Int32 + let limit: Int32 let premiumCount: Int32 let text: String + let isPremiumDisabled: Bool let sectionId: ItemListSectionId - init(theme: PresentationTheme, strings: PresentationStrings, icon: Icon, count: Int32, premiumCount: Int32, text: String, sectionId: ItemListSectionId) { + init(theme: PresentationTheme, strings: PresentationStrings, icon: Icon, count: Int32, limit: Int32, premiumCount: Int32, text: String, isPremiumDisabled: Bool, sectionId: ItemListSectionId) { self.theme = theme self.strings = strings self.icon = icon self.count = count + self.limit = limit self.premiumCount = premiumCount self.text = text + self.isPremiumDisabled = isPremiumDisabled self.sectionId = sectionId } @@ -76,7 +80,7 @@ private let textFont = Font.regular(15.0) private let boldTextFont = Font.semibold(15.0) class IncreaseLimitHeaderItemNode: ListViewItemNode { - private var hostView: ComponentHostView + private var hostView: ComponentHostView? private let titleNode: TextNode private let textNode: TextNode @@ -92,9 +96,7 @@ class IncreaseLimitHeaderItemNode: ListViewItemNode { self.textNode.isUserInteractionEnabled = false self.textNode.contentMode = .left self.textNode.contentsScale = UIScreen.main.scale - - self.hostView = ComponentHostView() - + super.init(layerBacked: false, dynamicBounce: false) self.addSubnode(self.titleNode) @@ -104,7 +106,9 @@ class IncreaseLimitHeaderItemNode: ListViewItemNode { override func didLoad() { super.didLoad() - self.view.addSubview(self.hostView) + let hostView = ComponentHostView() + self.hostView = hostView + self.view.addSubview(hostView) } func asyncLayout() -> (_ item: IncreaseLimitHeaderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { @@ -142,34 +146,46 @@ class IncreaseLimitHeaderItemNode: ListViewItemNode { badgeIconName = "Premium/Link" } - let size = strongSelf.hostView.update( - transition: .immediate, - component: AnyComponent(PremiumLimitDisplayComponent( - inactiveColor: item.theme.list.itemBlocksSeparatorColor.withAlphaComponent(0.5), - activeColors: [ - UIColor(rgb: 0x0077ff), - UIColor(rgb: 0x6b93ff), - UIColor(rgb: 0x8878ff), - UIColor(rgb: 0xe46ace) - ], - inactiveTitle: item.strings.Premium_Free, - inactiveValue: "", - inactiveTitleColor: item.theme.list.itemPrimaryTextColor, - activeTitle: item.strings.Premium_Premium, - activeValue: "\(item.premiumCount)", - activeTitleColor: .white, - badgeIconName: badgeIconName, - badgeText: "\(item.count)", - badgePosition: CGFloat(item.count) / CGFloat(item.premiumCount), - isPremiumDisabled: false - )), - environment: {}, - containerSize: CGSize(width: layout.size.width - params.leftInset - params.rightInset, height: 200.0) - ) - strongSelf.hostView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - size.width) / 2.0), y: -30.0), size: size) + let gradientColors: [UIColor] + if item.isPremiumDisabled { + gradientColors = [ + UIColor(rgb: 0x007afe), + UIColor(rgb: 0x5494ff) + ] + } else { + gradientColors = [ + UIColor(rgb: 0x0077ff), + UIColor(rgb: 0x6b93ff), + UIColor(rgb: 0x8878ff), + UIColor(rgb: 0xe46ace) + ] + } - let _ = textApply() - strongSelf.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - textLayout.size.width) / 2.0), y: size.height + textSpacing), size: textLayout.size) + if let hostView = strongSelf.hostView { + let size = hostView.update( + transition: .immediate, + component: AnyComponent(PremiumLimitDisplayComponent( + inactiveColor: item.theme.list.itemBlocksSeparatorColor.withAlphaComponent(0.5), + activeColors: gradientColors, + inactiveTitle: item.strings.Premium_Free, + inactiveValue: item.count > item.limit ? "\(item.limit)" : "", + inactiveTitleColor: item.theme.list.itemPrimaryTextColor, + activeTitle: item.strings.Premium_Premium, + activeValue: item.count >= item.premiumCount ? "" : "\(item.premiumCount)", + activeTitleColor: .white, + badgeIconName: badgeIconName, + badgeText: "\(item.count)", + badgePosition: CGFloat(item.count) / CGFloat(item.premiumCount), + isPremiumDisabled: item.isPremiumDisabled + )), + environment: {}, + containerSize: CGSize(width: layout.size.width - params.leftInset - params.rightInset, height: 200.0) + ) + hostView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - size.width) / 2.0), y: -30.0), size: size) + + let _ = textApply() + strongSelf.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - textLayout.size.width) / 2.0), y: size.height + textSpacing), size: textLayout.size) + } } }) } diff --git a/submodules/PeerInfoUI/Sources/OldChannelsController.swift b/submodules/PeerInfoUI/Sources/OldChannelsController.swift index e110b16f25..943bae2ec6 100644 --- a/submodules/PeerInfoUI/Sources/OldChannelsController.swift +++ b/submodules/PeerInfoUI/Sources/OldChannelsController.swift @@ -84,7 +84,7 @@ private enum OldChannelsEntryId: Hashable { } private enum OldChannelsEntry: ItemListNodeEntry { - case info(Int32, Int32, String) + case info(Int32, Int32, Int32, String, Bool) case peersHeader(String) case peer(Int, InactiveChannel, Bool) @@ -110,8 +110,8 @@ private enum OldChannelsEntry: ItemListNodeEntry { static func ==(lhs: OldChannelsEntry, rhs: OldChannelsEntry) -> Bool { switch lhs { - case let .info(count, premiumCount, text): - if case .info(count, premiumCount, text) = rhs { + case let .info(count, limit, premiumLimit, text, isPremiumDisabled): + if case .info(count, limit, premiumLimit, text, isPremiumDisabled) = rhs { return true } else { return false @@ -168,8 +168,8 @@ private enum OldChannelsEntry: ItemListNodeEntry { func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { let arguments = arguments as! OldChannelsItemArguments switch self { - case let .info(count, premiumCount, text): - return IncreaseLimitHeaderItem(theme: presentationData.theme, strings: presentationData.strings, icon: .group, count: count, premiumCount: premiumCount, text: text, sectionId: self.section) + case let .info(count, limit, premiumLimit, text, isPremiumDisabled): + return IncreaseLimitHeaderItem(theme: presentationData.theme, strings: presentationData.strings, icon: .group, count: count, limit: limit, premiumCount: premiumLimit, text: text, isPremiumDisabled: isPremiumDisabled, sectionId: self.section) case let .peersHeader(title): return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) case let .peer(_, peer, selected): @@ -185,10 +185,24 @@ private struct OldChannelsState: Equatable { var isSearching: Bool = false } -private func oldChannelsEntries(presentationData: PresentationData, state: OldChannelsState, limit: Int32, premiumLimit: Int32, peers: [InactiveChannel]?, intent: OldChannelsControllerIntent) -> [OldChannelsEntry] { +private func oldChannelsEntries(presentationData: PresentationData, state: OldChannelsState, isPremium: Bool, isPremiumDisabled: Bool, limit: Int32, premiumLimit: Int32, peers: [InactiveChannel]?, intent: OldChannelsControllerIntent) -> [OldChannelsEntry] { var entries: [OldChannelsEntry] = [] - - entries.append(.info(limit, premiumLimit, presentationData.strings.OldChannels_TooManyCommunitiesText("\(limit)", "\(premiumLimit)").string)) + + let count = max(limit, Int32(peers?.count ?? 0)) + var text: String? + if count >= premiumLimit { + text = presentationData.strings.OldChannels_TooManyCommunitiesFinalText("\(premiumLimit)").string + } else if count >= limit { + if isPremiumDisabled { + text = presentationData.strings.OldChannels_TooManyCommunitiesNoPremiumText("\(count)").string + } else { + text = presentationData.strings.OldChannels_TooManyCommunitiesText("\(count)", "\(premiumLimit)").string + } + } + + if let text = text { + entries.append(.info(count, limit, premiumLimit, text, isPremiumDisabled)) + } if let peers = peers, !peers.isEmpty { entries.append(.peersHeader(presentationData.strings.OldChannels_ChannelsHeader)) @@ -265,8 +279,6 @@ public func oldChannelsController(context: AccountContext, updatedPresentationDa var previousPeersWereEmpty = true - - let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData let signal = combineLatest( queue: Queue.mainQueue(), @@ -283,7 +295,8 @@ public func oldChannelsController(context: AccountContext, updatedPresentationDa let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { dismissImpl?() }) - let (_, limits, premiumLimits) = limits + let (accountPeer, limits, premiumLimits) = limits + let isPremium = accountPeer?.isPremium ?? false let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.OldChannels_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back)) @@ -322,6 +335,7 @@ public func oldChannelsController(context: AccountContext, updatedPresentationDa buttonText = presentationData.strings.Premium_IncreaseLimit colorful = true } + let footerItem: IncreaseLimitFooterItem? if state.isSearching && state.selectedPeers.count == 0 { footerItem = nil @@ -336,7 +350,9 @@ public func oldChannelsController(context: AccountContext, updatedPresentationDa }) } - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: oldChannelsEntries(presentationData: presentationData, state: state, limit: limits.maxChannelsCount, premiumLimit: premiumLimits.maxChannelsCount, peers: peers, intent: intent), style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, footerItem: footerItem, initialScrollToItem: ListViewScrollToItem(index: 0, position: .top(-navigationBarSearchContentHeight), animated: false, curve: .Default(duration: 0.0), directionHint: .Up), crossfadeState: peersAreEmptyUpdated, animateChanges: false) + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) + + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: oldChannelsEntries(presentationData: presentationData, state: state, isPremium: isPremium, isPremiumDisabled: premiumConfiguration.isPremiumDisabled, limit: limits.maxChannelsCount, premiumLimit: premiumLimits.maxChannelsCount, peers: peers, intent: intent), style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, footerItem: footerItem, initialScrollToItem: ListViewScrollToItem(index: 0, position: .top(-navigationBarSearchContentHeight), animated: false, curve: .Default(duration: 0.0), directionHint: .Up), crossfadeState: peersAreEmptyUpdated, animateChanges: false) return (controllerState, (listState, arguments)) } diff --git a/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift b/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift index 9fd0484b47..ff4d4bc66d 100644 --- a/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift +++ b/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift @@ -66,6 +66,7 @@ public final class SolidRoundedButtonNode: ASDisplayNode { private let gloss: Bool private let buttonBackgroundNode: ASImageNode + private var buttonBackgroundAnimationView: UIImageView? private var shimmerView: ShimmerEffectForegroundView? private var borderView: UIView? @@ -173,6 +174,8 @@ public final class SolidRoundedButtonNode: ASDisplayNode { self.buttonBackgroundNode = ASImageNode() self.buttonBackgroundNode.displaysAsynchronously = false self.buttonBackgroundNode.clipsToBounds = true + + self.buttonBackgroundNode.backgroundColor = theme.backgroundColor if theme.backgroundColors.count > 1 { self.buttonBackgroundNode.backgroundColor = nil @@ -182,9 +185,12 @@ public final class SolidRoundedButtonNode: ASDisplayNode { locations.append(delta * CGFloat(i)) } self.buttonBackgroundNode.image = generateGradientImage(size: CGSize(width: 200.0, height: height), colors: theme.backgroundColors, locations: locations, direction: .horizontal) - } else { - self.buttonBackgroundNode.backgroundColor = theme.backgroundColor + + let buttonBackgroundAnimationView = UIImageView() + buttonBackgroundAnimationView.image = generateGradientImage(size: CGSize(width: 200.0, height: height), colors: theme.backgroundColors, locations: locations, direction: .horizontal) + self.buttonBackgroundAnimationView = buttonBackgroundAnimationView } + self.buttonBackgroundNode.cornerRadius = cornerRadius self.buttonNode = HighlightTrackingButtonNode() @@ -244,34 +250,92 @@ public final class SolidRoundedButtonNode: ASDisplayNode { self.buttonBackgroundNode.layer.cornerCurve = .continuous } + if let buttonBackgroundAnimationView = self.buttonBackgroundAnimationView { + self.buttonBackgroundNode.view.addSubview(buttonBackgroundAnimationView) + } + + self.setupGloss() + } + + private func setupGloss() { if self.gloss { - let shimmerView = ShimmerEffectForegroundView() - self.shimmerView = shimmerView - - if #available(iOS 13.0, *) { - shimmerView.layer.cornerCurve = .continuous - shimmerView.layer.cornerRadius = self.buttonCornerRadius + if self.shimmerView == nil { + let shimmerView = ShimmerEffectForegroundView() + self.shimmerView = shimmerView + + if #available(iOS 13.0, *) { + shimmerView.layer.cornerCurve = .continuous + shimmerView.layer.cornerRadius = self.buttonCornerRadius + } + + let borderView = UIView() + borderView.isUserInteractionEnabled = false + self.borderView = borderView + + let borderMaskView = UIView() + borderMaskView.layer.borderWidth = 1.0 + UIScreenPixel + borderMaskView.layer.borderColor = UIColor.white.cgColor + borderMaskView.layer.cornerRadius = self.buttonCornerRadius + borderView.mask = borderMaskView + self.borderMaskView = borderMaskView + + let borderShimmerView = ShimmerEffectForegroundView() + self.borderShimmerView = borderShimmerView + borderView.addSubview(borderShimmerView) + + self.view.insertSubview(shimmerView, belowSubview: self.buttonNode.view) + self.view.insertSubview(borderView, belowSubview: self.buttonNode.view) + + self.updateShimmerParameters() + + if let width = self.validLayout { + _ = self.updateLayout(width: width, transition: .immediate) + } } + } else if self.shimmerView != nil { + self.shimmerView?.removeFromSuperview() + self.borderView?.removeFromSuperview() + self.borderMaskView?.removeFromSuperview() + self.borderShimmerView?.removeFromSuperview() - let borderView = UIView() - borderView.isUserInteractionEnabled = false - self.borderView = borderView + self.shimmerView = nil + self.borderView = nil + self.borderMaskView = nil + self.borderShimmerView = nil + } + } + + private func setupGradientAnimations() { + guard let buttonBackgroundAnimationView = self.buttonBackgroundAnimationView else { + return + } + + if let _ = buttonBackgroundAnimationView.layer.animation(forKey: "movement") { + } else { + let offset = (buttonBackgroundAnimationView.frame.width - self.frame.width) / 2.0 + let previousValue = buttonBackgroundAnimationView.center.x + var newValue: CGFloat = offset + if offset - previousValue < buttonBackgroundAnimationView.frame.width * 0.25 { + newValue -= buttonBackgroundAnimationView.frame.width * 0.35 + } + buttonBackgroundAnimationView.center = CGPoint(x: newValue, y: buttonBackgroundAnimationView.bounds.size.height / 2.0) - let borderMaskView = UIView() - borderMaskView.layer.borderWidth = 1.0 + UIScreenPixel - borderMaskView.layer.borderColor = UIColor.white.cgColor - borderMaskView.layer.cornerRadius = self.buttonCornerRadius - borderView.mask = borderMaskView - self.borderMaskView = borderMaskView + CATransaction.begin() - let borderShimmerView = ShimmerEffectForegroundView() - self.borderShimmerView = borderShimmerView - borderView.addSubview(borderShimmerView) + let animation = CABasicAnimation(keyPath: "position.x") + animation.duration = 4.5 + animation.fromValue = previousValue + animation.toValue = newValue + animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) - self.view.insertSubview(shimmerView, belowSubview: self.buttonNode.view) - self.view.insertSubview(borderView, belowSubview: self.buttonNode.view) - - self.updateShimmerParameters() + CATransaction.setCompletionBlock { [weak self] in +// if let isCurrentlyInHierarchy = self?.isCurrentlyInHierarchy, isCurrentlyInHierarchy { + self?.setupGradientAnimations() +// } + } + + buttonBackgroundAnimationView.layer.add(animation, forKey: "movement") + CATransaction.commit() } } @@ -354,6 +418,8 @@ public final class SolidRoundedButtonNode: ASDisplayNode { animationNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } } + + self.buttonBackgroundNode.backgroundColor = theme.backgroundColor if theme.backgroundColors.count > 1 { self.buttonBackgroundNode.backgroundColor = nil @@ -363,9 +429,17 @@ public final class SolidRoundedButtonNode: ASDisplayNode { locations.append(delta * CGFloat(i)) } self.buttonBackgroundNode.image = generateGradientImage(size: CGSize(width: 200.0, height: self.buttonHeight), colors: theme.backgroundColors, locations: locations, direction: .horizontal) + + if self.buttonBackgroundAnimationView == nil { + let buttonBackgroundAnimationView = UIImageView() + self.buttonBackgroundAnimationView = buttonBackgroundAnimationView + self.buttonBackgroundNode.view.addSubview(buttonBackgroundAnimationView) + } + + self.buttonBackgroundAnimationView?.image = self.buttonBackgroundNode.image } else { - self.buttonBackgroundNode.backgroundColor = theme.backgroundColor self.buttonBackgroundNode.image = nil + self.buttonBackgroundAnimationView?.image = nil } self.titleNode.attributedText = NSAttributedString(string: self.title ?? "", font: self.font == .bold ? Font.semibold(self.fontSize) : Font.regular(self.fontSize), textColor: theme.foregroundColor) @@ -407,6 +481,14 @@ public final class SolidRoundedButtonNode: ASDisplayNode { borderShimmerView.updateAbsoluteRect(CGRect(origin: CGPoint(x: width * 4.0, y: 0.0), size: buttonSize), within: CGSize(width: width * 9.0, height: buttonHeight)) } + if let buttonBackgroundAnimationView = self.buttonBackgroundAnimationView { + buttonBackgroundAnimationView.bounds = CGRect(origin: CGPoint(), size: CGSize(width: buttonSize.width * 2.4, height: buttonSize.height)) + if buttonBackgroundAnimationView.layer.animation(forKey: "movement") == nil { + buttonBackgroundAnimationView.center = CGPoint(x: buttonSize.width * 2.4 / 2.0 - buttonBackgroundAnimationView.frame.width * 0.35, y: buttonSize.height / 2.0) + } + self.setupGradientAnimations() + } + transition.updateFrame(node: self.buttonNode, frame: buttonFrame) if self.title != self.titleNode.attributedText?.string { @@ -663,7 +745,7 @@ public final class SolidRoundedButtonView: UIView { self.buttonBackgroundNode.image = generateGradientImage(size: CGSize(width: 200.0, height: height), colors: theme.backgroundColors, locations: locations, direction: .horizontal) let buttonBackgroundAnimationView = UIImageView() - buttonBackgroundAnimationView.image = generateGradientImage(size: CGSize(width: 200.0, height: height), colors: theme.backgroundColors, locations: locations, direction: .horizontal) + buttonBackgroundAnimationView.image = self.buttonBackgroundNode.image self.buttonBackgroundNode.addSubview(buttonBackgroundAnimationView) self.buttonBackgroundAnimationView = buttonBackgroundAnimationView } @@ -723,6 +805,10 @@ public final class SolidRoundedButtonView: UIView { self.setupGloss() } } + + required public init(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } private func setupGloss() { if self.gloss { @@ -772,10 +858,6 @@ public final class SolidRoundedButtonView: UIView { } } - required public init(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - private func setupGradientAnimations() { guard let buttonBackgroundAnimationView = self.buttonBackgroundAnimationView else { return @@ -943,8 +1025,10 @@ public final class SolidRoundedButtonView: UIView { locations.append(delta * CGFloat(i)) } self.buttonBackgroundNode.image = generateGradientImage(size: CGSize(width: 200.0, height: self.buttonHeight), colors: theme.backgroundColors, locations: locations, direction: .horizontal) + self.buttonBackgroundAnimationView?.image = self.buttonBackgroundNode.image } else { self.buttonBackgroundNode.image = nil + self.buttonBackgroundAnimationView?.image = nil } self.titleNode.attributedText = NSAttributedString(string: self.title ?? "", font: self.font == .bold ? Font.semibold(self.fontSize) : Font.regular(self.fontSize), textColor: theme.foregroundColor)