From 6d6999d2629844cfe641fcfd7166137340c46fb3 Mon Sep 17 00:00:00 2001 From: Sjmarf <78750526+Sjmarf@users.noreply.github.com> Date: Sun, 21 Jan 2024 16:11:40 +0000 Subject: [PATCH 01/10] Update AccountSwitcherSettingsView.swift --- .../Views/Account/AccountSwitcherSettingsView.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Mlem/Views/Tabs/Settings/Components/Views/Account/AccountSwitcherSettingsView.swift b/Mlem/Views/Tabs/Settings/Components/Views/Account/AccountSwitcherSettingsView.swift index 038e68243..086c3983d 100644 --- a/Mlem/Views/Tabs/Settings/Components/Views/Account/AccountSwitcherSettingsView.swift +++ b/Mlem/Views/Tabs/Settings/Components/Views/Account/AccountSwitcherSettingsView.swift @@ -31,6 +31,10 @@ struct AccountSwitcherSettingsView: View { .listRowBackground(Color(.systemGroupedBackground)) } AccountListView() + NavigationLink { EmptyView() } label: { + Label("Quick Switcher", systemImage: "platter.filled.bottom.iphone") + .labelStyle(SquircleLabelStyle(color: .teal)) + } } .fancyTabScrollCompatible() } From 47a63438d83cedd1deb9b359815e660eeff2ab4c Mon Sep 17 00:00:00 2001 From: Sjmarf <78750526+Sjmarf@users.noreply.github.com> Date: Sun, 21 Jan 2024 22:54:00 +0000 Subject: [PATCH 02/10] Update --- Mlem.xcodeproj/project.pbxproj | 18 +- Mlem/App State.swift | 4 +- Mlem/ContentView.swift | 6 +- Mlem/Enums/Settings/ProfileTabLabel.swift | 2 +- .../View+HandleLemmyLinks.swift | 4 + Mlem/Icons.swift | 1 - Mlem/Models/Saved Account.swift | 2 +- .../Destination Values/SettingsValues.swift | 2 + .../Settings/Components/Settings Item.swift | 46 +++-- .../Account/AccountGeneralSettingsView.swift | 2 + .../Views/Account/AccountSettingsView.swift | 19 +- .../Account/AccountSwitcherSettingsView.swift | 42 ---- .../Account/LocalAccountSettingsView.swift | 106 ++++++++++ .../AccountSwitcherSettingsView.swift | 145 ++++++++++++++ .../QuickSwitcherSettingsView.swift | 188 ++++++++++++++++++ .../Appearance/AppearanceSettingsView.swift | 2 +- .../TabBar/TabBarSettingsView.swift | 51 +---- .../Views/General/GeneralSettingsView.swift | 44 ---- 18 files changed, 516 insertions(+), 168 deletions(-) delete mode 100644 Mlem/Views/Tabs/Settings/Components/Views/Account/AccountSwitcherSettingsView.swift create mode 100644 Mlem/Views/Tabs/Settings/Components/Views/Account/LocalAccountSettingsView.swift create mode 100644 Mlem/Views/Tabs/Settings/Components/Views/AccountList/AccountSwitcherSettingsView.swift create mode 100644 Mlem/Views/Tabs/Settings/Components/Views/AccountList/QuickSwitcherSettingsView.swift diff --git a/Mlem.xcodeproj/project.pbxproj b/Mlem.xcodeproj/project.pbxproj index 8b2aaff2a..6ed2954bf 100644 --- a/Mlem.xcodeproj/project.pbxproj +++ b/Mlem.xcodeproj/project.pbxproj @@ -40,6 +40,8 @@ 031BF9552AB25AFB00F4517F /* SiteVersionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 031BF9542AB25AFB00F4517F /* SiteVersionTests.swift */; }; 032109472AA7C3FC00912DFC /* CommunityLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032109462AA7C3FC00912DFC /* CommunityLabelView.swift */; }; 032109492AA7C41800912DFC /* AvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032109482AA7C41800912DFC /* AvatarView.swift */; }; + 032C1E042B5D7DAC00FB4F23 /* QuickSwitcherSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032C1E032B5D7DAC00FB4F23 /* QuickSwitcherSettingsView.swift */; }; + 032C1E062B5DBDB100FB4F23 /* LocalAccountSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032C1E052B5DBDB100FB4F23 /* LocalAccountSettingsView.swift */; }; 032DD2FD2AC3594B00F1B33D /* LinkAttatchmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032DD2FC2AC3594B00F1B33D /* LinkAttatchmentView.swift */; }; 034C724F2A82B61200B8A4B8 /* LayoutWidgetTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 034C724E2A82B61200B8A4B8 /* LayoutWidgetTracker.swift */; }; 035EB0CA2A8687C200227859 /* JumpButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035EB0C92A8687C200227859 /* JumpButtonView.swift */; }; @@ -580,6 +582,8 @@ 031BF9542AB25AFB00F4517F /* SiteVersionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteVersionTests.swift; sourceTree = ""; }; 032109462AA7C3FC00912DFC /* CommunityLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommunityLabelView.swift; sourceTree = ""; }; 032109482AA7C41800912DFC /* AvatarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarView.swift; sourceTree = ""; }; + 032C1E032B5D7DAC00FB4F23 /* QuickSwitcherSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickSwitcherSettingsView.swift; sourceTree = ""; }; + 032C1E052B5DBDB100FB4F23 /* LocalAccountSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalAccountSettingsView.swift; sourceTree = ""; }; 032DD2FC2AC3594B00F1B33D /* LinkAttatchmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkAttatchmentView.swift; sourceTree = ""; }; 034C724E2A82B61200B8A4B8 /* LayoutWidgetTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutWidgetTracker.swift; sourceTree = ""; }; 035EB0C92A8687C200227859 /* JumpButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JumpButtonView.swift; sourceTree = ""; }; @@ -1103,10 +1107,10 @@ isa = PBXGroup; children = ( 0308E1132B0EA32A000CA955 /* AccountSettingsView.swift */, - 039C8DB62B35A32D0096BAAF /* AccountSwitcherSettingsView.swift */, 03A18CBA2B0FD6F000BA69D2 /* ProfileSettingsView.swift */, 031A617B2B1BDFD100ABF23B /* AdvancedAccountSettingsView.swift */, 03F4DC9E2B1A8AD500556C67 /* SignInAndSecuritySettingsView.swift */, + 032C1E052B5DBDB100FB4F23 /* LocalAccountSettingsView.swift */, 031A617D2B1CE90F00ABF23B /* ChangePasswordView.swift */, 03F4DC9C2B193F4C00556C67 /* MatrixLinkView.swift */, 03F4DCA22B1A8B0400556C67 /* AccountGeneralSettingsView.swift */, @@ -1285,6 +1289,15 @@ path = Community; sourceTree = ""; }; + 032C1E022B5D7D9C00FB4F23 /* AccountList */ = { + isa = PBXGroup; + children = ( + 039C8DB62B35A32D0096BAAF /* AccountSwitcherSettingsView.swift */, + 032C1E032B5D7DAC00FB4F23 /* QuickSwitcherSettingsView.swift */, + ); + path = AccountList; + sourceTree = ""; + }; 032CC9C52A844D8200322E2A /* LayoutWidgets */ = { isa = PBXGroup; children = ( @@ -1640,6 +1653,7 @@ 63344C522A07D189001BC616 /* Views */ = { isa = PBXGroup; children = ( + 032C1E022B5D7D9C00FB4F23 /* AccountList */, 0308E1122B0EA31F000CA955 /* Account */, 030AC0432A62F82B00037155 /* General */, 03E79F3D2AE3E6FF0006700D /* Sorting */, @@ -3026,6 +3040,7 @@ CD525F652A4B6D8F00BCA794 /* CommunityLinkView.swift in Sources */, 637218592A3A2AAD008C4816 /* APISiteAggregates.swift in Sources */, 50A881262A71A511003E3661 /* PersistenceRepository+Dependency.swift in Sources */, + 032C1E062B5DBDB100FB4F23 /* LocalAccountSettingsView.swift in Sources */, 6DA7E9A22A50764E0095AB68 /* UserViewTab.swift in Sources */, 50785F732A98E03F00117245 /* SiteInformationTracker+Dependency.swift in Sources */, CD863FBA2A6AEB5900A31ED9 /* View+FancyTabScrollCompatible.swift in Sources */, @@ -3389,6 +3404,7 @@ 637218552A3A2AAD008C4816 /* APITagline.swift in Sources */, 6322A5CB27F77A4D00135D4F /* Loading View.swift in Sources */, 03A1B3F72A84000400AB0DE0 /* APIContentAggregatesProtocol.swift in Sources */, + 032C1E042B5D7DAC00FB4F23 /* QuickSwitcherSettingsView.swift in Sources */, CD4DBC032A6F803C001A1E61 /* ReplyToPost.swift in Sources */, CD6483302A38D31C00EE6CA3 /* UpvoteCounterView.swift in Sources */, 032DD2FD2AC3594B00F1B33D /* LinkAttatchmentView.swift in Sources */, diff --git a/Mlem/App State.swift b/Mlem/App State.swift index 355edfa9b..6666f60fb 100644 --- a/Mlem/App State.swift +++ b/Mlem/App State.swift @@ -14,7 +14,7 @@ class AppState: ObservableObject { @Dependency(\.apiClient) var apiClient @AppStorage("defaultAccountId") var defaultAccountId: Int? - @AppStorage("profileTabLabel") var profileTabLabel: ProfileTabLabel = .username + @AppStorage("profileTabLabel") var profileTabLabel: ProfileTabLabel = .nickname @AppStorage("showUserAvatarOnProfileTab") var showUserAvatar: Bool = true @Published private(set) var currentActiveAccount: SavedAccount? @@ -26,8 +26,6 @@ class AppState: ObservableObject { } switch profileTabLabel { - case .username: - return currentActiveAccount.username case .instance: return currentActiveAccount.hostName ?? "Instance" case .nickname: diff --git a/Mlem/ContentView.swift b/Mlem/ContentView.swift index 564d22fcb..4e6700ba2 100644 --- a/Mlem/ContentView.swift +++ b/Mlem/ContentView.swift @@ -194,8 +194,10 @@ struct ContentView: View { // disable long press in accessibility mode to prevent conflict with HUD if !accessibilityFont { if accountsTracker.savedAccounts.count > 2 { - hapticManager.play(haptic: .rigidInfo, priority: .high) - isPresentingAccountSwitcher = true + if UserDefaults.standard.bool(forKey: "allowQuickSwitcherLongPressGesture") { + hapticManager.play(haptic: .rigidInfo, priority: .high) + isPresentingAccountSwitcher = true + } } else if accountsTracker.savedAccounts.count == 2 { hapticManager.play(haptic: .rigidInfo, priority: .high) for account in accountsTracker.savedAccounts where account != appState.currentActiveAccount { diff --git a/Mlem/Enums/Settings/ProfileTabLabel.swift b/Mlem/Enums/Settings/ProfileTabLabel.swift index ed733a360..26ad9b1de 100644 --- a/Mlem/Enums/Settings/ProfileTabLabel.swift +++ b/Mlem/Enums/Settings/ProfileTabLabel.swift @@ -8,7 +8,7 @@ import Foundation enum ProfileTabLabel: String { - case username, instance, nickname, anonymous + case nickname, instance, anonymous } extension ProfileTabLabel: SettingsOptions { diff --git a/Mlem/Extensions/View Modifiers/View+HandleLemmyLinks.swift b/Mlem/Extensions/View Modifiers/View+HandleLemmyLinks.swift index 39b2217be..22044b33b 100644 --- a/Mlem/Extensions/View Modifiers/View+HandleLemmyLinks.swift +++ b/Mlem/Extensions/View Modifiers/View+HandleLemmyLinks.swift @@ -102,6 +102,8 @@ struct HandleLemmyLinksDisplay: ViewModifier { SignInAndSecuritySettingsView() case .accountGeneral: AccountGeneralSettingsView() + case .accountLocal: + LocalAccountSettingsView() case .accountAdvanced: AdvancedAccountSettingsView() case .accountDiscussionLanguages: @@ -110,6 +112,8 @@ struct HandleLemmyLinksDisplay: ViewModifier { MatrixLinkView() case .accounts: AccountSwitcherSettingsView() + case .quickSwitcher: + QuickSwitcherSettingsView() case .general: GeneralSettingsView() case .sorting: diff --git a/Mlem/Icons.swift b/Mlem/Icons.swift index 180cf36c3..c4222c630 100644 --- a/Mlem/Icons.swift +++ b/Mlem/Icons.swift @@ -186,7 +186,6 @@ struct Icons { static let leftRight: String = "arrow.left.arrow.right" static let developerMode: String = "wrench.adjustable.fill" static let limitImageHeightSetting: String = "rectangle.compress.vertical" - static let swipeUpGestureSetting: String = "arrow.up.to.line.alt" // misc static let switchUser: String = "person.crop.circle.badge.plus" diff --git a/Mlem/Models/Saved Account.swift b/Mlem/Models/Saved Account.swift index 2043ddd0c..2d53da104 100644 --- a/Mlem/Models/Saved Account.swift +++ b/Mlem/Models/Saved Account.swift @@ -13,7 +13,7 @@ struct SavedAccount: Identifiable, Codable, Equatable, Hashable { let accessToken: String var siteVersion: SiteVersion? let username: String - let storedNickname: String? + var storedNickname: String? let avatarUrl: URL? var lastUsed: Date? // nil when loading SavedAccounts from before this was added diff --git a/Mlem/Navigation/Destination Values/SettingsValues.swift b/Mlem/Navigation/Destination Values/SettingsValues.swift index c0218b013..42dd44603 100644 --- a/Mlem/Navigation/Destination Values/SettingsValues.swift +++ b/Mlem/Navigation/Destination Values/SettingsValues.swift @@ -13,9 +13,11 @@ enum SettingsPage: DestinationValue { case signInAndSecurity case accountGeneral case accountAdvanced + case accountLocal case accountDiscussionLanguages case linkMatrixAccount case accounts + case quickSwitcher case general case sorting case contentFilters diff --git a/Mlem/Views/Tabs/Settings/Components/Settings Item.swift b/Mlem/Views/Tabs/Settings/Components/Settings Item.swift index 3041609de..ec706bbc2 100644 --- a/Mlem/Views/Tabs/Settings/Components/Settings Item.swift +++ b/Mlem/Views/Tabs/Settings/Components/Settings Item.swift @@ -73,30 +73,18 @@ struct SelectableSettingsItem: View { struct SquircleLabelStyle: LabelStyle { var color: Color - var fontSize: CGFloat = 14 - let ios16FontSize: CGFloat = 17 - - /** - This computed property handles the fact that on iOS 17, the font size of 17 makes the icons too large. The code will not compile if fontSize itself is either absent or computed, so we define it above, compute an adjusted value here, and use the computed value. - */ - var adjustedFontSize: CGFloat { - if #available(iOS 17.0, *) { - return fontSize - } else { - return ios16FontSize - } - } + var fontSize: CGFloat = 17 func makeBody(configuration: Configuration) -> some View { - Label { - configuration.title - } icon: { + HStack(alignment: .center, spacing: 16) { configuration.icon - .font(.system(size: adjustedFontSize)) + .font(.system(size: fontSize)) .foregroundColor(.white) .frame(width: AppConstants.settingsIconSize, height: AppConstants.settingsIconSize) .background(color) .clipShape(RoundedRectangle(cornerRadius: AppConstants.smallItemCornerRadius)) + .accessibilityHidden(true) + configuration.title } } } @@ -109,6 +97,30 @@ struct SettingsButtonStyle: ButtonStyle { } } +struct CheckboxToggleStyle: ToggleStyle { + func makeBody(configuration: Configuration) -> some View { + HStack { + configuration.label + Spacer() + VStack { + if configuration.isOn { + Image(systemName: "checkmark.circle.fill") + .foregroundStyle(.white, .blue) + .imageScale(.large) + } else { + Image(systemName: "circle") + .foregroundStyle(Color(uiColor: .tertiaryLabel)) + .imageScale(.large) + } + } + .animation(.default, value: configuration.isOn) + } + .onTapGesture { + configuration.isOn.toggle() + } + } +} + struct SettingsPickerButton: View { @Binding var isOn: Bool diff --git a/Mlem/Views/Tabs/Settings/Components/Views/Account/AccountGeneralSettingsView.swift b/Mlem/Views/Tabs/Settings/Components/Views/Account/AccountGeneralSettingsView.swift index 276513718..008fe4209 100644 --- a/Mlem/Views/Tabs/Settings/Components/Views/Account/AccountGeneralSettingsView.swift +++ b/Mlem/Views/Tabs/Settings/Components/Views/Account/AccountGeneralSettingsView.swift @@ -51,6 +51,8 @@ struct AccountGeneralSettingsView: View { } } } + } footer: { + Text("Show content flagged as Not Safe For Work.") } Section { SwitchableSettingsItem( diff --git a/Mlem/Views/Tabs/Settings/Components/Views/Account/AccountSettingsView.swift b/Mlem/Views/Tabs/Settings/Components/Views/Account/AccountSettingsView.swift index ffddd2e77..09295683a 100644 --- a/Mlem/Views/Tabs/Settings/Components/Views/Account/AccountSettingsView.swift +++ b/Mlem/Views/Tabs/Settings/Components/Views/Account/AccountSettingsView.swift @@ -36,7 +36,7 @@ struct AccountSettingsView: View { Section { VStack { AvatarView(url: info.localUserView.person.avatarUrl, type: .user, avatarSize: 96, iconResolution: .unrestricted) - Text(info.localUserView.person.displayName ?? info.localUserView.person.name) + Text(appState.currentActiveAccount?.nickname ?? info.localUserView.person.name) .font(.largeTitle) .padding(.top, 3) if let account = appState.currentActiveAccount, let hostName = account.hostName { @@ -81,6 +81,15 @@ struct AccountSettingsView: View { } .disabled(settingsDisabled) + Section { + NavigationLink(.settings(.accountLocal)) { + Label("Local Options", systemImage: "iphone.gen3") + .labelStyle(SquircleLabelStyle(color: .blue)) + } + } footer: { + Text("These options are stored locally in Mlem and not on your Lemmy account.") + } + // Section { // NavigationLink { EmptyView() } label: { // Label("Blocked Commuities", systemImage: "house.fill").labelStyle(SquircleLabelStyle(color: .gray)) @@ -118,7 +127,10 @@ struct AccountSettingsView: View { Button("Delete Account", role: .destructive) { accountForDeletion = appState.currentActiveAccount } - .frame(maxWidth: .infinity) + .frame(maxWidth: .infinity) + .sheet(item: $accountForDeletion) { account in + DeleteAccountView(account: account) + } } } else { @@ -128,8 +140,5 @@ struct AccountSettingsView: View { .navigationTitle("Account Settings") .fancyTabScrollCompatible() .hoistNavigation() - .sheet(item: $accountForDeletion) { account in - DeleteAccountView(account: account) - } } } diff --git a/Mlem/Views/Tabs/Settings/Components/Views/Account/AccountSwitcherSettingsView.swift b/Mlem/Views/Tabs/Settings/Components/Views/Account/AccountSwitcherSettingsView.swift deleted file mode 100644 index 086c3983d..000000000 --- a/Mlem/Views/Tabs/Settings/Components/Views/Account/AccountSwitcherSettingsView.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// AccountSwitcherSettingsView.swift -// Mlem -// -// Created by Sjmarf on 22/12/2023. -// - -import SwiftUI -import Dependencies - -struct AccountSwitcherSettingsView: View { - @Dependency(\.accountsTracker) var accountsTracker: SavedAccountTracker - - var body: some View { - Form { - Section { - VStack { - AccountIconStack( - accounts: Array(accountsTracker.savedAccounts.prefix(7)), - avatarSize: 64, - spacing: 32, - outlineWidth: 2.6, - backgroundColor: Color(UIColor.systemGroupedBackground) - ) - .padding(.top, -12) - Text("Accounts") - .font(.title) - .fontWeight(.bold) - } - .frame(maxWidth: .infinity) - .listRowBackground(Color(.systemGroupedBackground)) - } - AccountListView() - NavigationLink { EmptyView() } label: { - Label("Quick Switcher", systemImage: "platter.filled.bottom.iphone") - .labelStyle(SquircleLabelStyle(color: .teal)) - } - } - .fancyTabScrollCompatible() - } - -} diff --git a/Mlem/Views/Tabs/Settings/Components/Views/Account/LocalAccountSettingsView.swift b/Mlem/Views/Tabs/Settings/Components/Views/Account/LocalAccountSettingsView.swift new file mode 100644 index 000000000..f6e341b35 --- /dev/null +++ b/Mlem/Views/Tabs/Settings/Components/Views/Account/LocalAccountSettingsView.swift @@ -0,0 +1,106 @@ +// +// LocalAccountSettingsView.swift +// Mlem +// +// Created by Sjmarf on 21/01/2024. +// + +import SwiftUI +import Dependencies + +struct LocalAccountSettingsView: View { + @Dependency(\.favoriteCommunitiesTracker) var favoriteCommunitiesTracker + + @AppStorage("profileTabLabel") var profileTabLabel: ProfileTabLabel = .nickname + @AppStorage("showSettingsIcons") var showSettingsIcons: Bool = false + + @EnvironmentObject var appState: AppState + + @State var nickname: String = "" + @State private var isShowingFavoritesDeletionConfirmation: Bool = false + + var body: some View { + Form { + Section { + TextField("Nickname", text: $nickname, prompt: Text(appState.currentActiveAccount?.username ?? "Nickname")) + .autocorrectionDisabled(true) + .textInputAutocapitalization(.never) + .onSubmit { + guard var existingAccount = appState.currentActiveAccount else { + return + } + + let acceptedNickname = nickname.trimmed.isEmpty ? nil : nickname + existingAccount.storedNickname = nil + + let newAccount = SavedAccount( + from: existingAccount, + storedNickname: acceptedNickname, + avatarUrl: existingAccount.avatarUrl + ) + appState.setActiveAccount(newAccount) + } + .onAppear { + nickname = appState.currentActiveAccount?.storedNickname ?? "" + } + .onChange(of: appState.currentActiveAccount?.nickname) { nickname in + self.nickname = nickname ?? "" + } + } header: { + Text("Nickname") + } footer: { + if profileTabLabel == .nickname { + Text("The name shown in the account switcher and tab bar.") + } + Text("The name shown in the account switcher.") + } + + Section { + Button(role: .destructive) { + isShowingFavoritesDeletionConfirmation.toggle() + } label: { + Label { + Text("Delete Community Favorites") + } icon: { + if showSettingsIcons { + Image(systemName: Icons.delete) + } + } + .foregroundColor(.red) + .opacity(favoriteCommunitiesTracker.favoritesForCurrentAccount.isEmpty ? 0.6 : 1) + } + .disabled(favoriteCommunitiesTracker.favoritesForCurrentAccount.isEmpty) + .confirmationDialog( + "Delete community favorites for this account?", + isPresented: $isShowingFavoritesDeletionConfirmation, + titleVisibility: .visible + ) { + Button(role: .destructive) { + favoriteCommunitiesTracker.clearCurrentFavourites() + } label: { + Text("Delete all favorites") + } + + Button(role: .cancel) { + isShowingFavoritesDeletionConfirmation.toggle() + } label: { + Text("Cancel") + } + + } message: { + Text("You cannot undo this action.") + } + } footer: { + let favorites = favoriteCommunitiesTracker.favoritesForCurrentAccount + if favorites.isEmpty { + Text("You haven't favorited any communities on this account.") + } else { + Text("You've favorited ^[\(favorites.count) community](inflect:true) on this account.") + } + } + } + .navigationTitle("Local Options") + .fancyTabScrollCompatible() + .hoistNavigation() + } +} diff --git a/Mlem/Views/Tabs/Settings/Components/Views/AccountList/AccountSwitcherSettingsView.swift b/Mlem/Views/Tabs/Settings/Components/Views/AccountList/AccountSwitcherSettingsView.swift new file mode 100644 index 000000000..d7cfe4bef --- /dev/null +++ b/Mlem/Views/Tabs/Settings/Components/Views/AccountList/AccountSwitcherSettingsView.swift @@ -0,0 +1,145 @@ +// +// AccountSwitcherSettingsView.swift +// Mlem +// +// Created by Sjmarf on 22/12/2023. +// + +import SwiftUI +import Dependencies + +struct AccountSwitcherSettingsView: View { + @Dependency(\.accountsTracker) var accountsTracker: SavedAccountTracker + + var body: some View { + Form { + Section { + VStack { + AccountIconStack( + accounts: Array(accountsTracker.savedAccounts.prefix(7)), + avatarSize: 64, + spacing: 32, + outlineWidth: 2.6, + backgroundColor: Color(UIColor.systemGroupedBackground) + ) + .padding(.top, -12) + Text("Accounts") + .font(.title) + .fontWeight(.bold) + } + .frame(maxWidth: .infinity) + .listRowBackground(Color(.systemGroupedBackground)) + } + AccountListView() + if accountsTracker.savedAccounts.count == 2 { + Section { + HStack(alignment: .center, spacing: 24) { + TwoAccountSwitchView() + VStack(alignment: .leading, spacing: 10) { + Text("Tip") + .fontWeight(.semibold) + Text("Tap and hold the Profile Icon in the tab bar to quickly switch accounts from anywhere.") + .font(.footnote) + } + .foregroundStyle(.secondary) + } + } + .listRowInsets(EdgeInsets(top: 12, leading: 12, bottom: 12, trailing: 12)) + } else { + NavigationLink(.settings(.quickSwitcher)) { + Label { + VStack(alignment: .leading) { + Text("Quick Switcher") + Text("Switch accounts quickly from anywhere.") + .foregroundStyle(.secondary) + .font(.footnote) + } + } icon: { + Image(systemName: "platter.filled.bottom.iphone") + } + .labelStyle(SquircleLabelStyle(color: .teal)) + } + } + } + .fancyTabScrollCompatible() + } + +} + +private struct TwoAccountSwitchView: View { + + enum AnimationPhase: CaseIterable { + case none, fadeInTap, tap, none2, fadeInTap2, tap2 + + static var order: [AnimationPhase] = [.none, .fadeInTap, .tap, .none2, .fadeInTap2, .tap2] + } + + @Dependency(\.accountsTracker) var accountsTracker: SavedAccountTracker + + @State var animationTrigger: Bool = false + + let timer = Timer.publish(every: 5, on: .main, in: .common).autoconnect() + + var body: some View { + if #available(iOS 17.0, *) { + PhaseAnimator(AnimationPhase.order, trigger: animationTrigger) { phase in + IPhoneWithSheetView(showingSheet: false) + .overlay(alignment: .bottom) { + ZStack(alignment: .bottom) { + Circle() + .fill(.blue) + .frame(width: 12, height: 12) + .background { + if [.fadeInTap, .tap, .fadeInTap2, .tap2].contains(phase) { + Circle() + .fill(.blue) + .opacity([.tap, .tap2].contains(phase) ? 0 : 0.8) + .scaleEffect([.tap, .tap2].contains(phase) ? 2 : 1) + .transition(.identity) + } + } + .padding(.bottom, 4) + .opacity([.fadeInTap, .tap, .fadeInTap2, .tap2].contains(phase) ? 1 : 0) + + if [.none, .fadeInTap, .tap2].contains(phase) { + AvatarView( + url: accountsTracker.savedAccounts[0].avatarUrl, + type: .user, + avatarSize: 28, + iconResolution: .unrestricted + ) + .frame(maxWidth: .infinity, maxHeight: .infinity) + } else { + AvatarView( + url: accountsTracker.savedAccounts[1].avatarUrl, + type: .user, + avatarSize: 28, + iconResolution: .unrestricted + ) + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + } + } + } animation: { phase in + switch phase { + case .fadeInTap, .fadeInTap2: + return .easeOut(duration: 0.2).delay(1.5) + case .tap, .tap2: + return .easeOut(duration: 0.2).delay(0.3) + case .none, .none2: + return .easeOut(duration: 0.3) + } + } + .task { + animationTrigger.toggle() + } + .onReceive(timer) { _ in + animationTrigger.toggle() + } + + } else { + Image(systemName: "lightbulb.max.fill") + .imageScale(.large) + } + } +} diff --git a/Mlem/Views/Tabs/Settings/Components/Views/AccountList/QuickSwitcherSettingsView.swift b/Mlem/Views/Tabs/Settings/Components/Views/AccountList/QuickSwitcherSettingsView.swift new file mode 100644 index 000000000..594d04f0e --- /dev/null +++ b/Mlem/Views/Tabs/Settings/Components/Views/AccountList/QuickSwitcherSettingsView.swift @@ -0,0 +1,188 @@ +// +// QuickSwitcherSettingsView.swift +// Mlem +// +// Created by Sjmarf on 21/01/2024. +// + +import SwiftUI +import Dependencies + +struct QuickSwitcherSettingsView: View { + + @AppStorage("allowTabBarSwipeUpGesture") var allowTabBarSwipeUpGesture: Bool = true + @AppStorage("allowQuickSwitcherLongPressGesture") var allowQuickSwitcherLongPressGesture: Bool = true + + // TODO: iOS 16 deprecation + // Theoretically, if we don't provide a trigger it should loop indefinitely. This isn't working for some reason, so I'm adding + // a trigger and toggling it every n seconds instead. Maybe it's to do with the iOS 17 compiler `if` condition? Check once iOS 16 is deprecated. + @State var animationTrigger: Bool = false + + let timer = Timer.publish(every: 3.7, on: .main, in: .common).autoconnect() + + var body: some View { + Form { + Section { + HStack(alignment: .center, spacing: 24) { + LongPressAnimationView(animationTrigger: animationTrigger) + Toggle( + "Tapping and holding the Profile icon.", + isOn: $allowQuickSwitcherLongPressGesture + ) + .toggleStyle(CheckboxToggleStyle()) + } + + HStack(alignment: .center, spacing: 24) { + SwipeUpAnimationView(animationTrigger: animationTrigger) + Toggle( + "Swiping up from the tab bar.", + isOn: $allowTabBarSwipeUpGesture + ) + .toggleStyle(CheckboxToggleStyle()) + } + } header: { + Text("Open the Quick Switcher by...") + .textCase(nil) + } + .listRowInsets(EdgeInsets(top: 12, leading: 12, bottom: 12, trailing: 12)) + } + .fancyTabScrollCompatible() + .navigationTitle("Quick Switcher") + .navigationBarTitleDisplayMode(.inline) + .task { + animationTrigger.toggle() + } + .onReceive(timer) { _ in + animationTrigger.toggle() + } + } +} + +#Preview { + NavigationStack { + QuickSwitcherSettingsView() + } +} + +struct IPhoneWithSheetView: View { + @Environment(\.colorScheme) var colorScheme + + let showingSheet: Bool + + var body: some View { + VStack { + Spacer() + Color(colorScheme == .light ? .systemGray5 : .systemGray4) + .frame(height: 20) + + } + .overlay(alignment: .bottom) { + RoundedRectangle(cornerRadius: 6) + .fill(Color(.secondarySystemGroupedBackground)) + .frame(height: showingSheet ? 35 : 0) + } + .frame(width: 43, height: 80) + .background(Color(.tertiarySystemGroupedBackground)) + .clipShape(RoundedRectangle(cornerRadius: 10)) + .overlay(RoundedRectangle(cornerRadius: 10).strokeBorder(.secondary.opacity(0.5), lineWidth: 1)) + } +} + +private struct LongPressAnimationView: View { + + enum AnimationPhase: CaseIterable { + case sheetClosed, fadeInTap, tap, slideInSheet, sheetTap, slideOutSheet + } + + let animationTrigger: Bool + + var body: some View { + if #available(iOS 17.0, *) { + PhaseAnimator(AnimationPhase.allCases, trigger: animationTrigger) { phase in + IPhoneWithSheetView(showingSheet: [.slideInSheet, .sheetTap, .slideOutSheet].contains(phase)) + .overlay(alignment: .bottom) { + Circle() + .fill(.blue) + .frame(width: 12, height: 12) + .background { + if [.fadeInTap, .tap].contains(phase) { + Circle() + .fill(.blue) + .opacity(phase == .tap ? 0 : 0.8) + .scaleEffect(phase == .tap ? 2 : 1) + .transition(.identity) + } + } + .padding(.bottom, 4) + .opacity([.fadeInTap, .tap].contains(phase) ? 1 : 0) + Circle() + .fill(.blue) + .frame(width: 12, height: 12) + .opacity(phase == .sheetTap ? 1 : 0) + .padding(.top, 8) + .offset(y: -17) + } + } animation: { phase in + switch phase { + case .fadeInTap: + return .easeOut(duration: 0.2) + case .tap: + return .easeOut(duration: 0.2).delay(0.3) + case .slideInSheet: + return .easeOut(duration: 0.3) + case .sheetTap: + return .easeOut(duration: 0.05).delay(1.4) + default: + return .easeOut(duration: 0.3) + } + } + + } else { + EmptyView() + } + } +} + +private struct SwipeUpAnimationView: View { + + enum AnimationPhase: CaseIterable { + case sheetClosed, fadeInTap, slideInSheet, sheetTap, slideOutSheet + } + + let animationTrigger: Bool + + var body: some View { + if #available(iOS 17.0, *) { + PhaseAnimator(AnimationPhase.allCases, trigger: animationTrigger) { phase in + IPhoneWithSheetView(showingSheet: [.slideInSheet, .sheetTap, .slideOutSheet].contains(phase)) + .overlay(alignment: .bottom) { + Circle() + .fill(.blue) + .frame(width: 12, height: 12) + .padding(.bottom, 4) + .opacity([.fadeInTap].contains(phase) ? 1 : 0) + .offset(y: phase == .slideInSheet ? -30 : 0) + Circle() + .fill(.blue) + .frame(width: 12, height: 12) + .opacity(phase == .sheetTap ? 1 : 0) + .padding(.top, 8) + .offset(y: -17) + } + } animation: { phase in + switch phase { + case .fadeInTap: + return .easeOut(duration: 0.1).delay(0.6) + case .slideInSheet: + return .easeOut(duration: 0.3) + case .sheetTap: + return .easeOut(duration: 0.05).delay(1.4) + default: + return .easeOut(duration: 0.3) + } + } + } else { + EmptyView() + } + } +} diff --git a/Mlem/Views/Tabs/Settings/Components/Views/Appearance/AppearanceSettingsView.swift b/Mlem/Views/Tabs/Settings/Components/Views/Appearance/AppearanceSettingsView.swift index 8974b368f..8609e2b2b 100644 --- a/Mlem/Views/Tabs/Settings/Components/Views/Appearance/AppearanceSettingsView.swift +++ b/Mlem/Views/Tabs/Settings/Components/Views/Appearance/AppearanceSettingsView.swift @@ -54,7 +54,7 @@ struct AppearanceSettingsView: View { } NavigationLink(.appearanceSettings(.communities)) { - Label("Communities", systemImage: "house.fill").labelStyle(SquircleLabelStyle(color: .green, fontSize: 15)) + Label("Communities", systemImage: "house.fill").labelStyle(SquircleLabelStyle(color: .green, fontSize: 18)) } NavigationLink(.appearanceSettings(.users)) { diff --git a/Mlem/Views/Tabs/Settings/Components/Views/Appearance/TabBar/TabBarSettingsView.swift b/Mlem/Views/Tabs/Settings/Components/Views/Appearance/TabBar/TabBarSettingsView.swift index 0f0222986..b486c63bd 100644 --- a/Mlem/Views/Tabs/Settings/Components/Views/Appearance/TabBar/TabBarSettingsView.swift +++ b/Mlem/Views/Tabs/Settings/Components/Views/Appearance/TabBar/TabBarSettingsView.swift @@ -9,16 +9,11 @@ import Dependencies import SwiftUI struct TabBarSettingsView: View { - @AppStorage("profileTabLabel") var profileTabLabel: ProfileTabLabel = .username + @AppStorage("profileTabLabel") var profileTabLabel: ProfileTabLabel = .nickname @AppStorage("showTabNames") var showTabNames: Bool = true @AppStorage("showInboxUnreadBadge") var showInboxUnreadBadge: Bool = true @AppStorage("showUserAvatarOnProfileTab") var showUserAvatar: Bool = true - @AppStorage("allowTabBarSwipeUpGesture") var allowTabBarSwipeUpGesture: Bool = true @AppStorage("homeButtonExists") var homeButtonExists: Bool = false - - @EnvironmentObject var appState: AppState - - @State var textFieldEntry: String = "" var body: some View { Form { @@ -31,35 +26,6 @@ struct TabBarSettingsView: View { currentValue: $profileTabLabel, options: ProfileTabLabel.allCases ) - - if profileTabLabel == .nickname { - Label { - TextField(text: $textFieldEntry, prompt: Text(appState.currentActiveAccount?.nickname ?? "")) { - Text("Nickname") - } - .autocorrectionDisabled(true) - .textInputAutocapitalization(.never) - .onSubmit { - print(textFieldEntry) - guard let existingAccount = appState.currentActiveAccount else { - return - } - - // disallow blank nicknames - let acceptedNickname = textFieldEntry.trimmed.isEmpty ? existingAccount.username : textFieldEntry - - let newAccount = SavedAccount( - from: existingAccount, - storedNickname: acceptedNickname, - avatarUrl: existingAccount.avatarUrl - ) - appState.setActiveAccount(newAccount) - } - } icon: { - Image(systemName: Icons.nicknameField) - .foregroundColor(.pink) - } - } } Section { @@ -83,25 +49,10 @@ struct TabBarSettingsView: View { ) .disabled(profileTabLabel == .anonymous) } - if !homeButtonExists { - Section { - SwitchableSettingsItem( - settingPictureSystemName: Icons.swipeUpGestureSetting, - settingName: "Swipe Up For Account Switcher", - isTicked: $allowTabBarSwipeUpGesture - ) - } - } } .fancyTabScrollCompatible() .navigationTitle("Tab Bar") .navigationBarColor() .animation(.easeIn, value: profileTabLabel) - .onChange(of: appState.currentActiveAccount?.nickname) { nickname in - guard let nickname else { return } - print("new nickname: \(nickname)") - textFieldEntry = nickname - } - .hoistNavigation() } } diff --git a/Mlem/Views/Tabs/Settings/Components/Views/General/GeneralSettingsView.swift b/Mlem/Views/Tabs/Settings/Components/Views/General/GeneralSettingsView.swift index 5326ec24b..09141fcfa 100644 --- a/Mlem/Views/Tabs/Settings/Components/Views/General/GeneralSettingsView.swift +++ b/Mlem/Views/Tabs/Settings/Components/Views/General/GeneralSettingsView.swift @@ -9,7 +9,6 @@ import Dependencies import SwiftUI struct GeneralSettingsView: View { - @Dependency(\.favoriteCommunitiesTracker) var favoriteCommunitiesTracker @Dependency(\.siteInformation) var siteInformation: SiteInformationTracker @AppStorage("confirmImageUploads") var confirmImageUploads: Bool = true @@ -21,13 +20,10 @@ struct GeneralSettingsView: View { @AppStorage("hapticLevel") var hapticLevel: HapticPriority = .low @AppStorage("upvoteOnSave") var upvoteOnSave: Bool = false - @AppStorage("showSettingsIcons") var showSettingsIcons: Bool = false @AppStorage("openLinksInBrowser") var openLinksInBrowser: Bool = false @EnvironmentObject var appState: AppState - @State private var isShowingFavoritesDeletionConfirmation: Bool = false - var body: some View { List { Section { @@ -113,46 +109,6 @@ struct GeneralSettingsView: View { } footer: { Text("Optimizes performance for your internet speed. You may need to restart the app for all optimizations to take effect.") } - - Section { - Button(role: .destructive) { - isShowingFavoritesDeletionConfirmation.toggle() - } label: { - Label { - Text("Delete Community Favorites") - } icon: { - if showSettingsIcons { - Image(systemName: Icons.delete) - } - } - .foregroundColor(.red) - .opacity(favoriteCommunitiesTracker.favoritesForCurrentAccount.isEmpty ? 0.6 : 1) - } - .disabled(favoriteCommunitiesTracker.favoritesForCurrentAccount.isEmpty) - .confirmationDialog( - "Delete community favorites for this account?", - isPresented: $isShowingFavoritesDeletionConfirmation, - titleVisibility: .visible - ) { - Button(role: .destructive) { - favoriteCommunitiesTracker.clearCurrentFavourites() - } label: { - Text("Delete all favorites") - } - - Button(role: .cancel) { - isShowingFavoritesDeletionConfirmation.toggle() - } label: { - Text("Cancel") - } - - } message: { - Text("You cannot undo this action.") - } - - } footer: { - Text("Community favorites are stored on-device and are not tied to your Lemmy account.") - } } .fancyTabScrollCompatible() .navigationTitle("General") From 6a418266919028233f274c0c344e8e012cb671fb Mon Sep 17 00:00:00 2001 From: Sjmarf <78750526+Sjmarf@users.noreply.github.com> Date: Sun, 21 Jan 2024 23:15:39 +0000 Subject: [PATCH 03/10] Update --- .../Settings/Components/Settings Item.swift | 30 +++++++---- .../TabBar/TabBarSettingsView.swift | 52 ++++++++++++++----- 2 files changed, 58 insertions(+), 24 deletions(-) diff --git a/Mlem/Views/Tabs/Settings/Components/Settings Item.swift b/Mlem/Views/Tabs/Settings/Components/Settings Item.swift index ec706bbc2..000cf97d5 100644 --- a/Mlem/Views/Tabs/Settings/Components/Settings Item.swift +++ b/Mlem/Views/Tabs/Settings/Components/Settings Item.swift @@ -102,17 +102,7 @@ struct CheckboxToggleStyle: ToggleStyle { HStack { configuration.label Spacer() - VStack { - if configuration.isOn { - Image(systemName: "checkmark.circle.fill") - .foregroundStyle(.white, .blue) - .imageScale(.large) - } else { - Image(systemName: "circle") - .foregroundStyle(Color(uiColor: .tertiaryLabel)) - .imageScale(.large) - } - } + Checkbox(isOn: configuration.isOn) .animation(.default, value: configuration.isOn) } .onTapGesture { @@ -121,6 +111,24 @@ struct CheckboxToggleStyle: ToggleStyle { } } +struct Checkbox: View { + let isOn: Bool + + var body: some View { + VStack { + if isOn { + Image(systemName: "checkmark.circle.fill") + .foregroundStyle(.white, .blue) + .imageScale(.large) + } else { + Image(systemName: "circle") + .foregroundStyle(Color(uiColor: .tertiaryLabel)) + .imageScale(.large) + } + } + } +} + struct SettingsPickerButton: View { @Binding var isOn: Bool diff --git a/Mlem/Views/Tabs/Settings/Components/Views/Appearance/TabBar/TabBarSettingsView.swift b/Mlem/Views/Tabs/Settings/Components/Views/Appearance/TabBar/TabBarSettingsView.swift index b486c63bd..22fbb16da 100644 --- a/Mlem/Views/Tabs/Settings/Components/Views/Appearance/TabBar/TabBarSettingsView.swift +++ b/Mlem/Views/Tabs/Settings/Components/Views/Appearance/TabBar/TabBarSettingsView.swift @@ -15,19 +15,10 @@ struct TabBarSettingsView: View { @AppStorage("showUserAvatarOnProfileTab") var showUserAvatar: Bool = true @AppStorage("homeButtonExists") var homeButtonExists: Bool = false + @EnvironmentObject var appState: AppState + var body: some View { Form { - // TODO: options like this will need to be updated to only show when there is an active account - // present once guest mode is fully implemented - Section { - SelectableSettingsItem( - settingIconSystemName: Icons.profileTabSettings, - settingName: "Profile Tab Label", - currentValue: $profileTabLabel, - options: ProfileTabLabel.allCases - ) - } - Section { SwitchableSettingsItem( settingPictureSystemName: Icons.label, @@ -40,10 +31,45 @@ struct TabBarSettingsView: View { settingName: "Show Unread Count", isTicked: $showInboxUnreadBadge ) - + } + + // TODO: options like this will need to be updated to only show when there is an active account + // present once guest mode is fully implemented + Section("Profile Icon Appearance") { + HStack { + ForEach(ProfileTabLabel.allCases, id: \.self) { item in + VStack(spacing: 10) { + let account = appState.currentActiveAccount + if let avatar = account?.avatarUrl, item != .anonymous, showUserAvatar { + AvatarView(url: avatar, type: .user, avatarSize: 42, iconResolution: .unrestricted) + } else { + Image(systemName: Icons.user) + .resizable() + .frame(width: 42, height: 42) + } + Group { + switch item { + case .nickname: + Text(account?.nickname ?? "Nickname") + case .instance: + Text(account?.instanceLink.host() ?? "Instance") + case .anonymous: + Text("Profile") + } + } + .font(.footnote) + Checkbox(isOn: profileTabLabel == item) + } + .foregroundStyle(.secondary) + .frame(maxWidth: .infinity) + .onTapGesture { + profileTabLabel = item + } + } + } SwitchableSettingsItem( settingPictureSystemName: Icons.showAvatar, - settingName: "Show User Avatar", + settingName: "Show Avatar", // if `.anonymous` is selected the toggle here should always be false isTicked: profileTabLabel == .anonymous ? .constant(false) : $showUserAvatar ) From 32ae025e23597d528257e29208d93b58d28ed55a Mon Sep 17 00:00:00 2001 From: Sjmarf <78750526+Sjmarf@users.noreply.github.com> Date: Sun, 21 Jan 2024 23:46:15 +0000 Subject: [PATCH 04/10] Label tweaks --- .../Views/AccountList/QuickSwitcherSettingsView.swift | 4 ++-- .../Views/Appearance/TabBar/TabBarSettingsView.swift | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Mlem/Views/Tabs/Settings/Components/Views/AccountList/QuickSwitcherSettingsView.swift b/Mlem/Views/Tabs/Settings/Components/Views/AccountList/QuickSwitcherSettingsView.swift index 594d04f0e..fa133a501 100644 --- a/Mlem/Views/Tabs/Settings/Components/Views/AccountList/QuickSwitcherSettingsView.swift +++ b/Mlem/Views/Tabs/Settings/Components/Views/AccountList/QuickSwitcherSettingsView.swift @@ -26,7 +26,7 @@ struct QuickSwitcherSettingsView: View { HStack(alignment: .center, spacing: 24) { LongPressAnimationView(animationTrigger: animationTrigger) Toggle( - "Tapping and holding the Profile icon.", + "Tapping and holding the Profile icon", isOn: $allowQuickSwitcherLongPressGesture ) .toggleStyle(CheckboxToggleStyle()) @@ -35,7 +35,7 @@ struct QuickSwitcherSettingsView: View { HStack(alignment: .center, spacing: 24) { SwipeUpAnimationView(animationTrigger: animationTrigger) Toggle( - "Swiping up from the tab bar.", + "Swiping up from the tab bar", isOn: $allowTabBarSwipeUpGesture ) .toggleStyle(CheckboxToggleStyle()) diff --git a/Mlem/Views/Tabs/Settings/Components/Views/Appearance/TabBar/TabBarSettingsView.swift b/Mlem/Views/Tabs/Settings/Components/Views/Appearance/TabBar/TabBarSettingsView.swift index 22fbb16da..439b24b5e 100644 --- a/Mlem/Views/Tabs/Settings/Components/Views/Appearance/TabBar/TabBarSettingsView.swift +++ b/Mlem/Views/Tabs/Settings/Components/Views/Appearance/TabBar/TabBarSettingsView.swift @@ -22,13 +22,13 @@ struct TabBarSettingsView: View { Section { SwitchableSettingsItem( settingPictureSystemName: Icons.label, - settingName: "Show Tab Labels", + settingName: "Tab Labels", isTicked: $showTabNames ) SwitchableSettingsItem( settingPictureSystemName: Icons.unreadBadge, - settingName: "Show Unread Count", + settingName: "Inbox Unread Count", isTicked: $showInboxUnreadBadge ) } @@ -69,7 +69,7 @@ struct TabBarSettingsView: View { } SwitchableSettingsItem( settingPictureSystemName: Icons.showAvatar, - settingName: "Show Avatar", + settingName: "Avatar", // if `.anonymous` is selected the toggle here should always be false isTicked: profileTabLabel == .anonymous ? .constant(false) : $showUserAvatar ) From 1b6b79f9c5227247be7c5bb8b48f9794bfdc62be Mon Sep 17 00:00:00 2001 From: Sjmarf <78750526+Sjmarf@users.noreply.github.com> Date: Sun, 21 Jan 2024 23:52:08 +0000 Subject: [PATCH 05/10] Add Account Settings NavigationLink from Tab Bar Settings --- .../Settings/Components/Settings Item.swift | 17 +++++++++++++++++ .../Appearance/TabBar/TabBarSettingsView.swift | 11 ++++++++++- .../Views/General/GeneralSettingsView.swift | 12 +----------- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/Mlem/Views/Tabs/Settings/Components/Settings Item.swift b/Mlem/Views/Tabs/Settings/Components/Settings Item.swift index 000cf97d5..4d751f179 100644 --- a/Mlem/Views/Tabs/Settings/Components/Settings Item.swift +++ b/Mlem/Views/Tabs/Settings/Components/Settings Item.swift @@ -97,6 +97,23 @@ struct SettingsButtonStyle: ButtonStyle { } } +struct FooterLinkView: View { + let title: String + let destination: AppRoute + + var body: some View { + NavigationLink(destination) { + HStack(spacing: 3) { + Text(title) + Image(systemName: Icons.forward) + .fontWeight(.semibold) + .imageScale(.small) + } + .font(.footnote) + } + } +} + struct CheckboxToggleStyle: ToggleStyle { func makeBody(configuration: Configuration) -> some View { HStack { diff --git a/Mlem/Views/Tabs/Settings/Components/Views/Appearance/TabBar/TabBarSettingsView.swift b/Mlem/Views/Tabs/Settings/Components/Views/Appearance/TabBar/TabBarSettingsView.swift index 439b24b5e..25b0b174f 100644 --- a/Mlem/Views/Tabs/Settings/Components/Views/Appearance/TabBar/TabBarSettingsView.swift +++ b/Mlem/Views/Tabs/Settings/Components/Views/Appearance/TabBar/TabBarSettingsView.swift @@ -35,7 +35,7 @@ struct TabBarSettingsView: View { // TODO: options like this will need to be updated to only show when there is an active account // present once guest mode is fully implemented - Section("Profile Icon Appearance") { + Section { HStack { ForEach(ProfileTabLabel.allCases, id: \.self) { item in VStack(spacing: 10) { @@ -74,6 +74,15 @@ struct TabBarSettingsView: View { isTicked: profileTabLabel == .anonymous ? .constant(false) : $showUserAvatar ) .disabled(profileTabLabel == .anonymous) + } header: { + Text("Profile Icon Appearance") + } footer: { + if profileTabLabel == .nickname { + VStack(alignment: .leading, spacing: 3) { + Text("You can change your account's local nickname in Account Settings.") + FooterLinkView(title: "Account Settings", destination: .settings(.accountLocal)) + } + } } } .fancyTabScrollCompatible() diff --git a/Mlem/Views/Tabs/Settings/Components/Views/General/GeneralSettingsView.swift b/Mlem/Views/Tabs/Settings/Components/Views/General/GeneralSettingsView.swift index 09141fcfa..42d72eebe 100644 --- a/Mlem/Views/Tabs/Settings/Components/Views/General/GeneralSettingsView.swift +++ b/Mlem/Views/Tabs/Settings/Components/Views/General/GeneralSettingsView.swift @@ -61,17 +61,7 @@ struct GeneralSettingsView: View { // TODO: 0.17 deprecation remove this check if (siteInformation.version ?? .zero) >= .init("0.18.0") { - - NavigationLink(.settings(.accountGeneral)) { - HStack(spacing: 3) { - Text("Account Settings") - Image(systemName: "chevron.right") - .fontWeight(.semibold) - .imageScale(.small) - } - .font(.footnote) - } - + FooterLinkView(title: "Account Settings", destination: .settings(.accountGeneral)) } } } From 0d77442f9370c38b3f636ed3e581b6d80533f107 Mon Sep 17 00:00:00 2001 From: Sjmarf <78750526+Sjmarf@users.noreply.github.com> Date: Mon, 22 Jan 2024 00:03:30 +0000 Subject: [PATCH 06/10] Update TabBarSettingsView.swift --- .../Views/Appearance/TabBar/TabBarSettingsView.swift | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Mlem/Views/Tabs/Settings/Components/Views/Appearance/TabBar/TabBarSettingsView.swift b/Mlem/Views/Tabs/Settings/Components/Views/Appearance/TabBar/TabBarSettingsView.swift index 25b0b174f..8874a093f 100644 --- a/Mlem/Views/Tabs/Settings/Components/Views/Appearance/TabBar/TabBarSettingsView.swift +++ b/Mlem/Views/Tabs/Settings/Components/Views/Appearance/TabBar/TabBarSettingsView.swift @@ -77,11 +77,9 @@ struct TabBarSettingsView: View { } header: { Text("Profile Icon Appearance") } footer: { - if profileTabLabel == .nickname { - VStack(alignment: .leading, spacing: 3) { - Text("You can change your account's local nickname in Account Settings.") - FooterLinkView(title: "Account Settings", destination: .settings(.accountLocal)) - } + VStack(alignment: .leading, spacing: 3) { + Text("You can change your account's local nickname in Account Settings.") + FooterLinkView(title: "Account Settings", destination: .settings(.accountLocal)) } } } From 1d940b5b19c9ee10b86b65304aa66bfa07c457d3 Mon Sep 17 00:00:00 2001 From: Sjmarf <78750526+Sjmarf@users.noreply.github.com> Date: Mon, 22 Jan 2024 00:06:12 +0000 Subject: [PATCH 07/10] Update AdvancedAccountSettingsView.swift --- .../Components/Views/Account/AdvancedAccountSettingsView.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Mlem/Views/Tabs/Settings/Components/Views/Account/AdvancedAccountSettingsView.swift b/Mlem/Views/Tabs/Settings/Components/Views/Account/AdvancedAccountSettingsView.swift index 34a1e7580..b0bc6277b 100644 --- a/Mlem/Views/Tabs/Settings/Components/Views/Account/AdvancedAccountSettingsView.swift +++ b/Mlem/Views/Tabs/Settings/Components/Views/Account/AdvancedAccountSettingsView.swift @@ -45,6 +45,8 @@ struct AdvancedAccountSettingsView: View { } } } + } footer: { + Text("Bot accounts cannot vote on posts.") } } .navigationTitle("Advanced") From 06383ada96e6a0af84bb3cf8220c4989857ea809 Mon Sep 17 00:00:00 2001 From: Sjmarf <78750526+Sjmarf@users.noreply.github.com> Date: Mon, 22 Jan 2024 00:07:33 +0000 Subject: [PATCH 08/10] Casing --- .../Views/AccountList/AccountSwitcherSettingsView.swift | 2 +- .../Views/AccountList/QuickSwitcherSettingsView.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Mlem/Views/Tabs/Settings/Components/Views/AccountList/AccountSwitcherSettingsView.swift b/Mlem/Views/Tabs/Settings/Components/Views/AccountList/AccountSwitcherSettingsView.swift index d7cfe4bef..e1dff6ce3 100644 --- a/Mlem/Views/Tabs/Settings/Components/Views/AccountList/AccountSwitcherSettingsView.swift +++ b/Mlem/Views/Tabs/Settings/Components/Views/AccountList/AccountSwitcherSettingsView.swift @@ -38,7 +38,7 @@ struct AccountSwitcherSettingsView: View { VStack(alignment: .leading, spacing: 10) { Text("Tip") .fontWeight(.semibold) - Text("Tap and hold the Profile Icon in the tab bar to quickly switch accounts from anywhere.") + Text("Tap and hold the profile icon in the tab bar to quickly switch accounts from anywhere.") .font(.footnote) } .foregroundStyle(.secondary) diff --git a/Mlem/Views/Tabs/Settings/Components/Views/AccountList/QuickSwitcherSettingsView.swift b/Mlem/Views/Tabs/Settings/Components/Views/AccountList/QuickSwitcherSettingsView.swift index fa133a501..d8bb00b9f 100644 --- a/Mlem/Views/Tabs/Settings/Components/Views/AccountList/QuickSwitcherSettingsView.swift +++ b/Mlem/Views/Tabs/Settings/Components/Views/AccountList/QuickSwitcherSettingsView.swift @@ -26,7 +26,7 @@ struct QuickSwitcherSettingsView: View { HStack(alignment: .center, spacing: 24) { LongPressAnimationView(animationTrigger: animationTrigger) Toggle( - "Tapping and holding the Profile icon", + "Tapping and holding the profile icon", isOn: $allowQuickSwitcherLongPressGesture ) .toggleStyle(CheckboxToggleStyle()) From 541df6797f161e91323fc708ab32ad099b90b0de Mon Sep 17 00:00:00 2001 From: Sjmarf <78750526+Sjmarf@users.noreply.github.com> Date: Sun, 28 Jan 2024 18:56:21 +0000 Subject: [PATCH 09/10] Update --- Mlem/ContentView.swift | 24 ++-- .../AccountSwitcherSettingsView.swift | 114 ++---------------- .../QuickSwitcherSettingsView.swift | 106 ++++++++++++++-- 3 files changed, 120 insertions(+), 124 deletions(-) diff --git a/Mlem/ContentView.swift b/Mlem/ContentView.swift index 4e6700ba2..705154665 100644 --- a/Mlem/ContentView.swift +++ b/Mlem/ContentView.swift @@ -132,8 +132,12 @@ struct ContentView: View { ) } .sheet(isPresented: $isPresentingAccountSwitcher) { - QuickSwitcherView() - .presentationDetents([.medium, .large]) + if accountsTracker.savedAccounts.count == 1 { + AddSavedInstanceView(onboarding: false) + } else { + QuickSwitcherView() + .presentationDetents([.medium, .large]) + } } .sheet(item: $editorTracker.editResponse) { editing in NavigationStack { @@ -193,17 +197,17 @@ struct ContentView: View { .onEnded { _ in // disable long press in accessibility mode to prevent conflict with HUD if !accessibilityFont { - if accountsTracker.savedAccounts.count > 2 { - if UserDefaults.standard.bool(forKey: "allowQuickSwitcherLongPressGesture") { + if UserDefaults.standard.bool(forKey: "allowQuickSwitcherLongPressGesture") { + hapticManager.play(haptic: .rigidInfo, priority: .high) + if accountsTracker.savedAccounts.count == 2 { hapticManager.play(haptic: .rigidInfo, priority: .high) + for account in accountsTracker.savedAccounts where account != appState.currentActiveAccount { + setFlow(.account(account)) + break + } + } else { isPresentingAccountSwitcher = true } - } else if accountsTracker.savedAccounts.count == 2 { - hapticManager.play(haptic: .rigidInfo, priority: .high) - for account in accountsTracker.savedAccounts where account != appState.currentActiveAccount { - setFlow(.account(account)) - break - } } } } diff --git a/Mlem/Views/Tabs/Settings/Components/Views/AccountList/AccountSwitcherSettingsView.swift b/Mlem/Views/Tabs/Settings/Components/Views/AccountList/AccountSwitcherSettingsView.swift index e1dff6ce3..726c7f0c4 100644 --- a/Mlem/Views/Tabs/Settings/Components/Views/AccountList/AccountSwitcherSettingsView.swift +++ b/Mlem/Views/Tabs/Settings/Components/Views/AccountList/AccountSwitcherSettingsView.swift @@ -31,115 +31,21 @@ struct AccountSwitcherSettingsView: View { .listRowBackground(Color(.systemGroupedBackground)) } AccountListView() - if accountsTracker.savedAccounts.count == 2 { - Section { - HStack(alignment: .center, spacing: 24) { - TwoAccountSwitchView() - VStack(alignment: .leading, spacing: 10) { - Text("Tip") - .fontWeight(.semibold) - Text("Tap and hold the profile icon in the tab bar to quickly switch accounts from anywhere.") - .font(.footnote) - } - .foregroundStyle(.secondary) + NavigationLink(.settings(.quickSwitcher)) { + Label { + VStack(alignment: .leading) { + Text("Quick Switcher") + Text("Switch accounts quickly from anywhere.") + .foregroundStyle(.secondary) + .font(.footnote) } + } icon: { + Image(systemName: "platter.filled.bottom.iphone") } - .listRowInsets(EdgeInsets(top: 12, leading: 12, bottom: 12, trailing: 12)) - } else { - NavigationLink(.settings(.quickSwitcher)) { - Label { - VStack(alignment: .leading) { - Text("Quick Switcher") - Text("Switch accounts quickly from anywhere.") - .foregroundStyle(.secondary) - .font(.footnote) - } - } icon: { - Image(systemName: "platter.filled.bottom.iphone") - } - .labelStyle(SquircleLabelStyle(color: .teal)) - } + .labelStyle(SquircleLabelStyle(color: .teal)) } } .fancyTabScrollCompatible() } } - -private struct TwoAccountSwitchView: View { - - enum AnimationPhase: CaseIterable { - case none, fadeInTap, tap, none2, fadeInTap2, tap2 - - static var order: [AnimationPhase] = [.none, .fadeInTap, .tap, .none2, .fadeInTap2, .tap2] - } - - @Dependency(\.accountsTracker) var accountsTracker: SavedAccountTracker - - @State var animationTrigger: Bool = false - - let timer = Timer.publish(every: 5, on: .main, in: .common).autoconnect() - - var body: some View { - if #available(iOS 17.0, *) { - PhaseAnimator(AnimationPhase.order, trigger: animationTrigger) { phase in - IPhoneWithSheetView(showingSheet: false) - .overlay(alignment: .bottom) { - ZStack(alignment: .bottom) { - Circle() - .fill(.blue) - .frame(width: 12, height: 12) - .background { - if [.fadeInTap, .tap, .fadeInTap2, .tap2].contains(phase) { - Circle() - .fill(.blue) - .opacity([.tap, .tap2].contains(phase) ? 0 : 0.8) - .scaleEffect([.tap, .tap2].contains(phase) ? 2 : 1) - .transition(.identity) - } - } - .padding(.bottom, 4) - .opacity([.fadeInTap, .tap, .fadeInTap2, .tap2].contains(phase) ? 1 : 0) - - if [.none, .fadeInTap, .tap2].contains(phase) { - AvatarView( - url: accountsTracker.savedAccounts[0].avatarUrl, - type: .user, - avatarSize: 28, - iconResolution: .unrestricted - ) - .frame(maxWidth: .infinity, maxHeight: .infinity) - } else { - AvatarView( - url: accountsTracker.savedAccounts[1].avatarUrl, - type: .user, - avatarSize: 28, - iconResolution: .unrestricted - ) - .frame(maxWidth: .infinity, maxHeight: .infinity) - } - } - } - } animation: { phase in - switch phase { - case .fadeInTap, .fadeInTap2: - return .easeOut(duration: 0.2).delay(1.5) - case .tap, .tap2: - return .easeOut(duration: 0.2).delay(0.3) - case .none, .none2: - return .easeOut(duration: 0.3) - } - } - .task { - animationTrigger.toggle() - } - .onReceive(timer) { _ in - animationTrigger.toggle() - } - - } else { - Image(systemName: "lightbulb.max.fill") - .imageScale(.large) - } - } -} diff --git a/Mlem/Views/Tabs/Settings/Components/Views/AccountList/QuickSwitcherSettingsView.swift b/Mlem/Views/Tabs/Settings/Components/Views/AccountList/QuickSwitcherSettingsView.swift index d8bb00b9f..35a2703ba 100644 --- a/Mlem/Views/Tabs/Settings/Components/Views/AccountList/QuickSwitcherSettingsView.swift +++ b/Mlem/Views/Tabs/Settings/Components/Views/AccountList/QuickSwitcherSettingsView.swift @@ -9,9 +9,10 @@ import SwiftUI import Dependencies struct QuickSwitcherSettingsView: View { + @Dependency(\.accountsTracker) var accountsTracker: SavedAccountTracker - @AppStorage("allowTabBarSwipeUpGesture") var allowTabBarSwipeUpGesture: Bool = true @AppStorage("allowQuickSwitcherLongPressGesture") var allowQuickSwitcherLongPressGesture: Bool = true + @AppStorage("allowTabBarSwipeUpGesture") var allowTabBarSwipeUpGesture: Bool = false // TODO: iOS 16 deprecation // Theoretically, if we don't provide a trigger it should loop indefinitely. This isn't working for some reason, so I'm adding @@ -24,25 +25,40 @@ struct QuickSwitcherSettingsView: View { Form { Section { HStack(alignment: .center, spacing: 24) { - LongPressAnimationView(animationTrigger: animationTrigger) - Toggle( - "Tapping and holding the profile icon", - isOn: $allowQuickSwitcherLongPressGesture - ) - .toggleStyle(CheckboxToggleStyle()) + if accountsTracker.savedAccounts.count == 2 { + TwoAccountSwitchView(animationTrigger: animationTrigger) + Toggle( + "Tap and hold the profile icon to switch accounts", + isOn: $allowQuickSwitcherLongPressGesture + ) + .toggleStyle(CheckboxToggleStyle()) + } else { + LongPressAnimationView(animationTrigger: animationTrigger) + Toggle( + "Tapping and holding the profile icon", + isOn: $allowQuickSwitcherLongPressGesture + ) + .toggleStyle(CheckboxToggleStyle()) + } } HStack(alignment: .center, spacing: 24) { SwipeUpAnimationView(animationTrigger: animationTrigger) + let text = (accountsTracker.savedAccounts.count == 2 + ? "Swipe up from the tab bar to open the quick switcher" + : "Swiping up from the tab bar" + ) Toggle( - "Swiping up from the tab bar", + text, isOn: $allowTabBarSwipeUpGesture ) .toggleStyle(CheckboxToggleStyle()) } } header: { - Text("Open the Quick Switcher by...") - .textCase(nil) + if accountsTracker.savedAccounts.count > 2 { + Text("Open the Quick Switcher by...") + .textCase(nil) + } } .listRowInsets(EdgeInsets(top: 12, leading: 12, bottom: 12, trailing: 12)) } @@ -186,3 +202,73 @@ private struct SwipeUpAnimationView: View { } } } + +private struct TwoAccountSwitchView: View { + + enum AnimationPhase: CaseIterable { + case none, fadeInTap, tap, none2, fadeInTap2, tap2 + + static var order: [AnimationPhase] = [.none, .fadeInTap, .tap, .none2, .fadeInTap2, .tap2] + } + + @Dependency(\.accountsTracker) var accountsTracker: SavedAccountTracker + + let animationTrigger: Bool + + var body: some View { + if #available(iOS 17.0, *) { + PhaseAnimator(AnimationPhase.order, trigger: animationTrigger) { phase in + IPhoneWithSheetView(showingSheet: false) + .overlay(alignment: .bottom) { + ZStack(alignment: .bottom) { + Circle() + .fill(.blue) + .frame(width: 12, height: 12) + .background { + if [.fadeInTap, .tap, .fadeInTap2, .tap2].contains(phase) { + Circle() + .fill(.blue) + .opacity([.tap, .tap2].contains(phase) ? 0 : 0.8) + .scaleEffect([.tap, .tap2].contains(phase) ? 2 : 1) + .transition(.identity) + } + } + .padding(.bottom, 4) + .opacity([.fadeInTap, .tap, .fadeInTap2, .tap2].contains(phase) ? 1 : 0) + + if [.none, .fadeInTap, .tap2].contains(phase) { + AvatarView( + url: accountsTracker.savedAccounts[0].avatarUrl, + type: .user, + avatarSize: 28, + iconResolution: .unrestricted + ) + .frame(maxWidth: .infinity, maxHeight: .infinity) + } else { + AvatarView( + url: accountsTracker.savedAccounts[1].avatarUrl, + type: .user, + avatarSize: 28, + iconResolution: .unrestricted + ) + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + } + } + } animation: { phase in + switch phase { + case .fadeInTap, .fadeInTap2: + return .easeOut(duration: 0.2).delay(1.5) + case .tap, .tap2: + return .easeOut(duration: 0.2).delay(0.3) + case .none, .none2: + return .easeOut(duration: 0.3) + } + } + + } else { + Image(systemName: "lightbulb.max.fill") + .imageScale(.large) + } + } +} From c7ca68604b36b9da5941d5e98240cb7107f055bc Mon Sep 17 00:00:00 2001 From: Sjmarf <78750526+Sjmarf@users.noreply.github.com> Date: Sun, 28 Jan 2024 21:56:03 +0000 Subject: [PATCH 10/10] oops --- Mlem/ContentView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mlem/ContentView.swift b/Mlem/ContentView.swift index c7f7a8139..bcce712c8 100644 --- a/Mlem/ContentView.swift +++ b/Mlem/ContentView.swift @@ -179,7 +179,7 @@ struct ContentView: View { } func showAccountSwitcherDragCallback() { - if !homeButtonExists, allowTabBarSwipeUpGesture, accountsTracker.savedAccounts.count > 2 { + if !homeButtonExists, allowTabBarSwipeUpGesture, accountsTracker.savedAccounts.count > 1 { isPresentingAccountSwitcher = true } }