Исправление расширения поля пароля при переключении видимости: перенос toggle в UIKit
This commit is contained in:
@@ -140,6 +140,11 @@ private extension ChatListSearchContent {
|
||||
|
||||
ForEach(viewModel.recentSearches, id: \.publicKey) { recent in
|
||||
recentRow(recent)
|
||||
if recent.publicKey != viewModel.recentSearches.last?.publicKey {
|
||||
Divider()
|
||||
.padding(.leading, 68)
|
||||
.foregroundStyle(RosettaColors.Adaptive.divider)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +184,7 @@ private extension ChatListSearchContent {
|
||||
let colorIdx = RosettaColors.avatarColorIndex(for: user.title, publicKey: user.publicKey)
|
||||
|
||||
return Button {
|
||||
onSelectRecent(user.username.isEmpty ? user.publicKey : user.username)
|
||||
onOpenDialog(ChatRoute(recent: user))
|
||||
} label: {
|
||||
HStack(spacing: 10) {
|
||||
AvatarView(
|
||||
@@ -207,6 +212,7 @@ private extension ChatListSearchContent {
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.vertical, 5)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
@@ -263,6 +269,7 @@ private extension ChatListSearchContent {
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.vertical, 12)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
|
||||
@@ -32,16 +32,13 @@ struct ChatListView: View {
|
||||
@State private var hasPinnedChats = false
|
||||
@FocusState private var isSearchFocused: Bool
|
||||
|
||||
@MainActor static var _bodyCount = 0
|
||||
var body: some View {
|
||||
let _ = Self._bodyCount += 1
|
||||
let _ = print("🟡 ChatListView.body #\(Self._bodyCount)")
|
||||
NavigationStack(path: $navigationState.path) {
|
||||
VStack(spacing: 0) {
|
||||
// Custom search bar
|
||||
customSearchBar
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.top, 6)
|
||||
.padding(.top, 12)
|
||||
.padding(.bottom, 8)
|
||||
.background(
|
||||
(hasPinnedChats && !isSearchActive
|
||||
@@ -290,7 +287,7 @@ private extension ChatListView {
|
||||
Text("Edit")
|
||||
.font(.system(size: 17, weight: .medium))
|
||||
.foregroundStyle(RosettaColors.Adaptive.text)
|
||||
.frame(height: 44)
|
||||
.frame(height: 40)
|
||||
.padding(.horizontal, 10)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
@@ -312,7 +309,7 @@ private extension ChatListView {
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 22, height: 22)
|
||||
.frame(width: 44, height: 44)
|
||||
.frame(width: 40, height: 40)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityLabel("Add chat")
|
||||
@@ -323,7 +320,7 @@ private extension ChatListView {
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 20, height: 20)
|
||||
.frame(width: 44, height: 44)
|
||||
.frame(width: 40, height: 40)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityLabel("New chat")
|
||||
@@ -377,10 +374,7 @@ private struct ToolbarTitleView: View {
|
||||
/// Changes to these `@Observable` singletons only re-render this small view,
|
||||
/// not the parent ChatListView / NavigationStack.
|
||||
private struct ToolbarStoriesAvatar: View {
|
||||
@MainActor static var _bodyCount = 0
|
||||
var body: some View {
|
||||
let _ = Self._bodyCount += 1
|
||||
let _ = print("🟣 ToolbarStoriesAvatar.body #\(Self._bodyCount)")
|
||||
let pk = AccountManager.shared.currentAccount?.publicKey ?? ""
|
||||
let initials = RosettaColors.initials(
|
||||
name: SessionManager.shared.displayName, publicKey: pk
|
||||
@@ -396,10 +390,7 @@ private struct ToolbarStoriesAvatar: View {
|
||||
/// During handshake, `connectionState` changes 4+ times rapidly — this view
|
||||
/// absorbs those re-renders instead of cascading them to the NavigationStack.
|
||||
private struct DeviceVerificationBannersContainer: View {
|
||||
@MainActor static var _bodyCount = 0
|
||||
var body: some View {
|
||||
let _ = Self._bodyCount += 1
|
||||
let _ = print("⚪ DeviceVerificationBanners.body #\(Self._bodyCount)")
|
||||
let proto = ProtocolManager.shared
|
||||
|
||||
if proto.connectionState == .deviceVerificationRequired {
|
||||
@@ -424,11 +415,7 @@ private struct ChatListDialogContent: View {
|
||||
@ObservedObject var viewModel: ChatListViewModel
|
||||
@ObservedObject var navigationState: ChatListNavigationState
|
||||
var onPinnedStateChange: (Bool) -> Void = { _ in }
|
||||
@MainActor static var _bodyCount = 0
|
||||
|
||||
var body: some View {
|
||||
let _ = Self._bodyCount += 1
|
||||
let _ = print("🔶 ChatListDialogContent.body #\(Self._bodyCount)")
|
||||
let hasPinned = !viewModel.pinnedDialogs.isEmpty
|
||||
if viewModel.filteredDialogs.isEmpty && !viewModel.isLoading {
|
||||
ChatEmptyStateView(searchText: "")
|
||||
|
||||
@@ -46,8 +46,8 @@ final class ChatListViewModel: ObservableObject {
|
||||
var unpinnedDialogs: [Dialog] { filteredDialogs.filter { !$0.isPinned } }
|
||||
|
||||
var totalUnreadCount: Int {
|
||||
DialogRepository.shared.sortedDialogs
|
||||
.filter { !$0.isMuted }
|
||||
DialogRepository.shared.dialogs.values
|
||||
.lazy.filter { !$0.isMuted }
|
||||
.reduce(0) { $0 + $1.unreadCount }
|
||||
}
|
||||
|
||||
@@ -172,7 +172,7 @@ final class ChatListViewModel: ObservableObject {
|
||||
|
||||
var packet = PacketSearch()
|
||||
packet.privateKey = hash
|
||||
packet.search = query
|
||||
packet.search = query.lowercased()
|
||||
Self.logger.debug("📤 Sending search packet for '\(query)'")
|
||||
ProtocolManager.shared.sendPacket(packet)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import SwiftUI
|
||||
import Combine
|
||||
|
||||
// MARK: - ChatRowView
|
||||
|
||||
@@ -20,6 +21,11 @@ import SwiftUI
|
||||
struct ChatRowView: View {
|
||||
let dialog: Dialog
|
||||
|
||||
/// Desktop parity: recheck delivery timeout every 40s so clock → error
|
||||
/// transitions happen automatically without user scrolling.
|
||||
@State private var now = Date()
|
||||
private let recheckTimer = Timer.publish(every: 40, on: .main, in: .common).autoconnect()
|
||||
|
||||
var displayTitle: String {
|
||||
if dialog.isSavedMessages { return "Saved Messages" }
|
||||
if !dialog.opponentTitle.isEmpty { return dialog.opponentTitle }
|
||||
@@ -38,6 +44,7 @@ struct ChatRowView: View {
|
||||
.padding(.trailing, 16)
|
||||
.frame(height: 78)
|
||||
.contentShape(Rectangle())
|
||||
.onReceive(recheckTimer) { now = $0 }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,7 +222,7 @@ private extension ChatRowView {
|
||||
private var isWithinWaitingWindow: Bool {
|
||||
guard dialog.lastMessageTimestamp > 0 else { return true }
|
||||
let sentDate = Date(timeIntervalSince1970: Double(dialog.lastMessageTimestamp) / 1000)
|
||||
return Date().timeIntervalSince(sentDate) < Self.maxWaitingSeconds
|
||||
return now.timeIntervalSince(sentDate) < Self.maxWaitingSeconds
|
||||
}
|
||||
|
||||
var unreadBadge: some View {
|
||||
@@ -244,6 +251,16 @@ private extension ChatRowView {
|
||||
// MARK: - Time Formatting
|
||||
|
||||
private extension ChatRowView {
|
||||
private static let timeFormatter: DateFormatter = {
|
||||
let f = DateFormatter(); f.dateFormat = "h:mm a"; return f
|
||||
}()
|
||||
private static let dayFormatter: DateFormatter = {
|
||||
let f = DateFormatter(); f.dateFormat = "EEE"; return f
|
||||
}()
|
||||
private static let dateFormatter: DateFormatter = {
|
||||
let f = DateFormatter(); f.dateFormat = "dd.MM.yy"; return f
|
||||
}()
|
||||
|
||||
var formattedTime: String {
|
||||
guard dialog.lastMessageTimestamp > 0 else { return "" }
|
||||
|
||||
@@ -252,19 +269,13 @@ private extension ChatRowView {
|
||||
let calendar = Calendar.current
|
||||
|
||||
if calendar.isDateInToday(date) {
|
||||
let f = DateFormatter()
|
||||
f.dateFormat = "h:mm a"
|
||||
return f.string(from: date)
|
||||
return Self.timeFormatter.string(from: date)
|
||||
} else if calendar.isDateInYesterday(date) {
|
||||
return "Yesterday"
|
||||
} else if let days = calendar.dateComponents([.day], from: date, to: now).day, days < 7 {
|
||||
let f = DateFormatter()
|
||||
f.dateFormat = "EEE"
|
||||
return f.string(from: date)
|
||||
return Self.dayFormatter.string(from: date)
|
||||
} else {
|
||||
let f = DateFormatter()
|
||||
f.dateFormat = "dd.MM.yy"
|
||||
return f.string(from: date)
|
||||
return Self.dateFormatter.string(from: date)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user