IceCubesApp/Packages/Account/Sources/Account/AccountDetailViewModel.swift

251 lines
9.7 KiB
Swift
Raw Normal View History

2023-01-17 10:36:01 +00:00
import Env
2022-11-29 11:18:06 +00:00
import Models
2023-01-17 10:36:01 +00:00
import Network
2022-12-19 06:17:01 +00:00
import Status
2023-01-17 10:36:01 +00:00
import SwiftUI
2022-11-29 11:18:06 +00:00
@MainActor
2022-12-19 06:17:01 +00:00
class AccountDetailViewModel: ObservableObject, StatusesFetcher {
2022-11-29 11:18:06 +00:00
let accountId: String
2022-12-19 11:28:55 +00:00
var client: Client?
2022-12-27 12:49:54 +00:00
var isCurrentUser: Bool = false
2023-01-17 10:36:01 +00:00
enum AccountState {
2022-12-01 08:05:26 +00:00
case loading, data(account: Account), error(error: Error)
2022-11-29 11:18:06 +00:00
}
2023-01-17 10:36:01 +00:00
enum Tab: Int {
2023-01-09 18:26:56 +00:00
case statuses, favourites, bookmarks, followedTags, postsAndReplies, media, lists
2023-01-17 10:36:01 +00:00
static var currentAccountTabs: [Tab] {
2023-01-09 18:26:56 +00:00
[.statuses, .favourites, .bookmarks, .followedTags, .lists]
}
2023-01-17 10:36:01 +00:00
static var accountTabs: [Tab] {
[.statuses, .postsAndReplies, .media]
}
2023-01-17 10:36:01 +00:00
2023-01-09 18:26:56 +00:00
var iconName: String {
switch self {
2023-01-09 18:26:56 +00:00
case .statuses: return "bubble.right"
case .favourites: return "star"
case .bookmarks: return "bookmark"
case .followedTags: return "tag"
case .postsAndReplies: return "bubble.left.and.bubble.right"
case .media: return "photo.on.rectangle.angled"
case .lists: return "list.bullet"
}
}
}
2023-01-17 10:36:01 +00:00
enum TabState {
2023-01-04 17:37:58 +00:00
case followedTags
case statuses(statusesState: StatusesState)
case lists
}
2023-01-17 10:36:01 +00:00
@Published var accountState: AccountState = .loading
@Published var tabState: TabState = .statuses(statusesState: .loading) {
didSet {
/// Forward viewModel tabState related to statusesState to statusesState property
/// for `StatusesFetcher` conformance as we wrap StatusesState in TabState
switch tabState {
case let .statuses(statusesState):
self.statusesState = statusesState
default:
break
}
}
}
2023-01-17 10:36:01 +00:00
2022-12-18 19:30:19 +00:00
@Published var statusesState: StatusesState = .loading
2023-01-17 10:36:01 +00:00
2022-12-20 16:11:12 +00:00
@Published var relationship: Relationshionship?
2023-01-03 17:22:08 +00:00
@Published var pinned: [Status] = []
@Published var favourites: [Status] = []
2023-01-09 18:26:56 +00:00
@Published var bookmarks: [Status] = []
private var favouritesNextPage: LinkHandler?
2023-01-09 18:26:56 +00:00
private var bookmarksNextPage: LinkHandler?
2022-12-21 19:26:38 +00:00
@Published var featuredTags: [FeaturedTag] = []
2022-12-21 19:53:23 +00:00
@Published var fields: [Account.Field] = []
@Published var familliarFollowers: [Account] = []
@Published var selectedTab = Tab.statuses {
didSet {
switch selectedTab {
case .statuses, .postsAndReplies, .media:
tabTask?.cancel()
tabTask = Task {
await fetchStatuses()
}
default:
reloadTabState()
}
}
}
2023-01-17 10:36:01 +00:00
private(set) var account: Account?
private var tabTask: Task<Void, Never>?
2023-01-17 10:36:01 +00:00
2022-12-20 15:08:09 +00:00
private(set) var statuses: [Status] = []
2023-01-17 10:36:01 +00:00
/// When coming from a URL like a mention tap in a status.
2022-11-29 11:18:06 +00:00
init(accountId: String) {
self.accountId = accountId
2023-01-17 10:36:01 +00:00
isCurrentUser = false
2022-11-29 11:18:06 +00:00
}
2023-01-17 10:36:01 +00:00
/// When the account is already fetched by the parent caller.
2022-12-27 12:49:54 +00:00
init(account: Account) {
2023-01-17 10:36:01 +00:00
accountId = account.id
self.account = account
2023-01-17 10:36:01 +00:00
accountState = .data(account: account)
2022-12-17 12:37:46 +00:00
}
2023-01-17 10:36:01 +00:00
2023-01-10 20:09:20 +00:00
struct AccountData {
let account: Account
let featuredTags: [FeaturedTag]
let relationships: [Relationshionship]
let familliarFollowers: [FamilliarAccounts]
}
2023-01-17 10:36:01 +00:00
2022-11-29 11:18:06 +00:00
func fetchAccount() async {
2022-12-19 11:28:55 +00:00
guard let client else { return }
2022-11-29 11:18:06 +00:00
do {
2023-01-10 20:09:20 +00:00
let data = try await fetchAccountData(accountId: accountId, client: client)
accountState = .data(account: data.account)
2023-01-17 10:36:01 +00:00
2023-01-10 20:09:20 +00:00
account = data.account
fields = data.account.fields
featuredTags = data.featuredTags
featuredTags.sort { $0.statusesCountInt > $1.statusesCountInt }
relationship = data.relationships.first
familliarFollowers = data.familliarFollowers.first?.accounts ?? []
2023-01-17 10:36:01 +00:00
2022-11-29 11:18:06 +00:00
} catch {
2022-12-30 07:36:22 +00:00
if let account {
accountState = .data(account: account)
} else {
accountState = .error(error: error)
}
2022-11-29 11:18:06 +00:00
}
}
2023-01-17 10:36:01 +00:00
2023-01-10 20:09:20 +00:00
func fetchAccountData(accountId: String, client: Client) async throws -> AccountData {
async let account: Account = client.get(endpoint: Accounts.accounts(id: accountId))
async let featuredTags: [FeaturedTag] = client.get(endpoint: Accounts.featuredTags(id: accountId))
if client.isAuth && !isCurrentUser {
async let relationships: [Relationshionship] = client.get(endpoint: Accounts.relationships(ids: [accountId]))
async let familliarFollowers: [FamilliarAccounts] = client.get(endpoint: Accounts.familiarFollowers(withAccount: accountId))
return try await .init(account: account,
featuredTags: featuredTags,
relationships: relationships,
familliarFollowers: familliarFollowers)
}
return try await .init(account: account,
featuredTags: featuredTags,
relationships: [],
familliarFollowers: [])
}
2023-01-17 10:36:01 +00:00
2022-12-18 19:30:19 +00:00
func fetchStatuses() async {
2022-12-19 11:28:55 +00:00
guard let client else { return }
2022-12-18 19:30:19 +00:00
do {
tabState = .statuses(statusesState: .loading)
statuses =
2023-01-03 17:22:08 +00:00
try await client.get(endpoint: Accounts.statuses(id: accountId,
sinceId: nil,
tag: nil,
2023-01-17 10:36:01 +00:00
onlyMedia: selectedTab == .media ? true : nil,
excludeReplies: selectedTab == .statuses && !isCurrentUser ? true : nil,
pinned: nil))
if selectedTab == .statuses {
pinned =
try await client.get(endpoint: Accounts.statuses(id: accountId,
sinceId: nil,
tag: nil,
onlyMedia: nil,
excludeReplies: nil,
pinned: true))
2023-01-03 17:22:08 +00:00
}
if isCurrentUser {
(favourites, favouritesNextPage) = try await client.getWithLink(endpoint: Accounts.favourites(sinceId: nil))
2023-01-09 18:26:56 +00:00
(bookmarks, bookmarksNextPage) = try await client.getWithLink(endpoint: Accounts.bookmarks(sinceId: nil))
}
2022-12-21 11:39:29 +00:00
reloadTabState()
2022-12-18 19:30:19 +00:00
} catch {
tabState = .statuses(statusesState: .error(error: error))
2022-12-18 19:30:19 +00:00
}
}
2023-01-17 10:36:01 +00:00
2022-12-19 06:17:01 +00:00
func fetchNextPage() async {
2022-12-19 11:28:55 +00:00
guard let client else { return }
2022-12-18 19:30:19 +00:00
do {
switch selectedTab {
case .statuses, .postsAndReplies, .media:
guard let lastId = statuses.last?.id else { return }
tabState = .statuses(statusesState: .display(statuses: statuses, nextPageState: .loadingNextPage))
let newStatuses: [Status] =
2023-01-17 10:36:01 +00:00
try await client.get(endpoint: Accounts.statuses(id: accountId,
sinceId: lastId,
tag: nil,
onlyMedia: selectedTab == .media ? true : nil,
excludeReplies: selectedTab == .statuses && !isCurrentUser ? true : nil,
pinned: nil))
statuses.append(contentsOf: newStatuses)
tabState = .statuses(statusesState: .display(statuses: statuses,
nextPageState: newStatuses.count < 20 ? .none : .hasNextPage))
case .favourites:
guard let nextPageId = favouritesNextPage?.maxId else { return }
let newFavourites: [Status]
(newFavourites, favouritesNextPage) = try await client.getWithLink(endpoint: Accounts.favourites(sinceId: nextPageId))
favourites.append(contentsOf: newFavourites)
tabState = .statuses(statusesState: .display(statuses: favourites, nextPageState: .hasNextPage))
2023-01-09 18:26:56 +00:00
case .bookmarks:
guard let nextPageId = bookmarksNextPage?.maxId else { return }
let newBookmarks: [Status]
(newBookmarks, bookmarksNextPage) = try await client.getWithLink(endpoint: Accounts.bookmarks(sinceId: nextPageId))
bookmarks.append(contentsOf: newBookmarks)
tabState = .statuses(statusesState: .display(statuses: bookmarks, nextPageState: .hasNextPage))
case .followedTags, .lists:
break
}
2022-12-18 19:30:19 +00:00
} catch {
tabState = .statuses(statusesState: .error(error: error))
2022-12-18 19:30:19 +00:00
}
}
2023-01-17 10:36:01 +00:00
2022-12-21 11:39:29 +00:00
private func reloadTabState() {
switch selectedTab {
case .statuses, .postsAndReplies, .media:
tabState = .statuses(statusesState: .display(statuses: statuses, nextPageState: statuses.count < 20 ? .none : .hasNextPage))
2022-12-21 11:39:29 +00:00
case .favourites:
2022-12-24 14:09:17 +00:00
tabState = .statuses(statusesState: .display(statuses: favourites,
nextPageState: favouritesNextPage != nil ? .hasNextPage : .none))
2023-01-09 18:26:56 +00:00
case .bookmarks:
tabState = .statuses(statusesState: .display(statuses: bookmarks,
nextPageState: bookmarksNextPage != nil ? .hasNextPage : .none))
2022-12-21 11:39:29 +00:00
case .followedTags:
2023-01-04 17:37:58 +00:00
tabState = .followedTags
case .lists:
tabState = .lists
2022-12-21 11:39:29 +00:00
}
}
2023-01-17 10:36:01 +00:00
func handleEvent(event: any StreamEvent, currentAccount: CurrentAccount) {
if let event = event as? StreamEventUpdate {
if event.status.account.id == currentAccount.account?.id {
statuses.insert(event.status, at: 0)
statusesState = .display(statuses: statuses, nextPageState: .hasNextPage)
}
} else if let event = event as? StreamEventDelete {
statuses.removeAll(where: { $0.id == event.status })
statusesState = .display(statuses: statuses, nextPageState: .hasNextPage)
} else if let event = event as? StreamEventStatusUpdate {
if let originalIndex = statuses.firstIndex(where: { $0.id == event.status.id }) {
statuses[originalIndex] = event.status
statusesState = .display(statuses: statuses, nextPageState: .hasNextPage)
}
}
}
2022-11-29 11:18:06 +00:00
}