Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Edit Profile from "Profile" tab #866

Merged
merged 11 commits into from
Jan 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions Mlem/API/Models/Person/APIPerson.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import Foundation

// lemmy_db_schema::source::person::PersonSafe
struct APIPerson: Decodable, Identifiable, Hashable {
struct APIPerson: Decodable, Identifiable, Hashable, Equatable {
let id: Int
let name: String
var displayName: String?
Expand All @@ -29,12 +29,6 @@ struct APIPerson: Decodable, Identifiable, Hashable {
let instanceId: Int
}

extension APIPerson: Equatable {
static func == (lhs: APIPerson, rhs: APIPerson) -> Bool {
lhs.actorId == rhs.actorId
}
}

extension APIPerson {
var avatarUrl: URL? { LemmyURL(string: avatar)?.url }
var bannerUrl: URL? { LemmyURL(string: banner)?.url }
Expand Down
32 changes: 12 additions & 20 deletions Mlem/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,6 @@ struct ContentView: View {

var accessibilityFont: Bool { UIApplication.shared.preferredContentSizeCategory.isAccessibilityCategory }

var myUser: UserModel? {
if let person = siteInformation.myUserInfo?.localUserView.person {
return UserModel(from: person)
} else {
return nil
}
}

var body: some View {
FancyTabBar(selection: $tabSelection, navigationSelection: $tabNavigation, dragUpGestureCallback: showAccountSwitcherDragCallback) {
Group {
Expand All @@ -75,19 +67,19 @@ struct ContentView: View {
}
}

ProfileView(user: myUser)
.fancyTabItem(tag: TabSelection.profile) {
FancyTabBarLabel(
tag: TabSelection.profile,
customText: appState.tabDisplayName,
symbolConfiguration: .init(
symbol: FancyTabBarLabel.SymbolConfiguration.profile.symbol,
activeSymbol: FancyTabBarLabel.SymbolConfiguration.profile.activeSymbol,
remoteSymbolUrl: appState.profileTabRemoteSymbolUrl
)
ProfileView()
.fancyTabItem(tag: TabSelection.profile) {
FancyTabBarLabel(
tag: TabSelection.profile,
customText: appState.tabDisplayName,
symbolConfiguration: .init(
symbol: FancyTabBarLabel.SymbolConfiguration.profile.symbol,
activeSymbol: FancyTabBarLabel.SymbolConfiguration.profile.activeSymbol,
remoteSymbolUrl: appState.profileTabRemoteSymbolUrl
)
.simultaneousGesture(accountSwitchLongPress)
}
)
.simultaneousGesture(accountSwitchLongPress)
}

SearchRoot()
.fancyTabItem(tag: TabSelection.search) {
Expand Down
3 changes: 2 additions & 1 deletion Mlem/Models/Content/Instance/InstanceModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ struct InstanceModel {

mutating func update(with response: SiteResponse) {
self.administrators = response.admins.map {
var user = UserModel(from: $0, usesExternalData: true)
var user = UserModel(from: $0)
user.usesExternalData = true
user.isAdmin = true
return user
}
Expand Down
82 changes: 49 additions & 33 deletions Mlem/Models/Content/User/UserModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,37 +17,37 @@ struct UserModel {
@Dependency(\.notifier) var notifier

@available(*, deprecated, message: "Use attributes of the UserModel directly instead.")
var person: APIPerson
var person: APIPerson!

// Ids
let userId: Int
let instanceId: Int
let matrixUserId: String?
var userId: Int!
var instanceId: Int!
var matrixUserId: String?

// Text
let name: String
let displayName: String
let bio: String?
var name: String!
var displayName: String!
var bio: String?

// Images
let avatar: URL?
let banner: URL?
var avatar: URL?
var banner: URL?

// State
let banned: Bool
let local: Bool
let deleted: Bool
let isBot: Bool
var blocked: Bool
var banned: Bool!
var local: Bool!
var deleted: Bool!
var isBot: Bool!
var blocked: Bool!

// Dates
let creationDate: Date
let updatedDate: Date?
let banExpirationDate: Date?
var creationDate: Date!
var updatedDate: Date?
var banExpirationDate: Date?

// URLs
let profileUrl: URL
let sharedInboxUrl: URL?
var profileUrl: URL!
var sharedInboxUrl: URL?

// From APIPersonView
var isAdmin: Bool?
Expand All @@ -73,32 +73,41 @@ struct UserModel {
/// Creates a UserModel from an GetPersonDetailsResponse
/// - Parameter response: GetPersonDetailsResponse to create a UserModel representation of
init(from response: GetPersonDetailsResponse) {
self.init(from: response.personView)
self.site = response.site
self.moderatedCommunities = response.moderates.map { CommunityModel(from: $0.community) }
self.update(with: response)
}

/// Creates a UserModel from an APIPersonView
/// - Parameter apiPersonView: APIPersonView to create a UserModel representation of
init(from personView: APIPersonView, usesExternalData: Bool = false) {
self.init(from: personView.person)

init(from personView: APIPersonView) {
self.update(with: personView)
}

/// Creates a UserModel from an APIPerson. Note that using this initialiser nullifies count values, since
/// those are only accessable from APIPersonView.
/// - Parameter apiPerson: APIPerson to create a UserModel representation of
init(from person: APIPerson) {
update(with: person)
}

mutating func update(with response: GetPersonDetailsResponse) {
self.moderatedCommunities = response.moderates.map { CommunityModel(from: $0.community) }
self.update(with: response.personView)
}

mutating func update(with personView: APIPersonView) {
self.postCount = personView.counts.postCount
self.commentCount = personView.counts.commentCount

self.usesExternalData = usesExternalData

// TODO: 0.18 Deprecation
@Dependency(\.siteInformation) var siteInformation
if (siteInformation.version ?? .infinity) > .init("0.19.0") {
self.isAdmin = personView.isAdmin
}

self.update(with: personView.person)
}

/// Creates a UserModel from an APIPerson. Note that using this initialiser nullifies count values, since
/// those are only accessable from APIPersonView.
/// - Parameter apiPerson: APIPerson to create a UserModel representation of
init(from person: APIPerson) {
mutating func update(with person: APIPerson) {
self.person = person

self.userId = person.id
Expand Down Expand Up @@ -128,7 +137,9 @@ struct UserModel {

// Annoyingly, PersonView doesn't include whether the user is blocked so we can't
// actually determine this without making extra requests...
self.blocked = false
if self.blocked == nil {
self.blocked = false
}
}

// Once we've done other model types we should stop this from relying on API types
Expand Down Expand Up @@ -186,7 +197,7 @@ struct UserModel {

var fullyQualifiedUsername: String? {
if let host = self.profileUrl.host() {
return "\(name)@\(host)"
return "\(name!)@\(host)"
}
return nil
}
Expand Down Expand Up @@ -221,5 +232,10 @@ extension UserModel: Hashable {
hasher.combine(blocked)
hasher.combine(postCount)
hasher.combine(commentCount)
hasher.combine(displayName)
hasher.combine(bio)
hasher.combine(avatar)
hasher.combine(banner)
hasher.combine(matrixUserId)
}
}
46 changes: 42 additions & 4 deletions Mlem/Views/Tabs/Profile/Profile View.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,60 @@
//

import SwiftUI
import Dependencies

struct ProfileView: View {
// appstorage
@Dependency(\.siteInformation) var siteInformation
@AppStorage("shouldShowUserHeaders") var shouldShowUserHeaders: Bool = true

let user: UserModel?

@StateObject private var profileTabNavigation: AnyNavigationPath<AppRoute> = .init()
@StateObject private var editorSheetNavigation: AnyNavigationPath<AppRoute> = .init()

@StateObject private var navigation: Navigation = .init()
@StateObject private var sheetNavigation: Navigation = .init()

@State var isPresentingAccountSwitcher: Bool = false
@State var isPresentingProfileEditor: Bool = false

var body: some View {
ScrollViewReader { proxy in
NavigationStack(path: $profileTabNavigation.path) {
if let user {
UserView(user: user)
if let person = siteInformation.myUserInfo?.localUserView.person {
UserView(user: UserModel(from: person))
.handleLemmyViews()
.environmentObject(profileTabNavigation)
.tabBarNavigationEnabled(.profile, navigation)
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Button("Switch Account", systemImage: Icons.switchUser) {
isPresentingAccountSwitcher = true
}
}
// TODO: 0.17 deprecation
if (siteInformation.version ?? .infinity) >= .init("0.18.0") {
ToolbarItem(placement: .secondaryAction) {
Button("Edit", systemImage: Icons.edit) {
isPresentingProfileEditor = true
}
}
}
}
.sheet(isPresented: $isPresentingAccountSwitcher) {
Form {
AccountListView()
}
}
.sheet(isPresented: $isPresentingProfileEditor) {
NavigationStack(path: $editorSheetNavigation.path) {
ProfileSettingsView(showCloseButton: true)
.handleLemmyViews()
.environmentObject(editorSheetNavigation)
}
.handleLemmyLinkResolution(navigationPath: .constant(editorSheetNavigation))
.environment(\.navigationPathWithRoutes, $editorSheetNavigation.path)
.environment(\.navigation, sheetNavigation)
}
} else {
LoadingView(whatIsLoading: .profile)
.fancyTabScrollCompatible()
Expand Down
2 changes: 1 addition & 1 deletion Mlem/Views/Tabs/Profile/UserFeedView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ struct UserFeedView: View {
switch selectedTab {
case .communities:
Label(
"\(user.displayName) moderates \(communityTracker.items.count) communities.",
"\(user.displayName) moderates ^[\(communityTracker.items.count) communities](inflect: true).",
systemImage: Icons.moderationFill
)
.foregroundStyle(.secondary)
Expand Down
40 changes: 12 additions & 28 deletions Mlem/Views/Tabs/Profile/UserView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import Dependencies
import SwiftUI

// swiftlint:disable type_body_length
struct UserView: View {
@Dependency(\.apiClient) var apiClient
@Dependency(\.errorHandler) var errorHandler
Expand Down Expand Up @@ -151,25 +150,10 @@ struct UserView: View {
confirmationMenuFunction: confirmationMenuFunction
)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
ToolbarItemGroup(placement: .secondaryAction) {
let functions = user.menuFunctions { user = $0 }
if functions.count == 1, let first = functions.first {
MenuButton(menuFunction: first, confirmDestructive: confirmDestructive)
} else {
Menu {
ForEach(functions) { item in
MenuButton(menuFunction: item, confirmDestructive: confirmDestructive)
}
} label: {
Label("Menu", systemImage: Icons.menuCircle)
}
}
}
if isOwnProfile {
ToolbarItem(placement: .topBarLeading) {
Button("Switch Account", systemImage: Icons.switchUser) {
isPresentingAccountSwitcher = true
}
ForEach(functions) { item in
MenuButton(menuFunction: item, confirmDestructive: confirmDestructive)
}
}
}
Expand All @@ -186,9 +170,16 @@ struct UserView: View {
}
}
.refreshable {
await Task {
Task {
await tryReloadUser()
}.value
}
}
.onChange(of: siteInformation.myUserInfo?.localUserView.person) { newValue in
if isOwnProfile {
if let newValue {
self.user.update(with: newValue)
}
}
}
.hoistNavigation {
if navigationPath.isEmpty {
Expand All @@ -211,11 +202,6 @@ struct UserView: View {
.navigationBarColor()
.navigationTitle(user.displayName)
.navigationBarTitleDisplayMode(.inline)
.sheet(isPresented: $isPresentingAccountSwitcher) {
Form {
AccountListView()
}
}
}

var flairs: some View {
Expand Down Expand Up @@ -287,5 +273,3 @@ struct UserView: View {
.padding(.horizontal, AppConstants.postAndCommentSpacing)
}
}

// swiftlint:enable type_body_length
2 changes: 1 addition & 1 deletion Mlem/Views/Tabs/Search/Results/UserResultView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ struct UserResultView: View {

var title: String {
if user.blocked {
return "\(user.displayName) ∙ Blocked"
return "\(user.displayName!) ∙ Blocked"
} else {
return user.displayName
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,5 +115,6 @@ struct MatrixLinkView: View {
}
}
.hoistNavigation()
.interactiveDismissDisabled(hasEdited != .unedited)
}
}
Loading
Loading