Add following/followers.
This commit is contained in:
parent
d6a63036fb
commit
b3c542588c
|
@ -40,6 +40,9 @@
|
|||
F85D498329642FAC00751DF7 /* AttachmentData+Comperable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F85D498229642FAC00751DF7 /* AttachmentData+Comperable.swift */; };
|
||||
F85D49852964301800751DF7 /* StatusData+Attachments.swift in Sources */ = {isa = PBXBuildFile; fileRef = F85D49842964301800751DF7 /* StatusData+Attachments.swift */; };
|
||||
F85D49872964334100751DF7 /* String+Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = F85D49862964334100751DF7 /* String+Date.swift */; };
|
||||
F85DBF8F296732E20069BF89 /* FollowersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F85DBF8E296732E20069BF89 /* FollowersView.swift */; };
|
||||
F85DBF912967385F0069BF89 /* FollowingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F85DBF902967385F0069BF89 /* FollowingView.swift */; };
|
||||
F85DBF93296760790069BF89 /* CacheAvatarService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F85DBF92296760790069BF89 /* CacheAvatarService.swift */; };
|
||||
F866F6A0296040A8002E8F88 /* ApplicationSettings+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = F866F69E296040A8002E8F88 /* ApplicationSettings+CoreDataClass.swift */; };
|
||||
F866F6A1296040A8002E8F88 /* ApplicationSettings+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = F866F69F296040A8002E8F88 /* ApplicationSettings+CoreDataProperties.swift */; };
|
||||
F866F6A329604161002E8F88 /* AccountDataHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F866F6A229604161002E8F88 /* AccountDataHandler.swift */; };
|
||||
|
@ -100,6 +103,9 @@
|
|||
F85D498229642FAC00751DF7 /* AttachmentData+Comperable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttachmentData+Comperable.swift"; sourceTree = "<group>"; };
|
||||
F85D49842964301800751DF7 /* StatusData+Attachments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusData+Attachments.swift"; sourceTree = "<group>"; };
|
||||
F85D49862964334100751DF7 /* String+Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Date.swift"; sourceTree = "<group>"; };
|
||||
F85DBF8E296732E20069BF89 /* FollowersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowersView.swift; sourceTree = "<group>"; };
|
||||
F85DBF902967385F0069BF89 /* FollowingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowingView.swift; sourceTree = "<group>"; };
|
||||
F85DBF92296760790069BF89 /* CacheAvatarService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CacheAvatarService.swift; sourceTree = "<group>"; };
|
||||
F866F69E296040A8002E8F88 /* ApplicationSettings+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ApplicationSettings+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||
F866F69F296040A8002E8F88 /* ApplicationSettings+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ApplicationSettings+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
F866F6A229604161002E8F88 /* AccountDataHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDataHandler.swift; sourceTree = "<group>"; };
|
||||
|
@ -164,6 +170,8 @@
|
|||
F88FAD26295F400E009B20C9 /* NotificationsView.swift */,
|
||||
F866F6A629604629002E8F88 /* SignInView.swift */,
|
||||
F8A93D7D2965FD89001D8331 /* UserProfileView.swift */,
|
||||
F85DBF8E296732E20069BF89 /* FollowersView.swift */,
|
||||
F85DBF902967385F0069BF89 /* FollowingView.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
|
@ -293,6 +301,7 @@
|
|||
F8A93D7F2965FED4001D8331 /* AccountService.swift */,
|
||||
F8210DE02966D0C4001D9973 /* StatusService.swift */,
|
||||
F8210DEB2966F30C001D9973 /* UserFeedbackService.swift */,
|
||||
F85DBF92296760790069BF89 /* CacheAvatarService.swift */,
|
||||
);
|
||||
path = Services;
|
||||
sourceTree = "<group>";
|
||||
|
@ -383,6 +392,7 @@
|
|||
F866F6A0296040A8002E8F88 /* ApplicationSettings+CoreDataClass.swift in Sources */,
|
||||
F8210DEA2966E4F9001D9973 /* AnimatePlaceholderModifier.swift in Sources */,
|
||||
F8210DE52966E160001D9973 /* Color+SystemColors.swift in Sources */,
|
||||
F85DBF93296760790069BF89 /* CacheAvatarService.swift in Sources */,
|
||||
F88FAD23295F3FC4009B20C9 /* LocalFeedView.swift in Sources */,
|
||||
F88FAD2B295F43B8009B20C9 /* AccountData+CoreDataProperties.swift in Sources */,
|
||||
F85D4975296407F100751DF7 /* TimelineService.swift in Sources */,
|
||||
|
@ -391,6 +401,7 @@
|
|||
F88C2475295C37BB0006098B /* CoreDataHandler.swift in Sources */,
|
||||
F88FAD2A295F43B8009B20C9 /* AccountData+CoreDataClass.swift in Sources */,
|
||||
F8210DE12966D0C4001D9973 /* StatusService.swift in Sources */,
|
||||
F85DBF8F296732E20069BF89 /* FollowersView.swift in Sources */,
|
||||
F8A93D822965FF5D001D8331 /* MastodonClientAuthenticated+Account.swift in Sources */,
|
||||
F85D49872964334100751DF7 /* String+Date.swift in Sources */,
|
||||
F8341F92295C63BB009C8EE6 /* ImageStatus.swift in Sources */,
|
||||
|
@ -402,6 +413,7 @@
|
|||
F80048052961850500E6868A /* StatusData+CoreDataClass.swift in Sources */,
|
||||
F80048042961850500E6868A /* AttachmentData+CoreDataProperties.swift in Sources */,
|
||||
F83901A6295D8EC000456AE2 /* LabelIcon.swift in Sources */,
|
||||
F85DBF912967385F0069BF89 /* FollowingView.swift in Sources */,
|
||||
F800480A2961EA1900E6868A /* AttachmentDataHandler.swift in Sources */,
|
||||
F80048032961850500E6868A /* AttachmentData+CoreDataClass.swift in Sources */,
|
||||
F8341F90295C636C009C8EE6 /* UIImage+Exif.swift in Sources */,
|
||||
|
|
|
@ -17,20 +17,20 @@ extension AccountData {
|
|||
}
|
||||
|
||||
@NSManaged public var accessToken: String?
|
||||
@NSManaged public var acct: String?
|
||||
@NSManaged public var acct: String
|
||||
@NSManaged public var avatar: URL?
|
||||
@NSManaged public var avatarData: Data?
|
||||
@NSManaged public var createdAt: String?
|
||||
@NSManaged public var createdAt: String
|
||||
@NSManaged public var displayName: String?
|
||||
@NSManaged public var followersCount: Int32
|
||||
@NSManaged public var followingCount: Int32
|
||||
@NSManaged public var header: URL?
|
||||
@NSManaged public var id: String?
|
||||
@NSManaged public var id: String
|
||||
@NSManaged public var locked: Bool
|
||||
@NSManaged public var note: String?
|
||||
@NSManaged public var statusesCount: Int32
|
||||
@NSManaged public var url: URL?
|
||||
@NSManaged public var username: String?
|
||||
@NSManaged public var username: String
|
||||
@NSManaged public var clientId: String
|
||||
@NSManaged public var clientSecret: String
|
||||
@NSManaged public var clientVapidKey: String
|
||||
|
|
|
@ -14,7 +14,7 @@ extension StatusData {
|
|||
self.accountAvatar = status.account?.avatar
|
||||
self.accountDisplayName = status.account?.displayName
|
||||
self.accountId = status.account!.id
|
||||
self.accountUsername = status.account!.username
|
||||
self.accountUsername = status.account!.acct
|
||||
self.applicationName = status.application?.name
|
||||
self.applicationWebsite = status.application?.website
|
||||
self.bookmarked = status.bookmarked
|
||||
|
|
|
@ -52,4 +52,26 @@ extension MastodonClientAuthenticated {
|
|||
let (data, _) = try await urlSession.data(for: request)
|
||||
return try JSONDecoder().decode(Relationship.self, from: data)
|
||||
}
|
||||
|
||||
func getFollowers(for accountId: String, page: Int = 1) async throws -> [Account] {
|
||||
let request = try Self.request(
|
||||
for: baseURL,
|
||||
target: Mastodon.Account.followers(accountId, nil, nil, nil, nil, page),
|
||||
withBearerToken: token
|
||||
)
|
||||
|
||||
let (data, _) = try await urlSession.data(for: request)
|
||||
return try JSONDecoder().decode([Account].self, from: data)
|
||||
}
|
||||
|
||||
func getFollowing(for accountId: String, page: Int = 1) async throws -> [Account] {
|
||||
let request = try Self.request(
|
||||
for: baseURL,
|
||||
target: Mastodon.Account.following(accountId, nil, nil, nil, nil, page),
|
||||
withBearerToken: token
|
||||
)
|
||||
|
||||
let (data, _) = try await urlSession.data(for: request)
|
||||
return try JSONDecoder().decode([Account].self, from: data)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,4 +46,22 @@ public class AccountService {
|
|||
let client = MastodonClient(baseURL: serverUrl).getAuthenticated(token: accessToken)
|
||||
return try await client.follow(for: accountId)
|
||||
}
|
||||
|
||||
public func getFollowers(forAccountId accountId: String, andContext accountData: AccountData?, page: Int) async throws -> [Account] {
|
||||
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
|
||||
return []
|
||||
}
|
||||
|
||||
let client = MastodonClient(baseURL: serverUrl).getAuthenticated(token: accessToken)
|
||||
return try await client.getFollowers(for: accountId, page: page)
|
||||
}
|
||||
|
||||
public func getFollowing(forAccountId accountId: String, andContext accountData: AccountData?, page: Int) async throws -> [Account] {
|
||||
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
|
||||
return []
|
||||
}
|
||||
|
||||
let client = MastodonClient(baseURL: serverUrl).getAuthenticated(token: accessToken)
|
||||
return try await client.getFollowing(for: accountId, page: page)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
public class CacheAvatarService {
|
||||
public static let shared = CacheAvatarService()
|
||||
private init() { }
|
||||
|
||||
private var cache: Dictionary<String, UIImage> = [:]
|
||||
|
||||
func addImage(for id: String, data: Data) {
|
||||
if let uiImage = UIImage(data: data) {
|
||||
self.cache[id] = uiImage
|
||||
}
|
||||
}
|
||||
|
||||
func getImage(for id: String) -> UIImage? {
|
||||
return self.cache[id]
|
||||
}
|
||||
}
|
|
@ -26,7 +26,9 @@ struct DetailsView: View {
|
|||
accountDisplayName: statusData.accountDisplayName,
|
||||
accountUserName: statusData.accountUsername)
|
||||
.environmentObject(applicationState)) {
|
||||
UsernameRow(statusData: statusData)
|
||||
UsernameRow(accountAvatar: statusData.accountAvatar,
|
||||
accountDisplayName: statusData.accountDisplayName,
|
||||
accountUsername: statusData.accountUsername)
|
||||
}
|
||||
|
||||
HTMLFormattedText(statusData.content)
|
||||
|
@ -38,6 +40,7 @@ struct DetailsView: View {
|
|||
LabelIcon(iconName: "timelapse", value: "24.0 mm, f/1.8, 1/640s, ISO 100")
|
||||
LabelIcon(iconName: "calendar", value: "2 Oct 2022")
|
||||
}
|
||||
.padding(.bottom, 2)
|
||||
.foregroundColor(Color.lightGrayColor)
|
||||
|
||||
HStack {
|
||||
|
@ -55,12 +58,7 @@ struct DetailsView: View {
|
|||
.padding(8)
|
||||
}
|
||||
.padding(8)
|
||||
|
||||
Rectangle()
|
||||
.size(width: UIScreen.main.bounds.width, height: 4)
|
||||
.fill(Color.mainTextColor)
|
||||
.opacity(0.1)
|
||||
|
||||
|
||||
CommentsSection(statusId: statusData.id)
|
||||
}
|
||||
} else {
|
||||
|
@ -69,22 +67,10 @@ struct DetailsView: View {
|
|||
.fill(Color.placeholderText)
|
||||
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.width)
|
||||
|
||||
HStack (alignment: .center) {
|
||||
Circle()
|
||||
.fill(Color.placeholderText)
|
||||
.frame(width: 48.0, height: 48.0)
|
||||
|
||||
VStack (alignment: .leading) {
|
||||
Text("Verylong Displayname")
|
||||
.foregroundColor(Color.mainTextColor)
|
||||
Text("@username")
|
||||
.foregroundColor(Color.lightGrayColor)
|
||||
.font(.footnote)
|
||||
}
|
||||
.padding(.leading, 8)
|
||||
}.padding(8)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
UsernameRow(accountDisplayName: "Verylong Displayname",
|
||||
accountUsername: "@username")
|
||||
|
||||
Text("Lorem ispum text something")
|
||||
.foregroundColor(Color.lightGrayColor)
|
||||
.font(.footnote)
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import MastodonSwift
|
||||
|
||||
struct FollowersView: View {
|
||||
@EnvironmentObject var applicationState: ApplicationState
|
||||
|
||||
@State var accountId: String
|
||||
@State private var accounts: [Account] = []
|
||||
@State private var page = 1
|
||||
@State private var allItemsLoaded = false
|
||||
|
||||
var body: some View {
|
||||
List(accounts, id: \.id) { account in
|
||||
NavigationLink(destination: UserProfileView(
|
||||
accountId: account.id,
|
||||
accountDisplayName: account.displayName,
|
||||
accountUserName: account.acct)
|
||||
.environmentObject(applicationState)) {
|
||||
UsernameRow(accountAvatar: account.avatar,
|
||||
accountDisplayName: account.displayName,
|
||||
accountUsername: account.acct,
|
||||
cachedAvatar: CacheAvatarService.shared.getImage(for: account.id))
|
||||
}
|
||||
|
||||
if allItemsLoaded == false && accounts.last?.id == account.id {
|
||||
HStack(alignment: .center) {
|
||||
Spacer()
|
||||
ProgressView()
|
||||
.progressViewStyle(CircularProgressViewStyle())
|
||||
.onAppear {
|
||||
Task {
|
||||
self.page = self.page + 1
|
||||
await self.loadAccounts(page: self.page)
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationBarTitle("Followers")
|
||||
.listStyle(PlainListStyle())
|
||||
.task {
|
||||
if self.accounts.isEmpty == false {
|
||||
return
|
||||
}
|
||||
|
||||
await self.loadAccounts(page: self.page)
|
||||
}
|
||||
}
|
||||
|
||||
func loadAccounts(page: Int) async {
|
||||
do {
|
||||
let accountsFromApi = try await AccountService.shared.getFollowers(
|
||||
forAccountId: self.accountId,
|
||||
andContext: self.applicationState.accountData,
|
||||
page: page)
|
||||
|
||||
if accountsFromApi.isEmpty {
|
||||
self.allItemsLoaded = true
|
||||
return
|
||||
}
|
||||
|
||||
for account in accountsFromApi {
|
||||
guard let avatarUrl = account.avatar else {
|
||||
continue
|
||||
}
|
||||
|
||||
do {
|
||||
if let avatarData = try await RemoteFileService.shared.fetchData(url: avatarUrl) {
|
||||
CacheAvatarService.shared.addImage(for: account.id, data: avatarData)
|
||||
}
|
||||
} catch {
|
||||
print("Error \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
self.accounts.append(contentsOf: accountsFromApi)
|
||||
} catch {
|
||||
print("Error \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FollowersView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
FollowersView(accountId: "")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import MastodonSwift
|
||||
|
||||
struct FollowingView: View {
|
||||
@EnvironmentObject var applicationState: ApplicationState
|
||||
|
||||
@State var accountId: String
|
||||
@State private var accounts: [Account] = []
|
||||
@State private var page = 1
|
||||
@State private var allItemsLoaded = false
|
||||
|
||||
var body: some View {
|
||||
List(accounts, id: \.id) { account in
|
||||
NavigationLink(destination: UserProfileView(
|
||||
accountId: account.id,
|
||||
accountDisplayName: account.displayName,
|
||||
accountUserName: account.acct)
|
||||
.environmentObject(applicationState)) {
|
||||
UsernameRow(accountAvatar: account.avatar,
|
||||
accountDisplayName: account.displayName,
|
||||
accountUsername: account.acct,
|
||||
cachedAvatar: CacheAvatarService.shared.getImage(for: account.id))
|
||||
}
|
||||
|
||||
if allItemsLoaded == false && accounts.last?.id == account.id {
|
||||
HStack(alignment: .center) {
|
||||
Spacer()
|
||||
ProgressView()
|
||||
.progressViewStyle(CircularProgressViewStyle())
|
||||
.onAppear {
|
||||
Task {
|
||||
self.page = self.page + 1
|
||||
await self.loadAccounts(page: self.page)
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationBarTitle("Following")
|
||||
.listStyle(PlainListStyle())
|
||||
.task {
|
||||
if self.accounts.isEmpty == false {
|
||||
return
|
||||
}
|
||||
|
||||
await self.loadAccounts(page: self.page)
|
||||
}
|
||||
}
|
||||
|
||||
func loadAccounts(page: Int) async {
|
||||
do {
|
||||
let accountsFromApi = try await AccountService.shared.getFollowing(
|
||||
forAccountId: self.accountId,
|
||||
andContext: self.applicationState.accountData,
|
||||
page: page)
|
||||
|
||||
if accountsFromApi.isEmpty {
|
||||
self.allItemsLoaded = true
|
||||
return
|
||||
}
|
||||
|
||||
for account in accountsFromApi {
|
||||
guard let avatarUrl = account.avatar else {
|
||||
continue
|
||||
}
|
||||
|
||||
do {
|
||||
if let avatarData = try await RemoteFileService.shared.fetchData(url: avatarUrl) {
|
||||
CacheAvatarService.shared.addImage(for: account.id, data: avatarData)
|
||||
}
|
||||
} catch {
|
||||
print("Error \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
self.accounts.append(contentsOf: accountsFromApi)
|
||||
} catch {
|
||||
print("Error \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FollowingView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
FollowingView(accountId: "")
|
||||
}
|
||||
}
|
|
@ -21,7 +21,7 @@ struct MainView: View {
|
|||
}
|
||||
|
||||
private enum ViewMode {
|
||||
case home, local, federated, notifications
|
||||
case home, local, federated, profile, notifications
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
|
@ -43,6 +43,12 @@ struct MainView: View {
|
|||
LocalFeedView()
|
||||
case .federated:
|
||||
FederatedFeedView()
|
||||
case .profile:
|
||||
if let accountData = self.applicationState.accountData {
|
||||
UserProfileView(accountId: accountData.id,
|
||||
accountDisplayName: accountData.displayName,
|
||||
accountUserName: accountData.username)
|
||||
}
|
||||
case .notifications:
|
||||
NotificationsView()
|
||||
}
|
||||
|
@ -80,6 +86,15 @@ struct MainView: View {
|
|||
}
|
||||
|
||||
Divider()
|
||||
|
||||
Button {
|
||||
viewMode = .profile
|
||||
} label: {
|
||||
HStack {
|
||||
Text(self.getViewTitle(viewMode: .profile))
|
||||
Image(systemName: "person")
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
viewMode = .notifications
|
||||
|
@ -152,6 +167,8 @@ struct MainView: View {
|
|||
return "Local"
|
||||
case .federated:
|
||||
return "Federated"
|
||||
case .profile:
|
||||
return "Profile"
|
||||
case .notifications:
|
||||
return "Notifications"
|
||||
}
|
||||
|
|
|
@ -48,62 +48,73 @@ struct UserProfileView: View {
|
|||
|
||||
Spacer()
|
||||
|
||||
VStack(alignment: .center) {
|
||||
Text("\(account.followersCount)")
|
||||
.font(.title3)
|
||||
Text("Followers")
|
||||
.font(.subheadline)
|
||||
.opacity(0.6)
|
||||
}
|
||||
NavigationLink(destination: FollowersView(accountId: account.id)
|
||||
.environmentObject(applicationState)
|
||||
) {
|
||||
VStack(alignment: .center) {
|
||||
Text("\(account.followersCount)")
|
||||
.font(.title3)
|
||||
Text("Followers")
|
||||
.font(.subheadline)
|
||||
.opacity(0.6)
|
||||
}
|
||||
}.foregroundColor(Color.mainTextColor)
|
||||
|
||||
Spacer()
|
||||
|
||||
VStack(alignment: .center) {
|
||||
Text("\(account.followingCount)")
|
||||
.font(.title3)
|
||||
Text("Following")
|
||||
.font(.subheadline)
|
||||
.opacity(0.6)
|
||||
}
|
||||
NavigationLink(destination: FollowingView(accountId: account.id)
|
||||
.environmentObject(applicationState)
|
||||
) {
|
||||
VStack(alignment: .center) {
|
||||
Text("\(account.followingCount)")
|
||||
.font(.title3)
|
||||
Text("Following")
|
||||
.font(.subheadline)
|
||||
.opacity(0.6)
|
||||
}
|
||||
}.foregroundColor(Color.mainTextColor)
|
||||
}
|
||||
|
||||
HStack (alignment: .center) {
|
||||
Text(account.displayName ?? account.username)
|
||||
.foregroundColor(Color.mainTextColor)
|
||||
.font(.footnote)
|
||||
.fontWeight(.bold)
|
||||
Text("@\(account.username)")
|
||||
.foregroundColor(Color.lightGrayColor)
|
||||
.font(.footnote)
|
||||
VStack(alignment: .leading) {
|
||||
Text(account.displayName ?? account.username)
|
||||
.foregroundColor(Color.mainTextColor)
|
||||
.font(.footnote)
|
||||
.fontWeight(.bold)
|
||||
Text("@\(account.username)")
|
||||
.foregroundColor(Color.lightGrayColor)
|
||||
.font(.footnote)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
Task {
|
||||
do {
|
||||
if let relationship = try await AccountService.shared.follow(
|
||||
forAccountId: self.accountId,
|
||||
andContext: self.applicationState.accountData
|
||||
) {
|
||||
UserFeedbackService.shared.send()
|
||||
self.relationship = relationship
|
||||
if self.applicationState.accountData?.id != self.accountId {
|
||||
Button {
|
||||
Task {
|
||||
do {
|
||||
if let relationship = try await AccountService.shared.follow(
|
||||
forAccountId: self.accountId,
|
||||
andContext: self.applicationState.accountData
|
||||
) {
|
||||
UserFeedbackService.shared.send()
|
||||
self.relationship = relationship
|
||||
}
|
||||
} catch {
|
||||
print("Error \(error.localizedDescription)")
|
||||
}
|
||||
} catch {
|
||||
print("Error \(error.localizedDescription)")
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Image(systemName: relationship?.following == true ? "person.badge.minus" : "person.badge.plus")
|
||||
Text(relationship?.following == true ? "Unfollow" : (relationship?.followedBy == true ? "Follow back" : "Follow"))
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Image(systemName: relationship?.following == true ? "person.badge.minus" : "person.badge.plus")
|
||||
Text(relationship?.following == true ? "Unfollow" : (relationship?.followedBy == true ? "Follow back" : "Follow"))
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.tint(relationship?.following == true ? Color.dangerColor : .accentColor)
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.tint(relationship?.following == true ? Color.dangerColor : .accentColor)
|
||||
|
||||
}
|
||||
|
||||
if let note = account.note {
|
||||
if let note = account.note, !note.isEmpty {
|
||||
HTMLFormattedText(note, withFontSize: 14, andWidth: Int(UIScreen.main.bounds.width) - 16)
|
||||
.padding(.top, -10)
|
||||
.padding(.leading, -4)
|
||||
|
|
|
@ -20,6 +20,14 @@ struct CommentsSection: View {
|
|||
VStack {
|
||||
if let context = context {
|
||||
ForEach(context.descendants, id: \.id) { status in
|
||||
|
||||
if withDivider {
|
||||
Rectangle()
|
||||
.size(width: UIScreen.main.bounds.width, height: 4)
|
||||
.fill(Color.mainTextColor)
|
||||
.opacity(0.1)
|
||||
}
|
||||
|
||||
HStack (alignment: .top) {
|
||||
|
||||
if let account = status.account {
|
||||
|
@ -92,15 +100,7 @@ struct CommentsSection: View {
|
|||
.padding(.horizontal, 8)
|
||||
.padding(.bottom, 8)
|
||||
|
||||
|
||||
CommentsSection(statusId: status.id, withDivider: false)
|
||||
|
||||
if withDivider {
|
||||
Rectangle()
|
||||
.size(width: UIScreen.main.bounds.width, height: 4)
|
||||
.fill(Color.mainTextColor)
|
||||
.opacity(0.1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,28 +5,43 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import NukeUI
|
||||
|
||||
struct UsernameRow: View {
|
||||
@ObservedObject public var statusData: StatusData
|
||||
@State public var accountAvatar: URL?
|
||||
@State public var accountDisplayName: String?
|
||||
@State public var accountUsername: String
|
||||
@State public var cachedAvatar: UIImage?
|
||||
|
||||
var body: some View {
|
||||
HStack (alignment: .center) {
|
||||
AsyncImage(url: statusData.accountAvatar) { image in
|
||||
image
|
||||
if let cachedAvatar {
|
||||
Image(uiImage: cachedAvatar)
|
||||
.resizable()
|
||||
.clipShape(Circle())
|
||||
.aspectRatio(contentMode: .fit)
|
||||
} placeholder: {
|
||||
Image(systemName: "person.circle")
|
||||
.resizable()
|
||||
.foregroundColor(Color.mainTextColor)
|
||||
.frame(width: 48.0, height: 48.0)
|
||||
}
|
||||
else {
|
||||
AsyncImage(url: accountAvatar) { image in
|
||||
image
|
||||
.resizable()
|
||||
.clipShape(Circle())
|
||||
.aspectRatio(contentMode: .fit)
|
||||
} placeholder: {
|
||||
Image(systemName: "person.circle")
|
||||
.resizable()
|
||||
.clipShape(Circle())
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.foregroundColor(Color.mainTextColor)
|
||||
}
|
||||
.frame(width: 48.0, height: 48.0)
|
||||
}
|
||||
.frame(width: 48.0, height: 48.0)
|
||||
|
||||
VStack (alignment: .leading) {
|
||||
Text(statusData.accountDisplayName ?? statusData.accountUsername)
|
||||
Text(accountDisplayName ?? accountUsername)
|
||||
.foregroundColor(Color.mainTextColor)
|
||||
Text("@\(statusData.accountUsername)")
|
||||
Text("@\(accountUsername)")
|
||||
.foregroundColor(Color.lightGrayColor)
|
||||
.font(.footnote)
|
||||
}
|
||||
|
@ -37,6 +52,6 @@ struct UsernameRow: View {
|
|||
|
||||
struct UsernameRow_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
UsernameRow(statusData: StatusData())
|
||||
UsernameRow(accountUsername: "")
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue