Remove Client and RouterPath from StatusRowView env object

This commit is contained in:
Thomas Ricouard 2023-02-15 08:46:14 +01:00
parent 07153e1142
commit d958d10036
15 changed files with 96 additions and 70 deletions

View file

@ -29,7 +29,7 @@ public struct ReportView: View {
}
.listRowBackground(theme.primaryBackgroundColor)
StatusEmbeddedView(status: status)
StatusEmbeddedView(status: status, client: .init(server: ""), routerPath: RouterPath())
.allowsHitTesting(false)
.listRowBackground(theme.primaryBackgroundColor)
}

View file

@ -3,6 +3,7 @@ import Env
import Models
import Status
import SwiftUI
import Network
struct DisplaySettingsView: View {
typealias FontState = Theme.FontState
@ -12,7 +13,9 @@ struct DisplaySettingsView: View {
@EnvironmentObject private var userPreferences: UserPreferences
@State private var isFontSelectorPresented = false
private var previewStatusViewModel = StatusRowViewModel(status: Status.placeholder(forSettings: true, language: "la")) // translate from latin button
private var previewStatusViewModel = StatusRowViewModel(status: Status.placeholder(forSettings: true, language: "la"),
client: Client(server: ""),
routerPath: RouterPath()) // translate from latin button
var body: some View {
Form {
Section("settings.display.example-toot") {

View file

@ -68,7 +68,7 @@ public struct AccountDetailView: View {
if viewModel.selectedTab == .statuses {
pinnedPostsView
}
StatusesListView(fetcher: viewModel)
StatusesListView(fetcher: viewModel, client: client, routerPath: routerPath)
case .followedTags:
tagsListView
case .lists:
@ -336,7 +336,7 @@ public struct AccountDetailView: View {
.listRowSeparator(.hidden)
.listRowBackground(theme.primaryBackgroundColor)
ForEach(viewModel.pinned) { status in
StatusRowView(viewModel: .init(status: status))
StatusRowView(viewModel: .init(status: status, client: client, routerPath: routerPath))
}
Rectangle()
.fill(theme.secondaryBackgroundColor)

View file

@ -81,7 +81,7 @@ public struct ExploreView: View {
private var loadingView: some View {
ForEach(Status.placeholders()) { status in
StatusRowView(viewModel: .init(status: status, isCompact: false))
StatusRowView(viewModel: .init(status: status, client: client, routerPath: routerPath, isCompact: false))
.padding(.vertical, 8)
.redacted(reason: .placeholder)
.listRowBackground(theme.primaryBackgroundColor)
@ -112,7 +112,7 @@ public struct ExploreView: View {
if !results.statuses.isEmpty {
Section("explore.section.posts") {
ForEach(results.statuses) { status in
StatusRowView(viewModel: .init(status: status))
StatusRowView(viewModel: .init(status: status, client: client, routerPath: routerPath))
.listRowBackground(theme.primaryBackgroundColor)
.padding(.vertical, 8)
}
@ -184,7 +184,7 @@ public struct ExploreView: View {
Section("explore.section.trending.posts") {
ForEach(viewModel.trendingStatuses
.prefix(upTo: viewModel.trendingStatuses.count > 3 ? 3 : viewModel.trendingStatuses.count)) { status in
StatusRowView(viewModel: .init(status: status, isCompact: false))
StatusRowView(viewModel: .init(status: status, client: client, routerPath: routerPath, isCompact: false))
.listRowBackground(theme.primaryBackgroundColor)
.padding(.vertical, 8)
}
@ -192,7 +192,7 @@ public struct ExploreView: View {
NavigationLink {
List {
ForEach(viewModel.trendingStatuses) { status in
StatusRowView(viewModel: .init(status: status, isCompact: false))
StatusRowView(viewModel: .init(status: status, client: client, routerPath: routerPath, isCompact: false))
.listRowBackground(theme.primaryBackgroundColor)
.padding(.vertical, 8)
}

View file

@ -4,8 +4,10 @@ import Env
import Models
import Status
import SwiftUI
import Network
struct NotificationRowView: View {
@EnvironmentObject private var client: Client
@EnvironmentObject private var currentAccount: CurrentAccount
@EnvironmentObject private var theme: Theme
@EnvironmentObject private var routerPath: RouterPath
@ -132,9 +134,17 @@ struct NotificationRowView: View {
if let status = notification.status {
HStack {
if type == .mention {
StatusRowView(viewModel: .init(status: status, isCompact: true, showActions: true))
StatusRowView(viewModel: .init(status: status,
client: client,
routerPath: routerPath,
isCompact: true,
showActions: true))
} else {
StatusRowView(viewModel: .init(status: status, isCompact: true, showActions: false))
StatusRowView(viewModel: .init(status: status,
client: client,
routerPath: routerPath,
isCompact: true,
showActions: false))
.lineLimit(4)
.foregroundColor(.gray)
}

View file

@ -43,7 +43,7 @@ public struct StatusDetailView: View {
case let .display(status, context, date):
if !context.ancestors.isEmpty {
ForEach(context.ancestors) { ancestor in
StatusRowView(viewModel: .init(status: ancestor, isCompact: false))
StatusRowView(viewModel: .init(status: ancestor, client: client, routerPath: routerPath, isCompact: false))
}
}
@ -52,7 +52,7 @@ public struct StatusDetailView: View {
if !context.descendants.isEmpty {
ForEach(context.descendants) { descendant in
StatusRowView(viewModel: .init(status: descendant, isCompact: false))
StatusRowView(viewModel: .init(status: descendant, client: client, routerPath: routerPath, isCompact: false))
}
}
@ -108,6 +108,8 @@ public struct StatusDetailView: View {
private func makeCurrentStatusView(status: Status) -> some View {
StatusRowView(viewModel: .init(status: status,
client: client,
routerPath: routerPath,
isCompact: false,
isFocused: true))
.overlay {
@ -135,7 +137,7 @@ public struct StatusDetailView: View {
private var loadingDetailView: some View {
ForEach(Status.placeholders()) { status in
StatusRowView(viewModel: .init(status: status, isCompact: false))
StatusRowView(viewModel: .init(status: status, client: client, routerPath: routerPath, isCompact: false))
.redacted(reason: .placeholder)
}
}

View file

@ -15,6 +15,7 @@ public struct StatusEditorView: View {
@EnvironmentObject private var theme: Theme
@EnvironmentObject private var client: Client
@EnvironmentObject private var currentAccount: CurrentAccount
@EnvironmentObject private var routerPath: RouterPath
@Environment(\.dismiss) private var dismiss
@StateObject private var viewModel: StatusEditorViewModel
@ -45,13 +46,13 @@ public struct StatusEditorView: View {
.padding(.horizontal, .layoutPadding)
StatusEditorMediaView(viewModel: viewModel)
if let status = viewModel.embeddedStatus {
StatusEmbeddedView(status: status)
StatusEmbeddedView(status: status, client: client, routerPath: routerPath)
.padding(.horizontal, .layoutPadding)
.disabled(true)
} else if let status = viewModel.replyToStatus {
Divider()
.padding(.top, 20)
StatusEmbeddedView(status: status)
StatusEmbeddedView(status: status, client: client, routerPath: routerPath)
.padding(.horizontal, .layoutPadding)
.disabled(true)
}

View file

@ -2,22 +2,32 @@ import DesignSystem
import EmojiText
import Models
import SwiftUI
import Env
import Network
@MainActor
public struct StatusEmbeddedView: View {
@EnvironmentObject private var theme: Theme
public let status: Status
public let client: Client
public let routerPath: RouterPath
public init(status: Status) {
public init(status: Status, client: Client, routerPath: RouterPath) {
self.status = status
self.client = client
self.routerPath = routerPath
}
public var body: some View {
HStack {
VStack(alignment: .leading) {
makeAccountView(account: status.reblog?.account ?? status.account)
StatusRowView(viewModel: .init(status: status, isCompact: true, showActions: false))
StatusRowView(viewModel: .init(status: status,
client: client,
routerPath: routerPath,
isCompact: true,
showActions: false))
}
Spacer()
}

View file

@ -3,23 +3,28 @@ import Env
import Models
import Shimmer
import SwiftUI
import Network
public struct StatusesListView<Fetcher>: View where Fetcher: StatusesFetcher {
@EnvironmentObject private var theme: Theme
@ObservedObject private var fetcher: Fetcher
private let isRemote: Bool
private let routerPath: RouterPath
private let client: Client
public init(fetcher: Fetcher, isRemote: Bool = false) {
public init(fetcher: Fetcher, client: Client, routerPath: RouterPath, isRemote: Bool = false) {
self.fetcher = fetcher
self.isRemote = isRemote
self.client = client
self.routerPath = routerPath
}
public var body: some View {
switch fetcher.statusesState {
case .loading:
ForEach(Status.placeholders()) { status in
StatusRowView(viewModel: .init(status: status, isCompact: false))
StatusRowView(viewModel: .init(status: status, client: client, routerPath: routerPath, isCompact: false))
.redacted(reason: .placeholder)
}
case .error:
@ -35,7 +40,7 @@ public struct StatusesListView<Fetcher>: View where Fetcher: StatusesFetcher {
case let .display(statuses, nextPageState):
ForEach(statuses, id: \.viewId) { status in
let viewModel = StatusRowViewModel(status: status, isCompact: false, isRemote: isRemote)
let viewModel = StatusRowViewModel(status: status, client: client, routerPath: routerPath, isCompact: false, isRemote: isRemote)
if viewModel.filter?.filter.filterAction != .hide {
StatusRowView(viewModel: viewModel)
.id(status.id)

View file

@ -6,7 +6,6 @@ import SwiftUI
struct StatusActionsView: View {
@EnvironmentObject private var theme: Theme
@EnvironmentObject private var routerPath: RouterPath
@ObservedObject var viewModel: StatusRowViewModel
@MainActor
@ -108,7 +107,7 @@ struct StatusActionsView: View {
HapticManager.shared.fireHaptic(of: .notification(.success))
switch action {
case .respond:
routerPath.presentedSheet = .replyToStatusEditor(status: viewModel.localStatus ?? viewModel.status)
viewModel.routerPath.presentedSheet = .replyToStatusEditor(status: viewModel.localStatus ?? viewModel.status)
case .favorite:
if viewModel.isFavorited {
await viewModel.unFavorite()

View file

@ -6,7 +6,6 @@ struct StatusRowContextMenu: View {
@EnvironmentObject private var preferences: UserPreferences
@EnvironmentObject private var account: CurrentAccount
@EnvironmentObject private var currentInstance: CurrentInstance
@EnvironmentObject private var routerPath: RouterPath
@ObservedObject var viewModel: StatusRowViewModel
@ -41,7 +40,7 @@ struct StatusRowContextMenu: View {
systemImage: "bookmark")
}
Button {
routerPath.presentedSheet = .replyToStatusEditor(status: viewModel.status)
viewModel.routerPath.presentedSheet = .replyToStatusEditor(status: viewModel.status)
} label: {
Label("status.action.reply", systemImage: "arrowshape.turn.up.left")
}
@ -49,7 +48,7 @@ struct StatusRowContextMenu: View {
if viewModel.status.visibility == .pub, !viewModel.isRemote {
Button {
routerPath.presentedSheet = .quoteStatusEditor(status: viewModel.status)
viewModel.routerPath.presentedSheet = .quoteStatusEditor(status: viewModel.status)
} label: {
Label("status.action.quote", systemImage: "quote.bubble")
}
@ -110,7 +109,7 @@ struct StatusRowContextMenu: View {
}
if currentInstance.isEditSupported {
Button {
routerPath.presentedSheet = .editStatusEditor(status: viewModel.status)
viewModel.routerPath.presentedSheet = .editStatusEditor(status: viewModel.status)
} label: {
Label("status.action.edit", systemImage: "pencil")
}
@ -123,12 +122,12 @@ struct StatusRowContextMenu: View {
if !viewModel.isRemote {
Section(viewModel.status.account.acct) {
Button {
routerPath.presentedSheet = .mentionStatusEditor(account: viewModel.status.account, visibility: .pub)
viewModel.routerPath.presentedSheet = .mentionStatusEditor(account: viewModel.status.account, visibility: .pub)
} label: {
Label("status.action.mention", systemImage: "at")
}
Button {
routerPath.presentedSheet = .mentionStatusEditor(account: viewModel.status.account, visibility: .direct)
viewModel.routerPath.presentedSheet = .mentionStatusEditor(account: viewModel.status.account, visibility: .direct)
} label: {
Label("status.action.message", systemImage: "tray.full")
}
@ -136,7 +135,7 @@ struct StatusRowContextMenu: View {
}
Section {
Button(role: .destructive) {
routerPath.presentedSheet = .report(status: viewModel.status.reblogAsAsStatus ?? viewModel.status)
viewModel.routerPath.presentedSheet = .report(status: viewModel.status.reblogAsAsStatus ?? viewModel.status)
} label: {
Label("status.action.report", systemImage: "exclamationmark.bubble")
}

View file

@ -5,7 +5,6 @@ import SwiftUI
struct StatusRowDetailView: View {
@Environment(\.openURL) private var openURL
@EnvironmentObject private var routerPath: RouterPath
@ObservedObject var viewModel: StatusRowViewModel
@ -40,7 +39,7 @@ struct StatusRowDetailView: View {
Spacer()
}
.onTapGesture {
routerPath.presentedSheet = .statusEditHistory(status: viewModel.status.id)
viewModel.routerPath.presentedSheet = .statusEditHistory(status: viewModel.status.id)
}
.underline()
.font(.scaledCaption)
@ -50,7 +49,7 @@ struct StatusRowDetailView: View {
if viewModel.favoritesCount > 0 {
Divider()
Button {
routerPath.navigate(to: .favoritedBy(id: viewModel.status.id))
viewModel.routerPath.navigate(to: .favoritedBy(id: viewModel.status.id))
} label: {
HStack {
Text("status.summary.n-favorites \(viewModel.favoritesCount)")
@ -66,7 +65,7 @@ struct StatusRowDetailView: View {
if viewModel.reblogsCount > 0 {
Divider()
Button {
routerPath.navigate(to: .rebloggedBy(id: viewModel.status.id))
viewModel.routerPath.navigate(to: .rebloggedBy(id: viewModel.status.id))
} label: {
HStack {
Text("status.summary.n-boosts \(viewModel.reblogsCount)")

View file

@ -10,8 +10,6 @@ public struct StatusRowView: View {
@Environment(\.redactionReasons) private var reasons
@EnvironmentObject private var preferences: UserPreferences
@EnvironmentObject private var theme: Theme
@EnvironmentObject private var client: Client
@EnvironmentObject private var routerPath: RouterPath
@StateObject var viewModel: StatusRowViewModel
public init(viewModel: StatusRowViewModel) {
@ -42,7 +40,7 @@ public struct StatusRowView: View {
let status: AnyStatus = viewModel.status.reblog ?? viewModel.status
{
Button {
routerPath.navigate(to: .accountDetailWithAccount(account: status.account))
viewModel.routerPath.navigate(to: .accountDetailWithAccount(account: status.account))
} label: {
AvatarView(url: status.account.avatar, size: .status)
}
@ -59,7 +57,7 @@ public struct StatusRowView: View {
.tint(viewModel.isFocused ? theme.tintColor : .gray)
.contentShape(Rectangle())
.onTapGesture {
viewModel.navigateToDetail(routerPath: routerPath)
viewModel.navigateToDetail()
}
}
}
@ -68,7 +66,6 @@ public struct StatusRowView: View {
.onAppear {
viewModel.markSeen()
if reasons.isEmpty {
viewModel.client = client
if !viewModel.isCompact, viewModel.embeddedStatus == nil {
Task {
await viewModel.loadEmbeddedStatus()
@ -105,7 +102,7 @@ public struct StatusRowView: View {
Color.clear
.contentShape(Rectangle())
.onTapGesture {
viewModel.navigateToDetail(routerPath: routerPath)
viewModel.navigateToDetail()
}
}
.overlay {
@ -138,7 +135,7 @@ public struct StatusRowView: View {
// Add the individual mentions as accessibility actions
ForEach(viewModel.status.mentions, id: \.id) { mention in
Button("@\(mention.username)") {
routerPath.navigate(to: .accountDetail(id: mention.id))
viewModel.routerPath.navigate(to: .accountDetail(id: mention.id))
}
}
@ -149,7 +146,7 @@ public struct StatusRowView: View {
}
Button("@\(viewModel.status.account.username)") {
routerPath.navigate(to: .accountDetail(id: viewModel.status.account.id))
viewModel.routerPath.navigate(to: .accountDetail(id: viewModel.status.account.id))
}
contextMenu
@ -187,7 +184,7 @@ public struct StatusRowView: View {
.foregroundColor(.gray)
.fontWeight(.semibold)
.onTapGesture {
viewModel.navigateToAccountDetail(account: viewModel.status.account, routerPath: routerPath)
viewModel.navigateToAccountDetail(account: viewModel.status.account)
}
}
}
@ -206,7 +203,7 @@ public struct StatusRowView: View {
.foregroundColor(.gray)
.fontWeight(.semibold)
.onTapGesture {
viewModel.navigateToMention(mention: mention, routerPath: routerPath)
viewModel.navigateToMention(mention: mention)
}
}
}
@ -217,7 +214,7 @@ public struct StatusRowView: View {
if !viewModel.isCompact {
HStack(alignment: .center) {
Button {
viewModel.navigateToAccountDetail(account: status.account, routerPath: routerPath)
viewModel.navigateToAccountDetail(account: status.account)
} label: {
accountView(status: status)
}
@ -232,13 +229,13 @@ public struct StatusRowView: View {
makeStatusContentView(status: status)
.contentShape(Rectangle())
.onTapGesture {
viewModel.navigateToDetail(routerPath: routerPath)
viewModel.navigateToDetail()
}
}
}
.accessibilityElement(children: viewModel.isFocused ? .contain : .combine)
.accessibilityAction {
viewModel.navigateToDetail(routerPath: routerPath)
viewModel.navigateToDetail()
}
}
@ -278,7 +275,7 @@ public struct StatusRowView: View {
EmojiTextApp(status.content, emojis: status.emojis, language: status.language)
.font(.scaledBody)
.environment(\.openURL, OpenURLAction { url in
routerPath.handleStatus(status: status, url: url)
viewModel.routerPath.handleStatus(status: status, url: url)
})
Spacer()
}
@ -437,10 +434,10 @@ public struct StatusRowView: View {
if !viewModel.isCompact, !viewModel.isEmbedLoading,
let embed = viewModel.embeddedStatus
{
StatusEmbeddedView(status: embed)
StatusEmbeddedView(status: embed, client: viewModel.client, routerPath: viewModel.routerPath)
.fixedSize(horizontal: false, vertical: true)
} else if viewModel.isEmbedLoading, !viewModel.isCompact {
StatusEmbeddedView(status: .placeholder())
StatusEmbeddedView(status: .placeholder(), client: viewModel.client, routerPath: viewModel.routerPath)
.redacted(reason: .placeholder)
.shimmering()
}
@ -528,7 +525,7 @@ public struct StatusRowView: View {
private func makeSwipeButtonForRouterPath(action: StatusAction, destination: SheetDestinations) -> some View {
Button {
HapticManager.shared.fireHaptic(of: .notification(.success))
routerPath.presentedSheet = destination
viewModel.routerPath.presentedSheet = destination
} label: {
makeSwipeLabel(action: action, style: preferences.swipeActionsIconStyle)
}

View file

@ -55,15 +55,20 @@ public class StatusRowViewModel: ObservableObject {
}
}
var client: Client?
let client: Client
let routerPath: RouterPath
public init(status: Status,
client: Client,
routerPath: RouterPath,
isCompact: Bool = false,
isFocused: Bool = false,
isRemote: Bool = false,
showActions: Bool = true)
{
self.status = status
self.client = client
self.routerPath = routerPath
self.isCompact = isCompact
self.isFocused = isFocused
self.isRemote = isRemote
@ -95,7 +100,7 @@ public class StatusRowViewModel: ObservableObject {
}
}
func navigateToDetail(routerPath: RouterPath) {
func navigateToDetail() {
guard !isFocused else { return }
if isRemote, let url = URL(string: status.reblog?.url ?? status.url ?? "") {
routerPath.navigate(to: .remoteStatusDetail(url: url))
@ -104,7 +109,7 @@ public class StatusRowViewModel: ObservableObject {
}
}
func navigateToAccountDetail(account: Account, routerPath: RouterPath) {
func navigateToAccountDetail(account: Account) {
if isRemote, let url = account.url {
withAnimation {
isLoadingRemoteContent = true
@ -118,7 +123,7 @@ public class StatusRowViewModel: ObservableObject {
}
}
func navigateToMention(mention: Mention, routerPath: RouterPath) {
func navigateToMention(mention: Mention) {
if isRemote {
withAnimation {
isLoadingRemoteContent = true
@ -133,8 +138,7 @@ public class StatusRowViewModel: ObservableObject {
}
func loadEmbeddedStatus() async {
guard let client,
embeddedStatus == nil,
guard embeddedStatus == nil,
!status.content.statusesURLs.isEmpty,
let url = status.content.statusesURLs.first,
client.hasConnection(with: url)
@ -167,7 +171,7 @@ public class StatusRowViewModel: ObservableObject {
}
func favorite() async {
guard let client, client.isAuth else { return }
guard client.isAuth else { return }
isFavorited = true
favoritesCount += 1
do {
@ -180,7 +184,7 @@ public class StatusRowViewModel: ObservableObject {
}
func unFavorite() async {
guard let client, client.isAuth else { return }
guard client.isAuth else { return }
isFavorited = false
favoritesCount -= 1
do {
@ -193,7 +197,7 @@ public class StatusRowViewModel: ObservableObject {
}
func reblog() async {
guard let client, client.isAuth else { return }
guard client.isAuth else { return }
isReblogged = true
reblogsCount += 1
do {
@ -206,7 +210,7 @@ public class StatusRowViewModel: ObservableObject {
}
func unReblog() async {
guard let client, client.isAuth else { return }
guard client.isAuth else { return }
isReblogged = false
reblogsCount -= 1
do {
@ -219,7 +223,7 @@ public class StatusRowViewModel: ObservableObject {
}
func pin() async {
guard let client, client.isAuth else { return }
guard client.isAuth else { return }
isPinned = true
do {
let status: Status = try await client.post(endpoint: Statuses.pin(id: status.reblog?.id ?? status.id))
@ -230,7 +234,7 @@ public class StatusRowViewModel: ObservableObject {
}
func unPin() async {
guard let client, client.isAuth else { return }
guard client.isAuth else { return }
isPinned = false
do {
let status: Status = try await client.post(endpoint: Statuses.unpin(id: status.reblog?.id ?? status.id))
@ -241,7 +245,7 @@ public class StatusRowViewModel: ObservableObject {
}
func bookmark() async {
guard let client, client.isAuth else { return }
guard client.isAuth else { return }
isBookmarked = true
do {
let status: Status = try await client.post(endpoint: Statuses.bookmark(id: localStatusId ?? status.reblog?.id ?? status.id))
@ -252,7 +256,7 @@ public class StatusRowViewModel: ObservableObject {
}
func unbookmark() async {
guard let client, client.isAuth else { return }
guard client.isAuth else { return }
isBookmarked = false
do {
let status: Status = try await client.post(endpoint: Statuses.unbookmark(id: localStatusId ?? status.reblog?.id ?? status.id))
@ -263,14 +267,12 @@ public class StatusRowViewModel: ObservableObject {
}
func delete() async {
guard let client else { return }
do {
_ = try await client.delete(endpoint: Statuses.status(id: status.id))
} catch {}
}
func fetchActionsAccounts() async {
guard let client else { return }
do {
favoriters = try await client.get(endpoint: Statuses.favoritedBy(id: status.id, maxId: nil))
rebloggers = try await client.get(endpoint: Statuses.rebloggedBy(id: status.id, maxId: nil))
@ -303,7 +305,6 @@ public class StatusRowViewModel: ObservableObject {
}
private func translate(userLang: String, sourceLang _: String?) async {
guard let client else { return }
do {
withAnimation {
isLoadingTranslation = true
@ -329,7 +330,7 @@ public class StatusRowViewModel: ObservableObject {
}
func fetchRemoteStatus() async -> Bool {
guard isRemote, let client, let remoteStatusURL = URL(string: status.reblog?.url ?? status.url ?? "") else { return false }
guard isRemote, let remoteStatusURL = URL(string: status.reblog?.url ?? status.url ?? "") else { return false }
isLoadingRemoteContent = true
let results: SearchResults? = try? await client.get(endpoint: Search.search(query: remoteStatusURL.absoluteString,
type: "statuses",

View file

@ -43,9 +43,9 @@ public struct TimelineView: View {
}
switch viewModel.timeline {
case .remoteLocal:
StatusesListView(fetcher: viewModel, isRemote: true)
StatusesListView(fetcher: viewModel, client: client, routerPath: routerPath, isRemote: true)
default:
StatusesListView(fetcher: viewModel)
StatusesListView(fetcher: viewModel, client: client, routerPath: routerPath)
}
}
.id(client.id)