diff --git a/Mlem.xcodeproj/project.pbxproj b/Mlem.xcodeproj/project.pbxproj index 881d3f09b..eb934471b 100644 --- a/Mlem.xcodeproj/project.pbxproj +++ b/Mlem.xcodeproj/project.pbxproj @@ -193,6 +193,9 @@ 03AB48552CBC0B8000567FF9 /* AccountAdvancedSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03AB48542CBC0B8000567FF9 /* AccountAdvancedSettingsView.swift */; }; 03AB48572CBC0DFC00567FF9 /* AccountSignInSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03AB48562CBC0DFC00567FF9 /* AccountSignInSettingsView.swift */; }; 03AB48592CBC14CE00567FF9 /* AccountEmailSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03AB48582CBC14CE00567FF9 /* AccountEmailSettingsView.swift */; }; + 03AD0A822CFDBFA0001EF9F7 /* AccountLocalSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03AD0A812CFDBFA0001EF9F7 /* AccountLocalSettingsView.swift */; }; + 03AD0A842CFDC557001EF9F7 /* AccountNicknameFieldView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03AD0A832CFDC557001EF9F7 /* AccountNicknameFieldView.swift */; }; + 03AD09E82CF88007001EF9F7 /* MoreRepliesButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03AD09E72CF88007001EF9F7 /* MoreRepliesButton.swift */; }; 03AF91DD2C1B23E500E56644 /* ImageViewer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03AF91DC2C1B23E500E56644 /* ImageViewer.swift */; }; 03AF91DF2C1B243D00E56644 /* ZoomableContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03AF91DE2C1B243D00E56644 /* ZoomableContainer.swift */; }; 03AF91E12C1B25DE00E56644 /* UIDevice+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03AF91E02C1B25DE00E56644 /* UIDevice+Extensions.swift */; }; @@ -607,6 +610,9 @@ 03AB48542CBC0B8000567FF9 /* AccountAdvancedSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountAdvancedSettingsView.swift; sourceTree = ""; }; 03AB48562CBC0DFC00567FF9 /* AccountSignInSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSignInSettingsView.swift; sourceTree = ""; }; 03AB48582CBC14CE00567FF9 /* AccountEmailSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountEmailSettingsView.swift; sourceTree = ""; }; + 03AD0A812CFDBFA0001EF9F7 /* AccountLocalSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountLocalSettingsView.swift; sourceTree = ""; }; + 03AD0A832CFDC557001EF9F7 /* AccountNicknameFieldView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountNicknameFieldView.swift; sourceTree = ""; }; + 03AD09E72CF88007001EF9F7 /* MoreRepliesButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoreRepliesButton.swift; sourceTree = ""; }; 03AF91DC2C1B23E500E56644 /* ImageViewer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewer.swift; sourceTree = ""; }; 03AF91DE2C1B243D00E56644 /* ZoomableContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZoomableContainer.swift; sourceTree = ""; }; 03AF91E02C1B25DE00E56644 /* UIDevice+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDevice+Extensions.swift"; sourceTree = ""; }; @@ -899,6 +905,8 @@ 03AB48542CBC0B8000567FF9 /* AccountAdvancedSettingsView.swift */, 03AB48562CBC0DFC00567FF9 /* AccountSignInSettingsView.swift */, 03AB48582CBC14CE00567FF9 /* AccountEmailSettingsView.swift */, + 03AD0A812CFDBFA0001EF9F7 /* AccountLocalSettingsView.swift */, + 03AD0A832CFDC557001EF9F7 /* AccountNicknameFieldView.swift */, 03134A572BEC1C46002662CC /* AccountListSettingsView.swift */, 031E2D5A2BEFC9460003BC45 /* ThemeSettingsView.swift */, 03B72B6A2C28A0190023A6C4 /* SubscriptionListSettingsView.swift */, @@ -1166,6 +1174,7 @@ 03E0EF442CA74036002CB66C /* CommentPage.swift */, 03B0EB6E2C87827A00F79FDF /* ExpandedPostView.swift */, 033EF40F2CB9AEF7004D8A3F /* ExpandedPostView+Views.swift */, + 03AD09E72CF88007001EF9F7 /* MoreRepliesButton.swift */, 039F58852C7A810100C61658 /* ExpandedPostView+Logic.swift */, 03E0EF422CA73D7A002CB66C /* PostPage.swift */, ); @@ -2104,6 +2113,7 @@ 03AB48552CBC0B8000567FF9 /* AccountAdvancedSettingsView.swift in Sources */, 039F588A2C7B54FE00C61658 /* GeneralSettingsView.swift in Sources */, 03F967272CE218110081C9A3 /* PersonBanEditorView.swift in Sources */, + 03AD0A822CFDBFA0001EF9F7 /* AccountLocalSettingsView.swift in Sources */, 039F58992C7B697D00C61658 /* Bundle+Extensions.swift in Sources */, 033F84D92C2B61FB002E3EDF /* ToastType.swift in Sources */, 03A82FA12C0D1E8500D01A5C /* ApiClient+Extensions.swift in Sources */, @@ -2143,6 +2153,7 @@ 03C93CF02BEFFB1A00327BFE /* LoginCredentialsView.swift in Sources */, CD332D7C2CA71E6F00A53988 /* GifView.swift in Sources */, CD13CC652C5D2B9D001AF428 /* CircleCroppedImageView.swift in Sources */, + 03AD0A842CFDC557001EF9F7 /* AccountNicknameFieldView.swift in Sources */, CDAA02DD2C81792500D75633 /* SolarizedPalette.swift in Sources */, 035EDF032C2ED0DE00F51144 /* PersonListRowBody.swift in Sources */, 03FD6CB02C9B719100500FD6 /* View+PopupAnchor.swift in Sources */, @@ -2263,6 +2274,7 @@ CD317D4F2BE983ED008F63E2 /* MonochromePalette.swift in Sources */, 03AF91EA2C1CE96600E56644 /* Counter.swift in Sources */, 03B431C22C45BA00001A1EB5 /* MarkdownEditorToolbarView.swift in Sources */, + 03AD09E82CF88007001EF9F7 /* MoreRepliesButton.swift in Sources */, 034B948E2C0937BA00039AF4 /* FancyScrollView.swift in Sources */, CD332D7E2CA7486000A53988 /* String+Extensions.swift in Sources */, CD64A91E2CA62592007CA7E6 /* MediaView.swift in Sources */, diff --git a/Mlem/App/Models/Account/Account.swift b/Mlem/App/Models/Account/Account.swift index 2663e6aa3..633d5588d 100644 --- a/Mlem/App/Models/Account/Account.swift +++ b/Mlem/App/Models/Account/Account.swift @@ -25,6 +25,8 @@ protocol Account: AnyObject, Codable, ActorIdentifiable, Profile1Providing, Hash var nicknameSortKey: String { get } var instanceSortKey: String { get } var isActive: Bool { get } + + func setNickname(_ newValue: String) } // Profile1Providing conformance diff --git a/Mlem/App/Models/Account/GuestAccount.swift b/Mlem/App/Models/Account/GuestAccount.swift index 0b4bc51f5..2d6467658 100644 --- a/Mlem/App/Models/Account/GuestAccount.swift +++ b/Mlem/App/Models/Account/GuestAccount.swift @@ -105,6 +105,11 @@ class GuestAccount: Account { AccountsTracker.main.saveAccounts(ofType: .guest) } } + + func setNickname(_ newValue: String) { + storedNickname = newValue.isEmpty ? nil : newValue + AccountsTracker.main.saveAccounts(ofType: .guest) + } } extension GuestAccount: CacheIdentifiable { diff --git a/Mlem/App/Models/Account/UserAccount.swift b/Mlem/App/Models/Account/UserAccount.swift index 75487ff7e..2ed4e2059 100644 --- a/Mlem/App/Models/Account/UserAccount.swift +++ b/Mlem/App/Models/Account/UserAccount.swift @@ -145,6 +145,11 @@ class UserAccount: Account, CommunityOrPersonStub { guard let host else { return nil } return "@\(name)@\(host)" } + + func setNickname(_ newValue: String) { + storedNickname = newValue.isEmpty ? nil : newValue + AccountsTracker.main.saveAccounts(ofType: .user) + } } private func getKeychainId(actorId: URL) -> String { diff --git a/Mlem/App/Views/Root/Tabs/Settings/AccountLocalSettingsView.swift b/Mlem/App/Views/Root/Tabs/Settings/AccountLocalSettingsView.swift new file mode 100644 index 000000000..3fd6c01d3 --- /dev/null +++ b/Mlem/App/Views/Root/Tabs/Settings/AccountLocalSettingsView.swift @@ -0,0 +1,48 @@ +// +// AccountLocalSettingsView.swift +// Mlem +// +// Created by Sjmarf on 2024-12-02. +// + +import SwiftUI + +struct AccountLocalSettingsView: View { + @Environment(AppState.self) var appState + @Environment(Palette.self) var palette + + @State var isShowingFavoriteDeletionWarning: Bool = false + + var body: some View { + Form { + AccountNicknameFieldView() + if let userSession = AppState.main.firstSession as? UserSession { + Section { + Button("Delete Community Favorites", systemImage: Icons.delete, role: .destructive) { + isShowingFavoriteDeletionWarning = true + } + .disabled(userSession.account.favorites.isEmpty) + .tint(palette.warning) + .confirmationDialog( + "Delete Community Favorites", + isPresented: $isShowingFavoriteDeletionWarning + ) { + Button("Delete", role: .destructive) { + for community in userSession.subscriptions.favorites { + community.updateFavorite(false) + } + } + } message: { + Text("Are you sure you want to delete all community favorites for this account? This cannot be undone.") + } + } footer: { + if userSession.account.favorites.isEmpty { + Text("This account has no favorite communities.") + } else { + Text("This account has \(userSession.account.favorites.count) favorite communities.") + } + } + } + } + } +} diff --git a/Mlem/App/Views/Root/Tabs/Settings/AccountNicknameFieldView.swift b/Mlem/App/Views/Root/Tabs/Settings/AccountNicknameFieldView.swift new file mode 100644 index 000000000..100a826b9 --- /dev/null +++ b/Mlem/App/Views/Root/Tabs/Settings/AccountNicknameFieldView.swift @@ -0,0 +1,33 @@ +// +// AccountNicknameFieldView.swift +// Mlem +// +// Created by Sjmarf on 2024-12-02. +// + +import SwiftUI + +struct AccountNicknameFieldView: View { + @Environment(AppState.self) var appState + + @State var nickname: String + + init() { + self.nickname = AppState.main.firstAccount.storedNickname ?? "" + } + + var body: some View { + Section("Nickname") { + TextField( + "Nickname", + text: $nickname, + prompt: Text(appState.firstAccount.name) + ) + .onSubmit { + AppState.main.firstAccount.setNickname(nickname) + } + } footer: { + Text("The name shown in the account switcher.") + } + } +} diff --git a/Mlem/App/Views/Root/Tabs/Settings/AccountSettingsView.swift b/Mlem/App/Views/Root/Tabs/Settings/AccountSettingsView.swift index a91a29b0a..3bc4f0a69 100644 --- a/Mlem/App/Views/Root/Tabs/Settings/AccountSettingsView.swift +++ b/Mlem/App/Views/Root/Tabs/Settings/AccountSettingsView.swift @@ -60,14 +60,28 @@ struct AccountSettingsView: View { ) .tint(palette.negative) } + Section { + NavigationLink( + "Local Options", + systemImage: "iphone", + destination: .settings(.accountLocal) + ) + .tint(palette.colorfulAccent(2)) + } footer: { + Text("These options are stored locally in Mlem and not on your Lemmy account.") + } + } else { + AccountNicknameFieldView() } Group { Section { - Button(signOutLabel) { + Button { appState.firstAccount.signOut() + } label: { + Text(signOutLabel) + .frame(maxWidth: .infinity) } - .frame(maxWidth: .infinity) .confirmationDialog(signOutPrompt, isPresented: $showingSignOutConfirmation) { Button(signOutLabel, role: .destructive) { appState.firstAccount.signOut() @@ -79,10 +93,12 @@ struct AccountSettingsView: View { if let account = appState.firstAccount as? UserAccount { Section { - Button("Delete Account", role: .destructive) { + Button(role: .destructive) { navigation.openSheet(.deleteAccount(account)) + } label: { + Text("Delete Account") + .frame(maxWidth: .infinity) } - .frame(maxWidth: .infinity) } } } diff --git a/Mlem/App/Views/Shared/ExpandedPost/CommentPage.swift b/Mlem/App/Views/Shared/ExpandedPost/CommentPage.swift index 9c7f4d515..9f79ae0dd 100644 --- a/Mlem/App/Views/Shared/ExpandedPost/CommentPage.swift +++ b/Mlem/App/Views/Shared/ExpandedPost/CommentPage.swift @@ -14,14 +14,16 @@ struct CommentPage: View { @Environment(\.dismiss) var dismiss let comment: AnyComment + let initialComments: [Comment2]? @State var tracker: CommentTreeTracker? let showViewPostButton: Bool @State var post: Post3? - init(comment: AnyComment, showViewPostButton: Bool = false) { + init(comment: AnyComment, initialComments: [Comment2]?, showViewPostButton: Bool = false) { self.comment = comment self.showViewPostButton = showViewPostButton + self.initialComments = initialComments if let comment = comment.wrappedValue as? any Comment { self._tracker = .init(wrappedValue: .init(root: .comment(comment, parentCount: 1))) } else { @@ -85,9 +87,14 @@ struct CommentPage: View { if let comment = model.wrappedValue as? any Comment { if let tracker { tracker.root = .comment(comment, parentCount: 1) - tracker.loadingState = .idle Task { - await tracker.load() + if let initialComments { + await tracker.insertAdditionalComments(comments: initialComments) + tracker.loadingState = .done + } else { + tracker.loadingState = .idle + await tracker.load() + } } } else { tracker = .init(root: .comment(comment, parentCount: 1)) @@ -106,6 +113,13 @@ struct CommentPage: View { } } } + .onAppear { + if comment.isUpgraded, let tracker { + Task { + await tracker.load() + } + } + } } var currentDepth: Int { diff --git a/Mlem/App/Views/Shared/ExpandedPost/CommentTreeTracker.swift b/Mlem/App/Views/Shared/ExpandedPost/CommentTreeTracker.swift index 2da5999e8..84afa37d9 100644 --- a/Mlem/App/Views/Shared/ExpandedPost/CommentTreeTracker.swift +++ b/Mlem/App/Views/Shared/ExpandedPost/CommentTreeTracker.swift @@ -115,7 +115,7 @@ class CommentTreeTracker: Hashable { sort: sort, includedParentCount: parentCount, page: page, - maxDepth: Settings.main.maxCommentDepth, + maxDepth: min(8, Settings.main.maxCommentDepth) + parentCount, limit: 999 ) } @@ -155,10 +155,15 @@ class CommentTreeTracker: Hashable { } @MainActor - private func buildCommentTree(comments newComments: [Comment2]) async { - var output: [CommentWrapper] = [] + func insertAdditionalComments(comments newComments: [Comment2]) async { + await buildCommentTree(comments: newComments, clear: false) + } + + @MainActor + private func buildCommentTree(comments newComments: [Comment2], clear: Bool = true) async { + var output: [CommentWrapper] = clear ? [] : comments var commentsKeyedById: [Int: CommentWrapper] = [:] - var commentsKeyedByActorId: [URL: CommentWrapper] = [:] + var commentsKeyedByActorId: [URL: CommentWrapper] = clear ? [:] : commentsKeyedByActorId // From 0.19.0 onwards, a comment's parent is guaranteed to precede it in the array. // @@ -182,6 +187,10 @@ class CommentTreeTracker: Hashable { } for comment in sortedComments { + if commentsKeyedByActorId.keys.contains(comment.actorId) { + commentsKeyedById[comment.id] = commentsKeyedByActorId[comment.actorId] + continue + } let wrapper: CommentWrapper = .init(comment) commentsKeyedById[comment.id] = wrapper commentsKeyedByActorId[comment.actorId] = wrapper diff --git a/Mlem/App/Views/Shared/ExpandedPost/ExpandedPostView+Views.swift b/Mlem/App/Views/Shared/ExpandedPost/ExpandedPostView+Views.swift index c069c84a5..eff550a40 100644 --- a/Mlem/App/Views/Shared/ExpandedPost/ExpandedPostView+Views.swift +++ b/Mlem/App/Views/Shared/ExpandedPost/ExpandedPostView+Views.swift @@ -43,26 +43,7 @@ extension ExpandedPostView { .padding(.leading, CGFloat(comment.depth - tracker.proposedDepthOffset) * 10) .id(comment.actorId) case let .unloadedComments(comment, _): - Button { - navigation.push(.comment(comment, showViewPostButton: false)) - } label: { - HStack { - CommentBarView(depth: comment.depth + 1) - HStack { - Text("More Replies") - Image(systemName: Icons.forward) - } - .frame(maxWidth: .infinity) - .padding(.vertical, 8) - .foregroundStyle(palette.accent) - } - .background( - palette.secondaryGroupedBackground, - in: .rect(cornerRadius: Constants.main.standardSpacing) - ) - } - .padding(.leading, CGFloat(comment.depth + 1 - tracker.proposedDepthOffset) * 10) - .buttonStyle(.plain) + MoreRepliesButton(tracker: tracker, comment: comment) } } .padding(.horizontal, Constants.main.standardSpacing) diff --git a/Mlem/App/Views/Shared/ExpandedPost/ExpandedPostView.swift b/Mlem/App/Views/Shared/ExpandedPost/ExpandedPostView.swift index 9d76379cc..e9e7d68c1 100644 --- a/Mlem/App/Views/Shared/ExpandedPost/ExpandedPostView.swift +++ b/Mlem/App/Views/Shared/ExpandedPost/ExpandedPostView.swift @@ -67,7 +67,6 @@ struct ExpandedPostView: View { .task(id: tracker == nil) { if let tracker, post.api == appState.firstApi, tracker.loadingState == .idle { post.markRead() - await tracker.load() } } } else { diff --git a/Mlem/App/Views/Shared/ExpandedPost/MoreRepliesButton.swift b/Mlem/App/Views/Shared/ExpandedPost/MoreRepliesButton.swift new file mode 100644 index 000000000..340815a16 --- /dev/null +++ b/Mlem/App/Views/Shared/ExpandedPost/MoreRepliesButton.swift @@ -0,0 +1,85 @@ +// +// MoreRepliesButton.swift +// Mlem +// +// Created by Sjmarf on 2024-11-28. +// + +import SwiftUI + +struct MoreRepliesButton: View { + @Environment(NavigationLayer.self) var navigation + @Environment(Palette.self) var palette + + let tracker: CommentTreeTracker + let comment: CommentWrapper + + @State var isLoading: Bool = false + + var body: some View { + Button { + isLoading = true + Task { @MainActor in + do { + try await loadComments() + } catch { + handleError(error) + } + isLoading = false + } + } label: { + HStack { + CommentBarView(depth: comment.depth + 1) + HStack { + Text("More Replies") + Image(systemName: Icons.forward) + } + .frame(maxWidth: .infinity) + .padding(.vertical, 8) + .opacity(isLoading ? 0 : 1) + .overlay { + if isLoading { + ProgressView() + } + } + .foregroundStyle(palette.accent) + } + .background( + palette.secondaryGroupedBackground, + in: .rect(cornerRadius: Constants.main.standardSpacing) + ) + .paletteBorder(cornerRadius: Constants.main.standardSpacing) + } + .padding(.leading, CGFloat(comment.depth + 1 - tracker.proposedDepthOffset) * 10) + .buttonStyle(.plain) + } + + func loadComments() async throws { + let comments = try await comment.getChildren( + sort: tracker.sort, + includedParentCount: 0, + page: 1, + maxDepth: Settings.main.maxCommentDepth, + limit: 999 + ) + + // TODO: 0.18.0 deprecation: instead of using `max(by: )`, just take the last item of the array + // (from 0.19.0 onwards the last item of the array will be the deepest) + + guard let maxDepth = comments.max(by: { $0.depth < $1.depth })?.depth else { + return + } + // Do we want this threshold to change depending on screen size? Could be tricky if we load comments + // and then the user makes the window less wide (e.g. on iPad), in which case we'd need to hide + // the comments that exceed the new maximum width. + if maxDepth > 12 { + var comments = comments + if let parent = comment.parent { + comments.prepend(parent.comment2) + } + navigation.push(.comment(comment, comments: comments, showViewPostButton: false)) + } else { + await tracker.insertAdditionalComments(comments: comments) + } + } +} diff --git a/Mlem/App/Views/Shared/ExpandedPost/PostPage.swift b/Mlem/App/Views/Shared/ExpandedPost/PostPage.swift index 56ec20a8a..ff624d4ac 100644 --- a/Mlem/App/Views/Shared/ExpandedPost/PostPage.swift +++ b/Mlem/App/Views/Shared/ExpandedPost/PostPage.swift @@ -63,5 +63,12 @@ struct PostPage: View { } } .background(palette.groupedBackground) + .onAppear { + if post.isUpgraded, let tracker { + Task { + await tracker.load(ensuringPresenceOf: scrollTargetedComment) + } + } + } } } diff --git a/Mlem/App/Views/Shared/Navigation/NavigationPage+View.swift b/Mlem/App/Views/Shared/Navigation/NavigationPage+View.swift index eedcc3d7c..ff43fc2af 100644 --- a/Mlem/App/Views/Shared/Navigation/NavigationPage+View.swift +++ b/Mlem/App/Views/Shared/Navigation/NavigationPage+View.swift @@ -59,8 +59,8 @@ extension NavigationPage { PostPage(post: post, scrollTargetedComment: scrollTargetedComment?.wrappedValue) .environment(\.communityContext, communityContext?.wrappedValue) .navigationTransition_(sourceID: "post\(post.wrappedValue.actorId)", in: navigationNamespace) - case let .comment(comment, showViewPostButton): - CommentPage(comment: comment, showViewPostButton: showViewPostButton) + case let .comment(comment, comments: comments, showViewPostButton): + CommentPage(comment: comment, initialComments: comments, showViewPostButton: showViewPostButton) case let .person(person): PersonView(person: person) case let .createComment(context, commentTreeTracker): diff --git a/Mlem/App/Views/Shared/Navigation/NavigationPage.swift b/Mlem/App/Views/Shared/Navigation/NavigationPage.swift index 684ac697e..9216f3a61 100644 --- a/Mlem/App/Views/Shared/Navigation/NavigationPage.swift +++ b/Mlem/App/Views/Shared/Navigation/NavigationPage.swift @@ -21,7 +21,7 @@ enum NavigationPage: Hashable { communityContext: HashWrapper? = nil, navigationNamespace: Namespace.ID? = nil ) - case comment(_ comment: AnyComment, showViewPostButton: Bool) + case comment(_ comment: AnyComment, comments: [Comment2]?, showViewPostButton: Bool) case community(_ community: AnyCommunity) case person(_ person: AnyPerson) case instance(_ instance: InstanceHashWrapper) @@ -74,8 +74,12 @@ enum NavigationPage: Hashable { } } - static func comment(_ comment: any CommentStubProviding, showViewPostButton: Bool = true) -> NavigationPage { - Self.comment(.init(comment), showViewPostButton: showViewPostButton) + static func comment( + _ comment: any CommentStubProviding, + comments: [Comment2]? = nil, + showViewPostButton: Bool = true + ) -> NavigationPage { + Self.comment(.init(comment), comments: comments, showViewPostButton: showViewPostButton) } static func person(_ person: any PersonStubProviding) -> NavigationPage { diff --git a/Mlem/App/Views/Shared/Navigation/SettingsPage.swift b/Mlem/App/Views/Shared/Navigation/SettingsPage.swift index f1c92dc62..f36a37aeb 100644 --- a/Mlem/App/Views/Shared/Navigation/SettingsPage.swift +++ b/Mlem/App/Views/Shared/Navigation/SettingsPage.swift @@ -11,7 +11,7 @@ import SwiftUI enum SettingsPage: Hashable { case root case accounts, account - case accountGeneral, accountAdvanced, accountSignIn, accountChangeEmail + case accountGeneral, accountAdvanced, accountSignIn, accountChangeEmail, accountLocal case general, links, sorting case importExportSettings case theme, icon @@ -37,6 +37,8 @@ enum SettingsPage: Hashable { AccountAdvancedSettingsView() case .accountChangeEmail: AccountEmailSettingsView() + case .accountLocal: + AccountLocalSettingsView() case .accounts: AccountListSettingsView() case .general: diff --git a/Mlem/Localizable.xcstrings b/Mlem/Localizable.xcstrings index 16c8044d1..22785d3e4 100644 --- a/Mlem/Localizable.xcstrings +++ b/Mlem/Localizable.xcstrings @@ -198,6 +198,16 @@ }, "Apply to All Interaction Bars" : { + }, + "Are you sure you want to delete all community favorites for this account? This cannot be undone." : { + "localizations" : { + "en-GB" : { + "stringUnit" : { + "state" : "translated", + "value" : "Are you sure you want to delete all community favourites for this account? This cannot be undone." + } + } + } }, "Ask to confirm every time" : { @@ -455,6 +465,16 @@ }, "Delete Account" : { + }, + "Delete Community Favorites" : { + "localizations" : { + "en-GB" : { + "stringUnit" : { + "state" : "translated", + "value" : "Delete Community Favourites" + } + } + } }, "Delete posts and comments" : { @@ -823,6 +843,9 @@ }, "Local" : { + }, + "Local Options" : { + }, "Lock" : { @@ -922,6 +945,9 @@ }, "Next" : { + }, + "Nickname" : { + }, "No" : { @@ -1451,6 +1477,9 @@ } } } + }, + "The name shown in the account switcher." : { + }, "The number of child comments that are shown in a chain before the \"More Replies\" button is shown." : { @@ -1482,6 +1511,59 @@ }, "There were no recorded incidents today." : { + }, + "These options are stored locally in Mlem and not on your Lemmy account." : { + + }, + "This account has %lld favorite communities." : { + "localizations" : { + "en" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "This account has %lld favorite community." + } + }, + "other" : { + "stringUnit" : { + "state" : "new", + "value" : "This account has %lld favorite communities." + } + } + } + } + }, + "en-GB" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "This account has %lld favourite community." + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "This account has %lld favourite communities." + } + } + } + } + } + } + }, + "This account has no favorite communities." : { + "localizations" : { + "en-GB" : { + "stringUnit" : { + "state" : "translated", + "value" : "This account has no favourite communities." + } + } + } }, "This community likely contains graphic or explicit content." : {