feat: новые функции, исправлены критические ошибки сборки и баги интерфейса, больше подписей в файлах

This commit is contained in:
ichmagmaus 812
2026-03-04 22:06:16 +01:00
parent a614259289
commit f033954db2
81 changed files with 1256 additions and 298 deletions
+8 -3
View File
@@ -3,9 +3,14 @@ load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "ChatListUI",
module_name = "ChatListUI",
srcs = glob([
"Sources/**/*.swift",
]),
srcs = glob(
[
"Sources/**/*.swift",
],
exclude = [
"Sources/ChatListFilterTabContainerNode.swift",
],
),
copts = [
"-warnings-as-errors",
],
@@ -6595,8 +6595,17 @@ private final class ChatListLocationContext {
var proxyButton: AnyComponentWithIdentity<NavigationButtonComponentEnvironment>?
var storyButton: AnyComponentWithIdentity<NavigationButtonComponentEnvironment>?
// GHOSTGRAM: Account switcher liquid glass avatar button for the next account
var accountSwitcherButton: AnyComponentWithIdentity<NavigationButtonComponentEnvironment>?
private var accountSwitcherDisposable: Disposable?
private var accountSwitcherAvatarDisposable: Disposable?
var rightButtons: [AnyComponentWithIdentity<NavigationButtonComponentEnvironment>] {
var result: [AnyComponentWithIdentity<NavigationButtonComponentEnvironment>] = []
// Account switcher is first leftmost of the right-side buttons
if let accountSwitcherButton = self.accountSwitcherButton {
result.append(accountSwitcherButton)
}
if let rightButton = self.rightButton {
result.append(rightButton)
}
@@ -6631,6 +6640,90 @@ private final class ChatListLocationContext {
self.location = location
self.parentController = parentController
// GHOSTGRAM: Subscribe to account list and maintain the switcher button
if case .chatList(.root) = location {
self.accountSwitcherDisposable = (context.sharedContext.activeAccountsWithInfo
|> deliverOnMainQueue)
.start(next: { [weak self] (info: (primary: AccountRecordId?, accounts: [AccountWithInfo])) in
guard let self else { return }
let primaryId = info.primary
let accounts = info.accounts
// Only show when there is more than one account
guard accounts.count > 1, let primaryId = primaryId else {
if self.accountSwitcherButton != nil {
self.accountSwitcherButton = nil
let _ = self.parentController?.updateHeaderContent()
self.parentController?.requestLayout(transition: .immediate)
}
return
}
// Find next account cyclically
let currentIndex = accounts.firstIndex(where: { $0.account.id == primaryId }) ?? 0
let nextIndex = (currentIndex + 1) % accounts.count
let nextAccount = accounts[nextIndex]
let nextPeer = nextAccount.peer
let nextPeerId = "\(nextAccount.account.id)"
// Build button placeholder immediately (image loads async)
let buildButton: (UIImage?) -> Void = { [weak self] image in
guard let self else { return }
guard case .chatList(.root) = self.location else { return }
let sharedContext = self.context.sharedContext
let nextAccountId = nextAccount.account.id
self.accountSwitcherButton = AnyComponentWithIdentity(
id: "accountSwitcher",
component: AnyComponent(NavigationButtonComponent(
content: .avatar(peerId: nextPeerId, avatarImage: image),
pressed: { [weak sharedContext] _ in
sharedContext?.switchToAccount(id: nextAccountId, fromSettingsController: nil, withChatListController: nil)
}
))
)
// Trigger header rebuild
let _ = self.parentController?.updateHeaderContent()
self.parentController?.requestLayout(transition: .immediate)
}
// Attempt to load the peer's avatar from mediaBox
if let representation = nextPeer.smallProfileImage {
self.accountSwitcherAvatarDisposable?.dispose()
let resource = representation.resource
let account = nextAccount.account
// Try to read cached data first; if not ready, trigger a fetch then watch for completion
self.accountSwitcherAvatarDisposable = (account.postbox.mediaBox
.resourceData(resource)
|> deliverOnMainQueue)
.start(next: { data in
if data.complete, let uiImage = UIImage(contentsOfFile: data.path) {
buildButton(uiImage)
}
}, completed: {
// If resource was never complete after signal ended, show placeholder
buildButton(nil)
})
// Trigger the actual network fetch so mediaBox populates the resource
if let peerReference = PeerReference(nextPeer) {
let _ = fetchedMediaResource(
mediaBox: account.postbox.mediaBox,
userLocation: .peer(nextPeer.id),
userContentType: .avatar,
reference: .avatar(peer: peerReference, resource: resource)
).start()
}
} else {
// No photo show placeholder
buildButton(nil)
}
})
}
let hasProxy = context.sharedContext.accountManager.sharedData(keys: [SharedDataKeys.proxySettings])
|> map { sharedData -> (Bool, Bool) in
if let settings = sharedData.entries[SharedDataKeys.proxySettings]?.get(ProxySettings.self) {
@@ -6949,6 +7042,8 @@ private final class ChatListLocationContext {
deinit {
self.titleDisposable?.dispose()
self.stateDisposable?.dispose()
self.accountSwitcherDisposable?.dispose()
self.accountSwitcherAvatarDisposable?.dispose()
}
private func updateChatList(
@@ -6982,6 +7077,7 @@ private final class ChatListLocationContext {
if case .chatList(.root) = self.location {
self.rightButton = nil
self.storyButton = nil
self.accountSwitcherButton = nil
}
let title = !stateAndFilterId.state.selectedPeerIds.isEmpty ? presentationData.strings.ChatList_SelectedChats(Int32(stateAndFilterId.state.selectedPeerIds.count)) : defaultTitle
@@ -6997,6 +7093,7 @@ private final class ChatListLocationContext {
if case .chatList(.root) = self.location {
self.rightButton = nil
self.storyButton = nil
self.accountSwitcherButton = nil
}
self.leftButton = AnyComponentWithIdentity(id: "done", component: AnyComponent(NavigationButtonComponent(
content: .text(title: presentationData.strings.Common_Done, isBold: true),
@@ -14,6 +14,7 @@ import MergedAvatarsNode
import TextNodeWithEntities
import TextFormat
import AvatarNode
import GlobalControlPanelsContext
class ChatListNoticeItem: ListViewItem {
enum Action {
@@ -25,12 +26,12 @@ class ChatListNoticeItem: ListViewItem {
let context: AccountContext
let theme: PresentationTheme
let strings: PresentationStrings
let notice: ChatListNotice
let notice: GlobalControlPanelsContext.ChatListNotice
let action: (Action) -> Void
let selectable: Bool = true
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, notice: ChatListNotice, action: @escaping (Action) -> Void) {
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, notice: GlobalControlPanelsContext.ChatListNotice, action: @escaping (Action) -> Void) {
self.context = context
self.theme = theme
self.strings = strings
@@ -130,7 +131,7 @@ final class ChatListNoticeItemNode: ItemListRevealOptionsItemNode {
self.arrowNode = ASImageNode()
self.separatorNode = ASDisplayNode()
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
super.init(layerBacked: false, rotated: false, seeThrough: false)
self.contentContainer.clipsToBounds = true
self.clipsToBounds = true