Compare commits

..

No commits in common. "8acacdb8c1619412c712b7853f3000319c02adfa" and "198b51bd91edcf0c7a35f6515b093a32e29b959d" have entirely different histories.

5 changed files with 50 additions and 203 deletions

View File

@ -398,15 +398,6 @@
},
"Если не нашли ответ, напишите нам своё предложение или проблему." : {
"comment" : "FAQ: contact developers footer"
},
"Заблокированные" : {
},
"Заблокированные пользователи" : {
},
"Заблокируйте аккаунт, чтобы скрыть его сообщения и взаимодействия" : {
},
"Заглушка: Push-уведомления" : {
@ -1823,9 +1814,6 @@
},
"Публичная информация" : {
},
"Разблокировать" : {
},
"Разрешить пересылку сообщений" : {
"localizations" : {
@ -2189,9 +2177,6 @@
}
}
}
},
"У вас нет заблокированных пользователей" : {
},
"Уведомления" : {
"localizations" : {

View File

@ -32,7 +32,6 @@ struct ChatsTab: View {
@State private var isPendingChatActive: Bool = false
private let searchRevealDistance: CGFloat = 90
private let scrollToTopAnchorId = "ChatsListTopAnchor"
private var currentUserId: String? {
let userId = loginViewModel.userId
@ -110,9 +109,8 @@ struct ChatsTab: View {
}
private var chatList: some View {
ScrollViewReader { proxy in
ZStack {
List {
ZStack {
List {
// VStack(spacing: 0) {
// searchBar
// .padding(.horizontal, 16)
@ -120,70 +118,63 @@ struct ChatsTab: View {
// }
// .background(Color(UIColor.systemBackground))
if let message = viewModel.errorMessage {
Section {
HStack(alignment: .top, spacing: 8) {
Image(systemName: "exclamationmark.triangle.fill")
.foregroundColor(.orange)
Text(message)
if let message = viewModel.errorMessage {
Section {
HStack(alignment: .top, spacing: 8) {
Image(systemName: "exclamationmark.triangle.fill")
.foregroundColor(.orange)
Text(message)
.font(.subheadline)
.foregroundColor(.orange)
Spacer(minLength: 0)
Button(action: triggerChatsReload) {
Text(NSLocalizedString("Обновить", comment: ""))
.font(.subheadline)
.foregroundColor(.orange)
Spacer(minLength: 0)
Button(action: triggerChatsReload) {
Text(NSLocalizedString("Обновить", comment: ""))
.font(.subheadline)
}
}
.padding(.vertical, 4)
}
.listRowInsets(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16))
.padding(.vertical, 4)
}
.listRowInsets(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16))
}
if isSearching {
Section(header: localSearchHeader) {
if localSearchResults.isEmpty {
emptySearchResultView
.listRowInsets(EdgeInsets(top: 24, leading: 16, bottom: 24, trailing: 16))
.listRowSeparator(.hidden)
} else {
ForEach(localSearchResults) { chat in
chatRowItem(for: chat)
}
}
}
if isSearching {
Section(header: localSearchHeader) {
if localSearchResults.isEmpty {
emptySearchResultView
.listRowInsets(EdgeInsets(top: 24, leading: 16, bottom: 24, trailing: 16))
.listRowSeparator(.hidden)
} else {
let firstLocalChatId = localSearchResults.first?.chatId
ForEach(localSearchResults) { chat in
chatRowItem(for: chat)
.id(chat.chatId == firstLocalChatId ? scrollToTopAnchorId : chat.chatId)
}
}
}
Section(header: globalSearchHeader) {
globalSearchContent
}
} else {
Section(header: globalSearchHeader) {
globalSearchContent
}
} else {
// if let message = viewModel.errorMessage, viewModel.chats.isEmpty {
// errorState(message: message)
// } else
if viewModel.chats.isEmpty {
emptyState
} else {
if viewModel.chats.isEmpty {
emptyState
} else {
let firstChatId = viewModel.chats.first?.chatId
ForEach(viewModel.chats) { chat in
chatRowItem(for: chat)
.id(chat.chatId == firstChatId ? scrollToTopAnchorId : chat.chatId)
}
ForEach(viewModel.chats) { chat in
chatRowItem(for: chat)
}
if viewModel.isLoadingMore {
loadingMoreRow
}
if viewModel.isLoadingMore {
loadingMoreRow
}
}
}
.listStyle(.plain)
.modifier(ScrollDismissesKeyboardModifier())
.simultaneousGesture(searchBarGesture)
.simultaneousGesture(tapToDismissKeyboardGesture)
.onReceive(NotificationCenter.default.publisher(for: .chatsShouldScrollToTop)) { _ in
scrollChatsToTop(using: proxy)
}
}
.listStyle(.plain)
.modifier(ScrollDismissesKeyboardModifier())
.simultaneousGesture(searchBarGesture)
.simultaneousGesture(tapToDismissKeyboardGesture)
// .safeAreaInset(edge: .top) {
// VStack(spacing: 0) {
// searchBar
@ -195,8 +186,7 @@ struct ChatsTab: View {
// .background(Color(UIColor.systemBackground))
// }
pendingChatNavigationLink
}
pendingChatNavigationLink
}
}
@ -227,14 +217,6 @@ struct ChatsTab: View {
}
}
private func scrollChatsToTop(using proxy: ScrollViewProxy) {
DispatchQueue.main.async {
withAnimation(.spring(response: 0.35, dampingFraction: 0.75)) {
proxy.scrollTo(scrollToTopAnchorId, anchor: .top)
}
}
}
private var searchBarGesture: some Gesture {
DragGesture(minimumDistance: 10, coordinateSpace: .local)
.onChanged { value in
@ -337,10 +319,8 @@ struct ChatsTab: View {
if globalSearchResults.isEmpty {
globalSearchEmptyRow
} else {
let firstGlobalUserId = globalSearchResults.first?.id
ForEach(globalSearchResults) { user in
globalSearchRow(for: user)
.id(user.id == firstGlobalUserId ? AnyHashable(scrollToTopAnchorId) : AnyHashable(user.id))
}
}
}
@ -1201,5 +1181,4 @@ extension Notification.Name {
static let debugRefreshChats = Notification.Name("debugRefreshChats")
static let chatsShouldRefresh = Notification.Name("chatsShouldRefresh")
static let chatsReloadCompleted = Notification.Name("chatsReloadCompleted")
static let chatsShouldScrollToTop = Notification.Name("chatsShouldScrollToTop")
}

View File

@ -14,7 +14,7 @@ struct CustomTabBar: View {
}
TabBarButton(systemName: "bubble.left.and.bubble.right.fill", text: NSLocalizedString("Чаты", comment: ""), isSelected: selectedTab == 2) {
handleChatsTabTap()
selectedTab = 2
}
TabBarButton(systemName: "gearshape.fill", text: NSLocalizedString("Настройки", comment: ""), isSelected: selectedTab == 5) {
@ -34,7 +34,7 @@ struct CustomTabBar: View {
}
TabBarButton(systemName: "bubble.left.and.bubble.right.fill", text: NSLocalizedString("Чаты", comment: ""), isSelected: selectedTab == 2) {
handleChatsTabTap()
selectedTab = 2
}
TabBarButton(systemName: "person.crop.square", text: NSLocalizedString("Лицо", comment: ""), isSelected: selectedTab == 3) {
@ -93,13 +93,3 @@ struct CreateButton: View {
.offset(y: -3)
}
}
private extension CustomTabBar {
func handleChatsTabTap() {
if selectedTab == 2 {
NotificationCenter.default.post(name: .chatsShouldScrollToTop, object: nil)
} else {
selectedTab = 2
}
}
}

View File

@ -1,103 +0,0 @@
import SwiftUI
struct BlockedUsersView: View {
@State private var blockedUsers: [BlockedUser] = []
var body: some View {
List {
if blockedUsers.isEmpty {
emptyState
} else {
Section(header: Text(NSLocalizedString("Заблокированные", comment: ""))) {
ForEach(blockedUsers) { user in
HStack(spacing: 12) {
Circle()
.fill(Color.accentColor.opacity(0.15))
.frame(width: 44, height: 44)
.overlay(
Text(user.initials)
.font(.headline)
.foregroundColor(.accentColor)
)
VStack(alignment: .leading, spacing: 4) {
Text(user.displayName)
.font(.body)
if let handle = user.handle {
Text(handle)
.font(.caption)
.foregroundColor(.secondary)
}
}
Spacer()
Button(role: .destructive) {
unblock(user)
} label: {
Text(NSLocalizedString("Разблокировать", comment: ""))
}
.buttonStyle(.borderless)
}
.padding(.vertical, 4)
}
}
}
}
.navigationTitle(NSLocalizedString("Заблокированные", comment: ""))
.navigationBarTitleDisplayMode(.inline)
.task {
await loadBlockedUsers()
}
}
private var emptyState: some View {
VStack(spacing: 12) {
Image(systemName: "hand.raised")
.font(.system(size: 48))
.foregroundColor(.secondary)
Text(NSLocalizedString("У вас нет заблокированных пользователей", comment: ""))
.font(.headline)
.multilineTextAlignment(.center)
// Text(NSLocalizedString("Заблокируйте аккаунт, чтобы скрыть его сообщения и взаимодействия", comment: ""))
// .font(.subheadline)
// .foregroundColor(.secondary)
// .multilineTextAlignment(.center)
}
.frame(maxWidth: .infinity, alignment: .center)
.padding(.vertical, 32)
.listRowInsets(EdgeInsets(top: 16, leading: 16, bottom: 16, trailing: 16))
.listRowSeparator(.hidden)
}
private func loadBlockedUsers() async {
// TODO: integrate with real data source once available
}
private func unblock(_ user: BlockedUser) {
// TODO: implement unblock logic when backend is ready
blockedUsers.removeAll { $0.id == user.id }
}
}
private struct BlockedUser: Identifiable, Equatable {
let id: UUID
let displayName: String
let handle: String?
var initials: String {
let components = displayName.split(separator: " ")
let nameInitials = components.prefix(2).compactMap { $0.first }
if !nameInitials.isEmpty {
return nameInitials
.map { String($0).uppercased() }
.joined()
}
if let handle {
let filtered = handle.filter { $0.isLetter }.prefix(2)
if !filtered.isEmpty {
return filtered.uppercased()
}
}
return "??"
}
}

View File

@ -21,10 +21,6 @@ struct SettingsView: View {
NavigationLink(destination: EditPrivacyView()) {
Label(NSLocalizedString("Конфиденциальность", comment: ""), systemImage: "lock.fill")
}
NavigationLink(destination: BlockedUsersView()) {
Label(NSLocalizedString("Заблокированные пользователи", comment: ""), systemImage: "hand.raised.fill")
}
}
// MARK: - Безопасность