Compare commits
No commits in common. "452ccfacd6bdf6badce21ebee565c906faf37987" and "ec70d1f59d0ae39ae3805874f3585e357de6df5b" have entirely different histories.
452ccfacd6
...
ec70d1f59d
@ -598,9 +598,6 @@
|
|||||||
},
|
},
|
||||||
"Добро пожаловать в Yobble!" : {
|
"Добро пожаловать в Yobble!" : {
|
||||||
|
|
||||||
},
|
|
||||||
"Дождитесь отправки предыдущего сообщения." : {
|
|
||||||
|
|
||||||
},
|
},
|
||||||
"Дополнительные действия." : {
|
"Дополнительные действия." : {
|
||||||
"comment" : "Message profile more action description"
|
"comment" : "Message profile more action description"
|
||||||
@ -1913,9 +1910,6 @@
|
|||||||
},
|
},
|
||||||
"Ошибка в данных. Проверьте введённую информацию." : {
|
"Ошибка в данных. Проверьте введённую информацию." : {
|
||||||
|
|
||||||
},
|
|
||||||
"Ошибка отправки" : {
|
|
||||||
|
|
||||||
},
|
},
|
||||||
"Ошибка при деавторизации." : {
|
"Ошибка при деавторизации." : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|||||||
@ -6,7 +6,6 @@ final class PrivateChatViewModel: ObservableObject {
|
|||||||
@Published private(set) var isInitialLoading: Bool = false
|
@Published private(set) var isInitialLoading: Bool = false
|
||||||
@Published private(set) var isLoadingMore: Bool = false
|
@Published private(set) var isLoadingMore: Bool = false
|
||||||
@Published var errorMessage: String?
|
@Published var errorMessage: String?
|
||||||
@Published var sendingErrorMessage: String?
|
|
||||||
@Published private(set) var isSending: Bool = false
|
@Published private(set) var isSending: Bool = false
|
||||||
@Published private(set) var hasMore: Bool = true
|
@Published private(set) var hasMore: Bool = true
|
||||||
|
|
||||||
@ -75,12 +74,11 @@ final class PrivateChatViewModel: ObservableObject {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard trimmed.count <= maxMessageLength else {
|
guard trimmed.count <= maxMessageLength else {
|
||||||
sendingErrorMessage = NSLocalizedString("Сообщение слишком длинное.", comment: "")
|
errorMessage = NSLocalizedString("Сообщение слишком длинное.", comment: "")
|
||||||
completion(false)
|
completion(false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard !isSending else {
|
guard !isSending else {
|
||||||
sendingErrorMessage = NSLocalizedString("Дождитесь отправки предыдущего сообщения.", comment: "")
|
|
||||||
completion(false)
|
completion(false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -90,7 +88,6 @@ final class PrivateChatViewModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isSending = true
|
isSending = true
|
||||||
sendingErrorMessage = nil
|
|
||||||
|
|
||||||
chatService.sendPrivateMessage(chatId: chatId, content: trimmed) { [weak self] result in
|
chatService.sendPrivateMessage(chatId: chatId, content: trimmed) { [weak self] result in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
@ -116,7 +113,7 @@ final class PrivateChatViewModel: ObservableObject {
|
|||||||
self.errorMessage = nil
|
self.errorMessage = nil
|
||||||
completion(true)
|
completion(true)
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
self.sendingErrorMessage = self.message(for: error)
|
self.errorMessage = self.message(for: error)
|
||||||
completion(false)
|
completion(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,4 +213,4 @@ final class PrivateChatViewModel: ObservableObject {
|
|||||||
|
|
||||||
return lhs.messageId < rhs.messageId
|
return lhs.messageId < rhs.messageId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,7 +22,6 @@ struct PrivateChatView: View {
|
|||||||
@State private var legacyComposerHeight: CGFloat = 40
|
@State private var legacyComposerHeight: CGFloat = 40
|
||||||
@State private var isProfilePresented: Bool = false
|
@State private var isProfilePresented: Bool = false
|
||||||
@FocusState private var isComposerFocused: Bool
|
@FocusState private var isComposerFocused: Bool
|
||||||
@EnvironmentObject private var themeManager: ThemeManager
|
|
||||||
@EnvironmentObject private var messageCenter: IncomingMessageCenter
|
@EnvironmentObject private var messageCenter: IncomingMessageCenter
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
@ -66,14 +65,6 @@ struct PrivateChatView: View {
|
|||||||
}
|
}
|
||||||
.hidden()
|
.hidden()
|
||||||
}
|
}
|
||||||
.alert("Ошибка отправки", isPresented: Binding(
|
|
||||||
get: { viewModel.sendingErrorMessage != nil },
|
|
||||||
set: { if !$0 { viewModel.sendingErrorMessage = nil } }
|
|
||||||
), actions: {
|
|
||||||
Button("OK") { }
|
|
||||||
}, message: {
|
|
||||||
Text(viewModel.sendingErrorMessage ?? "Не удалось отправить сообщение.")
|
|
||||||
})
|
|
||||||
.navigationTitle(toolbarTitle)
|
.navigationTitle(toolbarTitle)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
// .navigationBarBackButtonHidden(true)
|
// .navigationBarBackButtonHidden(true)
|
||||||
@ -253,213 +244,104 @@ struct PrivateChatView: View {
|
|||||||
|
|
||||||
let topPadding: CGFloat = decoratedMessage.showHorns ? 5 : -12
|
let topPadding: CGFloat = decoratedMessage.showHorns ? 5 : -12
|
||||||
let verticalPadding: CGFloat = hasDecorations ? 0 : 0
|
let verticalPadding: CGFloat = hasDecorations ? 0 : 0
|
||||||
|
|
||||||
if #available(iOS 16.0, *) {
|
return HStack(alignment: .bottom, spacing: 8) {
|
||||||
return HStack(alignment: .bottom, spacing: 8) {
|
if isCurrentUser { Spacer(minLength: 32) }
|
||||||
if isCurrentUser { Spacer(minLength: 32) }
|
|
||||||
|
messageBubble(
|
||||||
messageBubble(
|
for: message,
|
||||||
for: message,
|
decorations: decoratedMessage,
|
||||||
decorations: decoratedMessage,
|
isCurrentUser: isCurrentUser
|
||||||
isCurrentUser: isCurrentUser
|
)
|
||||||
)
|
|
||||||
|
if !isCurrentUser { Spacer(minLength: 32) }
|
||||||
if !isCurrentUser { Spacer(minLength: 32) }
|
}
|
||||||
}
|
.padding(.horizontal, 8)
|
||||||
.padding(.horizontal, 8)
|
.padding(.top, topPadding)
|
||||||
.padding(.top, topPadding)
|
.padding(.vertical, verticalPadding)
|
||||||
.padding(.vertical, verticalPadding)
|
.contextMenu {
|
||||||
.contextMenu(menuItems: {
|
if isCurrentUser {
|
||||||
if isCurrentUser {
|
if message.isViewed == true {
|
||||||
if message.isViewed == true {
|
Text("Прочитано") // Placeholder for read status
|
||||||
Text("Прочитано") // Placeholder for read status
|
|
||||||
}
|
|
||||||
Button(action: {
|
|
||||||
// Reply action
|
|
||||||
}) {
|
|
||||||
Text("Ответить")
|
|
||||||
Image(systemName: "arrowshape.turn.up.left")
|
|
||||||
}
|
|
||||||
Button(action: {
|
|
||||||
// Copy action
|
|
||||||
}) {
|
|
||||||
Text("Копировать")
|
|
||||||
Image(systemName: "doc.on.doc")
|
|
||||||
}
|
|
||||||
Button(action: {
|
|
||||||
// Edit action
|
|
||||||
}) {
|
|
||||||
Text("Изменить")
|
|
||||||
Image(systemName: "pencil")
|
|
||||||
}
|
|
||||||
Button(action: {
|
|
||||||
// Pin action
|
|
||||||
}) {
|
|
||||||
Text("Закрепить")
|
|
||||||
Image(systemName: "pin")
|
|
||||||
}
|
|
||||||
Button(action: {
|
|
||||||
// Forward action
|
|
||||||
}) {
|
|
||||||
Text("Переслать")
|
|
||||||
Image(systemName: "arrowshape.turn.up.right")
|
|
||||||
}
|
|
||||||
Button(role: .destructive, action: {
|
|
||||||
// Delete action
|
|
||||||
}) {
|
|
||||||
Text("Удалить")
|
|
||||||
Image(systemName: "trash")
|
|
||||||
}
|
|
||||||
Button(action: {
|
|
||||||
// Select action
|
|
||||||
}) {
|
|
||||||
Text("Выбрать")
|
|
||||||
Image(systemName: "checkmark.circle")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Button(action: {
|
|
||||||
// Reply action
|
|
||||||
}) {
|
|
||||||
Text("Ответить")
|
|
||||||
Image(systemName: "arrowshape.turn.up.left")
|
|
||||||
}
|
|
||||||
Button(action: {
|
|
||||||
// Copy action
|
|
||||||
}) {
|
|
||||||
Text("Копировать")
|
|
||||||
Image(systemName: "doc.on.doc")
|
|
||||||
}
|
|
||||||
Button(action: {
|
|
||||||
// Pin action
|
|
||||||
}) {
|
|
||||||
Text("Закрепить")
|
|
||||||
Image(systemName: "pin")
|
|
||||||
}
|
|
||||||
Button(action: {
|
|
||||||
// Forward action
|
|
||||||
}) {
|
|
||||||
Text("Переслать")
|
|
||||||
Image(systemName: "arrowshape.turn.up.right")
|
|
||||||
}
|
|
||||||
Button(role: .destructive, action: {
|
|
||||||
// Delete action
|
|
||||||
}) {
|
|
||||||
Text("Удалить")
|
|
||||||
Image(systemName: "trash")
|
|
||||||
}
|
|
||||||
Button(action: {
|
|
||||||
// Select action
|
|
||||||
}) {
|
|
||||||
Text("Выбрать")
|
|
||||||
Image(systemName: "checkmark.circle")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, preview: {
|
Button(action: {
|
||||||
messageBubble(
|
// Reply action
|
||||||
for: message,
|
}) {
|
||||||
decorations: decoratedMessage,
|
Text("Ответить")
|
||||||
isCurrentUser: isCurrentUser
|
Image(systemName: "arrowshape.turn.up.left")
|
||||||
)
|
}
|
||||||
})
|
Button(action: {
|
||||||
} else {
|
// Copy action
|
||||||
return HStack(alignment: .bottom, spacing: 8) {
|
}) {
|
||||||
if isCurrentUser { Spacer(minLength: 32) }
|
Text("Копировать")
|
||||||
|
Image(systemName: "doc.on.doc")
|
||||||
messageBubble(
|
}
|
||||||
for: message,
|
Button(action: {
|
||||||
decorations: decoratedMessage,
|
// Edit action
|
||||||
isCurrentUser: isCurrentUser
|
}) {
|
||||||
)
|
Text("Изменить")
|
||||||
|
Image(systemName: "pencil")
|
||||||
if !isCurrentUser { Spacer(minLength: 32) }
|
}
|
||||||
}
|
Button(action: {
|
||||||
.padding(.horizontal, 8)
|
// Pin action
|
||||||
.padding(.top, topPadding)
|
}) {
|
||||||
.padding(.vertical, verticalPadding)
|
Text("Закрепить")
|
||||||
.contextMenu {
|
Image(systemName: "pin")
|
||||||
if isCurrentUser {
|
}
|
||||||
if message.isViewed == true {
|
Button(action: {
|
||||||
Text("Прочитано") // Placeholder for read status
|
// Forward action
|
||||||
}
|
}) {
|
||||||
Button(action: {
|
Text("Переслать")
|
||||||
// Reply action
|
Image(systemName: "arrowshape.turn.up.right")
|
||||||
}) {
|
}
|
||||||
Text("Ответить")
|
Button(role: .destructive, action: {
|
||||||
Image(systemName: "arrowshape.turn.up.left")
|
// Delete action
|
||||||
}
|
}) {
|
||||||
Button(action: {
|
Text("Удалить")
|
||||||
// Copy action
|
Image(systemName: "trash")
|
||||||
}) {
|
}
|
||||||
Text("Копировать")
|
Button(action: {
|
||||||
Image(systemName: "doc.on.doc")
|
// Select action
|
||||||
}
|
}) {
|
||||||
Button(action: {
|
Text("Выбрать")
|
||||||
// Edit action
|
Image(systemName: "checkmark.circle")
|
||||||
}) {
|
}
|
||||||
Text("Изменить")
|
} else {
|
||||||
Image(systemName: "pencil")
|
Button(action: {
|
||||||
}
|
// Reply action
|
||||||
Button(action: {
|
}) {
|
||||||
// Pin action
|
Text("Ответить")
|
||||||
}) {
|
Image(systemName: "arrowshape.turn.up.left")
|
||||||
Text("Закрепить")
|
}
|
||||||
Image(systemName: "pin")
|
Button(action: {
|
||||||
}
|
// Copy action
|
||||||
Button(action: {
|
}) {
|
||||||
// Forward action
|
Text("Копировать")
|
||||||
}) {
|
Image(systemName: "doc.on.doc")
|
||||||
Text("Переслать")
|
}
|
||||||
Image(systemName: "arrowshape.turn.up.right")
|
Button(action: {
|
||||||
}
|
// Pin action
|
||||||
Button(role: .destructive, action: {
|
}) {
|
||||||
// Delete action
|
Text("Закрепить")
|
||||||
}) {
|
Image(systemName: "pin")
|
||||||
Text("Удалить")
|
}
|
||||||
Image(systemName: "trash")
|
Button(action: {
|
||||||
}
|
// Forward action
|
||||||
Button(action: {
|
}) {
|
||||||
// Select action
|
Text("Переслать")
|
||||||
}) {
|
Image(systemName: "arrowshape.turn.up.right")
|
||||||
Text("Выбрать")
|
}
|
||||||
Image(systemName: "checkmark.circle")
|
Button(role: .destructive, action: {
|
||||||
}
|
// Delete action
|
||||||
} else {
|
}) {
|
||||||
Button(action: {
|
Text("Удалить")
|
||||||
// Reply action
|
Image(systemName: "trash")
|
||||||
}) {
|
}
|
||||||
Text("Ответить")
|
Button(action: {
|
||||||
Image(systemName: "arrowshape.turn.up.left")
|
// Select action
|
||||||
}
|
}) {
|
||||||
Button(action: {
|
Text("Выбрать")
|
||||||
// Copy action
|
Image(systemName: "checkmark.circle")
|
||||||
}) {
|
|
||||||
Text("Копировать")
|
|
||||||
Image(systemName: "doc.on.doc")
|
|
||||||
}
|
|
||||||
Button(action: {
|
|
||||||
// Pin action
|
|
||||||
}) {
|
|
||||||
Text("Закрепить")
|
|
||||||
Image(systemName: "pin")
|
|
||||||
}
|
|
||||||
Button(action: {
|
|
||||||
// Forward action
|
|
||||||
}) {
|
|
||||||
Text("Переслать")
|
|
||||||
Image(systemName: "arrowshape.turn.up.right")
|
|
||||||
}
|
|
||||||
Button(role: .destructive, action: {
|
|
||||||
// Delete action
|
|
||||||
}) {
|
|
||||||
Text("Удалить")
|
|
||||||
Image(systemName: "trash")
|
|
||||||
}
|
|
||||||
Button(action: {
|
|
||||||
// Select action
|
|
||||||
}) {
|
|
||||||
Text("Выбрать")
|
|
||||||
Image(systemName: "checkmark.circle")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -656,7 +538,7 @@ struct PrivateChatView: View {
|
|||||||
.padding(.horizontal, 6)
|
.padding(.horizontal, 6)
|
||||||
.padding(.top, 10)
|
.padding(.top, 10)
|
||||||
.padding(.bottom, 8)
|
.padding(.bottom, 8)
|
||||||
.modifier(ComposerBackgroundModifier(theme: themeManager.theme))
|
.background(.ultraThinMaterial)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func scrollToBottomButton(proxy: ScrollViewProxy) -> some View {
|
private func scrollToBottomButton(proxy: ScrollViewProxy) -> some View {
|
||||||
@ -731,10 +613,10 @@ struct PrivateChatView: View {
|
|||||||
let text = draftText.trimmingCharacters(in: .whitespacesAndNewlines)
|
let text = draftText.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
guard !text.isEmpty else { return }
|
guard !text.isEmpty else { return }
|
||||||
|
|
||||||
|
draftText = ""
|
||||||
scrollToBottomTrigger = .init()
|
scrollToBottomTrigger = .init()
|
||||||
viewModel.sendMessage(text: text) { success in
|
viewModel.sendMessage(text: text) { success in
|
||||||
if success {
|
if success {
|
||||||
draftText = ""
|
|
||||||
hasPositionedToBottom = true
|
hasPositionedToBottom = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1030,28 +912,6 @@ private var headerPlaceholderAvatar: some View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private struct ComposerBackgroundModifier: ViewModifier {
|
|
||||||
let theme: Theme
|
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
func body(content: Content) -> some View {
|
|
||||||
if theme == .oledDark {
|
|
||||||
content
|
|
||||||
.background(Color.black.opacity(0.85)) // ← глубокий чёрный
|
|
||||||
// .overlay(
|
|
||||||
// Color.black.opacity(0.25) // ← лёгкое затемнение сверху
|
|
||||||
// .allowsHitTesting(false)
|
|
||||||
// )
|
|
||||||
// content.background(.ultraThinMaterial)
|
|
||||||
// content.background(Color.black)
|
|
||||||
// content.background(Color(white: 0.15))
|
|
||||||
} else {
|
|
||||||
content.background(.ultraThinMaterial)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Helper model that stores a message alongside horn/leg flags for grouping sequences.
|
/// Helper model that stores a message alongside horn/leg flags for grouping sequences.
|
||||||
private struct DecoratedMessage: Identifiable {
|
private struct DecoratedMessage: Identifiable {
|
||||||
let message: MessageItem
|
let message: MessageItem
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user