mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2025-03-28 12:35:27 +00:00
Enhance the context menu for private messages (#1053)
* Enhance the message context menu A direct message can now directly be bookmarked, the author can be publicly mentioned and reported. Signed-off-by: Paul Schuetz <pa.schuetz@web.de> * Add options to the conversation list context menu Since the latest message is shown in the conversation list, the user can now interact with this message via the context menu similar to the messages in the conversation history. The "conversation" class had to be modified since bookmarking and liking a message would have led to a race condition (depending on the server) when fetching the conversations afterwards, so the only affected the message is now immediately updated. Signed-off-by: Paul Schuetz <pa.schuetz@web.de> * Remove child view models The child views models are removed, and the list row now only uses the conversation object managed by the list view model. Signed-off-by: Paul Schuetz <pa.schuetz@web.de> * Make unmodified var let The last state-var of a conversation isn't modified, instead, a new conversation is created. Therefore, the var is now a let. Signed-off-by: Paul Schuetz <pa.schuetz@web.de> --------- Signed-off-by: Paul Schuetz <pa.schuetz@web.de>
This commit is contained in:
parent
2ba2675ae4
commit
06629cc397
23 changed files with 171 additions and 26 deletions
|
@ -296,6 +296,7 @@
|
|||
"conversations.error.title" = "Памылка падчас загрузкі вашых паведамленняў";
|
||||
"conversations.navigation-title" = "Непасрэдныя паведамленні";
|
||||
"conversations.new.message.placeholder" = "Новае паведамленне";
|
||||
"conversations.latest.message" = "Latest Message";
|
||||
|
||||
// MARK: Package: DesignSystem
|
||||
"design.tag.n-posts-from-n-participants %lld %lld" = "%lld допісы ад %lld удзельнікаў";
|
||||
|
|
|
@ -290,6 +290,7 @@
|
|||
"conversations.error.title" = "S'ha produït un error";
|
||||
"conversations.navigation-title" = "Missatges directes";
|
||||
"conversations.new.message.placeholder" = "Missatge nou";
|
||||
"conversations.latest.message" = "Latest Message";
|
||||
|
||||
// MARK: Package: DesignSystem
|
||||
"design.tag.n-posts-from-n-participants %lld %lld" = "%lld publicacions de %lld participants";
|
||||
|
|
|
@ -292,6 +292,7 @@
|
|||
"conversations.error.title" = "Ein Fehler ist aufgetreten";
|
||||
"conversations.navigation-title" = "Direkte Nachrichten";
|
||||
"conversations.new.message.placeholder" = "Neue Nachricht";
|
||||
"conversations.latest.message" = "Letzte Nachricht";
|
||||
|
||||
// MARK: Package: DesignSystem
|
||||
"design.tag.n-posts-from-n-participants %lld %lld" = "%lld Beiträge von %lld Teilnehmenden";
|
||||
|
|
|
@ -293,6 +293,7 @@
|
|||
"conversations.error.title" = "An error occurred";
|
||||
"conversations.navigation-title" = "Direct Messages";
|
||||
"conversations.new.message.placeholder" = "New Message";
|
||||
"conversations.latest.message" = "Latest Message";
|
||||
|
||||
// MARK: Package: DesignSystem
|
||||
"design.tag.n-posts-from-n-participants %lld %lld" = "%lld posts from %lld participants";
|
||||
|
|
|
@ -292,6 +292,7 @@
|
|||
"conversations.error.title" = "An error occurred";
|
||||
"conversations.navigation-title" = "Direct Messages";
|
||||
"conversations.new.message.placeholder" = "New Message";
|
||||
"conversations.latest.message" = "Latest Message";
|
||||
|
||||
// MARK: Package: DesignSystem
|
||||
"design.tag.n-posts-from-n-participants %lld %lld" = "%lld posts from %lld participants";
|
||||
|
|
|
@ -292,6 +292,7 @@
|
|||
"conversations.error.title" = "Ha ocurrido un error";
|
||||
"conversations.navigation-title" = "Mensajes directos";
|
||||
"conversations.new.message.placeholder" = "Mensajes nuevos";
|
||||
"conversations.latest.message" = "Latest Message";
|
||||
|
||||
// MARK: Package: DesignSystem
|
||||
"design.tag.n-posts-from-n-participants %lld %lld" = "%lld publicaciones de %lld participantes";
|
||||
|
|
|
@ -292,6 +292,7 @@
|
|||
"conversations.error.title" = "Errorea gertatu da";
|
||||
"conversations.navigation-title" = "Mezu zuzenak";
|
||||
"conversations.new.message.placeholder" = "Mezu berria";
|
||||
"conversations.latest.message" = "Latest Message";
|
||||
|
||||
// MARK: Package: DesignSystem
|
||||
"design.theme.navigation-title" = "Gai hautatzailea";
|
||||
|
|
|
@ -291,6 +291,7 @@
|
|||
"conversations.error.title" = "Une erreur est survenue";
|
||||
"conversations.navigation-title" = "Messages directs";
|
||||
"conversations.new.message.placeholder" = "Nouveau message";
|
||||
"conversations.latest.message" = "Latest Message";
|
||||
|
||||
// MARK: Package: DesignSystem
|
||||
"design.tag.n-posts-from-n-participants %lld %lld" = "%lld publications de %lld participants";
|
||||
|
|
|
@ -292,6 +292,7 @@
|
|||
"conversations.error.title" = "Si è verificato in errore";
|
||||
"conversations.navigation-title" = "Messaggi diretti";
|
||||
"conversations.new.message.placeholder" = "Nuovo messaggio";
|
||||
"conversations.latest.message" = "Latest Message";
|
||||
|
||||
// MARK: Package: DesignSystem
|
||||
"design.tag.n-posts-from-n-participants %lld %lld" = "%lld post da %lld partecipanti";
|
||||
|
|
|
@ -291,6 +291,7 @@
|
|||
"conversations.error.title" = "エラーが発生しました";
|
||||
"conversations.navigation-title" = "ダイレクトメッセージ";
|
||||
"conversations.new.message.placeholder" = "新しいメッセージ";
|
||||
"conversations.latest.message" = "Latest Message";
|
||||
|
||||
// MARK: Package: DesignSystem
|
||||
"design.tag.n-posts-from-n-participants %lld %lld" = "%lld トゥートの投稿 %lld 人が投稿している";
|
||||
|
|
|
@ -292,6 +292,7 @@
|
|||
"conversations.error.title" = "오류";
|
||||
"conversations.navigation-title" = "다이렉트 메시지";
|
||||
"conversations.new.message.placeholder" = "새 메시지";
|
||||
"conversations.latest.message" = "Latest Message";
|
||||
|
||||
// MARK: Package: DesignSystem
|
||||
"design.tag.n-posts-from-n-participants %lld %lld" = "%lld개 글 (%lld명이 이야기 중)";
|
||||
|
|
|
@ -291,6 +291,7 @@
|
|||
"conversations.error.title" = "En feil oppstod";
|
||||
"conversations.navigation-title" = "Direktemeldinger";
|
||||
"conversations.new.message.placeholder" = "Ny melding";
|
||||
"conversations.latest.message" = "Latest Message";
|
||||
|
||||
// MARK: Package: DesignSystem
|
||||
"design.tag.n-posts-from-n-participants %lld %lld" = "%lld innlegg fra %lld deltakere";
|
||||
|
|
|
@ -289,6 +289,7 @@
|
|||
"conversations.error.title" = "Er heeft zich een fout voorgedaan";
|
||||
"conversations.navigation-title" = "Directe berichten";
|
||||
"conversations.new.message.placeholder" = "Nieuw bericht";
|
||||
"conversations.latest.message" = "Latest Message";
|
||||
|
||||
// MARK: Package: DesignSystem
|
||||
"design.tag.n-posts-from-n-participants %lld %lld" = "%lld posts van %lld deelnemers";
|
||||
|
|
|
@ -289,6 +289,7 @@
|
|||
"conversations.error.title" = "Wystąpił błąd";
|
||||
"conversations.navigation-title" = "Wiadomości bezpośrednie";
|
||||
"conversations.new.message.placeholder" = "Nowa wiadomość";
|
||||
"conversations.latest.message" = "Latest Message";
|
||||
|
||||
// MARK: Package: DesignSystem
|
||||
"design.theme.navigation-title" = "Wybór motywu";
|
||||
|
|
|
@ -291,6 +291,7 @@
|
|||
"conversations.error.title" = "Ocorreu um erro";
|
||||
"conversations.navigation-title" = "Mensagens diretas";
|
||||
"conversations.new.message.placeholder" = "Nova mensagem";
|
||||
"conversations.latest.message" = "Latest Message";
|
||||
|
||||
// MARK: Package: DesignSystem
|
||||
"design.tag.n-posts-from-n-participants %lld %lld" = "%lld postagens de %lld participantes";
|
||||
|
|
|
@ -287,6 +287,7 @@
|
|||
"conversations.error.title" = "Bir hata oluştu";
|
||||
"conversations.navigation-title" = "Direkt Mesajlar";
|
||||
"conversations.new.message.placeholder" = "Yeni Mesaj";
|
||||
"conversations.latest.message" = "Latest Message";
|
||||
|
||||
// MARK: Package: DesignSystem
|
||||
"design.tag.n-posts-from-n-participants %lld %lld" = "%lld katılımcılar tarafından %lld gönderi";
|
||||
|
|
|
@ -292,6 +292,7 @@
|
|||
"conversations.error.title" = "Виникла халепа";
|
||||
"conversations.navigation-title" = "Особисті повідомлення";
|
||||
"conversations.new.message.placeholder" = "Нове повідомлення";
|
||||
"conversations.latest.message" = "Latest Message";
|
||||
|
||||
// MARK: Package: DesignSystem
|
||||
"design.tag.n-posts-from-n-participants %lld %lld" = "%lld дописів від %lld учасників";
|
||||
|
|
|
@ -290,6 +290,7 @@
|
|||
"conversations.error.title" = "出错啦";
|
||||
"conversations.navigation-title" = "私信";
|
||||
"conversations.new.message.placeholder" = "新消息";
|
||||
"conversations.latest.message" = "Latest Message";
|
||||
|
||||
// MARK: Package: DesignSystem
|
||||
"design.tag.n-posts-from-n-participants %lld %lld" = "%lld 条嘟文来自 %lld 个参与者";
|
||||
|
|
|
@ -16,6 +16,7 @@ struct ConversationMessageView: View {
|
|||
let conversation: Conversation
|
||||
|
||||
@State private var isLiked: Bool = false
|
||||
@State private var isBookmarked: Bool = false
|
||||
|
||||
var body: some View {
|
||||
let isOwnMessage = message.account.id == currentAccount.account?.id
|
||||
|
@ -82,6 +83,7 @@ struct ConversationMessageView: View {
|
|||
}
|
||||
.onAppear {
|
||||
isLiked = message.favourited == true
|
||||
isBookmarked = message.bookmarked == true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -115,6 +117,22 @@ struct ConversationMessageView: View {
|
|||
Label(isLiked ? "status.action.unfavorite" : "status.action.favorite",
|
||||
systemImage: isLiked ? "star.fill" : "star")
|
||||
}
|
||||
Button { Task {
|
||||
do {
|
||||
let status: Status
|
||||
if isBookmarked {
|
||||
status = try await client.post(endpoint: Statuses.unbookmark(id: message.id))
|
||||
} else {
|
||||
status = try await client.post(endpoint: Statuses.bookmark(id: message.id))
|
||||
}
|
||||
withAnimation {
|
||||
isBookmarked = status.bookmarked == true
|
||||
}
|
||||
} catch {}
|
||||
} } label: {
|
||||
Label(isBookmarked ? "status.action.unbookmark" : "status.action.bookmark",
|
||||
systemImage: isBookmarked ? "bookmark.fill" : "bookmark")
|
||||
}
|
||||
Divider()
|
||||
if message.account.id == currentAccount.account?.id {
|
||||
Button("status.action.delete", role: .destructive) {
|
||||
|
@ -122,6 +140,21 @@ struct ConversationMessageView: View {
|
|||
_ = try await client.delete(endpoint: Statuses.status(id: message.id))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Section(message.reblog?.account.acct ?? message.account.acct) {
|
||||
Button {
|
||||
routerPath.presentedSheet = .mentionStatusEditor(account: message.reblog?.account ?? message.account, visibility: .pub)
|
||||
} label: {
|
||||
Label("status.action.mention", systemImage: "at")
|
||||
}
|
||||
}
|
||||
Section {
|
||||
Button(role: .destructive) {
|
||||
routerPath.presentedSheet = .report(status: message.reblogAsAsStatus ?? message)
|
||||
} label: {
|
||||
Label("status.action.report", systemImage: "exclamationmark.bubble")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,8 +9,9 @@ struct ConversationsListRow: View {
|
|||
@EnvironmentObject private var client: Client
|
||||
@EnvironmentObject private var routerPath: RouterPath
|
||||
@EnvironmentObject private var theme: Theme
|
||||
|
||||
let conversation: Conversation
|
||||
@EnvironmentObject private var currentAccount: CurrentAccount
|
||||
|
||||
@Binding var conversation: Conversation
|
||||
@ObservedObject var viewModel: ConversationsListViewModel
|
||||
|
||||
var body: some View {
|
||||
|
@ -32,8 +33,8 @@ struct ConversationsListRow: View {
|
|||
.foregroundColor(theme.tintColor)
|
||||
.frame(width: 10, height: 10)
|
||||
}
|
||||
if conversation.lastStatus != nil {
|
||||
Text(conversation.lastStatus!.createdAt.relativeFormatted)
|
||||
if let message = conversation.lastStatus {
|
||||
Text(message.createdAt.relativeFormatted)
|
||||
.font(.scaledFootnote)
|
||||
}
|
||||
}
|
||||
|
@ -91,6 +92,34 @@ struct ConversationsListRow: View {
|
|||
Label("conversations.action.mark-read", systemImage: "eye")
|
||||
}
|
||||
|
||||
if let message = conversation.lastStatus {
|
||||
Section("conversations.latest.message") {
|
||||
Button {
|
||||
UIPasteboard.general.string = message.content.asRawText
|
||||
} label: {
|
||||
Label("status.action.copy-text", systemImage: "doc.on.doc")
|
||||
}
|
||||
likeAndBookmark
|
||||
}
|
||||
Divider()
|
||||
if message.account.id != currentAccount.account?.id {
|
||||
Section(message.reblog?.account.acct ?? message.account.acct) {
|
||||
Button {
|
||||
routerPath.presentedSheet = .mentionStatusEditor(account: message.reblog?.account ?? message.account, visibility: .pub)
|
||||
} label: {
|
||||
Label("status.action.mention", systemImage: "at")
|
||||
}
|
||||
}
|
||||
Section {
|
||||
Button(role: .destructive) {
|
||||
routerPath.presentedSheet = .report(status: message.reblogAsAsStatus ?? message)
|
||||
} label: {
|
||||
Label("status.action.report", systemImage: "exclamationmark.bubble")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button(role: .destructive) {
|
||||
Task {
|
||||
await viewModel.delete(conversation: conversation)
|
||||
|
@ -99,4 +128,24 @@ struct ConversationsListRow: View {
|
|||
Label("conversations.action.delete", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var likeAndBookmark: some View {
|
||||
Button {
|
||||
Task {
|
||||
await viewModel.favorite(conversation: conversation)
|
||||
}
|
||||
} label: {
|
||||
Label(conversation.lastStatus?.favourited ?? false ? "status.action.unfavorite" : "status.action.favorite",
|
||||
systemImage: conversation.lastStatus?.favourited ?? false ? "star.fill" : "star")
|
||||
}
|
||||
Button {
|
||||
Task {
|
||||
await viewModel.bookmark(conversation: conversation)
|
||||
}
|
||||
} label: {
|
||||
Label(conversation.lastStatus?.bookmarked ?? false ? "status.action.unbookmark" : "status.action.bookmark",
|
||||
systemImage: conversation.lastStatus?.bookmarked ?? false ? "bookmark.fill" : "bookmark")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,29 +16,30 @@ public struct ConversationsListView: View {
|
|||
|
||||
public init() {}
|
||||
|
||||
private var conversations: [Conversation] {
|
||||
if viewModel.isLoadingFirstPage {
|
||||
return Conversation.placeholders()
|
||||
private var conversations: Binding<[Conversation]> {
|
||||
if viewModel.isLoadingFirstPage {
|
||||
return Binding.constant(Conversation.placeholders())
|
||||
} else {
|
||||
return $viewModel.conversations
|
||||
}
|
||||
}
|
||||
return viewModel.conversations
|
||||
}
|
||||
|
||||
|
||||
public var body: some View {
|
||||
ScrollView {
|
||||
LazyVStack {
|
||||
Group {
|
||||
if !conversations.isEmpty || viewModel.isLoadingFirstPage {
|
||||
ForEach(conversations) { conversation in
|
||||
if viewModel.isLoadingFirstPage {
|
||||
ConversationsListRow(conversation: conversation, viewModel: viewModel)
|
||||
.padding(.horizontal, .layoutPadding)
|
||||
.redacted(reason: .placeholder)
|
||||
} else {
|
||||
ConversationsListRow(conversation: conversation, viewModel: viewModel)
|
||||
.padding(.horizontal, .layoutPadding)
|
||||
}
|
||||
Divider()
|
||||
}
|
||||
ForEach(conversations) { $conversation in
|
||||
if viewModel.isLoadingFirstPage {
|
||||
ConversationsListRow(conversation: $conversation, viewModel: viewModel)
|
||||
.padding(.horizontal, .layoutPadding)
|
||||
.redacted(reason: .placeholder)
|
||||
} else {
|
||||
ConversationsListRow(conversation: $conversation, viewModel: viewModel)
|
||||
.padding(.horizontal, .layoutPadding)
|
||||
}
|
||||
Divider()
|
||||
}
|
||||
} else if conversations.isEmpty && !viewModel.isLoadingFirstPage && !viewModel.isError {
|
||||
EmptyView(iconName: "tray",
|
||||
title: "conversations.empty.title",
|
||||
|
|
|
@ -58,13 +58,50 @@ class ConversationsListViewModel: ObservableObject {
|
|||
await fetchConversations()
|
||||
}
|
||||
|
||||
func favorite(conversation: Conversation) async {
|
||||
guard let client, let message = conversation.lastStatus else { return }
|
||||
let endpoint: Endpoint
|
||||
if message.favourited ?? false {
|
||||
endpoint = Statuses.unfavorite(id: message.id)
|
||||
} else {
|
||||
endpoint = Statuses.favorite(id: message.id)
|
||||
}
|
||||
do {
|
||||
let status: Status = try await client.post(endpoint: endpoint)
|
||||
updateConversationWithNewLastStatus(conversation: conversation, newLastStatus: status)
|
||||
} catch {}
|
||||
}
|
||||
|
||||
func bookmark(conversation: Conversation) async {
|
||||
guard let client, let message = conversation.lastStatus else { return }
|
||||
let endpoint: Endpoint
|
||||
if message.bookmarked ?? false {
|
||||
endpoint = Statuses.unbookmark(id: message.id)
|
||||
} else {
|
||||
endpoint = Statuses.bookmark(id: message.id)
|
||||
}
|
||||
do {
|
||||
let status: Status = try await client.post(endpoint: endpoint)
|
||||
updateConversationWithNewLastStatus(conversation: conversation, newLastStatus: status)
|
||||
} catch {}
|
||||
}
|
||||
|
||||
private func updateConversationWithNewLastStatus(conversation: Conversation, newLastStatus: Status) {
|
||||
let newConversation = Conversation(id: conversation.id, unread: conversation.unread, lastStatus: newLastStatus, accounts: conversation.accounts)
|
||||
updateConversations(conversation: newConversation)
|
||||
}
|
||||
|
||||
private func updateConversations(conversation: Conversation) {
|
||||
if let index = conversations.firstIndex(where: { $0.id == conversation.id }) {
|
||||
conversations.remove(at: index)
|
||||
}
|
||||
conversations.insert(conversation, at: 0)
|
||||
conversations = conversations.sorted(by: { ($0.lastStatus?.createdAt.asDate ?? Date.now) > ($1.lastStatus?.createdAt.asDate ?? Date.now) })
|
||||
}
|
||||
|
||||
func handleEvent(event: any StreamEvent) {
|
||||
if let event = event as? StreamEventConversation {
|
||||
if let index = conversations.firstIndex(where: { $0.id == event.conversation.id }) {
|
||||
conversations.remove(at: index)
|
||||
}
|
||||
conversations.insert(event.conversation, at: 0)
|
||||
conversations = conversations.sorted(by: { ($0.lastStatus?.createdAt.asDate ?? Date.now) > ($1.lastStatus?.createdAt.asDate ?? Date.now) })
|
||||
updateConversations(conversation: event.conversation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,13 @@ public struct Conversation: Identifiable, Decodable, Hashable, Equatable {
|
|||
public let lastStatus: Status?
|
||||
public let accounts: [Account]
|
||||
|
||||
public init(id: String, unread: Bool, lastStatus: Status? = nil, accounts: [Account]) {
|
||||
self.id = id
|
||||
self.unread = unread
|
||||
self.lastStatus = lastStatus
|
||||
self.accounts = accounts
|
||||
}
|
||||
|
||||
public static func placeholder() -> Conversation {
|
||||
.init(id: UUID().uuidString, unread: false, lastStatus: .placeholder(), accounts: [.placeholder()])
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue