добавил кнопку Копировать тект в контекстном меню сообщения #13
This commit is contained in:
1 parent
888501485a
commit
fe2b14ecee
1 file changed
+52
-16
@@ -71,6 +71,18 @@ struct MessageBubble: View {
|
|||||||
Label("Переслать", systemImage: "arrowshape.turn.up.right")
|
Label("Переслать", systemImage: "arrowshape.turn.up.right")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if message.hasText {
|
||||||
|
Button(action: {
|
||||||
|
#if os(macOS)
|
||||||
|
NSPasteboard.general.clearContents()
|
||||||
|
NSPasteboard.general.setString(message.text, forType: .string)
|
||||||
|
#else
|
||||||
|
UIPasteboard.general.string = message.text
|
||||||
|
#endif
|
||||||
|
}) {
|
||||||
|
Label("Копировать текст", systemImage: "doc.on.doc")
|
||||||
|
}
|
||||||
|
}
|
||||||
if model.canRetryMediaMessage(message) {
|
if model.canRetryMediaMessage(message) {
|
||||||
Button(action: onRetry) {
|
Button(action: onRetry) {
|
||||||
Label("Повторить отправку", systemImage: "arrow.clockwise")
|
Label("Повторить отправку", systemImage: "arrow.clockwise")
|
||||||
@@ -142,16 +154,20 @@ struct MessageBubble: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var replySwipeIcon: some View {
|
private var replySwipeIcon: some View {
|
||||||
Image(systemName: replySwipeArmed
|
Image(
|
||||||
|
systemName: replySwipeArmed
|
||||||
? "arrowshape.turn.up.left.fill"
|
? "arrowshape.turn.up.left.fill"
|
||||||
: "arrowshape.turn.up.left")
|
: "arrowshape.turn.up.left"
|
||||||
|
)
|
||||||
.font(.system(size: 19, weight: .semibold))
|
.font(.system(size: 19, weight: .semibold))
|
||||||
.foregroundStyle(Color.accentColor)
|
.foregroundStyle(Color.accentColor)
|
||||||
.scaleEffect(
|
.scaleEffect(
|
||||||
CGFloat(0.8) + CGFloat(0.2) * MessageReplySwipePolicy.progress(for: replySwipeOffset)
|
CGFloat(0.8) + CGFloat(0.2) * MessageReplySwipePolicy.progress(for: replySwipeOffset)
|
||||||
)
|
)
|
||||||
.opacity(Double(
|
.opacity(
|
||||||
CGFloat(0.35) + CGFloat(0.65) * MessageReplySwipePolicy.progress(for: replySwipeOffset)
|
Double(
|
||||||
|
CGFloat(0.35) + CGFloat(0.65)
|
||||||
|
* MessageReplySwipePolicy.progress(for: replySwipeOffset)
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,7 +248,8 @@ struct MessageBubble: View {
|
|||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
guard let targetID = message.replyToID,
|
guard let targetID = message.replyToID,
|
||||||
message.kind == .text || message.kind == .system else { return }
|
message.kind == .text || message.kind == .system
|
||||||
|
else { return }
|
||||||
onReplyTap(targetID)
|
onReplyTap(targetID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -246,7 +263,10 @@ struct MessageBubble: View {
|
|||||||
if message.security != .omemo || message.delivery == .failed {
|
if message.security != .omemo || message.delivery == .failed {
|
||||||
HStack(spacing: 4) {
|
HStack(spacing: 4) {
|
||||||
if message.security != .omemo {
|
if message.security != .omemo {
|
||||||
Image(systemName: message.security == .plaintext ? "lock.open" : "exclamationmark.shield")
|
Image(
|
||||||
|
systemName: message.security == .plaintext
|
||||||
|
? "lock.open" : "exclamationmark.shield"
|
||||||
|
)
|
||||||
.foregroundStyle(message.security == .plaintext ? .orange : .red)
|
.foregroundStyle(message.security == .plaintext ? .orange : .red)
|
||||||
}
|
}
|
||||||
if message.delivery == .failed {
|
if message.delivery == .failed {
|
||||||
@@ -262,7 +282,10 @@ struct MessageBubble: View {
|
|||||||
private var standardMetadata: some View {
|
private var standardMetadata: some View {
|
||||||
HStack(spacing: 4) {
|
HStack(spacing: 4) {
|
||||||
if message.callHistory == nil, message.security != .omemo {
|
if message.callHistory == nil, message.security != .omemo {
|
||||||
Image(systemName: message.security == .plaintext ? "lock.open" : "exclamationmark.shield")
|
Image(
|
||||||
|
systemName: message.security == .plaintext
|
||||||
|
? "lock.open" : "exclamationmark.shield"
|
||||||
|
)
|
||||||
.foregroundStyle(message.security == .plaintext ? .orange : .red)
|
.foregroundStyle(message.security == .plaintext ? .orange : .red)
|
||||||
}
|
}
|
||||||
if message.editedAt != nil {
|
if message.editedAt != nil {
|
||||||
@@ -290,7 +313,9 @@ struct MessageBubble: View {
|
|||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.padding(.horizontal, 13)
|
.padding(.horizontal, 13)
|
||||||
.padding(.vertical, 9)
|
.padding(.vertical, 9)
|
||||||
.foregroundStyle(message.direction == .outgoing ? Color.white.opacity(0.86) : Color.secondary)
|
.foregroundStyle(
|
||||||
|
message.direction == .outgoing ? Color.white.opacity(0.86) : Color.secondary
|
||||||
|
)
|
||||||
.background(bubbleBackground)
|
.background(bubbleBackground)
|
||||||
.clipShape(messageBubbleShape)
|
.clipShape(messageBubbleShape)
|
||||||
} else {
|
} else {
|
||||||
@@ -370,12 +395,16 @@ struct MessageBubble: View {
|
|||||||
HStack(spacing: 12) {
|
HStack(spacing: 12) {
|
||||||
ZStack {
|
ZStack {
|
||||||
Circle()
|
Circle()
|
||||||
.fill(callAccentColor(for: history).opacity(
|
.fill(
|
||||||
|
callAccentColor(for: history).opacity(
|
||||||
message.direction == .outgoing ? 0.28 : 0.14
|
message.direction == .outgoing ? 0.28 : 0.14
|
||||||
))
|
))
|
||||||
Image(systemName: history.isVideo ? "video.fill" : "phone.fill")
|
Image(systemName: history.isVideo ? "video.fill" : "phone.fill")
|
||||||
.font(.system(size: 17, weight: .semibold))
|
.font(.system(size: 17, weight: .semibold))
|
||||||
Image(systemName: message.direction == .outgoing ? "arrow.up.right" : "arrow.down.left")
|
Image(
|
||||||
|
systemName: message.direction == .outgoing
|
||||||
|
? "arrow.up.right" : "arrow.down.left"
|
||||||
|
)
|
||||||
.font(.system(size: 8, weight: .heavy))
|
.font(.system(size: 8, weight: .heavy))
|
||||||
.padding(3)
|
.padding(3)
|
||||||
.background(Circle().fill(callDirectionBadgeBackground))
|
.background(Circle().fill(callDirectionBadgeBackground))
|
||||||
@@ -490,7 +519,8 @@ struct MessageBubble: View {
|
|||||||
if let replyTarget,
|
if let replyTarget,
|
||||||
replyTarget.kind == .photo
|
replyTarget.kind == .photo
|
||||||
|| replyTarget.kind == .video
|
|| replyTarget.kind == .video
|
||||||
|| replyTarget.kind == .videoNote {
|
|| replyTarget.kind == .videoNote
|
||||||
|
{
|
||||||
await model.prepareMediaPreview(replyTarget)
|
await model.prepareMediaPreview(replyTarget)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -611,7 +641,9 @@ struct MessageBubble: View {
|
|||||||
ZStack {
|
ZStack {
|
||||||
Circle()
|
Circle()
|
||||||
.fill(.white.opacity(message.direction == .outgoing ? 0.22 : 0.12))
|
.fill(.white.opacity(message.direction == .outgoing ? 0.22 : 0.12))
|
||||||
.frame(width: message.kind == .videoNote ? 58 : 38, height: message.kind == .videoNote ? 58 : 38)
|
.frame(
|
||||||
|
width: message.kind == .videoNote ? 58 : 38,
|
||||||
|
height: message.kind == .videoNote ? 58 : 38)
|
||||||
Image(systemName: mediaIcon)
|
Image(systemName: mediaIcon)
|
||||||
.font(.system(size: message.kind == .videoNote ? 24 : 17))
|
.font(.system(size: message.kind == .videoNote ? 24 : 17))
|
||||||
}
|
}
|
||||||
@@ -649,7 +681,8 @@ struct MessageBubble: View {
|
|||||||
parts.append(String(format: "%d:%02d", seconds / 60, seconds % 60))
|
parts.append(String(format: "%d:%02d", seconds / 60, seconds % 60))
|
||||||
}
|
}
|
||||||
if let byteCount = message.byteCount {
|
if let byteCount = message.byteCount {
|
||||||
parts.append(ByteCountFormatter.string(fromByteCount: Int64(byteCount), countStyle: .file))
|
parts.append(
|
||||||
|
ByteCountFormatter.string(fromByteCount: Int64(byteCount), countStyle: .file))
|
||||||
}
|
}
|
||||||
return parts.isEmpty ? "Нажмите для просмотра" : parts.joined(separator: " · ")
|
return parts.isEmpty ? "Нажмите для просмотра" : parts.joined(separator: " · ")
|
||||||
}
|
}
|
||||||
@@ -684,7 +717,10 @@ struct MessageBubble: View {
|
|||||||
private var bubbleBackground: some View {
|
private var bubbleBackground: some View {
|
||||||
if message.direction == .outgoing {
|
if message.direction == .outgoing {
|
||||||
LinearGradient(
|
LinearGradient(
|
||||||
colors: [Color(red: 0.13, green: 0.51, blue: 0.94), Color(red: 0.12, green: 0.63, blue: 0.94)],
|
colors: [
|
||||||
|
Color(red: 0.13, green: 0.51, blue: 0.94),
|
||||||
|
Color(red: 0.12, green: 0.63, blue: 0.94),
|
||||||
|
],
|
||||||
startPoint: .topLeading,
|
startPoint: .topLeading,
|
||||||
endPoint: .bottomTrailing
|
endPoint: .bottomTrailing
|
||||||
)
|
)
|
||||||
@@ -722,9 +758,9 @@ struct MessageBubble: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension View {
|
extension View {
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func lumaTextSelection() -> some View {
|
fileprivate func lumaTextSelection() -> some View {
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
textSelection(.enabled)
|
textSelection(.enabled)
|
||||||
#else
|
#else
|
||||||
|
|||||||
Reference in new issue
Block a user