mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
b362a9b732
13 changed files with 225 additions and 81 deletions
|
|
@ -1978,12 +1978,12 @@ ios_application(
|
|||
extensions = select({
|
||||
":disableExtensionsSetting": [],
|
||||
"//conditions:default": [
|
||||
#":ShareExtension",
|
||||
#":NotificationContentExtension",
|
||||
":ShareExtension",
|
||||
":NotificationContentExtension",
|
||||
":NotificationServiceExtension",
|
||||
#":IntentsExtension",
|
||||
#":WidgetExtension",
|
||||
#":BroadcastUploadExtension",
|
||||
":IntentsExtension",
|
||||
":WidgetExtension",
|
||||
":BroadcastUploadExtension",
|
||||
],
|
||||
}),
|
||||
watch_application = select({
|
||||
|
|
@ -2013,9 +2013,9 @@ xcodeproj(
|
|||
"Debug": {
|
||||
"//command_line_option:compilation_mode": "dbg",
|
||||
},
|
||||
#"Release": {
|
||||
# "//command_line_option:compilation_mode": "opt",
|
||||
#},
|
||||
"Release": {
|
||||
"//command_line_option:compilation_mode": "opt",
|
||||
},
|
||||
},
|
||||
default_xcode_configuration = "Debug"
|
||||
|
||||
|
|
|
|||
|
|
@ -181,7 +181,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||
private var rawStorySubscriptions: EngineStorySubscriptions?
|
||||
private var shouldFixStorySubscriptionOrder: Bool = false
|
||||
private var fixedStorySubscriptionOrder: [EnginePeer.Id] = []
|
||||
var orderedStorySubscriptions: EngineStorySubscriptions?
|
||||
private(set) var orderedStorySubscriptions: EngineStorySubscriptions?
|
||||
private var displayedStoriesTooltip: Bool = false
|
||||
|
||||
private var storyProgressDisposable: Disposable?
|
||||
private var storySubscriptionsDisposable: Disposable?
|
||||
|
|
@ -1853,11 +1854,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||
return
|
||||
}
|
||||
|
||||
var wasEmpty = true
|
||||
if let rawStorySubscriptions = self.rawStorySubscriptions, !rawStorySubscriptions.items.isEmpty {
|
||||
wasEmpty = false
|
||||
}
|
||||
|
||||
self.rawStorySubscriptions = rawStorySubscriptions
|
||||
var items: [EngineStorySubscriptions.Item] = []
|
||||
if self.shouldFixStorySubscriptionOrder {
|
||||
|
|
@ -1879,8 +1875,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||
)
|
||||
self.fixedStorySubscriptionOrder = items.map(\.peer.id)
|
||||
|
||||
let isEmpty = rawStorySubscriptions.items.isEmpty
|
||||
|
||||
let transition: ContainedViewLayoutTransition
|
||||
if self.didAppear {
|
||||
transition = .animated(duration: 0.4, curve: .spring)
|
||||
|
|
@ -1888,9 +1882,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||
transition = .immediate
|
||||
}
|
||||
|
||||
let _ = wasEmpty
|
||||
let _ = isEmpty
|
||||
|
||||
self.chatListDisplayNode.temporaryContentOffsetChangeTransition = transition
|
||||
self.requestLayout(transition: transition)
|
||||
self.chatListDisplayNode.temporaryContentOffsetChangeTransition = nil
|
||||
|
|
@ -1911,6 +1902,13 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||
}
|
||||
|
||||
self.storiesReady.set(.single(true))
|
||||
|
||||
Queue.mainQueue().after(1.0, { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.maybeDisplayStoryTooltip()
|
||||
})
|
||||
})
|
||||
self.storyProgressDisposable = (self.context.engine.messages.allStoriesUploadProgress()
|
||||
|> deliverOnMainQueue).start(next: { [weak self] progress in
|
||||
|
|
@ -1922,6 +1920,67 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||
}
|
||||
}
|
||||
|
||||
fileprivate func maybeDisplayStoryTooltip() {
|
||||
let content = self.updateHeaderContent()
|
||||
if content.secondaryContent != nil {
|
||||
return
|
||||
}
|
||||
guard let chatListTitle = content.primaryContent?.chatListTitle else {
|
||||
return
|
||||
}
|
||||
if chatListTitle.activity {
|
||||
return
|
||||
}
|
||||
if self.displayedStoriesTooltip {
|
||||
return
|
||||
}
|
||||
|
||||
if let orderedStorySubscriptions = self.orderedStorySubscriptions, !orderedStorySubscriptions.items.isEmpty {
|
||||
let _ = (ApplicationSpecificNotice.displayChatListStoriesTooltip(accountManager: self.context.sharedContext.accountManager)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] didDisplay in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if didDisplay {
|
||||
return
|
||||
}
|
||||
|
||||
if let navigationBarView = self.chatListDisplayNode.navigationBarView.view as? ChatListNavigationBar.View, !navigationBarView.storiesUnlocked, !self.displayedStoriesTooltip {
|
||||
if let storyPeerListView = self.chatListHeaderView()?.storyPeerListView(), let (anchorView, anchorRect) = storyPeerListView.anchorForTooltip() {
|
||||
self.displayedStoriesTooltip = true
|
||||
|
||||
let absoluteFrame = anchorView.convert(anchorRect, to: self.view)
|
||||
//TODO:localize
|
||||
|
||||
let itemList = orderedStorySubscriptions.items.prefix(3).map(\.peer.compactDisplayTitle)
|
||||
var itemListString: String = itemList.joined(separator: ", ")
|
||||
if #available(iOS 13.0, *) {
|
||||
let listFormatter = ListFormatter()
|
||||
listFormatter.locale = localeWithStrings(self.presentationData.strings)
|
||||
if let value = listFormatter.string(from: itemList) {
|
||||
itemListString = value
|
||||
}
|
||||
}
|
||||
|
||||
let text: String = "Tap above to view updates\nfrom \(itemListString)"
|
||||
|
||||
let tooltipController = TooltipController(content: .text(text), baseFontSize: self.presentationData.listsFontSize.baseDisplaySize, timeout: 30.0, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true, padding: 6.0, innerPadding: UIEdgeInsets(top: 2.0, left: 3.0, bottom: 2.0, right: 3.0))
|
||||
self.present(tooltipController, in: .current, with: TooltipControllerPresentationArguments(sourceNodeAndRect: { [weak self] in
|
||||
guard let self else {
|
||||
return nil
|
||||
}
|
||||
return (self.displayNode, absoluteFrame.insetBy(dx: 0.0, dy: 0.0).offsetBy(dx: 0.0, dy: 0.0))
|
||||
}))
|
||||
|
||||
#if !DEBUG
|
||||
let _ = ApplicationSpecificNotice.setDisplayChatListStoriesTooltip(accountManager: self.context.sharedContext.accountManager).start()
|
||||
#endif
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
public override func displayNodeDidLoad() {
|
||||
super.displayNodeDidLoad()
|
||||
|
||||
|
|
@ -2351,7 +2410,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||
}
|
||||
}
|
||||
|
||||
func updateHeaderContent(layout: ContainerViewLayout) -> (primaryContent: ChatListHeaderComponent.Content?, secondaryContent: ChatListHeaderComponent.Content?) {
|
||||
func updateHeaderContent() -> (primaryContent: ChatListHeaderComponent.Content?, secondaryContent: ChatListHeaderComponent.Content?) {
|
||||
var primaryContent: ChatListHeaderComponent.Content?
|
||||
if let primaryContext = self.primaryContext {
|
||||
var backTitle: String?
|
||||
|
|
@ -5684,6 +5743,13 @@ private final class ChatListLocationContext {
|
|||
}
|
||||
|
||||
self.parentController?.requestLayout(transition: .animated(duration: 0.45, curve: .spring))
|
||||
|
||||
Queue.mainQueue().after(1.0, { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.parentController?.maybeDisplayStoryTooltip()
|
||||
})
|
||||
}
|
||||
|
||||
private func updateForum(
|
||||
|
|
|
|||
|
|
@ -1918,7 +1918,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
|||
}
|
||||
|
||||
private func updateNavigationBar(layout: ContainerViewLayout, deferScrollApplication: Bool, transition: Transition) -> (navigationHeight: CGFloat, storiesInset: CGFloat) {
|
||||
let headerContent = self.controller?.updateHeaderContent(layout: layout)
|
||||
let headerContent = self.controller?.updateHeaderContent()
|
||||
|
||||
var tabsNode: ASDisplayNode?
|
||||
var tabsNodeIsSearch = false
|
||||
|
|
@ -2358,7 +2358,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
|||
}
|
||||
|
||||
private func shouldStopScrolling(listView: ListView, velocity: CGFloat, isPrimary: Bool) -> Bool {
|
||||
if abs(velocity) > 1.0 {
|
||||
if abs(velocity) > 0.8 {
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2795,7 +2795,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
hasUnseen: displayStoryIndicator,
|
||||
isDarkTheme: item.presentationData.theme.overallDarkAppearance,
|
||||
activeLineWidth: 2.0,
|
||||
inactiveLineWidth: 1.0 + UIScreenPixel
|
||||
inactiveLineWidth: 1.0 + UIScreenPixel,
|
||||
counters: nil
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: indicatorFrame.size
|
||||
|
|
|
|||
|
|
@ -201,8 +201,10 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
|||
})]
|
||||
}
|
||||
|
||||
var hasUnseenStories: Bool?
|
||||
var storyStats: (total: Int, unseen: Int)?
|
||||
if let storyData = storyData {
|
||||
storyStats = (storyData.count, storyData.unseenCount)
|
||||
|
||||
let text: String
|
||||
//TODO:localize
|
||||
if storyData.unseenCount != 0 {
|
||||
|
|
@ -219,12 +221,11 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
|||
}
|
||||
}
|
||||
status = .custom(string: text, multiline: false, isActive: false, icon: nil)
|
||||
hasUnseenStories = storyData.unseenCount != 0
|
||||
}
|
||||
|
||||
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: isSearch ? .generalSearch : .peer, peer: itemPeer, status: status, enabled: enabled, selection: selection, selectionPosition: .left, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), additionalActions: additionalActions, index: nil, header: header, action: { _ in
|
||||
interaction.openPeer(peer, .generic)
|
||||
}, itemHighlighting: interaction.itemHighlighting, contextAction: itemContextAction, hasUnseenStories: hasUnseenStories, openStories: { peer, sourceNode in
|
||||
}, itemHighlighting: interaction.itemHighlighting, contextAction: itemContextAction, storyStats: storyStats, openStories: { peer, sourceNode in
|
||||
if case let .peer(peerValue, _) = peer, let peerValue {
|
||||
interaction.openStories(peerValue, sourceNode)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -489,7 +489,7 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
|||
}
|
||||
|
||||
if let navigationBarComponentView = self.navigationBarView.view as? ChatListNavigationBar.View {
|
||||
navigationBarComponentView.applyScroll(offset: offset, allowAvatarsExpansion: true, transition: Transition(transition))
|
||||
navigationBarComponentView.applyScroll(offset: offset, allowAvatarsExpansion: false, transition: Transition(transition))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -180,7 +180,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
|
|||
let arrowAction: (() -> Void)?
|
||||
let animationCache: AnimationCache?
|
||||
let animationRenderer: MultiAnimationRenderer?
|
||||
let hasUnseenStories: Bool?
|
||||
let storyStats: (total: Int, unseen: Int)?
|
||||
let openStories: ((ContactsPeerItemPeer, ASDisplayNode) -> Void)?
|
||||
|
||||
public let selectable: Bool
|
||||
|
|
@ -217,7 +217,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
|
|||
contextAction: ((ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? = nil, arrowAction: (() -> Void)? = nil,
|
||||
animationCache: AnimationCache? = nil,
|
||||
animationRenderer: MultiAnimationRenderer? = nil,
|
||||
hasUnseenStories: Bool? = nil,
|
||||
storyStats: (total: Int, unseen: Int)? = nil,
|
||||
openStories: ((ContactsPeerItemPeer, ASDisplayNode) -> Void)? = nil
|
||||
) {
|
||||
self.presentationData = presentationData
|
||||
|
|
@ -248,7 +248,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
|
|||
self.arrowAction = arrowAction
|
||||
self.animationCache = animationCache
|
||||
self.animationRenderer = animationRenderer
|
||||
self.hasUnseenStories = hasUnseenStories
|
||||
self.storyStats = storyStats
|
||||
self.openStories = openStories
|
||||
|
||||
if let index = index {
|
||||
|
|
@ -1088,7 +1088,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||
|
||||
var avatarScale: CGFloat = 1.0
|
||||
|
||||
if item.hasUnseenStories != nil {
|
||||
if item.storyStats != nil {
|
||||
avatarScale *= (avatarFrame.width - 2.0 * 2.0) / avatarFrame.width
|
||||
}
|
||||
|
||||
|
|
@ -1096,7 +1096,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||
|
||||
let storyIndicatorScale: CGFloat = 1.0
|
||||
|
||||
if let displayStoryIndicator = item.hasUnseenStories {
|
||||
if let storyStats = item.storyStats {
|
||||
var indicatorTransition = Transition(transition)
|
||||
let avatarStoryIndicator: ComponentView<Empty>
|
||||
if let current = strongSelf.avatarStoryIndicator {
|
||||
|
|
@ -1113,10 +1113,11 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||
let _ = avatarStoryIndicator.update(
|
||||
transition: indicatorTransition,
|
||||
component: AnyComponent(AvatarStoryIndicatorComponent(
|
||||
hasUnseen: displayStoryIndicator,
|
||||
hasUnseen: storyStats.unseen != 0,
|
||||
isDarkTheme: item.presentationData.theme.overallDarkAppearance,
|
||||
activeLineWidth: 1.0 + UIScreenPixel,
|
||||
inactiveLineWidth: 1.0 + UIScreenPixel
|
||||
inactiveLineWidth: 1.0 + UIScreenPixel,
|
||||
counters: AvatarStoryIndicatorComponent.Counters(totalCount: storyStats.total, unseenCount: storyStats.unseen)
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: indicatorFrame.size
|
||||
|
|
|
|||
|
|
@ -173,6 +173,7 @@ private enum ApplicationSpecificGlobalNotice: Int32 {
|
|||
case chatWallpaperLightPreviewTip = 39
|
||||
case chatWallpaperDarkPreviewTip = 40
|
||||
case displayChatListContacts = 41
|
||||
case displayChatListStoriesTooltip = 42
|
||||
|
||||
var key: ValueBoxKey {
|
||||
let v = ValueBoxKey(length: 4)
|
||||
|
|
@ -394,6 +395,10 @@ private struct ApplicationSpecificNoticeKeys {
|
|||
static func displayChatListContacts() -> NoticeEntryKey {
|
||||
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.displayChatListContacts.key)
|
||||
}
|
||||
|
||||
static func displayChatListStoriesTooltip() -> NoticeEntryKey {
|
||||
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.displayChatListStoriesTooltip.key)
|
||||
}
|
||||
}
|
||||
|
||||
public struct ApplicationSpecificNotice {
|
||||
|
|
@ -1445,6 +1450,26 @@ public struct ApplicationSpecificNotice {
|
|||
|> ignoreValues
|
||||
}
|
||||
|
||||
public static func displayChatListStoriesTooltip(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Bool, NoError> {
|
||||
return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.displayChatListStoriesTooltip())
|
||||
|> map { view -> Bool in
|
||||
if let _ = view.value?.get(ApplicationSpecificBoolNotice.self) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static func setDisplayChatListStoriesTooltip(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Never, NoError> {
|
||||
return accountManager.transaction { transaction -> Void in
|
||||
if let entry = CodableEntry(ApplicationSpecificBoolNotice()) {
|
||||
transaction.setNotice(ApplicationSpecificNoticeKeys.displayChatListStoriesTooltip(), entry)
|
||||
}
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
public static func reset(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Void, NoError> {
|
||||
return accountManager.transaction { transaction -> Void in
|
||||
}
|
||||
|
|
|
|||
|
|
@ -215,7 +215,8 @@ public final class ChatAvatarNavigationNode: ASDisplayNode {
|
|||
hasUnseen: hasUnseenStories,
|
||||
isDarkTheme: theme.overallDarkAppearance,
|
||||
activeLineWidth: 1.0,
|
||||
inactiveLineWidth: 1.0
|
||||
inactiveLineWidth: 1.0,
|
||||
counters: nil
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: self.avatarNode.bounds.insetBy(dx: 2.0, dy: 2.0).size
|
||||
|
|
|
|||
|
|
@ -298,28 +298,6 @@ public final class ChatListNavigationBar: Component {
|
|||
self.addSubview(searchContentNode.view)
|
||||
}
|
||||
|
||||
/*let clippedStoriesOverscrollOffset = -min(0.0, clippedScrollOffset)
|
||||
let clippedStoriesOffset = max(0.0, min(clippedScrollOffset, defaultStoriesOffsetDistance))
|
||||
var storiesOffsetFraction: CGFloat
|
||||
var storiesUnlockedOffsetFraction: CGFloat
|
||||
if !component.isSearchActive, component.secondaryTransition == 0.0, let storySubscriptions = component.storySubscriptions, !storySubscriptions.items.isEmpty, allowAvatarsExpansion {
|
||||
if component.storiesUnlocked {
|
||||
storiesOffsetFraction = clippedStoriesOffset / defaultStoriesOffsetDistance
|
||||
storiesUnlockedOffsetFraction = 1.0
|
||||
} else {
|
||||
storiesOffsetFraction = 1.0 - (clippedStoriesOverscrollOffset / defaultStoriesOffsetDistance)
|
||||
storiesUnlockedOffsetFraction = 1.0
|
||||
}
|
||||
} else {
|
||||
storiesOffsetFraction = 1.0
|
||||
storiesUnlockedOffsetFraction = 1.0
|
||||
}
|
||||
|
||||
if self.applyScrollFractionAnimator != nil {
|
||||
storiesOffsetFraction = self.applyScrollFraction * storiesOffsetFraction + (1.0 - self.applyScrollFraction) * self.storiesOffsetStartFraction
|
||||
storiesUnlockedOffsetFraction = self.applyScrollUnlockedFraction * storiesUnlockedOffsetFraction + (1.0 - self.applyScrollUnlockedFraction) * self.storiesUnlockedStartFraction
|
||||
}*/
|
||||
|
||||
let searchSize = CGSize(width: currentLayout.size.width, height: navigationBarSearchContentHeight)
|
||||
var searchFrame = CGRect(origin: CGPoint(x: 0.0, y: visibleSize.height - searchSize.height), size: searchSize)
|
||||
if component.tabsNode != nil {
|
||||
|
|
@ -338,9 +316,6 @@ public final class ChatListNavigationBar: Component {
|
|||
searchContentNode.updateLayout(size: searchSize, leftInset: component.sideInset, rightInset: component.sideInset, transition: transition.containedViewLayoutTransition)
|
||||
|
||||
let headerTransition = transition
|
||||
/*if self.applyScrollFractionAnimator != nil {
|
||||
headerTransition = .immediate
|
||||
}*/
|
||||
|
||||
let storiesOffsetFraction: CGFloat
|
||||
let storiesUnlocked: Bool
|
||||
|
|
@ -362,9 +337,9 @@ public final class ChatListNavigationBar: Component {
|
|||
if allowAvatarsExpansion && transition.animation.isImmediate {
|
||||
if self.storiesUnlocked != storiesUnlocked {
|
||||
if storiesUnlocked {
|
||||
HapticFeedback().impact()
|
||||
HapticFeedback().impact(.veryLight)
|
||||
} else {
|
||||
HapticFeedback().tap()
|
||||
HapticFeedback().impact(.veryLight)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,21 +5,34 @@ import ComponentFlow
|
|||
import TelegramPresentationData
|
||||
|
||||
public final class AvatarStoryIndicatorComponent: Component {
|
||||
public struct Counters: Equatable {
|
||||
public var totalCount: Int
|
||||
public var unseenCount: Int
|
||||
|
||||
public init(totalCount: Int, unseenCount: Int) {
|
||||
self.totalCount = totalCount
|
||||
self.unseenCount = unseenCount
|
||||
}
|
||||
}
|
||||
|
||||
public let hasUnseen: Bool
|
||||
public let isDarkTheme: Bool
|
||||
public let activeLineWidth: CGFloat
|
||||
public let inactiveLineWidth: CGFloat
|
||||
public let counters: Counters?
|
||||
|
||||
public init(
|
||||
hasUnseen: Bool,
|
||||
isDarkTheme: Bool,
|
||||
activeLineWidth: CGFloat,
|
||||
inactiveLineWidth: CGFloat
|
||||
inactiveLineWidth: CGFloat,
|
||||
counters: Counters?
|
||||
) {
|
||||
self.hasUnseen = hasUnseen
|
||||
self.isDarkTheme = isDarkTheme
|
||||
self.activeLineWidth = activeLineWidth
|
||||
self.inactiveLineWidth = inactiveLineWidth
|
||||
self.counters = counters
|
||||
}
|
||||
|
||||
public static func ==(lhs: AvatarStoryIndicatorComponent, rhs: AvatarStoryIndicatorComponent) -> Bool {
|
||||
|
|
@ -35,6 +48,9 @@ public final class AvatarStoryIndicatorComponent: Component {
|
|||
if lhs.inactiveLineWidth != rhs.inactiveLineWidth {
|
||||
return false
|
||||
}
|
||||
if lhs.counters != rhs.counters {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -76,26 +92,79 @@ public final class AvatarStoryIndicatorComponent: Component {
|
|||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.setLineWidth(lineWidth)
|
||||
context.addEllipse(in: CGRect(origin: CGPoint(x: size.width * 0.5 - diameter * 0.5, y: size.height * 0.5 - diameter * 0.5), size: size).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5))
|
||||
context.replacePathWithStrokedPath()
|
||||
context.clip()
|
||||
|
||||
var locations: [CGFloat] = [1.0, 0.0]
|
||||
let colors: [CGColor]
|
||||
if component.hasUnseen {
|
||||
colors = [UIColor(rgb: 0x34C76F).cgColor, UIColor(rgb: 0x3DA1FD).cgColor]
|
||||
} else {
|
||||
if component.isDarkTheme {
|
||||
colors = [UIColor(rgb: 0x48484A).cgColor, UIColor(rgb: 0x48484A).cgColor]
|
||||
} else {
|
||||
colors = [UIColor(rgb: 0xD8D8E1).cgColor, UIColor(rgb: 0xD8D8E1).cgColor]
|
||||
if let counters = component.counters, counters.totalCount > 1 {
|
||||
let center = CGPoint(x: size.width * 0.5, y: size.height * 0.5)
|
||||
let radius = (diameter - lineWidth) * 0.5
|
||||
let spacing: CGFloat = 2.0
|
||||
let angularSpacing: CGFloat = spacing / radius
|
||||
let circleLength = CGFloat.pi * 2.0 * radius
|
||||
let segmentLength = (circleLength - spacing * CGFloat(counters.totalCount)) / CGFloat(counters.totalCount)
|
||||
let segmentAngle = segmentLength / radius
|
||||
|
||||
for pass in 0 ..< 2 {
|
||||
context.resetClip()
|
||||
|
||||
let startIndex: Int
|
||||
let endIndex: Int
|
||||
if pass == 0 {
|
||||
startIndex = 0
|
||||
endIndex = counters.totalCount - counters.unseenCount
|
||||
} else {
|
||||
startIndex = counters.totalCount - counters.unseenCount
|
||||
endIndex = counters.totalCount
|
||||
}
|
||||
if startIndex < endIndex {
|
||||
for i in startIndex ..< endIndex {
|
||||
let startAngle = CGFloat(i) * (angularSpacing + segmentAngle) - CGFloat.pi * 0.5 + angularSpacing * 0.5
|
||||
context.move(to: CGPoint(x: center.x + cos(startAngle) * radius, y: center.y + sin(startAngle) * radius))
|
||||
context.addArc(center: center, radius: radius, startAngle: startAngle, endAngle: startAngle + segmentAngle, clockwise: false)
|
||||
}
|
||||
|
||||
context.replacePathWithStrokedPath()
|
||||
context.clip()
|
||||
|
||||
var locations: [CGFloat] = [1.0, 0.0]
|
||||
let colors: [CGColor]
|
||||
if pass == 1 {
|
||||
colors = [UIColor(rgb: 0x34C76F).cgColor, UIColor(rgb: 0x3DA1FD).cgColor]
|
||||
} else {
|
||||
if component.isDarkTheme {
|
||||
colors = [UIColor(rgb: 0x48484A).cgColor, UIColor(rgb: 0x48484A).cgColor]
|
||||
} else {
|
||||
colors = [UIColor(rgb: 0xD8D8E1).cgColor, UIColor(rgb: 0xD8D8E1).cgColor]
|
||||
}
|
||||
}
|
||||
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
context.addEllipse(in: CGRect(origin: CGPoint(x: size.width * 0.5 - diameter * 0.5, y: size.height * 0.5 - diameter * 0.5), size: size).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5))
|
||||
|
||||
context.replacePathWithStrokedPath()
|
||||
context.clip()
|
||||
|
||||
var locations: [CGFloat] = [1.0, 0.0]
|
||||
let colors: [CGColor]
|
||||
if component.hasUnseen {
|
||||
colors = [UIColor(rgb: 0x34C76F).cgColor, UIColor(rgb: 0x3DA1FD).cgColor]
|
||||
} else {
|
||||
if component.isDarkTheme {
|
||||
colors = [UIColor(rgb: 0x48484A).cgColor, UIColor(rgb: 0x48484A).cgColor]
|
||||
} else {
|
||||
colors = [UIColor(rgb: 0xD8D8E1).cgColor, UIColor(rgb: 0xD8D8E1).cgColor]
|
||||
}
|
||||
}
|
||||
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
|
||||
}
|
||||
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
|
||||
})
|
||||
transition.setFrame(view: self.indicatorView, frame: CGRect(origin: CGPoint(x: (availableSize.width - imageDiameter) * 0.5, y: (availableSize.height - imageDiameter) * 0.5), size: CGSize(width: imageDiameter, height: imageDiameter)))
|
||||
|
||||
|
|
|
|||
|
|
@ -323,6 +323,10 @@ public final class StoryPeerListComponent: Component {
|
|||
})
|
||||
}
|
||||
|
||||
public func anchorForTooltip() -> (UIView, CGRect)? {
|
||||
return (self.collapsedButton, self.collapsedButton.bounds)
|
||||
}
|
||||
|
||||
public func transitionViewForItem(peerId: EnginePeer.Id) -> (UIView, StoryContainerScreen.TransitionView)? {
|
||||
if self.collapsedButton.isUserInteractionEnabled {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -470,7 +470,8 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
|
|||
hasUnseen: hasUnseenStories,
|
||||
isDarkTheme: theme.overallDarkAppearance,
|
||||
activeLineWidth: 3.0,
|
||||
inactiveLineWidth: 2.0
|
||||
inactiveLineWidth: 2.0,
|
||||
counters: nil
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: self.avatarNode.bounds.size
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue