Add user profile

This commit is contained in:
Marcin Czachursk 2023-01-04 20:56:26 +01:00
parent 8bb6b1d985
commit 3c2ee8c592
8 changed files with 237 additions and 27 deletions

View File

@ -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 */,
);

View File

@ -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
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)

View File

@ -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: "")
}
}

View File

@ -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 {

View File

@ -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))
}
}