IceCubesApp/Packages/Conversations/Sources/Conversations/List/ConversationsListView.swift
Paul Schuetz 06629cc397
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>
2023-02-26 06:45:31 +01:00

107 lines
3.3 KiB
Swift

import DesignSystem
import Env
import Models
import Network
import Shimmer
import SwiftUI
public struct ConversationsListView: View {
@EnvironmentObject private var preferences: UserPreferences
@EnvironmentObject private var routerPath: RouterPath
@EnvironmentObject private var watcher: StreamWatcher
@EnvironmentObject private var client: Client
@EnvironmentObject private var theme: Theme
@StateObject private var viewModel = ConversationsListViewModel()
public init() {}
private var conversations: Binding<[Conversation]> {
if viewModel.isLoadingFirstPage {
return Binding.constant(Conversation.placeholders())
} else {
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()
}
} else if conversations.isEmpty && !viewModel.isLoadingFirstPage && !viewModel.isError {
EmptyView(iconName: "tray",
title: "conversations.empty.title",
message: "conversations.empty.message")
} else if viewModel.isError {
ErrorView(title: "conversations.error.title",
message: "conversations.error.message",
buttonTitle: "conversations.error.button") {
Task {
await viewModel.fetchConversations()
}
}
}
if viewModel.nextPage != nil {
HStack {
Spacer()
ProgressView()
Spacer()
}
.onAppear {
if !viewModel.isLoadingNextPage {
Task {
await viewModel.fetchNextPage()
}
}
}
}
}
}
.padding(.top, .layoutPadding)
}
.scrollContentBackground(.hidden)
.background(theme.primaryBackgroundColor)
.navigationTitle("conversations.navigation-title")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
StatusEditorToolbarItem(visibility: .direct)
if UIDevice.current.userInterfaceIdiom == .pad && !preferences.showiPadSecondaryColumn {
SecondaryColumnToolbarItem()
}
}
.onChange(of: watcher.latestEvent?.id) { _ in
if let latestEvent = watcher.latestEvent {
viewModel.handleEvent(event: latestEvent)
}
}
.refreshable {
// note: this Task wrapper should not be necessary, but it reportedly crashes without it
// when refreshing on an empty list
Task {
await viewModel.fetchConversations()
}
}
.onAppear {
viewModel.client = client
if client.isAuth {
Task {
await viewModel.fetchConversations()
}
}
}
}
}