Add user profile
This commit is contained in:
parent
8bb6b1d985
commit
3c2ee8c592
|
@ -52,6 +52,9 @@
|
|||
F88FAD2B295F43B8009B20C9 /* AccountData+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88FAD29295F43B8009B20C9 /* AccountData+CoreDataProperties.swift */; };
|
||||
F88FAD2D295F4AD7009B20C9 /* ApplicationState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88FAD2C295F4AD7009B20C9 /* ApplicationState.swift */; };
|
||||
F88FAD32295F5029009B20C9 /* RemoteFileService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88FAD31295F5029009B20C9 /* RemoteFileService.swift */; };
|
||||
F8A93D7E2965FD89001D8331 /* UserProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A93D7D2965FD89001D8331 /* UserProfileView.swift */; };
|
||||
F8A93D802965FED4001D8331 /* AccountService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A93D7F2965FED4001D8331 /* AccountService.swift */; };
|
||||
F8A93D822965FF5D001D8331 /* MastodonClientAuthenticated+Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A93D812965FF5D001D8331 /* MastodonClientAuthenticated+Account.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
|
@ -101,6 +104,9 @@
|
|||
F88FAD29295F43B8009B20C9 /* AccountData+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccountData+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
F88FAD2C295F4AD7009B20C9 /* ApplicationState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationState.swift; sourceTree = "<group>"; };
|
||||
F88FAD31295F5029009B20C9 /* RemoteFileService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteFileService.swift; sourceTree = "<group>"; };
|
||||
F8A93D7D2965FD89001D8331 /* UserProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileView.swift; sourceTree = "<group>"; };
|
||||
F8A93D7F2965FED4001D8331 /* AccountService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountService.swift; sourceTree = "<group>"; };
|
||||
F8A93D812965FF5D001D8331 /* MastodonClientAuthenticated+Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonClientAuthenticated+Account.swift"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
@ -125,6 +131,7 @@
|
|||
F88FAD24295F3FF7009B20C9 /* FederatedFeedView.swift */,
|
||||
F88FAD26295F400E009B20C9 /* NotificationsView.swift */,
|
||||
F866F6A629604629002E8F88 /* SignInView.swift */,
|
||||
F8A93D7D2965FD89001D8331 /* UserProfileView.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
|
@ -134,6 +141,7 @@
|
|||
children = (
|
||||
F8341F8F295C636C009C8EE6 /* UIImage+Exif.swift */,
|
||||
F85D4980296417F700751DF7 /* MastodonClientAuthenticated+Context.swift */,
|
||||
F8A93D812965FF5D001D8331 /* MastodonClientAuthenticated+Account.swift */,
|
||||
F85D49862964334100751DF7 /* String+Date.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
|
@ -243,6 +251,7 @@
|
|||
F88FAD31295F5029009B20C9 /* RemoteFileService.swift */,
|
||||
F85D4970296402DC00751DF7 /* AuthorizationService.swift */,
|
||||
F85D4974296407F100751DF7 /* TimelineService.swift */,
|
||||
F8A93D7F2965FED4001D8331 /* AccountService.swift */,
|
||||
);
|
||||
path = Services;
|
||||
sourceTree = "<group>";
|
||||
|
@ -333,6 +342,7 @@
|
|||
F88FAD21295F3944009B20C9 /* HomeFeedView.swift in Sources */,
|
||||
F88C2475295C37BB0006098B /* CoreDataHandler.swift in Sources */,
|
||||
F88FAD2A295F43B8009B20C9 /* AccountData+CoreDataClass.swift in Sources */,
|
||||
F8A93D822965FF5D001D8331 /* MastodonClientAuthenticated+Account.swift in Sources */,
|
||||
F85D49872964334100751DF7 /* String+Date.swift in Sources */,
|
||||
F8341F92295C63BB009C8EE6 /* ImageStatus.swift in Sources */,
|
||||
F85D498329642FAC00751DF7 /* AttachmentData+Comperable.swift in Sources */,
|
||||
|
@ -344,6 +354,7 @@
|
|||
F800480A2961EA1900E6868A /* AttachmentDataHandler.swift in Sources */,
|
||||
F80048032961850500E6868A /* AttachmentData+CoreDataClass.swift in Sources */,
|
||||
F8341F90295C636C009C8EE6 /* UIImage+Exif.swift in Sources */,
|
||||
F8A93D7E2965FD89001D8331 /* UserProfileView.swift in Sources */,
|
||||
F85D4981296417F700751DF7 /* MastodonClientAuthenticated+Context.swift in Sources */,
|
||||
F88C246E295C37B80006098B /* MainView.swift in Sources */,
|
||||
F88C2478295C37BB0006098B /* Vernissage.xcdatamodeld in Sources */,
|
||||
|
@ -363,6 +374,7 @@
|
|||
F88FAD2D295F4AD7009B20C9 /* ApplicationState.swift in Sources */,
|
||||
F866F6A1296040A8002E8F88 /* ApplicationSettings+CoreDataProperties.swift in Sources */,
|
||||
F866F6AE29606367002E8F88 /* ApplicationViewMode.swift in Sources */,
|
||||
F8A93D802965FED4001D8331 /* AccountService.swift in Sources */,
|
||||
F866F6AA29605AFA002E8F88 /* SceneDelegate.swift in Sources */,
|
||||
F85D4973296406E700751DF7 /* BottomRight.swift in Sources */,
|
||||
);
|
||||
|
|
|
@ -60,10 +60,17 @@ extension CoreDataHandler {
|
|||
public static var preview: CoreDataHandler = {
|
||||
let result = CoreDataHandler(inMemory: true)
|
||||
let viewContext = result.container.viewContext
|
||||
for _ in 0..<10 {
|
||||
let newItem = AccountData(context: viewContext)
|
||||
newItem.id = "123"
|
||||
}
|
||||
|
||||
let statusData = StatusData(context: viewContext)
|
||||
statusData.id = "516272295308651148"
|
||||
statusData.uri = "https://pixelfed.social/p/z428/516272295308651148"
|
||||
statusData.url = URL(string: "https://pixelfed.social/p/z428/516272295308651148")
|
||||
statusData.content = "4: Along the way.<br />\n<a href=\"https://pixelfed.social/discover/tags/outerworld?src=hash\" title=\"#outerworld\" class=\"u-url hashtag\" rel=\"external nofollow noopener\">#outerworld</a> <a href=\"https://pixelfed.social/discover/tags/pixelfed365?src=hash\" title=\"#pixelfed365\" class=\"u-url hashtag\" rel=\"external nofollow noopener\">#pixelfed365</a> <a href=\"https://pixelfed.social/discover/tags/dresden?src=hash\" title=\"#dresden\" class=\"u-url hashtag\" rel=\"external nofollow noopener\">#dresden</a> <a href=\"https://pixelfed.social/discover/tags/photography?src=hash\" title=\"#photography\" class=\"u-url hashtag\" rel=\"external nofollow noopener\">#photography</a> <a href=\"https://pixelfed.social/discover/tags/smartphonephotography?src=hash\" title=\"#smartphonephotography\" class=\"u-url hashtag\" rel=\"external nofollow noopener\">#smartphonephotography</a> <a href=\"https://pixelfed.social/discover/tags/afternoons?src=hash\" title=\"#afternoons\" class=\"u-url hashtag\" rel=\"external nofollow noopener\">#afternoons</a> <a href=\"https://pixelfed.social/discover/tags/grey?src=hash\" title=\"#grey\" class=\"u-url hashtag\" rel=\"external nofollow noopener\">#grey</a>"
|
||||
statusData.reblogsCount = 12
|
||||
statusData.createdAt = "2023-01-04T15:21:47.000Z"
|
||||
statusData.visibility = "public"
|
||||
statusData.applicationName = "web"
|
||||
|
||||
do {
|
||||
try viewContext.save()
|
||||
} catch {
|
||||
|
@ -75,3 +82,20 @@ extension CoreDataHandler {
|
|||
return result
|
||||
}()
|
||||
}
|
||||
|
||||
public struct PreviewData {
|
||||
static func getStatus() -> StatusData {
|
||||
let statusData = StatusData()
|
||||
statusData.id = "516272295308651148"
|
||||
statusData.uri = "https://pixelfed.social/p/z428/516272295308651148"
|
||||
statusData.url = URL(string: "https://pixelfed.social/p/z428/516272295308651148")
|
||||
statusData.content = "4: Along the way.<br />\n<a href=\"https://pixelfed.social/discover/tags/outerworld?src=hash\" title=\"#outerworld\" class=\"u-url hashtag\" rel=\"external nofollow noopener\">#outerworld</a> <a href=\"https://pixelfed.social/discover/tags/pixelfed365?src=hash\" title=\"#pixelfed365\" class=\"u-url hashtag\" rel=\"external nofollow noopener\">#pixelfed365</a> <a href=\"https://pixelfed.social/discover/tags/dresden?src=hash\" title=\"#dresden\" class=\"u-url hashtag\" rel=\"external nofollow noopener\">#dresden</a> <a href=\"https://pixelfed.social/discover/tags/photography?src=hash\" title=\"#photography\" class=\"u-url hashtag\" rel=\"external nofollow noopener\">#photography</a> <a href=\"https://pixelfed.social/discover/tags/smartphonephotography?src=hash\" title=\"#smartphonephotography\" class=\"u-url hashtag\" rel=\"external nofollow noopener\">#smartphonephotography</a> <a href=\"https://pixelfed.social/discover/tags/afternoons?src=hash\" title=\"#afternoons\" class=\"u-url hashtag\" rel=\"external nofollow noopener\">#afternoons</a> <a href=\"https://pixelfed.social/discover/tags/grey?src=hash\" title=\"#grey\" class=\"u-url hashtag\" rel=\"external nofollow noopener\">#grey</a>"
|
||||
statusData.reblogsCount = 12
|
||||
statusData.createdAt = "2023-01-04T15:21:47.000Z"
|
||||
statusData.visibility = "public"
|
||||
statusData.applicationName = "web"
|
||||
|
||||
return statusData
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MastodonSwift
|
||||
|
||||
extension MastodonClientAuthenticated {
|
||||
func getAccount(for accountId: String) async throws -> Account {
|
||||
let request = try Self.request(
|
||||
for: baseURL,
|
||||
target: Mastodon.Account.account(accountId),
|
||||
withBearerToken: token
|
||||
)
|
||||
|
||||
let (data, _) = try await urlSession.data(for: request)
|
||||
return try JSONDecoder().decode(Account.self, from: data)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MastodonSwift
|
||||
|
||||
public class AccountService {
|
||||
public static let shared = AccountService()
|
||||
|
||||
public func getAccount(withId accountId: String, and accountData: AccountData?) async throws -> Account? {
|
||||
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let client = MastodonClient(baseURL: serverUrl).getAuthenticated(token: accessToken)
|
||||
return try await client.getAccount(for: accountId)
|
||||
}
|
||||
}
|
|
@ -18,7 +18,13 @@ struct DetailsView: View {
|
|||
ImagesCarousel(attachments: statusData.attachments())
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
UsernameRow(statusData: statusData)
|
||||
NavigationLink(destination: UserProfileView(
|
||||
accountId: statusData.accountId,
|
||||
accountDisplayName: statusData.accountDisplayName,
|
||||
accountUserName: statusData.accountUsername)
|
||||
.environmentObject(applicationState)) {
|
||||
UsernameRow(statusData: statusData)
|
||||
}
|
||||
|
||||
HTMLFormattedText(statusData.content)
|
||||
.padding(.leading, -4)
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import MastodonSwift
|
||||
|
||||
struct UserProfileView: View {
|
||||
@EnvironmentObject var applicationState: ApplicationState
|
||||
@State public var accountId: String
|
||||
@State public var accountDisplayName: String?
|
||||
@State public var accountUserName: String
|
||||
@State private var account: Account? = nil
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
if let account = self.account {
|
||||
|
||||
HStack(alignment: .center) {
|
||||
AsyncImage(url: account.avatar) { image in
|
||||
image
|
||||
.resizable()
|
||||
.clipShape(Circle())
|
||||
.aspectRatio(contentMode: .fit)
|
||||
} placeholder: {
|
||||
Image(systemName: "person.circle")
|
||||
.resizable()
|
||||
.foregroundColor(Color("MainTextColor"))
|
||||
}
|
||||
.frame(width: 96.0, height: 96.0)
|
||||
|
||||
Spacer()
|
||||
|
||||
VStack(alignment: .center) {
|
||||
Text("\(account.statusesCount)")
|
||||
.font(.title3)
|
||||
Text("Posts")
|
||||
.font(.subheadline)
|
||||
.opacity(0.6)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
VStack(alignment: .center) {
|
||||
Text("\(account.followersCount)")
|
||||
.font(.title3)
|
||||
Text("Followers")
|
||||
.font(.subheadline)
|
||||
.opacity(0.6)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
VStack(alignment: .center) {
|
||||
Text("\(account.followingCount)")
|
||||
.font(.title3)
|
||||
Text("Following")
|
||||
.font(.subheadline)
|
||||
.opacity(0.6)
|
||||
}
|
||||
}
|
||||
|
||||
HStack (alignment: .center) {
|
||||
Text(account.displayName ?? account.username)
|
||||
.foregroundColor(Color("DisplayNameColor"))
|
||||
.font(.footnote)
|
||||
.fontWeight(.bold)
|
||||
Text("@\(account.username)")
|
||||
.foregroundColor(Color("LightGrayColor"))
|
||||
.font(.footnote)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
// Folllow/Unfollow
|
||||
} label: {
|
||||
Text("Follow")
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.tint(.accentColor)
|
||||
|
||||
}
|
||||
|
||||
if let note = account.note {
|
||||
HTMLFormattedText(note, withFontSize: 14, andWidth: Int(UIScreen.main.bounds.width) - 16)
|
||||
.padding(.top, -10)
|
||||
.padding(.leading, -4)
|
||||
}
|
||||
|
||||
Text("Joined \(account.createdAt.toRelative(.isoDateTimeMilliSec))")
|
||||
.foregroundColor(Color("LightGrayColor").opacity(0.5))
|
||||
.font(.footnote)
|
||||
|
||||
Spacer()
|
||||
} else {
|
||||
ProgressView()
|
||||
.progressViewStyle(CircularProgressViewStyle())
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.navigationBarTitle(self.accountDisplayName ?? self.accountUserName)
|
||||
.onAppear {
|
||||
Task {
|
||||
do {
|
||||
if let account = try await AccountService.shared.getAccount(
|
||||
withId: self.accountId,
|
||||
and: self.applicationState.accountData
|
||||
) {
|
||||
self.account = account
|
||||
}
|
||||
} catch {
|
||||
print("Error \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct UserProfileView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
UserProfileView(accountId: "", accountDisplayName: "", accountUserName: "")
|
||||
}
|
||||
}
|
|
@ -21,17 +21,26 @@ struct CommentsSection: View {
|
|||
if let context = context {
|
||||
ForEach(context.descendants, id: \.id) { status in
|
||||
HStack (alignment: .top) {
|
||||
AsyncImage(url: status.account?.avatar) { image in
|
||||
image
|
||||
.resizable()
|
||||
.clipShape(Circle())
|
||||
.aspectRatio(contentMode: .fit)
|
||||
} placeholder: {
|
||||
Image(systemName: "person.circle")
|
||||
.resizable()
|
||||
.foregroundColor(Color("MainTextColor"))
|
||||
|
||||
if let account = status.account {
|
||||
NavigationLink(destination: UserProfileView(
|
||||
accountId: account.id,
|
||||
accountDisplayName: account.displayName,
|
||||
accountUserName: account.username)
|
||||
.environmentObject(applicationState)) {
|
||||
AsyncImage(url: account.avatar) { image in
|
||||
image
|
||||
.resizable()
|
||||
.clipShape(Circle())
|
||||
.aspectRatio(contentMode: .fit)
|
||||
} placeholder: {
|
||||
Image(systemName: "person.circle")
|
||||
.resizable()
|
||||
.foregroundColor(Color("MainTextColor"))
|
||||
}
|
||||
.frame(width: 32.0, height: 32.0)
|
||||
}
|
||||
}
|
||||
.frame(width: 32.0, height: 32.0)
|
||||
|
||||
VStack (alignment: .leading) {
|
||||
HStack (alignment: .top) {
|
||||
|
@ -48,19 +57,10 @@ struct CommentsSection: View {
|
|||
Text(status.createdAt.toRelative(.isoDateTimeMilliSec))
|
||||
.foregroundColor(Color("LightGrayColor").opacity(0.5))
|
||||
.font(.footnote)
|
||||
|
||||
/*
|
||||
Image(systemName: "message")
|
||||
.foregroundColor(Color.accentColor)
|
||||
Image(systemName: "hand.thumbsup")
|
||||
.foregroundColor(Color.accentColor)
|
||||
*/
|
||||
}
|
||||
.padding(.bottom, -10)
|
||||
|
||||
|
||||
|
||||
HTMLFormattedText(status.content, withFontSize: 14, andWidth: contentWidth)
|
||||
.padding(.top, -10)
|
||||
.padding(.leading, -4)
|
||||
|
||||
if status.mediaAttachments.count > 0 {
|
||||
|
|
|
@ -27,7 +27,7 @@ struct InteractionRow: View {
|
|||
// Reboost
|
||||
} label: {
|
||||
HStack(alignment: .center) {
|
||||
Image(systemName: statusData.reblogged ? "arrowshape.turn.up.forward.fill" : "arrowshape.turn.up.forward")
|
||||
Image(systemName: statusData.reblogged ? "paperplane.fill" : "paperplane")
|
||||
Text("\(statusData.reblogsCount)")
|
||||
.font(.caption)
|
||||
}
|
||||
|
@ -69,6 +69,7 @@ struct InteractionRow: View {
|
|||
|
||||
struct InteractionRow_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
InteractionRow(statusData: StatusData())
|
||||
InteractionRow(statusData: PreviewData.getStatus())
|
||||
.previewLayout(.fixed(width: 300, height: 70))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue